use std::io::{BufRead, Write};
use std::path::Path;
use std::{fs, io::BufReader};
use assert_cmd::Command;
use predicates::prelude::*;
use tempfile::{NamedTempFile, TempDir};
fn cmd() -> Command {
Command::cargo_bin("boreal").unwrap()
}
fn test_file(contents: &[u8]) -> NamedTempFile {
let mut file = NamedTempFile::new().unwrap();
file.write_all(contents).unwrap();
file
}
#[test]
fn test_no_arguments() {
cmd().assert().failure();
}
#[test]
fn test_invalid_path() {
cmd()
.arg("do_not_exist")
.arg("input")
.assert()
.stdout("")
.stderr(predicate::str::contains("IO error"))
.failure();
let rule_file = test_file(b"");
cmd()
.arg(rule_file.path())
.arg("bad_input")
.assert()
.stdout("")
.stderr(predicate::str::contains("Cannot scan bad_input"))
.failure();
}
#[test]
fn test_scan_file() {
let rule_file = test_file(
br#"
rule my_rule {
strings:
$a = "abc"
condition:
any of them
}"#,
);
let input = test_file(b"aaa");
cmd()
.arg(rule_file.path())
.arg(input.path())
.assert()
.stdout("")
.stderr("")
.success();
let input = test_file(b"zeabce");
cmd()
.arg(rule_file.path())
.arg(input.path())
.assert()
.stdout(format!("my_rule {}\n", input.path().display()))
.stderr("")
.success();
}
#[test]
#[cfg_attr(unix, ignore)]
fn test_scan_process() {
let rule_file = test_file(
br#"
rule process_scan {
strings:
$a = "PAYLOAD_ON_STACK"
condition:
$a
}"#,
);
let proc = BinHelper::run("stack");
let pid = proc.pid();
cmd()
.arg(rule_file.path())
.arg(pid.to_string())
.assert()
.stdout(format!("process_scan {}\n", pid))
.stderr("")
.success();
}
#[test]
#[cfg(target_os = "linux")]
fn test_scan_process_not_found() {
let rule_file = test_file(b"rule process_scan { condition: true }");
let mut pid = 999_999_999;
while Path::new("proc").join(pid.to_string()).exists() {
pid += 1;
}
cmd()
.arg(rule_file.path())
.arg(pid.to_string())
.assert()
.stdout("")
.stderr(format!("Cannot scan {}: unknown process\n", pid))
.failure();
}
#[test]
fn test_scan_file_with_process_name() {
let rule_file = test_file(
br#"
rule is_file {
strings:
$a = "buzo"
condition:
any of them
}"#,
);
let temp = TempDir::new().unwrap();
let file = temp.path().join("1");
fs::write(file, "gabuzomeu").unwrap();
cmd()
.current_dir(temp.path())
.arg(rule_file.path())
.arg("1")
.assert()
.stdout("is_file 1\n")
.stderr("")
.success();
}
#[test]
fn test_rule_error() {
let rule_file = test_file(
br#"rule a {
strings: $a = /[z-a]/
condition: $a
}"#,
);
let input = test_file(b"");
cmd()
.arg(rule_file.path())
.arg(input.path())
.assert()
.stdout("")
.stderr(
predicate::str::contains("error")
.and(predicate::str::contains("invalid regex class range")),
)
.failure();
}
#[test]
fn test_rule_warning() {
let rule_file = test_file(
br#"rule rule_with_warning {
condition:
"a"
}
"#,
);
let input = test_file(b"");
let path = input.path().display();
cmd()
.arg(rule_file.path())
.arg(input.path())
.assert()
.stdout(format!("rule_with_warning {path}\n"))
.stderr(
predicate::str::contains("warning").and(predicate::str::contains(
"implicit cast from a bytes value to a boolean",
)),
)
.success();
cmd()
.arg("--fail-on-warnings")
.arg(rule_file.path())
.arg(input.path())
.assert()
.stdout("")
.stderr(
predicate::str::contains("warning").and(predicate::str::contains(
"implicit cast from a bytes value to a boolean",
)),
)
.failure();
cmd()
.arg("-w")
.arg(rule_file.path())
.arg(input.path())
.assert()
.stdout(format!("rule_with_warning {path}\n"))
.stderr("")
.success();
}
#[test]
fn test_rule_include() {
let temp = TempDir::new().unwrap();
let rule_b = temp.path().join("b.yar");
fs::write(
rule_b,
r#"
rule included {
strings:
$ = "abc"
condition:
any of them
}
"#,
)
.unwrap();
let rule_a = temp.path().join("a.yar");
fs::write(
&rule_a,
r#"
include "b.yar"
rule includer {
strings:
$ = "xyz"
condition:
included and any of them
}
"#,
)
.unwrap();
let input = test_file(b"abc");
cmd()
.arg(&rule_a)
.arg(input.path())
.assert()
.stdout(format!("included {}\n", input.path().display()))
.success();
let input = test_file(b"xyz abc");
cmd()
.arg(rule_a)
.arg(input.path())
.assert()
.stdout(
predicate::str::contains(format!("included {}", input.path().display())).and(
predicate::str::contains(format!("includer {}", input.path().display())),
),
)
.success();
}
#[test]
fn test_rule_include_error() {
let temp = TempDir::new().unwrap();
let rule_b = temp.path().join("b.yar");
fs::write(
&rule_b,
r#"
rule included {
strings:
$ = /[z-a]/
condition:
any of them
}
"#,
)
.unwrap();
let rule_a = temp.path().join("a.yar");
fs::write(&rule_a, r#"include "b.yar""#).unwrap();
let input = test_file(b"");
cmd()
.arg(rule_a)
.arg(input.path())
.assert()
.stdout("")
.stderr(
predicate::str::contains(rule_b.canonicalize().unwrap().display().to_string())
.and(
predicate::str::contains("z-a"),
)
.and(
predicate::str::contains("invalid regex class range"),
),
)
.failure();
}
#[test]
#[cfg(unix)]
fn test_rule_dir() {
use std::os::unix::fs::symlink;
let rule_file = test_file(b"rule a { condition: true }");
let temp = TempDir::new().unwrap();
let temp2 = TempDir::new().unwrap();
let file_a = temp.path().join("a");
fs::write(&file_a, "").unwrap();
let file_b = temp.path().join("b");
fs::write(&file_b, "").unwrap();
let subdir = temp.path().join("subdir");
fs::create_dir(&subdir).unwrap();
let file_c = subdir.join("c");
fs::write(&file_c, "").unwrap();
let file2_d = temp2.path().join("d");
fs::write(&file2_d, "").unwrap();
let file2_e = temp2.path().join("e");
fs::write(&file2_e, "").unwrap();
let file_d = temp.path().join("d");
symlink(&file2_d, &file_d).unwrap();
let file_e = subdir.join("e");
symlink(&file2_e, &file_e).unwrap();
let match_str = |input: &Path| predicate::str::contains(format!("a {}", input.display()));
cmd()
.args(["--threads", "20"])
.arg(rule_file.path())
.arg(temp.path())
.assert()
.stdout(
match_str(&file_a)
.and(match_str(&file_b))
.and(match_str(&file_c).not())
.and(match_str(&file_d))
.and(match_str(&file_e).not()),
)
.stderr("")
.success();
cmd()
.arg("-N")
.arg(rule_file.path())
.arg(temp.path())
.assert()
.stdout(
match_str(&file_a)
.and(match_str(&file_b))
.and(match_str(&file_c).not())
.and(match_str(&file_d).not())
.and(match_str(&file_e).not()),
)
.stderr("")
.success();
cmd()
.arg("-r")
.arg(rule_file.path())
.arg(temp.path())
.assert()
.stdout(
match_str(&file_a)
.and(match_str(&file_b))
.and(match_str(&file_c))
.and(match_str(&file_d))
.and(match_str(&file_e)),
)
.stderr("")
.success();
cmd()
.arg("--recursive")
.arg("--no-follow-symlinks")
.args(["-p", "2"])
.arg(rule_file.path())
.arg(temp.path())
.assert()
.stdout(
match_str(&file_a)
.and(match_str(&file_b))
.and(match_str(&file_c))
.and(match_str(&file_d).not())
.and(match_str(&file_e).not()),
)
.stderr("")
.success();
}
#[test]
fn test_skip_larger() {
let rule_file = test_file(b"rule a { condition: true }");
let temp = TempDir::new().unwrap();
let file_a = temp.path().join("a");
fs::write(&file_a, "").unwrap();
let file_b = temp.path().join("b");
fs::write(&file_b, [0; 100]).unwrap();
let file_c = temp.path().join("c");
fs::write(&file_c, [0; 1024]).unwrap();
let match_file = |input: &Path| predicate::str::contains(format!("a {}", input.display()));
let skip_file =
|input: &Path| predicate::str::contains(format!("skipping {}", input.display()));
cmd()
.arg(rule_file.path())
.arg(temp.path())
.assert()
.stdout(
match_file(&file_a)
.and(match_file(&file_b))
.and(match_file(&file_c)),
)
.stderr("")
.success();
cmd()
.args(["--skip-larger", "512"])
.arg(rule_file.path())
.arg(temp.path())
.assert()
.stdout(
match_file(&file_a)
.and(match_file(&file_b))
.and(match_file(&file_c).not()),
)
.stderr(
skip_file(&file_a)
.not()
.and(skip_file(&file_b).not())
.and(skip_file(&file_c)),
)
.success();
cmd()
.args(["-z", "10"])
.arg(rule_file.path())
.arg(temp.path())
.assert()
.stdout(
match_file(&file_a)
.and(match_file(&file_b).not())
.and(match_file(&file_c).not()),
)
.stderr(
skip_file(&file_a)
.not()
.and(skip_file(&file_b))
.and(skip_file(&file_c)),
)
.success();
}
#[test]
fn test_print_module_data() {
let rule_file = test_file(
br#"
import "pe"
rule a {
condition: false
}
"#,
);
let input = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("..")
.join("boreal")
.join("tests")
.join("assets")
.join("libyara")
.join("data")
.join("mtxex.dll");
cmd()
.arg("-D")
.arg(rule_file.path())
.arg(input)
.assert()
.stdout(
predicate::str::starts_with("pe\n")
.and(predicate::str::contains("base_of_code = 4096 (0x1000)"))
.and(predicate::str::contains("base_of_data[undef]"))
.and(predicate::str::contains(
r#"
data_directories
[0]
size = 220 (0xdc)
"#,
))
.and(predicate::str::contains("delayed_import_details = []"))
.and(predicate::str::contains(r#"dll_name = "mtxex.dll""#))
.and(predicate::str::contains(
"[\"ProductName\"] = { 4d6963726f736f6674ae2057696e646f7773ae204f7065\
726174696e672053797374656d }",
))
.and(predicate::str::contains(
r#"
image_version
major = 10 (0xa)
minor = 0 (0x0)
"#,
))
.and(predicate::str::contains(
r#"
version_info
["CompanyName"] = "Microsoft Corporation"
"#,
)),
)
.stderr("")
.success();
}
#[test]
fn test_print_string_stats() {
let rule_file = test_file(
br#"
rule a {
strings:
$a = "ab<de>g"
$b = { 01 ( FE | EF ) }
$c = /foo\d??barbaz/ fullword
$d = /.{10}/ fullword
condition:
any of them
}
"#,
);
let stats = r#"
$a = "ab<de>g"
literals: ["ab<de>g"]
atoms: ["<de>"]
atoms quality: 84
algo: Literals
$b = { 01 ( FE | EF ) }
literals: [{ 01fe }, { 01ef }]
atoms: [{ 01fe }, { 01ef }]
atoms quality: 44
algo: Literals
$c = /foo\d??barbaz/ fullword
literals: ["barbaz"]
atoms: ["rbaz"]
atoms quality: 80
algo: Atomized { NonGreedy { reverse: Dfa, forward: none } }
$d = /.{10}/ fullword
literals: []
atoms: []
atoms quality: 0
algo: Raw
"#;
let input = test_file(b"");
cmd()
.arg("--string-stats")
.arg(rule_file.path())
.arg(input.path())
.assert()
.stdout(format!(
"default:a (from {}){}",
rule_file.path().display(),
stats
))
.stderr("")
.success();
}
#[test]
fn test_print_scan_stats() {
let rule_file = test_file(
br#"
rule a {
strings:
$a = "abc"
condition:
any of them
}
"#,
);
let input = test_file(b"abc");
cmd()
.arg("--scan-stats")
.arg(rule_file.path())
.arg(input.path())
.assert()
.stdout(
predicate::str::is_match(
r"Evaluation \{
no_scan_eval_duration: .*,
ac_duration: .*,
fetch_memory_duration: .*,
ac_confirm_duration: .*,
nb_ac_matches: .*,
rules_eval_duration: .*,
raw_regexes_eval_duration: .*,
memory_scanned_size: .*,
nb_memory_chunks: .*,
\}
",
)
.unwrap(),
)
.stderr("")
.success();
}
#[test]
#[cfg(unix)]
fn test_input_cannot_read() {
use std::os::unix::fs::{OpenOptionsExt, PermissionsExt};
let rule_file = test_file(b"rule bee { condition: true }");
let temp = TempDir::new().unwrap();
let child = temp.path().join("child");
let _file = fs::OpenOptions::new()
.create(true)
.truncate(true)
.write(true)
.mode(0o000)
.open(&child)
.unwrap();
let subdir = temp.path().join("subdir");
fs::create_dir(&subdir).unwrap();
fs::set_permissions(&subdir, fs::Permissions::from_mode(0o000)).unwrap();
cmd()
.arg("-r")
.arg(rule_file.path())
.arg(temp.path())
.assert()
.stdout("")
.stderr(
predicate::str::contains(format!("Cannot scan file {}", child.display())).and(
predicate::str::contains(format!("IO error for operation on {}", subdir.display())),
),
)
.success();
}
#[test]
fn test_module_names() {
cmd()
.arg("-M")
.assert()
.stdout(
predicate::str::contains("math\n")
.and(predicate::str::contains("string\n"))
.and(predicate::str::contains("time\n")),
)
.stderr("")
.success();
}
#[test]
#[cfg(feature = "memmap")]
fn test_no_mmap() {
let rule_file = test_file(
br#"
rule first {
strings:
$a = "abc"
condition:
any of them
}
rule second {
strings:
$a = "xyz"
condition:
any of them
}"#,
);
let input = test_file(b"xyabcz");
cmd()
.arg("--no-mmap")
.arg(rule_file.path())
.arg(input.path())
.assert()
.stdout(format!("first {}\n", input.path().display()))
.stderr("")
.success();
}
#[test]
fn test_console_log() {
let rule_file = test_file(
br#"
import "console"
rule logger {
condition:
console.log("this is ", "a log")
}"#,
);
let input = test_file(b"");
let path = input.path().display();
cmd()
.arg(rule_file.path())
.arg(input.path())
.assert()
.stdout(format!("this is a log\nlogger {path}\n"))
.stderr("")
.success();
cmd()
.arg("-q")
.arg(rule_file.path())
.arg(input.path())
.assert()
.stdout(format!("logger {path}\n"))
.stderr("")
.success();
}
#[test]
fn test_invalid_fragmented_scan_mode() {
cmd()
.arg("--fragmented-scan-mode")
.arg("bad_value")
.arg("rules.yar")
.arg("input")
.assert()
.stdout("")
.stderr(predicate::str::contains(
"invalid value 'bad_value' for \
'--fragmented-scan-mode <legacy|fast|singlepass>\': invalid value",
))
.failure();
}
#[test]
fn test_tags() {
let rule_file = test_file(
br#"
rule notag {
condition:
true
}
rule tag1: first {
condition:
true
}
rule tag3: first second third {
condition:
true
}
"#,
);
let input = test_file(b"");
let path = input.path().display();
cmd()
.arg("-g")
.arg(rule_file.path())
.arg(input.path())
.assert()
.stdout(format!(
"notag [] {path}\n\
tag1 [first] {path}\n\
tag3 [first,second,third] {path}\n"
))
.stderr("")
.success();
cmd()
.arg("-t")
.arg("first")
.arg(rule_file.path())
.arg(input.path())
.assert()
.stdout(format!("tag1 {path}\ntag3 {path}\n"))
.stderr("")
.success();
cmd()
.arg("--tag=third")
.arg(rule_file.path())
.arg(input.path())
.assert()
.stdout(format!("tag3 {path}\n"))
.stderr("")
.success();
cmd()
.arg("-t")
.arg("")
.arg(rule_file.path())
.arg(input.path())
.assert()
.stdout("")
.stderr("")
.success();
}
#[test]
fn test_identifier() {
let rule_file = test_file(
br#"
rule first { condition: true }
rule second { condition: true }
"#,
);
let input = test_file(b"");
let path = input.path().display();
cmd()
.arg("-i")
.arg("first")
.arg(rule_file.path())
.arg(input.path())
.assert()
.stdout(format!("first {path}\n"))
.stderr("")
.success();
cmd()
.arg("--identifier=second")
.arg(rule_file.path())
.arg(input.path())
.assert()
.stdout(format!("second {path}\n"))
.stderr("")
.success();
cmd()
.arg("--identifier=third")
.arg(rule_file.path())
.arg(input.path())
.assert()
.stdout("")
.stderr("")
.success();
}
#[test]
fn test_print_meta() {
let rule_file = test_file(
br#"
rule first: tag {
meta:
integer = -15
string = "d mol"
test = true
condition:
true
}
rule second: tag {
condition:
true
}
rule third: tag {
meta:
value = "ok"
condition:
true
}
rule fourth { condition: true }
"#,
);
let input = test_file(b"");
let path = input.path().display();
cmd()
.arg("-m")
.arg(rule_file.path())
.arg(input.path())
.assert()
.stdout(format!(
"first [integer=-15,string=\"d mol\",test=true] {path}\n\
second [] {path}\n\
third [value=\"ok\"] {path}\n\
fourth [] {path}\n"
))
.stderr("")
.success();
cmd()
.arg("-g")
.arg("--print-meta")
.arg(rule_file.path())
.arg(input.path())
.assert()
.stdout(format!(
"first [tag] [integer=-15,string=\"d mol\",test=true] {path}\n\
second [tag] [] {path}\n\
third [tag] [value=\"ok\"] {path}\n\
fourth [] [] {path}\n"
))
.stderr("")
.success();
}
#[test]
fn test_print_string_matches() {
let rule_file = test_file(
br#"
rule my_rule {
strings:
$a = /<.{1,5}?>/
$b = "abc"
condition:
// The rule can be evaluated without scanning the strings.
// This ensures that printing strings forces the computation of the
// string matches.
true or any of them
}"#,
);
let input = test_file(
b"<a>\n
<<abc>d>\n
<\x01\x02>\n
<a\tv>\n
<a\xFF>>\n
",
);
let path = input.path().display();
cmd()
.arg("-s")
.arg(rule_file.path())
.arg(input.path())
.assert()
.stdout(format!(
r#"my_rule {path}
0x0:$a: <a>
0x5:$a: <<abc>
0x6:$a: <abc>
0xf:$a: <\x01\x02>
0x15:$a: <a\tv>
0x1c:$a: <a\xff>
0x7:$b: abc
"#
))
.stderr("")
.success();
cmd()
.arg("-L")
.arg(rule_file.path())
.arg(input.path())
.assert()
.stdout(format!(
r#"my_rule {path}
0x0:3:$a
0x5:6:$a
0x6:5:$a
0xf:4:$a
0x15:5:$a
0x1c:4:$a
0x7:3:$b
"#
))
.stderr("")
.success();
cmd()
.arg("--print-strings")
.arg("--print-string-length")
.arg(rule_file.path())
.arg(input.path())
.assert()
.stdout(format!(
r#"my_rule {path}
0x0:3:$a: <a>
0x5:6:$a: <<abc>
0x6:5:$a: <abc>
0xf:4:$a: <\x01\x02>
0x15:5:$a: <a\tv>
0x1c:4:$a: <a\xff>
0x7:3:$b: abc
"#
))
.stderr("")
.success();
}
#[test]
fn test_timeout() {
let rule_file = test_file(
br#"
rule too_long {
condition:
for all i in (0..9223372036854775807) : (
for all j in (0..9223372036854775807) : (
for all k in (0..9223372036854775807) : (
for all l in (0..9223372036854775807) : (
i + j + k + l >= 0
)
)
)
)
}"#,
);
let input = test_file(b"");
let path = input.path().display();
cmd()
.arg("-a")
.arg("1")
.arg(rule_file.path())
.arg(input.path())
.assert()
.stdout("")
.stderr(format!("Cannot scan {path}: timeout\n"))
.failure();
}
#[test]
fn test_print_namespace() {
let rule_file = test_file(
br#"
rule first { condition: true }
"#,
);
let input = test_file(b"");
let path = input.path().display();
cmd()
.arg("-e")
.arg(rule_file.path())
.arg(input.path())
.assert()
.stdout(format!("default:first {path}\n"))
.stderr("")
.success();
}
#[test]
fn test_define_symbol() {
let input = test_file(b"");
let path = input.path().display();
let rule_file = test_file(
br#"
rule symbols {
condition:
symbol_float + 4.3 == 2.5 and
symbol_int + 5 == 2 and
symbol_true == true and
symbol_false == false and
symbol_str == "a_string" and
symbol_str2 == "2.5a" and
symbol_empty == ""
}
"#,
);
cmd()
.arg("-d")
.arg("symbol_float=-1.8")
.arg("--define=symbol_int=-3")
.arg("--define")
.arg("symbol_true=true")
.arg("--define")
.arg("symbol_false=false")
.arg("--define")
.arg("symbol_str=a_string")
.arg("--define")
.arg("symbol_str2=2.5a")
.arg("--define")
.arg("symbol_empty=")
.arg(rule_file.path())
.arg(input.path())
.assert()
.stdout(format!("symbols {path}\n"))
.stderr("")
.success();
cmd()
.arg("-d")
.arg("name")
.arg(rule_file.path())
.arg(input.path())
.assert()
.stdout("")
.stderr(predicate::str::contains(
"invalid value 'name' for '--define <VAR=VALUE>': \
missing '=' delimiter",
))
.failure();
let bad_rule = test_file(
br#"
rule bad_symbols {
condition:
symbol_float == "-1.8"
}
"#,
);
cmd()
.arg("-d")
.arg("symbol_float=-1.8")
.arg(bad_rule.path())
.arg(input.path())
.assert()
.stdout("")
.stderr(predicate::str::contains("expressions have invalid types"))
.failure();
}
#[test]
fn test_scan_list() {
let rule_file = test_file(b"rule a { condition: true }");
let dir1 = TempDir::new().unwrap();
let file_a = dir1.path().join("a");
fs::write(&file_a, "").unwrap();
let file_b = dir1.path().join("b");
fs::write(&file_b, "").unwrap();
let dir2 = TempDir::new().unwrap();
let subdir = dir2.path().join("subdir");
fs::create_dir(&subdir).unwrap();
let file_c = subdir.join("c");
fs::write(&file_c, "").unwrap();
let match_str = |input: &Path| predicate::str::contains(format!("a {}", input.display()));
let list = test_file(format!("{}\n{}\n", dir1.path().display(), file_c.display()).as_bytes());
cmd()
.arg("--scan-list")
.arg(rule_file.path())
.arg(list.path())
.assert()
.stdout(
match_str(&file_a)
.and(match_str(&file_b))
.and(match_str(&file_c)),
)
.stderr("")
.success();
let list = test_file(format!("{}\n{}\n", file_a.display(), dir2.path().display()).as_bytes());
cmd()
.arg("--scan-list")
.arg(rule_file.path())
.arg(list.path())
.assert()
.stdout(
match_str(&file_a)
.and(match_str(&file_b).not())
.and(match_str(&file_c).not()),
)
.stderr("")
.success();
cmd()
.arg("--scan-list")
.arg("-r")
.arg(rule_file.path())
.arg(list.path())
.assert()
.stdout(
match_str(&file_a)
.and(match_str(&file_b).not())
.and(match_str(&file_c)),
)
.stderr("")
.success();
let list = test_file(b"");
cmd()
.arg("--scan-list")
.arg(rule_file.path())
.arg(list.path())
.assert()
.stdout("")
.stderr("")
.success();
if cfg!(target_os = "linux") {
cmd()
.arg("--scan-list")
.arg(rule_file.path())
.arg(dir1.path())
.assert()
.stdout("")
.stderr(predicate::str::contains(format!(
"cannot read from scan list {}: Is a directory",
dir1.path().display()
)))
.failure();
cmd()
.arg("--scan-list")
.arg(rule_file.path())
.arg("invalid_path")
.assert()
.stdout("")
.stderr(predicate::str::contains(
"cannot open scan list invalid_path: No such file or directory",
))
.failure();
}
}
struct BinHelper {
proc: std::process::Child,
}
impl BinHelper {
fn run(arg: &str) -> Self {
let path = std::env::current_exe().unwrap();
let path = path.parent().unwrap();
let path = path.parent().unwrap();
let path = path.join(if cfg!(windows) {
"boreal-test-helpers.exe"
} else {
"boreal-test-helpers"
});
if !path.exists() {
panic!(
"File {} not found. \
You need to compile the `boreal-test-helpers` crate to run this test",
path.display()
);
}
let mut child = std::process::Command::new(path)
.arg(arg)
.stdout(std::process::Stdio::piped())
.spawn()
.unwrap();
let mut stdout = BufReader::new(child.stdout.take().unwrap());
let mut buffer = String::new();
loop {
buffer.clear();
stdout.read_line(&mut buffer).unwrap();
if buffer.trim() == "ready" {
break;
}
}
Self { proc: child }
}
fn pid(&self) -> u32 {
self.proc.id()
}
}
impl Drop for BinHelper {
fn drop(&mut self) {
let _ = self.proc.kill();
let _ = self.proc.wait();
}
}