diff --git a/crates/cargo-wdk/src/actions/build/build_task.rs b/crates/cargo-wdk/src/actions/build/build_task.rs index 4540166ad..4c891b881 100644 --- a/crates/cargo-wdk/src/actions/build/build_task.rs +++ b/crates/cargo-wdk/src/actions/build/build_task.rs @@ -10,6 +10,7 @@ use std::path::{Path, PathBuf}; use anyhow::Result; use cargo_metadata::Message; use clap_cargo::Features; +use mockall::automock; use mockall_double::double; use tracing::debug; use wdk_build::CpuArchitecture; @@ -24,6 +25,7 @@ use crate::{ }; /// Parameters for constructing a [`BuildTask`]. +#[derive(Clone, Copy)] pub struct BuildTaskParams<'a> { /// The name of the package to build pub package_name: &'a str, @@ -41,6 +43,32 @@ pub struct BuildTaskParams<'a> { pub verbosity_level: clap_verbosity_flag::Verbosity, } +#[derive(Debug, Default)] +#[cfg_attr(test, allow(dead_code))] +pub struct BuildTaskRunner {} + +#[automock] +#[allow(clippy::unused_self, clippy::elidable_lifetime_names)] +#[cfg_attr(test, allow(dead_code))] +impl BuildTaskRunner { + // Returns `Box>` rather than `impl Iterator<...>` because + // this method is `#[automock]`ed. mockall must be able to *name* the return + // type to generate the mock's expectation storage, and it cannot mock an + // opaque `impl Trait` return. Boxing into a trait object gives mockall a + // concrete, nameable type while still forwarding `BuildTask::run`'s lazy + // message stream, so consumers like `get_target_dir_from_output` can + // short-circuit instead of parsing/allocating every cargo message up front. + pub fn run<'a>( + &self, + params: &BuildTaskParams<'a>, + command_exec: &CommandExec, + ) -> Result>>, BuildTaskError> { + BuildTask::new(*params, command_exec).run().map(|messages| { + Box::new(messages) as Box>> + }) + } +} + /// Builds specified package by running `cargo build` pub struct BuildTask<'a> { params: BuildTaskParams<'a>, @@ -90,9 +118,14 @@ impl<'a> BuildTask<'a> { /// not a valid unicode /// * `BuildTaskError::CargoBuild` - If there is an error running the `cargo /// build` command + // `+ use<>` opts this RPIT out of capturing the `&self` lifetime (edition + // 2024 captures in-scope lifetimes by default). The returned iterator owns + // its buffer (`Cursor>`), so it is effectively `'static`; opting out + // of the capture lets `BuildTaskRunner::run` move it out of the temporary + // `BuildTask` and box it as a `'static` trait object. pub fn run( &self, - ) -> Result>, BuildTaskError> { + ) -> Result> + use<>, BuildTaskError> { debug!("Running cargo build"); let mut args = vec!["build".to_string()]; args.push("--message-format=json-render-diagnostics".to_string()); diff --git a/crates/cargo-wdk/src/actions/build/mod.rs b/crates/cargo-wdk/src/actions/build/mod.rs index 94ba1d017..fd1bbfa37 100644 --- a/crates/cargo-wdk/src/actions/build/mod.rs +++ b/crates/cargo-wdk/src/actions/build/mod.rs @@ -18,13 +18,17 @@ use std::{ }; use anyhow::Result; -use build_task::{BuildTask, BuildTaskParams}; +use build_task::BuildTaskParams; +#[double] +use build_task::BuildTaskRunner; use cargo_metadata::{CrateType, Message, Metadata as CargoMetadata, Package, TargetKind}; use clap_cargo::Features; use error::BuildActionError; use mockall_double::double; +use package_task::PackageTaskParams; +#[double] +use package_task::PackageTaskRunner; pub use package_task::SignMode; -use package_task::{PackageTask, PackageTaskParams}; use tracing::{debug, error as err, info, trace, warn}; use wdk_build::{ CpuArchitecture, @@ -63,6 +67,8 @@ pub struct BuildAction<'a> { command_exec: &'a CommandExec, fs: &'a Fs, metadata: &'a Metadata, + build_task_runner: BuildTaskRunner, + package_task_runner: PackageTaskRunner, } impl<'a> BuildAction<'a> { @@ -89,6 +95,26 @@ impl<'a> BuildAction<'a> { command_exec: &'a CommandExec, fs: &'a Fs, metadata: &'a Metadata, + ) -> Result { + Self::new_with_runners( + params, + wdk_build, + command_exec, + fs, + metadata, + BuildTaskRunner::default(), + PackageTaskRunner::default(), + ) + } + + fn new_with_runners( + params: &BuildActionParams<'a>, + wdk_build: &'a WdkBuild, + command_exec: &'a CommandExec, + fs: &'a Fs, + metadata: &'a Metadata, + build_task_runner: BuildTaskRunner, + package_task_runner: PackageTaskRunner, ) -> Result { // TODO: validate params anyhow::ensure!( @@ -108,6 +134,8 @@ impl<'a> BuildAction<'a> { command_exec, fs, metadata, + build_task_runner, + package_task_runner, }) } @@ -345,8 +373,8 @@ impl<'a> BuildAction<'a> { let package_name = package.name.as_str(); info!("Building package {package_name}"); - let build_task = BuildTask::new( - BuildTaskParams { + let output_message_iter = self.build_task_runner.run( + &BuildTaskParams { package_name, working_dir, profile: self.profile, @@ -356,8 +384,7 @@ impl<'a> BuildAction<'a> { verbosity_level: self.verbosity_level, }, self.command_exec, - ); - let output_message_iter = build_task.run()?; + )?; let wdk_metadata = if let Ok(wdk_metadata) = wdk_metadata { debug!("Found wdk metadata in package: {}", package_name); @@ -398,8 +425,8 @@ impl<'a> BuildAction<'a> { target_dir.display() ); - PackageTask::new( - PackageTaskParams { + self.package_task_runner.run( + &PackageTaskParams { package_name, working_dir, target_dir: &target_dir, @@ -411,8 +438,7 @@ impl<'a> BuildAction<'a> { self.wdk_build, self.command_exec, self.fs, - ) - .run()?; + )?; info!("Finished building {package_name}"); Ok(()) diff --git a/crates/cargo-wdk/src/actions/build/package_task.rs b/crates/cargo-wdk/src/actions/build/package_task.rs index 7bd2f3299..e599cf4f7 100644 --- a/crates/cargo-wdk/src/actions/build/package_task.rs +++ b/crates/cargo-wdk/src/actions/build/package_task.rs @@ -15,6 +15,7 @@ use std::{ result::Result, }; +use mockall::automock; use mockall_double::double; use tracing::{debug, info, warn}; use wdk_build::{CpuArchitecture, DriverConfig}; @@ -50,7 +51,7 @@ const WDR_TEST_CERT_STORE: &str = "WDRTestCertStore"; const WDR_LOCAL_TEST_CERT: &str = "WDRLocalTestCert"; const STAMPINF_VERSION_ENV_VAR: &str = "STAMPINF_VERSION"; -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct PackageTaskParams<'a> { pub package_name: &'a str, pub working_dir: &'a Path, @@ -61,6 +62,25 @@ pub struct PackageTaskParams<'a> { pub driver_model: DriverConfig, } +#[derive(Debug, Default)] +#[cfg_attr(test, allow(dead_code))] +pub struct PackageTaskRunner {} + +#[automock] +#[allow(clippy::unused_self, clippy::elidable_lifetime_names)] +#[cfg_attr(test, allow(dead_code))] +impl PackageTaskRunner { + pub fn run<'a>( + &self, + params: &PackageTaskParams<'a>, + wdk_build: &WdkBuild, + command_exec: &CommandExec, + fs: &Fs, + ) -> Result<(), PackageTaskError> { + PackageTask::new(params.clone(), wdk_build, command_exec, fs).run() + } +} + /// Supports low level driver packaging operations pub struct PackageTask<'a> { package_name: String, @@ -638,15 +658,38 @@ impl Drop for NamedMutex { } #[cfg(test)] +#[allow(clippy::needless_pass_by_value)] mod tests { use std::{ + os::windows::process::ExitStatusExt, path::PathBuf, process::{ExitStatus, Output}, + sync::Mutex, }; - use wdk_build::{CpuArchitecture, KmdfConfig}; + use mockall::predicate::eq; + use wdk_build::{CpuArchitecture, DriverConfig, KmdfConfig}; use super::*; + use crate::providers::{ + error::{CommandError, FileError}, + exec::MockCommandExec, + fs::MockFs, + wdk_build::MockWdkBuild, + }; + + // Serializes test execution within this module. The + // `stampinf_version_overrides_with_env_var` test mutates the + // `STAMPINF_VERSION` env var via `with_env`, which is process-global state. + // Other tests that call `run_stampinf()` read the same env var, so parallel + // execution causes non-deterministic failures when the env var is + // unexpectedly set. + // + // Lock acquisition recovers from poisoning + // (`unwrap_or_else(PoisonError::into_inner)`) so that a panic in one test does + // not cascade into unrelated failures across every other test that takes this + // lock. + static TEST_MUTEX: Mutex<()> = Mutex::new(()); #[test] fn new_succeeds_for_valid_args() { @@ -772,8 +815,279 @@ mod tests { PackageTask::new(package_task_params, &wdk_build, &command_exec, &fs); } + #[test] + fn run_packages_driver_with_expected_operations() { + let _lock = lock_test(); + let mut harness = PackageTaskHarness::new(); + let paths = harness.paths(); + + harness + .expect_prepare_inf(false) + .expect_generate_self_signed_cert_and_sign() + .expect_infverif(paths.dest_inf_file_path, "/w", None, true); + + harness.run_and_expect_success(); + } + + #[test] + fn run_verifies_signatures_when_enabled() { + let _lock = lock_test(); + let mut harness = PackageTaskHarness::new().with_sign_mode(SignMode::Test { + verify_signature: true, + }); + let paths = harness.paths(); + + harness + .expect_successful_packaging_without_verification() + .expect_signtool_verify(paths.dest_driver_binary_path) + .expect_signtool_verify(paths.dest_cat_file_path); + + harness.run_and_expect_success(); + } + + #[test] + fn run_skips_signing_and_verification_when_sign_mode_is_off() { + let _lock = lock_test(); + let mut harness = PackageTaskHarness::new().with_sign_mode(SignMode::Off); + let paths = harness.paths(); + + // With SignMode::Off, certificate generation and signtool sign/verify are + // skipped. No expectations are set for those commands, so any attempt to + // run them would fail the test as an unmatched mock expectation. + harness.expect_prepare_inf(false).expect_infverif( + paths.dest_inf_file_path, + "/w", + None, + true, + ); + + harness.run_and_expect_success(); + } + + #[test] + fn run_exports_certificate_from_store_when_it_already_exists() { + let _lock = lock_test(); + let mut harness = PackageTaskHarness::new(); + let paths = harness.paths(); + + harness + .expect_prepare_inf(true) + .expect_exists(paths.src_cert_file_path.clone(), false) + .expect_certmgr_exists_check(Ok(certmgr_store_output(true))) + .expect_certmgr_create_cert_from_store(paths.src_cert_file_path.clone()) + .expect_copy(paths.src_cert_file_path, paths.dest_cert_file_path, Ok(1)) + .expect_signtool_sign(paths.dest_driver_binary_path, true) + .expect_signtool_sign(paths.dest_cat_file_path, true) + .expect_infverif(paths.dest_inf_file_path, "/w", None, true); + + harness.run_and_expect_success(); + } + + #[test] + fn run_returns_error_when_inx_file_is_missing() { + let _lock = lock_test(); + let mut harness = PackageTaskHarness::new(); + let paths = harness.paths(); + harness.expect_exists(paths.src_inx_file_path.clone(), false); + + let task = harness.task(); + let result = task.run(); + + assert!(matches!( + result, + Err(PackageTaskError::MissingInxSrcFile(path)) + if path == paths.src_inx_file_path + )); + } + + #[test] + fn run_returns_error_when_copying_driver_binary_fails() { + let _lock = lock_test(); + let mut harness = PackageTaskHarness::new(); + let paths = harness.paths(); + + harness + .expect_exists(paths.src_inx_file_path, true) + .expect_exists(paths.dest_root_package_folder, true) + .expect_rename( + paths.src_driver_binary_file_path, + paths.src_renamed_driver_binary_file_path.clone(), + Ok(()), + ) + .expect_copy( + paths.src_renamed_driver_binary_file_path.clone(), + paths.dest_driver_binary_path.clone(), + Err(FileError::CopyError( + paths.src_renamed_driver_binary_file_path.clone(), + paths.dest_driver_binary_path.clone(), + std::io::Error::new(std::io::ErrorKind::PermissionDenied, "copy failed"), + )), + ); + + let task = harness.task(); + let result = task.run(); + + assert!(matches!( + result, + Err(PackageTaskError::FileIo(FileError::CopyError(src, dest, _))) + if src == paths.src_renamed_driver_binary_file_path + && dest == paths.dest_driver_binary_path + )); + } + + #[test] + fn run_returns_error_when_stampinf_fails() { + let _lock = lock_test(); + let mut harness = PackageTaskHarness::new(); + let paths = harness.paths(); + + harness.expect_copy_artifacts(true).expect_stampinf( + paths.dest_inf_file_path, + CpuArchitecture::Amd64, + Some(String::from("1.33")), + false, + ); + + let task = harness.task(); + let result = task.run(); + + assert!(matches!(result, Err(PackageTaskError::StampinfCommand(_)))); + } + + #[test] + fn run_returns_error_when_inf2cat_fails() { + let _lock = lock_test(); + let mut harness = PackageTaskHarness::new(); + let paths = harness.paths(); + + harness + .expect_copy_artifacts(true) + .expect_stampinf( + paths.dest_inf_file_path, + CpuArchitecture::Amd64, + Some(String::from("1.33")), + true, + ) + .expect_inf2cat(paths.dest_root_package_folder, "10_x64", false); + + let task = harness.task(); + let result = task.run(); + + assert!(matches!(result, Err(PackageTaskError::Inf2CatCommand(_)))); + } + + #[test] + fn run_returns_error_when_infverif_fails() { + let _lock = lock_test(); + let mut harness = PackageTaskHarness::new(); + let paths = harness.paths(); + + harness.expect_prepare_inf(true).expect_infverif( + paths.dest_inf_file_path, + "/w", + None, + false, + ); + + let task = harness.task(); + let result = task.run(); + + assert!(matches!( + result, + Err(PackageTaskError::InfVerificationCommand(_)) + )); + } + + #[test] + fn run_returns_error_when_certmgr_fails() { + let _lock = lock_test(); + let mut harness = PackageTaskHarness::new(); + let paths = harness.paths(); + + // Packaging succeeds up to infverif; certificate generation then queries the + // store via certmgr, which fails here. + harness + .expect_prepare_inf(true) + .expect_infverif(paths.dest_inf_file_path, "/w", None, true) + .expect_exists(paths.src_cert_file_path, false) + .expect_certmgr_exists_check(Err(command_error("certmgr.exe"))); + + let task = harness.task(); + let result = task.run(); + + assert!(matches!( + result, + Err(PackageTaskError::VerifyCertExistsInStoreCommand(_)) + )); + } + + #[test] + fn run_returns_error_when_makecert_fails() { + let _lock = lock_test(); + let mut harness = PackageTaskHarness::new(); + let paths = harness.paths(); + + // No cert on disk and none in the store, so the task falls through to + // makecert, which fails here. + harness + .expect_prepare_inf(true) + .expect_infverif(paths.dest_inf_file_path, "/w", None, true) + .expect_exists(paths.src_cert_file_path.clone(), false) + .expect_certmgr_exists_check(Ok(certmgr_store_output(false))) + .expect_certmgr_exists_check(Ok(certmgr_store_output(false))) + .expect_makecert(paths.src_cert_file_path, false); + + let task = harness.task(); + let result = task.run(); + + assert!(matches!( + result, + Err(PackageTaskError::CertGenerationInStoreCommand(_)) + )); + } + + #[test] + fn run_returns_error_when_signtool_fails() { + let _lock = lock_test(); + let mut harness = PackageTaskHarness::new(); + let paths = harness.paths(); + + // Certificate already on disk, so signing proceeds directly; signtool fails + // on the driver binary. + harness + .expect_prepare_inf(true) + .expect_infverif(paths.dest_inf_file_path, "/w", None, true) + .expect_exists(paths.src_cert_file_path.clone(), true) + .expect_copy(paths.src_cert_file_path, paths.dest_cert_file_path, Ok(1)) + .expect_signtool_sign(paths.dest_driver_binary_path, false); + + let task = harness.task(); + let result = task.run(); + + assert!(matches!( + result, + Err(PackageTaskError::DriverBinarySignCommand(_)) + )); + } + + #[test] + fn run_skips_infverif_for_samples_when_wdk_build_is_in_bugged_range() { + let _lock = lock_test(); + let mut harness = PackageTaskHarness::new().with_sample_class(true); + + // infverif calls detect_wdk_build_number and skips for samples in the bugged + // range, so no infverif command is expected. + harness + .expect_prepare_inf(true) + .expect_generate_self_signed_cert_and_sign() + .expect_wdk_build_number(25798); + + harness.run_and_expect_success(); + } + #[test] fn stampinf_version_overrides_with_env_var() { + let _lock = lock_test(); // verify both with and without the env var set scenarios let scenarios = [ ("env_set", Some("1.2.3.4"), true), @@ -839,6 +1153,550 @@ mod tests { } } + struct PackageTaskHarness { + package_name: &'static str, + working_dir: PathBuf, + target_dir: PathBuf, + arch: CpuArchitecture, + sign_mode: SignMode, + sample_class: bool, + driver_model: DriverConfig, + command_exec: MockCommandExec, + wdk_build: MockWdkBuild, + fs: MockFs, + } + + impl PackageTaskHarness { + fn new() -> Self { + Self { + package_name: "sample-driver", + working_dir: PathBuf::from(r"C:\abs\sample-driver"), + target_dir: PathBuf::from(r"C:\abs\sample-driver\target\debug"), + arch: CpuArchitecture::Amd64, + sign_mode: SignMode::Test { + verify_signature: false, + }, + sample_class: false, + driver_model: DriverConfig::Kmdf(KmdfConfig { + kmdf_version_major: 1, + target_kmdf_version_minor: 33, + minimum_kmdf_version_minor: Some(33), + }), + command_exec: MockCommandExec::new(), + wdk_build: MockWdkBuild::new(), + fs: MockFs::new(), + } + } + + fn with_sign_mode(mut self, sign_mode: SignMode) -> Self { + self.sign_mode = sign_mode; + self + } + + fn with_sample_class(mut self, sample_class: bool) -> Self { + self.sample_class = sample_class; + self + } + + fn paths(&self) -> PackageTaskPaths { + PackageTaskPaths { + src_inx_file_path: self.src_inx_file_path(), + src_driver_binary_file_path: self.src_driver_binary_file_path(), + src_renamed_driver_binary_file_path: self.src_renamed_driver_binary_file_path(), + src_pdb_file_path: self.src_pdb_file_path(), + src_map_file_path: self.src_map_file_path(), + src_cert_file_path: self.src_cert_file_path(), + dest_root_package_folder: self.dest_root_package_folder(), + dest_inf_file_path: self.dest_inf_file_path(), + dest_driver_binary_path: self.dest_driver_binary_path(), + dest_pdb_file_path: self.dest_pdb_file_path(), + dest_map_file_path: self.dest_map_file_path(), + dest_cert_file_path: self.dest_cert_file_path(), + dest_cat_file_path: self.dest_cat_file_path(), + } + } + + fn task(&self) -> PackageTask<'_> { + PackageTask::new( + PackageTaskParams { + package_name: self.package_name, + working_dir: &self.working_dir, + target_dir: &self.target_dir, + target_arch: &self.arch, + sign_mode: self.sign_mode, + sample_class: self.sample_class, + driver_model: self.driver_model.clone(), + }, + &self.wdk_build, + &self.command_exec, + &self.fs, + ) + } + + fn normalized_package_name(&self) -> String { + self.package_name.replace('-', "_") + } + + fn src_inx_file_path(&self) -> PathBuf { + self.working_dir + .join(format!("{}.inx", self.normalized_package_name())) + } + + fn src_driver_binary_file_path(&self) -> PathBuf { + self.target_dir + .join(format!("{}.dll", self.normalized_package_name())) + } + + fn src_renamed_driver_binary_file_path(&self) -> PathBuf { + self.target_dir + .join(format!("{}.sys", self.normalized_package_name())) + } + + fn src_pdb_file_path(&self) -> PathBuf { + self.target_dir + .join(format!("{}.pdb", self.normalized_package_name())) + } + + fn src_map_file_path(&self) -> PathBuf { + self.target_dir + .join("deps") + .join(format!("{}.map", self.normalized_package_name())) + } + + fn src_cert_file_path(&self) -> PathBuf { + self.target_dir.join(format!("{WDR_LOCAL_TEST_CERT}.cer")) + } + + fn dest_root_package_folder(&self) -> PathBuf { + self.target_dir + .join(format!("{}_package", self.normalized_package_name())) + } + + fn dest_inf_file_path(&self) -> PathBuf { + self.dest_root_package_folder() + .join(format!("{}.inf", self.normalized_package_name())) + } + + fn dest_driver_binary_path(&self) -> PathBuf { + self.dest_root_package_folder() + .join(format!("{}.sys", self.normalized_package_name())) + } + + fn dest_pdb_file_path(&self) -> PathBuf { + self.dest_root_package_folder() + .join(format!("{}.pdb", self.normalized_package_name())) + } + + fn dest_map_file_path(&self) -> PathBuf { + self.dest_root_package_folder() + .join(format!("{}.map", self.normalized_package_name())) + } + + fn dest_cert_file_path(&self) -> PathBuf { + self.dest_root_package_folder() + .join(format!("{WDR_LOCAL_TEST_CERT}.cer")) + } + + fn dest_cat_file_path(&self) -> PathBuf { + self.dest_root_package_folder() + .join(format!("{}.cat", self.normalized_package_name())) + } + } + + #[derive(Clone)] + struct PackageTaskPaths { + src_inx_file_path: PathBuf, + src_driver_binary_file_path: PathBuf, + src_renamed_driver_binary_file_path: PathBuf, + src_pdb_file_path: PathBuf, + src_map_file_path: PathBuf, + src_cert_file_path: PathBuf, + dest_root_package_folder: PathBuf, + dest_inf_file_path: PathBuf, + dest_driver_binary_path: PathBuf, + dest_pdb_file_path: PathBuf, + dest_map_file_path: PathBuf, + dest_cert_file_path: PathBuf, + dest_cat_file_path: PathBuf, + } + + impl PackageTaskHarness { + /// Expects the `.inx` check, the destination-folder check (and its + /// creation when `dest_root_exists` is false), the driver-binary + /// rename, and the copies of every packaged artifact (driver + /// binary, pdb, inf, map). This is the common prefix of every + /// successful packaging run up to (but not including) + /// `stampinf`. + fn expect_copy_artifacts(&mut self, dest_root_exists: bool) -> &mut Self { + let paths = self.paths(); + self.expect_exists(paths.src_inx_file_path.clone(), true) + .expect_exists(paths.dest_root_package_folder.clone(), dest_root_exists); + if !dest_root_exists { + self.expect_create_dir_ok(paths.dest_root_package_folder); + } + self.expect_rename( + paths.src_driver_binary_file_path, + paths.src_renamed_driver_binary_file_path.clone(), + Ok(()), + ) + .expect_copy( + paths.src_renamed_driver_binary_file_path, + paths.dest_driver_binary_path, + Ok(1), + ) + .expect_copy(paths.src_pdb_file_path, paths.dest_pdb_file_path, Ok(1)) + .expect_copy(paths.src_inx_file_path, paths.dest_inf_file_path, Ok(1)) + .expect_copy(paths.src_map_file_path, paths.dest_map_file_path, Ok(1)) + } + + /// Expects a successful `stampinf` followed by a successful `inf2cat`. + fn expect_stampinf_and_inf2cat(&mut self) -> &mut Self { + let paths = self.paths(); + self.expect_stampinf( + paths.dest_inf_file_path, + CpuArchitecture::Amd64, + Some(String::from("1.33")), + true, + ) + .expect_inf2cat(paths.dest_root_package_folder, "10_x64", true) + } + + /// Expects the full inf-preparation pipeline: artifact copies, + /// `stampinf`, and `inf2cat`. + fn expect_prepare_inf(&mut self, dest_root_exists: bool) -> &mut Self { + self.expect_copy_artifacts(dest_root_exists) + .expect_stampinf_and_inf2cat() + } + + /// Expects the self-signed-certificate path (no cert on disk or in the + /// store, so `makecert` generates one) followed by signing the driver + /// binary and catalog file. + fn expect_generate_self_signed_cert_and_sign(&mut self) -> &mut Self { + let paths = self.paths(); + self.expect_exists(paths.src_cert_file_path.clone(), false) + .expect_certmgr_exists_check(Ok(certmgr_store_output(false))) + .expect_certmgr_exists_check(Ok(certmgr_store_output(false))) + .expect_makecert(paths.src_cert_file_path.clone(), true) + .expect_copy(paths.src_cert_file_path, paths.dest_cert_file_path, Ok(1)) + .expect_signtool_sign(paths.dest_driver_binary_path, true) + .expect_signtool_sign(paths.dest_cat_file_path, true) + } + + /// Runs the packaging task and asserts it succeeds. + fn run_and_expect_success(&self) { + let result = self.task().run(); + assert!( + result.is_ok(), + "package task failed unexpectedly: {result:?}" + ); + } + + fn expect_successful_packaging_without_verification(&mut self) -> &mut Self { + let paths = self.paths(); + self.expect_prepare_inf(true) + .expect_exists(paths.src_cert_file_path.clone(), true) + .expect_copy(paths.src_cert_file_path, paths.dest_cert_file_path, Ok(1)) + .expect_signtool_sign(paths.dest_driver_binary_path, true) + .expect_signtool_sign(paths.dest_cat_file_path, true) + .expect_infverif(paths.dest_inf_file_path, "/w", None, true) + } + + fn expect_exists(&mut self, path: PathBuf, exists: bool) -> &mut Self { + self.fs + .expect_exists() + .with(eq(path)) + .once() + .return_once(move |_| exists); + self + } + + fn expect_create_dir_ok(&mut self, path: PathBuf) -> &mut Self { + self.fs + .expect_create_dir() + .with(eq(path)) + .once() + .return_once(|_| Ok(())); + self + } + + fn expect_rename( + &mut self, + src: PathBuf, + dest: PathBuf, + result: Result<(), FileError>, + ) -> &mut Self { + self.fs + .expect_rename() + .with(eq(src), eq(dest)) + .once() + .return_once(move |_, _| result); + self + } + + fn expect_copy( + &mut self, + src: PathBuf, + dest: PathBuf, + result: Result, + ) -> &mut Self { + self.fs + .expect_copy() + .with(eq(src), eq(dest)) + .once() + .return_once(move |_, _| result); + self + } + + /// Sets a single `command_exec.run` expectation matching `command` with + /// an exact argument list, returning `result`. The success and + /// failure cases share this one matcher, differing only in + /// `result`. + fn expect_command( + &mut self, + command: &'static str, + expected_args: Vec, + result: Result, + ) -> &mut Self { + self.command_exec + .expect_run() + .withf(move |cmd, args, _, _| { + cmd == command + && args.len() == expected_args.len() + && args + .iter() + .zip(&expected_args) + .all(|(a, e)| *a == e.as_str()) + }) + .once() + .return_once(move |_, _, _, _| result); + self + } + + fn expect_stampinf( + &mut self, + dest_inf_file_path: PathBuf, + arch: CpuArchitecture, + wdf_version: Option, + succeeds: bool, + ) -> &mut Self { + self.command_exec + .expect_run() + .withf(move |command, args, _, _| { + let dest_inf = dest_inf_file_path.to_string_lossy().to_string(); + command == "stampinf" + && args.len() >= 8 + && args[0] == "-f" + && args[1] == dest_inf + && args[2] == "-d" + && args[3] == "*" + && args[4] == "-a" + && args[5] == arch.to_string() + && args[6] == "-c" + && args[7] == "sample_driver.cat" + && args.windows(2).any(|window| window == ["-v", "*"]) + && wdf_version.as_deref().is_none_or(|version| { + args.windows(2).any(|window| window == ["-k", version]) + }) + }) + .once() + .return_once(move |_, _, _, _| command_outcome("stampinf", succeeds)); + self + } + + fn expect_inf2cat( + &mut self, + dest_root: PathBuf, + os_mapping: &str, + succeeds: bool, + ) -> &mut Self { + let trimmed_dest_root = dest_root + .to_string_lossy() + .trim_start_matches("\\\\?\\") + .to_string(); + self.expect_command( + "inf2cat", + vec![ + format!("/driver:{trimmed_dest_root}"), + format!("/os:{os_mapping}"), + "/uselocaltime".to_string(), + ], + command_outcome("inf2cat", succeeds), + ) + } + + fn expect_certmgr_exists_check( + &mut self, + result: Result, + ) -> &mut Self { + self.expect_command( + "certmgr.exe", + vec!["-s".to_string(), WDR_TEST_CERT_STORE.to_string()], + result, + ) + } + + fn expect_certmgr_create_cert_from_store(&mut self, cert_path: PathBuf) -> &mut Self { + let cert_path = cert_path.to_string_lossy().to_string(); + self.expect_command( + "certmgr.exe", + vec![ + "-put".to_string(), + "-s".to_string(), + WDR_TEST_CERT_STORE.to_string(), + "-c".to_string(), + "-n".to_string(), + WDR_LOCAL_TEST_CERT.to_string(), + cert_path, + ], + Ok(success_output()), + ) + } + + fn expect_makecert(&mut self, cert_path: PathBuf, succeeds: bool) -> &mut Self { + let cert_path = cert_path.to_string_lossy().to_string(); + self.expect_command( + "makecert", + vec![ + "-r".to_string(), + "-pe".to_string(), + "-a".to_string(), + "SHA256".to_string(), + "-eku".to_string(), + "1.3.6.1.5.5.7.3.3".to_string(), + "-ss".to_string(), + WDR_TEST_CERT_STORE.to_string(), + "-n".to_string(), + "CN=WDRLocalTestCert".to_string(), + cert_path, + ], + command_outcome("makecert", succeeds), + ) + } + + fn expect_signtool_sign(&mut self, file_path: PathBuf, succeeds: bool) -> &mut Self { + let file_path = file_path.to_string_lossy().to_string(); + self.expect_command( + "signtool", + vec![ + "sign".to_string(), + "/v".to_string(), + "/s".to_string(), + WDR_TEST_CERT_STORE.to_string(), + "/n".to_string(), + WDR_LOCAL_TEST_CERT.to_string(), + "/t".to_string(), + "http://timestamp.digicert.com".to_string(), + "/fd".to_string(), + "SHA256".to_string(), + file_path, + ], + command_outcome("signtool", succeeds), + ) + } + + fn expect_signtool_verify(&mut self, file_path: PathBuf) -> &mut Self { + let file_path = file_path.to_string_lossy().to_string(); + self.expect_command( + "signtool", + vec![ + "verify".to_string(), + "/v".to_string(), + "/pa".to_string(), + file_path, + ], + Ok(success_output()), + ) + } + + fn expect_infverif( + &mut self, + inf_path: PathBuf, + driver_flag: &str, + sample_flag: Option<&'static str>, + succeeds: bool, + ) -> &mut Self { + let inf_path = inf_path.to_string_lossy().to_string(); + let mut expected_args = vec!["/v".to_string(), driver_flag.to_string()]; + if let Some(sample_flag) = sample_flag { + expected_args.push(sample_flag.to_string()); + } + expected_args.push(inf_path); + self.expect_command( + "infverif", + expected_args, + command_outcome("infverif", succeeds), + ) + } + + fn expect_wdk_build_number(&mut self, build_number: u32) -> &mut Self { + self.wdk_build + .expect_detect_wdk_build_number() + .once() + .return_once(move || Ok(build_number)); + self + } + } + + fn lock_test() -> std::sync::MutexGuard<'static, ()> { + TEST_MUTEX + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner) + } + + fn success_output() -> Output { + Output { + status: ExitStatus::default(), + stdout: vec![], + stderr: vec![], + } + } + + /// Builds the `command_exec.run` result for a command expectation: a + /// success output when `succeeds`, otherwise a `CommandError` for + /// `command`. + fn command_outcome(command: &'static str, succeeds: bool) -> Result { + if succeeds { + Ok(success_output()) + } else { + Err(command_error(command)) + } + } + + /// Builds the `certmgr.exe -s` stdout indicating whether the test cert is + /// present in the store. + fn certmgr_store_output(has_cert: bool) -> Output { + let stdout = if has_cert { + r"==============Certificate # 1 ========== + Subject:: + [0,0] 2.5.4.3 (CN) WDRLocalTestCert + CertMgr Succeeded" + .as_bytes() + .to_vec() + } else { + r"==============No Certificates ========== + CertMgr Succeeded" + .as_bytes() + .to_vec() + }; + Output { + status: ExitStatus::default(), + stdout, + stderr: vec![], + } + } + + fn command_error(command: &'static str) -> CommandError { + CommandError::from_output( + command, + &[], + &Output { + status: ExitStatus::from_raw(1), + stdout: vec![], + stderr: b"command failed".to_vec(), + }, + ) + } + mod named_mutex { use std::{ ffi::CString, diff --git a/crates/cargo-wdk/src/actions/build/tests.rs b/crates/cargo-wdk/src/actions/build/tests.rs index 882ffda26..eb1cd71bb 100644 --- a/crates/cargo-wdk/src/actions/build/tests.rs +++ b/crates/cargo-wdk/src/actions/build/tests.rs @@ -2,3086 +2,1317 @@ // License: MIT OR Apache-2.0 #![allow(clippy::too_many_lines)] // Package tests are longer and splitting them into sub functions can make the code less readable #![allow(clippy::ref_option_ref)] // This is suppressed for mockall as it generates mocks with env_vars: &Option + use std::{ collections::HashMap, + fs, + io, os::windows::process::ExitStatusExt, path::{Path, PathBuf}, process::{ExitStatus, Output}, - result::Result::Ok, + sync::atomic::{AtomicU64, Ordering}, }; -use cargo_metadata::Metadata as CargoMetadata; +use cargo_metadata::{Message, Metadata as CargoMetadata}; use clap_cargo::Features; use mockall::predicate::eq; -use mockall_double::double; -use wdk_build::{ - CpuArchitecture, - DriverConfig, - metadata::{TryFromCargoMetadataError, Wdk}, -}; - -#[double] -use crate::providers::{ - exec::CommandExec, - fs::Fs, - metadata::Metadata as MetadataProvider, - wdk_build::WdkBuild, +use wdk_build::{CpuArchitecture, DriverConfig, metadata::TryFromCargoMetadataError}; + +use super::{ + BuildAction, + BuildActionError, + BuildActionParams, + SignMode, + build_task::{BuildTaskParams, MockBuildTaskRunner}, + package_task::{MockPackageTaskRunner, PackageTaskParams}, }; use crate::{ - actions::{ - Profile, - build::{BuildAction, BuildActionParams, SignMode, error::BuildActionError}, - to_target_triple, + actions::{Profile, to_target_triple}, + providers::{ + error::{CommandError, FileError}, + exec::MockCommandExec, + fs::MockFs, + metadata::MockMetadata, + wdk_build::MockWdkBuild, }, - providers::error::{CommandError, FileError}, }; -//////////////////////////////////////////////////////////////////////////////// -/// Standalone driver project tests -//////////////////////////////////////////////////////////////////////////////// -// Test name is of form Given When Then -// Given: A driver project -// When: Default values are provided -// Then: It builds successfully -#[test] -pub fn given_a_driver_project_when_default_values_are_provided_then_it_builds_successfully() { - // Input CLI args - let cwd = PathBuf::from("C:\\tmp"); - let profile = None; - let target_arch = CpuArchitecture::Amd64; - let verify_signature = false; - let sample_class = false; - // Driver project data - let driver_type = "KMDF"; - let driver_name = "sample-kmdf"; - let driver_version = "0.0.1"; - let wdk_metadata = get_cargo_metadata_wdk_metadata(driver_type, 1, 33); - let (workspace_member, package) = - get_cargo_metadata_package(&cwd, driver_name, driver_version, Some(&wdk_metadata)); - - let cargo_build_output = - create_cargo_build_output_json(driver_name, driver_version, &cwd, None, profile); - - let test_build_action = &TestBuildAction::new(cwd.clone(), profile, None, sample_class) - .set_up_standalone_driver_project((workspace_member, package)) - .expect_default_build_task_steps(driver_name, Some(cargo_build_output)) - .expect_probe_target_arch_using_cargo_rustc(&cwd, target_arch, None) - .expect_default_package_task_steps(driver_name, "KMDF", target_arch, verify_signature); - - assert_build_action_run_with_env_is_success( - &cwd, - profile, - None, - verify_signature, - sample_class, - test_build_action, - ); -} - -#[test] -pub fn given_a_driver_project_when_profile_is_release_then_it_builds_successfully() { - // Input CLI args - let cwd = PathBuf::from("C:\\tmp"); - let profile = Some(Profile::Release); - let target_arch = CpuArchitecture::Amd64; - let verify_signature = false; - let sample_class = false; - - // Driver project data - let driver_type = "KMDF"; - let driver_name = "sample-kmdf"; - let driver_version = "0.0.1"; - let wdk_metadata = get_cargo_metadata_wdk_metadata(driver_type, 1, 33); - let (workspace_member, package) = - get_cargo_metadata_package(&cwd, driver_name, driver_version, Some(&wdk_metadata)); - - let cargo_build_output = - create_cargo_build_output_json(driver_name, driver_version, &cwd, None, profile); - let test_build_action = &TestBuildAction::new(cwd.clone(), profile, None, sample_class) - .set_up_standalone_driver_project((workspace_member, package)) - .expect_default_build_task_steps(driver_name, Some(cargo_build_output)) - .expect_probe_target_arch_using_cargo_rustc(&cwd, target_arch, None) - .expect_default_package_task_steps(driver_name, "KMDF", target_arch, verify_signature); - - assert_build_action_run_with_env_is_success( - &cwd, - profile, - None, - verify_signature, - sample_class, - test_build_action, - ); -} - -#[test] -pub fn given_a_driver_project_when_target_arch_is_arm64_then_it_builds_successfully() { - // Input CLI args - let cwd = PathBuf::from("C:\\tmp"); - let profile = None; - let target_arch = CpuArchitecture::Arm64; - let verify_signature = false; - let sample_class = false; - - // Driver project data - let driver_type = "KMDF"; - let driver_name = "sample-kmdf"; - let driver_version = "0.0.1"; - let wdk_metadata = get_cargo_metadata_wdk_metadata(driver_type, 1, 33); - let (workspace_member, package) = - get_cargo_metadata_package(&cwd, driver_name, driver_version, Some(&wdk_metadata)); - - let cargo_build_output = create_cargo_build_output_json( - driver_name, - driver_version, - &cwd, - Some(&to_target_triple(target_arch)), - profile, - ); - let test_build_action = - &TestBuildAction::new(cwd.clone(), profile, Some(target_arch), sample_class) - .set_up_standalone_driver_project((workspace_member, package)) - .expect_default_build_task_steps(driver_name, Some(cargo_build_output)) - .expect_default_package_task_steps(driver_name, "KMDF", target_arch, verify_signature); - - assert_build_action_run_with_env_is_success( - &cwd, - profile, - Some(target_arch), - verify_signature, - sample_class, - test_build_action, - ); -} - -#[test] -pub fn given_a_driver_project_when_profile_is_release_and_target_arch_is_arm64_then_it_builds_successfully() - { - // Input CLI args - let cwd = PathBuf::from("C:\\tmp"); - let profile = Some(Profile::Release); - let target_arch = CpuArchitecture::Arm64; - let verify_signature = false; - let sample_class = false; - - // Driver project data - let driver_type = "KMDF"; - let driver_name = "sample-kmdf"; - let driver_version = "0.0.1"; - let wdk_metadata = get_cargo_metadata_wdk_metadata(driver_type, 1, 33); - let (workspace_member, package) = - get_cargo_metadata_package(&cwd, driver_name, driver_version, Some(&wdk_metadata)); - - let cargo_build_output = create_cargo_build_output_json( - driver_name, - driver_version, - &cwd, - Some(&to_target_triple(target_arch)), - profile, - ); - - let test_build_action = - &TestBuildAction::new(cwd.clone(), profile, Some(target_arch), sample_class) - .set_up_standalone_driver_project((workspace_member, package)) - .expect_default_build_task_steps(driver_name, Some(cargo_build_output)) - .expect_default_package_task_steps(driver_name, "KMDF", target_arch, verify_signature); - - assert_build_action_run_with_env_is_success( - &cwd, - profile, - Some(target_arch), - verify_signature, - sample_class, - test_build_action, - ); -} - -#[test] -pub fn given_a_driver_project_when_sample_class_is_true_then_it_builds_successfully() { - // Input CLI args - let cwd = PathBuf::from("C:\\tmp"); - let profile = None; - let target_arch = CpuArchitecture::Amd64; - let verify_signature = false; - let sample_class = true; - - // Driver project data - let driver_type = "KMDF"; - let driver_name = "sample-kmdf"; - let driver_version = "0.0.1"; - let wdk_metadata = get_cargo_metadata_wdk_metadata(driver_type, 1, 33); - let (workspace_member, package) = - get_cargo_metadata_package(&cwd, driver_name, driver_version, Some(&wdk_metadata)); - - let cargo_build_output = - create_cargo_build_output_json(driver_name, driver_version, &cwd, None, profile); - let test_build_action = &TestBuildAction::new(cwd.clone(), profile, None, sample_class) - .set_up_standalone_driver_project((workspace_member, package)) - .expect_default_build_task_steps(driver_name, Some(cargo_build_output)) - .expect_probe_target_arch_using_cargo_rustc(&cwd, target_arch, None) - .expect_default_package_task_steps(driver_name, driver_type, target_arch, verify_signature) - .expect_detect_wdk_build_number(25100u32); - - assert_build_action_run_with_env_is_success( - &cwd, - profile, - None, - verify_signature, - sample_class, - test_build_action, - ); -} - -#[test] -pub fn given_a_driver_project_when_verify_signature_is_true_then_it_builds_successfully() { - // Input CLI args - let cwd = PathBuf::from("C:\\tmp"); - let profile = None; - let target_arch = CpuArchitecture::Amd64; - let verify_signature = true; - let sample_class = false; - - // Driver project data - let driver_type = "KMDF"; - let driver_name = "sample-kmdf"; - let driver_version = "0.0.1"; - let wdk_metadata = get_cargo_metadata_wdk_metadata(driver_type, 1, 33); - let (workspace_member, package) = - get_cargo_metadata_package(&cwd, driver_name, driver_version, Some(&wdk_metadata)); - - let cargo_build_output = - create_cargo_build_output_json(driver_name, driver_version, &cwd, None, profile); - let test_build_action = &TestBuildAction::new(cwd.clone(), profile, None, sample_class) - .set_up_standalone_driver_project((workspace_member, package)) - .expect_default_build_task_steps(driver_name, Some(cargo_build_output)) - .expect_probe_target_arch_using_cargo_rustc(&cwd, target_arch, None) - .expect_default_package_task_steps(driver_name, driver_type, target_arch, verify_signature); - - assert_build_action_run_with_env_is_success( - &cwd, - profile, - None, - verify_signature, - sample_class, - test_build_action, - ); -} - -#[test] -pub fn given_a_driver_project_when_sign_mode_is_off_then_signing_and_verification_steps_are_skipped() - { - // Input CLI args - let cwd = PathBuf::from("C:\\tmp"); - let profile = None; - let target_arch = CpuArchitecture::Amd64; - let verify_signature = false; - let sample_class = false; - - // Driver project data - let driver_type = "KMDF"; - let driver_name = "sample-kmdf"; - let driver_version = "0.0.1"; - let wdk_metadata = get_cargo_metadata_wdk_metadata(driver_type, 1, 33); - let (workspace_member, package) = - get_cargo_metadata_package(&cwd, driver_name, driver_version, Some(&wdk_metadata)); - - let cargo_build_output = - create_cargo_build_output_json(driver_name, driver_version, &cwd, None, profile); - let test_build_action = &TestBuildAction::new(cwd.clone(), profile, None, sample_class) - .with_sign_mode(SignMode::Off) - .set_up_standalone_driver_project((workspace_member, package)) - .expect_default_build_task_steps(driver_name, Some(cargo_build_output)) - .expect_probe_target_arch_using_cargo_rustc(&cwd, target_arch, None) - .expect_package_task_steps_with_sign_mode_off(driver_name, driver_type, target_arch); - - assert_build_action_run_with_env_is_success( - &cwd, - profile, - None, - verify_signature, - sample_class, - test_build_action, - ); -} - -#[test] -pub fn given_a_driver_project_when_locked_is_set_then_it_is_forwarded_to_cargo_invocations() { - // Input CLI args - let cwd = PathBuf::from("C:\\tmp"); - let profile = None; - let target_arch = CpuArchitecture::Amd64; - let verify_signature = false; - let sample_class = false; - - // Driver project data - let driver_type = "KMDF"; - let driver_name = "sample-kmdf"; - let driver_version = "0.0.1"; - let wdk_metadata = get_cargo_metadata_wdk_metadata(driver_type, 1, 33); - let (workspace_member, package) = - get_cargo_metadata_package(&cwd, driver_name, driver_version, Some(&wdk_metadata)); - - let cargo_build_output = - create_cargo_build_output_json(driver_name, driver_version, &cwd, None, profile); - - let test_build_action = &TestBuildAction::new(cwd.clone(), profile, None, sample_class) - .with_locked(true) - .set_up_standalone_driver_project((workspace_member, package)) - .expect_default_build_task_steps(driver_name, Some(cargo_build_output)) - .expect_probe_target_arch_using_cargo_rustc(&cwd, target_arch, None) - .expect_default_package_task_steps(driver_name, driver_type, target_arch, verify_signature); - - assert_build_action_run_with_env_is_success( - &cwd, - profile, - None, - verify_signature, - sample_class, - test_build_action, - ); -} +const DEFAULT_DRIVER_NAME: &str = "sample-driver"; +const DEFAULT_DRIVER_VERSION: &str = "0.0.1"; -#[test] -pub fn given_a_driver_project_when_self_signed_exists_then_it_should_skip_calling_makecert() { - // Input CLI args - let cwd = PathBuf::from("C:\\tmp"); - let profile = None; - let target_arch = CpuArchitecture::Amd64; - let verify_signature = true; - let sample_class = false; - - // Driver project data - let driver_type = "KMDF"; - let driver_name = "sample-kmdf"; - let driver_version = "0.0.1"; - let wdk_metadata = get_cargo_metadata_wdk_metadata(driver_type, 1, 33); - let (workspace_member, package) = - get_cargo_metadata_package(&cwd, driver_name, driver_version, Some(&wdk_metadata)); - let expected_certmgr_output = Output { - status: ExitStatus::default(), - stdout: r"==============Certificate # 1 ========== - Subject:: - [0,0] 2.5.4.3 (CN) WDRLocalTestCert - Issuer:: - [0,0] 2.5.4.3 (CN) WDRLocalTestCert - SerialNumber:: - 5E 04 0D 63 35 20 76 A5 4A E1 96 BF CF 01 0F 96 - SHA1 Thumbprint:: - FB972842 C63CD369 E07D0C71 88E17921 B5813C71 - MD5 Thumbprint:: - 832B3F18 707EA3F6 54465207 345A93F1 - Provider Type:: 1 Provider Name:: Microsoft Strong Cryptographic Provider Container: 68f79a6e-6afa-4ec7-be5b-16d6656edd3f KeySpec: 2 - NotBefore:: - Tue Jan 28 13:51:04 2025 - NotAfter:: - Sun Jan 01 05:29:59 2040 - ==============No CTLs ========== - ==============No CRLs ========== - ============================================== - CertMgr Succeeded".as_bytes().to_vec(), - stderr: vec![], - }; +type PackageSpec<'a> = ( + &'a str, + PathBuf, + Option, + &'a str, + &'a str, + &'a str, +); - let expected_create_cert_output = Output { - status: ExitStatus::default(), - stdout: vec![], - stderr: vec![], - }; +mod standalone_driver_project { + use super::*; - let cargo_build_output = - create_cargo_build_output_json(driver_name, driver_version, &cwd, None, profile); - - let test_build_action = &TestBuildAction::new(cwd.clone(), profile, None, sample_class) - .set_up_standalone_driver_project((workspace_member, package)) - .expect_default_build_task_steps(driver_name, Some(cargo_build_output)) - .expect_probe_target_arch_using_cargo_rustc(&cwd, target_arch, None) - .expect_final_package_dir_exists(driver_name, &cwd, true) - .expect_inx_file_exists(driver_name, &cwd, true) - .expect_rename_driver_binary_dll_to_sys(driver_name, &cwd) - .expect_copy_driver_binary_sys_to_package_folder(driver_name, &cwd, true) - .expect_copy_pdb_file_to_package_folder(driver_name, &cwd, true) - .expect_copy_inx_file_to_package_folder(driver_name, &cwd, true, &cwd) - .expect_copy_map_file_to_package_folder(driver_name, &cwd, true) - .expect_stampinf(driver_name, &cwd, target_arch, None) - .expect_inf2cat(driver_name, &cwd, target_arch, None) - .expect_self_signed_cert_file_exists(&cwd, false) - .expect_certmgr_exists_check(Some(expected_certmgr_output)) - .expect_certmgr_create_cert_from_store(&cwd, Some(expected_create_cert_output)) - .expect_copy_self_signed_cert_file_to_package_folder(driver_name, &cwd, true) - .expect_signtool_sign_driver_binary_sys_file(driver_name, &cwd, None) - .expect_signtool_sign_cat_file(driver_name, &cwd, None) - .expect_infverif(driver_name, &cwd, "KMDF", None) - .expect_signtool_verify_driver_binary_sys_file(driver_name, &cwd, None) - .expect_signtool_verify_cat_file(driver_name, &cwd, None); - - assert_build_action_run_with_env_is_success( - &cwd, - profile, - None, - verify_signature, - sample_class, - test_build_action, - ); -} + #[test] + fn run_orchestrates_build_and_package_when_target_arch_is_detected() { + let cwd = PathBuf::from(r"C:\tmp\sample-driver"); + let target_dir = expected_target_dir(&cwd, None, None); + let mut harness = BuildActionHarness::new( + cwd.clone(), + None, + None, + SignMode::Test { + verify_signature: false, + }, + false, + ) + .set_up_standalone_driver_project(DEFAULT_DRIVER_NAME, Some(default_wdk_metadata())); -#[test] -pub fn given_a_driver_project_when_final_package_dir_exists_then_it_should_skip_creating_it() { - // Input CLI args - let cwd = PathBuf::from("C:\\tmp"); - let profile = None; - let target_arch = CpuArchitecture::Amd64; - let verify_signature = false; - let sample_class = false; - - // Driver project data - let driver_type = "KMDF"; - let driver_name = "sample-kmdf"; - let driver_version = "0.0.1"; - let wdk_metadata = get_cargo_metadata_wdk_metadata(driver_type, 1, 33); - let (workspace_member, package) = - get_cargo_metadata_package(&cwd, driver_name, driver_version, Some(&wdk_metadata)); - let expected_certmgr_output = get_certmgr_success_output(); - - let cargo_build_output = - create_cargo_build_output_json(driver_name, driver_version, &cwd, None, profile); - - let test_build_action = &TestBuildAction::new(cwd.clone(), profile, None, sample_class) - .set_up_standalone_driver_project((workspace_member, package)) - .expect_default_build_task_steps(driver_name, Some(cargo_build_output)) - .expect_probe_target_arch_using_cargo_rustc(&cwd, target_arch, None) - .expect_final_package_dir_exists(driver_name, &cwd, false) - .expect_dir_created(driver_name, &cwd, true) - .expect_inx_file_exists(driver_name, &cwd, true) - .expect_rename_driver_binary_dll_to_sys(driver_name, &cwd) - .expect_copy_driver_binary_sys_to_package_folder(driver_name, &cwd, true) - .expect_copy_pdb_file_to_package_folder(driver_name, &cwd, true) - .expect_copy_inx_file_to_package_folder(driver_name, &cwd, true, &cwd) - .expect_copy_map_file_to_package_folder(driver_name, &cwd, true) - .expect_stampinf(driver_name, &cwd, target_arch, None) - .expect_inf2cat(driver_name, &cwd, target_arch, None) - .expect_self_signed_cert_file_exists(&cwd, false) - .expect_certmgr_exists_check(Some(expected_certmgr_output)) - .expect_makecert(&cwd, None) - .expect_copy_self_signed_cert_file_to_package_folder(driver_name, &cwd, true) - .expect_signtool_sign_driver_binary_sys_file(driver_name, &cwd, None) - .expect_signtool_sign_cat_file(driver_name, &cwd, None) - .expect_infverif(driver_name, &cwd, "KMDF", None); - - assert_build_action_run_with_env_is_success( - &cwd, - profile, - None, - verify_signature, - sample_class, - test_build_action, - ); -} + harness.expect_build_runner( + DEFAULT_DRIVER_NAME, + &cwd, + None, + None, + Ok(cargo_build_messages( + DEFAULT_DRIVER_NAME, + DEFAULT_DRIVER_VERSION, + &cwd, + None, + None, + )), + ); + harness.expect_probe_target_arch_using_cargo_rustc(&cwd, CpuArchitecture::Amd64); + harness.expect_package_runner( + DEFAULT_DRIVER_NAME, + &cwd, + &target_dir, + CpuArchitecture::Amd64, + SignMode::Test { + verify_signature: false, + }, + false, + ); -#[test] -pub fn given_a_driver_project_when_inx_file_do_not_exist_then_package_should_fail() { - // Input CLI args - let cwd = PathBuf::from("C:\\tmp"); - let profile = None; - let target_arch = CpuArchitecture::Amd64; - let verify_signature = true; - let sample_class = false; - - // Driver project data - let driver_type = "KMDF"; - let driver_name = "sample-kmdf"; - let driver_version = "0.0.1"; - let wdk_metadata = get_cargo_metadata_wdk_metadata(driver_type, 1, 33); - let (workspace_member, package) = - get_cargo_metadata_package(&cwd, driver_name, driver_version, Some(&wdk_metadata)); - - let cargo_build_output = - create_cargo_build_output_json(driver_name, driver_version, &cwd, None, profile); - - let test_build_action = &TestBuildAction::new(cwd.clone(), profile, None, sample_class) - .set_up_standalone_driver_project((workspace_member, package)) - .expect_default_build_task_steps(driver_name, Some(cargo_build_output)) - .expect_probe_target_arch_using_cargo_rustc(&cwd, target_arch, None) - .expect_inx_file_exists(driver_name, &cwd, false); - - let build_action = initialize_build_action( - &cwd, - profile.as_ref(), - None, - verify_signature, - sample_class, - test_build_action, - ); - assert!(build_action.is_ok()); - let run_result = build_action.expect("Failed to init build action").run(); - - assert!(matches!( - run_result.as_ref().expect_err("expected error"), - BuildActionError::OneOrMoreWorkspaceMembersFailedToBuild(_) - )); -} + let build_action = initialize_build_action(&mut harness); + let result = build_action.run_from_workspace_root(&cwd); -#[test] -pub fn given_a_driver_project_when_copy_of_an_artifact_fails_then_the_package_should_fail() { - // Input CLI args - let cwd = PathBuf::from("C:\\tmp"); - let profile = None; - let target_arch = CpuArchitecture::Amd64; - let verify_signature = true; - let sample_class = false; - - // Driver project data - let driver_type = "KMDF"; - let driver_name = "sample-kmdf"; - let driver_version = "0.0.1"; - let wdk_metadata = get_cargo_metadata_wdk_metadata(driver_type, 1, 33); - let (workspace_member, package) = - get_cargo_metadata_package(&cwd, driver_name, driver_version, Some(&wdk_metadata)); - - let cargo_build_output = create_cargo_build_output_json( - driver_name, - driver_version, - &cwd, - Some(&to_target_triple(target_arch)), - profile, - ); - - let test_build_action = - &TestBuildAction::new(cwd.clone(), profile, Some(target_arch), sample_class) - .set_up_standalone_driver_project((workspace_member, package)) - .expect_default_build_task_steps(driver_name, Some(cargo_build_output)) - .expect_final_package_dir_exists(driver_name, &cwd, true) - .expect_inx_file_exists(driver_name, &cwd, true) - .expect_rename_driver_binary_dll_to_sys(driver_name, &cwd) - .expect_copy_driver_binary_sys_to_package_folder(driver_name, &cwd, false); - - let build_action = initialize_build_action( - &cwd, - profile.as_ref(), - Some(target_arch), - verify_signature, - sample_class, - test_build_action, - ); - assert!(build_action.is_ok()); - let run_result = build_action.expect("Failed to init build action").run(); - - assert!(matches!( - run_result.as_ref().expect_err("expected error"), - BuildActionError::OneOrMoreWorkspaceMembersFailedToBuild(_) - )); -} + assert!( + result.is_ok(), + "build action failed unexpectedly: {result:?}" + ); + } -#[test] -pub fn given_a_driver_project_when_stampinf_command_execution_fails_then_package_should_fail() { - // Input CLI args - let cwd = PathBuf::from("C:\\tmp"); - let profile = None; - let target_arch = CpuArchitecture::Amd64; - let verify_signature = true; - let sample_class = false; - - // Driver project data - let driver_type = "KMDF"; - let driver_name = "sample-kmdf"; - let driver_version = "0.0.1"; - let wdk_metadata = get_cargo_metadata_wdk_metadata(driver_type, 1, 33); - let (workspace_member, package) = - get_cargo_metadata_package(&cwd, driver_name, driver_version, Some(&wdk_metadata)); - - let expected_stampinf_output = Output { - status: ExitStatus::from_raw(1), - stdout: vec![], - stderr: vec![], - }; + #[test] + fn run_skips_probe_and_uses_explicit_target_arch() { + let cwd = PathBuf::from(r"C:\tmp\sample-driver"); + let profile = Some(Profile::Release); + let target_arch = CpuArchitecture::Arm64; + let target_dir = expected_target_dir(&cwd, Some(target_arch), profile); + let target_triple = to_target_triple(target_arch); + let mut harness = BuildActionHarness::new( + cwd.clone(), + profile, + Some(target_arch), + SignMode::Test { + verify_signature: true, + }, + true, + ) + .set_up_standalone_driver_project(DEFAULT_DRIVER_NAME, Some(default_wdk_metadata())); - let cargo_build_output = - create_cargo_build_output_json(driver_name, driver_version, &cwd, None, profile); - - let test_build_action = &TestBuildAction::new(cwd.clone(), profile, None, sample_class) - .set_up_standalone_driver_project((workspace_member, package)) - .expect_default_build_task_steps(driver_name, Some(cargo_build_output)) - .expect_probe_target_arch_using_cargo_rustc(&cwd, target_arch, None) - .expect_final_package_dir_exists(driver_name, &cwd, true) - .expect_inx_file_exists(driver_name, &cwd, true) - .expect_rename_driver_binary_dll_to_sys(driver_name, &cwd) - .expect_copy_driver_binary_sys_to_package_folder(driver_name, &cwd, true) - .expect_copy_pdb_file_to_package_folder(driver_name, &cwd, true) - .expect_copy_inx_file_to_package_folder(driver_name, &cwd, true, &cwd) - .expect_copy_map_file_to_package_folder(driver_name, &cwd, true) - .expect_stampinf( - driver_name, + harness.expect_build_runner( + DEFAULT_DRIVER_NAME, &cwd, + profile, + Some(target_arch), + Ok(cargo_build_messages( + DEFAULT_DRIVER_NAME, + DEFAULT_DRIVER_VERSION, + &cwd, + Some(target_triple.as_str()), + profile, + )), + ); + harness.expect_package_runner( + DEFAULT_DRIVER_NAME, + &cwd, + &target_dir, target_arch, - Some(expected_stampinf_output), + SignMode::Test { + verify_signature: true, + }, + true, ); - let build_action = initialize_build_action( - &cwd, - profile.as_ref(), - None, - verify_signature, - sample_class, - test_build_action, - ); - assert!(build_action.is_ok()); - let run_result = run_build_action(build_action); - - assert!(matches!( - run_result.as_ref().expect_err("expected error"), - BuildActionError::OneOrMoreWorkspaceMembersFailedToBuild(_) - )); -} + let build_action = initialize_build_action(&mut harness); + let result = build_action.run_from_workspace_root(&cwd); -#[test] -pub fn given_a_driver_project_when_inf2cat_command_execution_fails_then_package_should_fail() { - // Input CLI args - let cwd = PathBuf::from("C:\\tmp"); - let profile = None; - let target_arch = CpuArchitecture::Amd64; - let verify_signature = true; - let sample_class = false; - - // Driver project data - let driver_type = "KMDF"; - let driver_name = "sample-kmdf"; - let driver_version = "0.0.1"; - let wdk_metadata = get_cargo_metadata_wdk_metadata(driver_type, 1, 33); - let (workspace_member, package) = - get_cargo_metadata_package(&cwd, driver_name, driver_version, Some(&wdk_metadata)); - - let expected_inf2cat_output = Output { - status: ExitStatus::from_raw(1), - stdout: vec![], - stderr: vec![], - }; + assert!( + result.is_ok(), + "build action failed unexpectedly: {result:?}" + ); + } + + #[test] + fn run_skips_package_runner_for_non_driver_package() { + let cwd = PathBuf::from(r"C:\tmp\non-driver"); + let mut harness = BuildActionHarness::new( + cwd.clone(), + None, + None, + SignMode::Test { + verify_signature: false, + }, + false, + ) + .set_up_standalone_driver_project(DEFAULT_DRIVER_NAME, None); - let cargo_build_output = - create_cargo_build_output_json(driver_name, driver_version, &cwd, None, profile); - - let test_build_action = &TestBuildAction::new(cwd.clone(), profile, None, sample_class) - .set_up_standalone_driver_project((workspace_member, package)) - .expect_default_build_task_steps(driver_name, Some(cargo_build_output)) - .expect_probe_target_arch_using_cargo_rustc(&cwd, target_arch, None) - .expect_final_package_dir_exists(driver_name, &cwd, true) - .expect_inx_file_exists(driver_name, &cwd, true) - .expect_rename_driver_binary_dll_to_sys(driver_name, &cwd) - .expect_copy_driver_binary_sys_to_package_folder(driver_name, &cwd, true) - .expect_copy_pdb_file_to_package_folder(driver_name, &cwd, true) - .expect_copy_inx_file_to_package_folder(driver_name, &cwd, true, &cwd) - .expect_copy_map_file_to_package_folder(driver_name, &cwd, true) - .expect_stampinf(driver_name, &cwd, target_arch, None) - .expect_inf2cat( - driver_name, + harness.expect_build_runner( + DEFAULT_DRIVER_NAME, &cwd, - target_arch, - Some(expected_inf2cat_output), + None, + None, + Ok(cargo_build_messages( + DEFAULT_DRIVER_NAME, + DEFAULT_DRIVER_VERSION, + &cwd, + None, + None, + )), ); + harness.mock_package_task_runner.expect_run().never(); - let build_action = initialize_build_action( - &cwd, - profile.as_ref(), - None, - verify_signature, - sample_class, - test_build_action, - ); - assert!(build_action.is_ok()); - let run_result = run_build_action(build_action); - - assert!(matches!( - run_result.as_ref().expect_err("expected error"), - BuildActionError::OneOrMoreWorkspaceMembersFailedToBuild(_) - )); -} + let build_action = initialize_build_action(&mut harness); + let result = build_action.run_from_workspace_root(&cwd); -#[test] -pub fn given_a_driver_project_when_certmgr_command_execution_fails_then_package_should_fail() { - // Input CLI args - let cwd = PathBuf::from("C:\\tmp"); - let profile = None; - let target_arch = CpuArchitecture::Amd64; - let verify_signature = true; - let sample_class = false; - - // Driver project data - let driver_type = "KMDF"; - let driver_name = "sample-kmdf"; - let driver_version = "0.0.1"; - let wdk_metadata = get_cargo_metadata_wdk_metadata(driver_type, 1, 33); - let (workspace_member, package) = - get_cargo_metadata_package(&cwd, driver_name, driver_version, Some(&wdk_metadata)); - - let expected_output = Output { - status: ExitStatus::from_raw(1), - stdout: vec![], - stderr: vec![], - }; + assert!( + result.is_ok(), + "build action failed unexpectedly: {result:?}" + ); + } - let cargo_build_output = - create_cargo_build_output_json(driver_name, driver_version, &cwd, None, profile); - - let test_build_action = &TestBuildAction::new(cwd.clone(), profile, None, sample_class) - .set_up_standalone_driver_project((workspace_member, package)) - .expect_default_build_task_steps(driver_name, Some(cargo_build_output)) - .expect_probe_target_arch_using_cargo_rustc(&cwd, target_arch, None) - .expect_final_package_dir_exists(driver_name, &cwd, true) - .expect_inx_file_exists(driver_name, &cwd, true) - .expect_rename_driver_binary_dll_to_sys(driver_name, &cwd) - .expect_copy_driver_binary_sys_to_package_folder(driver_name, &cwd, true) - .expect_copy_pdb_file_to_package_folder(driver_name, &cwd, true) - .expect_copy_inx_file_to_package_folder(driver_name, &cwd, true, &cwd) - .expect_copy_map_file_to_package_folder(driver_name, &cwd, true) - .expect_stampinf(driver_name, &cwd, target_arch, None) - .expect_inf2cat(driver_name, &cwd, target_arch, None) - .expect_infverif(driver_name, &cwd, driver_type, None) - .expect_self_signed_cert_file_exists(&cwd, false) - .expect_certmgr_exists_check(Some(expected_output)); - - let build_action = initialize_build_action( - &cwd, - profile.as_ref(), - None, - verify_signature, - sample_class, - test_build_action, - ); - assert!(build_action.is_ok()); - let run_result = run_build_action(build_action); - - assert!(matches!( - run_result.as_ref().expect_err("expected error"), - BuildActionError::OneOrMoreWorkspaceMembersFailedToBuild(_) - )); -} + #[test] + fn run_skips_package_runner_when_no_cdylib_target() { + let cwd = PathBuf::from(r"C:\tmp\driver-lib"); + let mut harness = BuildActionHarness::new( + cwd.clone(), + None, + None, + SignMode::Test { + verify_signature: false, + }, + false, + ) + .set_up_with_custom_toml(&get_cargo_metadata( + &cwd, + vec![ + get_cargo_metadata_package_with_target( + &cwd, + DEFAULT_DRIVER_NAME, + DEFAULT_DRIVER_VERSION, + Some(default_wdk_metadata()), + "lib", + "lib", + "lib.rs", + ) + .1, + ], + &[get_cargo_metadata_package_with_target( + &cwd, + DEFAULT_DRIVER_NAME, + DEFAULT_DRIVER_VERSION, + Some(default_wdk_metadata()), + "lib", + "lib", + "lib.rs", + ) + .0], + None, + )); -#[test] -pub fn given_a_driver_project_when_makecert_command_execution_fails_then_package_should_fail() { - // Input CLI args - let cwd = PathBuf::from("C:\\tmp"); - let profile = None; - let target_arch = CpuArchitecture::Amd64; - let verify_signature = true; - let sample_class = false; - - // Driver project data - let driver_type = "KMDF"; - let driver_name = "sample-kmdf"; - let driver_version = "0.0.1"; - let wdk_metadata = get_cargo_metadata_wdk_metadata(driver_type, 1, 33); - let (workspace_member, package) = - get_cargo_metadata_package(&cwd, driver_name, driver_version, Some(&wdk_metadata)); - - let expected_output = Output { - status: ExitStatus::from_raw(1), - stdout: vec![], - stderr: vec![], - }; + harness.expect_build_runner(DEFAULT_DRIVER_NAME, &cwd, None, None, Ok(Vec::new())); + harness.mock_package_task_runner.expect_run().never(); - let cargo_build_output = - create_cargo_build_output_json(driver_name, driver_version, &cwd, None, profile); - - let test_build_action = &TestBuildAction::new(cwd.clone(), profile, None, sample_class) - .set_up_standalone_driver_project((workspace_member, package)) - .expect_default_build_task_steps(driver_name, Some(cargo_build_output)) - .expect_probe_target_arch_using_cargo_rustc(&cwd, target_arch, None) - .expect_final_package_dir_exists(driver_name, &cwd, true) - .expect_inx_file_exists(driver_name, &cwd, true) - .expect_rename_driver_binary_dll_to_sys(driver_name, &cwd) - .expect_copy_driver_binary_sys_to_package_folder(driver_name, &cwd, true) - .expect_copy_pdb_file_to_package_folder(driver_name, &cwd, true) - .expect_copy_inx_file_to_package_folder(driver_name, &cwd, true, &cwd) - .expect_copy_map_file_to_package_folder(driver_name, &cwd, true) - .expect_stampinf(driver_name, &cwd, target_arch, None) - .expect_inf2cat(driver_name, &cwd, target_arch, None) - .expect_infverif(driver_name, &cwd, driver_type, None) - .expect_self_signed_cert_file_exists(&cwd, false) - .expect_certmgr_exists_check(None) - .expect_makecert(&cwd, Some(expected_output)); - - let build_action = initialize_build_action( - &cwd, - profile.as_ref(), - None, - verify_signature, - sample_class, - test_build_action, - ); - assert!(build_action.is_ok()); - let run_result = run_build_action(build_action); - - assert!(matches!( - run_result.as_ref().expect_err("expected error"), - BuildActionError::OneOrMoreWorkspaceMembersFailedToBuild(_) - )); -} + let build_action = initialize_build_action(&mut harness); + let result = build_action.run_from_workspace_root(&cwd); -#[test] -pub fn given_a_driver_project_when_signtool_command_execution_fails_then_package_should_fail() { - // Input CLI args - let cwd = PathBuf::from("C:\\tmp"); - let profile = None; - let target_arch = CpuArchitecture::Amd64; - let verify_signature = true; - let sample_class = false; - - // Driver project data - let driver_type = "KMDF"; - let driver_name = "sample-kmdf"; - let driver_version = "0.0.1"; - let wdk_metadata = get_cargo_metadata_wdk_metadata(driver_type, 1, 33); - let (workspace_member, package) = - get_cargo_metadata_package(&cwd, driver_name, driver_version, Some(&wdk_metadata)); - - let cargo_build_output = - create_cargo_build_output_json(driver_name, driver_version, &cwd, None, profile); - - let expected_output = Output { - status: ExitStatus::from_raw(1), - stdout: vec![], - stderr: vec![], - }; + assert!( + result.is_ok(), + "build action failed unexpectedly: {result:?}" + ); + } - let test_build_action = &TestBuildAction::new(cwd.clone(), profile, None, sample_class) - .set_up_standalone_driver_project((workspace_member, package)) - .expect_default_build_task_steps(driver_name, Some(cargo_build_output)) - .expect_probe_target_arch_using_cargo_rustc(&cwd, target_arch, None) - .expect_final_package_dir_exists(driver_name, &cwd, true) - .expect_inx_file_exists(driver_name, &cwd, true) - .expect_rename_driver_binary_dll_to_sys(driver_name, &cwd) - .expect_copy_driver_binary_sys_to_package_folder(driver_name, &cwd, true) - .expect_copy_pdb_file_to_package_folder(driver_name, &cwd, true) - .expect_copy_inx_file_to_package_folder(driver_name, &cwd, true, &cwd) - .expect_copy_map_file_to_package_folder(driver_name, &cwd, true) - .expect_stampinf(driver_name, &cwd, target_arch, None) - .expect_inf2cat(driver_name, &cwd, target_arch, None) - .expect_infverif(driver_name, &cwd, driver_type, None) - .expect_self_signed_cert_file_exists(&cwd, false) - .expect_certmgr_exists_check(None) - .expect_makecert(&cwd, None) - .expect_copy_self_signed_cert_file_to_package_folder(driver_name, &cwd, true) - .expect_signtool_sign_driver_binary_sys_file(driver_name, &cwd, Some(expected_output)); - - let build_action = initialize_build_action( - &cwd, - profile.as_ref(), - None, - verify_signature, - sample_class, - test_build_action, - ); - assert!(build_action.is_ok()); - let run_result = run_build_action(build_action); - - assert!(matches!( - run_result.as_ref().expect_err("expected error"), - BuildActionError::OneOrMoreWorkspaceMembersFailedToBuild(_) - )); -} + #[test] + fn run_forwards_locked_and_features_to_metadata() { + let cwd = PathBuf::from(r"C:\tmp\sample-driver"); + let target_dir = expected_target_dir(&cwd, None, None); + let mut harness = BuildActionHarness::new( + cwd.clone(), + None, + None, + SignMode::Test { + verify_signature: false, + }, + false, + ) + .with_locked(true) + .with_features(non_default_features()) + .set_up_standalone_driver_project(DEFAULT_DRIVER_NAME, Some(default_wdk_metadata())); -#[test] -pub fn given_a_driver_project_when_infverif_command_execution_fails_then_package_should_fail() { - // Input CLI args - let cwd = PathBuf::from("C:\\tmp"); - let profile = None; - let target_arch = CpuArchitecture::Amd64; - let verify_signature = true; - let sample_class = false; - - // Driver project data - let driver_type = "KMDF"; - let driver_name = "sample-kmdf"; - let driver_version = "0.0.1"; - let wdk_metadata = get_cargo_metadata_wdk_metadata(driver_type, 1, 33); - let (workspace_member, package) = - get_cargo_metadata_package(&cwd, driver_name, driver_version, Some(&wdk_metadata)); - - let cargo_build_output = - create_cargo_build_output_json(driver_name, driver_version, &cwd, None, profile); - - let expected_output = Output { - status: ExitStatus::from_raw(1), - stdout: vec![], - stderr: vec![], - }; + harness.expect_build_runner( + DEFAULT_DRIVER_NAME, + &cwd, + None, + None, + Ok(cargo_build_messages( + DEFAULT_DRIVER_NAME, + DEFAULT_DRIVER_VERSION, + &cwd, + None, + None, + )), + ); + harness.expect_probe_target_arch_using_cargo_rustc(&cwd, CpuArchitecture::Amd64); + harness.expect_package_runner( + DEFAULT_DRIVER_NAME, + &cwd, + &target_dir, + CpuArchitecture::Amd64, + SignMode::Test { + verify_signature: false, + }, + false, + ); - let test_build_action = &TestBuildAction::new(cwd.clone(), profile, None, sample_class) - .set_up_standalone_driver_project((workspace_member, package)) - .expect_default_build_task_steps(driver_name, Some(cargo_build_output)) - .expect_probe_target_arch_using_cargo_rustc(&cwd, target_arch, None) - .expect_final_package_dir_exists(driver_name, &cwd, true) - .expect_inx_file_exists(driver_name, &cwd, true) - .expect_rename_driver_binary_dll_to_sys(driver_name, &cwd) - .expect_copy_driver_binary_sys_to_package_folder(driver_name, &cwd, true) - .expect_copy_pdb_file_to_package_folder(driver_name, &cwd, true) - .expect_copy_inx_file_to_package_folder(driver_name, &cwd, true, &cwd) - .expect_copy_map_file_to_package_folder(driver_name, &cwd, true) - .expect_stampinf(driver_name, &cwd, target_arch, None) - .expect_inf2cat(driver_name, &cwd, target_arch, None) - .expect_infverif(driver_name, &cwd, driver_type, Some(expected_output)); - - let build_action = initialize_build_action( - &cwd, - profile.as_ref(), - None, - verify_signature, - sample_class, - test_build_action, - ); - assert!(build_action.is_ok()); - let run_result = run_build_action(build_action); - - assert!(matches!( - run_result.as_ref().expect_err("expected error"), - BuildActionError::OneOrMoreWorkspaceMembersFailedToBuild(_) - )); -} + // The metadata, build-runner and cargo-rustc-probe expectations all assert + // that `--locked` and the selected features are forwarded; the run only + // succeeds if `BuildAction` forwards them to every downstream call. + let build_action = initialize_build_action(&mut harness); + let result = build_action.run_from_workspace_root(&cwd); -#[test] -pub fn given_a_non_driver_project_when_default_values_are_provided_with_no_wdk_metadata_are_provided_then_build_should_be_successful() - { - // Input CLI args - let cwd = PathBuf::from("C:\\tmp"); - let profile = None; - let verify_signature = true; - let sample_class = false; - - // Driver project data - let driver_name = "non-driver"; - let driver_version = "0.0.1"; - let (workspace_member, package) = - get_cargo_metadata_package(&cwd, driver_name, driver_version, None); - - let test_build_action = &TestBuildAction::new(cwd.clone(), profile, None, sample_class) - .set_up_standalone_driver_project((workspace_member, package)) - .expect_default_build_task_steps(driver_name, None); - - assert_build_action_run_is_success( - &cwd, - profile, - None, - verify_signature, - sample_class, - test_build_action, - ); -} + assert!( + result.is_ok(), + "build action failed unexpectedly: {result:?}" + ); + } -#[test] -pub fn given_a_invalid_driver_project_with_partial_wdk_metadata_when_valid_default_values_are_provided_then_wdk_metadata_parse_should_fail() - { - // Input CLI args - let cwd = PathBuf::from("C:\\tmp\\sample-driver"); - let profile = None; - let verify_signature = true; - let sample_class = false; - - // Driver project data - let driver_name = "sample-driver"; - let cargo_toml_metadata = invalid_driver_cargo_toml(); - - let test_build_action = &TestBuildAction::new(cwd.clone(), profile, None, sample_class) - .set_up_with_custom_toml(&cargo_toml_metadata) - .expect_default_build_task_steps(driver_name, None); - - let build_action = initialize_build_action( - &cwd, - profile.as_ref(), - None, - verify_signature, - sample_class, - test_build_action, - ); - assert!(build_action.is_ok()); - let run_result = build_action.expect("Failed to init build action").run(); - assert!(matches!( - run_result.as_ref().expect_err("expected error"), - BuildActionError::WdkMetadataParse(TryFromCargoMetadataError::WdkMetadataDeserialization { - metadata_source: _, - error_source: _ - }) - )); -} + #[test] + fn run_returns_wdk_metadata_parse_error_for_invalid_metadata() { + let cwd = PathBuf::from(r"C:\tmp\sample-driver"); + let mut harness = BuildActionHarness::new( + cwd.clone(), + None, + None, + SignMode::Test { + verify_signature: false, + }, + false, + ) + .set_up_with_custom_toml(&invalid_driver_cargo_toml()); -#[test] -pub fn given_a_driver_project_when_target_arch_is_not_provided_and_probing_cargo_rustc_fails_then_packaging_should_fail() - { - // Input CLI args - let cwd = PathBuf::from("C:\\tmp"); - let profile = None; - let verify_signature = false; - let sample_class = false; - // Driver project data - let driver_type = "KMDF"; - let driver_name = "sample-kmdf"; - let driver_version = "0.0.1"; - let wdk_metadata = get_cargo_metadata_wdk_metadata(driver_type, 1, 33); - let (workspace_member, package) = - get_cargo_metadata_package(&cwd, driver_name, driver_version, Some(&wdk_metadata)); - - let cargo_build_output = - create_cargo_build_output_json(driver_name, driver_version, &cwd, None, profile); - - let cargo_rustc_output = Output { - status: ExitStatus::from_raw(1), - stdout: vec![], - stderr: vec![], - }; + // The package still builds; packaging is skipped because the WDK metadata is + // invalid, and the parse error is surfaced after the build. + harness.expect_build_runner(DEFAULT_DRIVER_NAME, &cwd, None, None, Ok(Vec::new())); + harness.mock_package_task_runner.expect_run().never(); - let test_build_action = &TestBuildAction::new(cwd.clone(), profile, None, sample_class) - .set_up_standalone_driver_project((workspace_member, package)) - .expect_default_build_task_steps(driver_name, Some(cargo_build_output)) - .expect_probe_target_arch_using_cargo_rustc( - &cwd, - CpuArchitecture::Amd64, - Some(cargo_rustc_output), - ); + let build_action = initialize_build_action(&mut harness); + let result = build_action.run_from_workspace_root(&cwd); - let build_action = initialize_build_action( - &cwd, - profile.as_ref(), - None, - verify_signature, - sample_class, - test_build_action, - ); - assert!(build_action.is_ok()); - let run_result = build_action.expect("Failed to init build action").run(); - assert!(matches!( - run_result.as_ref().expect_err("expected error"), - BuildActionError::OneOrMoreWorkspaceMembersFailedToBuild(_) - )); + assert!(matches!( + result, + Err(BuildActionError::WdkMetadataParse( + TryFromCargoMetadataError::WdkMetadataDeserialization { .. } + )) + )); + } } -//////////////////////////////////////////////////////////////////////////////// -/// Workspace tests -//////////////////////////////////////////////////////////////////////////////// -#[test] -pub fn given_a_workspace_with_multiple_driver_and_non_driver_projects_when_default_values_are_provided_then_it_packages_successfully() - { - // Input CLI args - let cwd = PathBuf::from("C:\\tmp"); - let profile = None; - let target_arch = CpuArchitecture::Amd64; - let verify_signature = true; - let sample_class = false; - - // Driver project data - let driver_type = "KMDF"; - let driver_name_1 = "sample-kmdf-1"; - let driver_version_1 = "0.0.1"; - let driver_name_2 = "sample-kmdf-2"; - let driver_version_2 = "0.0.2"; - let non_driver = "non-driver"; - let non_driver_version = "0.0.3"; - - // Create artifact outputs for workspace packages - let artifact_1 = create_cargo_build_output_json_with_manifest( - driver_name_1, - driver_version_1, - &cwd, - &cwd.join(driver_name_1).join("Cargo.toml"), - None, - profile, - true, - ); - let artifact_2 = create_cargo_build_output_json_with_manifest( - driver_name_2, - driver_version_2, - &cwd, - &cwd.join(driver_name_2).join("Cargo.toml"), - None, - profile, - true, - ); - let artifact_non_driver = create_cargo_build_output_json_with_manifest( - non_driver, - non_driver_version, - &cwd, - &cwd.join(non_driver).join("Cargo.toml"), - None, - profile, - false, - ); - let wdk_metadata = get_cargo_metadata_wdk_metadata(driver_type, 1, 33); - let (workspace_member_1, package_1) = get_cargo_metadata_package( - &cwd.join(driver_name_1), - driver_name_1, - driver_version_1, - Some(&wdk_metadata), - ); - let (workspace_member_2, package_2) = get_cargo_metadata_package( - &cwd.join(driver_name_2), - driver_name_2, - driver_version_2, - Some(&wdk_metadata), - ); - let (workspace_member_3, package_3) = - get_cargo_metadata_package(&cwd.join(non_driver), non_driver, non_driver_version, None); - - let test_build_action = &TestBuildAction::new(cwd.clone(), profile, None, sample_class) +mod driver_workspace { + use super::*; + + #[test] + fn run_returns_workspace_error_when_one_member_fails() { + let workspace_root = PathBuf::from(r"C:\tmp\workspace"); + let driver_name_1 = "sample-kmdf-1"; + let driver_name_2 = "sample-kmdf-2"; + let driver_dir_1 = workspace_root.join(driver_name_1); + let driver_dir_2 = workspace_root.join(driver_name_2); + let target_arch = CpuArchitecture::Amd64; + let target_dir_1 = expected_target_dir(&driver_dir_1, Some(target_arch), None); + let target_triple = to_target_triple(target_arch); + let mut harness = BuildActionHarness::new( + workspace_root.clone(), + None, + Some(target_arch), + SignMode::Test { + verify_signature: false, + }, + false, + ) .set_up_workspace_with_multiple_driver_projects( - &cwd, - Some(wdk_metadata), + &workspace_root, vec![ - (workspace_member_1, package_1), - (workspace_member_2, package_2), - (workspace_member_3, package_3), + ( + driver_name_1, + driver_dir_1.clone(), + Some(default_wdk_metadata()), + "cdylib", + "cdylib", + "main.rs", + ), + ( + driver_name_2, + driver_dir_2.clone(), + Some(default_wdk_metadata()), + "cdylib", + "cdylib", + "main.rs", + ), ], - ) - .expect_detect_wdk_build_number(25100u32) - .expect_root_manifest_exists(&cwd, true) - .expect_cargo_build(driver_name_1, &cwd.join(driver_name_1), Some(artifact_1)) - .expect_probe_target_arch_using_cargo_rustc(&cwd.join(driver_name_1), target_arch, None) - .expect_default_package_task_steps_for_workspace( + ); + + harness.expect_build_runner( driver_name_1, - driver_type, - target_arch, - verify_signature, - ) - // Second driver project - .expect_cargo_build(driver_name_2, &cwd.join(driver_name_2), Some(artifact_2)) - .expect_probe_target_arch_using_cargo_rustc(&cwd.join(driver_name_2), target_arch, None) - .expect_default_package_task_steps_for_workspace( + &driver_dir_1, + None, + Some(target_arch), + Ok(cargo_build_messages( + driver_name_1, + DEFAULT_DRIVER_VERSION, + &driver_dir_1, + Some(target_triple.as_str()), + None, + )), + ); + harness.expect_build_runner( driver_name_2, - driver_type, + &driver_dir_2, + None, + Some(target_arch), + Err(build_task_error()), + ); + harness.expect_package_runner( + driver_name_1, + &driver_dir_1, + &target_dir_1, target_arch, - verify_signature, - ) - // Non-driver project - .expect_cargo_build(non_driver, &cwd.join(non_driver), Some(artifact_non_driver)); + SignMode::Test { + verify_signature: false, + }, + false, + ); - assert_build_action_run_with_env_is_success( - &cwd, - profile, - None, - verify_signature, - sample_class, - test_build_action, - ); -} + let build_action = initialize_build_action(&mut harness); + let result = build_action.run_from_workspace_root(&workspace_root); -#[test] -pub fn given_a_workspace_with_multiple_driver_and_non_driver_projects_when_cwd_is_driver_project_then_it_packages_driver_project_successfully() - { - // Input CLI args - let workspace_root_dir = PathBuf::from("C:\\tmp"); - let cwd = workspace_root_dir.join("sample-kmdf-1"); - let profile = None; - let target_arch = CpuArchitecture::Amd64; - let verify_signature = true; - let sample_class = false; - - // Driver project data - let driver_type = "KMDF"; - let driver_name_1 = "sample-kmdf-1"; - let driver_version_1 = "0.0.1"; - let driver_name_2 = "sample-kmdf-2"; - let driver_version_2 = "0.0.2"; - let non_driver = "non-driver"; - let non_driver_version = "0.0.3"; - let wdk_metadata = get_cargo_metadata_wdk_metadata(driver_type, 1, 33); - let (workspace_member_1, package_1) = get_cargo_metadata_package( - &workspace_root_dir.join(driver_name_1), - driver_name_1, - driver_version_1, - Some(&wdk_metadata), - ); - let (workspace_member_2, package_2) = get_cargo_metadata_package( - &workspace_root_dir.join(driver_name_2), - driver_name_2, - driver_version_2, - Some(&wdk_metadata), - ); - let (workspace_member_3, package_3) = get_cargo_metadata_package( - &workspace_root_dir.join(non_driver), - non_driver, - non_driver_version, - None, - ); - - let expected_certmgr_output = get_certmgr_success_output(); - - let cargo_build_output = create_cargo_build_output_json_with_manifest( - driver_name_1, - driver_version_1, - &workspace_root_dir, - &workspace_root_dir.join(driver_name_1).join("Cargo.toml"), - None, - profile, - true, - ); + assert!(matches!( + result, + Err(BuildActionError::OneOrMoreWorkspaceMembersFailedToBuild(path)) + if path == workspace_root + )); + } - let test_build_action = &TestBuildAction::new(cwd.clone(), profile, None, sample_class) // Even when cwd is changed to driver project inside the workspace, cargo metadata read - // is going to be for the whole workspace - .set_up_workspace_with_multiple_driver_projects( - &workspace_root_dir, - Some(wdk_metadata), - vec![ - (workspace_member_1, package_1), - (workspace_member_2, package_2), - (workspace_member_3, package_3), - ], - ) - .expect_detect_wdk_build_number(25100u32) - .expect_root_manifest_exists(&cwd, true) - .expect_cargo_build(driver_name_1, &cwd, Some(cargo_build_output)) - .expect_probe_target_arch_using_cargo_rustc(&cwd, target_arch, None) - .expect_final_package_dir_exists(driver_name_1, &workspace_root_dir, true) - .expect_inx_file_exists(driver_name_1, &cwd, true) - .expect_rename_driver_binary_dll_to_sys(driver_name_1, &workspace_root_dir) - .expect_copy_driver_binary_sys_to_package_folder(driver_name_1, &workspace_root_dir, true) - .expect_copy_pdb_file_to_package_folder(driver_name_1, &workspace_root_dir, true) - .expect_copy_inx_file_to_package_folder(driver_name_1, &cwd, true, &workspace_root_dir) - .expect_copy_map_file_to_package_folder(driver_name_1, &workspace_root_dir, true) - .expect_stampinf(driver_name_1, &workspace_root_dir, target_arch, None) - .expect_inf2cat(driver_name_1, &workspace_root_dir, target_arch, None) - .expect_self_signed_cert_file_exists(&workspace_root_dir, false) - .expect_certmgr_exists_check(Some(expected_certmgr_output)) - .expect_makecert(&workspace_root_dir, None) - .expect_copy_self_signed_cert_file_to_package_folder( - driver_name_1, - &workspace_root_dir, - true, + #[test] + fn run_orchestrates_only_targeted_member() { + let workspace_root = PathBuf::from(r"C:\tmp\workspace"); + let driver_name_1 = "sample-kmdf-1"; + let driver_name_2 = "sample-kmdf-2"; + let driver_dir_1 = workspace_root.join(driver_name_1); + let driver_dir_2 = workspace_root.join(driver_name_2); + let target_arch = CpuArchitecture::Amd64; + let target_dir_2 = expected_target_dir(&driver_dir_2, Some(target_arch), None); + let target_triple = to_target_triple(target_arch); + let mut harness = BuildActionHarness::new( + driver_dir_2.clone(), + None, + Some(target_arch), + SignMode::Test { + verify_signature: false, + }, + false, ) - .expect_signtool_sign_driver_binary_sys_file(driver_name_1, &workspace_root_dir, None) - .expect_signtool_sign_cat_file(driver_name_1, &workspace_root_dir, None) - .expect_signtool_verify_driver_binary_sys_file(driver_name_1, &workspace_root_dir, None) - .expect_signtool_verify_cat_file(driver_name_1, &workspace_root_dir, None) - .expect_infverif(driver_name_1, &workspace_root_dir, "KMDF", None); - - assert_build_action_run_with_env_is_success( - &cwd, - profile, - None, - verify_signature, - sample_class, - test_build_action, - ); -} - -#[test] -pub fn given_a_workspace_with_multiple_driver_and_non_driver_projects_when_verify_signature_is_false_then_it_skips_verify_tasks() - { - // Input CLI args - let cwd = PathBuf::from("C:\\tmp"); - let profile = None; - let target_arch = CpuArchitecture::Amd64; - let verify_signature = false; - let sample_class = false; - - // Driver project data - let driver_type = "KMDF"; - let driver_name_1 = "sample-kmdf-1"; - let driver_version_1 = "0.0.1"; - let driver_name_2 = "sample-kmdf-2"; - let driver_version_2 = "0.0.2"; - let non_driver = "non-driver"; - let non_driver_version = "0.0.3"; - - // Create artifact outputs for workspace packages - let artifact_1 = create_cargo_build_output_json_with_manifest( - driver_name_1, - driver_version_1, - &cwd, - &cwd.join(driver_name_1).join("Cargo.toml"), - None, - profile, - true, - ); - let artifact_2 = create_cargo_build_output_json_with_manifest( - driver_name_2, - driver_version_2, - &cwd, - &cwd.join(driver_name_2).join("Cargo.toml"), - None, - profile, - true, - ); - let artifact_non_driver = create_cargo_build_output_json_with_manifest( - non_driver, - non_driver_version, - &cwd, - &cwd.join(non_driver).join("Cargo.toml"), - None, - profile, - false, // NOT a driver - will use "lib" instead of "cdylib" - ); - let wdk_metadata = get_cargo_metadata_wdk_metadata(driver_type, 1, 33); - let (workspace_member_1, package_1) = get_cargo_metadata_package( - &cwd.join(driver_name_1), - driver_name_1, - driver_version_1, - Some(&wdk_metadata), - ); - let (workspace_member_2, package_2) = get_cargo_metadata_package( - &cwd.join(driver_name_2), - driver_name_2, - driver_version_2, - Some(&wdk_metadata), - ); - let (workspace_member_3, package_3) = - get_cargo_metadata_package(&cwd.join(non_driver), non_driver, non_driver_version, None); - - let test_build_action = &TestBuildAction::new(cwd.clone(), profile, None, sample_class) .set_up_workspace_with_multiple_driver_projects( - &cwd, - Some(wdk_metadata), + &workspace_root, vec![ - (workspace_member_1, package_1), - (workspace_member_2, package_2), - (workspace_member_3, package_3), + ( + driver_name_1, + driver_dir_1, + Some(default_wdk_metadata()), + "cdylib", + "cdylib", + "main.rs", + ), + ( + driver_name_2, + driver_dir_2.clone(), + Some(default_wdk_metadata()), + "cdylib", + "cdylib", + "main.rs", + ), ], - ) - .expect_detect_wdk_build_number(25100u32) - .expect_root_manifest_exists(&cwd, true) - .expect_cargo_build(driver_name_1, &cwd.join(driver_name_1), Some(artifact_1)) - .expect_probe_target_arch_using_cargo_rustc(&cwd.join(driver_name_1), target_arch, None) - .expect_default_package_task_steps_for_workspace( - driver_name_1, - driver_type, - target_arch, - verify_signature, - ) - // Second driver project - .expect_cargo_build(driver_name_2, &cwd.join(driver_name_2), Some(artifact_2)) - .expect_probe_target_arch_using_cargo_rustc(&cwd.join(driver_name_2), target_arch, None) - .expect_default_package_task_steps_for_workspace( + ); + + harness.expect_build_runner( + driver_name_2, + &driver_dir_2, + None, + Some(target_arch), + Ok(cargo_build_messages( + driver_name_2, + DEFAULT_DRIVER_VERSION, + &driver_dir_2, + Some(target_triple.as_str()), + None, + )), + ); + harness.expect_package_runner( driver_name_2, - driver_type, + &driver_dir_2, + &target_dir_2, target_arch, - verify_signature, - ) - // Non-driver project - .expect_cargo_build(non_driver, &cwd.join(non_driver), Some(artifact_non_driver)); + SignMode::Test { + verify_signature: false, + }, + false, + ); - assert_build_action_run_with_env_is_success( - &cwd, - profile, - None, - verify_signature, - sample_class, - test_build_action, - ); -} + let build_action = initialize_build_action(&mut harness); + let result = build_action.run_from_workspace_root(&driver_dir_2); -#[test] -pub fn given_a_workspace_with_multiple_driver_and_non_driver_projects_when_cwd_is_non_driver_project_then_it_builds_but_skips_packaging() - { - // Input CLI args - let workspace_root_dir = PathBuf::from("C:\\tmp"); - let cwd = workspace_root_dir.join("non-driver"); - let profile = None; - let verify_signature = true; - let sample_class = false; - - // Driver project data - let driver_type = "KMDF"; - let driver_name_1 = "sample-kmdf-1"; - let driver_version_1 = "0.0.1"; - let driver_name_2 = "sample-kmdf-2"; - let driver_version_2 = "0.0.2"; - let non_driver = "non-driver"; - let non_driver_version = "0.0.3"; - let wdk_metadata = get_cargo_metadata_wdk_metadata(driver_type, 1, 33); - let (workspace_member_1, package_1) = get_cargo_metadata_package( - &workspace_root_dir.join(driver_name_1), - driver_name_1, - driver_version_1, - Some(&wdk_metadata), - ); - let (workspace_member_2, package_2) = get_cargo_metadata_package( - &workspace_root_dir.join(driver_name_2), - driver_name_2, - driver_version_2, - Some(&wdk_metadata), - ); - let (workspace_member_3, package_3) = get_cargo_metadata_package( - &workspace_root_dir.join(non_driver), - non_driver, - non_driver_version, - None, - ); - - let test_build_action = &TestBuildAction::new(cwd.clone(), profile, None, sample_class) // Even when cwd is changed to driver project inside the workspace, cargo metadata read - // is going to be for the whole workspace - .set_up_workspace_with_multiple_driver_projects( - &workspace_root_dir, - Some(wdk_metadata), - vec![ - (workspace_member_1, package_1), - (workspace_member_2, package_2), - (workspace_member_3, package_3), - ], + assert!( + result.is_ok(), + "build action failed unexpectedly: {result:?}" + ); + } + + #[test] + fn run_propagates_build_error_on_failure() { + let workspace_root = PathBuf::from(r"C:\tmp\workspace"); + let cwd = workspace_root.join(DEFAULT_DRIVER_NAME); + let mut harness = BuildActionHarness::new( + cwd.clone(), + None, + None, + SignMode::Test { + verify_signature: false, + }, + false, ) - .expect_detect_wdk_build_number(25100u32) - .expect_root_manifest_exists(&cwd, true) - .expect_cargo_build(non_driver, &cwd, None); + .set_up_workspace_with_multiple_driver_projects( + &workspace_root, + vec![( + DEFAULT_DRIVER_NAME, + cwd.clone(), + Some(default_wdk_metadata()), + "cdylib", + "cdylib", + "main.rs", + )], + ); - assert_build_action_run_is_success( - &cwd, - profile, - None, - verify_signature, - sample_class, - test_build_action, - ); -} - -#[test] -pub fn given_a_workspace_with_multiple_distinct_wdk_configurations_at_each_workspace_member_level_when_default_values_are_provided_then_wdk_metadata_parse_should_fail() - { - // Input CLI args - let cwd = PathBuf::from("C:\\tmp"); - let profile = None; - let verify_signature = true; - let sample_class = false; - - // Driver project data - let driver_type_1 = "KMDF"; - let driver_name_1 = "sample-kmdf-1"; - let driver_type_2 = "UMDF"; - let driver_version_1 = "0.0.1"; - let driver_name_2 = "sample-kmdf-2"; - let driver_version_2 = "0.0.2"; - let wdk_metadata_1 = get_cargo_metadata_wdk_metadata(driver_type_1, 1, 33); - let wdk_metadata_2 = get_cargo_metadata_wdk_metadata(driver_type_2, 1, 33); - let (workspace_member_1, package_1) = get_cargo_metadata_package( - &cwd.join(driver_name_1), - driver_name_1, - driver_version_1, - Some(&wdk_metadata_1), - ); - let (workspace_member_2, package_2) = get_cargo_metadata_package( - &cwd.join(driver_name_2), - driver_name_2, - driver_version_2, - Some(&wdk_metadata_2), - ); - - let test_build_action = &TestBuildAction::new(cwd.clone(), profile, None, sample_class) - .set_up_workspace_with_multiple_driver_projects( + harness.expect_build_runner( + DEFAULT_DRIVER_NAME, &cwd, - Some(wdk_metadata_1), - vec![ - (workspace_member_1, package_1), - (workspace_member_2, package_2), - ], - ) - .expect_detect_wdk_build_number(25100u32) - .expect_root_manifest_exists(&cwd, true) - .expect_cargo_build(driver_name_1, &cwd.join(driver_name_1), None) - .expect_cargo_build(driver_name_2, &cwd.join(driver_name_2), None); - - let build_action = initialize_build_action( - &cwd, - profile.as_ref(), - None, - verify_signature, - sample_class, - test_build_action, - ); - assert!(build_action.is_ok()); - let run_result = build_action.expect("Failed to init build action").run(); - - assert!(matches!( - run_result.expect_err("run_result error in test: given_a_workspace_with_multiple_distinct_wdk_configurations_at_each_workspace_member_level_when_default_values_are_provided_then_wdk_metadata_parse_should_fail"), - BuildActionError::WdkMetadataParse( - TryFromCargoMetadataError::MultipleWdkConfigurationsDetected { - wdk_metadata_configurations: _ - } - ) - )); -} + None, + None, + Err(build_task_error()), + ); + + let build_action = initialize_build_action(&mut harness); + let result = build_action.run_from_workspace_root(&cwd); -#[test] -pub fn given_a_workspace_with_multiple_distinct_wdk_configurations_at_root_and_workspace_member_level_when_default_values_are_provided_then_wdk_metadata_parse_should_fail() - { - // Input CLI args - let cwd = PathBuf::from("C:\\tmp"); - let profile = None; - let verify_signature = true; - let sample_class = false; - - // Driver project data - let driver_type_1 = "KMDF"; - let driver_name_1 = "sample-kmdf-1"; - let driver_type_2 = "UMDF"; - let driver_version_1 = "0.0.1"; - let driver_name_2 = "sample-kmdf-2"; - let driver_version_2 = "0.0.2"; - let wdk_metadata_1 = get_cargo_metadata_wdk_metadata(driver_type_1, 1, 33); - let wdk_metadata_2 = get_cargo_metadata_wdk_metadata(driver_type_2, 1, 33); - let (workspace_member_1, package_1) = get_cargo_metadata_package( - &cwd.join(driver_name_1), - driver_name_1, - driver_version_1, - Some(&wdk_metadata_1), - ); - let (workspace_member_2, package_2) = get_cargo_metadata_package( - &cwd.join(driver_name_2), - driver_name_2, - driver_version_2, - Some(&wdk_metadata_1), - ); - - let test_build_action = &TestBuildAction::new(cwd.clone(), profile, None, sample_class) + assert!(matches!(result, Err(BuildActionError::BuildTask(_)))); + } + + #[test] + fn run_returns_wdk_metadata_parse_error_for_conflicting_member_configs() { + let workspace_root = PathBuf::from(r"C:\tmp\workspace"); + let driver_name_1 = "sample-kmdf-1"; + let driver_name_2 = "sample-kmdf-2"; + let driver_dir_1 = workspace_root.join(driver_name_1); + let driver_dir_2 = workspace_root.join(driver_name_2); + let mut harness = BuildActionHarness::new( + workspace_root.clone(), + None, + None, + SignMode::Test { + verify_signature: false, + }, + false, + ) .set_up_workspace_with_multiple_driver_projects( - &cwd, - Some(wdk_metadata_2), + &workspace_root, vec![ - (workspace_member_1, package_1), - (workspace_member_2, package_2), + ( + driver_name_1, + driver_dir_1.clone(), + Some(get_cargo_metadata_wdk_metadata("KMDF", 1, 33)), + "cdylib", + "cdylib", + "main.rs", + ), + ( + driver_name_2, + driver_dir_2.clone(), + Some(get_cargo_metadata_wdk_metadata("KMDF", 1, 35)), + "cdylib", + "cdylib", + "main.rs", + ), ], - ) - .expect_root_manifest_exists(&cwd, true) - .expect_detect_wdk_build_number(25100u32) - .expect_cargo_build(driver_name_1, &cwd.join(driver_name_1), None) - .expect_cargo_build(driver_name_2, &cwd.join(driver_name_2), None); - - let build_action = initialize_build_action( - &cwd, - profile.as_ref(), - None, - verify_signature, - sample_class, - test_build_action, - ); - assert!(build_action.is_ok()); - let run_result = build_action.expect("Failed to init build action").run(); - - assert!(matches!( - run_result.expect_err("run_result error in test: given_a_workspace_with_multiple_distinct_wdk_configurations_at_root_and_workspace_member_level_when_default_values_are_provided_then_wdk_metadata_parse_should_fail"), - BuildActionError::WdkMetadataParse( - TryFromCargoMetadataError::MultipleWdkConfigurationsDetected { - wdk_metadata_configurations: _ - } - ) - )); -} + ); -#[test] -pub fn given_a_workspace_only_with_non_driver_projects_when_cwd_is_workspace_root_then_build_should_be_successful() - { - // Input CLI args - let cwd = PathBuf::from("C:\\tmp"); - let profile = None; - let verify_signature = true; - let sample_class = false; - - // Driver project data - let non_driver = "non-driver"; - let non_driver_version = "0.0.3"; - let (workspace_member_3, package_3) = - get_cargo_metadata_package(&cwd.join(non_driver), non_driver, non_driver_version, None); - - let test_build_action = &TestBuildAction::new(cwd.clone(), profile, None, sample_class) // Even when cwd is changed to driver project inside the workspace, cargo metadata read - // is going to be for the whole workspace - .set_up_workspace_with_multiple_driver_projects( - &cwd, + // Each member still builds; packaging is skipped for all of them because the + // distinct WDK configurations can't be reconciled, and the parse error is + // surfaced after the build loop. + harness.expect_build_runner(driver_name_1, &driver_dir_1, None, None, Ok(Vec::new())); + harness.expect_build_runner(driver_name_2, &driver_dir_2, None, None, Ok(Vec::new())); + harness.mock_package_task_runner.expect_run().never(); + + let build_action = initialize_build_action(&mut harness); + let result = build_action.run_from_workspace_root(&workspace_root); + + assert!(matches!( + result, + Err(BuildActionError::WdkMetadataParse( + TryFromCargoMetadataError::MultipleWdkConfigurationsDetected { .. } + )) + )); + } + + #[test] + fn run_returns_wdk_metadata_parse_error_for_conflicting_root_and_member_configs() { + let workspace_root = PathBuf::from(r"C:\tmp\workspace"); + let driver_name = "sample-kmdf-1"; + let driver_dir = workspace_root.join(driver_name); + let mut harness = BuildActionHarness::new( + workspace_root.clone(), None, - vec![(workspace_member_3, package_3)], + None, + SignMode::Test { + verify_signature: false, + }, + false, ) - .expect_root_manifest_exists(&cwd, true) - .expect_detect_wdk_build_number(25100u32) - .expect_cargo_build(non_driver, &cwd.join(non_driver), None); + .set_up_workspace_with_root_metadata( + &workspace_root, + Some(get_cargo_metadata_wdk_metadata("UMDF", 2, 33)), + vec![( + driver_name, + driver_dir.clone(), + Some(get_cargo_metadata_wdk_metadata("KMDF", 1, 33)), + "cdylib", + "cdylib", + "main.rs", + )], + ); - assert_build_action_run_is_success( - &cwd, - profile, - None, - verify_signature, - sample_class, - test_build_action, - ); -} + // The member builds; packaging is skipped because the root and member WDK + // configurations conflict, and the parse error is surfaced after the build. + harness.expect_build_runner(driver_name, &driver_dir, None, None, Ok(Vec::new())); + harness.mock_package_task_runner.expect_run().never(); + + let build_action = initialize_build_action(&mut harness); + let result = build_action.run_from_workspace_root(&workspace_root); -#[test] -pub fn given_a_workspace_only_with_non_driver_projects_when_cwd_is_workspace_member_then_build_should_be_successful() - { - // Input CLI args - let workspace_root_dir = PathBuf::from("C:\\tmp"); - let cwd = workspace_root_dir.join("non-driver"); - let profile = None; - let verify_signature = true; - let sample_class = false; - - // Driver project data - let non_driver = "non-driver"; - let non_driver_version = "0.0.3"; - let (workspace_member_3, package_3) = get_cargo_metadata_package( - &workspace_root_dir.join(non_driver), - non_driver, - non_driver_version, - None, - ); - - let test_build_action = &TestBuildAction::new(cwd.clone(), profile, None, sample_class) // Even when cwd is changed to driver project inside the workspace, cargo metadata read - // is going to be for the whole workspace + assert!(matches!( + result, + Err(BuildActionError::WdkMetadataParse( + TryFromCargoMetadataError::MultipleWdkConfigurationsDetected { .. } + )) + )); + } + + #[test] + fn run_builds_non_driver_only_workspace_from_root() { + let workspace_root = PathBuf::from(r"C:\tmp\workspace"); + let non_driver_name = "non-driver"; + let non_driver_dir = workspace_root.join(non_driver_name); + let mut harness = BuildActionHarness::new( + workspace_root.clone(), + None, + None, + SignMode::Test { + verify_signature: false, + }, + false, + ) .set_up_workspace_with_multiple_driver_projects( - &workspace_root_dir, + &workspace_root, + vec![( + non_driver_name, + non_driver_dir.clone(), + None, + "lib", + "lib", + "lib.rs", + )], + ); + + // No WDK configuration anywhere; the non-driver member builds and packaging + // is skipped, so the run succeeds. + harness.expect_build_runner(non_driver_name, &non_driver_dir, None, None, Ok(Vec::new())); + harness.mock_package_task_runner.expect_run().never(); + + let build_action = initialize_build_action(&mut harness); + let result = build_action.run_from_workspace_root(&workspace_root); + + assert!( + result.is_ok(), + "build action failed unexpectedly: {result:?}" + ); + } + + #[test] + fn run_builds_non_driver_only_workspace_from_member() { + let workspace_root = PathBuf::from(r"C:\tmp\workspace"); + let non_driver_name = "non-driver"; + let non_driver_dir = workspace_root.join(non_driver_name); + let mut harness = BuildActionHarness::new( + non_driver_dir.clone(), + None, None, - vec![(workspace_member_3, package_3)], + SignMode::Test { + verify_signature: false, + }, + false, ) - .expect_root_manifest_exists(&cwd, true) - .expect_detect_wdk_build_number(25100u32) - .expect_cargo_build(non_driver, &cwd, None); + .set_up_workspace_with_multiple_driver_projects( + &workspace_root, + vec![( + non_driver_name, + non_driver_dir.clone(), + None, + "lib", + "lib", + "lib.rs", + )], + ); - assert_build_action_run_is_success( - &cwd, - profile, - None, - verify_signature, - sample_class, - test_build_action, - ); + // cwd is the non-driver member; it builds, packaging is skipped, and the run + // succeeds. + harness.expect_build_runner(non_driver_name, &non_driver_dir, None, None, Ok(Vec::new())); + harness.mock_package_task_runner.expect_run().never(); + + let build_action = initialize_build_action(&mut harness); + let result = build_action.run_from_workspace_root(&non_driver_dir); + + assert!( + result.is_ok(), + "build action failed unexpectedly: {result:?}" + ); + } } -fn assert_build_action_run_is_success( - cwd: &PathBuf, - profile: Option, - target_arch: Option, - verify_signature: bool, - sample_class: bool, - test_build_action: &TestBuildAction, -) { - let build_action = initialize_build_action( - cwd, - profile.as_ref(), - target_arch, - verify_signature, - sample_class, - test_build_action, - ); - assert!(build_action.is_ok()); - let run_result = build_action.expect("Failed to init build action").run(); - assert!( - run_result.is_ok(), - "build action failed unexpectedly: {run_result:?}" - ); +mod emulated_workspace { + use super::*; + + #[test] + fn run_builds_each_valid_project() { + let emulated_workspace = TestWorkspaceRoot::new("build-action-emulated-workspace"); + let driver_name_1 = "driver-a"; + let driver_name_2 = "driver-b"; + let ignored_dir = "docs"; + let driver_dir_1 = emulated_workspace.root.join(driver_name_1); + let driver_dir_2 = emulated_workspace.root.join(driver_name_2); + let ignored_dir_path = emulated_workspace.root.join(ignored_dir); + fs::create_dir_all(&driver_dir_1).expect("failed to create driver-a directory"); + fs::create_dir_all(&driver_dir_2).expect("failed to create driver-b directory"); + fs::create_dir_all(&ignored_dir_path).expect("failed to create docs directory"); + + let target_arch = CpuArchitecture::Amd64; + let target_dir_1 = expected_target_dir(&driver_dir_1, Some(target_arch), None); + let target_dir_2 = expected_target_dir(&driver_dir_2, Some(target_arch), None); + let target_triple = to_target_triple(target_arch); + let mut harness = BuildActionHarness::new( + emulated_workspace.root.clone(), + None, + Some(target_arch), + SignMode::Test { + verify_signature: false, + }, + false, + ); + + harness.expect_detect_wdk_build_number(25100); + harness.expect_root_manifest_exists(&emulated_workspace.root, false); + harness.expect_read_dir_entries(&emulated_workspace.root); + harness.expect_dir_cargo_toml_exists(&driver_dir_1, true); + harness.expect_dir_cargo_toml_exists(&driver_dir_1, true); + harness.expect_dir_cargo_toml_exists(&driver_dir_2, true); + harness.expect_dir_cargo_toml_exists(&ignored_dir_path, false); + harness.expect_dir_cargo_toml_exists(&ignored_dir_path, false); + harness.expect_metadata_for_paths(vec![ + ( + driver_dir_1.clone(), + metadata_from_packages( + &driver_dir_1, + vec![( + driver_name_1, + driver_dir_1.clone(), + Some(default_wdk_metadata()), + "cdylib", + "cdylib", + "main.rs", + )], + ), + ), + ( + driver_dir_2.clone(), + metadata_from_packages( + &driver_dir_2, + vec![( + driver_name_2, + driver_dir_2.clone(), + Some(default_wdk_metadata()), + "cdylib", + "cdylib", + "main.rs", + )], + ), + ), + ]); + harness.expect_build_runner( + driver_name_1, + &driver_dir_1, + None, + Some(target_arch), + Ok(cargo_build_messages( + driver_name_1, + DEFAULT_DRIVER_VERSION, + &driver_dir_1, + Some(target_triple.as_str()), + None, + )), + ); + harness.expect_build_runner( + driver_name_2, + &driver_dir_2, + None, + Some(target_arch), + Ok(cargo_build_messages( + driver_name_2, + DEFAULT_DRIVER_VERSION, + &driver_dir_2, + Some(target_triple.as_str()), + None, + )), + ); + harness.expect_package_runner( + driver_name_1, + &driver_dir_1, + &target_dir_1, + target_arch, + SignMode::Test { + verify_signature: false, + }, + false, + ); + harness.expect_package_runner( + driver_name_2, + &driver_dir_2, + &target_dir_2, + target_arch, + SignMode::Test { + verify_signature: false, + }, + false, + ); + + let build_action = initialize_build_action(&mut harness); + let result = crate::test_utils::with_env::<&str, &str, _, _>(&[], || build_action.run()); + + assert!( + result.is_ok(), + "build action failed unexpectedly: {result:?}" + ); + } } -fn initialize_build_action<'a>( - cwd: &'a PathBuf, - profile: Option<&'a Profile>, - target_arch: Option, - verify_signature: bool, - sample_class: bool, - test_build_action: &'a TestBuildAction, -) -> Result, anyhow::Error> { - let sign_mode = match test_build_action.sign_mode { - SignMode::Off => SignMode::Off, - SignMode::Test { .. } => SignMode::Test { verify_signature }, - }; - BuildAction::new( +fn initialize_build_action(harness: &mut BuildActionHarness) -> BuildAction<'_> { + BuildAction::new_with_runners( &BuildActionParams { - working_dir: cwd, - profile, - target_arch, - sign_mode, - is_sample_class: sample_class, - locked: test_build_action.locked, - features: &test_build_action.features, + working_dir: &harness.cwd, + profile: harness.profile.as_ref(), + target_arch: harness.target_arch, + sign_mode: harness.sign_mode, + is_sample_class: harness.sample_class, + locked: harness.locked, + features: &harness.features, verbosity_level: clap_verbosity_flag::Verbosity::new(1, 0), }, - test_build_action.mock_wdk_build_provider(), - test_build_action.mock_run_command(), - test_build_action.mock_fs_provider(), - test_build_action.mock_metadata_provider(), + &harness.mock_wdk_build_provider, + &harness.mock_run_command, + &harness.mock_fs_provider, + &harness.mock_metadata_provider, + std::mem::take(&mut harness.mock_build_task_runner), + std::mem::take(&mut harness.mock_package_task_runner), ) + .expect("failed to initialize build action") } -fn get_certmgr_success_output() -> Output { - Output { - status: ExitStatus::default(), - stdout: r"==============No Certificates ========== - ==============No CTLs ========== - ==============No CRLs ========== - ============================================== - CertMgr Succeeded" - .as_bytes() - .to_vec(), - stderr: vec![], +fn default_wdk_metadata() -> TestWdkMetadata { + get_cargo_metadata_wdk_metadata("KMDF", 1, 33) +} + +fn features_match(expected: &Features, actual: &Features) -> bool { + expected.all_features == actual.all_features + && expected.no_default_features == actual.no_default_features + && expected.features == actual.features +} + +fn forwards_locked(other_options: &[String], expected_locked: bool) -> bool { + other_options.iter().any(|opt| opt.as_str() == "--locked") == expected_locked +} + +#[allow(clippy::field_reassign_with_default)] +fn non_default_features() -> Features { + // `Features` is `#[non_exhaustive]`, so it can't be built with a struct + // literal; mutate a default instead. + let mut features = Features::default(); + features.no_default_features = true; + features.features = vec!["sample-feature".to_string()]; + features +} + +fn expected_target_dir( + cwd: &Path, + target_arch: Option, + profile: Option, +) -> PathBuf { + let mut target_dir = cwd.join("target"); + if let Some(target_arch) = target_arch { + target_dir = target_dir.join(to_target_triple(target_arch)); } + target_dir.join(match profile { + Some(Profile::Release) => "release", + _ => "debug", + }) } -fn assert_build_action_run_with_env_is_success( - cwd: &PathBuf, +fn cargo_build_messages( + package_name: &str, + package_version: &str, + cwd: &Path, + target_triple: Option<&str>, profile: Option, - target_arch: Option, - verify_signature: bool, - sample_class: bool, - test_build_action: &TestBuildAction, -) { - let build_action = initialize_build_action( - cwd, - profile.as_ref(), - target_arch, - verify_signature, - sample_class, - test_build_action, - ); - assert!(build_action.is_ok()); - let run_result = run_build_action(build_action); - assert!( - run_result.is_ok(), - "build action with env failed unexpectedly: {run_result:?}" - ); +) -> Vec> { + let output = + create_cargo_build_output_json(package_name, package_version, cwd, target_triple, profile); + Message::parse_stream(io::Cursor::new(output.stdout)).collect() } -fn run_build_action( - build_action: Result, anyhow::Error>, -) -> Result<(), BuildActionError> { - let build_action = build_action.expect("Failed to init build action"); - crate::test_utils::with_env::<&str, &str, _, _>(&[], || build_action.run()) +fn build_task_error() -> super::error::BuildTaskError { + super::error::BuildTaskError::CargoBuild(CommandError::from_output( + "cargo", + &["build"], + &Output { + status: ExitStatus::from_raw(1), + stdout: vec![], + stderr: b"cargo build failed".to_vec(), + }, + )) } -/// Helper functions -//////////////////////////////////////////////////////////////////////////////// -struct TestBuildAction { +struct BuildActionHarness { cwd: PathBuf, profile: Option, target_arch: Option, - sample_class: bool, sign_mode: SignMode, + sample_class: bool, locked: bool, features: Features, - - cargo_metadata: Option, - // mocks - mock_run_command: CommandExec, - mock_wdk_build_provider: WdkBuild, - mock_fs_provider: Fs, - mock_metadata_provider: MetadataProvider, + mock_run_command: MockCommandExec, + mock_wdk_build_provider: MockWdkBuild, + mock_fs_provider: MockFs, + mock_metadata_provider: MockMetadata, + mock_build_task_runner: MockBuildTaskRunner, + mock_package_task_runner: MockPackageTaskRunner, } -impl TestBuildAction { +impl BuildActionHarness { fn new( cwd: PathBuf, profile: Option, target_arch: Option, + sign_mode: SignMode, sample_class: bool, ) -> Self { - let mock_run_command = CommandExec::default(); - let mock_wdk_build_provider = WdkBuild::default(); - let mock_fs_provider = Fs::default(); - let mock_metadata_provider = MetadataProvider::default(); - Self { cwd, profile, target_arch, + sign_mode, sample_class, - sign_mode: SignMode::Test { - verify_signature: false, - }, locked: false, - features: Features::default(), - mock_run_command, - mock_wdk_build_provider, - mock_fs_provider, - mock_metadata_provider, - cargo_metadata: None, - } - } - - fn with_sign_mode(mut self, sign_mode: SignMode) -> Self { - self.sign_mode = sign_mode; - self - } - - fn with_locked(mut self, locked: bool) -> Self { - self.locked = locked; - self - } - - fn with_features(mut self, features: Features) -> Self { - self.features = features; - self - } - - fn set_up_standalone_driver_project( - mut self, - package_metadata: (TestMetadataWorkspaceMemberId, TestMetadataPackage), - ) -> Self { - let cargo_toml_metadata = get_cargo_metadata( - &self.cwd, - vec![package_metadata.1], - &[package_metadata.0], - None, - ); - let cargo_toml_metadata = - serde_json::from_str::(&cargo_toml_metadata) - .expect("Failed to parse cargo metadata in set_up_standalone_driver_project"); - let cargo_toml_metadata_clone = cargo_toml_metadata.clone(); - let expected_options: Vec = if self.locked { - vec!["--locked".to_string()] - } else { - vec![] - }; - let expected_features = self.features.clone(); - self.mock_metadata_provider - .expect_get_cargo_metadata_at_path() - .withf( - move |_working_dir: &Path, other_options: &Vec, features: &Features| { - *other_options == expected_options && *features == expected_features - }, - ) - .once() - .returning(move |_, _, _| Ok(cargo_toml_metadata_clone.clone())); - self.cargo_metadata = Some(cargo_toml_metadata); - self - } - - fn set_up_workspace_with_multiple_driver_projects( - mut self, - workspace_root_dir: &Path, - workspace_additional_metadata: Option, - package_metadata_list: Vec<(TestMetadataWorkspaceMemberId, TestMetadataPackage)>, - ) -> Self { - let cargo_toml_metadata = get_cargo_metadata( - workspace_root_dir, - package_metadata_list.iter().map(|p| p.1.clone()).collect(), - package_metadata_list - .into_iter() - .map(|p| p.0) - .collect::>() - .as_slice(), - workspace_additional_metadata, - ); - let cargo_toml_metadata = serde_json::from_str::( - &cargo_toml_metadata, - ) - .expect("Failed to parse cargo metadata in set_up_workspace_with_multiple_driver_projects"); - let cargo_toml_metadata_clone = cargo_toml_metadata.clone(); - let expected_options: Vec = if self.locked { - vec!["--locked".to_string()] - } else { - vec![] - }; - let expected_features = self.features.clone(); - self.mock_metadata_provider - .expect_get_cargo_metadata_at_path() - .withf( - move |_working_dir: &Path, other_options: &Vec, features: &Features| { - *other_options == expected_options && *features == expected_features - }, - ) - .once() - .returning(move |_, _, _| Ok(cargo_toml_metadata_clone.clone())); - self.cargo_metadata = Some(cargo_toml_metadata); - self - } - - fn set_up_with_custom_toml(mut self, cargo_toml_metadata: &str) -> Self { - let cargo_toml_metadata = - serde_json::from_str::(cargo_toml_metadata) - .expect("Failed to parse cargo metadata in set_up_with_custom_toml"); - let cargo_toml_metadata_clone = cargo_toml_metadata.clone(); - let expected_options: Vec = if self.locked { - vec!["--locked".to_string()] - } else { - vec![] - }; - let expected_features = self.features.clone(); - self.mock_metadata_provider - .expect_get_cargo_metadata_at_path() - .withf( - move |_working_dir: &Path, other_options: &Vec, features: &Features| { - *other_options == expected_options && *features == expected_features - }, - ) - .once() - .returning(move |_, _, _| Ok(cargo_toml_metadata_clone.clone())); - self.cargo_metadata = Some(cargo_toml_metadata); - self - } - - fn setup_target_dir(&self, dir_path: &Path) -> PathBuf { - let mut base = dir_path.join("target"); - let profile_dir_name = match self.profile { - Some(Profile::Release) => "release", - _ => "debug", - }; - if let Some(target_arch) = self.target_arch { - let triple = to_target_triple(target_arch); - base = base.join(triple); - } - base.join(profile_dir_name) - } -} - -// Presence of method ensures specific mock expectation is set -// Dir argument in any method means to operate on the relevant dir -// Output argument in any method means to override return output from default -// is_success boolean means success result of copy operation -// does_exist boolean means existence of the file or dir -// is_created boolean means whether the dir was created or not -impl TestBuildAction { - fn expect_default_build_task_steps( - self, - driver_name: &str, - cargo_build_output: Option, - ) -> Self { - let cwd = self.cwd.clone(); - self.expect_detect_wdk_build_number(25100u32) - .expect_root_manifest_exists(&cwd, true) - .expect_cargo_build(driver_name, &cwd, cargo_build_output) - } - - fn expect_default_package_task_steps( - self, - driver_name: &str, - driver_type: &str, - target_arch: CpuArchitecture, - verify_signature: bool, - ) -> Self { - let cwd = self.cwd.clone(); - let expected_certmgr_output = get_certmgr_success_output(); - let expectations = self - .expect_final_package_dir_exists(driver_name, &cwd, true) - .expect_inx_file_exists(driver_name, &cwd, true) - .expect_rename_driver_binary_dll_to_sys(driver_name, &cwd) - .expect_copy_driver_binary_sys_to_package_folder(driver_name, &cwd, true) - .expect_copy_pdb_file_to_package_folder(driver_name, &cwd, true) - .expect_copy_inx_file_to_package_folder(driver_name, &cwd, true, &cwd) - .expect_copy_map_file_to_package_folder(driver_name, &cwd, true) - .expect_stampinf(driver_name, &cwd, target_arch, None) - .expect_inf2cat(driver_name, &cwd, target_arch, None) - .expect_self_signed_cert_file_exists(&cwd, false) - .expect_certmgr_exists_check(Some(expected_certmgr_output)) - .expect_makecert(&cwd, None) - .expect_copy_self_signed_cert_file_to_package_folder(driver_name, &cwd, true) - .expect_signtool_sign_driver_binary_sys_file(driver_name, &cwd, None) - .expect_signtool_sign_cat_file(driver_name, &cwd, None) - .expect_infverif(driver_name, &cwd, driver_type, None); - if !verify_signature { - return expectations; - } - expectations - .expect_signtool_verify_driver_binary_sys_file(driver_name, &cwd, None) - .expect_signtool_verify_cat_file(driver_name, &cwd, None) - } - - /// Sets up package-task expectations for `SignMode::Off`: stampinf, - /// inf2cat, and infverif are still expected, but all certificate - /// generation, signing, and signature-verification steps are skipped. - fn expect_package_task_steps_with_sign_mode_off( - self, - driver_name: &str, - driver_type: &str, - target_arch: CpuArchitecture, - ) -> Self { - let cwd = self.cwd.clone(); - self.expect_final_package_dir_exists(driver_name, &cwd, true) - .expect_inx_file_exists(driver_name, &cwd, true) - .expect_rename_driver_binary_dll_to_sys(driver_name, &cwd) - .expect_copy_driver_binary_sys_to_package_folder(driver_name, &cwd, true) - .expect_copy_pdb_file_to_package_folder(driver_name, &cwd, true) - .expect_copy_inx_file_to_package_folder(driver_name, &cwd, true, &cwd) - .expect_copy_map_file_to_package_folder(driver_name, &cwd, true) - .expect_stampinf(driver_name, &cwd, target_arch, None) - .expect_inf2cat(driver_name, &cwd, target_arch, None) - .expect_infverif(driver_name, &cwd, driver_type, None) - } - - fn expect_default_package_task_steps_for_workspace( - self, - driver_name: &str, - driver_type: &str, - target_arch: CpuArchitecture, - verify_signature: bool, - ) -> Self { - let cwd = self.cwd.clone(); - let expected_certmgr_output = get_certmgr_success_output(); - let expectations = self - .expect_final_package_dir_exists(driver_name, &cwd, true) - .expect_inx_file_exists(driver_name, &cwd.join(driver_name), true) - .expect_rename_driver_binary_dll_to_sys(driver_name, &cwd) - .expect_copy_driver_binary_sys_to_package_folder(driver_name, &cwd, true) - .expect_copy_pdb_file_to_package_folder(driver_name, &cwd, true) - .expect_copy_inx_file_to_package_folder(driver_name, &cwd.join(driver_name), true, &cwd) - .expect_copy_map_file_to_package_folder(driver_name, &cwd, true) - .expect_stampinf(driver_name, &cwd, target_arch, None) - .expect_inf2cat(driver_name, &cwd, target_arch, None) - .expect_self_signed_cert_file_exists(&cwd, false) - .expect_certmgr_exists_check(Some(expected_certmgr_output)) - .expect_makecert(&cwd, None) - .expect_copy_self_signed_cert_file_to_package_folder(driver_name, &cwd, true) - .expect_signtool_sign_driver_binary_sys_file(driver_name, &cwd, None) - .expect_signtool_sign_cat_file(driver_name, &cwd, None) - .expect_infverif(driver_name, &cwd, driver_type, None); - if !verify_signature { - return expectations; - } - expectations - .expect_signtool_verify_driver_binary_sys_file(driver_name, &cwd, None) - .expect_signtool_verify_cat_file(driver_name, &cwd, None) - } - - fn expect_root_manifest_exists(mut self, root_dir: &Path, does_exist: bool) -> Self { - self.mock_fs_provider - .expect_exists() - .with(eq(root_dir.join("Cargo.toml"))) - .once() - .returning(move |_| does_exist); - self - } - - fn expect_self_signed_cert_file_exists(mut self, driver_dir: &Path, does_exist: bool) -> Self { - let expected_target_dir = self.setup_target_dir(driver_dir); - let expected_src_driver_cert_path = expected_target_dir.join("WDRLocalTestCert.cer"); - self.mock_fs_provider - .expect_exists() - .with(eq(expected_src_driver_cert_path)) - .once() - .returning(move |_| does_exist); - self - } - - fn expect_final_package_dir_exists( - mut self, - driver_name: &str, - cwd: &Path, - does_exist: bool, - ) -> Self { - let expected_driver_name_underscored = driver_name.replace('-', "_"); - let expected_target_dir = self.setup_target_dir(cwd); - let expected_final_package_dir_path = - expected_target_dir.join(format!("{expected_driver_name_underscored}_package")); - self.mock_fs_provider - .expect_exists() - .with(eq(expected_final_package_dir_path)) - .once() - .returning(move |_| does_exist); - self - } - - fn expect_dir_created(mut self, driver_name: &str, cwd: &Path, created: bool) -> Self { - let expected_driver_name_underscored = driver_name.replace('-', "_"); - let expected_target_dir = self.setup_target_dir(cwd); - let expected_final_package_dir_path = - expected_target_dir.join(format!("{expected_driver_name_underscored}_package")); - self.mock_fs_provider - .expect_create_dir() - .with(eq(expected_final_package_dir_path.clone())) - .once() - .returning(move |_| { - if created { - Ok(()) - } else { - Err(FileError::CreateDirError( - expected_final_package_dir_path.clone(), - std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "create error"), - )) - } - }); - self - } - - fn expect_cargo_build( - mut self, - driver_name: &str, - cwd: &Path, - override_output: Option, - ) -> Self { - // cargo build on the package - let expected_cargo_command: &'static str = "cargo"; - let manifest_path = cwd - .join("Cargo.toml") - .to_string_lossy() - .trim_start_matches("\\\\?\\") - .to_string(); - let mut expected_cargo_build_args: Vec = vec![ - "build", - "--message-format=json-render-diagnostics", - "-p", - &driver_name, - "--manifest-path", - &manifest_path, - ] - .into_iter() - .map(ToString::to_string) - .collect(); - if let Some(profile) = self.profile { - expected_cargo_build_args.push("--profile".to_string()); - expected_cargo_build_args.push(profile.to_string()); - } - - if let Some(target_arch) = self.target_arch { - expected_cargo_build_args.push("--target".to_string()); - expected_cargo_build_args.push(to_target_triple(target_arch)); - } - - if self.locked { - expected_cargo_build_args.push("--locked".to_string()); - } - - expected_cargo_build_args.push("-v".to_string()); - let expected_output = override_output.unwrap_or_else(|| Output { - status: ExitStatus::default(), - stdout: vec![], - stderr: vec![], - }); - self.mock_run_command - .expect_run() - .withf( - move |command: &str, - args: &[&str], - _env_vars: &Option<&HashMap<&str, &str>>, - _working_dir: &Option<&Path>| - -> bool { - command == expected_cargo_command && args == expected_cargo_build_args - }, - ) - .once() - .returning(move |_, _, _, _| Ok(expected_output.clone())); - self - } - - fn expect_probe_target_arch_using_cargo_rustc( - mut self, - driver_dir: &Path, - detected_arch: CpuArchitecture, - override_output: Option, - ) -> Self { - if self.target_arch.is_some() { - println!("`cargo rustc` must not be probed when target architecture is already set"); - return self; - } - let expected_working_dir = driver_dir.to_path_buf(); - let arch_str = match detected_arch { - CpuArchitecture::Amd64 => "x86_64", - CpuArchitecture::Arm64 => "aarch64", - }; - let mut expected_args: Vec = vec!["rustc".to_string()]; - if self.locked { - expected_args.push("--locked".to_string()); - } - expected_args.push("--".to_string()); - expected_args.push("--print".to_string()); - expected_args.push("cfg".to_string()); - let expected_args_for_err = expected_args.clone(); - self.mock_run_command - .expect_run() - .withf( - move |command: &str, - args: &[&str], - _env_vars: &Option<&HashMap<&str, &str>>, - working_dir: &Option<&Path>| { - command == "cargo" - && args == expected_args - && working_dir.is_some_and(|d| d == expected_working_dir.as_path()) - }, - ) - .once() - .returning(move |_, _, _, _| match override_output.clone() { - Some(output) => { - if output.status.code() == Some(0) { - Ok(Output { - status: ExitStatus::from_raw(0), - stdout: vec![], - stderr: vec![], - }) - } else { - let err_args: Vec<&str> = - expected_args_for_err.iter().map(String::as_str).collect(); - Err(CommandError::from_output("cargo", &err_args, &output)) - } - } - None => Ok(Output { - status: ExitStatus::default(), - stdout: format!("target_arch=\"{arch_str}\"\n").as_bytes().to_vec(), - stderr: vec![], - }), - }); - self - } - - fn expect_inx_file_exists( - mut self, - driver_name: &str, - driver_dir: &Path, - does_exist: bool, - ) -> Self { - let expected_driver_name_underscored = driver_name.replace('-', "_"); - let expected_inx_file_path = - driver_dir.join(format!("{expected_driver_name_underscored}.inx")); - self.mock_fs_provider - .expect_exists() - .with(eq(expected_inx_file_path)) - .once() - .returning(move |_| does_exist); - self - } - - fn expect_rename_driver_binary_dll_to_sys( - mut self, - driver_name: &str, - driver_dir: &Path, - ) -> Self { - let expected_driver_name_underscored = driver_name.replace('-', "_"); - let expected_target_dir = self.setup_target_dir(driver_dir); - let expected_src_driver_dll_path = - expected_target_dir.join(format!("{expected_driver_name_underscored}.dll")); - let expected_src_driver_sys_path = - expected_target_dir.join(format!("{expected_driver_name_underscored}.sys")); - self.mock_fs_provider - .expect_rename() - .with( - eq(expected_src_driver_dll_path), - eq(expected_src_driver_sys_path), - ) - .once() - .returning(|_, _| Ok(())); - self - } - - fn expect_copy_driver_binary_sys_to_package_folder( - mut self, - driver_name: &str, - driver_dir: &Path, - is_success: bool, - ) -> Self { - let expected_driver_name_underscored = driver_name.replace('-', "_"); - let expected_target_dir = self.setup_target_dir(driver_dir); - let expected_final_package_dir_path = - expected_target_dir.join(format!("{expected_driver_name_underscored}_package")); - let mock_non_zero_bytes_copied_size = 1000u64; - - let expected_src_driver_sys_path = - expected_target_dir.join(format!("{expected_driver_name_underscored}.sys")); - let expected_dest_driver_binary_path = - expected_final_package_dir_path.join(format!("{expected_driver_name_underscored}.sys")); - let expected_src_driver_binary_path = expected_src_driver_sys_path; - self.mock_fs_provider - .expect_copy() - .with( - eq(expected_src_driver_binary_path.clone()), - eq(expected_dest_driver_binary_path.clone()), - ) - .once() - .returning(move |_, _| { - if is_success { - Ok(mock_non_zero_bytes_copied_size) - } else { - Err(FileError::CopyError( - expected_src_driver_binary_path.clone(), - expected_dest_driver_binary_path.clone(), - std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "copy error"), - )) - } - }); - self - } - - fn expect_copy_pdb_file_to_package_folder( - mut self, - driver_name: &str, - driver_dir: &Path, - is_success: bool, - ) -> Self { - let expected_driver_name_underscored = driver_name.replace('-', "_"); - let expected_target_dir = self.setup_target_dir(driver_dir); - let expected_final_package_dir_path = - expected_target_dir.join(format!("{expected_driver_name_underscored}_package")); - let mock_non_zero_bytes_copied_size = 1000u64; - - // copy pdb file to package directory - let expected_src_driver_pdb_path = - expected_target_dir.join(format!("{expected_driver_name_underscored}.pdb")); - let expected_dest_driver_pdb_path = - expected_final_package_dir_path.join(format!("{expected_driver_name_underscored}.pdb")); - self.mock_fs_provider - .expect_copy() - .with( - eq(expected_src_driver_pdb_path.clone()), - eq(expected_dest_driver_pdb_path.clone()), - ) - .once() - .returning(move |_, _| { - if is_success { - Ok(mock_non_zero_bytes_copied_size) - } else { - Err(FileError::CopyError( - expected_src_driver_pdb_path.clone(), - expected_dest_driver_pdb_path.clone(), - std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "copy error"), - )) - } - }); - self - } - - fn expect_copy_inx_file_to_package_folder( - mut self, - driver_name: &str, - driver_dir: &Path, - is_success: bool, - workspace_root_dir: &Path, - ) -> Self { - let expected_driver_name_underscored = driver_name.replace('-', "_"); - let expected_target_dir = self.setup_target_dir(workspace_root_dir); - let expected_final_package_dir_path = - expected_target_dir.join(format!("{expected_driver_name_underscored}_package")); - let mock_non_zero_bytes_copied_size = 1000u64; - - // copy inx file to package directory - let expected_src_driver_inx_path = - driver_dir.join(format!("{expected_driver_name_underscored}.inx")); - let expected_dest_driver_inf_path = - expected_final_package_dir_path.join(format!("{expected_driver_name_underscored}.inf")); - self.mock_fs_provider - .expect_copy() - .with( - eq(expected_src_driver_inx_path.clone()), - eq(expected_dest_driver_inf_path.clone()), - ) - .once() - .returning(move |_, _| { - if is_success { - Ok(mock_non_zero_bytes_copied_size) - } else { - Err(FileError::CopyError( - expected_src_driver_inx_path.clone(), - expected_dest_driver_inf_path.clone(), - std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "copy error"), - )) - } - }); - self - } - - fn expect_copy_map_file_to_package_folder( - mut self, - driver_name: &str, - driver_dir: &Path, - is_success: bool, - ) -> Self { - let expected_driver_name_underscored = driver_name.replace('-', "_"); - let expected_target_dir = self.setup_target_dir(driver_dir); - let expected_final_package_dir_path = - expected_target_dir.join(format!("{expected_driver_name_underscored}_package")); - let mock_non_zero_bytes_copied_size = 1000u64; - - // copy map file to package directory - let expected_src_driver_map_path = expected_target_dir - .join("deps") - .join(format!("{expected_driver_name_underscored}.map")); - let expected_dest_driver_map_path = - expected_final_package_dir_path.join(format!("{expected_driver_name_underscored}.map")); - self.mock_fs_provider - .expect_copy() - .with( - eq(expected_src_driver_map_path.clone()), - eq(expected_dest_driver_map_path.clone()), - ) - .once() - .returning(move |_, _| { - if is_success { - Ok(mock_non_zero_bytes_copied_size) - } else { - Err(FileError::CopyError( - expected_src_driver_map_path.clone(), - expected_dest_driver_map_path.clone(), - std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "copy error"), - )) - } - }); - self - } - - fn expect_copy_self_signed_cert_file_to_package_folder( - mut self, - driver_name: &str, - driver_dir: &Path, - is_success: bool, - ) -> Self { - let expected_driver_name_underscored = driver_name.replace('-', "_"); - let expected_target_dir = self.setup_target_dir(driver_dir); - let expected_final_package_dir_path = - expected_target_dir.join(format!("{expected_driver_name_underscored}_package")); - let mock_non_zero_bytes_copied_size = 1000u64; - - // copy self signed certificate to package directory - let expected_src_cert_file_path = expected_target_dir.join("WDRLocalTestCert.cer"); - let expected_dest_driver_cert_path = - expected_final_package_dir_path.join("WDRLocalTestCert.cer"); - self.mock_fs_provider - .expect_copy() - .with( - eq(expected_src_cert_file_path.clone()), - eq(expected_dest_driver_cert_path.clone()), - ) - .once() - .returning(move |_, _| { - if is_success { - Ok(mock_non_zero_bytes_copied_size) - } else { - Err(FileError::CopyError( - expected_src_cert_file_path.clone(), - expected_dest_driver_cert_path.clone(), - std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "copy error"), - )) - } - }); - self - } - - fn expect_stampinf( - mut self, - driver_name: &str, - driver_dir: &Path, - target_arch: CpuArchitecture, - override_output: Option, - ) -> Self { - // Run stampinf command - let expected_driver_name_underscored = driver_name.replace('-', "_"); - let expected_target_dir = self.setup_target_dir(driver_dir); - let expected_final_package_dir_path = - expected_target_dir.join(format!("{expected_driver_name_underscored}_package")); - let expected_dest_driver_inf_path = - expected_final_package_dir_path.join(format!("{expected_driver_name_underscored}.inf")); - - let expected_stampinf_command: &'static str = "stampinf"; - let wdk_metadata = Wdk::try_from( - self.cargo_metadata - .as_ref() - .expect("cargo metadata must be available"), - ) - .expect("Wdk metadata must be available"); - - if let DriverConfig::Kmdf(kmdf_config) = wdk_metadata.driver_model { - let expected_cat_file_name = format!("{expected_driver_name_underscored}.cat"); - let expected_stampinf_args: Vec = vec![ - "-f".to_string(), - expected_dest_driver_inf_path.to_string_lossy().to_string(), - "-d".to_string(), - "*".to_string(), - "-a".to_string(), - target_arch.to_string(), - "-c".to_string(), - expected_cat_file_name, - "-v".to_string(), - "*".to_string(), - "-k".to_string(), - format!( - "{}.{}", - kmdf_config.kmdf_version_major, kmdf_config.target_kmdf_version_minor - ), - ]; - - self.mock_run_command - .expect_run() - .withf( - move |command: &str, - args: &[&str], - _env_vars: &Option<&HashMap<&str, &str>>, - _working_dir: &Option<&Path>| - -> bool { - println!("command: {command}, args: {args:?}"); - println!( - "expected_command: {expected_stampinf_command}, expected_args: \ - {expected_stampinf_args:?}" - ); - command == expected_stampinf_command && args == expected_stampinf_args - }, - ) - .once() - .returning(move |_, _, _, _| match override_output.clone() { - Some(output) => match output.status.code() { - Some(0) => Ok(Output { - status: ExitStatus::from_raw(0), - stdout: vec![], - stderr: vec![], - }), - _ => Err(CommandError::from_output("stampinf", &[], &output)), - }, - None => Ok(Output { - status: ExitStatus::default(), - stdout: vec![], - stderr: vec![], - }), - }); - } - self - } - - fn expect_inf2cat( - mut self, - driver_name: &str, - driver_dir: &Path, - target_arch: CpuArchitecture, - override_output: Option, - ) -> Self { - // Run inf2cat command - let expected_driver_name_underscored = driver_name.replace('-', "_"); - let expected_target_dir = self.setup_target_dir(driver_dir); - let expected_final_package_dir_path = - expected_target_dir.join(format!("{expected_driver_name_underscored}_package")); - - let expected_inf2cat_command: &'static str = "inf2cat"; - - let expected_inf2cat_arg = match target_arch { - CpuArchitecture::Amd64 => "10_x64", - CpuArchitecture::Arm64 => "Server10_arm64", - }; - let expected_inf2cat_args: Vec = vec![ - format!( - "/driver:{}", - expected_final_package_dir_path.to_string_lossy() - ), - format!("/os:{}", expected_inf2cat_arg), - "/uselocaltime".to_string(), - ]; - - self.mock_run_command - .expect_run() - .withf( - move |command: &str, - args: &[&str], - _env_vars: &Option<&HashMap<&str, &str>>, - _working_dir: &Option<&Path>| - -> bool { - println!("command: {command}, args: {args:?}"); - println!( - "expected_command: {expected_inf2cat_command}, expected_args: \ - {expected_inf2cat_args:?}" - ); - command == expected_inf2cat_command && args == expected_inf2cat_args - }, - ) - .once() - .returning(move |_, _, _, _| match override_output.clone() { - Some(output) => match output.status.code() { - Some(0) => Ok(Output { - status: ExitStatus::from_raw(0), - stdout: vec![], - stderr: vec![], - }), - _ => Err(CommandError::from_output("inf2cat", &[], &output)), - }, - None => Ok(Output { - status: ExitStatus::default(), - stdout: vec![], - stderr: vec![], - }), - }); - self - } - - fn expect_certmgr_exists_check(mut self, override_output: Option) -> Self { - // check for cert in cert store using certmgr - let expected_certmgr_command: &'static str = "certmgr.exe"; - let expected_certmgr_args: Vec = - vec!["-s".to_string(), "WDRTestCertStore".to_string()]; - self.mock_run_command - .expect_run() - .withf( - move |command: &str, - args: &[&str], - _env_vars: &Option<&HashMap<&str, &str>>, - _working_dir: &Option<&Path>| - -> bool { - command == expected_certmgr_command && args == expected_certmgr_args - }, - ) - .returning(move |_, _, _, _| match override_output.clone() { - Some(output) => match output.status.code() { - Some(0) => Ok(Output { - status: ExitStatus::from_raw(0), - stdout: output.stdout, - stderr: output.stderr, - }), - _ => Err(CommandError::from_output("certmgr", &[], &output)), - }, - None => Ok(Output { - status: ExitStatus::default(), - stdout: vec![], - stderr: vec![], - }), - }); - self - } - - fn expect_certmgr_create_cert_from_store( - mut self, - driver_dir: &Path, - override_output: Option, - ) -> Self { - // create cert from store using certmgr - let expected_target_dir = self.setup_target_dir(driver_dir); - let expected_self_signed_cert_file_path = expected_target_dir.join("WDRLocalTestCert.cer"); - - let expected_certmgr_command: &'static str = "certmgr.exe"; - let expected_certmgr_args: Vec = vec![ - "-put".to_string(), - "-s".to_string(), - "WDRTestCertStore".to_string(), - "-c".to_string(), - "-n".to_string(), - "WDRLocalTestCert".to_string(), - expected_self_signed_cert_file_path - .to_string_lossy() - .to_string(), - ]; - self.mock_run_command - .expect_run() - .withf( - move |command: &str, - args: &[&str], - _env_vars: &Option<&HashMap<&str, &str>>, - _working_dir: &Option<&Path>| - -> bool { - command == expected_certmgr_command && args == expected_certmgr_args - }, - ) - .once() - .returning(move |_, _, _, _| match override_output.clone() { - Some(output) => match output.status.code() { - Some(0) => Ok(Output { - status: ExitStatus::from_raw(0), - stdout: vec![], - stderr: vec![], - }), - _ => Err(CommandError::from_output("certmgr", &[], &output)), - }, - None => Ok(Output { - status: ExitStatus::default(), - stdout: vec![], - stderr: vec![], - }), - }); - self + features: Features::default(), + mock_run_command: MockCommandExec::new(), + mock_wdk_build_provider: MockWdkBuild::new(), + mock_fs_provider: MockFs::new(), + mock_metadata_provider: MockMetadata::new(), + mock_build_task_runner: MockBuildTaskRunner::new(), + mock_package_task_runner: MockPackageTaskRunner::new(), + } } - fn expect_makecert(mut self, driver_dir: &Path, override_output: Option) -> Self { - // create self signed certificate using makecert - let expected_target_dir = self.setup_target_dir(driver_dir); - let expected_makecert_command: &'static str = "makecert"; - let expected_src_driver_cert_path = expected_target_dir.join("WDRLocalTestCert.cer"); - let expected_makecert_args: Vec = vec![ - "-r".to_string(), - "-pe".to_string(), - "-a".to_string(), - "SHA256".to_string(), - "-eku".to_string(), - "1.3.6.1.5.5.7.3.3".to_string(), - "-ss".to_string(), - "WDRTestCertStore".to_string(), - "-n".to_string(), - "CN=WDRLocalTestCert".to_string(), - expected_src_driver_cert_path.to_string_lossy().to_string(), - ]; + fn with_locked(mut self, locked: bool) -> Self { + self.locked = locked; + self + } - self.mock_run_command - .expect_run() - .withf( - move |command: &str, - args: &[&str], - _env_vars: &Option<&HashMap<&str, &str>>, - _working_dir: &Option<&Path>| - -> bool { - command == expected_makecert_command && args == expected_makecert_args - }, - ) - .once() - .returning(move |_, _, _, _| match override_output.clone() { - Some(output) => match output.status.code() { - Some(0) => Ok(Output { - status: ExitStatus::from_raw(0), - stdout: vec![], - stderr: vec![], - }), - _ => Err(CommandError::from_output("makecert", &[], &output)), - }, - None => Ok(Output { - status: ExitStatus::default(), - stdout: vec![], - stderr: vec![], - }), - }); + fn with_features(mut self, features: Features) -> Self { + self.features = features; self } - fn expect_signtool_sign_driver_binary_sys_file( + fn set_up_standalone_driver_project( mut self, - driver_name: &str, - driver_dir: &Path, - override_output: Option, + package_name: &str, + metadata: Option, ) -> Self { - let expected_driver_name_underscored = driver_name.replace('-', "_"); - let expected_target_dir = self.setup_target_dir(driver_dir); - let expected_final_package_dir_path = - expected_target_dir.join(format!("{expected_driver_name_underscored}_package")); - let expected_signtool_command: &'static str = "signtool"; - - // sign driver binary using signtool - let expected_dest_driver_binary_path = - expected_final_package_dir_path.join(format!("{expected_driver_name_underscored}.sys")); - let expected_signtool_args: Vec = vec![ - "sign".to_string(), - "/v".to_string(), - "/s".to_string(), - "WDRTestCertStore".to_string(), - "/n".to_string(), - "WDRLocalTestCert".to_string(), - "/t".to_string(), - "http://timestamp.digicert.com".to_string(), - "/fd".to_string(), - "SHA256".to_string(), - expected_dest_driver_binary_path - .to_string_lossy() - .to_string(), - ]; - - self.mock_run_command - .expect_run() - .withf( - move |command: &str, - args: &[&str], - _env_vars: &Option<&HashMap<&str, &str>>, - _working_dir: &Option<&Path>| - -> bool { - command == expected_signtool_command && args == expected_signtool_args - }, - ) + let is_driver = metadata.is_some(); + let cargo_toml_metadata = metadata_from_packages( + &self.cwd, + vec![( + package_name, + self.cwd.clone(), + metadata, + if is_driver { "cdylib" } else { "lib" }, + if is_driver { "cdylib" } else { "lib" }, + if is_driver { "main.rs" } else { "lib.rs" }, + )], + ); + let cargo_toml_metadata_for_closure = cargo_toml_metadata; + let expected_locked = self.locked; + let expected_features = self.features.clone(); + self.mock_metadata_provider + .expect_get_cargo_metadata_at_path() + .withf(move |_working_dir, other_options, features| { + forwards_locked(other_options, expected_locked) + && features_match(&expected_features, features) + }) .once() - .returning(move |_, _, _, _| match override_output.clone() { - Some(output) => match output.status.code() { - Some(0) => Ok(Output { - status: ExitStatus::from_raw(0), - stdout: vec![], - stderr: vec![], - }), - _ => Err(CommandError::from_output("signtool", &[], &output)), - }, - None => Ok(Output { - status: ExitStatus::default(), - stdout: vec![], - stderr: vec![], - }), - }); + .returning(move |_, _, _| Ok(cargo_toml_metadata_for_closure.clone())); self } - fn expect_signtool_sign_cat_file( + fn set_up_workspace_with_multiple_driver_projects( mut self, - driver_name: &str, - driver_dir: &Path, - override_output: Option, + workspace_root_dir: &Path, + packages: Vec>, ) -> Self { - let expected_driver_name_underscored = driver_name.replace('-', "_"); - let expected_target_dir = self.setup_target_dir(driver_dir); - let expected_final_package_dir_path = - expected_target_dir.join(format!("{expected_driver_name_underscored}_package")); - let expected_signtool_command: &'static str = "signtool"; - - // sign driver cat file using signtool - let expected_dest_driver_cat_file_path = - expected_final_package_dir_path.join(format!("{expected_driver_name_underscored}.cat")); - let expected_signtool_args: Vec = vec![ - "sign".to_string(), - "/v".to_string(), - "/s".to_string(), - "WDRTestCertStore".to_string(), - "/n".to_string(), - "WDRLocalTestCert".to_string(), - "/t".to_string(), - "http://timestamp.digicert.com".to_string(), - "/fd".to_string(), - "SHA256".to_string(), - expected_dest_driver_cat_file_path - .to_string_lossy() - .to_string(), - ]; - self.mock_run_command - .expect_run() - .withf( - move |command: &str, - args: &[&str], - _env_vars: &Option<&HashMap<&str, &str>>, - _working_dir: &Option<&Path>| - -> bool { - command == expected_signtool_command && args == expected_signtool_args - }, - ) + let cargo_toml_metadata = metadata_from_packages(workspace_root_dir, packages); + let cargo_toml_metadata_for_closure = cargo_toml_metadata; + let expected_locked = self.locked; + let expected_features = self.features.clone(); + self.mock_metadata_provider + .expect_get_cargo_metadata_at_path() + .withf(move |_working_dir, other_options, features| { + forwards_locked(other_options, expected_locked) + && features_match(&expected_features, features) + }) .once() - .returning(move |_, _, _, _| match override_output.clone() { - Some(output) => match output.status.code() { - Some(0) => Ok(Output { - status: ExitStatus::from_raw(0), - stdout: vec![], - stderr: vec![], - }), - _ => Err(CommandError::from_output("signtool", &[], &output)), - }, - None => Ok(Output { - status: ExitStatus::default(), - stdout: vec![], - stderr: vec![], - }), - }); + .returning(move |_, _, _| Ok(cargo_toml_metadata_for_closure.clone())); self } - fn expect_signtool_verify_driver_binary_sys_file( + fn set_up_workspace_with_root_metadata( mut self, - driver_name: &str, - driver_dir: &Path, - override_output: Option, + workspace_root_dir: &Path, + root_metadata: Option, + packages: Vec>, ) -> Self { - let expected_driver_name_underscored = driver_name.replace('-', "_"); - let expected_target_dir = self.setup_target_dir(driver_dir); - let expected_final_package_dir_path = - expected_target_dir.join(format!("{expected_driver_name_underscored}_package")); - let expected_signtool_command: &'static str = "signtool"; - - // verify signed driver binary using signtool - let expected_dest_driver_binary_path = - expected_final_package_dir_path.join(format!("{expected_driver_name_underscored}.sys")); - let expected_signtool_verify_args: Vec = vec![ - "verify".to_string(), - "/v".to_string(), - "/pa".to_string(), - expected_dest_driver_binary_path - .to_string_lossy() - .to_string(), - ]; - self.mock_run_command - .expect_run() - .withf( - move |command: &str, - args: &[&str], - _env_vars: &Option<&HashMap<&str, &str>>, - _working_dir: &Option<&Path>| - -> bool { - command == expected_signtool_command && args == expected_signtool_verify_args - }, - ) + let cargo_toml_metadata = + metadata_from_packages_with_root(workspace_root_dir, root_metadata, packages); + let cargo_toml_metadata_for_closure = cargo_toml_metadata; + let expected_locked = self.locked; + let expected_features = self.features.clone(); + self.mock_metadata_provider + .expect_get_cargo_metadata_at_path() + .withf(move |_working_dir, other_options, features| { + forwards_locked(other_options, expected_locked) + && features_match(&expected_features, features) + }) .once() - .returning(move |_, _, _, _| match override_output.clone() { - Some(output) => match output.status.code() { - Some(0) => Ok(Output { - status: ExitStatus::from_raw(0), - stdout: vec![], - stderr: vec![], - }), - _ => Err(CommandError::from_output("signtool", &[], &output)), - }, - None => Ok(Output { - status: ExitStatus::default(), - stdout: vec![], - stderr: vec![], - }), - }); + .returning(move |_, _, _| Ok(cargo_toml_metadata_for_closure.clone())); self } - fn expect_signtool_verify_cat_file( - mut self, - driver_name: &str, - driver_dir: &Path, - override_output: Option, - ) -> Self { - let expected_driver_name_underscored = driver_name.replace('-', "_"); - let expected_target_dir = self.setup_target_dir(driver_dir); - let expected_final_package_dir_path = - expected_target_dir.join(format!("{expected_driver_name_underscored}_package")); - let expected_signtool_command: &'static str = "signtool"; - - // verify signed driver cat file using signtool - let expected_dest_driver_cat_file_path = - expected_final_package_dir_path.join(format!("{expected_driver_name_underscored}.cat")); - let expected_signtool_verify_args: Vec = vec![ - "verify".to_string(), - "/v".to_string(), - "/pa".to_string(), - expected_dest_driver_cat_file_path - .to_string_lossy() - .to_string(), - ]; - self.mock_run_command - .expect_run() - .withf( - move |command: &str, - args: &[&str], - _env_vars: &Option<&HashMap<&str, &str>>, - _working_dir: &Option<&Path>| - -> bool { - command == expected_signtool_command && args == expected_signtool_verify_args - }, - ) + fn set_up_with_custom_toml(mut self, cargo_toml_metadata: &str) -> Self { + let cargo_toml_metadata = serde_json::from_str::(cargo_toml_metadata) + .expect("failed to parse cargo metadata"); + let cargo_toml_metadata_for_closure = cargo_toml_metadata; + let expected_locked = self.locked; + let expected_features = self.features.clone(); + self.mock_metadata_provider + .expect_get_cargo_metadata_at_path() + .withf(move |_working_dir, other_options, features| { + forwards_locked(other_options, expected_locked) + && features_match(&expected_features, features) + }) .once() - .returning(move |_, _, _, _| match override_output.clone() { - Some(output) => match output.status.code() { - Some(0) => Ok(Output { - status: ExitStatus::from_raw(0), - stdout: vec![], - stderr: vec![], - }), - _ => Err(CommandError::from_output("stampinf", &[], &output)), - }, - None => Ok(Output { - status: ExitStatus::default(), - stdout: vec![], - stderr: vec![], - }), - }); + .returning(move |_, _, _| Ok(cargo_toml_metadata_for_closure.clone())); self } - fn expect_detect_wdk_build_number(mut self, expected_wdk_build_number: u32) -> Self { + fn expect_metadata_for_paths(&mut self, metadata_by_path: Vec<(PathBuf, CargoMetadata)>) { + let expected_locked = self.locked; + let expected_features = self.features.clone(); + self.mock_metadata_provider + .expect_get_cargo_metadata_at_path() + .withf(move |_working_dir, other_options, features| { + forwards_locked(other_options, expected_locked) + && features_match(&expected_features, features) + }) + .times(metadata_by_path.len()) + .returning(move |path, _other_options, _features| { + let requested_path = PathBuf::from(path); + metadata_by_path + .iter() + .find_map(|(expected_path, metadata)| { + (requested_path == *expected_path).then_some(metadata.clone()) + }) + .ok_or_else(|| { + cargo_metadata::Error::from(io::Error::new( + io::ErrorKind::NotFound, + format!("unexpected metadata path: {}", requested_path.display()), + )) + }) + }); + } + + fn expect_detect_wdk_build_number(&mut self, build_number: u32) { self.mock_wdk_build_provider .expect_detect_wdk_build_number() .once() - .returning(move || Ok(expected_wdk_build_number)); - self + .returning(move || Ok(build_number)); } - fn expect_infverif( - mut self, - driver_name: &str, - driver_dir: &Path, - driver_type: &str, - override_output: Option, - ) -> Self { - let mut expected_infverif_args = vec!["/v".to_string()]; - if driver_type.eq_ignore_ascii_case("KMDF") || driver_type.eq_ignore_ascii_case("WDM") { - expected_infverif_args.push("/w".to_string()); - } else { - expected_infverif_args.push("/u".to_string()); - } - if self.sample_class { - expected_infverif_args.push("/msft".to_string()); - } - let expected_infverif_command: &'static str = "infverif"; - let expected_driver_name_underscored = driver_name.replace('-', "_"); - let expected_target_dir = self.setup_target_dir(driver_dir); - let expected_final_package_dir_path = - expected_target_dir.join(format!("{expected_driver_name_underscored}_package")); - let expected_dest_inf_file_path = - expected_final_package_dir_path.join(format!("{expected_driver_name_underscored}.inf")); - expected_infverif_args.push(expected_dest_inf_file_path.to_string_lossy().to_string()); + fn expect_root_manifest_exists(&mut self, root_dir: &Path, exists: bool) { + let cargo_toml_path = root_dir.join("Cargo.toml"); + self.mock_fs_provider + .expect_exists() + .with(eq(cargo_toml_path)) + .once() + .returning(move |_| exists); + } + + fn expect_read_dir_entries(&mut self, root_dir: &Path) { + let root_dir = root_dir.to_path_buf(); + self.mock_fs_provider + .expect_read_dir_entries() + .with(eq(root_dir.clone())) + .once() + .returning(move |_| { + let mut entries: Vec = fs::read_dir(&root_dir) + .map_err(|e| FileError::ReadDirError(root_dir.clone(), e))? + .map(|entry_res| { + let entry = entry_res + .map_err(|e| FileError::ReadDirEntriesError(root_dir.clone(), e))?; + let entry_path = entry.path(); + let is_dir = entry + .file_type() + .map_err(|e| FileError::DirFileTypeError(entry_path.clone(), e))? + .is_dir(); + Ok(crate::providers::fs::DirEntryInfo { + path: entry_path, + is_dir, + }) + }) + .collect::, _>>()?; + entries.sort_by(|a, b| a.path.cmp(&b.path)); + Ok(entries) + }); + } + + fn expect_dir_cargo_toml_exists(&mut self, dir: &Path, exists: bool) { + let cargo_toml_path = dir.join("Cargo.toml"); + self.mock_fs_provider + .expect_exists() + .with(eq(cargo_toml_path)) + .once() + .returning(move |_| exists); + } + + fn expect_build_runner( + &mut self, + package_name: &str, + working_dir: &Path, + profile: Option, + target_arch: Option, + result: Result>, super::error::BuildTaskError>, + ) { + let expected_package_name = package_name.to_string(); + let expected_working_dir = working_dir.to_path_buf(); + let expected_profile = profile; + let expected_target_arch = target_arch; + let expected_locked = self.locked; + let expected_features = self.features.clone(); + self.mock_build_task_runner + .expect_run() + .withf(move |params: &BuildTaskParams<'_>, _command_exec| { + params.package_name == expected_package_name + && params.working_dir == expected_working_dir + && params.profile.copied() == expected_profile + && params.target_arch == expected_target_arch + && params.locked == expected_locked + && params.features.all_features == expected_features.all_features + && params.features.no_default_features == expected_features.no_default_features + && params.features.features == expected_features.features + }) + .once() + .return_once(move |_, _| { + result.map(|messages| { + Box::new(messages.into_iter()) + as Box>> + }) + }); + } + + fn expect_package_runner( + &mut self, + package_name: &str, + working_dir: &Path, + target_dir: &Path, + target_arch: CpuArchitecture, + sign_mode: SignMode, + sample_class: bool, + ) { + let expected_package_name = package_name.to_string(); + let expected_working_dir = working_dir.to_path_buf(); + let expected_target_dir = target_dir.to_path_buf(); + self.mock_package_task_runner + .expect_run() + .withf(move |params: &PackageTaskParams<'_>, _, _, _| { + params.package_name == expected_package_name + && params.working_dir == expected_working_dir + && params.target_dir == expected_target_dir + && *params.target_arch == target_arch + && params.sign_mode == sign_mode + && params.sample_class == sample_class + && matches!(params.driver_model, DriverConfig::Kmdf(_)) + }) + .once() + .return_once(|_, _, _, _| Ok(())); + } + fn expect_probe_target_arch_using_cargo_rustc( + &mut self, + working_dir: &Path, + detected_arch: CpuArchitecture, + ) { + let expected_working_dir = working_dir.to_path_buf(); + let arch_str = match detected_arch { + CpuArchitecture::Amd64 => "x86_64", + CpuArchitecture::Arm64 => "aarch64", + }; + let mut expected_args: Vec = vec!["rustc".to_string()]; + if self.locked { + expected_args.push("--locked".to_string()); + } + expected_args.extend(super::features_to_cargo_args(&self.features)); + expected_args.extend(["--".to_string(), "--print".to_string(), "cfg".to_string()]); self.mock_run_command .expect_run() .withf( move |command: &str, args: &[&str], _env_vars: &Option<&HashMap<&str, &str>>, - _working_dir: &Option<&Path>| - -> bool { - command == expected_infverif_command && args == expected_infverif_args + working_dir: &Option<&Path>| { + let expected_refs: Vec<&str> = + expected_args.iter().map(String::as_str).collect(); + command == "cargo" + && args == expected_refs.as_slice() + && working_dir.is_some_and(|dir| dir == expected_working_dir.as_path()) }, ) .once() - .returning(move |_, _, _, _| match override_output.clone() { - Some(output) => match output.status.code() { - Some(0) => Ok(Output { - status: ExitStatus::from_raw(0), - stdout: vec![], - stderr: vec![], - }), - _ => Err(CommandError::from_output("infverif", &[], &output)), - }, - None => Ok(Output { + .returning(move |_, _, _, _| { + Ok(Output { status: ExitStatus::default(), - stdout: vec![], + stdout: format!("target_arch=\"{arch_str}\"\n").into_bytes(), stderr: vec![], - }), + }) }); - self } +} - const fn mock_wdk_build_provider(&self) -> &WdkBuild { - &self.mock_wdk_build_provider - } +struct TestWorkspaceRoot { + root: PathBuf, +} - const fn mock_run_command(&self) -> &CommandExec { - &self.mock_run_command +impl TestWorkspaceRoot { + fn new(prefix: &str) -> Self { + static NEXT_ID: AtomicU64 = AtomicU64::new(0); + let unique_id = NEXT_ID.fetch_add(1, Ordering::Relaxed); + let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("target") + .join("test-artifacts") + .join(format!("{prefix}-{unique_id}")); + if root.exists() { + fs::remove_dir_all(&root).expect("failed to remove stale test workspace"); + } + fs::create_dir_all(&root).expect("failed to create test workspace"); + Self { root } } +} - const fn mock_fs_provider(&self) -> &Fs { - &self.mock_fs_provider +impl Drop for TestWorkspaceRoot { + fn drop(&mut self) { + if self.root.exists() { + let _ = fs::remove_dir_all(&self.root); + } } +} - const fn mock_metadata_provider(&self) -> &MetadataProvider { - &self.mock_metadata_provider - } +fn metadata_from_packages( + workspace_root_dir: &Path, + packages: Vec>, +) -> CargoMetadata { + metadata_from_packages_with_root(workspace_root_dir, None, packages) } fn invalid_driver_cargo_toml() -> String { + // A single cdylib driver package whose workspace-level `wdk.driver-model` is + // missing required fields (e.g. `kmdf-version-major`), so `Wdk::try_from` + // fails with `WdkMetadataDeserialization`. r#" { + "target_directory": "C:\\tmp\\sample-driver\\target", + "workspace_root": "C:\\tmp\\sample-driver", + "version": 1, "packages": [ { "name": "sample-driver", "version": "0.0.1", "id": "path+file:///C:/tmp/sample-driver#0.0.1", - "license": "MIT OR Apache-2.0", - "license_file": null, - "description": null, - "source": null, "dependencies": [], "targets": [ { - "kind": [ - "cdylib" - ], - "crate_types": [ - "cdylib" - ], + "kind": ["cdylib"], + "crate_types": ["cdylib"], "name": "sample_driver", "src_path": "C:\\tmp\\sample-driver\\src\\lib.rs", "edition": "2021", "doc": true, "doctest": false, "test": false - }, - { - "kind": [ - "custom-build" - ], - "crate_types": [ - "bin" - ], - "name": "build-script-build", - "src_path": "C:\\tmp\\sample-driver\\build.rs", - "edition": "2021", - "doc": false, - "doctest": false, - "test": false } ], - "features": { - "default": [], - "nightly": [ - "wdk/nightly", - "wdk-sys/nightly" - ] - }, + "features": {}, "manifest_path": "C:\\tmp\\sample-driver\\Cargo.toml", - "metadata": { - "wdk": {} - }, - "publish": [], "authors": [], "categories": [], "keywords": [], - "readme": null, - "repository": null, - "homepage": null, - "documentation": null, "edition": "2021", - "links": null, - "default_run": null, - "rust_version": null + "metadata": { + "wdk": {} + } } ], "workspace_members": [ "path+file:///C:/tmp/sample-driver#0.0.1" ], - "target_directory": "C:\\tmp\\sample-driver\\target", - "version": 1, - "workspace_root": "C:\\tmp\\sample-driver", "metadata": { "wdk": { "driver-model": { @@ -3094,10 +1325,41 @@ fn invalid_driver_cargo_toml() -> String { .to_string() } +fn metadata_from_packages_with_root( + workspace_root_dir: &Path, + root_metadata: Option, + packages: Vec>, +) -> CargoMetadata { + let mut package_json = Vec::new(); + let mut workspace_member_ids = Vec::new(); + for (package_name, package_dir, metadata, target_kind, crate_type, source_file) in packages { + let (workspace_member_id, package) = get_cargo_metadata_package_with_target( + &package_dir, + package_name, + DEFAULT_DRIVER_VERSION, + metadata, + target_kind, + crate_type, + source_file, + ); + workspace_member_ids.push(workspace_member_id); + package_json.push(package); + } + serde_json::from_str::(&get_cargo_metadata( + workspace_root_dir, + package_json, + &workspace_member_ids, + root_metadata, + )) + .expect("failed to parse cargo metadata") +} + #[derive(Clone)] struct TestMetadataPackage(String); + #[derive(Clone)] struct TestMetadataWorkspaceMemberId(String); + #[derive(Clone)] struct TestWdkMetadata(String); @@ -3140,25 +1402,19 @@ fn get_cargo_metadata( ) } -fn get_cargo_metadata_package( +fn get_cargo_metadata_package_with_target( root_dir: &Path, - default_package_name: &str, - default_package_version: &str, - metadata: Option<&TestWdkMetadata>, + package_name: &str, + package_version: &str, + metadata: Option, + target_kind: &str, + crate_type: &str, + source_file: &str, ) -> (TestMetadataWorkspaceMemberId, TestMetadataPackage) { let normalized_root = root_dir.to_string_lossy().replace('\\', "/"); let normalized_root = normalized_root.trim_start_matches("//?/"); - let package_id = - format!("path+file:///{normalized_root}#{default_package_name}@{default_package_version}"); - let (metadata_section, has_metadata) = metadata.map_or_else( - || (String::from("null"), false), - |metadata| ((metadata.0).clone(), true), - ); - let (target_kind, crate_type, source_file) = if has_metadata { - ("cdylib", "cdylib", "main.rs") - } else { - ("lib", "lib", "lib.rs") - }; + let package_id = format!("path+file:///{normalized_root}#{package_name}@{package_version}"); + let metadata_section = metadata.map_or_else(|| String::from("null"), |metadata| metadata.0); let manifest_path = root_dir .join("Cargo.toml") .to_string_lossy() @@ -3175,8 +1431,8 @@ fn get_cargo_metadata_package( TestMetadataPackage(format!( r#" {{ - "name": "{default_package_name}", - "version": "{default_package_version}", + "name": "{package_name}", + "version": "{package_version}", "id": "{package_id}", "dependencies": [], "targets": [ @@ -3187,7 +1443,7 @@ fn get_cargo_metadata_package( "crate_types": [ "{crate_type}" ], - "name": "{default_package_name}", + "name": "{package_name}", "src_path": "{source_path}", "edition": "2021", "doc": true, @@ -3277,13 +1533,11 @@ fn create_cargo_build_output_json_with_manifest( is_driver: bool, ) -> Output { let normalized_name = package_name.replace('-', "_"); - // Determine profile directory name let profile_dir = match profile { Some(Profile::Release) => "release", _ => "debug", }; - // For non-driver projects, use "lib" instead of "cdylib" to ensure BuildTask // returns DllNotFound let (kind, crate_types, file_ext) = if is_driver { @@ -3306,7 +1560,6 @@ fn create_cargo_build_output_json_with_manifest( let package_id = format!("path+file:///{package_dir}#{package_name}@{package_version}"); let manifest_path = strip_windows_extended_prefix(manifest_path); let artifact_path = strip_windows_extended_prefix(&artifact_path); - let pdb_path = Path::new(&artifact_path) .with_extension("pdb") .to_string_lossy() @@ -3361,11 +1614,14 @@ mod get_target_dir_from_output { fn unparsable_output_fails() { let workspace_root_dir = PathBuf::from(r"C:\tmp\sample-kmdf"); let wdk_metadata = super::get_cargo_metadata_wdk_metadata("KMDF", 1, 0); - let (_workspace_member, package_json) = super::get_cargo_metadata_package( + let (_workspace_member, package_json) = super::get_cargo_metadata_package_with_target( &workspace_root_dir, "sample-kmdf", "0.0.1", - Some(&wdk_metadata), + Some(wdk_metadata), + "cdylib", + "cdylib", + "main.rs", ); let package = serde_json::from_str::(&package_json.0) .expect("Failed to parse package json"); @@ -3389,11 +1645,14 @@ mod get_target_dir_from_output { fn no_matching_artifact_fails() { let workspace_root_dir = PathBuf::from(r"C:\tmp\sample-kmdf"); let wdk_metadata = super::get_cargo_metadata_wdk_metadata("KMDF", 1, 0); - let (_workspace_member, package_json) = super::get_cargo_metadata_package( + let (_workspace_member, package_json) = super::get_cargo_metadata_package_with_target( &workspace_root_dir, "sample-kmdf", "0.0.1", - Some(&wdk_metadata), + Some(wdk_metadata), + "cdylib", + "cdylib", + "main.rs", ); let package = serde_json::from_str::(&package_json.0) .expect("Failed to parse package json"); @@ -3424,11 +1683,14 @@ mod get_target_dir_from_output { fn matching_artifact_without_dll_fails() { let workspace_root_dir = PathBuf::from(r"C:\tmp\sample-kmdf"); let wdk_metadata = super::get_cargo_metadata_wdk_metadata("KMDF", 1, 0); - let (_workspace_member, package_json) = super::get_cargo_metadata_package( + let (_workspace_member, package_json) = super::get_cargo_metadata_package_with_target( &workspace_root_dir, "sample-kmdf", "0.0.1", - Some(&wdk_metadata), + Some(wdk_metadata), + "cdylib", + "cdylib", + "main.rs", ); let package = serde_json::from_str::(&package_json.0) .expect("Failed to parse package json"); @@ -3474,11 +1736,14 @@ mod get_target_dir_from_output { fn matching_dll_resolves_target_dir() { let workspace_root_dir = PathBuf::from(r"C:\tmp\sample-kmdf"); let wdk_metadata = super::get_cargo_metadata_wdk_metadata("KMDF", 1, 0); - let (_workspace_member, package_json) = super::get_cargo_metadata_package( + let (_workspace_member, package_json) = super::get_cargo_metadata_package_with_target( &workspace_root_dir, "sample-kmdf", "0.0.1", - Some(&wdk_metadata), + Some(wdk_metadata), + "cdylib", + "cdylib", + "main.rs", ); let package = serde_json::from_str::(&package_json.0) .expect("Failed to parse package json"); @@ -3525,20 +1790,24 @@ mod get_target_arch_from_cargo_rustc { process::{ExitStatus, Output}, }; - use clap_cargo::Features; use wdk_build::CpuArchitecture; - use super::{super::features_to_cargo_args, BuildActionError, TestBuildAction}; + use super::{BuildActionError, BuildActionHarness, SignMode, initialize_build_action}; fn run_parse_test(cfg_output: Vec, expected_arch: CpuArchitecture) { let cwd = PathBuf::from(r"C:\tmp"); - let mut test_build_action = TestBuildAction::new(cwd.clone(), None, None, false); - expect_cargo_rustc_print_cfg(&mut test_build_action, cwd.clone(), cfg_output); - - let build_action = - super::initialize_build_action(&cwd, None, None, true, false, &test_build_action) - .expect("Failed to init build action"); + let mut harness = BuildActionHarness::new( + cwd.clone(), + None, + None, + SignMode::Test { + verify_signature: false, + }, + false, + ); + expect_cargo_rustc_print_cfg(&mut harness, cwd.clone(), cfg_output); + let build_action = initialize_build_action(&mut harness); let arch = build_action .get_target_arch_from_cargo_rustc(&cwd) .expect("Expected target arch to be detected"); @@ -3567,43 +1836,24 @@ mod get_target_arch_from_cargo_rustc { } #[test] - fn parses_target_when_features_specified() { + fn unsupported_arch_returns_error() { let cwd = PathBuf::from(r"C:\tmp"); - let mut features = Features::default(); - features.no_default_features = true; - features.features = vec!["foo".to_string(), "bar".to_string()]; - let mut test_build_action = - TestBuildAction::new(cwd.clone(), None, None, false).with_features(features); - expect_cargo_rustc_print_cfg( - &mut test_build_action, + let mut harness = BuildActionHarness::new( cwd.clone(), - b"target_arch=\"x86_64\"\n".to_vec(), + None, + None, + SignMode::Test { + verify_signature: false, + }, + false, ); - - let build_action = - super::initialize_build_action(&cwd, None, None, true, false, &test_build_action) - .expect("Failed to init build action"); - - let arch = build_action - .get_target_arch_from_cargo_rustc(&cwd) - .expect("Expected target arch to be detected"); - assert_eq!(arch, CpuArchitecture::Amd64); - } - - #[test] - fn unsupported_arch_returns_error() { - let cwd = PathBuf::from(r"C:\tmp"); - let mut test_build_action = TestBuildAction::new(cwd.clone(), None, None, false); expect_cargo_rustc_print_cfg( - &mut test_build_action, + &mut harness, cwd.clone(), b"target_arch=\"mips\"\n".to_vec(), ); - let build_action = - super::initialize_build_action(&cwd, None, None, true, false, &test_build_action) - .expect("Failed to init build action"); - + let build_action = initialize_build_action(&mut harness); let err = build_action .get_target_arch_from_cargo_rustc(&cwd) .expect_err("Expected UnsupportedArchitecture error"); @@ -3613,17 +1863,22 @@ mod get_target_arch_from_cargo_rustc { #[test] fn missing_target_arch_returns_error() { let cwd = PathBuf::from(r"C:\tmp"); - let mut test_build_action = TestBuildAction::new(cwd.clone(), None, None, false); + let mut harness = BuildActionHarness::new( + cwd.clone(), + None, + None, + SignMode::Test { + verify_signature: false, + }, + false, + ); expect_cargo_rustc_print_cfg( - &mut test_build_action, + &mut harness, cwd.clone(), b"some_other_cfg=\"value\"\n".to_vec(), ); - let build_action = - super::initialize_build_action(&cwd, None, None, true, false, &test_build_action) - .expect("Failed to init build action"); - + let build_action = initialize_build_action(&mut harness); let err = build_action .get_target_arch_from_cargo_rustc(&cwd) .expect_err("Expected CannotDetectTargetArch error"); @@ -3633,13 +1888,18 @@ mod get_target_arch_from_cargo_rustc { #[test] fn invalid_utf8_returns_error() { let cwd = PathBuf::from(r"C:\tmp"); - let mut test_build_action = TestBuildAction::new(cwd.clone(), None, None, false); - expect_cargo_rustc_print_cfg(&mut test_build_action, cwd.clone(), vec![0xFF, 0xFE]); - - let build_action = - super::initialize_build_action(&cwd, None, None, true, false, &test_build_action) - .expect("Failed to init build action"); + let mut harness = BuildActionHarness::new( + cwd.clone(), + None, + None, + SignMode::Test { + verify_signature: false, + }, + false, + ); + expect_cargo_rustc_print_cfg(&mut harness, cwd.clone(), vec![0xFF, 0xFE]); + let build_action = initialize_build_action(&mut harness); let err = build_action .get_target_arch_from_cargo_rustc(&cwd) .expect_err("Expected CannotDetectTargetArch error"); @@ -3647,27 +1907,28 @@ mod get_target_arch_from_cargo_rustc { } fn expect_cargo_rustc_print_cfg( - test_build_action: &mut TestBuildAction, + harness: &mut BuildActionHarness, cwd: PathBuf, stdout: Vec, ) { let mut expected_args: Vec = vec!["rustc".to_string()]; - if test_build_action.locked { + if harness.locked { expected_args.push("--locked".to_string()); } - expected_args.extend(features_to_cargo_args(&test_build_action.features)); - expected_args.extend(["--", "--print", "cfg"].map(String::from)); - test_build_action + expected_args.extend(super::super::features_to_cargo_args(&harness.features)); + expected_args.extend(["--".to_string(), "--print".to_string(), "cfg".to_string()]); + harness .mock_run_command .expect_run() .withf( move |command: &str, args: &[&str], _env_vars: &Option<&HashMap<&str, &str>>, - working_dir: &Option<&Path>| - -> bool { + working_dir: &Option<&Path>| { + let expected_refs: Vec<&str> = + expected_args.iter().map(String::as_str).collect(); command == "cargo" - && args == expected_args + && args == expected_refs.as_slice() && matches!(working_dir, Some(dir) if *dir == cwd.as_path()) }, ) @@ -3680,4 +1941,33 @@ mod get_target_arch_from_cargo_rustc { }) }); } + + #[test] + fn probe_forwards_features() { + let cwd = PathBuf::from(r"C:\tmp"); + let mut harness = BuildActionHarness::new( + cwd.clone(), + None, + None, + SignMode::Test { + verify_signature: false, + }, + false, + ) + .with_features(super::non_default_features()); + // `expect_cargo_rustc_print_cfg` derives the expected args from + // `harness.features`, so this only matches if the selected features are + // forwarded into the `cargo rustc` probe invocation. + expect_cargo_rustc_print_cfg( + &mut harness, + cwd.clone(), + b"target_arch=\"x86_64\"\n".to_vec(), + ); + + let build_action = initialize_build_action(&mut harness); + let arch = build_action + .get_target_arch_from_cargo_rustc(&cwd) + .expect("Expected target arch to be detected"); + assert_eq!(arch, CpuArchitecture::Amd64); + } }