import os from copy import deepcopy from textwrap import dedent import mozunit import pytest from mach.util import get_state_dir from taskgraph.taskgraph import TaskGraph from tryselect.tasks import add_chunk_patterns @pytest.fixture(scope="module") def monkeymodule(): with pytest.MonkeyPatch.context() as mp: yield mp @pytest.fixture(scope="module", autouse=True) def setup_state_dir(monkeymodule, tmp_path_factory, run_mach): state_dir = tmp_path_factory.mktemp("mozbuild") machrc = state_dir / "machrc" machrc.write_text( dedent( """ [try] default=syntax """ ).lstrip() ) monkeymodule.setenv("EDITOR", "cat") monkeymodule.setenv("MOZBUILD_STATE_PATH", str(state_dir)) monkeymodule.setenv("MACH_TRY_PRESET_PATHS", str(state_dir / "try_presets.yml")) monkeymodule.setenv("MACHRC", machrc) get_state_dir(specific_to_topsrcdir=True) try: run_mach(["try", "--help"]) # create the virtualenv except SystemExit: pass @pytest.fixture def target_task_set(): return { "test/foo-opt": { "kind": "test", "label": "test/foo-opt", "attributes": {}, "task": {}, "optimization": {}, "dependencies": {}, }, "test/foo-debug": { "kind": "test", "label": "test/foo-debug", "attributes": {}, "task": {}, "optimization": {}, "dependencies": {}, }, "build-baz": { "kind": "build", "label": "build-baz", "attributes": {}, "task": {}, "optimization": {}, "dependencies": {}, }, } @pytest.fixture def full_task_set(target_task_set): full_task_set = deepcopy(target_task_set) full_task_set.update( { "test/bar-opt": { "kind": "test", "label": "test/bar-opt", "attributes": {}, "task": {}, "optimization": {}, "dependencies": {}, }, "test/bar-debug": { "kind": "test", "label": "test/bar-debug", "attributes": {}, "task": {}, "optimization": {}, "dependencies": {}, }, } ) return full_task_set @pytest.mark.skipif(os.name == "nt", reason="site validation fails in CI") @pytest.mark.parametrize( "selector,commands,expected", ( pytest.param( "fuzzy", ["try", "fuzzy", "-q", "'foo"], dedent( """ Commit message: Fuzzy query='foo mach try command: `./mach try fuzzy -q 'foo` Pushed via `mach try fuzzy` Calculated try_task_config.json: { "parameters": { "optimize_target_tasks": false, "try_task_config": { "env": { "TRY_SELECTOR": "fuzzy" }, "tasks": [ "test/foo-debug", "test/foo-opt" ] } }, "version": 2 } """ ).lstrip(), id="fuzzy", ), pytest.param( "fuzzy", ["try", "fuzzy", "-q", "'bar"], "no tasks selected\n", id="fuzzy", ), pytest.param( "fuzzy", ["try", "fuzzy", "--full", "-q", "'bar"], dedent( """ Commit message: Fuzzy query='bar mach try command: `./mach try fuzzy --full -q 'bar` Pushed via `mach try fuzzy` Calculated try_task_config.json: { "parameters": { "optimize_target_tasks": false, "try_task_config": { "env": { "TRY_SELECTOR": "fuzzy" }, "tasks": [ "test/bar-debug", "test/bar-opt" ] } }, "version": 2 } """ ).lstrip(), id="fuzzy", ), pytest.param( "fuzzy", ["try", "fuzzy", "--full", "-q", "'foo", "-q", "'bar"], dedent( """ Commit message: Fuzzy query='foo&query='bar mach try command: `./mach try fuzzy --full -q 'foo -q 'bar` Pushed via `mach try fuzzy` Calculated try_task_config.json: { "parameters": { "optimize_target_tasks": false, "try_task_config": { "env": { "TRY_SELECTOR": "fuzzy" }, "tasks": [ "test/bar-debug", "test/bar-opt", "test/foo-debug", "test/foo-opt" ] } }, "version": 2 } """ ).lstrip(), id="fuzzy multiple selectors", ), pytest.param( "fuzzy", ["try", "fuzzy", "--and", "-q", "'foo", "-q", "'opt"], dedent( """ Commit message: Fuzzy query='foo&query='opt mach try command: `./mach try fuzzy --and -q 'foo -q 'opt` Pushed via `mach try fuzzy` Calculated try_task_config.json: { "parameters": { "optimize_target_tasks": false, "try_task_config": { "env": { "TRY_SELECTOR": "fuzzy" }, "tasks": [ "test/foo-opt" ] } }, "version": 2 } """ ).lstrip(), id="fuzzy query intersection", ), pytest.param( "fuzzy", [ ["try", "fuzzy", "--save", "foo", "-q", "'test", "-q", "'opt"], ["try", "fuzzy", "--preset", "foo", "-xq", "'test"], ], dedent( """ preset saved, run with: --preset=foo Commit message: Fuzzy query='test&query='opt&query='test mach try command: `./mach try fuzzy --preset foo -xq 'test` Pushed via `mach try fuzzy` Calculated try_task_config.json: { "parameters": { "optimize_target_tasks": false, "try_task_config": { "env": { "TRY_SELECTOR": "fuzzy" }, "tasks": [ "test/foo-debug", "test/foo-opt" ] } }, "version": 2 } """ ).lstrip(), id="fuzzy intersection with preset containing multiple queries", ), pytest.param( "fuzzy", ["try", "fuzzy", "--full", "-q", "testfoo | 'testbar"], dedent( """ Commit message: Fuzzy query=testfoo | 'testbar mach try command: `./mach try fuzzy --full -q testfoo | 'testbar` Pushed via `mach try fuzzy` Calculated try_task_config.json: { "parameters": { "optimize_target_tasks": false, "try_task_config": { "env": { "TRY_SELECTOR": "fuzzy" }, "tasks": [ "test/foo-debug", "test/foo-opt" ] } }, "version": 2 } """ ).lstrip(), id="fuzzy exact match", ), pytest.param( "fuzzy", ["try", "fuzzy", "--full", "--exact", "-q", "testfoo | 'testbar"], dedent( """ Commit message: Fuzzy query=testfoo | 'testbar mach try command: `./mach try fuzzy --full --exact -q testfoo | 'testbar` Pushed via `mach try fuzzy` Calculated try_task_config.json: { "parameters": { "optimize_target_tasks": false, "try_task_config": { "env": { "TRY_SELECTOR": "fuzzy" }, "tasks": [ "test/bar-debug", "test/bar-opt" ] } }, "version": 2 } """ ).lstrip(), id="fuzzy exact match", ), pytest.param( "fuzzy", ["try", "fuzzy", "--env", "FOO=1", "--env", "BAR=baz", "-q", "'foo"], dedent( """ Commit message: Fuzzy query='foo mach try command: `./mach try fuzzy --env FOO=1 --env BAR=baz -q 'foo` Pushed via `mach try fuzzy` Calculated try_task_config.json: { "parameters": { "optimize_target_tasks": false, "try_task_config": { "env": { "BAR": "baz", "FOO": "1", "TRY_SELECTOR": "fuzzy" }, "tasks": [ "test/foo-debug", "test/foo-opt" ] } }, "version": 2 } """ ).lstrip(), id="fuzzy task config", ), pytest.param( "auto", ["try", "auto"], dedent( """ Commit message: Tasks automatically selected. mach try command: `./mach try auto` Pushed via `mach try auto` Calculated try_task_config.json: { "parameters": { "filters": [ "try_auto" ], "optimize_strategies": "gecko_taskgraph.optimize:tryselect.bugbug_reduced_manifests_config_selection_medium", "optimize_target_tasks": true, "test_manifest_loader": "bugbug", "try_mode": "try_auto", "try_task_config": {} }, "version": 2 } """ ).lstrip(), id="auto selector", ), pytest.param( "auto", ["try", "auto", "--closed-tree"], dedent( """ Commit message: Tasks automatically selected. ON A CLOSED TREE mach try command: `./mach try auto --closed-tree` Pushed via `mach try auto` Calculated try_task_config.json: { "parameters": { "filters": [ "try_auto" ], "optimize_strategies": "gecko_taskgraph.optimize:tryselect.bugbug_reduced_manifests_config_selection_medium", "optimize_target_tasks": true, "test_manifest_loader": "bugbug", "try_mode": "try_auto", "try_task_config": {} }, "version": 2 } """ ).lstrip(), id="auto", ), pytest.param( "auto", ["try", "auto", "--closed-tree", "-m", "foo {msg} bar"], dedent( """ Commit message: foo Tasks automatically selected. bar ON A CLOSED TREE mach try command: `./mach try auto --closed-tree -m foo {msg} bar` Pushed via `mach try auto` Calculated try_task_config.json: { "parameters": { "filters": [ "try_auto" ], "optimize_strategies": "gecko_taskgraph.optimize:tryselect.bugbug_reduced_manifests_config_selection_medium", "optimize_target_tasks": true, "test_manifest_loader": "bugbug", "try_mode": "try_auto", "try_task_config": {} }, "version": 2 } """ ).lstrip(), id="auto", ), pytest.param( "empty", ["try", "empty"], dedent( """ Commit message: No try selector specified, use "Add New Jobs" to select tasks. mach try command: `./mach try empty` Pushed via `mach try empty` Calculated try_task_config.json: { "parameters": { "optimize_target_tasks": false, "try_task_config": { "env": { "TRY_SELECTOR": "empty" }, "tasks": [] } }, "version": 2 } """ ).lstrip(), id="empty", ), pytest.param( "empty", ["try", "empty", "--closed-tree"], dedent( """ Commit message: No try selector specified, use "Add New Jobs" to select tasks. ON A CLOSED TREE mach try command: `./mach try empty --closed-tree` Pushed via `mach try empty` Calculated try_task_config.json: { "parameters": { "optimize_target_tasks": false, "try_task_config": { "env": { "TRY_SELECTOR": "empty" }, "tasks": [] } }, "version": 2 } """ ).lstrip(), id="empty", ), pytest.param( "empty", ["try", "empty", "--closed-tree", "-m", "foo {msg} bar"], dedent( """ Commit message: foo No try selector specified, use "Add New Jobs" to select tasks. bar ON A CLOSED TREE mach try command: `./mach try empty --closed-tree -m foo {msg} bar` Pushed via `mach try empty` Calculated try_task_config.json: { "parameters": { "optimize_target_tasks": false, "try_task_config": { "env": { "TRY_SELECTOR": "empty" }, "tasks": [] } }, "version": 2 } """ ).lstrip(), id="empty", ), pytest.param( "fuzzy", ["try", "fuzzy", "-q", "foo", "--message", "Foobar"], dedent( """ Commit message: Foobar Fuzzy query=foo mach try command: `./mach try fuzzy -q foo --message Foobar` Pushed via `mach try fuzzy` Calculated try_task_config.json: { "parameters": { "optimize_target_tasks": false, "try_task_config": { "env": { "TRY_SELECTOR": "fuzzy" }, "tasks": [ "test/foo-debug", "test/foo-opt" ] } }, "version": 2 } """ ).lstrip(), id="message fuzzy", ), pytest.param( "fuzzy", ["try", "fuzzy", "-q", "foo", "-m", "Foobar: {msg}"], dedent( """ Commit message: Foobar: Fuzzy query=foo mach try command: `./mach try fuzzy -q foo -m Foobar: {msg}` Pushed via `mach try fuzzy` Calculated try_task_config.json: { "parameters": { "optimize_target_tasks": false, "try_task_config": { "env": { "TRY_SELECTOR": "fuzzy" }, "tasks": [ "test/foo-debug", "test/foo-opt" ] } }, "version": 2 } """ ).lstrip(), id="message fuzzy", ), pytest.param( "syntax", ["try", "syntax", "-p", "linux", "-u", "mochitests", "--message", "Foobar"], dedent( """ Commit message: Foobar try: -b do -p linux -u mochitests mach try command: `./mach try syntax -p linux -u mochitests --message Foobar` Pushed via `mach try syntax` """ ).lstrip(), id="message syntax", ), pytest.param( "syntax", ["try", "syntax", "-p", "linux", "-u", "mochitests", "-m", "Foobar: {msg}"], dedent( """ Commit message: Foobar: try: -b do -p linux -u mochitests mach try command: `./mach try syntax -p linux -u mochitests -m Foobar: {msg}` Pushed via `mach try syntax` """ ).lstrip(), id="message syntax", ), pytest.param( "syntax", [ [ "try", "--save", "foo", "-b", "do", "-p", "linux", "-u", "mochitests", "-t", "none", "--tag", "foo", ], ["try", "--preset", "foo"], ["try", "syntax", "--preset", "foo"], ["try", "--edit-presets"], ], dedent( """ preset saved, run with: --preset=foo Commit message: try: -b do -p linux -u mochitests -t none --tag foo mach try command: `./mach try --preset foo` Pushed via `mach try syntax` Commit message: try: -b do -p linux -u mochitests -t none --tag foo mach try command: `./mach try syntax --preset foo` Pushed via `mach try syntax` foo: dry_run: true platforms: - linux selector: syntax tags: - foo talos: - none tests: - mochitests """ ).lstrip(), id="preset with no subcommand", ), pytest.param( "syntax", [ [ "try", "syntax", "--save", "foo", "-b", "do", "-p", "linux", "-u", "mochitests", "-t", "none", "--tag", "foo", ], ["try", "--preset", "foo"], ["try", "syntax", "--preset", "foo"], ["try", "--edit-presets"], ], dedent( """ preset saved, run with: --preset=foo Commit message: try: -b do -p linux -u mochitests -t none --tag foo mach try command: `./mach try --preset foo` Pushed via `mach try syntax` Commit message: try: -b do -p linux -u mochitests -t none --tag foo mach try command: `./mach try syntax --preset foo` Pushed via `mach try syntax` foo: dry_run: true no_artifact: true platforms: - linux selector: syntax tags: - foo talos: - none tests: - mochitests """ ).lstrip(), id="preset with syntax subcommand", ), pytest.param( "fuzzy", [ ["try", "fuzzy", "--save", "foo", "-q", "'foo", "--rebuild", "5"], ["try", "fuzzy", "--preset", "foo"], ["try", "--preset", "foo"], ["try", "--edit-presets"], ], dedent( """ preset saved, run with: --preset=foo Commit message: Fuzzy query='foo mach try command: `./mach try fuzzy --preset foo` Pushed via `mach try fuzzy` Calculated try_task_config.json: { "parameters": { "optimize_target_tasks": false, "try_task_config": { "env": { "TRY_SELECTOR": "fuzzy" }, "rebuild": 5, "tasks": [ "test/foo-debug", "test/foo-opt" ] } }, "version": 2 } Commit message: Fuzzy query='foo mach try command: `./mach try --preset foo` Pushed via `mach try fuzzy` Calculated try_task_config.json: { "parameters": { "optimize_target_tasks": false, "try_task_config": { "env": { "TRY_SELECTOR": "fuzzy" }, "rebuild": 5, "tasks": [ "test/foo-debug", "test/foo-opt" ] } }, "version": 2 } foo: dry_run: true no_artifact: true query: - "'foo" rebuild: 5 selector: fuzzy """ ).lstrip(), id="preset with fuzzy subcommand", ), pytest.param( "fuzzy", [ ["try", "fuzzy", "--save", "foo", "-q", "'foo", "--rebuild", "5"], ["try", "fuzzy", "--preset", "foo", "-q" "'build"], ["try", "fuzzy", "--preset", "foo", "-xq" "'opt"], ], dedent( """ preset saved, run with: --preset=foo Commit message: Fuzzy query='foo&query='build mach try command: `./mach try fuzzy --preset foo -q'build` Pushed via `mach try fuzzy` Calculated try_task_config.json: { "parameters": { "optimize_target_tasks": false, "try_task_config": { "env": { "TRY_SELECTOR": "fuzzy" }, "rebuild": 5, "tasks": [ "build-baz", "test/foo-debug", "test/foo-opt" ] } }, "version": 2 } Commit message: Fuzzy query='foo&query='opt mach try command: `./mach try fuzzy --preset foo -xq'opt` Pushed via `mach try fuzzy` Calculated try_task_config.json: { "parameters": { "optimize_target_tasks": false, "try_task_config": { "env": { "TRY_SELECTOR": "fuzzy" }, "rebuild": 5, "tasks": [ "test/foo-opt" ] } }, "version": 2 } """ ).lstrip(), id="preset queries can be appended", ), pytest.param( "fuzzy", [ ["try", "fuzzy", "--save", "foo", "-q", "'foo", "--rebuild", "5"], [ "try", "fuzzy", "--preset", "foo", "--gecko-profile-features=nostacksampling,cpu", ], ], dedent( """ preset saved, run with: --preset=foo Commit message: Fuzzy query='foo mach try command: `./mach try fuzzy --preset foo --gecko-profile-features=nostacksampling,cpu` Pushed via `mach try fuzzy` Calculated try_task_config.json: { "parameters": { "optimize_target_tasks": false, "try_task_config": { "env": { "TRY_SELECTOR": "fuzzy" }, "gecko-profile": true, "gecko-profile-features": "nostacksampling,cpu", "rebuild": 5, "tasks": [ "test/foo-debug", "test/foo-opt" ] } }, "version": 2 } """ ).lstrip(), id="gecko-profile handling", ), pytest.param( "fuzzy", [ [ "try", "fuzzy", "--save", "foo", "-q", "'foo", "--rebuild", "5", "--gecko-profile-features=nostacksampling,cpu", ], ["try", "fuzzy", "--preset", "foo"], ["try", "fuzzy", "--edit-presets", "foo"], ], dedent( """ preset saved, run with: --preset=foo Commit message: Fuzzy query='foo mach try command: `./mach try fuzzy --preset foo` Pushed via `mach try fuzzy` Calculated try_task_config.json: { "parameters": { "optimize_target_tasks": false, "try_task_config": { "env": { "TRY_SELECTOR": "fuzzy" }, "gecko-profile": true, "gecko-profile-features": "nostacksampling,cpu", "rebuild": 5, "tasks": [ "test/foo-debug", "test/foo-opt" ] } }, "version": 2 } foo: dry_run: true gecko_profile_features: nostacksampling,cpu no_artifact: true query: - "'foo" rebuild: 5 selector: fuzzy """ ).lstrip(), id="gecko-profile handling", ), ), ) def test_run_mach( capfd, mocker, run_mach, target_task_set, full_task_set, selector, commands, expected, ): """These tests were initially converted from the `cramtest` framework. It's likely there is duplication of test coverage between here and the specific selector tests.""" mocker.patch("tryselect.push.display_push_estimates") capfd.readouterr() if isinstance(commands[0], str): commands = [commands] for cmd in commands: m = mocker.patch("tryselect.push.get_sys_argv") m.return_value = f"./mach {' '.join(cmd)}" cmd.append("--no-push") if selector in cmd: cmd.append("--no-artifact") if selector == "fuzzy": def fake_generate_tasks(*args, **kwargs): task_set = target_task_set if "--full" in cmd: task_set = full_task_set return add_chunk_patterns(TaskGraph.from_json(task_set)[1]) m = mocker.patch(f"tryselect.selectors.{selector}.generate_tasks") m.side_effect = fake_generate_tasks try: cmd.insert(0, f"--settings={os.environ['MACHRC']}") run_mach(cmd) except SystemExit: pass out, _ = capfd.readouterr() assert out == expected if __name__ == "__main__": mozunit.main()