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