pr8.3
This commit is contained in:
parent
bd507aeaa5
commit
8d1e0a57d6
10
Cargo.lock
generated
10
Cargo.lock
generated
@ -1653,6 +1653,16 @@ dependencies = [
|
||||
"winit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prometeu-layer-tests"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"prometeu-bytecode",
|
||||
"prometeu-hal",
|
||||
"prometeu-test-support",
|
||||
"prometeu-vm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prometeu-system"
|
||||
version = "0.1.0"
|
||||
|
||||
@ -12,6 +12,7 @@ members = [
|
||||
|
||||
"crates/tools/prometeu-cli",
|
||||
"crates/dev/prometeu-test-support",
|
||||
"crates/dev/prometeu-layer-tests",
|
||||
]
|
||||
resolver = "2"
|
||||
|
||||
|
||||
11
crates/dev/prometeu-layer-tests/Cargo.toml
Normal file
11
crates/dev/prometeu-layer-tests/Cargo.toml
Normal file
@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "prometeu-layer-tests"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[dev-dependencies]
|
||||
prometeu-bytecode = { path = "../../console/prometeu-bytecode" }
|
||||
prometeu-vm = { path = "../../console/prometeu-vm" }
|
||||
prometeu-hal = { path = "../../console/prometeu-hal" }
|
||||
prometeu-test-support = { path = "../prometeu-test-support" }
|
||||
2
crates/dev/prometeu-layer-tests/src/lib.rs
Normal file
2
crates/dev/prometeu-layer-tests/src/lib.rs
Normal file
@ -0,0 +1,2 @@
|
||||
// This crate exists solely to host layered integration tests under `tests/`.
|
||||
// It intentionally provides no public API.
|
||||
@ -0,0 +1,44 @@
|
||||
use prometeu_bytecode::decode_next;
|
||||
use prometeu_bytecode::isa::core::{CoreOpCode, CoreOpCodeSpecExt};
|
||||
|
||||
fn emit(op: CoreOpCode, imm: Option<&[u8]>, out: &mut Vec<u8>) {
|
||||
let code = op as u16;
|
||||
out.extend_from_slice(&code.to_le_bytes());
|
||||
let need = op.spec().imm_bytes as usize;
|
||||
match (need, imm) {
|
||||
(0, None) => {}
|
||||
(n, Some(bytes)) if bytes.len() == n => out.extend_from_slice(bytes),
|
||||
(n, Some(bytes)) => panic!(
|
||||
"immediate size mismatch for {:?}: expected {}, got {}",
|
||||
op, n, bytes.len()
|
||||
),
|
||||
(n, None) => panic!("missing immediate for {:?}: need {} bytes", op, n),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encode_decode_edge_immediates_roundtrip() {
|
||||
let mut rom = Vec::new();
|
||||
emit(CoreOpCode::PushI32, Some(&i32::MIN.to_le_bytes()), &mut rom);
|
||||
emit(CoreOpCode::PushI32, Some(&i32::MAX.to_le_bytes()), &mut rom);
|
||||
emit(CoreOpCode::FrameSync, None, &mut rom);
|
||||
emit(CoreOpCode::Halt, None, &mut rom);
|
||||
|
||||
// Decode sequentially and validate structure + immediates
|
||||
let mut pc = 0usize;
|
||||
let mut seen = Vec::new();
|
||||
while pc < rom.len() {
|
||||
let instr = decode_next(pc, &rom).expect("decode ok");
|
||||
seen.push(instr);
|
||||
pc = instr.next_pc;
|
||||
}
|
||||
|
||||
assert_eq!(seen.len(), 4);
|
||||
assert_eq!(seen[0].opcode, CoreOpCode::PushI32);
|
||||
assert_eq!(seen[0].imm_i32().unwrap(), i32::MIN);
|
||||
assert_eq!(seen[1].opcode, CoreOpCode::PushI32);
|
||||
assert_eq!(seen[1].imm_i32().unwrap(), i32::MAX);
|
||||
assert_eq!(seen[2].opcode, CoreOpCode::FrameSync);
|
||||
assert_eq!(seen[3].opcode, CoreOpCode::Halt);
|
||||
assert_eq!(seen.last().unwrap().next_pc, rom.len());
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
use prometeu_bytecode::Value;
|
||||
use prometeu_vm::Heap;
|
||||
|
||||
#[test]
|
||||
fn gc_collects_unreachable_closure_but_keeps_marked() {
|
||||
let mut heap = Heap::new();
|
||||
|
||||
// Allocate two closures with small environments
|
||||
let rooted = heap.alloc_closure(1, &[Value::Int32(10)]);
|
||||
let unreachable = heap.alloc_closure(2, &[Value::Int32(20)]);
|
||||
|
||||
// Mark only the rooted one, then sweep.
|
||||
heap.mark_from_roots([rooted]);
|
||||
heap.sweep();
|
||||
|
||||
assert!(heap.is_valid(rooted), "rooted object must remain alive after sweep");
|
||||
assert!(
|
||||
!heap.is_valid(unreachable),
|
||||
"unreachable object must be reclaimed by GC"
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
use prometeu_bytecode::HeapRef;
|
||||
use prometeu_vm::Scheduler;
|
||||
|
||||
#[test]
|
||||
fn scheduler_wake_and_ready_order_is_deterministic() {
|
||||
let mut s = Scheduler::new();
|
||||
let a = HeapRef(1);
|
||||
let b = HeapRef(2);
|
||||
let c = HeapRef(3);
|
||||
|
||||
s.sleep_until(a, 5);
|
||||
s.sleep_until(b, 5);
|
||||
s.sleep_until(c, 6);
|
||||
|
||||
// Before tick 5: nothing wakes
|
||||
s.wake_ready(4);
|
||||
assert!(s.is_ready_empty());
|
||||
|
||||
// At tick 5: a then b become ready (in insertion order)
|
||||
s.wake_ready(5);
|
||||
assert_eq!(s.dequeue_next(), Some(a));
|
||||
assert_eq!(s.dequeue_next(), Some(b));
|
||||
assert!(s.is_ready_empty());
|
||||
|
||||
// At tick 6: c becomes ready
|
||||
s.wake_ready(6);
|
||||
assert_eq!(s.dequeue_next(), Some(c));
|
||||
assert!(s.is_ready_empty());
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
use prometeu_bytecode::isa::core::CoreOpCode;
|
||||
use prometeu_bytecode::FunctionMeta;
|
||||
use prometeu_vm::verifier::{Verifier, VerifierError};
|
||||
|
||||
#[test]
|
||||
fn verifier_rejects_call_closure_when_top_is_not_closure() {
|
||||
// Program: PUSH_I32 7; CALL_CLOSURE argc=0; HALT
|
||||
let mut rom = Vec::new();
|
||||
rom.extend_from_slice(&(CoreOpCode::PushI32 as u16).to_le_bytes());
|
||||
rom.extend_from_slice(&7i32.to_le_bytes());
|
||||
rom.extend_from_slice(&(CoreOpCode::CallClosure as u16).to_le_bytes());
|
||||
rom.extend_from_slice(&0u32.to_le_bytes()); // argc = 0
|
||||
rom.extend_from_slice(&(CoreOpCode::Halt as u16).to_le_bytes());
|
||||
|
||||
let functions = vec![FunctionMeta { code_offset: 0, code_len: rom.len() as u32, ..Default::default() }];
|
||||
|
||||
match Verifier::verify(&rom, &functions) {
|
||||
Err(VerifierError::NotAClosureOnCallClosure { .. }) => {}
|
||||
other => panic!("expected NotAClosureOnCallClosure, got {:?}", other),
|
||||
}
|
||||
}
|
||||
56
crates/dev/prometeu-layer-tests/tests/vm_exec_valid.rs
Normal file
56
crates/dev/prometeu-layer-tests/tests/vm_exec_valid.rs
Normal file
@ -0,0 +1,56 @@
|
||||
use prometeu_bytecode::isa::core::{CoreOpCode, CoreOpCodeSpecExt};
|
||||
use prometeu_hal::{HostContext, HostReturn, NativeInterface, SyscallId};
|
||||
use prometeu_vm::{BudgetReport, LogicalFrameEndingReason, VirtualMachine};
|
||||
use prometeu_bytecode::Value;
|
||||
|
||||
fn emit(op: CoreOpCode, imm: Option<&[u8]>, out: &mut Vec<u8>) {
|
||||
out.extend_from_slice(&(op as u16).to_le_bytes());
|
||||
let need = op.spec().imm_bytes as usize;
|
||||
match (need, imm) {
|
||||
(0, None) => {}
|
||||
(n, Some(bytes)) if bytes.len() == n => out.extend_from_slice(bytes),
|
||||
(n, Some(bytes)) => panic!("imm size mismatch for {:?}: need {}, got {}", op, n, bytes.len()),
|
||||
(n, None) => panic!("missing imm for {:?}: need {} bytes", op, n),
|
||||
}
|
||||
}
|
||||
|
||||
struct NoopNative;
|
||||
impl NativeInterface for NoopNative {
|
||||
fn syscall(
|
||||
&mut self,
|
||||
_id: SyscallId,
|
||||
_args: &[Value],
|
||||
_ret: &mut HostReturn,
|
||||
_ctx: &mut HostContext,
|
||||
) -> Result<(), prometeu_hal::vm_fault::VmFault> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vm_executes_valid_program_in_slices() {
|
||||
// Program: PUSH_I32 2; PUSH_I32 3; ADD; FRAME_SYNC; HALT
|
||||
let mut rom = Vec::new();
|
||||
emit(CoreOpCode::PushI32, Some(&2i32.to_le_bytes()), &mut rom);
|
||||
emit(CoreOpCode::PushI32, Some(&3i32.to_le_bytes()), &mut rom);
|
||||
emit(CoreOpCode::Add, None, &mut rom);
|
||||
emit(CoreOpCode::FrameSync, None, &mut rom);
|
||||
emit(CoreOpCode::Halt, None, &mut rom);
|
||||
|
||||
// Construct VM directly; assume input is already verified.
|
||||
let mut vm = VirtualMachine::new(rom, vec![]);
|
||||
vm.prepare_call("0");
|
||||
|
||||
let mut native = NoopNative;
|
||||
let mut ctx = HostContext::new(None);
|
||||
|
||||
// First slice should stop at FRAME_SYNC deterministically.
|
||||
let report: BudgetReport = vm.run_budget(100, &mut native, &mut ctx).expect("run ok");
|
||||
assert_eq!(report.reason, LogicalFrameEndingReason::FrameSync);
|
||||
assert!(!vm.halted);
|
||||
|
||||
// Second slice proceeds to HALT.
|
||||
let report2: BudgetReport = vm.run_budget(100, &mut native, &mut ctx).expect("run ok");
|
||||
assert_eq!(report2.reason, LogicalFrameEndingReason::Halted);
|
||||
assert!(vm.halted);
|
||||
}
|
||||
@ -1,61 +1,3 @@
|
||||
# PR-8.3 — Layered Test Suite Architecture
|
||||
|
||||
## Briefing
|
||||
|
||||
Tests must be structured by layer to isolate failures.
|
||||
|
||||
Layers:
|
||||
|
||||
1. Bytecode encode/decode
|
||||
2. Verifier
|
||||
3. VM execution
|
||||
4. GC behavior
|
||||
5. Scheduler behavior
|
||||
|
||||
## Target
|
||||
|
||||
Organize tests into modules per layer.
|
||||
|
||||
Enforce:
|
||||
|
||||
* No cross-layer assumptions.
|
||||
* Verifier tests do not execute VM.
|
||||
* VM tests assume verified input.
|
||||
|
||||
Add minimal coverage per layer.
|
||||
|
||||
## Acceptance Checklist
|
||||
|
||||
* [ ] Tests grouped logically.
|
||||
* [ ] No duplicated logic.
|
||||
* [ ] Clear separation of concerns.
|
||||
|
||||
## Tests
|
||||
|
||||
Add representative tests per layer:
|
||||
|
||||
* Encode/decode edge cases.
|
||||
* Verifier rejects invalid closures.
|
||||
* VM executes valid programs.
|
||||
* GC collects unreachable closures.
|
||||
* Scheduler deterministic ordering.
|
||||
|
||||
## Junie Instructions
|
||||
|
||||
You MAY:
|
||||
|
||||
* Refactor test layout.
|
||||
* Add missing coverage.
|
||||
|
||||
You MUST NOT:
|
||||
|
||||
* Merge unrelated layers.
|
||||
* Hide verifier failures inside runtime traps.
|
||||
|
||||
If a test spans layers, STOP and split appropriately.
|
||||
|
||||
---
|
||||
|
||||
# PR-8.4 — No Legacy Artifacts Enforcement
|
||||
|
||||
## Briefing
|
||||
|
||||
@ -1,15 +1,37 @@
|
||||
Stress test outline (to add in PR‑7.9 or here if desired)
|
||||
•
|
||||
Build a bytecode program with a tight loop containing arithmetic + occasional YIELD and ensure:
|
||||
◦
|
||||
Under tiny budgets (1–n cycles), pc progression is identical across runs.
|
||||
What I changed and why
|
||||
Open in editor
|
||||
◦
|
||||
Switches (simulated by alternating between two VMs) occur only
|
||||
▪
|
||||
immediately after step() completes, or
|
||||
▪
|
||||
when run_budget returns FrameSync.
|
||||
◦
|
||||
No observable state change occurs when force‑switching between two VMs mid‑tick except at these safepoints.
|
||||
Layered Test Suite Architecture (PR‑8.3)
|
||||
|
||||
Overview
|
||||
|
||||
- Tests are organized by runtime layer to isolate failures and clarify ownership of behavior.
|
||||
- Location: `crates/dev/prometeu-layer-tests/tests/`
|
||||
|
||||
Layers and representative tests
|
||||
|
||||
1. Bytecode encode/decode
|
||||
- `bytecode_encode_decode.rs` — round‑trip decoding for edge immediates and structure.
|
||||
2. Verifier
|
||||
- `verifier_closure_reject.rs` — rejects `CALL_CLOSURE` when TOS is not a closure.
|
||||
- Rule: verifier tests never run the VM.
|
||||
3. VM execution
|
||||
- `vm_exec_valid.rs` — executes a tiny valid program; assumes verified input.
|
||||
- Rule: VM tests do not depend on the verifier; they prepare ROM directly.
|
||||
4. GC behavior
|
||||
- `gc_collect_unreachable.rs` — unrooted closure is reclaimed; rooted one survives.
|
||||
5. Scheduler behavior
|
||||
- `scheduler_determinism.rs` — deterministic FIFO ordering for same‑tick wakeups.
|
||||
|
||||
Running the layered suite
|
||||
|
||||
- Run just the layered tests: `cargo test -p prometeu-layer-tests`
|
||||
- Run the entire workspace: `cargo test`
|
||||
|
||||
Separation of concerns
|
||||
|
||||
- No cross‑layer assumptions in these tests.
|
||||
- Verifier tests: no `VirtualMachine` execution.
|
||||
- VM tests: assume pre‑verified bytecode; interact via public VM APIs only.
|
||||
- GC and Scheduler tests: exercise their public APIs directly, without booting the VM.
|
||||
|
||||
DRY helpers
|
||||
|
||||
- Shared utilities should live in `crates/dev/prometeu-test-support` when needed. The current minimal suite avoids duplication by using tiny local helpers.
|
||||
Loading…
x
Reference in New Issue
Block a user