Skip to content

Commit 9910c7d

Browse files
Add integration tests
1 parent c977587 commit 9910c7d

File tree

3 files changed

+275
-0
lines changed

3 files changed

+275
-0
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ tree-sitter-mozjs = { path = "./tree-sitter-mozjs", version = "=0.19.0" }
3939

4040
[dev-dependencies]
4141
pretty_assertions = "^1.0"
42+
serde_json = { version = "^1.0", features = ["arbitrary_precision"] }
4243

4344
[workspace]
4445
members = ["rust-code-analysis-cli", "rust-code-analysis-web"]

tests/test.rs

Lines changed: 273 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,273 @@
1+
use globset::GlobSet;
2+
use globset::{Glob, GlobSetBuilder};
3+
use rust_code_analysis::LANG;
4+
use rust_code_analysis::*;
5+
use std::fs::*;
6+
use std::io::BufReader;
7+
use std::path::Path;
8+
use std::path::PathBuf;
9+
use std::process;
10+
use std::process::Command;
11+
use std::sync::Once;
12+
13+
// Synchronization primitive
14+
static INIT: Once = Once::new();
15+
16+
#[derive(Debug)]
17+
struct Config {
18+
language: Option<LANG>,
19+
output_folder: String,
20+
}
21+
22+
fn act_on_file(path: PathBuf, cfg: &Config) -> std::io::Result<()> {
23+
// Open file
24+
let source = if let Some(source) = read_file_with_eol(&path)? {
25+
source
26+
} else {
27+
return Ok(());
28+
};
29+
30+
// Guess programming language
31+
let language = if let Some(language) = cfg.language {
32+
language
33+
} else if let Some(language) = guess_language(&source, &path).0 {
34+
language
35+
} else {
36+
return Ok(());
37+
};
38+
39+
// Get FuncSpace struct
40+
let funcspace_struct = get_function_spaces(&language, source, &path, None).unwrap();
41+
42+
// Build json file path
43+
let file_path = Path::new(&cfg.output_folder)
44+
.join(path.strip_prefix("./").unwrap())
45+
.into_os_string()
46+
.into_string()
47+
.unwrap()
48+
+ ".json";
49+
50+
// Get Value struct from json file
51+
let file = File::open(file_path)?;
52+
let reader = BufReader::new(file);
53+
let value_struct = serde_json::from_reader(reader)?;
54+
55+
// Compare the 2 structs
56+
compare_structs(&funcspace_struct, &value_struct);
57+
58+
Ok(())
59+
}
60+
61+
/// Compares a FuncSpace and a Value field by field
62+
fn compare_structs(funcspace: &FuncSpace, value: &serde_json::Value) {
63+
let fsname = funcspace
64+
.name
65+
.as_deref()
66+
.unwrap_or_default()
67+
.replace("\r", "");
68+
let name1 = Path::new(&fsname);
69+
let name2 = Path::new(value["name"].as_str().unwrap_or_default());
70+
assert_eq!(name1, name2);
71+
72+
let start_line1 = funcspace.start_line;
73+
let start_line2 = value["start_line"].as_u64().unwrap() as usize;
74+
assert_eq!(start_line1, start_line2);
75+
76+
let end_line1 = funcspace.end_line;
77+
let end_line2 = value["end_line"].as_u64().unwrap() as usize;
78+
assert_eq!(end_line1, end_line2);
79+
80+
let kind1 = &format!("{}", funcspace.kind);
81+
let kind2 = value["kind"].as_str().unwrap_or_default();
82+
assert_eq!(kind1, kind2);
83+
84+
let metrics1 = &funcspace.metrics;
85+
let metrics2 = &value["metrics"];
86+
87+
let nargs1 = &metrics1.nargs;
88+
let nargs2 = &metrics2["nargs"];
89+
compare_f64(nargs1.fn_args_sum(), &nargs2["total_functions"]);
90+
compare_f64(nargs1.closure_args_sum(), &nargs2["total_closures"]);
91+
compare_f64(nargs1.fn_args_average(), &nargs2["average_functions"]);
92+
compare_f64(nargs1.closure_args_average(), &nargs2["average_closures"]);
93+
compare_f64(nargs1.nargs_total(), &nargs2["total"]);
94+
compare_f64(nargs1.nargs_average(), &nargs2["average"]);
95+
compare_f64(nargs1.fn_args_min(), &nargs2["functions_min"]);
96+
compare_f64(nargs1.fn_args_max(), &nargs2["functions_max"]);
97+
compare_f64(nargs1.closure_args_min(), &nargs2["closures_min"]);
98+
compare_f64(nargs1.closure_args_max(), &nargs2["closures_max"]);
99+
100+
let nexits1 = &metrics1.nexits;
101+
let nexits2 = &metrics2["nexits"];
102+
compare_f64(nexits1.exit(), &nexits2["sum"]);
103+
compare_f64(nexits1.exit_average(), &nexits2["average"]);
104+
compare_f64(nexits1.exit(), &nexits2["min"]);
105+
compare_f64(nexits1.exit_average(), &nexits2["max"]);
106+
107+
let cognitive1 = &metrics1.cognitive;
108+
let cognitive2 = &metrics2["cognitive"];
109+
compare_f64(cognitive1.cognitive_sum(), &cognitive2["sum"]);
110+
compare_f64(cognitive1.cognitive_average(), &cognitive2["average"]);
111+
compare_f64(cognitive1.cognitive_min(), &cognitive2["min"]);
112+
compare_f64(cognitive1.cognitive_max(), &cognitive2["max"]);
113+
114+
let cyclomatic1 = &metrics1.cyclomatic;
115+
let cyclomatic2 = &metrics2["cyclomatic"];
116+
compare_f64(cyclomatic1.cyclomatic_sum(), &cyclomatic2["sum"]);
117+
compare_f64(cyclomatic1.cyclomatic_average(), &cyclomatic2["average"]);
118+
compare_f64(cyclomatic1.cyclomatic_min(), &cyclomatic2["min"]);
119+
compare_f64(cyclomatic1.cyclomatic_max(), &cyclomatic2["max"]);
120+
121+
let halstead1 = &metrics1.halstead;
122+
let halstead2 = &metrics2["halstead"];
123+
compare_f64(halstead1.u_operators(), &halstead2["n1"]);
124+
compare_f64(halstead1.operators(), &halstead2["N1"]);
125+
compare_f64(halstead1.u_operands(), &halstead2["n2"]);
126+
compare_f64(halstead1.operands(), &halstead2["N2"]);
127+
compare_f64(halstead1.length(), &halstead2["length"]);
128+
compare_f64(
129+
halstead1.estimated_program_length(),
130+
&halstead2["estimated_program_length"],
131+
);
132+
compare_f64(halstead1.purity_ratio(), &halstead2["purity_ratio"]);
133+
compare_f64(halstead1.vocabulary(), &halstead2["vocabulary"]);
134+
compare_f64(halstead1.volume(), &halstead2["volume"]);
135+
compare_f64(halstead1.difficulty(), &halstead2["difficulty"]);
136+
compare_f64(halstead1.level(), &halstead2["level"]);
137+
compare_f64(halstead1.effort(), &halstead2["effort"]);
138+
compare_f64(halstead1.time(), &halstead2["time"]);
139+
compare_f64(halstead1.bugs(), &halstead2["bugs"]);
140+
141+
let loc1 = &metrics1.loc;
142+
let loc2 = &metrics2["loc"];
143+
compare_f64(loc1.sloc(), &loc2["sloc"]);
144+
compare_f64(loc1.ploc(), &loc2["ploc"]);
145+
compare_f64(loc1.lloc(), &loc2["lloc"]);
146+
compare_f64(loc1.cloc(), &loc2["cloc"]);
147+
compare_f64(loc1.blank(), &loc2["blank"]);
148+
149+
let nom1 = &metrics1.nom;
150+
let nom2 = &metrics2["nom"];
151+
compare_f64(nom1.functions_sum(), &nom2["functions"]);
152+
compare_f64(nom1.closures_sum(), &nom2["closures"]);
153+
compare_f64(nom1.total(), &nom2["total"]);
154+
compare_f64(nom1.functions_min(), &nom2["functions_min"]);
155+
compare_f64(nom1.functions_max(), &nom2["functions_max"]);
156+
compare_f64(nom1.closures_min(), &nom2["closures_min"]);
157+
compare_f64(nom1.closures_max(), &nom2["closures_max"]);
158+
159+
let mi1 = &metrics1.mi;
160+
let mi2 = &metrics2["mi"];
161+
compare_f64(mi1.mi_original(), &mi2["mi_original"]);
162+
compare_f64(mi1.mi_sei(), &mi2["mi_sei"]);
163+
compare_f64(mi1.mi_visual_studio(), &mi2["mi_visual_studio"]);
164+
165+
// Recursion
166+
for (pos, s) in funcspace.spaces.iter().enumerate() {
167+
compare_structs(s, &value["spaces"][pos]);
168+
}
169+
}
170+
171+
/// Compares two f64 values truncated to 3 decimals
172+
fn compare_f64(f1: f64, f2: &serde_json::Value) {
173+
if f1.is_nan() || f1.is_infinite() {
174+
assert!(f2.is_null());
175+
} else {
176+
let ft1 = f64::trunc(f1 * 1000.0) / 1000.0;
177+
let ft2 = f64::trunc(f2.as_f64().unwrap() * 1000.0) / 1000.0;
178+
assert!((ft1 - ft2).abs() < f64::EPSILON);
179+
}
180+
}
181+
182+
const OUTPUT_FOLDER: &str = "./tests/repositories/rca-output";
183+
184+
/// Produces metrics runtime and compares them with previously generated json files
185+
fn compare_rca_output_with_files(
186+
repo_branch: &str,
187+
repo_url: &str,
188+
repo_folder: &str,
189+
include: &[&str],
190+
) {
191+
// The first test clones the repository
192+
// Next tests wait here until the repository is cloned
193+
INIT.call_once(|| {
194+
clone_repository(
195+
"main",
196+
"https://github.com/SoftengPoliTo/rca-output.git",
197+
OUTPUT_FOLDER,
198+
);
199+
});
200+
201+
clone_repository(repo_branch, repo_url, repo_folder);
202+
203+
let num_jobs = 4;
204+
205+
let cfg = Config {
206+
language: None,
207+
output_folder: OUTPUT_FOLDER.to_owned(),
208+
};
209+
210+
let mut gsbi = GlobSetBuilder::new();
211+
for file in include {
212+
gsbi.add(Glob::new(file).unwrap());
213+
}
214+
215+
let files_data = FilesData {
216+
include: gsbi.build().unwrap(),
217+
exclude: GlobSet::empty(),
218+
paths: vec![repo_folder.to_owned()],
219+
};
220+
221+
if let Err(e) = ConcurrentRunner::new(num_jobs, act_on_file).run(cfg, files_data) {
222+
eprintln!("{:?}", e);
223+
process::exit(1);
224+
}
225+
}
226+
227+
/// Runs a git clone command
228+
fn clone_repository(branch: &str, url: &str, destination: &str) {
229+
if !Path::new(destination).exists() {
230+
Command::new("git")
231+
.args([
232+
"clone",
233+
"--depth",
234+
"1",
235+
"--branch",
236+
branch,
237+
url,
238+
destination,
239+
])
240+
.output()
241+
.expect("Git clone failed");
242+
}
243+
}
244+
245+
#[test]
246+
fn test_deepspeech() {
247+
compare_rca_output_with_files(
248+
"v0.9.3",
249+
"https://github.com/mozilla/DeepSpeech.git",
250+
"./tests/repositories/DeepSpeech",
251+
&["*.cc", "*.cpp", "*.h", "*.hh"],
252+
);
253+
}
254+
255+
#[test]
256+
fn test_grcov() {
257+
compare_rca_output_with_files(
258+
"v0.8.2",
259+
"https://github.com/mozilla/grcov.git",
260+
"./tests/repositories/grcov",
261+
&["*.rs"],
262+
);
263+
}
264+
265+
#[test]
266+
fn test_pdfjs() {
267+
compare_rca_output_with_files(
268+
"v2.12.313",
269+
"https://github.com/mozilla/pdf.js.git",
270+
"./tests/repositories/pdf.js",
271+
&["*.js"],
272+
);
273+
}

0 commit comments

Comments
 (0)