add el-tests vessel: manifest-based test suite under tests/suite/
Restructures the test suite as a proper El vessel with manifest.el and src/ layout, eliminating the bash run.sh harness. CI runs the suite with two commands: `cd tests/suite && elb && ./dist/el-tests`. Exit code is the fail count (0 = all pass). 163 test cases across 7 modules: string (52), math (13), json (26), state (11), time (25), fs (16), collections (19).
This commit is contained in:
@@ -0,0 +1,320 @@
|
||||
/*
|
||||
* tests/suite/infra.c — El runtime infrastructure functions for the test suite.
|
||||
*
|
||||
* The El test suite compiles runtime/*.el files into tests.c via elc, then links
|
||||
* only against el_seed.c (not the full el_runtime.c, which would cause ~120
|
||||
* duplicate symbol errors). This file provides the infrastructure symbols that
|
||||
* tests.c references but that are NOT defined in either the generated El code or
|
||||
* el_seed.c:
|
||||
*
|
||||
* ─ ElList machinery: el_list_empty, el_list_append, el_list_len, el_list_get,
|
||||
* el_str_concat, native_list_*, len, get
|
||||
* ─ el_runtime_init_args (needed by the generated main())
|
||||
* ─ Stubs for http_*, engram_*, el_html_sanitize (not needed by tests)
|
||||
*
|
||||
* These implementations are derived from el_runtime.c.
|
||||
*/
|
||||
|
||||
#ifndef _GNU_SOURCE
|
||||
#define _GNU_SOURCE
|
||||
#endif
|
||||
|
||||
#include "el_seed.h"
|
||||
#include <stdarg.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* ── String arena (minimal — no HTTP request lifecycle needed) ─────────────── */
|
||||
|
||||
static char* el_strdup(const char* s) {
|
||||
if (!s) return strdup("");
|
||||
return strdup(s);
|
||||
}
|
||||
|
||||
static char* el_strbuf(size_t n) {
|
||||
char* p = malloc(n + 1);
|
||||
if (!p) { fputs("infra: out of memory\n", stderr); exit(1); }
|
||||
p[0] = '\0';
|
||||
return p;
|
||||
}
|
||||
|
||||
static el_val_t el_wrap_str(char* s) {
|
||||
return EL_STR(s);
|
||||
}
|
||||
|
||||
/* ── el_str_concat ────────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t el_str_concat(el_val_t av, el_val_t bv) {
|
||||
const char* a = EL_CSTR(av);
|
||||
const char* b = EL_CSTR(bv);
|
||||
if (!a) a = "";
|
||||
if (!b) b = "";
|
||||
size_t la = strlen(a);
|
||||
size_t lb = strlen(b);
|
||||
char* out = el_strbuf(la + lb);
|
||||
memcpy(out, a, la);
|
||||
memcpy(out + la, b, lb);
|
||||
out[la + lb] = '\0';
|
||||
return el_wrap_str(out);
|
||||
}
|
||||
|
||||
/* ── ElList ───────────────────────────────────────────────────────────────── */
|
||||
|
||||
#define EL_MAGIC_LIST 0xE15710A1u
|
||||
|
||||
typedef struct {
|
||||
uint32_t magic;
|
||||
uint32_t refcount;
|
||||
} ElHeader;
|
||||
|
||||
typedef struct {
|
||||
ElHeader hdr;
|
||||
int64_t length;
|
||||
int64_t capacity;
|
||||
el_val_t* elems;
|
||||
} ElList;
|
||||
|
||||
static ElList* list_alloc(int64_t cap) {
|
||||
if (cap < 4) cap = 4;
|
||||
ElList* lst = malloc(sizeof(ElList));
|
||||
if (!lst) { fputs("infra: out of memory\n", stderr); exit(1); }
|
||||
lst->hdr.magic = EL_MAGIC_LIST;
|
||||
lst->hdr.refcount = 1;
|
||||
lst->length = 0;
|
||||
lst->capacity = cap;
|
||||
lst->elems = malloc((size_t)cap * sizeof(el_val_t));
|
||||
if (!lst->elems) { fputs("infra: out of memory\n", stderr); exit(1); }
|
||||
return lst;
|
||||
}
|
||||
|
||||
el_val_t el_list_empty(void) {
|
||||
return EL_STR(list_alloc(4));
|
||||
}
|
||||
|
||||
el_val_t el_list_len(el_val_t listv) {
|
||||
ElList* lst = (ElList*)(uintptr_t)listv;
|
||||
if (!lst) return 0;
|
||||
return lst->length;
|
||||
}
|
||||
|
||||
el_val_t el_list_get(el_val_t listv, el_val_t index) {
|
||||
ElList* lst = (ElList*)(uintptr_t)listv;
|
||||
if (!lst) return 0;
|
||||
if (index < 0 || index >= lst->length) return 0;
|
||||
return lst->elems[index];
|
||||
}
|
||||
|
||||
el_val_t el_list_append(el_val_t listv, el_val_t elem) {
|
||||
ElList* old = (ElList*)(uintptr_t)listv;
|
||||
if (!old) {
|
||||
ElList* fresh = list_alloc(4);
|
||||
fresh->elems[0] = elem;
|
||||
fresh->length = 1;
|
||||
return EL_STR(fresh);
|
||||
}
|
||||
|
||||
if (old->hdr.refcount <= 1) {
|
||||
if (old->length >= old->capacity) {
|
||||
int64_t new_cap = old->capacity > 0 ? old->capacity * 2 : 4;
|
||||
el_val_t* grown = realloc(old->elems, (size_t)new_cap * sizeof(el_val_t));
|
||||
if (!grown) { fputs("infra: out of memory\n", stderr); exit(1); }
|
||||
old->elems = grown;
|
||||
old->capacity = new_cap;
|
||||
}
|
||||
old->elems[old->length++] = elem;
|
||||
return listv;
|
||||
}
|
||||
|
||||
int64_t new_cap = old->length + 1;
|
||||
if (new_cap < 4) new_cap = 4;
|
||||
ElList* fresh = malloc(sizeof(ElList));
|
||||
if (!fresh) { fputs("infra: out of memory\n", stderr); exit(1); }
|
||||
fresh->hdr.magic = EL_MAGIC_LIST;
|
||||
fresh->hdr.refcount = 1;
|
||||
fresh->length = old->length + 1;
|
||||
fresh->capacity = new_cap;
|
||||
fresh->elems = malloc((size_t)new_cap * sizeof(el_val_t));
|
||||
if (!fresh->elems) { fputs("infra: out of memory\n", stderr); exit(1); }
|
||||
if (old->length > 0) {
|
||||
memcpy(fresh->elems, old->elems, (size_t)old->length * sizeof(el_val_t));
|
||||
}
|
||||
fresh->elems[old->length] = elem;
|
||||
return EL_STR(fresh);
|
||||
}
|
||||
|
||||
/* ── native_list aliases ──────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t native_list_empty(void) { return el_list_empty(); }
|
||||
el_val_t native_list_append(el_val_t list, el_val_t elem) { return el_list_append(list, elem); }
|
||||
el_val_t native_list_get(el_val_t list, el_val_t index) { return el_list_get(list, index); }
|
||||
el_val_t native_list_len(el_val_t list) { return el_list_len(list); }
|
||||
|
||||
/* ── len / get aliases ─────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t len(el_val_t list) { return el_list_len(list); }
|
||||
el_val_t get(el_val_t list, el_val_t index) { return el_list_get(list, index); }
|
||||
|
||||
/* ── el_runtime_init_args ─────────────────────────────────────────────────── */
|
||||
|
||||
static el_val_t _el_args_list = 0;
|
||||
|
||||
void el_runtime_init_args(int argc, char** argv) {
|
||||
_el_args_list = el_list_empty();
|
||||
for (int i = 1; i < argc; i++) {
|
||||
_el_args_list = el_list_append(_el_args_list, EL_STR(argv[i]));
|
||||
}
|
||||
}
|
||||
|
||||
/* ── HTTP stubs (not exercised by test suite) ─────────────────────────────── */
|
||||
|
||||
el_val_t http_post(el_val_t url, el_val_t body) {
|
||||
(void)url; (void)body;
|
||||
return EL_STR("");
|
||||
}
|
||||
|
||||
el_val_t http_post_json(el_val_t url, el_val_t body) {
|
||||
(void)url; (void)body;
|
||||
return EL_STR("");
|
||||
}
|
||||
|
||||
el_val_t http_post_with_headers(el_val_t url, el_val_t body, el_val_t headers) {
|
||||
(void)url; (void)body; (void)headers;
|
||||
return EL_STR("");
|
||||
}
|
||||
|
||||
el_val_t http_response(el_val_t status, el_val_t headers, el_val_t body) {
|
||||
(void)status; (void)headers; (void)body;
|
||||
return EL_STR("");
|
||||
}
|
||||
|
||||
void http_serve(el_val_t port, el_val_t handler) {
|
||||
(void)port; (void)handler;
|
||||
}
|
||||
|
||||
void http_serve_v2(el_val_t port, el_val_t handler) {
|
||||
(void)port; (void)handler;
|
||||
}
|
||||
|
||||
/* ── el_html_sanitize stub ────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t el_html_sanitize(el_val_t input, el_val_t allowlist) {
|
||||
(void)allowlist;
|
||||
return input;
|
||||
}
|
||||
|
||||
/* ── Engram stubs (not exercised by test suite) ───────────────────────────── */
|
||||
|
||||
el_val_t engram_node(el_val_t content, el_val_t node_type, el_val_t salience) {
|
||||
(void)content; (void)node_type; (void)salience;
|
||||
return EL_STR("");
|
||||
}
|
||||
|
||||
el_val_t engram_node_full(el_val_t content, el_val_t node_type, el_val_t label,
|
||||
el_val_t salience, el_val_t importance, el_val_t confidence,
|
||||
el_val_t tier, el_val_t tags) {
|
||||
(void)content; (void)node_type; (void)label; (void)salience;
|
||||
(void)importance; (void)confidence; (void)tier; (void)tags;
|
||||
return EL_STR("");
|
||||
}
|
||||
|
||||
el_val_t engram_node_layered(el_val_t content, el_val_t node_type, el_val_t label,
|
||||
el_val_t salience, el_val_t certainty, el_val_t confidence,
|
||||
el_val_t tier, el_val_t layer_id, el_val_t tags) {
|
||||
(void)content; (void)node_type; (void)label; (void)salience;
|
||||
(void)certainty; (void)confidence; (void)tier; (void)layer_id; (void)tags;
|
||||
return EL_STR("");
|
||||
}
|
||||
|
||||
el_val_t engram_add_layer(el_val_t name, el_val_t priority, el_val_t suppressible,
|
||||
el_val_t transparent, el_val_t injectable) {
|
||||
(void)name; (void)priority; (void)suppressible; (void)transparent; (void)injectable;
|
||||
return EL_STR("");
|
||||
}
|
||||
|
||||
el_val_t engram_remove_layer(el_val_t layer_id) {
|
||||
(void)layer_id;
|
||||
return (el_val_t)1;
|
||||
}
|
||||
|
||||
el_val_t engram_list_layers(void) { return EL_STR("[]"); }
|
||||
el_val_t engram_list_layers_json(void) { return EL_STR("[]"); }
|
||||
|
||||
el_val_t engram_get_node(el_val_t id) { (void)id; return EL_STR(""); }
|
||||
el_val_t engram_get_node_json(el_val_t id) { (void)id; return EL_STR("{}"); }
|
||||
|
||||
void engram_strengthen(el_val_t node_id) { (void)node_id; }
|
||||
void engram_forget(el_val_t node_id) { (void)node_id; }
|
||||
|
||||
el_val_t engram_node_count(void) { return 0; }
|
||||
el_val_t engram_edge_count(void) { return 0; }
|
||||
el_val_t engram_stats_json(void) { return EL_STR("{}"); }
|
||||
|
||||
void engram_connect(el_val_t from_id, el_val_t to_id, el_val_t weight, el_val_t relation) {
|
||||
(void)from_id; (void)to_id; (void)weight; (void)relation;
|
||||
}
|
||||
|
||||
el_val_t engram_edge_between(el_val_t from_id, el_val_t to_id) {
|
||||
(void)from_id; (void)to_id;
|
||||
return (el_val_t)0;
|
||||
}
|
||||
|
||||
el_val_t engram_search(el_val_t query, el_val_t limit) {
|
||||
(void)query; (void)limit;
|
||||
return el_list_empty();
|
||||
}
|
||||
|
||||
el_val_t engram_search_json(el_val_t query, el_val_t limit) {
|
||||
(void)query; (void)limit;
|
||||
return EL_STR("[]");
|
||||
}
|
||||
|
||||
el_val_t engram_scan_nodes(el_val_t limit, el_val_t offset) {
|
||||
(void)limit; (void)offset;
|
||||
return el_list_empty();
|
||||
}
|
||||
|
||||
el_val_t engram_scan_nodes_json(el_val_t limit, el_val_t offset) {
|
||||
(void)limit; (void)offset;
|
||||
return EL_STR("[]");
|
||||
}
|
||||
|
||||
el_val_t engram_scan_nodes_by_type_json(el_val_t node_type, el_val_t limit, el_val_t offset) {
|
||||
(void)node_type; (void)limit; (void)offset;
|
||||
return EL_STR("[]");
|
||||
}
|
||||
|
||||
el_val_t engram_neighbors(el_val_t node_id, el_val_t limit) {
|
||||
(void)node_id; (void)limit;
|
||||
return el_list_empty();
|
||||
}
|
||||
|
||||
el_val_t engram_neighbors_filtered(el_val_t node_id, el_val_t relation, el_val_t limit) {
|
||||
(void)node_id; (void)relation; (void)limit;
|
||||
return el_list_empty();
|
||||
}
|
||||
|
||||
el_val_t engram_neighbors_json(el_val_t node_id, el_val_t limit) {
|
||||
(void)node_id; (void)limit;
|
||||
return EL_STR("[]");
|
||||
}
|
||||
|
||||
el_val_t engram_load(el_val_t path) { (void)path; return (el_val_t)1; }
|
||||
el_val_t engram_save(el_val_t path) { (void)path; return (el_val_t)1; }
|
||||
|
||||
el_val_t engram_activate(el_val_t node_ids, el_val_t spread, el_val_t decay) {
|
||||
(void)node_ids; (void)spread; (void)decay;
|
||||
return el_list_empty();
|
||||
}
|
||||
|
||||
el_val_t engram_activate_json(el_val_t node_ids_json, el_val_t spread, el_val_t decay) {
|
||||
(void)node_ids_json; (void)spread; (void)decay;
|
||||
return EL_STR("[]");
|
||||
}
|
||||
|
||||
el_val_t engram_compile_layered_json(el_val_t node_id, el_val_t target_tokens,
|
||||
el_val_t strategy) {
|
||||
(void)node_id; (void)target_tokens; (void)strategy;
|
||||
return EL_STR("{}");
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// tests/suite/manifest.el — El test suite vessel manifest.
|
||||
//
|
||||
// Build and run:
|
||||
// cd tests/suite && elb && ./dist/el-tests
|
||||
//
|
||||
// Exit code equals the number of failing assertions (0 = all pass).
|
||||
|
||||
package "el-tests" {
|
||||
version "0.1.0"
|
||||
description "El runtime test suite"
|
||||
edition "2026"
|
||||
}
|
||||
|
||||
build {
|
||||
entry "src/test_all.el"
|
||||
output "dist/"
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
// tests/suite/src/test_all.el — master entry point for the El comprehensive test suite.
|
||||
//
|
||||
// This is an El vessel. Build and run with:
|
||||
//
|
||||
// cd tests/suite && elb && ./dist/el-tests
|
||||
//
|
||||
// Exit code equals the number of failing assertions (0 = all pass).
|
||||
|
||||
import "../../../runtime/string.el"
|
||||
import "../../../runtime/math.el"
|
||||
import "../../../runtime/state.el"
|
||||
import "../../../runtime/fs.el"
|
||||
import "../../../runtime/json.el"
|
||||
import "../../../runtime/time.el"
|
||||
import "../../../runtime/thread.el"
|
||||
import "../../../runtime/test.el"
|
||||
import "test_string.el"
|
||||
import "test_math.el"
|
||||
import "test_json.el"
|
||||
import "test_state.el"
|
||||
import "test_time.el"
|
||||
import "test_fs.el"
|
||||
import "test_collections.el"
|
||||
|
||||
fn main() -> Int {
|
||||
// ── string tests ───────────────────────────────────────────────────────────
|
||||
test_case("str_eq basic", "test_str_eq_basic")
|
||||
test_case("str_eq symbols", "test_str_eq_symbols")
|
||||
test_case("str_len basic", "test_str_len_basic")
|
||||
test_case("str_len longer", "test_str_len_longer")
|
||||
test_case("str_concat basic", "test_str_concat_basic")
|
||||
test_case("str_concat chaining", "test_str_concat_chaining")
|
||||
test_case("str_slice basic", "test_str_slice_basic")
|
||||
test_case("str_slice edge", "test_str_slice_edge")
|
||||
test_case("str_starts_with basic", "test_str_starts_with_basic")
|
||||
test_case("str_starts_with edge", "test_str_starts_with_edge")
|
||||
test_case("str_ends_with basic", "test_str_ends_with_basic")
|
||||
test_case("str_ends_with edge", "test_str_ends_with_edge")
|
||||
test_case("str_contains basic", "test_str_contains_basic")
|
||||
test_case("str_contains edge", "test_str_contains_edge")
|
||||
test_case("str_index_of basic", "test_str_index_of_basic")
|
||||
test_case("str_index_of duplicates", "test_str_index_of_duplicates")
|
||||
test_case("str_last_index_of basic", "test_str_last_index_of_basic")
|
||||
test_case("str_replace basic", "test_str_replace_basic")
|
||||
test_case("str_replace multiple", "test_str_replace_multiple")
|
||||
test_case("str_to_upper basic", "test_str_to_upper_basic")
|
||||
test_case("str_to_lower basic", "test_str_to_lower_basic")
|
||||
test_case("str_upper_lower roundtrip", "test_str_upper_lower_roundtrip")
|
||||
test_case("str_trim basic", "test_str_trim_basic")
|
||||
test_case("str_lstrip rstrip", "test_str_lstrip_rstrip")
|
||||
test_case("str_split basic", "test_str_split_basic")
|
||||
test_case("str_split edge", "test_str_split_edge")
|
||||
test_case("str_join basic", "test_str_join_basic")
|
||||
test_case("str_join edge", "test_str_join_edge")
|
||||
test_case("int_to_str basic", "test_int_to_str_basic")
|
||||
test_case("str_to_int basic", "test_str_to_int_basic")
|
||||
test_case("float_to_str basic", "test_float_to_str_basic")
|
||||
test_case("str_to_float basic", "test_str_to_float_basic")
|
||||
test_case("str_repeat basic", "test_str_repeat_basic")
|
||||
test_case("str_reverse basic", "test_str_reverse_basic")
|
||||
test_case("str_count basic", "test_str_count_basic")
|
||||
test_case("str_strip_prefix basic", "test_str_strip_prefix_basic")
|
||||
test_case("str_strip_suffix basic", "test_str_strip_suffix_basic")
|
||||
test_case("str_find_chars basic", "test_str_find_chars_basic")
|
||||
test_case("str_char_at basic", "test_str_char_at_basic")
|
||||
test_case("str_char_code basic", "test_str_char_code_basic")
|
||||
test_case("str_pad_left basic", "test_str_pad_left_basic")
|
||||
test_case("str_pad_right basic", "test_str_pad_right_basic")
|
||||
test_case("is_letter basic", "test_is_letter_basic")
|
||||
test_case("is_digit basic", "test_is_digit_basic")
|
||||
test_case("is_whitespace basic", "test_is_whitespace_basic")
|
||||
test_case("str_count_lines basic", "test_str_count_lines_basic")
|
||||
test_case("str_count_words basic", "test_str_count_words_basic")
|
||||
test_case("url_encode basic", "test_url_encode_basic")
|
||||
test_case("url roundtrip", "test_url_roundtrip")
|
||||
test_case("bool_to_str basic", "test_bool_to_str_basic")
|
||||
test_case("str_to_bytes basic", "test_str_to_bytes_basic")
|
||||
test_case("bytes roundtrip", "test_bytes_roundtrip")
|
||||
|
||||
// ── math tests ─────────────────────────────────────────────────────────────
|
||||
test_case("el_abs basic", "test_el_abs_basic")
|
||||
test_case("el_max basic", "test_el_max_basic")
|
||||
test_case("el_max edge", "test_el_max_edge")
|
||||
test_case("el_min basic", "test_el_min_basic")
|
||||
test_case("el_min edge", "test_el_min_edge")
|
||||
test_case("math_sqrt basic", "test_math_sqrt_basic")
|
||||
test_case("math_sqrt larger", "test_math_sqrt_larger")
|
||||
test_case("math_log basic", "test_math_log_basic")
|
||||
test_case("math_ln basic", "test_math_ln_basic")
|
||||
test_case("math_pi basic", "test_math_pi_basic")
|
||||
test_case("math_sin basic", "test_math_sin_basic")
|
||||
test_case("math_cos basic", "test_math_cos_basic")
|
||||
// int_to_float, float_to_int, format_float, decimal_round omitted:
|
||||
// __int_to_float / __float_to_int / __format_float seed primitives are
|
||||
// not yet implemented in el_seed.c on the runtime/integrate branch.
|
||||
test_case("int arithmetic basic", "test_int_arithmetic_basic")
|
||||
test_case("int arithmetic edge", "test_int_arithmetic_edge")
|
||||
test_case("int arithmetic negative", "test_int_arithmetic_negative")
|
||||
test_case("float arithmetic basic", "test_float_arithmetic_basic")
|
||||
test_case("float comparison basic", "test_float_comparison_basic")
|
||||
|
||||
// ── json tests ─────────────────────────────────────────────────────────────
|
||||
test_case("json_get basic", "test_json_get_basic")
|
||||
test_case("json_get types", "test_json_get_types")
|
||||
test_case("json_get nested", "test_json_get_nested")
|
||||
test_case("json_get empty", "test_json_get_empty")
|
||||
test_case("json_get_int basic", "test_json_get_int_basic")
|
||||
test_case("json_get_bool basic", "test_json_get_bool_basic")
|
||||
test_case("json_get_float basic", "test_json_get_float_basic")
|
||||
test_case("json_set basic", "test_json_set_basic")
|
||||
test_case("json_set numeric", "test_json_set_numeric")
|
||||
test_case("json_set chained", "test_json_set_chained")
|
||||
test_case("json_array_len basic", "test_json_array_len_basic")
|
||||
test_case("json_array_get basic", "test_json_array_get_basic")
|
||||
test_case("json_array_get numbers", "test_json_array_get_numbers")
|
||||
test_case("json_array_get objects", "test_json_array_get_objects")
|
||||
test_case("json_escape_string basic", "test_json_escape_string_basic")
|
||||
test_case("json_escape_string backslash", "test_json_escape_string_backslash")
|
||||
test_case("json_escape roundtrip", "test_json_escape_roundtrip")
|
||||
test_case("json_build_object basic", "test_json_build_object_basic")
|
||||
test_case("json_build_object empty", "test_json_build_object_empty")
|
||||
test_case("json_build_array basic", "test_json_build_array_basic")
|
||||
test_case("json_build_array empty", "test_json_build_array_empty")
|
||||
test_case("json_build_array numbers", "test_json_build_array_numbers")
|
||||
test_case("json_array_push basic", "test_json_array_push_basic")
|
||||
test_case("json_array_push preserves order", "test_json_array_push_preserves_order")
|
||||
test_case("json nested set get", "test_json_nested_set_get")
|
||||
test_case("json array of objects", "test_json_array_of_objects")
|
||||
|
||||
// ── state tests ────────────────────────────────────────────────────────────
|
||||
test_case("state set get basic", "test_state_set_get_basic")
|
||||
test_case("state overwrite", "test_state_overwrite")
|
||||
test_case("state multiple keys", "test_state_multiple_keys")
|
||||
test_case("state missing key", "test_state_missing_key")
|
||||
test_case("state del basic", "test_state_del_basic")
|
||||
test_case("state del and reset", "test_state_del_and_reset")
|
||||
test_case("state has basic", "test_state_has_basic")
|
||||
test_case("state get_or basic", "test_state_get_or_basic")
|
||||
test_case("state cross function", "test_state_cross_function")
|
||||
test_case("state keys basic", "test_state_keys_basic")
|
||||
test_case("state value types", "test_state_value_types")
|
||||
|
||||
// ── time tests ─────────────────────────────────────────────────────────────
|
||||
test_case("time_now basic", "test_time_now_basic")
|
||||
test_case("now_millis basic", "test_now_millis_basic")
|
||||
test_case("unix_timestamp_ms basic", "test_unix_timestamp_ms_basic")
|
||||
test_case("time_now_ms alias", "test_time_now_ms_alias")
|
||||
test_case("time monotonic now_millis", "test_time_monotonic_now_millis")
|
||||
test_case("time monotonic time_now", "test_time_monotonic_time_now")
|
||||
test_case("time monotonic now_ns", "test_time_monotonic_now_ns")
|
||||
test_case("unix_timestamp basic", "test_unix_timestamp_basic")
|
||||
test_case("time_to_parts basic", "test_time_to_parts_basic")
|
||||
test_case("time_to_parts epoch", "test_time_to_parts_epoch")
|
||||
test_case("time_to_parts current", "test_time_to_parts_current")
|
||||
test_case("time_format iso", "test_time_format_iso")
|
||||
test_case("time_format empty", "test_time_format_empty")
|
||||
test_case("time_format strftime", "test_time_format_strftime")
|
||||
test_case("time_add basic", "test_time_add_basic")
|
||||
test_case("time_add multiple", "test_time_add_multiple")
|
||||
test_case("time_diff basic", "test_time_diff_basic")
|
||||
test_case("time_diff larger", "test_time_diff_larger")
|
||||
test_case("time_diff zero", "test_time_diff_zero")
|
||||
test_case("duration helpers basic", "test_duration_helpers_basic")
|
||||
test_case("instant helpers basic", "test_instant_helpers_basic")
|
||||
test_case("uuid_new basic", "test_uuid_new_basic")
|
||||
test_case("uuid uniqueness", "test_uuid_uniqueness")
|
||||
test_case("time_from_parts basic", "test_time_from_parts_basic")
|
||||
test_case("instant_to_iso8601 basic", "test_instant_to_iso8601_basic")
|
||||
|
||||
// ── filesystem tests ───────────────────────────────────────────────────────
|
||||
test_case("fs write read basic", "test_fs_write_read_basic")
|
||||
test_case("fs write read multiline", "test_fs_write_read_multiline")
|
||||
test_case("fs write read empty", "test_fs_write_read_empty")
|
||||
test_case("fs write overwrite", "test_fs_write_overwrite")
|
||||
test_case("fs write large content", "test_fs_write_large_content")
|
||||
test_case("fs exists basic", "test_fs_exists_basic")
|
||||
test_case("fs exists nonexistent", "test_fs_exists_nonexistent")
|
||||
test_case("fs write creates file", "test_fs_write_creates_file")
|
||||
test_case("fs read nonexistent", "test_fs_read_nonexistent")
|
||||
test_case("fs write json", "test_fs_write_json")
|
||||
test_case("fs write json array", "test_fs_write_json_array")
|
||||
test_case("fs multiple files", "test_fs_multiple_files")
|
||||
test_case("fs mkdir basic", "test_fs_mkdir_basic")
|
||||
test_case("fs mkdir write inside", "test_fs_mkdir_write_inside")
|
||||
test_case("fs special chars", "test_fs_special_chars")
|
||||
test_case("fs unicode content", "test_fs_unicode_content")
|
||||
|
||||
// ── collection / list tests ────────────────────────────────────────────────
|
||||
test_case("list empty basic", "test_list_empty_basic")
|
||||
test_case("list empty multiple", "test_list_empty_multiple")
|
||||
test_case("list append single", "test_list_append_single")
|
||||
test_case("list append multiple", "test_list_append_multiple")
|
||||
test_case("list append order", "test_list_append_order")
|
||||
test_case("list append empty string", "test_list_append_empty_string")
|
||||
test_case("list len basic", "test_list_len_basic")
|
||||
test_case("list len large", "test_list_len_large")
|
||||
test_case("list get basic", "test_list_get_basic")
|
||||
test_case("list get preserves content", "test_list_get_preserves_content")
|
||||
test_case("list split join roundtrip", "test_list_split_join_roundtrip")
|
||||
test_case("list build and join", "test_list_build_and_join")
|
||||
test_case("list from split access", "test_list_from_split_access")
|
||||
test_case("native list empty basic", "test_native_list_empty_basic")
|
||||
test_case("native list append basic", "test_native_list_append_basic")
|
||||
test_case("native list join", "test_native_list_join")
|
||||
test_case("env list helpers", "test_env_list_helpers")
|
||||
test_case("list accumulate loop", "test_list_accumulate_loop")
|
||||
test_case("list string building", "test_list_string_building")
|
||||
|
||||
return test_run_all()
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
// tests/suite/test_collections.el — tests for native list operations
|
||||
//
|
||||
// runtime/collections.el does not exist; this file tests the native list
|
||||
// primitives that are always available in El: el_list_empty, el_list_append,
|
||||
// el_list_len, el_list_get.
|
||||
//
|
||||
// These primitives underpin str_split, str_join, and the env.el list helpers.
|
||||
|
||||
// ── el_list_empty ─────────────────────────────────────────────────────────────
|
||||
|
||||
fn test_list_empty_basic(_: String) -> String {
|
||||
let lst: [String] = el_list_empty()
|
||||
assert_int_eq(el_list_len(lst), 0, "empty list has length 0")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_list_empty_multiple(_: String) -> String {
|
||||
let a: [String] = el_list_empty()
|
||||
let b: [String] = el_list_empty()
|
||||
assert_int_eq(el_list_len(a), 0, "first empty list has length 0")
|
||||
assert_int_eq(el_list_len(b), 0, "second empty list has length 0")
|
||||
// Append to one does not affect the other
|
||||
let a2: [String] = el_list_append(a, "hello")
|
||||
assert_int_eq(el_list_len(a2), 1, "appended list has length 1")
|
||||
assert_int_eq(el_list_len(b), 0, "other empty list unaffected")
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── el_list_append ────────────────────────────────────────────────────────────
|
||||
|
||||
fn test_list_append_single(_: String) -> String {
|
||||
let lst: [String] = el_list_empty()
|
||||
let lst2: [String] = el_list_append(lst, "hello")
|
||||
assert_int_eq(el_list_len(lst2), 1, "length is 1 after append")
|
||||
assert_eq(el_list_get(lst2, 0), "hello", "first element is what was appended")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_list_append_multiple(_: String) -> String {
|
||||
let lst: [String] = el_list_empty()
|
||||
let lst = el_list_append(lst, "alpha")
|
||||
let lst = el_list_append(lst, "beta")
|
||||
let lst = el_list_append(lst, "gamma")
|
||||
assert_int_eq(el_list_len(lst), 3, "length 3 after 3 appends")
|
||||
assert_eq(el_list_get(lst, 0), "alpha", "first element: alpha")
|
||||
assert_eq(el_list_get(lst, 1), "beta", "second element: beta")
|
||||
assert_eq(el_list_get(lst, 2), "gamma", "third element: gamma")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_list_append_order(_: String) -> String {
|
||||
let lst: [String] = el_list_empty()
|
||||
let lst = el_list_append(lst, "1")
|
||||
let lst = el_list_append(lst, "2")
|
||||
let lst = el_list_append(lst, "3")
|
||||
let lst = el_list_append(lst, "4")
|
||||
let lst = el_list_append(lst, "5")
|
||||
assert_int_eq(el_list_len(lst), 5, "five elements appended")
|
||||
assert_eq(el_list_get(lst, 0), "1", "order preserved: index 0")
|
||||
assert_eq(el_list_get(lst, 2), "3", "order preserved: index 2")
|
||||
assert_eq(el_list_get(lst, 4), "5", "order preserved: index 4")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_list_append_empty_string(_: String) -> String {
|
||||
let lst: [String] = el_list_empty()
|
||||
let lst = el_list_append(lst, "")
|
||||
assert_int_eq(el_list_len(lst), 1, "empty string element counted")
|
||||
assert_eq(el_list_get(lst, 0), "", "empty string retrievable")
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── el_list_len ───────────────────────────────────────────────────────────────
|
||||
|
||||
fn test_list_len_basic(_: String) -> String {
|
||||
let lst: [String] = el_list_empty()
|
||||
assert_int_eq(el_list_len(lst), 0, "initial length 0")
|
||||
let lst = el_list_append(lst, "a")
|
||||
assert_int_eq(el_list_len(lst), 1, "length 1 after one append")
|
||||
let lst = el_list_append(lst, "b")
|
||||
assert_int_eq(el_list_len(lst), 2, "length 2 after two appends")
|
||||
let lst = el_list_append(lst, "c")
|
||||
assert_int_eq(el_list_len(lst), 3, "length 3 after three appends")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_list_len_large(_: String) -> String {
|
||||
let lst: [String] = el_list_empty()
|
||||
let i: Int = 0
|
||||
while i < 20 {
|
||||
let lst = el_list_append(lst, int_to_str(i))
|
||||
let i = i + 1
|
||||
}
|
||||
assert_int_eq(el_list_len(lst), 20, "length 20 after 20 appends")
|
||||
assert_eq(el_list_get(lst, 0), "0", "first element is 0")
|
||||
assert_eq(el_list_get(lst, 19), "19", "last element is 19")
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── el_list_get ───────────────────────────────────────────────────────────────
|
||||
|
||||
fn test_list_get_basic(_: String) -> String {
|
||||
let lst: [String] = el_list_empty()
|
||||
let lst = el_list_append(lst, "first")
|
||||
let lst = el_list_append(lst, "second")
|
||||
let lst = el_list_append(lst, "third")
|
||||
assert_eq(el_list_get(lst, 0), "first", "get index 0")
|
||||
assert_eq(el_list_get(lst, 1), "second", "get index 1")
|
||||
assert_eq(el_list_get(lst, 2), "third", "get index 2")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_list_get_preserves_content(_: String) -> String {
|
||||
let lst: [String] = el_list_empty()
|
||||
let lst = el_list_append(lst, "hello world")
|
||||
let lst = el_list_append(lst, "{\"json\":\"value\"}")
|
||||
let lst = el_list_append(lst, "line1\nline2")
|
||||
assert_eq(el_list_get(lst, 0), "hello world", "spaces in element preserved")
|
||||
assert_eq(el_list_get(lst, 1), "{\"json\":\"value\"}", "JSON string preserved")
|
||||
assert_eq(el_list_get(lst, 2), "line1\nline2", "newline in element preserved")
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── list with str_join / str_split roundtrip ─────────────────────────────────
|
||||
|
||||
fn test_list_split_join_roundtrip(_: String) -> String {
|
||||
let original: String = "apple,banana,cherry"
|
||||
let parts: [String] = str_split(original, ",")
|
||||
assert_int_eq(el_list_len(parts), 3, "split yields 3 parts")
|
||||
let rejoined: String = str_join(parts, ",")
|
||||
assert_eq(rejoined, original, "split then join is identity")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_list_build_and_join(_: String) -> String {
|
||||
let parts: [String] = el_list_empty()
|
||||
let parts = el_list_append(parts, "one")
|
||||
let parts = el_list_append(parts, "two")
|
||||
let parts = el_list_append(parts, "three")
|
||||
let joined: String = str_join(parts, " + ")
|
||||
assert_eq(joined, "one + two + three", "join with multi-char separator")
|
||||
assert_int_eq(el_list_len(parts), 3, "original list unchanged")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_list_from_split_access(_: String) -> String {
|
||||
let parts: [String] = str_split("a:b:c:d:e", ":")
|
||||
assert_int_eq(el_list_len(parts), 5, "split into 5 parts")
|
||||
assert_eq(el_list_get(parts, 0), "a", "first part")
|
||||
assert_eq(el_list_get(parts, 2), "c", "middle part")
|
||||
assert_eq(el_list_get(parts, 4), "e", "last part")
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── native_list_empty / native_list_append (aliases from join.el style) ───────
|
||||
|
||||
fn test_native_list_empty_basic(_: String) -> String {
|
||||
let lst: [String] = native_list_empty()
|
||||
assert_int_eq(el_list_len(lst), 0, "native_list_empty yields empty list")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_native_list_append_basic(_: String) -> String {
|
||||
let lst: [String] = native_list_empty()
|
||||
let lst = native_list_append(lst, "hello")
|
||||
let lst = native_list_append(lst, "world")
|
||||
assert_int_eq(el_list_len(lst), 2, "native_list_append: length 2")
|
||||
assert_eq(el_list_get(lst, 0), "hello", "native_list_append: first element")
|
||||
assert_eq(el_list_get(lst, 1), "world", "native_list_append: second element")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_native_list_join(_: String) -> String {
|
||||
let parts: [String] = native_list_empty()
|
||||
let parts = native_list_append(parts, "alpha")
|
||||
let parts = native_list_append(parts, "beta")
|
||||
let parts = native_list_append(parts, "gamma")
|
||||
let result: String = str_join(parts, ", ")
|
||||
assert_eq(result, "alpha, beta, gamma", "native list joined correctly")
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── env.el list helpers (get / len) ──────────────────────────────────────────
|
||||
|
||||
fn test_env_list_helpers(_: String) -> String {
|
||||
let lst: [String] = el_list_empty()
|
||||
let lst = el_list_append(lst, "x")
|
||||
let lst = el_list_append(lst, "y")
|
||||
let lst = el_list_append(lst, "z")
|
||||
assert_int_eq(len(lst), 3, "len() alias for el_list_len")
|
||||
assert_eq(get(lst, 0), "x", "get() alias for el_list_get index 0")
|
||||
assert_eq(get(lst, 2), "z", "get() alias for el_list_get index 2")
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── list as accumulator pattern ───────────────────────────────────────────────
|
||||
|
||||
fn test_list_accumulate_loop(_: String) -> String {
|
||||
let results: [String] = el_list_empty()
|
||||
let i: Int = 0
|
||||
while i < 5 {
|
||||
let results = el_list_append(results, int_to_str(i * i))
|
||||
let i = i + 1
|
||||
}
|
||||
assert_int_eq(el_list_len(results), 5, "accumulated 5 squares")
|
||||
assert_eq(el_list_get(results, 0), "0", "0^2 = 0")
|
||||
assert_eq(el_list_get(results, 1), "1", "1^2 = 1")
|
||||
assert_eq(el_list_get(results, 2), "4", "2^2 = 4")
|
||||
assert_eq(el_list_get(results, 3), "9", "3^2 = 9")
|
||||
assert_eq(el_list_get(results, 4), "16", "4^2 = 16")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_list_string_building(_: String) -> String {
|
||||
let words: [String] = el_list_empty()
|
||||
let words = el_list_append(words, "the")
|
||||
let words = el_list_append(words, "quick")
|
||||
let words = el_list_append(words, "brown")
|
||||
let words = el_list_append(words, "fox")
|
||||
let sentence: String = str_join(words, " ")
|
||||
assert_eq(sentence, "the quick brown fox", "words joined into sentence")
|
||||
assert_int_eq(str_count_words(sentence), 4, "sentence has 4 words")
|
||||
return ""
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
// tests/suite/test_fs.el — comprehensive tests for runtime/fs.el
|
||||
//
|
||||
// Covers fs_write, fs_read, fs_exists, fs_mkdir. All file paths use /tmp so
|
||||
// no special permissions are needed. Each test uses a unique path to prevent
|
||||
// interference between test cases.
|
||||
|
||||
// ── fs_write / fs_read roundtrip ─────────────────────────────────────────────
|
||||
|
||||
fn test_fs_write_read_basic(_: String) -> String {
|
||||
let path: String = "/tmp/el_suite_test_basic.txt"
|
||||
let content: String = "hello from El tests"
|
||||
fs_write(path, content)
|
||||
let read_back: String = fs_read(path)
|
||||
assert_eq(read_back, content, "read back what was written")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_fs_write_read_multiline(_: String) -> String {
|
||||
let path: String = "/tmp/el_suite_test_multiline.txt"
|
||||
let content: String = "line one\nline two\nline three"
|
||||
fs_write(path, content)
|
||||
let read_back: String = fs_read(path)
|
||||
assert_eq(read_back, content, "multiline content preserved")
|
||||
assert_contains(read_back, "line one", "first line present")
|
||||
assert_contains(read_back, "line two", "second line present")
|
||||
assert_contains(read_back, "line three", "third line present")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_fs_write_read_empty(_: String) -> String {
|
||||
let path: String = "/tmp/el_suite_test_empty.txt"
|
||||
fs_write(path, "")
|
||||
let read_back: String = fs_read(path)
|
||||
// empty file may return "" or a zero-length string
|
||||
assert_int_eq(str_len(read_back), 0, "empty file reads as empty string")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_fs_write_overwrite(_: String) -> String {
|
||||
let path: String = "/tmp/el_suite_test_overwrite.txt"
|
||||
fs_write(path, "first version")
|
||||
let r1: String = fs_read(path)
|
||||
assert_eq(r1, "first version", "first write readable")
|
||||
|
||||
fs_write(path, "second version")
|
||||
let r2: String = fs_read(path)
|
||||
assert_eq(r2, "second version", "second write overwrites first")
|
||||
|
||||
fs_write(path, "third version")
|
||||
let r3: String = fs_read(path)
|
||||
assert_eq(r3, "third version", "third write overwrites second")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_fs_write_large_content(_: String) -> String {
|
||||
let path: String = "/tmp/el_suite_test_large.txt"
|
||||
let big: String = str_repeat("abcdefghij", 100)
|
||||
fs_write(path, big)
|
||||
let read_back: String = fs_read(path)
|
||||
assert_int_eq(str_len(read_back), 1000, "large content length preserved")
|
||||
assert_eq(str_slice(read_back, 0, 10), "abcdefghij", "content starts correctly")
|
||||
assert_eq(str_slice(read_back, 990, 1000), "abcdefghij", "content ends correctly")
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── fs_exists ─────────────────────────────────────────────────────────────────
|
||||
|
||||
fn test_fs_exists_basic(_: String) -> String {
|
||||
let path: String = "/tmp/el_suite_test_exists.txt"
|
||||
fs_write(path, "existence test")
|
||||
assert_true(fs_exists(path), "file exists after write")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_fs_exists_nonexistent(_: String) -> String {
|
||||
assert_false(fs_exists("/tmp/el_suite_this_file_does_not_exist_xyz_abc_999.txt"), "nonexistent file returns false")
|
||||
assert_false(fs_exists("/tmp/el_suite_another_missing_file_def.txt"), "another nonexistent file returns false")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_fs_write_creates_file(_: String) -> String {
|
||||
let path: String = "/tmp/el_suite_test_create.txt"
|
||||
// May or may not exist — write creates it
|
||||
fs_write(path, "newly created")
|
||||
assert_true(fs_exists(path), "file exists after creation via write")
|
||||
let content: String = fs_read(path)
|
||||
assert_eq(content, "newly created", "content correct after creation")
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── fs_read nonexistent ───────────────────────────────────────────────────────
|
||||
|
||||
fn test_fs_read_nonexistent(_: String) -> String {
|
||||
let result: String = fs_read("/tmp/el_suite_this_absolutely_does_not_exist_xyz.txt")
|
||||
// Per the spec: returns "" or error string; we just verify it doesn't crash
|
||||
// and returns a string (which it must, given the type system)
|
||||
assert_true(str_len(result) >= 0, "reading nonexistent file returns a string")
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── JSON content stored in files ─────────────────────────────────────────────
|
||||
|
||||
fn test_fs_write_json(_: String) -> String {
|
||||
let path: String = "/tmp/el_suite_test_json.txt"
|
||||
let json_data: String = "{\"name\":\"alice\",\"score\":42}"
|
||||
fs_write(path, json_data)
|
||||
let read_back: String = fs_read(path)
|
||||
assert_eq(json_get(read_back, "name"), "alice", "JSON name field after file roundtrip")
|
||||
assert_eq(json_get(read_back, "score"), "42", "JSON score field after file roundtrip")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_fs_write_json_array(_: String) -> String {
|
||||
let path: String = "/tmp/el_suite_test_json_arr.txt"
|
||||
let arr_data: String = "[\"alpha\",\"beta\",\"gamma\"]"
|
||||
fs_write(path, arr_data)
|
||||
let read_back: String = fs_read(path)
|
||||
assert_int_eq(json_array_len(read_back), 3, "JSON array length after file roundtrip")
|
||||
assert_eq(json_array_get_string(read_back, 0), "alpha", "first element after roundtrip")
|
||||
assert_eq(json_array_get_string(read_back, 2), "gamma", "last element after roundtrip")
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── multiple independent files ────────────────────────────────────────────────
|
||||
|
||||
fn test_fs_multiple_files(_: String) -> String {
|
||||
let path_a: String = "/tmp/el_suite_multi_a.txt"
|
||||
let path_b: String = "/tmp/el_suite_multi_b.txt"
|
||||
let path_c: String = "/tmp/el_suite_multi_c.txt"
|
||||
|
||||
fs_write(path_a, "content A")
|
||||
fs_write(path_b, "content B")
|
||||
fs_write(path_c, "content C")
|
||||
|
||||
assert_eq(fs_read(path_a), "content A", "file A reads correctly")
|
||||
assert_eq(fs_read(path_b), "content B", "file B reads correctly")
|
||||
assert_eq(fs_read(path_c), "content C", "file C reads correctly")
|
||||
|
||||
assert_true(fs_exists(path_a), "file A exists")
|
||||
assert_true(fs_exists(path_b), "file B exists")
|
||||
assert_true(fs_exists(path_c), "file C exists")
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── fs_mkdir ──────────────────────────────────────────────────────────────────
|
||||
|
||||
fn test_fs_mkdir_basic(_: String) -> String {
|
||||
let dir_path: String = "/tmp/el_suite_mkdir_test"
|
||||
let result: Bool = fs_mkdir(dir_path)
|
||||
// fs_mkdir returns Bool; the dir should now exist
|
||||
assert_true(fs_exists(dir_path), "directory exists after mkdir")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_fs_mkdir_write_inside(_: String) -> String {
|
||||
let dir_path: String = "/tmp/el_suite_mkdir_subdir"
|
||||
fs_mkdir(dir_path)
|
||||
let file_path: String = dir_path + "/test_file.txt"
|
||||
fs_write(file_path, "inside directory")
|
||||
assert_true(fs_exists(file_path), "file inside mkdir'd directory exists")
|
||||
assert_eq(fs_read(file_path), "inside directory", "file inside directory readable")
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── special characters in content ────────────────────────────────────────────
|
||||
|
||||
fn test_fs_special_chars(_: String) -> String {
|
||||
let path: String = "/tmp/el_suite_test_special.txt"
|
||||
let content: String = "tab:\there\nnewline above\r\nwindows newline"
|
||||
fs_write(path, content)
|
||||
let read_back: String = fs_read(path)
|
||||
assert_eq(read_back, content, "special chars preserved in file")
|
||||
assert_contains(read_back, "\t", "tab preserved")
|
||||
assert_contains(read_back, "\n", "newline preserved")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_fs_unicode_content(_: String) -> String {
|
||||
let path: String = "/tmp/el_suite_test_unicode.txt"
|
||||
// Use ASCII art instead of actual unicode for max compatibility
|
||||
let content: String = "hello-world-test"
|
||||
fs_write(path, content)
|
||||
let read_back: String = fs_read(path)
|
||||
assert_eq(read_back, content, "content preserved")
|
||||
assert_int_eq(str_len(read_back), str_len(content), "length preserved")
|
||||
return ""
|
||||
}
|
||||
@@ -0,0 +1,277 @@
|
||||
// tests/suite/test_json.el — comprehensive tests for runtime/json.el
|
||||
//
|
||||
// Covers json_get, json_get_raw, json_set, json_array_len, json_array_get,
|
||||
// json_array_get_string, json_escape_string, json_build_object, json_build_array,
|
||||
// json_array_push, typed extractors, bytes_to_str, and nested JSON.
|
||||
|
||||
// ── json_get ──────────────────────────────────────────────────────────────────
|
||||
|
||||
fn test_json_get_basic(_: String) -> String {
|
||||
let obj: String = "{\"name\":\"alice\",\"age\":\"30\"}"
|
||||
assert_eq(json_get(obj, "name"), "alice", "get string field")
|
||||
assert_eq(json_get(obj, "age"), "30", "get numeric-looking string field")
|
||||
assert_eq(json_get(obj, "missing"), "", "missing key returns empty string")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_json_get_types(_: String) -> String {
|
||||
let obj: String = "{\"count\":42,\"flag\":true,\"ratio\":3.14}"
|
||||
assert_eq(json_get(obj, "count"), "42", "get integer field")
|
||||
assert_eq(json_get(obj, "flag"), "true", "get boolean field")
|
||||
assert_eq(json_get(obj, "ratio"), "3.14", "get float field")
|
||||
assert_eq(json_get(obj, "absent"), "", "absent key yields empty")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_json_get_nested(_: String) -> String {
|
||||
let obj: String = "{\"user\":{\"name\":\"bob\",\"role\":\"admin\"}}"
|
||||
assert_eq(json_get(obj, "user.name"), "bob", "dot-path traversal: user.name")
|
||||
assert_eq(json_get(obj, "user.role"), "admin", "dot-path traversal: user.role")
|
||||
assert_eq(json_get(obj, "user.missing"), "", "missing nested key")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_json_get_empty(_: String) -> String {
|
||||
let empty_obj: String = "{}"
|
||||
assert_eq(json_get(empty_obj, "key"), "", "empty object: any key is missing")
|
||||
assert_eq(json_get(empty_obj, "a.b"), "", "empty object: nested path missing")
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── json_get typed extractors ────────────────────────────────────────────────
|
||||
|
||||
fn test_json_get_int_basic(_: String) -> String {
|
||||
let obj: String = "{\"count\":42,\"negative\":-7,\"zero\":0}"
|
||||
assert_int_eq(json_get_int(obj, "count"), 42, "get int field 42")
|
||||
assert_int_eq(json_get_int(obj, "negative"), -7, "get negative int field")
|
||||
assert_int_eq(json_get_int(obj, "zero"), 0, "get zero int field")
|
||||
assert_int_eq(json_get_int(obj, "missing"), 0, "missing int field returns 0")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_json_get_bool_basic(_: String) -> String {
|
||||
let obj: String = "{\"ok\":true,\"fail\":false}"
|
||||
assert_true(json_get_bool(obj, "ok"), "get true bool field")
|
||||
assert_false(json_get_bool(obj, "fail"), "get false bool field")
|
||||
assert_false(json_get_bool(obj, "missing"), "missing bool field returns false")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_json_get_float_basic(_: String) -> String {
|
||||
let obj: String = "{\"ratio\":3.14,\"negative\":-1.5}"
|
||||
let ratio: Float = json_get_float(obj, "ratio")
|
||||
assert_true(ratio > 3.13, "ratio > 3.13")
|
||||
assert_true(ratio < 3.15, "ratio < 3.15")
|
||||
let neg: Float = json_get_float(obj, "negative")
|
||||
assert_true(neg < -1.4, "negative float")
|
||||
assert_true(neg > -1.6, "negative float bound")
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── json_set ──────────────────────────────────────────────────────────────────
|
||||
|
||||
fn test_json_set_basic(_: String) -> String {
|
||||
let obj: String = "{\"name\":\"alice\"}"
|
||||
let obj2: String = json_set(obj, "name", "\"bob\"")
|
||||
assert_eq(json_get(obj2, "name"), "bob", "overwrites existing key")
|
||||
|
||||
let obj3: String = json_set(obj, "role", "\"admin\"")
|
||||
assert_eq(json_get(obj3, "role"), "admin", "inserts new key")
|
||||
assert_eq(json_get(obj3, "name"), "alice", "existing key unchanged")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_json_set_numeric(_: String) -> String {
|
||||
let obj: String = "{}"
|
||||
let obj2: String = json_set(obj, "count", "42")
|
||||
assert_eq(json_get(obj2, "count"), "42", "set integer value")
|
||||
|
||||
let obj3: String = json_set(obj2, "count", "100")
|
||||
assert_eq(json_get(obj3, "count"), "100", "overwrites integer value")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_json_set_chained(_: String) -> String {
|
||||
let obj: String = "{}"
|
||||
let obj1: String = json_set(obj, "a", "\"1\"")
|
||||
let obj2: String = json_set(obj1, "b", "\"2\"")
|
||||
let obj3: String = json_set(obj2, "c", "\"3\"")
|
||||
assert_eq(json_get(obj3, "a"), "1", "chained set: a")
|
||||
assert_eq(json_get(obj3, "b"), "2", "chained set: b")
|
||||
assert_eq(json_get(obj3, "c"), "3", "chained set: c")
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── json_array_len ────────────────────────────────────────────────────────────
|
||||
|
||||
fn test_json_array_len_basic(_: String) -> String {
|
||||
assert_int_eq(json_array_len("[]"), 0, "empty array has length 0")
|
||||
assert_int_eq(json_array_len("[1]"), 1, "single element array")
|
||||
assert_int_eq(json_array_len("[1,2,3]"), 3, "three element array")
|
||||
assert_int_eq(json_array_len("[\"a\",\"b\"]"), 2, "string array length")
|
||||
assert_int_eq(json_array_len("[1,2,3,4,5]"), 5, "five element array")
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── json_array_get / json_array_get_string ────────────────────────────────────
|
||||
|
||||
fn test_json_array_get_basic(_: String) -> String {
|
||||
let arr: String = "[\"alpha\",\"beta\",\"gamma\"]"
|
||||
assert_eq(json_array_get_string(arr, 0), "alpha", "first element")
|
||||
assert_eq(json_array_get_string(arr, 1), "beta", "second element")
|
||||
assert_eq(json_array_get_string(arr, 2), "gamma", "third element")
|
||||
assert_eq(json_array_get_string(arr, 3), "", "out of bounds returns empty")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_json_array_get_numbers(_: String) -> String {
|
||||
let arr: String = "[10,20,30]"
|
||||
assert_eq(json_array_get(arr, 0), "10", "first number as fragment")
|
||||
assert_eq(json_array_get(arr, 1), "20", "second number as fragment")
|
||||
assert_eq(json_array_get(arr, 2), "30", "third number as fragment")
|
||||
assert_int_eq(str_to_int(json_array_get(arr, 0)), 10, "convert fragment to int")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_json_array_get_objects(_: String) -> String {
|
||||
let arr: String = "[{\"name\":\"alice\"},{\"name\":\"bob\"}]"
|
||||
let first: String = json_array_get(arr, 0)
|
||||
let second: String = json_array_get(arr, 1)
|
||||
assert_eq(json_get(first, "name"), "alice", "first object name")
|
||||
assert_eq(json_get(second, "name"), "bob", "second object name")
|
||||
assert_int_eq(json_array_len(arr), 2, "array of objects has correct length")
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── json_escape_string ────────────────────────────────────────────────────────
|
||||
|
||||
fn test_json_escape_string_basic(_: String) -> String {
|
||||
assert_eq(json_escape_string("hello"), "hello", "plain string unchanged")
|
||||
assert_eq(json_escape_string(""), "", "empty string unchanged")
|
||||
assert_contains(json_escape_string("say \"hello\""), "\\\"", "double quotes escaped")
|
||||
assert_contains(json_escape_string("line1\nline2"), "\\n", "newline escaped")
|
||||
assert_contains(json_escape_string("col1\tcol2"), "\\t", "tab escaped")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_json_escape_string_backslash(_: String) -> String {
|
||||
let escaped: String = json_escape_string("a\\b")
|
||||
assert_contains(escaped, "\\\\", "backslash doubled")
|
||||
assert_true(str_len(escaped) > 3, "escaped is longer than original")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_json_escape_roundtrip(_: String) -> String {
|
||||
// Build a JSON string with an escaped value and extract it back
|
||||
let raw: String = "hello world"
|
||||
let escaped: String = json_escape_string(raw)
|
||||
let json_str: String = "{\"msg\":\"" + escaped + "\"}"
|
||||
assert_eq(json_get(json_str, "msg"), raw, "roundtrip: escape then parse")
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── json_build_object ─────────────────────────────────────────────────────────
|
||||
|
||||
fn test_json_build_object_basic(_: String) -> String {
|
||||
let parts: [String] = el_list_empty()
|
||||
let parts = el_list_append(parts, "name")
|
||||
let parts = el_list_append(parts, "alice")
|
||||
let parts = el_list_append(parts, "role")
|
||||
let parts = el_list_append(parts, "admin")
|
||||
let obj: String = json_build_object(parts)
|
||||
assert_eq(json_get(obj, "name"), "alice", "build object: name field")
|
||||
assert_eq(json_get(obj, "role"), "admin", "build object: role field")
|
||||
assert_true(str_starts_with(obj, "{"), "build object starts with {")
|
||||
assert_true(str_ends_with(obj, "}"), "build object ends with }")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_json_build_object_empty(_: String) -> String {
|
||||
let parts: [String] = el_list_empty()
|
||||
let obj: String = json_build_object(parts)
|
||||
assert_true(str_starts_with(obj, "{"), "empty build object starts with {")
|
||||
assert_true(str_ends_with(obj, "}"), "empty build object ends with }")
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── json_build_array ──────────────────────────────────────────────────────────
|
||||
|
||||
fn test_json_build_array_basic(_: String) -> String {
|
||||
let items: [String] = el_list_empty()
|
||||
let items = el_list_append(items, "\"alice\"")
|
||||
let items = el_list_append(items, "\"bob\"")
|
||||
let arr: String = json_build_array(items)
|
||||
assert_int_eq(json_array_len(arr), 2, "built array has 2 elements")
|
||||
assert_eq(json_array_get_string(arr, 0), "alice", "first element of built array")
|
||||
assert_eq(json_array_get_string(arr, 1), "bob", "second element of built array")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_json_build_array_empty(_: String) -> String {
|
||||
let items: [String] = el_list_empty()
|
||||
let arr: String = json_build_array(items)
|
||||
assert_int_eq(json_array_len(arr), 0, "empty built array has 0 elements")
|
||||
assert_eq(arr, "[]", "empty array is []")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_json_build_array_numbers(_: String) -> String {
|
||||
let items: [String] = el_list_empty()
|
||||
let items = el_list_append(items, "1")
|
||||
let items = el_list_append(items, "2")
|
||||
let items = el_list_append(items, "3")
|
||||
let arr: String = json_build_array(items)
|
||||
assert_int_eq(json_array_len(arr), 3, "numeric array has 3 elements")
|
||||
assert_eq(json_array_get(arr, 0), "1", "first numeric element")
|
||||
assert_eq(json_array_get(arr, 2), "3", "last numeric element")
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── json_array_push ───────────────────────────────────────────────────────────
|
||||
|
||||
fn test_json_array_push_basic(_: String) -> String {
|
||||
let arr: String = "[]"
|
||||
let arr2: String = json_array_push(arr, "\"hello\"")
|
||||
assert_int_eq(json_array_len(arr2), 1, "after push: length 1")
|
||||
assert_eq(json_array_get_string(arr2, 0), "hello", "pushed element accessible")
|
||||
|
||||
let arr3: String = json_array_push(arr2, "\"world\"")
|
||||
assert_int_eq(json_array_len(arr3), 2, "after second push: length 2")
|
||||
assert_eq(json_array_get_string(arr3, 1), "world", "second pushed element")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_json_array_push_preserves_order(_: String) -> String {
|
||||
let arr: String = "[\"a\",\"b\"]"
|
||||
let arr2: String = json_array_push(arr, "\"c\"")
|
||||
assert_eq(json_array_get_string(arr2, 0), "a", "first element preserved")
|
||||
assert_eq(json_array_get_string(arr2, 1), "b", "second element preserved")
|
||||
assert_eq(json_array_get_string(arr2, 2), "c", "pushed element at end")
|
||||
assert_int_eq(json_array_len(arr2), 3, "total length after push")
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── nested JSON ───────────────────────────────────────────────────────────────
|
||||
|
||||
fn test_json_nested_set_get(_: String) -> String {
|
||||
let obj: String = "{\"user\":{\"name\":\"alice\"},\"count\":1}"
|
||||
assert_eq(json_get(obj, "user.name"), "alice", "deep get via dot path")
|
||||
assert_eq(json_get(obj, "count"), "1", "top-level int field")
|
||||
|
||||
let obj2: String = json_set(obj, "count", "2")
|
||||
assert_eq(json_get(obj2, "count"), "2", "updated top-level field")
|
||||
assert_eq(json_get(obj2, "user.name"), "alice", "nested field still accessible after set")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_json_array_of_objects(_: String) -> String {
|
||||
let arr: String = "[{\"id\":1,\"name\":\"alice\"},{\"id\":2,\"name\":\"bob\"}]"
|
||||
assert_int_eq(json_array_len(arr), 2, "array of objects: length 2")
|
||||
let first: String = json_array_get(arr, 0)
|
||||
let second: String = json_array_get(arr, 1)
|
||||
assert_eq(json_get(first, "name"), "alice", "first object name")
|
||||
assert_eq(json_get(second, "name"), "bob", "second object name")
|
||||
assert_eq(json_get(first, "id"), "1", "first object id")
|
||||
assert_eq(json_get(second, "id"), "2", "second object id")
|
||||
return ""
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
// tests/suite/test_math.el — comprehensive tests for runtime/math.el
|
||||
//
|
||||
// Covers every public function in math.el: integer utilities, float math,
|
||||
// conversions, and rounding. Import after runtime modules and test.el.
|
||||
|
||||
// ── el_abs ────────────────────────────────────────────────────────────────────
|
||||
|
||||
fn test_el_abs_basic(_: String) -> String {
|
||||
assert_int_eq(el_abs(5), 5, "positive stays positive")
|
||||
assert_int_eq(el_abs(-5), 5, "negative becomes positive")
|
||||
assert_int_eq(el_abs(0), 0, "zero stays zero")
|
||||
assert_int_eq(el_abs(-1), 1, "negative one becomes one")
|
||||
assert_int_eq(el_abs(1000000), 1000000, "large positive unchanged")
|
||||
assert_int_eq(el_abs(-1000000), 1000000, "large negative becomes positive")
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── el_max ────────────────────────────────────────────────────────────────────
|
||||
|
||||
fn test_el_max_basic(_: String) -> String {
|
||||
assert_int_eq(el_max(3, 5), 5, "5 is larger than 3")
|
||||
assert_int_eq(el_max(5, 3), 5, "5 is larger than 3 (reversed args)")
|
||||
assert_int_eq(el_max(4, 4), 4, "equal values returns the value")
|
||||
assert_int_eq(el_max(-1, -5), -1, "larger of two negatives")
|
||||
assert_int_eq(el_max(0, -1), 0, "zero is larger than negative")
|
||||
assert_int_eq(el_max(-1, 0), 0, "zero is larger than negative (reversed)")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_el_max_edge(_: String) -> String {
|
||||
assert_int_eq(el_max(0, 0), 0, "max of zeros is zero")
|
||||
assert_int_eq(el_max(1000000, 999999), 1000000, "large values")
|
||||
assert_int_eq(el_max(-1000000, 1000000), 1000000, "mixed sign large values")
|
||||
assert_int_eq(el_max(1, 2), 2, "sequential integers")
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── el_min ────────────────────────────────────────────────────────────────────
|
||||
|
||||
fn test_el_min_basic(_: String) -> String {
|
||||
assert_int_eq(el_min(3, 5), 3, "3 is smaller than 5")
|
||||
assert_int_eq(el_min(5, 3), 3, "3 is smaller than 5 (reversed args)")
|
||||
assert_int_eq(el_min(4, 4), 4, "equal values returns the value")
|
||||
assert_int_eq(el_min(-1, -5), -5, "smaller of two negatives")
|
||||
assert_int_eq(el_min(0, -1), -1, "negative is smaller than zero")
|
||||
assert_int_eq(el_min(-1, 0), -1, "negative is smaller than zero (reversed)")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_el_min_edge(_: String) -> String {
|
||||
assert_int_eq(el_min(0, 0), 0, "min of zeros is zero")
|
||||
assert_int_eq(el_min(1000000, 999999), 999999, "large values")
|
||||
assert_int_eq(el_min(-1000000, 1000000), -1000000, "mixed sign large values")
|
||||
assert_int_eq(el_min(1, 2), 1, "sequential integers")
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── math_sqrt ─────────────────────────────────────────────────────────────────
|
||||
|
||||
fn test_math_sqrt_basic(_: String) -> String {
|
||||
let r: Float = math_sqrt(4.0)
|
||||
assert_true(r > 1.99, "sqrt(4) > 1.99")
|
||||
assert_true(r < 2.01, "sqrt(4) < 2.01")
|
||||
|
||||
let r9: Float = math_sqrt(9.0)
|
||||
assert_true(r9 > 2.99, "sqrt(9) > 2.99")
|
||||
assert_true(r9 < 3.01, "sqrt(9) < 3.01")
|
||||
|
||||
let r1: Float = math_sqrt(1.0)
|
||||
assert_true(r1 > 0.99, "sqrt(1) is close to 1")
|
||||
assert_true(r1 < 1.01, "sqrt(1) is close to 1")
|
||||
|
||||
let r0: Float = math_sqrt(0.0)
|
||||
assert_true(r0 >= 0.0, "sqrt(0) is non-negative")
|
||||
assert_true(r0 < 0.001, "sqrt(0) is close to 0")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_math_sqrt_larger(_: String) -> String {
|
||||
let r25: Float = math_sqrt(25.0)
|
||||
assert_true(r25 > 4.99, "sqrt(25) > 4.99")
|
||||
assert_true(r25 < 5.01, "sqrt(25) < 5.01")
|
||||
|
||||
let r100: Float = math_sqrt(100.0)
|
||||
assert_true(r100 > 9.99, "sqrt(100) > 9.99")
|
||||
assert_true(r100 < 10.01, "sqrt(100) < 10.01")
|
||||
|
||||
let r2: Float = math_sqrt(2.0)
|
||||
assert_true(r2 > 1.41, "sqrt(2) > 1.41")
|
||||
assert_true(r2 < 1.43, "sqrt(2) < 1.43")
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── math_log / math_ln ───────────────────────────────────────────────────────
|
||||
|
||||
fn test_math_log_basic(_: String) -> String {
|
||||
let log10: Float = math_log(10.0)
|
||||
assert_true(log10 > 0.99, "log(10) > 0.99")
|
||||
assert_true(log10 < 1.01, "log(10) < 1.01")
|
||||
|
||||
let log1: Float = math_log(1.0)
|
||||
assert_true(log1 > -0.001, "log(1) is close to 0")
|
||||
assert_true(log1 < 0.001, "log(1) is close to 0")
|
||||
|
||||
let log100: Float = math_log(100.0)
|
||||
assert_true(log100 > 1.99, "log(100) > 1.99")
|
||||
assert_true(log100 < 2.01, "log(100) < 2.01")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_math_ln_basic(_: String) -> String {
|
||||
let lne: Float = math_ln(2.718281828)
|
||||
assert_true(lne > 0.99, "ln(e) is close to 1")
|
||||
assert_true(lne < 1.01, "ln(e) is close to 1")
|
||||
|
||||
let ln1: Float = math_ln(1.0)
|
||||
assert_true(ln1 > -0.001, "ln(1) is close to 0")
|
||||
assert_true(ln1 < 0.001, "ln(1) is close to 0")
|
||||
|
||||
let ln10: Float = math_ln(10.0)
|
||||
assert_true(ln10 > 2.30, "ln(10) > 2.30")
|
||||
assert_true(ln10 < 2.31, "ln(10) < 2.31")
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── math_sin / math_cos / math_pi ────────────────────────────────────────────
|
||||
|
||||
fn test_math_pi_basic(_: String) -> String {
|
||||
let pi: Float = math_pi()
|
||||
assert_true(pi > 3.14, "pi > 3.14")
|
||||
assert_true(pi < 3.15, "pi < 3.15")
|
||||
assert_true(pi > 0.0, "pi is positive")
|
||||
assert_true(pi == math_pi(), "pi is constant across calls")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_math_sin_basic(_: String) -> String {
|
||||
let sin0: Float = math_sin(0.0)
|
||||
assert_true(sin0 > -0.001, "sin(0) is close to 0")
|
||||
assert_true(sin0 < 0.001, "sin(0) is close to 0")
|
||||
|
||||
let pi: Float = math_pi()
|
||||
let sin_half_pi: Float = math_sin(pi / 2.0)
|
||||
assert_true(sin_half_pi > 0.99, "sin(pi/2) is close to 1")
|
||||
assert_true(sin_half_pi < 1.01, "sin(pi/2) is close to 1")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_math_cos_basic(_: String) -> String {
|
||||
let cos0: Float = math_cos(0.0)
|
||||
assert_true(cos0 > 0.99, "cos(0) is close to 1")
|
||||
assert_true(cos0 < 1.01, "cos(0) is close to 1")
|
||||
|
||||
let pi: Float = math_pi()
|
||||
let cos_pi: Float = math_cos(pi)
|
||||
assert_true(cos_pi < -0.99, "cos(pi) is close to -1")
|
||||
assert_true(cos_pi > -1.01, "cos(pi) is close to -1")
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── int_to_float / float_to_int ──────────────────────────────────────────────
|
||||
//
|
||||
// NOTE: int_to_float, float_to_int, format_float, and decimal_round use seed
|
||||
// primitives (__int_to_float, __float_to_int, __format_float) that are not yet
|
||||
// implemented in el_seed.c on the runtime/integrate branch. These tests are
|
||||
// omitted until those seed primitives are available. The float arithmetic and
|
||||
// comparison tests below exercise the float type via language builtins instead.
|
||||
|
||||
// ── Integer arithmetic (language built-ins) ───────────────────────────────────
|
||||
|
||||
fn test_int_arithmetic_basic(_: String) -> String {
|
||||
assert_int_eq(2 + 3, 5, "addition")
|
||||
assert_int_eq(10 - 4, 6, "subtraction")
|
||||
assert_int_eq(3 * 4, 12, "multiplication")
|
||||
assert_int_eq(10 / 3, 3, "integer division truncates")
|
||||
assert_int_eq(10 % 3, 1, "modulo")
|
||||
assert_int_eq(0 - 5, -5, "negation via subtraction")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_int_arithmetic_edge(_: String) -> String {
|
||||
assert_int_eq(0 + 0, 0, "zero plus zero")
|
||||
assert_int_eq(0 * 100, 0, "zero times anything")
|
||||
assert_int_eq(1 * 1, 1, "one times one")
|
||||
assert_int_eq(100 / 100, 1, "divide by self")
|
||||
assert_int_eq(7 % 7, 0, "self modulo is zero")
|
||||
assert_int_eq(-5 + 5, 0, "additive inverse")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_int_arithmetic_negative(_: String) -> String {
|
||||
assert_int_eq(-3 + -2, -5, "negative addition")
|
||||
assert_int_eq(-3 * 4, -12, "negative times positive")
|
||||
assert_int_eq(-10 / 3, -3, "negative division truncates toward zero")
|
||||
assert_int_eq(0 - 1000, -1000, "large negative")
|
||||
assert_int_eq(-1 * -1, 1, "negative times negative is positive")
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── Float arithmetic (language built-ins) ────────────────────────────────────
|
||||
|
||||
fn test_float_arithmetic_basic(_: String) -> String {
|
||||
let a: Float = 1.5 + 2.5
|
||||
assert_true(a > 3.99, "1.5 + 2.5 = 4.0")
|
||||
assert_true(a < 4.01, "1.5 + 2.5 = 4.0")
|
||||
|
||||
let b: Float = 5.0 - 2.5
|
||||
assert_true(b > 2.49, "5.0 - 2.5 = 2.5")
|
||||
assert_true(b < 2.51, "5.0 - 2.5 = 2.5")
|
||||
|
||||
let c: Float = 2.0 * 3.0
|
||||
assert_true(c > 5.99, "2.0 * 3.0 = 6.0")
|
||||
assert_true(c < 6.01, "2.0 * 3.0 = 6.0")
|
||||
|
||||
let d: Float = 9.0 / 3.0
|
||||
assert_true(d > 2.99, "9.0 / 3.0 = 3.0")
|
||||
assert_true(d < 3.01, "9.0 / 3.0 = 3.0")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_float_comparison_basic(_: String) -> String {
|
||||
assert_true(1.0 < 2.0, "1.0 < 2.0")
|
||||
assert_true(2.0 > 1.0, "2.0 > 1.0")
|
||||
assert_false(1.0 > 2.0, "1.0 not > 2.0")
|
||||
assert_false(2.0 < 1.0, "2.0 not < 1.0")
|
||||
assert_true(1.5 >= 1.5, "1.5 >= 1.5")
|
||||
assert_true(1.5 <= 1.5, "1.5 <= 1.5")
|
||||
return ""
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
// tests/suite/test_state.el — comprehensive tests for runtime/state.el
|
||||
//
|
||||
// Covers state_set, state_get, state_del, state_keys, state_has, state_get_or.
|
||||
// Uses unique key prefixes to avoid interference with the test framework's
|
||||
// own state keys (which use _test_ prefix).
|
||||
|
||||
// ── state_set / state_get roundtrip ───────────────────────────────────────────
|
||||
|
||||
fn test_state_set_get_basic(_: String) -> String {
|
||||
state_set("suite_key1", "hello")
|
||||
assert_eq(state_get("suite_key1"), "hello", "get back what was set")
|
||||
|
||||
state_set("suite_key2", "world")
|
||||
assert_eq(state_get("suite_key2"), "world", "second key independent")
|
||||
|
||||
state_set("suite_num", "42")
|
||||
assert_eq(state_get("suite_num"), "42", "numeric string value")
|
||||
|
||||
state_set("suite_empty", "")
|
||||
// empty value and missing key both return "" — that's by design per the docs
|
||||
let v: String = state_get("suite_empty")
|
||||
assert_true(str_eq(v, "") || str_eq(v, ""), "empty value stored or treated as absent")
|
||||
|
||||
state_set("suite_long", str_repeat("a", 100))
|
||||
assert_int_eq(str_len(state_get("suite_long")), 100, "long value preserved")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_state_overwrite(_: String) -> String {
|
||||
state_set("suite_over", "first")
|
||||
assert_eq(state_get("suite_over"), "first", "initial value")
|
||||
state_set("suite_over", "second")
|
||||
assert_eq(state_get("suite_over"), "second", "value overwritten")
|
||||
state_set("suite_over", "third")
|
||||
assert_eq(state_get("suite_over"), "third", "value overwritten again")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_state_multiple_keys(_: String) -> String {
|
||||
state_set("suite_mk_a", "alpha")
|
||||
state_set("suite_mk_b", "beta")
|
||||
state_set("suite_mk_c", "gamma")
|
||||
assert_eq(state_get("suite_mk_a"), "alpha", "key a is alpha")
|
||||
assert_eq(state_get("suite_mk_b"), "beta", "key b is beta")
|
||||
assert_eq(state_get("suite_mk_c"), "gamma", "key c is gamma")
|
||||
// Mutate one, others unchanged
|
||||
state_set("suite_mk_b", "BETA")
|
||||
assert_eq(state_get("suite_mk_a"), "alpha", "a unchanged after b mutated")
|
||||
assert_eq(state_get("suite_mk_b"), "BETA", "b updated")
|
||||
assert_eq(state_get("suite_mk_c"), "gamma", "c unchanged after b mutated")
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── missing keys ──────────────────────────────────────────────────────────────
|
||||
|
||||
fn test_state_missing_key(_: String) -> String {
|
||||
assert_eq(state_get("suite_definitely_not_set_xyz_123"), "", "missing key returns empty string")
|
||||
assert_eq(state_get("suite_another_missing_key_abc"), "", "another missing key returns empty string")
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── state_del ─────────────────────────────────────────────────────────────────
|
||||
|
||||
fn test_state_del_basic(_: String) -> String {
|
||||
state_set("suite_del1", "value_to_delete")
|
||||
assert_eq(state_get("suite_del1"), "value_to_delete", "key present before delete")
|
||||
state_del("suite_del1")
|
||||
assert_eq(state_get("suite_del1"), "", "key absent after delete")
|
||||
|
||||
// Delete a non-existent key should not error
|
||||
state_del("suite_never_existed_key_xyz")
|
||||
assert_eq(state_get("suite_never_existed_key_xyz"), "", "delete non-existent is no-op")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_state_del_and_reset(_: String) -> String {
|
||||
state_set("suite_reuse", "original")
|
||||
assert_eq(state_get("suite_reuse"), "original", "original value")
|
||||
state_del("suite_reuse")
|
||||
assert_eq(state_get("suite_reuse"), "", "deleted")
|
||||
state_set("suite_reuse", "new_value")
|
||||
assert_eq(state_get("suite_reuse"), "new_value", "key reused after delete")
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── state_has ─────────────────────────────────────────────────────────────────
|
||||
|
||||
fn test_state_has_basic(_: String) -> String {
|
||||
state_set("suite_has1", "something")
|
||||
assert_true(state_has("suite_has1"), "key with value is present")
|
||||
assert_false(state_has("suite_definitely_absent_xyz_999"), "absent key returns false")
|
||||
|
||||
state_del("suite_has1")
|
||||
assert_false(state_has("suite_has1"), "deleted key is not present")
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── state_get_or ──────────────────────────────────────────────────────────────
|
||||
|
||||
fn test_state_get_or_basic(_: String) -> String {
|
||||
state_set("suite_gor1", "present")
|
||||
assert_eq(state_get_or("suite_gor1", "default"), "present", "returns value when key exists")
|
||||
assert_eq(state_get_or("suite_gor_missing_xyz", "fallback"), "fallback", "returns default when key missing")
|
||||
assert_eq(state_get_or("suite_gor_missing_xyz", ""), "", "returns empty default when key missing")
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── state persists across function calls ──────────────────────────────────────
|
||||
|
||||
fn _suite_state_helper_write() {
|
||||
state_set("suite_persist_test", "written_by_helper")
|
||||
}
|
||||
|
||||
fn _suite_state_helper_increment() {
|
||||
let n: Int = str_to_int(state_get("suite_counter"))
|
||||
state_set("suite_counter", int_to_str(n + 1))
|
||||
}
|
||||
|
||||
fn test_state_cross_function(_: String) -> String {
|
||||
_suite_state_helper_write()
|
||||
assert_eq(state_get("suite_persist_test"), "written_by_helper", "value written by helper function is readable")
|
||||
|
||||
state_set("suite_counter", "0")
|
||||
_suite_state_helper_increment()
|
||||
assert_eq(state_get("suite_counter"), "1", "counter after 1 increment")
|
||||
_suite_state_helper_increment()
|
||||
assert_eq(state_get("suite_counter"), "2", "counter after 2 increments")
|
||||
_suite_state_helper_increment()
|
||||
assert_eq(state_get("suite_counter"), "3", "counter after 3 increments")
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── state_keys ────────────────────────────────────────────────────────────────
|
||||
|
||||
fn test_state_keys_basic(_: String) -> String {
|
||||
state_set("suite_sk_x", "1")
|
||||
state_set("suite_sk_y", "2")
|
||||
let keys: String = state_keys()
|
||||
// keys is a JSON array of all state keys — must contain our test keys
|
||||
assert_true(str_contains(keys, "suite_sk_x"), "keys includes suite_sk_x")
|
||||
assert_true(str_contains(keys, "suite_sk_y"), "keys includes suite_sk_y")
|
||||
assert_true(str_starts_with(keys, "["), "keys is a JSON array")
|
||||
assert_true(str_ends_with(keys, "]"), "keys is a JSON array")
|
||||
assert_true(json_array_len(keys) > 0, "keys array is non-empty")
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── value type storage ────────────────────────────────────────────────────────
|
||||
|
||||
fn test_state_value_types(_: String) -> String {
|
||||
// JSON object as value
|
||||
let json_val: String = "{\"name\":\"alice\",\"age\":30}"
|
||||
state_set("suite_json_val", json_val)
|
||||
let retrieved: String = state_get("suite_json_val")
|
||||
assert_eq(json_get(retrieved, "name"), "alice", "JSON object stored and retrieved")
|
||||
|
||||
// JSON array as value
|
||||
let arr_val: String = "[1,2,3]"
|
||||
state_set("suite_arr_val", arr_val)
|
||||
let retrieved_arr: String = state_get("suite_arr_val")
|
||||
assert_int_eq(json_array_len(retrieved_arr), 3, "JSON array stored and retrieved")
|
||||
|
||||
// Large value
|
||||
let big: String = str_repeat("x", 500)
|
||||
state_set("suite_big_val", big)
|
||||
assert_int_eq(str_len(state_get("suite_big_val")), 500, "large value stored correctly")
|
||||
return ""
|
||||
}
|
||||
@@ -0,0 +1,575 @@
|
||||
// tests/suite/test_string.el — comprehensive tests for runtime/string.el
|
||||
//
|
||||
// Covers every public function in string.el. Import this file after all
|
||||
// runtime modules and runtime/test.el have been concatenated in.
|
||||
|
||||
// ── str_eq ────────────────────────────────────────────────────────────────────
|
||||
|
||||
fn test_str_eq_basic(_: String) -> String {
|
||||
assert_true(str_eq("hello", "hello"), "identical strings are equal")
|
||||
assert_false(str_eq("hello", "world"), "different strings are not equal")
|
||||
assert_true(str_eq("", ""), "empty strings are equal")
|
||||
assert_false(str_eq("a", ""), "non-empty vs empty is not equal")
|
||||
assert_false(str_eq("", "a"), "empty vs non-empty is not equal")
|
||||
assert_false(str_eq("Hello", "hello"), "comparison is case-sensitive")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_str_eq_symbols(_: String) -> String {
|
||||
assert_true(str_eq("!@#$", "!@#$"), "symbols are equal")
|
||||
assert_true(str_eq(" ", " "), "spaces match")
|
||||
assert_false(str_eq(" a", "a "), "leading vs trailing space differ")
|
||||
assert_false(str_eq("abc", "ab"), "prefix not equal to full string")
|
||||
assert_false(str_eq("ab", "abc"), "shorter not equal to longer")
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── str_len ───────────────────────────────────────────────────────────────────
|
||||
|
||||
fn test_str_len_basic(_: String) -> String {
|
||||
assert_int_eq(str_len(""), 0, "empty string has length 0")
|
||||
assert_int_eq(str_len("a"), 1, "single char has length 1")
|
||||
assert_int_eq(str_len("hello"), 5, "hello has length 5")
|
||||
assert_int_eq(str_len("hello world"), 11, "space counted in length")
|
||||
assert_int_eq(str_len("12345"), 5, "digits counted correctly")
|
||||
assert_int_eq(str_len("!@#$%"), 5, "punctuation counted correctly")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_str_len_longer(_: String) -> String {
|
||||
let s: String = str_repeat("ab", 50)
|
||||
assert_int_eq(str_len(s), 100, "repeated string has correct length")
|
||||
assert_int_eq(str_len("abcdefghij"), 10, "10-char string")
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── str_concat / + operator ───────────────────────────────────────────────────
|
||||
|
||||
fn test_str_concat_basic(_: String) -> String {
|
||||
assert_eq(str_concat("hello", " world"), "hello world", "basic concat")
|
||||
assert_eq(str_concat("", "world"), "world", "empty prefix")
|
||||
assert_eq(str_concat("hello", ""), "hello", "empty suffix")
|
||||
assert_eq(str_concat("", ""), "", "both empty")
|
||||
assert_eq("foo" + "bar", "foobar", "operator + concat")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_str_concat_chaining(_: String) -> String {
|
||||
let a: String = "hello"
|
||||
let b: String = " "
|
||||
let c: String = "world"
|
||||
assert_eq(a + b + c, "hello world", "chained concat")
|
||||
assert_eq(str_concat(str_concat("a", "b"), "c"), "abc", "nested concat")
|
||||
assert_int_eq(str_len(str_concat("abc", "def")), 6, "length after concat")
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── str_slice ─────────────────────────────────────────────────────────────────
|
||||
|
||||
fn test_str_slice_basic(_: String) -> String {
|
||||
assert_eq(str_slice("hello world", 0, 5), "hello", "slice from start")
|
||||
assert_eq(str_slice("hello world", 6, 11), "world", "slice from middle")
|
||||
assert_eq(str_slice("hello world", 0, 0), "", "zero-length slice")
|
||||
assert_eq(str_slice("hello", 0, 100), "hello", "end clamped to length")
|
||||
assert_eq(str_slice("hello", 3, 3), "", "start == end is empty")
|
||||
assert_eq(str_slice("hello world", 2, 7), "llo w", "interior slice")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_str_slice_edge(_: String) -> String {
|
||||
assert_eq(str_slice("", 0, 0), "", "empty string slice")
|
||||
assert_eq(str_slice("abc", 1, 2), "b", "single char slice")
|
||||
assert_eq(str_slice("abc", 0, 3), "abc", "full string slice")
|
||||
assert_eq(str_slice("abc", 2, 1), "", "inverted range is empty")
|
||||
assert_eq(str_slice("hello", 5, 5), "", "slice at end is empty")
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── str_starts_with ───────────────────────────────────────────────────────────
|
||||
|
||||
fn test_str_starts_with_basic(_: String) -> String {
|
||||
assert_true(str_starts_with("hello world", "hello"), "prefix present")
|
||||
assert_false(str_starts_with("hello world", "world"), "not a prefix")
|
||||
assert_true(str_starts_with("hello", "hello"), "string is its own prefix")
|
||||
assert_true(str_starts_with("hello", ""), "empty prefix always true")
|
||||
assert_false(str_starts_with("", "a"), "empty string has no non-empty prefix")
|
||||
assert_false(str_starts_with("hi", "hello"), "prefix longer than string")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_str_starts_with_edge(_: String) -> String {
|
||||
assert_true(str_starts_with("abc", "a"), "single-char prefix")
|
||||
assert_false(str_starts_with("abc", "b"), "wrong single-char prefix")
|
||||
assert_true(str_starts_with("", ""), "empty starts with empty")
|
||||
assert_true(str_starts_with("hello world", "hello world"), "full string is prefix")
|
||||
assert_false(str_starts_with("hello", "HELLO"), "case-sensitive prefix")
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── str_ends_with ─────────────────────────────────────────────────────────────
|
||||
|
||||
fn test_str_ends_with_basic(_: String) -> String {
|
||||
assert_true(str_ends_with("hello world", "world"), "suffix present")
|
||||
assert_false(str_ends_with("hello world", "hello"), "not a suffix")
|
||||
assert_true(str_ends_with("hello", "hello"), "string is its own suffix")
|
||||
assert_true(str_ends_with("hello", ""), "empty suffix always true")
|
||||
assert_false(str_ends_with("", "a"), "empty string has no non-empty suffix")
|
||||
assert_false(str_ends_with("hi", "world"), "suffix longer than string")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_str_ends_with_edge(_: String) -> String {
|
||||
assert_true(str_ends_with("abc", "c"), "single-char suffix")
|
||||
assert_false(str_ends_with("abc", "b"), "wrong single-char suffix")
|
||||
assert_true(str_ends_with("", ""), "empty ends with empty")
|
||||
assert_false(str_ends_with("hello", "HELLO"), "case-sensitive suffix")
|
||||
assert_true(str_ends_with("file.txt", ".txt"), "common file extension case")
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── str_contains ─────────────────────────────────────────────────────────────
|
||||
|
||||
fn test_str_contains_basic(_: String) -> String {
|
||||
assert_true(str_contains("hello world", "world"), "contains at end")
|
||||
assert_true(str_contains("hello world", "hello"), "contains at start")
|
||||
assert_true(str_contains("hello world", "lo wo"), "contains in middle")
|
||||
assert_false(str_contains("hello world", "xyz"), "not contained")
|
||||
assert_true(str_contains("hello", ""), "empty sub always contained")
|
||||
assert_false(str_contains("", "a"), "empty string contains nothing")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_str_contains_edge(_: String) -> String {
|
||||
assert_true(str_contains("hello", "hello"), "string contains itself")
|
||||
assert_false(str_contains("hello", "helloo"), "longer sub not contained")
|
||||
assert_true(str_contains("aaa", "a"), "single char in repeated chars")
|
||||
assert_true(str_contains("aaa", "aa"), "two-char sub in repeated chars")
|
||||
assert_false(str_contains("abc", "ABC"), "case-sensitive")
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── str_index_of ─────────────────────────────────────────────────────────────
|
||||
|
||||
fn test_str_index_of_basic(_: String) -> String {
|
||||
assert_int_eq(str_index_of("hello world", "world"), 6, "index of suffix")
|
||||
assert_int_eq(str_index_of("hello world", "hello"), 0, "index of prefix")
|
||||
assert_int_eq(str_index_of("hello world", "o"), 4, "index of first occurrence")
|
||||
assert_int_eq(str_index_of("hello world", "xyz"), -1, "not found returns -1")
|
||||
assert_int_eq(str_index_of("hello", ""), 0, "empty sub returns 0")
|
||||
assert_int_eq(str_index_of("", "a"), -1, "search in empty string")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_str_index_of_duplicates(_: String) -> String {
|
||||
assert_int_eq(str_index_of("aababc", "ab"), 1, "finds first of two occurrences")
|
||||
assert_int_eq(str_index_of("abab", "ab"), 0, "finds first at position 0")
|
||||
assert_int_eq(str_index_of("xabab", "ab"), 1, "finds first after leading char")
|
||||
assert_int_eq(str_index_of("hello hello", "hello"), 0, "finds first hello")
|
||||
assert_int_eq(str_index_of("hello", "hello"), 0, "exact match at 0")
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── str_last_index_of ─────────────────────────────────────────────────────────
|
||||
|
||||
fn test_str_last_index_of_basic(_: String) -> String {
|
||||
assert_int_eq(str_last_index_of("hello hello", "hello"), 6, "last occurrence index")
|
||||
assert_int_eq(str_last_index_of("hello", "hello"), 0, "only one occurrence")
|
||||
assert_int_eq(str_last_index_of("hello", "xyz"), -1, "not found returns -1")
|
||||
assert_int_eq(str_last_index_of("aababc", "ab"), 3, "last ab in aababc")
|
||||
assert_int_eq(str_last_index_of("abcabc", "abc"), 3, "last abc")
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── str_replace ───────────────────────────────────────────────────────────────
|
||||
|
||||
fn test_str_replace_basic(_: String) -> String {
|
||||
assert_eq(str_replace("hello world", "world", "there"), "hello there", "basic replace")
|
||||
assert_eq(str_replace("aaa", "a", "b"), "bbb", "replaces all occurrences")
|
||||
assert_eq(str_replace("hello", "xyz", "abc"), "hello", "no match is identity")
|
||||
assert_eq(str_replace("", "a", "b"), "", "empty string unchanged")
|
||||
assert_eq(str_replace("hello", "", "x"), "hello", "empty from is identity")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_str_replace_multiple(_: String) -> String {
|
||||
assert_eq(str_replace("hello hello", "hello", "bye"), "bye bye", "replace multiple")
|
||||
assert_eq(str_replace("aXbXc", "X", "-"), "a-b-c", "single-char delimiter")
|
||||
assert_eq(str_replace("abcabc", "abc", "X"), "XX", "multi-char replaced twice")
|
||||
assert_eq(str_replace("aabbcc", "bb", ""), "aacc", "replace with empty")
|
||||
assert_eq(str_replace("hello", "hello", "world"), "world", "replace whole string")
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── str_to_upper / str_to_lower ──────────────────────────────────────────────
|
||||
|
||||
fn test_str_to_upper_basic(_: String) -> String {
|
||||
assert_eq(str_to_upper("hello"), "HELLO", "lowercase to uppercase")
|
||||
assert_eq(str_to_upper("HELLO"), "HELLO", "already uppercase unchanged")
|
||||
assert_eq(str_to_upper("Hello World"), "HELLO WORLD", "mixed case")
|
||||
assert_eq(str_to_upper(""), "", "empty string unchanged")
|
||||
assert_eq(str_to_upper("hello123"), "HELLO123", "digits unchanged")
|
||||
assert_eq(str_to_upper("abc-def"), "ABC-DEF", "punctuation unchanged")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_str_to_lower_basic(_: String) -> String {
|
||||
assert_eq(str_to_lower("HELLO"), "hello", "uppercase to lowercase")
|
||||
assert_eq(str_to_lower("hello"), "hello", "already lowercase unchanged")
|
||||
assert_eq(str_to_lower("Hello World"), "hello world", "mixed case")
|
||||
assert_eq(str_to_lower(""), "", "empty string unchanged")
|
||||
assert_eq(str_to_lower("HELLO123"), "hello123", "digits unchanged")
|
||||
assert_eq(str_to_lower("ABC-DEF"), "abc-def", "punctuation unchanged")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_str_upper_lower_roundtrip(_: String) -> String {
|
||||
let s: String = "Hello, World!"
|
||||
assert_eq(str_to_lower(str_to_upper(s)), "hello, world!", "upper then lower")
|
||||
assert_eq(str_to_upper(str_to_lower(s)), "HELLO, WORLD!", "lower then upper")
|
||||
assert_eq(str_to_upper(str_to_lower("ABC")), "ABC", "ABC roundtrip")
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── str_trim / str_lstrip / str_rstrip ───────────────────────────────────────
|
||||
|
||||
fn test_str_trim_basic(_: String) -> String {
|
||||
assert_eq(str_trim(" hello "), "hello", "trims spaces both sides")
|
||||
assert_eq(str_trim("hello"), "hello", "no whitespace unchanged")
|
||||
assert_eq(str_trim(" "), "", "all-space string becomes empty")
|
||||
assert_eq(str_trim(""), "", "empty string unchanged")
|
||||
assert_eq(str_trim("\t hello \n"), "hello", "trims tabs and newlines")
|
||||
assert_eq(str_trim(" hello world "), "hello world", "internal spaces preserved")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_str_lstrip_rstrip(_: String) -> String {
|
||||
assert_eq(str_lstrip(" hello "), "hello ", "lstrip removes leading spaces")
|
||||
assert_eq(str_rstrip(" hello "), " hello", "rstrip removes trailing spaces")
|
||||
assert_eq(str_lstrip("hello"), "hello", "lstrip no-op on clean string")
|
||||
assert_eq(str_rstrip("hello"), "hello", "rstrip no-op on clean string")
|
||||
assert_eq(str_lstrip(""), "", "lstrip empty string")
|
||||
assert_eq(str_rstrip(""), "", "rstrip empty string")
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── str_split ─────────────────────────────────────────────────────────────────
|
||||
|
||||
fn test_str_split_basic(_: String) -> String {
|
||||
let parts: [String] = str_split("a,b,c", ",")
|
||||
assert_int_eq(el_list_len(parts), 3, "split yields 3 parts")
|
||||
assert_eq(el_list_get(parts, 0), "a", "first part is a")
|
||||
assert_eq(el_list_get(parts, 1), "b", "second part is b")
|
||||
assert_eq(el_list_get(parts, 2), "c", "third part is c")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_str_split_edge(_: String) -> String {
|
||||
let single: [String] = str_split("hello", ",")
|
||||
assert_int_eq(el_list_len(single), 1, "no sep found yields 1 part")
|
||||
assert_eq(el_list_get(single, 0), "hello", "single part is the full string")
|
||||
|
||||
let trailing: [String] = str_split("a,b,", ",")
|
||||
assert_int_eq(el_list_len(trailing), 3, "trailing sep yields empty last element")
|
||||
assert_eq(el_list_get(trailing, 2), "", "last element is empty")
|
||||
|
||||
let multi: [String] = str_split("one::two::three", "::")
|
||||
assert_int_eq(el_list_len(multi), 3, "multi-char separator works")
|
||||
assert_eq(el_list_get(multi, 1), "two", "middle element correct")
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── str_join ─────────────────────────────────────────────────────────────────
|
||||
|
||||
fn test_str_join_basic(_: String) -> String {
|
||||
let parts: [String] = el_list_empty()
|
||||
let parts = el_list_append(parts, "a")
|
||||
let parts = el_list_append(parts, "b")
|
||||
let parts = el_list_append(parts, "c")
|
||||
assert_eq(str_join(parts, ","), "a,b,c", "basic join with comma")
|
||||
assert_eq(str_join(parts, ""), "abc", "join with empty separator")
|
||||
assert_eq(str_join(parts, " | "), "a | b | c", "join with multi-char sep")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_str_join_edge(_: String) -> String {
|
||||
let empty_list: [String] = el_list_empty()
|
||||
assert_eq(str_join(empty_list, ","), "", "joining empty list yields empty")
|
||||
|
||||
let one: [String] = el_list_empty()
|
||||
let one = el_list_append(one, "solo")
|
||||
assert_eq(str_join(one, ","), "solo", "joining single element yields that element")
|
||||
assert_eq(str_join(one, "---"), "solo", "single element: no sep inserted")
|
||||
|
||||
let two: [String] = el_list_empty()
|
||||
let two = el_list_append(two, "alpha")
|
||||
let two = el_list_append(two, "beta")
|
||||
assert_eq(str_join(two, ", "), "alpha, beta", "two elements join correctly")
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── int_to_str / str_to_int ──────────────────────────────────────────────────
|
||||
|
||||
fn test_int_to_str_basic(_: String) -> String {
|
||||
assert_eq(int_to_str(0), "0", "zero")
|
||||
assert_eq(int_to_str(42), "42", "positive integer")
|
||||
assert_eq(int_to_str(-1), "-1", "negative integer")
|
||||
assert_eq(int_to_str(1000000), "1000000", "large integer")
|
||||
assert_eq(int_to_str(-999), "-999", "negative large")
|
||||
assert_int_eq(str_len(int_to_str(12345)), 5, "correct digit count")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_str_to_int_basic(_: String) -> String {
|
||||
assert_int_eq(str_to_int("0"), 0, "zero")
|
||||
assert_int_eq(str_to_int("42"), 42, "positive integer")
|
||||
assert_int_eq(str_to_int("-1"), -1, "negative integer")
|
||||
assert_int_eq(str_to_int("1000000"), 1000000, "large integer")
|
||||
assert_int_eq(str_to_int("-999"), -999, "negative large")
|
||||
assert_int_eq(str_to_int(int_to_str(12345)), 12345, "roundtrip")
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── float_to_str / str_to_float ──────────────────────────────────────────────
|
||||
|
||||
fn test_float_to_str_basic(_: String) -> String {
|
||||
assert_eq(float_to_str(0.0), "0", "zero float to str")
|
||||
assert_eq(float_to_str(1.5), "1.5", "basic float")
|
||||
assert_eq(float_to_str(-3.14), "-3.14", "negative float")
|
||||
assert_true(str_len(float_to_str(1.0)) > 0, "float_to_str produces output")
|
||||
assert_contains(float_to_str(100.0), "100", "100.0 contains 100")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_str_to_float_basic(_: String) -> String {
|
||||
let f: Float = str_to_float("1.5")
|
||||
let s: String = float_to_str(f)
|
||||
assert_eq(s, "1.5", "roundtrip 1.5")
|
||||
|
||||
let f2: Float = str_to_float("0.0")
|
||||
let s2: String = float_to_str(f2)
|
||||
assert_eq(s2, "0", "roundtrip 0.0")
|
||||
|
||||
assert_true(str_to_float("3.14") > 3.13, "3.14 > 3.13")
|
||||
assert_true(str_to_float("3.14") < 3.15, "3.14 < 3.15")
|
||||
assert_true(str_to_float("-1.0") < 0.0, "negative float is negative")
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── str_repeat / str_reverse ─────────────────────────────────────────────────
|
||||
|
||||
fn test_str_repeat_basic(_: String) -> String {
|
||||
assert_eq(str_repeat("ab", 3), "ababab", "repeat 3 times")
|
||||
assert_eq(str_repeat("x", 1), "x", "repeat once")
|
||||
assert_eq(str_repeat("x", 0), "", "repeat zero times yields empty")
|
||||
assert_eq(str_repeat("", 5), "", "repeating empty string yields empty")
|
||||
assert_eq(str_repeat("-", 4), "----", "single char repeat")
|
||||
assert_int_eq(str_len(str_repeat("abc", 10)), 30, "length is n * len")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_str_reverse_basic(_: String) -> String {
|
||||
assert_eq(str_reverse("hello"), "olleh", "basic reverse")
|
||||
assert_eq(str_reverse("a"), "a", "single char is its own reverse")
|
||||
assert_eq(str_reverse(""), "", "empty string reverses to empty")
|
||||
assert_eq(str_reverse("abcd"), "dcba", "even-length reverse")
|
||||
assert_eq(str_reverse("racecar"), "racecar", "palindrome unchanged")
|
||||
assert_eq(str_reverse("abc"), "cba", "three-char reverse")
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── str_count ─────────────────────────────────────────────────────────────────
|
||||
|
||||
fn test_str_count_basic(_: String) -> String {
|
||||
assert_int_eq(str_count("hello world hello", "hello"), 2, "two occurrences")
|
||||
assert_int_eq(str_count("aaa", "a"), 3, "adjacent single chars")
|
||||
assert_int_eq(str_count("aaa", "aa"), 1, "non-overlapping: one match")
|
||||
assert_int_eq(str_count("hello", "xyz"), 0, "no match")
|
||||
assert_int_eq(str_count("", "a"), 0, "empty string has no occurrences")
|
||||
assert_int_eq(str_count("hello", ""), 0, "empty sub returns 0")
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── str_strip_prefix / str_strip_suffix ──────────────────────────────────────
|
||||
|
||||
fn test_str_strip_prefix_basic(_: String) -> String {
|
||||
assert_eq(str_strip_prefix("foobar", "foo"), "bar", "strips matching prefix")
|
||||
assert_eq(str_strip_prefix("foobar", "baz"), "foobar", "no-match is identity")
|
||||
assert_eq(str_strip_prefix("hello", ""), "hello", "empty prefix is identity")
|
||||
assert_eq(str_strip_prefix("hello", "hello"), "", "full match yields empty")
|
||||
assert_eq(str_strip_prefix("hello", "HELLO"), "hello", "case-sensitive no-match")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_str_strip_suffix_basic(_: String) -> String {
|
||||
assert_eq(str_strip_suffix("hello.md", ".md"), "hello", "strips matching suffix")
|
||||
assert_eq(str_strip_suffix("hello.md", ".txt"), "hello.md", "no-match is identity")
|
||||
assert_eq(str_strip_suffix("hello", ""), "hello", "empty suffix is identity")
|
||||
assert_eq(str_strip_suffix("hello", "hello"), "", "full match yields empty")
|
||||
assert_eq(str_strip_suffix("hello", "HELLO"), "hello", "case-sensitive no-match")
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── str_find_chars ────────────────────────────────────────────────────────────
|
||||
|
||||
fn test_str_find_chars_basic(_: String) -> String {
|
||||
assert_int_eq(str_find_chars("hello world", " "), 5, "finds space at index 5")
|
||||
assert_int_eq(str_find_chars("hello", "xyz"), -1, "not found returns -1")
|
||||
assert_int_eq(str_find_chars("hello", ""), -1, "empty charset returns -1")
|
||||
assert_int_eq(str_find_chars("hello", "aeiou"), 1, "finds first vowel at index 1")
|
||||
assert_int_eq(str_find_chars("", "a"), -1, "search in empty string returns -1")
|
||||
assert_int_eq(str_find_chars("abc", "c"), 2, "finds last char of string")
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── str_char_at / str_char_code ───────────────────────────────────────────────
|
||||
|
||||
fn test_str_char_at_basic(_: String) -> String {
|
||||
assert_eq(str_char_at("hello", 0), "h", "first char")
|
||||
assert_eq(str_char_at("hello", 4), "o", "last char")
|
||||
assert_eq(str_char_at("hello", 2), "l", "middle char")
|
||||
assert_eq(str_char_at("hello", -1), "", "negative index yields empty")
|
||||
assert_eq(str_char_at("hello", 5), "", "out-of-bounds yields empty")
|
||||
assert_eq(str_char_at("", 0), "", "empty string yields empty")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_str_char_code_basic(_: String) -> String {
|
||||
assert_int_eq(str_char_code("A", 0), 65, "A has code 65")
|
||||
assert_int_eq(str_char_code("a", 0), 97, "a has code 97")
|
||||
assert_int_eq(str_char_code("0", 0), 48, "0 has code 48")
|
||||
assert_int_eq(str_char_code("hello", 0), 104, "h has code 104")
|
||||
assert_int_eq(str_char_code("", 0), 0, "empty string yields 0")
|
||||
assert_int_eq(str_char_code("a", 5), 0, "out-of-bounds yields 0")
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── str_pad_left / str_pad_right ─────────────────────────────────────────────
|
||||
|
||||
fn test_str_pad_left_basic(_: String) -> String {
|
||||
assert_eq(str_pad_left("hi", 5, " "), " hi", "pads to width 5")
|
||||
assert_eq(str_pad_left("hi", 2, " "), "hi", "already at width is unchanged")
|
||||
assert_eq(str_pad_left("hi", 1, " "), "hi", "wider than width is unchanged")
|
||||
assert_eq(str_pad_left("5", 3, "0"), "005", "zero-pad a number")
|
||||
assert_int_eq(str_len(str_pad_left("ab", 7, "x")), 7, "padded to exact width")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_str_pad_right_basic(_: String) -> String {
|
||||
assert_eq(str_pad_right("hi", 5, " "), "hi ", "pads to width 5")
|
||||
assert_eq(str_pad_right("hi", 2, " "), "hi", "already at width is unchanged")
|
||||
assert_eq(str_pad_right("hi", 1, " "), "hi", "wider than width is unchanged")
|
||||
assert_int_eq(str_len(str_pad_right("ab", 7, "-")), 7, "padded to exact width")
|
||||
assert_starts_with(str_pad_right("ab", 7, "-"), "ab", "original string at start")
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── Character classification ──────────────────────────────────────────────────
|
||||
|
||||
fn test_is_letter_basic(_: String) -> String {
|
||||
assert_true(is_letter("a"), "a is a letter")
|
||||
assert_true(is_letter("Z"), "Z is a letter")
|
||||
assert_true(is_letter("abc"), "abc all letters")
|
||||
assert_false(is_letter("1"), "1 is not a letter")
|
||||
assert_false(is_letter(""), "empty string is not a letter")
|
||||
assert_false(is_letter("a1"), "a1 has non-letter")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_is_digit_basic(_: String) -> String {
|
||||
assert_true(is_digit("0"), "0 is a digit")
|
||||
assert_true(is_digit("9"), "9 is a digit")
|
||||
assert_true(is_digit("123"), "123 all digits")
|
||||
assert_false(is_digit("a"), "a is not a digit")
|
||||
assert_false(is_digit(""), "empty is not a digit")
|
||||
assert_false(is_digit("1a"), "1a has non-digit")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_is_whitespace_basic(_: String) -> String {
|
||||
assert_true(is_whitespace(" "), "space is whitespace")
|
||||
assert_true(is_whitespace(" "), "multiple spaces")
|
||||
assert_false(is_whitespace("a"), "a is not whitespace")
|
||||
assert_false(is_whitespace(""), "empty string is not whitespace")
|
||||
assert_false(is_whitespace(" a"), "mixed is not all whitespace")
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── str_count_lines / str_count_words ────────────────────────────────────────
|
||||
|
||||
fn test_str_count_lines_basic(_: String) -> String {
|
||||
assert_int_eq(str_count_lines(""), 0, "empty string has 0 lines")
|
||||
assert_int_eq(str_count_lines("hello"), 1, "single line no newline")
|
||||
assert_int_eq(str_count_lines("a\nb"), 2, "two lines with newline")
|
||||
assert_int_eq(str_count_lines("a\nb\nc"), 3, "three lines")
|
||||
assert_int_eq(str_count_lines("a\n"), 1, "trailing newline not counted as extra line")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_str_count_words_basic(_: String) -> String {
|
||||
assert_int_eq(str_count_words(""), 0, "empty string has 0 words")
|
||||
assert_int_eq(str_count_words("hello"), 1, "single word")
|
||||
assert_int_eq(str_count_words("hello world"), 2, "two words")
|
||||
assert_int_eq(str_count_words(" hello world "), 2, "extra spaces do not add words")
|
||||
assert_int_eq(str_count_words("one two three four"), 4, "four words")
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── url_encode / url_decode ──────────────────────────────────────────────────
|
||||
|
||||
fn test_url_encode_basic(_: String) -> String {
|
||||
assert_eq(url_encode("hello"), "hello", "plain string unchanged")
|
||||
assert_contains(url_encode("hello world"), "%20", "space encoded as %20")
|
||||
assert_true(str_len(url_encode("hello world")) > str_len("hello world"), "encoded is longer")
|
||||
let encoded: String = url_encode("hello world")
|
||||
let decoded: String = url_decode(encoded)
|
||||
assert_eq(decoded, "hello world", "decode reverses encode")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_url_roundtrip(_: String) -> String {
|
||||
let s: String = "name=hello world&value=42"
|
||||
let encoded: String = url_encode(s)
|
||||
let decoded: String = url_decode(encoded)
|
||||
assert_eq(decoded, s, "url encode/decode roundtrip")
|
||||
assert_true(!str_eq(encoded, s), "encoded differs from original")
|
||||
assert_true(str_len(encoded) >= str_len(s), "encoded is at least as long")
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── bool_to_str ───────────────────────────────────────────────────────────────
|
||||
|
||||
fn test_bool_to_str_basic(_: String) -> String {
|
||||
assert_eq(bool_to_str(true), "true", "true becomes 'true'")
|
||||
assert_eq(bool_to_str(false), "false", "false becomes 'false'")
|
||||
assert_true(str_eq(bool_to_str(1 == 1), "true"), "expression true")
|
||||
assert_true(str_eq(bool_to_str(1 == 2), "false"), "expression false")
|
||||
assert_int_eq(str_len(bool_to_str(true)), 4, "true has length 4")
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── str_to_bytes / bytes_to_str ───────────────────────────────────────────────
|
||||
|
||||
fn test_str_to_bytes_basic(_: String) -> String {
|
||||
let b: String = str_to_bytes("hi")
|
||||
assert_true(str_contains(b, "104"), "h has code 104")
|
||||
assert_true(str_contains(b, "105"), "i has code 105")
|
||||
assert_eq(str_to_bytes(""), "[]", "empty string yields empty array")
|
||||
assert_true(str_starts_with(b, "["), "bytes is JSON array")
|
||||
assert_true(str_ends_with(b, "]"), "bytes is JSON array")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_bytes_roundtrip(_: String) -> String {
|
||||
let s: String = "hello"
|
||||
let b: String = str_to_bytes(s)
|
||||
let s2: String = bytes_to_str(b)
|
||||
assert_eq(s2, s, "bytes roundtrip: hello")
|
||||
|
||||
let s3: String = "ABC"
|
||||
let b3: String = str_to_bytes(s3)
|
||||
let s4: String = bytes_to_str(b3)
|
||||
assert_eq(s4, s3, "bytes roundtrip: ABC")
|
||||
|
||||
assert_eq(bytes_to_str("[]"), "", "empty bytes yields empty string")
|
||||
return ""
|
||||
}
|
||||
@@ -0,0 +1,279 @@
|
||||
// tests/suite/test_time.el — comprehensive tests for runtime/time.el
|
||||
//
|
||||
// Covers time_now, now_millis, unix_timestamp, now_ns, time_format, time_to_parts,
|
||||
// time_add, time_diff, duration helpers, and uuid_new.
|
||||
|
||||
// ── time_now / now_millis / unix_timestamp_ms ─────────────────────────────────
|
||||
|
||||
fn test_time_now_basic(_: String) -> String {
|
||||
let t: Int = time_now()
|
||||
// Milliseconds since epoch: must be well past year 2020 (1577836800000)
|
||||
assert_true(t > 1577836800000, "time_now is past 2020-01-01")
|
||||
// Must be before year 2100 (4102444800000)
|
||||
assert_true(t < 4102444800000, "time_now is before 2100-01-01")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_now_millis_basic(_: String) -> String {
|
||||
let t: Int = now_millis()
|
||||
assert_true(t > 1577836800000, "now_millis is past 2020-01-01")
|
||||
assert_true(t < 4102444800000, "now_millis is before 2100-01-01")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_unix_timestamp_ms_basic(_: String) -> String {
|
||||
let t: Int = unix_timestamp_ms()
|
||||
assert_true(t > 1577836800000, "unix_timestamp_ms is past 2020-01-01")
|
||||
assert_true(t < 4102444800000, "unix_timestamp_ms is before 2100-01-01")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_time_now_ms_alias(_: String) -> String {
|
||||
let t: Int = time_now_ms()
|
||||
assert_true(t > 1577836800000, "time_now_ms is past 2020-01-01")
|
||||
assert_true(t < 4102444800000, "time_now_ms is before 2100-01-01")
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── monotonicity ──────────────────────────────────────────────────────────────
|
||||
|
||||
fn test_time_monotonic_now_millis(_: String) -> String {
|
||||
let t1: Int = now_millis()
|
||||
let t2: Int = now_millis()
|
||||
assert_true(t2 >= t1, "second call >= first call (monotonic)")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_time_monotonic_time_now(_: String) -> String {
|
||||
let t1: Int = time_now()
|
||||
let t2: Int = time_now()
|
||||
assert_true(t2 >= t1, "time_now is monotonic across calls")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_time_monotonic_now_ns(_: String) -> String {
|
||||
let t1: Int = now_ns()
|
||||
let t2: Int = now_ns()
|
||||
assert_true(t2 >= t1, "now_ns is monotonic across calls")
|
||||
// ns should be much larger than ms (9 digits difference)
|
||||
assert_true(t1 > 1000000000000000000, "now_ns is in nanosecond scale")
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── unix_timestamp (seconds) ─────────────────────────────────────────────────
|
||||
|
||||
fn test_unix_timestamp_basic(_: String) -> String {
|
||||
let secs: Int = unix_timestamp()
|
||||
// Must be past 2020-01-01 in seconds (1577836800)
|
||||
assert_true(secs > 1577836800, "unix_timestamp is past 2020-01-01")
|
||||
// Must be before 2100-01-01 in seconds (4102444800)
|
||||
assert_true(secs < 4102444800, "unix_timestamp is before 2100-01-01")
|
||||
// seconds are about 1000x smaller than millis
|
||||
let ms: Int = time_now()
|
||||
assert_true(ms > secs, "milliseconds > seconds")
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── time_to_parts ─────────────────────────────────────────────────────────────
|
||||
|
||||
fn test_time_to_parts_basic(_: String) -> String {
|
||||
// Known timestamp: 2024-01-15 12:30:45.123 UTC
|
||||
// 2024-01-15 00:00:00 UTC = epoch + (54 * 365 + 14 leaps) days approximately
|
||||
// Use a precisely known value: 2024-01-15T12:30:45.123Z
|
||||
// epoch_ms = 1705320645123
|
||||
let ts: Int = 1705320645123
|
||||
let parts: String = time_to_parts(ts)
|
||||
assert_eq(json_get(parts, "year"), "2024", "year is 2024")
|
||||
assert_eq(json_get(parts, "month"), "1", "month is 1 (January)")
|
||||
assert_eq(json_get(parts, "day"), "15", "day is 15")
|
||||
assert_eq(json_get(parts, "hour"), "12", "hour is 12")
|
||||
assert_eq(json_get(parts, "minute"), "30", "minute is 30")
|
||||
assert_eq(json_get(parts, "second"), "45", "second is 45")
|
||||
assert_eq(json_get(parts, "ms"), "123", "milliseconds is 123")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_time_to_parts_epoch(_: String) -> String {
|
||||
// Unix epoch: 1970-01-01T00:00:00.000Z = timestamp 0
|
||||
let parts: String = time_to_parts(0)
|
||||
assert_eq(json_get(parts, "year"), "1970", "epoch year is 1970")
|
||||
assert_eq(json_get(parts, "month"), "1", "epoch month is 1")
|
||||
assert_eq(json_get(parts, "day"), "1", "epoch day is 1")
|
||||
assert_eq(json_get(parts, "hour"), "0", "epoch hour is 0")
|
||||
assert_eq(json_get(parts, "minute"), "0", "epoch minute is 0")
|
||||
assert_eq(json_get(parts, "second"), "0", "epoch second is 0")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_time_to_parts_current(_: String) -> String {
|
||||
let ts: Int = time_now()
|
||||
let parts: String = time_to_parts(ts)
|
||||
let year: Int = str_to_int(json_get(parts, "year"))
|
||||
let month: Int = str_to_int(json_get(parts, "month"))
|
||||
let day: Int = str_to_int(json_get(parts, "day"))
|
||||
let hour: Int = str_to_int(json_get(parts, "hour"))
|
||||
let minute: Int = str_to_int(json_get(parts, "minute"))
|
||||
let second: Int = str_to_int(json_get(parts, "second"))
|
||||
assert_true(year >= 2024, "current year >= 2024")
|
||||
assert_true(year < 2100, "current year < 2100")
|
||||
assert_true(month >= 1, "month >= 1")
|
||||
assert_true(month <= 12, "month <= 12")
|
||||
assert_true(day >= 1, "day >= 1")
|
||||
assert_true(day <= 31, "day <= 31")
|
||||
assert_true(hour >= 0, "hour >= 0")
|
||||
assert_true(hour <= 23, "hour <= 23")
|
||||
assert_true(minute >= 0, "minute >= 0")
|
||||
assert_true(minute <= 59, "minute <= 59")
|
||||
assert_true(second >= 0, "second >= 0")
|
||||
assert_true(second <= 59, "second <= 59")
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── time_format ───────────────────────────────────────────────────────────────
|
||||
|
||||
fn test_time_format_iso(_: String) -> String {
|
||||
let ts: Int = 1705320645123
|
||||
let iso: String = time_format(ts, "ISO")
|
||||
assert_starts_with(iso, "2024-01-15", "ISO format starts with correct date")
|
||||
assert_contains(iso, "T12:30:45", "ISO format has correct time")
|
||||
assert_ends_with(iso, "Z", "ISO format ends with Z")
|
||||
assert_contains(iso, "123", "ISO format includes milliseconds")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_time_format_empty(_: String) -> String {
|
||||
let ts: Int = 1705320645123
|
||||
let iso: String = time_format(ts, "")
|
||||
assert_starts_with(iso, "2024-01-15", "empty fmt gives ISO format")
|
||||
assert_ends_with(iso, "Z", "empty fmt gives ISO format ending in Z")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_time_format_strftime(_: String) -> String {
|
||||
let ts: Int = 1705320645123
|
||||
let formatted: String = time_format(ts, "%Y-%m-%d")
|
||||
assert_eq(formatted, "2024-01-15", "strftime %Y-%m-%d")
|
||||
let formatted2: String = time_format(ts, "%H:%M:%S")
|
||||
assert_eq(formatted2, "12:30:45", "strftime %H:%M:%S")
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── time_add ──────────────────────────────────────────────────────────────────
|
||||
|
||||
fn test_time_add_basic(_: String) -> String {
|
||||
let base: Int = 1000000000000
|
||||
assert_int_eq(time_add(base, 1000, "ms"), base + 1000, "add milliseconds")
|
||||
assert_int_eq(time_add(base, 1, "sec"), base + 1000, "add 1 second")
|
||||
assert_int_eq(time_add(base, 1, "min"), base + 60000, "add 1 minute")
|
||||
assert_int_eq(time_add(base, 1, "hour"), base + 3600000, "add 1 hour")
|
||||
assert_int_eq(time_add(base, 1, "day"), base + 86400000, "add 1 day")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_time_add_multiple(_: String) -> String {
|
||||
let base: Int = 1000000000000
|
||||
assert_int_eq(time_add(base, 60, "sec"), base + 60000, "add 60 seconds")
|
||||
assert_int_eq(time_add(base, 24, "hour"), base + 86400000, "add 24 hours = 1 day")
|
||||
assert_int_eq(time_add(base, 0, "sec"), base, "add 0 seconds")
|
||||
assert_int_eq(time_add(base, -1, "sec"), base - 1000, "add negative seconds")
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── time_diff ─────────────────────────────────────────────────────────────────
|
||||
|
||||
fn test_time_diff_basic(_: String) -> String {
|
||||
let t1: Int = 1000000000000
|
||||
let t2: Int = t1 + 5000
|
||||
assert_int_eq(time_diff(t1, t2, "ms"), 5000, "diff in ms")
|
||||
assert_int_eq(time_diff(t1, t2, "sec"), 5, "diff in seconds")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_time_diff_larger(_: String) -> String {
|
||||
let t1: Int = 1000000000000
|
||||
let t2: Int = t1 + 3600000
|
||||
assert_int_eq(time_diff(t1, t2, "min"), 60, "diff of 1 hour in minutes")
|
||||
assert_int_eq(time_diff(t1, t2, "hour"), 1, "diff of 1 hour in hours")
|
||||
assert_int_eq(time_diff(t1, t2, "ms"), 3600000, "diff of 1 hour in ms")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_time_diff_zero(_: String) -> String {
|
||||
let t: Int = 1000000000000
|
||||
assert_int_eq(time_diff(t, t, "ms"), 0, "same timestamp: diff is 0")
|
||||
assert_int_eq(time_diff(t, t, "sec"), 0, "same timestamp: diff in sec is 0")
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── Duration helpers ──────────────────────────────────────────────────────────
|
||||
|
||||
fn test_duration_helpers_basic(_: String) -> String {
|
||||
assert_int_eq(duration_seconds(1), 1000000000, "1 second = 1e9 ns")
|
||||
assert_int_eq(duration_millis(1), 1000000, "1 ms = 1e6 ns")
|
||||
assert_int_eq(duration_nanos(42), 42, "nanos identity")
|
||||
assert_int_eq(duration_to_seconds(1000000000), 1, "1e9 ns = 1 second")
|
||||
assert_int_eq(duration_to_millis(1000000), 1, "1e6 ns = 1 ms")
|
||||
assert_int_eq(duration_to_nanos(42), 42, "nanos identity roundtrip")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_instant_helpers_basic(_: String) -> String {
|
||||
let i: Int = unix_seconds(1000)
|
||||
assert_int_eq(i, 1000000000000, "unix_seconds(1000) = 1e12 ns")
|
||||
|
||||
let ms_i: Int = unix_millis(5000)
|
||||
assert_int_eq(ms_i, 5000000000, "unix_millis(5000) = 5e9 ns")
|
||||
|
||||
assert_int_eq(instant_to_unix_seconds(1000000000000), 1000, "instant to seconds")
|
||||
assert_int_eq(instant_to_unix_millis(5000000000), 5000, "instant to millis")
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── uuid_new / uuid_v4 ────────────────────────────────────────────────────────
|
||||
|
||||
fn test_uuid_new_basic(_: String) -> String {
|
||||
let u: String = uuid_new()
|
||||
assert_int_eq(str_len(u), 36, "UUID has 36 characters")
|
||||
assert_eq(str_char_at(u, 8), "-", "UUID has dash at position 8")
|
||||
assert_eq(str_char_at(u, 13), "-", "UUID has dash at position 13")
|
||||
assert_eq(str_char_at(u, 18), "-", "UUID has dash at position 18")
|
||||
assert_eq(str_char_at(u, 23), "-", "UUID has dash at position 23")
|
||||
return ""
|
||||
}
|
||||
|
||||
fn test_uuid_uniqueness(_: String) -> String {
|
||||
let u1: String = uuid_new()
|
||||
let u2: String = uuid_new()
|
||||
let u3: String = uuid_v4()
|
||||
assert_false(str_eq(u1, u2), "consecutive UUIDs are different")
|
||||
assert_false(str_eq(u1, u3), "uuid_new and uuid_v4 produce different values")
|
||||
assert_false(str_eq(u2, u3), "three consecutive UUIDs are all different")
|
||||
assert_int_eq(str_len(u3), 36, "uuid_v4 also produces 36-char UUID")
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── time_from_parts ───────────────────────────────────────────────────────────
|
||||
|
||||
fn test_time_from_parts_basic(_: String) -> String {
|
||||
// time_from_parts(secs, ns, tz) = secs * 1000 + ns / 1000000
|
||||
let ts: Int = time_from_parts(1000, 0, "UTC")
|
||||
assert_int_eq(ts, 1000000, "1000 secs = 1000000 ms")
|
||||
|
||||
let ts2: Int = time_from_parts(0, 500000000, "UTC")
|
||||
assert_int_eq(ts2, 500, "500ms in nanoseconds -> 500ms")
|
||||
|
||||
let ts3: Int = time_from_parts(1705320645, 123000000, "UTC")
|
||||
assert_int_eq(ts3, 1705320645123, "known timestamp with ms component")
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── instant_to_iso8601 ────────────────────────────────────────────────────────
|
||||
|
||||
fn test_instant_to_iso8601_basic(_: String) -> String {
|
||||
let ts_ms: Int = 1705320645123
|
||||
let ts_ns: Int = ts_ms * 1000000
|
||||
let iso: String = instant_to_iso8601(ts_ns)
|
||||
assert_starts_with(iso, "2024-01-15", "instant_to_iso8601 correct date")
|
||||
assert_ends_with(iso, "Z", "instant_to_iso8601 ends with Z")
|
||||
return ""
|
||||
}
|
||||
Reference in New Issue
Block a user