diff --git a/Cargo.lock b/Cargo.lock
index caf08406..7fe57c85 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1017,6 +1017,7 @@ dependencies = [
"tracing",
"tsify-next",
"uuid",
+ "walkdir",
"wasm-bindgen",
]
diff --git a/cli-tests/src/upload.rs b/cli-tests/src/upload.rs
index 13f7ec92..498f2f3e 100644
--- a/cli-tests/src/upload.rs
+++ b/cli-tests/src/upload.rs
@@ -21,7 +21,7 @@ use lazy_static::lazy_static;
use predicates::prelude::*;
use pretty_assertions::assert_eq;
use prost::Message;
-use tempfile::tempdir;
+use tempfile::{tempdir, TempDir};
#[cfg(target_os = "macos")]
use test_utils::inputs::unpack_archive_to_dir;
use test_utils::{
@@ -39,9 +39,9 @@ use crate::utils::{
// NOTE: must be multi threaded to start a mock server
#[tokio::test(flavor = "multi_thread")]
async fn upload_bundle() {
- let temp_dir = tempdir().unwrap();
+ let temp_dir = TempDir::with_prefix("not-hidden").unwrap();
generate_mock_git_repo(&temp_dir);
- generate_mock_valid_junit_xmls(&temp_dir);
+ generate_mock_valid_junit_xmls(temp_dir.path());
generate_mock_codeowners(&temp_dir);
let state = MockServerBuilder::new().spawn_mock_server().await;
@@ -1403,7 +1403,7 @@ enum CreateBundleResponse {
}
#[tokio::test(flavor = "multi_thread")]
-async fn do_not_quarantines_tests_when_quarantine_disabled_set() {
+async fn do_not_quarantine_tests_when_quarantine_disabled_set() {
let temp_dir = tempdir().unwrap();
generate_mock_git_repo(&temp_dir);
generate_mock_valid_junit_xmls(&temp_dir);
@@ -1626,6 +1626,66 @@ async fn uses_passed_exit_code_if_unquarantined_tests_fail() {
println!("{assert}");
}
+#[tokio::test(flavor = "multi_thread")]
+async fn uploaded_file_contains_updated_test_files() {
+ let temp_dir = TempDir::with_prefix("not_hidden").unwrap();
+ generate_mock_git_repo(&temp_dir);
+
+ let inner_dir = temp_dir.path().join("inner_dir");
+ fs::create_dir(inner_dir).unwrap();
+
+ let test_location = temp_dir.path().join("inner_dir").join("test_file.ts");
+ let mut test_file = fs::File::create(test_location).unwrap();
+ write!(test_file, r#"it("does stuff", x)"#).unwrap();
+
+ let junit_location = temp_dir.path().join("junit_file.xml");
+ let mut junit_file = fs::File::create(junit_location).unwrap();
+ write!(junit_file, r#"
+
+
+
+
+
+
+
+ "#).unwrap();
+
+ let state = MockServerBuilder::new().spawn_mock_server().await;
+ let assert = CommandBuilder::upload(temp_dir.path(), state.host.clone())
+ .junit_paths("junit_file.xml")
+ .command()
+ .assert()
+ .success();
+
+ let requests = state.requests.lock().unwrap().clone();
+ assert_eq!(requests.len(), 3);
+
+ let file_upload = assert_matches!(requests.get(1).unwrap(), RequestPayload::S3Upload(d) => d);
+ let file = fs::File::open(file_upload.join("meta.json")).unwrap();
+ let reader = BufReader::new(file);
+ let bundle_meta: BundleMeta = serde_json::from_reader(reader).unwrap();
+ let internal_bundled_file = bundle_meta.internal_bundled_file.as_ref().unwrap();
+ let bin = fs::read(file_upload.join(&internal_bundled_file.path)).unwrap();
+ let report = proto::test_context::test_run::TestResult::decode(&*bin).unwrap();
+
+ assert_eq!(report.test_case_runs.len(), 1);
+ let test_case_run = &report.test_case_runs.first().unwrap();
+ assert_eq!(test_case_run.classname, "test_file.ts");
+ let expected_file = temp_dir
+ .path()
+ .canonicalize()
+ .unwrap()
+ .join("inner_dir")
+ .join("test_file.ts")
+ .to_str()
+ .unwrap()
+ .to_string();
+ assert_eq!(test_case_run.file, String::from("test_file.ts"));
+ assert_eq!(test_case_run.detected_file, expected_file);
+
+ println!("{assert}");
+}
+
#[tokio::test(flavor = "multi_thread")]
async fn does_not_print_exit_code_with_validation_reports_none() {
let temp_dir = tempdir().unwrap();
diff --git a/cli-tests/src/utils.rs b/cli-tests/src/utils.rs
index 3335b62b..5fdcccc3 100644
--- a/cli-tests/src/utils.rs
+++ b/cli-tests/src/utils.rs
@@ -41,16 +41,18 @@ fn generate_mock_valid_junit_mocker() -> JunitMock {
JunitMock::new(junit_mock::Options::default())
}
-pub fn generate_mock_valid_junit_xmls>(directory: T) -> Vec {
+pub fn generate_mock_valid_junit_xmls + Clone>(directory: T) -> Vec {
let mut jm = generate_mock_valid_junit_mocker();
- let reports = jm.generate_reports();
+ let tmp_dir = Some(directory.clone());
+ let reports = jm.generate_reports(&tmp_dir);
jm.write_reports_to_file(directory.as_ref(), reports)
.unwrap()
}
-pub fn generate_mock_bazel_bep>(directory: T) -> PathBuf {
+pub fn generate_mock_bazel_bep + Clone>(directory: T) -> PathBuf {
let mut jm = generate_mock_valid_junit_mocker();
- let reports = jm.generate_reports();
+ let tmp_dir = Some(directory.clone());
+ let reports = jm.generate_reports(&tmp_dir);
let mock_junits = jm
.write_reports_to_file(directory.as_ref(), &reports)
.unwrap();
@@ -128,33 +130,36 @@ pub fn generate_mock_bazel_bep>(directory: T) -> PathBuf {
file_path
}
-pub fn generate_mock_invalid_junit_xmls>(directory: T) {
+pub fn generate_mock_invalid_junit_xmls + Clone>(directory: T) {
let mut jm_options = junit_mock::Options::default();
jm_options.test_suite.test_suite_names = Some(vec!["".to_string()]);
jm_options.global.timestamp = Utc::now()
.fixed_offset()
.checked_sub_signed(TimeDelta::minutes(1));
let mut jm = JunitMock::new(jm_options);
- let reports = jm.generate_reports();
+ let tmp_dir = Some(directory.clone());
+ let reports = jm.generate_reports(&tmp_dir);
jm.write_reports_to_file(directory.as_ref(), reports)
.unwrap();
}
-pub fn generate_mock_suboptimal_junit_xmls>(directory: T) {
+pub fn generate_mock_suboptimal_junit_xmls + Clone>(directory: T) {
let mut jm_options = junit_mock::Options::default();
jm_options.global.timestamp = Utc::now()
.fixed_offset()
.checked_sub_signed(TimeDelta::hours(24));
let mut jm = JunitMock::new(jm_options);
- let reports = jm.generate_reports();
+ let tmp_dir = Some(directory.clone());
+ let reports = jm.generate_reports(&tmp_dir);
jm.write_reports_to_file(directory.as_ref(), reports)
.unwrap();
}
-pub fn generate_mock_missing_filepath_suboptimal_junit_xmls>(directory: T) {
+pub fn generate_mock_missing_filepath_suboptimal_junit_xmls + Clone>(directory: T) {
let jm_options = junit_mock::Options::default();
let mut jm = JunitMock::new(jm_options);
- let mut reports = jm.generate_reports();
+ let tmp_dir = Some(directory.clone());
+ let mut reports = jm.generate_reports(&tmp_dir);
for report in reports.iter_mut() {
for testsuite in report.test_suites.iter_mut() {
for test_case in testsuite.test_cases.iter_mut() {
diff --git a/cli-tests/src/validate.rs b/cli-tests/src/validate.rs
index 071661f0..501787df 100644
--- a/cli-tests/src/validate.rs
+++ b/cli-tests/src/validate.rs
@@ -3,12 +3,12 @@ use superconsole::{
style::{style, Color, Stylize},
Line, Span,
};
-use tempfile::tempdir;
+use tempfile::{tempdir, TempDir};
use crate::{
command_builder::CommandBuilder,
utils::{
- generate_mock_codeowners, generate_mock_invalid_junit_xmls,
+ generate_mock_codeowners, generate_mock_git_repo, generate_mock_invalid_junit_xmls,
generate_mock_missing_filepath_suboptimal_junit_xmls, generate_mock_suboptimal_junit_xmls,
generate_mock_valid_junit_xmls, write_junit_xml_to_dir,
},
@@ -170,7 +170,7 @@ fn validate_invalid_xml() {
#[test]
fn validate_suboptimal_junits() {
- let temp_dir = tempdir().unwrap();
+ let temp_dir = TempDir::with_prefix("not-hidden").unwrap();
generate_mock_suboptimal_junit_xmls(&temp_dir);
let assert = CommandBuilder::validate(temp_dir.path())
@@ -196,6 +196,7 @@ fn validate_suboptimal_junits() {
#[test]
fn validate_missing_filepath_suboptimal_junits() {
let temp_dir = tempdir().unwrap();
+ generate_mock_git_repo(&temp_dir);
generate_mock_missing_filepath_suboptimal_junit_xmls(&temp_dir);
generate_mock_codeowners(&temp_dir);
diff --git a/cli/src/context.rs b/cli/src/context.rs
index c47acb5b..0a4499e2 100644
--- a/cli/src/context.rs
+++ b/cli/src/context.rs
@@ -215,6 +215,7 @@ pub fn generate_internal_file(
file_sets: &[FileSet],
temp_dir: &TempDir,
codeowners: Option<&CodeOwners>,
+ repo: &BundleRepo,
show_warnings: bool,
variant: Option,
) -> anyhow::Result<(
@@ -254,10 +255,11 @@ pub fn generate_internal_file(
Ok(validate(
&reports[0],
file_set.test_runner_report.map(|t| t.into()),
+ repo,
)),
);
}
- test_case_runs.extend(junit_parser.into_test_case_runs(codeowners));
+ test_case_runs.extend(junit_parser.into_test_case_runs(codeowners, repo));
}
}
}
diff --git a/cli/src/upload_command.rs b/cli/src/upload_command.rs
index 1c00cb8d..b9fa9c64 100644
--- a/cli/src/upload_command.rs
+++ b/cli/src/upload_command.rs
@@ -317,6 +317,7 @@ pub async fn run_upload(
&meta.base_props.file_sets,
&temp_dir,
meta.base_props.codeowners.as_ref(),
+ &meta.base_props.repo,
// hide warnings on parsed xcresult output
#[cfg(target_os = "macos")]
upload_args.xcresult_path.is_none(),
diff --git a/cli/src/validate_command.rs b/cli/src/validate_command.rs
index 67c6c733..7716f6c1 100644
--- a/cli/src/validate_command.rs
+++ b/cli/src/validate_command.rs
@@ -19,6 +19,7 @@ use context::{
JunitValidationLevel,
},
},
+ repo::BundleRepo,
};
use display::end_output::EndOutput;
use pluralizer::pluralize;
@@ -537,6 +538,7 @@ async fn validate(
(parsed_reports, parse_issues)
},
);
+ let repo = BundleRepo::new(None, None, None, None, None, None, false).unwrap_or_default();
let file_parse_issues = gen_parse_issues(parse_issues);
let report_validations: JunitFileToValidation = parsed_reports
@@ -544,7 +546,11 @@ async fn validate(
.map(|(file, (report, test_runner_report))| {
(
file,
- validate_report(&report, test_runner_report.map(TestRunnerReport::from)),
+ validate_report(
+ &report,
+ test_runner_report.map(TestRunnerReport::from),
+ &repo,
+ ),
)
})
.collect();
diff --git a/context-js/src/lib.rs b/context-js/src/lib.rs
index 72c045ac..634d482c 100644
--- a/context-js/src/lib.rs
+++ b/context-js/src/lib.rs
@@ -107,6 +107,7 @@ pub fn junit_validate(
junit::bindings::BindingsJunitReportValidation::from(junit::validator::validate(
&report.clone().into(),
test_runner_report.map(junit::junit_path::TestRunnerReport::from),
+ &repo::BundleRepo::default(),
))
}
diff --git a/context-py/src/lib.rs b/context-py/src/lib.rs
index e1727a5b..e38dda6f 100644
--- a/context-py/src/lib.rs
+++ b/context-py/src/lib.rs
@@ -125,6 +125,7 @@ fn junit_validate(
junit::bindings::BindingsJunitReportValidation::from(junit::validator::validate(
&report.into(),
test_runner_report.map(TestRunnerReport::from),
+ &repo::BundleRepo::default(),
))
}
diff --git a/context/Cargo.toml b/context/Cargo.toml
index d5f5f60c..9645d526 100644
--- a/context/Cargo.toml
+++ b/context/Cargo.toml
@@ -38,6 +38,7 @@ prost-wkt-types = { version = "0.5.1", features = ["vendored-protox"] }
tracing = "0.1.41"
prost = "0.12.6"
codeowners = { version = "0.1.3", path = "../codeowners" }
+walkdir = "2.5.0"
[target.'cfg(target_os = "linux")'.dependencies]
pyo3 = { version = "0.22.5", optional = true, features = [
diff --git a/context/src/junit/bindings.rs b/context/src/junit/bindings.rs
index ae13da87..f78b1bc8 100644
--- a/context/src/junit/bindings.rs
+++ b/context/src/junit/bindings.rs
@@ -166,6 +166,7 @@ impl From for BindingsTestCase {
attempt_number,
is_quarantined,
codeowners,
+ detected_file: _detected_file,
}: TestCaseRun,
) -> Self {
let started_at = started_at.unwrap_or_default();
@@ -1067,8 +1068,11 @@ mod tests {
#[test]
fn parse_test_report_to_bindings() {
use prost_wkt_types::Timestamp;
+ use tempfile::TempDir;
- use crate::junit::validator::validate;
+ use crate::{junit::validator::validate, repo::BundleRepo};
+
+ let temp_dir = TempDir::with_prefix("not-hidden").unwrap();
let test_started_at = Timestamp {
seconds: 1000,
nanos: 0,
@@ -1080,11 +1084,13 @@ mod tests {
let codeowner1 = CodeOwner {
name: "@user".into(),
};
+ let test_file = temp_dir.path().join("test_file");
+ let file_str = String::from(test_file.as_os_str().to_str().unwrap());
let test1 = TestCaseRun {
id: "test_id1".into(),
name: "test_name".into(),
classname: "test_classname".into(),
- file: "test_file".into(),
+ file: file_str.clone(),
parent_name: "test_parent_name1".into(),
line: 1,
status: TestCaseRunStatus::Success.into(),
@@ -1100,7 +1106,7 @@ mod tests {
id: "test_id2".into(),
name: "test_name".into(),
classname: "test_classname".into(),
- file: "test_file".into(),
+ file: file_str,
parent_name: "test_parent_name2".into(),
line: 1,
status: TestCaseRunStatus::Failure.into(),
@@ -1188,7 +1194,11 @@ mod tests {
assert_eq!(test_case2.codeowners.clone().unwrap().len(), 0);
// verify that the test report is valid
- let results = validate(&converted_bindings.clone().into(), None);
+ let results = validate(
+ &converted_bindings.clone().into(),
+ None,
+ &BundleRepo::default(),
+ );
assert_eq!(results.all_issues_flat().len(), 1);
results
.all_issues_flat()
@@ -1224,6 +1234,8 @@ mod tests {
#[cfg(feature = "bindings")]
#[test]
fn test_junit_conversion_paths() {
+ use crate::repo::BundleRepo;
+
let mut junit_parser = JunitParser::new();
let file_contents = r#"
@@ -1242,7 +1254,7 @@ mod tests {
assert!(parsed_results.is_ok());
// Get test case runs from parser
- let test_case_runs = junit_parser.into_test_case_runs(None);
+ let test_case_runs = junit_parser.into_test_case_runs(None, &BundleRepo::default());
assert_eq!(test_case_runs.len(), 2);
// Convert test case runs to bindings
diff --git a/context/src/junit/file_extractor.rs b/context/src/junit/file_extractor.rs
new file mode 100644
index 00000000..5e080b9e
--- /dev/null
+++ b/context/src/junit/file_extractor.rs
@@ -0,0 +1,419 @@
+use std::fs;
+use std::path::Path;
+
+use quick_junit::{TestCase, XmlString};
+use walkdir::{DirEntry, WalkDir};
+
+use super::parser::extra_attrs;
+use crate::repo::BundleRepo;
+
+fn not_hidden(entry: &DirEntry) -> bool {
+ entry
+ .file_name()
+ .to_str()
+ .map(|file_string| !file_string.starts_with('.'))
+ .unwrap_or(true)
+}
+
+fn contains_test(path: &String, test_xml_name: &XmlString) -> bool {
+ let test_name = test_xml_name.as_str();
+ // Frameworks like vitest handle tests with an it() call inside a describe() call by
+ // using "{describe_name} > {it_name}" as the test name, so we split on that in order
+ // to get substrings that we can search for.
+ let mut test_parts = test_name.split(" > ");
+ fs::read_to_string(Path::new(path))
+ .ok()
+ .map(|text| {
+ let has_full_name = text.contains(test_name);
+ let has_name_splits = test_parts.all(|p| text.contains(p));
+ has_full_name || has_name_splits
+ })
+ .unwrap_or(false)
+}
+
+fn file_containing_tests(file_paths: Vec, test_name: &XmlString) -> Option {
+ let mut matching_paths = file_paths
+ .iter()
+ .filter(|path| contains_test(path, test_name));
+ let first_match = matching_paths.next();
+ let another_match = matching_paths.next();
+ match (first_match, another_match) {
+ (None, _) => None,
+ (Some(only_match), None) => Some((*only_match).clone()),
+ (_, _) => None,
+ }
+}
+
+// None if is not a file or file does not exist, Some(absolute path) if it does exist in the root
+fn convert_to_absolute(
+ initial: &XmlString,
+ repo: &BundleRepo,
+ test_name: &XmlString,
+) -> Option {
+ let initial_str = String::from(initial.as_str());
+ let path = Path::new(&initial_str);
+ let repo_root_path = Path::new(&repo.repo_root);
+ if path.is_absolute() {
+ path.to_str().map(String::from)
+ } else if repo_root_path.is_absolute() && repo_root_path.exists() {
+ let mut walk = WalkDir::new(repo.repo_root.clone())
+ .into_iter()
+ .filter_entry(not_hidden)
+ .filter_map(|result| {
+ if let Ok(entry) = result {
+ if entry.path().ends_with(path) {
+ entry.path().as_os_str().to_str().map(String::from).clone()
+ } else {
+ None
+ }
+ } else {
+ None
+ }
+ });
+ let first_match = walk.next();
+ let another_match = walk.next();
+ match (first_match, another_match) {
+ (None, _) => None,
+ (Some(only_match), None) => Some(only_match),
+ (Some(first_match), Some(second_match)) => file_containing_tests(
+ [vec![first_match, second_match], walk.collect()].concat(),
+ test_name,
+ ),
+ }
+ } else if path
+ .file_name()
+ .iter()
+ .flat_map(|os| os.to_str())
+ .all(|name| name.contains('.'))
+ {
+ Some(initial_str)
+ } else {
+ None
+ }
+}
+
+fn validate_as_filename(initial: &XmlString) -> Option {
+ let initial_str = String::from(initial.as_str());
+ let path = Path::new(&initial_str);
+ if path.extension().is_some() {
+ Some(initial_str)
+ } else {
+ None
+ }
+}
+
+pub fn filename_for_test_case(test_case: &TestCase) -> String {
+ test_case
+ .extra
+ .get(extra_attrs::FILE)
+ .or(test_case.extra.get(extra_attrs::FILEPATH))
+ .or(test_case.classname.as_ref())
+ .iter()
+ .flat_map(|s| validate_as_filename(s))
+ .next()
+ .unwrap_or_default()
+}
+
+pub fn detected_file_for_test_case(test_case: &TestCase, repo: &BundleRepo) -> String {
+ test_case
+ .extra
+ .get(extra_attrs::FILE)
+ .or(test_case.extra.get(extra_attrs::FILEPATH))
+ .or(test_case.classname.as_ref())
+ .iter()
+ .flat_map(|s| convert_to_absolute(s, repo, &test_case.name))
+ .next()
+ .unwrap_or_default()
+}
+
+#[cfg(test)]
+mod tests {
+ use std::fs;
+ use std::path::PathBuf;
+
+ use quick_junit::XmlString;
+ use tempfile::{tempdir, TempDir};
+
+ use super::*;
+ use crate::repo::RepoUrlParts;
+
+ fn stringify(fp: PathBuf) -> String {
+ String::from(fp.as_os_str().to_str().unwrap())
+ }
+
+ fn bundle_repo(dir: &Path) -> BundleRepo {
+ BundleRepo {
+ repo: RepoUrlParts::default(),
+ repo_root: String::from(dir.as_os_str().to_str().unwrap()),
+ repo_url: String::from(""),
+ repo_head_sha: String::from(""),
+ repo_head_sha_short: None,
+ repo_head_branch: String::from(""),
+ repo_head_commit_epoch: 0,
+ repo_head_commit_message: String::from(""),
+ repo_head_author_name: String::from(""),
+ repo_head_author_email: String::from(""),
+ }
+ }
+
+ #[test]
+ fn test_contains_test_if_unsplit_test_name_present() {
+ let temp_dir = tempdir().unwrap();
+ let file_path = temp_dir.as_ref().join("match.txt");
+ let text = r#"
+ describe("description", {
+ it("my_super_good_test", {
+ })
+ })
+ "#;
+ fs::write(file_path.clone(), text).unwrap();
+ let actual = contains_test(&stringify(file_path), &XmlString::new("my_super_good_test"));
+ assert!(actual);
+ }
+
+ #[test]
+ fn test_contains_test_if_split_test_name_present() {
+ let temp_dir = tempdir().unwrap();
+ let file_path = temp_dir.as_ref().join("match.txt");
+ let text = r#"
+ describe("description", {
+ it("my_super_good_test", {
+ })
+ })
+ "#;
+ fs::write(file_path.clone(), text).unwrap();
+ let actual = contains_test(
+ &stringify(file_path),
+ &XmlString::new("description > my_super_good_test"),
+ );
+ assert!(actual);
+ }
+
+ #[test]
+ fn test_contains_test_if_part_of_split_test_name_present() {
+ let temp_dir = tempdir().unwrap();
+ let file_path = temp_dir.as_ref().join("match.txt");
+ let text = r#"
+ it("my_super_good_test", {
+ })
+ "#;
+ fs::write(file_path.clone(), text).unwrap();
+ let actual = contains_test(
+ &stringify(file_path),
+ &XmlString::new("description > my_super_good_test"),
+ );
+ assert!(!actual);
+ }
+
+ #[test]
+ fn test_contains_test_if_test_name_not_present() {
+ let temp_dir = tempdir().unwrap();
+ let file_path = temp_dir.as_ref().join("match.txt");
+ let text = r#"
+ describe("description", {
+ it("my_super_good_test", {
+ })
+ })
+ "#;
+ fs::write(file_path.clone(), text).unwrap();
+ let actual = contains_test(&stringify(file_path), &XmlString::new("totally_different"));
+ assert!(!actual);
+ }
+
+ #[test]
+ fn test_file_containing_test_if_none_contains_test() {
+ let temp_dir = tempdir().unwrap();
+ let file_path_1 = temp_dir.as_ref().join("file_1.txt");
+ let text_1 = r#"it("test_1")"#;
+ fs::write(file_path_1.clone(), text_1).unwrap();
+ let file_path_2 = temp_dir.as_ref().join("file_2.txt");
+ let text_2 = r#"it("test_2")"#;
+ fs::write(file_path_2.clone(), text_2).unwrap();
+ let file_path_3 = temp_dir.as_ref().join("file_3.txt");
+ let text_3 = r#"it("test_3")"#;
+ fs::write(file_path_3.clone(), text_3).unwrap();
+ let actual = file_containing_tests(
+ vec![
+ stringify(file_path_1),
+ stringify(file_path_2),
+ stringify(file_path_3),
+ ],
+ &XmlString::new("totally_different"),
+ );
+ assert_eq!(actual, None);
+ }
+
+ #[test]
+ fn test_file_containing_test_if_one_contains_test() {
+ let temp_dir = tempdir().unwrap();
+ let file_path_1 = temp_dir.as_ref().join("file_1.txt");
+ let text_1 = r#"it("test_1")"#;
+ fs::write(file_path_1.clone(), text_1).unwrap();
+ let file_path_2 = temp_dir.as_ref().join("file_2.txt");
+ let text_2 = r#"it("test_2")"#;
+ fs::write(file_path_2.clone(), text_2).unwrap();
+ let file_path_3 = temp_dir.as_ref().join("file_3.txt");
+ let text_3 = r#"it("test_3")"#;
+ fs::write(file_path_3.clone(), text_3).unwrap();
+ let actual = file_containing_tests(
+ vec![
+ stringify(file_path_1),
+ stringify(file_path_2.clone()),
+ stringify(file_path_3),
+ ],
+ &XmlString::new("test_2"),
+ );
+ assert_eq!(actual, Some(stringify(file_path_2)));
+ }
+
+ #[test]
+ fn test_file_containing_test_if_multiple_contain_test() {
+ let temp_dir = tempdir().unwrap();
+ let file_path_1 = temp_dir.as_ref().join("file_1.txt");
+ let text_1 = r#"it("common_test")"#;
+ fs::write(file_path_1.clone(), text_1).unwrap();
+ let file_path_2 = temp_dir.as_ref().join("file_2.txt");
+ let text_2 = r#"it("common_test")"#;
+ fs::write(file_path_2.clone(), text_2).unwrap();
+ let file_path_3 = temp_dir.as_ref().join("file_3.txt");
+ let text_3 = r#"it("test_3")"#;
+ fs::write(file_path_3.clone(), text_3).unwrap();
+ let actual = file_containing_tests(
+ vec![
+ stringify(file_path_1),
+ stringify(file_path_2),
+ stringify(file_path_3),
+ ],
+ &XmlString::new("common_test"),
+ );
+ assert_eq!(actual, None);
+ }
+
+ #[test]
+ fn test_convert_to_absolute_when_no_repo_root() {
+ let temp_dir = TempDir::with_prefix("not-hidden").unwrap();
+ let file_path = temp_dir.as_ref().join("test.txt");
+ let text = r#"it("test")"#;
+ fs::write(file_path.clone(), text).unwrap();
+ let actual = convert_to_absolute(
+ &XmlString::new("test.txt"),
+ &BundleRepo::default(),
+ &XmlString::new("test"),
+ );
+ assert_eq!(actual, Some(String::from("test.txt")));
+ }
+
+ #[test]
+ fn test_convert_to_absolute_when_already_absolute() {
+ let temp_dir = TempDir::with_prefix("not-hidden").unwrap();
+ let file_path = temp_dir.as_ref().join("test.txt");
+ let text = r#"it("test")"#;
+ fs::write(file_path.clone(), text).unwrap();
+ let actual = convert_to_absolute(
+ &XmlString::new(stringify(file_path.clone())),
+ &bundle_repo(temp_dir.as_ref()),
+ &XmlString::new("test"),
+ );
+ assert_eq!(actual, Some(stringify(file_path)));
+ }
+
+ #[test]
+ fn test_convert_to_absolute_when_no_file_matches() {
+ let temp_dir = TempDir::with_prefix("not-hidden").unwrap();
+ let file_path_1 = temp_dir.as_ref().join("file_1.txt");
+ let text_1 = r#"it("test_1")"#;
+ fs::write(file_path_1.clone(), text_1).unwrap();
+ let file_path_2 = temp_dir.as_ref().join("file_2.txt");
+ let text_2 = r#"it("test_2")"#;
+ fs::write(file_path_2.clone(), text_2).unwrap();
+ let file_path_3 = temp_dir.as_ref().join("file_3.txt");
+ let text_3 = r#"it("test_3")"#;
+ fs::write(file_path_3.clone(), text_3).unwrap();
+ let actual = convert_to_absolute(
+ &XmlString::new("not_a_test.txt"),
+ &bundle_repo(temp_dir.as_ref()),
+ &XmlString::new("test"),
+ );
+ assert_eq!(actual, None);
+ }
+
+ #[test]
+ fn test_convert_to_absolute_when_one_file_matches() {
+ let temp_dir = TempDir::with_prefix("not-hidden").unwrap();
+ let inner_dir = "inner_dir";
+ fs::create_dir(temp_dir.as_ref().join(inner_dir)).unwrap();
+ let file_path_1 = temp_dir.as_ref().join(inner_dir).join("file_1.txt");
+ let text_1 = r#"it("test_1")"#;
+ fs::write(file_path_1.clone(), text_1).unwrap();
+ let file_path_2 = temp_dir.as_ref().join(inner_dir).join("file_2.txt");
+ let text_2 = r#"it("test_2")"#;
+ fs::write(file_path_2.clone(), text_2).unwrap();
+ let file_path_3 = temp_dir.as_ref().join(inner_dir).join("file_3.txt");
+ let text_3 = r#"it("test_3")"#;
+ fs::write(file_path_3.clone(), text_3).unwrap();
+ let actual = convert_to_absolute(
+ &XmlString::new("file_1.txt"),
+ &bundle_repo(temp_dir.as_ref()),
+ &XmlString::new("test"),
+ );
+ assert_eq!(actual, Some(stringify(file_path_1)));
+ }
+
+ #[test]
+ fn test_convert_to_absolute_when_many_files_match_and_none_contain() {
+ let temp_dir = TempDir::with_prefix("not-hidden").unwrap();
+ let inner_dir = "inner_dir";
+ let other_dir = "other_dir";
+ fs::create_dir(temp_dir.as_ref().join(inner_dir)).unwrap();
+ fs::create_dir(temp_dir.as_ref().join(other_dir)).unwrap();
+ let file_path_1 = temp_dir.as_ref().join(inner_dir).join("file.txt");
+ let text_1 = r#"it("test_1")"#;
+ fs::write(file_path_1.clone(), text_1).unwrap();
+ let file_path_2 = temp_dir.as_ref().join(other_dir).join("file.txt");
+ let text_2 = r#"it("test_2")"#;
+ fs::write(file_path_2.clone(), text_2).unwrap();
+ let actual = convert_to_absolute(
+ &XmlString::new("file.txt"),
+ &bundle_repo(temp_dir.as_ref()),
+ &XmlString::new("totally_different"),
+ );
+ assert_eq!(actual, None);
+ }
+
+ #[test]
+ fn test_convert_to_absolute_when_many_files_match_and_one_contains() {
+ let temp_dir = TempDir::with_prefix("not-hidden").unwrap();
+ let inner_dir = "inner_dir";
+ let other_dir = "other_dir";
+ fs::create_dir(temp_dir.as_ref().join(inner_dir)).unwrap();
+ fs::create_dir(temp_dir.as_ref().join(other_dir)).unwrap();
+ let file_path_1 = temp_dir.as_ref().join(inner_dir).join("file.txt");
+ let text_1 = r#"it("test_1")"#;
+ fs::write(file_path_1.clone(), text_1).unwrap();
+ let file_path_2 = temp_dir.as_ref().join(other_dir).join("file.txt");
+ let text_2 = r#"it("test_2")"#;
+ fs::write(file_path_2.clone(), text_2).unwrap();
+ let actual = convert_to_absolute(
+ &XmlString::new("file.txt"),
+ &bundle_repo(temp_dir.as_ref()),
+ &XmlString::new("test_1"),
+ );
+ assert_eq!(actual, Some(stringify(file_path_1)));
+ }
+
+ #[test]
+ fn test_convert_to_absolute_when_only_match_is_in_hidden_directory() {
+ let temp_dir = TempDir::with_prefix("not-hidden").unwrap();
+ let hidden_dir = ".hidden";
+ fs::create_dir(temp_dir.as_ref().join(hidden_dir)).unwrap();
+ let file_path = temp_dir.as_ref().join(hidden_dir).join("test.txt");
+ let text = r#"it("test")"#;
+ fs::write(file_path.clone(), text).unwrap();
+ let actual = convert_to_absolute(
+ &XmlString::new("test.txt"),
+ &bundle_repo(temp_dir.as_ref()),
+ &XmlString::new("test"),
+ );
+ assert_eq!(actual, None);
+ }
+}
diff --git a/context/src/junit/mod.rs b/context/src/junit/mod.rs
index 23716c81..70457f9a 100644
--- a/context/src/junit/mod.rs
+++ b/context/src/junit/mod.rs
@@ -1,6 +1,7 @@
#[cfg(feature = "bindings")]
pub mod bindings;
mod date_parser;
+mod file_extractor;
pub mod junit_path;
pub mod parser;
pub mod validator;
diff --git a/context/src/junit/parser.rs b/context/src/junit/parser.rs
index 27e586c0..b4da66f6 100644
--- a/context/src/junit/parser.rs
+++ b/context/src/junit/parser.rs
@@ -21,6 +21,10 @@ use thiserror::Error;
use wasm_bindgen::prelude::*;
use super::date_parser::JunitDateParser;
+use crate::{
+ junit::file_extractor::{detected_file_for_test_case, filename_for_test_case},
+ repo::BundleRepo,
+};
const TAG_REPORT: &[u8] = b"testsuites";
const TAG_TEST_SUITE: &[u8] = b"testsuite";
@@ -192,11 +196,17 @@ impl JunitParser {
self.reports
}
- pub fn into_test_case_runs(self, codeowners: Option<&CodeOwners>) -> Vec {
+ pub fn into_test_case_runs(
+ self,
+ codeowners: Option<&CodeOwners>,
+ repo: &BundleRepo,
+ ) -> Vec {
let mut test_case_runs = Vec::new();
for report in self.reports {
for test_suite in report.test_suites {
for test_case in test_suite.test_cases {
+ let file = filename_for_test_case(&test_case);
+ let detected_file = detected_file_for_test_case(&test_case, repo);
let mut test_case_run = TestCaseRun {
name: test_case.name.into(),
parent_name: test_suite.name.to_string(),
@@ -259,12 +269,6 @@ impl JunitParser {
TestCaseRunStatus::Failure.into()
}
};
- let file = test_case
- .extra
- .get(extra_attrs::FILE)
- .or_else(|| test_case.extra.get(extra_attrs::FILEPATH))
- .map(|v| v.to_string())
- .unwrap_or_default();
if !file.is_empty() && codeowners.is_some() {
let codeowners: Option> = codeowners
.as_ref()
@@ -277,6 +281,7 @@ impl JunitParser {
}
}
test_case_run.file = file;
+ test_case_run.detected_file = detected_file;
test_case_run.line = test_case
.extra
.get(extra_attrs::LINE)
@@ -854,7 +859,7 @@ mod tests {
use prost_wkt_types::Timestamp;
use proto::test_context::test_run::TestCaseRunStatus;
- use crate::junit::parser::JunitParser;
+ use crate::{junit::parser::JunitParser, repo::BundleRepo};
#[test]
fn test_into_test_case_runs() {
let mut junit_parser = JunitParser::new();
@@ -875,7 +880,7 @@ mod tests {
"#;
let parsed_results = junit_parser.parse(BufReader::new(file_contents.as_bytes()));
assert!(parsed_results.is_ok());
- let test_case_runs = junit_parser.into_test_case_runs(None);
+ let test_case_runs = junit_parser.into_test_case_runs(None, &BundleRepo::default());
assert_eq!(test_case_runs.len(), 2);
let test_case_run1 = &test_case_runs[0];
assert_eq!(test_case_run1.name, "test_variant_truncation1");
diff --git a/context/src/junit/validator.rs b/context/src/junit/validator.rs
index 9f04adbc..b5e405c6 100644
--- a/context/src/junit/validator.rs
+++ b/context/src/junit/validator.rs
@@ -10,7 +10,8 @@ use thiserror::Error;
#[cfg(feature = "wasm")]
use wasm_bindgen::prelude::*;
-use super::parser::extra_attrs;
+use crate::junit::file_extractor::detected_file_for_test_case;
+use crate::repo::BundleRepo;
use crate::{
junit::junit_path::TestRunnerReport,
string_safety::{validate_field_len, FieldLen},
@@ -79,6 +80,7 @@ impl Default for JunitValidationType {
pub fn validate(
report: &Report,
test_runner_report: Option,
+ repo: &BundleRepo,
) -> JunitReportValidation {
let mut report_validation = JunitReportValidation::default();
@@ -126,15 +128,9 @@ pub fn validate(
}
};
- match validate_field_len::(
- test_case
- .extra
- .get(extra_attrs::FILE)
- .or(test_case.extra.get(extra_attrs::FILEPATH))
- .as_ref()
- .map(|s| s.as_str())
- .unwrap_or_default(),
- ) {
+ match validate_field_len::(detected_file_for_test_case(
+ test_case, repo,
+ )) {
FieldLen::Valid => (),
FieldLen::TooShort(s) => {
test_case_validation.add_issue(JunitValidationIssue::SubOptimal(
diff --git a/context/tests/junit.rs b/context/tests/junit.rs
index 0e2192ec..0a9fab60 100644
--- a/context/tests/junit.rs
+++ b/context/tests/junit.rs
@@ -1,18 +1,22 @@
use std::{fs, io::BufReader, time::Duration};
use chrono::{Days, NaiveTime, TimeDelta, Utc};
-use context::junit::{
+use context::{
self,
- junit_path::{TestRunnerReport, TestRunnerReportStatus},
- parser::JunitParser,
- validator::{
- JunitReportValidationIssue, JunitReportValidationIssueSubOptimal,
- JunitTestCaseValidationIssue, JunitTestCaseValidationIssueInvalid,
- JunitTestCaseValidationIssueSubOptimal, JunitTestSuiteValidationIssue,
- JunitTestSuiteValidationIssueInvalid, JunitTestSuiteValidationIssueSubOptimal,
- JunitValidationIssue, JunitValidationIssueType, JunitValidationLevel,
- TestRunnerReportValidationIssue, TestRunnerReportValidationIssueSubOptimal,
+ junit::{
+ self,
+ junit_path::{TestRunnerReport, TestRunnerReportStatus},
+ parser::JunitParser,
+ validator::{
+ JunitReportValidationIssue, JunitReportValidationIssueSubOptimal,
+ JunitTestCaseValidationIssue, JunitTestCaseValidationIssueInvalid,
+ JunitTestCaseValidationIssueSubOptimal, JunitTestSuiteValidationIssue,
+ JunitTestSuiteValidationIssueInvalid, JunitTestSuiteValidationIssueSubOptimal,
+ JunitValidationIssue, JunitValidationIssueType, JunitValidationLevel,
+ TestRunnerReportValidationIssue, TestRunnerReportValidationIssueSubOptimal,
+ },
},
+ repo::BundleRepo,
};
use junit_mock::JunitMock;
use quick_junit::Report;
@@ -57,7 +61,8 @@ fn generate_mock_junit_reports(
let mut jm = JunitMock::new(options);
let seed = jm.get_seed();
- let reports = jm.generate_reports();
+ let tmp_dir: Option = None;
+ let reports = jm.generate_reports(&tmp_dir);
(seed, reports)
}
@@ -90,7 +95,8 @@ fn validate_test_suite_name_too_short() {
test_suite.name = String::new().into();
}
- let report_validation = junit::validator::validate(&generated_report, None);
+ let report_validation =
+ junit::validator::validate(&generated_report, None, &BundleRepo::default());
assert_eq!(
report_validation.max_level(),
@@ -126,7 +132,8 @@ fn validate_test_case_name_too_short() {
}
}
- let report_validation = junit::validator::validate(&generated_report, None);
+ let report_validation =
+ junit::validator::validate(&generated_report, None, &BundleRepo::default());
assert_eq!(
report_validation.max_level(),
@@ -159,7 +166,8 @@ fn validate_test_suite_name_too_long() {
test_suite.name = "a".repeat(junit::validator::MAX_FIELD_LEN + 1).into();
}
- let report_validation = junit::validator::validate(&generated_report, None);
+ let report_validation =
+ junit::validator::validate(&generated_report, None, &BundleRepo::default());
assert_eq!(
report_validation.max_level(),
@@ -195,7 +203,8 @@ fn validate_test_case_name_too_long() {
}
}
- let report_validation = junit::validator::validate(&generated_report, None);
+ let report_validation =
+ junit::validator::validate(&generated_report, None, &BundleRepo::default());
assert_eq!(
report_validation.max_level(),
@@ -233,7 +242,8 @@ fn validate_max_level() {
}
}
- let report_validation = junit::validator::validate(&generated_report, None);
+ let report_validation =
+ junit::validator::validate(&generated_report, None, &BundleRepo::default());
assert_eq!(
report_validation.max_level(),
@@ -304,7 +314,8 @@ fn validate_timestamps() {
}
}
- let report_validation = junit::validator::validate(&generated_report, None);
+ let report_validation =
+ junit::validator::validate(&generated_report, None, &BundleRepo::default());
assert_eq!(
report_validation.max_level(),
@@ -338,7 +349,8 @@ fn validate_test_runner_report_overrides_timestamp() {
options.global.timestamp = Some(old_timestamp.fixed_offset());
let mut jm = JunitMock::new(options);
let seed = jm.get_seed();
- let mut generated_reports = jm.generate_reports();
+ let tmp_dir: Option = None;
+ let mut generated_reports = jm.generate_reports(&tmp_dir);
let generated_report = generated_reports.pop().unwrap();
@@ -351,8 +363,11 @@ fn validate_test_runner_report_overrides_timestamp() {
.checked_add_signed(TimeDelta::minutes(1))
.unwrap(),
};
- let override_report_validation =
- junit::validator::validate(&generated_report, Some(override_report));
+ let override_report_validation = junit::validator::validate(
+ &generated_report,
+ Some(override_report),
+ &BundleRepo::default(),
+ );
pretty_assertions::assert_eq!(
override_report_validation.all_issues(),
&[
@@ -388,8 +403,11 @@ fn validate_test_runner_report_overrides_timestamp() {
.checked_add_signed(TimeDelta::minutes(1))
.unwrap(),
};
- let override_report_validation =
- junit::validator::validate(&generated_report, Some(override_report));
+ let override_report_validation = junit::validator::validate(
+ &generated_report,
+ Some(override_report),
+ &BundleRepo::default(),
+ );
pretty_assertions::assert_eq!(
override_report_validation.all_issues(),
&[
@@ -425,8 +443,11 @@ fn validate_test_runner_report_overrides_timestamp() {
.checked_add_signed(TimeDelta::minutes(1))
.unwrap(),
};
- let override_report_validation =
- junit::validator::validate(&generated_report, Some(override_report));
+ let override_report_validation = junit::validator::validate(
+ &generated_report,
+ Some(override_report),
+ &BundleRepo::default(),
+ );
pretty_assertions::assert_eq!(
override_report_validation.all_issues(),
&[
@@ -464,8 +485,11 @@ fn validate_test_runner_report_overrides_timestamp() {
.checked_sub_signed(TimeDelta::minutes(1))
.unwrap(),
};
- let override_report_validation =
- junit::validator::validate(&generated_report, Some(override_report));
+ let override_report_validation = junit::validator::validate(
+ &generated_report,
+ Some(override_report),
+ &BundleRepo::default(),
+ );
pretty_assertions::assert_eq!(
override_report_validation.test_runner_report.issues(),
&[TestRunnerReportValidationIssue::SubOptimal(
@@ -487,8 +511,11 @@ fn validate_test_runner_report_overrides_timestamp() {
.checked_add_signed(TimeDelta::minutes(1))
.unwrap(),
};
- let override_report_validation =
- junit::validator::validate(&generated_report, Some(override_report));
+ let override_report_validation = junit::validator::validate(
+ &generated_report,
+ Some(override_report),
+ &BundleRepo::default(),
+ );
pretty_assertions::assert_eq!(
override_report_validation.all_issues(),
&[],
@@ -498,7 +525,8 @@ fn validate_test_runner_report_overrides_timestamp() {
}
{
- let report_validation = junit::validator::validate(&generated_report, None);
+ let report_validation =
+ junit::validator::validate(&generated_report, None, &BundleRepo::default());
pretty_assertions::assert_eq!(
report_validation.all_issues(),
&[JunitValidationIssueType::Report(
@@ -536,8 +564,11 @@ fn validate_test_runner_report_overrides_timestamp() {
test_case.timestamp = Some(test_case_timestamp);
});
});
- let override_report_validation =
- junit::validator::validate(&generated_report, Some(override_report));
+ let override_report_validation = junit::validator::validate(
+ &generated_report,
+ Some(override_report),
+ &BundleRepo::default(),
+ );
pretty_assertions::assert_eq!(
override_report_validation.all_issues(),
&[
@@ -588,7 +619,8 @@ fn parse_round_trip_and_validate_fuzzed() {
for (index, generated_report) in generated_reports.iter().enumerate() {
let serialized_generated_report = serialize_report(generated_report);
let first_parsed_report = parse_report(&serialized_generated_report);
- let report_validation = junit::validator::validate(&first_parsed_report, None);
+ let report_validation =
+ junit::validator::validate(&first_parsed_report, None, &BundleRepo::default());
assert_eq!(
report_validation.max_level(),
@@ -619,7 +651,8 @@ fn parse_round_trip_and_validate_fuzzed() {
fn parse_without_testsuites_element() {
let options = new_mock_junit_options(1, Some(1), Some(1), true);
let mut jm = JunitMock::new(options);
- let reports = jm.generate_reports();
+ let tmp_dir: Option = None;
+ let reports = jm.generate_reports(&tmp_dir);
let tempdir = TempDir::new().unwrap();
let xml_path = jm
diff --git a/junit-mock/src/lib.rs b/junit-mock/src/lib.rs
index 5ab74d3c..b7f8464f 100644
--- a/junit-mock/src/lib.rs
+++ b/junit-mock/src/lib.rs
@@ -254,7 +254,7 @@ impl JunitMock {
self.timestamp += duration;
}
- pub fn generate_reports(&mut self) -> Vec {
+ pub fn generate_reports>(&mut self, tmp_dir: &Option) -> Vec {
self.timestamp = self
.options
.global
@@ -280,7 +280,7 @@ impl JunitMock {
let mut report = Report::new(report_name);
report.set_timestamp(self.timestamp);
self.total_duration = Duration::new(0, 0);
- report.add_test_suites(self.generate_test_suites());
+ report.add_test_suites(self.generate_test_suites(tmp_dir));
report.set_time(self.total_duration);
let duration =
self.fake_duration(self.options.report.report_duration_range.clone());
@@ -337,7 +337,7 @@ impl JunitMock {
Ok(())
}
- fn generate_test_suites(&mut self) -> Vec {
+ fn generate_test_suites>(&mut self, tmp_dir: &Option) -> Vec {
self.options
.test_suite
.test_suite_names
@@ -357,7 +357,7 @@ impl JunitMock {
let mut test_suite = TestSuite::new(test_suite_name);
test_suite.set_timestamp(self.timestamp);
let last_duration = self.total_duration;
- test_suite.add_test_cases(self.generate_test_cases());
+ test_suite.add_test_cases(self.generate_test_cases(tmp_dir));
test_suite.set_time(self.total_duration - last_duration);
if self.rand_bool(self.options.test_suite.test_suite_sys_out_percentage) {
test_suite.set_system_out(self.fake_paragraphs());
@@ -370,7 +370,7 @@ impl JunitMock {
.collect()
}
- fn generate_test_cases(&mut self) -> Vec {
+ fn generate_test_cases>(&mut self, tmp_dir: &Option) -> Vec {
let classnames = self
.options
.test_case
@@ -383,7 +383,7 @@ impl JunitMock {
})
.unwrap_or_else(|| {
(0..self.options.test_case.test_case_random_count)
- .map(|_| fake::faker::filesystem::en::DirPath().fake_with_rng(&mut self.rng))
+ .map(|_| fake::faker::lorem::en::Word().fake_with_rng(&mut self.rng))
.collect()
});
@@ -411,8 +411,21 @@ impl JunitMock {
let is_skipped = matches!(&test_case_status, TestCaseStatus::Skipped { .. });
let mut test_case = TestCase::new(test_case_name, test_case_status);
- let file: String =
- fake::faker::filesystem::en::FilePath().fake_with_rng(&mut self.rng);
+ let file: String = if let Some(parent_dir) = tmp_dir {
+ let path = parent_dir
+ .as_ref()
+ .join::(fake::faker::lorem::en::Word().fake_with_rng(&mut self.rng))
+ .join::(
+ fake::faker::filesystem::en::FileName().fake_with_rng(&mut self.rng),
+ );
+ std::fs::create_dir_all(path.clone().parent().unwrap()).unwrap();
+ if !path.exists() {
+ std::fs::File::create_new(path.clone()).unwrap();
+ }
+ String::from(path.clone().as_os_str().to_str().unwrap())
+ } else {
+ fake::faker::filesystem::en::FilePath().fake_with_rng(&mut self.rng)
+ };
test_case.extra.insert("file".into(), file.into());
test_case.set_classname(format!("{test_case_classname}/{test_case_name}"));
test_case.set_assertions(self.rng.gen_range(1..10));
diff --git a/junit-mock/src/main.rs b/junit-mock/src/main.rs
index d8c2fbd6..3d7dc617 100644
--- a/junit-mock/src/main.rs
+++ b/junit-mock/src/main.rs
@@ -20,7 +20,8 @@ fn main() -> Result<()> {
let mut jm = JunitMock::new(options);
println!("Using seed `{}` to generate random data.", jm.get_seed());
- let reports = jm.generate_reports();
+ let tmp_dir: Option = None;
+ let reports = jm.generate_reports(&tmp_dir);
jm.write_reports_to_file(directory, &reports)?;
diff --git a/proto/proto/test_context.proto b/proto/proto/test_context.proto
index 6930eb84..182b80b5 100644
--- a/proto/proto/test_context.proto
+++ b/proto/proto/test_context.proto
@@ -29,6 +29,7 @@ message TestCaseRun {
string status_output_message = 11;
bool is_quarantined = 12;
repeated CodeOwner codeowners = 13;
+ string detected_file = 14;
}
message UploaderMetadata {