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 * @bug 8146486 8172432 27 * @summary Fail to create a MR modular JAR with a versioned entry in 28 * base-versioned empty package 29 * @modules java.base/jdk.internal.module 30 * jdk.compiler 31 * jdk.jartool 32 * @library /test/lib 33 * @build jdk.test.lib.util.FileUtils 34 * jdk.test.lib.Utils 35 * jdk.test.lib.Asserts 36 * jdk.test.lib.JDKToolFinder 37 * jdk.test.lib.JDKToolLauncher 38 * jdk.test.lib.Platform 39 * jdk.test.lib.process.* 40 * @run testng Basic 41 */ 42 43 import org.testng.Assert; 44 import org.testng.annotations.AfterClass; 45 import org.testng.annotations.Test; 46 47 import java.io.ByteArrayInputStream; 48 import java.io.ByteArrayOutputStream; 49 import java.io.IOException; 50 import java.io.PrintStream; 51 import java.io.UncheckedIOException; 52 import java.lang.module.ModuleDescriptor; 53 import java.lang.module.ModuleDescriptor.Version; 54 import java.nio.file.Files; 55 import java.nio.file.Path; 56 import java.nio.file.Paths; 57 import java.util.Arrays; 58 import java.util.Optional; 59 import java.util.Set; 60 import java.util.spi.ToolProvider; 61 import java.util.stream.Collectors; 62 import java.util.stream.Stream; 63 import java.util.zip.ZipFile; 64 65 import jdk.internal.module.ModuleInfoExtender; 66 import jdk.test.lib.util.FileUtils; 67 68 public class Basic { 69 private static final ToolProvider JAR_TOOL = ToolProvider.findFirst("jar") 70 .orElseThrow(() -> new RuntimeException("jar tool not found")); 71 private static final ToolProvider JAVAC_TOOL = ToolProvider.findFirst("javac") 72 .orElseThrow(() -> new RuntimeException("javac tool not found")); 73 private final String linesep = System.lineSeparator(); 74 private final Path testsrc; 75 private final Path userdir; 76 private final ByteArrayOutputStream outbytes = new ByteArrayOutputStream(); 77 private final PrintStream out = new PrintStream(outbytes, true); 78 private final ByteArrayOutputStream errbytes = new ByteArrayOutputStream(); 79 private final PrintStream err = new PrintStream(errbytes, true); 80 81 public Basic() throws IOException { 82 testsrc = Paths.get(System.getProperty("test.src")); 83 userdir = Paths.get(System.getProperty("user.dir", ".")); 84 85 // compile the classes directory 86 Path source = testsrc.resolve("src").resolve("classes"); 87 Path destination = Paths.get("classes"); 88 javac(source, destination); 89 90 // compile the mr9 directory including module-info.java 91 source = testsrc.resolve("src").resolve("mr9"); 92 destination = Paths.get("mr9"); 93 javac(source, destination); 94 95 // move module-info.class for later use 96 Files.move(destination.resolve("module-info.class"), 97 Paths.get("module-info.class")); 98 } 99 100 private void javac(Path source, Path destination) throws IOException { 101 String[] args = Stream.concat( 102 Stream.of("-d", destination.toString()), 103 Files.walk(source) 104 .map(Path::toString) 105 .filter(s -> s.endsWith(".java")) 106 ).toArray(String[]::new); 107 JAVAC_TOOL.run(System.out, System.err, args); 108 } 109 110 private int jar(String cmd) { 111 outbytes.reset(); 112 errbytes.reset(); 113 return JAR_TOOL.run(out, err, cmd.split(" +")); 114 } 115 116 @AfterClass 117 public void cleanup() throws IOException { 118 Files.walk(userdir, 1) 119 .filter(p -> !p.equals(userdir)) 120 .forEach(p -> { 121 try { 122 if (Files.isDirectory(p)) { 123 FileUtils.deleteFileTreeWithRetry(p); 124 } else { 125 FileUtils.deleteFileIfExistsWithRetry(p); 126 } 127 } catch (IOException x) { 128 throw new UncheckedIOException(x); 129 } 130 }); 131 } 132 133 // updates a valid multi-release jar with a new public class in 134 // versioned section and fails 135 @Test 136 public void test1() { 137 // successful build of multi-release jar 138 int rc = jar("-cf mmr.jar -C classes . --release 9 -C mr9 p/Hi.class"); 139 Assert.assertEquals(rc, 0); 140 141 jar("-tf mmr.jar"); 142 143 Set<String> actual = lines(outbytes); 144 Set<String> expected = Set.of( 145 "META-INF/", 146 "META-INF/MANIFEST.MF", 147 "p/", 148 "p/Hi.class", 149 "META-INF/versions/9/p/Hi.class" 150 ); 151 Assert.assertEquals(actual, expected); 152 153 // failed build because of new public class 154 rc = jar("-uf mmr.jar --release 9 -C mr9 p/internal/Bar.class"); 155 Assert.assertEquals(rc, 1); 156 157 String s = new String(errbytes.toByteArray()); 158 Assert.assertTrue(Message.NOT_FOUND_IN_BASE_ENTRY.match(s, "p/internal/Bar.class")); 159 } 160 161 // updates a valid multi-release jar with a module-info class and new 162 // concealed public class in versioned section and succeeds 163 @Test 164 public void test2() { 165 // successful build of multi-release jar 166 int rc = jar("-cf mmr.jar -C classes . --release 9 -C mr9 p/Hi.class"); 167 Assert.assertEquals(rc, 0); 168 169 // successful build because of module-info and new public class 170 rc = jar("-uf mmr.jar module-info.class --release 9 -C mr9 p/internal/Bar.class"); 171 Assert.assertEquals(rc, 0); 172 173 String s = new String(errbytes.toByteArray()); 174 Assert.assertTrue(Message.NEW_CONCEALED_PACKAGE_WARNING.match(s, "p/internal/Bar.class")); 175 176 jar("-tf mmr.jar"); 177 178 Set<String> actual = lines(outbytes); 179 Set<String> expected = Set.of( 180 "META-INF/", 181 "META-INF/MANIFEST.MF", 182 "p/", 183 "p/Hi.class", 184 "META-INF/versions/9/p/Hi.class", 185 "META-INF/versions/9/p/internal/Bar.class", 186 "module-info.class" 187 ); 188 Assert.assertEquals(actual, expected); 189 } 190 191 // jar tool fails building mmr.jar because of new public class 192 @Test 193 public void test3() { 194 int rc = jar("-cf mmr.jar -C classes . --release 9 -C mr9 ."); 195 Assert.assertEquals(rc, 1); 196 197 String s = new String(errbytes.toByteArray()); 198 Assert.assertTrue(Message.NOT_FOUND_IN_BASE_ENTRY.match(s, "p/internal/Bar.class")); 199 } 200 201 // jar tool succeeds building mmr.jar because of concealed package 202 @Test 203 public void test4() { 204 int rc = jar("-cf mmr.jar module-info.class -C classes . " + 205 "--release 9 module-info.class -C mr9 ."); 206 Assert.assertEquals(rc, 0); 207 208 String s = new String(errbytes.toByteArray()); 209 Assert.assertTrue(Message.NEW_CONCEALED_PACKAGE_WARNING.match(s, "p/internal/Bar.class")); 210 211 jar("-tf mmr.jar"); 212 213 Set<String> actual = lines(outbytes); 214 Set<String> expected = Set.of( 215 "META-INF/", 216 "META-INF/MANIFEST.MF", 217 "module-info.class", 218 "META-INF/versions/9/module-info.class", 219 "p/", 220 "p/Hi.class", 221 "META-INF/versions/9/", 222 "META-INF/versions/9/p/", 223 "META-INF/versions/9/p/Hi.class", 224 "META-INF/versions/9/p/internal/", 225 "META-INF/versions/9/p/internal/Bar.class" 226 ); 227 Assert.assertEquals(actual, expected); 228 } 229 230 // jar tool does two updates, no exported packages, all concealed. 231 // Along with various --describe-module variants 232 @Test 233 public void test5() throws IOException { 234 // compile the mr10 directory 235 Path source = testsrc.resolve("src").resolve("mr10"); 236 Path destination = Paths.get("mr10"); 237 javac(source, destination); 238 239 // create a directory for this tests special files 240 Files.createDirectory(Paths.get("test5")); 241 242 // create an empty module-info.java 243 String hi = "module hi {" + linesep + "}" + linesep; 244 Path modinfo = Paths.get("test5", "module-info.java"); 245 Files.write(modinfo, hi.getBytes()); 246 247 // and compile it 248 javac(modinfo, Paths.get("test5")); 249 250 int rc = jar("--create --file mr.jar -C classes ."); 251 Assert.assertEquals(rc, 0); 252 253 rc = jar("--update --file mr.jar -C test5 module-info.class" 254 + " --release 9 -C mr9 ."); 255 Assert.assertEquals(rc, 0); 256 257 jar("tf mr.jar"); 258 259 Set<String> actual = lines(outbytes); 260 Set<String> expected = Set.of( 261 "META-INF/", 262 "META-INF/MANIFEST.MF", 263 "p/", 264 "p/Hi.class", 265 "META-INF/versions/9/", 266 "META-INF/versions/9/p/", 267 "META-INF/versions/9/p/Hi.class", 268 "META-INF/versions/9/p/internal/", 269 "META-INF/versions/9/p/internal/Bar.class", 270 "module-info.class" 271 ); 272 Assert.assertEquals(actual, expected); 273 274 jar("-d --file mr.jar"); 275 276 String uri = (Paths.get("mr.jar")).toUri().toString(); 277 uri = "jar:" + uri + "/!module-info.class"; 278 279 actual = lines(outbytes); 280 expected = Set.of( 281 "hi " + uri, 282 "requires java.base mandated", 283 "contains p", 284 "contains p.internal" 285 ); 286 Assert.assertEquals(actual, expected); 287 288 rc = jar("--update --file mr.jar --release 10 -C mr10 ."); 289 Assert.assertEquals(rc, 0); 290 291 jar("tf mr.jar"); 292 293 actual = lines(outbytes); 294 expected = Set.of( 295 "META-INF/", 296 "META-INF/MANIFEST.MF", 297 "p/", 298 "p/Hi.class", 299 "META-INF/versions/9/", 300 "META-INF/versions/9/p/", 301 "META-INF/versions/9/p/Hi.class", 302 "META-INF/versions/9/p/internal/", 303 "META-INF/versions/9/p/internal/Bar.class", 304 "META-INF/versions/10/", 305 "META-INF/versions/10/p/", 306 "META-INF/versions/10/p/internal/", 307 "META-INF/versions/10/p/internal/bar/", 308 "META-INF/versions/10/p/internal/bar/Gee.class", 309 "module-info.class" 310 ); 311 Assert.assertEquals(actual, expected); 312 313 jar("-d --file mr.jar"); 314 315 actual = lines(outbytes); 316 expected = Set.of( 317 "hi " + uri, 318 "requires java.base mandated", 319 "contains p", 320 "contains p.internal", 321 "contains p.internal.bar" 322 ); 323 Assert.assertEquals(actual, expected); 324 325 for (String release : new String[] {"9" , "10", "100", "1000"}) { 326 jar("-d --file mr.jar --release " + release); 327 actual = lines(outbytes); 328 Assert.assertEquals(actual, expected); 329 } 330 } 331 332 // root and versioned module-info entries have different main-class, version 333 // attributes 334 @Test 335 public void test6() throws IOException { 336 // create a directory for this tests special files 337 Files.createDirectory(Paths.get("test6")); 338 Files.createDirectory(Paths.get("test6-v9")); 339 340 // compile the classes directory 341 Path src = testsrc.resolve("src").resolve("classes"); 342 Path dst = Paths.get("test6"); 343 javac(src, dst); 344 345 byte[] mdBytes = Files.readAllBytes(Paths.get("module-info.class")); 346 347 ModuleInfoExtender mie = ModuleInfoExtender.newExtender( 348 new ByteArrayInputStream(mdBytes)); 349 350 mie.mainClass("p.Main"); 351 mie.version(Version.parse("1.0")); 352 353 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 354 mie.write(baos); 355 Files.write(Paths.get("test6", "module-info.class"), baos.toByteArray()); 356 Files.write(Paths.get("test6-v9", "module-info.class"), baos.toByteArray()); 357 358 int rc = jar("--create --file mmr.jar -C test6 . --release 9 -C test6-v9 ."); 359 Assert.assertEquals(rc, 0); 360 361 362 // different main-class 363 mie = ModuleInfoExtender.newExtender(new ByteArrayInputStream(mdBytes)); 364 mie.mainClass("p.Main2"); 365 mie.version(Version.parse("1.0")); 366 baos.reset(); 367 mie.write(baos); 368 Files.write(Paths.get("test6-v9", "module-info.class"), baos.toByteArray()); 369 370 rc = jar("--create --file mmr.jar -C test6 . --release 9 -C test6-v9 ."); 371 Assert.assertEquals(rc, 1); 372 373 Assert.assertTrue(Message.CONTAINS_DIFFERENT_MAINCLASS.match( 374 new String(errbytes.toByteArray()), 375 "META-INF/versions/9/module-info.class")); 376 377 // different version 378 mie = ModuleInfoExtender.newExtender(new ByteArrayInputStream(mdBytes)); 379 mie.mainClass("p.Main"); 380 mie.version(Version.parse("2.0")); 381 baos.reset(); 382 mie.write(baos); 383 Files.write(Paths.get("test6-v9", "module-info.class"), baos.toByteArray()); 384 385 rc = jar("--create --file mmr.jar -C test6 . --release 9 -C test6-v9 ."); 386 Assert.assertEquals(rc, 1); 387 388 Assert.assertTrue(Message.CONTAINS_DIFFERENT_VERSION.match( 389 new String(errbytes.toByteArray()), 390 "META-INF/versions/9/module-info.class")); 391 392 } 393 394 // versioned mmr without root module-info.class 395 @Test 396 public void test7() throws IOException { 397 // create a directory for this tests special files 398 Files.createDirectory(Paths.get("test7")); 399 Files.createDirectory(Paths.get("test7-v9")); 400 Files.createDirectory(Paths.get("test7-v10")); 401 402 // compile the classes directory 403 Path src = testsrc.resolve("src").resolve("classes"); 404 Path dst = Paths.get("test7"); 405 javac(src, dst); 406 407 // move module-info.class to v9 later use 408 Files.copy(Paths.get("module-info.class"), 409 Paths.get("test7-v9", "module-info.class")); 410 411 Files.copy(Paths.get("test7-v9", "module-info.class"), 412 Paths.get("test7-v10", "module-info.class")); 413 414 int rc = jar("--create --file mmr.jar --main-class=p.Main -C test7 . --release 9 -C test7-v9 . --release 10 -C test7-v10 ."); 415 Assert.assertEquals(rc, 0); 416 417 jar("-d --file=mmr.jar"); 418 Set<String> actual = lines(outbytes); 419 Set<String> expected = Set.of( 420 "releases: 9 10", 421 "No root module descriptor, specify --release" 422 ); 423 Assert.assertEquals(actual, expected); 424 425 String uriPrefix = "jar:" + (Paths.get("mmr.jar")).toUri().toString(); 426 427 jar("-d --file=mmr.jar --release 9"); 428 actual = lines(outbytes); 429 expected = Set.of( 430 "releases: 9 10", 431 "m1 " + uriPrefix + "/!META-INF/versions/9/module-info.class", 432 "requires java.base mandated", 433 "exports p", 434 "main-class p.Main" 435 ); 436 Assert.assertEquals(actual, expected); 437 438 jar("-d --file=mmr.jar --release 10"); 439 actual = lines(outbytes); 440 expected = Set.of( 441 "releases: 9 10", 442 "m1 " + uriPrefix + "/!META-INF/versions/10/module-info.class", 443 "requires java.base mandated", 444 "exports p", 445 "main-class p.Main" 446 ); 447 Assert.assertEquals(actual, expected); 448 449 for (String release : new String[] {"11", "12", "15", "100"}) { 450 jar("-d --file mmr.jar --release " + release); 451 actual = lines(outbytes); 452 Assert.assertEquals(actual, expected); 453 } 454 455 Optional<String> exp = Optional.of("p.Main"); 456 try (ZipFile zf = new ZipFile("mmr.jar")) { 457 Assert.assertTrue(zf.getEntry("module-info.class") == null); 458 459 ModuleDescriptor md = ModuleDescriptor.read( 460 zf.getInputStream(zf.getEntry("META-INF/versions/9/module-info.class"))); 461 Assert.assertEquals(md.mainClass(), exp); 462 463 md = ModuleDescriptor.read( 464 zf.getInputStream(zf.getEntry("META-INF/versions/10/module-info.class"))); 465 Assert.assertEquals(md.mainClass(), exp); 466 } 467 } 468 469 private static Set<String> lines(ByteArrayOutputStream baos) { 470 String s = new String(baos.toByteArray()); 471 return Arrays.stream(s.split("\\R")) 472 .map(l -> l.trim()) 473 .filter(l -> l.length() > 0) 474 .collect(Collectors.toSet()); 475 } 476 477 static enum Message { 478 CONTAINS_DIFFERENT_MAINCLASS( 479 ": module-info.class in a versioned directory contains different \"main-class\"" 480 ), 481 CONTAINS_DIFFERENT_VERSION( 482 ": module-info.class in a versioned directory contains different \"version\"" 483 ), 484 NOT_FOUND_IN_BASE_ENTRY( 485 ", contains a new public class not found in base entries" 486 ), 487 NEW_CONCEALED_PACKAGE_WARNING( 488 " is a public class" + 489 " in a concealed package, placing this jar on the class path will result" + 490 " in incompatible public interfaces" 491 ); 492 493 final String msg; 494 Message(String msg) { 495 this.msg = msg; 496 } 497 498 /* 499 * Test if the given output contains this message ignoring the line break. 500 */ 501 boolean match(String output, String entry) { 502 System.out.println("Expected: " + entry + msg); 503 System.out.println("Found: " + output); 504 return Arrays.stream(output.split("\\R")) 505 .collect(Collectors.joining(" ")) 506 .contains(entry + msg); 507 } 508 } 509 }