Skip to content

Commit 157a5b9

Browse files
committed
improved fuzz testing, check cache failures
1 parent 24aef72 commit 157a5b9

File tree

6 files changed

+145
-32
lines changed

6 files changed

+145
-32
lines changed

fuzz-tests/src/bindings/fs_tests_backend.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@ impl FsTestsBackendCanister {
9797
)
9898
}
9999
}
100+
pub const CANISTER_ID: Principal =
101+
Principal::from_slice(&[255, 255, 255, 255, 255, 224, 0, 2, 1, 1]); // lz3um-vp777-77777-aaaba-cai
100102

101103
pub fn new(caller: &super::Caller, canister_id: Principal) -> FsTestsBackendCanister {
102104
FsTestsBackendCanister {
@@ -120,7 +122,7 @@ pub fn deploy(deployer: &super::Deployer) -> super::DeployBuilder<FsTestsBackend
120122
}
121123
}
122124
pub fn canister_id() -> Option<Principal> {
123-
None
125+
Some(Principal::from_text("lz3um-vp777-77777-aaaba-cai").unwrap())
124126
}
125127

126128
pub fn wasm() -> Option<Vec<u8>> {

fuzz-tests/src/tests.rs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::{fs::OpenOptions, io::Write};
22

33
use ic_test::IcpTest;
44

5-
use crate::test_setup;
5+
use crate::{bindings::fs_tests_backend, test_setup};
66

77
#[tokio::test]
88
async fn test_basic_fs_check() {
@@ -21,7 +21,20 @@ async fn test_fs_durability() {
2121

2222
let env = test_setup::setup(IcpTest::new().await).await;
2323

24-
let c = env.fs_tests_backend.do_fs_test().call().await;
24+
let icp_user = env.icp_test.icp.test_user(0);
25+
26+
let backend = env.fs_tests_backend;
27+
28+
let _c = backend.do_fs_test().call().await;
29+
30+
// re-deploy
31+
let backend = fs_tests_backend::deploy(&icp_user)
32+
.with_upgrade()
33+
.call()
34+
.await;
35+
36+
let c = backend.do_fs_test().call().await;
37+
2538
let computed = c.trim();
2639

2740
let e = std::fs::read_to_string("../target/release/report.txt").unwrap();
@@ -32,7 +45,7 @@ async fn test_fs_durability() {
3245
let expected_log = expected_log_.trim();
3346

3447
if computed != expected {
35-
let computed_log = env.fs_tests_backend.get_log().call().await;
48+
let computed_log = backend.get_log().call().await;
3649

3750
// write scans and logs into a separate files for comparisons
3851
let mut a = OpenOptions::new()

ic-test.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
"candid_path": "test/canisters/fs_tests/src/fs_tests_backend/fs_tests_backend.did",
3030
"generate_bindings": true,
3131
"wasm": "target/wasm32-wasip1/release/fs_tests_backend_nowasi.wasm",
32-
"specified_id": null
32+
"specified_id": "lz3um-vp777-77777-aaaba-cai"
3333
}
3434
}
3535
}

test/canisters/fs_tests/src/fs_tests_backend/src/canister.rs

Lines changed: 58 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use std::{
1414
collections::BTreeMap,
1515
fs::{self, FileType, Permissions},
1616
io::{Seek, SeekFrom},
17+
path::PathBuf,
1718
};
1819

1920
pub struct SimpleRng {
@@ -69,12 +70,21 @@ pub fn get_seed() -> u64 {
6970
}
7071

7172
pub(crate) fn new_log(path: &str) {
72-
let file = fs::OpenOptions::new()
73-
.create(true)
74-
.write(true)
75-
.truncate(true)
76-
.open(path)
77-
.unwrap();
73+
let p = std::path::Path::new(path);
74+
75+
let file = if p.exists() {
76+
fs::OpenOptions::new()
77+
.create(false)
78+
.append(true)
79+
.open(path)
80+
.unwrap()
81+
} else {
82+
fs::OpenOptions::new()
83+
.create(true)
84+
.append(true)
85+
.open(path)
86+
.unwrap()
87+
};
7888

7989
LOG.with(|log| {
8090
*log.borrow_mut() = Some(file);
@@ -112,6 +122,11 @@ fn init() {
112122
});
113123
}
114124

125+
#[ic_cdk::post_upgrade]
126+
fn upgrade() {
127+
init();
128+
}
129+
115130
use sha2::{Digest, Sha256};
116131
use std::fs::File;
117132
use std::io::BufReader;
@@ -265,7 +280,28 @@ pub fn get_log() -> String {
265280
fn get_random_file(
266281
parent_path: &std::path::Path,
267282
op_count: u64,
283+
opened_files: &mut BTreeMap<String, File>,
268284
) -> anyhow::Result<std::path::PathBuf> {
285+
// sometimes return an opened file
286+
if next_rand(2) < 1 {
287+
let len = opened_files.len();
288+
let file = opened_files.iter().nth(next_rand(len as u64) as usize);
289+
290+
if let Some(f) = file {
291+
let path = PathBuf::new().join(f.0);
292+
293+
// just recreate the file, if it doesn't exist
294+
let _res = fs::OpenOptions::new()
295+
.write(true)
296+
.append(false)
297+
.truncate(false)
298+
.create(true)
299+
.open(&path);
300+
301+
return Ok(path);
302+
}
303+
}
304+
269305
let mut files: Vec<fs::DirEntry> = fs::read_dir(parent_path)?
270306
.filter_map(Result::ok)
271307
.filter(|e| e.path().is_file() && e.file_name().to_string_lossy() != "log.txt")
@@ -289,6 +325,9 @@ fn get_random_file(
289325

290326
// no files, create a new one for writing
291327
let path = parent_path.join(format!("file{op_count}.txt"));
328+
329+
File::create(&path).unwrap();
330+
292331
Ok(path)
293332
}
294333

@@ -354,6 +393,7 @@ fn generate_random_file_structure(
354393
max_depth: u64,
355394

356395
parent_path: &std::path::Path,
396+
357397
opened_files: &mut BTreeMap<String, File>,
358398
) -> anyhow::Result<u64> {
359399
let depth = depth + 1;
@@ -375,7 +415,7 @@ fn generate_random_file_structure(
375415
// Create a new file
376416
let path = parent_path.join(format!("file{current_op}.txt"));
377417
log(&format!("Open or create file {path:?}"));
378-
let mut file = File::create(&path)?;
418+
let mut file = File::create(&path).unwrap();
379419
file.flush()?;
380420
opened_files.insert(path.as_path().to_string_lossy().to_string(), file);
381421
}
@@ -446,7 +486,7 @@ fn generate_random_file_structure(
446486
}
447487
4 => {
448488
// Read text from a random file
449-
let file = get_random_file(parent_path, current_op)?;
489+
let file = get_random_file(parent_path, current_op, opened_files)?;
450490

451491
if file.exists() {
452492
//
@@ -469,7 +509,7 @@ fn generate_random_file_structure(
469509
}
470510
5 => {
471511
// Truncate file (delete its contents)
472-
let file = get_random_file(parent_path, current_op)?;
512+
let file = get_random_file(parent_path, current_op, opened_files)?;
473513

474514
log(&format!("Truncate {file:?}"));
475515

@@ -483,7 +523,8 @@ fn generate_random_file_structure(
483523
}
484524
6 => {
485525
// Rename file
486-
let from = get_random_file(parent_path, current_op)?;
526+
let from = get_random_file(parent_path, current_op, opened_files)?;
527+
487528
let to = parent_path.join(format!("file{current_op}_renamed.txt"));
488529
log(&format!("Rename file from {from:?} to {to:?}"));
489530
let res = fs::rename(&from, &to);
@@ -500,7 +541,8 @@ fn generate_random_file_structure(
500541
}
501542
7 => {
502543
// Copy file
503-
let from = get_random_file(parent_path, current_op)?;
544+
let from = get_random_file(parent_path, current_op, opened_files)?;
545+
504546
let to = parent_path.join(format!("file{current_op}_copy.txt"));
505547
log(&format!("Copy file from {from:?} to {to:?}"));
506548
let res = fs::copy(&from, &to);
@@ -510,7 +552,7 @@ fn generate_random_file_structure(
510552
}
511553
8 => {
512554
// Delete file
513-
let path = get_random_file(parent_path, current_op)?;
555+
let path = get_random_file(parent_path, current_op, opened_files)?;
514556

515557
// we do not have dangling file support yet, so do not try to delete an opened file...
516558
if opened_files.contains_key(&path.as_path().to_string_lossy().to_string()) {
@@ -584,7 +626,8 @@ fn generate_random_file_structure(
584626
.create(true)
585627
.open(&save_path)?;
586628

587-
let path = get_random_file(parent_path, current_op)?;
629+
let path = get_random_file(parent_path, current_op, opened_files)?;
630+
588631
let meta = fs::metadata(&path)?;
589632

590633
save.write_all(
@@ -634,7 +677,7 @@ fn generate_random_file_structure(
634677
}
635678
15 => {
636679
// Move file into subdirectory
637-
let from = get_random_file(parent_path, current_op)?;
680+
let from = get_random_file(parent_path, current_op, opened_files)?;
638681
let filename = from.file_name().unwrap().to_string_lossy().to_string();
639682

640683
let dir = get_random_dir(parent_path, current_op)?;
@@ -647,7 +690,7 @@ fn generate_random_file_structure(
647690
}
648691
16 => {
649692
// write some prepared text into one of the files at a random position
650-
let path = get_random_file(parent_path, current_op)?;
693+
let path = get_random_file(parent_path, current_op, opened_files)?;
651694

652695
let mut save = fs::OpenOptions::new()
653696
.write(true)

test/canisters/fs_tests/src/fs_tests_backend/src/main.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ fn main() {
1111

1212
env::set_current_dir("playground").unwrap();
1313

14+
// double fs_test call (for the canister upgrade imitation)
15+
let _scan = canister::do_fs_test();
16+
//
1417
let scan = canister::do_fs_test();
1518

1619
println!("{scan}");
Lines changed: 64 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,22 @@
1-
use std::{fs::File, io::Write};
1+
use std::{
2+
fs::File,
3+
io::{Seek, SeekFrom, Write},
4+
};
25

36
use crate::canister::{generate_random_fs, new_log, scan_directory};
47

58
pub fn do_fs_test_interal() -> String {
69
new_log("log.txt");
7-
generate_random_fs(5, 200, 20);
8-
//failing_test();
10+
generate_random_fs(42, 3500, 20);
11+
//failing_dangling_file_test();
12+
//open_file_test();
13+
//cache_failure_test();
914

1015
scan_directory(".".to_string())
1116
}
1217

1318
// we don't have dangling file support yet
19+
#[allow(dead_code)]
1420
pub fn failing_dangling_file_test() {
1521
let mut file_a = File::create("a.txt").unwrap();
1622
file_a.flush().unwrap();
@@ -21,15 +27,61 @@ pub fn failing_dangling_file_test() {
2127
}
2228
}
2329

30+
#[allow(dead_code)]
2431
pub fn open_file_test() {
25-
//truncate=false,write=false,append=false,create=true
2632
// special combination fails
27-
let res = std::fs::OpenOptions::new()
28-
.write(false)
29-
.read(true)
30-
.append(true)
31-
.truncate(false)
32-
.create(true)
33-
.open("some_file.txt")
34-
.unwrap();
33+
34+
for i in 0..32 {
35+
let r = i % 2;
36+
let w = (i >> 1) % 2;
37+
let a = (i >> 2) % 2;
38+
let t = (i >> 3) % 2;
39+
let c = (i >> 4) % 2;
40+
41+
let fname = format!("r{r}w{w}a{a}t{t}c{c}.txt");
42+
43+
let _r = std::fs::OpenOptions::new()
44+
.read(r == 1)
45+
.write(w == 1)
46+
.append(a == 1)
47+
.truncate(t == 1)
48+
.create(c == 1)
49+
.open(&fname);
50+
}
51+
}
52+
53+
#[allow(dead_code)]
54+
pub fn cache_failure_test() {
55+
let filename = "cache.txt";
56+
let offset_step = 1024 * 64; // 64K - guarantees different chunks of every setup
57+
58+
{
59+
// initial write
60+
let mut file = std::fs::OpenOptions::new()
61+
.read(true)
62+
.write(true)
63+
.create(true)
64+
.truncate(true)
65+
.open(filename)
66+
.unwrap();
67+
68+
let _ = file.seek(SeekFrom::Start(offset_step));
69+
let _ = file.write_all(&[2]);
70+
}
71+
72+
{
73+
// truncate file
74+
let mut file = std::fs::OpenOptions::new()
75+
.read(true)
76+
.write(true)
77+
.create(true)
78+
.truncate(true)
79+
.open(filename)
80+
.unwrap();
81+
82+
let _ = file.seek(SeekFrom::Start(2 * offset_step));
83+
let _ = file.write_all(&[7]);
84+
let _ = file.seek(SeekFrom::Start(offset_step));
85+
let _ = file.write_all(&[6]);
86+
}
3587
}

0 commit comments

Comments
 (0)