Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

1.13.5 backports 3 #17451

Merged
merged 4 commits into from
May 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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