fix(test): expect_eq and expect_near compare floats as floats
On a float or double they emitted an i32 compare and clang refused the IR; they compare as the wider float kind now and a failure prints the numbers with %g. Reseed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
f5e7fe4245
commit
31d842fca4
8 changed files with 21598 additions and 20759 deletions
|
|
@ -1782,7 +1782,8 @@ ludicc app.ludic --emit-llvm -o app.ll # stop at LLVM IR
|
||||||
```
|
```
|
||||||
|
|
||||||
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; a test block is type-checked like `entry`, so a generic function
|
`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
|
||||||
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-floats.md
Normal file
5
changes/expect-floats.md
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
bump: patch
|
||||||
|
type: fix
|
||||||
|
**`expect_eq` and `expect_near` take floats.** On a float or a double they compared with an integer
|
||||||
|
instruction, and the build failed in clang ("defined with type 'float' but expected 'i32'"); they
|
||||||
|
compare as floats now (the wider kind of the two) and a failure prints the numbers.
|
||||||
21
examples/library/testing_floats.ludic
Normal file
21
examples/library/testing_floats.ludic
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
# testing_floats.ludic — expect_eq and expect_near compare floats as floats (and doubles as
|
||||||
|
# doubles), and a failure prints the numbers. Its runner prints:
|
||||||
|
# == 3 passed, 0 failed ==
|
||||||
|
program TestingFloats {
|
||||||
|
numbers float
|
||||||
|
function half(x: float) -> float {
|
||||||
|
return x / 2.0
|
||||||
|
}
|
||||||
|
test "near on floats" {
|
||||||
|
expect_near(half(3.0), 1.52, 0.05)
|
||||||
|
expect_near(0.1 + 0.2, 0.3, 0.0001)
|
||||||
|
}
|
||||||
|
test "eq on floats" {
|
||||||
|
expect_eq(half(3.0), 1.5)
|
||||||
|
expect_eq(half(4.0), 2)
|
||||||
|
}
|
||||||
|
test "doubles" {
|
||||||
|
let d: double = 2.5
|
||||||
|
expect_near(d, 2.0, 1.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -23,6 +23,46 @@ function emit_expect_fail(cond1: pointer, msgsym: pointer, got: pointer, want: p
|
||||||
emit(`{lok}:\n`)
|
emit(`{lok}:\n`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# expect_eq / expect_near on floats: compared as floats (the wider of the two kinds), and the
|
||||||
|
# values printed as numbers - an i32 compare of a float was an IR type error
|
||||||
|
var g_uses_expect_fp: bool = false
|
||||||
|
function emit_expect_fp(e: Node, a: Val, b: Val, tol: Val) -> Val {
|
||||||
|
g_uses_expect_fp = true
|
||||||
|
var t = "float"
|
||||||
|
if (a.ty == "double") or (b.ty == "double") { t = "double" }
|
||||||
|
if tol != null and (tol.ty == "double") { t = "double" }
|
||||||
|
let ac = to_fp(a, t, "expect")
|
||||||
|
let bc = to_fp(b, t, "expect")
|
||||||
|
var c = ""
|
||||||
|
var what = "expect_eq"
|
||||||
|
if tol == null { c = emit_bind(`fcmp oeq {t} {ac}, {bc}`) }
|
||||||
|
else {
|
||||||
|
what = "expect_near"
|
||||||
|
let tc = to_fp(tol, t, "expect_near")
|
||||||
|
let d = emit_bind(`fsub {t} {ac}, {bc}`)
|
||||||
|
let nd = emit_bind(`fneg {t} {d}`)
|
||||||
|
let isneg = emit_bind(`fcmp olt {t} {d}, 0.0`)
|
||||||
|
let ad = emit_bind(`select i1 {isneg}, {t} {nd}, {t} {d}`)
|
||||||
|
c = emit_bind(`fcmp ole {t} {ad}, {tc}`)
|
||||||
|
}
|
||||||
|
let msg = emit_str_const(`{expect_where(e)}: {what} failed`)
|
||||||
|
var ag = ac
|
||||||
|
var bg = bc
|
||||||
|
if (t == "float") {
|
||||||
|
ag = emit_bind(`fpext float {ac} to double`)
|
||||||
|
bg = emit_bind(`fpext float {bc} to double`)
|
||||||
|
}
|
||||||
|
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")
|
||||||
|
emit(` call i32 (ptr, ...) @printf(ptr @.fmt_expect_fp, ptr {msg}, double {ag}, double {bg})\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)}` }
|
||||||
|
|
@ -686,6 +726,7 @@ function emit_call(e: Node) -> Val {
|
||||||
if (name == "expect_eq") {
|
if (name == "expect_eq") {
|
||||||
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) }
|
||||||
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)
|
||||||
|
|
@ -694,6 +735,7 @@ function emit_call(e: Node) -> Val {
|
||||||
if (name == "expect_near") {
|
if (name == "expect_near") {
|
||||||
g_uses_expect = true
|
g_uses_expect = true
|
||||||
let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1]); let tol = emit_expr(e.kids[2])
|
let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1]); let tol = emit_expr(e.kids[2])
|
||||||
|
if is_fp(a.ty) or is_fp(b.ty) or is_fp(tol.ty) { return emit_expect_fp(e, a, b, tol) }
|
||||||
let d = emit_bind(`sub i32 {a.code}, {b.code}`)
|
let d = emit_bind(`sub i32 {a.code}, {b.code}`)
|
||||||
let neg = emit_bind(`sub i32 0, {d}`)
|
let neg = emit_bind(`sub i32 0, {d}`)
|
||||||
let isneg = emit_bind(`icmp slt i32 {d}, 0`)
|
let isneg = emit_bind(`icmp slt i32 {d}, 0`)
|
||||||
|
|
|
||||||
|
|
@ -107,6 +107,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_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
|
||||||
emit("define i32 @main(i32 %argc, ptr %argv) {\nentry:\n")
|
emit("define i32 @main(i32 %argc, ptr %argv) {\nentry:\n")
|
||||||
|
|
|
||||||
21136
selfhost/ludicc.seed.ll
21136
selfhost/ludicc.seed.ll
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -435,6 +435,17 @@ function scaffold_names_case() -> void {
|
||||||
ok(lbl)
|
ok(lbl)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# a failed expect_near on floats says the numbers, as floats, at its file:line
|
||||||
|
function expect_fp_fail_case() -> void {
|
||||||
|
let lbl = "expect_near on floats fails with the numbers at file:line"
|
||||||
|
let src = `{tmp_dir()}/expfp.ludic`
|
||||||
|
write_file(src, "program F {\n numbers float\n test \"off\" {\n expect_near(1.5, 2.0, 0.1)\n }\n}\n")
|
||||||
|
if not shq(`bin/ludicc {src} -o {tmp_dir()}/expfp > {tmp_dir()}/expfp.err 2>&1`) { bad2(lbl, capture_line(`grep -i error {tmp_dir()}/expfp.err | head -1`)); return }
|
||||||
|
if shq(`{tmp_dir()}/expfp > {tmp_dir()}/expfp.out 2>&1`) { bad2(lbl, "a failing test passed"); return }
|
||||||
|
let got = capture_line(`head -1 {tmp_dir()}/expfp.out`)
|
||||||
|
if got == `{src}:4: expect_near failed (got 1.5, want 2)` { ok(lbl) } else { bad2(lbl, `said [{got}]`) }
|
||||||
|
}
|
||||||
|
|
||||||
# `ludic test <dir>`: the test programs under a package, each test block in a process of its own
|
# `ludic test <dir>`: the test programs under a package, each test block in a process of its own
|
||||||
# (a global one test changes is fresh in the next), and a failed expect named by file:line
|
# (a global one test changes is fresh in the next), and a failed expect named by file:line
|
||||||
function test_dir_case() -> void {
|
function test_dir_case() -> void {
|
||||||
|
|
@ -715,6 +726,8 @@ function cmd_dev_test() -> int {
|
||||||
spec_case("library/testing", "== 6 passed, 0 failed ==")
|
spec_case("library/testing", "== 6 passed, 0 failed ==")
|
||||||
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 ==")
|
||||||
|
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)")
|
||||||
spec_case("library/coverage", "== 3 passed, 0 failed ==")
|
spec_case("library/coverage", "== 3 passed, 0 failed ==")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue