From 9cb6b7dbc0e6125cef84e540b13523d32ef68da6 Mon Sep 17 00:00:00 2001 From: fred Date: Thu, 19 Feb 2026 08:38:03 -0300 Subject: [PATCH 1/6] chore: add a clone test case --- .../cloner/AbstractSolutionClonerTest.java | 14 ++++ ...estdataDeepCloneEntityProvidingEntity.java | 62 +++++++++++++++++ ...tdataDeepCloneEntityProvidingSolution.java | 67 +++++++++++++++++++ 3 files changed, 143 insertions(+) create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/entityproviding/deepclone/TestdataDeepCloneEntityProvidingEntity.java create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/entityproviding/deepclone/TestdataDeepCloneEntityProvidingSolution.java diff --git a/core/src/test/java/ai/timefold/solver/core/impl/domain/solution/cloner/AbstractSolutionClonerTest.java b/core/src/test/java/ai/timefold/solver/core/impl/domain/solution/cloner/AbstractSolutionClonerTest.java index bcb29797ba2..d53e4123152 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/domain/solution/cloner/AbstractSolutionClonerTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/domain/solution/cloner/AbstractSolutionClonerTest.java @@ -66,6 +66,7 @@ import ai.timefold.solver.core.testdomain.shadow.dependency.TestdataDependencyEntity; import ai.timefold.solver.core.testdomain.shadow.dependency.TestdataDependencySolution; import ai.timefold.solver.core.testdomain.shadow.dependency.TestdataDependencyValue; +import ai.timefold.solver.core.testdomain.valuerange.entityproviding.deepclone.TestdataDeepCloneEntityProvidingSolution; import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.Test; @@ -378,6 +379,19 @@ protected void cloneDeeplyNestedSolution() { assertThat(cloneValue.getId()).isEqualTo(originalValue.getId()); } + @Test + void deepCloneWithEntityValueRange() { + var solutionDescriptor = TestdataDeepCloneEntityProvidingSolution.buildSolutionDescriptor(); + var cloner = createSolutionCloner(solutionDescriptor); + var original = TestdataDeepCloneEntityProvidingSolution.generateSolution(); + var clone = cloner.cloneSolution(original); + + var firstEntity = clone.getEntityList().get(0); + assertThat(firstEntity.getValueRange()).contains(firstEntity.getValue()); + var secondEntity = clone.getEntityList().get(1); + assertThat(secondEntity.getValueRange()).contains(secondEntity.getValue()); + } + @Test void cloneExtendedThirdPartySolution() { var solutionDescriptor = TestdataExtendedThirdPartySolution.buildSolutionDescriptor(); diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/entityproviding/deepclone/TestdataDeepCloneEntityProvidingEntity.java b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/entityproviding/deepclone/TestdataDeepCloneEntityProvidingEntity.java new file mode 100644 index 00000000000..af9b03515d5 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/entityproviding/deepclone/TestdataDeepCloneEntityProvidingEntity.java @@ -0,0 +1,62 @@ +package ai.timefold.solver.core.testdomain.valuerange.entityproviding.deepclone; + +import java.util.List; + +import ai.timefold.solver.core.api.domain.entity.PlanningEntity; +import ai.timefold.solver.core.api.domain.solution.cloner.DeepPlanningClone; +import ai.timefold.solver.core.api.domain.valuerange.ValueRangeProvider; +import ai.timefold.solver.core.api.domain.variable.PlanningVariable; +import ai.timefold.solver.core.impl.domain.entity.descriptor.EntityDescriptor; +import ai.timefold.solver.core.impl.domain.variable.descriptor.GenuineVariableDescriptor; +import ai.timefold.solver.core.testdomain.TestdataObject; +import ai.timefold.solver.core.testdomain.TestdataValue; + +@PlanningEntity +public class TestdataDeepCloneEntityProvidingEntity extends TestdataObject { + + public static EntityDescriptor buildEntityDescriptor() { + return TestdataDeepCloneEntityProvidingSolution.buildSolutionDescriptor() + .findEntityDescriptorOrFail(TestdataDeepCloneEntityProvidingEntity.class); + } + + public static GenuineVariableDescriptor buildVariableDescriptorForValue() { + return buildEntityDescriptor().getGenuineVariableDescriptor("value"); + } + + private List valueRange; + + @DeepPlanningClone + private TestdataValue value; + + public TestdataDeepCloneEntityProvidingEntity() { + // Required for cloning + } + + public TestdataDeepCloneEntityProvidingEntity(String code, List valueRange) { + this(code, valueRange, null); + } + + public TestdataDeepCloneEntityProvidingEntity(String code, List valueRange, TestdataValue value) { + super(code); + this.valueRange = valueRange; + this.value = value; + } + + @PlanningVariable(valueRangeProviderRefs = "valueRange") + public TestdataValue getValue() { + return value; + } + + public void setValue(TestdataValue value) { + this.value = value; + } + + @ValueRangeProvider(id = "valueRange") + public List getValueRange() { + return valueRange; + } + + public void setValueRange(List valueRange) { + this.valueRange = valueRange; + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/entityproviding/deepclone/TestdataDeepCloneEntityProvidingSolution.java b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/entityproviding/deepclone/TestdataDeepCloneEntityProvidingSolution.java new file mode 100644 index 00000000000..c85eb10d34a --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/entityproviding/deepclone/TestdataDeepCloneEntityProvidingSolution.java @@ -0,0 +1,67 @@ +package ai.timefold.solver.core.testdomain.valuerange.entityproviding.deepclone; + +import java.util.List; + +import ai.timefold.solver.core.api.domain.solution.PlanningEntityCollectionProperty; +import ai.timefold.solver.core.api.domain.solution.PlanningScore; +import ai.timefold.solver.core.api.domain.solution.PlanningSolution; +import ai.timefold.solver.core.api.score.buildin.simple.SimpleScore; +import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor; +import ai.timefold.solver.core.preview.api.domain.metamodel.PlanningSolutionMetaModel; +import ai.timefold.solver.core.testdomain.TestdataObject; +import ai.timefold.solver.core.testdomain.TestdataValue; + +@PlanningSolution +public class TestdataDeepCloneEntityProvidingSolution extends TestdataObject { + + public static SolutionDescriptor buildSolutionDescriptor() { + return SolutionDescriptor.buildSolutionDescriptor(TestdataDeepCloneEntityProvidingSolution.class, + TestdataDeepCloneEntityProvidingEntity.class); + } + + public static PlanningSolutionMetaModel buildMetaModel() { + return buildSolutionDescriptor().getMetaModel(); + } + + public static TestdataDeepCloneEntityProvidingSolution generateSolution() { + var solution = new TestdataDeepCloneEntityProvidingSolution("s1"); + var value1 = new TestdataValue("1"); + var value2 = new TestdataValue("2"); + var entity1 = new TestdataDeepCloneEntityProvidingEntity("1", List.of(value1, value2)); + entity1.setValue(value1); + var entity2 = new TestdataDeepCloneEntityProvidingEntity("2", List.of(value1, value2)); + entity2.setValue(value2); + solution.setEntityList(List.of(entity1, entity2)); + return solution; + } + + private List entityList; + + private SimpleScore score; + + public TestdataDeepCloneEntityProvidingSolution() { + // Required for cloning + } + + public TestdataDeepCloneEntityProvidingSolution(String code) { + super(code); + } + + @PlanningEntityCollectionProperty + public List getEntityList() { + return entityList; + } + + public void setEntityList(List entityList) { + this.entityList = entityList; + } + + @PlanningScore + public SimpleScore getScore() { + return score; + } + + public void setScore(SimpleScore score) { + this.score = score; + } +} From ef671f1823d610ef78d8e7653a02dabde0158f9c Mon Sep 17 00:00:00 2001 From: fred Date: Thu, 19 Feb 2026 11:02:49 -0300 Subject: [PATCH 2/6] feat: add cloning fail fast --- .../solution/cloner/DeepCloningUtils.java | 26 ++++++++++++++++ .../cloner/AbstractSolutionClonerTest.java | 31 ++++++++++--------- ...TestdataInvalidEntityProvidingEntity.java} | 22 ++++++------- ...stdataInvalidEntityProvidingSolution.java} | 30 +++++++++--------- 4 files changed, 69 insertions(+), 40 deletions(-) rename core/src/test/java/ai/timefold/solver/core/testdomain/{valuerange/entityproviding/deepclone/TestdataDeepCloneEntityProvidingEntity.java => clone/deepcloning/field/invalid/TestdataInvalidEntityProvidingEntity.java} (60%) rename core/src/test/java/ai/timefold/solver/core/testdomain/{valuerange/entityproviding/deepclone/TestdataDeepCloneEntityProvidingSolution.java => clone/deepcloning/field/invalid/TestdataInvalidEntityProvidingSolution.java} (54%) diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/solution/cloner/DeepCloningUtils.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/solution/cloner/DeepCloningUtils.java index 0c959a2fc25..ec7ccb12b4b 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/solution/cloner/DeepCloningUtils.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/solution/cloner/DeepCloningUtils.java @@ -32,6 +32,7 @@ import ai.timefold.solver.core.api.domain.lookup.PlanningId; import ai.timefold.solver.core.api.domain.solution.cloner.DeepPlanningClone; import ai.timefold.solver.core.api.domain.variable.PlanningListVariable; +import ai.timefold.solver.core.api.domain.variable.PlanningVariable; import ai.timefold.solver.core.api.score.Score; import ai.timefold.solver.core.impl.domain.common.ReflectionHelper; import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor; @@ -83,6 +84,22 @@ public static boolean isFieldDeepCloned(SolutionDescriptor solutionDescriptor if (isImmutable(fieldType)) { return false; } else { + // Problem facts + // assigned to basic variables that enable deep cloning must also enable deep cloning for the fact type. + // Otherwise, the solver might fail to recognize that an assigned value belongs to a value range. + if (isFieldAPlanningBasicVariable(field, owningClass) && isFieldADeepCloneProperty(field, owningClass) + && !isClassDeepCloned(solutionDescriptor, field.getType())) { + throw new IllegalStateException(""" + The field (%s) of class (%s) is configured to be deep-cloned, + but its type (%s) is not deep-cloned. + Maybe remove the @%s annotation from the field? + Maybe annotate the type (%s) with @%s?""" + .formatted(field.getName(), owningClass.getCanonicalName(), + field.getType().getCanonicalName(), + DeepPlanningClone.class.getSimpleName(), + field.getType().getCanonicalName(), + DeepPlanningClone.class.getSimpleName())); + } return needsDeepClone(solutionDescriptor, field, owningClass); } @@ -207,6 +224,15 @@ private static boolean isFieldAPlanningListVariable(Field field, Class owning } } + private static boolean isFieldAPlanningBasicVariable(Field field, Class owningClass) { + if (!field.isAnnotationPresent(PlanningVariable.class)) { + Method getterMethod = ReflectionHelper.getGetterMethod(owningClass, field.getName()); + return getterMethod != null && getterMethod.isAnnotationPresent(PlanningVariable.class); + } else { + return true; + } + } + private DeepCloningUtils() { // No external instances. } diff --git a/core/src/test/java/ai/timefold/solver/core/impl/domain/solution/cloner/AbstractSolutionClonerTest.java b/core/src/test/java/ai/timefold/solver/core/impl/domain/solution/cloner/AbstractSolutionClonerTest.java index d53e4123152..343fecce182 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/domain/solution/cloner/AbstractSolutionClonerTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/domain/solution/cloner/AbstractSolutionClonerTest.java @@ -2,6 +2,7 @@ import static ai.timefold.solver.core.testutil.PlannerAssert.assertCode; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.SoftAssertions.assertSoftly; import java.time.Duration; @@ -40,6 +41,7 @@ import ai.timefold.solver.core.testdomain.clone.deepcloning.TestdataVariousTypes; import ai.timefold.solver.core.testdomain.clone.deepcloning.field.TestdataFieldAnnotatedDeepCloningEntity; import ai.timefold.solver.core.testdomain.clone.deepcloning.field.TestdataFieldAnnotatedDeepCloningSolution; +import ai.timefold.solver.core.testdomain.clone.deepcloning.field.invalid.TestdataInvalidEntityProvidingSolution; import ai.timefold.solver.core.testdomain.collection.TestdataArrayBasedEntity; import ai.timefold.solver.core.testdomain.collection.TestdataArrayBasedSolution; import ai.timefold.solver.core.testdomain.collection.TestdataEntityCollectionPropertyEntity; @@ -66,7 +68,6 @@ import ai.timefold.solver.core.testdomain.shadow.dependency.TestdataDependencyEntity; import ai.timefold.solver.core.testdomain.shadow.dependency.TestdataDependencySolution; import ai.timefold.solver.core.testdomain.shadow.dependency.TestdataDependencyValue; -import ai.timefold.solver.core.testdomain.valuerange.entityproviding.deepclone.TestdataDeepCloneEntityProvidingSolution; import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.Test; @@ -379,19 +380,6 @@ protected void cloneDeeplyNestedSolution() { assertThat(cloneValue.getId()).isEqualTo(originalValue.getId()); } - @Test - void deepCloneWithEntityValueRange() { - var solutionDescriptor = TestdataDeepCloneEntityProvidingSolution.buildSolutionDescriptor(); - var cloner = createSolutionCloner(solutionDescriptor); - var original = TestdataDeepCloneEntityProvidingSolution.generateSolution(); - var clone = cloner.cloneSolution(original); - - var firstEntity = clone.getEntityList().get(0); - assertThat(firstEntity.getValueRange()).contains(firstEntity.getValue()); - var secondEntity = clone.getEntityList().get(1); - assertThat(secondEntity.getValueRange()).contains(secondEntity.getValue()); - } - @Test void cloneExtendedThirdPartySolution() { var solutionDescriptor = TestdataExtendedThirdPartySolution.buildSolutionDescriptor(); @@ -1222,6 +1210,21 @@ void cloneExtendedShadowEntities() { .isNotSameAs(original.shadowEntityList.get(0)); } + @Test + void failDeepCloneRequiredTypeAnnotation() { + var solutionDescriptor = TestdataInvalidEntityProvidingSolution.buildSolutionDescriptor(); + var original = TestdataInvalidEntityProvidingSolution.generateSolution(); + assertThatCode(() -> { + var cloner = createSolutionCloner(solutionDescriptor); + cloner.cloneSolution(original); + }).hasMessageContaining( + "The field (value) of class (ai.timefold.solver.core.testdomain.clone.deepcloning.field.invalid.TestdataInvalidEntityProvidingEntity) is configured to be deep-cloned") + .hasMessageContaining("but its type (ai.timefold.solver.core.testdomain.TestdataValue) is not deep-cloned") + .hasMessageContaining("Maybe remove the @DeepPlanningClone annotation from the field?") + .hasMessageContaining( + "Maybe annotate the type (ai.timefold.solver.core.testdomain.TestdataValue) with @DeepPlanningClone?"); + } + private static class MaxStackFrameFinder { int maxStackFrames = 0; diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/entityproviding/deepclone/TestdataDeepCloneEntityProvidingEntity.java b/core/src/test/java/ai/timefold/solver/core/testdomain/clone/deepcloning/field/invalid/TestdataInvalidEntityProvidingEntity.java similarity index 60% rename from core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/entityproviding/deepclone/TestdataDeepCloneEntityProvidingEntity.java rename to core/src/test/java/ai/timefold/solver/core/testdomain/clone/deepcloning/field/invalid/TestdataInvalidEntityProvidingEntity.java index af9b03515d5..4ad6f43d1a8 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/entityproviding/deepclone/TestdataDeepCloneEntityProvidingEntity.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/clone/deepcloning/field/invalid/TestdataInvalidEntityProvidingEntity.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.testdomain.valuerange.entityproviding.deepclone; +package ai.timefold.solver.core.testdomain.clone.deepcloning.field.invalid; import java.util.List; @@ -12,31 +12,32 @@ import ai.timefold.solver.core.testdomain.TestdataValue; @PlanningEntity -public class TestdataDeepCloneEntityProvidingEntity extends TestdataObject { +public class TestdataInvalidEntityProvidingEntity extends TestdataObject { - public static EntityDescriptor buildEntityDescriptor() { - return TestdataDeepCloneEntityProvidingSolution.buildSolutionDescriptor() - .findEntityDescriptorOrFail(TestdataDeepCloneEntityProvidingEntity.class); + public static EntityDescriptor buildEntityDescriptor() { + return TestdataInvalidEntityProvidingSolution.buildSolutionDescriptor() + .findEntityDescriptorOrFail(TestdataInvalidEntityProvidingEntity.class); } - public static GenuineVariableDescriptor buildVariableDescriptorForValue() { + public static GenuineVariableDescriptor buildVariableDescriptorForValue() { return buildEntityDescriptor().getGenuineVariableDescriptor("value"); } + @ValueRangeProvider(id = "valueRange") private List valueRange; @DeepPlanningClone - private TestdataValue value; + private TestdataValue value; // TestdataValue is not deep-cloned, and the cloning logic should fail-fast - public TestdataDeepCloneEntityProvidingEntity() { + public TestdataInvalidEntityProvidingEntity() { // Required for cloning } - public TestdataDeepCloneEntityProvidingEntity(String code, List valueRange) { + public TestdataInvalidEntityProvidingEntity(String code, List valueRange) { this(code, valueRange, null); } - public TestdataDeepCloneEntityProvidingEntity(String code, List valueRange, TestdataValue value) { + public TestdataInvalidEntityProvidingEntity(String code, List valueRange, TestdataValue value) { super(code); this.valueRange = valueRange; this.value = value; @@ -51,7 +52,6 @@ public void setValue(TestdataValue value) { this.value = value; } - @ValueRangeProvider(id = "valueRange") public List getValueRange() { return valueRange; } diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/entityproviding/deepclone/TestdataDeepCloneEntityProvidingSolution.java b/core/src/test/java/ai/timefold/solver/core/testdomain/clone/deepcloning/field/invalid/TestdataInvalidEntityProvidingSolution.java similarity index 54% rename from core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/entityproviding/deepclone/TestdataDeepCloneEntityProvidingSolution.java rename to core/src/test/java/ai/timefold/solver/core/testdomain/clone/deepcloning/field/invalid/TestdataInvalidEntityProvidingSolution.java index c85eb10d34a..86cebbd584a 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/entityproviding/deepclone/TestdataDeepCloneEntityProvidingSolution.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/clone/deepcloning/field/invalid/TestdataInvalidEntityProvidingSolution.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.testdomain.valuerange.entityproviding.deepclone; +package ai.timefold.solver.core.testdomain.clone.deepcloning.field.invalid; import java.util.List; @@ -12,47 +12,47 @@ import ai.timefold.solver.core.testdomain.TestdataValue; @PlanningSolution -public class TestdataDeepCloneEntityProvidingSolution extends TestdataObject { +public class TestdataInvalidEntityProvidingSolution extends TestdataObject { - public static SolutionDescriptor buildSolutionDescriptor() { - return SolutionDescriptor.buildSolutionDescriptor(TestdataDeepCloneEntityProvidingSolution.class, - TestdataDeepCloneEntityProvidingEntity.class); + public static SolutionDescriptor buildSolutionDescriptor() { + return SolutionDescriptor.buildSolutionDescriptor(TestdataInvalidEntityProvidingSolution.class, + TestdataInvalidEntityProvidingEntity.class); } - public static PlanningSolutionMetaModel buildMetaModel() { + public static PlanningSolutionMetaModel buildMetaModel() { return buildSolutionDescriptor().getMetaModel(); } - public static TestdataDeepCloneEntityProvidingSolution generateSolution() { - var solution = new TestdataDeepCloneEntityProvidingSolution("s1"); + public static TestdataInvalidEntityProvidingSolution generateSolution() { + var solution = new TestdataInvalidEntityProvidingSolution("s1"); var value1 = new TestdataValue("1"); var value2 = new TestdataValue("2"); - var entity1 = new TestdataDeepCloneEntityProvidingEntity("1", List.of(value1, value2)); + var entity1 = new TestdataInvalidEntityProvidingEntity("1", List.of(value1, value2)); entity1.setValue(value1); - var entity2 = new TestdataDeepCloneEntityProvidingEntity("2", List.of(value1, value2)); + var entity2 = new TestdataInvalidEntityProvidingEntity("2", List.of(value1, value2)); entity2.setValue(value2); solution.setEntityList(List.of(entity1, entity2)); return solution; } - private List entityList; + private List entityList; private SimpleScore score; - public TestdataDeepCloneEntityProvidingSolution() { + public TestdataInvalidEntityProvidingSolution() { // Required for cloning } - public TestdataDeepCloneEntityProvidingSolution(String code) { + public TestdataInvalidEntityProvidingSolution(String code) { super(code); } @PlanningEntityCollectionProperty - public List getEntityList() { + public List getEntityList() { return entityList; } - public void setEntityList(List entityList) { + public void setEntityList(List entityList) { this.entityList = entityList; } From 2134d7f02892fcbe876ceb7156c6b23e7aec5a99 Mon Sep 17 00:00:00 2001 From: fred Date: Thu, 19 Feb 2026 11:44:17 -0300 Subject: [PATCH 3/6] chore: address comments --- .../domain/solution/cloner/AbstractSolutionClonerTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/src/test/java/ai/timefold/solver/core/impl/domain/solution/cloner/AbstractSolutionClonerTest.java b/core/src/test/java/ai/timefold/solver/core/impl/domain/solution/cloner/AbstractSolutionClonerTest.java index 343fecce182..a0efdaea21b 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/domain/solution/cloner/AbstractSolutionClonerTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/domain/solution/cloner/AbstractSolutionClonerTest.java @@ -3,6 +3,7 @@ import static ai.timefold.solver.core.testutil.PlannerAssert.assertCode; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.SoftAssertions.assertSoftly; import java.time.Duration; @@ -1214,7 +1215,7 @@ void cloneExtendedShadowEntities() { void failDeepCloneRequiredTypeAnnotation() { var solutionDescriptor = TestdataInvalidEntityProvidingSolution.buildSolutionDescriptor(); var original = TestdataInvalidEntityProvidingSolution.generateSolution(); - assertThatCode(() -> { + assertThatThrownBy(() -> { var cloner = createSolutionCloner(solutionDescriptor); cloner.cloneSolution(original); }).hasMessageContaining( From 90666aabaafa3c31b5b7058fab8a345eff9562c7 Mon Sep 17 00:00:00 2001 From: fred Date: Thu, 19 Feb 2026 11:47:37 -0300 Subject: [PATCH 4/6] chore: formatting --- .../impl/domain/solution/cloner/AbstractSolutionClonerTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/core/src/test/java/ai/timefold/solver/core/impl/domain/solution/cloner/AbstractSolutionClonerTest.java b/core/src/test/java/ai/timefold/solver/core/impl/domain/solution/cloner/AbstractSolutionClonerTest.java index a0efdaea21b..361fb3d8dd2 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/domain/solution/cloner/AbstractSolutionClonerTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/domain/solution/cloner/AbstractSolutionClonerTest.java @@ -2,7 +2,6 @@ import static ai.timefold.solver.core.testutil.PlannerAssert.assertCode; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.SoftAssertions.assertSoftly; From c211bf0d6cd7831199228e3f30780e1d0e281282 Mon Sep 17 00:00:00 2001 From: fred Date: Thu, 19 Feb 2026 12:15:54 -0300 Subject: [PATCH 5/6] chore: temp change --- .../impl/domain/solution/cloner/AbstractSolutionClonerTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/test/java/ai/timefold/solver/core/impl/domain/solution/cloner/AbstractSolutionClonerTest.java b/core/src/test/java/ai/timefold/solver/core/impl/domain/solution/cloner/AbstractSolutionClonerTest.java index 361fb3d8dd2..dc3956b0f06 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/domain/solution/cloner/AbstractSolutionClonerTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/domain/solution/cloner/AbstractSolutionClonerTest.java @@ -1218,7 +1218,7 @@ void failDeepCloneRequiredTypeAnnotation() { var cloner = createSolutionCloner(solutionDescriptor); cloner.cloneSolution(original); }).hasMessageContaining( - "The field (value) of class (ai.timefold.solver.core.testdomain.clone.deepcloning.field.invalid.TestdataInvalidEntityProvidingEntity) is configured to be deep-cloned") + "The field (value) of class (ai.timefold.solver.core.testdomain.clone.deepcloning.field.invalid.TestdataInvalidEntityProvidingEntity) is configured to be deep-clonedadsf") .hasMessageContaining("but its type (ai.timefold.solver.core.testdomain.TestdataValue) is not deep-cloned") .hasMessageContaining("Maybe remove the @DeepPlanningClone annotation from the field?") .hasMessageContaining( From d0cc1e53c51566fca8437b658e59890eb71c45c9 Mon Sep 17 00:00:00 2001 From: fred Date: Thu, 19 Feb 2026 12:16:01 -0300 Subject: [PATCH 6/6] Revert "chore: temp change" This reverts commit c211bf0d6cd7831199228e3f30780e1d0e281282. --- .../impl/domain/solution/cloner/AbstractSolutionClonerTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/test/java/ai/timefold/solver/core/impl/domain/solution/cloner/AbstractSolutionClonerTest.java b/core/src/test/java/ai/timefold/solver/core/impl/domain/solution/cloner/AbstractSolutionClonerTest.java index dc3956b0f06..361fb3d8dd2 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/domain/solution/cloner/AbstractSolutionClonerTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/domain/solution/cloner/AbstractSolutionClonerTest.java @@ -1218,7 +1218,7 @@ void failDeepCloneRequiredTypeAnnotation() { var cloner = createSolutionCloner(solutionDescriptor); cloner.cloneSolution(original); }).hasMessageContaining( - "The field (value) of class (ai.timefold.solver.core.testdomain.clone.deepcloning.field.invalid.TestdataInvalidEntityProvidingEntity) is configured to be deep-clonedadsf") + "The field (value) of class (ai.timefold.solver.core.testdomain.clone.deepcloning.field.invalid.TestdataInvalidEntityProvidingEntity) is configured to be deep-cloned") .hasMessageContaining("but its type (ai.timefold.solver.core.testdomain.TestdataValue) is not deep-cloned") .hasMessageContaining("Maybe remove the @DeepPlanningClone annotation from the field?") .hasMessageContaining(