Skip to content

Commit

Permalink
Merge pull request #17451 from gsmet/1.13.5-backports-3
Browse files Browse the repository at this point in the history
1.13.5 backports 3
  • Loading branch information
gsmet authored May 25, 2021
2 parents 07dc6a7 + 1a22331 commit 761d8fd
Show file tree
Hide file tree
Showing 5 changed files with 191 additions and 1 deletion.
2 changes: 1 addition & 1 deletion .github/workflows/ci-actions-incremental.yml
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ jobs:
# Common GIB_ARGS for all CI cases (hint: see also root pom.xml):
# - disableSelectedProjectsHandling: required to detect changes in jobs that use -pl
# - untracked: to ignore files created by jobs (and uncommitted to be consistent)
GIB_ARGS="-Dincremental -Dgib.disableSelectedProjectsHandling -Dgib.untracked=false -Dgib.uncommitted=false"
GIB_ARGS="-Dgib.disable -Dincremental -Dgib.disableSelectedProjectsHandling -Dgib.untracked=false -Dgib.uncommitted=false"
if [ -n "$PULL_REQUEST_BASE" ]
then
# The PR defines a clear merge target so just use that branch for reference, *unless*:
Expand Down
71 changes: 71 additions & 0 deletions docs/src/main/asciidoc/optaplanner.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -711,6 +711,77 @@ On the server side, the `info` log show what OptaPlanner did in those five secon

=== Test the application

==== Test the constraints

To test each constraint in isolation, use a `ConstraintVerifier` in unit tests. This tests each constraint's corner cases in isolation from the other tests, which lowers maintenance when adding a new constraint with proper test coverage.

Add a `optaplanner-test` dependency in your `pom.xml`:
[source,xml]
----
<dependency>
<groupId>org.optaplanner</groupId>
<artifactId>optaplanner-test</artifactId>
<scope>test</scope>
</dependency>
----

Create the `src/test/java/org/acme/optaplanner/solver/TimeTableConstraintProviderTest.java` class:
[source,java]
----
package org.acme.optaplanner.solver;
import java.time.DayOfWeek;
import java.time.LocalTime;
import javax.inject.Inject;
import io.quarkus.test.junit.QuarkusTest;
import org.acme.optaplanner.domain.Lesson;
import org.acme.optaplanner.domain.Room;
import org.acme.optaplanner.domain.TimeTable;
import org.acme.optaplanner.domain.Timeslot;
import org.junit.jupiter.api.Test;
import org.optaplanner.test.api.score.stream.ConstraintVerifier;
@QuarkusTest
class TimeTableConstraintProviderTest {
private static final Room ROOM = new Room("Room1");
private static final Timeslot TIMESLOT1 = new Timeslot(DayOfWeek.MONDAY, LocalTime.of(9,0), LocalTime.NOON);
private static final Timeslot TIMESLOT2 = new Timeslot(DayOfWeek.TUESDAY, LocalTime.of(9,0), LocalTime.NOON);
@Inject
ConstraintVerifier<TimeTableConstraintProvider, TimeTable> constraintVerifier;
@Test
void roomConflict() {
Lesson firstLesson = new Lesson(1, "Subject1", "Teacher1", "Group1");
Lesson conflictingLesson = new Lesson(2, "Subject2", "Teacher2", "Group2");
Lesson nonConflictingLesson = new Lesson(3, "Subject3", "Teacher3", "Group3");
firstLesson.setRoom(ROOM);
firstLesson.setTimeslot(TIMESLOT1);
conflictingLesson.setRoom(ROOM);
conflictingLesson.setTimeslot(TIMESLOT1);
nonConflictingLesson.setRoom(ROOM);
nonConflictingLesson.setTimeslot(TIMESLOT2);
constraintVerifier.verifyThat(TimeTableConstraintProvider::roomConflict)
.given(firstLesson, conflictingLesson, nonConflictingLesson)
.penalizesBy(1);
}
}
----

This test verifies that the constraint `TimeTableConstraintProvider::roomConflict`, when given three lessons in the same room, where two lessons have the same timeslot, it penalizes with a match weight of `1`. So with a constraint weight of `10hard` it would reduce the score by `-10hard`.

Notice how `ConstraintVerifier` ignores the constraint weight during testing - even if those constraint weights are hard coded in the `ConstraintProvider` - because constraints weights change regularly before going into production. This way, constraint weight tweaking does not break the unit tests.

==== Test the solver

