1 /*
  2  * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.
  8  *
  9  * This code is distributed in the hope that it will be useful, but WITHOUT
 10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 12  * version 2 for more details (a copy is included in the LICENSE file that
 13  * accompanied this code).
 14  *
 15  * You should have received a copy of the GNU General Public License version
 16  * 2 along with this work; if not, write to the Free Software Foundation,
 17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 18  *
 19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 20  * or visit www.oracle.com if you need additional information or have any
 21  * questions.
 22  *
 23  */
 24 
 25 /*
 26  * @test
 27  * @summary "AOT" aliases for traditional CDS command-line options
 28  * @requires vm.cds
 29  * @requires vm.flagless
 30  * @library /test/lib /test/hotspot/jtreg/runtime/cds/appcds/test-classes
 31  * @build Hello
 32  * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar hello.jar Hello
 33  * @run driver AOTFlags
 34  */
 35 
 36 import java.io.File;
 37 import jdk.test.lib.cds.CDSTestUtils;
 38 import jdk.test.lib.helpers.ClassFileInstaller;
 39 import jdk.test.lib.process.OutputAnalyzer;
 40 import jdk.test.lib.process.ProcessTools;
 41 
 42 public class AOTFlags {
 43     static String appJar = ClassFileInstaller.getJarPath("hello.jar");
 44     static String aotConfigFile = "hello.aotconfig";
 45     static String aotCacheFile = "hello.aot";
 46     static String helloClass = "Hello";
 47 
 48     public static void main(String[] args) throws Exception {
 49         positiveTests();
 50         negativeTests();
 51     }
 52 
 53     static void positiveTests() throws Exception {
 54         String hasTrainingDataPattern = "MethodTrainingData *= *[1-9]";
 55         String noTrainingDataPattern = "MethodTrainingData *= *0";
 56         String hasAOTCodePattern = "Shared file region .ac. .: *[1-9]";
 57         String noAOTCodePattern = "Shared file region .ac. .: *0";
 58         String hasMappedAOTCodePattern = "Mapped [0-9]+ bytes at address 0x[0-9a-f]+ from AOT Code Cache";
 59 
 60         //----------------------------------------------------------------------
 61         printTestCase("Training Run");
 62         ProcessBuilder pb = ProcessTools.createLimitedTestJavaProcessBuilder(
 63             "-XX:AOTMode=record",
 64             "-XX:AOTConfiguration=" + aotConfigFile,
 65             "-Xlog:aot=debug",
 66             "-cp", appJar, helloClass);
 67 
 68         OutputAnalyzer out = CDSTestUtils.executeAndLog(pb, "train");
 69         out.shouldContain("Hello World");
 70         out.shouldContain("AOTConfiguration recorded: " + aotConfigFile);
 71         out.shouldMatch(hasTrainingDataPattern);
 72         out.shouldMatch(noAOTCodePattern);
 73         out.shouldHaveExitValue(0);
 74 
 75         //----------------------------------------------------------------------
 76         printTestCase("Assembly Phase (AOTClassLinking unspecified -> should be enabled by default)");
 77         pb = ProcessTools.createLimitedTestJavaProcessBuilder(
 78             "-XX:AOTMode=create",
 79             "-XX:AOTConfiguration=" + aotConfigFile,
 80             "-XX:AOTCache=" + aotCacheFile,
 81             "-Xlog:aot",
 82             "-cp", appJar);
 83         out = CDSTestUtils.executeAndLog(pb, "asm");
 84         out.shouldContain("AOTCache creation is complete");
 85         out.shouldMatch("hello[.]aot");
 86         out.shouldMatch(hasTrainingDataPattern);
 87         out.shouldMatch(hasAOTCodePattern);
 88         out.shouldHaveExitValue(0);
 89 
 90         //----------------------------------------------------------------------
 91         printTestCase("Production Run with AOTCache");
 92         pb = ProcessTools.createLimitedTestJavaProcessBuilder(
 93             "-XX:AOTCache=" + aotCacheFile,
 94             "-Xlog:aot",
 95             "-Xlog:aot+codecache*",
 96             "-cp", appJar, helloClass);
 97         out = CDSTestUtils.executeAndLog(pb, "prod");
 98         out.shouldContain("Using AOT-linked classes: true (static archive: has aot-linked classes)");
 99         out.shouldContain("Opened AOT cache hello.aot.");
100         out.shouldContain("Hello World");
101         out.shouldMatch(hasMappedAOTCodePattern);
102         out.shouldHaveExitValue(0);
103 
104         //----------------------------------------------------------------------
105         printTestCase("AOTMode=off");
106         pb = ProcessTools.createLimitedTestJavaProcessBuilder(
107             "-XX:AOTCache=" + aotCacheFile,
108             "--show-version",
109             "-Xlog:aot",
110             "-XX:AOTMode=off",
111             "-cp", appJar, helloClass);
112         out = CDSTestUtils.executeAndLog(pb, "prod");
113         out.shouldNotContain(", sharing");
114         out.shouldNotContain("Opened AOT cache hello.aot.");
115         out.shouldContain("Hello World");
116         out.shouldHaveExitValue(0);
117 
118         //----------------------------------------------------------------------
119         printTestCase("AOTMode=auto");
120         pb = ProcessTools.createLimitedTestJavaProcessBuilder(
121             "-XX:AOTCache=" + aotCacheFile,
122             "--show-version",
123             "-Xlog:aot",
124             "-XX:AOTMode=auto",
125             "-cp", appJar, helloClass);
126         out = CDSTestUtils.executeAndLog(pb, "prod");
127         out.shouldContain(", sharing");
128         out.shouldContain("Opened AOT cache hello.aot.");
129         out.shouldContain("Hello World");
130         out.shouldHaveExitValue(0);
131 
132         //----------------------------------------------------------------------
133         printTestCase("AOTMode=on");
134         pb = ProcessTools.createLimitedTestJavaProcessBuilder(
135             "-XX:AOTCache=" + aotCacheFile,
136             "--show-version",
137             "-Xlog:aot",
138             "-XX:AOTMode=on",
139             "-cp", appJar, helloClass);
140         out = CDSTestUtils.executeAndLog(pb, "prod");
141         out.shouldContain(", sharing");
142         out.shouldContain("Opened AOT cache hello.aot.");
143         out.shouldContain("Hello World");
144         out.shouldHaveExitValue(0);
145 
146         //----------------------------------------------------------------------
147         printTestCase("Assembly Phase with -XX:-AOTClassLinking");
148         pb = ProcessTools.createLimitedTestJavaProcessBuilder(
149             "-XX:AOTMode=create",
150             "-XX:-AOTClassLinking",
151             "-XX:AOTConfiguration=" + aotConfigFile,
152             "-XX:AOTCache=" + aotCacheFile,
153             "-Xlog:aot=debug",
154             "-cp", appJar);
155         out = CDSTestUtils.executeAndLog(pb, "asm");
156         out.shouldContain("AOTCache creation is complete");
157         out.shouldMatch("hello[.]aot");
158         out.shouldMatch(noTrainingDataPattern);
159         out.shouldMatch(noAOTCodePattern);
160         out.shouldHaveExitValue(0);
161 
162         //----------------------------------------------------------------------
163         printTestCase("Production Run with AOTCache, which was created with -XX:-AOTClassLinking");
164         pb = ProcessTools.createLimitedTestJavaProcessBuilder(
165             "-XX:AOTCache=" + aotCacheFile,
166             "-Xlog:aot",
167             "-Xlog:aot+codecache*",
168             "-cp", appJar, helloClass);
169         out = CDSTestUtils.executeAndLog(pb, "prod");
170         out.shouldContain("Using AOT-linked classes: false (static archive: no aot-linked classes)");
171         out.shouldContain("Opened AOT cache hello.aot.");
172         out.shouldContain("Hello World");
173         out.shouldNotMatch(hasMappedAOTCodePattern);
174         out.shouldHaveExitValue(0);
175 
176         //----------------------------------------------------------------------
177         printTestCase("Training run with -XX:-AOTClassLinking, but assembly run with -XX:+AOTClassLinking");
178         pb = ProcessTools.createLimitedTestJavaProcessBuilder(
179             "-XX:AOTMode=record",
180             "-XX:-AOTClassLinking",
181             "-XX:AOTConfiguration=" + aotConfigFile,
182             "-Xlog:aot=debug",
183             "-cp", appJar, helloClass);
184         out = CDSTestUtils.executeAndLog(pb, "train");
185         out.shouldContain("Hello World");
186         out.shouldContain("AOTConfiguration recorded: " + aotConfigFile);
187         out.shouldHaveExitValue(0);
188 
189         pb = ProcessTools.createLimitedTestJavaProcessBuilder(
190             "-XX:AOTMode=create",
191             "-XX:+AOTClassLinking",
192             "-XX:AOTConfiguration=" + aotConfigFile,
193             "-XX:AOTCache=" + aotCacheFile,
194             "-Xlog:aot=debug",
195             "-cp", appJar);
196         out = CDSTestUtils.executeAndLog(pb, "asm");
197         out.shouldContain("AOTCache creation is complete");
198         out.shouldMatch("hello[.]aot");
199         out.shouldHaveExitValue(0);
200 
201         //----------------------------------------------------------------------
202         printTestCase("One step training run (JEP-514");
203 
204         // Set all AOTMode/AOTCacheOutput/AOTConfiguration
205         pb = ProcessTools.createLimitedTestJavaProcessBuilder(
206             "-XX:AOTMode=record",
207             "-XX:AOTCacheOutput=" + aotCacheFile,
208             "-XX:AOTConfiguration=" + aotConfigFile,
209             "-Xlog:aot=debug",
210             "-cp", appJar, helloClass);
211         out = CDSTestUtils.executeAndLog(pb, "ontstep-train");
212         out.shouldContain("Hello World");
213         out.shouldContain("AOTConfiguration recorded: " + aotConfigFile);
214         out.shouldContain("AOTCache creation is complete: hello.aot");
215         out.shouldHaveExitValue(0);
216 
217         // Set AOTCacheOutput/AOTConfiguration only; Ergo for: AOTMode=record
218         pb = ProcessTools.createLimitedTestJavaProcessBuilder(
219             "-XX:AOTCacheOutput=" + aotCacheFile,
220             "-XX:AOTConfiguration=" + aotConfigFile,
221             "-Xlog:aot=debug",
222             "-cp", appJar, helloClass);
223         out = CDSTestUtils.executeAndLog(pb, "ontstep-train");
224         out.shouldContain("Hello World");
225         out.shouldContain("AOTConfiguration recorded: " + aotConfigFile);
226         out.shouldContain("AOTCache creation is complete: hello.aot");
227         out.shouldHaveExitValue(0);
228 
229         // Set AOTCacheOutput only; Ergo for: AOTMode=record, AOTConfiguration=<temp>
230         pb = ProcessTools.createLimitedTestJavaProcessBuilder(
231             "-XX:AOTCacheOutput=" + aotCacheFile,
232             "-Xlog:aot=debug",
233             "-cp", appJar, helloClass);
234         out = CDSTestUtils.executeAndLog(pb, "ontstep-train");
235         out.shouldContain("Hello World");
236         out.shouldContain("Temporary AOTConfiguration recorded: " + aotCacheFile + ".config");
237         out.shouldContain("AOTCache creation is complete: hello.aot");
238         out.shouldHaveExitValue(0);
239 
240         // Quoating of space characters in child JVM process
241         pb = ProcessTools.createLimitedTestJavaProcessBuilder(
242             "-XX:AOTCacheOutput=" + aotCacheFile,
243             "-Dmy.prop=My string -Xshare:off here", // -Xshare:off should not be treated as a single VM opt for the child JVM
244             "-Xlog:aot=debug",
245             "-cp", appJar, helloClass);
246         out = CDSTestUtils.executeAndLog(pb, "ontstep-train");
247         out.shouldContain("Hello World");
248         out.shouldContain("AOTCache creation is complete: hello.aot");
249         out.shouldMatch("Picked up JAVA_TOOL_OPTIONS:.* -Dmy.prop=My' 'string' '-Xshare:off' 'here");
250         out.shouldHaveExitValue(0);
251 
252         // Training run with -XX:+PrintTieredEvents (see JDK-8362530).
253         printTestCase("Training run with -XX:+PrintTieredEvents");
254         pb = ProcessTools.createLimitedTestJavaProcessBuilder(
255             "-XX:AOTMode=record",
256             "-XX:+PrintTieredEvents",
257             "-XX:AOTConfiguration=" + aotConfigFile,
258             "-cp", appJar, helloClass);
259         out = CDSTestUtils.executeAndLog(pb, "train-with-tiered-events");
260         out.shouldHaveExitValue(0);
261     }
262 
263     static void negativeTests() throws Exception {
264        //----------------------------------------------------------------------
265         printTestCase("Mixing old and new options");
266         String mixOldNewErrSuffix = " cannot be used at the same time with -Xshare:on, -Xshare:auto, "
267             + "-Xshare:off, -Xshare:dump, DumpLoadedClassList, SharedClassListFile, "
268             + "or SharedArchiveFile";
269 
270         ProcessBuilder pb = ProcessTools.createLimitedTestJavaProcessBuilder(
271             "-Xshare:off",
272             "-XX:AOTConfiguration=" + aotConfigFile,
273             "-cp", appJar, helloClass);
274 
275         OutputAnalyzer out = CDSTestUtils.executeAndLog(pb, "neg");
276         out.shouldContain("Option AOTConfiguration" + mixOldNewErrSuffix);
277         out.shouldNotHaveExitValue(0);
278 
279         pb = ProcessTools.createLimitedTestJavaProcessBuilder(
280             "-XX:SharedArchiveFile=" + aotCacheFile,
281             "-XX:AOTCache=" + aotCacheFile,
282             "-cp", appJar, helloClass);
283         out = CDSTestUtils.executeAndLog(pb, "neg");
284         out.shouldContain("Option AOTCache" + mixOldNewErrSuffix);
285         out.shouldNotHaveExitValue(0);
286 
287         //----------------------------------------------------------------------
288         printTestCase("Use AOTConfiguration without AOTMode/AOTCacheOutput");
289         pb = ProcessTools.createLimitedTestJavaProcessBuilder(
290             "-XX:AOTConfiguration=" + aotConfigFile,
291             "-cp", appJar, helloClass);
292 
293         out = CDSTestUtils.executeAndLog(pb, "neg");
294         out.shouldContain("AOTConfiguration can only be used with when AOTMode is record or create (selected AOTMode = auto)");
295         out.shouldNotHaveExitValue(0);
296 
297         //----------------------------------------------------------------------
298         printTestCase("Use AOTConfiguration with AOTMode=on");
299         pb = ProcessTools.createLimitedTestJavaProcessBuilder(
300             "-XX:AOTMode=on",
301             "-XX:AOTConfiguration=" + aotConfigFile,
302             "-cp", appJar, helloClass);
303 
304         out = CDSTestUtils.executeAndLog(pb, "neg");
305         out.shouldContain("AOTConfiguration can only be used with when AOTMode is record or create (selected AOTMode = on)");
306         out.shouldNotHaveExitValue(0);
307 
308         //----------------------------------------------------------------------
309         printTestCase("Use AOTMode without AOTCacheOutput or AOTConfiguration");
310         pb = ProcessTools.createLimitedTestJavaProcessBuilder(
311             "-XX:AOTMode=record",
312             "-cp", appJar, helloClass);
313 
314         out = CDSTestUtils.executeAndLog(pb, "neg");
315         out.shouldContain("At least one of AOTCacheOutput and AOTConfiguration must be specified when using -XX:AOTMode=record");
316 
317         pb = ProcessTools.createLimitedTestJavaProcessBuilder(
318             "-XX:AOTMode=create",
319             "-cp", appJar, helloClass);
320 
321         out = CDSTestUtils.executeAndLog(pb, "neg");
322         out.shouldContain("AOTConfiguration must be specified when using -XX:AOTMode=create");
323         out.shouldNotHaveExitValue(0);
324 
325         //----------------------------------------------------------------------
326         printTestCase("Bad AOTMode");
327         pb = ProcessTools.createLimitedTestJavaProcessBuilder(
328             "-XX:AOTMode=foo",
329             "-cp", appJar, helloClass);
330 
331         out = CDSTestUtils.executeAndLog(pb, "neg");
332         out.shouldContain("Unrecognized value foo for AOTMode. Must be one of the following: off, record, create, auto, on");
333         out.shouldNotHaveExitValue(0);
334 
335         //----------------------------------------------------------------------
336         printTestCase("AOTCache specified with -XX:AOTMode=record");
337         pb = ProcessTools.createLimitedTestJavaProcessBuilder(
338             "-XX:AOTMode=record",
339             "-XX:AOTConfiguration=" + aotConfigFile,
340             "-XX:AOTCache=" + aotCacheFile,
341             "-cp", appJar, helloClass);
342 
343         out = CDSTestUtils.executeAndLog(pb, "neg");
344         out.shouldContain("AOTCache must not be specified when using -XX:AOTMode=record");
345         out.shouldNotHaveExitValue(0);
346 
347         //----------------------------------------------------------------------
348         printTestCase("AOTCache/AOTCacheOutput not specified with -XX:AOTMode=create");
349         pb = ProcessTools.createLimitedTestJavaProcessBuilder(
350             "-XX:AOTMode=create",
351             "-XX:AOTConfiguration=" + aotConfigFile,
352             "-cp", appJar, helloClass);
353 
354         out = CDSTestUtils.executeAndLog(pb, "neg");
355         out.shouldContain("AOTCache or AOTCacheOutput must be specified when using -XX:AOTMode=create");
356         out.shouldNotHaveExitValue(0);
357 
358         //----------------------------------------------------------------------
359         printTestCase("AOTCache and AOTCacheOutput have different values");
360         pb = ProcessTools.createLimitedTestJavaProcessBuilder(
361             "-XX:AOTMode=create",
362             "-XX:AOTConfiguration=" + aotConfigFile,
363             "-XX:AOTCache=aaa",
364             "-XX:AOTCacheOutput=aaa",
365             "-cp", appJar, helloClass);
366 
367         out = CDSTestUtils.executeAndLog(pb, "neg");
368         out.shouldContain("Only one of AOTCache or AOTCacheOutput can be specified");
369         out.shouldNotHaveExitValue(0);
370 
371         //----------------------------------------------------------------------
372         printTestCase("No such config file");
373         pb = ProcessTools.createLimitedTestJavaProcessBuilder(
374             "-XX:AOTMode=create",
375             "-XX:AOTConfiguration=no-such-file",
376             "-XX:AOTCache=" + aotCacheFile,
377             "-cp", appJar, helloClass);
378 
379         out = CDSTestUtils.executeAndLog(pb, "neg");
380         out.shouldContain("Must be a valid AOT configuration generated by the current JVM: no-such-file");
381         out.shouldNotHaveExitValue(0);
382 
383         //----------------------------------------------------------------------
384         printTestCase("AOTConfiguration file cannot be used as a CDS archive");
385 
386         // first make sure we have a valid aotConfigFile
387         pb = ProcessTools.createLimitedTestJavaProcessBuilder(
388             "-XX:AOTMode=record",
389             "-XX:AOTConfiguration=" + aotConfigFile,
390             "-cp", appJar, helloClass);
391 
392         out = CDSTestUtils.executeAndLog(pb, "train");
393         out.shouldHaveExitValue(0);
394 
395         // Cannot use this config file as a AOT cache
396         pb = ProcessTools.createLimitedTestJavaProcessBuilder(
397             "-XX:AOTMode=on",
398             "-XX:AOTCache=" + aotConfigFile,
399             "-cp", appJar, helloClass);
400 
401         out = CDSTestUtils.executeAndLog(pb, "neg");
402         out.shouldContain("Not a valid AOT cache (hello.aotconfig)");
403         out.shouldNotHaveExitValue(0);
404 
405         // Cannot use this config file as a CDS archive
406         pb = ProcessTools.createLimitedTestJavaProcessBuilder(
407             "-Xshare:on",
408             "-XX:SharedArchiveFile=" + aotConfigFile,
409             "-cp", appJar, helloClass);
410 
411         out = CDSTestUtils.executeAndLog(pb, "neg");
412         out.shouldContain("Not a valid shared archive file (hello.aotconfig)");
413         out.shouldNotHaveExitValue(0);
414 
415         //----------------------------------------------------------------------
416         printTestCase("Classpath mismatch when creating archive");
417 
418         pb = ProcessTools.createLimitedTestJavaProcessBuilder(
419             "-XX:AOTMode=create",
420             "-XX:AOTConfiguration=" + aotConfigFile,
421             "-XX:AOTCache=" + aotCacheFile,
422             "-cp", "noSuchJar.jar");
423 
424         out = CDSTestUtils.executeAndLog(pb, "neg");
425         out.shouldContain("class path and/or module path are not compatible with the ones " +
426                           "specified when the AOTConfiguration file was recorded");
427         out.shouldContain("Unable to use create AOT cache");
428         out.shouldHaveExitValue(1);
429 
430         //----------------------------------------------------------------------
431         printTestCase("Cannot use multiple paths in AOTConfiguration");
432 
433         pb = ProcessTools.createLimitedTestJavaProcessBuilder(
434             "-XX:AOTMode=record",
435             "-XX:AOTConfiguration=" + aotConfigFile + File.pathSeparator + "dummy",
436             "-cp", "noSuchJar.jar");
437 
438         out = CDSTestUtils.executeAndLog(pb, "neg");
439         out.shouldContain("Option AOTConfiguration must specify a single file name");
440         out.shouldHaveExitValue(1);
441 
442         //----------------------------------------------------------------------
443         printTestCase("Cannot use multiple paths in AOTCache");
444 
445         pb = ProcessTools.createLimitedTestJavaProcessBuilder(
446             "-XX:AOTMode=create",
447             "-XX:AOTConfiguration=" + aotConfigFile,
448             "-XX:AOTCache=" + aotCacheFile + File.pathSeparator + "dummy",
449             "-cp", "noSuchJar.jar");
450 
451         out = CDSTestUtils.executeAndLog(pb, "neg");
452         out.shouldContain("Option AOTCache must specify a single file name");
453         out.shouldHaveExitValue(1);
454 
455         //----------------------------------------------------------------------
456         printTestCase("Cannot use a dynamic CDS archive for -XX:AOTCache");
457         String staticArchive = "static.jsa";
458         String dynamicArchive = "dynamic.jsa";
459 
460         pb = ProcessTools.createLimitedTestJavaProcessBuilder(
461             "-Xshare:dump",
462             "-XX:SharedArchiveFile=" + staticArchive);
463         out = CDSTestUtils.executeAndLog(pb, "static");
464         out.shouldHaveExitValue(0);
465 
466         pb = ProcessTools.createLimitedTestJavaProcessBuilder(
467             "-XX:SharedArchiveFile=" + staticArchive,
468             "-XX:ArchiveClassesAtExit=" + dynamicArchive,
469             "--version");
470         out = CDSTestUtils.executeAndLog(pb, "dynamic");
471         out.shouldHaveExitValue(0);
472 
473         pb = ProcessTools.createLimitedTestJavaProcessBuilder(
474             "-Xlog:aot",
475             "-XX:AOTMode=on",
476             "-XX:AOTCache=" + dynamicArchive,
477             "--version");
478 
479         out = CDSTestUtils.executeAndLog(pb, "neg");
480         out.shouldContain("Unable to use AOT cache.");
481         out.shouldContain("Not a valid AOT cache (dynamic.jsa)");
482         out.shouldHaveExitValue(1);
483 
484         //----------------------------------------------------------------------
485         testEmptyValue("AOTCache");
486         testEmptyValue("AOTConfiguration");
487         testEmptyValue("AOTMode");
488         testEmptyValue("AOTCacheOutput");
489     }
490 
491     static void testEmptyValue(String option) throws Exception {
492         printTestCase("Empty values for " + option);
493         ProcessBuilder pb = ProcessTools.createLimitedTestJavaProcessBuilder(
494             "-XX:" + option + "=", "--version");
495         OutputAnalyzer out = CDSTestUtils.executeAndLog(pb, "neg");
496         out.shouldContain("Improperly specified VM option '" + option + "='");
497         out.shouldHaveExitValue(1);
498     }
499 
500     static int testNum = 0;
501     static void printTestCase(String s) {
502         System.out.println("vvvvvvv TEST CASE " + testNum + ": " + s + ": starts here vvvvvvv");
503         testNum++;
504     }
505 }