diff --git a/.github/workflows/jf-cli.yml b/.github/workflows/jf-cli.yml index e78a55ba0..db297eafe 100755 --- a/.github/workflows/jf-cli.yml +++ b/.github/workflows/jf-cli.yml @@ -788,7 +788,6 @@ jobs: ARTIFACT_DIGEST=$(sha256sum target/spring-petclinic-*.jar | awk '{print "sha256:"$1}') echo "artifact_digest=$ARTIFACT_DIGEST" >> $GITHUB_OUTPUT - - name: "Evidence: Build Info" # continue-on-error: true env: @@ -805,6 +804,18 @@ jobs: cat ./${{env.EVD_JSON}} jf evd create --build-name ${{env.BUILD_NAME}} --build-number ${{env.BUILD_ID}} --predicate ./${{env.EVD_JSON}} --predicate-type https://cyclonedx.org/bom/v1.4 --key "${{secrets.KRISHNAM_JFROG_EVD_PRIVATEKEY}}" --key-alias ${{secrets.EVIDENCE_KEY_ALIAS}} + - name: "Evidence: Test Results" + continue-on-error: true + env: + PY_SCRIPT: "jfrog/convert/convert_surefire_to_json.py" + EVD_JSON: "target/surefire-reports/test-results.json" # https://jfrog.com/evidence/signature/v1 + run: | + jf mvn test -Denforcer.skip=true + + python3 ./${{env.PY_SCRIPT}} ./${{env.EVD_JSON}} + + cat ./${{env.EVD_JSON}} + jf evd create --build-name ${{env.BUILD_NAME}} --build-number ${{env.BUILD_ID}} --predicate ./${{env.EVD_JSON}} --predicate-type https://jfrog.com/evidence/test-results/v1 --key "${{secrets.KRISHNAM_JFROG_EVD_PRIVATEKEY}}" --key-alias ${{secrets.EVIDENCE_KEY_ALIAS}} # - name: "Evidence: Build Publish" # # continue-on-error: true diff --git a/jfcli.sh b/jfcli.sh index 87978dc55..5ddfc1f96 100755 --- a/jfcli.sh +++ b/jfcli.sh @@ -9,5 +9,8 @@ export BUILD_NAME="spring-petclinic" BUILD_ID="cmd.$(date '+%Y-%m-%d-%H-%M')" jf mvnc --global --repo-resolve-releases ${RT_REPO_VIRTUAL} --repo-resolve-snapshots ${RT_REPO_VIRTUAL} # jf ca --format=table --threads=100 -# -Denforcer.skip=true -jf mvn clean install -DskipTests=true -Denforcer.skip=true -f pom.xml --build-name ${BUILD_NAME} --build-number ${BUILD_ID} \ No newline at end of file +# +jf mvn clean install -DskipTests=true -Denforcer.skip=true -f pom.xml --build-name ${BUILD_NAME} --build-number ${BUILD_ID} + +jf mvn test -Denforcer.skip=true +python3 ./jfrog/convert/convert_surefire_to_json.py ./target/surefire-reports/test-results.json \ No newline at end of file diff --git a/jfrog/convert/convert_surefire_to_json.py b/jfrog/convert/convert_surefire_to_json.py new file mode 100755 index 000000000..c722ddd03 --- /dev/null +++ b/jfrog/convert/convert_surefire_to_json.py @@ -0,0 +1,146 @@ +#!/usr/bin/env python3 +""" +Converts Maven Surefire XML test reports to a single JSON file. +Reads all TEST-*.xml files from target/surefire-reports/ and converts them to JSON. +""" +import json +import os +import sys +import xml.etree.ElementTree as ET +from pathlib import Path + + +def find_project_root(): + """Find the project root directory by looking for pom.xml or build.gradle.""" + current = Path(__file__).resolve().parent + while current != current.parent: + if (current / 'pom.xml').exists() or (current / 'build.gradle').exists(): + return current + current = current.parent + # Fallback to parent of jfrog/convert + return Path(__file__).resolve().parent.parent.parent + + +def parse_test_suite(xml_file): + """Parse a single TEST-*.xml file and return test suite data.""" + try: + tree = ET.parse(xml_file) + root = tree.getroot() + + suite_name = root.get('name', xml_file.stem.replace('TEST-', '')) + tests = int(root.get('tests', 0)) + failures = int(root.get('failures', 0)) + errors = int(root.get('errors', 0)) + skipped = int(root.get('skipped', 0)) + time = float(root.get('time', 0.0)) + + test_cases = [] + for testcase in root.findall('testcase'): + test_case = { + 'name': testcase.get('name', ''), + 'classname': testcase.get('classname', ''), + 'time': float(testcase.get('time', 0.0)) + } + + failure = testcase.find('failure') + if failure is not None: + test_case['failure'] = { + 'message': failure.get('message', ''), + 'type': failure.get('type', ''), + 'content': failure.text or '' + } + + error = testcase.find('error') + if error is not None: + test_case['error'] = { + 'message': error.get('message', ''), + 'type': error.get('type', ''), + 'content': error.text or '' + } + + skipped_elem = testcase.find('skipped') + if skipped_elem is not None: + test_case['skipped'] = skipped_elem.text or '' + + test_cases.append(test_case) + + return { + 'name': suite_name, + 'tests': tests, + 'failures': failures, + 'errors': errors, + 'skipped': skipped, + 'time': time, + 'testCases': test_cases + } + except Exception as e: + print(f"Error parsing {xml_file}: {e}", file=sys.stderr) + return None + + +def main(): + # Find project root + project_root = find_project_root() + reports_dir = project_root / 'target' / 'surefire-reports' + + # Determine output file (default to project root, or use command line arg) + if len(sys.argv) > 1: + output_file = Path(sys.argv[1]) + else: + output_file = project_root / 'test-results.json' + + if not reports_dir.exists(): + print(f"Reports directory does not exist: {reports_dir}", file=sys.stderr) + sys.exit(1) + + test_results = [] + total_tests = 0 + total_failures = 0 + total_errors = 0 + total_skipped = 0 + total_time = 0.0 + + # Find all TEST-*.xml files + xml_files = sorted(reports_dir.glob('TEST-*.xml')) + + if not xml_files: + print(f"No TEST-*.xml files found in {reports_dir}", file=sys.stderr) + sys.exit(1) + + print(f"Found {len(xml_files)} XML test report file(s)...") + + for xml_file in xml_files: + suite = parse_test_suite(xml_file) + if suite: + test_results.append(suite) + total_tests += suite['tests'] + total_failures += suite['failures'] + total_errors += suite['errors'] + total_skipped += suite['skipped'] + total_time += suite['time'] + + # Create JSON output + json_output = { + 'summary': { + 'totalTests': total_tests, + 'totalFailures': total_failures, + 'totalErrors': total_errors, + 'totalSkipped': total_skipped, + 'totalTime': total_time + }, + 'suites': test_results + } + + # Write JSON file + output_file.parent.mkdir(parents=True, exist_ok=True) + with open(output_file, 'w') as f: + json.dump(json_output, f, indent=2) + + print(f"\nTest results converted to JSON: {output_file}") + print(f"Summary: {total_tests} tests, {total_failures} failures, {total_errors} errors, {total_skipped} skipped") + print(f"Processed {len(test_results)} test suite(s)") + + +if __name__ == '__main__': + main() + diff --git a/spring-petclinic.code-workspace b/spring-petclinic.code-workspace index a200db562..eec07f433 100644 --- a/spring-petclinic.code-workspace +++ b/spring-petclinic.code-workspace @@ -5,6 +5,7 @@ } ], "settings": { - "java.compile.nullAnalysis.mode": "automatic" + "java.compile.nullAnalysis.mode": "automatic", + "java.configuration.updateBuildConfiguration": "disabled" } } \ No newline at end of file diff --git a/test-results.json b/test-results.json new file mode 100644 index 000000000..358ce3e25 --- /dev/null +++ b/test-results.json @@ -0,0 +1,442 @@ +{ + "summary": { + "totalTests": 42, + "totalFailures": 0, + "totalErrors": 0, + "totalSkipped": 2, + "totalTime": 7.714999999999999 + }, + "suites": [ + { + "name": "org.springframework.samples.petclinic.MySqlIntegrationTests", + "tests": 2, + "failures": 0, + "errors": 0, + "skipped": 2, + "time": 0.001, + "testCases": [ + { + "name": "testFindAll", + "classname": "org.springframework.samples.petclinic.MySqlIntegrationTests", + "time": 0.0, + "skipped": "" + }, + { + "name": "testOwnerDetails", + "classname": "org.springframework.samples.petclinic.MySqlIntegrationTests", + "time": 0.0, + "skipped": "" + } + ] + }, + { + "name": "org.springframework.samples.petclinic.PetClinicIntegrationTests", + "tests": 2, + "failures": 0, + "errors": 0, + "skipped": 0, + "time": 0.748, + "testCases": [ + { + "name": "testFindAll", + "classname": "org.springframework.samples.petclinic.PetClinicIntegrationTests", + "time": 0.014 + }, + { + "name": "testOwnerDetails", + "classname": "org.springframework.samples.petclinic.PetClinicIntegrationTests", + "time": 0.051 + } + ] + }, + { + "name": "org.springframework.samples.petclinic.PostgresIntegrationTests", + "tests": 0, + "failures": 0, + "errors": 0, + "skipped": 0, + "time": 0.215, + "testCases": [] + }, + { + "name": "org.springframework.samples.petclinic.model.ValidatorTests", + "tests": 1, + "failures": 0, + "errors": 0, + "skipped": 0, + "time": 0.013, + "testCases": [ + { + "name": "shouldNotValidateWhenFirstNameEmpty", + "classname": "org.springframework.samples.petclinic.model.ValidatorTests", + "time": 0.012 + } + ] + }, + { + "name": "org.springframework.samples.petclinic.owner.OwnerControllerTests", + "tests": 13, + "failures": 0, + "errors": 0, + "skipped": 0, + "time": 0.488, + "testCases": [ + { + "name": "testProcessUpdateOwnerFormHasErrors", + "classname": "org.springframework.samples.petclinic.owner.OwnerControllerTests", + "time": 0.099 + }, + { + "name": "testProcessCreationFormSuccess", + "classname": "org.springframework.samples.petclinic.owner.OwnerControllerTests", + "time": 0.005 + }, + { + "name": "testInitFindForm", + "classname": "org.springframework.samples.petclinic.owner.OwnerControllerTests", + "time": 0.011 + }, + { + "name": "testShowOwner", + "classname": "org.springframework.samples.petclinic.owner.OwnerControllerTests", + "time": 0.026 + }, + { + "name": "testProcessUpdateOwnerFormWithIdMismatch", + "classname": "org.springframework.samples.petclinic.owner.OwnerControllerTests", + "time": 0.004 + }, + { + "name": "testInitUpdateOwnerForm", + "classname": "org.springframework.samples.petclinic.owner.OwnerControllerTests", + "time": 0.009 + }, + { + "name": "testInitCreationForm", + "classname": "org.springframework.samples.petclinic.owner.OwnerControllerTests", + "time": 0.008 + }, + { + "name": "testProcessFindFormByLastName", + "classname": "org.springframework.samples.petclinic.owner.OwnerControllerTests", + "time": 0.004 + }, + { + "name": "testProcessFindFormNoOwnersFound", + "classname": "org.springframework.samples.petclinic.owner.OwnerControllerTests", + "time": 0.014 + }, + { + "name": "testProcessFindFormSuccess", + "classname": "org.springframework.samples.petclinic.owner.OwnerControllerTests", + "time": 0.036 + }, + { + "name": "testProcessUpdateOwnerFormSuccess", + "classname": "org.springframework.samples.petclinic.owner.OwnerControllerTests", + "time": 0.004 + }, + { + "name": "testProcessCreationFormHasErrors", + "classname": "org.springframework.samples.petclinic.owner.OwnerControllerTests", + "time": 0.007 + }, + { + "name": "testProcessUpdateOwnerFormUnchangedSuccess", + "classname": "org.springframework.samples.petclinic.owner.OwnerControllerTests", + "time": 0.003 + } + ] + }, + { + "name": "org.springframework.samples.petclinic.owner.PetControllerTests", + "tests": 0, + "failures": 0, + "errors": 0, + "skipped": 0, + "time": 0.383, + "testCases": [ + { + "name": "testProcessCreationFormSuccess", + "classname": "org.springframework.samples.petclinic.owner.PetControllerTests", + "time": 0.01 + }, + { + "name": "testProcessUpdateFormSuccess", + "classname": "org.springframework.samples.petclinic.owner.PetControllerTests", + "time": 0.006 + }, + { + "name": "testInitCreationForm", + "classname": "org.springframework.samples.petclinic.owner.PetControllerTests", + "time": 0.035 + }, + { + "name": "testProcessUpdateFormWithInvalidBirthDate", + "classname": "org.springframework.samples.petclinic.owner.PetControllerTests$ProcessUpdateFormHasErrors", + "time": 0.018 + }, + { + "name": "testProcessUpdateFormWithBlankName", + "classname": "org.springframework.samples.petclinic.owner.PetControllerTests$ProcessUpdateFormHasErrors", + "time": 0.013 + }, + { + "name": "testProcessCreationFormWithBlankName", + "classname": "org.springframework.samples.petclinic.owner.PetControllerTests$ProcessCreationFormHasErrors", + "time": 0.015 + }, + { + "name": "testInitUpdateForm", + "classname": "org.springframework.samples.petclinic.owner.PetControllerTests$ProcessCreationFormHasErrors", + "time": 0.012 + }, + { + "name": "testProcessCreationFormWithMissingPetType", + "classname": "org.springframework.samples.petclinic.owner.PetControllerTests$ProcessCreationFormHasErrors", + "time": 0.012 + }, + { + "name": "testProcessCreationFormWithDuplicateName", + "classname": "org.springframework.samples.petclinic.owner.PetControllerTests$ProcessCreationFormHasErrors", + "time": 0.011 + }, + { + "name": "testProcessCreationFormWithInvalidBirthDate", + "classname": "org.springframework.samples.petclinic.owner.PetControllerTests$ProcessCreationFormHasErrors", + "time": 0.01 + } + ] + }, + { + "name": "org.springframework.samples.petclinic.owner.PetTypeFormatterTests", + "tests": 3, + "failures": 0, + "errors": 0, + "skipped": 0, + "time": 0.006, + "testCases": [ + { + "name": "shouldThrowParseException", + "classname": "org.springframework.samples.petclinic.owner.PetTypeFormatterTests", + "time": 0.003 + }, + { + "name": "testPrint", + "classname": "org.springframework.samples.petclinic.owner.PetTypeFormatterTests", + "time": 0.0 + }, + { + "name": "shouldParse", + "classname": "org.springframework.samples.petclinic.owner.PetTypeFormatterTests", + "time": 0.001 + } + ] + }, + { + "name": "org.springframework.samples.petclinic.owner.PetValidatorTests", + "tests": 0, + "failures": 0, + "errors": 0, + "skipped": 0, + "time": 0.019, + "testCases": [ + { + "name": "testValidate", + "classname": "org.springframework.samples.petclinic.owner.PetValidatorTests", + "time": 0.013 + }, + { + "name": "testValidateWithInvalidPetName", + "classname": "org.springframework.samples.petclinic.owner.PetValidatorTests$ValidateHasErrors", + "time": 0.002 + }, + { + "name": "testValidateWithInvalidPetType", + "classname": "org.springframework.samples.petclinic.owner.PetValidatorTests$ValidateHasErrors", + "time": 0.001 + }, + { + "name": "testValidateWithInvalidBirthDate", + "classname": "org.springframework.samples.petclinic.owner.PetValidatorTests$ValidateHasErrors", + "time": 0.0 + } + ] + }, + { + "name": "org.springframework.samples.petclinic.owner.VisitControllerTests", + "tests": 3, + "failures": 0, + "errors": 0, + "skipped": 0, + "time": 0.159, + "testCases": [ + { + "name": "testProcessNewVisitFormHasErrors", + "classname": "org.springframework.samples.petclinic.owner.VisitControllerTests", + "time": 0.032 + }, + { + "name": "testProcessNewVisitFormSuccess", + "classname": "org.springframework.samples.petclinic.owner.VisitControllerTests", + "time": 0.004 + }, + { + "name": "testInitNewVisitForm", + "classname": "org.springframework.samples.petclinic.owner.VisitControllerTests", + "time": 0.006 + } + ] + }, + { + "name": "org.springframework.samples.petclinic.service.ClinicServiceTests", + "tests": 10, + "failures": 0, + "errors": 0, + "skipped": 0, + "time": 1.948, + "testCases": [ + { + "name": "shouldFindVets", + "classname": "org.springframework.samples.petclinic.service.ClinicServiceTests", + "time": 0.087 + }, + { + "name": "shouldFindOwnersByLastName", + "classname": "org.springframework.samples.petclinic.service.ClinicServiceTests", + "time": 0.056 + }, + { + "name": "shouldAddNewVisitForPet", + "classname": "org.springframework.samples.petclinic.service.ClinicServiceTests", + "time": 0.023 + }, + { + "name": "shouldUpdateOwner", + "classname": "org.springframework.samples.petclinic.service.ClinicServiceTests", + "time": 0.004 + }, + { + "name": "shouldFindVisitsByPetId", + "classname": "org.springframework.samples.petclinic.service.ClinicServiceTests", + "time": 0.006 + }, + { + "name": "shouldInsertPetIntoDatabaseAndGenerateId", + "classname": "org.springframework.samples.petclinic.service.ClinicServiceTests", + "time": 0.009 + }, + { + "name": "shouldInsertOwner", + "classname": "org.springframework.samples.petclinic.service.ClinicServiceTests", + "time": 0.007 + }, + { + "name": "shouldFindSingleOwnerWithPet", + "classname": "org.springframework.samples.petclinic.service.ClinicServiceTests", + "time": 0.004 + }, + { + "name": "shouldUpdatePetName", + "classname": "org.springframework.samples.petclinic.service.ClinicServiceTests", + "time": 0.079 + }, + { + "name": "shouldFindAllPetTypes", + "classname": "org.springframework.samples.petclinic.service.ClinicServiceTests", + "time": 0.014 + } + ] + }, + { + "name": "org.springframework.samples.petclinic.system.CrashControllerIntegrationTests", + "tests": 2, + "failures": 0, + "errors": 0, + "skipped": 0, + "time": 1.074, + "testCases": [ + { + "name": "testTriggerExceptionHtml", + "classname": "org.springframework.samples.petclinic.system.CrashControllerIntegrationTests", + "time": 0.145 + }, + { + "name": "testTriggerExceptionJson", + "classname": "org.springframework.samples.petclinic.system.CrashControllerIntegrationTests", + "time": 0.026 + } + ] + }, + { + "name": "org.springframework.samples.petclinic.system.CrashControllerTests", + "tests": 1, + "failures": 0, + "errors": 0, + "skipped": 0, + "time": 0.004, + "testCases": [ + { + "name": "testTriggerException", + "classname": "org.springframework.samples.petclinic.system.CrashControllerTests", + "time": 0.003 + } + ] + }, + { + "name": "org.springframework.samples.petclinic.system.I18nPropertiesSyncTest", + "tests": 2, + "failures": 0, + "errors": 0, + "skipped": 0, + "time": 0.025, + "testCases": [ + { + "name": "checkNonInternationalizedStrings", + "classname": "org.springframework.samples.petclinic.system.I18nPropertiesSyncTest", + "time": 0.021 + }, + { + "name": "checkI18nPropertyFilesAreInSync", + "classname": "org.springframework.samples.petclinic.system.I18nPropertiesSyncTest", + "time": 0.003 + } + ] + }, + { + "name": "org.springframework.samples.petclinic.vet.VetControllerTests", + "tests": 2, + "failures": 0, + "errors": 0, + "skipped": 0, + "time": 2.573, + "testCases": [ + { + "name": "testShowVetListHtml", + "classname": "org.springframework.samples.petclinic.vet.VetControllerTests", + "time": 0.559 + }, + { + "name": "testShowResourcesVetList", + "classname": "org.springframework.samples.petclinic.vet.VetControllerTests", + "time": 0.072 + } + ] + }, + { + "name": "org.springframework.samples.petclinic.vet.VetTests", + "tests": 1, + "failures": 0, + "errors": 0, + "skipped": 0, + "time": 0.059, + "testCases": [ + { + "name": "testSerialization", + "classname": "org.springframework.samples.petclinic.vet.VetTests", + "time": 0.059 + } + ] + } + ] +} \ No newline at end of file