[EmbedUrlProcessorServlet] Non-deterministic behavior of Class.getDeclaredFields() might fail test EmbedUrlProcessorServletTest.testUrlWithRegisteredProvider() #2611
However, ObjectMapper.writeValue() converts the value into JSON by fetching the fields of UrlProcessorResultImpl, which are processor and options, using Java reflection Class.getDeclaredFields() at AnnotatedFieldCollector.java:73
The official Javadoc of Class.getDeclaredFields() mentions that the elements are not returned in any particular order. This causes issue where the order of class fields are expected to be in a specific order.
But this order might be changed based on the order of fields returned by Class.getDeclaredFields(), which could result in the following error
[INFO] Results:
[INFO]
[ERROR] Failures:
[ERROR] EmbedUrlProcessorServletTest.testUrlWithRegisteredProvider:78 Does not match the expected response output. ==> expected: <{"processor":"pinterest","options":{"pinId":"99360735500167749"}}> but was: <{"options":{"pinId":"99360735500167749"},"processor":"pinterest"}>
[INFO]
[ERROR] Tests run: 1, Failures: 1, Errors: 0, Skipped: 0
[INFO]
Steps to reproduce
We could observe the above mentioned issue by running the test through NonDex, which shuffles the order of elements returned by Class.getDeclaredFields()
# Clone the Repo
https://github.com/adobe/aem-core-wcm-components.git
**Expected behavior/code**
- For any amount of runs, the test should have passes successfully (with `NonDex` too).
- The expected order of elements might not necessarily be `processor` followed by `options`.
**Environment**
- Core Components version: `2.23.5-SNAPSHOT`
- Java version: `Java(TM) SE Runtime Environment (build 1.8.0_381-b09)`
**Possible Solution 1**
- Since the test validates the data of the json and not the order, we can convert the strings to `JsonNode` and compare the nodes.
JsonNode actual = mapper.readTree(context.response().getOutputAsString());
assertEquals(expected, actual, "Does not match the expected response output.");
**Possible Solution 2**
Since the order of elements might change inside ObjectMapper and there are only two possible strings returned, we could expect both the possibilities in the test.
Bug Report
Current Behavior
EmbedUrlProcessorServlet.doGet()
writes the result fetch intoUrlProcessor.Result
asJSON
usingObjectMapper
at https://github.com/adobe/aem-core-wcm-components/blob/3524e33424bf6831faca014284c5bad803d3744d/bundles/core/src/main/java/com/adobe/cq/wcm/core/components/internal/servlets/embed/EmbedUrlProcessorServlet.java#L69-L84ObjectMapper.writeValue()
converts the value intoJSON
by fetching the fields ofUrlProcessorResultImpl
, which areprocessor
andoptions
, using Java reflectionClass.getDeclaredFields()
atAnnotatedFieldCollector.java:73
Class.getDeclaredFields()
mentions that the elements are not returned in any particular order. This causes issue where the order of class fields are expected to be in a specific order.com.adobe.cq.wcm.core.components.internal.servlets.embed.EmbedUrlProcessorServletTest.testUrlWithRegisteredProvider()
, where the JSON string result from theEmbedUrlProcessorServlet.doGet()
is expected to be in the order ofprocess
followed byoptions
at https://github.com/adobe/aem-core-wcm-components/blob/3524e33424bf6831faca014284c5bad803d3744d/bundles/core/src/test/java/com/adobe/cq/wcm/core/components/internal/servlets/embed/EmbedUrlProcessorServletTest.java#L74Class.getDeclaredFields()
, which could result in the following errorSteps to reproduce
Class.getDeclaredFields()
Compile the module byte-buddy-dep
mvn clean install -pl bundles/core -am -DskipTests
(Optional) Run the unit test
mvn -pl bundles/core test -Dtest=com.adobe.cq.wcm.core.components.internal.servlets.embed.EmbedUrlProcessorServletTest#testUrlWithRegisteredProvider
Run the unit test using NonDex
mvn -pl bundles/core edu.illinois:nondex-maven-plugin:2.1.1:nondex -Dtest=com.adobe.cq.wcm.core.components.internal.servlets.embed.EmbedUrlProcessorServletTest#testUrlWithRegisteredProvider
+/- com.adobe.cq.wcm.core.components.internal.servlets.embed.EmbedUrlProcessorServletTest.testUrlWithRegisteredProvider()
String expectedOutput = "{\"processor\":\"pinterest\",\"options\":{\"pinId\":\"99360735500167749\"}}";
assertEquals(expectedOutput, context.response().getOutputAsString(), "Does not match the expected response output.");
ObjectMapper mapper = new ObjectMapper();
JsonNode expected = mapper.readTree("{\"processor\":\"pinterest\",\"options\":{\"pinId\":\"99360735500167749\"}}");
JsonNode actual = mapper.readTree(context.response().getOutputAsString());
assertEquals(expected, actual, "Does not match the expected response output.");
Since the order of elements might change inside
ObjectMapper
and there are only two possible strings returned, we could expect both the possibilities in the test.String expectedOutput = "{\"processor\":\"pinterest\",\"options\":{\"pinId\":\"99360735500167749\"}}";
assertEquals(expectedOutput, context.response().getOutputAsString(), "Does not match the expected response output.");
String expectedOutput1 = "{\"processor\":\"pinterest\",\"options\":{\"pinId\":\"99360735500167749\"}}";
String expectedOutput2 = "{\"options\":{\"pinId\":\"99360735500167749\"},\"processor\":\"pinterest\"}";
String actualOutput = context.response().getOutputAsString();
assertTrue(expectedOutput1.equals(actualOutput) || expectedOutput2.equals(actualOutput), "Does not match the expected response output.");
If the proposed solution looks good, I can create a PR.