-
Notifications
You must be signed in to change notification settings - Fork 0
docs: update compatibility claims with measured metrics from JsonSchemaCheckIT #33
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
…tibility statistics - Add comprehensive SuiteMetrics class with thread-safe counters - Track groups discovered, tests discovered, validations run, passed/failed - Categorize skips: unsupportedSchemaGroup, testException, lenientMismatch - Add console summary line with detailed metrics breakdown - Support JSON/CSV export via -Djson.schema.metrics=json|csv - Add per-file breakdown for detailed analysis - Preserve existing strict/lenient behavior while adding metrics - Zero additional dependencies, thread-safe implementation Fixes #31
…maCheckIT - Replace estimated 71% compatibility with actual measured 63.3% (1,153 of 1,822 tests) - Add comprehensive metrics reporting documentation - Document test coverage: 420 groups, 1,657 validations, 576 skips categorized - Add usage examples for JSON/CSV metrics export - Clarify distinction between lenient and strict mode results - Provide defensible statistics based on actual test suite measurements The documentation now reflects the accurate, measured compatibility statistics provided by the new metrics system rather than estimates.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
Updates the JSON Schema module with comprehensive compatibility metrics collection from the official JSON Schema Test Suite, replacing vague compatibility claims with measured statistics.
- Adds detailed metrics tracking for test discovery, execution, passes, failures, and skip categorization
- Implements console output and optional JSON/CSV export formats for metrics
- Updates documentation with specific measured compatibility percentages (63.3% pass rate)
Reviewed Changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| JsonSchemaCheckIT.java | Implements comprehensive metrics collection with thread-safe counters and export functionality |
| README.md | Updates compatibility claims from vague "~71%" to measured "63.3%" with detailed statistics |
| AGENTS.md | Adds metrics usage documentation and current compatibility breakdown |
| README.md (root) | Updates compatibility section with measured metrics and usage examples |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
| var totals = new StringBuilder(); | ||
| totals.append("{\n"); | ||
| totals.append(" \"mode\": \"").append(strict ? "STRICT" : "LENIENT").append("\",\n"); | ||
| totals.append(" \"timestamp\": \"").append(timestamp).append("\",\n"); | ||
| totals.append(" \"totals\": {\n"); | ||
| totals.append(" \"groupsDiscovered\": ").append(METRICS.groupsDiscovered.sum()).append(",\n"); | ||
| totals.append(" \"testsDiscovered\": ").append(METRICS.testsDiscovered.sum()).append(",\n"); | ||
| totals.append(" \"validationsRun\": ").append(METRICS.validationsRun.sum()).append(",\n"); | ||
| totals.append(" \"passed\": ").append(METRICS.passed.sum()).append(",\n"); | ||
| totals.append(" \"failed\": ").append(METRICS.failed.sum()).append(",\n"); | ||
| totals.append(" \"skipped\": {\n"); | ||
| totals.append(" \"unsupportedSchemaGroup\": ").append(METRICS.skipUnsupportedGroup.sum()).append(",\n"); | ||
| totals.append(" \"testException\": ").append(METRICS.skipTestException.sum()).append(",\n"); | ||
| totals.append(" \"lenientMismatch\": ").append(METRICS.skipLenientMismatch.sum()).append("\n"); | ||
| totals.append(" }\n"); | ||
| totals.append(" },\n"); | ||
| totals.append(" \"perFile\": [\n"); | ||
|
|
||
| var files = new java.util.ArrayList<String>(METRICS.perFile.keySet()); | ||
| java.util.Collections.sort(files); | ||
| var first = true; | ||
| for (String file : files) { | ||
| var counters = METRICS.perFile.get(file); | ||
| if (!first) totals.append(",\n"); | ||
| first = false; | ||
| totals.append(" {\n"); | ||
| totals.append(" \"file\": \"").append(file).append("\",\n"); | ||
| totals.append(" \"groups\": ").append(counters.groups.sum()).append(",\n"); | ||
| totals.append(" \"tests\": ").append(counters.tests.sum()).append(",\n"); | ||
| totals.append(" \"run\": ").append(counters.run.sum()).append(",\n"); | ||
| totals.append(" \"pass\": ").append(counters.pass.sum()).append(",\n"); | ||
| totals.append(" \"fail\": ").append(counters.fail.sum()).append(",\n"); | ||
| totals.append(" \"skipUnsupported\": ").append(counters.skipUnsupported.sum()).append(",\n"); | ||
| totals.append(" \"skipException\": ").append(counters.skipException.sum()).append(",\n"); | ||
| totals.append(" \"skipMismatch\": ").append(counters.skipMismatch.sum()).append("\n"); | ||
| totals.append(" }"); | ||
| } | ||
| totals.append("\n ]\n"); | ||
| totals.append("}\n"); | ||
| return totals.toString(); |
Copilot
AI
Sep 15, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Manual JSON string building is error-prone and hard to maintain. Consider using Jackson ObjectMapper (already available as MAPPER) to serialize a proper data structure instead of manual string concatenation."
| var totals = new StringBuilder(); | |
| totals.append("{\n"); | |
| totals.append(" \"mode\": \"").append(strict ? "STRICT" : "LENIENT").append("\",\n"); | |
| totals.append(" \"timestamp\": \"").append(timestamp).append("\",\n"); | |
| totals.append(" \"totals\": {\n"); | |
| totals.append(" \"groupsDiscovered\": ").append(METRICS.groupsDiscovered.sum()).append(",\n"); | |
| totals.append(" \"testsDiscovered\": ").append(METRICS.testsDiscovered.sum()).append(",\n"); | |
| totals.append(" \"validationsRun\": ").append(METRICS.validationsRun.sum()).append(",\n"); | |
| totals.append(" \"passed\": ").append(METRICS.passed.sum()).append(",\n"); | |
| totals.append(" \"failed\": ").append(METRICS.failed.sum()).append(",\n"); | |
| totals.append(" \"skipped\": {\n"); | |
| totals.append(" \"unsupportedSchemaGroup\": ").append(METRICS.skipUnsupportedGroup.sum()).append(",\n"); | |
| totals.append(" \"testException\": ").append(METRICS.skipTestException.sum()).append(",\n"); | |
| totals.append(" \"lenientMismatch\": ").append(METRICS.skipLenientMismatch.sum()).append("\n"); | |
| totals.append(" }\n"); | |
| totals.append(" },\n"); | |
| totals.append(" \"perFile\": [\n"); | |
| var files = new java.util.ArrayList<String>(METRICS.perFile.keySet()); | |
| java.util.Collections.sort(files); | |
| var first = true; | |
| for (String file : files) { | |
| var counters = METRICS.perFile.get(file); | |
| if (!first) totals.append(",\n"); | |
| first = false; | |
| totals.append(" {\n"); | |
| totals.append(" \"file\": \"").append(file).append("\",\n"); | |
| totals.append(" \"groups\": ").append(counters.groups.sum()).append(",\n"); | |
| totals.append(" \"tests\": ").append(counters.tests.sum()).append(",\n"); | |
| totals.append(" \"run\": ").append(counters.run.sum()).append(",\n"); | |
| totals.append(" \"pass\": ").append(counters.pass.sum()).append(",\n"); | |
| totals.append(" \"fail\": ").append(counters.fail.sum()).append(",\n"); | |
| totals.append(" \"skipUnsupported\": ").append(counters.skipUnsupported.sum()).append(",\n"); | |
| totals.append(" \"skipException\": ").append(counters.skipException.sum()).append(",\n"); | |
| totals.append(" \"skipMismatch\": ").append(counters.skipMismatch.sum()).append("\n"); | |
| totals.append(" }"); | |
| } | |
| totals.append("\n ]\n"); | |
| totals.append("}\n"); | |
| return totals.toString(); | |
| // Build the "skipped" map | |
| var skipped = new java.util.LinkedHashMap<String, Object>(); | |
| skipped.put("unsupportedSchemaGroup", METRICS.skipUnsupportedGroup.sum()); | |
| skipped.put("testException", METRICS.skipTestException.sum()); | |
| skipped.put("lenientMismatch", METRICS.skipLenientMismatch.sum()); | |
| // Build the "totals" map | |
| var totalsMap = new java.util.LinkedHashMap<String, Object>(); | |
| totalsMap.put("groupsDiscovered", METRICS.groupsDiscovered.sum()); | |
| totalsMap.put("testsDiscovered", METRICS.testsDiscovered.sum()); | |
| totalsMap.put("validationsRun", METRICS.validationsRun.sum()); | |
| totalsMap.put("passed", METRICS.passed.sum()); | |
| totalsMap.put("failed", METRICS.failed.sum()); | |
| totalsMap.put("skipped", skipped); | |
| // Build the "perFile" list | |
| var files = new java.util.ArrayList<String>(METRICS.perFile.keySet()); | |
| java.util.Collections.sort(files); | |
| var perFileList = new java.util.ArrayList<java.util.Map<String, Object>>(); | |
| for (String file : files) { | |
| var counters = METRICS.perFile.get(file); | |
| var fileMap = new java.util.LinkedHashMap<String, Object>(); | |
| fileMap.put("file", file); | |
| fileMap.put("groups", counters.groups.sum()); | |
| fileMap.put("tests", counters.tests.sum()); | |
| fileMap.put("run", counters.run.sum()); | |
| fileMap.put("pass", counters.pass.sum()); | |
| fileMap.put("fail", counters.fail.sum()); | |
| fileMap.put("skipUnsupported", counters.skipUnsupported.sum()); | |
| fileMap.put("skipException", counters.skipException.sum()); | |
| fileMap.put("skipMismatch", counters.skipMismatch.sum()); | |
| perFileList.add(fileMap); | |
| } | |
| // Build the top-level map | |
| var summary = new java.util.LinkedHashMap<String, Object>(); | |
| summary.put("mode", strict ? "STRICT" : "LENIENT"); | |
| summary.put("timestamp", timestamp); | |
| summary.put("totals", totalsMap); | |
| summary.put("perFile", perFileList); | |
| try { | |
| return MAPPER.writeValueAsString(summary); | |
| } catch (Exception e) { | |
| throw new RuntimeException("Failed to serialize JSON summary", e); | |
| } |
| var totals = new StringBuilder(); | ||
| totals.append("{\n"); | ||
| totals.append(" \"mode\": \"").append(strict ? "STRICT" : "LENIENT").append("\",\n"); | ||
| totals.append(" \"timestamp\": \"").append(timestamp).append("\",\n"); | ||
| totals.append(" \"totals\": {\n"); | ||
| totals.append(" \"groupsDiscovered\": ").append(METRICS.groupsDiscovered.sum()).append(",\n"); | ||
| totals.append(" \"testsDiscovered\": ").append(METRICS.testsDiscovered.sum()).append(",\n"); | ||
| totals.append(" \"validationsRun\": ").append(METRICS.validationsRun.sum()).append(",\n"); | ||
| totals.append(" \"passed\": ").append(METRICS.passed.sum()).append(",\n"); | ||
| totals.append(" \"failed\": ").append(METRICS.failed.sum()).append(",\n"); | ||
| totals.append(" \"skipped\": {\n"); | ||
| totals.append(" \"unsupportedSchemaGroup\": ").append(METRICS.skipUnsupportedGroup.sum()).append(",\n"); | ||
| totals.append(" \"testException\": ").append(METRICS.skipTestException.sum()).append(",\n"); | ||
| totals.append(" \"lenientMismatch\": ").append(METRICS.skipLenientMismatch.sum()).append("\n"); | ||
| totals.append(" }\n"); | ||
| totals.append(" },\n"); | ||
| totals.append(" \"perFile\": [\n"); | ||
|
|
||
| var files = new java.util.ArrayList<String>(METRICS.perFile.keySet()); | ||
| java.util.Collections.sort(files); | ||
| var first = true; | ||
| for (String file : files) { | ||
| var counters = METRICS.perFile.get(file); | ||
| if (!first) totals.append(",\n"); | ||
| first = false; | ||
| totals.append(" {\n"); | ||
| totals.append(" \"file\": \"").append(file).append("\",\n"); | ||
| totals.append(" \"groups\": ").append(counters.groups.sum()).append(",\n"); | ||
| totals.append(" \"tests\": ").append(counters.tests.sum()).append(",\n"); | ||
| totals.append(" \"run\": ").append(counters.run.sum()).append(",\n"); | ||
| totals.append(" \"pass\": ").append(counters.pass.sum()).append(",\n"); | ||
| totals.append(" \"fail\": ").append(counters.fail.sum()).append(",\n"); | ||
| totals.append(" \"skipUnsupported\": ").append(counters.skipUnsupported.sum()).append(",\n"); | ||
| totals.append(" \"skipException\": ").append(counters.skipException.sum()).append(",\n"); | ||
| totals.append(" \"skipMismatch\": ").append(counters.skipMismatch.sum()).append("\n"); | ||
| totals.append(" }"); | ||
| } | ||
| totals.append("\n ]\n"); | ||
| totals.append("}\n"); | ||
| return totals.toString(); |
Copilot
AI
Sep 15, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Manual JSON string building is error-prone and hard to maintain. Consider using Jackson ObjectMapper (already available as MAPPER) to serialize a proper data structure instead of manual string concatenation."
| var totals = new StringBuilder(); | |
| totals.append("{\n"); | |
| totals.append(" \"mode\": \"").append(strict ? "STRICT" : "LENIENT").append("\",\n"); | |
| totals.append(" \"timestamp\": \"").append(timestamp).append("\",\n"); | |
| totals.append(" \"totals\": {\n"); | |
| totals.append(" \"groupsDiscovered\": ").append(METRICS.groupsDiscovered.sum()).append(",\n"); | |
| totals.append(" \"testsDiscovered\": ").append(METRICS.testsDiscovered.sum()).append(",\n"); | |
| totals.append(" \"validationsRun\": ").append(METRICS.validationsRun.sum()).append(",\n"); | |
| totals.append(" \"passed\": ").append(METRICS.passed.sum()).append(",\n"); | |
| totals.append(" \"failed\": ").append(METRICS.failed.sum()).append(",\n"); | |
| totals.append(" \"skipped\": {\n"); | |
| totals.append(" \"unsupportedSchemaGroup\": ").append(METRICS.skipUnsupportedGroup.sum()).append(",\n"); | |
| totals.append(" \"testException\": ").append(METRICS.skipTestException.sum()).append(",\n"); | |
| totals.append(" \"lenientMismatch\": ").append(METRICS.skipLenientMismatch.sum()).append("\n"); | |
| totals.append(" }\n"); | |
| totals.append(" },\n"); | |
| totals.append(" \"perFile\": [\n"); | |
| var files = new java.util.ArrayList<String>(METRICS.perFile.keySet()); | |
| java.util.Collections.sort(files); | |
| var first = true; | |
| for (String file : files) { | |
| var counters = METRICS.perFile.get(file); | |
| if (!first) totals.append(",\n"); | |
| first = false; | |
| totals.append(" {\n"); | |
| totals.append(" \"file\": \"").append(file).append("\",\n"); | |
| totals.append(" \"groups\": ").append(counters.groups.sum()).append(",\n"); | |
| totals.append(" \"tests\": ").append(counters.tests.sum()).append(",\n"); | |
| totals.append(" \"run\": ").append(counters.run.sum()).append(",\n"); | |
| totals.append(" \"pass\": ").append(counters.pass.sum()).append(",\n"); | |
| totals.append(" \"fail\": ").append(counters.fail.sum()).append(",\n"); | |
| totals.append(" \"skipUnsupported\": ").append(counters.skipUnsupported.sum()).append(",\n"); | |
| totals.append(" \"skipException\": ").append(counters.skipException.sum()).append(",\n"); | |
| totals.append(" \"skipMismatch\": ").append(counters.skipMismatch.sum()).append("\n"); | |
| totals.append(" }"); | |
| } | |
| totals.append("\n ]\n"); | |
| totals.append("}\n"); | |
| return totals.toString(); | |
| try { | |
| // Root object | |
| java.util.Map<String, Object> root = new java.util.LinkedHashMap<>(); | |
| root.put("mode", strict ? "STRICT" : "LENIENT"); | |
| root.put("timestamp", timestamp); | |
| // Totals object | |
| java.util.Map<String, Object> totals = new java.util.LinkedHashMap<>(); | |
| totals.put("groupsDiscovered", METRICS.groupsDiscovered.sum()); | |
| totals.put("testsDiscovered", METRICS.testsDiscovered.sum()); | |
| totals.put("validationsRun", METRICS.validationsRun.sum()); | |
| totals.put("passed", METRICS.passed.sum()); | |
| totals.put("failed", METRICS.failed.sum()); | |
| java.util.Map<String, Object> skipped = new java.util.LinkedHashMap<>(); | |
| skipped.put("unsupportedSchemaGroup", METRICS.skipUnsupportedGroup.sum()); | |
| skipped.put("testException", METRICS.skipTestException.sum()); | |
| skipped.put("lenientMismatch", METRICS.skipLenientMismatch.sum()); | |
| totals.put("skipped", skipped); | |
| root.put("totals", totals); | |
| // Per-file array | |
| var files = new java.util.ArrayList<String>(METRICS.perFile.keySet()); | |
| java.util.Collections.sort(files); | |
| java.util.List<java.util.Map<String, Object>> perFileList = new java.util.ArrayList<>(); | |
| for (String file : files) { | |
| var counters = METRICS.perFile.get(file); | |
| java.util.Map<String, Object> fileMap = new java.util.LinkedHashMap<>(); | |
| fileMap.put("file", file); | |
| fileMap.put("groups", counters.groups.sum()); | |
| fileMap.put("tests", counters.tests.sum()); | |
| fileMap.put("run", counters.run.sum()); | |
| fileMap.put("pass", counters.pass.sum()); | |
| fileMap.put("fail", counters.fail.sum()); | |
| fileMap.put("skipUnsupported", counters.skipUnsupported.sum()); | |
| fileMap.put("skipException", counters.skipException.sum()); | |
| fileMap.put("skipMismatch", counters.skipMismatch.sum()); | |
| perFileList.add(fileMap); | |
| } | |
| root.put("perFile", perFileList); | |
| // Serialize to pretty JSON | |
| return MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(root); | |
| } catch (Exception e) { | |
| throw new RuntimeException("Failed to build JSON summary", e); | |
| } |
| totals.append(" }"); | ||
| } | ||
| totals.append("\n ]\n"); | ||
| totals.append("}\n"); |
Copilot
AI
Sep 15, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Manual JSON string building is error-prone and hard to maintain. Consider using Jackson ObjectMapper (already available as MAPPER) to serialize a proper data structure instead of manual string concatenation."
| if (!METRICS_FMT.isEmpty()) { | ||
| var outDir = java.nio.file.Path.of("target"); | ||
| java.nio.file.Files.createDirectories(outDir); | ||
| var ts = java.time.OffsetDateTime.now().toString(); |
Copilot
AI
Sep 15, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The timestamp format from OffsetDateTime.toString() may contain characters that are problematic for filenames or JSON values. Consider using a more controlled format like DateTimeFormatter.ISO_INSTANT or a custom format."
- Added escapeJson() method to handle quotes, backslashes, and control characters - Added escapeCsv() method to handle commas, quotes, and newlines - Fixed malformed string literals in escape sequences - Prevents broken output when filenames contain special characters - Ensures downstream parsing of JSON/CSV metrics works correctly The escaping properly handles: - JSON: quotes, backslashes, control characters, Unicode - CSV: commas, quotes, newlines (wraps in quotes when needed)
e18ffdb to
ae661e6
Compare
|
I am going to close this as it was work that was continuing on #30 which is a demo of using things on a real schema. rather that try to merge this as is on main i will get the feedback fixed on the branch for that other ticket. |
Summary
This PR implements comprehensive metrics collection in JsonSchemaCheckIT to provide defensible, repeatable compatibility statistics instead of estimated percentages.
Changes
unsupportedSchemaGroup: Whole groups skipped at compile timetestException: Individual tests that threw exceptionslenientMismatch: Expected≠actual in lenient mode-Djson.schema.metrics=json|csvExample Output
Console Summary (Lenient Mode)
Console Summary (Strict Mode)
Actual Measured Compatibility
63.3% (1,153 of 1,822 tests pass) - replacing the previous estimated claim of 71%
Test Coverage: 420 test groups, 1,657 validation attempts, 576 total skips categorized
Compatibility
Usage
Documentation Updates
Impact
This provides the defensible metrics needed to support compatibility claims: