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",
|
"winit",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "prometeu-layer-tests"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"prometeu-bytecode",
|
||||||
|
"prometeu-hal",
|
||||||
|
"prometeu-test-support",
|
||||||
|
"prometeu-vm",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "prometeu-system"
|
name = "prometeu-system"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|||||||
@ -12,6 +12,7 @@ members = [
|
|||||||
|
|
||||||
"crates/tools/prometeu-cli",
|
"crates/tools/prometeu-cli",
|
||||||
"crates/dev/prometeu-test-support",
|
"crates/dev/prometeu-test-support",
|
||||||
|
"crates/dev/prometeu-layer-tests",
|
||||||
]
|
]
|
||||||
resolver = "2"
|
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
|
# PR-8.4 — No Legacy Artifacts Enforcement
|
||||||
|
|
||||||
## Briefing
|
## Briefing
|
||||||
|
|||||||
@ -1,15 +1,37 @@
|
|||||||
Stress test outline (to add in PR‑7.9 or here if desired)
|
Layered Test Suite Architecture (PR‑8.3)
|
||||||
•
|
|
||||||
Build a bytecode program with a tight loop containing arithmetic + occasional YIELD and ensure:
|
Overview
|
||||||
◦
|
|
||||||
Under tiny budgets (1–n cycles), pc progression is identical across runs.
|
- Tests are organized by runtime layer to isolate failures and clarify ownership of behavior.
|
||||||
What I changed and why
|
- Location: `crates/dev/prometeu-layer-tests/tests/`
|
||||||
Open in editor
|
|
||||||
◦
|
Layers and representative tests
|
||||||
Switches (simulated by alternating between two VMs) occur only
|
|
||||||
▪
|
1. Bytecode encode/decode
|
||||||
immediately after step() completes, or
|
- `bytecode_encode_decode.rs` — round‑trip decoding for edge immediates and structure.
|
||||||
▪
|
2. Verifier
|
||||||
when run_budget returns FrameSync.
|
- `verifier_closure_reject.rs` — rejects `CALL_CLOSURE` when TOS is not a closure.
|
||||||
◦
|
- Rule: verifier tests never run the VM.
|
||||||
No observable state change occurs when force‑switching between two VMs mid‑tick except at these safepoints.
|
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