blob: 767b8bb2256f18c5cfc3726a26d3bbe635a19d20 [file] [log] [blame]
# Copyright 2015 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import math
import unittest
from dashboard import find_step
from dashboard import math_utils
# Sample data where there is a small-ish step upwards around revision 304772.
_QUITE_STEPPISH = [
(304469, 1056467.555),
(304496, 1053394.222),
(304521, 1052791.555),
(304537, 1061429.777),
(304554, 1057143.111),
(304561, 1058818.222),
(304571, 1052204.0),
(304575, 1056174.222),
(304580, 1057829.333),
(304585, 1065234.222),
(304591, 1049981.333),
(304600, 1071542.222),
(304613, 1059949.777),
(304632, 1056992.888),
(304650, 1061859.111),
(304666, 1053670.222),
(304685, 1068547.111),
(304714, 1059868.444),
(304739, 1056345.777),
(304759, 1051712.0),
(304767, 1058595.555),
(304772, 1081055.555),
(304783, 1080473.777),
(304791, 1088900.0),
(304794, 1083874.222),
(304797, 1082668.444),
(304809, 1086152.444),
(304822, 1090828.444),
(304839, 1094134.666),
(304868, 1087627.555),
(304892, 1085648.444),
(304916, 1082401.777),
(304957, 1090009.777),
(304968, 1080076.444),
(304984, 1085424.888),
(304990, 1085520.444),
(304995, 1089851.111),
(305001, 1077174.222),
(305003, 1085427.111),
(305007, 1080062.222),
]
# Sample data where there is no clear step pattern.
_NOT_STEPPISH = [
(301306, 965.568),
(301307, 962.378),
(301310, 962.355),
(301313, 959.623),
(301315, 967.824),
(301317, 956.898),
(301321, 1090.237),
(301324, 957.379),
(301330, 961.496),
(301335, 951.585),
(301340, 950.437),
(301349, 952.022),
(301354, 965.407),
(301364, 962.668),
(301366, 960.527),
(301386, 1198.086),
(301403, 979.097),
(301405, 955.033),
(301430, 952.072),
(301456, 1026.557),
(301467, 952.001),
(301490, 993.909),
(301529, 960.194),
(301548, 960.707),
(301557, 976.913),
(301558, 980.134),
(301568, 1144.155),
(301576, 1234.563),
(301589, 959.423),
(301592, 959.784),
(301599, 953.104),
(301604, 978.390),
(301628, 994.807),
(301633, 970.342),
(301641, 964.696),
(301650, 960.404),
(301665, 960.229),
(301679, 978.075),
(301696, 1176.206),
(301709, 979.868),
(301732, 973.265),
]
class FindStepTest(unittest.TestCase):
def testFindStep_EmptyList_ReturnsNone(self):
self.assertIsNone(find_step.FindStep([]))
def testFindStep_LengthOneList_ReturnsNone(self):
self.assertIsNone(find_step.FindStep([(1, 1.0)]))
def testFindStep_LengthTwoList_FindsStep(self):
self.assertEqual(2, (find_step.FindStep([(1, 1.0), (2, 2.0)])))
def testFindStep_QuiteSteppishExample(self):
self.assertEqual(304772, find_step.FindStep(_QUITE_STEPPISH))
y_values = [y for _, y in _QUITE_STEPPISH]
self.assertEqual(21, find_step._MinimizeDistanceFromStep(y_values))
self.assertAlmostEqual(
4863.05166991, find_step._DistanceFromStep(y_values, 21))
self.assertAlmostEqual(
5.55211054, find_step._RegressionSizeScore(y_values, 21))
def testFindStep_NotSteppishExample(self):
self.assertIsNone(find_step.FindStep(_NOT_STEPPISH))
y_values = [y for _, y in _NOT_STEPPISH]
self.assertEqual(15, find_step._MinimizeDistanceFromStep(y_values))
self.assertAlmostEqual(
67.44240311, find_step._DistanceFromStep(y_values, 15))
self.assertAlmostEqual(
0.53773162, find_step._RegressionSizeScore(y_values, 15))
def testSteppiness_PerfectStep_ReturnsOne(self):
step = [3, 3, 3, 3, 10, 10, 10]
self.assertEqual(1.0, find_step.Steppiness(step, 4))
def testSteppiness_ImperfectStep_ReturnsValueBetweenZeroAndOne(self):
step = [1, 2, 1, 2, 4, 3, 5, 4]
self.assertAlmostEqual(0.56005865, find_step.Steppiness(step, 4))
def testSteppiness_LengthOfSeriesDoesntAffectScore(self):
step = [1, 2, 1, 2, 4, 3, 5, 4]
step_long = [1, 2, 1, 2, 2, 1, 2, 1, 4, 3, 5, 4, 5, 3, 4, 4]
self.assertAlmostEqual(
find_step.Steppiness(step, 4),
find_step.Steppiness(step_long, 8))
def testSteppiness_UpDownPattern_ReturnsZero(self):
step = [1, 0, 1, 0, 1, 0, 1, 0]
self.assertAlmostEqual(0.0, find_step.Steppiness(step, 4))
def testNormalize_EmptyInput_ReturnsEmptyList(self):
self.assertEqual(0, len(find_step._Normalize([])))
def testNormalizeZeroVariance(self):
# A list with a standard deviation of zero cannot be normalized because
# the standard deviation cannot be made equal to 1.
normalized = find_step._Normalize([5.0])
self.assertEqual(1, len(normalized))
self.assertTrue(math.isnan(normalized[0]))
normalized = find_step._Normalize([5.0, 5.0])
self.assertEqual(2, len(normalized))
self.assertTrue(math.isnan(normalized[0]))
self.assertTrue(math.isnan(normalized[1]))
def testNormalize_ShortList(self):
self.assertEqual([-1, 1], find_step._Normalize([3, 4]))
self.assertEqual(
[-1.2247448713915889, 0, 1.2247448713915889],
find_step._Normalize([3, 4, 5]))
def testNormalize_ResultMeanIsZeroAndStdDevIsOne(self):
# When a data series is normalized, it is guaranteed that the result
# should have a mean of 0.0 and a standard deviation and variance of 1.0.
_, y_values = zip(*_QUITE_STEPPISH)
normalized = find_step._Normalize(y_values)
self.assertAlmostEqual(0.0, math_utils.Mean(normalized))
self.assertAlmostEqual(1.0, math_utils.StandardDeviation(normalized))
def testMinimizeDistanceFromStep(self):
self.assertRaises(ValueError, find_step._MinimizeDistanceFromStep, [])
self.assertRaises(ValueError, find_step._MinimizeDistanceFromStep, [1])
self.assertEqual(1, find_step._MinimizeDistanceFromStep([1, 2]))
self.assertEqual(3, find_step._MinimizeDistanceFromStep([1, 2, 3, 5]))
self.assertEqual(
3, find_step._MinimizeDistanceFromStep([2.3, 2.0, 2.2, 4.4, 4.0]))
def testRegressionSizeScore(self):
self.assertEqual(0.0, find_step._RegressionSizeScore([1, 1], 1))
self.assertEqual(float('inf'), find_step._RegressionSizeScore([1, 2], 1))
self.assertEqual(7.0, find_step._RegressionSizeScore([3, 5, 10, 12], 2))
self.assertLess(find_step._RegressionSizeScore([3, 5, 10, 12], 2),
find_step._RegressionSizeScore([3, 4, 10, 11], 2))
self.assertLess(find_step._RegressionSizeScore([3, 5, 10, 12], 2),
find_step._RegressionSizeScore([3, 5, 20, 22], 2))
def testDistanceFromStep(self):
self.assertEqual(0.0, find_step._DistanceFromStep([], 0))
self.assertEqual(0.0, find_step._DistanceFromStep([0, 1], 1))
self.assertEqual(0.0, find_step._DistanceFromStep([4, 4, 2, 2], 2))
self.assertEqual(1.0, find_step._DistanceFromStep([3, 5, 10, 12], 2))
def testStepFunctionValues(self):
self.assertEqual([], find_step._StepFunctionValues([], 0))
self.assertEqual([2, 2, 2, 4],
find_step._StepFunctionValues([1, 2, 3, 4], 3))
self.assertEqual([1.5, 1.5, 3.5, 3.5],
find_step._StepFunctionValues([1, 2, 3, 4], 2))
def testRootMeanSquareDeviation(self):
self.assertEqual(0.0, find_step._RootMeanSquareDeviation([], []))
self.assertEqual(1.0, find_step._RootMeanSquareDeviation([1, 2], [2, 3]))
self.assertEqual(2.0, find_step._RootMeanSquareDeviation([5.5], [3.5]))
def testSumOfSquaredResiduals(self):
self.assertEqual(0.0, find_step._SumOfSquaredResiduals([], []))
self.assertEqual(0.0, find_step._SumOfSquaredResiduals([1, 2, 3], []))
self.assertEqual(0.0, find_step._SumOfSquaredResiduals([1, 2], [1, 2]))
self.assertEqual(2.0, find_step._SumOfSquaredResiduals([1, 2], [2, 3]))
self.assertEqual(4.0, find_step._SumOfSquaredResiduals([5.5], [3.5]))
self.assertEqual(8.0, find_step._SumOfSquaredResiduals([5.5, 4], [3.5, 6]))
if __name__ == '__main__':
unittest.main()