1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
#![allow(clippy::unwrap_used)]
use codewalk::{CodeWalker, WalkConfig};
use std::fs;
use std::sync::{Arc, Barrier};
use std::thread;
#[test]
fn test_concurrent_stress() {
let num_threads = 32;
let barrier = Arc::new(Barrier::new(num_threads + 1));
// Set up a shared directory with some files
let dir = Arc::new(tempfile::tempdir().unwrap());
// Create an initial tree
for i in 0..10 {
let nested_dir = dir.path().join(format!("dir_{i}"));
fs::create_dir_all(&nested_dir).unwrap();
for j in 0..10 {
let file_path = nested_dir.join(format!("file_{j}.txt"));
fs::write(&file_path, format!("Content of {i}-{j}")).unwrap();
}
}
let mut handles = vec![];
for t_id in 0..num_threads {
let b = Arc::clone(&barrier);
let d = Arc::clone(&dir);
handles.push(thread::spawn(move || {
// Wait for all threads to be ready
b.wait();
// Depending on the thread, do different things to stress the engine
if t_id % 4 == 0 {
// Mutator thread: continually creates and deletes a file
let path = d.path().join(format!("volatile_{t_id}.txt"));
for _ in 0..50 {
if let Err(e) = fs::write(&path, "volatile data") {
assert!(
matches!(
e.kind(),
std::io::ErrorKind::NotFound | std::io::ErrorKind::PermissionDenied
),
"Expected valid IO error, got {:?}",
e
);
}
if let Err(e) = fs::remove_file(&path) {
assert!(
matches!(
e.kind(),
std::io::ErrorKind::NotFound | std::io::ErrorKind::PermissionDenied
),
"Expected valid IO error, got {:?}",
e
);
}
}
} else if t_id % 4 == 1 {
// Mutator thread: deep nesting
let path = d.path().join(format!("deep_nest_{t_id}"));
if let Err(e) = fs::create_dir(&path) {
assert!(
matches!(
e.kind(),
std::io::ErrorKind::AlreadyExists | std::io::ErrorKind::NotFound
),
"Expected valid IO error, got {:?}",
e
);
}
if let Err(e) = fs::write(path.join("deep.txt"), "deep") {
assert!(
matches!(
e.kind(),
std::io::ErrorKind::NotFound | std::io::ErrorKind::PermissionDenied
),
"Expected valid IO error, got {:?}",
e
);
}
if let Err(e) = fs::remove_file(path.join("deep.txt")) {
assert!(
matches!(
e.kind(),
std::io::ErrorKind::NotFound | std::io::ErrorKind::PermissionDenied
),
"Expected valid IO error, got {:?}",
e
);
}
if let Err(e) = fs::remove_dir(&path) {
assert!(
matches!(
e.kind(),
std::io::ErrorKind::NotFound
| std::io::ErrorKind::DirectoryNotEmpty
| std::io::ErrorKind::PermissionDenied
),
"Expected valid IO error, got {:?}",
e
);
}
} else {
// Reader thread: walk the tree
let config = WalkConfig::default();
let walker = CodeWalker::new(d.path(), config);
let walk_result = walker.walk();
// Concurrent mutation might cause walk to fail with a NotFound IO error.
// We handle it gracefully.
if let Err(e) = walk_result {
assert!(
matches!(
e,
codewalk::error::CodewalkError::Io(_)
| codewalk::error::CodewalkError::Ignore(_)
),
"Expected IO or Ignore error during concurrent walk, got {:?}",
e
);
return;
}
let entries = walk_result.unwrap();
// Assert something meaningful
assert!(
!entries.is_empty(),
"Thread {} found 0 entries, expected at least the static ones",
t_id
);
// Stress the content reader
for entry in entries.iter().take(5) {
let content_res = entry.content();
// We don't use `let _ = ...`, we explicitly match. It can fail if mutator thread deleted it.
match content_res {
Ok(content) => {
assert!(!content.is_empty(), "Read empty content where not expected");
}
Err(e) => {
assert!(
matches!(e, codewalk::error::CodewalkError::Io(_)),
"Expected IO error for volatile file, got {:?}",
e
);
}
}
}
}
}));
}
// Release the threads!
barrier.wait();
// Wait for all threads to finish
for handle in handles {
let res = handle.join();
assert!(res.is_ok(), "Thread panicked during stress test");
}
}