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