-
Notifications
You must be signed in to change notification settings - Fork 26
/
Copy pathmod.rs
444 lines (416 loc) · 15.5 KB
/
mod.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
#![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::<Table>().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::<Table>().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\<username>\AppData\Local\Temp\cargo_wix_text_######"
/// ```
///
/// where `<username>` 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 <TEST_NAME>; Remove-Item Env:\CARGO_WIX_TEST_LOG
/// ```
///
/// where `<TEST_NAME>` 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::<i32>()
.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();
}