244 lines
8.0 KiB
Java
244 lines
8.0 KiB
Java
/*
|
|
* Copyright (C) 2023 Google Inc.
|
|
*
|
|
* 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 com.google.gson.it;
|
|
|
|
import static com.google.common.truth.Truth.assertThat;
|
|
import static org.junit.Assert.assertThrows;
|
|
import static org.junit.Assert.fail;
|
|
|
|
import java.lang.reflect.InvocationTargetException;
|
|
import java.lang.reflect.Method;
|
|
import java.net.URL;
|
|
import java.net.URLClassLoader;
|
|
import java.nio.file.Files;
|
|
import java.nio.file.Path;
|
|
import java.nio.file.Paths;
|
|
import java.util.Arrays;
|
|
import java.util.List;
|
|
import java.util.function.BiConsumer;
|
|
import org.junit.Before;
|
|
import org.junit.Test;
|
|
import org.junit.runner.RunWith;
|
|
import org.junit.runners.Parameterized;
|
|
import org.junit.runners.Parameterized.Parameter;
|
|
import org.junit.runners.Parameterized.Parameters;
|
|
|
|
/**
|
|
* Integration test verifying behavior of shrunken and obfuscated JARs.
|
|
*/
|
|
@RunWith(Parameterized.class)
|
|
public class ShrinkingIT {
|
|
// These JAR files are prepared by the Maven build
|
|
public static final Path PROGUARD_RESULT_PATH = Paths.get("target/proguard-output.jar");
|
|
public static final Path R8_RESULT_PATH = Paths.get("target/r8-output.jar");
|
|
|
|
@Parameters(name = "{index}: {0}")
|
|
public static List<Path> jarsToTest() {
|
|
return Arrays.asList(PROGUARD_RESULT_PATH, R8_RESULT_PATH);
|
|
}
|
|
|
|
@Parameter
|
|
public Path jarToTest;
|
|
|
|
@Before
|
|
public void verifyJarExists() {
|
|
if (!Files.isRegularFile(jarToTest)) {
|
|
fail("JAR file " + jarToTest + " does not exist; run this test with `mvn clean verify`");
|
|
}
|
|
}
|
|
|
|
@FunctionalInterface
|
|
interface TestAction {
|
|
void run(Class<?> c) throws Exception;
|
|
}
|
|
|
|
private void runTest(String className, TestAction testAction) throws Exception {
|
|
// Use bootstrap class loader; load all custom classes from JAR and not
|
|
// from dependencies of this test
|
|
ClassLoader classLoader = null;
|
|
|
|
// Load the shrunken and obfuscated JARs with a separate class loader, then load
|
|
// the main test class from it and let the test action invoke its test methods
|
|
try (URLClassLoader loader = new URLClassLoader(new URL[] {jarToTest.toUri().toURL()}, classLoader)) {
|
|
Class<?> c = loader.loadClass(className);
|
|
testAction.run(c);
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void test() throws Exception {
|
|
StringBuilder output = new StringBuilder();
|
|
|
|
runTest("com.example.Main", c -> {
|
|
Method m = c.getMethod("runTests", BiConsumer.class);
|
|
m.invoke(null, (BiConsumer<String, String>) (name, content) -> output.append(name + "\n" + content + "\n===\n"));
|
|
});
|
|
|
|
assertThat(output.toString()).isEqualTo(String.join("\n",
|
|
"Write: TypeToken anonymous",
|
|
"[",
|
|
" {",
|
|
" \"custom\": 1",
|
|
" }",
|
|
"]",
|
|
"===",
|
|
"Read: TypeToken anonymous",
|
|
"[ClassWithAdapter[3]]",
|
|
"===",
|
|
"Write: TypeToken manual",
|
|
"[",
|
|
" {",
|
|
" \"custom\": 1",
|
|
" }",
|
|
"]",
|
|
"===",
|
|
"Read: TypeToken manual",
|
|
"[ClassWithAdapter[3]]",
|
|
"===",
|
|
"Write: Named fields",
|
|
"{",
|
|
" \"myField\": 2,",
|
|
" \"notAccessedField\": -1",
|
|
"}",
|
|
"===",
|
|
"Read: Named fields",
|
|
"3",
|
|
"===",
|
|
"Write: SerializedName",
|
|
"{",
|
|
" \"myField\": 2,",
|
|
" \"notAccessed\": -1",
|
|
"}",
|
|
"===",
|
|
"Read: SerializedName",
|
|
"3",
|
|
"===",
|
|
"Read: No JDK Unsafe; initial constructor value",
|
|
"-3",
|
|
"===",
|
|
"Read: No JDK Unsafe; custom value",
|
|
"3",
|
|
"===",
|
|
"Write: Enum",
|
|
"\"FIRST\"",
|
|
"===",
|
|
"Read: Enum",
|
|
"SECOND",
|
|
"===",
|
|
"Write: Enum SerializedName",
|
|
"\"one\"",
|
|
"===",
|
|
"Read: Enum SerializedName",
|
|
"SECOND",
|
|
"===",
|
|
"Write: @Expose",
|
|
"{\"i\":0}",
|
|
"===",
|
|
"Write: Version annotations",
|
|
"{\"i1\":0,\"i4\":0}",
|
|
"===",
|
|
"Write: JsonAdapter on fields",
|
|
"{",
|
|
" \"f\": \"adapter-null\",",
|
|
" \"f1\": \"adapter-1\",",
|
|
" \"f2\": \"factory-2\",",
|
|
" \"f3\": \"serializer-3\",",
|
|
// For f4 only a JsonDeserializer is registered, so serialization falls back to reflection
|
|
" \"f4\": {",
|
|
" \"s\": \"4\"",
|
|
" }",
|
|
"}",
|
|
"===",
|
|
"Read: JsonAdapter on fields",
|
|
// For f3 only a JsonSerializer is registered, so for deserialization value is read as is using reflection
|
|
"ClassWithJsonAdapterAnnotation[f1=adapter-1, f2=factory-2, f3=3, f4=deserializer-4]",
|
|
"===",
|
|
"Read: Generic TypeToken",
|
|
"{t=read-1}",
|
|
"===",
|
|
"Read: Using Generic",
|
|
"{g={t=read-1}}",
|
|
"===",
|
|
"Read: Using Generic TypeToken",
|
|
"{g={t=read-1}}",
|
|
"===",
|
|
""
|
|
));
|
|
}
|
|
|
|
@Test
|
|
public void testDefaultConstructor() throws Exception {
|
|
runTest("com.example.DefaultConstructorMain", c -> {
|
|
Method m = c.getMethod("runTest");
|
|
|
|
if (jarToTest.equals(PROGUARD_RESULT_PATH)) {
|
|
Object result = m.invoke(null);
|
|
assertThat(result).isEqualTo("value");
|
|
} else {
|
|
// R8 performs more aggressive optimizations
|
|
Exception e = assertThrows(InvocationTargetException.class, () -> m.invoke(null));
|
|
assertThat(e).hasCauseThat().hasMessageThat().isEqualTo(
|
|
"Abstract classes can't be instantiated! Adjust the R8 configuration or register an InstanceCreator"
|
|
+ " or a TypeAdapter for this type. Class name: com.example.DefaultConstructorMain$TestClass"
|
|
+ "\nSee https://github.com/google/gson/blob/main/Troubleshooting.md#r8-abstract-class"
|
|
);
|
|
}
|
|
});
|
|
}
|
|
|
|
@Test
|
|
public void testDefaultConstructorNoJdkUnsafe() throws Exception {
|
|
runTest("com.example.DefaultConstructorMain", c -> {
|
|
Method m = c.getMethod("runTestNoJdkUnsafe");
|
|
|
|
if (jarToTest.equals(PROGUARD_RESULT_PATH)) {
|
|
Object result = m.invoke(null);
|
|
assertThat(result).isEqualTo("value");
|
|
} else {
|
|
// R8 performs more aggressive optimizations
|
|
Exception e = assertThrows(InvocationTargetException.class, () -> m.invoke(null));
|
|
assertThat(e).hasCauseThat().hasMessageThat().isEqualTo(
|
|
"Unable to create instance of class com.example.DefaultConstructorMain$TestClassNotAbstract;"
|
|
+ " usage of JDK Unsafe is disabled. Registering an InstanceCreator or a TypeAdapter for this type,"
|
|
+ " adding a no-args constructor, or enabling usage of JDK Unsafe may fix this problem. Or adjust"
|
|
+ " your R8 configuration to keep the no-args constructor of the class."
|
|
);
|
|
}
|
|
});
|
|
}
|
|
|
|
@Test
|
|
public void testNoDefaultConstructor() throws Exception {
|
|
runTest("com.example.DefaultConstructorMain", c -> {
|
|
Method m = c.getMethod("runTestNoDefaultConstructor");
|
|
|
|
if (jarToTest.equals(PROGUARD_RESULT_PATH)) {
|
|
Object result = m.invoke(null);
|
|
assertThat(result).isEqualTo("value");
|
|
} else {
|
|
// R8 performs more aggressive optimizations
|
|
Exception e = assertThrows(InvocationTargetException.class, () -> m.invoke(null));
|
|
assertThat(e).hasCauseThat().hasMessageThat().isEqualTo(
|
|
"Abstract classes can't be instantiated! Adjust the R8 configuration or register an InstanceCreator"
|
|
+ " or a TypeAdapter for this type. Class name: com.example.DefaultConstructorMain$TestClassWithoutDefaultConstructor"
|
|
+ "\nSee https://github.com/google/gson/blob/main/Troubleshooting.md#r8-abstract-class"
|
|
);
|
|
}
|
|
});
|
|
}
|
|
}
|