preserve application and error inference in test builds
This commit is contained in:
+2
-1
@@ -18,7 +18,8 @@ roadmap and milestone history.
|
|||||||
- `hide` makes any named top-level declaration file-local; declarations are public by default,
|
- `hide` makes any named top-level declaration file-local; declarations are public by default,
|
||||||
leading underscores are ordinary identifier characters, and imports are always file-local
|
leading underscores are ordinary identifier characters, and imports are always file-local
|
||||||
- relative `.h` imports as synthetic C header package namespaces
|
- relative `.h` imports as synthetic C header package namespaces
|
||||||
- native `name test { ... }` declarations with implicit fallible-void results, plus anonymous
|
- native `name test { ... }` declarations with fallible-void results inferred from `testing.Error`
|
||||||
|
and errors propagated by `try`, plus anonymous
|
||||||
transitive `test import "..."` discovery used only by test builds
|
transitive `test import "..."` discovery used only by test builds
|
||||||
- root `main` validation with trap executable recovery for missing or unusable entry points
|
- root `main` validation with trap executable recovery for missing or unusable entry points
|
||||||
|
|
||||||
|
|||||||
@@ -227,6 +227,7 @@ Function :: struct {
|
|||||||
c_abi: bool,
|
c_abi: bool,
|
||||||
imported: bool,
|
imported: bool,
|
||||||
generated: bool,
|
generated: bool,
|
||||||
|
analysis_root: bool,
|
||||||
test: bool,
|
test: bool,
|
||||||
file_hidden: bool,
|
file_hidden: bool,
|
||||||
has_body: bool,
|
has_body: bool,
|
||||||
|
|||||||
@@ -229,6 +229,7 @@ Checker :: struct {
|
|||||||
sink_symbol: symbol.Id,
|
sink_symbol: symbol.Id,
|
||||||
type_symbol: symbol.Id,
|
type_symbol: symbol.Id,
|
||||||
current_result: types.Type,
|
current_result: types.Type,
|
||||||
|
inferred_test_error: ^types.Type,
|
||||||
current_build_ctx: ^Build_Ctx,
|
current_build_ctx: ^Build_Ctx,
|
||||||
current_comptime_values: []Comptime_Value,
|
current_comptime_values: []Comptime_Value,
|
||||||
static_state: Ct_State,
|
static_state: Ct_State,
|
||||||
@@ -4129,6 +4130,17 @@ Infer_Frame :: struct {
|
|||||||
template: ast.Function_Id,
|
template: ast.Function_Id,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
merge_inferred_test_error :: proc(checker: ^Checker, incoming: types.Type) {
|
||||||
|
current := checker.inferred_test_error
|
||||||
|
if current == nil || !types.is_valid(incoming) ||
|
||||||
|
types.can_sum_widen(incoming, current^, &checker.module.types) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if merged, err := types.compose_sum(&checker.module.types, current^, incoming); err == .None {
|
||||||
|
current^ = merged
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
infer_nested_expr :: proc(
|
infer_nested_expr :: proc(
|
||||||
checker: ^Checker,
|
checker: ^Checker,
|
||||||
expr_id: ast.Expr_Id,
|
expr_id: ast.Expr_Id,
|
||||||
@@ -4313,6 +4325,7 @@ infer_compound_expr :: proc(
|
|||||||
case .Try:
|
case .Try:
|
||||||
left_expected := expected if checker.ast_module.exprs[expr.left].kind == .Call else types.INVALID
|
left_expected := expected if checker.ast_module.exprs[expr.left].kind == .Call else types.INVALID
|
||||||
value := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types, left_expected)
|
value := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types, left_expected)
|
||||||
|
merge_inferred_test_error(checker, types.fallible_error(value, store))
|
||||||
return types.fallible_success(value, store) if types.kind(value, store) == .Fallible else types.INVALID
|
return types.fallible_success(value, store) if types.kind(value, store) == .Fallible else types.INVALID
|
||||||
case .Catch:
|
case .Catch:
|
||||||
left_expected := expected if checker.ast_module.exprs[expr.left].kind == .Call else types.INVALID
|
left_expected := expected if checker.ast_module.exprs[expr.left].kind == .Call else types.INVALID
|
||||||
@@ -5488,7 +5501,16 @@ infer_spec_locals_and_result :: proc(
|
|||||||
}
|
}
|
||||||
|
|
||||||
result := types.INVALID
|
result := types.INVALID
|
||||||
|
test_error := function.error
|
||||||
|
previous_test_error := checker.inferred_test_error
|
||||||
|
checker.inferred_test_error = &test_error if function.test else nil
|
||||||
infer_statements(checker, function.body, &locals, local_types, function.pkg, function.file, demanded, &result, result_hint)
|
infer_statements(checker, function.body, &locals, local_types, function.pkg, function.file, demanded, &result, result_hint)
|
||||||
|
checker.inferred_test_error = previous_test_error
|
||||||
|
if function.test && !types.equal(function.error, test_error) {
|
||||||
|
checker.ast_module.functions[spec.template].error = test_error
|
||||||
|
success := types.fallible_success(checker.specs[id].result, &checker.module.types)
|
||||||
|
checker.specs[id].result = types.fallible(&checker.module.types, success, test_error)
|
||||||
|
}
|
||||||
if types.is_constraint(declared) {
|
if types.is_constraint(declared) {
|
||||||
// Narrow the inferred result to the constraint's family; an out-of-family
|
// Narrow the inferred result to the constraint's family; an out-of-family
|
||||||
// result (e.g. returning a non-integer from an `int` function) yields
|
// result (e.g. returning a non-integer from an `int` function) yields
|
||||||
@@ -5948,6 +5970,11 @@ infer_all :: proc(checker: ^Checker) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for function, index in checker.ast_module.functions {
|
||||||
|
if function.analysis_root {
|
||||||
|
ensure_spec(checker, ast.function_id(index), nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
main_template := find_template(checker, checker.main_symbol, 0)
|
main_template := find_template(checker, checker.main_symbol, 0)
|
||||||
if main_template != ast.INVALID_FUNCTION {
|
if main_template != ast.INVALID_FUNCTION {
|
||||||
ensure_spec(checker, main_template, nil)
|
ensure_spec(checker, main_template, nil)
|
||||||
@@ -6035,7 +6062,9 @@ infer_all :: proc(checker: ^Checker) {
|
|||||||
|
|
||||||
for index := 0; index < len(checker.specs); index += 1 {
|
for index := 0; index < len(checker.specs); index += 1 {
|
||||||
id := spec_id(index)
|
id := spec_id(index)
|
||||||
|
before := checker.specs[id].result
|
||||||
inferred := infer_spec_result(checker, id)
|
inferred := infer_spec_result(checker, id)
|
||||||
|
changed = !types.equal(before, checker.specs[id].result) || changed
|
||||||
changed = merge_inferred_type(&checker.module.types, &checker.specs[id].result, inferred) || changed
|
changed = merge_inferred_type(&checker.module.types, &checker.specs[id].result, inferred) || changed
|
||||||
}
|
}
|
||||||
if len(checker.specs) != spec_count {
|
if len(checker.specs) != spec_count {
|
||||||
|
|||||||
+16
-3
@@ -165,20 +165,28 @@ append_runner :: proc(
|
|||||||
diagnostics: ^source.Diagnostics,
|
diagnostics: ^source.Diagnostics,
|
||||||
symbols: ^symbol.Table,
|
symbols: ^symbol.Table,
|
||||||
testing_pkg: ast.Package_Id,
|
testing_pkg: ast.Package_Id,
|
||||||
|
testing_error: types.Type,
|
||||||
tests: []Test_Entry,
|
tests: []Test_Entry,
|
||||||
) {
|
) {
|
||||||
builder := strings.builder_make(module.allocator)
|
builder := strings.builder_make(module.allocator)
|
||||||
defer strings.builder_destroy(&builder)
|
defer strings.builder_destroy(&builder)
|
||||||
|
for entry, index in tests {
|
||||||
|
test := module.functions[entry.function]
|
||||||
|
alias := fmt.tprintf("__brolang_test_%d", index)
|
||||||
|
fmt.sbprintf(&builder, "hide __brolang_test_adapter_%d func() void ! __brolang_testing.Error ", index)
|
||||||
|
strings.write_string(&builder, "{\n\t")
|
||||||
|
fmt.sbprintf(&builder, "%s.%s() catch |_| ", alias, symbol.resolve(symbols, test.name))
|
||||||
|
strings.write_string(&builder, "{\n\t\treturn .expectation_failed\n\t}\n}\n\n")
|
||||||
|
}
|
||||||
strings.write_string(&builder, "main func() i32 {\n\tfailed i32 = 0\n")
|
strings.write_string(&builder, "main func() i32 {\n\tfailed i32 = 0\n")
|
||||||
for entry, index in tests {
|
for entry, index in tests {
|
||||||
test_id := entry.function
|
test_id := entry.function
|
||||||
test := module.functions[test_id]
|
test := module.functions[test_id]
|
||||||
pkg := module.packages[test.pkg]
|
pkg := module.packages[test.pkg]
|
||||||
alias := fmt.tprintf("__brolang_test_%d", index)
|
|
||||||
name := fmt.tprintf("%s.%s", filepath.base(pkg.path), symbol.resolve(symbols, test.name))
|
name := fmt.tprintf("%s.%s", filepath.base(pkg.path), symbol.resolve(symbols, test.name))
|
||||||
strings.write_string(&builder, "\tif (!__brolang_testing.run(\"")
|
strings.write_string(&builder, "\tif (!__brolang_testing.run(\"")
|
||||||
write_brolang_string(&builder, name)
|
write_brolang_string(&builder, name)
|
||||||
fmt.sbprintf(&builder, "\", %s.%s)) ", alias, symbol.resolve(symbols, test.name))
|
fmt.sbprintf(&builder, "\", __brolang_test_adapter_%d)) ", index)
|
||||||
strings.write_string(&builder, "{\n\t\tfailed += 1\n\t}\n")
|
strings.write_string(&builder, "{\n\t\tfailed += 1\n\t}\n")
|
||||||
}
|
}
|
||||||
fmt.sbprintf(&builder, "\t__brolang_testing.summary(%d - failed, failed)\n", len(tests))
|
fmt.sbprintf(&builder, "\t__brolang_testing.summary(%d - failed, failed)\n", len(tests))
|
||||||
@@ -190,7 +198,11 @@ append_runner :: proc(
|
|||||||
append(&module.files, ast.File{source=source_id, pkg=0})
|
append(&module.files, ast.File{source=source_id, pkg=0})
|
||||||
stream := lexer.lex(&sources.items[source_id], diagnostics, symbols, module.allocator)
|
stream := lexer.lex(&sources.items[source_id], diagnostics, symbols, module.allocator)
|
||||||
defer delete(stream.items)
|
defer delete(stream.items)
|
||||||
|
function_start := len(module.functions)
|
||||||
parser.parse_into(&stream, &sources.items[source_id], diagnostics, module, 0, file_id)
|
parser.parse_into(&stream, &sources.items[source_id], diagnostics, module, 0, file_id)
|
||||||
|
for index in 0..<len(tests) {
|
||||||
|
module.functions[function_start+index].error = testing_error
|
||||||
|
}
|
||||||
|
|
||||||
append(&module.imports, ast.Import{
|
append(&module.imports, ast.Import{
|
||||||
alias=symbol.intern(symbols, "__brolang_testing"),
|
alias=symbol.intern(symbols, "__brolang_testing"),
|
||||||
@@ -271,11 +283,12 @@ prepare_tests :: proc(
|
|||||||
}
|
}
|
||||||
} else if function.pkg == 0 && function.name == main_name {
|
} else if function.pkg == 0 && function.name == main_name {
|
||||||
function.generated = true
|
function.generated = true
|
||||||
|
function.analysis_root = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
slice.sort_by(tests[:], test_entry_less)
|
slice.sort_by(tests[:], test_entry_less)
|
||||||
inject_assertion_locations(module, sources, symbols, testing_pkg)
|
inject_assertion_locations(module, sources, symbols, testing_pkg)
|
||||||
append_runner(module, sources, diagnostics, symbols, testing_pkg, tests[:])
|
append_runner(module, sources, diagnostics, symbols, testing_pkg, error_type, tests[:])
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
+23
-2
@@ -12827,9 +12827,25 @@ native_test_framework_discovers_reports_and_preserves_main :: proc(t: ^testing.T
|
|||||||
|
|
||||||
test import "../dependency"
|
test import "../dependency"
|
||||||
|
|
||||||
main func() i32 { return 77 }
|
Token :: struct { start int }
|
||||||
|
OperationError :: enum { failed }
|
||||||
|
|
||||||
|
scan func() void {
|
||||||
|
cursor usize = 0
|
||||||
|
_ = Token{start = cursor}
|
||||||
|
}
|
||||||
|
|
||||||
|
operation func(fail bool) void ! OperationError {
|
||||||
|
if (fail) return .failed
|
||||||
|
}
|
||||||
|
|
||||||
|
main func() i32 {
|
||||||
|
scan()
|
||||||
|
return 77
|
||||||
|
}
|
||||||
|
|
||||||
root_passes test {
|
root_passes test {
|
||||||
|
try operation(false)
|
||||||
try testing.expect(true)
|
try testing.expect(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -12840,6 +12856,10 @@ root_fails test {
|
|||||||
root_continues test {
|
root_continues test {
|
||||||
try testing.expect(true)
|
try testing.expect(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
root_errors test {
|
||||||
|
try operation(true)
|
||||||
|
}
|
||||||
`
|
`
|
||||||
dependency_text := `testing :: import "@std/testing"
|
dependency_text := `testing :: import "@std/testing"
|
||||||
|
|
||||||
@@ -12873,9 +12893,10 @@ dependency_passes test {
|
|||||||
testing.expect(t, strings.contains(output, "FAIL root.root_fails"))
|
testing.expect(t, strings.contains(output, "FAIL root.root_fails"))
|
||||||
testing.expect(t, strings.contains(output, "expected 42, found 41"))
|
testing.expect(t, strings.contains(output, "expected 42, found 41"))
|
||||||
testing.expect(t, strings.contains(output, "PASS root.root_continues"))
|
testing.expect(t, strings.contains(output, "PASS root.root_continues"))
|
||||||
|
testing.expect(t, strings.contains(output, "FAIL root.root_errors"))
|
||||||
testing.expect(t, strings.contains(output, "PASS dependency.dependency_passes"))
|
testing.expect(t, strings.contains(output, "PASS dependency.dependency_passes"))
|
||||||
testing.expect(t, strings.contains(output, root_path))
|
testing.expect(t, strings.contains(output, root_path))
|
||||||
testing.expect(t, strings.contains(output, "3 passed, 1 failed"))
|
testing.expect(t, strings.contains(output, "3 passed, 2 failed"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
|
|||||||
Reference in New Issue
Block a user