Commit 6d17f0a1 authored by Tatuya Kamada's avatar Tatuya Kamada

simulation: Test the divergence on Float Equivalence Tester with the epsilon-span by default

Before:
   Float Equivalence Tester does not test the divergence even if Divergence Provider is ON
    
After:
     Float Equivalence Tester test the divergence with the epsilon-span if Divergence Provider is ON
    
Before, we needed the following condition to test the divergence at Float Equivalence Tester:
    
       "Divergence Provider is ON"  AND
       ("Absolute Tolerance has some value"  OR
        "Relative Tolerance has some value" )
    
However this default behavior was not consistent with other testers, and
difficult to notice from the user interface.
Thus test the divergence by default with the epsilon span which is defined in 
FloatEquivalenceTester class.
    
The epsilon-span is the same amount that when we put 0.0 into the ranges.
The 0.0 has been the configuration of erp5_configurator_standard_trade_template.

/reviewed-on nexedi/erp5!865
parent 932d1d1d
...@@ -117,14 +117,23 @@ class FloatEquivalenceTester(Predicate, EquivalenceTesterMixin): ...@@ -117,14 +117,23 @@ class FloatEquivalenceTester(Predicate, EquivalenceTesterMixin):
# XXX we should use appropriate property sheets and getter methods # XXX we should use appropriate property sheets and getter methods
# for these properties. # for these properties.
# Maybe, but beware of default values of quantity when doing so # Maybe, but beware of default values of quantity when doing so
absolute_tolerance_min = self.getProperty('quantity_range_min') tolerance_base = self.getProperty('tolerance_base')
# If tolerance_base is None, check the divergece with epsilon-span by default
# If tolerance_base is not None, we can use tolerance_base (absolute has priority)
if tolerance_base is None:
absolute_tolerance_min = self.getProperty('quantity_range_min') or -epsilon
else:
absolute_tolerance_min = self.getProperty('quantity_range_min')
if absolute_tolerance_min is not None and \ if absolute_tolerance_min is not None and \
delta < (absolute_tolerance_min or - epsilon): delta < (absolute_tolerance_min or - epsilon):
return ( return (
prevision_value, decision_value, prevision_value, decision_value,
explanation_start + 'is less than ${value}.', explanation_start + 'is less than ${value}.',
getMappingDict(value=absolute_tolerance_min)) getMappingDict(value=absolute_tolerance_min))
absolute_tolerance_max = self.getProperty('quantity_range_max') if tolerance_base is None:
absolute_tolerance_max = self.getProperty('quantity_range_max') or epsilon
else:
absolute_tolerance_max = self.getProperty('quantity_range_max')
if absolute_tolerance_max is not None and \ if absolute_tolerance_max is not None and \
delta > (absolute_tolerance_max or epsilon): delta > (absolute_tolerance_max or epsilon):
return ( return (
...@@ -132,7 +141,6 @@ class FloatEquivalenceTester(Predicate, EquivalenceTesterMixin): ...@@ -132,7 +141,6 @@ class FloatEquivalenceTester(Predicate, EquivalenceTesterMixin):
explanation_start + 'is larger than ${value}.', explanation_start + 'is larger than ${value}.',
getMappingDict(value=absolute_tolerance_max)) getMappingDict(value=absolute_tolerance_max))
tolerance_base = self.getProperty('tolerance_base')
base = None base = None
if tolerance_base == 'resource_quantity_precision': if tolerance_base == 'resource_quantity_precision':
# Precision of this movement's resource base unit quantity # Precision of this movement's resource base unit quantity
......
...@@ -31,6 +31,7 @@ from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase ...@@ -31,6 +31,7 @@ from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase
from Products.ERP5Type.tests.Sequence import Sequence from Products.ERP5Type.tests.Sequence import Sequence
from Products.ERP5.tests.testPackingList import TestPackingListMixin from Products.ERP5.tests.testPackingList import TestPackingListMixin
from Products.ERP5Type.UnrestrictedMethod import UnrestrictedMethod from Products.ERP5Type.UnrestrictedMethod import UnrestrictedMethod
from Products.ERP5.Document.FloatEquivalenceTester import DEFAULT_PRECISION
class TestDivergenceTester(TestPackingListMixin, ERP5TypeTestCase): class TestDivergenceTester(TestPackingListMixin, ERP5TypeTestCase):
""" """
...@@ -339,6 +340,173 @@ class TestDivergenceTester(TestPackingListMixin, ERP5TypeTestCase): ...@@ -339,6 +340,173 @@ class TestDivergenceTester(TestPackingListMixin, ERP5TypeTestCase):
divergence_tester.setDecimalExponent('0.01') divergence_tester.setDecimalExponent('0.01')
self.assertTrue(divergence_tester_compare(3.0, 3.001)) self.assertTrue(divergence_tester_compare(3.0, 3.001))
def stepAddDefaultRangePriceDivergenceTester(self, sequence):
self._addDivergenceTester(
sequence,
tester_name='price',
tester_type='Float Divergence Tester',
)
def stepCheckFloatDivergenceTesterWithTheDefaultRange(self, sequence):
"""
Check Float Divergence tester behavior, especially around the
default range = epsilon = abs(prevision_value * DEFAULT_PRECISION)
"""
divergence_tester = sequence['price_divergence_tester']
decision = sequence['order_line']
prevision = decision.getDeliveryRelatedValue(
portal_type=self.simulation_movement_portal_type)
def divergence_tester_compare(prevision_value, decision_value):
prevision.setPrice(prevision_value)
decision.setPrice(decision_value)
return divergence_tester.compare(prevision, decision)
prevision_value = 3.0
# Def. epsilon
# The is the epsilon defined by
# FloatEquivalenceTester._comparePrevisionDecisionValue()
epsilon = abs(prevision_value * DEFAULT_PRECISION)
self.assertFalse(divergence_tester.isDecimalAlignmentEnabled())
a_value_slightly_bigger_than_the_range = 3.0 + epsilon + DEFAULT_PRECISION
# Since
# delta = abs(decision - prevision)
# delta = (3.0 + epsilon + DEFAULT_PRECISION) - 3.0
# = epsilon + DEFAULT_PRECISION
# epsilon + DEFAULT_PRECISION < -epsilon is False
# epsilon + DEFAULT_PRECISION > epsilon is True
# --> Diverged <=> compare() returns False
self.assertFalse(
divergence_tester_compare(3.0, a_value_slightly_bigger_than_the_range))
a_value_slightly_smaller_than_the_range = 3.0 + epsilon - DEFAULT_PRECISION
# Since,
# delta = abs(decision - prevision)
# delta = (3.0 + epsilon - DEFAULT_PRECISION) - 3.0
# = epsilon - DEFAULT_PRECISION
# epsilon - DEFAULT_PRECISION < -epsilon is False
# epsilon - DEFAULT_PRECISION > epsilon is False
# --> Not diverged <=> compare() returns True
self.assertTrue(
divergence_tester_compare(3.0, a_value_slightly_smaller_than_the_range))
the_value_equal_to_the_range = 3.0 + epsilon
# Since,
# delta = abs(decision - prevision)
# delta = (3.0 + epsilon) - 3.0
# = epsilon
# epsilon < -epsilon is False
# epsilon > epsilon is False
# -- Not diverged <=> compare() returns True
self.assertTrue(
divergence_tester_compare(3.0, the_value_equal_to_the_range))
# The behavior for absolute_range is the same when we set
# 0.0 for the range.
# That is why the default bihavior is safe since most of sample
# test data has 0.0 for its range.
self.assertFalse(divergence_tester_compare(3.0, 3.001))
self.assertTrue(divergence_tester_compare(3.0, 3.0))
def testFloatDivergenceTesterAroundTheDefaultRange(
self, quiet=quiet, run=run_all_test):
"""
All the ranges of Float Divergence Tester:
- quantity_range_max,
- quantity_rang_min,
- tolerance_range_max,
- tolerance_range_min
are None by default. 0.0 is NOT the default value.
This is partly because FloatEquivalenceTester use None range as a flag
whether we use absolute_range or tolerance_range.
The logic uses the fact that 0.0 is not None, but bool(0.0) is False.
So here checking how it works around the range when they are default value
"""
if not run: return
sequence_string = self.confirmed_order_without_packing_list + """
ResetDeliveringRule
AddDefaultRangePriceDivergenceTester
Tic
CheckFloatDivergenceTesterWithTheDefaultRange
"""
sequence = Sequence(self)
sequence(sequence_string, quiet=self.quiet)
def stepSetNewPrice(self, sequence=None,
sequence_list=None, **kw):
packing_list = sequence.get('packing_list')
movement = sequence.get('movement')
movement.setPrice(movement.getPrice()+1234)
def stepSetPreviousPrice(self, sequence=None,
sequence_list=None, **kw):
simulation_movement = sequence.get('sim_mvt')
movement = sequence.get('movement')
movement.setPrice(simulation_movement.getPrice())
def stepSetPreviousPricePlusAValueSmallerThanTheEpsilon(
self, sequence=None, sequence_list=None, **kw):
simulation_movement = sequence.get('sim_mvt')
movement = sequence.get('movement')
prevision = simulation_movement.getPrice()
self.assertEqual(prevision, 555.0)
self.assertEqual(DEFAULT_PRECISION, 1e-12)
# Since the epsilon here is, epsilon = 555.0 * 1e-12.
# The following difference is smaller than the epsilon,
# thus the prevision and decision will be regarded as equal.
decision = prevision * (1 + 1e-15)
self.assertNotEqual(prevision, decision)
movement.setPrice(decision)
def stepSetMinimumRangeIntoPriceDivergenceTesterRegardingTestedPropertyToleranceBase(
self, sequence):
tester = sequence['price_divergence_tester']
tester.setProperty('tolerance_base', 'tested_property')
# 1 digit smaller than the DEFAULT_PRECISION meaning more strict than default
tester.setProperty('tolerance_range_max', 1e-13)
tester.setProperty('tolerance_range_min', 1e-13)
def testFloatDivergenceTesterWithTheDefaultRangeRegardingTestedPropertyToleranceBase(
self, quiet=quiet, run=run_all_test):
"""
Test Float Divergence Tester with the default range in a real use-case,
in addition, test a tolerance base configurataion: tested_property.
The defined behavior checking the divergence in FloatEquivalenceTester:
1. If nothing is configured, the default epsilon range is used
2. If not having absolete and having relative, the relative value is used
3. If having both absolete and relative value, the absolute value is used
This test checks case 1 and 2
"""
if not run: return
sequence_string = TestPackingListMixin.default_sequence + """
SetPackingListMovementAndSimulationMovement
ResetDeliveringRule
SetNewPrice
Tic
CheckPackingListIsNotDivergent
AddDefaultRangePriceDivergenceTester
CheckPackingListIsDivergent
SetPreviousPrice
Tic
CheckPackingListIsNotDivergent
SetPreviousPricePlusAValueSmallerThanTheEpsilon
Tic
CheckPackingListIsNotDivergent
SetMinimumRangeIntoPriceDivergenceTesterRegardingTestedPropertyToleranceBase
Tic
CheckPackingListIsDivergent
"""
sequence = Sequence(self)
sequence(sequence_string, quiet=self.quiet)
def test_suite(): def test_suite():
suite = unittest.TestSuite() suite = unittest.TestSuite()
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment