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