Skip to content

Commit fd706fb

Browse files
Add integration tests
1 parent 4fa58ce commit fd706fb

File tree

3 files changed

+278
-0
lines changed

3 files changed

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

0 commit comments

Comments
 (0)