Skip to content

Commit a03a78e

Browse files
committed
Add some testing
* top level test_data dir has sub dirs for scenarios, two have been created (single cargo, two python) * restructure kondo/main into kondo/main and lib so integ test can run * unit test: kondo/discover and kondo-lib/clean * integration test: cli run of "kondo -- --version" command * infrastructure for creating a test project to run kondo in destructivly
1 parent 69c153b commit a03a78e

File tree

26 files changed

+755
-144
lines changed

26 files changed

+755
-144
lines changed

Cargo.lock

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

kondo-lib/Cargo.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,10 @@ license = "MIT"
1111
[dependencies]
1212
ignore = "0.4.18"
1313
walkdir = "2"
14+
15+
[dev-dependencies]
16+
# need recursive copy for testing
17+
fs_extra = "1.3.0"
18+
19+
# need temporary test dirs
20+
tempfile = "3.8.0"

kondo-lib/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
mod lib_test;
2+
13
use std::{
24
borrow::Cow,
35
error::{self, Error},

kondo-lib/src/lib_test.rs

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
#[cfg(test)]
2+
mod test {
3+
4+
use crate::{Project, ProjectType, ScanOptions};
5+
use std::{io::Write, path::PathBuf};
6+
7+
// Given test data, clean should remove some files
8+
#[test]
9+
fn test_clean() {
10+
let scan_options: ScanOptions = ScanOptions {
11+
follow_symlinks: false,
12+
same_file_system: true,
13+
};
14+
15+
let tempdir = create_fake_python_project("test_data".to_string());
16+
let path = tempdir.path().join("test_data");
17+
18+
println!("path: {:?}", path);
19+
println!("tempdir: {:?}", tempdir.path());
20+
21+
let project_a = Project {
22+
path,
23+
project_type: ProjectType::Python,
24+
};
25+
26+
assert!(
27+
project_a.size(&scan_options) > 0,
28+
"size of project ought to be greater than 0"
29+
);
30+
assert!(project_a.path.exists(), "project ought to exist");
31+
32+
// Run clean and check before and after that file exists and is deleted
33+
assert!(
34+
project_a.path.join("__pycache__/cache.data").exists(),
35+
"cache file ought to exist"
36+
);
37+
Project::clean(&project_a);
38+
assert!(
39+
!project_a.path.join("__pycache__/cache.data").exists(),
40+
"cache file should have been deleted"
41+
);
42+
43+
assert!(project_a.path.exists(), "project ought to still exist");
44+
45+
// clean up
46+
tempdir.close().unwrap();
47+
}
48+
49+
// #[ignore = "this is probably "]
50+
#[test]
51+
fn test_clean_nested_python_projects() {
52+
// make alpha project
53+
let alpha_tmp_dir = create_fake_python_project("alpha".to_string());
54+
55+
// inside of alpha, make nested project
56+
let project_nested_dir = create_fake_python_project_in_dir(
57+
alpha_tmp_dir.path().clone().to_path_buf(),
58+
"nested".to_string(),
59+
);
60+
61+
// Given alpha project
62+
let project_alpha = Project {
63+
path: alpha_tmp_dir.into_path(),
64+
project_type: ProjectType::Python,
65+
};
66+
// and nested project
67+
let project_nested = Project {
68+
path: project_nested_dir.clone(),
69+
project_type: ProjectType::Python,
70+
};
71+
72+
// Clean!
73+
Project::clean(&project_alpha);
74+
Project::clean(&project_nested);
75+
// Both project dirs exist
76+
assert!(
77+
project_alpha.path.exists(),
78+
"project alpha ought to still exist"
79+
);
80+
assert!(
81+
project_nested_dir.exists(),
82+
"nested project ought to still exist"
83+
);
84+
85+
// Both cache files are gone
86+
assert!(
87+
!project_alpha.path.join("__pycache__/cache.data").exists(),
88+
"cache file of alpha should have been deleted"
89+
);
90+
assert!(
91+
!project_nested_dir.join("__pycache__/cache.data").exists(),
92+
"cache file of nested project should have been deleted"
93+
);
94+
}
95+
// TODO: this code is duplicated at konod/src/main.rs
96+
// Given a name, create a new simulated python project in a safe to delete directry
97+
pub fn create_fake_python_project(name: String) -> tempfile::TempDir {
98+
// Make a new project in a temporary directory
99+
let tmp_dir = tempfile::tempdir().unwrap();
100+
create_fake_python_project_in_dir(tmp_dir.path().to_path_buf(), name);
101+
tmp_dir
102+
}
103+
104+
pub fn create_fake_python_project_in_dir(dir: PathBuf, name: String) -> PathBuf {
105+
// make a new root in the dir
106+
let project_dir = dir.join(name);
107+
std::fs::create_dir_all(&project_dir).unwrap();
108+
109+
// Must have a directory to hold the project.
110+
let cache_dir = project_dir.join("__pycache__");
111+
std::fs::create_dir(&cache_dir).unwrap();
112+
113+
// Must have data in the cache to delete
114+
let mut data_file = std::fs::File::create(cache_dir.join("cache.data")).unwrap();
115+
data_file.write_all(b"#oodles of cache')\n").unwrap();
116+
let mut data_file_b = std::fs::File::create(cache_dir.join("other.cache")).unwrap();
117+
data_file_b.write_all(b"#oodles of cache')\n").unwrap();
118+
119+
// and a file of type .py to signal we're a python project
120+
let mut python_file = std::fs::File::create(project_dir.join("main.py")).unwrap();
121+
python_file
122+
.write_all(b"#!/bin/python\n\nprint('Hello, world!')\n")
123+
.unwrap();
124+
project_dir.to_path_buf()
125+
}
126+
}

kondo/Cargo.toml

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,28 @@ keywords = ["clean", "cleanup", "delete", "free"]
1717
exclude = ["test_dir"]
1818
edition = "2021"
1919

20-
2120
[dependencies]
2221
clap = { version = "4", features = ["derive"] }
2322
clap_complete = "4"
2423

24+
[dev-dependencies]
25+
# need recursive copy for testing
26+
fs_extra = "1.3.0"
27+
28+
# need temporary test dirs
29+
tempfile = "3.8.0"
30+
2531
[dependencies.kondo-lib]
2632
path = "../kondo-lib"
2733
version = "0.7"
34+
35+
[lib]
36+
# rlib here is important for getting our integ test to run
37+
# https://github.com/rust-lang/cargo/issues/6659
38+
crate-type = ["rlib", "staticlib", "cdylib"]
39+
bench = false
40+
41+
[[bin]]
42+
name = "kondo"
43+
test = true
44+
bench = false

kondo/src/lib.rs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
use std::{error::Error, fmt, num::ParseIntError};
2+
3+
#[derive(Debug)]
4+
pub enum ParseAgeFilterError {
5+
ParseIntError(ParseIntError),
6+
InvalidUnit,
7+
}
8+
9+
impl fmt::Display for ParseAgeFilterError {
10+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
11+
match self {
12+
ParseAgeFilterError::ParseIntError(e) => e.fmt(f),
13+
ParseAgeFilterError::InvalidUnit => {
14+
"invalid age unit, must be one of m, h, d, w, M, y".fmt(f)
15+
}
16+
}
17+
}
18+
}
19+
20+
impl From<ParseIntError> for ParseAgeFilterError {
21+
fn from(e: ParseIntError) -> Self {
22+
Self::ParseIntError(e)
23+
}
24+
}
25+
26+
impl Error for ParseAgeFilterError {}
27+
28+
pub fn parse_age_filter(age_filter: &str) -> Result<u64, ParseAgeFilterError> {
29+
const MINUTE: u64 = 60;
30+
const HOUR: u64 = MINUTE * 60;
31+
const DAY: u64 = HOUR * 24;
32+
const WEEK: u64 = DAY * 7;
33+
const MONTH: u64 = WEEK * 4;
34+
const YEAR: u64 = DAY * 365;
35+
36+
let (digit_end, unit) = age_filter
37+
.char_indices()
38+
.last()
39+
.ok_or(ParseAgeFilterError::InvalidUnit)?;
40+
41+
let multiplier = match unit {
42+
'm' => MINUTE,
43+
'h' => HOUR,
44+
'd' => DAY,
45+
'w' => WEEK,
46+
'M' => MONTH,
47+
'y' => YEAR,
48+
_ => return Err(ParseAgeFilterError::InvalidUnit),
49+
};
50+
51+
let count = age_filter[..digit_end].parse::<u64>()?;
52+
let seconds = count * multiplier;
53+
Ok(seconds)
54+
}
55+
56+
#[test]
57+
fn test_age_filter_120s() {
58+
let hours = parse_age_filter("2h").unwrap();
59+
let minutes = parse_age_filter("120m").unwrap();
60+
61+
assert_eq!(minutes, hours);
62+
}
63+
#[test]
64+
fn test_age_filter_10m() {
65+
let res = parse_age_filter("10m");
66+
let age_filter = res.unwrap();
67+
assert_eq!(age_filter, (60 * 10));
68+
}
69+
70+
#[ignore = "failing unexpectedly. BUG?"]
71+
#[test]
72+
fn test_age_filter_year_months() {
73+
let year = parse_age_filter("1y").unwrap();
74+
let months = parse_age_filter("12M").unwrap();
75+
76+
assert_eq!(year, months);
77+
}

0 commit comments

Comments
 (0)