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 5from __future__ import annotations 6 7import argparse 8from unittest import mock 9 10import hjson 11 12from crossbench import path as pth 13from crossbench.cli.config.probe import ProbeConfig, ProbeListConfig 14from crossbench.probes.power_sampler import PowerSamplerProbe 15from crossbench.probes.v8.log import V8LogProbe 16from crossbench.types import JsonDict 17from tests import test_helper 18from tests.crossbench.base import CrossbenchFakeFsTestCase 19 20 21class TestProbeConfig(CrossbenchFakeFsTestCase): 22 # pylint: disable=expression-not-assigned 23 24 def parse_config(self, config_data) -> ProbeListConfig: 25 probe_config_file = pth.LocalPath("/probe.config.hjson") 26 with probe_config_file.open("w", encoding="utf-8") as f: 27 hjson.dump(config_data, f) 28 return ProbeListConfig.parse(probe_config_file) 29 30 def test_invalid_empty(self): 31 with self.assertRaises(argparse.ArgumentTypeError): 32 self.parse_config({}).probes 33 with self.assertRaises(argparse.ArgumentTypeError): 34 self.parse_config({"foo": {}}).probes 35 36 def test_invalid_names(self): 37 with self.assertRaises(argparse.ArgumentTypeError): 38 self.parse_config({"probes": {"invalid probe name": {}}}).probes 39 40 def test_empty(self): 41 config = self.parse_config({"probes": {}}) 42 self.assertListEqual(config.probes, []) 43 44 def test_single_v8_log(self): 45 js_flags = ["--log-maps", "--log-function-events"] 46 config = self.parse_config( 47 {"probes": { 48 "v8.log": { 49 "prof": True, 50 "js_flags": js_flags, 51 } 52 }}) 53 self.assertTrue(len(config.probes), 1) 54 probe = config.probes[0] 55 assert isinstance(probe, V8LogProbe) 56 for flag in js_flags + ["--log-all"]: 57 self.assertIn(flag, probe.js_flags) 58 59 def test_from_cli_args(self): 60 file = pth.LocalPath("probe.config.hjson") 61 js_flags = ["--log-maps", "--log-function-events"] 62 config_data: JsonDict = { 63 "probes": { 64 "v8.log": { 65 "prof": True, 66 "js_flags": js_flags, 67 } 68 } 69 } 70 with file.open("w", encoding="utf-8") as f: 71 hjson.dump(config_data, f) 72 args = mock.Mock(probe_config=file) 73 config = ProbeListConfig.from_cli_args(args) 74 self.assertTrue(len(config.probes), 1) 75 probe = config.probes[0] 76 assert isinstance(probe, V8LogProbe) 77 for flag in js_flags + ["--log-all"]: 78 self.assertIn(flag, probe.js_flags) 79 80 def test_inline_config(self): 81 mock_d8_file = pth.LocalPath("out/d8") 82 self.fs.create_file(mock_d8_file, st_size=8 * 1024) 83 config_data = {"d8_binary": str(mock_d8_file)} 84 args = mock.Mock(probe_config=None, throw=True, wraps=False) 85 86 args.probe = [ 87 ProbeConfig.parse(f"v8.log{hjson.dumps(config_data)}"), 88 ] 89 config = ProbeListConfig.from_cli_args(args) 90 self.assertTrue(len(config.probes), 1) 91 probe = config.probes[0] 92 self.assertTrue(isinstance(probe, V8LogProbe)) 93 94 args.probe = [ 95 ProbeConfig.parse(f"v8.log:{hjson.dumps(config_data)}"), 96 ] 97 config = ProbeListConfig.from_cli_args(args) 98 self.assertTrue(len(config.probes), 1) 99 probe = config.probes[0] 100 self.assertTrue(isinstance(probe, V8LogProbe)) 101 102 def test_inline_config_invalid(self): 103 mock_d8_file = pth.LocalPath("out/d8") 104 self.fs.create_file(mock_d8_file) 105 config_data = {"d8_binary": str(mock_d8_file)} 106 trailing_brace = "}" 107 with self.assertRaises(argparse.ArgumentTypeError): 108 ProbeConfig.parse(f"v8.log{hjson.dumps(config_data)}{trailing_brace}") 109 with self.assertRaises(argparse.ArgumentTypeError): 110 ProbeConfig.parse(f"v8.log:{hjson.dumps(config_data)}{trailing_brace}") 111 with self.assertRaises(argparse.ArgumentTypeError): 112 ProbeConfig.parse("v8.log::") 113 114 def test_inline_config_dir_instead_of_file(self): 115 mock_dir = pth.LocalPath("some/dir") 116 mock_dir.mkdir(parents=True) 117 config_data = {"d8_binary": str(mock_dir)} 118 args = mock.Mock( 119 probe=[ProbeConfig.parse(f"v8.log{hjson.dumps(config_data)}")], 120 probe_config=None, 121 throw=True, 122 wraps=False) 123 with self.assertRaises(argparse.ArgumentTypeError) as cm: 124 ProbeListConfig.from_cli_args(args) 125 self.assertIn(str(mock_dir), str(cm.exception)) 126 127 def test_inline_config_non_existent_file(self): 128 config_data = {"d8_binary": "does/not/exist/d8"} 129 args = mock.Mock( 130 probe=[ProbeConfig.parse(f"v8.log{hjson.dumps(config_data)}")], 131 probe_config=None, 132 throw=True, 133 wraps=False) 134 with self.assertRaises(argparse.ArgumentTypeError) as cm: 135 ProbeListConfig.from_cli_args(args) 136 expected_path = pth.LocalPath("does/not/exist/d8") 137 self.assertIn(str(expected_path), str(cm.exception)) 138 139 def test_multiple_probes(self): 140 powersampler_bin = pth.LocalPath("/powersampler.bin") 141 powersampler_bin.touch() 142 config = self.parse_config({ 143 "probes": { 144 "v8.log": { 145 "log_all": True, 146 }, 147 "powersampler": { 148 "bin_path": str(powersampler_bin) 149 } 150 } 151 }) 152 self.assertTrue(len(config.probes), 2) 153 log_probe = config.probes[0] 154 assert isinstance(log_probe, V8LogProbe) 155 powersampler_probe = config.probes[1] 156 assert isinstance(powersampler_probe, PowerSamplerProbe) 157 self.assertEqual(powersampler_probe.bin_path, powersampler_bin) 158 159 160if __name__ == "__main__": 161 test_helper.run_pytest(__file__) 162