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