Example #1
0
def make_tasks(group_id):
    return [
        TestTask(
            id=j,
            label=f"test-task{j}",
            result="failed",
            _results=[GroupResult(group=group_id, ok=False)],
        ) for j in range(1, 4)
    ]
Example #2
0
    def _normalized_tasks(tasks):
        # If we are missing one of these keys, discard the task.
        required_keys = (
            "id",
            "label",
            "result",
        )

        # Normalize and validate.
        normalized_tasks = []
        for task in tasks.values():
            missing = [k for k in required_keys if k not in task]
            taskstr = task.get("label", task["id"])

            if missing:
                logger.trace(
                    f"Skipping task '{taskstr}' because it is missing "
                    f"the following attributes: {', '.join(missing)}")
                continue

            if task.get("tags"):
                task["tags"] = {t["name"]: t["value"] for t in task["tags"]}

            if task.get("classification_note"):
                if isinstance(task["classification_note"], list):
                    task["classification_note"] = task["classification_note"][
                        -1]

            groups = task.pop("_result_group", None)
            oks = task.pop("_result_ok", None)

            if groups is not None:
                if oks:
                    task["_results"] = [
                        GroupResult(group=group, ok=ok)
                        for group, ok in zip(groups, oks)
                    ]

            normalized_tasks.append(task)

        return [Task.create(**task) for task in normalized_tasks]
Example #3
0
def test_results_for_incomplete_task(responses):
    push = FakePush("autoland", "rev")

    for state in ["running", "pending", "unscheduled", "exception"]:
        task = Task.create(
            id=1,
            label="test-task",
            state="running",
        )
        task.retrieve_results(push)
        assert task.results == []

    responses.add(
        responses.GET,
        "https://firefox-ci-tc.services.mozilla.com/api/queue/v1/task/1/artifacts",
        json={
            "artifacts": [{"name": "errorsummary.log"}],
        },
        status=200,
    )

    responses.add(
        responses.GET,
        "https://firefox-ci-tc.services.mozilla.com/api/queue/v1/task/1/artifacts/errorsummary.log",
        body=r"""
            {"action": "test_groups", "line": 3, "groups": ["layout/base/tests/browser.ini"]}
            {"status": "OK", "duration": 12430, "line": 4465, "group": "layout/base/tests/browser.ini", "action": "group_result"}
        """.strip(),
        status=200,
    )

    task = Task.create(
        id=1,
        label="test-task",
        state="completed",
    )
    task.retrieve_results(push)
    assert task.results == [
        GroupResult(group="layout/base/tests/browser.ini", ok=True, duration=12430),
    ]
Example #4
0
    def tasks(self):
        """All tasks that ran on the push, including retriggers and backfills.

        Returns:
            list: A list of `Task` objects.
        """

        args = Namespace(rev=self.rev, branch=self.branch)
        tasks = defaultdict(dict)
        retries = defaultdict(int)

        list_keys = (
            "_result_ok",
            "_result_group",
        )

        def add(result):
            if "header" in result:
                result["data"] = [
                    {
                        field: value
                        for field, value in zip(result["header"], entry)
                        if value is not None
                    }
                    for entry in result["data"]
                ]

            for task in result["data"]:
                if "id" not in task:
                    logger.trace(f"Skipping {task} because of missing id.")
                    continue

                task_id = task["id"]

                # If a task is re-run, use the data from the last run.
                if "retry_id" in task:
                    if task["retry_id"] < retries[task_id]:
                        logger.trace(
                            f"Skipping {task} because there is a newer run of it."
                        )
                        continue

                    retries[task_id] = task["retry_id"]

                    # We don't need to store the retry ID.
                    del task["retry_id"]

                cur_task = tasks[task_id]

                for key, val in task.items():
                    if key in list_keys:
                        if key not in cur_task:
                            cur_task[key] = []

                        cur_task[key].append(val)
                    else:
                        cur_task[key] = val

        # Gather information from the treeherder table.
        try:
            add(run_query("push_tasks_from_treeherder", args))
        except MissingDataError:
            pass

        # Gather information from the unittest table. We allow missing data for this table because
        # ActiveData only holds very recent data in it, but we have fallbacks on Taskcluster
        # artifacts.
        # TODO: We have fallbacks for groups and results, but not for kind.
        try:
            add(run_query("push_tasks_results_from_unittest", args))
        except MissingDataError:
            pass

        try:
            add(run_query("push_tasks_groups_from_unittest", args))
        except MissingDataError:
            pass

        # If we are missing one of these keys, discard the task.
        required_keys = (
            "id",
            "label",
        )

        # Normalize and validate.
        normalized_tasks = []
        for task in tasks.values():
            missing = [k for k in required_keys if k not in task]
            taskstr = task.get("label", task["id"])

            if missing:
                logger.trace(
                    f"Skipping task '{taskstr}' because it is missing "
                    f"the following attributes: {', '.join(missing)}"
                )
                continue

            if task.get("tags"):
                task["tags"] = {t["name"]: t["value"] for t in task["tags"]}

            if task.get("classification_note"):
                if isinstance(task["classification_note"], list):
                    task["classification_note"] = task["classification_note"][-1]

            if task.get("_groups"):
                if isinstance(task["_groups"], str):
                    task["_groups"] = [task["_groups"]]

            if task.get("_result_ok"):
                oks = task.pop("_result_ok")

                if task.get("_result_group"):
                    groups = task.pop("_result_group")

                    task["_results"] = [
                        GroupResult(group=group, ok=ok)
                        for group, ok in zip(groups, oks)
                    ]

            normalized_tasks.append(task)

        return [Task.create(**task) for task in normalized_tasks]
