mirror of
https://github.com/PatchRequest/BusyWork
synced 2026-06-09 16:04:04 +00:00
Add work data flow integration with .feed() API and 196 tests
Tasks can now accept arbitrary user variables via .feed() on the builder. Data is cloned on entry and woven into task control flow (XOR into buffers, seed derivation for loop bounds, hash inputs, cipher keys), making busywork blocks indistinguishable from real data processing under data-flow analysis. 31 tasks across COMPUTE, MEMORY, and CRYPTO actively consume fed data. All originals remain untouched — tasks receive read-only references to clones. Test suite covers: per-type serialization (52), direct invocation of every task with 10 work-data shapes x parameter extremes (18), mirror tests proving data actually changes computation (28), data isolation (15), stress/regression (23), and integration across all categories, intensities, and edge cases.
This commit is contained in:
@@ -56,3 +56,6 @@ cat-crypto = [
|
||||
"windows/Win32_Security_Cryptography",
|
||||
"windows/Win32_Foundation",
|
||||
]
|
||||
|
||||
[dev-dependencies]
|
||||
rand = "0.8"
|
||||
|
||||
@@ -47,7 +47,50 @@ BusyWork::new(Intensity::Ultra)
|
||||
.run();
|
||||
```
|
||||
|
||||
Drop it in any loop — every iteration looks different:
|
||||
### Data Flow Integration
|
||||
|
||||
Busywork blocks can blend into surrounding code by processing **your variables**. Feed any data from the current scope — tasks will weave it into their control flow (hashing it, sorting it, using it as loop bounds), making the block indistinguishable from real data processing to static or dynamic analysis.
|
||||
|
||||
All data is **cloned on feed** — originals are never modified.
|
||||
|
||||
```rust
|
||||
let session_id: u64 = 0xDEADBEEF;
|
||||
let payload = vec![0x41u8; 1024];
|
||||
let user_name = "admin";
|
||||
let packet_count: u32 = 147;
|
||||
|
||||
BusyWork::new(Intensity::Medium)
|
||||
.allow(Categories::COMPUTE | Categories::MEMORY)
|
||||
.feed(&session_id) // integers, floats, bools
|
||||
.feed(&payload) // Vec<u8>, &[u8], [u8; N]
|
||||
.feed(user_name) // &str, String
|
||||
.feed(&packet_count) // unlimited parameters
|
||||
.run();
|
||||
```
|
||||
|
||||
Without `.feed()`, tasks generate random input data — detectable as isolated noise blocks with no data dependency on the surrounding program. With `.feed()`, the busywork reads and processes the same variables as your real code. A data-flow analyzer cannot determine which computations are "real" and which are busywork.
|
||||
|
||||
**Works with custom types** — implement `FeedWork` for your own structs:
|
||||
|
||||
```rust
|
||||
use busywork::FeedWork;
|
||||
|
||||
struct GameState { x: f32, y: f32, hp: u32 }
|
||||
|
||||
impl FeedWork for GameState {
|
||||
fn write_work_bytes(&self, out: &mut Vec<u8>) {
|
||||
self.x.write_work_bytes(out);
|
||||
self.y.write_work_bytes(out);
|
||||
self.hp.write_work_bytes(out);
|
||||
}
|
||||
}
|
||||
|
||||
BusyWork::new(Intensity::Low)
|
||||
.feed(&game_state)
|
||||
.run();
|
||||
```
|
||||
|
||||
### Loop example
|
||||
|
||||
```rust
|
||||
loop {
|
||||
@@ -149,6 +192,7 @@ Every design decision optimizes for evasion:
|
||||
|-----------|----------------|
|
||||
| **Random task selection per call** | Behavioral sequence matching — no two calls produce the same API call order |
|
||||
| **±30% jitter on all parameters** | Timing heuristics — iteration counts, buffer sizes, call depths all vary |
|
||||
| **Data flow integration via `.feed()`** | Data-flow analysis — busywork operates on the same variables as surrounding code, preventing isolation as "noise blocks" |
|
||||
| **Task registry rebuilt per call** | Static analysis — no stable global function pointer table to fingerprint |
|
||||
| **Function pointers, not trait objects** | Vtable signature detection — each task is a direct call to a unique address |
|
||||
| **`black_box` on all results** | Dead-code elimination — compiler can't optimize away the work |
|
||||
@@ -170,10 +214,34 @@ Call 4: FindFirstFile(*.dll) × 40 → GetVolumeInformation → memcpy(16KB) ×
|
||||
|
||||
No repeating pattern. Each call exercises different subsystems, different buffer sizes, different iteration counts. To an EDR, it looks like a normal application doing normal things.
|
||||
|
||||
With `.feed()`, the data flowing through these operations comes from your actual program variables — hash chains process your session tokens, sorts operate on your counters, crypto operations encrypt your payloads. A taint-tracking analyzer following your variables will see them consumed by what appears to be legitimate processing.
|
||||
|
||||
## Testing
|
||||
|
||||
196 tests across 4 test suites:
|
||||
|
||||
| Suite | Tests | What it covers |
|
||||
|-------|-------|----------------|
|
||||
| `workdata::tests` | 52 | Every `FeedWork` type, `blend_into` XOR correctness, `blend_seed`/`derive_usize` determinism, clone independence, empty/zero edge cases |
|
||||
| `tasks::tests` | 18 | Every registered task function called directly with 10 work-data shapes × parameter extremes (zero, min, large). Registry completeness (76 tasks, no duplicates) |
|
||||
| `tasks::compute::tests` | 19 | Mirror tests proving work data changes computation: hash inputs differ, fibonacci starting values shift, sieve limits get bias, cipher keys incorporate fed data, distinct inputs produce distinct seeds |
|
||||
| `tasks::memory::tests` | 9 | Mirror tests proving work data flows into memory tasks: buffers modified, patterns XOR'd, ring buffers seeded, reversibility verified |
|
||||
| `tests/smoke.rs` | 9 | Basic API smoke tests, category filtering, jitter toggle |
|
||||
| `tests/bench.rs` | 4 | Intensity/category benchmarks, jitter variance measurement |
|
||||
| `tests/feed.rs` | 62 | Data isolation (15 types verified untouched after run), per-category with feed, intensity levels, edge cases (NaN, Unicode, 64KB), builder combinations, type coverage |
|
||||
| `tests/robustness.rs` | 23 | Stress (500 rapid calls, 10k small feeds, 1MB feed), custom `FeedWork` impl, regression patterns (empty→real, alternating, max values) |
|
||||
|
||||
```
|
||||
cargo test --lib # 101 unit tests
|
||||
cargo test --test feed # 62 integration tests
|
||||
cargo test --test robustness # 23 stress/regression tests
|
||||
```
|
||||
|
||||
## Stats
|
||||
|
||||
```
|
||||
76 tasks across 7 categories
|
||||
2,688 lines of Rust
|
||||
31 tasks actively consume fed work data (COMPUTE, MEMORY, CRYPTO)
|
||||
196 tests across unit, integration, stress, and mirror test suites
|
||||
0 time objects in the library binary
|
||||
```
|
||||
|
||||
+13
-1
@@ -1,12 +1,14 @@
|
||||
use crate::categories::Categories;
|
||||
use crate::dispatch;
|
||||
use crate::intensity::Intensity;
|
||||
use crate::workdata::{FeedWork, WorkData};
|
||||
|
||||
pub struct BusyWork {
|
||||
intensity: Intensity,
|
||||
allow: Categories,
|
||||
deny: Categories,
|
||||
jitter: bool,
|
||||
work_data: WorkData,
|
||||
}
|
||||
|
||||
impl BusyWork {
|
||||
@@ -16,6 +18,7 @@ impl BusyWork {
|
||||
allow: Categories::all(),
|
||||
deny: Categories::empty(),
|
||||
jitter: true,
|
||||
work_data: WorkData::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,8 +37,17 @@ impl BusyWork {
|
||||
self
|
||||
}
|
||||
|
||||
/// Feed a variable from the surrounding code into the busywork block.
|
||||
/// The value is cloned immediately — the original is never touched.
|
||||
/// Tasks will weave this data into their control flow, making the block
|
||||
/// indistinguishable from real data processing.
|
||||
pub fn feed(mut self, data: &(impl FeedWork + ?Sized)) -> Self {
|
||||
self.work_data.feed(data);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn run(&self) {
|
||||
let effective = (self.allow & Categories::available()) & !self.deny;
|
||||
dispatch::execute(self.intensity, effective, self.jitter);
|
||||
dispatch::execute(self.intensity, effective, self.jitter, &self.work_data);
|
||||
}
|
||||
}
|
||||
|
||||
+3
-2
@@ -2,9 +2,10 @@ use crate::categories::Categories;
|
||||
use crate::intensity::Intensity;
|
||||
use crate::jitter;
|
||||
use crate::tasks::{self, TaskParams};
|
||||
use crate::workdata::WorkData;
|
||||
use rand::seq::SliceRandom;
|
||||
|
||||
pub fn execute(intensity: Intensity, effective: Categories, jitter_enabled: bool) {
|
||||
pub fn execute(intensity: Intensity, effective: Categories, jitter_enabled: bool, work_data: &WorkData) {
|
||||
let mut rng = rand::thread_rng();
|
||||
let all = tasks::all_tasks();
|
||||
let eligible: Vec<_> = all
|
||||
@@ -42,6 +43,6 @@ pub fn execute(intensity: Intensity, effective: Categories, jitter_enabled: bool
|
||||
base.call_depth
|
||||
},
|
||||
};
|
||||
(task.func)(¶ms, &mut rng);
|
||||
(task.func)(¶ms, &mut rng, work_data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,10 +4,12 @@ mod dispatch;
|
||||
mod intensity;
|
||||
mod jitter;
|
||||
mod tasks;
|
||||
mod workdata;
|
||||
|
||||
pub use builder::BusyWork;
|
||||
pub use categories::Categories;
|
||||
pub use intensity::Intensity;
|
||||
pub use workdata::{FeedWork, WorkData};
|
||||
|
||||
pub fn busywork(intensity: Intensity) {
|
||||
BusyWork::new(intensity).run();
|
||||
|
||||
+266
-28
@@ -1,5 +1,6 @@
|
||||
use crate::categories::Categories;
|
||||
use crate::tasks::{TaskDescriptor, TaskParams};
|
||||
use crate::workdata::WorkData;
|
||||
use flate2::write::{DeflateDecoder, DeflateEncoder};
|
||||
use flate2::Compression;
|
||||
use md5::Md5;
|
||||
@@ -84,9 +85,10 @@ pub fn register() -> Vec<TaskDescriptor> {
|
||||
]
|
||||
}
|
||||
|
||||
fn hash_sha256_loop(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
fn hash_sha256_loop(params: &TaskParams, rng: &mut ThreadRng, work: &WorkData) {
|
||||
let mut data = vec![0u8; 64];
|
||||
rng.fill_bytes(&mut data);
|
||||
work.blend_into(&mut data);
|
||||
for _ in 0..params.iterations {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(&data);
|
||||
@@ -96,9 +98,10 @@ fn hash_sha256_loop(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
black_box(&data);
|
||||
}
|
||||
|
||||
fn hash_md5_loop(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
fn hash_md5_loop(params: &TaskParams, rng: &mut ThreadRng, work: &WorkData) {
|
||||
let mut data = vec![0u8; 64];
|
||||
rng.fill_bytes(&mut data);
|
||||
work.blend_into(&mut data);
|
||||
for _ in 0..params.iterations {
|
||||
let mut hasher = Md5::new();
|
||||
hasher.update(&data);
|
||||
@@ -108,8 +111,13 @@ fn hash_md5_loop(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
black_box(&data);
|
||||
}
|
||||
|
||||
fn prime_sieve(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
let limit = params.iterations.saturating_mul(100).min(10_000_000);
|
||||
fn prime_sieve(params: &TaskParams, _rng: &mut ThreadRng, work: &WorkData) {
|
||||
let work_bias = work.derive_usize(0) % 1000;
|
||||
let limit = params
|
||||
.iterations
|
||||
.saturating_mul(100)
|
||||
.saturating_add(work_bias)
|
||||
.min(10_000_000);
|
||||
if limit < 2 {
|
||||
return;
|
||||
}
|
||||
@@ -130,7 +138,7 @@ fn prime_sieve(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
black_box(count);
|
||||
}
|
||||
|
||||
fn matrix_multiply(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
fn matrix_multiply(params: &TaskParams, rng: &mut ThreadRng, work: &WorkData) {
|
||||
let n = ((params.iterations as f64).sqrt() as usize).max(2).min(512);
|
||||
let mut a = vec![0.0f64; n * n];
|
||||
let mut b = vec![0.0f64; n * n];
|
||||
@@ -140,6 +148,12 @@ fn matrix_multiply(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
for x in b.iter_mut() {
|
||||
*x = rng.gen::<f64>();
|
||||
}
|
||||
if !work.is_empty() {
|
||||
let wb = work.as_bytes();
|
||||
for (i, x) in a.iter_mut().enumerate() {
|
||||
*x += (wb[i % wb.len()] as f64) / 256.0;
|
||||
}
|
||||
}
|
||||
let mut c = vec![0.0f64; n * n];
|
||||
for i in 0..n {
|
||||
for k in 0..n {
|
||||
@@ -152,19 +166,23 @@ fn matrix_multiply(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
black_box(&c);
|
||||
}
|
||||
|
||||
fn sort_random_arrays(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
fn sort_random_arrays(params: &TaskParams, rng: &mut ThreadRng, work: &WorkData) {
|
||||
let size = (params.buffer_size / 8).max(1);
|
||||
for _ in 0..params.call_depth {
|
||||
let mut data: Vec<u64> = (0..size).map(|_| rng.gen()).collect();
|
||||
let seed = work.blend_seed();
|
||||
for round in 0..params.call_depth {
|
||||
let mut data: Vec<u64> = (0..size)
|
||||
.map(|_| rng.gen::<u64>() ^ seed.wrapping_add(round as u64))
|
||||
.collect();
|
||||
data.sort_unstable();
|
||||
black_box(&data);
|
||||
}
|
||||
}
|
||||
|
||||
fn compress_decompress(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
fn compress_decompress(params: &TaskParams, rng: &mut ThreadRng, work: &WorkData) {
|
||||
let size = params.buffer_size.min(65536);
|
||||
let mut data = vec![0u8; size];
|
||||
rng.fill_bytes(&mut data);
|
||||
work.blend_into(&mut data);
|
||||
for _ in 0..params.call_depth {
|
||||
let mut encoder = DeflateEncoder::new(Vec::new(), Compression::fast());
|
||||
if encoder.write_all(&data).is_err() {
|
||||
@@ -186,9 +204,10 @@ fn compress_decompress(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
}
|
||||
}
|
||||
|
||||
fn fibonacci_sequence(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
let mut a: u128 = 0;
|
||||
let mut b: u128 = 1;
|
||||
fn fibonacci_sequence(params: &TaskParams, _rng: &mut ThreadRng, work: &WorkData) {
|
||||
let seed = work.blend_seed();
|
||||
let mut a: u128 = seed as u128;
|
||||
let mut b: u128 = 1u128.wrapping_add(seed as u128);
|
||||
for _ in 0..params.iterations {
|
||||
let next = a.wrapping_add(b);
|
||||
a = b;
|
||||
@@ -197,9 +216,10 @@ fn fibonacci_sequence(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
black_box(b);
|
||||
}
|
||||
|
||||
fn xor_cipher(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
fn xor_cipher(params: &TaskParams, rng: &mut ThreadRng, work: &WorkData) {
|
||||
let mut key = [0u8; 256];
|
||||
rng.fill_bytes(&mut key);
|
||||
work.blend_into(&mut key);
|
||||
let size = params.buffer_size.min(1_048_576);
|
||||
for _ in 0..params.call_depth {
|
||||
let mut data = vec![0u8; size];
|
||||
@@ -211,10 +231,11 @@ fn xor_cipher(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
}
|
||||
}
|
||||
|
||||
fn collatz_sequence(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
fn collatz_sequence(params: &TaskParams, rng: &mut ThreadRng, work: &WorkData) {
|
||||
let iteration_limit = 10_000usize;
|
||||
let seed = work.blend_seed();
|
||||
for _ in 0..params.iterations {
|
||||
let mut n: u64 = rng.gen::<u64>().saturating_add(1);
|
||||
let mut n: u64 = rng.gen::<u64>().saturating_add(1) ^ seed;
|
||||
let mut steps = 0usize;
|
||||
while n != 1 && steps < iteration_limit {
|
||||
if n % 2 == 0 {
|
||||
@@ -228,12 +249,18 @@ fn collatz_sequence(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
}
|
||||
}
|
||||
|
||||
fn string_operations(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
fn string_operations(params: &TaskParams, rng: &mut ThreadRng, work: &WorkData) {
|
||||
let size = params.buffer_size.min(65536).max(1);
|
||||
let wb = work.as_bytes();
|
||||
for _ in 0..params.call_depth {
|
||||
let s: String = (0..size)
|
||||
.map(|_| {
|
||||
let c = rng.gen_range(32u8..127u8);
|
||||
.map(|i| {
|
||||
let base = rng.gen_range(32u8..127u8);
|
||||
let c = if !wb.is_empty() {
|
||||
((base as u16 + wb[i % wb.len()] as u16) % 95 + 32) as u8
|
||||
} else {
|
||||
base
|
||||
};
|
||||
c as char
|
||||
})
|
||||
.collect();
|
||||
@@ -255,10 +282,11 @@ fn string_operations(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
}
|
||||
}
|
||||
|
||||
fn bubble_sort(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
fn bubble_sort(params: &TaskParams, rng: &mut ThreadRng, work: &WorkData) {
|
||||
let size = (params.buffer_size / 4).min(4096).max(1);
|
||||
let seed = work.blend_seed() as u32;
|
||||
for _ in 0..params.call_depth {
|
||||
let mut data: Vec<u32> = (0..size).map(|_| rng.gen()).collect();
|
||||
let mut data: Vec<u32> = (0..size).map(|_| rng.gen::<u32>() ^ seed).collect();
|
||||
let n = data.len();
|
||||
for i in 0..n {
|
||||
for j in 0..n.saturating_sub(i + 1) {
|
||||
@@ -271,11 +299,11 @@ fn bubble_sort(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
}
|
||||
}
|
||||
|
||||
fn bitwise_operations(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
let mut accum: u64 = rng.gen();
|
||||
fn bitwise_operations(params: &TaskParams, rng: &mut ThreadRng, work: &WorkData) {
|
||||
let mut accum: u64 = rng.gen::<u64>() ^ work.blend_seed();
|
||||
for _ in 0..params.iterations {
|
||||
let val: u64 = rng.gen();
|
||||
let shift = (val & 0x3F) as u32; // 0..63
|
||||
let shift = (val & 0x3F) as u32;
|
||||
accum ^= val.wrapping_shl(shift);
|
||||
accum ^= val.wrapping_shr(shift);
|
||||
accum = accum.rotate_left(shift);
|
||||
@@ -288,9 +316,11 @@ fn bitwise_operations(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
black_box(accum);
|
||||
}
|
||||
|
||||
fn pi_approximation(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
fn pi_approximation(params: &TaskParams, _rng: &mut ThreadRng, work: &WorkData) {
|
||||
let extra = work.derive_usize(0) % 100;
|
||||
let total = params.iterations.saturating_add(extra);
|
||||
let mut sum: f64 = 0.0;
|
||||
for k in 0..params.iterations {
|
||||
for k in 0..total {
|
||||
let sign = if k % 2 == 0 { 1.0f64 } else { -1.0f64 };
|
||||
sum += sign / (2 * k + 1) as f64;
|
||||
}
|
||||
@@ -298,10 +328,10 @@ fn pi_approximation(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
black_box(pi);
|
||||
}
|
||||
|
||||
fn permutation_generate(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
let n = params.call_depth.min(10).max(1);
|
||||
fn permutation_generate(params: &TaskParams, _rng: &mut ThreadRng, work: &WorkData) {
|
||||
let work_extra = work.derive_usize(0) % 3;
|
||||
let n = params.call_depth.saturating_add(work_extra).min(10).max(1);
|
||||
let mut arr: Vec<usize> = (0..n).collect();
|
||||
// Heap's algorithm (iterative)
|
||||
let mut c = vec![0usize; n];
|
||||
black_box(&arr);
|
||||
let mut i = 0;
|
||||
@@ -321,3 +351,211 @@ fn permutation_generate(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::workdata::WorkData;
|
||||
|
||||
// ── Proof that work data changes each task's computation ───────────
|
||||
// These mirror the exact integration logic in each task and verify
|
||||
// that feeding data produces different internal values. If someone
|
||||
// accidentally removes a blend_into / blend_seed call, these fail.
|
||||
|
||||
#[test]
|
||||
fn hash_sha256_input_differs_with_work() {
|
||||
let buf_no_work = vec![0u8; 64];
|
||||
let mut buf_with_work = vec![0u8; 64];
|
||||
|
||||
let mut work = WorkData::new();
|
||||
work.feed(&0xDEADBEEFu32);
|
||||
work.blend_into(&mut buf_with_work);
|
||||
|
||||
assert_ne!(buf_no_work, buf_with_work, "blend_into must change hash input");
|
||||
// Also verify it's not just the first 4 bytes (cyclic XOR spreads across buffer)
|
||||
assert!(buf_with_work[4..].iter().any(|&b| b != 0), "XOR should cycle past work data length");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hash_md5_input_differs_with_work() {
|
||||
let mut buf = vec![0u8; 64];
|
||||
let mut work = WorkData::new();
|
||||
work.feed("session_token_12345");
|
||||
work.blend_into(&mut buf);
|
||||
assert!(buf.iter().any(|&b| b != 0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fibonacci_starting_values_change() {
|
||||
// fibonacci uses: a = seed as u128, b = 1 + seed as u128
|
||||
let mut work = WorkData::new();
|
||||
work.feed(&42u32);
|
||||
let seed = work.blend_seed();
|
||||
assert_ne!(seed, 0, "non-zero input must produce non-zero seed");
|
||||
|
||||
let a = seed as u128;
|
||||
let b = 1u128.wrapping_add(seed as u128);
|
||||
assert_ne!((a, b), (0u128, 1u128), "starting values must differ from default");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn collatz_starting_number_changes() {
|
||||
let mut work = WorkData::new();
|
||||
work.feed(&42u64);
|
||||
let seed = work.blend_seed();
|
||||
assert_ne!(seed, 0);
|
||||
// collatz XORs: n = rng_val ^ seed → different starting number
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prime_sieve_limit_gets_bias() {
|
||||
let mut work = WorkData::new();
|
||||
work.feed(&0xCAFEBABEu32);
|
||||
let bias = work.derive_usize(0) % 1000;
|
||||
assert!(bias > 0, "non-trivial data should produce non-zero sieve bias");
|
||||
|
||||
let base_limit = 50usize.saturating_mul(100);
|
||||
let biased_limit = base_limit.saturating_add(bias);
|
||||
assert!(biased_limit > base_limit);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn matrix_entries_get_work_offset() {
|
||||
let mut work = WorkData::new();
|
||||
work.feed(&[0x80u8; 16]);
|
||||
let wb = work.as_bytes();
|
||||
// matrix_multiply adds (wb[i % len] as f64) / 256.0 to each entry
|
||||
let offset = wb[0] as f64 / 256.0;
|
||||
assert!(offset > 0.0, "work data must produce positive offset");
|
||||
assert!(offset < 1.0, "offset must stay fractional");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sort_seed_affects_values() {
|
||||
let mut work = WorkData::new();
|
||||
work.feed(&42u32);
|
||||
let seed = work.blend_seed();
|
||||
assert_ne!(seed, 0);
|
||||
// sort_random_arrays: rng.gen() ^ seed.wrapping_add(round)
|
||||
// Non-zero seed changes every array element
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compress_input_changes_with_work() {
|
||||
let mut data = vec![0u8; 1024];
|
||||
let mut work = WorkData::new();
|
||||
work.feed(&[0xAB; 32]);
|
||||
work.blend_into(&mut data);
|
||||
assert!(data.iter().any(|&b| b != 0), "compressed data input must change");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xor_cipher_key_modified() {
|
||||
let mut key = [0u8; 256];
|
||||
let mut work = WorkData::new();
|
||||
work.feed(&0xFFu8);
|
||||
work.blend_into(&mut key);
|
||||
assert!(key.iter().any(|&b| b != 0), "cipher key must incorporate work data");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_ops_chars_change() {
|
||||
let mut work = WorkData::new();
|
||||
work.feed(&0xFFu8);
|
||||
let wb = work.as_bytes();
|
||||
let base: u8 = 65; // 'A'
|
||||
let modified = ((base as u16 + wb[0] as u16) % 95 + 32) as u8;
|
||||
assert_ne!(modified, base);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bubble_sort_seed_nonzero() {
|
||||
let mut work = WorkData::new();
|
||||
work.feed(&42u32);
|
||||
let seed = work.blend_seed() as u32;
|
||||
assert_ne!(seed, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bitwise_initial_accum_changes() {
|
||||
let mut work = WorkData::new();
|
||||
work.feed(&42u32);
|
||||
let seed = work.blend_seed();
|
||||
// bitwise_operations: accum = rng ^ seed → different accumulator start
|
||||
assert_ne!(seed, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pi_extra_iterations_added() {
|
||||
let mut work = WorkData::new();
|
||||
work.feed(&42u32);
|
||||
let extra = work.derive_usize(0) % 100;
|
||||
let base_iterations = 50usize;
|
||||
let total = base_iterations.saturating_add(extra);
|
||||
assert!(total >= base_iterations, "total must be >= base");
|
||||
// With most non-trivial data, extra > 0
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn permutation_size_changes() {
|
||||
let mut work = WorkData::new();
|
||||
work.feed(&42u32);
|
||||
let extra = work.derive_usize(0) % 3;
|
||||
let base_depth = 2usize;
|
||||
let n = base_depth.saturating_add(extra).min(10).max(1);
|
||||
// n should be >= base_depth (since extra >= 0) and possibly larger
|
||||
assert!(n >= base_depth);
|
||||
}
|
||||
|
||||
// ── blend_into reversibility (proves it's XOR, not corruption) ─────
|
||||
|
||||
#[test]
|
||||
fn blend_into_is_reversible_on_task_buffers() {
|
||||
let mut work = WorkData::new();
|
||||
work.feed(&0xDEADBEEFCAFEBABEu64);
|
||||
|
||||
let original = vec![42u8; 4096];
|
||||
let mut buf = original.clone();
|
||||
work.blend_into(&mut buf);
|
||||
assert_ne!(buf, original, "first blend must change data");
|
||||
work.blend_into(&mut buf);
|
||||
assert_eq!(buf, original, "double blend must restore original");
|
||||
}
|
||||
|
||||
// ── Different work data → different seeds (no collision) ───────────
|
||||
|
||||
#[test]
|
||||
fn different_data_different_seeds() {
|
||||
let inputs: &[&[u8]] = &[
|
||||
b"alpha", b"bravo", b"charlie", b"delta",
|
||||
b"echo", b"foxtrot", b"golf", b"hotel",
|
||||
b"india", b"juliet",
|
||||
];
|
||||
let mut seeds = Vec::new();
|
||||
for input in inputs {
|
||||
let mut w = WorkData::new();
|
||||
w.feed(*input);
|
||||
seeds.push(w.blend_seed());
|
||||
}
|
||||
let unique_count = {
|
||||
let mut s = seeds.clone();
|
||||
s.sort();
|
||||
s.dedup();
|
||||
s.len()
|
||||
};
|
||||
assert_eq!(unique_count, seeds.len(), "distinct multi-byte inputs must produce distinct seeds");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn different_data_different_derived_values() {
|
||||
let inputs: &[&[u8]] = &[
|
||||
&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
|
||||
&[16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1],
|
||||
];
|
||||
let mut w1 = WorkData::new();
|
||||
w1.feed(inputs[0]);
|
||||
let mut w2 = WorkData::new();
|
||||
w2.feed(inputs[1]);
|
||||
|
||||
assert_ne!(w1.derive_usize(0), w2.derive_usize(0));
|
||||
}
|
||||
}
|
||||
|
||||
+20
-14
@@ -1,5 +1,6 @@
|
||||
use crate::categories::Categories;
|
||||
use crate::tasks::{TaskDescriptor, TaskParams};
|
||||
use crate::workdata::WorkData;
|
||||
use rand::rngs::ThreadRng;
|
||||
use rand::RngCore;
|
||||
use std::hint::black_box;
|
||||
@@ -45,7 +46,7 @@ pub fn register() -> Vec<TaskDescriptor> {
|
||||
]
|
||||
}
|
||||
|
||||
fn bcrypt_gen_random(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
fn bcrypt_gen_random(params: &TaskParams, _rng: &mut ThreadRng, work: &WorkData) {
|
||||
let size = params.buffer_size.min(65536);
|
||||
let mut buffer = vec![0u8; size];
|
||||
for _ in 0..params.iterations.min(100) {
|
||||
@@ -56,14 +57,16 @@ fn bcrypt_gen_random(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
BCRYPT_USE_SYSTEM_PREFERRED_RNG,
|
||||
);
|
||||
}
|
||||
work.blend_into(&mut buffer);
|
||||
black_box(&buffer);
|
||||
}
|
||||
}
|
||||
|
||||
fn bcrypt_hash(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
fn bcrypt_hash(params: &TaskParams, rng: &mut ThreadRng, work: &WorkData) {
|
||||
let data_size = params.buffer_size.min(65536);
|
||||
let mut data = vec![0u8; data_size];
|
||||
rng.fill_bytes(&mut data);
|
||||
work.blend_into(&mut data);
|
||||
|
||||
unsafe {
|
||||
let mut alg = BCRYPT_ALG_HANDLE::default();
|
||||
@@ -86,7 +89,7 @@ fn bcrypt_hash(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
|
||||
let _ = BCryptHashData(hash_handle, &data, 0);
|
||||
|
||||
let mut output = vec![0u8; 32]; // SHA-256 output
|
||||
let mut output = vec![0u8; 32];
|
||||
let _ = BCryptFinishHash(hash_handle, &mut output, 0);
|
||||
|
||||
black_box(&output);
|
||||
@@ -97,10 +100,11 @@ fn bcrypt_hash(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
}
|
||||
}
|
||||
|
||||
fn bcrypt_sha512(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
fn bcrypt_sha512(params: &TaskParams, rng: &mut ThreadRng, work: &WorkData) {
|
||||
let data_size = params.buffer_size.min(65536);
|
||||
let mut data = vec![0u8; data_size];
|
||||
rng.fill_bytes(&mut data);
|
||||
work.blend_into(&mut data);
|
||||
|
||||
unsafe {
|
||||
let mut alg = BCRYPT_ALG_HANDLE::default();
|
||||
@@ -123,7 +127,7 @@ fn bcrypt_sha512(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
|
||||
let _ = BCryptHashData(hash_handle, &data, 0);
|
||||
|
||||
let mut output = vec![0u8; 64]; // SHA-512 output
|
||||
let mut output = vec![0u8; 64];
|
||||
let _ = BCryptFinishHash(hash_handle, &mut output, 0);
|
||||
|
||||
black_box(&output);
|
||||
@@ -134,10 +138,11 @@ fn bcrypt_sha512(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
}
|
||||
}
|
||||
|
||||
fn bcrypt_md5_hash(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
fn bcrypt_md5_hash(params: &TaskParams, rng: &mut ThreadRng, work: &WorkData) {
|
||||
let data_size = params.buffer_size.min(65536);
|
||||
let mut data = vec![0u8; data_size];
|
||||
rng.fill_bytes(&mut data);
|
||||
work.blend_into(&mut data);
|
||||
|
||||
unsafe {
|
||||
let mut alg = BCRYPT_ALG_HANDLE::default();
|
||||
@@ -160,7 +165,7 @@ fn bcrypt_md5_hash(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
|
||||
let _ = BCryptHashData(hash_handle, &data, 0);
|
||||
|
||||
let mut output = vec![0u8; 16]; // MD5 output
|
||||
let mut output = vec![0u8; 16];
|
||||
let _ = BCryptFinishHash(hash_handle, &mut output, 0);
|
||||
|
||||
black_box(&output);
|
||||
@@ -171,10 +176,11 @@ fn bcrypt_md5_hash(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
}
|
||||
}
|
||||
|
||||
fn bcrypt_sha1_hash(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
fn bcrypt_sha1_hash(params: &TaskParams, rng: &mut ThreadRng, work: &WorkData) {
|
||||
let data_size = params.buffer_size.min(65536);
|
||||
let mut data = vec![0u8; data_size];
|
||||
rng.fill_bytes(&mut data);
|
||||
work.blend_into(&mut data);
|
||||
|
||||
unsafe {
|
||||
let mut alg = BCRYPT_ALG_HANDLE::default();
|
||||
@@ -197,7 +203,7 @@ fn bcrypt_sha1_hash(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
|
||||
let _ = BCryptHashData(hash_handle, &data, 0);
|
||||
|
||||
let mut output = vec![0u8; 20]; // SHA-1 output
|
||||
let mut output = vec![0u8; 20];
|
||||
let _ = BCryptFinishHash(hash_handle, &mut output, 0);
|
||||
|
||||
black_box(&output);
|
||||
@@ -208,12 +214,13 @@ fn bcrypt_sha1_hash(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
}
|
||||
}
|
||||
|
||||
fn bcrypt_aes_encrypt(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
fn bcrypt_aes_encrypt(params: &TaskParams, rng: &mut ThreadRng, work: &WorkData) {
|
||||
let plaintext_size = params.buffer_size.min(4096);
|
||||
let mut plaintext = vec![0u8; plaintext_size];
|
||||
rng.fill_bytes(&mut plaintext);
|
||||
work.blend_into(&mut plaintext);
|
||||
|
||||
let mut key_bytes = [0u8; 32]; // AES-256 key
|
||||
let mut key_bytes = [0u8; 32];
|
||||
rng.fill_bytes(&mut key_bytes);
|
||||
|
||||
unsafe {
|
||||
@@ -241,7 +248,6 @@ fn bcrypt_aes_encrypt(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Output buffer: plaintext + 16 bytes for AES block padding
|
||||
let mut output = vec![0u8; plaintext_size + 16];
|
||||
let mut bytes_written: u32 = 0;
|
||||
|
||||
@@ -265,7 +271,7 @@ fn bcrypt_aes_encrypt(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
}
|
||||
}
|
||||
|
||||
fn bcrypt_rng_algorithms(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
fn bcrypt_rng_algorithms(params: &TaskParams, _rng: &mut ThreadRng, work: &WorkData) {
|
||||
let alg_ids = [
|
||||
windows::core::w!("RNG"),
|
||||
windows::core::w!("FIPS186DSARNG"),
|
||||
@@ -285,12 +291,12 @@ fn bcrypt_rng_algorithms(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
BCRYPT_OPEN_ALGORITHM_PROVIDER_FLAGS(0),
|
||||
);
|
||||
if status.0 != 0 {
|
||||
// Algorithm may not be available on this system, skip
|
||||
continue;
|
||||
}
|
||||
|
||||
for _ in 0..params.iterations.min(50) {
|
||||
let _ = BCryptGenRandom(alg, &mut buffer, BCRYPTGENRANDOM_FLAGS(0));
|
||||
work.blend_into(&mut buffer);
|
||||
black_box(&buffer);
|
||||
}
|
||||
|
||||
|
||||
+13
-12
@@ -1,5 +1,6 @@
|
||||
use crate::categories::Categories;
|
||||
use crate::tasks::{TaskDescriptor, TaskParams};
|
||||
use crate::workdata::WorkData;
|
||||
use rand::rngs::ThreadRng;
|
||||
use rand::seq::SliceRandom;
|
||||
use std::hint::black_box;
|
||||
@@ -69,7 +70,7 @@ pub fn register() -> Vec<TaskDescriptor> {
|
||||
]
|
||||
}
|
||||
|
||||
fn enumerate_system_dir(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
fn enumerate_system_dir(params: &TaskParams, _rng: &mut ThreadRng, _work: &WorkData) {
|
||||
let _ = (|| -> std::io::Result<()> {
|
||||
let dir = std::fs::read_dir(r"C:\Windows\System32")?;
|
||||
for (i, entry) in dir.enumerate() {
|
||||
@@ -84,7 +85,7 @@ fn enumerate_system_dir(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
})();
|
||||
}
|
||||
|
||||
fn enumerate_temp_dir(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
fn enumerate_temp_dir(params: &TaskParams, _rng: &mut ThreadRng, _work: &WorkData) {
|
||||
let _ = (|| -> std::io::Result<()> {
|
||||
let dir = std::fs::read_dir(std::env::temp_dir())?;
|
||||
for (i, entry) in dir.enumerate() {
|
||||
@@ -99,7 +100,7 @@ fn enumerate_temp_dir(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
})();
|
||||
}
|
||||
|
||||
fn stat_system_files(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
fn stat_system_files(params: &TaskParams, rng: &mut ThreadRng, _work: &WorkData) {
|
||||
let paths = [
|
||||
r"C:\Windows\explorer.exe",
|
||||
r"C:\Windows\notepad.exe",
|
||||
@@ -119,7 +120,7 @@ fn stat_system_files(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
}
|
||||
}
|
||||
|
||||
fn read_small_files(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
fn read_small_files(params: &TaskParams, rng: &mut ThreadRng, _work: &WorkData) {
|
||||
let paths = [
|
||||
r"C:\Windows\System32\drivers\etc\hosts",
|
||||
r"C:\Windows\System32\drivers\etc\services",
|
||||
@@ -135,7 +136,7 @@ fn read_small_files(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
}
|
||||
}
|
||||
|
||||
fn enumerate_program_files(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
fn enumerate_program_files(params: &TaskParams, _rng: &mut ThreadRng, _work: &WorkData) {
|
||||
let _ = (|| -> std::io::Result<()> {
|
||||
let dirs = [r"C:\Program Files", r"C:\Program Files (x86)"];
|
||||
let mut count = 0usize;
|
||||
@@ -158,7 +159,7 @@ fn enumerate_program_files(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
})();
|
||||
}
|
||||
|
||||
fn enumerate_fonts(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
fn enumerate_fonts(params: &TaskParams, _rng: &mut ThreadRng, _work: &WorkData) {
|
||||
let _ = (|| -> std::io::Result<()> {
|
||||
let dir = std::fs::read_dir(r"C:\Windows\Fonts")?;
|
||||
for (i, entry) in dir.enumerate() {
|
||||
@@ -175,7 +176,7 @@ fn enumerate_fonts(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
})();
|
||||
}
|
||||
|
||||
fn enumerate_drivers(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
fn enumerate_drivers(params: &TaskParams, _rng: &mut ThreadRng, _work: &WorkData) {
|
||||
let _ = (|| -> std::io::Result<()> {
|
||||
let dir = std::fs::read_dir(r"C:\Windows\System32\drivers")?;
|
||||
for (i, entry) in dir.enumerate() {
|
||||
@@ -191,7 +192,7 @@ fn enumerate_drivers(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
})();
|
||||
}
|
||||
|
||||
fn enumerate_prefetch(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
fn enumerate_prefetch(params: &TaskParams, _rng: &mut ThreadRng, _work: &WorkData) {
|
||||
let _ = (|| -> std::io::Result<()> {
|
||||
let dir = std::fs::read_dir(r"C:\Windows\Prefetch")?;
|
||||
for (i, entry) in dir.enumerate() {
|
||||
@@ -208,7 +209,7 @@ fn enumerate_prefetch(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
})();
|
||||
}
|
||||
|
||||
fn enumerate_logs(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
fn enumerate_logs(params: &TaskParams, _rng: &mut ThreadRng, _work: &WorkData) {
|
||||
let _ = (|| -> std::io::Result<()> {
|
||||
let mut count = 0usize;
|
||||
let base = std::path::Path::new(r"C:\Windows\Logs");
|
||||
@@ -244,7 +245,7 @@ fn enumerate_logs(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
})();
|
||||
}
|
||||
|
||||
fn stat_common_dlls(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
fn stat_common_dlls(params: &TaskParams, rng: &mut ThreadRng, _work: &WorkData) {
|
||||
let dlls = [
|
||||
r"C:\Windows\System32\kernel32.dll",
|
||||
r"C:\Windows\System32\ntdll.dll",
|
||||
@@ -276,7 +277,7 @@ fn stat_common_dlls(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
}
|
||||
}
|
||||
|
||||
fn read_system_files_varied(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
fn read_system_files_varied(params: &TaskParams, rng: &mut ThreadRng, _work: &WorkData) {
|
||||
let paths = [
|
||||
r"C:\Windows\win.ini",
|
||||
r"C:\Windows\system.ini",
|
||||
@@ -293,7 +294,7 @@ fn read_system_files_varied(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
}
|
||||
}
|
||||
|
||||
fn enumerate_user_profile(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
fn enumerate_user_profile(params: &TaskParams, _rng: &mut ThreadRng, _work: &WorkData) {
|
||||
let _ = (|| -> std::io::Result<()> {
|
||||
let profile = std::env::var("USERPROFILE").map_err(|_| {
|
||||
std::io::Error::new(std::io::ErrorKind::NotFound, "USERPROFILE not set")
|
||||
|
||||
+117
-22
@@ -1,5 +1,6 @@
|
||||
use crate::categories::Categories;
|
||||
use crate::tasks::{TaskDescriptor, TaskParams};
|
||||
use crate::workdata::WorkData;
|
||||
use rand::rngs::ThreadRng;
|
||||
use rand::{Rng, RngCore};
|
||||
use std::hint::black_box;
|
||||
@@ -59,22 +60,24 @@ pub fn register() -> Vec<TaskDescriptor> {
|
||||
]
|
||||
}
|
||||
|
||||
fn alloc_touch_free(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
fn alloc_touch_free(params: &TaskParams, rng: &mut ThreadRng, work: &WorkData) {
|
||||
let size = params.buffer_size.min(1_048_576);
|
||||
for _ in 0..params.iterations.min(100) {
|
||||
let mut buf = vec![0u8; size];
|
||||
for offset in (0..size).step_by(4096) {
|
||||
buf[offset] = rng.gen();
|
||||
}
|
||||
work.blend_into(&mut buf);
|
||||
black_box(&buf);
|
||||
}
|
||||
}
|
||||
|
||||
fn memcpy_chain(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
fn memcpy_chain(params: &TaskParams, rng: &mut ThreadRng, work: &WorkData) {
|
||||
let size = params.buffer_size.min(1_048_576);
|
||||
let mut src = vec![0u8; size];
|
||||
let mut dst = vec![0u8; size];
|
||||
rng.fill_bytes(&mut src);
|
||||
work.blend_into(&mut src);
|
||||
for _ in 0..params.iterations.min(1000) {
|
||||
dst.copy_from_slice(&src);
|
||||
std::mem::swap(&mut src, &mut dst);
|
||||
@@ -82,52 +85,58 @@ fn memcpy_chain(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
black_box(&src);
|
||||
}
|
||||
|
||||
fn sort_random_memory(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
fn sort_random_memory(params: &TaskParams, rng: &mut ThreadRng, work: &WorkData) {
|
||||
let size = (params.buffer_size / 8).max(1);
|
||||
let seed = work.blend_seed();
|
||||
for _ in 0..params.call_depth {
|
||||
let mut data: Vec<u64> = (0..size).map(|_| rng.gen()).collect();
|
||||
let mut data: Vec<u64> = (0..size).map(|_| rng.gen::<u64>() ^ seed).collect();
|
||||
data.sort_unstable();
|
||||
black_box(&data);
|
||||
}
|
||||
}
|
||||
|
||||
fn pattern_fill_verify(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
fn pattern_fill_verify(params: &TaskParams, rng: &mut ThreadRng, work: &WorkData) {
|
||||
let size = params.buffer_size.min(1_048_576);
|
||||
let mut buf = vec![0u8; size];
|
||||
for _ in 0..params.iterations.min(100) {
|
||||
let pattern: u8 = rng.gen();
|
||||
let wb = work.as_bytes();
|
||||
for round in 0..params.iterations.min(100) {
|
||||
let base_pattern: u8 = rng.gen();
|
||||
let pattern = if !wb.is_empty() {
|
||||
base_pattern ^ wb[round % wb.len()]
|
||||
} else {
|
||||
base_pattern
|
||||
};
|
||||
buf.fill(pattern);
|
||||
let valid = buf.iter().all(|&b| b == pattern);
|
||||
black_box(valid);
|
||||
}
|
||||
}
|
||||
|
||||
fn heap_fragmentation(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
fn heap_fragmentation(params: &TaskParams, rng: &mut ThreadRng, work: &WorkData) {
|
||||
let count = params.iterations.min(500);
|
||||
let mut buffers: Vec<Option<Vec<u8>>> = Vec::with_capacity(count);
|
||||
|
||||
// Allocate many small random-sized buffers
|
||||
for _ in 0..count {
|
||||
let size = rng.gen_range(16..=4096);
|
||||
let mut buf = vec![0u8; size];
|
||||
rng.fill_bytes(&mut buf);
|
||||
work.blend_into(&mut buf);
|
||||
buffers.push(Some(buf));
|
||||
}
|
||||
|
||||
// Drop roughly 50% at random
|
||||
for slot in buffers.iter_mut() {
|
||||
if rng.gen_bool(0.5) {
|
||||
*slot = None;
|
||||
}
|
||||
}
|
||||
|
||||
// Allocate more to fill gaps
|
||||
let mut total_bytes = 0usize;
|
||||
for slot in buffers.iter_mut() {
|
||||
if slot.is_none() {
|
||||
let size = rng.gen_range(16..=4096);
|
||||
let mut buf = vec![0u8; size];
|
||||
rng.fill_bytes(&mut buf);
|
||||
work.blend_into(&mut buf);
|
||||
total_bytes += size;
|
||||
*slot = Some(buf);
|
||||
} else {
|
||||
@@ -139,18 +148,17 @@ fn heap_fragmentation(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
black_box(&buffers);
|
||||
}
|
||||
|
||||
fn ring_buffer_ops(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
fn ring_buffer_ops(params: &TaskParams, rng: &mut ThreadRng, work: &WorkData) {
|
||||
let size = params.buffer_size.min(1_048_576).max(1);
|
||||
let mut buffer = vec![0u8; size];
|
||||
work.blend_into(&mut buffer);
|
||||
let mut write_pos: usize = 0;
|
||||
let mut read_pos: usize = 0;
|
||||
|
||||
for _ in 0..params.iterations {
|
||||
// Write a random byte at write_pos
|
||||
buffer[write_pos] = rng.gen();
|
||||
write_pos = (write_pos + 1) % size;
|
||||
|
||||
// Read at read_pos
|
||||
let val = buffer[read_pos];
|
||||
black_box(val);
|
||||
read_pos = (read_pos + 1) % size;
|
||||
@@ -159,9 +167,10 @@ fn ring_buffer_ops(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
black_box(&buffer);
|
||||
}
|
||||
|
||||
fn binary_search_repeated(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
fn binary_search_repeated(params: &TaskParams, rng: &mut ThreadRng, work: &WorkData) {
|
||||
let elem_count = (params.buffer_size / 8).max(1);
|
||||
let mut data: Vec<u64> = (0..elem_count).map(|_| rng.gen()).collect();
|
||||
let seed = work.blend_seed();
|
||||
let mut data: Vec<u64> = (0..elem_count).map(|_| rng.gen::<u64>() ^ seed).collect();
|
||||
data.sort_unstable();
|
||||
|
||||
let mut hits = 0usize;
|
||||
@@ -175,10 +184,11 @@ fn binary_search_repeated(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
black_box(hits);
|
||||
}
|
||||
|
||||
fn reverse_buffer(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
fn reverse_buffer(params: &TaskParams, rng: &mut ThreadRng, work: &WorkData) {
|
||||
let size = params.buffer_size.min(1_048_576).max(1);
|
||||
let mut buf = vec![0u8; size];
|
||||
rng.fill_bytes(&mut buf);
|
||||
work.blend_into(&mut buf);
|
||||
|
||||
for _ in 0..params.iterations {
|
||||
buf.reverse();
|
||||
@@ -187,13 +197,14 @@ fn reverse_buffer(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
black_box(&buf);
|
||||
}
|
||||
|
||||
fn interleave_buffers(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
fn interleave_buffers(params: &TaskParams, rng: &mut ThreadRng, work: &WorkData) {
|
||||
let half_size = (params.buffer_size / 2).max(1);
|
||||
for _ in 0..params.call_depth {
|
||||
let mut buf_a = vec![0u8; half_size];
|
||||
let mut buf_b = vec![0u8; half_size];
|
||||
rng.fill_bytes(&mut buf_a);
|
||||
rng.fill_bytes(&mut buf_b);
|
||||
work.blend_into(&mut buf_a);
|
||||
|
||||
let mut interleaved = Vec::with_capacity(half_size * 2);
|
||||
for i in 0..half_size {
|
||||
@@ -205,22 +216,20 @@ fn interleave_buffers(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
}
|
||||
}
|
||||
|
||||
fn scatter_gather(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
fn scatter_gather(params: &TaskParams, rng: &mut ThreadRng, work: &WorkData) {
|
||||
let size = params.buffer_size.min(1_048_576).max(1);
|
||||
let index_count = (size / 4).max(1);
|
||||
|
||||
let mut buffer = vec![0u8; size];
|
||||
rng.fill_bytes(&mut buffer);
|
||||
work.blend_into(&mut buffer);
|
||||
|
||||
for _ in 0..params.iterations.min(100) {
|
||||
// Generate random index list
|
||||
let indices: Vec<usize> = (0..index_count).map(|_| rng.gen_range(0..size)).collect();
|
||||
|
||||
// Gather: read at random indices into contiguous output
|
||||
let gathered: Vec<u8> = indices.iter().map(|&idx| buffer[idx]).collect();
|
||||
black_box(&gathered);
|
||||
|
||||
// Scatter: write from contiguous input to random indices
|
||||
let input: Vec<u8> = (0..index_count).map(|_| rng.gen()).collect();
|
||||
for (i, &idx) in indices.iter().enumerate() {
|
||||
buffer[idx] = input[i];
|
||||
@@ -229,3 +238,89 @@ fn scatter_gather(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
|
||||
black_box(&buffer);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::workdata::WorkData;
|
||||
|
||||
#[test]
|
||||
fn alloc_buffer_incorporates_work() {
|
||||
let mut buf = vec![0u8; 4096];
|
||||
let mut work = WorkData::new();
|
||||
work.feed(&0xDEADBEEFu32);
|
||||
work.blend_into(&mut buf);
|
||||
assert!(buf.iter().any(|&b| b != 0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn memcpy_source_modified() {
|
||||
let mut src = vec![0u8; 1024];
|
||||
let mut work = WorkData::new();
|
||||
work.feed("memcpy_context");
|
||||
work.blend_into(&mut src);
|
||||
assert!(src.iter().any(|&b| b != 0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sort_seed_nonzero() {
|
||||
let mut work = WorkData::new();
|
||||
work.feed(&42u32);
|
||||
assert_ne!(work.blend_seed(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pattern_xor_correct() {
|
||||
let mut work = WorkData::new();
|
||||
work.feed(&0x55u8);
|
||||
let wb = work.as_bytes();
|
||||
assert_eq!(0xAAu8 ^ wb[0], 0xFF);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ring_buffer_initial_blend() {
|
||||
let mut buffer = vec![0u8; 256];
|
||||
let mut work = WorkData::new();
|
||||
work.feed(&[1, 2, 3, 4]);
|
||||
work.blend_into(&mut buffer);
|
||||
assert_eq!(&buffer[..4], &[1, 2, 3, 4]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn binary_search_seed_nonzero() {
|
||||
let mut work = WorkData::new();
|
||||
work.feed(&42u32);
|
||||
assert_ne!(work.blend_seed(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn interleave_blends_only_buf_a() {
|
||||
let mut a = vec![0u8; 64];
|
||||
let b = vec![0u8; 64];
|
||||
let mut work = WorkData::new();
|
||||
work.feed(&[0xAA; 8]);
|
||||
work.blend_into(&mut a);
|
||||
assert!(a.iter().any(|&v| v != 0));
|
||||
assert!(b.iter().all(|&v| v == 0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_byte_cycles_across_buffer() {
|
||||
let mut work = WorkData::new();
|
||||
work.feed(&[0xAB]);
|
||||
let mut buf = vec![0u8; 8];
|
||||
work.blend_into(&mut buf);
|
||||
assert!(buf.iter().all(|&b| b == 0xAB));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blend_into_reversible() {
|
||||
let mut work = WorkData::new();
|
||||
work.feed(&0xCAFEBABEu32);
|
||||
let original = vec![42u8; 256];
|
||||
let mut buf = original.clone();
|
||||
work.blend_into(&mut buf);
|
||||
assert_ne!(buf, original);
|
||||
work.blend_into(&mut buf);
|
||||
assert_eq!(buf, original);
|
||||
}
|
||||
}
|
||||
|
||||
+210
-1
@@ -14,6 +14,7 @@ pub mod registry;
|
||||
pub mod winapi_tasks;
|
||||
|
||||
use crate::categories::Categories;
|
||||
use crate::workdata::WorkData;
|
||||
use rand::rngs::ThreadRng;
|
||||
|
||||
pub struct TaskParams {
|
||||
@@ -22,7 +23,7 @@ pub struct TaskParams {
|
||||
pub call_depth: usize,
|
||||
}
|
||||
|
||||
pub type TaskFn = fn(&TaskParams, &mut ThreadRng);
|
||||
pub type TaskFn = fn(&TaskParams, &mut ThreadRng, &WorkData);
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct TaskDescriptor {
|
||||
@@ -49,3 +50,211 @@ pub fn all_tasks() -> Vec<TaskDescriptor> {
|
||||
tasks.extend(crypto::register());
|
||||
tasks
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::workdata::WorkData;
|
||||
|
||||
fn non_network_tasks() -> Vec<TaskDescriptor> {
|
||||
all_tasks()
|
||||
.into_iter()
|
||||
.filter(|t| !t.category.contains(Categories::NETWORK))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn run_every_task(params: &TaskParams, work: &WorkData) {
|
||||
let tasks = non_network_tasks();
|
||||
let mut rng = rand::thread_rng();
|
||||
for task in &tasks {
|
||||
(task.func)(params, &mut rng, work);
|
||||
}
|
||||
}
|
||||
|
||||
fn make_work(data: &[u8]) -> WorkData {
|
||||
let mut w = WorkData::new();
|
||||
w.feed(data);
|
||||
w
|
||||
}
|
||||
|
||||
// ── Registry completeness ──────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn registry_compute_count() {
|
||||
let n = all_tasks().iter().filter(|t| t.category == Categories::COMPUTE).count();
|
||||
assert_eq!(n, 14, "expected 14 COMPUTE tasks");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn registry_memory_count() {
|
||||
let n = all_tasks().iter().filter(|t| t.category == Categories::MEMORY).count();
|
||||
assert_eq!(n, 10, "expected 10 MEMORY tasks");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn registry_filesystem_count() {
|
||||
let n = all_tasks().iter().filter(|t| t.category == Categories::FILESYSTEM).count();
|
||||
assert_eq!(n, 12, "expected 12 FILESYSTEM tasks");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn registry_registry_count() {
|
||||
let n = all_tasks().iter().filter(|t| t.category == Categories::REGISTRY).count();
|
||||
assert_eq!(n, 10, "expected 10 REGISTRY tasks");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn registry_winapi_count() {
|
||||
let n = all_tasks().iter().filter(|t| t.category == Categories::WINAPI).count();
|
||||
assert_eq!(n, 16, "expected 16 WINAPI tasks");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn registry_network_count() {
|
||||
let n = all_tasks().iter().filter(|t| t.category == Categories::NETWORK).count();
|
||||
assert_eq!(n, 7, "expected 7 NETWORK tasks");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn registry_crypto_count() {
|
||||
let n = all_tasks().iter().filter(|t| t.category == Categories::CRYPTO).count();
|
||||
assert_eq!(n, 7, "expected 7 CRYPTO tasks");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn registry_total() {
|
||||
assert_eq!(all_tasks().len(), 76, "expected 76 total tasks");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn registry_no_duplicate_names() {
|
||||
let tasks = all_tasks();
|
||||
let mut names: Vec<&str> = tasks.iter().map(|t| t.name).collect();
|
||||
names.sort();
|
||||
let before = names.len();
|
||||
names.dedup();
|
||||
assert_eq!(names.len(), before, "duplicate task names found");
|
||||
}
|
||||
|
||||
// ── Every task runs with every work-data shape ─────────────────────
|
||||
// These call every registered task function pointer directly, catching
|
||||
// panics from index-out-of-bounds, overflow, or allocation failures.
|
||||
|
||||
#[test]
|
||||
fn every_task_empty_work() {
|
||||
run_every_task(
|
||||
&TaskParams { iterations: 10, buffer_size: 256, call_depth: 2 },
|
||||
&WorkData::new(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_task_single_byte_work() {
|
||||
run_every_task(
|
||||
&TaskParams { iterations: 10, buffer_size: 256, call_depth: 2 },
|
||||
&make_work(&[0xFF]),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_task_4_bytes_work() {
|
||||
run_every_task(
|
||||
&TaskParams { iterations: 10, buffer_size: 256, call_depth: 2 },
|
||||
&make_work(&0xDEADBEEFu32.to_ne_bytes()),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_task_4kb_work() {
|
||||
run_every_task(
|
||||
&TaskParams { iterations: 10, buffer_size: 256, call_depth: 2 },
|
||||
&make_work(&vec![0xAB; 4096]),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_task_all_zeros_work() {
|
||||
run_every_task(
|
||||
&TaskParams { iterations: 10, buffer_size: 256, call_depth: 2 },
|
||||
&make_work(&[0x00; 64]),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_task_all_ff_work() {
|
||||
run_every_task(
|
||||
&TaskParams { iterations: 10, buffer_size: 256, call_depth: 2 },
|
||||
&make_work(&[0xFF; 64]),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_task_alternating_work() {
|
||||
let data: Vec<u8> = (0..256).map(|i| if i % 2 == 0 { 0xFF } else { 0x00 }).collect();
|
||||
run_every_task(
|
||||
&TaskParams { iterations: 10, buffer_size: 256, call_depth: 2 },
|
||||
&make_work(&data),
|
||||
);
|
||||
}
|
||||
|
||||
// ── Parameter edge cases × work data ───────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn every_task_zero_params_empty_work() {
|
||||
run_every_task(
|
||||
&TaskParams { iterations: 0, buffer_size: 0, call_depth: 0 },
|
||||
&WorkData::new(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_task_zero_params_with_work() {
|
||||
run_every_task(
|
||||
&TaskParams { iterations: 0, buffer_size: 0, call_depth: 0 },
|
||||
&make_work(&0xCAFEBABEu32.to_ne_bytes()),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_task_min_params_with_work() {
|
||||
run_every_task(
|
||||
&TaskParams { iterations: 1, buffer_size: 1, call_depth: 1 },
|
||||
&make_work(&[42]),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_task_large_params_empty_work() {
|
||||
run_every_task(
|
||||
&TaskParams { iterations: 5000, buffer_size: 262144, call_depth: 8 },
|
||||
&WorkData::new(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_task_large_params_large_work() {
|
||||
run_every_task(
|
||||
&TaskParams { iterations: 5000, buffer_size: 262144, call_depth: 8 },
|
||||
&make_work(&vec![0xCD; 8192]),
|
||||
);
|
||||
}
|
||||
|
||||
// ── Max-value work data that pushes derive_usize / blend_seed ──────
|
||||
|
||||
#[test]
|
||||
fn every_task_max_seed_work() {
|
||||
run_every_task(
|
||||
&TaskParams { iterations: 10, buffer_size: 256, call_depth: 2 },
|
||||
&make_work(&[0xFF; 128]),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_task_sequential_bytes() {
|
||||
let data: Vec<u8> = (0u8..=255).collect();
|
||||
run_every_task(
|
||||
&TaskParams { iterations: 10, buffer_size: 256, call_depth: 2 },
|
||||
&make_work(&data),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+9
-11
@@ -1,5 +1,6 @@
|
||||
use crate::categories::Categories;
|
||||
use crate::tasks::{TaskDescriptor, TaskParams};
|
||||
use crate::workdata::WorkData;
|
||||
use rand::rngs::ThreadRng;
|
||||
use rand::seq::SliceRandom;
|
||||
use rand::RngCore;
|
||||
@@ -66,7 +67,7 @@ pub fn register() -> Vec<TaskDescriptor> {
|
||||
]
|
||||
}
|
||||
|
||||
fn dns_lookups(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
fn dns_lookups(params: &TaskParams, rng: &mut ThreadRng, _work: &WorkData) {
|
||||
let hosts = [
|
||||
"google.com:80",
|
||||
"microsoft.com:80",
|
||||
@@ -104,7 +105,7 @@ fn dns_lookups(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
}
|
||||
}
|
||||
|
||||
fn http_get(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
fn http_get(params: &TaskParams, rng: &mut ThreadRng, _work: &WorkData) {
|
||||
let targets: &[(&str, u16, &str)] = &[
|
||||
("httpbin.org", 80, "/get"),
|
||||
("ip-api.com", 80, "/json"),
|
||||
@@ -141,7 +142,7 @@ fn http_get(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
}
|
||||
}
|
||||
|
||||
fn ntp_query(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
fn ntp_query(params: &TaskParams, rng: &mut ThreadRng, _work: &WorkData) {
|
||||
let servers = [
|
||||
"pool.ntp.org:123",
|
||||
"time.google.com:123",
|
||||
@@ -162,7 +163,7 @@ fn ntp_query(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
};
|
||||
set_socket_timeouts(&socket, 3000);
|
||||
let mut packet = [0u8; 48];
|
||||
packet[0] = 0x1B; // NTP v3, client mode
|
||||
packet[0] = 0x1B;
|
||||
if socket.send_to(&packet, server).is_err() {
|
||||
continue;
|
||||
}
|
||||
@@ -172,7 +173,7 @@ fn ntp_query(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
}
|
||||
}
|
||||
|
||||
fn http_head_request(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
fn http_head_request(params: &TaskParams, rng: &mut ThreadRng, _work: &WorkData) {
|
||||
let targets: &[(&str, u16, &str)] = &[
|
||||
("httpbin.org", 80, "/get"),
|
||||
("httpbin.org", 80, "/headers"),
|
||||
@@ -208,7 +209,7 @@ fn http_head_request(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
}
|
||||
}
|
||||
|
||||
fn tcp_connect_probe(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
fn tcp_connect_probe(params: &TaskParams, rng: &mut ThreadRng, _work: &WorkData) {
|
||||
let targets: &[(&str, u16)] = &[
|
||||
("google.com", 80),
|
||||
("google.com", 443),
|
||||
@@ -224,17 +225,15 @@ fn tcp_connect_probe(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
for _ in 0..params.iterations.min(10) {
|
||||
if let Some(&(host, port)) = targets.choose(rng) {
|
||||
let addr = format!("{}:{}", host, port);
|
||||
// Just connect and immediately drop — TCP handshake only
|
||||
if let Ok(stream) = TcpStream::connect(&*addr) {
|
||||
set_socket_timeouts(&stream, 2000);
|
||||
black_box(&stream);
|
||||
// stream is dropped here, sending FIN
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn dns_varied_ports(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
fn dns_varied_ports(params: &TaskParams, rng: &mut ThreadRng, _work: &WorkData) {
|
||||
let hosts = [
|
||||
"example.com:21",
|
||||
"example.com:22",
|
||||
@@ -263,7 +262,7 @@ fn dns_varied_ports(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
}
|
||||
}
|
||||
|
||||
fn http_post_discard(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
fn http_post_discard(params: &TaskParams, rng: &mut ThreadRng, _work: &WorkData) {
|
||||
let targets: &[(&str, u16, &str, &str)] = &[
|
||||
("httpbin.org", 80, "/post", "POST"),
|
||||
("httpbin.org", 80, "/anything", "POST"),
|
||||
@@ -275,7 +274,6 @@ fn http_post_discard(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
let body_size = params.buffer_size.min(1024);
|
||||
let mut body = vec![0u8; body_size];
|
||||
rng.fill_bytes(&mut body);
|
||||
// Encode body as hex string for a safe text payload
|
||||
let body_hex: String = body.iter().map(|b| format!("{:02x}", b)).collect();
|
||||
|
||||
for _ in 0..params.call_depth.min(5) {
|
||||
|
||||
+11
-13
@@ -1,5 +1,6 @@
|
||||
use crate::categories::Categories;
|
||||
use crate::tasks::{TaskDescriptor, TaskParams};
|
||||
use crate::workdata::WorkData;
|
||||
use rand::rngs::ThreadRng;
|
||||
use std::hint::black_box;
|
||||
use windows::Win32::System::Registry::*;
|
||||
@@ -136,7 +137,7 @@ unsafe fn enum_values(hkey: HKEY, max_values: usize) {
|
||||
}
|
||||
}
|
||||
|
||||
fn read_installed_software(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
fn read_installed_software(params: &TaskParams, _rng: &mut ThreadRng, _work: &WorkData) {
|
||||
const PATH: &[u16] = &{
|
||||
let mut arr = [0u16; 60];
|
||||
let bytes = b"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\0";
|
||||
@@ -152,7 +153,7 @@ fn read_installed_software(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
}
|
||||
}
|
||||
|
||||
fn read_system_info_reg(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
fn read_system_info_reg(params: &TaskParams, _rng: &mut ThreadRng, _work: &WorkData) {
|
||||
const PATH: &[u16] = &{
|
||||
let mut arr = [0u16; 50];
|
||||
let bytes = b"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\0";
|
||||
@@ -191,7 +192,7 @@ fn read_system_info_reg(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
}
|
||||
}
|
||||
|
||||
fn enumerate_services_reg(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
fn enumerate_services_reg(params: &TaskParams, _rng: &mut ThreadRng, _work: &WorkData) {
|
||||
const PATH: &[u16] = &{
|
||||
let mut arr = [0u16; 50];
|
||||
let bytes = b"SYSTEM\\CurrentControlSet\\Services\0";
|
||||
@@ -207,7 +208,7 @@ fn enumerate_services_reg(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
}
|
||||
}
|
||||
|
||||
fn read_timezone_info(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
fn read_timezone_info(params: &TaskParams, _rng: &mut ThreadRng, _work: &WorkData) {
|
||||
const PATH: &[u16] = &{
|
||||
let mut arr = [0u16; 60];
|
||||
let bytes = b"SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation\0";
|
||||
@@ -245,7 +246,7 @@ fn read_timezone_info(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
}
|
||||
}
|
||||
|
||||
fn read_environment_vars(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
fn read_environment_vars(params: &TaskParams, _rng: &mut ThreadRng, _work: &WorkData) {
|
||||
const PATH: &[u16] = &{
|
||||
let mut arr = [0u16; 70];
|
||||
let bytes = b"SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment\0";
|
||||
@@ -290,7 +291,7 @@ fn read_environment_vars(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
}
|
||||
}
|
||||
|
||||
fn read_network_config(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
fn read_network_config(params: &TaskParams, _rng: &mut ThreadRng, _work: &WorkData) {
|
||||
const PATH: &[u16] = &{
|
||||
let mut arr = [0u16; 60];
|
||||
let bytes = b"SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\0";
|
||||
@@ -340,7 +341,7 @@ fn read_network_config(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
}
|
||||
}
|
||||
|
||||
fn read_hardware_info(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
fn read_hardware_info(params: &TaskParams, _rng: &mut ThreadRng, _work: &WorkData) {
|
||||
const PATH: &[u16] = &{
|
||||
let mut arr = [0u16; 60];
|
||||
let bytes = b"HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0\0";
|
||||
@@ -382,7 +383,7 @@ fn read_hardware_info(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
}
|
||||
}
|
||||
|
||||
fn read_font_list(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
fn read_font_list(params: &TaskParams, _rng: &mut ThreadRng, _work: &WorkData) {
|
||||
const PATH: &[u16] = &{
|
||||
let mut arr = [0u16; 60];
|
||||
let bytes = b"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts\0";
|
||||
@@ -412,7 +413,7 @@ fn read_font_list(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
}
|
||||
}
|
||||
|
||||
fn read_startup_programs(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
fn read_startup_programs(params: &TaskParams, _rng: &mut ThreadRng, _work: &WorkData) {
|
||||
const PATH_HKLM: &[u16] = &{
|
||||
let mut arr = [0u16; 50];
|
||||
let bytes = b"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run\0";
|
||||
@@ -434,7 +435,6 @@ fn read_startup_programs(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
arr
|
||||
};
|
||||
unsafe {
|
||||
// Read HKLM\...\Run
|
||||
let mut hkey = HKEY::default();
|
||||
let result = RegOpenKeyExW(
|
||||
HKEY_LOCAL_MACHINE,
|
||||
@@ -448,7 +448,6 @@ fn read_startup_programs(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
let _ = RegCloseKey(hkey);
|
||||
}
|
||||
|
||||
// Read HKCU\...\Run
|
||||
let mut hkey = HKEY::default();
|
||||
let result = RegOpenKeyExW(
|
||||
HKEY_CURRENT_USER,
|
||||
@@ -464,7 +463,7 @@ fn read_startup_programs(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
}
|
||||
}
|
||||
|
||||
fn read_file_associations(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
fn read_file_associations(params: &TaskParams, _rng: &mut ThreadRng, _work: &WorkData) {
|
||||
unsafe {
|
||||
let mut name_buf = [0u16; 256];
|
||||
for i in 0..params.iterations as u32 {
|
||||
@@ -482,7 +481,6 @@ fn read_file_associations(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
if result.0 != 0 {
|
||||
break;
|
||||
}
|
||||
// Only process entries starting with '.'
|
||||
if name_len > 0 && name_buf[0] == b'.' as u16 {
|
||||
black_box(&name_buf[..name_len as usize]);
|
||||
}
|
||||
|
||||
+18
-24
@@ -1,5 +1,6 @@
|
||||
use crate::categories::Categories;
|
||||
use crate::tasks::{TaskDescriptor, TaskParams};
|
||||
use crate::workdata::WorkData;
|
||||
use rand::rngs::ThreadRng;
|
||||
use rand::Rng;
|
||||
use std::hint::black_box;
|
||||
@@ -100,15 +101,13 @@ pub fn register() -> Vec<TaskDescriptor> {
|
||||
]
|
||||
}
|
||||
|
||||
// ── Existing tasks ──────────────────────────────────────────────────────────
|
||||
|
||||
unsafe extern "system" fn enum_window_callback(hwnd: HWND, lparam: LPARAM) -> BOOL {
|
||||
let windows = &mut *(lparam.0 as *mut Vec<HWND>);
|
||||
windows.push(hwnd);
|
||||
BOOL(1)
|
||||
}
|
||||
|
||||
fn enumerate_windows(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
fn enumerate_windows(params: &TaskParams, _rng: &mut ThreadRng, _work: &WorkData) {
|
||||
unsafe {
|
||||
let mut windows: Vec<HWND> = Vec::new();
|
||||
let ptr = &mut windows as *mut Vec<HWND> as isize;
|
||||
@@ -122,7 +121,7 @@ fn enumerate_windows(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
}
|
||||
}
|
||||
|
||||
fn enumerate_processes(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
fn enumerate_processes(params: &TaskParams, _rng: &mut ThreadRng, _work: &WorkData) {
|
||||
unsafe {
|
||||
let snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
||||
let snapshot = match snapshot {
|
||||
@@ -149,7 +148,7 @@ fn enumerate_processes(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
}
|
||||
}
|
||||
|
||||
fn query_system_info(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
fn query_system_info(params: &TaskParams, _rng: &mut ThreadRng, _work: &WorkData) {
|
||||
unsafe {
|
||||
for _ in 0..params.call_depth {
|
||||
let mut sys_info = SYSTEM_INFO::default();
|
||||
@@ -166,20 +165,18 @@ fn query_system_info(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
}
|
||||
}
|
||||
|
||||
fn read_clipboard(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
fn read_clipboard(params: &TaskParams, _rng: &mut ThreadRng, _work: &WorkData) {
|
||||
unsafe {
|
||||
for _ in 0..params.call_depth {
|
||||
if OpenClipboard(HWND::default()).is_ok() {
|
||||
let _ = black_box(GetClipboardData(1)); // CF_TEXT
|
||||
let _ = black_box(GetClipboardData(1));
|
||||
let _ = CloseClipboard();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── New tasks ───────────────────────────────────────────────────────────────
|
||||
|
||||
fn get_system_metrics(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
fn get_system_metrics(params: &TaskParams, rng: &mut ThreadRng, _work: &WorkData) {
|
||||
const METRIC_INDICES: [i32; 10] = [
|
||||
0, // SM_CXSCREEN
|
||||
1, // SM_CYSCREEN
|
||||
@@ -203,7 +200,7 @@ fn get_system_metrics(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_foreground_window_info(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
fn get_foreground_window_info(params: &TaskParams, _rng: &mut ThreadRng, _work: &WorkData) {
|
||||
unsafe {
|
||||
for _ in 0..params.call_depth {
|
||||
let hwnd = GetForegroundWindow();
|
||||
@@ -224,7 +221,7 @@ fn get_foreground_window_info(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_cursor_position(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
fn get_cursor_position(params: &TaskParams, _rng: &mut ThreadRng, _work: &WorkData) {
|
||||
unsafe {
|
||||
for _ in 0..params.iterations {
|
||||
let mut point = POINT::default();
|
||||
@@ -235,7 +232,7 @@ fn get_cursor_position(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_desktop_window_info(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
fn get_desktop_window_info(params: &TaskParams, _rng: &mut ThreadRng, _work: &WorkData) {
|
||||
unsafe {
|
||||
for _ in 0..params.call_depth {
|
||||
let hwnd = GetDesktopWindow();
|
||||
@@ -259,7 +256,7 @@ fn get_desktop_window_info(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_logical_drives_info(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
fn get_logical_drives_info(params: &TaskParams, _rng: &mut ThreadRng, _work: &WorkData) {
|
||||
unsafe {
|
||||
let mask = GetLogicalDrives();
|
||||
if mask == 0 {
|
||||
@@ -277,7 +274,6 @@ fn get_logical_drives_info(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
black_box(drive_type);
|
||||
}
|
||||
|
||||
// Repeat the iteration to burn more cycles based on params
|
||||
for _ in 1..params.call_depth {
|
||||
let m = GetLogicalDrives();
|
||||
black_box(m);
|
||||
@@ -285,7 +281,7 @@ fn get_logical_drives_info(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_volume_info(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
fn get_volume_info(params: &TaskParams, _rng: &mut ThreadRng, _work: &WorkData) {
|
||||
unsafe {
|
||||
for _ in 0..params.call_depth {
|
||||
let mut volume_name = [0u16; 260];
|
||||
@@ -313,7 +309,7 @@ fn get_volume_info(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_disk_free_space(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
fn get_disk_free_space(params: &TaskParams, _rng: &mut ThreadRng, _work: &WorkData) {
|
||||
unsafe {
|
||||
for _ in 0..params.call_depth {
|
||||
let mut free_bytes_available: u64 = 0;
|
||||
@@ -335,7 +331,7 @@ fn get_disk_free_space(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
}
|
||||
}
|
||||
|
||||
fn find_files_pattern(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
fn find_files_pattern(params: &TaskParams, _rng: &mut ThreadRng, _work: &WorkData) {
|
||||
unsafe {
|
||||
let mut find_data = WIN32_FIND_DATAW::default();
|
||||
let handle = FindFirstFileW(w!("C:\\Windows\\System32\\*.dll"), &mut find_data);
|
||||
@@ -361,7 +357,7 @@ fn find_files_pattern(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_module_handles(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
fn get_module_handles(params: &TaskParams, rng: &mut ThreadRng, _work: &WorkData) {
|
||||
let dll_names: &[PCWSTR] = &[
|
||||
w!("kernel32.dll"),
|
||||
w!("ntdll.dll"),
|
||||
@@ -393,7 +389,7 @@ fn get_module_handles(params: &TaskParams, rng: &mut ThreadRng) {
|
||||
}
|
||||
}
|
||||
|
||||
fn virtual_query_memory(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
fn virtual_query_memory(params: &TaskParams, _rng: &mut ThreadRng, _work: &WorkData) {
|
||||
unsafe {
|
||||
let mut addr: usize = 0;
|
||||
let info_size = std::mem::size_of::<MEMORY_BASIC_INFORMATION>();
|
||||
@@ -413,9 +409,7 @@ fn virtual_query_memory(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
black_box(info.State);
|
||||
black_box(info.Type);
|
||||
|
||||
// Advance to the next region
|
||||
let region_size = if info.RegionSize == 0 {
|
||||
// Avoid infinite loop on zero-size region
|
||||
4096
|
||||
} else {
|
||||
info.RegionSize
|
||||
@@ -428,7 +422,7 @@ fn virtual_query_memory(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_system_directories(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
fn get_system_directories(params: &TaskParams, _rng: &mut ThreadRng, _work: &WorkData) {
|
||||
unsafe {
|
||||
for _ in 0..params.call_depth {
|
||||
let mut sys_dir = [0u16; 260];
|
||||
@@ -446,7 +440,7 @@ fn get_system_directories(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_process_thread_ids(params: &TaskParams, _rng: &mut ThreadRng) {
|
||||
fn get_process_thread_ids(params: &TaskParams, _rng: &mut ThreadRng, _work: &WorkData) {
|
||||
unsafe {
|
||||
for _ in 0..params.iterations {
|
||||
let pid = GetCurrentProcessId();
|
||||
|
||||
+560
@@ -0,0 +1,560 @@
|
||||
#[derive(Clone, Default)]
|
||||
pub struct WorkData {
|
||||
bytes: Vec<u8>,
|
||||
}
|
||||
|
||||
pub trait FeedWork {
|
||||
fn write_work_bytes(&self, out: &mut Vec<u8>);
|
||||
}
|
||||
|
||||
macro_rules! impl_feed_int {
|
||||
($($t:ty),*) => {
|
||||
$(impl FeedWork for $t {
|
||||
fn write_work_bytes(&self, out: &mut Vec<u8>) {
|
||||
out.extend_from_slice(&self.to_ne_bytes());
|
||||
}
|
||||
})*
|
||||
};
|
||||
}
|
||||
|
||||
impl_feed_int!(u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize, f32, f64);
|
||||
|
||||
impl FeedWork for bool {
|
||||
fn write_work_bytes(&self, out: &mut Vec<u8>) {
|
||||
out.push(*self as u8);
|
||||
}
|
||||
}
|
||||
|
||||
impl FeedWork for str {
|
||||
fn write_work_bytes(&self, out: &mut Vec<u8>) {
|
||||
out.extend_from_slice(self.as_bytes());
|
||||
}
|
||||
}
|
||||
|
||||
impl FeedWork for String {
|
||||
fn write_work_bytes(&self, out: &mut Vec<u8>) {
|
||||
out.extend_from_slice(self.as_bytes());
|
||||
}
|
||||
}
|
||||
|
||||
impl FeedWork for [u8] {
|
||||
fn write_work_bytes(&self, out: &mut Vec<u8>) {
|
||||
out.extend_from_slice(self);
|
||||
}
|
||||
}
|
||||
|
||||
impl FeedWork for Vec<u8> {
|
||||
fn write_work_bytes(&self, out: &mut Vec<u8>) {
|
||||
out.extend_from_slice(self);
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> FeedWork for [u8; N] {
|
||||
fn write_work_bytes(&self, out: &mut Vec<u8>) {
|
||||
out.extend_from_slice(self);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: FeedWork + ?Sized> FeedWork for &T {
|
||||
fn write_work_bytes(&self, out: &mut Vec<u8>) {
|
||||
(*self).write_work_bytes(out);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: FeedWork + ?Sized> FeedWork for Box<T> {
|
||||
fn write_work_bytes(&self, out: &mut Vec<u8>) {
|
||||
(**self).write_work_bytes(out);
|
||||
}
|
||||
}
|
||||
|
||||
impl WorkData {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn feed(&mut self, data: &(impl FeedWork + ?Sized)) {
|
||||
data.write_work_bytes(&mut self.bytes);
|
||||
}
|
||||
|
||||
pub(crate) fn is_empty(&self) -> bool {
|
||||
self.bytes.is_empty()
|
||||
}
|
||||
|
||||
pub(crate) fn as_bytes(&self) -> &[u8] {
|
||||
&self.bytes
|
||||
}
|
||||
|
||||
/// XOR work bytes cyclically into buf, creating a data dependency.
|
||||
pub(crate) fn blend_into(&self, buf: &mut [u8]) {
|
||||
if self.bytes.is_empty() {
|
||||
return;
|
||||
}
|
||||
for (i, byte) in buf.iter_mut().enumerate() {
|
||||
*byte ^= self.bytes[i % self.bytes.len()];
|
||||
}
|
||||
}
|
||||
|
||||
/// Fold all bytes into a u64 seed for control-flow integration.
|
||||
pub(crate) fn blend_seed(&self) -> u64 {
|
||||
let mut seed: u64 = 0;
|
||||
for (i, &b) in self.bytes.iter().enumerate() {
|
||||
seed ^= (b as u64).wrapping_shl((i as u32).wrapping_mul(7) & 63);
|
||||
}
|
||||
seed
|
||||
}
|
||||
|
||||
/// Derive a usize from work data at the given logical index.
|
||||
pub(crate) fn derive_usize(&self, index: usize) -> usize {
|
||||
if self.bytes.is_empty() {
|
||||
return 0;
|
||||
}
|
||||
let offset = index.wrapping_mul(7) % self.bytes.len();
|
||||
let mut buf = [0u8; 8];
|
||||
for (i, slot) in buf.iter_mut().enumerate() {
|
||||
*slot = self.bytes[(offset + i) % self.bytes.len()];
|
||||
}
|
||||
usize::from_ne_bytes(buf)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
// ── Construction & feed ────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn new_is_empty() {
|
||||
let wd = WorkData::new();
|
||||
assert!(wd.is_empty());
|
||||
assert_eq!(wd.as_bytes().len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_is_empty() {
|
||||
let wd = WorkData::default();
|
||||
assert!(wd.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feed_u8() {
|
||||
let mut wd = WorkData::new();
|
||||
wd.feed(&0xABu8);
|
||||
assert_eq!(wd.as_bytes(), &[0xAB]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feed_u16() {
|
||||
let mut wd = WorkData::new();
|
||||
wd.feed(&0x1234u16);
|
||||
assert_eq!(wd.as_bytes().len(), 2);
|
||||
assert_eq!(wd.as_bytes(), &0x1234u16.to_ne_bytes());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feed_u32() {
|
||||
let mut wd = WorkData::new();
|
||||
wd.feed(&42u32);
|
||||
assert!(!wd.is_empty());
|
||||
assert_eq!(wd.as_bytes().len(), 4);
|
||||
assert_eq!(wd.as_bytes(), &42u32.to_ne_bytes());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feed_u64() {
|
||||
let mut wd = WorkData::new();
|
||||
wd.feed(&0xDEADBEEFCAFEBABEu64);
|
||||
assert_eq!(wd.as_bytes().len(), 8);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feed_u128() {
|
||||
let mut wd = WorkData::new();
|
||||
wd.feed(&1u128);
|
||||
assert_eq!(wd.as_bytes().len(), 16);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feed_usize() {
|
||||
let mut wd = WorkData::new();
|
||||
wd.feed(&999usize);
|
||||
assert_eq!(wd.as_bytes().len(), std::mem::size_of::<usize>());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feed_i8() {
|
||||
let mut wd = WorkData::new();
|
||||
wd.feed(&(-1i8));
|
||||
assert_eq!(wd.as_bytes(), &(-1i8).to_ne_bytes());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feed_i16() {
|
||||
let mut wd = WorkData::new();
|
||||
wd.feed(&(-256i16));
|
||||
assert_eq!(wd.as_bytes().len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feed_i32() {
|
||||
let mut wd = WorkData::new();
|
||||
wd.feed(&(-42i32));
|
||||
assert_eq!(wd.as_bytes(), &(-42i32).to_ne_bytes());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feed_i64() {
|
||||
let mut wd = WorkData::new();
|
||||
wd.feed(&i64::MIN);
|
||||
assert_eq!(wd.as_bytes().len(), 8);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feed_i128() {
|
||||
let mut wd = WorkData::new();
|
||||
wd.feed(&i128::MAX);
|
||||
assert_eq!(wd.as_bytes().len(), 16);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feed_isize() {
|
||||
let mut wd = WorkData::new();
|
||||
wd.feed(&(-1isize));
|
||||
assert_eq!(wd.as_bytes().len(), std::mem::size_of::<isize>());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feed_f32() {
|
||||
let mut wd = WorkData::new();
|
||||
wd.feed(&3.14f32);
|
||||
assert_eq!(wd.as_bytes().len(), 4);
|
||||
assert_eq!(wd.as_bytes(), &3.14f32.to_ne_bytes());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feed_f64() {
|
||||
let mut wd = WorkData::new();
|
||||
wd.feed(&2.718f64);
|
||||
assert_eq!(wd.as_bytes().len(), 8);
|
||||
assert_eq!(wd.as_bytes(), &2.718f64.to_ne_bytes());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feed_bool_true() {
|
||||
let mut wd = WorkData::new();
|
||||
wd.feed(&true);
|
||||
assert_eq!(wd.as_bytes(), &[1]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feed_bool_false() {
|
||||
let mut wd = WorkData::new();
|
||||
wd.feed(&false);
|
||||
assert_eq!(wd.as_bytes(), &[0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feed_str_literal() {
|
||||
let mut wd = WorkData::new();
|
||||
wd.feed("hello");
|
||||
assert_eq!(wd.as_bytes(), b"hello");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feed_string_owned() {
|
||||
let mut wd = WorkData::new();
|
||||
let s = String::from("world");
|
||||
wd.feed(&s);
|
||||
assert_eq!(wd.as_bytes(), b"world");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feed_byte_slice() {
|
||||
let mut wd = WorkData::new();
|
||||
let data: &[u8] = &[1, 2, 3];
|
||||
wd.feed(data);
|
||||
assert_eq!(wd.as_bytes(), &[1, 2, 3]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feed_vec_u8() {
|
||||
let mut wd = WorkData::new();
|
||||
let data = vec![10u8, 20, 30];
|
||||
wd.feed(&data);
|
||||
assert_eq!(wd.as_bytes(), &[10, 20, 30]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feed_byte_array() {
|
||||
let mut wd = WorkData::new();
|
||||
wd.feed(&[0xAA, 0xBB, 0xCC]);
|
||||
assert_eq!(wd.as_bytes(), &[0xAA, 0xBB, 0xCC]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feed_boxed_u32() {
|
||||
let mut wd = WorkData::new();
|
||||
let boxed: Box<u32> = Box::new(42);
|
||||
wd.feed(&boxed);
|
||||
assert_eq!(wd.as_bytes(), &42u32.to_ne_bytes());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feed_boxed_str() {
|
||||
let mut wd = WorkData::new();
|
||||
let boxed: Box<str> = "boxed".into();
|
||||
wd.feed(&boxed);
|
||||
assert_eq!(wd.as_bytes(), b"boxed");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feed_reference_delegates() {
|
||||
let mut wd1 = WorkData::new();
|
||||
wd1.feed(&42u32);
|
||||
let mut wd2 = WorkData::new();
|
||||
let val = 42u32;
|
||||
let r = &val;
|
||||
wd2.feed(r);
|
||||
assert_eq!(wd1.as_bytes(), wd2.as_bytes());
|
||||
}
|
||||
|
||||
// ── Accumulation ───────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn feed_accumulates_bytes() {
|
||||
let mut wd = WorkData::new();
|
||||
wd.feed(&1u8);
|
||||
assert_eq!(wd.as_bytes().len(), 1);
|
||||
wd.feed(&2u8);
|
||||
assert_eq!(wd.as_bytes().len(), 2);
|
||||
wd.feed("abc");
|
||||
assert_eq!(wd.as_bytes().len(), 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feed_multiple_types_concatenates() {
|
||||
let mut wd = WorkData::new();
|
||||
wd.feed(&1u8);
|
||||
wd.feed(&2u16);
|
||||
wd.feed(&3u32);
|
||||
wd.feed(&4u64);
|
||||
assert_eq!(wd.as_bytes().len(), 1 + 2 + 4 + 8);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feed_order_matters() {
|
||||
let mut wd1 = WorkData::new();
|
||||
wd1.feed(&1u8);
|
||||
wd1.feed(&2u8);
|
||||
|
||||
let mut wd2 = WorkData::new();
|
||||
wd2.feed(&2u8);
|
||||
wd2.feed(&1u8);
|
||||
|
||||
assert_ne!(wd1.as_bytes(), wd2.as_bytes());
|
||||
}
|
||||
|
||||
// ── Empty / zero-length feeds ──────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn feed_empty_string_stays_empty() {
|
||||
let mut wd = WorkData::new();
|
||||
wd.feed("");
|
||||
assert!(wd.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feed_empty_slice_stays_empty() {
|
||||
let mut wd = WorkData::new();
|
||||
let empty: &[u8] = &[];
|
||||
wd.feed(empty);
|
||||
assert!(wd.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feed_empty_vec_stays_empty() {
|
||||
let mut wd = WorkData::new();
|
||||
let empty: Vec<u8> = vec![];
|
||||
wd.feed(&empty);
|
||||
assert!(wd.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feed_zero_length_array() {
|
||||
let mut wd = WorkData::new();
|
||||
wd.feed(&[0u8; 0]);
|
||||
assert!(wd.is_empty());
|
||||
}
|
||||
|
||||
// ── blend_into ─────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn blend_into_empty_is_noop() {
|
||||
let wd = WorkData::new();
|
||||
let mut buf = [1u8, 2, 3, 4];
|
||||
wd.blend_into(&mut buf);
|
||||
assert_eq!(buf, [1, 2, 3, 4]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blend_into_xors_single_byte() {
|
||||
let mut wd = WorkData::new();
|
||||
wd.feed(&[0xFF]);
|
||||
let mut buf = [0x0F, 0xF0, 0xAA, 0x55];
|
||||
wd.blend_into(&mut buf);
|
||||
assert_eq!(buf, [0x0F ^ 0xFF, 0xF0 ^ 0xFF, 0xAA ^ 0xFF, 0x55 ^ 0xFF]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blend_into_xors_cyclically() {
|
||||
let mut wd = WorkData::new();
|
||||
wd.feed(&[0xFF, 0x00]);
|
||||
let mut buf = [0x0F, 0xF0, 0x0F, 0xF0];
|
||||
wd.blend_into(&mut buf);
|
||||
assert_eq!(buf, [0xF0, 0xF0, 0xF0, 0xF0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blend_into_larger_data_than_buf() {
|
||||
let mut wd = WorkData::new();
|
||||
wd.feed(&[0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88]);
|
||||
let mut buf = [0x00, 0x00, 0x00];
|
||||
wd.blend_into(&mut buf);
|
||||
assert_eq!(buf, [0x11, 0x22, 0x33]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blend_into_zero_key_is_noop() {
|
||||
let mut wd = WorkData::new();
|
||||
wd.feed(&[0x00, 0x00, 0x00, 0x00]);
|
||||
let mut buf = [0xAB, 0xCD, 0xEF, 0x01];
|
||||
wd.blend_into(&mut buf);
|
||||
assert_eq!(buf, [0xAB, 0xCD, 0xEF, 0x01]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blend_into_double_xor_restores_original() {
|
||||
let mut wd = WorkData::new();
|
||||
wd.feed(&[0xDE, 0xAD, 0xBE, 0xEF]);
|
||||
let original = [0x01, 0x02, 0x03, 0x04];
|
||||
let mut buf = original;
|
||||
wd.blend_into(&mut buf);
|
||||
assert_ne!(buf, original);
|
||||
wd.blend_into(&mut buf);
|
||||
assert_eq!(buf, original);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blend_into_empty_buf() {
|
||||
let mut wd = WorkData::new();
|
||||
wd.feed(&42u32);
|
||||
let mut buf: [u8; 0] = [];
|
||||
wd.blend_into(&mut buf);
|
||||
}
|
||||
|
||||
// ── blend_seed ─────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn blend_seed_empty_is_zero() {
|
||||
let wd = WorkData::new();
|
||||
assert_eq!(wd.blend_seed(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blend_seed_deterministic() {
|
||||
let mut wd1 = WorkData::new();
|
||||
wd1.feed(&42u32);
|
||||
let mut wd2 = WorkData::new();
|
||||
wd2.feed(&42u32);
|
||||
assert_eq!(wd1.blend_seed(), wd2.blend_seed());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blend_seed_differs_for_different_data() {
|
||||
let mut wd1 = WorkData::new();
|
||||
wd1.feed(&42u32);
|
||||
let mut wd2 = WorkData::new();
|
||||
wd2.feed(&43u32);
|
||||
assert_ne!(wd1.blend_seed(), wd2.blend_seed());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blend_seed_nonzero_for_nonzero_data() {
|
||||
let mut wd = WorkData::new();
|
||||
wd.feed(&0xDEADBEEFu32);
|
||||
assert_ne!(wd.blend_seed(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blend_seed_single_byte() {
|
||||
let mut wd = WorkData::new();
|
||||
wd.feed(&0xFFu8);
|
||||
assert_ne!(wd.blend_seed(), 0);
|
||||
}
|
||||
|
||||
// ── derive_usize ───────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn derive_usize_empty_is_zero() {
|
||||
let wd = WorkData::new();
|
||||
assert_eq!(wd.derive_usize(0), 0);
|
||||
assert_eq!(wd.derive_usize(1), 0);
|
||||
assert_eq!(wd.derive_usize(999), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn derive_usize_deterministic() {
|
||||
let mut wd = WorkData::new();
|
||||
wd.feed(&0xDEADBEEFu64);
|
||||
let a = wd.derive_usize(0);
|
||||
let b = wd.derive_usize(0);
|
||||
assert_eq!(a, b);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn derive_usize_varies_by_index() {
|
||||
let mut wd = WorkData::new();
|
||||
wd.feed(&[1u8, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]);
|
||||
let a = wd.derive_usize(0);
|
||||
let b = wd.derive_usize(1);
|
||||
assert_ne!(a, b);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn derive_usize_works_with_small_data() {
|
||||
let mut wd = WorkData::new();
|
||||
wd.feed(&1u8);
|
||||
let _ = wd.derive_usize(0);
|
||||
let _ = wd.derive_usize(100);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn derive_usize_large_index_no_panic() {
|
||||
let mut wd = WorkData::new();
|
||||
wd.feed(&42u32);
|
||||
let _ = wd.derive_usize(usize::MAX);
|
||||
let _ = wd.derive_usize(usize::MAX / 2);
|
||||
}
|
||||
|
||||
// ── Clone independence ─────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn clone_is_independent() {
|
||||
let mut wd = WorkData::new();
|
||||
wd.feed(&42u32);
|
||||
let mut cloned = wd.clone();
|
||||
cloned.feed(&99u32);
|
||||
assert_eq!(wd.as_bytes().len(), 4);
|
||||
assert_eq!(cloned.as_bytes().len(), 8);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clone_has_same_data() {
|
||||
let mut wd = WorkData::new();
|
||||
wd.feed(&42u32);
|
||||
wd.feed("hello");
|
||||
let cloned = wd.clone();
|
||||
assert_eq!(wd.as_bytes(), cloned.as_bytes());
|
||||
assert_eq!(wd.blend_seed(), cloned.blend_seed());
|
||||
}
|
||||
}
|
||||
+582
@@ -0,0 +1,582 @@
|
||||
use busywork::{BusyWork, Categories, Intensity};
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
// DATA ISOLATION: originals must never be modified after feed + run
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
#[test]
|
||||
fn original_u8_untouched() {
|
||||
let val: u8 = 0xFF;
|
||||
BusyWork::new(Intensity::Low).allow(Categories::COMPUTE).feed(&val).run();
|
||||
assert_eq!(val, 0xFF);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn original_u16_untouched() {
|
||||
let val: u16 = 12345;
|
||||
BusyWork::new(Intensity::Low).allow(Categories::COMPUTE).feed(&val).run();
|
||||
assert_eq!(val, 12345);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn original_u32_untouched() {
|
||||
let val: u32 = 42;
|
||||
BusyWork::new(Intensity::Low).allow(Categories::COMPUTE).feed(&val).run();
|
||||
assert_eq!(val, 42);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn original_u64_untouched() {
|
||||
let val: u64 = 0xDEAD_BEEF_CAFE_BABE;
|
||||
BusyWork::new(Intensity::Low).allow(Categories::COMPUTE).feed(&val).run();
|
||||
assert_eq!(val, 0xDEAD_BEEF_CAFE_BABE);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn original_u128_untouched() {
|
||||
let val: u128 = u128::MAX;
|
||||
BusyWork::new(Intensity::Low).allow(Categories::COMPUTE).feed(&val).run();
|
||||
assert_eq!(val, u128::MAX);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn original_i32_untouched() {
|
||||
let val: i32 = -42;
|
||||
BusyWork::new(Intensity::Low).allow(Categories::COMPUTE).feed(&val).run();
|
||||
assert_eq!(val, -42);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn original_i64_untouched() {
|
||||
let val: i64 = i64::MIN;
|
||||
BusyWork::new(Intensity::Low).allow(Categories::COMPUTE).feed(&val).run();
|
||||
assert_eq!(val, i64::MIN);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn original_f32_untouched() {
|
||||
let val: f32 = std::f32::consts::PI;
|
||||
BusyWork::new(Intensity::Low).allow(Categories::COMPUTE).feed(&val).run();
|
||||
assert_eq!(val, std::f32::consts::PI);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn original_f64_untouched() {
|
||||
let val: f64 = std::f64::consts::E;
|
||||
BusyWork::new(Intensity::Low).allow(Categories::COMPUTE).feed(&val).run();
|
||||
assert_eq!(val, std::f64::consts::E);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn original_bool_untouched() {
|
||||
let val = true;
|
||||
BusyWork::new(Intensity::Low).allow(Categories::COMPUTE).feed(&val).run();
|
||||
assert!(val);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn original_string_untouched() {
|
||||
let val = String::from("sensitive_data_do_not_modify");
|
||||
BusyWork::new(Intensity::Low).allow(Categories::COMPUTE).feed(&val).run();
|
||||
assert_eq!(val, "sensitive_data_do_not_modify");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn original_vec_untouched() {
|
||||
let val = vec![1u8, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
||||
BusyWork::new(Intensity::Low).allow(Categories::MEMORY).feed(&val).run();
|
||||
assert_eq!(val, vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn original_array_untouched() {
|
||||
let val: [u8; 8] = [0xCA, 0xFE, 0xBA, 0xBE, 0xDE, 0xAD, 0xBE, 0xEF];
|
||||
BusyWork::new(Intensity::Low).allow(Categories::COMPUTE).feed(&val).run();
|
||||
assert_eq!(val, [0xCA, 0xFE, 0xBA, 0xBE, 0xDE, 0xAD, 0xBE, 0xEF]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn original_multiple_vars_untouched() {
|
||||
let a: u32 = 42;
|
||||
let b = String::from("hello");
|
||||
let c = vec![0xAAu8, 0xBB];
|
||||
let d: f64 = 3.14;
|
||||
let e = true;
|
||||
|
||||
BusyWork::new(Intensity::Low)
|
||||
.allow(Categories::COMPUTE | Categories::MEMORY)
|
||||
.feed(&a)
|
||||
.feed(&b)
|
||||
.feed(&c)
|
||||
.feed(&d)
|
||||
.feed(&e)
|
||||
.run();
|
||||
|
||||
assert_eq!(a, 42);
|
||||
assert_eq!(b, "hello");
|
||||
assert_eq!(c, vec![0xAA, 0xBB]);
|
||||
assert_eq!(d, 3.14);
|
||||
assert!(e);
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
// PER-CATEGORY SMOKE WITH FEED DATA
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
fn sample_feed() -> BusyWork {
|
||||
BusyWork::new(Intensity::Low)
|
||||
.feed(&42u32)
|
||||
.feed(&0xDEADBEEFu64)
|
||||
.feed("test_context_data")
|
||||
.feed(&vec![0xAAu8, 0xBB, 0xCC, 0xDD])
|
||||
.feed(&3.14f64)
|
||||
.feed(&true)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feed_compute_no_panic() {
|
||||
sample_feed().allow(Categories::COMPUTE).run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feed_memory_no_panic() {
|
||||
sample_feed().allow(Categories::MEMORY).run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feed_filesystem_no_panic() {
|
||||
sample_feed().allow(Categories::FILESYSTEM).run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feed_registry_no_panic() {
|
||||
sample_feed().allow(Categories::REGISTRY).run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feed_winapi_no_panic() {
|
||||
sample_feed().allow(Categories::WINAPI).run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feed_crypto_no_panic() {
|
||||
sample_feed().allow(Categories::CRYPTO).run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feed_all_categories_no_panic() {
|
||||
sample_feed().deny(Categories::NETWORK).run();
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
// INTENSITY LEVELS WITH FEED DATA
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
#[test]
|
||||
fn feed_low_compute() {
|
||||
BusyWork::new(Intensity::Low)
|
||||
.allow(Categories::COMPUTE)
|
||||
.feed(&42u32)
|
||||
.feed("low_test")
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feed_low_memory() {
|
||||
BusyWork::new(Intensity::Low)
|
||||
.allow(Categories::MEMORY)
|
||||
.feed(&42u32)
|
||||
.feed(&vec![1u8, 2, 3, 4])
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feed_medium_compute() {
|
||||
BusyWork::new(Intensity::Medium)
|
||||
.allow(Categories::COMPUTE)
|
||||
.feed(&42u32)
|
||||
.feed("medium_test")
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feed_medium_memory() {
|
||||
BusyWork::new(Intensity::Medium)
|
||||
.allow(Categories::MEMORY)
|
||||
.feed(&0xCAFEBABEu32)
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feed_high_compute() {
|
||||
BusyWork::new(Intensity::High)
|
||||
.allow(Categories::COMPUTE)
|
||||
.feed(&42u32)
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feed_high_memory() {
|
||||
BusyWork::new(Intensity::High)
|
||||
.allow(Categories::MEMORY)
|
||||
.feed(&42u32)
|
||||
.run();
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
// EDGE CASES
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
#[test]
|
||||
fn feed_empty_string() {
|
||||
BusyWork::new(Intensity::Low)
|
||||
.allow(Categories::COMPUTE)
|
||||
.feed("")
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feed_empty_vec() {
|
||||
let empty: Vec<u8> = vec![];
|
||||
BusyWork::new(Intensity::Low)
|
||||
.allow(Categories::COMPUTE)
|
||||
.feed(&empty)
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feed_single_byte() {
|
||||
BusyWork::new(Intensity::Low)
|
||||
.allow(Categories::COMPUTE)
|
||||
.feed(&0xFFu8)
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feed_all_zeros() {
|
||||
BusyWork::new(Intensity::Low)
|
||||
.allow(Categories::COMPUTE | Categories::MEMORY)
|
||||
.feed(&0u8)
|
||||
.feed(&0u16)
|
||||
.feed(&0u32)
|
||||
.feed(&0u64)
|
||||
.feed(&0.0f32)
|
||||
.feed(&0.0f64)
|
||||
.feed(&false)
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feed_max_values() {
|
||||
BusyWork::new(Intensity::Low)
|
||||
.allow(Categories::COMPUTE)
|
||||
.feed(&u8::MAX)
|
||||
.feed(&u16::MAX)
|
||||
.feed(&u32::MAX)
|
||||
.feed(&u64::MAX)
|
||||
.feed(&i8::MIN)
|
||||
.feed(&i16::MIN)
|
||||
.feed(&i32::MIN)
|
||||
.feed(&i64::MIN)
|
||||
.feed(&f64::MAX)
|
||||
.feed(&f64::MIN)
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feed_special_floats() {
|
||||
BusyWork::new(Intensity::Low)
|
||||
.allow(Categories::COMPUTE)
|
||||
.feed(&f32::NAN)
|
||||
.feed(&f64::INFINITY)
|
||||
.feed(&f64::NEG_INFINITY)
|
||||
.feed(&f64::MIN_POSITIVE)
|
||||
.feed(&f64::EPSILON)
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feed_large_data_64kb() {
|
||||
let big = vec![0xABu8; 64 * 1024];
|
||||
BusyWork::new(Intensity::Low)
|
||||
.allow(Categories::COMPUTE | Categories::MEMORY)
|
||||
.feed(&big)
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feed_same_value_repeated() {
|
||||
let val = 42u32;
|
||||
BusyWork::new(Intensity::Low)
|
||||
.allow(Categories::COMPUTE)
|
||||
.feed(&val)
|
||||
.feed(&val)
|
||||
.feed(&val)
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feed_256_u8_values() {
|
||||
let mut builder = BusyWork::new(Intensity::Low).allow(Categories::COMPUTE);
|
||||
for i in 0u8..=255 {
|
||||
builder = builder.feed(&i);
|
||||
}
|
||||
builder.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feed_100_u64_values() {
|
||||
let mut builder = BusyWork::new(Intensity::Low).allow(Categories::COMPUTE);
|
||||
for i in 0u64..100 {
|
||||
builder = builder.feed(&i);
|
||||
}
|
||||
builder.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feed_long_string() {
|
||||
let long = "A".repeat(10_000);
|
||||
BusyWork::new(Intensity::Low)
|
||||
.allow(Categories::COMPUTE)
|
||||
.feed(&long)
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feed_unicode_string() {
|
||||
BusyWork::new(Intensity::Low)
|
||||
.allow(Categories::COMPUTE)
|
||||
.feed("Ünïcödé Strïñg 日本語 中文 🎉")
|
||||
.run();
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
// BUILDER COMBINATIONS
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
#[test]
|
||||
fn feed_with_deny() {
|
||||
BusyWork::new(Intensity::Medium)
|
||||
.deny(Categories::NETWORK)
|
||||
.feed(&42u32)
|
||||
.feed("context")
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feed_with_allow_and_deny() {
|
||||
BusyWork::new(Intensity::Medium)
|
||||
.allow(Categories::COMPUTE | Categories::MEMORY | Categories::NETWORK)
|
||||
.deny(Categories::NETWORK)
|
||||
.feed(&42u32)
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feed_with_jitter_off() {
|
||||
BusyWork::new(Intensity::Low)
|
||||
.allow(Categories::COMPUTE)
|
||||
.jitter(false)
|
||||
.feed(&42u32)
|
||||
.feed("deterministic")
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feed_with_jitter_on() {
|
||||
BusyWork::new(Intensity::Low)
|
||||
.allow(Categories::COMPUTE)
|
||||
.jitter(true)
|
||||
.feed(&42u32)
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feed_with_empty_categories() {
|
||||
BusyWork::new(Intensity::Low)
|
||||
.allow(Categories::empty())
|
||||
.feed(&42u32)
|
||||
.feed("should be safe even with no categories")
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feed_before_allow() {
|
||||
BusyWork::new(Intensity::Low)
|
||||
.feed(&42u32)
|
||||
.allow(Categories::COMPUTE)
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feed_after_allow() {
|
||||
BusyWork::new(Intensity::Low)
|
||||
.allow(Categories::COMPUTE)
|
||||
.feed(&42u32)
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feed_interleaved_with_builder_options() {
|
||||
BusyWork::new(Intensity::Low)
|
||||
.feed(&1u32)
|
||||
.allow(Categories::COMPUTE | Categories::MEMORY)
|
||||
.feed(&2u64)
|
||||
.deny(Categories::NETWORK)
|
||||
.feed("interleaved")
|
||||
.jitter(false)
|
||||
.feed(&3.14f64)
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feed_only_deny_no_allow() {
|
||||
BusyWork::new(Intensity::Low)
|
||||
.deny(Categories::NETWORK | Categories::REGISTRY)
|
||||
.feed(&42u32)
|
||||
.run();
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
// TYPE COVERAGE: every FeedWork implementation path
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
#[test]
|
||||
fn feed_all_unsigned_integers() {
|
||||
BusyWork::new(Intensity::Low)
|
||||
.allow(Categories::COMPUTE)
|
||||
.feed(&1u8)
|
||||
.feed(&2u16)
|
||||
.feed(&3u32)
|
||||
.feed(&4u64)
|
||||
.feed(&5u128)
|
||||
.feed(&6usize)
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feed_all_signed_integers() {
|
||||
BusyWork::new(Intensity::Low)
|
||||
.allow(Categories::COMPUTE)
|
||||
.feed(&(-1i8))
|
||||
.feed(&(-2i16))
|
||||
.feed(&(-3i32))
|
||||
.feed(&(-4i64))
|
||||
.feed(&(-5i128))
|
||||
.feed(&(-6isize))
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feed_all_float_types() {
|
||||
BusyWork::new(Intensity::Low)
|
||||
.allow(Categories::COMPUTE)
|
||||
.feed(&1.0f32)
|
||||
.feed(&2.0f64)
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feed_str_literal_directly() {
|
||||
BusyWork::new(Intensity::Low)
|
||||
.allow(Categories::COMPUTE)
|
||||
.feed("literal")
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feed_string_by_ref() {
|
||||
let s = String::from("owned");
|
||||
BusyWork::new(Intensity::Low)
|
||||
.allow(Categories::COMPUTE)
|
||||
.feed(&s)
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feed_byte_slice_from_vec() {
|
||||
let v = vec![1u8, 2, 3];
|
||||
let slice: &[u8] = &v;
|
||||
BusyWork::new(Intensity::Low)
|
||||
.allow(Categories::COMPUTE)
|
||||
.feed(slice)
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feed_fixed_array() {
|
||||
let arr: [u8; 16] = [0; 16];
|
||||
BusyWork::new(Intensity::Low)
|
||||
.allow(Categories::COMPUTE)
|
||||
.feed(&arr)
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feed_boxed_value() {
|
||||
let boxed: Box<u32> = Box::new(42);
|
||||
BusyWork::new(Intensity::Low)
|
||||
.allow(Categories::COMPUTE)
|
||||
.feed(&boxed)
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feed_boxed_bytes() {
|
||||
let boxed: Box<[u8]> = vec![1u8, 2, 3].into_boxed_slice();
|
||||
BusyWork::new(Intensity::Low)
|
||||
.allow(Categories::COMPUTE)
|
||||
.feed(&boxed)
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feed_double_reference() {
|
||||
let val = 42u32;
|
||||
let ref1 = &val;
|
||||
BusyWork::new(Intensity::Low)
|
||||
.allow(Categories::COMPUTE)
|
||||
.feed(ref1)
|
||||
.run();
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
// WITHOUT FEED: existing behavior unbroken
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
#[test]
|
||||
fn no_feed_compute() {
|
||||
BusyWork::new(Intensity::Low).allow(Categories::COMPUTE).run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_feed_memory() {
|
||||
BusyWork::new(Intensity::Low).allow(Categories::MEMORY).run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_feed_all_categories() {
|
||||
BusyWork::new(Intensity::Low).deny(Categories::NETWORK).run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_feed_with_jitter_off() {
|
||||
BusyWork::new(Intensity::Low)
|
||||
.allow(Categories::COMPUTE)
|
||||
.jitter(false)
|
||||
.run();
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
// NETWORK (separate, slower due to timeouts)
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn feed_network_no_panic() {
|
||||
BusyWork::new(Intensity::Low)
|
||||
.allow(Categories::NETWORK)
|
||||
.feed(&80u16)
|
||||
.feed("network_context")
|
||||
.run();
|
||||
}
|
||||
@@ -0,0 +1,303 @@
|
||||
use busywork::{BusyWork, Categories, FeedWork, Intensity, WorkData};
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
// STRESS: rapid sequential calls with varying data
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
#[test]
|
||||
fn stress_500_calls_varying_u32() {
|
||||
for i in 0u32..500 {
|
||||
BusyWork::new(Intensity::Low)
|
||||
.allow(Categories::COMPUTE)
|
||||
.jitter(false)
|
||||
.feed(&i)
|
||||
.run();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stress_200_calls_varying_bytes() {
|
||||
for i in 0u16..200 {
|
||||
let data = i.to_ne_bytes();
|
||||
BusyWork::new(Intensity::Low)
|
||||
.allow(Categories::COMPUTE | Categories::MEMORY)
|
||||
.feed(&data)
|
||||
.run();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stress_100_calls_same_data() {
|
||||
let data = vec![0xABu8; 256];
|
||||
for _ in 0..100 {
|
||||
BusyWork::new(Intensity::Low)
|
||||
.allow(Categories::COMPUTE)
|
||||
.feed(&data)
|
||||
.run();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stress_50_calls_growing_data() {
|
||||
for size in 0..50 {
|
||||
let data = vec![0xCDu8; size * 100];
|
||||
BusyWork::new(Intensity::Low)
|
||||
.allow(Categories::COMPUTE)
|
||||
.feed(&data)
|
||||
.run();
|
||||
}
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
// EXTREME DATA SIZES
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
#[test]
|
||||
fn extreme_1mb_feed() {
|
||||
let big = vec![0xEFu8; 1024 * 1024];
|
||||
BusyWork::new(Intensity::Low)
|
||||
.allow(Categories::COMPUTE | Categories::MEMORY)
|
||||
.feed(&big)
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extreme_10000_small_feeds() {
|
||||
let mut builder = BusyWork::new(Intensity::Low).allow(Categories::COMPUTE);
|
||||
for i in 0u16..10_000 {
|
||||
builder = builder.feed(&i);
|
||||
}
|
||||
builder.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extreme_feed_then_empty_categories() {
|
||||
let big = vec![0xFFu8; 64 * 1024];
|
||||
BusyWork::new(Intensity::Ultra)
|
||||
.allow(Categories::empty())
|
||||
.feed(&big)
|
||||
.run();
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
// WORKDATA DIRECT API
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
#[test]
|
||||
fn workdata_feed_many_types_no_panic() {
|
||||
let mut wd = WorkData::new();
|
||||
wd.feed(&0u8);
|
||||
wd.feed(&0u16);
|
||||
wd.feed(&0u32);
|
||||
wd.feed(&0u64);
|
||||
wd.feed(&0u128);
|
||||
wd.feed(&0usize);
|
||||
wd.feed(&0i8);
|
||||
wd.feed(&0i16);
|
||||
wd.feed(&0i32);
|
||||
wd.feed(&0i64);
|
||||
wd.feed(&0i128);
|
||||
wd.feed(&0isize);
|
||||
wd.feed(&0.0f32);
|
||||
wd.feed(&0.0f64);
|
||||
wd.feed(&false);
|
||||
wd.feed(&true);
|
||||
wd.feed("");
|
||||
wd.feed("hello");
|
||||
wd.feed(&String::from("world"));
|
||||
let v = vec![1u8, 2, 3];
|
||||
wd.feed(&v);
|
||||
let s: &[u8] = &[4, 5, 6];
|
||||
wd.feed(s);
|
||||
wd.feed(&[7u8, 8, 9]);
|
||||
let boxed: Box<u32> = Box::new(42);
|
||||
wd.feed(&boxed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn workdata_clone_divergence() {
|
||||
let mut a = WorkData::new();
|
||||
a.feed(&42u32);
|
||||
let mut b = a.clone();
|
||||
b.feed(&99u32);
|
||||
// After divergence, using both should be safe
|
||||
BusyWork::new(Intensity::Low)
|
||||
.allow(Categories::COMPUTE)
|
||||
.feed(&42u32)
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn workdata_reuse_across_runs() {
|
||||
let mut wd = WorkData::new();
|
||||
wd.feed(&42u32);
|
||||
wd.feed("persistent_context");
|
||||
|
||||
for _ in 0..10 {
|
||||
let mut builder = BusyWork::new(Intensity::Low).allow(Categories::COMPUTE);
|
||||
// Re-feed same data (simulating reuse pattern)
|
||||
builder = builder.feed(&42u32);
|
||||
builder = builder.feed("persistent_context");
|
||||
builder.run();
|
||||
}
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
// CUSTOM TYPE: user implements FeedWork for their own struct
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
struct GameState {
|
||||
player_x: f32,
|
||||
player_y: f32,
|
||||
health: u32,
|
||||
score: u64,
|
||||
}
|
||||
|
||||
impl FeedWork for GameState {
|
||||
fn write_work_bytes(&self, out: &mut Vec<u8>) {
|
||||
self.player_x.write_work_bytes(out);
|
||||
self.player_y.write_work_bytes(out);
|
||||
self.health.write_work_bytes(out);
|
||||
self.score.write_work_bytes(out);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn custom_type_feed() {
|
||||
let state = GameState {
|
||||
player_x: 100.5,
|
||||
player_y: -200.3,
|
||||
health: 75,
|
||||
score: 12345678,
|
||||
};
|
||||
BusyWork::new(Intensity::Low)
|
||||
.allow(Categories::COMPUTE)
|
||||
.feed(&state)
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn custom_type_original_untouched() {
|
||||
let state = GameState {
|
||||
player_x: 100.5,
|
||||
player_y: -200.3,
|
||||
health: 75,
|
||||
score: 12345678,
|
||||
};
|
||||
BusyWork::new(Intensity::Low)
|
||||
.allow(Categories::COMPUTE)
|
||||
.feed(&state)
|
||||
.run();
|
||||
assert_eq!(state.player_x, 100.5);
|
||||
assert_eq!(state.player_y, -200.3);
|
||||
assert_eq!(state.health, 75);
|
||||
assert_eq!(state.score, 12345678);
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
// MIXED CATEGORIES WITH FEED
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
#[test]
|
||||
fn feed_compute_and_memory() {
|
||||
BusyWork::new(Intensity::Medium)
|
||||
.allow(Categories::COMPUTE | Categories::MEMORY)
|
||||
.feed(&42u32)
|
||||
.feed(&vec![0xAB; 128])
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feed_compute_and_filesystem() {
|
||||
BusyWork::new(Intensity::Low)
|
||||
.allow(Categories::COMPUTE | Categories::FILESYSTEM)
|
||||
.feed(&42u32)
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feed_compute_and_crypto() {
|
||||
BusyWork::new(Intensity::Low)
|
||||
.allow(Categories::COMPUTE | Categories::CRYPTO)
|
||||
.feed(&42u32)
|
||||
.feed("crypto_context")
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feed_memory_and_crypto() {
|
||||
BusyWork::new(Intensity::Low)
|
||||
.allow(Categories::MEMORY | Categories::CRYPTO)
|
||||
.feed(&0xCAFEBABEu32)
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feed_all_except_network() {
|
||||
BusyWork::new(Intensity::Medium)
|
||||
.deny(Categories::NETWORK)
|
||||
.feed(&42u32)
|
||||
.feed("full_context_test")
|
||||
.feed(&vec![1u8, 2, 3, 4, 5])
|
||||
.feed(&3.14f64)
|
||||
.run();
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
// REGRESSION: specific patterns that could trigger bugs
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
#[test]
|
||||
fn regression_single_zero_byte_feed() {
|
||||
BusyWork::new(Intensity::Low)
|
||||
.allow(Categories::COMPUTE | Categories::MEMORY)
|
||||
.feed(&0u8)
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regression_feed_after_empty_feed() {
|
||||
BusyWork::new(Intensity::Low)
|
||||
.allow(Categories::COMPUTE)
|
||||
.feed(&Vec::<u8>::new())
|
||||
.feed(&42u32)
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regression_many_empty_feeds_then_real() {
|
||||
let mut builder = BusyWork::new(Intensity::Low).allow(Categories::COMPUTE);
|
||||
for _ in 0..100 {
|
||||
builder = builder.feed("");
|
||||
}
|
||||
builder = builder.feed(&42u32);
|
||||
builder.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regression_alternating_empty_and_data() {
|
||||
let mut builder = BusyWork::new(Intensity::Low).allow(Categories::COMPUTE);
|
||||
for i in 0u32..50 {
|
||||
builder = builder.feed("");
|
||||
builder = builder.feed(&i);
|
||||
builder = builder.feed(&Vec::<u8>::new());
|
||||
}
|
||||
builder.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regression_max_u64_feed() {
|
||||
BusyWork::new(Intensity::Low)
|
||||
.allow(Categories::COMPUTE)
|
||||
.feed(&u64::MAX)
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regression_all_ones_large() {
|
||||
let data = vec![0xFFu8; 8192];
|
||||
BusyWork::new(Intensity::Low)
|
||||
.allow(Categories::COMPUTE | Categories::MEMORY)
|
||||
.feed(&data)
|
||||
.run();
|
||||
}
|
||||
Reference in New Issue
Block a user