Convert test-results xmls to json file

This commit is contained in:
Krishna Manchikalapudi 2025-11-12 18:41:28 -08:00
parent 602ee9b358
commit 8571ca5667
5 changed files with 607 additions and 4 deletions

View file

@ -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

View file

@ -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}
#
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

View file

@ -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()

View file

@ -5,6 +5,7 @@
}
],
"settings": {
"java.compile.nullAnalysis.mode": "automatic"
"java.compile.nullAnalysis.mode": "automatic",
"java.configuration.updateBuildConfiguration": "disabled"
}
}

442
test-results.json Normal file
View file

@ -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
}
]
}
]
}