Example #5
0
def test_GroupSummary_classifications():
    task1 = Task.create(
        id=1,
        label="test-task1",
        result="failed",
        classification="fixed by commit",
        classification_note="xxx",
    )
    task1._results = [GroupResult("group1", False, duration=42)]
    assert GroupSummary("group1", [task1]).classifications == [
        ("fixed by commit", "xxx")
    ]
    with pytest.raises(AssertionError):
        GroupSummary("group2", [task1])

    task1 = Task.create(
        id=1,
        label="test-task1",
        result="failed",
        classification="fixed by commit",
        classification_note="xxx",
    )
    task1._results = [
        GroupResult("group1", False, duration=42),
        GroupResult("group2", False, duration=42),
    ]
    assert GroupSummary("group1", [task1]).classifications == [
        ("fixed by commit", "xxx")
    ]
    assert GroupSummary("group2", [task1]).classifications == [
        ("fixed by commit", "xxx")
    ]

    task1 = Task.create(
        id=1, label="test-task1", result="failed", classification="intermittent"
    )
    task1._results = [
        GroupResult("group1", False, duration=42),
        GroupResult("group2", False, duration=42),
    ]
    assert GroupSummary("group1", [task1]).classifications == [("intermittent", None)]
    assert GroupSummary("group2", [task1]).classifications == [("intermittent", None)]

    task1 = Task.create(
        id=1,
        label="test-task1",
        result="failed",
        classification="fixed by commit",
        classification_note="xxx",
    )
    task1._results = [
        GroupResult("group1", True, duration=42),
        GroupResult("group2", False, duration=42),
    ]
    assert GroupSummary("group1", [task1]).classifications == []
    assert GroupSummary("group2", [task1]).classifications == [
        ("fixed by commit", "xxx")
    ]

    task1 = Task.create(
        id=1,
        label="test-task1",
        result="failed",
        classification="fixed by commit",
        classification_note="xxx",
    )
    task1._results = [
        GroupResult("group1", True, duration=42),
        GroupResult("group2", False, duration=42),
    ]
    task2 = Task.create(
        id=1, label="test-task1", result="failed", classification="intermittent"
    )
    task2._results = [
        GroupResult("group1", False, duration=42),
        GroupResult("group2", False, duration=42),
    ]
    assert GroupSummary("group1", [task1, task2]).classifications == [
        ("intermittent", None)
    ]
    assert GroupSummary("group2", [task1, task2]).classifications == [
        ("fixed by commit", "xxx"),
        ("intermittent", None),
    ]
Example #6
0
# -*- coding: utf-8 -*-

import json
import re

import pytest

from mozci.errors import ArtifactNotFound, TaskNotFound
from mozci.task import GroupResult, GroupSummary, Task
from mozci.util.taskcluster import get_artifact_url, get_index_url

GR_2 = GroupResult(group="group2", ok=True, duration=42)
GR_3 = GroupResult(group="group2", ok=True, duration=42)


class FakePush:
    def __init__(self, branch, rev):
        self.branch = branch
        self.rev = rev


@pytest.fixture
def create_task():
    id = 0

    def inner(**kwargs):
        nonlocal id
        task = Task.create(id=id, **kwargs)
        id += 1
        return task
Example #7
0
    ],
}

