kernel: Add the package and test targets

0ea2981a6b6dec11f0f0def6bed8f12a360747f32e258bb1169ff1377e5289d5
Build kernel tests as a separate package with entry-package test
discovery. Add checked half-open ranges for memory and execution
windows.

Assisted-by: Codex:gpt-6
Alexis Sellier committed ago 1 parent 56a00753
Makefile +15 -2
21 21
22 22
# Verify the emulator binary exists.
23 23
EMU_PATH := $(shell command -v $(EMU) 2>/dev/null)
24 24
25 25
default: emulator $(RAD_BIN)
26 -
test: emulator seed-test std-test bin-test
26 +
test: emulator seed-test std-test bin-test kernel-test
27 27
28 28
seed-test:
29 29
	@seed/test
30 30
31 31
# Emulator command check
64 64
	@rm -f lib/std.test.rv64 \
65 65
		lib/std.test.rv64.debug \
66 66
		lib/std.test.rv64.s \
67 67
		lib/std.test.rv64.o
68 68
69 +
# Kernel modules and tests use a separate package and test entry point.
70 +
KERNEL_SRC := $(shell find kernel -name '*.rad' ! -name 'tests.rad' ! -path '*/tests/*' 2>/dev/null)
71 +
KERNEL_TEST_SRC := kernel/kernel/tests.rad $(shell find kernel/kernel/tests -name '*.rad' 2>/dev/null)
72 +
KERNEL := -pkg kernel $(patsubst %,-mod %,$(sort $(KERNEL_SRC)))
73 +
KERNEL_TEST := $(BIN_DIR)/kernel.test.rv64
74 +
75 +
kernel-test: emulator $(KERNEL_TEST)
76 +
	@$(EMU) $(EMU_FLAGS) -run $(KERNEL_TEST)
77 +
78 +
$(KERNEL_TEST): $(KERNEL_SRC) $(KERNEL_TEST_SRC) $(STD_LIB) $(RAD_BIN)
79 +
	@echo "radiance kernel tests => $@"
80 +
	@$(RADIANCE) -test $(STD_TEST) $(KERNEL) $(patsubst %,-mod %,$(KERNEL_TEST_SRC)) -entry kernel -o $@
81 +
69 82
# Binary Tests
70 83
71 84
BIN_TEST_DIR := test/tests
72 85
# Only tests with `//! returns:` are compiled to binaries and executed.
73 86
BIN_TEST_EXE_SRC := $(shell grep -rl '^//! returns:' $(BIN_TEST_DIR))
121 134
clean: clean-std-test clean-bin-test clean-rad
122 135
123 136
t: test
124 137
c: clean
125 138
126 -
.PHONY: test clean default seed-test std-test bin-test seed \
139 +
.PHONY: test clean default seed-test std-test bin-test kernel-test seed \
127 140
	clean-std-test clean-bin-test clean-rad emulator
128 141
.SUFFIXES:
129 142
.DELETE_ON_ERROR:
130 143
.SILENT:
compiler/radiance.rad +5 -3
665 665
    return ast::synthNode(arena, ast::NodeValue::Call(ast::Call { callee, args }));
666 666
}
667 667
668 668
/// Inject a test runner into the entry package's root module.
669 669
///
670 -
/// Scans all parsed modules for `@test fn` declarations, then appends
670 +
/// Scans the entry package for `@test fn` declarations, then appends
671 671
/// a synthetic entry point to the root module's AST block:
672 672
///
673 673
/// ```
674 674
/// @default fn #testMain() -> i32 {
675 675
///     return testing::runAllTests(&[
685 685
    arena: &mut ast::NodeArena
686 686
) throws (Error) {
687 687
    let entryPkg = try getEntryPackage(ctx);
688 688
    let root = try getRootModule(&entryPkg, &ctx.graph);
689 689
690 -
    // Collect all test functions across all modules.
690 +
    // Collect test functions from the entry package's modules.
691 691
    let mut tests: [TestDesc; MAX_TESTS] = undefined;
692 692
    let mut testCount: u32 = 0;
693 693
694 694
    for modIdx in 0..ctx.graph.entriesLen {
695 695
        if let entry = module::get(&ctx.graph, modIdx as u16) {
696 -
            collectModuleTests(entry, &mut tests[..], &mut testCount);
696 +
            if entry.packageId == entryPkg.id {
697 +
                collectModuleTests(entry, &mut tests[..], &mut testCount);
698 +
            }
697 699
        }
698 700
    }
699 701
    if testCount == 0 {
700 702
        throw error(&["fatal:", "no test functions found"]);
701 703
    }
kernel/kernel.rad added +6 -0
1 +
//! Kernel resource management and machine execution.
2 +
3 +
use std::testing;
4 +
5 +
export mod range;
6 +
@test export mod tests;
kernel/kernel/range.rad added +22 -0
1 +
//! Checked half-open ranges for physical memory and CPU windows.
2 +
3 +
/// A nonempty half-open interval. Construct intervals with `new`.
4 +
export record Range: Copy {
5 +
    /// First included address or tick.
6 +
    start: u64,
7 +
    /// First excluded address or tick.
8 +
    end: u64,
9 +
}
10 +
11 +
/// Construct a nonempty interval. Reject addition overflow.
12 +
export fn new(start: u64, length: u64) -> ?Range {
13 +
    if length == 0 or length > 0xffffffffffffffff - start {
14 +
        return nil;
15 +
    }
16 +
    return Range { start, end: start + length };
17 +
}
18 +
19 +
/// Check that the complete nonempty interval is inside the outer interval.
20 +
export fn contains(outer: Range, inner: Range) -> bool {
21 +
    return outer.start <= inner.start and inner.start < inner.end and inner.end <= outer.end;
22 +
}
kernel/kernel/tests.rad added +3 -0
1 +
//! Kernel unit tests. Machine execution tests have separate entry points.
2 +
3 +
export mod range;
kernel/kernel/tests/range.rad added +27 -0
1 +
//! Bounds and overflow checks for kernel intervals.
2 +
3 +
use kernel::range;
4 +
use std::testing;
5 +
6 +
/// Check empty intervals and overflow at the address-space boundary.
7 +
@test fn construction() throws (testing::TestError) {
8 +
    try testing::expect(range::new(0, 0) == nil);
9 +
    try testing::expect(range::new(0xffffffffffffffff, 1) == nil);
10 +
    try testing::expect(range::new(1, 0xffffffffffffffff) == nil);
11 +
    let span = range::new(0, 0xffffffffffffffff) else {
12 +
        throw testing::TestError::Failed;
13 +
    };
14 +
    try testing::expect(span.start == 0);
15 +
    try testing::expect(span.end == 0xffffffffffffffff);
16 +
}
17 +
18 +
/// Check exact bounds, partial overlap, and the excluded upper bound.
19 +
@test fn containment() throws (testing::TestError) {
20 +
    let outer = range::Range { start: 4096, end: 8192 };
21 +
    try testing::expect(range::contains(outer, outer));
22 +
    try testing::expect(range::contains(outer, range::Range { start: 4097, end: 8191 }));
23 +
    try testing::expect(not range::contains(outer, range::Range { start: 4095, end: 4097 }));
24 +
    try testing::expect(not range::contains(outer, range::Range { start: 8191, end: 8193 }));
25 +
    try testing::expect(not range::contains(outer, range::Range { start: 8192, end: 8193 }));
26 +
    try testing::expect(not range::contains(outer, range::Range { start: 4096, end: 4096 }));
27 +
}