xref: /aosp_15_r20/external/autotest/client/cros/power/power_telemetry_utils_unittest.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
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