pr3.7
This commit is contained in:
parent
3297980055
commit
7feeabc1b6
@ -92,6 +92,11 @@ pub struct VirtualMachine {
|
|||||||
pub halted: bool,
|
pub halted: bool,
|
||||||
/// Set of ROM addresses used for software breakpoints in the debugger.
|
/// Set of ROM addresses used for software breakpoints in the debugger.
|
||||||
pub breakpoints: std::collections::HashSet<usize>,
|
pub breakpoints: std::collections::HashSet<usize>,
|
||||||
|
/// GC: number of newly allocated live objects threshold to trigger a collection at safepoint.
|
||||||
|
/// The GC only runs at safepoints (e.g., FRAME_SYNC). 0 disables automatic GC.
|
||||||
|
pub gc_alloc_threshold: usize,
|
||||||
|
/// GC: snapshot of live objects count after the last collection (or VM init).
|
||||||
|
last_gc_live_count: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -120,6 +125,8 @@ impl VirtualMachine {
|
|||||||
cycles: 0,
|
cycles: 0,
|
||||||
halted: false,
|
halted: false,
|
||||||
breakpoints: std::collections::HashSet::new(),
|
breakpoints: std::collections::HashSet::new(),
|
||||||
|
gc_alloc_threshold: 1024, // conservative default; tests may override
|
||||||
|
last_gc_live_count: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,6 +147,7 @@ impl VirtualMachine {
|
|||||||
self.heap = Heap::new();
|
self.heap = Heap::new();
|
||||||
self.cycles = 0;
|
self.cycles = 0;
|
||||||
self.halted = true; // execution is impossible until a successful load
|
self.halted = true; // execution is impossible until a successful load
|
||||||
|
self.last_gc_live_count = 0;
|
||||||
|
|
||||||
// Only recognized format is loadable: PBS v0 industrial format
|
// Only recognized format is loadable: PBS v0 industrial format
|
||||||
let program = if program_bytes.starts_with(b"PBS\0") {
|
let program = if program_bytes.starts_with(b"PBS\0") {
|
||||||
@ -967,6 +975,30 @@ impl VirtualMachine {
|
|||||||
OpCode::FrameSync => {
|
OpCode::FrameSync => {
|
||||||
// Marks the logical end of a frame: consume cycles and signal to the driver
|
// Marks the logical end of a frame: consume cycles and signal to the driver
|
||||||
self.cycles += OpCode::FrameSync.cycles();
|
self.cycles += OpCode::FrameSync.cycles();
|
||||||
|
|
||||||
|
// GC Safepoint: only at FRAME_SYNC
|
||||||
|
if self.gc_alloc_threshold > 0 {
|
||||||
|
let live_now = self.heap.len();
|
||||||
|
let since_last = live_now.saturating_sub(self.last_gc_live_count);
|
||||||
|
if since_last >= self.gc_alloc_threshold {
|
||||||
|
// Collect GC roots from VM state
|
||||||
|
struct CollectRoots(Vec<prometeu_bytecode::HeapRef>);
|
||||||
|
impl crate::roots::RootVisitor for CollectRoots {
|
||||||
|
fn visit_heap_ref(&mut self, r: prometeu_bytecode::HeapRef) {
|
||||||
|
self.0.push(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut collector = CollectRoots(Vec::new());
|
||||||
|
self.visit_roots(&mut collector);
|
||||||
|
|
||||||
|
// Run mark-sweep
|
||||||
|
self.heap.mark_from_roots(collector.0);
|
||||||
|
self.heap.sweep();
|
||||||
|
// Update baseline for next cycles
|
||||||
|
self.last_gc_live_count = self.heap.len();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return Err(LogicalFrameEndingReason::FrameSync);
|
return Err(LogicalFrameEndingReason::FrameSync);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2461,4 +2493,82 @@ mod tests {
|
|||||||
_ => panic!("Expected Trap, got {:?}", report.reason),
|
_ => panic!("Expected Trap, got {:?}", report.reason),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_gc_triggers_only_at_frame_sync() {
|
||||||
|
use crate::object::ObjectKind;
|
||||||
|
|
||||||
|
// ROM: NOP; FRAME_SYNC; HALT
|
||||||
|
let mut rom = Vec::new();
|
||||||
|
rom.extend_from_slice(&(OpCode::Nop as u16).to_le_bytes());
|
||||||
|
rom.extend_from_slice(&(OpCode::FrameSync as u16).to_le_bytes());
|
||||||
|
rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes());
|
||||||
|
|
||||||
|
let mut vm = new_test_vm(rom.clone(), vec![]);
|
||||||
|
vm.program.functions = std::sync::Arc::from(vec![prometeu_bytecode::FunctionMeta {
|
||||||
|
code_offset: 0,
|
||||||
|
code_len: rom.len() as u32,
|
||||||
|
..Default::default()
|
||||||
|
}]);
|
||||||
|
|
||||||
|
// Set a very low threshold to trigger GC as soon as we hit FRAME_SYNC
|
||||||
|
vm.gc_alloc_threshold = 1;
|
||||||
|
|
||||||
|
// Allocate an unreachable object (no roots referencing it)
|
||||||
|
let _orphan = vm.heap.allocate_object(ObjectKind::Bytes, &[1, 2, 3]);
|
||||||
|
assert_eq!(vm.heap.len(), 1);
|
||||||
|
|
||||||
|
let mut native = MockNative;
|
||||||
|
let mut ctx = HostContext::new(None);
|
||||||
|
|
||||||
|
// Step 1: NOP — should not run GC
|
||||||
|
vm.step(&mut native, &mut ctx).unwrap();
|
||||||
|
assert_eq!(vm.heap.len(), 1, "GC must not run except at safepoints");
|
||||||
|
|
||||||
|
// Step 2: FRAME_SYNC — GC should run and reclaim the unreachable object
|
||||||
|
match vm.step(&mut native, &mut ctx) {
|
||||||
|
Err(LogicalFrameEndingReason::FrameSync) => {}
|
||||||
|
other => panic!("Expected FrameSync, got {:?}", other),
|
||||||
|
}
|
||||||
|
assert_eq!(vm.heap.len(), 0, "Unreachable object must be reclaimed at FRAME_SYNC");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_gc_keeps_roots_and_collects_unreachable_at_frame_sync() {
|
||||||
|
use crate::object::ObjectKind;
|
||||||
|
|
||||||
|
// ROM: FRAME_SYNC; HALT
|
||||||
|
let mut rom = Vec::new();
|
||||||
|
rom.extend_from_slice(&(OpCode::FrameSync as u16).to_le_bytes());
|
||||||
|
rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes());
|
||||||
|
|
||||||
|
let mut vm = new_test_vm(rom.clone(), vec![]);
|
||||||
|
vm.program.functions = std::sync::Arc::from(vec![prometeu_bytecode::FunctionMeta {
|
||||||
|
code_offset: 0,
|
||||||
|
code_len: rom.len() as u32,
|
||||||
|
..Default::default()
|
||||||
|
}]);
|
||||||
|
|
||||||
|
vm.gc_alloc_threshold = 1;
|
||||||
|
|
||||||
|
// Allocate two objects; make one a root by placing it on the operand stack
|
||||||
|
let rooted = vm.heap.allocate_object(ObjectKind::Bytes, &[9, 9]);
|
||||||
|
let unreachable = vm.heap.allocate_object(ObjectKind::Bytes, &[8, 8, 8]);
|
||||||
|
assert_eq!(vm.heap.len(), 2);
|
||||||
|
vm.operand_stack.push(Value::HeapRef(rooted));
|
||||||
|
|
||||||
|
let mut native = MockNative;
|
||||||
|
let mut ctx = HostContext::new(None);
|
||||||
|
|
||||||
|
// Execute FRAME_SYNC: should trigger GC
|
||||||
|
match vm.step(&mut native, &mut ctx) {
|
||||||
|
Err(LogicalFrameEndingReason::FrameSync) => {}
|
||||||
|
other => panic!("Expected FrameSync, got {:?}", other),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rooted must survive; unreachable must be collected
|
||||||
|
assert_eq!(vm.heap.len(), 1);
|
||||||
|
assert!(vm.heap.is_valid(rooted));
|
||||||
|
assert!(!vm.heap.is_valid(unreachable));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
401
files/TODOs.md
401
files/TODOs.md
@ -1,50 +1,3 @@
|
|||||||
# PR-3.7 — Integrate GC Cycle at Safepoint (`FRAME_SYNC`)
|
|
||||||
|
|
||||||
### Briefing
|
|
||||||
|
|
||||||
The GC must only run at safepoints. This PR connects the mark-sweep collector to the VM’s safepoint logic, primarily at `FRAME_SYNC`.
|
|
||||||
|
|
||||||
### Target
|
|
||||||
|
|
||||||
* Trigger GC cycles only at safepoints.
|
|
||||||
* Keep execution deterministic.
|
|
||||||
|
|
||||||
### Work items
|
|
||||||
|
|
||||||
* Identify safepoint handling code in the VM.
|
|
||||||
* Add logic:
|
|
||||||
|
|
||||||
* If allocation threshold exceeded, run GC at `FRAME_SYNC`.
|
|
||||||
* Ensure GC does not run in arbitrary instruction contexts.
|
|
||||||
|
|
||||||
### Acceptance checklist
|
|
||||||
|
|
||||||
* [ ] GC runs only at safepoints.
|
|
||||||
* [ ] No GC during arbitrary instruction execution.
|
|
||||||
* [ ] VM remains deterministic.
|
|
||||||
* [ ] `cargo test` passes.
|
|
||||||
|
|
||||||
### Tests
|
|
||||||
|
|
||||||
* Add a test where allocations trigger GC only at `FRAME_SYNC`.
|
|
||||||
|
|
||||||
### Junie instructions
|
|
||||||
|
|
||||||
**You MAY:**
|
|
||||||
|
|
||||||
* Hook GC invocation into safepoint handling.
|
|
||||||
|
|
||||||
**You MUST NOT:**
|
|
||||||
|
|
||||||
* Trigger GC at random points.
|
|
||||||
* Add background or concurrent GC.
|
|
||||||
|
|
||||||
**If unclear:**
|
|
||||||
|
|
||||||
* Ask before modifying safepoint semantics.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# PR-3.8 — GC Smoke and Stress Tests
|
# PR-3.8 — GC Smoke and Stress Tests
|
||||||
|
|
||||||
### Briefing
|
### Briefing
|
||||||
@ -89,3 +42,357 @@ We need confidence that the GC behaves correctly under simple and stressed condi
|
|||||||
**If unclear:**
|
**If unclear:**
|
||||||
|
|
||||||
* Ask before changing test scenarios.
|
* Ask before changing test scenarios.
|
||||||
|
# PR-4.1 — Define Canonical Stack Effect Table for Core ISA
|
||||||
|
|
||||||
|
### Briefing
|
||||||
|
|
||||||
|
The verifier must rely on a single canonical definition of stack effects for every opcode. This PR introduces the formal stack effect table for the new core ISA.
|
||||||
|
|
||||||
|
### Target
|
||||||
|
|
||||||
|
* Provide a single authoritative stack effect definition per opcode.
|
||||||
|
* Ensure verifier and tooling use the same source of truth.
|
||||||
|
|
||||||
|
### Work items
|
||||||
|
|
||||||
|
* Introduce a stack effect table or equivalent structure:
|
||||||
|
|
||||||
|
* Input slot count.
|
||||||
|
* Output slot count.
|
||||||
|
* Attach stack effect metadata to each opcode.
|
||||||
|
* Ensure the table is used by the verifier entry points.
|
||||||
|
* Document stack effects in code comments (English).
|
||||||
|
|
||||||
|
### Acceptance checklist
|
||||||
|
|
||||||
|
* [ ] Every opcode has a defined stack effect.
|
||||||
|
* [ ] Verifier uses the canonical table.
|
||||||
|
* [ ] No duplicated or ad-hoc stack effect logic remains.
|
||||||
|
* [ ] `cargo test` passes.
|
||||||
|
|
||||||
|
### Tests
|
||||||
|
|
||||||
|
* Add unit test ensuring all opcodes have stack effects defined.
|
||||||
|
|
||||||
|
### Junie instructions
|
||||||
|
|
||||||
|
**You MAY:**
|
||||||
|
|
||||||
|
* Introduce new metadata tables for opcodes.
|
||||||
|
* Refactor verifier code to use the canonical table.
|
||||||
|
|
||||||
|
**You MUST NOT:**
|
||||||
|
|
||||||
|
* Change opcode semantics.
|
||||||
|
* Introduce new instructions.
|
||||||
|
|
||||||
|
**If unclear:**
|
||||||
|
|
||||||
|
* Ask before assigning stack effects.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# PR-4.2 — Basic Stack Safety Verification (Underflow/Overflow)
|
||||||
|
|
||||||
|
### Briefing
|
||||||
|
|
||||||
|
The verifier must ensure the stack never underflows or exceeds defined limits during execution.
|
||||||
|
|
||||||
|
### Target
|
||||||
|
|
||||||
|
* Implement stack safety checks across the control-flow graph.
|
||||||
|
|
||||||
|
### Work items
|
||||||
|
|
||||||
|
* Simulate stack depth across instructions.
|
||||||
|
* Detect:
|
||||||
|
|
||||||
|
* Stack underflow.
|
||||||
|
* Stack overflow beyond declared limits.
|
||||||
|
* Emit appropriate verifier errors.
|
||||||
|
|
||||||
|
### Acceptance checklist
|
||||||
|
|
||||||
|
* [ ] Underflow cases are rejected.
|
||||||
|
* [ ] Overflow cases are rejected.
|
||||||
|
* [ ] Valid programs pass.
|
||||||
|
* [ ] `cargo test` passes.
|
||||||
|
|
||||||
|
### Tests
|
||||||
|
|
||||||
|
* Add tests:
|
||||||
|
|
||||||
|
* Program with underflow → verifier error.
|
||||||
|
* Program exceeding stack limit → verifier error.
|
||||||
|
* Valid program → passes.
|
||||||
|
|
||||||
|
### Junie instructions
|
||||||
|
|
||||||
|
**You MAY:**
|
||||||
|
|
||||||
|
* Add stack depth simulation.
|
||||||
|
* Introduce verifier error types.
|
||||||
|
|
||||||
|
**You MUST NOT:**
|
||||||
|
|
||||||
|
* Change runtime stack implementation.
|
||||||
|
* Introduce dynamic stack resizing.
|
||||||
|
|
||||||
|
**If unclear:**
|
||||||
|
|
||||||
|
* Ask before choosing stack limit rules.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# PR-4.3 — Control Flow and Jump Target Verification
|
||||||
|
|
||||||
|
### Briefing
|
||||||
|
|
||||||
|
The verifier must ensure all control flow transfers are valid and do not jump into the middle of instructions or outside function boundaries.
|
||||||
|
|
||||||
|
### Target
|
||||||
|
|
||||||
|
* Validate all jump targets.
|
||||||
|
* Reject invalid or unsafe control flow.
|
||||||
|
|
||||||
|
### Work items
|
||||||
|
|
||||||
|
* Use canonical layout utilities to identify instruction boundaries.
|
||||||
|
* Verify:
|
||||||
|
|
||||||
|
* Jump targets land on valid instruction boundaries.
|
||||||
|
* Targets are within the function range.
|
||||||
|
* Reject invalid targets with a verifier error.
|
||||||
|
|
||||||
|
### Acceptance checklist
|
||||||
|
|
||||||
|
* [ ] Invalid jump targets are rejected.
|
||||||
|
* [ ] Valid programs pass verification.
|
||||||
|
* [ ] No reliance on runtime traps for these cases.
|
||||||
|
* [ ] `cargo test` passes.
|
||||||
|
|
||||||
|
### Tests
|
||||||
|
|
||||||
|
* Add tests:
|
||||||
|
|
||||||
|
* Jump to middle of instruction → verifier error.
|
||||||
|
* Jump outside function → verifier error.
|
||||||
|
* Valid jump → passes.
|
||||||
|
|
||||||
|
### Junie instructions
|
||||||
|
|
||||||
|
**You MAY:**
|
||||||
|
|
||||||
|
* Reuse layout utilities for boundary checks.
|
||||||
|
* Add verifier error cases.
|
||||||
|
|
||||||
|
**You MUST NOT:**
|
||||||
|
|
||||||
|
* Modify instruction encoding.
|
||||||
|
* Introduce new trap codes.
|
||||||
|
|
||||||
|
**If unclear:**
|
||||||
|
|
||||||
|
* Ask before defining jump rules.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# PR-4.4 — Function Boundary and Terminator Verification
|
||||||
|
|
||||||
|
### Briefing
|
||||||
|
|
||||||
|
Functions must follow canonical entry and exit rules. The verifier must enforce valid terminators and boundaries.
|
||||||
|
|
||||||
|
### Target
|
||||||
|
|
||||||
|
* Ensure functions start and end correctly.
|
||||||
|
* Ensure terminator instructions are valid.
|
||||||
|
|
||||||
|
### Work items
|
||||||
|
|
||||||
|
* Verify function entry points are valid instruction boundaries.
|
||||||
|
* Verify function exit instructions:
|
||||||
|
|
||||||
|
* Proper `RET` usage.
|
||||||
|
* Proper `FRAME_SYNC` placement (as per spec).
|
||||||
|
* Reject functions without valid termination.
|
||||||
|
|
||||||
|
### Acceptance checklist
|
||||||
|
|
||||||
|
* [ ] All functions have valid entry and exit.
|
||||||
|
* [ ] Invalid termination is rejected.
|
||||||
|
* [ ] `cargo test` passes.
|
||||||
|
|
||||||
|
### Tests
|
||||||
|
|
||||||
|
* Add tests:
|
||||||
|
|
||||||
|
* Function without terminator → verifier error.
|
||||||
|
* Properly terminated function → passes.
|
||||||
|
|
||||||
|
### Junie instructions
|
||||||
|
|
||||||
|
**You MAY:**
|
||||||
|
|
||||||
|
* Add boundary validation logic.
|
||||||
|
|
||||||
|
**You MUST NOT:**
|
||||||
|
|
||||||
|
* Redefine function semantics.
|
||||||
|
* Change terminator opcode behavior.
|
||||||
|
|
||||||
|
**If unclear:**
|
||||||
|
|
||||||
|
* Ask before enforcing termination rules.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# PR-4.5 — Multi-Return (`ret_slots`) Verification
|
||||||
|
|
||||||
|
### Briefing
|
||||||
|
|
||||||
|
The new ABI supports multi-return via `ret_slots`. The verifier must ensure the stack shape matches the declared return slot count.
|
||||||
|
|
||||||
|
### Target
|
||||||
|
|
||||||
|
* Validate return slot counts at call and return sites.
|
||||||
|
|
||||||
|
### Work items
|
||||||
|
|
||||||
|
* For each function:
|
||||||
|
|
||||||
|
* Read declared `ret_slots`.
|
||||||
|
* At `RET`:
|
||||||
|
|
||||||
|
* Ensure stack contains exactly the required number of slots.
|
||||||
|
* At call sites:
|
||||||
|
|
||||||
|
* Ensure caller expects correct number of return slots.
|
||||||
|
|
||||||
|
### Acceptance checklist
|
||||||
|
|
||||||
|
* [ ] Mismatched return slot counts are rejected.
|
||||||
|
* [ ] Correct programs pass.
|
||||||
|
* [ ] `cargo test` passes.
|
||||||
|
|
||||||
|
### Tests
|
||||||
|
|
||||||
|
* Add tests:
|
||||||
|
|
||||||
|
* Too few return slots → verifier error.
|
||||||
|
* Too many return slots → verifier error.
|
||||||
|
* Correct return slots → passes.
|
||||||
|
|
||||||
|
### Junie instructions
|
||||||
|
|
||||||
|
**You MAY:**
|
||||||
|
|
||||||
|
* Use function metadata to validate returns.
|
||||||
|
|
||||||
|
**You MUST NOT:**
|
||||||
|
|
||||||
|
* Change calling convention semantics.
|
||||||
|
* Modify function metadata layout.
|
||||||
|
|
||||||
|
**If unclear:**
|
||||||
|
|
||||||
|
* Ask before implementing slot rules.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# PR-4.6 — Verifier Error Model Consolidation
|
||||||
|
|
||||||
|
### Briefing
|
||||||
|
|
||||||
|
Verifier errors must be deterministic, structured, and clearly separated from runtime traps.
|
||||||
|
|
||||||
|
### Target
|
||||||
|
|
||||||
|
* Introduce a coherent verifier error model.
|
||||||
|
|
||||||
|
### Work items
|
||||||
|
|
||||||
|
* Define a `VerifierError` enum covering:
|
||||||
|
|
||||||
|
* Stack underflow.
|
||||||
|
* Stack overflow.
|
||||||
|
* Invalid jump target.
|
||||||
|
* Invalid function boundary.
|
||||||
|
* Return slot mismatch.
|
||||||
|
* Ensure verifier returns structured errors.
|
||||||
|
* Update tests to expect structured errors.
|
||||||
|
|
||||||
|
### Acceptance checklist
|
||||||
|
|
||||||
|
* [ ] Verifier errors are structured and deterministic.
|
||||||
|
* [ ] No reliance on runtime traps for verifier failures.
|
||||||
|
* [ ] `cargo test` passes.
|
||||||
|
|
||||||
|
### Tests
|
||||||
|
|
||||||
|
* Update existing tests to assert specific verifier errors.
|
||||||
|
|
||||||
|
### Junie instructions
|
||||||
|
|
||||||
|
**You MAY:**
|
||||||
|
|
||||||
|
* Introduce a new verifier error enum.
|
||||||
|
* Refactor error returns.
|
||||||
|
|
||||||
|
**You MUST NOT:**
|
||||||
|
|
||||||
|
* Map verifier errors to runtime traps.
|
||||||
|
* Change runtime trap behavior.
|
||||||
|
|
||||||
|
**If unclear:**
|
||||||
|
|
||||||
|
* Ask before merging or renaming error categories.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# PR-4.7 — Verifier Golden Test Suite
|
||||||
|
|
||||||
|
### Briefing
|
||||||
|
|
||||||
|
We need a stable suite of valid and invalid bytecode samples to ensure verifier correctness over time.
|
||||||
|
|
||||||
|
### Target
|
||||||
|
|
||||||
|
* Introduce golden tests for the verifier.
|
||||||
|
|
||||||
|
### Work items
|
||||||
|
|
||||||
|
* Add a set of small bytecode samples:
|
||||||
|
|
||||||
|
* Valid programs.
|
||||||
|
* Invalid programs for each verifier error.
|
||||||
|
* Implement golden tests asserting:
|
||||||
|
|
||||||
|
* Successful verification.
|
||||||
|
* Specific error kinds for invalid programs.
|
||||||
|
|
||||||
|
### Acceptance checklist
|
||||||
|
|
||||||
|
* [ ] Golden tests exist for all verifier error categories.
|
||||||
|
* [ ] Tests are deterministic.
|
||||||
|
* [ ] `cargo test` passes.
|
||||||
|
|
||||||
|
### Tests
|
||||||
|
|
||||||
|
* New golden tests only.
|
||||||
|
|
||||||
|
### Junie instructions
|
||||||
|
|
||||||
|
**You MAY:**
|
||||||
|
|
||||||
|
* Add deterministic golden tests.
|
||||||
|
|
||||||
|
**You MUST NOT:**
|
||||||
|
|
||||||
|
* Modify verifier logic to fit tests without understanding the cause.
|
||||||
|
* Introduce random or timing-based tests.
|
||||||
|
|
||||||
|
**If unclear:**
|
||||||
|
|
||||||
|
* Ask before changing test expectations.
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user