1/*
2 * Copyright (c) 2016, 2017, 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 * @test
26 * @library /test/lib
27 * @modules java.base/jdk.internal.misc
28 *          jdk.compiler
29 *          jdk.jartool
30 * @build jdk.test.lib.util.FileUtils
31 *        jdk.test.lib.Utils
32 *        jdk.test.lib.Asserts
33 *        jdk.test.lib.JDKToolFinder
34 *        jdk.test.lib.JDKToolLauncher
35 *        jdk.test.lib.Platform
36 *        jdk.test.lib.process.*
37 *        MRTestBase
38 * @run testng Basic
39 */
40
41import static org.testng.Assert.*;
42
43import jdk.test.lib.util.FileUtils;
44import org.testng.annotations.*;
45
46import java.io.File;
47import java.nio.file.*;
48import java.util.*;
49import java.util.jar.JarFile;
50import java.util.zip.ZipFile;
51
52public class Basic extends MRTestBase {
53
54    @Test
55    // create a regular, non-multi-release jar
56    public void test00() throws Throwable {
57        String jarfile = "test.jar";
58
59        compile("test01");  //use same data as test01
60
61        Path classes = Paths.get("classes");
62        jar("cf", jarfile, "-C", classes.resolve("base").toString(), ".")
63                .shouldHaveExitValue(SUCCESS);
64
65        checkMultiRelease(jarfile, false);
66
67        Map<String, String[]> names = Map.of(
68                "version/Main.class",
69                new String[]{"base", "version", "Main.class"},
70
71                "version/Version.class",
72                new String[]{"base", "version", "Version.class"}
73        );
74
75        compare(jarfile, names);
76
77        FileUtils.deleteFileIfExistsWithRetry(Paths.get(jarfile));
78        FileUtils.deleteFileTreeWithRetry(Paths.get(usr, "classes"));
79    }
80
81    @Test
82    // create a multi-release jar
83    public void test01() throws Throwable {
84        String jarfile = "test.jar";
85
86        compile("test01");
87
88        Path classes = Paths.get("classes");
89        jar("cf", jarfile, "-C", classes.resolve("base").toString(), ".",
90                "--release", "9", "-C", classes.resolve("v9").toString(), ".",
91                "--release", "10", "-C", classes.resolve("v10").toString(), ".")
92                .shouldHaveExitValue(SUCCESS);
93
94        checkMultiRelease(jarfile, true);
95
96        Map<String, String[]> names = Map.of(
97                "version/Main.class",
98                new String[]{"base", "version", "Main.class"},
99
100                "version/Version.class",
101                new String[]{"base", "version", "Version.class"},
102
103                "META-INF/versions/9/version/Version.class",
104                new String[]{"v9", "version", "Version.class"},
105
106                "META-INF/versions/10/version/Version.class",
107                new String[]{"v10", "version", "Version.class"}
108        );
109
110        compare(jarfile, names);
111
112        FileUtils.deleteFileIfExistsWithRetry(Paths.get(jarfile));
113        FileUtils.deleteFileTreeWithRetry(Paths.get(usr, "classes"));
114    }
115
116    @Test
117    public void versionFormat() throws Throwable {
118        String jarfile = "test.jar";
119
120        compile("test01");
121
122        Path classes = Paths.get("classes");
123
124        // valid
125        for (String release : List.of("10000", "09", "00010", "10")) {
126            jar("cf", jarfile, "-C", classes.resolve("base").toString(), ".",
127                    "--release", release, "-C", classes.resolve("v10").toString(), ".")
128                    .shouldHaveExitValue(SUCCESS)
129                    .shouldBeEmpty();
130        }
131        // invalid
132        for (String release : List.of("9.0", "8", "v9",
133                "9v", "0", "-10")) {
134            jar("cf", jarfile, "-C", classes.resolve("base").toString(), ".",
135                    "--release", release, "-C", classes.resolve("v10").toString(), ".")
136                    .shouldNotHaveExitValue(SUCCESS)
137                    .shouldContain("release " + release + " not valid");
138        }
139        FileUtils.deleteFileIfExistsWithRetry(Paths.get(jarfile));
140        FileUtils.deleteFileTreeWithRetry(Paths.get(usr, "classes"));
141    }
142
143    @Test
144    // update a regular jar to a multi-release jar
145    public void test02() throws Throwable {
146        String jarfile = "test.jar";
147
148        compile("test01");  //use same data as test01
149
150        Path classes = Paths.get("classes");
151        jar("cf", jarfile, "-C", classes.resolve("base").toString(), ".")
152                .shouldHaveExitValue(SUCCESS);
153
154        checkMultiRelease(jarfile, false);
155
156        jar("uf", jarfile,
157                "--release", "9", "-C", classes.resolve("v9").toString(), ".")
158                .shouldHaveExitValue(SUCCESS);
159
160        checkMultiRelease(jarfile, true);
161
162        Map<String, String[]> names = Map.of(
163                "version/Main.class",
164                new String[]{"base", "version", "Main.class"},
165
166                "version/Version.class",
167                new String[]{"base", "version", "Version.class"},
168
169                "META-INF/versions/9/version/Version.class",
170                new String[]{"v9", "version", "Version.class"}
171        );
172
173        compare(jarfile, names);
174
175        FileUtils.deleteFileIfExistsWithRetry(Paths.get(jarfile));
176        FileUtils.deleteFileTreeWithRetry(Paths.get(usr, "classes"));
177    }
178
179    @Test
180    // replace a base entry and a versioned entry
181    public void test03() throws Throwable {
182        String jarfile = "test.jar";
183
184        compile("test01");  //use same data as test01
185
186        Path classes = Paths.get("classes");
187        jar("cf", jarfile, "-C", classes.resolve("base").toString(), ".",
188                "--release", "9", "-C", classes.resolve("v9").toString(), ".")
189                .shouldHaveExitValue(SUCCESS);
190
191        checkMultiRelease(jarfile, true);
192
193        Map<String, String[]> names = Map.of(
194                "version/Main.class",
195                new String[]{"base", "version", "Main.class"},
196
197                "version/Version.class",
198                new String[]{"base", "version", "Version.class"},
199
200                "META-INF/versions/9/version/Version.class",
201                new String[]{"v9", "version", "Version.class"}
202        );
203
204        compare(jarfile, names);
205
206        // write the v9 version/Version.class entry in base and the v10
207        // version/Version.class entry in versions/9 section
208        jar("uf", jarfile, "-C", classes.resolve("v9").toString(), "version",
209                "--release", "9", "-C", classes.resolve("v10").toString(), ".")
210                .shouldHaveExitValue(SUCCESS);
211
212        checkMultiRelease(jarfile, true);
213
214        names = Map.of(
215                "version/Main.class",
216                new String[]{"base", "version", "Main.class"},
217
218                "version/Version.class",
219                new String[]{"v9", "version", "Version.class"},
220
221                "META-INF/versions/9/version/Version.class",
222                new String[]{"v10", "version", "Version.class"}
223        );
224
225        compare(jarfile, names);
226
227        FileUtils.deleteFileIfExistsWithRetry(Paths.get(jarfile));
228        FileUtils.deleteFileTreeWithRetry(Paths.get(usr, "classes"));
229    }
230
231    /*
232     * The following tests exercise the jar validator
233     */
234
235    @Test
236    // META-INF/versions/9 class has different api than base class
237    public void test04() throws Throwable {
238        String jarfile = "test.jar";
239
240        compile("test01");  //use same data as test01
241
242        Path classes = Paths.get("classes");
243
244        // replace the v9 class
245        Path source = Paths.get(src, "data", "test04", "v9", "version");
246        javac(classes.resolve("v9"), source.resolve("Version.java"));
247
248        jar("cf", jarfile, "-C", classes.resolve("base").toString(), ".",
249                "--release", "9", "-C", classes.resolve("v9").toString(), ".")
250                .shouldNotHaveExitValue(SUCCESS)
251                .shouldContain("different api from earlier");
252
253        FileUtils.deleteFileIfExistsWithRetry(Paths.get(jarfile));
254        FileUtils.deleteFileTreeWithRetry(Paths.get(usr, "classes"));
255    }
256
257    @Test
258    // META-INF/versions/9 contains an extra public class
259    public void test05() throws Throwable {
260        String jarfile = "test.jar";
261
262        compile("test01");  //use same data as test01
263
264        Path classes = Paths.get("classes");
265
266        // add the new v9 class
267        Path source = Paths.get(src, "data", "test05", "v9", "version");
268        javac(classes.resolve("v9"), source.resolve("Extra.java"));
269
270        jar("cf", jarfile, "-C", classes.resolve("base").toString(), ".",
271                "--release", "9", "-C", classes.resolve("v9").toString(), ".")
272                .shouldNotHaveExitValue(SUCCESS)
273                .shouldContain("contains a new public class");
274
275        FileUtils.deleteFileIfExistsWithRetry(Paths.get(jarfile));
276        FileUtils.deleteFileTreeWithRetry(Paths.get(usr, "classes"));
277    }
278
279    @Test
280    // META-INF/versions/9 contains an extra package private class -- this is okay
281    public void test06() throws Throwable {
282        String jarfile = "test.jar";
283
284        compile("test01");  //use same data as test01
285
286        Path classes = Paths.get("classes");
287
288        // add the new v9 class
289        Path source = Paths.get(src, "data", "test06", "v9", "version");
290        javac(classes.resolve("v9"), source.resolve("Extra.java"));
291
292        jar("cf", jarfile, "-C", classes.resolve("base").toString(), ".",
293                "--release", "9", "-C", classes.resolve("v9").toString(), ".")
294                .shouldHaveExitValue(SUCCESS);
295
296        FileUtils.deleteFileIfExistsWithRetry(Paths.get(jarfile));
297        FileUtils.deleteFileTreeWithRetry(Paths.get(usr, "classes"));
298    }
299
300    @Test
301    // META-INF/versions/9 contains an identical class to base entry class
302    // this is okay but produces warning
303    public void test07() throws Throwable {
304        String jarfile = "test.jar";
305
306        compile("test01");  //use same data as test01
307
308        Path classes = Paths.get("classes");
309
310        // add the new v9 class
311        Path source = Paths.get(src, "data", "test01", "base", "version");
312        javac(classes.resolve("v9"), source.resolve("Version.java"));
313
314        jar("cf", jarfile, "-C", classes.resolve("base").toString(), ".",
315                "--release", "9", "-C", classes.resolve("v9").toString(), ".")
316                .shouldHaveExitValue(SUCCESS)
317                .shouldContain("contains a class that")
318                .shouldContain("is identical");
319
320        FileUtils.deleteFileIfExistsWithRetry(Paths.get(jarfile));
321        FileUtils.deleteFileTreeWithRetry(Paths.get(usr, "classes"));
322    }
323
324    @Test
325    // META-INF/versions/9 contains an identical class to previous version entry class
326    // this is okay but produces warning
327    public void identicalClassToPreviousVersion() throws Throwable {
328        String jarfile = "test.jar";
329
330        compile("test01");  //use same data as test01
331
332        Path classes = Paths.get("classes");
333
334        jar("cf", jarfile, "-C", classes.resolve("base").toString(), ".",
335                "--release", "9", "-C", classes.resolve("v9").toString(), ".")
336                .shouldHaveExitValue(SUCCESS)
337                .shouldBeEmpty();
338        jar("uf", jarfile,
339                "--release", "10", "-C", classes.resolve("v9").toString(), ".")
340                .shouldHaveExitValue(SUCCESS)
341                .shouldContain("contains a class that")
342                .shouldContain("is identical");
343
344        FileUtils.deleteFileIfExistsWithRetry(Paths.get(jarfile));
345        FileUtils.deleteFileTreeWithRetry(Paths.get(usr, "classes"));
346    }
347
348    @Test
349    // resources with same name in different versions
350    // this is okay but produces warning
351    public void test08() throws Throwable {
352        String jarfile = "test.jar";
353
354        compile("test01");  //use same data as test01
355
356        Path classes = Paths.get("classes");
357
358        // add a resource to the base
359        Path source = Paths.get(src, "data", "test01", "base", "version");
360        Files.copy(source.resolve("Version.java"), classes.resolve("base")
361                .resolve("version").resolve("Version.java"));
362
363        jar("cf", jarfile, "-C", classes.resolve("base").toString(), ".",
364                "--release", "9", "-C", classes.resolve("v9").toString(), ".")
365                .shouldHaveExitValue(SUCCESS)
366                .shouldBeEmpty();
367
368        // now add a different resource with same name to META-INF/version/9
369        Files.copy(source.resolve("Main.java"), classes.resolve("v9")
370                .resolve("version").resolve("Version.java"));
371
372        jar("cf", jarfile, "-C", classes.resolve("base").toString(), ".",
373                "--release", "9", "-C", classes.resolve("v9").toString(), ".")
374                .shouldHaveExitValue(SUCCESS)
375                .shouldContain("multiple resources with same name");
376
377        FileUtils.deleteFileIfExistsWithRetry(Paths.get(jarfile));
378        FileUtils.deleteFileTreeWithRetry(Paths.get(usr, "classes"));
379    }
380
381    @Test
382    // a class with an internal name different from the external name
383    public void test09() throws Throwable {
384        String jarfile = "test.jar";
385
386        compile("test01");  //use same data as test01
387
388        Path classes = Paths.get("classes");
389
390        Path base = classes.resolve("base").resolve("version");
391
392        Files.copy(base.resolve("Main.class"), base.resolve("Foo.class"));
393
394        jar("cf", jarfile, "-C", classes.resolve("base").toString(), ".",
395                "--release", "9", "-C", classes.resolve("v9").toString(), ".")
396                .shouldNotHaveExitValue(SUCCESS)
397                .shouldContain("names do not match");
398
399        FileUtils.deleteFileIfExistsWithRetry(Paths.get(jarfile));
400        FileUtils.deleteFileTreeWithRetry(Paths.get(usr, "classes"));
401    }
402
403    @Test
404    // assure that basic nested classes are acceptable
405    public void test10() throws Throwable {
406        String jarfile = "test.jar";
407
408        compile("test01");  //use same data as test01
409
410        Path classes = Paths.get("classes");
411
412        // add a base class with a nested class
413        Path source = Paths.get(src, "data", "test10", "base", "version");
414        javac(classes.resolve("base"), source.resolve("Nested.java"));
415
416        // add a versioned class with a nested class
417        source = Paths.get(src, "data", "test10", "v9", "version");
418        javac(classes.resolve("v9"), source.resolve("Nested.java"));
419
420        jar("cf", jarfile, "-C", classes.resolve("base").toString(), ".",
421                "--release", "9", "-C", classes.resolve("v9").toString(), ".")
422                .shouldHaveExitValue(SUCCESS);
423
424        FileUtils.deleteFileIfExistsWithRetry(Paths.get(jarfile));
425        FileUtils.deleteFileTreeWithRetry(Paths.get(usr, "classes"));
426    }
427
428    @Test
429    // a base entry contains a nested class that doesn't have a matching top level class
430    public void test11() throws Throwable {
431        String jarfile = "test.jar";
432
433        compile("test01");  //use same data as test01
434
435        Path classes = Paths.get("classes");
436
437        // add a base class with a nested class
438        Path source = Paths.get(src, "data", "test10", "base", "version");
439        javac(classes.resolve("base"), source.resolve("Nested.java"));
440
441        // remove the top level class, thus isolating the nested class
442        Files.delete(classes.resolve("base").resolve("version").resolve("Nested.class"));
443
444        // add a versioned class with a nested class
445        source = Paths.get(src, "data", "test10", "v9", "version");
446        javac(classes.resolve("v9"), source.resolve("Nested.java"));
447
448        List<String> output = jar("cf", jarfile,
449                "-C", classes.resolve("base").toString(), ".",
450                "--release", "9", "-C", classes.resolve("v9").toString(), ".")
451                .shouldNotHaveExitValue(SUCCESS)
452                .asLines();
453
454        assertTrue(output.size() == 4);
455        assertTrue(output.get(0).contains("an isolated nested class"),
456                output.get(0));
457        assertTrue(output.get(1).contains("contains a new public class"),
458                output.get(1));
459        assertTrue(output.get(2).contains("an isolated nested class"),
460                output.get(2));
461        assertTrue(output.get(3).contains("invalid multi-release jar file"),
462                output.get(3));
463
464        FileUtils.deleteFileIfExistsWithRetry(Paths.get(jarfile));
465        FileUtils.deleteFileTreeWithRetry(Paths.get(usr, "classes"));
466    }
467
468    @Test
469    // a versioned entry contains a nested class that doesn't have a matching top level class
470    public void test12() throws Throwable {
471        String jarfile = "test.jar";
472
473        compile("test01");  //use same data as test01
474
475        Path classes = Paths.get("classes");
476
477        // add a base class with a nested class
478        Path source = Paths.get(src, "data", "test10", "base", "version");
479        javac(classes.resolve("base"), source.resolve("Nested.java"));
480
481        // add a versioned class with a nested class
482        source = Paths.get(src, "data", "test10", "v9", "version");
483        javac(classes.resolve("v9"), source.resolve("Nested.java"));
484
485        // remove the top level class, thus isolating the nested class
486        Files.delete(classes.resolve("v9").resolve("version").resolve("Nested.class"));
487
488        jar("cf", jarfile, "-C", classes.resolve("base").toString(), ".",
489                "--release", "9", "-C", classes.resolve("v9").toString(), ".")
490                .shouldNotHaveExitValue(SUCCESS)
491                .shouldContain("an isolated nested class");
492
493        FileUtils.deleteFileIfExistsWithRetry(Paths.get(jarfile));
494        FileUtils.deleteFileTreeWithRetry(Paths.get(usr, "classes"));
495    }
496
497    @Test
498    public void testCustomManifest() throws Throwable {
499        String jarfile = "test.jar";
500
501        compile("test01");
502
503        Path classes = Paths.get("classes");
504        Path manifest = Paths.get("Manifest.txt");
505
506        // create
507        Files.write(manifest, "Class-Path: MyUtils.jar\n".getBytes());
508
509        jar("cfm", jarfile, manifest.toString(),
510                "-C", classes.resolve("base").toString(), ".",
511                "--release", "10", "-C", classes.resolve("v10").toString(), ".")
512                .shouldHaveExitValue(SUCCESS)
513                .shouldBeEmpty();
514
515        try (JarFile jf = new JarFile(new File(jarfile), true,
516                ZipFile.OPEN_READ, JarFile.runtimeVersion())) {
517            assertTrue(jf.isMultiRelease(), "Not multi-release jar");
518            assertEquals(jf.getManifest()
519                            .getMainAttributes()
520                            .getValue("Class-Path"),
521                    "MyUtils.jar");
522        }
523
524        // update
525        Files.write(manifest, "Multi-release: false\n".getBytes());
526
527        jar("ufm", jarfile, manifest.toString(),
528                "-C", classes.resolve("base").toString(), ".",
529                "--release", "9", "-C", classes.resolve("v10").toString(), ".")
530                .shouldHaveExitValue(SUCCESS)
531                .shouldContain("WARNING: Duplicate name in Manifest: Multi-release.");
532
533        try (JarFile jf = new JarFile(new File(jarfile), true,
534                ZipFile.OPEN_READ, JarFile.runtimeVersion())) {
535            assertTrue(jf.isMultiRelease(), "Not multi-release jar");
536            assertEquals(jf.getManifest()
537                            .getMainAttributes()
538                            .getValue("Class-Path"),
539                    "MyUtils.jar");
540        }
541
542        FileUtils.deleteFileIfExistsWithRetry(Paths.get(jarfile));
543        FileUtils.deleteFileTreeWithRetry(Paths.get(usr, "classes"));
544    }
545}
546