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
47 changes: 46 additions & 1 deletion launchable/test_runners/maven.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import glob
import os
import re
import xml.etree.ElementTree as ET
from typing import Dict, List, Optional, Tuple

import click
Expand Down Expand Up @@ -46,6 +47,31 @@ def is_file(f: str) -> bool:
return False


def parse_surefire_reports() -> List[Dict[str, str]]:
"""Collect the test classes Surefire discovered from its TEST-*.xml reports.

Intended to be run against reports produced by a JUnit 5 dry run
(`mvn test -Djunit.platform.execution.dryRun.enabled=true`): Surefire applies
all of its own filtering (excludedGroups, includedGroups, excludes, profiles,
...) before the dry run, so only the tests Maven would actually execute get a
report. Each report's root `name` attribute is the fully-qualified class name.
"""
test_paths = []
report_pattern = os.path.join('**', 'target', 'surefire-reports', 'TEST-*.xml')
report_files = glob.glob(report_pattern, recursive=True)

for report_file in report_files:
try:
classname = ET.parse(report_file).getroot().get('name')
if classname:
test_paths.append({"type": "class", "name": classname})
except ET.ParseError as e:
click.echo(click.style(
"Warning: Could not parse {}: {}".format(report_file, e), fg="yellow"), err=True)

return test_paths


@click.option(
'--test-compile-created-file',
'test_compile_created_file',
Expand All @@ -61,6 +87,13 @@ def is_file(f: str) -> bool:
is_flag=True,
help="Scan testCompile/default-testCompile/createdFiles.lst for *.lst files generated by `mvn compile` and use them as test inputs.", # noqa: E501
)
@click.option(
'--scan-dryrun-results',
'is_scan_dryrun_results',
required=False,
is_flag=True,
help="Scan surefire reports generated by `mvn test -Djunit.platform.execution.dryRun.enabled=true`. Only the tests Maven would actually run (after applying pom.xml excludedGroups/excludes/etc.) are sent to the subset. Run the dry run before this command. JUnit 5 only.", # noqa: E501
)
@click.option(
'--exclude',
'exclude_rules',
Expand All @@ -70,7 +103,19 @@ def is_file(f: str) -> bool:
)
@click.argument('source_roots', required=False, nargs=-1)
@launchable.subset
def subset(client, source_roots, test_compile_created_file, is_scan_test_compile_lst, exclude_rules: Tuple[str, ...]):
def subset(client, source_roots, test_compile_created_file, is_scan_test_compile_lst, is_scan_dryrun_results,
exclude_rules: Tuple[str, ...]):

if is_scan_dryrun_results:
tests = parse_surefire_reports()
if not tests:
raise click.UsageError(
"No surefire reports found under **/target/surefire-reports/. "
"Run `mvn test -Djunit.platform.execution.dryRun.enabled=true` before this command.")
for test in tests:
client.test_paths.append([test])
client.run()
return

# Compile exclude rules
compiled_exclude_rules = []
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<testsuite name="com.example.launchable.CalculatorTest" time="0" tests="1" errors="0" skipped="1" failures="0">
<testcase name="add" classname="com.example.launchable.CalculatorTest" time="0">
<skipped/>
</testcase>
</testsuite>
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<testsuite name="com.example.launchable.UserTest" time="0" tests="1" errors="0" skipped="1" failures="0">
<testcase name="create" classname="com.example.launchable.UserTest" time="0">
<skipped/>
</testcase>
</testsuite>
12 changes: 12 additions & 0 deletions tests/data/maven/subset_scan_dryrun_results_result.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"testPaths": [
[{"type": "class", "name": "com.example.launchable.CalculatorTest"}],
[{"type": "class", "name": "com.example.launchable.UserTest"}]
],
"testRunner": "maven",
"goal": {"type": "subset-by-percentage", "percentage": 0.1},
"ignoreNewTests": false,
"session": {"id": "16"},
"getTestsFromGuess": false,
"getTestsFromPreviousSessions": false
}
30 changes: 30 additions & 0 deletions tests/test_runners/test_maven.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,36 @@ def test_subset_with_exclude(self):
self.assert_success(result)
self.assert_subset_payload('subset_with_exclude_rules_result.json')

@responses.activate
@mock.patch.dict(os.environ, {"LAUNCHABLE_TOKEN": CliTestCase.launchable_token})
def test_scan_dryrun_results(self):
# Reports live under <dryrun-test>/target/surefire-reports/. The flag globs
# **/target/surefire-reports/TEST-*.xml relative to the cwd, so run from there.
test_data_dir = str(self.test_files_dir.joinpath('dryrun-test').resolve())
original_dir = os.getcwd()
try:
os.chdir(test_data_dir)
result = self.cli('subset', '--target', '10%', '--session',
self.session, 'maven', '--scan-dryrun-results')
self.assert_success(result)
self.assert_subset_payload('subset_scan_dryrun_results_result.json')
finally:
os.chdir(original_dir)

@responses.activate
@mock.patch.dict(os.environ, {"LAUNCHABLE_TOKEN": CliTestCase.launchable_token})
def test_scan_dryrun_results_no_reports(self):
original_dir = os.getcwd()
with tempfile.TemporaryDirectory() as temp_dir:
try:
os.chdir(temp_dir)
result = self.cli('subset', '--target', '10%', '--session',
self.session, 'maven', '--scan-dryrun-results')
self.assertNotEqual(result.exit_code, 0)
self.assertIn("No surefire reports found", result.output)
finally:
os.chdir(original_dir)

def test_glob(self):
for x in [
'foo/BarTest.java',
Expand Down
Loading