async def test_dispatch(): """ Test message dispatch from a queue to 2 queues """ bus = MessageBus() bus.add_queue("input") # limit size to immediately stop execution for unit test bus.add_queue("output1", maxsize=3) bus.add_queue("output2", maxsize=3) assert isinstance(bus.queues["input"], asyncio.Queue) assert isinstance(bus.queues["output1"], asyncio.Queue) assert isinstance(bus.queues["output2"], asyncio.Queue) assert bus.queues["input"].qsize() == 0 assert bus.queues["output1"].qsize() == 0 assert bus.queues["output2"].qsize() == 0 await bus.send("input", 1) await bus.send("input", 2) # Convert all strings from input in uppercase assert bus.queues["input"].qsize() == 2 task = asyncio.create_task(bus.dispatch("input", ["output1", "output2"])) await bus.receive("output1") == 1 await bus.receive("output2") == 1 await bus.receive("output1") == 2 await bus.receive("output2") == 2 task.cancel() assert bus.queues["input"].qsize() == 0 assert bus.queues["output1"].qsize() == 0 assert bus.queues["output2"].qsize() == 0
async def test_conversion(): """ Test message conversion between 2 queues """ bus = MessageBus() bus.add_queue("input") bus.add_queue( "output", maxsize=3) # limit size to immediately stop execution for unit test assert isinstance(bus.queues["input"], asyncio.Queue) assert isinstance(bus.queues["output"], asyncio.Queue) assert bus.queues["input"].qsize() == 0 assert bus.queues["output"].qsize() == 0 await bus.send("input", "test x") await bus.send("input", "hello world.") await bus.send("output", "lowercase") # Convert all strings from input in uppercase assert bus.queues["input"].qsize() == 2 task = asyncio.create_task( bus.run(lambda x: x.upper(), "input", ["output"])) await bus.receive("output") == "lowercase" await bus.receive("output") == "TEST X" await bus.receive("output") == "HELLO WORLD." task.cancel() assert bus.queues["input"].qsize() == 0 assert bus.queues["output"].qsize() == 0
async def test_start_test_selection(PhabricatorMock, mock_taskcluster): bus = MessageBus() bus.add_queue(QUEUE_MONITORING_COMMUNITY) build = PhabricatorBuild( MockRequest( diff="125397", repo="PHID-REPO-saax4qdxlbbhahhp2kg5", revision="36474", target="PHID-HMBT-icusvlfibcebizyd33op", )) taskcluster_config.secrets["test_selection_enabled"] = True taskcluster_config.secrets["test_selection_share"] = 1.0 with PhabricatorMock as phab: phab.load_patches_stack(build) phab.update_state(build) phab.load_reviewers(build) bugbug_utils = BugbugUtils(phab.api) bugbug_utils.register(bus) with responses.RequestsMock(assert_all_requests_are_fired=True) as rsps: def trigger_hook_callback(request): payload = json.loads(request.body) assert payload == { "DIFF_ID": 125397, "PHABRICATOR_DEPLOYMENT": "prod", "RUNNABLE_JOBS": "http://taskcluster.test/api/index/v1/task/gecko.v2.try.revision.MyRevision.firefox.decision/artifacts/public%2Frunnable-jobs.json", } return ( 200, { "Content-Type": "application/json" }, json.dumps({"status": { "taskId": "xxx" }}), ) rsps.add_callback( responses.POST, "http://community_taskcluster.test/api/hooks/v1/hooks/project-relman/bugbug-test-select/trigger", callback=trigger_hook_callback, ) await bugbug_utils.start_test_selection(build, "MyRevision") group_id, hook_id, task_id = await bus.receive( QUEUE_MONITORING_COMMUNITY) assert group_id == "project-relman" assert hook_id == "bugbug-test-select" assert task_id == "xxx"
async def test_failure_general(PhabricatorMock, mock_mc): """ Run mercurial worker on a single diff and check the treeherder link publication as an artifact Use a Python common exception to trigger a broken build """ diff = { "phid": "PHID-DIFF-test123", "id": 1234, "baseRevision": None, "revisionPHID": "PHID-DREV-deadbeef", } build = MockBuild(1234, "PHID-REPO-mc", 5678, "PHID-somehash", diff) with PhabricatorMock as phab: phab.load_patches_stack(build) bus = MessageBus() bus.add_queue("phabricator") # Get initial tip commit in repo initial = mock_mc.repo.tip() # The patched and config files should not exist at first repo_dir = mock_mc.repo.root().decode("utf-8") config = os.path.join(repo_dir, "try_task_config.json") target = os.path.join(repo_dir, "test.txt") assert not os.path.exists(target) assert not os.path.exists(config) # Raise an exception during the workflow to trigger a broken build def boom(*args): raise Exception("Boom") mock_mc.apply_build = boom worker = MercurialWorker( "mercurial", "phabricator", repositories={"PHID-REPO-mc": mock_mc} ) worker.register(bus) assert len(worker.repositories) == 1 await bus.send("mercurial", build) assert bus.queues["mercurial"].qsize() == 1 task = asyncio.create_task(worker.run()) # Check the unit result was published mode, out_build, details = await bus.receive("phabricator") assert mode == "fail:general" assert out_build == build assert details["duration"] > 0 assert details["message"] == "Boom" task.cancel() # Clone should not be modified tip = mock_mc.repo.tip() assert tip.node == initial.node
async def test_treeherder_link(PhabricatorMock, mock_mc): """ Run mercurial worker on a single diff and check the treeherder link publication as an artifact """ # Preload the build diff = { "phid": "PHID-DIFF-test123", "revisionPHID": "PHID-DREV-deadbeef", "id": 1234, "baseRevision": "abcdef12345", } build = MockBuild(1234, "PHID-REPO-mc", 5678, "PHID-HMBT-somehash", diff) with PhabricatorMock as phab: phab.load_patches_stack(build) bus = MessageBus() bus.add_queue("phabricator") # Get initial tip commit in repo initial = mock_mc.repo.tip() # The patched and config files should not exist at first repo_dir = mock_mc.repo.root().decode("utf-8") config = os.path.join(repo_dir, "try_task_config.json") target = os.path.join(repo_dir, "test.txt") assert not os.path.exists(target) assert not os.path.exists(config) worker = MercurialWorker( "mercurial", "phabricator", repositories={"PHID-REPO-mc": mock_mc} ) worker.register(bus) assert len(worker.repositories) == 1 await bus.send("mercurial", build) assert bus.queues["mercurial"].qsize() == 1 task = asyncio.create_task(worker.run()) # Check the treeherder link was queued mode, out_build, details = await bus.receive("phabricator") tip = mock_mc.repo.tip() assert mode == "success" assert out_build == build assert details[ "treeherder_url" ] == "https://treeherder.mozilla.org/#/jobs?repo=try&revision={}".format( tip.node.decode("utf-8") ) assert details["revision"] == tip.node.decode("utf-8") task.cancel() # Tip should be updated assert tip.node != initial.node
async def test_failure_mercurial(PhabricatorMock, mock_mc): """ Run mercurial worker on a single diff and check the treeherder link publication as an artifact Apply a bad mercurial patch to trigger a mercurial fail """ diff = { "revisionPHID": "PHID-DREV-666", "baseRevision": "missing", "phid": "PHID-DIFF-666", "id": 666, } build = MockBuild(1234, "PHID-REPO-mc", 5678, "PHID-build-666", diff) with PhabricatorMock as phab: phab.load_patches_stack(build) bus = MessageBus() bus.add_queue("phabricator") # Get initial tip commit in repo initial = mock_mc.repo.tip() # The patched and config files should not exist at first repo_dir = mock_mc.repo.root().decode("utf-8") config = os.path.join(repo_dir, "try_task_config.json") target = os.path.join(repo_dir, "test.txt") assert not os.path.exists(target) assert not os.path.exists(config) worker = MercurialWorker( "mercurial", "phabricator", repositories={"PHID-REPO-mc": mock_mc} ) worker.register(bus) assert len(worker.repositories) == 1 await bus.send("mercurial", build) assert bus.queues["mercurial"].qsize() == 1 task = asyncio.create_task(worker.run()) # Check the treeherder link was queued mode, out_build, details = await bus.receive("phabricator") assert mode == "fail:mercurial" assert out_build == build assert details["duration"] > 0 assert details["message"] == MERCURIAL_FAILURE task.cancel() # Clone should not be modified tip = mock_mc.repo.tip() assert tip.node == initial.node
async def test_message_passing_mp(): """ Test sending & receiving messages on a multiprocessing queueu """ bus = MessageBus() bus.add_queue("test", mp=True) assert isinstance(bus.queues["test"], multiprocessing.queues.Queue) await bus.send("test", {"payload": 1234}) await bus.send("test", {"another": "deadbeef"}) await bus.send("test", "covfefe") assert bus.queues["test"].qsize() == 3 msg = await bus.receive("test") assert msg == {"payload": 1234} msg = await bus.receive("test") assert msg == {"another": "deadbeef"} msg = await bus.receive("test") assert msg == "covfefe" assert bus.queues["test"].qsize() == 0
async def test_maxsize(): """ Test a queue maxsize behaves as expected Maxsize=-1 is enabled by default """ bus = MessageBus() bus.add_queue("async") bus.add_queue("mp", mp=True) assert bus.queues["async"].maxsize == -1 # No maxsize getter on mp queues assert bus.queues["async"].empty() assert bus.queues["mp"].empty() for i in range(1000): await bus.send("async", i) await bus.send("mp", i) assert not bus.queues["async"].full() assert not bus.queues["mp"].full()
async def test_run_async_parallel(): """ Test using run to get messages from a queue using an async function executed in parallel """ bus = MessageBus() bus.add_queue("input") bus.add_queue("end", maxsize=1) assert isinstance(bus.queues["input"], asyncio.Queue) assert bus.queues["input"].qsize() == 0 await bus.send("input", 0) await bus.send("input", 1) await bus.send("input", 2) assert bus.queues["input"].qsize() == 3 done = {0: False, 1: False, 2: False} async def torun(count): await asyncio.sleep(0) if count == 0: # Wait for 7 seconds, in the meantime other tasks will be scheduled # and executed. await asyncio.sleep(7) elif count == 1: pass elif count == 2: await bus.send("end", count) else: assert False done[count] = True task = asyncio.create_task(bus.run(torun, "input", sequential=False)) await bus.receive("end") == 1 task.cancel() assert bus.queues["input"].qsize() == 0 assert bus.queues["end"].qsize() == 0 assert done == {0: False, 1: True, 2: True}
async def test_run_async_without_output_queue(): """ Test using run to get messages from a queue using an async function and without an output queue """ bus = MessageBus() bus.add_queue("input") bus.add_queue("end", maxsize=1) assert isinstance(bus.queues["input"], asyncio.Queue) assert bus.queues["input"].qsize() == 0 await bus.send("input", "test x") await bus.send("input", "hello world.") assert bus.queues["input"].qsize() == 2 count = 0 async def torun(payload): nonlocal count if count == 0: assert payload == "test x" elif count == 1: assert payload == "hello world." await bus.send("end", count) else: assert False count += 1 task = asyncio.create_task(bus.run(torun, "input")) await bus.receive("end") == 1 task.cancel() assert bus.queues["input"].qsize() == 0 assert bus.queues["end"].qsize() == 0
def test_queue_creation(): """ Test queues creation with different types """ bus = MessageBus() assert len(bus.queues) == 0 bus.add_queue("test") assert len(bus.queues) == 1 with pytest.raises(AssertionError) as e: bus.add_queue("test") assert str(e.value) == "Queue test already setup" assert len(bus.queues) == 1 bus.add_queue("another") assert len(bus.queues) == 2 bus.add_queue("different", mp=True) assert len(bus.queues) == 3 assert isinstance(bus.queues["test"], asyncio.Queue) assert isinstance(bus.queues["another"], asyncio.Queue) assert isinstance(bus.queues["different"], multiprocessing.queues.Queue)
async def test_crash_utf8_author(PhabricatorMock, mock_mc): """ Run mercurial worker on a single diff but the patch author has utf-8 chars in its name """ diff = { "revisionPHID": "PHID-DREV-badutf8", "baseRevision": "missing", "phid": "PHID-DIFF-badutf8", "id": 555, } build = MockBuild(4444, "PHID-REPO-mc", 5555, "PHID-build-badutf8", diff) with PhabricatorMock as phab: phab.load_patches_stack(build) bus = MessageBus() bus.add_queue("phabricator") # The patched and config files should not exist at first repo_dir = mock_mc.repo.root().decode("utf-8") config = os.path.join(repo_dir, "try_task_config.json") target = os.path.join(repo_dir, "test.txt") assert not os.path.exists(target) assert not os.path.exists(config) worker = MercurialWorker( "mercurial", "phabricator", repositories={"PHID-REPO-mc": mock_mc} ) worker.register(bus) assert len(worker.repositories) == 1 await bus.send("mercurial", build) assert bus.queues["mercurial"].qsize() == 1 # Run the mercurial worker on that patch only task = asyncio.create_task(worker.run()) mode, out_build, details = await bus.receive("phabricator") task.cancel() # Check we have the patch with utf-8 author properly applied assert [(c.author, c.desc) for c in mock_mc.repo.log()] == [ ( b"libmozevent <*****@*****.**>", b"try_task_config for code-review\n" b"Differential Diff: PHID-DIFF-badutf8", ), ( b"Andr\xc3\xa9 XXXX <*****@*****.**>", b"This patch has an author with utf8 chars\n" b"Differential Diff: PHID-DIFF-badutf8", ), (b"test", b"Readme"), ] # The phab output should be successful assert mode == "success" assert out_build == build assert details[ "treeherder_url" ] == "https://treeherder.mozilla.org/#/jobs?repo=try&revision={}".format( mock_mc.repo.tip().node.decode("utf-8") ) assert details["revision"] == mock_mc.repo.tip().node.decode("utf-8")
async def test_push_to_try_nss(PhabricatorMock, mock_nss): """ Run mercurial worker on a single diff with a push to try server, but with NSS support (try syntax) """ diff = { "phid": "PHID-DIFF-test123", "revisionPHID": "PHID-DREV-deadbeef", "id": 1234, # Revision does not exist, will apply on tip "baseRevision": "abcdef12345", } build = MockBuild(1234, "PHID-REPO-nss", 5678, "PHID-HMBT-deadbeef", diff) with PhabricatorMock as phab: phab.load_patches_stack(build) bus = MessageBus() bus.add_queue("phabricator") # Get initial tip commit in repo initial = mock_nss.repo.tip() # The patched and config files should not exist at first repo_dir = mock_nss.repo.root().decode("utf-8") config = os.path.join(repo_dir, "try_task_config.json") target = os.path.join(repo_dir, "test.txt") assert not os.path.exists(target) assert not os.path.exists(config) worker = MercurialWorker( "mercurial", "phabricator", repositories={"PHID-REPO-nss": mock_nss} ) worker.register(bus) assert len(worker.repositories) == 1 await bus.send("mercurial", build) assert bus.queues["mercurial"].qsize() == 1 task = asyncio.create_task(worker.run()) # Check the treeherder link was queued mode, out_build, details = await bus.receive("phabricator") tip = mock_nss.repo.tip() assert mode == "success" assert out_build == build assert details[ "treeherder_url" ] == "https://treeherder.mozilla.org/#/jobs?repo=try&revision={}".format( tip.node.decode("utf-8") ) assert details["revision"] == tip.node.decode("utf-8") task.cancel() # The target should have content now assert os.path.exists(target) assert open(target).read() == "First Line\nSecond Line\n" # The config should have content now assert os.path.exists(config) assert json.load(open(config)) == { "version": 2, "parameters": { "code-review": {"phabricator-build-target": "PHID-HMBT-deadbeef"} }, } # Get tip commit in repo # It should be different from the initial one (patches + config have applied) assert tip.node != initial.node # Check all commits messages assert [c.desc for c in mock_nss.repo.log()] == [ b"try: -a -b XXX -c YYY", b"Bug XXX - A second commit message\nDifferential Diff: PHID-DIFF-test123", b"Bug XXX - A first commit message\nDifferential Diff: PHID-DIFF-xxxx", b"Readme", ] # Check the push to try has been called # with tip commit ssh_conf = 'ssh -o StrictHostKeyChecking="no" -o User="******" -o IdentityFile="{}"'.format( mock_nss.ssh_key_path ) mock_nss.repo.push.assert_called_with( dest=b"http://nss/try", force=True, rev=tip.node, ssh=ssh_conf.encode("utf-8") )
async def test_dont_push_skippable_files_to_try(PhabricatorMock, mock_mc): """ Run mercurial worker on a single diff that skips the push to try server """ bus = MessageBus() bus.add_queue("phabricator") # Preload the build diff = { "phid": "PHID-DIFF-test123", "revisionPHID": "PHID-DREV-deadbeef", "id": 1234, # Revision does not exist, will apply on tip "baseRevision": "abcdef12345", } build = MockBuild(1234, "PHID-REPO-mc", 5678, "PHID-HMBT-deadbeef", diff) with PhabricatorMock as phab: phab.load_patches_stack(build) # Get initial tip commit in repo initial = mock_mc.repo.tip() # The patched and config files should not exist at first repo_dir = mock_mc.repo.root().decode("utf-8") config = os.path.join(repo_dir, "try_task_config.json") target = os.path.join(repo_dir, "test.txt") assert not os.path.exists(target) assert not os.path.exists(config) worker = MercurialWorker( "mercurial", "phabricator", repositories={"PHID-REPO-mc": mock_mc}, skippable_files=["test.txt"], ) worker.register(bus) assert len(worker.repositories) == 1 await bus.send("mercurial", build) assert bus.queues["mercurial"].qsize() == 1 task = asyncio.create_task(worker.run()) # Check the treeherder link was queued mode, out_build, details = await bus.receive("phabricator") tip = mock_mc.repo.tip() assert mode == "fail:ineligible" assert out_build == build assert ( details["message"] == "Modified files match skippable internal configuration files" ) task.cancel() # The target should have content now assert os.path.exists(target) assert open(target).read() == "First Line\nSecond Line\n" # Get tip commit in repo # It should be different from the initial one (patches + config have applied) assert tip.node != initial.node # Check all commits messages assert [c.desc for c in mock_mc.repo.log()] == [ b"Bug XXX - A second commit message\nDifferential Diff: PHID-DIFF-test123", b"Bug XXX - A first commit message\nDifferential Diff: PHID-DIFF-xxxx", b"Readme", ] # Check all commits authors assert [c.author for c in mock_mc.repo.log()] == [ b"John Doe <*****@*****.**>", b"randomUsername <random>", b"test", ] # Check the push to try has not been called mock_mc.repo.push.assert_not_called()
async def test_push_to_try_existing_rev(PhabricatorMock, mock_mc): """ Run mercurial worker on a single diff with a push to try server but applying on an existing revision """ bus = MessageBus() bus.add_queue("phabricator") repo_dir = mock_mc.repo.root().decode("utf-8") def _readme(content): # Make a commit on README.md in the repo readme = os.path.join(repo_dir, "README.md") with open(readme, "a") as f: f.write(content) _, rev = mock_mc.repo.commit(message=content.encode("utf-8"), user=b"test") return rev # Make two commits, the first one is our base base = _readme("Local base for diffs") extra = _readme("EXTRA") # Preload the build diff = { "phid": "PHID-DIFF-solo", "revisionPHID": "PHID-DREV-solo", "id": 9876, # Revision does not exist, will apply on tip "baseRevision": base, } build = MockBuild(1234, "PHID-REPO-mc", 5678, "PHID-HMBT-deadbeef", diff) build.revision_url = "http://phab.test/D1234" with PhabricatorMock as phab: phab.load_patches_stack(build) # The patched and config files should not exist at first target = os.path.join(repo_dir, "solo.txt") config = os.path.join(repo_dir, "try_task_config.json") assert not os.path.exists(target) assert not os.path.exists(config) worker = MercurialWorker( "mercurial", "phabricator", repositories={"PHID-REPO-mc": mock_mc} ) worker.register(bus) assert len(worker.repositories) == 1 await bus.send("mercurial", build) assert bus.queues["mercurial"].qsize() == 1 task = asyncio.create_task(worker.run()) # Check the treeherder link was queued mode, out_build, details = await bus.receive("phabricator") tip = mock_mc.repo.tip() assert mode == "success" assert out_build == build assert details[ "treeherder_url" ] == "https://treeherder.mozilla.org/#/jobs?repo=try&revision={}".format( tip.node.decode("utf-8") ) assert details["revision"] == tip.node.decode("utf-8") task.cancel() # The target should have content now assert os.path.exists(target) assert open(target).read() == "Solo PATCH\n" # Check the try_task_config file assert os.path.exists(config) assert json.load(open(config)) == { "version": 2, "parameters": { "target_tasks_method": "codereview", "optimize_target_tasks": True, "phabricator_diff": "PHID-HMBT-deadbeef", }, } # Get tip commit in repo # It should be different from the initial one (patches and config have applied) assert tip.node != base assert ( tip.desc == b"""try_task_config for http://phab.test/D1234 Differential Diff: PHID-DIFF-solo""" ) # Check the push to try has been called # with tip commit ssh_conf = 'ssh -o StrictHostKeyChecking="no" -o User="******" -o IdentityFile="{}"'.format( mock_mc.ssh_key_path ) mock_mc.repo.push.assert_called_with( dest=b"http://mozilla-central/try", force=True, rev=tip.node, ssh=ssh_conf.encode("utf-8"), ) # Check the parent is the solo patch commit parents = mock_mc.repo.parents(tip.node) assert len(parents) == 1 parent = parents[0] assert ( parent.desc == b"A nice human readable commit message\nDifferential Diff: PHID-DIFF-solo" ) # Check the grand parent is the base, not extra great_parents = mock_mc.repo.parents(parent.node) assert len(great_parents) == 1 great_parent = great_parents[0] assert great_parent.node == base # Extra commit should not appear assert parent.node != extra assert great_parent.node != extra assert "EXTRA" not in open(os.path.join(repo_dir, "README.md")).read()
async def test_got_try_task_end(PhabricatorMock, mock_taskcluster): bus = MessageBus() build = PhabricatorBuild( MockRequest( diff="125397", repo="PHID-REPO-saax4qdxlbbhahhp2kg5", revision="36474", target="PHID-HMBT-icusvlfibcebizyd33op", )) payload = { "routing": { "exchange": "exchange/taskcluster-queue/v1/task-completed", "key": "primary.fUAKaIdkSF6K1NlOgx7-LA.0.aws.i-0a45c84b1709af6a7.gecko-t.t-win10-64.gecko-level-1.RHY-YSgBQ7KlTAaQ5ZWP5g._", "other_routes": [ b"route.tc-treeherder.v2.try.028980a035fb3e214f7645675a01a52234aad0fe.455891" ], }, "body": { "status": { "taskId": "W2SMZ3bYTeanBq-WNpUeHA", "provisionerId": "gecko-t", "workerType": "t-linux-xlarge", "schedulerId": "gecko-level-1", "taskGroupId": "HDnvYOibTMS8h_5Qzv6fWg", "deadline": "2019-11-23T14:04:41.581Z", "expires": "2019-12-06T14:04:41.581Z", "retriesLeft": 5, "state": "completed", "runs": [{ "runId": 0, "state": "completed", "reasonCreated": "scheduled", "reasonResolved": "completed", "workerGroup": "aws", "workerId": "i-01a6b2a05e2211f7c", "takenUntil": "2019-11-22T15:51:57.083Z", "scheduled": "2019-11-22T15:31:56.661Z", "started": "2019-11-22T15:31:57.162Z", "resolved": "2019-11-22T15:42:46.684Z", }], }, "runId": 0, "task": { "tags": { "kind": "test", "worker-implementation": "docker-worker", "createdForUser": "******", "retrigger": "true", "label": "test-linux64-shippable/opt-awsy-tp6-e10s", "os": "linux", } }, "workerGroup": "aws", "workerId": "i-01a6b2a05e2211f7c", "version": 1, }, } taskGroupId = payload["body"]["status"]["taskGroupId"] taskcluster_config.secrets["test_selection_enabled"] = True taskcluster_config.secrets["test_selection_share"] = 0.1 with PhabricatorMock as phab: bus.add_queue(QUEUE_PHABRICATOR_RESULTS) phab.load_patches_stack(build) phab.update_state(build) phab.load_reviewers(build) bugbug_utils = BugbugUtils(phab.api) bugbug_utils.register(bus) try: await bugbug_utils.task_group_to_push.rem(taskGroupId) except KeyError: pass # Nothing happens for tasks that are not test tasks. payload["body"]["task"]["tags"]["kind"] = "source-test" await bugbug_utils.got_try_task_end(payload) assert bus.queues[QUEUE_PHABRICATOR_RESULTS].empty() # Nothing happens for tasks that are not registered. payload["body"]["task"]["tags"]["kind"] = "test" await bugbug_utils.got_try_task_end(payload) assert bus.queues[QUEUE_PHABRICATOR_RESULTS].empty() push = { "build": build, "revision": "028980a035fb3e214f7645675a01a52234aad0fe", } await bugbug_utils.task_group_to_push.set(taskGroupId, push) payload["body"]["status"]["state"] = "completed" await bugbug_utils.got_try_task_end(payload) mode, build_, extras = await bus.receive(QUEUE_PHABRICATOR_RESULTS) assert mode == "test_result" assert build_ == build assert extras == { "name": "test-linux64-shippable/opt-awsy-tp6-e10s", "result": UnitResultState.Pass, "details": None, } payload["body"]["status"]["state"] = "failed" await bugbug_utils.got_try_task_end(payload) mode, build_, extras = await bus.receive(QUEUE_PHABRICATOR_RESULTS) assert mode == "test_result" assert build_ == build assert extras == { "name": "test-linux64-shippable/opt-awsy-tp6-e10s", "result": UnitResultState.Fail, "details": "https://treeherder.mozilla.org/#/jobs?repo=try&revision=028980a035fb3e214f7645675a01a52234aad0fe&selectedTaskRun=W2SMZ3bYTeanBq-WNpUeHA-0", }
class Events(object): """ Listen to HTTP notifications from phabricator and trigger new try jobs """ def __init__(self, cache_root): # Create message bus shared amongst processes self.bus = MessageBus() publish = taskcluster_config.secrets["PHABRICATOR"].get( "publish", False) # Check the redis support is enabled on Heroku if heroku.in_dyno(): assert self.bus.redis_enabled is True, "Need Redis on Heroku" community_config = taskcluster_config.secrets.get( "taskcluster_community") test_selection_enabled = taskcluster_config.secrets.get( "test_selection_enabled", False) # Run webserver & pulse on web dyno or single instance if not heroku.in_dyno() or heroku.in_web_dyno(): # Create web server self.webserver = WebServer(QUEUE_WEB_BUILDS) self.webserver.register(self.bus) # Create pulse listener exchanges = {} if taskcluster_config.secrets["autoland_enabled"]: logger.info("Autoland ingestion is enabled") # autoland ingestion exchanges[QUEUE_PULSE_AUTOLAND] = [(PULSE_TASK_GROUP_RESOLVED, ["#.gecko-level-3.#"])] # Create pulse listeners for bugbug test selection task and unit test failures. if community_config is not None and test_selection_enabled: exchanges[QUEUE_PULSE_TRY_TASK_END] = [ (PULSE_TASK_COMPLETED, ["#.gecko-level-1.#"]), (PULSE_TASK_FAILED, ["#.gecko-level-1.#"]), # https://bugzilla.mozilla.org/show_bug.cgi?id=1599863 # ( # "exchange/taskcluster-queue/v1/task-exception", # ["#.gecko-level-1.#"], # ), ] self.community_pulse = PulseListener( { QUEUE_PULSE_BUGBUG_TEST_SELECT: [( "exchange/taskcluster-queue/v1/task-completed", ["route.project.relman.bugbug.test_select"], )] }, taskcluster_config.secrets["communitytc_pulse_user"], taskcluster_config.secrets["communitytc_pulse_password"], "communitytc", ) # Manually register to set queue as redis self.community_pulse.bus = self.bus self.bus.add_queue(QUEUE_PULSE_BUGBUG_TEST_SELECT, redis=True) self.bus.add_queue(QUEUE_PULSE_TRY_TASK_END, redis=True) else: self.community_pulse = None if exchanges: self.pulse = PulseListener( exchanges, taskcluster_config.secrets["pulse_user"], taskcluster_config.secrets["pulse_password"], ) # Manually register to set queue as redis self.pulse.bus = self.bus self.bus.add_queue(QUEUE_PULSE_AUTOLAND, redis=True) else: self.pulse = None else: self.bugbug_utils = None self.webserver = None self.pulse = None self.community_pulse = None logger.info("Skipping webserver, bugbug and pulse consumers") # Register queues for workers self.bus.add_queue(QUEUE_PULSE_AUTOLAND, redis=True) self.bus.add_queue(QUEUE_PULSE_BUGBUG_TEST_SELECT, redis=True) self.bus.add_queue(QUEUE_PULSE_TRY_TASK_END, redis=True) self.bus.add_queue(QUEUE_WEB_BUILDS, redis=True) # Run work processes on worker dyno or single instance if not heroku.in_dyno() or heroku.in_worker_dyno(): self.workflow = CodeReview( api_key=taskcluster_config.secrets["PHABRICATOR"]["api_key"], url=taskcluster_config.secrets["PHABRICATOR"]["url"], publish=publish, user_blacklist=taskcluster_config.secrets["user_blacklist"], ) self.workflow.register(self.bus) # Build mercurial worker and queue self.mercurial = MercurialWorker( QUEUE_MERCURIAL, QUEUE_MERCURIAL_APPLIED, repositories=self.workflow.get_repositories( taskcluster_config.secrets["repositories"], cache_root, default_ssh_key=taskcluster_config.secrets["ssh_key"], ), ) self.mercurial.register(self.bus) # Setup monitoring for newly created tasks self.monitoring = Monitoring( taskcluster_config, QUEUE_MONITORING, taskcluster_config.secrets["admins"], MONITORING_PERIOD, ) self.monitoring.register(self.bus) # Setup monitoring for newly created community tasks if community_config is not None: self.community_monitoring = Monitoring( community_taskcluster_config, QUEUE_MONITORING_COMMUNITY, taskcluster_config.secrets["admins"], MONITORING_PERIOD, ) self.community_monitoring.register(self.bus) else: self.community_monitoring = None self.bugbug_utils = BugbugUtils(self.workflow.api) self.bugbug_utils.register(self.bus) else: self.workflow = None self.mercurial = None self.monitoring = None self.community_monitoring = None self.bugbug_utils = None logger.info("Skipping workers consumers") def run(self): consumers = [] # Code review main workflow if self.workflow: consumers += [ # Process Phabricator build received from webserver self.bus.run(self.workflow.process_build, QUEUE_WEB_BUILDS, sequential=False), # Publish results on Phabricator self.bus.run( self.workflow.publish_results, QUEUE_PHABRICATOR_RESULTS, sequential=False, ), # Trigger autoland tasks self.bus.run( self.workflow.trigger_autoland, QUEUE_PULSE_AUTOLAND, sequential=False, ), # Send to phabricator results publication for normal processing and to bugbug for further analysis self.bus.dispatch( QUEUE_MERCURIAL_APPLIED, [QUEUE_PHABRICATOR_RESULTS, QUEUE_BUGBUG_TRY_PUSH], ), ] if self.bugbug_utils: consumers += [ self.bus.run(self.bugbug_utils.process_build, QUEUE_BUGBUG, sequential=False), self.bus.run( self.bugbug_utils.process_push, QUEUE_BUGBUG_TRY_PUSH, sequential=False, ), self.bus.run( self.bugbug_utils.got_try_task_end, QUEUE_PULSE_TRY_TASK_END, sequential=False, ), self.bus.run( self.bugbug_utils.got_bugbug_test_select_end, QUEUE_PULSE_BUGBUG_TEST_SELECT, sequential=False, ), ] # Add mercurial task if self.mercurial: consumers.append(self.mercurial.run()) # Add monitoring task if self.monitoring: consumers.append(self.monitoring.run()) # Add community monitoring task if self.community_monitoring: consumers.append(self.community_monitoring.run()) # Add pulse listener for task results. if self.pulse: consumers.append(self.pulse.run()) # Add communitytc pulse listener for test selection results. if self.community_pulse: consumers.append(self.community_pulse.run()) # Start the web server in its own process if self.webserver: self.webserver.start() if consumers: # Run all tasks concurrently run_tasks(consumers) else: # Keep the web server process running asyncio.get_event_loop().run_forever() # Make sure any pending task is run. run_tasks(asyncio.Task.all_tasks()) # Stop the webserver when other async processes are stopped if self.webserver: self.webserver.stop()