compiler: Check and initialize test discovery storage
b213f338699a7fbaf36f5b52cdbdd5891ee19ed79cc06a5cc22c0e28e9960c46
1 parent
da9699b9
Makefile
+10 -2
| 29 | 29 | ||
| 30 | 30 | # Verify the emulator binary exists. |
|
| 31 | 31 | EMU_PATH := $(shell command -v $(EMU) 2>/dev/null) |
|
| 32 | 32 | ||
| 33 | 33 | default: emulator $(RAD_BIN) |
|
| 34 | - | test: emulator seed-test std-test bin-test |
|
| 34 | + | test: emulator seed-test std-test bin-test driver-test |
|
| 35 | 35 | ||
| 36 | 36 | seed-test: |
|
| 37 | 37 | @seed/test |
|
| 38 | 38 | ||
| 39 | 39 | # Emulator command check |
| 79 | 79 | $(addsuffix .s,$(STD_LIB_TEST)) \ |
|
| 80 | 80 | $(addsuffix .o,$(STD_LIB_TEST)) |
|
| 81 | 81 | ||
| 82 | 82 | # Binary Tests |
|
| 83 | 83 | ||
| 84 | + | # Driver fixtures exercise test discovery through the compiler command line. |
|
| 85 | + | driver-test: $(RAD_BIN) |
|
| 86 | + | @RAD_EMULATOR="$(EMU)" RAD_BIN="$(RAD_BIN)" test/driver |
|
| 87 | + | ||
| 88 | + | driver-test-image: $(RAD_BIN) |
|
| 89 | + | @$(RADIANCE) -test $(STD) -mod lib/std/testing.rad $(DRIVER_TEST_INPUTS) $(DRIVER_TEST_FLAGS) \ |
|
| 90 | + | -entry selected -o $(DRIVER_TEST_OUTPUT) |
|
| 91 | + | ||
| 84 | 92 | BIN_TEST_DIR := test/tests |
|
| 85 | 93 | # Only tests with `//! returns:` are compiled to binaries and executed. |
|
| 86 | 94 | BIN_TEST_EXE_SRC := $(shell grep -rl '^//! returns:' $(BIN_TEST_DIR)) |
|
| 87 | 95 | BIN_TEST_RAD_EXE_SRC := $(filter %.rad,$(BIN_TEST_EXE_SRC)) |
|
| 88 | 96 | BIN_TEST_RAS_EXE_SRC := $(filter %.ras,$(BIN_TEST_EXE_SRC)) |
| 141 | 149 | ||
| 142 | 150 | t: test |
|
| 143 | 151 | c: clean |
|
| 144 | 152 | ||
| 145 | 153 | .PHONY: test clean default seed-test std-test bin-test seed \ |
|
| 146 | - | clean-std-test clean-bin-test clean-rad emulator |
|
| 154 | + | clean-std-test clean-bin-test clean-rad emulator driver-test driver-test-image |
|
| 147 | 155 | .SUFFIXES: |
|
| 148 | 156 | .DELETE_ON_ERROR: |
|
| 149 | 157 | .SILENT: |
compiler/radiance.rad
+21 -15
| 657 | 657 | } |
|
| 658 | 658 | ||
| 659 | 659 | /// Scan a single module's AST for `@test` functions and append them to `tests`. |
|
| 660 | 660 | fn collectModuleTests( |
|
| 661 | 661 | entry: *module::ModuleEntry, |
|
| 662 | - | tests: &mut [TestDesc], |
|
| 662 | + | tests: &mut [?TestDesc], |
|
| 663 | 663 | testCount: &mut u32 |
|
| 664 | 664 | ) { |
|
| 665 | 665 | let modAst = module::astFor(entry) else { |
|
| 666 | 666 | return; |
|
| 667 | 667 | }; |
| 682 | 682 | } |
|
| 683 | 683 | } |
|
| 684 | 684 | } |
|
| 685 | 685 | } |
|
| 686 | 686 | ||
| 687 | + | /// Collect initialized test descriptors from one package in module order. |
|
| 688 | + | fn collectPackageTests(graph: &module::ModuleGraph, packageId: u16, tests: &mut [?TestDesc]) -> u32 { |
|
| 689 | + | let mut count: u32 = 0; |
|
| 690 | + | for modIdx in 0..graph.entriesLen { |
|
| 691 | + | if let entry = module::get(graph, modIdx as u16) { |
|
| 692 | + | if entry.packageId == packageId { |
|
| 693 | + | collectModuleTests(entry, tests, &mut count); |
|
| 694 | + | } |
|
| 695 | + | } |
|
| 696 | + | } |
|
| 697 | + | return count; |
|
| 698 | + | } |
|
| 699 | + | ||
| 687 | 700 | /// Synthesize a `testing::test("mod", "name", mod::fn)` call for one test. |
|
| 688 | 701 | unsafe fn synthTestCall(arena: &mut ast::NodeArena, desc: &TestDesc) -> *ast::Node { |
|
| 689 | 702 | let callee = synthScopeAccess(arena, &["testing", "test"]); |
|
| 690 | 703 | let modStr = il::formatQualifiedName( |
|
| 691 | 704 | &mut arena.arena, |
| 694 | 707 | ); |
|
| 695 | 708 | let modArg = ast::synthNode(arena, ast::NodeValue::String(modStr)); |
|
| 696 | 709 | let nameArg = ast::synthNode(arena, ast::NodeValue::String(desc.fnName)); |
|
| 697 | 710 | ||
| 698 | 711 | // Intra-package path: skip the package name prefix. |
|
| 699 | - | let mut funcPath: [*[u8]; 16] = undefined; |
|
| 712 | + | let mut funcPath: [*[u8]; 16] = [""; 16]; |
|
| 700 | 713 | for j in 1..desc.modPath.len { |
|
| 701 | 714 | set funcPath[j - 1] = desc.modPath[j]; |
|
| 702 | 715 | } |
|
| 703 | 716 | set funcPath[desc.modPath.len - 1] = desc.fnName; |
|
| 704 | 717 | let funcArg = synthScopeAccess(arena, &funcPath[..desc.modPath.len]); |
| 733 | 746 | ) throws (Error) { |
|
| 734 | 747 | let entryPkg = try getEntryPackage(ctx); |
|
| 735 | 748 | let root = try getRootModule(&entryPkg, &ctx.graph); |
|
| 736 | 749 | ||
| 737 | 750 | // Collect test functions from the entry package's modules. |
|
| 738 | - | let mut tests: [TestDesc; MAX_TESTS] = undefined; |
|
| 739 | - | let mut testCount: u32 = 0; |
|
| 740 | - | ||
| 741 | - | for modIdx in 0..ctx.graph.entriesLen { |
|
| 742 | - | if let entry = module::get(&ctx.graph, modIdx as u16) { |
|
| 743 | - | if entry.packageId == entryPkg.id { |
|
| 744 | - | collectModuleTests(entry, &mut tests[..], &mut testCount); |
|
| 745 | - | } |
|
| 746 | - | } |
|
| 747 | - | } |
|
| 751 | + | let mut tests: [?TestDesc; MAX_TESTS] = [nil; MAX_TESTS]; |
|
| 752 | + | let testCount = collectPackageTests(&ctx.graph, entryPkg.id, &mut tests[..]); |
|
| 748 | 753 | if testCount == 0 { |
|
| 749 | 754 | throw error(&["fatal:", "no test functions found"]); |
|
| 750 | 755 | } |
|
| 751 | - | let mut countBuf: [u8; 10] = undefined; |
|
| 756 | + | let mut countBuf: [u8; 10] = [0; 10]; |
|
| 752 | 757 | let start = fmt::formatU32(testCount, &mut countBuf[..]); |
|
| 753 | 758 | io::printError("radiance: "); |
|
| 754 | 759 | io::printError(entryPkg.name); |
|
| 755 | 760 | io::printError(": found "); |
|
| 756 | 761 | io::printError(&countBuf[start..]); |
| 764 | 769 | throw error(&["failed to set test runner AST"]); |
|
| 765 | 770 | }; |
|
| 766 | 771 | } |
|
| 767 | 772 | ||
| 768 | 773 | /// Synthesize the test entry point. |
|
| 769 | - | unsafe fn synthTestMainFn(arena: &mut ast::NodeArena, tests: &[TestDesc]) -> *ast::Node { |
|
| 774 | + | unsafe fn synthTestMainFn(arena: &mut ast::NodeArena, tests: &[?TestDesc]) -> *ast::Node { |
|
| 770 | 775 | // Build array literal: `[testing::test(...), ...]`. |
|
| 771 | 776 | let a = alloc::arenaAllocator(&mut arena.arena); |
|
| 772 | 777 | let mut elements = ast::nodeSlice(arena, tests.len as u32); |
|
| 773 | 778 | for i in 0..tests.len { |
|
| 774 | - | elements.append(synthTestCall(arena, &tests[i]), a); |
|
| 779 | + | let desc = tests[i] else panic "synthTestMainFn: missing active test"; |
|
| 780 | + | elements.append(synthTestCall(arena, &desc), a); |
|
| 775 | 781 | } |
|
| 776 | 782 | let arrayLit = ast::synthNode(arena, ast::NodeValue::ArrayLit(elements)); |
|
| 777 | 783 | ||
| 778 | 784 | // Build: `&[...]`. |
|
| 779 | 785 | let testsRef = ast::synthNode(arena, ast::NodeValue::AddressOf(ast::AddressOf { |
test/driver
added
+85 -0
| 1 | + | #!/bin/sh |
|
| 2 | + | # Verify test discovery, package selection, and descriptor capacity. |
|
| 3 | + | set -eu |
|
| 4 | + | ulimit -c 0 |
|
| 5 | + | work=$(mktemp -d) |
|
| 6 | + | trap 'rm -rf "$work"' EXIT HUP INT TERM |
|
| 7 | + | mkdir "$work/selected" |
|
| 8 | + | ||
| 9 | + | # The dependency test must not enter the selected package's runner. |
|
| 10 | + | printf '%s\n' '/// Test outside the entry package.' '@test fn excluded() { assert false; }' > "$work/dependency.rad" |
|
| 11 | + | printf '%s\n' 'use std::testing;' '/// Test in a child module.' \ |
|
| 12 | + | '@test fn childTest() throws (testing::TestError) { assert true; }' > "$work/selected/child.rad" |
|
| 13 | + | ||
| 14 | + | # Compile through Make so the driver uses the standard memory settings. |
|
| 15 | + | compile() { |
|
| 16 | + | timeout 300 "${MAKE:-make}" --no-print-directory driver-test-image \ |
|
| 17 | + | RAD_BIN="${RAD_BIN:-bin/radiance.rv64.dev}" \ |
|
| 18 | + | RAD_EMULATOR="${RAD_EMULATOR:-emulator}" \ |
|
| 19 | + | DRIVER_TEST_INPUTS="-pkg dependency -mod $work/dependency.rad -pkg selected -mod $work/selected.rad $1" \ |
|
| 20 | + | DRIVER_TEST_FLAGS="${2:-}" \ |
|
| 21 | + | DRIVER_TEST_OUTPUT="$work/result.rv64" > "$work/compile.log" 2>&1 |
|
| 22 | + | } |
|
| 23 | + | ||
| 24 | + | # Check one successful runner and its exact number of executed tests. |
|
| 25 | + | run() { |
|
| 26 | + | if ! compile "$1"; then |
|
| 27 | + | cat "$work/compile.log" |
|
| 28 | + | exit 1 |
|
| 29 | + | fi |
|
| 30 | + | if ! timeout 10 "${RAD_EMULATOR:-emulator}" -run "$work/result.rv64" > "$work/run.log" 2>&1; then |
|
| 31 | + | cat "$work/run.log" |
|
| 32 | + | exit 1 |
|
| 33 | + | fi |
|
| 34 | + | grep -Fq "test result: ok. $2 passed; 0 failed" "$work/run.log" |
|
| 35 | + | test "$(grep -c '^test selected::' "$work/run.log")" -eq "$2" |
|
| 36 | + | } |
|
| 37 | + | ||
| 38 | + | printf '%s\n' 'use std::testing;' 'mod child;' \ |
|
| 39 | + | '/// Test in the package root.' '@test fn rootTest() throws (testing::TestError) { assert true; }' > "$work/selected.rad" |
|
| 40 | + | run "-mod $work/selected/child.rad" 2 |
|
| 41 | + | grep -Fq 'test selected::rootTest ... ok' "$work/run.log" |
|
| 42 | + | grep -Fq 'test selected::child::childTest ... ok' "$work/run.log" |
|
| 43 | + | ||
| 44 | + | printf '%s\n' 'use std::testing;' > "$work/selected.rad" |
|
| 45 | + | if compile ''; then |
|
| 46 | + | echo 'driver: empty test discovery unexpectedly succeeded' |
|
| 47 | + | exit 1 |
|
| 48 | + | fi |
|
| 49 | + | grep -Fq 'no test functions found' "$work/compile.log" |
|
| 50 | + | ||
| 51 | + | # MAX_TESTS is the compiler's supported discovery capacity. |
|
| 52 | + | for count in 1024 1025; do |
|
| 53 | + | awk -v count="$count" -v directory="$work/selected" 'BEGIN { |
|
| 54 | + | print "use std::testing;" |
|
| 55 | + | for (i = 0; i < count; i++) { |
|
| 56 | + | part = int(i / 256) |
|
| 57 | + | path = directory "/part" part ".rad" |
|
| 58 | + | if (i % 256 == 0) { |
|
| 59 | + | printf "/// Capacity test group.\nmod part%d;\n", part |
|
| 60 | + | print "use std::testing;" > path |
|
| 61 | + | } |
|
| 62 | + | print "/// Generated capacity test." > path |
|
| 63 | + | printf "@test fn test%d() throws (testing::TestError) { assert true; }\n", i > path |
|
| 64 | + | } |
|
| 65 | + | }' > "$work/selected.rad" |
|
| 66 | + | inputs='' |
|
| 67 | + | for path in "$work/selected"/part*.rad; do |
|
| 68 | + | inputs="$inputs -mod $path" |
|
| 69 | + | done |
|
| 70 | + | if [ "$count" -eq 1024 ]; then |
|
| 71 | + | if ! compile "$inputs" '-dump il'; then |
|
| 72 | + | cat "$work/compile.log" |
|
| 73 | + | exit 1 |
|
| 74 | + | fi |
|
| 75 | + | grep -Fq 'selected: found 1024 test(s)' "$work/compile.log" |
|
| 76 | + | test "$(grep -c 'call.*std::testing::test' "$work/compile.log")" -eq "$count" |
|
| 77 | + | else |
|
| 78 | + | if compile "$inputs"; then |
|
| 79 | + | echo 'driver: test discovery overflow unexpectedly succeeded' |
|
| 80 | + | exit 1 |
|
| 81 | + | fi |
|
| 82 | + | grep -Fq 'Runtime error (EBREAK)' "$work/compile.log" |
|
| 83 | + | fi |
|
| 84 | + | done |
|
| 85 | + | echo 'driver tests: 4 passed' |