Skip to content

Commit bde5032

Browse files
committed
Fix nested container generic type resolution and wildcard override on earlyReturn container path
1 parent dc41400 commit bde5032

File tree

6 files changed

+316
-17
lines changed

6 files changed

+316
-17
lines changed

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/ThenApplyOrderingAdapterTest.java

Lines changed: 188 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,21 @@
2121
import static org.assertj.core.api.BDDAssertions.then;
2222

2323
import java.util.ArrayList;
24+
import java.util.Arrays;
25+
import java.util.Collections;
2426
import java.util.List;
2527

2628
import net.jqwik.api.Property;
2729
import net.jqwik.api.PropertyDefaults;
2830

2931
import com.navercorp.fixturemonkey.FixtureMonkey;
32+
3033
import com.navercorp.fixturemonkey.test.FixtureMonkeyTestSpecs.ComplexObject;
3134
import com.navercorp.fixturemonkey.test.FixtureMonkeyTestSpecs.DoubleNestedStringListWrapper;
35+
import com.navercorp.fixturemonkey.test.FixtureMonkeyTestSpecs.ListListStringObject;
36+
import com.navercorp.fixturemonkey.test.FixtureMonkeyTestSpecs.NestedStringArrayWrapper;
3237
import com.navercorp.fixturemonkey.test.FixtureMonkeyTestSpecs.NestedStringListWrapper;
38+
import com.navercorp.fixturemonkey.test.FixtureMonkeyTestSpecs.StringArrayWrapper;
3339
import com.navercorp.fixturemonkey.test.FixtureMonkeyTestSpecs.StringListWrapper;
3440

3541
@PropertyDefaults(tries = 10)
@@ -729,7 +735,7 @@ void setDecomposedMultipleIndicesThenApplyWildcardSize() {
729735
then(actual.get(1).getValues().get(1)).isEqualTo("y");
730736
}
731737

