diff --git a/cli/src/upload_command.rs b/cli/src/upload_command.rs index d3966b31..4cb142cc 100644 --- a/cli/src/upload_command.rs +++ b/cli/src/upload_command.rs @@ -44,7 +44,7 @@ pub const DRY_RUN_OUTPUT_DIR: &str = "bundle_upload"; pub struct UploadArgs { #[arg( long, - required_unless_present_any = [JUNIT_GLOB_REQUIRED_UNLESS_PRESENT_ARG, "bazel_bep_path", "test_reports"], + required_unless_present_any = [JUNIT_GLOB_REQUIRED_UNLESS_PRESENT_ARG, "bazel_bep_path", "test_reports", "swift_test_xunit_paths"], conflicts_with = "bazel_bep_path", value_delimiter = ',', value_parser = clap::builder::NonEmptyStringValueParser::new(), @@ -53,20 +53,20 @@ pub struct UploadArgs { pub junit_paths: Vec, #[arg( long, - required_unless_present_any = [JUNIT_GLOB_REQUIRED_UNLESS_PRESENT_ARG, "junit_paths", "test_reports"], + required_unless_present_any = [JUNIT_GLOB_REQUIRED_UNLESS_PRESENT_ARG, "junit_paths", "test_reports", "swift_test_xunit_paths"], help = "Path to Bazel Build Event Protocol JSON file. BEP files contain test results and build metadata." )] pub bazel_bep_path: Option, #[cfg(target_os = "macos")] #[arg(long, - required_unless_present_any = ["junit_paths", "bazel_bep_path", "test_reports"], + required_unless_present_any = ["junit_paths", "bazel_bep_path", "test_reports", "swift_test_xunit_paths"], conflicts_with_all = ["junit_paths", "bazel_bep_path"], required = false, help = "Path to Xcode XCResult bundle directory (macOS only)." )] pub xcresult_path: Option, #[arg( long, - required_unless_present_any = [JUNIT_GLOB_REQUIRED_UNLESS_PRESENT_ARG, "junit_paths", "bazel_bep_path"], + required_unless_present_any = [JUNIT_GLOB_REQUIRED_UNLESS_PRESENT_ARG, "junit_paths", "bazel_bep_path", "swift_test_xunit_paths"], value_delimiter = ',', value_parser = clap::builder::NonEmptyStringValueParser::new(), help = "Comma-separated list of glob patterns to test report files. Supports JUnit XML, Bazel BEP, and XCResult formats." diff --git a/cli/tests/common/command_builder.rs b/cli/tests/common/command_builder.rs index 2000b896..c2aa7657 100644 --- a/cli/tests/common/command_builder.rs +++ b/cli/tests/common/command_builder.rs @@ -436,6 +436,7 @@ pub enum PathsState { JunitPaths(String), BazelBepPath(String), XCResultPath(String), + SwiftTestXunitPaths(String), } impl PathsState { @@ -444,6 +445,9 @@ impl PathsState { PathsState::JunitPaths(path) => vec![String::from("--junit-paths"), path.clone()], PathsState::BazelBepPath(path) => vec![String::from("--bazel-bep-path"), path.clone()], PathsState::XCResultPath(path) => vec![String::from("--xcresult-path"), path.clone()], + PathsState::SwiftTestXunitPaths(paths) => { + vec![String::from("--swift-test-xunit-paths"), paths.clone()] + } } } } @@ -498,6 +502,11 @@ impl<'b> CommandBuilder<'b> { self } + pub fn swift_test_xunit_paths(&mut self, new_paths: &str) -> &mut Self { + self.paths_state = Some(PathsState::SwiftTestXunitPaths(String::from(new_paths))); + self + } + pub fn extra_args(&mut self, args: &[&str]) -> &mut Self { self.extra_args = args.iter().map(|arg| String::from(*arg)).collect(); self diff --git a/cli/tests/upload.rs b/cli/tests/upload.rs index 8db92602..f74c759e 100644 --- a/cli/tests/upload.rs +++ b/cli/tests/upload.rs @@ -3194,10 +3194,7 @@ async fn upload_bundle_using_swift_test_xunit() { let state = MockServerBuilder::new().spawn_mock_server().await; CommandBuilder::upload(temp_dir.path(), state.host.clone()) - .extra_args(&[ - "--swift-test-xunit-paths", - "xunit-swift-testing.xml,xunit.xml", - ]) + .swift_test_xunit_paths("xunit-swift-testing.xml,xunit.xml") .command() .assert() .success(); @@ -3208,7 +3205,7 @@ async fn upload_bundle_using_swift_test_xunit() { serde_json::from_reader(fs::File::open(tar_extract_directory.join("meta.json")).unwrap()) .unwrap(); - let mut files = std::collections::HashMap::new(); + let mut cases: Vec<(String, Option)> = Vec::new(); for file_set in &bundle_meta.base_props.file_sets { for file in &file_set.files { let mut parser = JunitParser::new(); @@ -3222,13 +3219,25 @@ async fn upload_bundle_using_swift_test_xunit() { .iter() .find(|(key, _)| key.as_str() == "file") .map(|(_, value)| value.as_str().to_owned()); - files.insert(case.name.as_str().to_owned(), file); + cases.push((case.name.as_str().to_owned(), file)); } } } } } + // Three tests across the two files, each bundled exactly once. A count of six is the + // signature of the same reports arriving through a junit glob as well. + assert_eq!( + cases.len(), + 3, + "expected each test bundled once, got {cases:?}" + ); + + let files = cases + .into_iter() + .collect::>>(); + for (name, expected) in [ ("helloworld()", "Tests/MyCLITests/TopLevel.swift"), ("shared()", "Tests/MyCLITests/Suites.swift"), diff --git a/xcresult/tests/common/mod.rs b/xcresult/tests/common/mod.rs index 72f6be4d..81820c28 100644 --- a/xcresult/tests/common/mod.rs +++ b/xcresult/tests/common/mod.rs @@ -9,15 +9,32 @@ #![allow(dead_code)] -use std::{fs::File, path::Path}; +use std::{fs::File, path::Path, time::Duration}; use context::repo::RepoUrlParts; use flate2::read::GzDecoder; use lazy_static::lazy_static; use tar::Archive; use temp_testdir::TempDir; +use xcresult::test_locations::Limits; #[cfg(target_os = "macos")] -use xcresult::{test_locations::Limits, xcresult::XCResult}; +use xcresult::xcresult::XCResult; + +/// The limits every test here resolves under. +/// +/// The shipped defaults size one resolution on a machine doing nothing else. A test binary +/// starts one language server per test, all of them at once, under coverage instrumentation +/// and alongside the rest of the workspace's suite — and a `sourcekit-lsp` that has not +/// answered `initialize` inside `request_timeout` is abandoned rather than waited on, so +/// every test in that process resolves to nothing. Only the clock is relaxed: what the tests +/// assert on is still resolved the way the defaults resolve it. +pub fn limits() -> Limits { + Limits { + budget: Duration::from_secs(300), + request_timeout: Duration::from_secs(120), + ..Limits::default() + } +} /// The bundles are checked in as tarballs, so a test reads one by unpacking it into a /// temporary directory that is removed with the `TempDir`. @@ -54,7 +71,7 @@ pub fn declaration_report, U: AsRef>( ORG_URL_SLUG.clone(), REPO_FULL_NAME.clone(), repo_root.as_ref(), - Limits::default(), + limits(), ) .expect("the declaration path reads the bundle"); @@ -151,7 +168,7 @@ pub fn assert_the_declaration_flag_moves_only_the_file( ORG_URL_SLUG.clone(), REPO_FULL_NAME.clone(), root, - Limits::default(), + limits(), ) .expect("the declaration path reads the bundle"); diff --git a/xcresult/tests/swift_test_xunit.rs b/xcresult/tests/swift_test_xunit.rs index 81b8735a..67a1184d 100644 --- a/xcresult/tests/swift_test_xunit.rs +++ b/xcresult/tests/swift_test_xunit.rs @@ -1,10 +1,13 @@ //! `swift test --xunit-output` reports no file at all, and needs no Xcode to produce — so //! this is the shape the declaration path takes on Linux. See the fixture's README. +mod common; + use std::{collections::HashMap, path::Path}; +use common::limits; use rstest::rstest; -use xcresult::test_locations::{Limits, TestKey, TestLocationIndex}; +use xcresult::test_locations::{TestKey, TestLocationIndex}; use xcresult::xcrun::find_program; const FIXTURE_ROOT: &str = "tests/fixture-src/swift-test-xunit"; @@ -73,7 +76,7 @@ fn resolve_from(xunit: &str) -> HashMap<(String, String), String> { ) }) .collect::>(); - let index = TestLocationIndex::resolve(Path::new(FIXTURE_ROOT), &keys, Limits::default()); + let index = TestLocationIndex::resolve(Path::new(FIXTURE_ROOT), &keys, limits()); cases .into_iter() @@ -233,7 +236,7 @@ mod parity { String::from("trunk"), String::from("github.com/trunk-io/analytics-cli"), Path::new(FIXTURE_ROOT), - Limits::default(), + limits(), ) .expect("the declaration path reads the bundle");