A good application includes test coverage.
In a JUnit test, generate a test dataset and send it to the `TimeTableResource` to solve.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,28 @@ public void build(HibernateValidatorRecorder recorder, RecorderContext recorderC
} else if (annotation.target().kind() == AnnotationTarget.Kind.CLASS) {
contributeClass(classNamesToBeValidated, indexView, annotation.target().asClass().name());
// no need for reflection in the case of a class level constraint
} else if (annotation.target().kind() == AnnotationTarget.Kind.TYPE) {
// container element constraints
AnnotationTarget enclosingTarget = annotation.target().asType().enclosingTarget();
if (enclosingTarget.kind() == AnnotationTarget.Kind.FIELD) {
contributeClass(classNamesToBeValidated, indexView, enclosingTarget.asField().declaringClass().name());
reflectiveFields.produce(new ReflectiveFieldBuildItem(enclosingTarget.asField()));
if (annotation.target().asType().target() != null) {
contributeClassMarkedForCascadingValidation(classNamesToBeValidated, indexView,
consideredAnnotation,
annotation.target().asType().target());
}
} else if (enclosingTarget.kind() == AnnotationTarget.Kind.METHOD) {
contributeClass(classNamesToBeValidated, indexView, enclosingTarget.asMethod().declaringClass().name());
reflectiveMethods.produce(new ReflectiveMethodBuildItem(enclosingTarget.asMethod()));
if (annotation.target().asType().target() != null) {
contributeClassMarkedForCascadingValidation(classNamesToBeValidated, indexView,
consideredAnnotation,
annotation.target().asType().target());
}
contributeMethodsWithInheritedValidation(methodsWithInheritedValidation, indexView,
enclosingTarget.asMethod());
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package io.quarkus.hibernate.validator.test;

import static org.assertj.core.api.Assertions.assertThat;

import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.inject.Inject;
import javax.validation.ValidatorFactory;
import javax.validation.constraints.NotBlank;

import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.test.QuarkusUnitTest;

public class ContainerElementConstraintsTest {

@Inject
ValidatorFactory validatorFactory;

@RegisterExtension
static final QuarkusUnitTest test = new QuarkusUnitTest().setArchiveProducer(() -> ShrinkWrap
.create(JavaArchive.class).addClasses(TestBean.class));

@Test
public void testContainerElementConstraint() {
assertThat(validatorFactory.getValidator().validate(new TestBean())).hasSize(1);
}

@Test
public void testNestedContainerElementConstraint() {
assertThat(validatorFactory.getValidator().validate(new NestedTestBean())).hasSize(1);
}

@Test
public void testMethodParameterContainerElementConstraint() throws NoSuchMethodException, SecurityException {
Map<String, List<String>> invalidMap = new HashMap<>();
invalidMap.put("key", Collections.singletonList(""));

assertThat(validatorFactory.getValidator().forExecutables().validateParameters(new MethodParameterTestBean(),
MethodParameterTestBean.class.getMethod("test", Map.class), new Object[] { invalidMap })).hasSize(1);
}

@Test
public void testMethodReturnValueContainerElementConstraint() throws NoSuchMethodException, SecurityException {
Map<String, List<String>> invalidMap = new HashMap<>();
invalidMap.put("key", Collections.singletonList(""));

assertThat(validatorFactory.getValidator().forExecutables().validateReturnValue(new MethodReturnValueTestBean(),
MethodReturnValueTestBean.class.getMethod("test"), invalidMap)).hasSize(1);
}

static class TestBean {

public Map<String, @NotBlank String> constrainedMap;

public TestBean() {
Map<String, String> invalidMap = new HashMap<>();
invalidMap.put("key", "");

this.constrainedMap = invalidMap;
}
}

static class NestedTestBean {

public Map<String, List<@NotBlank String>> constrainedMap;

public NestedTestBean() {
Map<String, List<String>> invalidMap = new HashMap<>();
invalidMap.put("key", Collections.singletonList(""));

this.constrainedMap = invalidMap;
}
}

static class MethodParameterTestBean {

public void test(Map<String, List<@NotBlank String>> constrainedMap) {
// do nothing
}
}

static class MethodReturnValueTestBean {

public Map<String, List<@NotBlank String>> test() {
return null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import org.jboss.jandex.IndexView;

import com.mongodb.client.MongoClient;
import com.mongodb.client.model.changestream.ChangeStreamDocument;
import com.mongodb.event.CommandListener;
import com.mongodb.event.ConnectionPoolListener;

Expand Down Expand Up @@ -118,6 +119,7 @@ List<ReflectiveClassBuildItem> addExtensionPointsToNative(CodecProviderBuildItem
reflectiveClassNames.addAll(propertyCodecProviders.getPropertyCodecProviderClassNames());
reflectiveClassNames.addAll(bsonDiscriminators.getBsonDiscriminatorClassNames());
reflectiveClassNames.addAll(commandListeners.getCommandListenerClassNames());
reflectiveClassNames.add(ChangeStreamDocument.class.getName());

return reflectiveClassNames.stream()
.map(s -> new ReflectiveClassBuildItem(true, true, false, s))
Expand Down

0 comments on commit 761d8fd

Please sign in to comment.