Skip to content

Commit a4f4489

Browse files
authored
Merge pull request #1005 from launchableinc/maven-java-nested-class
Support nested classes in Maven plugin
2 parents 3b003a1 + 6e5d832 commit a4f4489

File tree

7 files changed

+104
-21
lines changed

7 files changed

+104
-21
lines changed

launchable/test_runners/gradle.py

Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import os
22
from typing import Dict, List
3-
from unittest import TestCase, TestSuite
43

54
import click
65

7-
from ..testpath import TestPath
6+
from launchable.utils.java import junit5_nested_class_path_builder
7+
88
from ..utils.file_name_pattern import jvm_test_pattern
99
from . import launchable
1010

@@ -109,21 +109,5 @@ def to_class_file(class_name: str):
109109
@click.argument('reports', required=True, nargs=-1)
110110
@launchable.record.tests
111111
def record_tests(client, reports):
112-
default_path_builder = client.path_builder
113-
114-
def path_builder(case: TestCase, suite: TestSuite,
115-
report_file: str) -> TestPath:
116-
"""
117-
With @Nested tests in JUnit 5, test class names have inner class names
118-
like com.launchableinc.rocket_car_gradle.AppTest$InnerClass.
119-
It causes a problem in subsetting bacause Launchable CLI can't detect inner classes in subsetting.
120-
So, we need to ignore the inner class names. The inner class name is separated by $.
121-
Note: Launchable allows to use $ in test paths. But we decided ignoring it in this case
122-
beause $ in the class name is not a common case.
123-
"""
124-
test_path = default_path_builder(case, suite, report_file)
125-
return [{**item, "name": item["name"].split("$")[0]} if item["type"] == "class" else item for item in test_path]
126-
127-
client.path_builder = path_builder
128-
112+
client.path_builder = junit5_nested_class_path_builder(client.path_builder)
129113
launchable.CommonRecordTestImpls.load_report_files(client=client, source_roots=reports)

launchable/test_runners/maven.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import click
55

66
from launchable.utils import glob
7+
from launchable.utils.java import junit5_nested_class_path_builder
78

89
from . import launchable
910

@@ -113,4 +114,8 @@ def format_same_bin(s: str) -> List[Dict[str, str]]:
113114
#
114115
# So to collectly find tests without duplications, we need to find surefire-reports/TEST-*.xml
115116
# not surefire-reports/**/TEST-*.xml nor surefire-reports/*.xml
116-
record_tests = launchable.CommonRecordTestImpls(__name__).report_files(file_mask="TEST-*.xml")
117+
@click.argument('reports', required=True, nargs=-1)
118+
@launchable.record.tests
119+
def record_tests(client, reports):
120+
client.path_builder = junit5_nested_class_path_builder(client.path_builder)
121+
launchable.CommonRecordTestImpls.load_report_files(client=client, source_roots=reports)

launchable/utils/java.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22
import shutil
33
import subprocess
44
import sys
5+
from typing import Callable
6+
from unittest import TestCase, TestSuite
7+
8+
from launchable.testpath import TestPath
59

610

711
def get_java_command():
@@ -27,3 +31,28 @@ def cygpath(p):
2731
if sys.platform == 'cygwin':
2832
p = subprocess.check_output(['cygpath', '-w', p]).decode().strip()
2933
return p
34+
35+
36+
def junit5_nested_class_path_builder(
37+
default_path_builder: Callable[[TestCase, TestSuite, str], TestPath]) -> Callable[[TestCase, TestSuite, str], TestPath]:
38+
"""
39+
Creates a path builder function that handles JUnit 5 nested class names.
40+
41+
With @Nested tests in JUnit 5, test class names have inner class names
42+
like com.launchableinc.rocket_car.NestedTest$InnerClass.
43+
It causes a problem in subsetting because Launchable CLI can't detect inner classes in subsetting.
44+
So, we need to ignore the inner class names. The inner class name is separated by $.
45+
Note: Launchable allows $ in test paths. But we decided to remove it in this case
46+
because $ in the class name is not a common case.
47+
48+
Args:
49+
default_path_builder: The original path builder function to wrap
50+
51+
Returns:
52+
A function that wraps the default path builder and handles nested class names
53+
"""
54+
def path_builder(case: TestCase, suite: TestSuite, report_file: str) -> TestPath:
55+
test_path = default_path_builder(case, suite, report_file)
56+
return [{**item, "name": item["name"].split("$")[0]} if item["type"] == "class" else item for item in test_path]
57+
58+
return path_builder

setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ classifiers =
1616
packages = find:
1717
install_requires =
1818
click>=8.0,<8.1;python_version=='3.6'
19-
click>=8.1;python_version>'3.6'
19+
click>=8.1,<8.2;python_version>'3.6'
2020
requests>=2.25;python_version>='3.6'
2121
urllib3>=1.26
2222
junitparser>=2.0.0

tests/data/maven/record_test_result.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,24 @@
1818
"stderr": "",
1919
"data": null
2020
},
21+
{
22+
"type": "case",
23+
"testPath": [
24+
{
25+
"type": "class",
26+
"name": "com.launchableinc.rocket_car_maven.NestedTest"
27+
},
28+
{
29+
"type": "testcase",
30+
"name": "shouldAnswerWithTrue"
31+
}
32+
],
33+
"duration": 0.001,
34+
"status": 1,
35+
"stdout": "",
36+
"stderr": "",
37+
"data": null
38+
},
2139
{
2240
"type": "case",
2341
"testPath": [
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<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" name="com.launchableinc.rocket_car_maven.NestedTest$InnerClass" time="0.023" tests="1" errors="0" skipped="0" failures="0">
3+
<testcase name="shouldAnswerWithTrue" classname="com.launchableinc.rocket_car_maven.NestedTest$InnerClass" time="0.001" />
4+
</testsuite>

tests/test_runners/test_maven.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,49 @@ def test_record_test_maven(self):
129129
self.assert_success(result)
130130
self.assert_record_tests_payload("record_test_result.json")
131131

132+
@responses.activate
133+
@mock.patch.dict(os.environ, {"LAUNCHABLE_TOKEN": CliTestCase.launchable_token})
134+
def test_record_test_maven_with_nested_class(self):
135+
"""Verify that class names containing $ (inner class marker) are processed correctly during test recording"""
136+
# Test the path_builder function directly by extracting it from the maven module
137+
from unittest import TestCase as UnitTestCase
138+
from unittest import TestSuite as UnitTestSuite
139+
140+
# Extract the implementation from maven.py directly
141+
# This gets the implementation without going through the CLI/Click command
142+
def create_custom_path_builder(default_path_builder):
143+
def path_builder(case, suite, report_file):
144+
test_path = default_path_builder(case, suite, report_file)
145+
return [{**item, "name": item["name"].split("$")[0]} if item["type"] == "class" else item for item in test_path]
146+
return path_builder
147+
148+
# Mock the default path builder that would return a class with $ in it
149+
def default_path_builder(case, suite, report_file):
150+
return [{"type": "class", "name": "com.launchableinc.rocket_car_maven.NestedTest$InnerClass"}]
151+
152+
# Create our custom path builder function
153+
custom_path_builder = create_custom_path_builder(default_path_builder)
154+
155+
# Test it directly with dummy inputs
156+
test_case = UnitTestCase()
157+
test_suite = UnitTestSuite()
158+
report_file = "TEST-nested.xml"
159+
160+
# Call the path_builder
161+
result_path = custom_path_builder(test_case, test_suite, report_file)
162+
163+
# Verify the result - it should remove everything after $
164+
self.assertEqual(result_path[0]["name"], "com.launchableinc.rocket_car_maven.NestedTest")
165+
self.assertNotIn("$", result_path[0]["name"])
166+
167+
# Now run the actual CLI command to ensure integration works
168+
result = self.cli('record', 'tests', '--session', self.session,
169+
'maven',
170+
str(self.test_files_dir) + "/maven/reports/TEST-1.xml",
171+
str(self.test_files_dir) + "/maven/reports/TEST-2.xml",
172+
str(self.test_files_dir) + "/maven/reports/TEST-nested.xml")
173+
self.assert_success(result)
174+
132175
def test_glob(self):
133176
for x in [
134177
'foo/BarTest.java',

0 commit comments

Comments
 (0)