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