/* * Copyright 2014 Pivotal Software Inc. and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springsource.loaded.test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import org.junit.Ignore; import org.junit.Test; import org.springsource.loaded.ReloadableType; import org.springsource.loaded.TypeRegistry; import org.springsource.loaded.test.infra.Result; /** * Test reloading of Java 8. * * @author Andy Clement * @since 1.2 */ public class Java8Tests extends SpringLoadedTests { @Test public void theBasics() { String t = "basic.FirstClass"; TypeRegistry typeRegistry = getTypeRegistry(t); byte[] sc = loadBytesForClass(t); ReloadableType rtype = new ReloadableType(t, sc, 1, typeRegistry, null); assertEquals(1, rtype.getId()); assertEquals(t, rtype.getName()); assertEquals(slashed(t), rtype.getSlashedName()); assertNotNull(rtype.getTypeDescriptor()); assertEquals(typeRegistry, rtype.getTypeRegistry()); } @Test public void issue104() throws Exception { String t = "bugs.Issue104"; TypeRegistry typeRegistry = getTypeRegistry(t); byte[] sc = loadBytesForClass(t); ReloadableType rtype = typeRegistry.addType(t, sc); Class<?> clazz = rtype.getClazz(); @SuppressWarnings("unused") Result r = runUnguarded(clazz, "run"); r = runUnguarded(clazz, "run"); rtype.loadNewVersion("002", rtype.bytesInitial); r = runUnguarded(clazz, "run"); // TODO should assert something but the issue is that the JVM crashes... } @Test public void issue173() throws Exception { String t = "bugs.Issue173"; TypeRegistry typeRegistry = getTypeRegistry(t); byte[] sc = loadBytesForClass(t); ReloadableType rtype = typeRegistry.addType(t, sc); Class<?> clazz = rtype.getClazz(); @SuppressWarnings("unused") Result r = runUnguarded(clazz, "run"); r = runUnguarded(clazz, "run"); rtype.loadNewVersion("002", rtype.bytesInitial); r = runUnguarded(clazz, "run"); assertEquals("https://www.redacted.com/path", r.returnValue); } @Test public void callBasicType() throws Exception { String t = "basic.FirstClass"; TypeRegistry typeRegistry = getTypeRegistry(t); byte[] sc = loadBytesForClass(t); ReloadableType rtype = typeRegistry.addType(t, sc); Class<?> simpleClass = rtype.getClazz(); Result r = runUnguarded(simpleClass, "run"); r = runUnguarded(simpleClass, "run"); assertEquals(8, r.returnValue); rtype.loadNewVersion("002", rtype.bytesInitial); r = runUnguarded(simpleClass, "run"); assertEquals(8, r.returnValue); } @Test public void lambdaA() throws Exception { String t = "basic.LambdaA"; TypeRegistry typeRegistry = getTypeRegistry(t); byte[] sc = loadBytesForClass(t); ReloadableType rtype = typeRegistry.addType(t, sc); Class<?> simpleClass = rtype.getClazz(); Result r = runUnguarded(simpleClass, "run"); r = runUnguarded(simpleClass, "run"); assertEquals(77, r.returnValue); rtype.loadNewVersion("002", rtype.bytesInitial); r = runUnguarded(simpleClass, "run"); assertEquals(77, r.returnValue); } @Test public void changingALambda() throws Exception { String t = "basic.LambdaA"; TypeRegistry typeRegistry = getTypeRegistry(t); byte[] sc = loadBytesForClass(t); ReloadableType rtype = typeRegistry.addType(t, sc); Class<?> simpleClass = rtype.getClazz(); Result r = runUnguarded(simpleClass, "run"); r = runUnguarded(simpleClass, "run"); assertEquals(77, r.returnValue); byte[] renamed = retrieveRename(t, t + "2", t + "2$Foo:" + t + "$Foo"); rtype.loadNewVersion("002", renamed); r = runUnguarded(simpleClass, "run"); assertEquals(88, r.returnValue); } @Test public void lambdaWithParameter() throws Exception { String t = "basic.LambdaB"; TypeRegistry typeRegistry = getTypeRegistry(t); byte[] sc = loadBytesForClass(t); ReloadableType rtype = typeRegistry.addType(t, sc); Class<?> simpleClass = rtype.getClazz(); Result r = runUnguarded(simpleClass, "run"); r = runUnguarded(simpleClass, "run"); assertEquals(99L, r.returnValue); byte[] renamed = retrieveRename(t, t + "2", t + "2$Foo:" + t + "$Foo"); rtype.loadNewVersion("002", renamed); r = runUnguarded(simpleClass, "run"); assertEquals(176L, r.returnValue); } @Test public void lambdaWithTwoParameters() throws Exception { String t = "basic.LambdaC"; TypeRegistry typeRegistry = getTypeRegistry(t); byte[] sc = loadBytesForClass(t); ReloadableType rtype = typeRegistry.addType(t, sc); Class<?> simpleClass = rtype.getClazz(); Result r = runUnguarded(simpleClass, "run"); r = runUnguarded(simpleClass, "run"); assertEquals(6L, r.returnValue); byte[] renamed = retrieveRename(t, t + "2", t + "2$Boo:" + t + "$Boo"); rtype.loadNewVersion("002", renamed); r = runUnguarded(simpleClass, "run"); assertEquals(5L, r.returnValue); } @Test public void lambdaWithThreeMixedTypeParameters() throws Exception { String t = "basic.LambdaD"; TypeRegistry typeRegistry = getTypeRegistry(t); byte[] sc = loadBytesForClass(t); ReloadableType rtype = typeRegistry.addType(t, sc); Class<?> simpleClass = rtype.getClazz(); Result r = runUnguarded(simpleClass, "run"); r = runUnguarded(simpleClass, "run"); assertEquals("true342abc", r.returnValue); byte[] renamed = retrieveRename(t, t + "2", t + "2$Boo:" + t + "$Boo"); rtype.loadNewVersion("002", renamed); r = runUnguarded(simpleClass, "run"); assertEquals("def264true", r.returnValue); } @Test public void lambdaWithCapturedVariable() throws Exception { String t = "basic.LambdaE"; TypeRegistry typeRegistry = getTypeRegistry(t); byte[] sc = loadBytesForClass(t); ReloadableType rtype = typeRegistry.addType(t, sc); Class<?> simpleClass = rtype.getClazz(); Result r = runUnguarded(simpleClass, "run"); r = runUnguarded(simpleClass, "run"); assertEquals("aaaa", r.returnValue); byte[] renamed = retrieveRename(t, t + "2", t + "2$Boo:" + t + "$Boo"); rtype.loadNewVersion("002", renamed); r = runUnguarded(simpleClass, "run"); assertEquals("aaaaaaaa", r.returnValue); } @Test public void lambdaWithThis() throws Exception { String t = "basic.LambdaF"; TypeRegistry typeRegistry = getTypeRegistry(t); byte[] sc = loadBytesForClass(t); ReloadableType rtype = typeRegistry.addType(t, sc); Class<?> simpleClass = rtype.getClazz(); Result r = runUnguarded(simpleClass, "run"); r = runUnguarded(simpleClass, "run"); assertEquals("aaaaaaa", r.returnValue); byte[] renamed = retrieveRename(t, t + "2", t + "2$Boo:" + t + "$Boo"); rtype.loadNewVersion("002", renamed); r = runUnguarded(simpleClass, "run"); assertEquals("a:a:a:", r.returnValue); } @Test public void lambdaWithNonPublicInnerInterface() throws Exception { String t = "basic.LambdaG"; TypeRegistry typeRegistry = getTypeRegistry("basic..*"); // Since Boo needs promoting to public, have to ensure it is directly loaded: typeRegistry.addType(t + "$Boo", loadBytesForClass(t + "$Boo")); byte[] sc = loadBytesForClass(t); ReloadableType rtype = typeRegistry.addType(t, sc); Class<?> simpleClass = rtype.getClazz(); Result r = runUnguarded(simpleClass, "run"); r = runUnguarded(simpleClass, "run"); assertEquals(99, r.returnValue); byte[] renamed = retrieveRename(t, t + "2", t + "2$Boo:" + t + "$Boo"); rtype.loadNewVersion("002", renamed); r = runUnguarded(simpleClass, "run"); assertEquals(44, r.returnValue); } // A class implements an interface containing a default method and invokes it. // That default method is reloaded and modified to do something else. @Ignore @Test public void defaultMethods() throws Exception { String t = "basic.DefaultMethodsI1A"; String t2 = "basic.DefaultMethodsC1A"; TypeRegistry typeRegistry = getTypeRegistry("basic..*"); byte[] ia = loadBytesForClass(t); ReloadableType rtypeI = typeRegistry.addType(t, ia); byte[] ca = loadBytesForClass(t2); ReloadableType rtypeC = typeRegistry.addType(t2, ca); Class<?> simpleClass = rtypeC.getClazz(); Result r = runUnguarded(simpleClass, "run"); r = runUnguarded(simpleClass, "run"); assertEquals(42, r.returnValue); // byte[] renamed = retrieveRename(t, t + "2"); rtypeI.loadNewVersion("002", ia); // ClassPrinter.print(rtypeI.getBytesLoaded()); r = runUnguarded(simpleClass, "run"); assertEquals(42, r.returnValue); byte[] renamed2 = retrieveRename(t, t + "2"); rtypeI.loadNewVersion(renamed2); r = runUnguarded(simpleClass, "run"); assertEquals(42, r.returnValue); assertEquals("FOO", r.stdout); } // A class implements an interface. A default method is added to the interface on // a reload and invoked from a reloaded version of the class. @Ignore @Test public void defaultMethods2() throws Exception { String t = "basic.DefaultMethodsI2A"; String t2 = "basic.DefaultMethodsC2A"; TypeRegistry typeRegistry = getTypeRegistry("basic..*"); byte[] ia = loadBytesForClass(t); ReloadableType rtypeI = typeRegistry.addType(t, ia); byte[] ca = loadBytesForClass(t2); ReloadableType rtypeC = typeRegistry.addType(t2, ca); Class<?> simpleClass = rtypeC.getClazz(); Result r = runUnguarded(simpleClass, "run"); r = runUnguarded(simpleClass, "run"); assertEquals(42, r.returnValue); // byte[] renamed = retrieveRename(t, t + "2"); rtypeI.loadNewVersion("002", ia); // ClassPrinter.print(rtypeI.getBytesLoaded()); r = runUnguarded(simpleClass, "run"); assertEquals(42, r.returnValue); byte[] renamed = retrieveRename(t, t + "2"); rtypeI.loadNewVersion(renamed); renamed = retrieveRename(t2, t2 + "2", t + "2:" + t); rtypeC.loadNewVersion(renamed); r = runUnguarded(simpleClass, "run"); assertEquals(42, r.returnValue); assertEquals("FOO", r.stdout); } @Test public void multipleLambdasInOneMethod() throws Exception { String t = "basic.LambdaH"; TypeRegistry typeRegistry = getTypeRegistry("basic..*"); // Since Foo needs promoting to public, have to ensure it is directly loaded: typeRegistry.addType(t + "$Foo", loadBytesForClass(t + "$Foo")); byte[] sc = loadBytesForClass(t); ReloadableType rtype = typeRegistry.addType(t, sc); Class<?> simpleClass = rtype.getClazz(); Result r = runUnguarded(simpleClass, "run"); r = runUnguarded(simpleClass, "run"); assertEquals(56, r.returnValue); rtype.loadNewVersion("002", rtype.bytesInitial); r = runUnguarded(simpleClass, "run"); assertEquals(56, r.returnValue); } @Test public void lambdaSignatureChange() throws Exception { String t = "basic.LambdaI"; TypeRegistry typeRegistry = getTypeRegistry("basic..*"); // Since Foo needs promoting to public, have to ensure it is directly loaded: ReloadableType itype = typeRegistry.addType(t + "$Foo", loadBytesForClass(t + "$Foo")); byte[] sc = loadBytesForClass(t); ReloadableType rtype = typeRegistry.addType(t, sc); Class<?> simpleClass = rtype.getClazz(); Result r = runUnguarded(simpleClass, "run"); r = runUnguarded(simpleClass, "run"); assertEquals("a", r.returnValue); itype.loadNewVersion("002", retrieveRename(t + "$Foo", t + "2$Foo")); rtype.loadNewVersion("002", retrieveRename(t, t + "2", t + "2$Foo:" + t + "$Foo")); r = runUnguarded(simpleClass, "run"); assertEquals("ab", r.returnValue); } @Test public void lambdaInvokeVirtual() throws Exception { String t = "basic.LambdaJ"; TypeRegistry typeRegistry = getTypeRegistry("basic..*"); // Since Foo needs promoting to public, have to ensure it is directly loaded: ReloadableType itype = typeRegistry.addType(t + "$Foo", loadBytesForClass(t + "$Foo")); byte[] sc = loadBytesForClass(t); ReloadableType rtype = typeRegistry.addType(t, sc); Class<?> simpleClass = rtype.getClazz(); Result r = runUnguarded(simpleClass, "run"); r = runUnguarded(simpleClass, "run"); assertEquals("fooa", r.returnValue); itype.loadNewVersion("002", retrieveRename(t + "$Foo", t + "2$Foo")); rtype.loadNewVersion("002", retrieveRename(t, t + "2", t + "2$Foo:" + t + "$Foo")); r = runUnguarded(simpleClass, "run"); assertEquals("fooab", r.returnValue); } // https://github.com/spring-projects/spring-loaded/issues/87 @Test public void lambdaMethodReference() throws Exception { String t = "basic.LambdaM"; TypeRegistry typeRegistry = getTypeRegistry("basic..*"); byte[] sc = loadBytesForClass(t); ReloadableType rtype = typeRegistry.addType(t, sc); Class<?> simpleClass = rtype.getClazz(); Result r = runUnguarded(simpleClass, "run"); r = runUnguarded(simpleClass, "run"); assertEquals("{5=test3}", r.returnValue); rtype.loadNewVersion("2", retrieveRename(t, t + "2"));//, t + "2$Foo:" + t + "$Foo")); r = runUnguarded(simpleClass, "run"); assertEquals("{10=test3}", r.returnValue); } // https://github.com/spring-projects/spring-loaded/issues/87 // This variant reloads both pieces @Test public void lambdaMethodReferenceInAnotherClass() throws Exception { TypeRegistry typeRegistry = getTypeRegistry("basic..*"); byte[] sc = loadBytesForClass("basic.LambdaN"); ReloadableType rtype = typeRegistry.addType("basic.LambdaN", sc); byte[] helperBytes = loadBytesForClass("basic.HelperN"); ReloadableType htype = typeRegistry.addType("basic.HelperN", helperBytes); Class<?> simpleClass = rtype.getClazz(); Result r = runUnguarded(simpleClass, "run"); r = runUnguarded(simpleClass, "run"); assertEquals("{15=test3}", r.returnValue); rtype.loadNewVersion(sc); htype.loadNewVersion(helperBytes); r = runUnguarded(simpleClass, "run"); assertEquals("{15=test3}", r.returnValue); } // This variant reloads only the caller @Test public void lambdaMethodReferenceInAnotherClass2() throws Exception { TypeRegistry typeRegistry = getTypeRegistry("basic..*"); byte[] sc = loadBytesForClass("basic.LambdaN"); ReloadableType rtype = typeRegistry.addType("basic.LambdaN", sc); byte[] helperBytes = loadBytesForClass("basic.HelperN"); typeRegistry.addType("basic.HelperN", helperBytes); Class<?> simpleClass = rtype.getClazz(); Result r = runUnguarded(simpleClass, "run"); r = runUnguarded(simpleClass, "run"); assertEquals("{15=test3}", r.returnValue); rtype.loadNewVersion(sc); // htype.loadNewVersion(helperBytes); // don't reload the helper r = runUnguarded(simpleClass, "run"); assertEquals("{15=test3}", r.returnValue); } // This variant reloads only the helper (target) @Test public void lambdaMethodReferenceInAnotherClass3() throws Exception { TypeRegistry typeRegistry = getTypeRegistry("basic..*"); byte[] sc = loadBytesForClass("basic.LambdaN"); ReloadableType rtype = typeRegistry.addType("basic.LambdaN", sc); byte[] helperBytes = loadBytesForClass("basic.HelperN"); ReloadableType htype = typeRegistry.addType("basic.HelperN", helperBytes); Class<?> simpleClass = rtype.getClazz(); Result r = runUnguarded(simpleClass, "run"); r = runUnguarded(simpleClass, "run"); assertEquals("{15=test3}", r.returnValue); // rtype.loadNewVersion(sc); htype.loadNewVersion(helperBytes); // try { r = runUnguarded(simpleClass, "run"); assertEquals("{15=test3}", r.returnValue); // fail("did not expect that to work"); // } // catch (Exception e) { // e.printStackTrace(); // // Caused by: java.lang.NoClassDefFoundError: basic/HelperN$$E2 // // at basic.LambdaN$$Lambda$8/2085857771.apply(Unknown Source) // // at java.util.stream.Collectors.lambda$toMap$172(Collectors.java:1320) // // at java.util.stream.Collectors$$Lambda$5/1521118594.accept(Unknown Source) // // That happens because LambdaN, which has not been reloaded, is loaded by classloader X, the computed // // method to satisfy the lambda is in the executor for the helper, which is in a child classloader - that // // is not visible from the one that loaded LambdaN. // // However, part of the resolution process in the Java8 handling forces LambdaN to reload, so next // // time we go in, the class can be seen because LambdaN$$E2 is in the same classloader. That is // // why when we repeat what we just did, it'll work // } // r = runUnguarded(simpleClass, "run"); // assertEquals("{15=test3}", r.returnValue); } @Test public void lambdaWithDoubleDotConstructor() throws Exception { String t = "basic.LambdaO"; TypeRegistry typeRegistry = getTypeRegistry(t); byte[] sc = loadBytesForClass(t); ReloadableType rtype = typeRegistry.addType(t, sc); Class<?> simpleClass = rtype.getClazz(); Result r = runUnguarded(simpleClass, "run"); r = runUnguarded(simpleClass, "run"); assertEquals(3, r.returnValue); byte[] renamed = retrieveRename(t, t + "2"); rtype.loadNewVersion("002", renamed); r = runUnguarded(simpleClass, "run"); assertEquals(4, r.returnValue); } @Test public void streamWithLambda() throws Exception { String t = "basic.StreamA"; TypeRegistry typeRegistry = getTypeRegistry("basic..*"); byte[] sc = loadBytesForClass(t); ReloadableType rtype = typeRegistry.addType(t, sc); Class<?> simpleClass = rtype.getClazz(); Result r = runUnguarded(simpleClass, "run"); assertEquals(3, r.returnValue); byte[] renamed = retrieveRename(t, t + "2", t + "2$Foo:" + t + "$Foo"); rtype.loadNewVersion("002", renamed); r = runUnguarded(simpleClass, "run"); assertEquals(4, r.returnValue); } // inner interface (for the invokeinterface BSM) @Test public void streamWithLambdaInvokedVirtually() throws Exception { String t = "basic.StreamB"; TypeRegistry typeRegistry = getTypeRegistry("basic..*"); byte[] sc = loadBytesForClass(t); ReloadableType rtype = typeRegistry.addType(t, sc); Class<?> simpleClass = rtype.getClazz(); Result r = runUnguarded(simpleClass, "run"); assertEquals(3, r.returnValue); byte[] renamed = retrieveRename(t, t + "2", t + "2$Foo:" + t + "$Foo"); rtype.loadNewVersion("002", renamed); r = runUnguarded(simpleClass, "run"); assertEquals(4, r.returnValue); } // not an inner interface this time (for the invokeinterface BSM) @Test public void streamWithLambdaInvokedVirtually2() throws Exception { String t = "basic.StreamBB"; TypeRegistry typeRegistry = getTypeRegistry("basic..*"); byte[] sc = loadBytesForClass(t); ReloadableType rtype = typeRegistry.addType(t, sc); Class<?> simpleClass = rtype.getClazz(); Result r = runUnguarded(simpleClass, "run"); assertEquals(3, r.returnValue); byte[] renamed = retrieveRename(t, t + "2"); rtype.loadNewVersion("002", renamed); r = runUnguarded(simpleClass, "run"); assertEquals(4, r.returnValue); } @Test public void streamWithoutLambda() throws Exception { String t = "basic.StreamC"; TypeRegistry typeRegistry = getTypeRegistry("basic..*"); byte[] sc = loadBytesForClass(t); ReloadableType rtype = typeRegistry.addType(t, sc); Class<?> simpleClass = rtype.getClazz(); Result r = runUnguarded(simpleClass, "run"); assertEquals(3, r.returnValue); byte[] renamed = retrieveRename(t, t + "2"); rtype.loadNewVersion("002", renamed); r = runUnguarded(simpleClass, "run"); assertEquals(4, r.returnValue); } @Ignore @Test public void lambdaWithVirtualMethodUse() throws Exception { // not yet written } @Ignore @Test public void altMetaFactoryUsage() throws Exception { // not yet written } // TODO catchers and lambda methods (non static ones) }