fix(test): expect_eq on strings compares their text and prints both on a failure
It lowered to an i32 compare of two pointers, which the IR refused. Two strings with the same text are equal now, a null only to a null, and a failure says expect_eq failed (got "camp", want "lake"). examples/library/testing_strings.ludic (two tests fail on purpose, and the output is checked); ludic.base's actions_test uses it again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
55c1de8734
commit
2fdefa6040
9 changed files with 22471 additions and 21866 deletions
|
|
@ -2057,7 +2057,8 @@ number is above FILE's `name value` lines; `--baseline FILE` writes them.
|
||||||
|
|
||||||
A test program is a file of `test "name" { ... }` blocks with `expect(cond)`, `expect_eq(a, b)` and
|
A test program is a file of `test "name" { ... }` blocks with `expect(cond)`, `expect_eq(a, b)` and
|
||||||
`expect_near(a, b, tol)` in them (on ints and fixeds, or on floats and doubles, which compare - and
|
`expect_near(a, b, tol)` in them (on ints and fixeds, or on floats and doubles, which compare - and
|
||||||
print - as floats); a test block is type-checked like `entry`, so a generic function
|
print - as floats; `expect_eq` on strings compares their text, a null equal only to a null, and
|
||||||
|
prints both: `expect_eq failed (got "camp", want "lake")`); a test block is type-checked like `entry`, so a generic function
|
||||||
called from one works as it does anywhere. `ludic test` finds `tests/*.ludic` and `src/**/*_test.ludic`;
|
called from one works as it does anywhere. `ludic test` finds `tests/*.ludic` and `src/**/*_test.ludic`;
|
||||||
given a directory, it runs every `*_test.ludic` under it and every file straight inside a `tests/`
|
given a directory, it runs every `*_test.ludic` under it and every file straight inside a `tests/`
|
||||||
directory under it. **Each test block runs in a process of its own**, so a global one test changes
|
directory under it. **Each test block runs in a process of its own**, so a global one test changes
|
||||||
|
|
|
||||||
5
changes/expect-eq-strings.md
Normal file
5
changes/expect-eq-strings.md
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
bump: patch
|
||||||
|
type: fix
|
||||||
|
**`expect_eq` on strings compares their text.** It lowered to an integer compare of two pointers,
|
||||||
|
which the IR refused; now two strings with the same text are equal (a null only to a null), and a
|
||||||
|
failure prints both: `expect_eq failed (got "camp", want "lake")`.
|
||||||
17
examples/library/testing_strings.ludic
Normal file
17
examples/library/testing_strings.ludic
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
# testing_strings.ludic - expect_eq on strings compares their text, not where they are kept, and a
|
||||||
|
# failure prints both (two of these fail on purpose: the runner reports them and exits 1)
|
||||||
|
program TestingStrings {
|
||||||
|
function joined(a: string, b: string) -> string { return a + b }
|
||||||
|
test "equal text built two ways" {
|
||||||
|
expect_eq(joined("ca", "mp"), "camp")
|
||||||
|
let none: string = null
|
||||||
|
expect_eq(none, null)
|
||||||
|
}
|
||||||
|
test "different text fails, and says both" {
|
||||||
|
expect_eq(joined("ca", "mp"), "lake")
|
||||||
|
}
|
||||||
|
test "a null is not a string" {
|
||||||
|
let none: string = null
|
||||||
|
expect_eq(none, "camp")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -25,6 +25,6 @@ program ActionsTest {
|
||||||
core_add(b, s)
|
core_add(b, s)
|
||||||
core_tick_all(b, tick_new(0.016, 1, 0.0))
|
core_tick_all(b, tick_new(0.016, 1, 0.0))
|
||||||
expect(d.open)
|
expect(d.open)
|
||||||
expect(d.seen == "o")
|
expect_eq(d.seen, "o")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,35 @@ function emit_expect_fp(e: Node, a: Val, b: Val, tol: Val) -> Val {
|
||||||
return val("0", "void")
|
return val("0", "void")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# expect_eq on strings: equal by content (two nulls are equal, a null and a string are not), and
|
||||||
|
# both printed on a failure - it was an i32 compare of two pointers, which the IR refused
|
||||||
|
var g_uses_expect_str: bool = false
|
||||||
|
function emit_expect_str(e: Node, a: Val, b: Val) -> Val {
|
||||||
|
g_uses_expect_str = true
|
||||||
|
let an = emit_bind(`icmp eq ptr {a.code}, null`)
|
||||||
|
let bn = emit_bind(`icmp eq ptr {b.code}, null`)
|
||||||
|
let anyn = emit_bind(`or i1 {an}, {bn}`)
|
||||||
|
let same = emit_bind(`icmp eq ptr {a.code}, {b.code}`)
|
||||||
|
let empty = emit_str_const("")
|
||||||
|
let sa = emit_bind(`select i1 {anyn}, ptr {empty}, ptr {a.code}`)
|
||||||
|
let sb = emit_bind(`select i1 {anyn}, ptr {empty}, ptr {b.code}`)
|
||||||
|
let r = emit_bind(`call i32 @strcmp(ptr {sa}, ptr {sb})`)
|
||||||
|
let eq = emit_bind(`icmp eq i32 {r}, 0`)
|
||||||
|
let c = emit_bind(`select i1 {anyn}, i1 {same}, i1 {eq}`)
|
||||||
|
let msg = emit_str_const(`{expect_where(e)}: expect_eq failed`)
|
||||||
|
let nul = emit_str_const("<null>")
|
||||||
|
let lok = lbl("exok")
|
||||||
|
let lbad = lbl("exbad")
|
||||||
|
emit(` br i1 {c}, label %{lok}, label %{lbad}\n`)
|
||||||
|
emit(`{lbad}:\n`)
|
||||||
|
emit(" store i32 1, ptr @L_test_fail\n")
|
||||||
|
let pa = emit_bind(`select i1 {an}, ptr {nul}, ptr {a.code}`)
|
||||||
|
let pb = emit_bind(`select i1 {bn}, ptr {nul}, ptr {b.code}`)
|
||||||
|
emit(` call i32 (ptr, ...) @printf(ptr @.fmt_expect_str, ptr {msg}, ptr {pa}, ptr {pb})\n`)
|
||||||
|
emit(` br label %{lok}\n`)
|
||||||
|
emit(`{lok}:\n`)
|
||||||
|
return val("0", "void")
|
||||||
|
}
|
||||||
# an assertion names the file it is written in, as the compiler was given it, and its line
|
# an assertion names the file it is written in, as the compiler was given it, and its line
|
||||||
function expect_where(e: Node) -> pointer {
|
function expect_where(e: Node) -> pointer {
|
||||||
if e.file != null { return `{e.file}:{itoa(e.line)}` }
|
if e.file != null { return `{e.file}:{itoa(e.line)}` }
|
||||||
|
|
@ -737,6 +766,7 @@ function emit_call(e: Node) -> Val {
|
||||||
g_uses_expect = true
|
g_uses_expect = true
|
||||||
let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1])
|
let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1])
|
||||||
if is_fp(a.ty) or is_fp(b.ty) { return emit_expect_fp(e, a, b, null) }
|
if is_fp(a.ty) or is_fp(b.ty) { return emit_expect_fp(e, a, b, null) }
|
||||||
|
if (a.ty == "string") or (b.ty == "string") { return emit_expect_str(e, a, b) }
|
||||||
let c = emit_bind(`icmp eq i32 {a.code}, {b.code}`)
|
let c = emit_bind(`icmp eq i32 {a.code}, {b.code}`)
|
||||||
let msg = emit_str_const(`{expect_where(e)}: expect_eq failed`)
|
let msg = emit_str_const(`{expect_where(e)}: expect_eq failed`)
|
||||||
emit_expect_fail(c, msg, a.code, b.code)
|
emit_expect_fail(c, msg, a.code, b.code)
|
||||||
|
|
|
||||||
|
|
@ -108,6 +108,7 @@ function emit_test_runner() -> void {
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(g_tests) { emit_test_fn(g_tests[i], i); i += 1 }
|
while i < len(g_tests) { emit_test_fn(g_tests[i], i); i += 1 }
|
||||||
if g_uses_expect { emith("@.fmt_expect = private unnamed_addr constant [22 x i8] c\"%s (got %d, want %d)\\0A\\00\"\n") }
|
if g_uses_expect { emith("@.fmt_expect = private unnamed_addr constant [22 x i8] c\"%s (got %d, want %d)\\0A\\00\"\n") }
|
||||||
|
if g_uses_expect_str { emith("@.fmt_expect_str = private unnamed_addr constant [26 x i8] c\"%s (got \\22%s\\22, want \\22%s\\22)\\0A\\00\"\n") }
|
||||||
if g_uses_expect_fp { emith("@.fmt_expect_fp = private unnamed_addr constant [22 x i8] c\"%s (got %g, want %g)\\0A\\00\"\n") }
|
if g_uses_expect_fp { emith("@.fmt_expect_fp = private unnamed_addr constant [22 x i8] c\"%s (got %g, want %g)\\0A\\00\"\n") }
|
||||||
|
|
||||||
ll_t = 0; ll_lbl = 0
|
ll_t = 0; ll_lbl = 0
|
||||||
|
|
|
||||||
22134
selfhost/ludicc.seed.ll
22134
selfhost/ludicc.seed.ll
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -537,6 +537,16 @@ function migrate_case() -> void {
|
||||||
if ran == "2 names, first a" { ok(lbl) } else { bad2(lbl, `ran [{ran}]`) }
|
if ran == "2 names, first a" { ok(lbl) } else { bad2(lbl, `ran [{ran}]`) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# expect_eq on strings: the text is compared, and a failure prints both (the spec fails on purpose)
|
||||||
|
function expect_str_case() -> void {
|
||||||
|
let lbl = "testing_strings.ludic (expect_eq compares strings by content, a null only equal to a null, and prints both on a failure)"
|
||||||
|
let out = `{tmp_dir()}/t_strings`
|
||||||
|
if not shq(`bin/ludicc examples/library/testing_strings.ludic -o {out} > {out}.log 2>&1`) { bad2(lbl, capture_line(`grep -i error {out}.log | head -1`)); return }
|
||||||
|
if shq(`{out} > {out}.txt 2>&1`) { bad2(lbl, "two failing tests, and it exited 0"); return }
|
||||||
|
let got = capture(`cat {out}.txt`)
|
||||||
|
let want = "ok - equal text built two ways\nexamples/library/testing_strings.ludic:11: expect_eq failed (got \"camp\", want \"lake\")\nFAIL - different text fails, and says both\nexamples/library/testing_strings.ludic:15: expect_eq failed (got \"<null>\", want \"camp\")\nFAIL - a null is not a string\n== 1 passed, 2 failed ==\n"
|
||||||
|
if s_trim(got) == s_trim(want) { ok(lbl) } else { bad2(lbl, `said [{s_trim(got)}]`) }
|
||||||
|
}
|
||||||
# a program built headless with the toolchain and run: its first line
|
# a program built headless with the toolchain and run: its first line
|
||||||
function headless_case(path: pointer, exp: pointer, label: pointer) -> void {
|
function headless_case(path: pointer, exp: pointer, label: pointer) -> void {
|
||||||
let out = `{tmp_dir()}/h_{flat(path)}`
|
let out = `{tmp_dir()}/h_{flat(path)}`
|
||||||
|
|
@ -879,6 +889,7 @@ function cmd_dev_test() -> int {
|
||||||
spec_case("library/testing_generic", "== 2 passed, 0 failed ==")
|
spec_case("library/testing_generic", "== 2 passed, 0 failed ==")
|
||||||
spec_case("library/testing_random", "== 1 passed, 0 failed ==")
|
spec_case("library/testing_random", "== 1 passed, 0 failed ==")
|
||||||
spec_case("library/testing_floats", "== 3 passed, 0 failed ==")
|
spec_case("library/testing_floats", "== 3 passed, 0 failed ==")
|
||||||
|
expect_str_case()
|
||||||
expect_fp_fail_case()
|
expect_fp_fail_case()
|
||||||
feat_case("library/random_plain", "", "1 1 1", "random_plain.ludic (Random.* and a bare rng_range in a program with no ECS)")
|
feat_case("library/random_plain", "", "1 1 1", "random_plain.ludic (Random.* and a bare rng_range in a program with no ECS)")
|
||||||
feat_case("library/value_direct", "", "{\"fish\":3} 3", "value_direct.ludic (value_* / json_* called directly splice the value tree)")
|
feat_case("library/value_direct", "", "{\"fish\":3} 3", "value_direct.ludic (value_* / json_* called directly splice the value tree)")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue