1 /* 2 * Copyright (c) 2022, 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 8292817 26 * @summary binary compatibility tests for value objects 27 * @library /tools/lib 28 * @modules jdk.compiler/com.sun.tools.javac.api 29 * jdk.compiler/com.sun.tools.javac.main 30 * jdk.compiler/com.sun.tools.javac.util 31 * jdk.compiler/com.sun.tools.javac.code 32 * @build toolbox.ToolBox toolbox.JavacTask 33 * @enablePreview 34 * @run main ValueObjectsBinaryCompatibilityTests 35 */ 36 37 import java.io.IOException; 38 import java.nio.file.Files; 39 import java.nio.file.Path; 40 import java.nio.file.Paths; 41 42 import toolbox.TestRunner; 43 import toolbox.ToolBox; 44 import toolbox.JavaTask; 45 import toolbox.JavacTask; 46 import toolbox.Task; 47 48 public class ValueObjectsBinaryCompatibilityTests extends TestRunner { 49 ToolBox tb; 50 51 ValueObjectsBinaryCompatibilityTests() { 52 super(System.err); 53 tb = new ToolBox(); 54 } 55 56 protected void runTests() throws Exception { 57 runTests(m -> new Object[]{Paths.get(m.getName())}); 58 } 59 60 public static void main(String... args) throws Exception { 61 new ValueObjectsBinaryCompatibilityTests().runTests(); 62 } 63 64 Path[] findJavaFiles(Path... paths) throws IOException { 65 return tb.findJavaFiles(paths); 66 } 67 68 /* 1- compiles the first version of the source code, code1, along with the client source code 69 * 2- executes the client class just to make sure that it works, sanity check 70 * 3- compiles the second version, code2 71 * 4- executes the client class and makes sure that the VM throws the expected error or not 72 * depending on the shouldFail argument 73 */ 74 private void testCompatibilityAfterChange( 75 Path base, 76 String code1, 77 String code2, 78 String clientCode, 79 boolean shouldFail, 80 Class<?> expectedError) throws Exception { 81 Path src = base.resolve("src"); 82 Path pkg = src.resolve("pkg"); 83 Path src1 = pkg.resolve("Test"); 84 Path client = pkg.resolve("Client"); 85 86 tb.writeJavaFiles(src1, code1); 87 tb.writeJavaFiles(client, clientCode); 88 89 Path out = base.resolve("out"); 90 Files.createDirectories(out); 91 92 new JavacTask(tb) 93 .outdir(out) 94 .options("--enable-preview", "-source", String.valueOf(Runtime.version().feature())) 95 .files(findJavaFiles(pkg)) 96 .run(); 97 98 // let's execute to check that it's working 99 String output = new JavaTask(tb) 100 .classpath(out.toString()) 101 .classArgs("pkg.Client") 102 .vmOptions("--enable-preview") 103 .run() 104 .writeAll() 105 .getOutput(Task.OutputKind.STDOUT); 106 107 // let's first check that it runs wo issues 108 if (!output.contains("Hello World!")) { 109 throw new AssertionError("execution of Client didn't finish"); 110 } 111 112 // now lets change the first class 113 tb.writeJavaFiles(src1, code2); 114 115 new JavacTask(tb) 116 .outdir(out) 117 .files(findJavaFiles(src1)) 118 .options("--enable-preview", "-source", String.valueOf(Runtime.version().feature())) 119 .run(); 120 121 if (shouldFail) { 122 // let's now check that we get the expected error 123 output = new JavaTask(tb) 124 .classpath(out.toString()) 125 .classArgs("pkg.Client") 126 .vmOptions("--enable-preview") 127 .run(Task.Expect.FAIL) 128 .writeAll() 129 .getOutput(Task.OutputKind.STDERR); 130 if (!output.contains(expectedError.getName())) { 131 throw new AssertionError(expectedError.getName() + " expected"); 132 } 133 } else { 134 new JavaTask(tb) 135 .classpath(out.toString()) 136 .classArgs("pkg.Client") 137 .vmOptions("--enable-preview") 138 .run(Task.Expect.SUCCESS); 139 } 140 } 141 142 @Test 143 public void testAbstractValueToValueClass(Path base) throws Exception { 144 /* Removing the abstract modifier from a value class declaration has the side-effect of making the class final, 145 * with binary compatibility risks outlined in 13.4.2.3 146 */ 147 testCompatibilityAfterChange( 148 base, 149 """ 150 package pkg; 151 public abstract value class A {} 152 """, 153 """ 154 package pkg; 155 public value class A {} 156 """, 157 """ 158 package pkg; 159 public value class Client extends A { 160 public static void main(String... args) { 161 System.out.println("Hello World!"); 162 } 163 } 164 """, 165 true, 166 IncompatibleClassChangeError.class 167 ); 168 } 169 170 @Test 171 public void testAbstractOrFinalIdentityToValueClass(Path base) throws Exception { 172 /* Modifying an abstract or final identity class to be a value class does not break compatibility 173 * with pre-existing binaries 174 */ 175 testCompatibilityAfterChange( 176 base, 177 """ 178 package pkg; 179 public abstract class A {} 180 """, 181 """ 182 package pkg; 183 public abstract value class A {} 184 """, 185 """ 186 package pkg; 187 public class Client extends A { 188 public static void main(String... args) { 189 System.out.println("Hello World!"); 190 } 191 } 192 """, 193 false, 194 null 195 ); 196 197 testCompatibilityAfterChange( 198 base, 199 """ 200 package pkg; 201 public final class A {} 202 """, 203 """ 204 package pkg; 205 public final value class A {} 206 """, 207 """ 208 package pkg; 209 public class Client { 210 A a; 211 public static void main(String... args) { 212 System.out.println("Hello World!"); 213 } 214 } 215 """, 216 false, 217 null 218 ); 219 } 220 221 @Test 222 public void testAddingValueModifier(Path base) throws Exception { 223 /* Adding the value modifier to a non-abstract, non-final class declaration has the side-effect of making 224 * the class final, with binary compatibility risks outlined in 13.4.2.3 225 */ 226 testCompatibilityAfterChange( 227 base, 228 """ 229 package pkg; 230 public class A {} 231 """, 232 """ 233 package pkg; 234 public value class A {} 235 """, 236 """ 237 package pkg; 238 public class Client extends A { 239 public static void main(String... args) { 240 System.out.println("Hello World!"); 241 } 242 } 243 """, 244 true, 245 IncompatibleClassChangeError.class 246 ); 247 } 248 249 @Test 250 public void testValueToIdentityClass(Path base) throws Exception { 251 /* If a value class is changed to be an identity class, then an IncompatibleClassChangeError is thrown if a 252 * binary of a pre-existing value subclass of this class is loaded 253 */ 254 testCompatibilityAfterChange( 255 base, 256 """ 257 package pkg; 258 public abstract value class A {} 259 """, 260 """ 261 package pkg; 262 public abstract class A {} 263 """, 264 """ 265 package pkg; 266 public value class Client extends A { 267 public static void main(String... args) { 268 System.out.println("Hello World!"); 269 } 270 } 271 """, 272 true, 273 IncompatibleClassChangeError.class 274 ); 275 /* Removing the value modifier from a non-abstract value class does not break compatibility with 276 * pre-existing binaries. 277 */ 278 testCompatibilityAfterChange( 279 base, 280 """ 281 package pkg; 282 public value class A {} 283 """, 284 """ 285 package pkg; 286 public class A {} 287 """, 288 """ 289 package pkg; 290 public class Client { 291 public static void main(String... args) { 292 A a = new A(); 293 System.out.println("Hello World!"); 294 } 295 } 296 """, 297 false, 298 null 299 ); 300 } 301 }