1 /*
2  * Copyright (c) 2015, 2018, 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 import java.io.*;
25 import java.lang.reflect.Method;
26 import java.nio.file.Files;
27 import java.nio.file.Path;
28 import java.nio.file.Paths;
29 import java.util.ArrayList;
30 import java.util.Arrays;
31 import java.util.List;
32 import java.util.function.Consumer;
33 import java.util.jar.JarEntry;
34 import java.util.jar.JarInputStream;
35 import java.util.jar.JarOutputStream;
36 import java.util.stream.Stream;
37 
38 import jdk.test.lib.util.FileUtils;
39 import jdk.test.lib.JDKToolFinder;
40 import org.testng.annotations.BeforeTest;
41 import org.testng.annotations.Test;
42 
43 import static java.lang.String.format;
44 import static java.lang.System.out;
45 import static java.nio.charset.StandardCharsets.UTF_8;
46 import static org.testng.Assert.assertFalse;
47 import static org.testng.Assert.assertTrue;
48 
49 /*
50  * @test
51  * @bug 8170952
52  * @library /lib/testlibrary /test/lib
53  * @build jdk.test.lib.Platform
54  *        jdk.test.lib.util.FileUtils
55  *        jdk.test.lib.JDKToolFinder
56  * @run testng CLICompatibility
57  * @summary Basic test for compatibility of CLI options
58  */
59 
60 public class CLICompatibility {
61     static final Path TEST_CLASSES = Paths.get(System.getProperty("test.classes", "."));
62     static final Path USER_DIR = Paths.get(System.getProperty("user.dir"));
63 
64     static final String TOOL_VM_OPTIONS = System.getProperty("test.tool.vm.opts", "");
65 
66     final boolean legacyOnly;  // for running on older JDK's ( test validation )
67 
68     // Resources we know to exist, that can be used for creating jar files.
69     static final String RES1 = "CLICompatibility.class";
70     static final String RES2 = "CLICompatibility$Result.class";
71 
72     @BeforeTest
setupResourcesForJar()73     public void setupResourcesForJar() throws Exception {
74         // Copy the files that we are going to use for creating/updating test
75         // jar files, so that they can be referred to without '-C dir'
76         Files.copy(TEST_CLASSES.resolve(RES1), USER_DIR.resolve(RES1));
77         Files.copy(TEST_CLASSES.resolve(RES2), USER_DIR.resolve(RES2));
78     }
79 
80     static final IOConsumer<InputStream> ASSERT_CONTAINS_RES1 = in -> {
81         try (JarInputStream jin = new JarInputStream(in)) {
82             assertTrue(jarContains(jin, RES1), "Failed to find " + RES1);
83         }
84     };
85     static final IOConsumer<InputStream> ASSERT_CONTAINS_RES2 = in -> {
86         try (JarInputStream jin = new JarInputStream(in)) {
87             assertTrue(jarContains(jin, RES2), "Failed to find " + RES2);
88         }
89     };
90     static final IOConsumer<InputStream> ASSERT_CONTAINS_MAINFEST = in -> {
91         try (JarInputStream jin = new JarInputStream(in)) {
92             assertTrue(jin.getManifest() != null, "No META-INF/MANIFEST.MF");
93         }
94     };
95     static final IOConsumer<InputStream> ASSERT_DOES_NOT_CONTAIN_MAINFEST = in -> {
96         try (JarInputStream jin = new JarInputStream(in)) {
97             assertTrue(jin.getManifest() == null, "Found unexpected META-INF/MANIFEST.MF");
98         }
99     };
100 
101     static final FailCheckerWithMessage FAIL_TOO_MANY_MAIN_OPS =
102         new FailCheckerWithMessage("You may not specify more than one '-cuxtid' options",
103         /* legacy */ "{ctxui}[vfmn0Me] [jar-file] [manifest-file] [entry-point] [-C dir] files");
104 
105     // Create
106 
107     @Test
createBadArgs()108     public void createBadArgs() {
109         final FailCheckerWithMessage FAIL_CREATE_NO_ARGS = new FailCheckerWithMessage(
110                 "'c' flag requires manifest or input files to be specified!");
111 
112         jar("c")
113             .assertFailure()
114             .resultChecker(FAIL_CREATE_NO_ARGS);
115 
116         jar("-c")
117             .assertFailure()
118             .resultChecker(FAIL_CREATE_NO_ARGS);
119 
120         if (!legacyOnly)
121             jar("--create")
122                 .assertFailure()
123                 .resultChecker(FAIL_CREATE_NO_ARGS);
124 
125         jar("ct")
126             .assertFailure()
127             .resultChecker(FAIL_TOO_MANY_MAIN_OPS);
128 
129         jar("-ct")
130             .assertFailure()
131             .resultChecker(FAIL_TOO_MANY_MAIN_OPS);
132 
133         if (!legacyOnly)
134             jar("--create --list")
135                 .assertFailure()
136                 .resultChecker(FAIL_TOO_MANY_MAIN_OPS);
137     }
138 
139     @Test
createWriteToFile()140     public void createWriteToFile() throws IOException {
141         Path path = Paths.get("createJarFile.jar");  // for creating
142         String jn = path.toString();
143         for (String opts : new String[]{"cf " + jn, "-cf " + jn, "--create --file=" + jn}) {
144             if (legacyOnly && opts.startsWith("--"))
145                 continue;
146 
147             jar(opts, RES1)
148                 .assertSuccess()
149                 .resultChecker(r -> {
150                     ASSERT_CONTAINS_RES1.accept(Files.newInputStream(path));
151                     ASSERT_CONTAINS_MAINFEST.accept(Files.newInputStream(path));
152                 });
153         }
154         FileUtils.deleteFileIfExistsWithRetry(path);
155     }
156 
157     @Test
createWriteToStdout()158     public void createWriteToStdout() throws IOException {
159         for (String opts : new String[]{"c", "-c", "--create"}) {
160             if (legacyOnly && opts.startsWith("--"))
161                 continue;
162 
163             jar(opts, RES1)
164                 .assertSuccess()
165                 .resultChecker(r -> {
166                     ASSERT_CONTAINS_RES1.accept(r.stdoutAsStream());
167                     ASSERT_CONTAINS_MAINFEST.accept(r.stdoutAsStream());
168                 });
169         }
170     }
171 
172     @Test
createWriteToStdoutNoManifest()173     public void createWriteToStdoutNoManifest() throws IOException {
174         for (String opts : new String[]{"cM", "-cM", "--create --no-manifest"} ){
175             if (legacyOnly && opts.startsWith("--"))
176                 continue;
177 
178             jar(opts, RES1)
179                 .assertSuccess()
180                 .resultChecker(r -> {
181                     ASSERT_CONTAINS_RES1.accept(r.stdoutAsStream());
182                     ASSERT_DOES_NOT_CONTAIN_MAINFEST.accept(r.stdoutAsStream());
183                 });
184         }
185     }
186 
187     // Update
188 
189     @Test
updateBadArgs()190     public void updateBadArgs() {
191         final FailCheckerWithMessage FAIL_UPDATE_NO_ARGS = new FailCheckerWithMessage(
192                 "'u' flag requires manifest, 'e' flag or input files to be specified!");
193 
194         jar("u")
195             .assertFailure()
196             .resultChecker(FAIL_UPDATE_NO_ARGS);
197 
198         jar("-u")
199             .assertFailure()
200             .resultChecker(FAIL_UPDATE_NO_ARGS);
201 
202         if (!legacyOnly)
203             jar("--update")
204                 .assertFailure()
205                 .resultChecker(FAIL_UPDATE_NO_ARGS);
206 
207         jar("ut")
208             .assertFailure()
209             .resultChecker(FAIL_TOO_MANY_MAIN_OPS);
210 
211         jar("-ut")
212             .assertFailure()
213             .resultChecker(FAIL_TOO_MANY_MAIN_OPS);
214 
215         if (!legacyOnly)
216             jar("--update --list")
217                 .assertFailure()
218                 .resultChecker(FAIL_TOO_MANY_MAIN_OPS);
219     }
220 
221     @Test
updateReadFileWriteFile()222     public void updateReadFileWriteFile() throws IOException {
223         Path path = Paths.get("updateReadWriteStdout.jar");  // for updating
224         String jn = path.toString();
225 
226         for (String opts : new String[]{"uf " + jn, "-uf " + jn, "--update --file=" + jn}) {
227             if (legacyOnly && opts.startsWith("--"))
228                 continue;
229 
230             createJar(path, RES1);
231             jar(opts, RES2)
232                 .assertSuccess()
233                 .resultChecker(r -> {
234                     ASSERT_CONTAINS_RES1.accept(Files.newInputStream(path));
235                     ASSERT_CONTAINS_RES2.accept(Files.newInputStream(path));
236                     ASSERT_CONTAINS_MAINFEST.accept(Files.newInputStream(path));
237                 });
238         }
239         FileUtils.deleteFileIfExistsWithRetry(path);
240     }
241 
242     @Test
updateReadStdinWriteStdout()243     public void updateReadStdinWriteStdout() throws IOException {
244         Path path = Paths.get("updateReadStdinWriteStdout.jar");
245 
246         for (String opts : new String[]{"u", "-u", "--update"}) {
247             if (legacyOnly && opts.startsWith("--"))
248                 continue;
249 
250             createJar(path, RES1);
251             jarWithStdin(path.toFile(), opts, RES2)
252                 .assertSuccess()
253                 .resultChecker(r -> {
254                     ASSERT_CONTAINS_RES1.accept(r.stdoutAsStream());
255                     ASSERT_CONTAINS_RES2.accept(r.stdoutAsStream());
256                     ASSERT_CONTAINS_MAINFEST.accept(r.stdoutAsStream());
257                 });
258         }
259         FileUtils.deleteFileIfExistsWithRetry(path);
260     }
261 
262     @Test
updateReadStdinWriteStdoutNoManifest()263     public void updateReadStdinWriteStdoutNoManifest() throws IOException {
264         Path path = Paths.get("updateReadStdinWriteStdoutNoManifest.jar");
265 
266         for (String opts : new String[]{"uM", "-uM", "--update --no-manifest"} ){
267             if (legacyOnly && opts.startsWith("--"))
268                 continue;
269 
270             createJar(path, RES1);
271             jarWithStdin(path.toFile(), opts, RES2)
272                 .assertSuccess()
273                 .resultChecker(r -> {
274                     ASSERT_CONTAINS_RES1.accept(r.stdoutAsStream());
275                     ASSERT_CONTAINS_RES2.accept(r.stdoutAsStream());
276                     ASSERT_DOES_NOT_CONTAIN_MAINFEST.accept(r.stdoutAsStream());
277                 });
278         }
279         FileUtils.deleteFileIfExistsWithRetry(path);
280     }
281 
282     // List
283 
284     @Test
listBadArgs()285     public void listBadArgs() {
286         jar("tx")
287             .assertFailure()
288             .resultChecker(FAIL_TOO_MANY_MAIN_OPS);
289 
290         jar("-tx")
291             .assertFailure()
292             .resultChecker(FAIL_TOO_MANY_MAIN_OPS);
293 
294         if (!legacyOnly)
295             jar("--list --extract")
296                 .assertFailure()
297                 .resultChecker(FAIL_TOO_MANY_MAIN_OPS);
298     }
299 
300     @Test
listReadFromFileWriteToStdout()301     public void listReadFromFileWriteToStdout() throws IOException {
302         Path path = Paths.get("listReadFromFileWriteToStdout.jar");  // for listing
303         createJar(path, RES1);
304         String jn = path.toString();
305 
306         for (String opts : new String[]{"tf " + jn, "-tf " + jn, "--list --file " + jn}) {
307             if (legacyOnly && opts.startsWith("--"))
308                 continue;
309 
310             jar(opts)
311                 .assertSuccess()
312                 .resultChecker(r ->
313                     assertTrue(r.output.contains("META-INF/MANIFEST.MF") && r.output.contains(RES1),
314                                "Failed, got [" + r.output + "]")
315                 );
316         }
317         FileUtils.deleteFileIfExistsWithRetry(path);
318     }
319 
320     @Test
listReadFromStdinWriteToStdout()321     public void listReadFromStdinWriteToStdout() throws IOException {
322         Path path = Paths.get("listReadFromStdinWriteToStdout.jar");
323         createJar(path, RES1);
324 
325         for (String opts : new String[]{"t", "-t", "--list"} ){
326             if (legacyOnly && opts.startsWith("--"))
327                 continue;
328 
329             jarWithStdin(path.toFile(), opts)
330                 .assertSuccess()
331                 .resultChecker(r ->
332                     assertTrue(r.output.contains("META-INF/MANIFEST.MF") && r.output.contains(RES1),
333                                "Failed, got [" + r.output + "]")
334                 );
335         }
336         FileUtils.deleteFileIfExistsWithRetry(path);
337     }
338 
339     // Extract
340 
341     @Test
extractBadArgs()342     public void extractBadArgs() {
343         jar("xi")
344             .assertFailure()
345             .resultChecker(FAIL_TOO_MANY_MAIN_OPS);
346 
347         jar("-xi")
348             .assertFailure()
349             .resultChecker(FAIL_TOO_MANY_MAIN_OPS);
350 
351         if (!legacyOnly) {
352             jar("--extract --generate-index")
353                 .assertFailure()
354                 .resultChecker(new FailCheckerWithMessage(
355                                    "option --generate-index requires an argument"));
356 
357             jar("--extract --generate-index=foo")
358                 .assertFailure()
359                 .resultChecker(FAIL_TOO_MANY_MAIN_OPS);
360         }
361     }
362 
363     @Test
extractReadFromStdin()364     public void extractReadFromStdin() throws IOException {
365         Path path = Paths.get("extract");
366         Path jarPath = path.resolve("extractReadFromStdin.jar"); // for extracting
367         createJar(jarPath, RES1);
368 
369         for (String opts : new String[]{"x" ,"-x", "--extract"}) {
370             if (legacyOnly && opts.startsWith("--"))
371                 continue;
372 
373             jarWithStdinAndWorkingDir(jarPath.toFile(), path.toFile(), opts)
374                 .assertSuccess()
375                 .resultChecker(r ->
376                     assertTrue(Files.exists(path.resolve(RES1)),
377                                "Expected to find:" + path.resolve(RES1))
378                 );
379             FileUtils.deleteFileIfExistsWithRetry(path.resolve(RES1));
380         }
381         FileUtils.deleteFileTreeWithRetry(path);
382     }
383 
384     @Test
extractReadFromFile()385     public void extractReadFromFile() throws IOException {
386         Path path = Paths.get("extract");
387         String jn = "extractReadFromFile.jar";
388         Path jarPath = path.resolve(jn);
389         createJar(jarPath, RES1);
390 
391         for (String opts : new String[]{"xf "+jn ,"-xf "+jn, "--extract --file "+jn}) {
392             if (legacyOnly && opts.startsWith("--"))
393                 continue;
394 
395             jarWithStdinAndWorkingDir(null, path.toFile(), opts)
396                 .assertSuccess()
397                 .resultChecker(r ->
398                     assertTrue(Files.exists(path.resolve(RES1)),
399                                "Expected to find:" + path.resolve(RES1))
400                 );
401             FileUtils.deleteFileIfExistsWithRetry(path.resolve(RES1));
402         }
403         FileUtils.deleteFileTreeWithRetry(path);
404     }
405 
406     // Basic help
407 
408     @Test
helpBadOptionalArg()409     public void helpBadOptionalArg() {
410         if (legacyOnly)
411             return;
412 
413         jar("--help:")
414             .assertFailure();
415 
416         jar("--help:blah")
417             .assertFailure();
418     }
419 
420     @Test
help()421     public void help() {
422         if (legacyOnly)
423             return;
424 
425         jar("-h")
426             .assertSuccess()
427             .resultChecker(r ->
428                 assertTrue(r.output.startsWith("Usage: jar [OPTION...] [ [--release VERSION] [-C dir] files]"),
429                            "Failed, got [" + r.output + "]")
430             );
431 
432         jar("--help")
433             .assertSuccess()
434             .resultChecker(r -> {
435                 assertTrue(r.output.startsWith("Usage: jar [OPTION...] [ [--release VERSION] [-C dir] files]"),
436                            "Failed, got [" + r.output + "]");
437                 assertFalse(r.output.contains("--do-not-resolve-by-default"));
438                 assertFalse(r.output.contains("--warn-if-resolved"));
439             });
440 
441         jar("--help:compat")
442             .assertSuccess()
443             .resultChecker(r ->
444                 assertTrue(r.output.startsWith("Compatibility Interface:"),
445                            "Failed, got [" + r.output + "]")
446             );
447 
448         jar("--help-extra")
449             .assertSuccess()
450             .resultChecker(r -> {
451                 assertTrue(r.output.startsWith("Usage: jar [OPTION...] [ [--release VERSION] [-C dir] files]"),
452                            "Failed, got [" + r.output + "]");
453                 assertTrue(r.output.contains("--do-not-resolve-by-default"));
454                 assertTrue(r.output.contains("--warn-if-resolved"));
455             });
456     }
457 
458     // -- Infrastructure
459 
jarContains(JarInputStream jis, String entryName)460     static boolean jarContains(JarInputStream jis, String entryName)
461         throws IOException
462     {
463         JarEntry e;
464         boolean found = false;
465         while((e = jis.getNextJarEntry()) != null) {
466             if (e.getName().equals(entryName))
467                 return true;
468         }
469         return false;
470     }
471 
472     /* Creates a simple jar with entries of size 0, good enough for testing */
createJar(Path path, String... entries)473     static void createJar(Path path, String... entries) throws IOException {
474         FileUtils.deleteFileIfExistsWithRetry(path);
475         Path parent = path.getParent();
476         if (parent != null)
477             Files.createDirectories(parent);
478         try (OutputStream out = Files.newOutputStream(path);
479              JarOutputStream jos = new JarOutputStream(out)) {
480             JarEntry je = new JarEntry("META-INF/MANIFEST.MF");
481             jos.putNextEntry(je);
482             jos.closeEntry();
483 
484             for (String entry : entries) {
485                 je = new JarEntry(entry);
486                 jos.putNextEntry(je);
487                 jos.closeEntry();
488             }
489         }
490     }
491 
492     static class FailCheckerWithMessage implements Consumer<Result> {
493         final String[] messages;
FailCheckerWithMessage(String... m)494         FailCheckerWithMessage(String... m) {
495             messages = m;
496         }
497         @Override
accept(Result r)498         public void accept(Result r) {
499             //out.printf("%s%n", r.output);
500             boolean found = false;
501             for (String m : messages) {
502                 if (r.output.contains(m)) {
503                     found = true;
504                     break;
505                 }
506             }
507             assertTrue(found,
508                        "Excepted out to contain one of: " + Arrays.asList(messages)
509                            + " but got: " + r.output);
510         }
511     }
512 
jar(String... args)513     static Result jar(String... args) {
514         return jarWithStdinAndWorkingDir(null, null, args);
515     }
516 
jarWithStdin(File stdinSource, String... args)517     static Result jarWithStdin(File stdinSource, String... args) {
518         return jarWithStdinAndWorkingDir(stdinSource, null, args);
519     }
520 
jarWithStdinAndWorkingDir(File stdinFrom, File workingDir, String... args)521     static Result jarWithStdinAndWorkingDir(File stdinFrom,
522                                             File workingDir,
523                                             String... args) {
524         String jar = getJDKTool("jar");
525         List<String> commands = new ArrayList<>();
526         commands.add(jar);
527         if (!TOOL_VM_OPTIONS.isEmpty()) {
528             commands.addAll(Arrays.asList(TOOL_VM_OPTIONS.split("\\s+", -1)));
529         }
530         Stream.of(args).map(s -> s.split(" "))
531                        .flatMap(Arrays::stream)
532                        .forEach(x -> commands.add(x));
533         ProcessBuilder p = new ProcessBuilder(commands);
534         if (stdinFrom != null)
535             p.redirectInput(stdinFrom);
536         if (workingDir != null)
537             p.directory(workingDir);
538         return run(p);
539     }
540 
run(ProcessBuilder pb)541     static Result run(ProcessBuilder pb) {
542         Process p;
543         byte[] stdout, stderr;
544         out.printf("Running: %s%n", pb.command());
545         try {
546             p = pb.start();
547         } catch (IOException e) {
548             throw new RuntimeException(
549                     format("Couldn't start process '%s'", pb.command()), e);
550         }
551 
552         String output;
553         try {
554             stdout = readAllBytes(p.getInputStream());
555             stderr = readAllBytes(p.getErrorStream());
556 
557             output = toString(stdout, stderr);
558         } catch (IOException e) {
559             throw new RuntimeException(
560                     format("Couldn't read process output '%s'", pb.command()), e);
561         }
562 
563         try {
564             p.waitFor();
565         } catch (InterruptedException e) {
566             throw new RuntimeException(
567                     format("Process hasn't finished '%s'", pb.command()), e);
568         }
569         return new Result(p.exitValue(), stdout, stderr, output);
570     }
571 
572     static final Path JAVA_HOME = Paths.get(System.getProperty("java.home"));
573 
getJDKTool(String name)574     static String getJDKTool(String name) {
575         try {
576             return JDKToolFinder.getJDKTool(name);
577         } catch (Exception x) {
578             Path j = JAVA_HOME.resolve("bin").resolve(name);
579             if (Files.exists(j))
580                 return j.toString();
581             j = JAVA_HOME.resolve("..").resolve("bin").resolve(name);
582             if (Files.exists(j))
583                 return j.toString();
584             throw new RuntimeException(x);
585         }
586     }
587 
toString(byte[] ba1, byte[] ba2)588     static String toString(byte[] ba1, byte[] ba2) {
589         return (new String(ba1, UTF_8)).concat(new String(ba2, UTF_8));
590     }
591 
592     static class Result {
593         final int exitValue;
594         final byte[] stdout;
595         final byte[] stderr;
596         final String output;
597 
Result(int exitValue, byte[] stdout, byte[] stderr, String output)598         private Result(int exitValue, byte[] stdout, byte[] stderr, String output) {
599             this.exitValue = exitValue;
600             this.stdout = stdout;
601             this.stderr = stderr;
602             this.output = output;
603         }
604 
stdoutAsStream()605         InputStream stdoutAsStream() { return new ByteArrayInputStream(stdout); }
606 
assertSuccess()607         Result assertSuccess() { assertTrue(exitValue == 0, output); return this; }
assertFailure()608         Result assertFailure() { assertTrue(exitValue != 0, output); return this; }
609 
resultChecker(IOConsumer<Result> r)610         Result resultChecker(IOConsumer<Result> r) {
611             try {  r.accept(this); return this; }
612             catch (IOException x) { throw new UncheckedIOException(x); }
613         }
614 
resultChecker(FailCheckerWithMessage c)615         Result resultChecker(FailCheckerWithMessage c) { c.accept(this); return this; }
616     }
617 
accept(T t)618     interface IOConsumer<T> { void accept(T t) throws IOException ;  }
619 
620     // readAllBytes implementation so the test can be run pre 1.9 ( legacyOnly )
readAllBytes(InputStream is)621     static byte[] readAllBytes(InputStream is) throws IOException {
622         byte[] buf = new byte[8192];
623         int capacity = buf.length;
624         int nread = 0;
625         int n;
626         for (;;) {
627             // read to EOF which may read more or less than initial buffer size
628             while ((n = is.read(buf, nread, capacity - nread)) > 0)
629                 nread += n;
630 
631             // if the last call to read returned -1, then we're done
632             if (n < 0)
633                 break;
634 
635             // need to allocate a larger buffer
636             capacity = capacity << 1;
637 
638             buf = Arrays.copyOf(buf, capacity);
639         }
640         return (capacity == nread) ? buf : Arrays.copyOf(buf, nread);
641     }
642 
643     // Standalone entry point for running with, possibly older, JDKs.
main(String[] args)644     public static void main(String[] args) throws Throwable {
645         boolean legacyOnly = false;
646         if (args.length != 0 && args[0].equals("legacyOnly"))
647             legacyOnly = true;
648 
649         CLICompatibility test = new CLICompatibility(legacyOnly);
650         for (Method m : CLICompatibility.class.getDeclaredMethods()) {
651             if (m.getAnnotation(Test.class) != null) {
652                 System.out.println("Invoking " + m.getName());
653                 m.invoke(test);
654             }
655         }
656     }
CLICompatibility(boolean legacyOnly)657     CLICompatibility(boolean legacyOnly) { this.legacyOnly = legacyOnly; }
CLICompatibility()658     CLICompatibility() { this.legacyOnly = false; }
659 }
660