Skip to content

Commit 2e683a3

Browse files
committed
test: add proptests for LruCache
1 parent b6d9604 commit 2e683a3

File tree

3 files changed

+184
-7
lines changed

3 files changed

+184
-7
lines changed

Cargo.lock

Lines changed: 79 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

stacks-common/Cargo.toml

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,25 @@
11
[package]
22
name = "stacks-common"
33
version = "0.0.1"
4-
authors = [ "Jude Nelson <[email protected]>",
5-
"Aaron Blankstein <[email protected]>",
6-
"Ludo Galabru <[email protected]>" ]
4+
authors = [
5+
"Jude Nelson <[email protected]>",
6+
"Aaron Blankstein <[email protected]>",
7+
"Ludo Galabru <[email protected]>",
8+
]
79
license = "GPLv3"
810
homepage = "https://github.com/blockstack/stacks-blockchain"
911
repository = "https://github.com/blockstack/stacks-blockchain"
1012
description = "Common modules for blockstack_lib, libclarity"
11-
keywords = [ "stacks", "stx", "bitcoin", "crypto", "blockstack", "decentralized", "dapps", "blockchain" ]
13+
keywords = [
14+
"stacks",
15+
"stx",
16+
"bitcoin",
17+
"crypto",
18+
"blockstack",
19+
"decentralized",
20+
"dapps",
21+
"blockchain",
22+
]
1223
readme = "README.md"
1324
resolver = "2"
1425
edition = "2021"
@@ -25,7 +36,7 @@ serde_derive = "1"
2536
sha3 = "0.10.1"
2637
ripemd = "0.1.1"
2738
lazy_static = "1.4.0"
28-
slog = { version = "2.5.2", features = [ "max_level_trace" ] }
39+
slog = { version = "2.5.2", features = ["max_level_trace"] }
2940
slog-term = "2.6.0"
3041
slog-json = { version = "2.3.0", optional = true }
3142
chrono = "0.4.19"
@@ -37,7 +48,12 @@ rusqlite = { workspace = true, optional = true }
3748
nix = "0.23"
3849

3950
[target.'cfg(windows)'.dependencies]
40-
winapi = { version = "0.3", features = ["consoleapi", "handleapi", "synchapi", "winbase"] }
51+
winapi = { version = "0.3", features = [
52+
"consoleapi",
53+
"handleapi",
54+
"synchapi",
55+
"winbase",
56+
] }
4157

4258
[target.'cfg(windows)'.dev-dependencies]
4359
winapi = { version = "0.3", features = ["fileapi", "processenv", "winnt"] }
@@ -63,6 +79,7 @@ features = ["std"]
6379

6480
[dev-dependencies]
6581
rand_core = { workspace = true }
82+
proptest = "1.6.0"
6683

6784
[features]
6885
default = ["developer-mode"]

stacks-common/src/util/lru_cache.rs

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,3 +305,85 @@ mod tests {
305305
assert_eq!(flushed, [(3, 3), (2, 2)]);
306306
}
307307
}
308+
309+
#[cfg(test)]
310+
mod property_tests {
311+
use proptest::prelude::*;
312+
313+
use super::*;
314+
315+
#[derive(Debug, Clone)]
316+
enum CacheOp {
317+
Insert(u32),
318+
Get(u32),
319+
InsertClean(u32),
320+
Flush,
321+
}
322+
323+
prop_compose! {
324+
fn arbitrary_op()(op_type in 0..4, value in 0..100u32) -> CacheOp {
325+
match op_type {
326+
0 => CacheOp::Insert(value),
327+
1 => CacheOp::Get(value),
328+
2 => CacheOp::InsertClean(value),
329+
_ => CacheOp::Flush,
330+
}
331+
}
332+
}
333+
334+
proptest! {
335+
#![proptest_config(ProptestConfig::with_cases(1_000_000))]
336+
337+
#[test]
338+
fn doesnt_crash_with_random_operations(ops in prop::collection::vec(arbitrary_op(), 1..1000)) {
339+
let mut cache = LruCache::new(10);
340+
for op in ops {
341+
match op {
342+
CacheOp::Insert(v) => { cache.insert(v, v); }
343+
CacheOp::Get(v) => { cache.get(&v); }
344+
CacheOp::InsertClean(v) => { cache.insert_clean(v, v); }
345+
CacheOp::Flush => { cache.flush(|_, _| Ok::<(), ()>(())).unwrap(); }
346+
}
347+
}
348+
}
349+
350+
#[test]
351+
fn maintains_size_invariant(ops in prop::collection::vec(0..100u32, 1..1000)) {
352+
let capacity = 10;
353+
let mut cache = LruCache::new(capacity);
354+
for op in ops {
355+
cache.insert(op, op);
356+
prop_assert!(cache.cache.len() <= capacity);
357+
prop_assert!(cache.order.len() <= capacity);
358+
}
359+
}
360+
361+
#[test]
362+
fn maintains_lru_order(ops in prop::collection::vec(arbitrary_op(), 1..1000)) {
363+
let mut cache = LruCache::new(10);
364+
for op in ops {
365+
match op {
366+
CacheOp::Insert(v) => { cache.insert(v, v); }
367+
CacheOp::Get(v) => { cache.get(&v); }
368+
CacheOp::InsertClean(v) => { cache.insert_clean(v, v); }
369+
CacheOp::Flush => { cache.flush(|_, _| Ok::<(), ()>(())).unwrap(); }
370+
}
371+
// Verify linked list integrity
372+
if !cache.order.is_empty() {
373+
let mut curr = cache.head;
374+
let mut count = 0;
375+
while curr != cache.capacity {
376+
if count >= cache.order.len() {
377+
prop_assert!(false, "Linked list cycle detected");
378+
}
379+
if cache.order[curr].next != cache.capacity {
380+
prop_assert_eq!(cache.order[cache.order[curr].next].prev, curr);
381+
}
382+
curr = cache.order[curr].next;
383+
count += 1;
384+
}
385+
}
386+
}
387+
}
388+
}
389+
}

0 commit comments

Comments
 (0)