732-
@Property(tries = 1000)
738+
@Property(tries = 1)
733739
void setDecomposedThenApplyWildcardSetOverride() {
734740
// given
735741
List<String> innerList = new ArrayList<>();
@@ -740,6 +746,7 @@ void setDecomposedThenApplyWildcardSetOverride() {
740746
// when
741747
List<StringListWrapper> actual = SUT.giveMeBuilder(NestedStringListWrapper.class)
742748
.set("values[0].values", innerList)
749+
.set("values[1].values", Arrays.asList("a", "b"))
743750
.thenApply((obj, builder) ->
744751
builder.size("values", 2).size("values[*].values", 2).set("values[*].values[*]", "override")
745752
)
@@ -921,4 +928,184 @@ void thenApplySizeThenSetNullContainer() {
921928
// then
922929
then(actual.getStrList()).isNull();
923930
}
931+
932+
@Property
933+
void setDecomposedThenApplyWildcardSetOverrideWithoutSizeChange() {
934+
// given
935+
List<String> innerList = Arrays.asList("1", "2", "3");
936+
937+
// when
938+
List<StringListWrapper> actual = SUT.giveMeBuilder(NestedStringListWrapper.class)
939+
.size("values", 2)
940+
.size("values[*].values", 3)
941+
.set("values[0].values", innerList)
942+
.set("values[1].values", innerList)
943+
.thenApply((obj, builder) ->
944+
builder.set("values[*].values[*]", "override")
945+
)
946+
.sample()
947+
.getValues();
948+
949+
// then
950+
then(actual).hasSize(2);
951+
for (int j = 0; j < 2; j++) {
952+
then(actual.get(j).getValues()).hasSize(3);
953+
for (int k = 0; k < 3; k++) {
954+
then(actual.get(j).getValues().get(k)).isEqualTo("override");
955+
}
956+
}
957+
}
958+
959+
@Property
960+
void setDecomposedArrayThenApplyWildcardSetOverride() {
961+
// given
962+
StringArrayWrapper wrapper = new StringArrayWrapper();
963+
wrapper.setValues(new String[]{"1", "2", "3"});
964+
965+
// when
966+
List<StringArrayWrapper> actual = SUT.giveMeBuilder(NestedStringArrayWrapper.class)
967+
.set("values", Collections.singletonList(wrapper))
968+
.thenApply((obj, builder) ->
969+
builder.size("values", 2)
970+
.size("values[*].values", 2)
971+
.set("values[*].values[*]", "override")
972+
)
973+
.sample()
974+
.getValues();
975+
976+
// then
977+
then(actual).hasSize(2);
978+
for (int j = 0; j < 2; j++) {
979+
then(actual.get(j).getValues()).hasSize(2);
980+
for (int k = 0; k < 2; k++) {
981+
then(actual.get(j).getValues()[k]).isEqualTo("override");
982+
}
983+
}
984+
}
985+
986+
@Property
987+
void setDecomposedAfterThenApplyWildcardSetOverride() {
988+
// given
989+
List<String> innerList = Arrays.asList("a", "b");
990+
991+
// when
992+
List<StringListWrapper> actual = SUT.giveMeBuilder(NestedStringListWrapper.class)
993+
.thenApply((obj, builder) ->
994+
builder.set("values[*].values[*]", "override")
995+
)
996+
.size("values", 2)
997+
.size("values[*].values", 2)
998+
.set("values[0].values", innerList)
999+
.sample()
1000+
.getValues();
1001+
1002+
// then
1003+
then(actual).hasSize(2);
1004+
then(actual.get(0).getValues()).hasSize(2);
1005+
then(actual.get(0).getValues().get(0)).isEqualTo("a");
1006+
then(actual.get(0).getValues().get(1)).isEqualTo("b");
1007+
}
1008+
1009+
@Property(tries = 1)
1010+
void setDecomposedObjectThenApplyWildcardSetOverride() {
1011+
// when
1012+
StringListWrapper stringListWrapper = new StringListWrapper();
1013+
stringListWrapper.setValues(Collections.singletonList("1"));
1014+
List<StringListWrapper> actual = SUT.giveMeBuilder(NestedStringListWrapper.class)
1015+
.set("values", Collections.singletonList(stringListWrapper))
1016+
.thenApply((obj, builder) ->
1017+
builder.size("values", 2)
1018+
.size("values[*].values", 2)
1019+
.set("values[*].values[*]", "override")
1020+
)
1021+
.sample()
1022+
.getValues();
1023+
1024+
// then
1025+
then(actual).hasSize(2);
1026+
then(actual.get(0).getValues()).hasSize(2);
1027+
then(actual.get(0).getValues().get(0)).isEqualTo("override");
1028+
then(actual.get(0).getValues().get(1)).isEqualTo("override");
1029+
then(actual.get(1).getValues()).hasSize(2);
1030+
then(actual.get(1).getValues().get(0)).isEqualTo("override");
1031+
then(actual.get(1).getValues().get(1)).isEqualTo("override");
1032+
}
1033+
1034+
@Property(tries = 1)
1035+
void setDecomposedNestedContainerThenApplyWildcardSetOverride() {
1036+
// given
1037+
List<List<String>> values = Arrays.asList(
1038+
Arrays.asList("a", "b"),
1039+
Arrays.asList("c", "d")
1040+
);
1041+
1042+
// when
1043+
List<List<String>> actual = SUT.giveMeBuilder(ListListStringObject.class)
1044+
.set("values", values)
1045+
.thenApply((obj, builder) ->
1046+
builder.size("values", 2)
1047+
.size("values[*]", 2)
1048+
.set("values[*][*]", "override")
1049+
)
1050+
.sample()
1051+
.getValues();
1052+
1053+
// then
1054+
then(actual).hasSize(2);
1055+
for (int i = 0; i < 2; i++) {
1056+
then(actual.get(i)).hasSize(2);
1057+
then(actual.get(i).get(0)).isEqualTo("override");
1058+
then(actual.get(i).get(1)).isEqualTo("override");
1059+
}
1060+
}
1061+
1062+
@Property(tries = 1)
1063+
void listListStringSizeAndSet() {
1064+
// when
1065+
List<List<String>> actual = SUT.giveMeBuilder(ListListStringObject.class)
1066+
.size("values", 2)
1067+
.size("values[*]", 2)
1068+
.set("values[*][*]", "test")
1069+
.sample()
1070+
.getValues();
1071+
1072+
// then
1073+
then(actual).hasSize(2);
1074+
for (int i = 0; i < 2; i++) {
1075+
then(actual.get(i)).hasSize(2);
1076+
then(actual.get(i).get(0)).isEqualTo("test");
1077+
then(actual.get(i).get(1)).isEqualTo("test");
1078+
}
1079+
}
1080+
1081+
@Property(tries = 1)
1082+
void listListStringSetThenSize() {
1083+
// given
1084+
FixtureMonkey fm = FixtureMonkey.builder()
1085+
.defaultNotNull(true)
1086+
.plugin(new JavaNodeTreeAdapterPlugin())
1087+
.build();
1088+
1089+
List<List<String>> values = Arrays.asList(
1090+
Arrays.asList("a", "b"),
1091+
Arrays.asList("c", "d")
1092+
);
1093+
1094+
// when
1095+
List<List<String>> actual = fm.giveMeBuilder(ListListStringObject.class)
1096+
.set("values", values)
1097+
.size("values", 2)
1098+
.size("values[*]", 2)
1099+
.set("values[*][*]", "override")
1100+
.sample()
1101+
.getValues();
1102+
1103+
// then
1104+
then(actual).hasSize(2);
1105+
for (int i = 0; i < 2; i++) {
1106+
then(actual.get(i)).hasSize(2);
1107+
then(actual.get(i).get(0)).isEqualTo("override");
1108+
then(actual.get(i).get(1)).isEqualTo("override");
1109+
}
1110+
}
9241111
}

fixture-monkey/src/test/java/com/navercorp/fixturemonkey/test/FixtureMonkeyTestSpecs.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,19 @@ public static class GenericSimpleChild extends GenericValue<SimpleObject> {
334334
String childValue;
335335
}
336336

337+
@Data
338+
public static class StringArrayWrapper {
339+
340+
private String[] values;
341+
}
342+
343+
@Setter
344+
@Getter
345+
public static class NestedStringArrayWrapper {
346+
347+
List<StringArrayWrapper> values;
348+
}
349+
337350
@Setter
338351
@Getter
339352
public static class NestedStringListWrapper {
@@ -348,6 +361,13 @@ public static class DoubleNestedStringListWrapper {
348361
List<NestedStringListWrapper> values;
349362
}
350363

364+
@Setter
365+
@Getter
366+
public static class ListListStringObject {
367+
368+
List<List<String>> values;
369+
}
370+
351371
@Getter
352372
public static class ConstructorJavaTypeObject {
353373

0 commit comments

Comments
 (0)