summaryrefslogtreecommitdiffstats
path: root/tools/tryselect/test
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
commit2aa4a82499d4becd2284cdb482213d541b8804dd (patch)
treeb80bf8bf13c3766139fbacc530efd0dd9d54394c /tools/tryselect/test
parentInitial commit. (diff)
downloadfirefox-upstream.tar.xz
firefox-upstream.zip
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'tools/tryselect/test')
-rw-r--r--tools/tryselect/test/conftest.py104
-rw-r--r--tools/tryselect/test/cram.ini5
-rw-r--r--tools/tryselect/test/python.ini15
-rw-r--r--tools/tryselect/test/setup.sh99
-rw-r--r--tools/tryselect/test/test_again.py77
-rw-r--r--tools/tryselect/test/test_auto.py34
-rw-r--r--tools/tryselect/test/test_auto.t61
-rw-r--r--tools/tryselect/test/test_chooser.py81
-rw-r--r--tools/tryselect/test/test_empty.t47
-rw-r--r--tools/tryselect/test/test_fuzzy.py56
-rw-r--r--tools/tryselect/test/test_fuzzy.t200
-rw-r--r--tools/tryselect/test/test_message.t63
-rw-r--r--tools/tryselect/test/test_mozharness_integration.py145
-rw-r--r--tools/tryselect/test/test_preset.t271
-rw-r--r--tools/tryselect/test/test_presets.py61
-rw-r--r--tools/tryselect/test/test_task_configs.py148
-rw-r--r--tools/tryselect/test/test_tasks.py59
17 files changed, 1526 insertions, 0 deletions
diff --git a/tools/tryselect/test/conftest.py b/tools/tryselect/test/conftest.py
new file mode 100644
index 0000000000..a2c697d8fb
--- /dev/null
+++ b/tools/tryselect/test/conftest.py
@@ -0,0 +1,104 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+from __future__ import absolute_import, print_function, unicode_literals
+
+import os
+
+import pytest
+import yaml
+from mock import MagicMock
+from moztest.resolve import TestResolver
+from taskgraph.graph import Graph
+from taskgraph.task import Task
+from taskgraph.taskgraph import TaskGraph
+
+from tryselect import push
+
+
+@pytest.fixture
+def tg(request):
+ if not hasattr(request.module, "TASKS"):
+ pytest.fail(
+ "'tg' fixture used from a module that didn't define the TASKS variable"
+ )
+
+ tasks = request.module.TASKS
+ for task in tasks:
+ task.setdefault("task", {})
+ task["task"].setdefault("tags", {})
+
+ tasks = {t["label"]: Task(**t) for t in tasks}
+ return TaskGraph(tasks, Graph(tasks.keys(), set()))
+
+
+@pytest.fixture
+def patch_resolver(monkeypatch):
+ def inner(suites, tests):
+ def fake_test_metadata(*args, **kwargs):
+ return suites, tests
+
+ monkeypatch.setattr(TestResolver, "resolve_metadata", fake_test_metadata)
+
+ return inner
+
+
+@pytest.fixture(autouse=True)
+def patch_vcs(monkeypatch):
+ attrs = {
+ "path": push.vcs.path,
+ }
+ mock = MagicMock()
+ mock.configure_mock(**attrs)
+ monkeypatch.setattr(push, "vcs", mock)
+
+
+@pytest.fixture(scope="session")
+def run_mach():
+ import mach_bootstrap
+ from mach.config import ConfigSettings
+ from tryselect.tasks import build
+
+ mach = mach_bootstrap.bootstrap(build.topsrcdir)
+
+ def inner(args):
+ mach.settings = ConfigSettings()
+ return mach.run(args)
+
+ return inner
+
+
+def pytest_generate_tests(metafunc):
+ if all(
+ fixture in metafunc.fixturenames
+ for fixture in ("task_config", "args", "expected")
+ ):
+
+ def load_tests():
+ for task_config, tests in metafunc.module.TASK_CONFIG_TESTS.items():
+ for args, expected in tests:
+ yield (task_config, args, expected)
+
+ tests = list(load_tests())
+ ids = ["{} {}".format(t[0], " ".join(t[1])).strip() for t in tests]
+ metafunc.parametrize("task_config,args,expected", tests, ids=ids)
+
+ elif all(
+ fixture in metafunc.fixturenames for fixture in ("shared_name", "shared_preset")
+ ):
+ preset_path = os.path.join(
+ push.build.topsrcdir, "tools", "tryselect", "try_presets.yml"
+ )
+ with open(preset_path, "r") as fh:
+ presets = list(yaml.safe_load(fh).items())
+
+ ids = [p[0] for p in presets]
+
+ # Mark fuzzy presets on Windows xfail due to fzf not being installed.
+ if os.name == "nt":
+ for i, preset in enumerate(presets):
+ if preset[1]["selector"] == "fuzzy":
+ presets[i] = pytest.param(*preset, marks=pytest.mark.xfail)
+
+ metafunc.parametrize("shared_name,shared_preset", presets, ids=ids)
diff --git a/tools/tryselect/test/cram.ini b/tools/tryselect/test/cram.ini
new file mode 100644
index 0000000000..b1f0d8dfec
--- /dev/null
+++ b/tools/tryselect/test/cram.ini
@@ -0,0 +1,5 @@
+[test_auto.t]
+[test_empty.t]
+[test_fuzzy.t]
+[test_message.t]
+[test_preset.t]
diff --git a/tools/tryselect/test/python.ini b/tools/tryselect/test/python.ini
new file mode 100644
index 0000000000..c56a340af3
--- /dev/null
+++ b/tools/tryselect/test/python.ini
@@ -0,0 +1,15 @@
+[DEFAULT]
+subsuite=try
+
+[test_again.py]
+[test_auto.py]
+[test_chooser.py]
+requirements = tools/tryselect/selectors/chooser/requirements.txt
+[test_fuzzy.py]
+[test_mozharness_integration.py]
+[test_presets.py]
+# Modifies "task_duration_history.json" in .mozbuild. Since other tests depend on this file, this test
+# shouldn't be run in parallel with those other tests.
+sequential = true
+[test_tasks.py]
+[test_task_configs.py]
diff --git a/tools/tryselect/test/setup.sh b/tools/tryselect/test/setup.sh
new file mode 100644
index 0000000000..70fc456de3
--- /dev/null
+++ b/tools/tryselect/test/setup.sh
@@ -0,0 +1,99 @@
+export topsrcdir=$TESTDIR/../../../
+export MOZBUILD_STATE_PATH=$TMP/mozbuild
+export MACH_TRY_PRESET_PATHS=$MOZBUILD_STATE_PATH/try_presets.yml
+
+# This helps to find fzf when running these tests locally, since normally fzf
+# would be found via MOZBUILD_STATE_PATH pointing to $HOME/.mozbuild
+export PATH=$PATH:$HOME/.mozbuild/fzf/bin
+
+export MACHRC=$TMP/machrc
+cat > $MACHRC << EOF
+[try]
+default=syntax
+EOF
+
+cmd="$topsrcdir/mach python -c 'from mozboot.util import get_state_dir; print(get_state_dir(srcdir=True))'"
+# First run local state dir generation so it doesn't affect test output.
+eval $cmd > /dev/null 2>&1
+# Now run it again to get the actual directory.
+cachedir=$(eval $cmd)/cache/taskgraph
+mkdir -p $cachedir
+
+cat > $cachedir/target_task_set << EOF
+{
+ "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": {}
+ }
+}
+EOF
+
+cat > $cachedir/full_task_set << EOF
+{
+ "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": {}
+ },
+ "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": {}
+ },
+ "build-baz": {
+ "kind": "build",
+ "label": "build-baz",
+ "attributes": {},
+ "task": {},
+ "optimization": {},
+ "dependencies": {}
+ }
+}
+EOF
+
+# set mtime to the future so we don't re-generate tasks
+find $cachedir -type f -exec touch -d "next day" {} +
+
+export testargs="--no-push --no-artifact"
diff --git a/tools/tryselect/test/test_again.py b/tools/tryselect/test/test_again.py
new file mode 100644
index 0000000000..8f57eed58f
--- /dev/null
+++ b/tools/tryselect/test/test_again.py
@@ -0,0 +1,77 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+from __future__ import absolute_import, print_function, unicode_literals
+
+import os
+
+import mozunit
+import pytest
+
+from six.moves import reload_module as reload
+
+from tryselect import push
+from tryselect.selectors import again
+
+
+@pytest.fixture(autouse=True)
+def patch_history_path(tmpdir, monkeypatch):
+ monkeypatch.setattr(push, "history_path", tmpdir.join("history.json").strpath)
+ reload(again)
+
+
+def test_try_again(monkeypatch):
+ push.push_to_try(
+ "fuzzy",
+ "Fuzzy message",
+ try_task_config=push.generate_try_task_config(
+ "fuzzy",
+ ["foo", "bar"],
+ {"use-artifact-builds": True},
+ ),
+ )
+
+ assert os.path.isfile(push.history_path)
+ with open(push.history_path, "r") as fh:
+ assert len(fh.readlines()) == 1
+
+ def fake_push_to_try(*args, **kwargs):
+ return args, kwargs
+
+ monkeypatch.setattr(push, "push_to_try", fake_push_to_try)
+ reload(again)
+
+ args, kwargs = again.run()
+
+ assert args[0] == "again"
+ assert args[1] == "Fuzzy message"
+
+ try_task_config = kwargs.pop("try_task_config")
+ assert sorted(try_task_config.get("tasks")) == sorted(["foo", "bar"])
+ assert try_task_config.get("env") == {"TRY_SELECTOR": "fuzzy"}
+ assert try_task_config.get("use-artifact-builds")
+
+ with open(push.history_path, "r") as fh:
+ assert len(fh.readlines()) == 1
+
+
+def test_no_push_does_not_generate_history(tmpdir):
+ assert not os.path.isfile(push.history_path)
+
+ push.push_to_try(
+ "fuzzy",
+ "Fuzzy",
+ try_task_config=push.generate_try_task_config(
+ "fuzzy",
+ ["foo", "bar"],
+ {"use-artifact-builds": True},
+ ),
+ push=False,
+ )
+ assert not os.path.isfile(push.history_path)
+ assert again.run() == 1
+
+
+if __name__ == "__main__":
+ mozunit.main()
diff --git a/tools/tryselect/test/test_auto.py b/tools/tryselect/test/test_auto.py
new file mode 100644
index 0000000000..b21ad9ae8d
--- /dev/null
+++ b/tools/tryselect/test/test_auto.py
@@ -0,0 +1,34 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+from __future__ import absolute_import, print_function, unicode_literals
+
+import mozunit
+import pytest
+
+from tryselect.selectors.auto import AutoParser
+
+
+def test_strategy_validation():
+ parser = AutoParser()
+ args = parser.parse_args(["--strategy", "relevant_tests"])
+ assert args.strategy == "taskgraph.optimize:tryselect.relevant_tests"
+
+ args = parser.parse_args(
+ ["--strategy", "taskgraph.optimize:experimental.relevant_tests"]
+ )
+ assert args.strategy == "taskgraph.optimize:experimental.relevant_tests"
+
+ with pytest.raises(SystemExit):
+ parser.parse_args(["--strategy", "taskgraph.optimize:tryselect"])
+
+ with pytest.raises(SystemExit):
+ parser.parse_args(["--strategy", "foo"])
+
+ with pytest.raises(SystemExit):
+ parser.parse_args(["--strategy", "foo:bar"])
+
+
+if __name__ == "__main__":
+ mozunit.main()
diff --git a/tools/tryselect/test/test_auto.t b/tools/tryselect/test/test_auto.t
new file mode 100644
index 0000000000..8565a08afa
--- /dev/null
+++ b/tools/tryselect/test/test_auto.t
@@ -0,0 +1,61 @@
+
+ $ . $TESTDIR/setup.sh
+ $ cd $topsrcdir
+
+Test auto selector
+
+ $ ./mach try auto $testargs
+ Commit message:
+ Tasks automatically selected.
+
+ Pushed via `mach try auto`
+ Calculated try_task_config.json:
+ {
+ "parameters": {
+ "optimize_strategies": "taskgraph.optimize:tryselect.bugbug_debug_disperse",
+ "optimize_target_tasks": true,
+ "target_tasks_method": "try_auto",
+ "test_manifest_loader": "bugbug",
+ "try_mode": "try_auto",
+ "try_task_config": {}
+ },
+ "version": 2
+ }
+
+
+ $ ./mach try auto $testargs --closed-tree
+ Commit message:
+ Tasks automatically selected. ON A CLOSED TREE
+
+ Pushed via `mach try auto`
+ Calculated try_task_config.json:
+ {
+ "parameters": {
+ "optimize_strategies": "taskgraph.optimize:tryselect.bugbug_debug_disperse",
+ "optimize_target_tasks": true,
+ "target_tasks_method": "try_auto",
+ "test_manifest_loader": "bugbug",
+ "try_mode": "try_auto",
+ "try_task_config": {}
+ },
+ "version": 2
+ }
+
+ $ ./mach try auto $testargs --closed-tree -m "foo {msg} bar"
+ Commit message:
+ foo Tasks automatically selected. bar ON A CLOSED TREE
+
+ Pushed via `mach try auto`
+ Calculated try_task_config.json:
+ {
+ "parameters": {
+ "optimize_strategies": "taskgraph.optimize:tryselect.bugbug_debug_disperse",
+ "optimize_target_tasks": true,
+ "target_tasks_method": "try_auto",
+ "test_manifest_loader": "bugbug",
+ "try_mode": "try_auto",
+ "try_task_config": {}
+ },
+ "version": 2
+ }
+
diff --git a/tools/tryselect/test/test_chooser.py b/tools/tryselect/test/test_chooser.py
new file mode 100644
index 0000000000..217351c206
--- /dev/null
+++ b/tools/tryselect/test/test_chooser.py
@@ -0,0 +1,81 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+from __future__ import absolute_import, print_function, unicode_literals
+
+import mozunit
+import pytest
+
+from tryselect.selectors.chooser.app import create_application
+
+
+TASKS = [
+ {
+ "kind": "build",
+ "label": "build-windows",
+ "attributes": {
+ "build_platform": "windows",
+ },
+ },
+ {
+ "kind": "test",
+ "label": "test-windows-mochitest-e10s",
+ "attributes": {
+ "unittest_suite": "mochitest-browser-chrome",
+ "mochitest_try_name": "mochitest-browser-chrome",
+ },
+ },
+]
+
+
+@pytest.fixture
+def app(tg):
+ app = create_application(tg)
+ app.config["TESTING"] = True
+
+ ctx = app.app_context()
+ ctx.push()
+ yield app
+ ctx.pop()
+
+
+def test_try_chooser(app):
+ client = app.test_client()
+
+ response = client.get("/")
+ assert response.status_code == 200
+
+ expected_output = [
+ b"""<title>Try Chooser Enhanced</title>""",
+ b"""<input class="filter" type="checkbox" id=windows name="build" value='{"build_platform": ["windows"]}' onchange="console.log('checkbox onchange triggered');apply();">""", # noqa
+ b"""<input class="filter" type="checkbox" id=mochitest-browser-chrome name="test" value='{"unittest_suite": ["mochitest-browser-chrome"]}' onchange="console.log('checkbox onchange triggered');apply();">""", # noqa
+ ]
+
+ for expected in expected_output:
+ assert expected in response.data
+
+ response = client.post("/", data={"action": "Cancel"})
+ assert response.status_code == 200
+ assert b"You may now close this page" in response.data
+ assert app.tasks == []
+
+ response = client.post("/", data={"action": "Push", "selected-tasks": ""})
+ assert response.status_code == 200
+ assert b"You may now close this page" in response.data
+ assert app.tasks == []
+
+ response = client.post(
+ "/",
+ data={
+ "action": "Push",
+ "selected-tasks": "build-windows\ntest-windows-mochitest-e10s",
+ },
+ )
+ assert response.status_code == 200
+ assert b"You may now close this page" in response.data
+ assert set(app.tasks) == set(["build-windows", "test-windows-mochitest-e10s"])
+
+
+if __name__ == "__main__":
+ mozunit.main()
diff --git a/tools/tryselect/test/test_empty.t b/tools/tryselect/test/test_empty.t
new file mode 100644
index 0000000000..f6dea366c4
--- /dev/null
+++ b/tools/tryselect/test/test_empty.t
@@ -0,0 +1,47 @@
+ $ . $TESTDIR/setup.sh
+ $ cd $topsrcdir
+
+Test empty selector
+
+ $ ./mach try empty --no-push
+ Commit message:
+ No try selector specified, use "Add New Jobs" to select tasks.
+
+ Pushed via `mach try empty`
+ Calculated try_task_config.json:
+ {
+ "env": {
+ "TRY_SELECTOR": "empty"
+ },
+ "tasks": [],
+ "version": 1
+ }
+
+ $ ./mach try empty --no-push --closed-tree
+ Commit message:
+ No try selector specified, use "Add New Jobs" to select tasks. ON A CLOSED TREE
+
+ Pushed via `mach try empty`
+ Calculated try_task_config.json:
+ {
+ "env": {
+ "TRY_SELECTOR": "empty"
+ },
+ "tasks": [],
+ "version": 1
+ }
+
+ $ ./mach try empty --no-push --closed-tree -m "foo {msg} bar"
+ Commit message:
+ foo No try selector specified, use "Add New Jobs" to select tasks. bar ON A CLOSED TREE
+
+ Pushed via `mach try empty`
+ Calculated try_task_config.json:
+ {
+ "env": {
+ "TRY_SELECTOR": "empty"
+ },
+ "tasks": [],
+ "version": 1
+ }
+
diff --git a/tools/tryselect/test/test_fuzzy.py b/tools/tryselect/test/test_fuzzy.py
new file mode 100644
index 0000000000..55fc01ad99
--- /dev/null
+++ b/tools/tryselect/test/test_fuzzy.py
@@ -0,0 +1,56 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+from __future__ import absolute_import, print_function, unicode_literals
+
+import os
+
+import mozunit
+import pytest
+
+
+@pytest.mark.skipif(os.name == "nt", reason="fzf not installed on host")
+def test_query_paths(run_mach, capfd):
+ cmd = [
+ "try",
+ "fuzzy",
+ "--no-push",
+ "-q",
+ "^test-linux '64/debug-xpcshell-e10s-",
+ "caps/tests/unit/test_origin.js",
+ ]
+ assert run_mach(cmd) == 0
+
+ output = capfd.readouterr().out
+ print(output)
+
+ # If there are more than one tasks here, it means that something went wrong
+ # with the path filtering.
+ expected = """
+ "tasks": [
+ "test-linux1804-64/debug-xpcshell-e10s-1"
+ ]""".lstrip()
+
+ assert expected in output
+
+
+@pytest.mark.skipif(os.name == "nt", reason="fzf not installed on host")
+def test_query(run_mach, capfd):
+ cmd = ["try", "fuzzy", "--no-push", "-q", "'source-test-python-taskgraph-tests-py2"]
+ assert run_mach(cmd) == 0
+
+ output = capfd.readouterr().out
+ print(output)
+
+ # Should only ever mach one task exactly.
+ expected = """
+ "tasks": [
+ "source-test-python-taskgraph-tests-py2"
+ ]""".lstrip()
+
+ assert expected in output
+
+
+if __name__ == "__main__":
+ mozunit.main()
diff --git a/tools/tryselect/test/test_fuzzy.t b/tools/tryselect/test/test_fuzzy.t
new file mode 100644
index 0000000000..d3042b0fe7
--- /dev/null
+++ b/tools/tryselect/test/test_fuzzy.t
@@ -0,0 +1,200 @@
+ $ . $TESTDIR/setup.sh
+ $ cd $topsrcdir
+
+Test fuzzy selector
+
+ $ ./mach try fuzzy $testargs -q "'foo"
+ Commit message:
+ Fuzzy query='foo
+
+ Pushed via `mach try fuzzy`
+ Calculated try_task_config.json:
+ {
+ "env": {
+ "TRY_SELECTOR": "fuzzy"
+ },
+ "tasks": [
+ "test/foo-debug",
+ "test/foo-opt"
+ ],
+ "version": 1
+ }
+
+ $ ./mach try fuzzy $testargs -q "'bar"
+ no tasks selected
+ $ ./mach try fuzzy $testargs --full -q "'bar"
+ Commit message:
+ Fuzzy query='bar
+
+ Pushed via `mach try fuzzy`
+ Calculated try_task_config.json:
+ {
+ "env": {
+ "TRY_SELECTOR": "fuzzy"
+ },
+ "tasks": [
+ "test/bar-debug",
+ "test/bar-opt"
+ ],
+ "version": 1
+ }
+
+
+Test multiple selectors
+
+ $ ./mach try fuzzy $testargs --full -q "'foo" -q "'bar"
+ Commit message:
+ Fuzzy query='foo&query='bar
+
+ Pushed via `mach try fuzzy`
+ Calculated try_task_config.json:
+ {
+ "env": {
+ "TRY_SELECTOR": "fuzzy"
+ },
+ "tasks": [
+ "test/bar-debug",
+ "test/bar-opt",
+ "test/foo-debug",
+ "test/foo-opt"
+ ],
+ "version": 1
+ }
+
+
+Test query intersection
+
+ $ ./mach try fuzzy $testargs --and -q "'foo" -q "'opt"
+ Commit message:
+ Fuzzy query='foo&query='opt
+
+ Pushed via `mach try fuzzy`
+ Calculated try_task_config.json:
+ {
+ "env": {
+ "TRY_SELECTOR": "fuzzy"
+ },
+ "tasks": [
+ "test/foo-opt"
+ ],
+ "version": 1
+ }
+
+
+Test intersection with preset containing multiple queries
+
+ $ ./mach try fuzzy --save foo -q "'test" -q "'opt"
+ preset saved, run with: --preset=foo
+
+ $ ./mach try fuzzy $testargs --preset foo -xq "'test"
+ Commit message:
+ Fuzzy query='test&query='opt&query='test
+
+ Pushed via `mach try fuzzy`
+ Calculated try_task_config.json:
+ {
+ "env": {
+ "TRY_SELECTOR": "fuzzy"
+ },
+ "tasks": [
+ "test/foo-debug",
+ "test/foo-opt"
+ ],
+ "version": 1
+ }
+
+ $ ./mach try $testargs --preset foo -xq "'test"
+ Commit message:
+ Fuzzy query='test&query='opt&query='test
+
+ Pushed via `mach try fuzzy`
+ Calculated try_task_config.json:
+ {
+ "env": {
+ "TRY_SELECTOR": "fuzzy"
+ },
+ "tasks": [
+ "test/foo-debug",
+ "test/foo-opt"
+ ],
+ "version": 1
+ }
+
+
+Test exact match
+
+ $ ./mach try fuzzy $testargs --full -q "testfoo | 'testbar"
+ Commit message:
+ Fuzzy query=testfoo | 'testbar
+
+ Pushed via `mach try fuzzy`
+ Calculated try_task_config.json:
+ {
+ "env": {
+ "TRY_SELECTOR": "fuzzy"
+ },
+ "tasks": [
+ "test/foo-debug",
+ "test/foo-opt"
+ ],
+ "version": 1
+ }
+
+ $ ./mach try fuzzy $testargs --full --exact -q "testfoo | 'testbar"
+ Commit message:
+ Fuzzy query=testfoo | 'testbar
+
+ Pushed via `mach try fuzzy`
+ Calculated try_task_config.json:
+ {
+ "env": {
+ "TRY_SELECTOR": "fuzzy"
+ },
+ "tasks": [
+ "test/bar-debug",
+ "test/bar-opt"
+ ],
+ "version": 1
+ }
+
+
+
+Test task config
+
+ $ ./mach try fuzzy --no-push --artifact -q "'foo"
+ Commit message:
+ Fuzzy query='foo
+
+ Pushed via `mach try fuzzy`
+ Calculated try_task_config.json:
+ {
+ "env": {
+ "TRY_SELECTOR": "fuzzy"
+ },
+ "tasks": [
+ "test/foo-debug",
+ "test/foo-opt"
+ ],
+ "use-artifact-builds": true,
+ "version": 1
+ }
+
+ $ ./mach try fuzzy $testargs --env FOO=1 --env BAR=baz -q "'foo"
+ Commit message:
+ Fuzzy query='foo
+
+ Pushed via `mach try fuzzy`
+ Calculated try_task_config.json:
+ {
+ "env": {
+ "BAR": "baz",
+ "FOO": "1",
+ "TRY_SELECTOR": "fuzzy"
+ },
+ "tasks": [
+ "test/foo-debug",
+ "test/foo-opt"
+ ],
+ "version": 1
+ }
+
diff --git a/tools/tryselect/test/test_message.t b/tools/tryselect/test/test_message.t
new file mode 100644
index 0000000000..25a104833c
--- /dev/null
+++ b/tools/tryselect/test/test_message.t
@@ -0,0 +1,63 @@
+ $ . $TESTDIR/setup.sh
+ $ cd $topsrcdir
+
+Test custom commit messages with fuzzy selector
+
+ $ ./mach try fuzzy $testargs -q foo --message "Foobar"
+ Commit message:
+ Foobar
+
+ Fuzzy query=foo
+
+ Pushed via `mach try fuzzy`
+ Calculated try_task_config.json:
+ {
+ "env": {
+ "TRY_SELECTOR": "fuzzy"
+ },
+ "tasks": [
+ "test/foo-debug",
+ "test/foo-opt"
+ ],
+ "version": 1
+ }
+
+ $ ./mach try fuzzy $testargs -q foo -m "Foobar: {msg}"
+ Commit message:
+ Foobar: Fuzzy query=foo
+
+ Pushed via `mach try fuzzy`
+ Calculated try_task_config.json:
+ {
+ "env": {
+ "TRY_SELECTOR": "fuzzy"
+ },
+ "tasks": [
+ "test/foo-debug",
+ "test/foo-opt"
+ ],
+ "version": 1
+ }
+
+ $ unset EDITOR
+ $ ./mach try fuzzy $testargs -q foo -m > /dev/null 2>&1
+ [2]
+
+
+Test custom commit messages with syntax selector
+
+ $ ./mach try syntax $testargs -p linux -u mochitests --message "Foobar"
+ Commit message:
+ Foobar
+
+ try: -b do -p linux -u mochitests
+
+ Pushed via `mach try syntax`
+ $ ./mach try syntax $testargs -p linux -u mochitests -m "Foobar: {msg}"
+ Commit message:
+ Foobar: try: -b do -p linux -u mochitests
+
+ Pushed via `mach try syntax`
+ $ unset EDITOR
+ $ ./mach try syntax $testargs -p linux -u mochitests -m > /dev/null 2>&1
+ [2]
diff --git a/tools/tryselect/test/test_mozharness_integration.py b/tools/tryselect/test/test_mozharness_integration.py
new file mode 100644
index 0000000000..4c6d4dc491
--- /dev/null
+++ b/tools/tryselect/test/test_mozharness_integration.py
@@ -0,0 +1,145 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+from __future__ import absolute_import, print_function, unicode_literals
+
+import imp
+import json
+import os
+
+import mozunit
+import pytest
+
+from tryselect.tasks import build, resolve_tests_by_suite
+
+MOZHARNESS_SCRIPTS = {
+ "android_emulator_unittest": {
+ "class_name": "AndroidEmulatorTest",
+ "configs": [
+ "android/android_common.py",
+ ],
+ "xfail": [
+ "cppunittest",
+ "crashtest-qr",
+ "gtest",
+ "geckoview-junit",
+ "jittest",
+ "jsreftest",
+ "reftest-qr",
+ ],
+ },
+ "desktop_unittest": {
+ "class_name": "DesktopUnittest",
+ "configs": [
+ "unittests/linux_unittest.py",
+ "unittests/mac_unittest.py",
+ "unittests/win_unittest.py",
+ ],
+ "xfail": [
+ "cppunittest",
+ "gtest",
+ "jittest",
+ "jittest-chunked",
+ "jittest1",
+ "jittest2",
+ "jsreftest",
+ "mochitest-valgrind-plain",
+ "reftest-gpu",
+ "reftest-no-accel",
+ ],
+ },
+}
+"""A suite being listed in a script's `xfail` list means it won't work
+properly with MOZHARNESS_TEST_PATHS (the mechanism |mach try fuzzy <path>|
+uses).
+"""
+
+
+def get_mozharness_test_paths(name):
+ scriptdir = os.path.join(build.topsrcdir, "testing", "mozharness", "scripts")
+
+ files = imp.find_module(name, [scriptdir])
+ mod = imp.load_module("scripts.{}".format(name), *files)
+
+ class_name = MOZHARNESS_SCRIPTS[name]["class_name"]
+ cls = getattr(mod, class_name)
+ return cls(require_config_file=False)._get_mozharness_test_paths
+
+
+@pytest.fixture(scope="module")
+def all_suites():
+ from moztest.resolve import _test_flavors, _test_subsuites
+
+ all_suites = []
+ for flavor in _test_flavors:
+ all_suites.append({"flavor": flavor, "srcdir_relpath": "test"})
+
+ for flavor, subsuite in _test_subsuites:
+ all_suites.append(
+ {"flavor": flavor, "subsuite": subsuite, "srcdir_relpath": "test"}
+ )
+
+ return all_suites
+
+
+def generate_suites_from_config(path):
+ configdir = os.path.join(build.topsrcdir, "testing", "mozharness", "configs")
+
+ parent, name = os.path.split(path)
+ name = os.path.splitext(name)[0]
+
+ files = imp.find_module("{}".format(name), [os.path.join(configdir, parent)])
+ mod = imp.load_module("config.{}".format(name), *files)
+ config = mod.config
+
+ for category in sorted(config["suite_definitions"]):
+ key = "all_{}_suites".format(category)
+ if key not in config:
+ yield category,
+ continue
+
+ for suite in sorted(config["all_{}_suites".format(category)]):
+ yield category, suite
+
+
+def generate_suites():
+ for name, script in MOZHARNESS_SCRIPTS.items():
+ seen = set()
+
+ for path in script["configs"]:
+ for suite in generate_suites_from_config(path):
+ if suite in seen:
+ continue
+ seen.add(suite)
+
+ item = (name, suite)
+
+ if suite[-1] in script["xfail"]:
+ item = pytest.param(item, marks=pytest.mark.xfail)
+
+ yield item
+
+
+def idfn(item):
+ name, suite = item
+ return "{}/{}".format(name, suite[-1])
+
+
+@pytest.mark.parametrize("item", generate_suites(), ids=idfn)
+def test_suites(item, patch_resolver, all_suites):
+ """An integration test to make sure the suites returned by
+ `tasks.resolve_tests_by_suite` match up with the names defined in
+ mozharness.
+ """
+ patch_resolver([], all_suites)
+ suites = resolve_tests_by_suite(["test"])
+ os.environ["MOZHARNESS_TEST_PATHS"] = json.dumps(suites)
+
+ name, suite = item
+ func = get_mozharness_test_paths(name)
+ assert func(*suite)
+
+
+if __name__ == "__main__":
+ mozunit.main()
diff --git a/tools/tryselect/test/test_preset.t b/tools/tryselect/test/test_preset.t
new file mode 100644
index 0000000000..bd57bc0fc6
--- /dev/null
+++ b/tools/tryselect/test/test_preset.t
@@ -0,0 +1,271 @@
+ $ . $TESTDIR/setup.sh
+ $ cd $topsrcdir
+
+Test preset with no subcommand
+
+ $ ./mach try $testargs --save foo -b do -p linux -u mochitests -t none --tag foo
+ preset saved, run with: --preset=foo
+
+ $ ./mach try $testargs --preset foo
+ Commit message:
+ try: -b do -p linux -u mochitests -t none --tag foo
+
+ Pushed via `mach try syntax`
+
+ $ ./mach try syntax $testargs --preset foo
+ Commit message:
+ try: -b do -p linux -u mochitests -t none --tag foo
+
+ Pushed via `mach try syntax`
+
+ $ ./mach try $testargs --list-presets
+ Presets from */mozbuild/try_presets.yml: (glob)
+
+ foo:
+ no_artifact: true
+ platforms:
+ - linux
+ selector: syntax
+ tags:
+ - foo
+ talos:
+ - none
+ tests:
+ - mochitests
+
+ $ unset EDITOR
+ $ ./mach try $testargs --edit-presets
+ error: must set the $EDITOR environment variable to use --edit-presets
+ $ export EDITOR=cat
+ $ ./mach try $testargs --edit-presets
+ foo:
+ no_artifact: true
+ platforms:
+ - linux
+ selector: syntax
+ tags:
+ - foo
+ talos:
+ - none
+ tests:
+ - mochitests
+
+Test preset with syntax subcommand
+
+ $ ./mach try syntax $testargs --save bar -b do -p win32 -u none -t all --tag bar
+ preset saved, run with: --preset=bar
+
+ $ ./mach try syntax $testargs --preset bar
+ Commit message:
+ try: -b do -p win32 -u none -t all --tag bar
+
+ Pushed via `mach try syntax`
+
+ $ ./mach try $testargs --preset bar
+ Commit message:
+ try: -b do -p win32 -u none -t all --tag bar
+
+ Pushed via `mach try syntax`
+
+ $ ./mach try syntax $testargs --list-presets
+ Presets from */mozbuild/try_presets.yml: (glob)
+
+ bar:
+ no_artifact: true
+ platforms:
+ - win32
+ push: false
+ selector: syntax
+ tags:
+ - bar
+ talos:
+ - all
+ tests:
+ - none
+ foo:
+ no_artifact: true
+ platforms:
+ - linux
+ selector: syntax
+ tags:
+ - foo
+ talos:
+ - none
+ tests:
+ - mochitests
+
+ $ ./mach try syntax $testargs --edit-presets
+ bar:
+ no_artifact: true
+ platforms:
+ - win32
+ push: false
+ selector: syntax
+ tags:
+ - bar
+ talos:
+ - all
+ tests:
+ - none
+ foo:
+ no_artifact: true
+ platforms:
+ - linux
+ selector: syntax
+ tags:
+ - foo
+ talos:
+ - none
+ tests:
+ - mochitests
+
+Test preset with fuzzy subcommand
+
+ $ ./mach try fuzzy $testargs --save baz -q "'foo" --rebuild 5
+ preset saved, run with: --preset=baz
+
+ $ ./mach try fuzzy $testargs --preset baz
+ Commit message:
+ Fuzzy query='foo
+
+ Pushed via `mach try fuzzy`
+ Calculated try_task_config.json:
+ {
+ "env": {
+ "TRY_SELECTOR": "fuzzy"
+ },
+ "rebuild": 5,
+ "tasks": [
+ "test/foo-debug",
+ "test/foo-opt"
+ ],
+ "version": 1
+ }
+
+
+ $ ./mach try $testargs --preset baz
+ Commit message:
+ Fuzzy query='foo
+
+ Pushed via `mach try fuzzy`
+ Calculated try_task_config.json:
+ {
+ "env": {
+ "TRY_SELECTOR": "fuzzy"
+ },
+ "rebuild": 5,
+ "tasks": [
+ "test/foo-debug",
+ "test/foo-opt"
+ ],
+ "version": 1
+ }
+
+
+Queries can be appended to presets
+
+ $ ./mach try fuzzy $testargs --preset baz -q "'build"
+ Commit message:
+ Fuzzy query='foo&query='build
+
+ Pushed via `mach try fuzzy`
+ Calculated try_task_config.json:
+ {
+ "env": {
+ "TRY_SELECTOR": "fuzzy"
+ },
+ "rebuild": 5,
+ "tasks": [
+ "build-baz",
+ "test/foo-debug",
+ "test/foo-opt"
+ ],
+ "version": 1
+ }
+
+
+ $ ./mach try $testargs --preset baz -xq "'opt"
+ Commit message:
+ Fuzzy query='foo&query='opt
+
+ Pushed via `mach try fuzzy`
+ Calculated try_task_config.json:
+ {
+ "env": {
+ "TRY_SELECTOR": "fuzzy"
+ },
+ "rebuild": 5,
+ "tasks": [
+ "test/foo-opt"
+ ],
+ "version": 1
+ }
+
+
+ $ ./mach try fuzzy $testargs --list-presets
+ Presets from */mozbuild/try_presets.yml: (glob)
+
+ bar:
+ no_artifact: true
+ platforms:
+ - win32
+ push: false
+ selector: syntax
+ tags:
+ - bar
+ talos:
+ - all
+ tests:
+ - none
+ baz:
+ no_artifact: true
+ push: false
+ query:
+ - '''foo'
+ rebuild: 5
+ selector: fuzzy
+ foo:
+ no_artifact: true
+ platforms:
+ - linux
+ selector: syntax
+ tags:
+ - foo
+ talos:
+ - none
+ tests:
+ - mochitests
+
+ $ ./mach try fuzzy $testargs --edit-presets
+ bar:
+ no_artifact: true
+ platforms:
+ - win32
+ push: false
+ selector: syntax
+ tags:
+ - bar
+ talos:
+ - all
+ tests:
+ - none
+ baz:
+ no_artifact: true
+ push: false
+ query:
+ - '''foo'
+ rebuild: 5
+ selector: fuzzy
+ foo:
+ no_artifact: true
+ platforms:
+ - linux
+ selector: syntax
+ tags:
+ - foo
+ talos:
+ - none
+ tests:
+ - mochitests
+
+ $ rm $MOZBUILD_STATE_PATH/try_presets.yml
diff --git a/tools/tryselect/test/test_presets.py b/tools/tryselect/test/test_presets.py
new file mode 100644
index 0000000000..9376a5a267
--- /dev/null
+++ b/tools/tryselect/test/test_presets.py
@@ -0,0 +1,61 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+from __future__ import absolute_import, print_function, unicode_literals
+
+import mozunit
+import pytest
+
+
+TASKS = [
+ {
+ "kind": "build",
+ "label": "build-windows",
+ "attributes": {
+ "build_platform": "windows",
+ },
+ },
+ {
+ "kind": "test",
+ "label": "test-windows-mochitest-e10s",
+ "attributes": {
+ "unittest_suite": "mochitest",
+ "unittest_flavor": "browser-chrome",
+ "mochitest_try_name": "mochitest",
+ },
+ },
+]
+
+
+@pytest.fixture(autouse=True)
+def skip_taskgraph_generation(monkeypatch, tg):
+ def fake_generate_tasks(*args, **kwargs):
+ return tg
+
+ from tryselect import tasks
+
+ monkeypatch.setattr(tasks, "generate_tasks", fake_generate_tasks)
+
+
+@pytest.mark.xfail(
+ strict=False, reason="Bug 1635204: " "test_shared_presets[sample-suites] is flaky"
+)
+def test_shared_presets(run_mach, shared_name, shared_preset):
+ """This test makes sure that we don't break any of the in-tree presets when
+ renaming/removing variables in any of the selectors.
+ """
+ assert "description" in shared_preset
+ assert "selector" in shared_preset
+
+ selector = shared_preset["selector"]
+ if selector == "fuzzy":
+ assert "query" in shared_preset
+ assert isinstance(shared_preset["query"], list)
+
+ # Run the preset and assert there were no exceptions.
+ assert run_mach(["try", "--no-push", "--preset", shared_name]) == 0
+
+
+if __name__ == "__main__":
+ mozunit.main()
diff --git a/tools/tryselect/test/test_task_configs.py b/tools/tryselect/test/test_task_configs.py
new file mode 100644
index 0000000000..0a5ae93392
--- /dev/null
+++ b/tools/tryselect/test/test_task_configs.py
@@ -0,0 +1,148 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+from __future__ import absolute_import, print_function, unicode_literals
+
+import inspect
+from argparse import ArgumentParser
+
+import mozunit
+import pytest
+import subprocess
+from textwrap import dedent
+
+from tryselect.task_config import all_task_configs, Pernosco
+
+
+# task configs have a list of tests of the form (input, expected)
+TASK_CONFIG_TESTS = {
+ "artifact": [
+ (["--no-artifact"], None),
+ (["--artifact"], {"use-artifact-builds": True}),
+ ],
+ "chemspill-prio": [
+ ([], None),
+ (["--chemspill-prio"], {"chemspill-prio": {}}),
+ ],
+ "env": [
+ ([], None),
+ (["--env", "foo=bar", "--env", "num=10"], {"env": {"foo": "bar", "num": "10"}}),
+ ],
+ "path": [
+ ([], None),
+ (
+ ["dom/indexedDB"],
+ {"env": {"MOZHARNESS_TEST_PATHS": '{"xpcshell": ["dom/indexedDB"]}'}},
+ ),
+ (
+ ["dom/indexedDB", "testing"],
+ {
+ "env": {
+ "MOZHARNESS_TEST_PATHS": '{"xpcshell": ["dom/indexedDB", "testing"]}'
+ }
+ },
+ ),
+ (["invalid/path"], SystemExit),
+ ],
+ "pernosco": [
+ ([], None),
+ ],
+ "rebuild": [
+ ([], None),
+ (["--rebuild", "10"], {"rebuild": 10}),
+ (["--rebuild", "1"], SystemExit),
+ (["--rebuild", "21"], SystemExit),
+ ],
+ "worker-overrides": [
+ ([], None),
+ (
+ ["--worker-override", "alias=worker/pool"],
+ {"worker-overrides": {"alias": "worker/pool"}},
+ ),
+ (
+ [
+ "--worker-override",
+ "alias=worker/pool",
+ "--worker-override",
+ "alias=other/pool",
+ ],
+ SystemExit,
+ ),
+ (
+ ["--worker-suffix", "b-linux=-dev"],
+ {"worker-overrides": {"b-linux": "gecko-1/b-linux-dev"}},
+ ),
+ (
+ [
+ "--worker-override",
+ "b-linux=worker/pool" "--worker-suffix",
+ "b-linux=-dev",
+ ],
+ SystemExit,
+ ),
+ ],
+}
+
+
+@pytest.fixture
+def config_patch_resolver(patch_resolver):
+ def inner(paths):
+ patch_resolver(
+ [], [{"flavor": "xpcshell", "srcdir_relpath": path} for path in paths]
+ )
+
+ return inner
+
+
+def test_task_configs(config_patch_resolver, task_config, args, expected):
+ parser = ArgumentParser()
+
+ cfg = all_task_configs[task_config]()
+ cfg.add_arguments(parser)
+
+ if inspect.isclass(expected) and issubclass(expected, BaseException):
+ with pytest.raises(expected):
+ args = parser.parse_args(args)
+ if task_config == "path":
+ config_patch_resolver(**vars(args))
+
+ cfg.try_config(**vars(args))
+ else:
+ args = parser.parse_args(args)
+ if task_config == "path":
+ config_patch_resolver(**vars(args))
+ assert cfg.try_config(**vars(args)) == expected
+
+
+@pytest.fixture
+def patch_pernosco_email_check(monkeypatch):
+ def inner(val):
+ def fake_check_output(*args, **kwargs):
+ return val
+
+ monkeypatch.setattr(subprocess, "check_output", fake_check_output)
+
+ return inner
+
+
+def test_pernosco(patch_pernosco_email_check):
+ patch_pernosco_email_check(
+ dedent(
+ """
+ user foobar@mozilla.com
+ hostname hg.mozilla.com
+ """
+ )
+ )
+
+ parser = ArgumentParser()
+
+ cfg = Pernosco()
+ cfg.add_arguments(parser)
+ args = parser.parse_args(["--pernosco"])
+ assert cfg.try_config(**vars(args)) == {"env": {"PERNOSCO": "1"}}
+
+
+if __name__ == "__main__":
+ mozunit.main()
diff --git a/tools/tryselect/test/test_tasks.py b/tools/tryselect/test/test_tasks.py
new file mode 100644
index 0000000000..b338f8fc02
--- /dev/null
+++ b/tools/tryselect/test/test_tasks.py
@@ -0,0 +1,59 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+from __future__ import absolute_import, print_function, unicode_literals
+
+import mozunit
+
+from tryselect.tasks import filter_tasks_by_paths, resolve_tests_by_suite
+
+
+def test_filter_tasks_by_paths(patch_resolver):
+ tasks = ["foobar/xpcshell-1", "foobar/mochitest", "foobar/xpcshell"]
+
+ patch_resolver(["xpcshell"], {})
+ assert list(filter_tasks_by_paths(tasks, "dummy")) == []
+
+ patch_resolver([], [{"flavor": "xpcshell"}])
+ assert list(filter_tasks_by_paths(tasks, "dummy")) == [
+ "foobar/xpcshell-1",
+ "foobar/xpcshell",
+ ]
+
+
+def test_resolve_tests_by_suite(patch_resolver):
+ patch_resolver([], [{"flavor": "xpcshell", "srcdir_relpath": "xpcshell.js"}])
+ assert resolve_tests_by_suite(["xpcshell.js"]) == {
+ "xpcshell": ["xpcshell.js"],
+ }
+
+ patch_resolver(
+ [],
+ [
+ {
+ "flavor": "xpcshell",
+ "srcdir_relpath": "xpcshell.js",
+ "manifest_relpath": "xpcshell.ini",
+ },
+ ],
+ )
+ assert resolve_tests_by_suite(["xpcshell.ini"]) == {
+ "xpcshell": ["xpcshell.ini"],
+ }
+
+ patch_resolver(
+ [],
+ [
+ {"flavor": "xpcshell", "srcdir_relpath": "xpcshell.js"},
+ {"flavor": "mochitest", "srcdir_relpath": "mochitest.js"},
+ ],
+ )
+ assert resolve_tests_by_suite(["xpcshell.js", "mochitest.js"]) == {
+ "xpcshell": ["xpcshell.js"],
+ "mochitest-plain": ["mochitest.js"],
+ }
+
+
+if __name__ == "__main__":
+ mozunit.main()