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