#![allow(dead_code)] extern crate assert_fs; extern crate env_logger; extern crate log; extern crate sxd_document; extern crate sxd_xpath; use assert_fs::prelude::*; use self::sxd_document::parser; use self::sxd_xpath::{Context, Factory}; use assert_fs::TempDir; use env_logger::fmt::Color as LogColor; use env_logger::Builder; use log::{Level, LevelFilter}; use std::env; use std::fs; use std::fs::{File, OpenOptions}; use std::io::{Read, Write}; use std::path::Path; use std::process::Command; use toml::{Table, Value}; pub const TARGET_NAME: &str = "target"; // Cannot use dashes. WiX Toolset only allows A-Z, a-z, digits, underscores (_), or periods (.) // for attribute IDs. pub const PACKAGE_NAME: &str = "cargowixtest"; pub const NO_CAPTURE_VAR_NAME: &str = "CARGO_WIX_TEST_NO_CAPTURE"; pub const PERSIST_VAR_NAME: &str = "CARGO_WIX_TEST_PERSIST"; pub const MISC_NAME: &str = "misc"; pub const SUBPACKAGE1_NAME: &str = "subproject1"; pub const SUBPACKAGE2_NAME: &str = "subproject2"; fn create_test_package_at_path(path: &Path, package_name: &str) { let cargo_init_status = Command::new("cargo") .arg("init") .arg("--bin") .arg("--quiet") .arg("--vcs") .arg("none") .arg("--name") .arg(package_name) .arg(path) .status() .expect("Creation of test Cargo package"); assert!(cargo_init_status.success()); let cargo_path = path.join("Cargo.toml"); let mut toml = { let mut cargo_toml_handle = File::open(&cargo_path).unwrap(); let mut cargo_toml_content = String::new(); cargo_toml_handle .read_to_string(&mut cargo_toml_content) .unwrap(); cargo_toml_content.parse::().unwrap() }; { toml.get_mut("package") .map(|p| { match p { Value::Table(ref mut t) => t.insert( String::from("authors"), Value::Array(vec![Value::from("author1"), Value::from("author2")]), ), _ => panic!("The 'package' section is not a table"), }; Some(p) }) .expect("A package section for the Cargo.toml"); let toml_string = toml.to_string(); let mut cargo_toml_handle = File::create(cargo_path).unwrap(); cargo_toml_handle.write_all(toml_string.as_bytes()).unwrap(); } } pub fn add_license_to_package(path: &Path, license: &str) { let cargo_path = path.join("Cargo.toml"); let mut toml = { let mut cargo_toml_handle = File::open(&cargo_path).unwrap(); let mut cargo_toml_content = String::new(); cargo_toml_handle .read_to_string(&mut cargo_toml_content) .unwrap(); cargo_toml_content.parse::
().unwrap() }; { toml.get_mut("package") .map(|p| { match p { Value::Table(ref mut t) => { t.insert(String::from("license"), Value::from(license)) } _ => panic!("The 'package' section is not a table"), }; Some(p) }) .expect("A package section for the Cargo.toml"); let toml_string = toml.to_string(); let mut cargo_toml_handle = File::create(cargo_path).unwrap(); cargo_toml_handle.write_all(toml_string.as_bytes()).unwrap(); } } /// Create a new cargo project/package for a binary project in a temporary /// directory. /// /// This provides a unique, isolated Cargo project/package for testing. A /// temporary directory is created. Then, a cargo project is initialized within /// the temporary directory. The package/project is initialized without any /// version control system (vcs). The command that is ultimately executed to /// create the cargo project in the temporary directory is: /// /// ``` /// > cargo init --bin --quiet --vcs none --name cargowixtest "C:\Users\\AppData\Local\Temp\cargo_wix_text_######" /// ``` /// /// where `` is replaced with the current logged in user for the /// Windows Operating System (OS) and `######` is a hash ID that guarantees the /// folder is unique. /// /// # Panics /// /// This will panic if a temporary directory fails to be created or if cargo /// fails to create the project/package. pub fn create_test_package() -> TempDir { let temp_dir = TempDir::new().unwrap(); create_test_package_at_path(temp_dir.path(), PACKAGE_NAME); temp_dir.into_persistent_if(env::var(PERSIST_VAR_NAME).is_ok()) } /// Create a new cargo project/package for a project with multiple binaries in a /// temporary directory. See the [create_test_package] function for more /// information. /// /// Following creation of the project, the manifest file (Cargo.toml) is /// modified to include multiple `[[bin]]` sections for multiple binaries. The /// original `main.rs` file that is created for the first binary is copied for /// each of the other binaries. A total of three (3) binaries will be created /// and added to the manifest file. /// /// [create_test_package]: fn.create_test_package.html /// /// # Panics /// /// This will panic if a temporary directory fails to be created or if cargo /// fails to create the project/package. /// /// It will also panic if it cannot modify the manifest file (Cargo.toml) or the /// project layout for multiple binaries. pub fn create_test_package_multiple_binaries() -> TempDir { let package = create_test_package(); let package_manifest = package.child("Cargo.toml"); let package_src = package.child("src"); { let mut cargo_toml_handle = OpenOptions::new() .read(true) .append(true) .open(package_manifest.path()) .unwrap(); cargo_toml_handle .write_all( r#"[[bin]] name = "main1" path = "src/main1.rs" [[bin]] name = "main2" path = "src/main2.rs" [[bin]] name = "main3" path = "src/main3.rs" "# .as_bytes(), ) .unwrap(); } let package_original_main = package_src.child("main.rs"); fs::copy( package_original_main.path(), package_src.child("main1.rs").path(), ) .unwrap(); fs::copy( package_original_main.path(), package_src.child("main2.rs").path(), ) .unwrap(); fs::copy( package_original_main.path(), package_src.child("main3.rs").path(), ) .unwrap(); fs::remove_file(package_original_main.path()).unwrap(); package } /// Create a new cargo project/package for a project with a /// `[package.metadata.wix]` section. /// /// Following creation of the project, the manifest file (Cargo.toml) is /// modified to include a `[package.metadata.wix]` section. /// /// # Panics /// /// This will panic if a temporary directory fails to be created or if cargo /// fails to create the project/package. /// /// It will also panic if it cannot modify the manifest file (Cargo.toml) or the /// project layout for multiple binaries. pub fn create_test_package_metadata() -> TempDir { let package = create_test_package(); let package_manifest = package.child("Cargo.toml"); let mut cargo_toml_handle = OpenOptions::new() .read(true) .append(true) .open(package_manifest.path()) .unwrap(); cargo_toml_handle .write_all( r#"[package.metadata.wix] name = "Metadata" version = "2.1.0" compiler-args = ["-nologo", "-wx", "-arch", "x64"] linker-args = ["-nologo"] "# .as_bytes(), ) .unwrap(); package } /// Create a new cargo project/package for a project with a /// `[profile.{profile}]` section. /// /// Following creation of the project, the manifest file (Cargo.toml) is /// modified to include a `[profile.{profile}]` section. /// /// # Panics /// /// This will panic if a temporary directory fails to be created or if cargo /// fails to create the project/package. /// /// It will also panic if it cannot modify the manifest file (Cargo.toml) or the /// project layout for multiple binaries. pub fn create_test_package_profile(profile: &str) -> TempDir { let package = create_test_package(); let package_manifest = package.child("Cargo.toml"); let mut cargo_toml_handle = OpenOptions::new() .read(true) .append(true) .open(package_manifest.path()) .unwrap(); cargo_toml_handle .write_all( format!( r#" [profile.{profile}] inherits = "release" lto = "thin" "# ) .as_bytes(), ) .unwrap(); package } /// Create a new cargo project/package for a project with multiple WXS files. /// /// # Panics /// /// This will panic if a temporary directory fails to be created or if cargo /// fails to create the project/package. /// /// It will also panic if it cannot modify the manifest file (Cargo.toml) or the /// project layout for multiple binaries. /// /// This function will panic if the `wix` sub-folder could not be created. pub fn create_test_package_multiple_wxs_sources() -> TempDir { let one_wxs = include_str!("one.wxs"); let two_wxs = include_str!("two.wxs"); let three_wxs = include_str!("three.wxs"); let package = create_test_package(); let mut misc_dir = package.path().join(MISC_NAME); fs::create_dir(&misc_dir).unwrap(); misc_dir.push("one.wxs"); let mut one_wxs_handle = File::create(&misc_dir).unwrap(); one_wxs_handle.write_all(one_wxs.as_bytes()).unwrap(); misc_dir.pop(); misc_dir.push("two.wxs"); let mut two_wxs_handle = File::create(&misc_dir).unwrap(); two_wxs_handle.write_all(two_wxs.as_bytes()).unwrap(); misc_dir.pop(); misc_dir.push("three.wxs"); let mut three_wxs_handle = File::create(&misc_dir).unwrap(); three_wxs_handle.write_all(three_wxs.as_bytes()).unwrap(); package } /// Create a new cargo workspace with multiple sub-projects in a /// temporary directory. See the [create_test_package] function for more /// information. pub fn create_test_workspace() -> TempDir { let temp_dir = TempDir::new().unwrap(); fs::create_dir(temp_dir.path().join(SUBPACKAGE1_NAME)).unwrap(); fs::create_dir(temp_dir.path().join(SUBPACKAGE2_NAME)).unwrap(); create_test_package_at_path(&temp_dir.path().join(SUBPACKAGE1_NAME), SUBPACKAGE1_NAME); create_test_package_at_path(&temp_dir.path().join(SUBPACKAGE2_NAME), SUBPACKAGE2_NAME); fs::write( temp_dir.path().join("Cargo.toml"), format!( r#"[workspace] members = [{SUBPACKAGE1_NAME:?}, {SUBPACKAGE2_NAME:?}]"# ), ) .unwrap(); temp_dir.into_persistent_if(env::var(PERSIST_VAR_NAME).is_ok()) } /// Evaluates an XPath expression for a WiX Source file. /// /// This registers the WiX XML namespace with the `wix` prefix. So, XPath /// expressions should use `/wix:Wix/` as the start and prefix all element/node /// names with the `wix:` prefix. Note, attributes should _not_ have the `wix:` /// prefix. /// /// All values are currently returned as strings. pub fn evaluate_xpath(wxs: &Path, xpath: &str) -> String { let mut wxs = File::open(wxs).expect("Open Wix Source file"); let mut wxs_content = String::new(); wxs.read_to_string(&mut wxs_content) .expect("Read WiX Source file"); let wxs_package = parser::parse(&wxs_content).expect("Parsing WiX Source file"); let wxs_document = wxs_package.as_document(); let mut context = Context::new(); context.set_namespace("wix", "http://schemas.microsoft.com/wix/2006/wi"); let xpath = Factory::new().build(xpath).unwrap().unwrap(); xpath .evaluate(&context, wxs_document.root()) .unwrap() .string() } /// Initializes the logging for the integration tests. /// /// When a test fails, it is useful to re-run the tests with logging statements /// enabled to debug the failed test. This initializes the logging based on the /// `CARGO_WIX_TEST_LOG` environment variable, which takes an integer as a /// value. A `0` value, or not setting the environment variable, turns off /// logging. Each increment of the integer value will increase the number of /// statements that are logged up to 5 (Trace). /// /// If the `CARGO_WIX_TEST_LOG` value is greater than zero (0), then log /// statements will be emitted to the terminal/console regardless of the /// `--nocapture` option for cargo tests. In other words, log statements are /// *not* captured by cargo's testing framework with this implementation. Thus, /// it is recommended to *not* activate logging if running all of the tests. /// Logging should be done for isolated tests. Not capturing the log statements /// by cargo's test framework keeps the formatting and coloring. There might be /// a decrease in performance as well. /// /// Log statements are formatted the same as the verbosity format for the CLI. /// /// # Examples /// /// Enabling logging for tests in Powershell requires two commands and an /// optional third command to undo: /// /// ```powershell /// PS C:\Path\to\Cargo\Wix> $env:CARGO_WIX_TEST_LOG=5 /// PS C:\Path\to\Cargo\Wix> cargo test /// PS C:\Path\to\Cargo\Wix> Remove-Item Env:\CARGO_WIX_TEST_LOG /// ``` /// /// This can be collapsed into a single line as: /// /// ```powershell /// PS C:\Path\to\Cargo\Wix> $env:CARGO_WIX_TEST_LOG=5; cargo test; Remove-Item Env:\CARGO_WIX_TEST_LOG /// ``` /// /// But again, logging should only be activated for isolated tests to avoid /// relatively large number of statements being written: /// /// ```powershell /// PS C:\Path\to\Cargo\Wix> $env:CARGO_WIX_TEST_LOG=5; cargo test ; Remove-Item Env:\CARGO_WIX_TEST_LOG /// ``` /// /// where `` is the name of a test, a.k.a. function name with the `#[test]` attribute. pub fn init_logging() { let log_level = match std::env::var("CARGO_WIX_TEST_LOG") { Ok(level) => level .parse::() .expect("Integer for CARGO_WIX_TEST_LOG value"), Err(_) => 0, }; let mut builder = Builder::new(); builder .format(|buf, record| { // This implementation for a format is copied from the default format implemented for the // `env_logger` crate but modified to use a colon, `:`, to separate the level from the // message and change the colors to match the previous colors used by the `loggerv` crate. let mut level_style = buf.style(); let level = record.level(); match level { // Light Gray, or just Gray, is not a supported color for non-ANSI enabled Windows // consoles, so TRACE and DEBUG statements are differentiated by boldness but use the // same white color. Level::Trace => level_style.set_color(LogColor::White).set_bold(false), Level::Debug => level_style.set_color(LogColor::White).set_bold(true), Level::Info => level_style.set_color(LogColor::Green).set_bold(true), Level::Warn => level_style.set_color(LogColor::Yellow).set_bold(true), Level::Error => level_style.set_color(LogColor::Red).set_bold(true), }; let write_level = write!(buf, "{:>5}: ", level_style.value(level)); let write_args = writeln!(buf, "{}", record.args()); write_level.and(write_args) }) .filter( Some("wix"), match log_level { 0 => LevelFilter::Off, 1 => LevelFilter::Error, 2 => LevelFilter::Warn, 3 => LevelFilter::Info, 4 => LevelFilter::Debug, _ => LevelFilter::Trace, }, ) .try_init() .ok(); }