55import jdk .sandbox .java .util .json .Json ;
66import org .junit .jupiter .api .DynamicTest ;
77import org .junit .jupiter .api .TestFactory ;
8+ import org .junit .jupiter .api .AfterAll ;
89import org .junit .jupiter .api .Assumptions ;
910
1011import java .io .File ;
1112import java .nio .file .Files ;
1213import java .nio .file .Path ;
14+ import java .util .concurrent .ConcurrentHashMap ;
15+ import java .util .concurrent .atomic .LongAdder ;
1316import java .util .stream .Stream ;
1417import java .util .stream .StreamSupport ;
1518
@@ -25,6 +28,8 @@ public class JsonSchemaCheckIT {
2528 new File ("target/json-schema-test-suite/tests/draft2020-12" );
2629 private static final ObjectMapper MAPPER = new ObjectMapper ();
2730 private static final boolean STRICT = Boolean .getBoolean ("json.schema.strict" );
31+ private static final String METRICS_FMT = System .getProperty ("json.schema.metrics" , "" ).trim ();
32+ private static final SuiteMetrics METRICS = new SuiteMetrics ();
2833
2934 @ SuppressWarnings ("resource" )
3035 @ TestFactory
@@ -37,6 +42,19 @@ Stream<DynamicTest> runOfficialSuite() throws Exception {
3742 private Stream <DynamicTest > testsFromFile (Path file ) {
3843 try {
3944 JsonNode root = MAPPER .readTree (file .toFile ());
45+
46+ // Count groups and tests discovered
47+ int groupCount = root .size ();
48+ METRICS .groupsDiscovered .add (groupCount );
49+ perFile (file ).groups .add (groupCount );
50+
51+ int testCount = 0 ;
52+ for (JsonNode group : root ) {
53+ testCount += group .get ("tests" ).size ();
54+ }
55+ METRICS .testsDiscovered .add (testCount );
56+ perFile (file ).tests .add (testCount );
57+
4058 return StreamSupport .stream (root .spliterator (), false )
4159 .flatMap (group -> {
4260 String groupDesc = group .get ("description" ).asText ();
@@ -55,29 +73,62 @@ private Stream<DynamicTest> testsFromFile(Path file) {
5573 try {
5674 actual = schema .validate (
5775 Json .parse (test .get ("data" ).toString ())).valid ();
76+
77+ // Count validation attempt
78+ METRICS .validationsRun .increment ();
79+ perFile (file ).run .increment ();
5880 } catch (Exception e ) {
5981 String reason = e .getMessage () == null ? e .getClass ().getSimpleName () : e .getMessage ();
6082 System .err .println ("[JsonSchemaCheckIT] Skipping test due to exception: "
6183 + groupDesc + " — " + reason + " (" + file .getFileName () + ")" );
84+
85+ // Count exception skip
86+ METRICS .skipTestException .increment ();
87+ perFile (file ).skipException .increment ();
88+
6289 if (STRICT ) throw e ;
6390 Assumptions .assumeTrue (false , "Skipped: " + reason );
6491 return ; // not reached when strict
6592 }
6693
6794 if (STRICT ) {
68- assertEquals (expected , actual );
95+ try {
96+ assertEquals (expected , actual );
97+ // Count pass in strict mode
98+ METRICS .passed .increment ();
99+ perFile (file ).pass .increment ();
100+ } catch (AssertionError e ) {
101+ // Count failure in strict mode
102+ METRICS .failed .increment ();
103+ perFile (file ).fail .increment ();
104+ throw e ;
105+ }
69106 } else if (expected != actual ) {
70107 System .err .println ("[JsonSchemaCheckIT] Mismatch (ignored): "
71108 + groupDesc + " — expected=" + expected + ", actual=" + actual
72109 + " (" + file .getFileName () + ")" );
110+
111+ // Count lenient mismatch skip
112+ METRICS .skipLenientMismatch .increment ();
113+ perFile (file ).skipMismatch .increment ();
114+
73115 Assumptions .assumeTrue (false , "Mismatch ignored" );
116+ } else {
117+ // Count pass in lenient mode
118+ METRICS .passed .increment ();
119+ perFile (file ).pass .increment ();
74120 }
75121 }));
76122 } catch (Exception ex ) {
77123 // Unsupported schema for this group; emit a single skipped test for visibility
78124 String reason = ex .getMessage () == null ? ex .getClass ().getSimpleName () : ex .getMessage ();
79125 System .err .println ("[JsonSchemaCheckIT] Skipping group due to unsupported schema: "
80126 + groupDesc + " — " + reason + " (" + file .getFileName () + ")" );
127+
128+ // Count unsupported group skip
129+ METRICS .skipUnsupportedGroup .increment ();
130+ perFile (file ).skipUnsupported .increment ();
131+
81132 return Stream .of (DynamicTest .dynamicTest (
82133 groupDesc + " – SKIPPED: " + reason ,
83134 () -> { if (STRICT ) throw ex ; Assumptions .assumeTrue (false , "Unsupported schema: " + reason ); }
@@ -88,4 +139,146 @@ private Stream<DynamicTest> testsFromFile(Path file) {
88139 throw new RuntimeException ("Failed to process " + file , ex );
89140 }
90141 }
142+
143+ private static SuiteMetrics .FileCounters perFile (Path file ) {
144+ return METRICS .perFile .computeIfAbsent (file .getFileName ().toString (), k -> new SuiteMetrics .FileCounters ());
145+ }
146+
147+ @ AfterAll
148+ static void printAndPersistMetrics () throws Exception {
149+ var strict = STRICT ;
150+ var totalRun = METRICS .validationsRun .sum ();
151+ var passed = METRICS .passed .sum ();
152+ var failed = METRICS .failed .sum ();
153+ var skippedU = METRICS .skipUnsupportedGroup .sum ();
154+ var skippedE = METRICS .skipTestException .sum ();
155+ var skippedM = METRICS .skipLenientMismatch .sum ();
156+
157+ System .out .printf (
158+ "JSON-SCHEMA SUITE (%s): groups=%d testsScanned=%d run=%d passed=%d failed=%d skipped={unsupported=%d, exception=%d, lenientMismatch=%d}%n" ,
159+ strict ? "STRICT" : "LENIENT" ,
160+ METRICS .groupsDiscovered .sum (),
161+ METRICS .testsDiscovered .sum (),
162+ totalRun , passed , failed , skippedU , skippedE , skippedM
163+ );
164+
165+ if (!METRICS_FMT .isEmpty ()) {
166+ var outDir = java .nio .file .Path .of ("target" );
167+ java .nio .file .Files .createDirectories (outDir );
168+ var ts = java .time .OffsetDateTime .now ().toString ();
169+ if ("json" .equalsIgnoreCase (METRICS_FMT )) {
170+ var json = buildJsonSummary (strict , ts );
171+ java .nio .file .Files .writeString (outDir .resolve ("json-schema-compat.json" ), json );
172+ } else if ("csv" .equalsIgnoreCase (METRICS_FMT )) {
173+ var csv = buildCsvSummary (strict , ts );
174+ java .nio .file .Files .writeString (outDir .resolve ("json-schema-compat.csv" ), csv );
175+ }
176+ }
177+ }
178+
179+ private static String buildJsonSummary (boolean strict , String timestamp ) {
180+ var totals = new StringBuilder ();
181+ totals .append ("{\n " );
182+ totals .append (" \" mode\" : \" " ).append (strict ? "STRICT" : "LENIENT" ).append ("\" ,\n " );
183+ totals .append (" \" timestamp\" : \" " ).append (timestamp ).append ("\" ,\n " );
184+ totals .append (" \" totals\" : {\n " );
185+ totals .append (" \" groupsDiscovered\" : " ).append (METRICS .groupsDiscovered .sum ()).append (",\n " );
186+ totals .append (" \" testsDiscovered\" : " ).append (METRICS .testsDiscovered .sum ()).append (",\n " );
187+ totals .append (" \" validationsRun\" : " ).append (METRICS .validationsRun .sum ()).append (",\n " );
188+ totals .append (" \" passed\" : " ).append (METRICS .passed .sum ()).append (",\n " );
189+ totals .append (" \" failed\" : " ).append (METRICS .failed .sum ()).append (",\n " );
190+ totals .append (" \" skipped\" : {\n " );
191+ totals .append (" \" unsupportedSchemaGroup\" : " ).append (METRICS .skipUnsupportedGroup .sum ()).append (",\n " );
192+ totals .append (" \" testException\" : " ).append (METRICS .skipTestException .sum ()).append (",\n " );
193+ totals .append (" \" lenientMismatch\" : " ).append (METRICS .skipLenientMismatch .sum ()).append ("\n " );
194+ totals .append (" }\n " );
195+ totals .append (" },\n " );
196+ totals .append (" \" perFile\" : [\n " );
197+
198+ var files = new java .util .ArrayList <String >(METRICS .perFile .keySet ());
199+ java .util .Collections .sort (files );
200+ var first = true ;
201+ for (String file : files ) {
202+ var counters = METRICS .perFile .get (file );
203+ if (!first ) totals .append (",\n " );
204+ first = false ;
205+ totals .append (" {\n " );
206+ totals .append (" \" file\" : \" " ).append (file ).append ("\" ,\n " );
207+ totals .append (" \" groups\" : " ).append (counters .groups .sum ()).append (",\n " );
208+ totals .append (" \" tests\" : " ).append (counters .tests .sum ()).append (",\n " );
209+ totals .append (" \" run\" : " ).append (counters .run .sum ()).append (",\n " );
210+ totals .append (" \" pass\" : " ).append (counters .pass .sum ()).append (",\n " );
211+ totals .append (" \" fail\" : " ).append (counters .fail .sum ()).append (",\n " );
212+ totals .append (" \" skipUnsupported\" : " ).append (counters .skipUnsupported .sum ()).append (",\n " );
213+ totals .append (" \" skipException\" : " ).append (counters .skipException .sum ()).append (",\n " );
214+ totals .append (" \" skipMismatch\" : " ).append (counters .skipMismatch .sum ()).append ("\n " );
215+ totals .append (" }" );
216+ }
217+ totals .append ("\n ]\n " );
218+ totals .append ("}\n " );
219+ return totals .toString ();
220+ }
221+
222+ private static String buildCsvSummary (boolean strict , String timestamp ) {
223+ var csv = new StringBuilder ();
224+ csv .append ("mode,timestamp,groupsDiscovered,testsDiscovered,validationsRun,passed,failed,skipUnsupportedGroup,skipTestException,skipLenientMismatch\n " );
225+ csv .append (strict ? "STRICT" : "LENIENT" ).append ("," );
226+ csv .append (timestamp ).append ("," );
227+ csv .append (METRICS .groupsDiscovered .sum ()).append ("," );
228+ csv .append (METRICS .testsDiscovered .sum ()).append ("," );
229+ csv .append (METRICS .validationsRun .sum ()).append ("," );
230+ csv .append (METRICS .passed .sum ()).append ("," );
231+ csv .append (METRICS .failed .sum ()).append ("," );
232+ csv .append (METRICS .skipUnsupportedGroup .sum ()).append ("," );
233+ csv .append (METRICS .skipTestException .sum ()).append ("," );
234+ csv .append (METRICS .skipLenientMismatch .sum ()).append ("\n " );
235+
236+ csv .append ("\n perFile breakdown:\n " );
237+ csv .append ("file,groups,tests,run,pass,fail,skipUnsupported,skipException,skipMismatch\n " );
238+
239+ var files = new java .util .ArrayList <String >(METRICS .perFile .keySet ());
240+ java .util .Collections .sort (files );
241+ for (String file : files ) {
242+ var counters = METRICS .perFile .get (file );
243+ csv .append (file ).append ("," );
244+ csv .append (counters .groups .sum ()).append ("," );
245+ csv .append (counters .tests .sum ()).append ("," );
246+ csv .append (counters .run .sum ()).append ("," );
247+ csv .append (counters .pass .sum ()).append ("," );
248+ csv .append (counters .fail .sum ()).append ("," );
249+ csv .append (counters .skipUnsupported .sum ()).append ("," );
250+ csv .append (counters .skipException .sum ()).append ("," );
251+ csv .append (counters .skipMismatch .sum ()).append ("\n " );
252+ }
253+ return csv .toString ();
254+ }
255+ }
256+
257+ /**
258+ * Thread-safe metrics container for the JSON Schema Test Suite run.
259+ */
260+ final class SuiteMetrics {
261+ final LongAdder groupsDiscovered = new LongAdder ();
262+ final LongAdder testsDiscovered = new LongAdder ();
263+
264+ final LongAdder validationsRun = new LongAdder (); // attempted validations
265+ final LongAdder passed = new LongAdder ();
266+ final LongAdder failed = new LongAdder ();
267+
268+ final LongAdder skipUnsupportedGroup = new LongAdder ();
269+ final LongAdder skipTestException = new LongAdder (); // lenient only
270+ final LongAdder skipLenientMismatch = new LongAdder (); // lenient only
271+
272+ final ConcurrentHashMap <String , FileCounters > perFile = new ConcurrentHashMap <>();
273+
274+ static final class FileCounters {
275+ final LongAdder groups = new LongAdder ();
276+ final LongAdder tests = new LongAdder ();
277+ final LongAdder run = new LongAdder ();
278+ final LongAdder pass = new LongAdder ();
279+ final LongAdder fail = new LongAdder ();
280+ final LongAdder skipUnsupported = new LongAdder ();
281+ final LongAdder skipException = new LongAdder ();
282+ final LongAdder skipMismatch = new LongAdder ();
283+ }
91284}
0 commit comments