Write runtime hints with deterministic order

This commit updates the JSON writers to use a deterministic order for
arrays. Previously, the order could change with the same content,
breaking caching.

Closes gh-31852
This commit is contained in:
Stéphane Nicoll
2023-12-15 16:55:13 +01:00
parent 7965c1969f
commit eaf7a28250
11 changed files with 183 additions and 42 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@ package org.springframework.aot.nativex;
import java.io.StringWriter;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import org.json.JSONException;
import org.junit.jupiter.api.Test;
@@ -32,8 +33,9 @@ import org.springframework.aot.hint.TypeReference;
* Tests for {@link ProxyHintsWriter}.
*
* @author Sebastien Deleuze
* @author Stephane Nicoll
*/
public class ProxyHintsWriterTests {
class ProxyHintsWriterTests {
@Test
void empty() throws JSONException {
@@ -63,6 +65,18 @@ public class ProxyHintsWriterTests {
]""", hints);
}
@Test
void shouldWriteEntriesInNaturalOrder() throws JSONException {
ProxyHints hints = new ProxyHints();
hints.registerJdkProxy(Supplier.class);
hints.registerJdkProxy(Function.class);
assertEquals("""
[
{ "interfaces": [ "java.util.function.Function" ] },
{ "interfaces": [ "java.util.function.Supplier" ] }
]""", hints);
}
@Test
void shouldWriteInnerClass() throws JSONException {
ProxyHints hints = new ProxyHints();
@@ -88,7 +102,7 @@ public class ProxyHintsWriterTests {
StringWriter out = new StringWriter();
BasicJsonWriter writer = new BasicJsonWriter(out, "\t");
ProxyHintsWriter.INSTANCE.write(writer, hints);
JSONAssert.assertEquals(expectedString, out.toString(), JSONCompareMode.NON_EXTENSIBLE);
JSONAssert.assertEquals(expectedString, out.toString(), JSONCompareMode.STRICT);
}
interface Inner {

View File

@@ -33,10 +33,13 @@ import org.springframework.aot.hint.TypeReference;
import org.springframework.core.codec.StringDecoder;
import org.springframework.util.MimeType;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ReflectionHintsWriter}.
*
* @author Sebastien Deleuze
* @author Stephane Nicoll
*/
public class ReflectionHintsWriterTests {
@@ -59,6 +62,7 @@ public class ReflectionHintsWriterTests {
MemberCategory.PUBLIC_CLASSES, MemberCategory.DECLARED_CLASSES)
.withField("DEFAULT_CHARSET")
.withField("defaultCharset")
.withField("aScore")
.withConstructor(TypeReference.listOf(List.class, boolean.class, MimeType.class), ExecutableMode.INTROSPECT)
.withMethod("setDefaultCharset", List.of(TypeReference.of(Charset.class)), ExecutableMode.INVOKE)
.withMethod("getDefaultCharset", Collections.emptyList(), ExecutableMode.INTROSPECT));
@@ -80,6 +84,7 @@ public class ReflectionHintsWriterTests {
"allPublicClasses": true,
"allDeclaredClasses": true,
"fields": [
{ "name": "aScore" },
{ "name": "DEFAULT_CHARSET" },
{ "name": "defaultCharset" }
],
@@ -203,17 +208,83 @@ public class ReflectionHintsWriterTests {
@Test
void ignoreLambda() throws JSONException {
Runnable anonymousRunnable = () -> { };
Runnable anonymousRunnable = () -> {};
ReflectionHints hints = new ReflectionHints();
hints.registerType(anonymousRunnable.getClass());
assertEquals("[]", hints);
}
@Test
void sortTypeHints() {
ReflectionHints hints = new ReflectionHints();
hints.registerType(Integer.class, builder -> {});
hints.registerType(Long.class, builder -> {});
ReflectionHints hints2 = new ReflectionHints();
hints2.registerType(Long.class, builder -> {});
hints2.registerType(Integer.class, builder -> {});
assertThat(writeJson(hints)).isEqualTo(writeJson(hints2));
}
@Test
void sortFieldHints() {
ReflectionHints hints = new ReflectionHints();
hints.registerType(Integer.class, builder -> {
builder.withField("first");
builder.withField("second");
});
ReflectionHints hints2 = new ReflectionHints();
hints2.registerType(Integer.class, builder -> {
builder.withField("second");
builder.withField("first");
});
assertThat(writeJson(hints)).isEqualTo(writeJson(hints2));
}
@Test
void sortConstructorHints() {
ReflectionHints hints = new ReflectionHints();
hints.registerType(Integer.class, builder -> {
builder.withConstructor(List.of(TypeReference.of(String.class)), ExecutableMode.INVOKE);
builder.withConstructor(List.of(TypeReference.of(String.class),
TypeReference.of(Integer.class)), ExecutableMode.INVOKE);
});
ReflectionHints hints2 = new ReflectionHints();
hints2.registerType(Integer.class, builder -> {
builder.withConstructor(List.of(TypeReference.of(String.class),
TypeReference.of(Integer.class)), ExecutableMode.INVOKE);
builder.withConstructor(List.of(TypeReference.of(String.class)), ExecutableMode.INVOKE);
});
assertThat(writeJson(hints)).isEqualTo(writeJson(hints2));
}
@Test
void sortMethodHints() {
ReflectionHints hints = new ReflectionHints();
hints.registerType(Integer.class, builder -> {
builder.withMethod("test", Collections.emptyList(), ExecutableMode.INVOKE);
builder.withMethod("another", Collections.emptyList(), ExecutableMode.INVOKE);
});
ReflectionHints hints2 = new ReflectionHints();
hints2.registerType(Integer.class, builder -> {
builder.withMethod("another", Collections.emptyList(), ExecutableMode.INVOKE);
builder.withMethod("test", Collections.emptyList(), ExecutableMode.INVOKE);
});
assertThat(writeJson(hints)).isEqualTo(writeJson(hints2));
}
private void assertEquals(String expectedString, ReflectionHints hints) throws JSONException {
JSONAssert.assertEquals(expectedString, writeJson(hints), JSONCompareMode.STRICT);
}
private String writeJson(ReflectionHints hints) {
StringWriter out = new StringWriter();
BasicJsonWriter writer = new BasicJsonWriter(out, "\t");
ReflectionHintsWriter.INSTANCE.write(writer, hints);
JSONAssert.assertEquals(expectedString, out.toString(), JSONCompareMode.NON_EXTENSIBLE);
return out.toString();
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -49,11 +49,11 @@ class ResourceHintsWriterTests {
{
"resources": {
"includes": [
{ "pattern": "\\\\Qcom/example/test.properties\\\\E"},
{ "pattern": "\\\\Q/\\\\E" },
{ "pattern": "\\\\Qcom\\\\E"},
{ "pattern": "\\\\Qcom/example\\\\E"},
{ "pattern": "\\\\Qcom/example/another.properties\\\\E"}
{ "pattern": "\\\\Qcom/example/another.properties\\\\E"},
{ "pattern": "\\\\Qcom/example/test.properties\\\\E"}
]
}
}""", hints);
@@ -82,10 +82,10 @@ class ResourceHintsWriterTests {
{
"resources": {
"includes": [
{ "pattern": "\\\\Qcom/example/\\\\E.*\\\\Q.properties\\\\E"},
{ "pattern": "\\\\Q/\\\\E" },
{ "pattern": "\\\\Qcom\\\\E"},
{ "pattern": "\\\\Qcom/example\\\\E"}
{ "pattern": "\\\\Qcom/example\\\\E"},
{ "pattern": "\\\\Qcom/example/\\\\E.*\\\\Q.properties\\\\E"}
]
}
}""", hints);
@@ -99,9 +99,9 @@ class ResourceHintsWriterTests {
{
"resources": {
"includes": [
{ "pattern": "\\\\Qstatic/\\\\E.*"},
{ "pattern": "\\\\Q/\\\\E" },
{ "pattern": "\\\\Qstatic\\\\E"}
{ "pattern": "\\\\Qstatic\\\\E"},
{ "pattern": "\\\\Qstatic/\\\\E.*"}
]
}
}""", hints);
@@ -116,13 +116,13 @@ class ResourceHintsWriterTests {
{
"resources": {
"includes": [
{ "pattern": "\\\\Qcom/example/\\\\E.*\\\\Q.properties\\\\E"},
{ "pattern": "\\\\Q/\\\\E"},
{ "pattern": "\\\\Qcom\\\\E"},
{ "pattern": "\\\\Qcom/example\\\\E"},
{ "pattern": "\\\\Qorg/other/\\\\E.*\\\\Q.properties\\\\E"},
{ "pattern": "\\\\Qcom/example/\\\\E.*\\\\Q.properties\\\\E"},
{ "pattern": "\\\\Qorg\\\\E"},
{ "pattern": "\\\\Qorg/other\\\\E"}
{ "pattern": "\\\\Qorg/other\\\\E"},
{ "pattern": "\\\\Qorg/other/\\\\E.*\\\\Q.properties\\\\E"}
],
"excludes": [
{ "pattern": "\\\\Qcom/example/to-ignore.properties\\\\E"},
@@ -140,10 +140,10 @@ class ResourceHintsWriterTests {
{
"resources": {
"includes": [
{ "condition": { "typeReachable": "com.example.Test"}, "pattern": "\\\\Qcom/example/test.properties\\\\E"},
{ "condition": { "typeReachable": "com.example.Test"}, "pattern": "\\\\Q/\\\\E"},
{ "condition": { "typeReachable": "com.example.Test"}, "pattern": "\\\\Qcom\\\\E"},
{ "condition": { "typeReachable": "com.example.Test"}, "pattern": "\\\\Qcom/example\\\\E"}
{ "condition": { "typeReachable": "com.example.Test"}, "pattern": "\\\\Qcom/example\\\\E"},
{ "condition": { "typeReachable": "com.example.Test"}, "pattern": "\\\\Qcom/example/test.properties\\\\E"}
]
}
}""", hints);
@@ -157,10 +157,10 @@ class ResourceHintsWriterTests {
{
"resources": {
"includes": [
{ "pattern": "\\\\Qjava/lang/String.class\\\\E" },
{ "pattern": "\\\\Q/\\\\E" },
{ "pattern": "\\\\Qjava\\\\E" },
{ "pattern": "\\\\Qjava/lang\\\\E" }
{ "pattern": "\\\\Qjava/lang\\\\E" },
{ "pattern": "\\\\Qjava/lang/String.class\\\\E" }
]
}
}""", hints);
@@ -169,8 +169,8 @@ class ResourceHintsWriterTests {
@Test
void registerResourceBundle() throws JSONException {
ResourceHints hints = new ResourceHints();
hints.registerResourceBundle("com.example.message");
hints.registerResourceBundle("com.example.message2");
hints.registerResourceBundle("com.example.message");
assertEquals("""
{
"bundles": [
@@ -184,7 +184,7 @@ class ResourceHintsWriterTests {
StringWriter out = new StringWriter();
BasicJsonWriter writer = new BasicJsonWriter(out, "\t");
ResourceHintsWriter.INSTANCE.write(writer, hints);
JSONAssert.assertEquals(expectedString, out.toString(), JSONCompareMode.NON_EXTENSIBLE);
JSONAssert.assertEquals(expectedString, out.toString(), JSONCompareMode.STRICT);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -52,8 +52,8 @@ public class SerializationHintsWriterTests {
@Test
void shouldWriteMultipleHints() throws JSONException {
SerializationHints hints = new SerializationHints()
.registerType(TypeReference.of(String.class))
.registerType(TypeReference.of(Environment.class));
.registerType(TypeReference.of(Environment.class))
.registerType(TypeReference.of(String.class));
assertEquals("""
[
{ "name": "java.lang.String" },
@@ -75,7 +75,7 @@ public class SerializationHintsWriterTests {
StringWriter out = new StringWriter();
BasicJsonWriter writer = new BasicJsonWriter(out, "\t");
SerializationHintsWriter.INSTANCE.write(writer, hints);
JSONAssert.assertEquals(expectedString, out.toString(), JSONCompareMode.NON_EXTENSIBLE);
JSONAssert.assertEquals(expectedString, out.toString(), JSONCompareMode.STRICT);
}
}