2020-02-10 19:38:42 +03:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
2020-04-06 22:45:54 +03:00
|
|
|
from itertools import count
|
|
|
|
|
2020-02-24 19:16:03 +03:00
|
|
|
import pytest
|
|
|
|
|
2020-04-03 18:30:59 +03:00
|
|
|
from mozci.errors import ChildPushNotFound, ParentPushNotFound, PushNotFound
|
2020-04-03 16:57:19 +03:00
|
|
|
from mozci.push import Push
|
2020-02-24 19:16:03 +03:00
|
|
|
from mozci.util.hgmo import HGMO
|
2020-04-06 22:45:54 +03:00
|
|
|
from mozci.util.taskcluster import get_artifact_url, get_index_url
|
2020-02-24 19:16:03 +03:00
|
|
|
|
|
|
|
|
2020-04-01 19:16:48 +03:00
|
|
|
@pytest.fixture
|
|
|
|
def create_changesets():
|
|
|
|
"""Return a set of changesets in automationrelevance format.
|
|
|
|
|
|
|
|
Ordered from base -> head.
|
|
|
|
"""
|
|
|
|
|
|
|
|
def node(i):
|
|
|
|
i = str(i)
|
|
|
|
pad = "0" * (40 - len(i))
|
|
|
|
return pad + i
|
|
|
|
|
|
|
|
def inner(num, extra=None, head=1):
|
|
|
|
changesets = []
|
|
|
|
for i in reversed(range(head, num + head)):
|
|
|
|
c = {
|
|
|
|
"node": node(i),
|
|
|
|
"parents": [node(i + 1)],
|
|
|
|
"pushhead": node(head),
|
|
|
|
}
|
|
|
|
if isinstance(extra, list):
|
|
|
|
c.update(extra[num - i])
|
|
|
|
elif isinstance(extra, dict):
|
|
|
|
c.update(extra)
|
|
|
|
|
|
|
|
changesets.append(c)
|
|
|
|
|
|
|
|
return changesets
|
|
|
|
|
|
|
|
return inner
|
|
|
|
|
|
|
|
|
2020-02-24 22:28:25 +03:00
|
|
|
def test_create_push(responses):
|
2020-04-01 19:16:48 +03:00
|
|
|
ctx = {
|
|
|
|
"branch": "integration/autoland",
|
|
|
|
"push_id_start": "122",
|
|
|
|
"push_id_end": "123",
|
|
|
|
}
|
2020-02-24 19:16:03 +03:00
|
|
|
responses.add(
|
|
|
|
responses.GET,
|
2020-04-01 19:16:48 +03:00
|
|
|
HGMO.JSON_PUSHES_TEMPLATE.format(**ctx),
|
2020-02-24 19:16:03 +03:00
|
|
|
json={
|
2020-02-26 19:05:30 +03:00
|
|
|
"pushes": {
|
|
|
|
"123": {
|
|
|
|
"changesets": ["123456"],
|
|
|
|
"date": 1213174092,
|
|
|
|
"user": "user@example.org",
|
2020-02-24 19:16:03 +03:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
status=200,
|
|
|
|
)
|
2020-03-23 18:08:27 +03:00
|
|
|
responses.add(
|
|
|
|
responses.GET,
|
|
|
|
HGMO.JSON_TEMPLATE.format(branch="integration/autoland", rev="abcdef"),
|
|
|
|
json={"node": "abcdef"},
|
|
|
|
status=200,
|
|
|
|
)
|
|
|
|
responses.add(
|
|
|
|
responses.GET,
|
|
|
|
HGMO.JSON_TEMPLATE.format(branch="integration/autoland", rev="123456"),
|
|
|
|
json={"node": "123456"},
|
|
|
|
status=200,
|
|
|
|
)
|
2020-02-24 19:16:03 +03:00
|
|
|
|
|
|
|
p1 = Push("abcdef")
|
|
|
|
p2 = p1.create_push(123)
|
2020-02-26 19:05:30 +03:00
|
|
|
assert p2.rev == "123456"
|
2020-02-24 19:16:03 +03:00
|
|
|
assert p2.id == 123
|
|
|
|
assert p2.date == 1213174092
|
2020-03-23 18:08:27 +03:00
|
|
|
|
|
|
|
|
|
|
|
def test_push_does_not_exist(responses):
|
|
|
|
# We hit hgmo when 'rev' is less than 40 characters.
|
|
|
|
rev = "foobar"
|
|
|
|
responses.add(
|
|
|
|
responses.GET,
|
|
|
|
HGMO.JSON_TEMPLATE.format(branch="integration/autoland", rev=rev),
|
|
|
|
json={f"error": "unknown revision '{rev}'"},
|
|
|
|
status=404,
|
|
|
|
)
|
|
|
|
|
|
|
|
with pytest.raises(PushNotFound):
|
|
|
|
Push(rev)
|
|
|
|
|
|
|
|
# Otherwise we need to hit hgmo some other way.
|
|
|
|
rev = "a" * 40
|
|
|
|
responses.add(
|
|
|
|
responses.GET,
|
|
|
|
HGMO.JSON_TEMPLATE.format(branch="integration/autoland", rev=rev),
|
|
|
|
json={f"error": "unknown revision '{rev}'"},
|
|
|
|
status=404,
|
|
|
|
)
|
|
|
|
p = Push(rev)
|
|
|
|
with pytest.raises(PushNotFound):
|
|
|
|
p.id
|
2020-03-31 14:00:42 +03:00
|
|
|
|
|
|
|
|
|
|
|
def test_push_bugs(responses):
|
|
|
|
rev = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
|
|
|
|
|
|
|
responses.add(
|
|
|
|
responses.GET,
|
|
|
|
f"https://hg.mozilla.org/integration/autoland/json-automationrelevance/{rev}",
|
|
|
|
json={
|
|
|
|
"changesets": [
|
|
|
|
{"bugs": [{"no": "1624503"}]},
|
|
|
|
{"bugs": [{"no": "1624503"}]},
|
|
|
|
]
|
|
|
|
},
|
|
|
|
status=200,
|
|
|
|
)
|
|
|
|
|
|
|
|
p = Push(rev)
|
|
|
|
assert p.bugs == {"1624503"}
|
|
|
|
|
|
|
|
|
|
|
|
def test_push_bugs_different(responses):
|
|
|
|
rev = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
|
|
|
|
|
|
|
responses.add(
|
|
|
|
responses.GET,
|
|
|
|
f"https://hg.mozilla.org/integration/autoland/json-automationrelevance/{rev}",
|
|
|
|
json={
|
|
|
|
"changesets": [
|
|
|
|
{"bugs": [{"no": "1617050"}]},
|
|
|
|
{"bugs": [{"no": "1625220"}]},
|
|
|
|
{"bugs": [{"no": "1625220"}]},
|
|
|
|
{"bugs": [{"no": "1625220"}]},
|
|
|
|
{"bugs": [{"no": "1595768"}]},
|
|
|
|
{"bugs": [{"no": "1595768"}]},
|
|
|
|
]
|
|
|
|
},
|
|
|
|
status=200,
|
|
|
|
)
|
|
|
|
|
|
|
|
p = Push(rev)
|
|
|
|
assert p.bugs == {"1617050", "1625220", "1595768"}
|
|
|
|
|
|
|
|
|
|
|
|
def test_push_bugs_multiple(responses):
|
|
|
|
rev = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
|
|
|
|
|
|
|
responses.add(
|
|
|
|
responses.GET,
|
|
|
|
f"https://hg.mozilla.org/integration/autoland/json-automationrelevance/{rev}",
|
|
|
|
json={
|
|
|
|
"changesets": [
|
|
|
|
{"bugs": [{"no": "1617050"}, {"no": "123"}]},
|
|
|
|
{"bugs": [{"no": "1617050"}]},
|
|
|
|
{"bugs": [{"no": "456"}]},
|
|
|
|
]
|
|
|
|
},
|
|
|
|
status=200,
|
|
|
|
)
|
|
|
|
|
|
|
|
p = Push(rev)
|
|
|
|
assert p.bugs == {"123", "456", "1617050"}
|
2020-04-01 19:16:48 +03:00
|
|
|
|
|
|
|
|
|
|
|
def test_push_parent_on_autoland(responses):
|
|
|
|
ctx = {
|
|
|
|
"branch": "integration/autoland",
|
|
|
|
"push_id_start": "121",
|
|
|
|
"push_id_end": "122",
|
|
|
|
}
|
|
|
|
responses.add(
|
|
|
|
responses.GET,
|
|
|
|
HGMO.JSON_PUSHES_TEMPLATE.format(**ctx),
|
|
|
|
json={
|
|
|
|
"pushes": {
|
|
|
|
"122": {
|
|
|
|
"changesets": ["b" * 40],
|
|
|
|
"date": 1213174092,
|
|
|
|
"user": "user@example.org",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
status=200,
|
|
|
|
)
|
|
|
|
|
|
|
|
p1 = Push("a" * 40)
|
|
|
|
p1._id = 123
|
|
|
|
parent = p1.parent
|
|
|
|
|
|
|
|
assert parent.id == 122
|
|
|
|
|
|
|
|
|
|
|
|
def test_push_parent_on_try(responses, create_changesets):
|
|
|
|
changesets = create_changesets(
|
|
|
|
4,
|
|
|
|
[
|
|
|
|
{"phase": "public"},
|
|
|
|
{"phase": "public"},
|
|
|
|
{"phase": "draft"},
|
|
|
|
{"phase": "draft"},
|
|
|
|
],
|
|
|
|
)
|
|
|
|
|
|
|
|
from pprint import pprint
|
|
|
|
|
|
|
|
pprint(changesets, indent=2)
|
|
|
|
head = changesets[-1]["node"]
|
|
|
|
ctx = {"branch": "try", "rev": head}
|
|
|
|
|
|
|
|
# We'll query the initial pushes' changesets first.
|
|
|
|
responses.add(
|
|
|
|
responses.GET,
|
|
|
|
HGMO.AUTOMATION_RELEVANCE_TEMPLATE.format(**ctx),
|
|
|
|
json={"changesets": changesets},
|
|
|
|
status=200,
|
|
|
|
)
|
|
|
|
|
|
|
|
# Should find changesets[1] as the parent and then start searching for it.
|
|
|
|
parent_rev = changesets[1]["node"]
|
|
|
|
|
|
|
|
# First we'll search mozilla-central, but won't find parent_rev.
|
|
|
|
ctx["rev"] = parent_rev
|
|
|
|
ctx["branch"] = "mozilla-central"
|
|
|
|
responses.add(
|
|
|
|
responses.GET,
|
|
|
|
HGMO.AUTOMATION_RELEVANCE_TEMPLATE.format(**ctx),
|
|
|
|
json={f"error": "unknown revision '{parent_rev}'"},
|
|
|
|
status=404,
|
|
|
|
)
|
|
|
|
|
|
|
|
# Next we'll search mozilla-beta, we'll find parent_rev but it's not a push head.
|
|
|
|
ctx["branch"] = "mozilla-beta"
|
|
|
|
changesets = create_changesets(4, {"phase": "public"})
|
|
|
|
responses.add(
|
|
|
|
responses.GET,
|
|
|
|
HGMO.AUTOMATION_RELEVANCE_TEMPLATE.format(**ctx),
|
|
|
|
json={"changesets": changesets},
|
|
|
|
status=200,
|
|
|
|
)
|
|
|
|
|
|
|
|
# Finally we'll search mozilla-release, we find it and it's the push head!
|
|
|
|
ctx["branch"] = "mozilla-release"
|
|
|
|
changesets = create_changesets(2, {"phase": "public"}, head=3)
|
|
|
|
responses.add(
|
|
|
|
responses.GET,
|
|
|
|
HGMO.AUTOMATION_RELEVANCE_TEMPLATE.format(**ctx),
|
|
|
|
json={"changesets": changesets},
|
|
|
|
status=200,
|
|
|
|
)
|
|
|
|
|
|
|
|
# Now run it and assert.
|
|
|
|
push = Push(head, branch="try")
|
|
|
|
parent = push.parent
|
|
|
|
assert parent.rev == parent_rev
|
|
|
|
assert parent.branch == "mozilla-release"
|
|
|
|
|
|
|
|
|
|
|
|
def test_push_parent_on_try_fails_with_merge_commit(responses, create_changesets):
|
|
|
|
ctx = {
|
|
|
|
"branch": "try",
|
|
|
|
"rev": "a" * 40,
|
|
|
|
}
|
|
|
|
|
|
|
|
# Finding parent fails on merge commits.
|
|
|
|
responses.add(
|
|
|
|
responses.GET,
|
|
|
|
HGMO.AUTOMATION_RELEVANCE_TEMPLATE.format(**ctx),
|
|
|
|
json={"changesets": create_changesets(1, {"parents": ["b" * 40, "c" * 40]})},
|
|
|
|
status=200,
|
|
|
|
)
|
|
|
|
|
|
|
|
push = Push(ctx["rev"], ctx["branch"])
|
|
|
|
with pytest.raises(ParentPushNotFound):
|
|
|
|
push.parent
|
|
|
|
|
|
|
|
|
|
|
|
def test_push_parent_on_try_fails_when_not_a_push_head(responses, create_changesets):
|
|
|
|
changesets = create_changesets(3)
|
|
|
|
head = changesets[-1]["node"]
|
|
|
|
ctx = {
|
|
|
|
"branch": "try",
|
|
|
|
"rev": head,
|
|
|
|
}
|
|
|
|
responses.add(
|
|
|
|
responses.GET,
|
|
|
|
HGMO.AUTOMATION_RELEVANCE_TEMPLATE.format(**ctx),
|
|
|
|
json={"changesets": changesets},
|
|
|
|
status=200,
|
|
|
|
)
|
|
|
|
|
|
|
|
# We raise if rev is not found or a push head anywhere.
|
|
|
|
ctx["rev"] = changesets[0]["parents"][0]
|
|
|
|
for branch in (
|
|
|
|
"mozilla-central",
|
|
|
|
"mozilla-beta",
|
|
|
|
"mozilla-release",
|
|
|
|
"integration/autoland",
|
|
|
|
):
|
|
|
|
ctx["branch"] = branch
|
|
|
|
responses.add(
|
|
|
|
responses.GET,
|
|
|
|
HGMO.AUTOMATION_RELEVANCE_TEMPLATE.format(**ctx),
|
|
|
|
json={"changesets": changesets},
|
|
|
|
status=200,
|
|
|
|
)
|
|
|
|
|
|
|
|
push = Push(head, branch="try")
|
|
|
|
with pytest.raises(ParentPushNotFound):
|
|
|
|
push.parent
|
2020-04-03 18:30:59 +03:00
|
|
|
|
|
|
|
|
|
|
|
def test_push_child_raises(responses):
|
|
|
|
rev = "a" * 40
|
|
|
|
|
|
|
|
# Try and mozilla-unified are not supported.
|
|
|
|
for branch in ("try", "mozilla-unified"):
|
|
|
|
push = Push(rev, branch=branch)
|
|
|
|
with pytest.raises(ChildPushNotFound):
|
|
|
|
push.child
|
|
|
|
|
|
|
|
# A push with no children raises.
|
|
|
|
push = Push(rev, branch="integration/autoland")
|
|
|
|
push._id = 100
|
|
|
|
url = HGMO.JSON_PUSHES_TEMPLATE.format(
|
|
|
|
branch=push.branch, push_id_start=push.id, push_id_end=push.id + 1,
|
|
|
|
)
|
|
|
|
responses.add(
|
|
|
|
responses.GET, url, json={"lastpushid": push.id, "pushes": {}}, status=200,
|
|
|
|
)
|
|
|
|
|
|
|
|
with pytest.raises(ChildPushNotFound):
|
|
|
|
push.child
|
2020-04-06 22:45:54 +03:00
|
|
|
|
|
|
|
|
|
|
|
def test_generate_all_shadow_scheduler_tasks(responses):
|
|
|
|
rev = "a" * 40
|
|
|
|
shadow_schedulers = (
|
|
|
|
(
|
|
|
|
"bar",
|
|
|
|
["task-1", "task-3", "task-4"],
|
|
|
|
), # names will be generated alphabetically
|
|
|
|
("foo", ["task-2", "task-4"]),
|
|
|
|
)
|
|
|
|
|
|
|
|
push = Push(rev)
|
|
|
|
responses.add(
|
|
|
|
responses.GET,
|
|
|
|
get_index_url(push.index + ".taskgraph.decision"),
|
|
|
|
json={"taskId": 1},
|
|
|
|
status=200,
|
|
|
|
)
|
|
|
|
|
|
|
|
id = count(2)
|
|
|
|
responses.add(
|
|
|
|
responses.GET,
|
|
|
|
get_artifact_url(1, "public/task-graph.json"),
|
|
|
|
json={
|
|
|
|
next(id): {"label": f"source-test-shadow-scheduler-{s[0]}"}
|
|
|
|
for s in shadow_schedulers
|
|
|
|
},
|
|
|
|
status=200,
|
|
|
|
)
|
|
|
|
|
|
|
|
id = count(2)
|
|
|
|
for s in shadow_schedulers:
|
|
|
|
s_id = next(id)
|
|
|
|
responses.add(
|
|
|
|
responses.GET,
|
|
|
|
get_index_url(push.index + f".source.shadow-scheduler-{s[0]}"),
|
|
|
|
json={"taskId": s_id},
|
|
|
|
status=200,
|
|
|
|
)
|
|
|
|
|
|
|
|
responses.add(
|
|
|
|
responses.GET,
|
|
|
|
get_artifact_url(s_id, "public/shadow-scheduler/optimized_tasks.list"),
|
|
|
|
stream=True,
|
|
|
|
body="\n".join(s[1]),
|
|
|
|
status=200,
|
|
|
|
)
|
|
|
|
|
|
|
|
# retrieve the data
|
|
|
|
for i, (name, tasks) in enumerate(push.generate_all_shadow_scheduler_tasks()):
|
|
|
|
print(i, name, tasks)
|
|
|
|
assert name == shadow_schedulers[i][0]
|
|
|
|
assert tasks == set(shadow_schedulers[i][1])
|