Skip to content

Commit d68babf

Browse files
authored
Fix nested container generic type resolution and wildcard override on earlyReturn container path (#1270)
1 parent ed22ca4 commit d68babf

File tree

17 files changed

+816
-104
lines changed

17 files changed

+816
-104
lines changed

fixture-monkey/src/main/java/com/navercorp/fixturemonkey/adapter/analysis/ManipulatorAnalyzer.java

Lines changed: 41 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
import com.navercorp.fixturemonkey.tree.StaticNodeResolver;
7575
import com.navercorp.objectfarm.api.expression.PathExpression;
7676
import com.navercorp.objectfarm.api.input.ContainerDetector;
77+
import com.navercorp.objectfarm.api.input.ExtractedField;
7778
import com.navercorp.objectfarm.api.input.FieldExtractor;
7879
import com.navercorp.objectfarm.api.input.ValueAnalysisResult;
7980
import com.navercorp.objectfarm.api.input.ValueAnalyzer;
@@ -734,47 +735,57 @@ private static ContainerDetector createContainerDetector(DecomposedContainerValu
734735
* @param nameResolver resolves property names for path expressions
735736
*/
736737
private static FieldExtractor createFieldExtractor(DecomposeNameResolver nameResolver) {
737-
return (value, basePath) -> {
738-
Map<String, @Nullable Object> result = new HashMap<>();
738+
return new FieldExtractor() {
739+
@Override
740+
public Map<String, ExtractedField> extractFields(Object value, String basePath) {
741+
List<Property> childProperties = getChildProperties(value);
742+
if (childProperties == null) {
743+
return new HashMap<>();
744+
}
739745

740-
if (value == null) {
746+
Map<String, ExtractedField> result = new HashMap<>();
747+
for (Property childProperty : childProperties) {
748+
String childPath = basePath + "." + nameResolver.resolve(childProperty);
749+
Class<?> declaredType = com.navercorp.fixturemonkey.api.type.Types.getActualType(
750+
childProperty.getType());
751+
result.put(childPath, new ExtractedField(childProperty.getValue(value), declaredType));
752+
}
741753
return result;
742754
}
743755

744-
Class<?> clazz = value.getClass();
756+
private @Nullable List<Property> getChildProperties(Object value) {
757+
if (value == null) {
758+
return null;
759+
}
745760

746-
if (clazz.isPrimitive() || clazz == String.class || clazz.isEnum() || clazz.isArray()) {
747-
return result;
748-
}
761+
Class<?> clazz = value.getClass();
749762

750-
if (isBoxedPrimitive(clazz)) {
751-
return result;
752-
}
763+
if (clazz.isPrimitive() || clazz == String.class || clazz.isEnum() || clazz.isArray()) {
764+
return null;
765+
}
753766

754-
if (
755-
value instanceof Collection
756-
|| value instanceof Map
757-
|| value instanceof Iterator
758-
|| value instanceof Stream
759-
) {
760-
return result;
761-
}
767+
if (isBoxedPrimitive(clazz)) {
768+
return null;
769+
}
762770

763-
if (isJavaType(clazz)) {
764-
return result;
765-
}
771+
if (
772+
value instanceof Collection
773+
|| value instanceof Map
774+
|| value instanceof Iterator
775+
|| value instanceof Stream
776+
) {
777+
return null;
778+
}
766779

767-
AnnotatedType annotatedType =
768-
generateAnnotatedTypeWithoutAnnotation(clazz);
769-
Property parentProperty = new TypeParameterProperty(annotatedType);
770-
List<Property> childProperties = FIELD_PROPERTY_GENERATOR.generateChildProperties(parentProperty);
780+
if (isJavaType(clazz)) {
781+
return null;
782+
}
771783

772-
for (Property childProperty : childProperties) {
773-
String childPath = basePath + "." + nameResolver.resolve(childProperty);
774-
result.put(childPath, childProperty.getValue(value));
784+
AnnotatedType annotatedType =
785+
generateAnnotatedTypeWithoutAnnotation(clazz);
786+
Property parentProperty = new TypeParameterProperty(annotatedType);
787+
return FIELD_PROPERTY_GENERATOR.generateChildProperties(parentProperty);
775788
}
776-
777-
return result;
778789
};
779790
}
780791

fixture-monkey/src/main/java/com/navercorp/fixturemonkey/adapter/projection/ValueProjectionAssembler.java

Lines changed: 74 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
package com.navercorp.fixturemonkey.adapter.projection;
2020

21+
import java.lang.reflect.Array;
2122
import java.lang.reflect.Field;
2223
import java.lang.reflect.Modifier;
2324
import java.util.ArrayList;
@@ -297,8 +298,17 @@ private CombinableArbitrary<?> assembleNode(
297298
);
298299
applyDecomposeResult(decomposeResult, state);
299300
if (decomposeResult.hasEarlyReturn()) {
301+
Object earlyValue = decomposeResult.getEarlyReturnValue();
302+
303+
// Apply wildcard overrides to container elements when a wildcard has higher order
304+
if (isCurrentTypeContainer && earlyValue != null && !state.wildcardEntries.isEmpty()) {
305+
earlyValue = applyWildcardOverridesToContainer(
306+
earlyValue, currentPath, parentOrder, state
307+
);
308+
}
309+
300310
return wrapValueWithFiltersAndCustomizers(
301-
decomposeResult.getEarlyReturnValue(),
311+
earlyValue,
302312
currentPath,
303313
currentRawType,
304314
state
@@ -577,6 +587,7 @@ private CombinableArbitrary<?> assembleNode(
577587
result = options.getDefaultArbitraryGenerator().generate(context);
578588
}
579589

590+
580591
result = applyFilters(result, currentPath, currentRawType, state);
581592
result = applyCustomizers(result, currentPath, state);
582593

@@ -1970,9 +1981,9 @@ private boolean hasFieldLevelTypeSelectorSiblings(PathExpression rootTypePath, A
19701981
return null;
19711982
}
19721983
if (container.getClass().isArray()) {
1973-
int length = java.lang.reflect.Array.getLength(container);
1984+
int length = Array.getLength(container);
19741985
if (index >= 0 && index < length) {
1975-
return java.lang.reflect.Array.get(container, index);
1986+
return Array.get(container, index);
19761987
}
19771988
return null;
19781989
}
@@ -2098,6 +2109,66 @@ private static void applyDecomposeResult(DecomposeResult result, AssemblyState s
20982109
}
20992110
}
21002111

2112+
/**
2113+
* Applies wildcard overrides to container elements when a wildcard has a higher order
2114+
* than the container's own value. This handles the case where a container value is
2115+
* returned via earlyReturn (all decomposed elements match), but a wildcard like
2116+
* {@code $.values[*].values[*]} should override individual elements.
2117+
*/
2118+
private Object applyWildcardOverridesToContainer(
2119+
Object container,
2120+
PathExpression containerPath,
2121+
ValueOrder containerOrder,
2122+
AssemblyState state
2123+
) {
2124+
if (container instanceof List) {
2125+
List<?> list = (List<?>)container;
2126+
List<@Nullable Object> result = null;
2127+
for (int i = 0; i < list.size(); i++) {
2128+
PathExpression elementPath = containerPath.index(i);
2129+
for (Map.Entry<PathExpression, ValueCandidate> entry : state.wildcardEntries) {
2130+
if (entry.getKey().matches(elementPath)
2131+
&& entry.getValue().order.compareTo(containerOrder) > 0) {
2132+
if (result == null) {
2133+
result = new ArrayList<>(list);
2134+
}
2135+
result.set(i, resolveLazyValue(entry.getValue().value, false, state));
2136+
break;
2137+
}
2138+
}
2139+
}
2140+
return result != null ? result : container;
2141+
} else if (container.getClass().isArray()) {
2142+
int length = Array.getLength(container);
2143+
boolean modified = false;
2144+
for (int i = 0; i < length; i++) {
2145+
PathExpression elementPath = containerPath.index(i);
2146+
for (Map.Entry<PathExpression, ValueCandidate> entry : state.wildcardEntries) {
2147+
if (entry.getKey().matches(elementPath)
2148+
&& entry.getValue().order.compareTo(containerOrder) > 0) {
2149+
if (!modified) {
2150+
Class<?> componentType = container.getClass().getComponentType();
2151+
if (componentType == null) {
2152+
break;
2153+
}
2154+
Object copy = Array.newInstance(componentType, length);
2155+
//noinspection SuspiciousSystemArraycopy
2156+
System.arraycopy(container, 0, copy, 0, length);
2157+
container = copy;
2158+
modified = true;
2159+
}
2160+
Object resolved = resolveLazyValue(entry.getValue().value, false, state);
2161+
if (resolved != null) {
2162+
Array.set(container, i, resolved);
2163+
}
2164+
break;
2165+
}
2166+
}
2167+
}
2168+
}
2169+
return container;
2170+
}
2171+
21012172
private @Nullable Object resolveLazyValue(@Nullable Object value, boolean isFromRegister, AssemblyState state) {
21022173
if (!(value instanceof LazyValueHolder)) {
21032174
return value;

fixture-monkey/src/test/java/com/navercorp/fixturemonkey/adapter/ContainerAdapterTest.java

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,10 @@
3131
import com.navercorp.fixturemonkey.FixtureMonkey;
3232
import com.navercorp.fixturemonkey.api.type.TypeReference;
3333
import com.navercorp.fixturemonkey.test.FixtureMonkeyTestSpecs.EnumObject;
34+
import com.navercorp.fixturemonkey.test.FixtureMonkeyTestSpecs.ListListStringObject;
3435
import com.navercorp.fixturemonkey.test.FixtureMonkeyTestSpecs.MapEntryWrapper;
36+
import com.navercorp.fixturemonkey.test.FixtureMonkeyTestSpecs.MapStringListObject;
37+
import com.navercorp.fixturemonkey.test.FixtureMonkeyTestSpecs.OptionalListStringObject;
3538
import com.navercorp.fixturemonkey.test.FixtureMonkeyTestSpecs.SimpleObject;
3639
import com.navercorp.fixturemonkey.test.FixtureMonkeyTestSpecs.TwoEnum;
3740

@@ -148,4 +151,52 @@ void sampleMapEntryWrapper() {
148151
then(actual.getComplexEntry().getValue()).isNotNull();
149152
}
150153

154+
@Property
155+
void sampleNestedListList() {
156+
// when
157+
ListListStringObject actual = SUT.giveMeBuilder(ListListStringObject.class)
158+
.size("values", 2)
159+
.size("values[*]", 2)
160+
.sample();
161+
162+
// then
163+
then(actual.getValues()).hasSize(2);
164+
for (List<String> inner : actual.getValues()) {
165+
then(inner).hasSize(2);
166+
for (String s : inner) {
167+
then(s).isNotNull();
168+
}
169+
}
170+
}
171+
172+
@Property
173+
void sampleMapWithListValue() {
174+
// when
175+
MapStringListObject actual = SUT.giveMeBuilder(MapStringListObject.class)
176+
.size("values", 2)
177+
.sample();
178+
179+
// then
180+
then(actual.getValues()).hasSize(2);
181+
for (Map.Entry<String, List<String>> entry : actual.getValues().entrySet()) {
182+
then(entry.getKey()).isNotNull();
183+
then(entry.getValue()).isNotNull();
184+
}
185+
}
186+
187+
@Property
188+
void sampleOptionalWithList() {
189+
// when
190+
OptionalListStringObject actual = SUT.giveMeBuilder(OptionalListStringObject.class)
191+
.size("value", 2)
192+
.sample();
193+
194+
// then
195+
then(actual.getValue()).isPresent();
196+
then(actual.getValue().get()).hasSize(2);
197+
for (String s : actual.getValue().get()) {
198+
then(s).isNotNull();
199+
}
200+
}
201+
151202
}

fixture-monkey/src/test/java/com/navercorp/fixturemonkey/adapter/CustomizationAdapterTest.java

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1375,4 +1375,64 @@ void sizeThenSetLazyElement() {
13751375
then(actual.getValues()).hasSize(3);
13761376
then(actual.getValues().get(0)).isEqualTo("LAZY_FIRST");
13771377
}
1378+
1379+
@Property
1380+
void setNonPublicJdkListThenApplyOverridesElements() {
1381+
// given
1382+
List<String> arraysAsList = Arrays.asList("a", "b");
1383+
1384+
// when
1385+
List<String> actual = SUT.giveMeBuilder(StringListWrapper.class)
1386+
.set("values", arraysAsList)
1387+
.thenApply((obj, builder) ->
1388+
builder.set("values[0]", "override")
1389+
)
1390+
.sample()
1391+
.getValues();
1392+
1393+
// then
1394+
then(actual).hasSize(2);
1395+
then(actual.get(0)).isEqualTo("override");
1396+
then(actual.get(1)).isEqualTo("b");
1397+
}
1398+
1399+
@Property
1400+
void setCollectionsSingletonListThenApplyOverridesElements() {
1401+
// given
1402+
List<String> singletonList = Collections.singletonList("original");
1403+
1404+
// when
1405+
List<String> actual = SUT.giveMeBuilder(StringListWrapper.class)
1406+
.set("values", singletonList)
1407+
.thenApply((obj, builder) ->
1408+
builder.set("values[0]", "override")
1409+
)
1410+
.sample()
1411+
.getValues();
1412+
1413+
// then
1414+
then(actual).hasSize(1);
1415+
then(actual.get(0)).isEqualTo("override");
1416+
}
1417+
1418+
@Property
1419+
void setIterableWithArraysAsListThenApplyOverridesElements() {
1420+
// given
1421+
List<String> arraysAsList = Arrays.asList("a", "b");
1422+
1423+
// when
1424+
ComplexObject actual = SUT.giveMeBuilder(ComplexObject.class)
1425+
.set("strIterable", arraysAsList)
1426+
.thenApply((obj, builder) ->
1427+
builder.set("strIterable[0]", "override")
1428+
)
1429+
.sample();
1430+
1431+
// then
1432+
List<String> values = new ArrayList<>();
1433+
actual.getStrIterable().forEach(values::add);
1434+
then(values).hasSize(2);
1435+
then(values.get(0)).isEqualTo("override");
1436+
then(values.get(1)).isEqualTo("b");
1437+
}
13781438
}

0 commit comments

Comments
 (0)