feat: lifetime elision, tooling CI, registry, and LSP locals
Ship the QUALITY_PLAN stretch from ownership through ecosystem: C.1 lifetime elision (bootstrap + selfhost), bux fmt/test/doc CI hooks, stdlib goldens, package registry (bux search/add), and LSP 0.4 position-sensitive locals with inferred let types. Full-tree format pass plus Map/Set remove double-free fix.
This commit is contained in:
@@ -248,5 +248,126 @@ func Main() -> int {
|
||||
let val: int = (*r).x;
|
||||
return val;
|
||||
}
|
||||
""")
|
||||
check(not res.hasErrors)
|
||||
|
||||
# --- C.1 Lifetime elision ---
|
||||
|
||||
test "@[Checked] elided lifetime: return param ref is OK":
|
||||
let res = checkSource("""
|
||||
@[Checked]
|
||||
func Identity(p: &int) -> &int {
|
||||
return p;
|
||||
}
|
||||
@[Checked]
|
||||
func Main() -> int {
|
||||
var x: int = 7;
|
||||
let r: &int = Identity(&x);
|
||||
return *r;
|
||||
}
|
||||
""")
|
||||
check(not res.hasErrors)
|
||||
|
||||
test "@[Checked] explicit lifetime 'a works":
|
||||
let res = checkSource("""
|
||||
@[Checked]
|
||||
func Identity<'a>(p: &'a int) -> &'a int {
|
||||
return p;
|
||||
}
|
||||
@[Checked]
|
||||
func Main() -> int {
|
||||
var x: int = 3;
|
||||
let r: &int = Identity(&x);
|
||||
return *r;
|
||||
}
|
||||
""")
|
||||
check(not res.hasErrors)
|
||||
|
||||
test "@[Checked] rejects return of reference to local":
|
||||
let res = checkSource("""
|
||||
@[Checked]
|
||||
func Dangle(p: &int) -> &int {
|
||||
var x: int = 1;
|
||||
return &x;
|
||||
}
|
||||
@[Checked]
|
||||
func Main() -> int {
|
||||
return 0;
|
||||
}
|
||||
""")
|
||||
check(res.hasErrors)
|
||||
check(res.diagnostics[0].message.contains("local"))
|
||||
|
||||
test "@[Checked] rejects return ref with no input reference":
|
||||
let res = checkSource("""
|
||||
@[Checked]
|
||||
func Bad() -> &int {
|
||||
var x: int = 1;
|
||||
return &x;
|
||||
}
|
||||
@[Checked]
|
||||
func Main() -> int {
|
||||
return 0;
|
||||
}
|
||||
""")
|
||||
check(res.hasErrors)
|
||||
check(res.diagnostics[0].message.contains("no input reference") or
|
||||
res.diagnostics[0].message.contains("local"))
|
||||
|
||||
test "@[Checked] elision fails with multiple input refs":
|
||||
let res = checkSource("""
|
||||
@[Checked]
|
||||
func Pick(a: &int, b: &int) -> &int {
|
||||
return a;
|
||||
}
|
||||
@[Checked]
|
||||
func Main() -> int {
|
||||
return 0;
|
||||
}
|
||||
""")
|
||||
check(res.hasErrors)
|
||||
check(res.diagnostics[0].message.contains("lifetime elision failed") or
|
||||
res.diagnostics[0].message.contains("lifetime mismatch"))
|
||||
|
||||
test "@[Checked] multiple inputs OK with explicit lifetime":
|
||||
let res = checkSource("""
|
||||
@[Checked]
|
||||
func Pick<'a>(a: &'a int, b: &'a int) -> &'a int {
|
||||
return a;
|
||||
}
|
||||
@[Checked]
|
||||
func Main() -> int {
|
||||
var x: int = 1;
|
||||
var y: int = 2;
|
||||
let r: &int = Pick(&x, &y);
|
||||
return *r;
|
||||
}
|
||||
""")
|
||||
check(not res.hasErrors)
|
||||
|
||||
test "@[Checked] let-bound reborrow of param may be returned":
|
||||
let res = checkSource("""
|
||||
@[Checked]
|
||||
func ViaLet(p: &int) -> &int {
|
||||
let r: &int = p;
|
||||
return r;
|
||||
}
|
||||
@[Checked]
|
||||
func Main() -> int {
|
||||
var x: int = 9;
|
||||
return *ViaLet(&x);
|
||||
}
|
||||
""")
|
||||
check(not res.hasErrors)
|
||||
|
||||
test "unchecked may return &local (no lifetime checks)":
|
||||
let res = checkSource("""
|
||||
func Dangle() -> &int {
|
||||
var x: int = 1;
|
||||
return &x;
|
||||
}
|
||||
func Main() -> int {
|
||||
return 0;
|
||||
}
|
||||
""")
|
||||
check(not res.hasErrors)
|
||||
@@ -0,0 +1,7 @@
|
||||
[Package]
|
||||
Name = "elision_multi_input"
|
||||
Version = "0.1.0"
|
||||
Type = "bin"
|
||||
|
||||
[Build]
|
||||
Output = "Bin"
|
||||
@@ -0,0 +1,7 @@
|
||||
error: type errors in project
|
||||
error: lifetime elision failed: return type needs an explicit lifetime (multiple input references); e.g. func F<'a>(a: &'a T, b: &'a U) -> &'a T
|
||||
--> FILE:2:1
|
||||
|
|
||||
2 | func Pick(a: &int, b: &int) -> &int {
|
||||
| ^^^^
|
||||
= help: add an explicit lifetime, e.g. func F<'a>(x: &'a T, y: &'a U) -> &'a T
|
||||
@@ -0,0 +1,9 @@
|
||||
@[Checked]
|
||||
func Pick(a: &int, b: &int) -> &int {
|
||||
return a;
|
||||
}
|
||||
|
||||
@[Checked]
|
||||
func Main() -> int {
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
[Package]
|
||||
Name = "return_local_ref"
|
||||
Version = "0.1.0"
|
||||
Type = "bin"
|
||||
|
||||
[Build]
|
||||
Output = "Bin"
|
||||
@@ -0,0 +1,7 @@
|
||||
error: type errors in project
|
||||
error: cannot return reference to local variable
|
||||
--> FILE:4:5
|
||||
|
|
||||
4 | return &x;
|
||||
| ^^^^^^
|
||||
= help: return a value, or return a reference borrowed from a function parameter
|
||||
@@ -0,0 +1,10 @@
|
||||
@[Checked]
|
||||
func Dangle(p: &int) -> &int {
|
||||
var x: int = 42;
|
||||
return &x;
|
||||
}
|
||||
|
||||
@[Checked]
|
||||
func Main() -> int {
|
||||
return 0;
|
||||
}
|
||||
@@ -10,20 +10,20 @@ enum Result {
|
||||
func Main() -> int {
|
||||
let r1: Result = Result { tag: Result_Ok };
|
||||
r1.data.Ok_0 = 42;
|
||||
|
||||
|
||||
let r2: Result = Result { tag: Result_Err };
|
||||
r2.data.Err_0 = "error message";
|
||||
|
||||
|
||||
if r1.tag == Result_Ok {
|
||||
PrintLine("r1 is Ok:");
|
||||
PrintInt(r1.data.Ok_0);
|
||||
PrintLine("");
|
||||
}
|
||||
|
||||
|
||||
if r2.tag == Result_Err {
|
||||
PrintLine("r2 is Err:");
|
||||
PrintLine(r2.data.Err_0);
|
||||
}
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -23,12 +23,12 @@ func ColorName(c: Color) -> String {
|
||||
|
||||
func Main() -> int {
|
||||
let myColor: Color = Color::Green;
|
||||
|
||||
|
||||
PrintLine("My color is:");
|
||||
PrintLine(ColorName(myColor));
|
||||
PrintLine("Color value:");
|
||||
PrintInt(myColor as int);
|
||||
PrintLine("");
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -11,13 +11,13 @@ func Fibonacci(n: int) -> int {
|
||||
|
||||
func Main() -> int {
|
||||
PrintLine("Fibonacci sequence:");
|
||||
|
||||
|
||||
var i: int = 0;
|
||||
while i < 10 {
|
||||
let fib: int = Fibonacci(i);
|
||||
PrintLine(Fmt_Fmt1("{0}", String_FromInt(fib)));
|
||||
i = i + 1;
|
||||
}
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -13,14 +13,14 @@ func Max<T>(a: T, b: T) -> T {
|
||||
func Main() -> int {
|
||||
let m1: int = Max<int>(10, 20);
|
||||
let m2: int = Max<int>(5, 3);
|
||||
|
||||
|
||||
PrintLine("Max(10, 20) = ");
|
||||
PrintInt(m1);
|
||||
PrintLine("");
|
||||
|
||||
|
||||
PrintLine("Max(5, 3) = ");
|
||||
PrintInt(m2);
|
||||
PrintLine("");
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ extend Rectangle {
|
||||
func Area(self: Rectangle) -> int {
|
||||
return self.width * self.height;
|
||||
}
|
||||
|
||||
|
||||
func Perimeter(self: Rectangle) -> int {
|
||||
return 2 * (self.width + self.height);
|
||||
}
|
||||
@@ -19,7 +19,7 @@ extend Rectangle {
|
||||
|
||||
func Main() -> int {
|
||||
let rect: Rectangle = Rectangle { width: 10, height: 5 };
|
||||
|
||||
|
||||
PrintLine("Rectangle:");
|
||||
PrintLine("Width = ");
|
||||
PrintInt(rect.width);
|
||||
@@ -33,6 +33,6 @@ func Main() -> int {
|
||||
PrintLine("Perimeter = ");
|
||||
PrintInt(rect.Perimeter());
|
||||
PrintLine("");
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1,49 +1,49 @@
|
||||
module Main {
|
||||
|
||||
import Std::Array::{Array, Array_New, Array_Push};
|
||||
import Std::Array::{Array, Array_New, Array_Push};
|
||||
|
||||
struct Record {
|
||||
name: String;
|
||||
value: int;
|
||||
}
|
||||
|
||||
struct Box {
|
||||
items: Array<Record>;
|
||||
}
|
||||
|
||||
enum Payload {
|
||||
Ok(Record),
|
||||
Err(String),
|
||||
}
|
||||
|
||||
enum Color { Red, Green }
|
||||
|
||||
func Name(c: Color) -> String {
|
||||
match c {
|
||||
Color::Red => "red",
|
||||
Color::Green => "green",
|
||||
struct Record {
|
||||
name: String;
|
||||
value: int;
|
||||
}
|
||||
}
|
||||
|
||||
func Main() -> int {
|
||||
var box: Box;
|
||||
box.items = Array_New<Record>(4);
|
||||
Array_Push<Record>(&box.items, Record { name: "x", value: 10 });
|
||||
struct Box {
|
||||
items: Array<Record>;
|
||||
}
|
||||
|
||||
for it in box.items {
|
||||
if it.value == 10 {
|
||||
return 0;
|
||||
enum Payload {
|
||||
Ok(Record),
|
||||
Err(String),
|
||||
}
|
||||
|
||||
enum Color { Red, Green }
|
||||
|
||||
func Name(c: Color) -> String {
|
||||
match c {
|
||||
Color::Red => "red",
|
||||
Color::Green => "green",
|
||||
}
|
||||
}
|
||||
|
||||
var r: Payload;
|
||||
r.tag = Payload_Ok;
|
||||
r.data.Ok_0 = Record { name: "y", value: 20 };
|
||||
if r.data.Ok_0.value == 20 {
|
||||
return 0;
|
||||
func Main() -> int {
|
||||
var box: Box;
|
||||
box.items = Array_New<Record>(4);
|
||||
Array_Push<Record>(&box.items, Record { name: "x", value: 10 });
|
||||
|
||||
for it in box.items {
|
||||
if it.value == 10 {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
var r: Payload;
|
||||
r.tag = Payload_Ok;
|
||||
r.data.Ok_0 = Record { name: "y", value: 20 };
|
||||
if r.data.Ok_0.value == 20 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,30 +5,30 @@ import Std::String::{String_Len, String_Eq, String_Concat};
|
||||
func Main() -> int {
|
||||
let hello: String = "Hello";
|
||||
let world: String = "World";
|
||||
|
||||
|
||||
PrintLine("String operations:");
|
||||
|
||||
|
||||
// Length
|
||||
PrintLine("Length of 'Hello':");
|
||||
PrintInt(String_Len(hello));
|
||||
PrintLine("");
|
||||
|
||||
|
||||
// Concatenation
|
||||
let greeting: String = String_Concat(hello, ", ");
|
||||
let greeting2: String = String_Concat(greeting, world);
|
||||
let full: String = String_Concat(greeting2, "!");
|
||||
|
||||
|
||||
PrintLine("Concatenated:");
|
||||
PrintLine(full);
|
||||
|
||||
|
||||
// Equality
|
||||
if String_Eq(hello, "Hello") {
|
||||
PrintLine("'Hello' equals 'Hello'");
|
||||
}
|
||||
|
||||
|
||||
if !String_Eq(hello, world) {
|
||||
PrintLine("'Hello' does not equal 'World'");
|
||||
}
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ func Main() -> int {
|
||||
let p1: Point = Point { x: 10, y: 20 };
|
||||
let p2: Point = Point { x: 5, y: 15 };
|
||||
let sum: Point = AddPoints(p1, p2);
|
||||
|
||||
|
||||
PrintLine("Point sum:");
|
||||
PrintLine("x = ");
|
||||
PrintInt(sum.x);
|
||||
@@ -24,6 +24,6 @@ func Main() -> int {
|
||||
PrintLine("y = ");
|
||||
PrintInt(sum.y);
|
||||
PrintLine("");
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
[Package]
|
||||
Name = "stdlib_array"
|
||||
Version = "0.1.0"
|
||||
Type = "bin"
|
||||
|
||||
[Build]
|
||||
Output = "Bin"
|
||||
@@ -0,0 +1,3 @@
|
||||
stdlib_array: ok
|
||||
PASS:
|
||||
stdlib_array
|
||||
@@ -0,0 +1,50 @@
|
||||
// Stdlib golden: Array helpers + Contains/IndexOf/Extend
|
||||
import Std::Io::{PrintLine};
|
||||
import Std::Array::{
|
||||
Array, Array_New, Array_Push, Array_Pop, Array_Clear, Array_IsEmpty,
|
||||
Array_First, Array_Last, Array_Cap, Array_Reserve, Array_Len, Array_Get,
|
||||
Array_Contains, Array_IndexOf, Array_Extend, Array_Free
|
||||
};
|
||||
import Std::Test::{
|
||||
Test_AssertTrue, Test_AssertFalse, Test_AssertEqInt, Test_Pass
|
||||
};
|
||||
|
||||
func Main() -> int {
|
||||
var arr: Array<int> = Array_New<int>(2);
|
||||
Array_Reserve<int>(&arr, 8);
|
||||
Test_AssertTrue(Array_Cap<int>(&arr) >= 8);
|
||||
Test_AssertTrue(Array_IsEmpty<int>(&arr));
|
||||
|
||||
Array_Push<int>(&arr, 10);
|
||||
Array_Push<int>(&arr, 20);
|
||||
Array_Push<int>(&arr, 30);
|
||||
|
||||
Test_AssertFalse(Array_IsEmpty<int>(&arr));
|
||||
Test_AssertEqInt(Array_Len<int>(&arr) as int, 3);
|
||||
Test_AssertEqInt(Array_First<int>(&arr), 10);
|
||||
Test_AssertEqInt(Array_Last<int>(&arr), 30);
|
||||
Test_AssertTrue(Array_Contains<int>(&arr, 20));
|
||||
Test_AssertFalse(Array_Contains<int>(&arr, 99));
|
||||
Test_AssertEqInt(Array_IndexOf<int>(&arr, 30), 2);
|
||||
|
||||
let popped: int = Array_Pop<int>(&arr);
|
||||
Test_AssertEqInt(popped, 30);
|
||||
Test_AssertEqInt(Array_Len<int>(&arr) as int, 2);
|
||||
|
||||
var extra: Array<int> = Array_New<int>(2);
|
||||
Array_Push<int>(&extra, 40);
|
||||
Array_Push<int>(&extra, 50);
|
||||
Array_Extend<int>(&arr, &extra);
|
||||
Test_AssertEqInt(Array_Len<int>(&arr) as int, 4);
|
||||
Test_AssertEqInt(Array_Get<int>(&arr, 3), 50);
|
||||
|
||||
Array_Clear<int>(&arr);
|
||||
Test_AssertTrue(Array_IsEmpty<int>(&arr));
|
||||
Test_AssertTrue(Array_Cap<int>(&arr) >= 8);
|
||||
|
||||
Array_Free<int>(&arr);
|
||||
Array_Free<int>(&extra);
|
||||
PrintLine("stdlib_array: ok");
|
||||
Test_Pass("stdlib_array");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
[Package]
|
||||
Name = "stdlib_collections"
|
||||
Version = "0.1.0"
|
||||
Type = "bin"
|
||||
|
||||
[Build]
|
||||
Output = "Bin"
|
||||
@@ -0,0 +1,3 @@
|
||||
stdlib_collections: ok
|
||||
PASS:
|
||||
stdlib_collections
|
||||
@@ -0,0 +1,67 @@
|
||||
// Stdlib golden: Map / Set / Result / Option helpers
|
||||
import Std::Io::{PrintLine};
|
||||
import Std::Map::{
|
||||
Map, Map_New, Map_Set, Map_Get, Map_Has, Map_Remove, Map_Clear,
|
||||
Map_Len, Map_IsEmpty, Map_Free
|
||||
};
|
||||
import Std::Set::{
|
||||
Set, Set_New, Set_Add, Set_Has, Set_Remove, Set_Len, Set_IsEmpty, Set_Free
|
||||
};
|
||||
import Std::Result::{
|
||||
Result, Result_NewOk, Result_NewErr, Result_IsOk, Result_IsErr,
|
||||
Result_UnwrapOr, Result_Or, Result_UnwrapErr
|
||||
};
|
||||
import Std::Option::{
|
||||
Option, Option_NewSome, Option_NewNone, Option_IsSome, Option_Or, Option_UnwrapOr
|
||||
};
|
||||
import Std::String::{String_Eq};
|
||||
import Std::Test::{
|
||||
Test_AssertTrue, Test_AssertFalse, Test_AssertEqInt, Test_Pass
|
||||
};
|
||||
|
||||
func Main() -> int {
|
||||
var m: Map<int, int> = Map_New<int, int>(16);
|
||||
Map_Set<int, int>(&m, 1, 100);
|
||||
Map_Set<int, int>(&m, 2, 200);
|
||||
Map_Set<int, int>(&m, 3, 300);
|
||||
Test_AssertEqInt(Map_Len<int, int>(&m) as int, 3);
|
||||
Test_AssertTrue(Map_Has<int, int>(&m, 2));
|
||||
Test_AssertTrue(Map_Remove<int, int>(&m, 2));
|
||||
Test_AssertFalse(Map_Has<int, int>(&m, 2));
|
||||
Test_AssertEqInt(Map_Get<int, int>(&m, 1), 100);
|
||||
Test_AssertFalse(Map_Remove<int, int>(&m, 99));
|
||||
Map_Clear<int, int>(&m);
|
||||
Test_AssertTrue(Map_IsEmpty<int, int>(&m));
|
||||
Map_Free<int, int>(&m);
|
||||
|
||||
var s: Set<int> = Set_New<int>(16);
|
||||
Set_Add<int>(&s, 10);
|
||||
Set_Add<int>(&s, 20);
|
||||
Set_Add<int>(&s, 30);
|
||||
Test_AssertTrue(Set_Remove<int>(&s, 20));
|
||||
Test_AssertFalse(Set_Has<int>(&s, 20));
|
||||
Test_AssertTrue(Set_Has<int>(&s, 10));
|
||||
Test_AssertEqInt(Set_Len<int>(&s) as int, 2);
|
||||
Test_AssertFalse(Set_IsEmpty<int>(&s));
|
||||
Set_Free<int>(&s);
|
||||
|
||||
let ok: Result = Result_NewOk(42);
|
||||
let err: Result = Result_NewErr("boom");
|
||||
Test_AssertTrue(Result_IsOk(ok));
|
||||
Test_AssertTrue(Result_IsErr(err));
|
||||
Test_AssertEqInt(Result_UnwrapOr(err, -1), -1);
|
||||
let recovered: Result = Result_Or(err, Result_NewOk(7));
|
||||
Test_AssertEqInt(Result_UnwrapOr(recovered, 0), 7);
|
||||
Test_AssertTrue(String_Eq(Result_UnwrapErr(err), "boom"));
|
||||
|
||||
let some: Option = Option_NewSome(5);
|
||||
let none: Option = Option_NewNone();
|
||||
Test_AssertTrue(Option_IsSome(some));
|
||||
Test_AssertEqInt(Option_UnwrapOr(none, 9), 9);
|
||||
let filled: Option = Option_Or(none, Option_NewSome(3));
|
||||
Test_AssertEqInt(Option_UnwrapOr(filled, 0), 3);
|
||||
|
||||
PrintLine("stdlib_collections: ok");
|
||||
Test_Pass("stdlib_collections");
|
||||
return 0;
|
||||
}
|
||||
Executable
+103
@@ -0,0 +1,103 @@
|
||||
#!/usr/bin/env bash
|
||||
# Golden behavioral tests for stdlib modules.
|
||||
# Usage: from repo root: tests/stdlib_golden/run.sh [path/to/buxc]
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
|
||||
BUXC_ARG="${1:-$ROOT/buxc}"
|
||||
DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
|
||||
# Resolve to absolute path so `cd` into test packages still finds the binary.
|
||||
if [[ "$BUXC_ARG" = /* ]]; then
|
||||
BUXC="$BUXC_ARG"
|
||||
else
|
||||
BUXC="$(cd "$(dirname "$BUXC_ARG")" && pwd)/$(basename "$BUXC_ARG")"
|
||||
fi
|
||||
|
||||
if [[ ! -x "$BUXC" && ! -f "$BUXC" ]]; then
|
||||
echo "error: buxc not found at $BUXC (run make build first)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
passed=0
|
||||
failed=0
|
||||
skipped=0
|
||||
|
||||
normalize_out() {
|
||||
# Drop absolute paths; trim trailing whitespace/blank lines
|
||||
sed -E \
|
||||
-e "s|$ROOT|ROOT|g" \
|
||||
-e "s|$DIR|DIR|g" \
|
||||
-e 's/[[:space:]]+$//' \
|
||||
| sed -e :a -e '/^\n*$/{$d;N;ba' -e '}'
|
||||
}
|
||||
|
||||
for case_dir in "$DIR"/*/; do
|
||||
name="$(basename "$case_dir")"
|
||||
[[ -f "$case_dir/bux.toml" ]] || continue
|
||||
[[ -f "$case_dir/src/Main.bux" ]] || continue
|
||||
|
||||
if [[ ! -f "$case_dir/expected.out" ]]; then
|
||||
echo " SKIP $name (no expected.out)"
|
||||
skipped=$((skipped + 1))
|
||||
continue
|
||||
fi
|
||||
|
||||
# Build + run; capture stdout+stderr
|
||||
out=""
|
||||
if ! out="$(cd "$case_dir" && "$BUXC" run . 2>&1)"; then
|
||||
echo " FAIL $name (build/run non-zero)"
|
||||
printf '%s\n' "$out" | head -40
|
||||
failed=$((failed + 1))
|
||||
continue
|
||||
fi
|
||||
|
||||
got="$(printf '%s\n' "$out" | normalize_out)"
|
||||
exp="$(cat "$case_dir/expected.out" | normalize_out)"
|
||||
|
||||
# Match on key status lines (tests may also print build noise)
|
||||
if printf '%s\n' "$got" | grep -Fqx "$(printf '%s' "$exp" | head -1)" 2>/dev/null; then
|
||||
# Prefer full expected lines all present
|
||||
all_ok=1
|
||||
while IFS= read -r line; do
|
||||
[[ -z "$line" ]] && continue
|
||||
if ! printf '%s\n' "$got" | grep -Fqx "$line"; then
|
||||
all_ok=0
|
||||
break
|
||||
fi
|
||||
done <<< "$exp"
|
||||
if [[ $all_ok -eq 1 ]]; then
|
||||
echo " PASS $name"
|
||||
passed=$((passed + 1))
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
|
||||
# Fallback: every non-empty expected line appears as substring
|
||||
all_ok=1
|
||||
while IFS= read -r line; do
|
||||
[[ -z "$line" ]] && continue
|
||||
if ! printf '%s\n' "$got" | grep -Fq "$line"; then
|
||||
all_ok=0
|
||||
break
|
||||
fi
|
||||
done <<< "$exp"
|
||||
|
||||
if [[ $all_ok -eq 1 ]]; then
|
||||
echo " PASS $name"
|
||||
passed=$((passed + 1))
|
||||
else
|
||||
echo " FAIL $name"
|
||||
echo "---- expected lines ----"
|
||||
printf '%s\n' "$exp"
|
||||
echo "---- got (tail) ----"
|
||||
printf '%s\n' "$got" | tail -20
|
||||
echo "--------------"
|
||||
failed=$((failed + 1))
|
||||
fi
|
||||
done
|
||||
|
||||
echo "Stdlib golden tests: $passed passed, $failed failed, $skipped skipped"
|
||||
if [[ $failed -gt 0 ]]; then
|
||||
exit 1
|
||||
fi
|
||||
@@ -0,0 +1,7 @@
|
||||
[Package]
|
||||
Name = "stdlib_string"
|
||||
Version = "0.1.0"
|
||||
Type = "bin"
|
||||
|
||||
[Build]
|
||||
Output = "Bin"
|
||||
@@ -0,0 +1,3 @@
|
||||
stdlib_string: ok
|
||||
PASS:
|
||||
stdlib_string
|
||||
@@ -0,0 +1,35 @@
|
||||
// Stdlib golden: String_IsEmpty / IsBlank / Repeat / ReplaceAll
|
||||
import Std::Io::{PrintLine};
|
||||
import Std::String::{
|
||||
String_IsEmpty, String_IsBlank, String_Repeat, String_ReplaceAll,
|
||||
String_Eq, String_Len, String_Contains, String_StartsWith, String_EndsWith
|
||||
};
|
||||
import Std::Test::{
|
||||
Test_AssertTrue, Test_AssertFalse, Test_AssertEqInt, Test_AssertEqString, Test_Pass
|
||||
};
|
||||
|
||||
func Main() -> int {
|
||||
Test_AssertTrue(String_IsEmpty(""));
|
||||
Test_AssertFalse(String_IsEmpty("x"));
|
||||
Test_AssertTrue(String_IsBlank(""));
|
||||
Test_AssertTrue(String_IsBlank(" \t\n"));
|
||||
Test_AssertFalse(String_IsBlank(" x "));
|
||||
|
||||
Test_AssertEqString(String_Repeat(".", 5), ".....");
|
||||
Test_AssertEqInt(String_Len(String_Repeat("ab", 3)) as int, 6);
|
||||
Test_AssertEqString(String_Repeat("x", 0), "");
|
||||
Test_AssertEqString(String_Repeat("ok", 1), "ok");
|
||||
|
||||
let multi: String = String_ReplaceAll("a-b-a-b-a", "a", "X");
|
||||
Test_AssertEqString(multi, "X-b-X-b-X");
|
||||
let safe: String = String_ReplaceAll("..", ".", "x.");
|
||||
Test_AssertEqString(safe, "x.x.");
|
||||
|
||||
Test_AssertTrue(String_Contains("hello", "ell"));
|
||||
Test_AssertTrue(String_StartsWith("hello", "he"));
|
||||
Test_AssertTrue(String_EndsWith("hello", "lo"));
|
||||
|
||||
PrintLine("stdlib_string: ok");
|
||||
Test_Pass("stdlib_string");
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user