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 8import unittest 9 10from crossbench.cli.config.browser_variants import (FlagsConfig, 11 FlagsGroupConfig, 12 FlagsVariantConfig) 13from crossbench.config import ConfigError 14from crossbench.exception import ArgumentTypeMultiException 15from crossbench.flags.base import Flags 16from tests import test_helper 17 18 19class FlagsConfigTestCase(unittest.TestCase): 20 21 def test_invalid_empty(self): 22 with self.assertRaises(ArgumentTypeMultiException) as cm: 23 FlagsConfig.parse("") 24 self.assertIn("empty", str(cm.exception).lower()) 25 with self.assertRaises(ConfigError) as cm: 26 FlagsConfig.parse_str("") 27 self.assertIn("empty", str(cm.exception).lower()) 28 29 def test_empty_dict(self): 30 config = FlagsConfig.parse({}) 31 self.assertFalse(config) 32 33 def test_parse_empty_group(self): 34 config = FlagsConfig.parse({ 35 "a": None, 36 "b": {}, 37 "c": tuple(), 38 }) 39 self.assertEqual(len(config), 3) 40 for group in config.values(): 41 self.assertFalse(group) 42 self.assertFalse(config["a"]) 43 self.assertFalse(config["b"]) 44 self.assertFalse(config["c"]) 45 46 def test_parse_str(self): 47 config = FlagsConfig.parse("--foo --bar") 48 self.assertEqual(len(config), 1) 49 self.assertEqual(str(config["default"][0].flags), "--foo --bar") 50 51 def test_parse_single_str_groups(self): 52 config = FlagsConfig.parse({ 53 "a": "--foo=1 --bar", 54 "b": "--foo=2 --bar", 55 }) 56 self.assertEqual(len(config), 2) 57 self.assertEqual(len(config["a"]), 1) 58 self.assertEqual(len(config["b"]), 1) 59 flags_a = config["a"][0].flags 60 flags_b = config["b"][0].flags 61 self.assertEqual(len(flags_a), 2) 62 self.assertEqual(len(flags_b), 2) 63 self.assertEqual(str(flags_a), "--foo=1 --bar") 64 self.assertEqual(str(flags_b), "--foo=2 --bar") 65 66 def test_parse_single_dict_groups(self): 67 config = FlagsConfig.parse({ 68 "a": { 69 "--foo": "1", 70 "--bar": None, 71 }, 72 "b": { 73 "--foo": "2", 74 "--bar": None 75 } 76 }) 77 self.assertEqual(len(config), 2) 78 self.assertEqual(len(config["a"]), 1) 79 self.assertEqual(len(config["b"]), 1) 80 flags_a = config["a"][0].flags 81 flags_b = config["b"][0].flags 82 self.assertEqual(len(flags_a), 2) 83 self.assertEqual(len(flags_b), 2) 84 self.assertEqual(str(flags_a), "--foo=1 --bar") 85 self.assertEqual(str(flags_b), "--foo=2 --bar") 86 87 def test_parse_multi_str_groups(self): 88 config = FlagsConfig.parse({ 89 "a": [ 90 "--foo=1 --bar=1", 91 "--foo=1 --bar=2", 92 ], 93 "b": "--foo=2 --bar", 94 }) 95 self.assertEqual(len(config), 2) 96 self.assertEqual(len(config["a"]), 2) 97 self.assertEqual(len(config["b"]), 1) 98 labels = tuple(v.label for v in config["a"]) # pylint: disable=no-member 99 self.assertTupleEqual(labels, ("foo=1_bar=1", "foo=1_bar=2")) 100 variants_a = config["a"] 101 flags_a_1 = variants_a[0].flags 102 flags_a_2 = variants_a[1].flags 103 self.assertEqual(str(flags_a_1), "--foo=1 --bar=1") 104 self.assertEqual(str(flags_a_2), "--foo=1 --bar=2") 105 106 flags_b = config["b"][0].flags 107 self.assertEqual(len(flags_b), 2) 108 self.assertEqual(str(flags_b), "--foo=2 --bar") 109 110 def test_parse_multi_dict_str_groups(self): 111 config = FlagsConfig.parse({ 112 "a": { 113 "label_a_1": "--foo=1 --bar=1", 114 "label_a_2": "--foo=1 --bar=2", 115 } 116 }) 117 self.assertEqual(len(config), 1) 118 self.assertEqual(len(config["a"]), 2) 119 120 self.assertTupleEqual( 121 tuple(v.label for v in config["a"]), ("label_a_1", "label_a_2")) 122 variants_a = config["a"] 123 flags_a_1 = variants_a[0].flags 124 flags_a_2 = variants_a[1].flags 125 self.assertEqual(str(flags_a_1), "--foo=1 --bar=1") 126 self.assertEqual(str(flags_a_2), "--foo=1 --bar=2") 127 128 def test_parse_multi_dict_dict_groups(self): 129 config = FlagsConfig.parse({ 130 "a": { 131 "label_a_1": { 132 "--foo": "1", 133 "--bar": "1" 134 }, 135 "label_a_2": { 136 "--bar": "2", 137 "--foo": "1", 138 } 139 } 140 }) 141 self.assertEqual(len(config), 1) 142 self.assertEqual(len(config["a"]), 2) 143 self.assertTupleEqual( 144 tuple(v.label for v in config["a"]), ("label_a_1", "label_a_2")) 145 variants_a = config["a"] 146 flags_a_1 = variants_a[0].flags 147 flags_a_2 = variants_a[1].flags 148 self.assertEqual(str(flags_a_1), "--foo=1 --bar=1") 149 self.assertEqual(str(flags_a_2), "--bar=2 --foo=1") 150 151 def test_parse_variants_groups(self): 152 config = FlagsConfig.parse( 153 {"a": { 154 "--foo": [None, "1"], 155 "--bar": ["1", "2"], 156 }}) 157 self.assertEqual(len(config), 1) 158 self.assertEqual(len(config["a"]), 4) 159 160 self.assertTupleEqual( 161 tuple(v.label for v in config["a"]), 162 ("bar=1", "bar=2", "foo=1_bar=1", "foo=1_bar=2")) 163 variants_a = config["a"] 164 self.assertEqual(str(variants_a[0].flags), "--bar=1") 165 self.assertEqual(str(variants_a[1].flags), "--bar=2") 166 self.assertEqual(str(variants_a[2].flags), "--foo=1 --bar=1") 167 self.assertEqual(str(variants_a[3].flags), "--foo=1 --bar=2") 168 169 170class FlagsVariantConfigTestCase(unittest.TestCase): 171 172 def test_empty(self): 173 empty = FlagsVariantConfig("default") 174 self.assertEqual(empty.label, "default") 175 self.assertFalse(empty.flags) 176 self.assertEqual(empty.index, 0) 177 178 def test_merge_copy(self): 179 flags_a = Flags.parse("--foo-a") 180 flags_b = Flags.parse("--bar-b=1") 181 variant_a = FlagsVariantConfig("label_a", 0, flags_a) 182 variant_b = FlagsVariantConfig("label_b", 1, flags_b) 183 variant = variant_a.merge_copy(variant_b) 184 self.assertEqual(variant.label, "label_a_label_b") 185 self.assertEqual(str(variant.flags), "--foo-a --bar-b=1") 186 self.assertEqual(variant.index, 0) 187 188 variant = variant_a.merge_copy(variant_b, index=11, label="custom_label") 189 self.assertEqual(variant.label, "custom_label") 190 self.assertEqual(str(variant.flags), "--foo-a --bar-b=1") 191 self.assertEqual(variant.index, 11) 192 193 def test_equal(self): 194 variant_a = FlagsVariantConfig.parse("label_a", 0, "--foo=a") 195 variant_b = FlagsVariantConfig.parse("label_b", 1, "--foo=a") 196 variant_c = FlagsVariantConfig.parse("label_b", 1, "--foo=b") 197 self.assertEqual(variant_a, variant_b) 198 self.assertEqual(variant_b, variant_a) 199 self.assertNotEqual(variant_a, variant_c) 200 self.assertNotEqual(variant_b, variant_c) 201 variants = set((variant_a,)) 202 self.assertIn(variant_a, variants) 203 self.assertIn(variant_b, variants) 204 self.assertNotIn(variant_c, variants) 205 206 207class FlagsGroupConfigTestCase(unittest.TestCase): 208 209 def test_parse_empty(self): 210 for empty in (None, [], (), {}, "", " "): 211 with self.subTest(flags=empty): 212 self.assertFalse(FlagsGroupConfig.parse(empty)) 213 214 def test_parse_invalid(self): 215 for invalid in (-1, 0, 1): 216 with self.subTest(invalid=invalid): 217 with self.assertRaises(ConfigError): 218 FlagsGroupConfig.parse(invalid) 219 220 def test_parse_str_single(self): 221 group = FlagsGroupConfig.parse("--foo-a=1") 222 self.assertEqual(len(group), 1) 223 self.assertEqual(str(group[0].flags), "--foo-a=1") 224 self.assertEqual(group[0].label, "default") 225 226 def test_parse_str_multiple(self): 227 group = FlagsGroupConfig.parse(("--foo-a=1 --bar", "--foo-a=2")) 228 self.assertEqual(len(group), 2) 229 self.assertEqual(str(group[0].flags), "--foo-a=1 --bar") 230 self.assertEqual(str(group[1].flags), "--foo-a=2") 231 232 def test_parse_str_multiple_empty(self): 233 group = FlagsGroupConfig.parse(("", "--foo", "-foo=v1")) 234 self.assertEqual(len(group), 3) 235 self.assertEqual(str(group[0].flags), "") 236 self.assertEqual(str(group[1].flags), "--foo") 237 self.assertEqual(str(group[2].flags), "-foo=v1") 238 239 def test_parse_dict_simple(self): 240 group = FlagsGroupConfig.parse({"--foo": "1", "--bar": "2"}) 241 self.assertEqual(len(group), 1) 242 self.assertEqual(str(group[0].flags), "--foo=1 --bar=2") 243 self.assertEqual(group[0].label, "default") 244 245 def test_parse_dict_invalid_variant(self): 246 for invalid in (-1, 0): 247 with self.subTest(invalid=invalid): 248 with self.assertRaises(ValueError): 249 FlagsGroupConfig.parse({ 250 "--foo": "1", 251 "--invalid": invalid, 252 "--bar": "2", 253 }) 254 255 def test_parse_duplicate_variant_value(self): 256 for duplicate in (None, "", "value"): 257 with self.subTest(duplicate=duplicate): 258 with self.assertRaises(ValueError) as cm: 259 FlagsGroupConfig.parse({"--duplicate": [duplicate, duplicate]}) 260 self.assertIn("duplicate", str(cm.exception)) 261 with self.assertRaises(ConfigError) as cm: 262 FlagsGroupConfig.parse( 263 ["--foo --duplicate='foo'", "--foo --duplicate='foo'"]) 264 self.assertIn("duplicate", str(cm.exception)) 265 266 def test_parse_dict_single_with_labels(self): 267 group = FlagsGroupConfig.parse({ 268 "config_1": "--foo=1 --bar", 269 "config_2": "", 270 }) 271 self.assertEqual(len(group), 2) 272 self.assertEqual(str(group[0].flags), "--foo=1 --bar") 273 self.assertEqual(str(group[1].flags), "") 274 self.assertEqual(group[0].label, "config_1") 275 self.assertEqual(group[1].label, "config_2") 276 for index, group in enumerate(group): 277 self.assertEqual(group.index, index) 278 279 def test_parse_dict_with_labels_duplicate_flags(self): 280 with self.assertRaises(argparse.ArgumentTypeError) as cm: 281 _ = FlagsGroupConfig.parse({ 282 "config_1": "--foo=1 --bar", 283 "config_2": "--foo=1 --bar", 284 }) 285 self.assertIn("duplicate", str(cm.exception).lower()) 286 self.assertIn("--foo=1 --bar", str(cm.exception).lower()) 287 288 def test_parse_dict_single(self): 289 group = FlagsGroupConfig.parse({ 290 "--foo": "1", 291 "--bar": None, 292 }) 293 self.assertEqual(len(group), 1) 294 self.assertEqual(str(group[0].flags), "--foo=1 --bar") 295 296 def test_parse_dict_multiple_3_x_1(self): 297 group = FlagsGroupConfig.parse({ 298 "--foo": [None, "1", "2"], 299 "--bar": None, 300 }) 301 self.assertEqual(len(group), 3) 302 self.assertEqual(str(group[0].flags), "--bar") 303 self.assertEqual(str(group[1].flags), "--foo=1 --bar") 304 self.assertEqual(str(group[2].flags), "--foo=2 --bar") 305 for index, group in enumerate(group): 306 self.assertEqual(group.index, index) 307 308 def test_parse_dict_multiple_2_x_2(self): 309 group = FlagsGroupConfig.parse({ 310 "--foo": [None, "a"], 311 "--bar": [None, "b"], 312 }) 313 self.assertEqual(len(group), 4) 314 self.assertEqual(str(group[0].flags), "") 315 self.assertEqual(str(group[1].flags), "--bar=b") 316 self.assertEqual(str(group[2].flags), "--foo=a") 317 self.assertEqual(str(group[3].flags), "--foo=a --bar=b") 318 self.assertEqual(group[0].label, "default") 319 self.assertEqual(group[1].label, "bar=b") 320 self.assertEqual(group[2].label, "foo=a") 321 self.assertEqual(group[3].label, "foo=a_bar=b") 322 for index, group in enumerate(group): 323 self.assertEqual(group.index, index) 324 325 def test_product_single(self): 326 group_a = FlagsGroupConfig.parse("--foo-a=1") 327 group_b = FlagsGroupConfig.parse("--foo-b=1") 328 self.assertEqual(group_a[0].label, "default") 329 self.assertEqual(group_b[0].label, "default") 330 group = group_a.product(group_b) 331 self.assertEqual(len(group), 1) 332 self.assertEqual(str(group[0].flags), "--foo-a=1 --foo-b=1") 333 self.assertEqual(group[0].label, "default") 334 335 def test_product_empty_empty(self): 336 group_a = FlagsGroupConfig() 337 group_b = FlagsGroupConfig() 338 group = group_a.product(group_b) 339 self.assertFalse(group) 340 group = group_a.product(group_b, group_b, group_b) 341 self.assertFalse(group) 342 343 def test_product_same(self): 344 group_a = FlagsGroupConfig.parse("--foo-b=1") 345 self.assertEqual(group_a[0].label, "default") 346 group = group_a.product(group_a) 347 self.assertEqual(len(group), 1) 348 self.assertEqual(str(group[0].flags), "--foo-b=1") 349 self.assertEqual(group[0].label, "default") 350 group = group_a.product(group_a, group_a, group_a) 351 self.assertEqual(len(group), 1) 352 self.assertEqual(str(group[0].flags), "--foo-b=1") 353 self.assertEqual(group[0].label, "default") 354 355 def test_product_same_values(self): 356 group_a = FlagsGroupConfig.parse("--foo-b=1") 357 group_b = FlagsGroupConfig.parse("--foo-b=1") 358 group = group_a.product(group_b) 359 self.assertEqual(len(group), 1) 360 self.assertEqual(str(group[0].flags), "--foo-b=1") 361 group = group_a.product(group_a, group_a, group_a) 362 self.assertEqual(len(group), 1) 363 self.assertEqual(str(group[0].flags), "--foo-b=1") 364 365 def test_product_empty(self): 366 group_a = FlagsGroupConfig.parse("") 367 group_b = FlagsGroupConfig.parse("--foo-b=1") 368 group = group_a.product(group_b) 369 self.assertEqual(len(group), 1) 370 self.assertEqual(str(group[0].flags), "--foo-b=1") 371 group = group_b.product(group_a) 372 self.assertEqual(len(group), 1) 373 self.assertEqual(str(group[0].flags), "--foo-b=1") 374 375 def test_product_2_x_1(self): 376 group_a = FlagsGroupConfig.parse(( 377 None, 378 "--foo-a=1", 379 )) 380 group_b = FlagsGroupConfig.parse("--foo-b=1") 381 group = group_a.product(group_b) 382 self.assertEqual(len(group), 2) 383 self.assertEqual(str(group[0].flags), "--foo-b=1") 384 self.assertEqual(str(group[1].flags), "--foo-a=1 --foo-b=1") 385 self.assertEqual(group[0].label, "default") 386 self.assertEqual(group[1].label, "foo_a=1") 387 388 def test_product_2_x_2(self): 389 group_a = FlagsGroupConfig.parse(( 390 None, 391 "--foo-a=1", 392 )) 393 group_b = FlagsGroupConfig.parse((None, "--foo-b=1")) 394 group = group_a.product(group_b) 395 self.assertEqual(len(group), 4) 396 self.assertEqual(str(group[0].flags), "") 397 self.assertEqual(str(group[1].flags), "--foo-b=1") 398 self.assertEqual(str(group[2].flags), "--foo-a=1") 399 self.assertEqual(str(group[3].flags), "--foo-a=1 --foo-b=1") 400 self.assertEqual(group[0].label, "default") 401 self.assertEqual(group[1].label, "foo_b=1") 402 self.assertEqual(group[2].label, "foo_a=1") 403 self.assertEqual(group[3].label, "foo_a=1_foo_b=1") 404 for index, group in enumerate(group): 405 self.assertEqual(group.index, index) 406 407 def test_product_conflicting(self): 408 group_a = FlagsGroupConfig.parse(("--foo=1")) 409 group_b = FlagsGroupConfig.parse(("--foo=2")) 410 with self.assertRaises(ValueError) as cm: 411 group_a.product(group_b) 412 self.assertIn("different previous value", str(cm.exception)) 413 414 415if __name__ == "__main__": 416 test_helper.run_pytest(__file__) 417