NUMBER_OF_DEFAULT_GROUPS = 5
NUMBER_OF_INTERMITTENT_GROUPS_IN_DEFAULT = 2
GROUP_SUMMARIES_DEFAULT = {
    group.name: group
    for group in [
        GroupSummary(
            f"group{i}",
            [
                Task.create(
                    id=j,
                    label=f"test-task{j}",
                    result="failed",
                    _results=[GroupResult(group=f"group{i}", ok=False)],
                ) for j in range(1, 4)
            ] + ([
                Task.create(
                    id=4,
                    label="test-task1",
                    result="passed",
                    _results=[GroupResult(group=f"group{i}", ok=True)],
                )
            ] if i <= NUMBER_OF_INTERMITTENT_GROUPS_IN_DEFAULT else []),
        ) for i in range(1, NUMBER_OF_DEFAULT_GROUPS + 1)
    ]
}


def test_group_summaries_default_status():
Example #8
0
    def tasks(self):
        """All tasks that ran on the push, including retriggers and backfills.

        Returns:
            list: A list of `Task` objects.
        """
        # Gather information from the treeherder table.
        tasks = []
        try:
            tasks = data.handler.get("push_tasks",
                                     branch=self.branch,
                                     rev=self.rev)
        except MissingDataError:
            pass

        # Gather task tags from the task table.
        try:
            tags_by_task = data.handler.get("push_tasks_tags",
                                            branch=self.branch,
                                            rev=self.rev)
            for task in tasks:
                tags = tags_by_task.get(task["id"])
                if tags:
                    if "tags" not in task:
                        task["tags"] = {}
                    task["tags"].update(tags)
        except MissingDataError:
            pass

        # Let's gather error/results from cache or AD/Taskcluster
        test_tasks_results = config.cache.get(self.push_uuid, {})
        was_cached = len(test_tasks_results.keys()) != 0
        groups = None
        if not was_cached:
            # Gather information from the unittest table. We allow missing data for this table because
            # ActiveData only holds very recent data in it, but we have fallbacks on Taskcluster
            # artifacts.
            try:
                groups = data.handler.get("push_test_groups",
                                          branch=self.branch,
                                          rev=self.rev)
                for task in tasks:
                    results = groups.get(task["id"])
                    if results is not None:
                        task["_results"] = [
                            GroupResult(group=group, ok=ok)
                            for group, ok in results.items()
                        ]

            except MissingDataError:
                pass

        tasks = [Task.create(**task) for task in tasks]

        # Add any data available in the cache
        if was_cached:
            # Let's add cached error summaries to TestTasks
            for t in tasks:
                if isinstance(t, TestTask):
                    error_summary = test_tasks_results.get(t.id)
                    # Only Test tasks have stored error summary information in the cache
                    if error_summary:
                        t._errors = error_summary["errors"]
                        t._results = error_summary["results"]
            logger.debug(
                "Fetched tasks errors/results for {} from the cache".format(
                    self.push_uuid))

        # Gather group data which could have been missing in ActiveData.
        concurrent.futures.wait(
            [
                Push.THREAD_POOL_EXECUTOR.submit(lambda task: task.groups,
                                                 task)
                for task in tasks if isinstance(task, TestTask)
            ],
            return_when=concurrent.futures.FIRST_EXCEPTION,
        )

        # Now we can cache the results.
        self._cache_test_tasks(tasks)

        return tasks
Example #9
0
# -*- coding: utf-8 -*-

import json

import pytest

from mozci.errors import ArtifactNotFound, TaskNotFound
from mozci.task import GroupResult, GroupSummary, Task
from mozci.util.taskcluster import get_artifact_url, get_index_url

GR_2 = GroupResult(group="group2", ok=True)
GR_3 = GroupResult(group="group2", ok=True)


class FakePush:
    def __init__(self, branch, rev):
        self.branch = branch
        self.rev = rev


@pytest.fixture
def create_task():
    id = 0

    def inner(**kwargs):
        nonlocal id
        task = Task.create(id=id, **kwargs)
        id += 1
        return task

    return inner
Example #10
0
}

NUMBER_OF_DEFAULT_GROUPS = 5
NUMBER_OF_INTERMITTENT_GROUPS_IN_DEFAULT = 2
GROUP_SUMMARIES_DEFAULT = {
    group.name: group
    for group in [
        GroupSummary(
            f"group{i}",
            [
                Task.create(
                    id=j,
                    label=f"test-task{j}",
                    result="failed",
                    _results=[
                        GroupResult(group=f"group{i}", ok=False, duration=42)
                    ],
                ) for j in range(1, 4)
            ] + ([
                Task.create(
                    id=4,
                    label="test-task1",
                    result="passed",
                    _results=[
                        GroupResult(group=f"group{i}", ok=True, duration=42)
                    ],
                )
            ] if i <= NUMBER_OF_INTERMITTENT_GROUPS_IN_DEFAULT else []),
        ) for i in range(1, NUMBER_OF_DEFAULT_GROUPS + 1)
    ]
}