| # Copyright 2023 The Fuchsia Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import argparse |
| import os |
| import tempfile |
| import unittest |
| import unittest.mock as mock |
| |
| from parameterized import parameterized |
| |
| import args |
| import config |
| |
| # We need a place to create a temporary file that is used to ensure |
| # output directory checking works correctly. Place it here and then |
| # cleanup after the tests are done executing. |
| GLOBAL_TEMP_DIRECTORY = tempfile.TemporaryDirectory() |
| GLOBAL_FILE_NAME = os.path.join(GLOBAL_TEMP_DIRECTORY.name, "tempfile") |
| open(GLOBAL_FILE_NAME, "w").close() |
| |
| |
| class TestArgs(unittest.TestCase): |
| @classmethod |
| def tearDownClass(cls) -> None: |
| GLOBAL_TEMP_DIRECTORY.cleanup() |
| return super().tearDownClass() |
| |
| def test_empty(self) -> None: |
| flags = args.parse_args([]) |
| flags.validate() |
| |
| @parameterized.expand( |
| [ |
| ( |
| "cannot show status with --simple", |
| ["--simple", "--status"], |
| ), |
| ( |
| "cannot show style with --simple", |
| ["--simple", "--style"], |
| ), |
| ( |
| "cannot show status when terminal is not a TTY", |
| ["--status"], |
| ), |
| ( |
| "cannot run only host and only device tests", |
| ["--device", "--host"], |
| ), |
| ( |
| "cannot exceed maximum status frequency", |
| ["--status-delay", ".00001"], |
| ), |
| ( |
| "cannot have a negative suggestion count", |
| ["--suggestion-count", "-1"], |
| ), |
| ( |
| "cannot run tests 0 times", |
| ["--count", "0"], |
| ), |
| ( |
| "cannot have a negative timeout", |
| ["--timeout", "-3"], |
| ), |
| ( |
| "cannot output to a file", |
| ["--ffx-output-directory", GLOBAL_FILE_NAME], |
| ), |
| ( |
| "cannot output to a file (artifact flag)", |
| ["--artifact-output-directory", GLOBAL_FILE_NAME], |
| ), |
| ( |
| "cannot output to a file (outdir flag)", |
| ["--outdir", GLOBAL_FILE_NAME], |
| ), |
| ( |
| "cannot set a negative --parallel", |
| ["--parallel", "-1"], |
| ), |
| ( |
| "cannot set a negative --parallel-cases", |
| ["--parallel-cases", "-1"], |
| ), |
| ( |
| "invalid environment variable formatting is checked", |
| ["-e", "abcd"], |
| ), |
| ( |
| "--break-on-failure and --breakpoint flags are not supported with host tests.", |
| ["--break-on-failure", "--host"], |
| ), |
| ( |
| "--break-on-failure and --breakpoint flags are not supported with host tests.", |
| ["--breakpoint", "test.cc:123", "--host"], |
| ), |
| ( |
| "--breakpoint does not support --use-existing-debugger.", |
| ["--breakpoint", "test.cc:123", "--use-existing-debugger"], |
| ), |
| ( |
| "--break-on-failure must be set when passing --use-existing-debugger.", |
| ["--use-existing-debugger"], |
| ), |
| ] |
| ) |
| @mock.patch("args.termout.is_valid", return_value=False) |
| def test_validation_errors( |
| self, _unused_name: str, arg_list: list[str], _mock: mock.Mock |
| ) -> None: |
| flags = args.parse_args(arg_list) |
| try: |
| with self.assertRaises(args.FlagError): |
| flags.validate() |
| except AssertionError: |
| raise AssertionError("Expected FlagError from " + str(arg_list)) |
| |
| def test_gemini_analysis(self) -> None: |
| # test default behavior (no flag) |
| flags = args.parse_args([]) |
| flags.validate() |
| self.assertEqual(flags.gemini_analysis, None) |
| |
| # test flag without a value (should default to 1) |
| flags = args.parse_args(["--gemini-analysis"]) |
| flags.validate() |
| self.assertEqual(flags.gemini_analysis, 1) |
| |
| # test explicit values |
| for i in range(1, 4): |
| flags = args.parse_args([f"--gemini-analysis={i}"]) |
| flags.validate() |
| self.assertEqual(flags.gemini_analysis, i) |
| |
| # test invalid value |
| with self.assertRaises(argparse.ArgumentError): |
| args.parse_args(["--gemini-analysis=0"]) |
| |
| # test invalid value |
| with self.assertRaises(argparse.ArgumentError): |
| args.parse_args(["--gemini-analysis=5"]) |
| |
| def test_gemini_model_arg(self) -> None: |
| # test default behavior (no flag) |
| flags = args.parse_args([]) |
| flags.validate() |
| self.assertEqual( |
| flags.gemini_model, "gemini-2.5-flash-lite-preview-09-2025" |
| ) |
| |
| # test explicit value |
| flags = args.parse_args(["--gemini-model", "test-model"]) |
| flags.validate() |
| self.assertEqual(flags.gemini_model, "test-model") |
| |
| def test_simple(self) -> None: |
| flags = args.parse_args(["--simple"]) |
| flags.validate() |
| self.assertEqual(flags.style, False) |
| self.assertEqual(flags.status, False) |
| |
| def test_e2e(self) -> None: |
| flags = args.parse_args(["--only-e2e"]) |
| flags.validate() |
| self.assertEqual(flags.e2e, True) |
| self.assertEqual(flags.only_e2e, True) |
| |
| def test_use_test_pilot(self) -> None: |
| flags = args.parse_args(["--use-test-pilot"]) |
| flags.validate() |
| self.assertEqual(flags.use_test_pilot, True) |
| |
| flags = args.parse_args([]) |
| flags.validate() |
| self.assertEqual(flags.use_test_pilot, False) |
| |
| def test_expand_output_variable(self) -> None: |
| flags = args.parse_args(["--outdir", "$FUCHSIA_OUT/test_out"]) |
| flags.validate() |
| flags.update_artifacts_directory_with_out_path("out/default") |
| self.assertEqual( |
| flags.artifact_output_directory, "out/default/test_out" |
| ) |
| |
| flags = args.parse_args(["--outdir", "${FUCHSIA_OUT}/test_out2"]) |
| flags.validate() |
| flags.update_artifacts_directory_with_out_path("out/default2") |
| self.assertEqual( |
| flags.artifact_output_directory, "out/default2/test_out2" |
| ) |
| |
| def test_default_merging(self) -> None: |
| config_file = config.ConfigFile( |
| "path", args.parse_args(["--parallel=10"]) |
| ) |
| flags = args.parse_args([], config_file.default_flags) |
| self.assertEqual(flags.parallel, 10) |
| flags = args.parse_args(["--parallel=1"], config_file.default_flags) |
| self.assertEqual(flags.parallel, 1) |
| |
| def test_selections_after_test_filter(self) -> None: |
| """Passing more selections after a --test-filter works""" |
| flags = args.parse_args(["foo", "--test-filter", "some*", "bar"]) |
| flags.validate() |
| self.assertListEqual(flags.test_filter, ["some*"]) |
| self.assertListEqual(flags.selection, ["foo", "bar"]) |
| |
| flags = args.parse_args( |
| [ |
| "foo", |
| "--test-filter", |
| "some*", |
| "bar", |
| "-a", |
| "baz", |
| "alpha", |
| "--test-filter", |
| "something*", |
| "beta", |
| "-c", |
| "gamma.cm", |
| ] |
| ) |
| flags.validate() |
| self.assertListEqual(flags.test_filter, ["some*", "something*"]) |
| self.assertListEqual( |
| flags.selection, |
| [ |
| "foo", |
| "bar", |
| "--and", |
| "baz", |
| "alpha", |
| "beta", |
| "--component", |
| "gamma.cm", |
| ], |
| ) |
| |
| def test_exact_after_selections(self) -> None: |
| """Passing --exact after a selection works""" |
| flags = args.parse_args(["-p", "foo", "-a", "-c", "bar", "--exact"]) |
| flags.validate() |
| self.assertEqual(flags.exact, True) |
| self.assertListEqual( |
| flags.selection, |
| ["--package", "foo", "--and", "--component", "bar"], |
| ) |
| |
| @parameterized.expand( |
| [ |
| ("default is None", [], [], None), |
| ("config file overrides output", [], ["--output"], True), |
| ("-o shows output", ["-o"], [], True), |
| ("--output shows output", ["--output"], [], True), |
| ("--no-output hides output", ["--no-output"], [], False), |
| ( |
| "--no-output overrides config", |
| ["--no-output"], |
| ["--output"], |
| False, |
| ), |
| ] |
| ) |
| def test_output_toggle( |
| self, |
| _unused_name: str, |
| arguments: list[str], |
| config_arguments: list[str], |
| expected_value: bool, |
| ) -> None: |
| config_file = config.ConfigFile( |
| "path", args.parse_args(config_arguments) |
| ) |
| flags = args.parse_args(arguments, config_file.default_flags) |
| self.assertEqual(flags.output, expected_value) |
| |
| @parameterized.expand( |
| [ |
| ("<no arguments>", [], False, False), |
| ("--break-on-failure", ["--break-on-failure"], True, True), |
| ("--breakpoint", ["--breakpoint", "test.cc:123"], True, True), |
| ( |
| "--break-on-failure --use-existing-debugger", |
| ["--break-on-failure", "--use-existing-debugger"], |
| True, |
| False, |
| ), |
| ( |
| "--breakpoint --break-on-failure", |
| ["--break-on-failure", "--breakpoint", "test.cc:123"], |
| True, |
| True, |
| ), |
| ] |
| ) |
| def test_should_start_debugger( |
| self, |
| _unused_name: str, |
| arguments: list[str], |
| debugger_will_attach: bool, |
| debugger_should_spawn: bool, |
| ) -> None: |
| """Passing --break-on-failure or --breakpoint does not set debugger_should_spawn().""" |
| flags = args.parse_args(arguments) |
| flags.validate() |
| self.assertEqual(flags.debugger_will_attach(), debugger_will_attach) |
| self.assertEqual(flags.debugger_should_spawn(), debugger_should_spawn) |