• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2024 The Chromium Authors
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5import argparse
6from typing import List
7from unittest import mock
8
9from immutabledict import immutabledict
10
11from crossbench import path as pth
12from crossbench.browsers.browser import Browser
13from crossbench.env import HostEnvironment
14from crossbench.plt.linux import LinuxPlatform
15from crossbench.probes.cpu_frequency_map import CPUFrequencyMap
16from crossbench.probes.frequency import FrequencyProbe, FrequencyProbeContext
17from crossbench.runner.run import Run
18from tests import test_helper
19from tests.crossbench.base import CrossbenchFakeFsTestCase
20
21
22# TODO(crbug.com/372862708): Turn most of these into unit tests for
23# CPUFrequencyMap and leave only 1-2 tests that verify the communication between
24# the 2 classes.
25
26
27class FrequencyProbeTestCase(CrossbenchFakeFsTestCase):
28  __test__ = True
29
30  def setUp(self):
31    super().setUp()
32    self.platform = LinuxPlatform()
33
34  def test_parse_invalid_map_value(self):
35    with self.assertRaisesRegex(argparse.ArgumentTypeError, "Invalid value"):
36      FrequencyProbe.from_config({"cpus": {"cpu0": "invalid"}})
37
38  def test_parse_conflicting_wildcard(self):
39    with self.assertRaisesRegex(argparse.ArgumentTypeError,
40                                "should be the only key"):
41      FrequencyProbe.from_config({"cpus": {"*": "max", "cpu0": "min"}})
42
43  def test_key(self):
44    key1 = FrequencyProbe.from_config({"cpus": {"cpu0": 1111}}).key
45    key2 = FrequencyProbe.from_config({"cpus": {"*": 2222}}).key
46
47    self.assertIsNotNone(key1)
48    self.assertIsNotNone(key2)
49    self.assertNotEqual(key1, key2)
50
51  def test_validate_fails_due_to_missing_cpus_dir(self):
52    probe = FrequencyProbe.from_config({"cpus": {"cpu0": 42}})
53    # No call to self._create_cpu_dir().
54
55    with self.assertRaisesRegex(FileNotFoundError,
56                                "/sys/devices/system/cpu not found"):
57      probe.validate_browser(
58          mock.Mock(spec=HostEnvironment), self._create_mock_browser())
59
60  def test_validate_fails_due_to_missing_cpu_name(self):
61    probe = FrequencyProbe.from_config({"cpus": {"nonexistent-cpu": 1}})
62    self._create_cpu_dir("cpu0", [1])
63
64    with self.assertRaisesRegex(ValueError, "nonexistent-cpu"):
65      probe.validate_browser(
66          mock.Mock(spec=HostEnvironment), self._create_mock_browser())
67
68  def test_validate_fails_due_to_missing_numerical_frequency(self):
69    probe = FrequencyProbe.from_config({"cpus": {"cpu0": 42}})
70    self._create_cpu_dir("cpu0", [1, 2])
71    self._create_cpu_dir("cpu1", [42])
72
73    with self.assertRaisesRegex(
74        ValueError, r"Target frequency 42 for cpu0 not allowed in linux.*. "
75        r"Available frequencies: \[1, 2\]"):
76      probe.validate_browser(
77          mock.Mock(spec=HostEnvironment), self._create_mock_browser())
78
79  def test_validate_fails_due_to_missing_numerical_frequency_with_wildcard(
80      self):
81    probe = FrequencyProbe.from_config({"cpus": {"*": 42}})
82    self._create_cpu_dir("cpu0", [1, 2])
83
84    with self.assertRaisesRegex(
85        ValueError, r"Target frequency 42 for cpu0 not allowed in linux.*. "
86        r"Available frequencies: \[1, 2\]"):
87      probe.validate_browser(
88          mock.Mock(spec=HostEnvironment), self._create_mock_browser())
89
90  def test_validate_succeeds_with_extremes(self):
91    probe = FrequencyProbe.from_config({"cpus": {"cpu0": "max", "cpu1": "min"}})
92    self._create_cpu_dir("cpu0", [1, 2])
93    self._create_cpu_dir("cpu1", [1, 2])
94    browser = self._create_mock_browser()
95
96    # Implicitly asserts no exception occurs.
97    probe.validate_browser(mock.Mock(spec=HostEnvironment), browser)
98    frequency_map = probe.cpu_frequency_map.get_target_frequencies(
99        browser.platform)
100
101    self.assertDictEqual(
102        dict(frequency_map), {
103            pth.AnyPosixPath("/sys/devices/system/cpu/cpu0/cpufreq"): 2,
104            pth.AnyPosixPath("/sys/devices/system/cpu/cpu1/cpufreq"): 1,
105        })
106
107
108  def test_validate_succeeds_without_wildcard(self):
109    probe = FrequencyProbe.from_config(
110        {"cpus": {
111            "cpu0": 2,
112            "cpu1": 2,
113            "cpu2": 2
114        }})
115    # Use different orders to stress the parsing logic.
116    self._create_cpu_dir("cpu0", [2, 1, 3])
117    self._create_cpu_dir("cpu1", [1, 2, 3])
118    self._create_cpu_dir("cpu2", [1, 3, 2])
119    self._create_cpu_dir("cpu3", [42, 42, 42])
120    self._create_cpu_dir("cpu4", [42, 42, 42])
121    browser = self._create_mock_browser()
122
123    # Implicitly asserts no exception occurs.
124    probe.validate_browser(mock.Mock(spec=HostEnvironment), browser)
125    frequency_map = probe.cpu_frequency_map.get_target_frequencies(
126        browser.platform)
127
128    self.assertDictEqual(
129        dict(frequency_map), {
130            pth.AnyPosixPath("/sys/devices/system/cpu/cpu0/cpufreq"): 2,
131            pth.AnyPosixPath("/sys/devices/system/cpu/cpu1/cpufreq"): 2,
132            pth.AnyPosixPath("/sys/devices/system/cpu/cpu2/cpufreq"): 2,
133        })
134
135  def test_validate_succeeds_with_wildcard(self):
136    probe = FrequencyProbe.from_config({"cpus": {"*": 2}})
137    # Use different orders to stress the parsing logic.
138    self._create_cpu_dir("cpu0", [2, 1, 3])
139    self._create_cpu_dir("cpu1", [1, 2, 3])
140    self._create_cpu_dir("cpu2", [1, 3, 2])
141    browser = self._create_mock_browser()
142
143    # Implicitly asserts no exception occurs.
144    probe.validate_browser(mock.Mock(spec=HostEnvironment), browser)
145    frequency_map = probe.cpu_frequency_map.get_target_frequencies(
146        browser.platform)
147
148    self.assertDictEqual(
149        dict(frequency_map), {
150            pth.AnyPosixPath("/sys/devices/system/cpu/cpu0/cpufreq"): 2,
151            pth.AnyPosixPath("/sys/devices/system/cpu/cpu1/cpufreq"): 2,
152            pth.AnyPosixPath("/sys/devices/system/cpu/cpu2/cpufreq"): 2,
153        })
154
155  def test_validate_succeeds_for_empty_configs(self):
156    browser = self._create_mock_browser()
157    self._create_cpu_dir("cpu0", [1, 2, 3])
158
159    FrequencyProbe.from_config({}).validate_browser(
160        mock.Mock(spec=HostEnvironment), browser)
161    FrequencyProbe.from_config({
162        "cpus": {},
163    }).validate_browser(mock.Mock(spec=HostEnvironment), browser)
164
165  def test_validate_string_wildcard(self):
166    probe = FrequencyProbe.from_config({"cpus": "max"})
167    self._create_cpu_dir("cpu0", [1, 2, 3])
168    self._create_cpu_dir("cpu1", [1, 2, 3])
169    browser = self._create_mock_browser()
170
171    # Implicitly asserts no exception occurs.
172    probe.validate_browser(mock.Mock(spec=HostEnvironment), browser)
173    frequency_map = probe.cpu_frequency_map.get_target_frequencies(
174        browser.platform)
175
176    self.assertDictEqual(
177        dict(frequency_map), {
178            pth.AnyPosixPath("/sys/devices/system/cpu/cpu0/cpufreq"): 3,
179            pth.AnyPosixPath("/sys/devices/system/cpu/cpu1/cpufreq"): 3,
180        })
181
182  def test_start_and_stop(self):
183    self._create_cpu_dir("cpu0", [1, 2, 3])
184    mock_map = mock.Mock(spec=CPUFrequencyMap)
185    mock_map.get_target_frequencies = mock.Mock(
186        return_value=immutabledict(
187            {pth.AnyPosixPath("/sys/devices/system/cpu/cpu0/cpufreq"): 2}))
188    mock_probe = mock.Mock(spec=FrequencyProbe)
189    type(mock_probe).cpu_frequency_map = mock.PropertyMock(
190        return_value=mock_map)
191    mock_run = mock.Mock(spec=Run)
192    mock_run.browser = self._create_mock_browser()
193    context = FrequencyProbeContext(mock_probe, mock_run)
194
195    min_file = pth.AnyPosixPath(
196        "/sys/devices/system/cpu/cpu0/cpufreq/scaling_min_freq")
197    max_file = pth.AnyPosixPath(
198        "/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq")
199    self.assertEqual(self.platform.cat(min_file), "1\n")
200    self.assertEqual(self.platform.cat(max_file), "3\n")
201
202    context.start()
203
204    self.assertEqual(self.platform.cat(min_file), "2\n")
205    self.assertEqual(self.platform.cat(max_file), "2\n")
206    mock_map.get_target_frequencies.assert_called_with(self.platform)
207
208    context.stop()
209
210    self.assertEqual(self.platform.cat(min_file), "1\n")
211    self.assertEqual(self.platform.cat(max_file), "3\n")
212
213  def _create_mock_browser(self):
214    mock_browser = mock.Mock(spec=Browser)
215    mock_browser.platform = self.platform
216    return mock_browser
217
218  def _create_cpu_dir(self, cpu_name: str, available_frequencies: List[int]):
219    cpu_dir = pth.AnyPosixPath(f"/sys/devices/system/cpu/{cpu_name}/cpufreq")
220    self.platform.mkdir(cpu_dir, parents=True, exist_ok=True)
221    self.platform.set_file_contents(
222        cpu_dir / "scaling_available_frequencies",
223        " ".join(map(str, available_frequencies)) + "\n")
224    self.platform.set_file_contents(cpu_dir / "scaling_min_freq",
225                                    str(min(available_frequencies)) + "\n")
226    self.platform.set_file_contents(cpu_dir / "scaling_max_freq",
227                                    str(max(available_frequencies)) + "\n")
228
229if __name__ == "__main__":
230  test_helper.run_pytest(__file__)
231