Skip to content
This repository has been archived by the owner on Jul 17, 2024. It is now read-only.

Commit

Permalink
fix: Add tests for new ConstraintVerifier methods
Browse files Browse the repository at this point in the history
  • Loading branch information
Christopher-Chianelli authored and triceo committed Jul 8, 2024
1 parent d454af1 commit 1643b23
Show file tree
Hide file tree
Showing 2 changed files with 146 additions and 16 deletions.
110 changes: 106 additions & 4 deletions tests/test_constraint_verifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
MultiConstraintVerification as JavaMultiConstraintVerification)

def verifier_suite(verifier: ConstraintVerifier, same_value, is_value_one,
EntityValueIndictment, EntityValueJustification, EntityValuePairJustification,
solution, e1, e2, e3, v1, v2, v3):
verifier.verify_that(same_value) \
.given(e1, e2) \
Expand All @@ -37,6 +38,11 @@ def verifier_suite(verifier: ConstraintVerifier, same_value, is_value_one,
.given(e1, e2) \
.penalizes(1)

with pytest.raises(AssertionError):
verifier.verify_that(same_value) \
.given(e1, e2) \
.indicts_with_exactly(EntityValueIndictment(e1, v1))

e1.value = v1
e2.value = v1
e3.value = v1
Expand All @@ -47,7 +53,47 @@ def verifier_suite(verifier: ConstraintVerifier, same_value, is_value_one,

verifier.verify_that(same_value) \
.given(e1, e2) \
.penalizes()
.penalizes(1)

verifier.verify_that(same_value) \
.given(e1, e2) \
.indicts_with(EntityValueIndictment(e1, e1.value), EntityValueIndictment(e2, v1)) \
.penalizes_by(1)

verifier.verify_that(same_value) \
.given(e1, e2) \
.indicts_with_exactly(EntityValueIndictment(e1, e1.value), EntityValueIndictment(e2, v1)) \
.penalizes_by(1)

verifier.verify_that(same_value) \
.given(e1, e2) \
.indicts_with(EntityValueIndictment(e1, v1))

verifier.verify_that(same_value) \
.given(e1, e2) \
.justifies_with(EntityValuePairJustification((e1, e2), v1, SimpleScore(-1))) \
.penalizes_by(1)

verifier.verify_that(same_value) \
.given(e1, e2) \
.justifies_with_exactly(EntityValuePairJustification((e1, e2), v1, SimpleScore(-1))) \
.penalizes_by(1)

with pytest.raises(AssertionError):
verifier.verify_that(same_value) \
.given(e1, e2) \
.indicts_with_exactly(EntityValueIndictment(e1, v1))


with pytest.raises(AssertionError):
verifier.verify_that(same_value) \
.given(e1, e2) \
.justifies_with(EntityValuePairJustification((e1, e2), v1, SimpleScore(1)))

with pytest.raises(AssertionError):
verifier.verify_that(same_value) \
.given(e1, e2, e3) \
.justifies_with_exactly(EntityValuePairJustification((e1, e2), v1, SimpleScore(1)))

with pytest.raises(AssertionError):
verifier.verify_that(same_value) \
Expand All @@ -68,10 +114,28 @@ def verifier_suite(verifier: ConstraintVerifier, same_value, is_value_one,
.given(e1, e2, e3) \
.penalizes(3)

verifier.verify_that(same_value) \
.given(e1, e2, e3) \
.penalizes_more_than(2)

verifier.verify_that(same_value) \
.given(e1, e2, e3) \
.penalizes_less_than(4)

verifier.verify_that(same_value) \
.given(e1, e2, e3) \
.penalizes()

with pytest.raises(AssertionError):
verifier.verify_that(same_value) \
.given(e1, e2, e3) \
.penalizes_more_than(3)

with pytest.raises(AssertionError):
verifier.verify_that(same_value) \
.given(e1, e2, e3) \
.penalizes_less_than(3)

with pytest.raises(AssertionError):
verifier.verify_that(same_value) \
.given(e1, e2, e3) \
Expand Down Expand Up @@ -199,28 +263,64 @@ def verifier_suite(verifier: ConstraintVerifier, same_value, is_value_one,


def test_constraint_verifier_create():
@dataclass
@dataclass(unsafe_hash=True)
class Value:
code: str

def __str__(self):
return f'Value({self.code})'

@planning_entity
@dataclass
@dataclass(unsafe_hash=True)
class Entity:
code: str
value: Annotated[Value, PlanningVariable] = field(default=None)
value: Annotated[Value | None, PlanningVariable] = field(default=None)

def __str__(self):
return f'Entity({self.code}, {self.value})'

@dataclass(unsafe_hash=True)
class EntityValueIndictment:
entity: Entity
value: Value

def __str__(self):
return f'EntityValueIndictment({self.entity}, {self.value})'

@dataclass(unsafe_hash=True)
class EntityValueJustification(ConstraintJustification):
entity: Entity
value: Value
score: SimpleScore

def __str__(self):
return f'EntityValueJustification({self.entity}, {self.value}, {self.score})'

@dataclass(unsafe_hash=True)
class EntityValuePairJustification(ConstraintJustification):
entities: tuple[Entity]
value: Value
score: SimpleScore

def __str__(self):
return f'EntityValuePairJustification({self.entities}, {self.value}, {self.score})'

def same_value(constraint_factory: ConstraintFactory):
return (constraint_factory.for_each(Entity)
.join(Entity, Joiners.less_than(lambda e: e.code),
Joiners.equal(lambda e: e.value))
.penalize(SimpleScore.ONE)
.indict_with(lambda e1, e2: [EntityValueIndictment(e1, e1.value), EntityValueIndictment(e2, e2.value)])
.justify_with(lambda e1, e2, score: EntityValuePairJustification((e1, e2), e1.value, score))
.as_constraint('Same Value')
)

def is_value_one(constraint_factory: ConstraintFactory):
return (constraint_factory.for_each(Entity)
.filter(lambda e: e.value.code == 'v1')
.reward(SimpleScore.ONE)
.indict_with(lambda e: [EntityValueIndictment(e, e.value)])
.justify_with(lambda e, score: EntityValueJustification(e, e.value, score))
.as_constraint('Value 1')
)

Expand Down Expand Up @@ -259,6 +359,7 @@ class Solution:
solution = Solution([e1, e2, e3], [v1, v2, v3])

verifier_suite(verifier, same_value, is_value_one,
EntityValueIndictment, EntityValueJustification, EntityValuePairJustification,
solution, e1, e2, e3, v1, v2, v3)

verifier = ConstraintVerifier.build(my_constraints, Solution, Entity)
Expand All @@ -274,6 +375,7 @@ class Solution:
solution = Solution([e1, e2, e3], [v1, v2, v3])

verifier_suite(verifier, same_value, is_value_one,
EntityValueIndictment, EntityValueJustification, EntityValuePairJustification,
solution, e1, e2, e3, v1, v2, v3)


Expand Down
52 changes: 40 additions & 12 deletions timefold-solver-python-core/src/main/python/test/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ class SingleConstraintAssertion:
def __init__(self, delegate):
self.delegate = delegate

def justifies_with(self, message: str = None, *justifications: 'ConstraintJustification') \
def justifies_with(self, *justifications: 'ConstraintJustification', message: str = None) \
-> 'SingleConstraintAssertion':
"""
Asserts that the constraint being tested, given a set of facts, results in given justifications.
Expand All @@ -192,15 +192,22 @@ def justifies_with(self, message: str = None, *justifications: 'ConstraintJustif
when the expected justifications are not observed
"""
from java.lang import AssertionError as JavaAssertionError # noqa
from _jpyinterpreter import convert_to_java_python_like_object
from java.util import HashMap
reference_map = HashMap()
wrapped_justifications = []
for justification in justifications:
wrapped_justification = convert_to_java_python_like_object(justification, reference_map)
wrapped_justifications.append(wrapped_justification)
try:
if message is None:
return self.delegate.justifiesWith(justifications)
return SingleConstraintAssertion(self.delegate.justifiesWith(*wrapped_justifications))
else:
return self.delegate.justifiesWith(message, justifications)
return SingleConstraintAssertion(self.delegate.justifiesWith(message, *wrapped_justifications))
except JavaAssertionError as e:
raise AssertionError(e.getMessage())

def justifies_with_exactly(self, message: str = None, *justifications: 'ConstraintJustification') \
def justifies_with_exactly(self, *justifications: 'ConstraintJustification', message: str = None) \
-> 'SingleConstraintAssertion':
"""
Asserts that the constraint being tested, given a set of facts, results in given justifications an no others.
Expand All @@ -219,15 +226,22 @@ def justifies_with_exactly(self, message: str = None, *justifications: 'Constrai
when the expected justifications are not observed
"""
from java.lang import AssertionError as JavaAssertionError # noqa
from _jpyinterpreter import convert_to_java_python_like_object
from java.util import HashMap
reference_map = HashMap()
wrapped_justifications = []
for justification in justifications:
wrapped_justification = convert_to_java_python_like_object(justification, reference_map)
wrapped_justifications.append(wrapped_justification)
try:
if message is None:
return self.delegate.justifiesWithExactly(justifications)
return SingleConstraintAssertion(self.delegate.justifiesWithExactly(*wrapped_justifications))
else:
return self.delegate.justifiesWithExactly(message, justifications)
return SingleConstraintAssertion(self.delegate.justifiesWithExactly(message, *wrapped_justifications))
except JavaAssertionError as e:
raise AssertionError(e.getMessage())

def indicts_with(self, message: str = None, *indictments) -> 'SingleConstraintAssertion':
def indicts_with(self, *indictments, message: str = None) -> 'SingleConstraintAssertion':
"""
Asserts that the constraint being tested, given a set of facts, results in given indictments.
Expand All @@ -245,15 +259,22 @@ def indicts_with(self, message: str = None, *indictments) -> 'SingleConstraintAs
when the expected indictments are not observed
"""
from java.lang import AssertionError as JavaAssertionError # noqa
from _jpyinterpreter import convert_to_java_python_like_object
from java.util import HashMap
reference_map = HashMap()
wrapped_indictments = []
for indictment in indictments:
wrapped_indictment = convert_to_java_python_like_object(indictment, reference_map)
wrapped_indictments.append(wrapped_indictment)
try:
if message is None:
return self.delegate.indictsWith(indictments)
return SingleConstraintAssertion(self.delegate.indictsWith(*wrapped_indictments))
else:
return self.delegate.indictsWith(message, indictments)
return SingleConstraintAssertion(self.delegate.indictsWith(message, *wrapped_indictments))
except JavaAssertionError as e:
raise AssertionError(e.getMessage())

def indicts_with_exactly(self, message: str = None, *indictments) -> 'SingleConstraintAssertion':
def indicts_with_exactly(self, *indictments, message: str = None) -> 'SingleConstraintAssertion':
"""
Asserts that the constraint being tested, given a set of facts, results in given indictments an no others.
Expand All @@ -271,11 +292,18 @@ def indicts_with_exactly(self, message: str = None, *indictments) -> 'SingleCons
when the expected indictments are not observed
"""
from java.lang import AssertionError as JavaAssertionError # noqa
from _jpyinterpreter import convert_to_java_python_like_object
from java.util import HashMap
reference_map = HashMap()
wrapped_indictments = []
for indictment in indictments:
wrapped_indictment = convert_to_java_python_like_object(indictment, reference_map)
wrapped_indictments.append(wrapped_indictment)
try:
if message is None:
return self.delegate.indictsWithExactly(indictments)
return SingleConstraintAssertion(self.delegate.indictsWithExactly(*wrapped_indictments))
else:
return self.delegate.indictsWithExactly(message, indictments)
return SingleConstraintAssertion(self.delegate.indictsWithExactly(message, *wrapped_indictments))
except JavaAssertionError as e:
raise AssertionError(e.getMessage())

Expand Down

0 comments on commit 1643b23

Please sign in to comment.