Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,20 @@ You'll get:

Please be sure to increment `--node-index` arg.

You can exclude specific test files from being split using the `--tests-exclude-glob` option:

```
$ split-test --junit-xml-report-dir report --node-index 0 --node-total 2 --tests-glob 'spec/**/*_spec.rb' --tests-exclude-glob 'spec/system/**/*_spec.rb'
```

This will include all files matching `spec/**/*_spec.rb` but exclude any files matching `spec/system/**/*_spec.rb`. This is useful when you want to run system specs separately from unit tests, as system specs typically take longer and may require different setup.

You can also specify multiple exclude patterns by using the `--tests-exclude-glob` option multiple times:

```
$ split-test --junit-xml-report-dir report --node-index 0 --node-total 2 --tests-glob 'spec/**/*_spec.rb' --tests-exclude-glob 'spec/system/**/*_spec.rb' --tests-exclude-glob 'spec/integration/**/*_spec.rb'
```

You can use `--debug` option to make sure how it's grouped:

```
Expand Down
18 changes: 15 additions & 3 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ struct Opt {
#[structopt(long, required = true)]
tests_glob: Vec<String>,
#[structopt(long)]
tests_exclude_glob: Vec<String>,
#[structopt(long)]
node_index: usize,
#[structopt(long)]
node_total: usize,
Expand Down Expand Up @@ -63,7 +65,7 @@ impl<'a> Node<'a> {
}
}

fn expand_globs(patterns: &Vec<String>) -> Result<Vec<PathBuf>> {
fn expand_globs(patterns: &Vec<String>, exclude_patterns: &Vec<String>) -> Result<Vec<PathBuf>> {
let mut files = HashSet::new();

for pattern in patterns {
Expand All @@ -72,6 +74,16 @@ fn expand_globs(patterns: &Vec<String>) -> Result<Vec<PathBuf>> {
}
}

if !exclude_patterns.is_empty() {
let mut exclude_files = HashSet::new();
for exclude_pattern in exclude_patterns {
for path in glob(&exclude_pattern)? {
exclude_files.insert(canonicalize(path?)?);
}
}
files = files.difference(&exclude_files).cloned().collect();
}

let mut files = files.into_iter().collect::<Vec<_>>();
files.sort();

Expand All @@ -87,7 +99,7 @@ fn get_test_file_results(junit_xml_report_dir: &PathBuf) -> Result<HashMap<PathB

let mut test_file_results = HashMap::new();

for xml_path in expand_globs(&vec![String::from(xml_glob)])? {
for xml_path in expand_globs(&vec![String::from(xml_glob)], &vec![])? {
let reader = BufReader::new(File::open(&xml_path)?);
let test_result_xml: TestResultXml = from_reader(reader).unwrap_or_else(|err| {
warn!("Failed to parse XML file {:?}: {}", xml_path, err);
Expand Down Expand Up @@ -146,7 +158,7 @@ fn main() -> Result<()> {

let test_file_results = get_test_file_results(&args.junit_xml_report_dir)?;

let test_files = expand_globs(&args.tests_glob)?;
let test_files = expand_globs(&args.tests_glob, &args.tests_exclude_glob)?;
if test_files.len() == 0 {
bail!("Test file is not found. pattern: {:?}", args.tests_glob);
}
Expand Down
95 changes: 95 additions & 0 deletions tests/integration_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -240,3 +240,98 @@ fn test_invalid_report() -> Result<(), Box<dyn std::error::Error>> {

Ok(())
}

#[test]
fn test_exclude_glob() -> Result<(), Box<dyn std::error::Error>> {
let mut cmd = Command::cargo_bin("split-test")?;

// Test excluding specific files - should include a_test.rb and c_test.rb, exclude b_test.rb
cmd.current_dir("tests/fixtures/minitest")
.arg("--junit-xml-report-dir")
.arg("report")
.arg("--node-index")
.arg("0")
.arg("--node-total")
.arg("1")
.arg("--tests-glob")
.arg("*_test.rb")
.arg("--tests-exclude-glob")
.arg("b_test.rb");

cmd.assert()
.success()
.stdout(predicate::str::contains("a_test.rb"))
.stdout(predicate::str::contains("b_test.rb").not())
.stdout(predicate::str::contains("c_test.rb"))
.stderr(predicate::str::is_empty());

// Test excluding with wildcard - exclude all files starting with 'a'
cmd = Command::cargo_bin("split-test")?;

cmd.current_dir("tests/fixtures/minitest")
.arg("--junit-xml-report-dir")
.arg("report")
.arg("--node-index")
.arg("0")
.arg("--node-total")
.arg("1")
.arg("--tests-glob")
.arg("*_test.rb")
.arg("--tests-exclude-glob")
.arg("a*_test.rb");

cmd.assert()
.success()
.stdout(predicate::str::contains("a_test.rb").not())
.stdout(predicate::str::contains("b_test.rb"))
.stdout(predicate::str::contains("c_test.rb"))
.stderr(predicate::str::is_empty());

// Test with split across nodes and exclude
cmd = Command::cargo_bin("split-test")?;

cmd.current_dir("tests/fixtures/minitest")
.arg("--junit-xml-report-dir")
.arg("report")
.arg("--node-index")
.arg("0")
.arg("--node-total")
.arg("2")
.arg("--tests-glob")
.arg("*_test.rb")
.arg("--tests-exclude-glob")
.arg("c_test.rb");

cmd.assert()
.success()
.stdout(predicate::str::contains("a_test.rb"))
.stdout(predicate::str::contains("b_test.rb").not())
.stdout(predicate::str::contains("c_test.rb").not())
.stderr(predicate::str::is_empty());

// Test with multiple exclude patterns - exclude both a_test.rb and c_test.rb
cmd = Command::cargo_bin("split-test")?;

cmd.current_dir("tests/fixtures/minitest")
.arg("--junit-xml-report-dir")
.arg("report")
.arg("--node-index")
.arg("0")
.arg("--node-total")
.arg("1")
.arg("--tests-glob")
.arg("*_test.rb")
.arg("--tests-exclude-glob")
.arg("a_test.rb")
.arg("--tests-exclude-glob")
.arg("c_test.rb");

cmd.assert()
.success()
.stdout(predicate::str::contains("a_test.rb").not())
.stdout(predicate::str::contains("b_test.rb"))
.stdout(predicate::str::contains("c_test.rb").not())
.stderr(predicate::str::is_empty());

Ok(())
}