e8b22e16a2
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).
576 lines
28 KiB
EmacsLisp
576 lines
28 KiB
EmacsLisp
// 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 ""
|
|
}
|