From 6bc4b28872d0a67486cde61524a17f0d556d48e7 Mon Sep 17 00:00:00 2001 From: psakthivel Date: Tue, 14 Apr 2026 21:46:25 +0530 Subject: [PATCH] feat(LCHIB-693): Enhance verify command to display organization and workspace names from API response --- launchable/test_runners/maven.py | 94 +++++++++++++--- tests/data/maven/README.md | 75 +++++++++++++ ...pplication.ExcludeTestApplicationTests.xml | 87 ++++++++++++++ ...EST-com.launchable.demo.CalculatorTest.xml | 91 +++++++++++++++ ...TEST-com.launchable.demo.MixedTagsTest.xml | 79 +++++++++++++ ...pplication.ExcludeTestApplicationTests.txt | 4 + .../com.launchable.demo.CalculatorTest.txt | 4 + .../com.launchable.demo.MixedTagsTest.txt | 4 + tests/test_runners/test_maven.py | 106 ++++++++++++++++++ 9 files changed, 529 insertions(+), 15 deletions(-) create mode 100644 tests/data/maven/README.md create mode 100644 tests/data/maven/dryrun-test/target/surefire-reports/TEST-com.example.excludetestapplication.ExcludeTestApplicationTests.xml create mode 100644 tests/data/maven/dryrun-test/target/surefire-reports/TEST-com.launchable.demo.CalculatorTest.xml create mode 100644 tests/data/maven/dryrun-test/target/surefire-reports/TEST-com.launchable.demo.MixedTagsTest.xml create mode 100644 tests/data/maven/dryrun-test/target/surefire-reports/com.example.excludetestapplication.ExcludeTestApplicationTests.txt create mode 100644 tests/data/maven/dryrun-test/target/surefire-reports/com.launchable.demo.CalculatorTest.txt create mode 100644 tests/data/maven/dryrun-test/target/surefire-reports/com.launchable.demo.MixedTagsTest.txt diff --git a/launchable/test_runners/maven.py b/launchable/test_runners/maven.py index df7211a9b..6cdf05645 100644 --- a/launchable/test_runners/maven.py +++ b/launchable/test_runners/maven.py @@ -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 @@ -46,6 +47,33 @@ def is_file(f: str) -> bool: return False +def parse_surefire_reports() -> List[Dict[str, str]]: + """Parse Maven Surefire XML reports from target/surefire-reports/.""" + test_paths = [] + report_pattern = '**/target/surefire-reports/TEST-*.xml' + report_files = glob.glob(report_pattern, recursive=True) + + if not report_files: + return [] + + click.echo(f"Found {len(report_files)} surefire report(s)", err=True) + + for report_file in report_files: + try: + tree = ET.parse(report_file) + root = tree.getroot() + classname = root.get('name') + + if classname: + 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) + + click.echo(f"Total tests discovered: {len(test_paths)}", err=True) + return test_paths + + @click.option( '--test-compile-created-file', 'test_compile_created_file', @@ -61,6 +89,12 @@ 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', + is_flag=True, + help="Scan surefire reports generated by mvn test -DdryRun=true", +) @click.option( '--exclude', 'exclude_rules', @@ -70,7 +104,14 @@ 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, ...] +): # Compile exclude rules compiled_exclude_rules = [] @@ -100,23 +141,45 @@ def file2test(f: str) -> Optional[List]: else: return None - files_to_read = list(test_compile_created_file) - if is_scan_test_compile_lst: - if len(test_compile_created_file) > 0: - click.echo(click.style( - "Warning: --test-compile-created-file is overridden by --scan-test-compile-lst", fg="yellow"), - err=True) + if is_scan_dryrun_results: + click.echo("Scanning Maven dry-run results...", err=True) + tests = parse_surefire_reports() + + if not tests: + click.secho( + "Warning: No surefire reports found. Did you run 'mvn test -DdryRun=true'?", + fg='yellow', + err=True + ) + click.secho( + "Please run: mvn test -DdryRun=true", + fg='cyan', + err=True + ) + return + + # Add tests to client (each test path must be a list) + for test in tests: + client.test_paths.append([test]) - pattern = os.path.join('**', 'createdFiles.lst') - files_to_read = glob.glob(pattern, recursive=True) + elif is_scan_test_compile_lst or test_compile_created_file: + if is_scan_test_compile_lst: + if len(test_compile_created_file) > 0: + click.echo(click.style( + "Warning: --test-compile-created-file is overridden by --scan-test-compile-lst", fg="yellow"), + err=True) - if not files_to_read: - click.echo(click.style( - "Warning: No .lst files. Please run after executing `mvn test-compile`", fg="yellow"), - err=True) - return + pattern = os.path.join('**', 'createdFiles.lst') + files_to_read = glob.glob(pattern, recursive=True) + + if not files_to_read: + click.echo(click.style( + "Warning: No .lst files. Please run after executing `mvn test-compile`", fg="yellow"), + err=True) + return + elif test_compile_created_file: + files_to_read = list(test_compile_created_file) - if files_to_read: for file in files_to_read: with open(file, 'r') as f: lines = f.readlines() @@ -132,6 +195,7 @@ def file2test(f: str) -> Optional[List]: path = file2test(l) if path: client.test_paths.append(path) + else: for root in source_roots: client.scan(root, '**/*', file2test) diff --git a/tests/data/maven/README.md b/tests/data/maven/README.md new file mode 100644 index 000000000..33cf259c8 --- /dev/null +++ b/tests/data/maven/README.md @@ -0,0 +1,75 @@ +# Maven Test Data + +This directory contains test fixtures for the Maven test runner, including sample Maven Surefire reports used to test the `--scan-dryrun-results` feature. + +## Directory Structure + +- `dryrun-test/` - Test data for the `--scan-dryrun-results` feature + - `target/surefire-reports/` - Sample Maven Surefire reports (XML and TXT formats) +- `reports/` - Sample test result XML files for various test scenarios + +## How to Test Manually with Your Own Project + +To manually test the `--scan-dryrun-results` feature with your own Maven project: + +```bash +# 1. Navigate to your Maven project +cd /path/to/your/maven-project + +# 2. Run Maven dry-run to generate reports +mvn test -DdryRun=true + +# 3. Verify reports were created +ls -la target/surefire-reports/TEST-*.xml + +# 4. Set up environment (if not already set) +export LAUNCHABLE_TOKEN='v1:your-org/your-workspace:your-actual-token' + +# 5. Record build +launchable record build --name 'test-build-name' + +# 6. Create a session +launchable record session --build 'test-build-name' +# Copy the session ID from output, e.g., builds/test-build-name/test_sessions/123 + +# 7. Run subset with --scan-dryrun-results +launchable subset maven \ + --scan-dryrun-results \ + --session builds/test-build-name/test_sessions/123 \ + --target 10% +``` + +## Creating Test Files for `--scan-dryrun-results` + +The test files in `dryrun-test/target/surefire-reports/` are fixtures that simulate Maven Surefire reports generated by running tests with Maven's dry-run mode. + +**Source:** These fixtures were generated from the [smart-tests-integration-examples](https://github.com/cloudbees-oss/smart-tests-integration-examples) repository, specifically from `maven/test-exclusion` project. + +### How to Create/Update These Files + +The `dryrun-test/` directory contains only the generated reports as test fixtures. To create or update these files: + +```bash +# 1. Clone the integration examples repository +git clone https://github.com/cloudbees-oss/smart-tests-integration-examples.git +cd smart-tests-integration-examples/maven/test-exclusion + +# 2. Run Maven dry-run to generate Surefire reports +mvn test -DdryRun=true + +# 3. Copy the generated reports to this repository +cp target/surefire-reports/TEST-*.xml /path/to/smart-tests-cli/tests/data/maven/dryrun-test/target/surefire-reports/ +cp target/surefire-reports/*.txt /path/to/smart-tests-cli/tests/data/maven/dryrun-test/target/surefire-reports/ +``` + +The reports include: +- XML files: `TEST-*.xml` (detailed test execution results with test class names) +- TXT files: `*.txt` (summary information for each test class) + +### Purpose + +These fixtures are used to test: +- Parsing of Maven Surefire XML reports +- The `--scan-dryrun-results` flag functionality +- Integration with Launchable's subset API +- Handling of filtered/excluded tests diff --git a/tests/data/maven/dryrun-test/target/surefire-reports/TEST-com.example.excludetestapplication.ExcludeTestApplicationTests.xml b/tests/data/maven/dryrun-test/target/surefire-reports/TEST-com.example.excludetestapplication.ExcludeTestApplicationTests.xml new file mode 100644 index 000000000..14ed22172 --- /dev/null +++ b/tests/data/maven/dryrun-test/target/surefire-reports/TEST-com.example.excludetestapplication.ExcludeTestApplicationTests.xml @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/data/maven/dryrun-test/target/surefire-reports/TEST-com.launchable.demo.CalculatorTest.xml b/tests/data/maven/dryrun-test/target/surefire-reports/TEST-com.launchable.demo.CalculatorTest.xml new file mode 100644 index 000000000..f841f4cc7 --- /dev/null +++ b/tests/data/maven/dryrun-test/target/surefire-reports/TEST-com.launchable.demo.CalculatorTest.xml @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/data/maven/dryrun-test/target/surefire-reports/TEST-com.launchable.demo.MixedTagsTest.xml b/tests/data/maven/dryrun-test/target/surefire-reports/TEST-com.launchable.demo.MixedTagsTest.xml new file mode 100644 index 000000000..46f5bdf94 --- /dev/null +++ b/tests/data/maven/dryrun-test/target/surefire-reports/TEST-com.launchable.demo.MixedTagsTest.xml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/data/maven/dryrun-test/target/surefire-reports/com.example.excludetestapplication.ExcludeTestApplicationTests.txt b/tests/data/maven/dryrun-test/target/surefire-reports/com.example.excludetestapplication.ExcludeTestApplicationTests.txt new file mode 100644 index 000000000..e0fcabc56 --- /dev/null +++ b/tests/data/maven/dryrun-test/target/surefire-reports/com.example.excludetestapplication.ExcludeTestApplicationTests.txt @@ -0,0 +1,4 @@ +------------------------------------------------------------------------------- +Test set: com.example.excludetestapplication.ExcludeTestApplicationTests +------------------------------------------------------------------------------- +Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.898 s -- in com.example.excludetestapplication.ExcludeTestApplicationTests diff --git a/tests/data/maven/dryrun-test/target/surefire-reports/com.launchable.demo.CalculatorTest.txt b/tests/data/maven/dryrun-test/target/surefire-reports/com.launchable.demo.CalculatorTest.txt new file mode 100644 index 000000000..e075f8c64 --- /dev/null +++ b/tests/data/maven/dryrun-test/target/surefire-reports/com.launchable.demo.CalculatorTest.txt @@ -0,0 +1,4 @@ +------------------------------------------------------------------------------- +Test set: com.launchable.demo.CalculatorTest +------------------------------------------------------------------------------- +Tests run: 6, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.026 s -- in com.launchable.demo.CalculatorTest diff --git a/tests/data/maven/dryrun-test/target/surefire-reports/com.launchable.demo.MixedTagsTest.txt b/tests/data/maven/dryrun-test/target/surefire-reports/com.launchable.demo.MixedTagsTest.txt new file mode 100644 index 000000000..caa26cb94 --- /dev/null +++ b/tests/data/maven/dryrun-test/target/surefire-reports/com.launchable.demo.MixedTagsTest.txt @@ -0,0 +1,4 @@ +------------------------------------------------------------------------------- +Test set: com.launchable.demo.MixedTagsTest +------------------------------------------------------------------------------- +Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.004 s -- in com.launchable.demo.MixedTagsTest diff --git a/tests/test_runners/test_maven.py b/tests/test_runners/test_maven.py index aa6669e6a..fc2d998ca 100644 --- a/tests/test_runners/test_maven.py +++ b/tests/test_runners/test_maven.py @@ -242,3 +242,109 @@ def test_glob(self): 'foo/Util.class', ]: self.assertFalse(maven.is_file(x)) + + def test_parse_surefire_reports_valid(self): + """Test parsing valid surefire reports from test data directory""" + # Change to test data directory with surefire reports + original_dir = os.getcwd() + test_data_dir = str(self.test_files_dir.joinpath('dryrun-test').resolve()) + + try: + os.chdir(test_data_dir) + tests = maven.parse_surefire_reports() + + # Should find test reports (at least 2) + self.assertGreaterEqual(len(tests), 2) + + # Verify test class names + test_names = {test['name'] for test in tests} + self.assertIn('com.launchable.demo.CalculatorTest', test_names) + self.assertIn('com.launchable.demo.MixedTagsTest', test_names) + + # Verify format + for test in tests: + self.assertEqual(test['type'], 'class') + self.assertIsInstance(test['name'], str) + finally: + os.chdir(original_dir) + + def test_parse_surefire_reports_no_files(self): + """Test parsing when no surefire reports exist""" + # Create empty temp directory + with tempfile.TemporaryDirectory() as temp_dir: + original_dir = os.getcwd() + try: + os.chdir(temp_dir) + tests = maven.parse_surefire_reports() + + # Should return empty list when no reports found + self.assertEqual(tests, []) + finally: + os.chdir(original_dir) + + def test_parse_surefire_reports_invalid_xml(self): + """Test parsing handles invalid XML gracefully""" + with tempfile.TemporaryDirectory() as temp_dir: + original_dir = os.getcwd() + try: + os.chdir(temp_dir) + + # Create target/surefire-reports directory + os.makedirs('target/surefire-reports', exist_ok=True) + + # Create invalid XML file + invalid_xml_path = os.path.join('target/surefire-reports', 'TEST-Invalid.xml') + with open(invalid_xml_path, 'w') as f: + f.write('') + + # Should handle parse error gracefully + tests = maven.parse_surefire_reports() + + # Should return empty list (invalid file skipped) + self.assertEqual(tests, []) + finally: + os.chdir(original_dir) + + @responses.activate + @mock.patch.dict(os.environ, {"LAUNCHABLE_TOKEN": CliTestCase.launchable_token}) + def test_subset_with_scan_dryrun_results(self): + """Test subset command with --scan-dryrun-results flag""" + # Change to test data directory with surefire reports + original_dir = os.getcwd() + test_data_dir = str(self.test_files_dir.joinpath('dryrun-test').resolve()) + + try: + os.chdir(test_data_dir) + + result = self.cli('subset', '--target', '10%', '--session', + self.session, 'maven', '--scan-dryrun-results') + + self.assert_success(result) + + # Verify output mentions scanning dry-run results + self.assertIn('Scanning Maven dry-run results', result.output) + self.assertIn('Found', result.output) + self.assertIn('surefire report', result.output) + finally: + os.chdir(original_dir) + + @responses.activate + @mock.patch.dict(os.environ, {"LAUNCHABLE_TOKEN": CliTestCase.launchable_token}) + def test_subset_with_scan_dryrun_results_no_reports(self): + """Test subset command with --scan-dryrun-results when no reports exist""" + with tempfile.TemporaryDirectory() as temp_dir: + original_dir = os.getcwd() + try: + os.chdir(temp_dir) + + result = self.cli('subset', '--target', '10%', '--session', + self.session, 'maven', '--scan-dryrun-results') + + # Should still succeed but show warning + self.assert_success(result) + + # Verify warning message displayed + self.assertIn('Warning: No surefire reports found', result.output) + self.assertIn('mvn test -DdryRun=true', result.output) + finally: + os.chdir(original_dir)