Skip to content
Closed
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
42 changes: 42 additions & 0 deletions smart_tests/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 Annotated, Dict, List

import click
Expand Down Expand Up @@ -50,6 +51,29 @@ 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:
if classname := ET.parse(report_file).getroot().get('name'):
test_paths.append({"type": "class", "name": classname})
except ET.ParseError as e:
click.secho(f"Warning: Could not parse {report_file}: {e}", fg='yellow', err=True)

return test_paths


@smart_tests.subset
def subset(
client: Subset,
Expand All @@ -68,6 +92,12 @@ def subset(
help="Scan testCompile/default-testCompile/createdFiles.lst for *.lst files generated by `mvn compile` and "
"use them as test inputs."
)] = False,
is_scan_dryrun_results: Annotated[bool, typer.Option(
"--scan-dryrun-results",
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."
)] = False,
exclude_rules: Annotated[List[str], typer.Option(
"--exclude",
multiple=True,
Expand Down Expand Up @@ -108,6 +138,18 @@ def file2test(f: str) -> List | None:
if source_roots is None:
source_roots = []

if is_scan_dryrun_results:
tests = parse_surefire_reports()
if not tests:
raise BadCmdLineException(
"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.same_bin_formatter = lambda s: [{"type": "class", "name": s}]
client.run()
return

files_to_read = list(test_compile_created_file)
if is_scan_test_compile_lst:
if len(test_compile_created_file) > 0:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<testsuite xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://maven.apache.org/surefire/maven-surefire-plugin/xsd/surefire-test-report.xsd" version="3.0.2" name="com.example.demo.CalculatorTest" time="0.004" tests="1" errors="0" skipped="1" failures="0">
<testcase name="add" classname="com.example.demo.CalculatorTest" time="0"/>
</testsuite>
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<testsuite xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://maven.apache.org/surefire/maven-surefire-plugin/xsd/surefire-test-report.xsd" version="3.0.2" name="com.example.demo.UserTest" time="0.002" tests="1" errors="0" skipped="1" failures="0">
<testcase name="create" classname="com.example.demo.UserTest" time="0"/>
</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.demo.CalculatorTest"}],
[{"type": "class", "name": "com.example.demo.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 @@ -167,6 +167,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, {"SMART_TESTS_TOKEN": CliTestCase.smart_tests_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, {"SMART_TESTS_TOKEN": CliTestCase.smart_tests_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