1#!/usr/bin/python3 2# Copyright 2019 The Chromium OS Authors. All rights reserved. 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6"""Unit tests for power telemetry utils.""" 7 8import unittest 9 10import common 11 12from autotest_lib.client.cros.power import power_telemetry_utils 13 14 15class TestInterpolateData(unittest.TestCase): 16 """Collection of tests to test smooten_data function in utils.""" 17 18 def test_Interpolate(self): 19 """Test that regular smoothening of data works.""" 20 data = [1.2, 3.6, float('nan'), float('nan'), 2.7] 21 expected_interp_data = [1.2, 3.6, 3.3, 3.0, 2.7] 22 interp_data = power_telemetry_utils.interpolate_missing_data(data) 23 self.assertListEqual(interp_data, expected_interp_data) 24 25 def test_InterpolateAllNaN(self): 26 """Test that a full NaN array cannot be smoothed.""" 27 data = [float('nan'), float('nan'), float('nan'), float('nan')] 28 with self.assertRaisesRegexp(power_telemetry_utils.TelemetryUtilsError, 29 'Data has no valid readings.'): 30 power_telemetry_utils.interpolate_missing_data(data) 31 32 def test_InterpolateGapStartAtBeginning(self): 33 """Test that a gap starting at the start gets the first known value.""" 34 data = [float('nan'), float('nan'), 2.6] 35 expected_interp_data = [2.6, 2.6, 2.6] 36 interp_data = power_telemetry_utils.interpolate_missing_data(data) 37 self.assertListEqual(interp_data, expected_interp_data) 38 39 def test_InterpolateGapEndsAtEnd(self): 40 """Test that a gap that ends at the end receives the last known value.""" 41 data = [2.6, float('nan'), float('nan')] 42 expected_interp_data = [2.6, 2.6, 2.6] 43 interp_data = power_telemetry_utils.interpolate_missing_data(data) 44 self.assertListEqual(interp_data, expected_interp_data) 45 46 def test_InterpolateTwoGaps(self): 47 """Test that two distinct gaps receive distinct values.""" 48 data = [2.6, float('nan'), 3.4, 2.0, float('nan'), 2.5] 49 expected_interp_data = [2.6, 3.0, 3.4, 2.0, 2.25, 2.5] 50 interp_data = power_telemetry_utils.interpolate_missing_data(data) 51 self.assertListEqual(interp_data, expected_interp_data) 52 53 def test_InterpolateHandlesIntegerDivision(self): 54 """Test that integer division does not cause issues.""" 55 data = [2, float('nan'), 3] 56 expected_interp_data = [2, 2.5, 3] 57 interp_data = power_telemetry_utils.interpolate_missing_data(data) 58 self.assertListEqual(interp_data, expected_interp_data) 59 60 def test_AcceptableNaNRatio(self): 61 """Validation succeeds if the ratio of NaN is below the threshold.""" 62 data = [2, float('nan'), 3, 4, 5, 6] 63 # This should pass as there are only 1/6 NaN in the data. 64 max_nan_ratio = 0.3 65 args = {'max_nan_ratio': max_nan_ratio} 66 interp_data = power_telemetry_utils.interpolate_missing_data( 67 data, **args) 68 69 def test_ExcessiveNaNRatio(self): 70 """Validation fails if the ratio of NaN to valid readings is too high.""" 71 data = [2, float('nan'), 3, 4, 5, 6] 72 # This should fail as there are 1/6 NaN in the data. 73 max_nan_ratio = 0.1 74 args = {'max_nan_ratio': max_nan_ratio} 75 with self.assertRaisesRegexp(power_telemetry_utils.TelemetryUtilsError, 76 'NaN ratio of'): 77 interp_data = power_telemetry_utils.interpolate_missing_data( 78 data, **args) 79 80 def test_ExcessiveNaNSampleGap(self): 81 """Validation fails on too many consecutive NaN samples.""" 82 data = [2, float('nan'), float('nan'), float('nan'), 3, 4, 5, 6] 83 # This should fail as there is a 3 NaN gap. 84 max_sample_gap = 2 85 args = {'max_sample_gap': max_sample_gap} 86 with self.assertRaisesRegexp(power_telemetry_utils.TelemetryUtilsError, 87 'Too many consecutive NaN samples:'): 88 interp_data = power_telemetry_utils.interpolate_missing_data( 89 data, **args) 90 91 def test_ExcessiveNaNSampleGapAtBeginning(self): 92 """Validation fails on too many consecutive NaN samples at the start.""" 93 data = [float('nan'), float('nan'), float('nan'), 2] 94 # This should fail as there is a 3 NaN gap. 95 max_sample_gap = 2 96 args = {'max_sample_gap': max_sample_gap} 97 with self.assertRaisesRegexp(power_telemetry_utils.TelemetryUtilsError, 98 'Too many consecutive NaN samples:'): 99 interp_data = power_telemetry_utils.interpolate_missing_data( 100 data, **args) 101 102 def test_ExcessiveNaNSampleGapAtEnd(self): 103 """Validation fails on too many consecutive NaN samples at the end.""" 104 data = [2, float('nan'), float('nan'), float('nan')] 105 # This should fail as there is a 3 NaN gap. 106 max_sample_gap = 2 107 args = {'max_sample_gap': max_sample_gap} 108 with self.assertRaisesRegexp(power_telemetry_utils.TelemetryUtilsError, 109 'Too many consecutive NaN samples:'): 110 interp_data = power_telemetry_utils.interpolate_missing_data( 111 data, **args) 112 113 def test_AcceptableNaNTimeSampleGap(self): 114 """Validation succeeds if NaN gap is below threshold given a timeline.""" 115 data = [2, float('nan'), float('nan'), 3, 4, 5, 6] 116 # Timeline is s for the data above. 117 timeline = [1, 4, 7, 10, 13, 16, 19] 118 # This should not fail as there is only 9s gap. 119 max_sample_time_gap = 10 120 args = { 121 'max_sample_time_gap': max_sample_time_gap, 122 'timeline': timeline 123 } 124 interp_data = power_telemetry_utils.interpolate_missing_data( 125 data, **args) 126 127 def test_ExcessiveNaNTimeSampleGap(self): 128 """Validation fails if NaN gap is too long on a given timeline.""" 129 data = [2, float('nan'), float('nan'), 3, 4, 5, 6] 130 # Timeline is s for the data above. 131 timeline = [1, 4, 7, 10, 13, 16, 19] 132 # This should fail as there 9s of gap. 133 max_sample_time_gap = 8 134 args = { 135 'max_sample_time_gap': max_sample_time_gap, 136 'timeline': timeline 137 } 138 with self.assertRaisesRegexp(power_telemetry_utils.TelemetryUtilsError, 139 'Excessively long sample gap'): 140 interp_data = power_telemetry_utils.interpolate_missing_data( 141 data, **args) 142 143 def test_NaNTimeSampleGapRequiresTimeline(self): 144 """|timeline| arg is required if checking for sample gap time.""" 145 data = [2, float('nan'), float('nan'), 3, 4, 5, 6] 146 # Timeline is s for the data above. 147 timeline = [1, 4, 7, 10, 13, 16, 19] 148 # This should fail the timeline is not provided in the args but the check 149 # is requested. 150 max_sample_time_gap = 2 151 args = {'max_sample_time_gap': max_sample_time_gap} 152 with self.assertRaisesRegexp(power_telemetry_utils.TelemetryUtilsError, 153 'Supplying max_sample_time_gap'): 154 interp_data = power_telemetry_utils.interpolate_missing_data( 155 data, **args) 156 157 158if __name__ == "__main__": 159 unittest.main() 160