1 /* 2 * Copyright (c) 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 * @summary Tests for API validator. 27 * @library /test/lib 28 * @modules java.base/jdk.internal.misc 29 * jdk.compiler 30 * jdk.jartool 31 * @build jdk.test.lib.util.FileUtils 32 * jdk.test.lib.Utils 33 * jdk.test.lib.Asserts 34 * jdk.test.lib.JDKToolFinder 35 * jdk.test.lib.JDKToolLauncher 36 * jdk.test.lib.Platform 37 * jdk.test.lib.process.* 38 * MRTestBase 39 * @run testng/timeout=1200 ApiValidatorTest 40 */ 41 42 import jdk.test.lib.process.OutputAnalyzer; 43 import jdk.test.lib.util.FileUtils; 44 import org.testng.annotations.AfterMethod; 45 import org.testng.annotations.BeforeMethod; 46 import org.testng.annotations.DataProvider; 47 import org.testng.annotations.Test; 48 49 import java.io.IOException; 50 import java.lang.reflect.Method; 51 import java.nio.file.Files; 52 import java.nio.file.Path; 53 import java.nio.file.Paths; 54 import java.util.regex.Matcher; 55 import java.util.regex.Pattern; 56 57 public class ApiValidatorTest extends MRTestBase { 58 59 static final Pattern MODULE_PATTERN = Pattern.compile("module (\\w+)"); 60 static final Pattern CLASS_PATTERN = Pattern.compile("package (\\w+).*public class (\\w+)"); 61 62 private Path root; 63 private Path classes; 64 65 @BeforeMethod 66 void testInit(Method method) { 67 root = Paths.get(method.getName()); 68 classes = root.resolve("classes"); 69 } 70 71 @AfterMethod 72 void testCleanup() throws IOException { 73 FileUtils.deleteFileTreeWithRetry(root); 74 } 75 76 77 @Test(dataProvider = "signatureChange") 78 public void changeMethodSignature(String sigBase, String sigV10, 79 boolean isAcceptable) throws Throwable { 80 81 String METHOD_SIG = "#SIG"; 82 String classTemplate = 83 "public class C { \n" + 84 " " + METHOD_SIG + "{ throw new RuntimeException(); };\n" + 85 "}\n"; 86 String base = classTemplate.replace(METHOD_SIG, sigBase); 87 String v10 = classTemplate.replace(METHOD_SIG, sigV10); 88 89 compileTemplate(classes.resolve("base"), base); 90 compileTemplate(classes.resolve("v10"), v10); 91 92 String jarfile = root.resolve("test.jar").toString(); 93 OutputAnalyzer result = jar("cf", jarfile, 94 "-C", classes.resolve("base").toString(), ".", 95 "--release", "10", "-C", classes.resolve("v10").toString(), 96 "."); 97 if (isAcceptable) { 98 result.shouldHaveExitValue(SUCCESS) 99 .shouldBeEmpty(); 100 } else { 101 result.shouldNotHaveExitValue(SUCCESS) 102 .shouldContain("contains a class with different api from earlier version"); 103 } 104 } 105 106 @DataProvider 107 Object[][] signatureChange() { 108 return new Object[][]{ 109 {"public int m()", "protected int m()", false}, 110 {"protected int m()", "public int m()", false}, 111 {"public int m()", "int m()", false}, 112 {"protected int m()", "private int m()", false}, 113 {"private int m()", "int m()", true}, 114 {"int m()", "private int m()", true}, 115 {"int m()", "private int m(boolean b)", true}, 116 {"public int m()", "public int m(int i)", false}, 117 {"public int m()", "public int k()", false}, 118 {"public int m()", "private int k()", false}, 119 // @ignore JDK-8172147 {"public int m()", "public boolean m()", false}, 120 // @ignore JDK-8172147 {"public boolean", "public Boolean", false}, 121 // @ignore JDK-8172147 {"public <T> T", "public <T extends String> T", false}, 122 }; 123 } 124 125 @Test(dataProvider = "publicAPI") 126 public void introducingPublicMembers(String publicAPI) throws Throwable { 127 String API = "#API"; 128 String classTemplate = 129 "public class C { \n" + 130 " " + API + "\n" + 131 " public void method(){ };\n" + 132 "}\n"; 133 String base = classTemplate.replace(API, ""); 134 String v10 = classTemplate.replace(API, publicAPI); 135 136 compileTemplate(classes.resolve("base"), base); 137 compileTemplate(classes.resolve("v10"), v10); 138 139 String jarfile = root.resolve("test.jar").toString(); 140 jar("cf", jarfile, "-C", classes.resolve("base").toString(), ".", 141 "--release", "10", "-C", classes.resolve("v10").toString(), ".") 142 .shouldNotHaveExitValue(SUCCESS) 143 .shouldContain("contains a class with different api from earlier version"); 144 } 145 146 @DataProvider 147 Object[][] publicAPI() { 148 return new Object[][]{ 149 // @ignore JDK-8172148 {"protected class Inner { public void m(){ } } "}, // protected inner class 150 // @ignore JDK-8172148 {"public class Inner { public void m(){ } }"}, // public inner class 151 // @ignore JDK-8172148 {"public enum E { A; }"}, // public enum 152 {"public void m(){ }"}, // public method 153 {"protected void m(){ }"}, // protected method 154 }; 155 } 156 157 @Test(dataProvider = "privateAPI") 158 public void introducingPrivateMembers(String privateAPI) throws Throwable { 159 String API = "#API"; 160 String classTemplate = 161 "public class C { \n" + 162 " " + API + "\n" + 163 " public void method(){ };\n" + 164 "}\n"; 165 String base = classTemplate.replace(API, ""); 166 String v10 = classTemplate.replace(API, privateAPI); 167 168 compileTemplate(classes.resolve("base"), base); 169 compileTemplate(classes.resolve("v10"), v10); 170 171 String jarfile = root.resolve("test.jar").toString(); 172 jar("cf", jarfile, "-C", classes.resolve("base").toString(), ".", 173 "--release", "10", "-C", classes.resolve("v10").toString(), ".") 174 .shouldHaveExitValue(SUCCESS); 175 // add release 176 jar("uf", jarfile, 177 "--release", "11", "-C", classes.resolve("v10").toString(), ".") 178 .shouldHaveExitValue(SUCCESS); 179 // replace release 180 jar("uf", jarfile, 181 "--release", "11", "-C", classes.resolve("v10").toString(), ".") 182 .shouldHaveExitValue(SUCCESS); 183 } 184 185 @DataProvider 186 Object[][] privateAPI() { 187 return new Object[][]{ 188 {"private class Inner { public void m(){ } } "}, // private inner class 189 {"class Inner { public void m(){ } }"}, // package private inner class 190 {"enum E { A; }"}, // package private enum 191 // Local class and private method 192 {"private void m(){ class Inner { public void m(){} } Inner i = null; }"}, 193 {"void m(){ }"}, // package private method 194 }; 195 } 196 197 private void compileTemplate(Path classes, String template) throws Throwable { 198 Path classSourceFile = Files.createDirectories( 199 classes.getParent().resolve("src").resolve(classes.getFileName())) 200 .resolve("C.java"); 201 Files.write(classSourceFile, template.getBytes()); 202 javac(classes, classSourceFile); 203 } 204 205 /* Modular multi-release checks */ 206 207 @Test 208 public void moduleNameHasChanged() throws Throwable { 209 210 compileModule(classes.resolve("base"), "module A { }"); 211 compileModule(classes.resolve("v10"), "module B { }"); 212 213 String jarfile = root.resolve("test.jar").toString(); 214 jar("cf", jarfile, "-C", classes.resolve("base").toString(), ".", 215 "--release", "10", "-C", classes.resolve("v10").toString(), ".") 216 .shouldNotHaveExitValue(SUCCESS) 217 .shouldContain("incorrect name"); 218 219 // update module-info release 220 jar("cf", jarfile, "-C", classes.resolve("base").toString(), ".", 221 "--release", "10", "-C", classes.resolve("base").toString(), ".") 222 .shouldHaveExitValue(SUCCESS); 223 jar("uf", jarfile, 224 "--release", "10", "-C", classes.resolve("v10").toString(), ".") 225 .shouldNotHaveExitValue(SUCCESS) 226 .shouldContain("incorrect name"); 227 } 228 229 // @Test @ignore 8173370 230 public void moduleBecomeOpen() throws Throwable { 231 232 compileModule(classes.resolve("base"), "module A { }"); 233 compileModule(classes.resolve("v10"), "open module A { }"); 234 235 String jarfile = root.resolve("test.jar").toString(); 236 jar("cf", jarfile, "-C", classes.resolve("base").toString(), ".", 237 "--release", "10", "-C", classes.resolve("v10").toString(), ".") 238 .shouldNotHaveExitValue(SUCCESS) 239 .shouldContain("FIX ME"); 240 241 // update module-info release 242 jar("cf", jarfile, "-C", classes.resolve("base").toString(), ".", 243 "--release", "10", "-C", classes.resolve("base").toString(), ".") 244 .shouldHaveExitValue(SUCCESS); 245 jar("uf", jarfile, 246 "--release", "10", "-C", classes.resolve("v10").toString(), ".") 247 .shouldNotHaveExitValue(SUCCESS) 248 .shouldContain("FIX ME"); 249 } 250 251 @Test 252 public void moduleRequires() throws Throwable { 253 254 String BASE_VERSION_DIRECTIVE = "requires jdk.compiler;"; 255 // add transitive flag 256 moduleDirectivesCase(BASE_VERSION_DIRECTIVE, 257 "requires transitive jdk.compiler;", 258 false, 259 "contains additional \"requires transitive\""); 260 // remove requires 261 moduleDirectivesCase(BASE_VERSION_DIRECTIVE, 262 "", 263 true, 264 ""); 265 // add requires 266 moduleDirectivesCase(BASE_VERSION_DIRECTIVE, 267 "requires jdk.compiler; requires jdk.jartool;", 268 true, 269 ""); 270 // add requires transitive 271 moduleDirectivesCase(BASE_VERSION_DIRECTIVE, 272 "requires jdk.compiler; requires transitive jdk.jartool;", 273 false, 274 "contains additional \"requires transitive\""); 275 } 276 277 @Test 278 public void moduleExports() throws Throwable { 279 280 String BASE_VERSION_DIRECTIVE = "exports pkg1; exports pkg2 to jdk.compiler;"; 281 // add export 282 moduleDirectivesCase(BASE_VERSION_DIRECTIVE, 283 BASE_VERSION_DIRECTIVE + " exports pkg3;", 284 false, 285 "contains different \"exports\""); 286 // change exports to qualified exports 287 moduleDirectivesCase(BASE_VERSION_DIRECTIVE, 288 "exports pkg1 to jdk.compiler; exports pkg2;", 289 false, 290 "contains different \"exports\""); 291 // remove exports 292 moduleDirectivesCase(BASE_VERSION_DIRECTIVE, 293 "exports pkg1;", 294 false, 295 "contains different \"exports\""); 296 // add qualified exports 297 moduleDirectivesCase(BASE_VERSION_DIRECTIVE, 298 BASE_VERSION_DIRECTIVE + " exports pkg3 to jdk.compiler;", 299 false, 300 "contains different \"exports\""); 301 } 302 303 @Test 304 public void moduleOpens() throws Throwable { 305 306 String BASE_VERSION_DIRECTIVE = "opens pkg1; opens pkg2 to jdk.compiler;"; 307 // add opens 308 moduleDirectivesCase(BASE_VERSION_DIRECTIVE, 309 BASE_VERSION_DIRECTIVE + " opens pkg3;", 310 false, 311 "contains different \"opens\""); 312 // change opens to qualified opens 313 moduleDirectivesCase(BASE_VERSION_DIRECTIVE, 314 "opens pkg1 to jdk.compiler; opens pkg2;", 315 false, 316 "contains different \"opens\""); 317 // remove opens 318 moduleDirectivesCase(BASE_VERSION_DIRECTIVE, 319 "opens pkg1;", 320 false, 321 "contains different \"opens\""); 322 // add qualified opens 323 moduleDirectivesCase(BASE_VERSION_DIRECTIVE, 324 BASE_VERSION_DIRECTIVE + " opens pkg3 to jdk.compiler;", 325 false, 326 "contains different \"opens\""); 327 } 328 329 @Test 330 public void moduleProvides() throws Throwable { 331 332 String BASE_VERSION_DIRECTIVE = "provides pkg1.A with pkg1.A;"; 333 // add provides 334 moduleDirectivesCase(BASE_VERSION_DIRECTIVE, 335 BASE_VERSION_DIRECTIVE + " provides pkg2.B with pkg2.B;", 336 false, 337 "contains different \"provides\""); 338 // change service impl 339 moduleDirectivesCase(BASE_VERSION_DIRECTIVE, 340 "provides pkg1.A with pkg2.B;", 341 false, 342 "contains different \"provides\""); 343 // remove provides 344 moduleDirectivesCase(BASE_VERSION_DIRECTIVE, 345 "", 346 false, 347 "contains different \"provides\""); 348 } 349 350 @Test 351 public void moduleUses() throws Throwable { 352 353 String BASE_VERSION_DIRECTIVE = "uses pkg1.A;"; 354 // add 355 moduleDirectivesCase(BASE_VERSION_DIRECTIVE, 356 BASE_VERSION_DIRECTIVE + " uses pkg2.B;", 357 true, 358 ""); 359 // replace 360 moduleDirectivesCase(BASE_VERSION_DIRECTIVE, 361 "uses pkg2.B;", 362 true, 363 ""); 364 // remove 365 moduleDirectivesCase(BASE_VERSION_DIRECTIVE, 366 "", 367 true, 368 ""); 369 } 370 371 private void moduleDirectivesCase(String baseDirectives, 372 String versionedDirectives, 373 boolean expectSuccess, 374 String expectedMessage) throws Throwable { 375 String[] moduleClasses = { 376 "package pkg1; public class A { }", 377 "package pkg2; public class B extends pkg1.A { }", 378 "package pkg3; public class C extends pkg2.B { }"}; 379 compileModule(classes.resolve("base"), 380 "module A { " + baseDirectives + " }", 381 moduleClasses); 382 compileModule(classes.resolve("v10"), 383 "module A { " + versionedDirectives + " }", 384 moduleClasses); 385 386 String jarfile = root.resolve("test.jar").toString(); 387 OutputAnalyzer output = jar("cf", jarfile, 388 "-C", classes.resolve("base").toString(), ".", 389 "--release", "10", "-C", classes.resolve("v10").toString(), "."); 390 if (expectSuccess) { 391 output.shouldHaveExitValue(SUCCESS); 392 } else { 393 output.shouldNotHaveExitValue(SUCCESS) 394 .shouldContain(expectedMessage); 395 } 396 } 397 398 private void compileModule(Path classes, String moduleSource, 399 String... classSources) throws Throwable { 400 Matcher moduleMatcher = MODULE_PATTERN.matcher(moduleSource); 401 moduleMatcher.find(); 402 String name = moduleMatcher.group(1); 403 Path moduleinfo = Files.createDirectories( 404 classes.getParent().resolve("src").resolve(name)) 405 .resolve("module-info.java"); 406 Files.write(moduleinfo, moduleSource.getBytes()); 407 408 Path[] sourceFiles = new Path[classSources.length + 1]; 409 sourceFiles[0] = moduleinfo; 410 411 for (int i = 0; i < classSources.length; i++) { 412 String classSource = classSources[i]; 413 Matcher classMatcher = CLASS_PATTERN.matcher(classSource); 414 classMatcher.find(); 415 String packageName = classMatcher.group(1); 416 String className = classMatcher.group(2); 417 418 Path packagePath = moduleinfo.getParent() 419 .resolve(packageName.replace('.', '/')); 420 Path sourceFile = Files.createDirectories(packagePath) 421 .resolve(className + ".java"); 422 Files.write(sourceFile, classSource.getBytes()); 423 424 sourceFiles[i + 1] = sourceFile; 425 } 426 427 javac(classes, sourceFiles); 428 } 429 } 430