Exemple #1
0
    def get(self, job_id):
        job = Job.query.options(
            subqueryload_all(Job.phases),
            joinedload('project', innerjoin=True),
        ).get(job_id)
        if job is None:
            return '', 404

        previous_runs = Job.query.filter(
            Job.project == job.project,
            Job.date_created < job.date_created,
            Job.status == Status.finished,
            Job.id != job.id,
        ).order_by(Job.date_created.desc())[:NUM_PREVIOUS_RUNS]

        # find all parent groups (root trees)
        test_groups = sorted(TestGroup.query.filter(
            TestGroup.job_id == job.id,
            TestGroup.parent_id == None,  # NOQA: we have to use == here
        ), key=lambda x: x.name)

        test_failures = TestGroup.query.options(
            joinedload('parent'),
        ).filter(
            TestGroup.job_id == job.id,
            TestGroup.result == Result.failed,
            TestGroup.num_leaves == 0,
        ).order_by(TestGroup.name.asc())
        num_test_failures = test_failures.count()
        test_failures = test_failures[:25]

        if test_failures:
            failure_origins = find_failure_origins(
                job, test_failures)
            for test_failure in test_failures:
                test_failure.origin = failure_origins.get(test_failure)

        extended_serializers = {
            TestGroup: TestGroupWithOriginSerializer(),
        }

        log_sources = list(LogSource.query.options(
            joinedload('step'),
        ).filter(
            LogSource.job_id == job.id,
        ).order_by(LogSource.date_created.asc()))

        context = self.serialize(job)
        context.update({
            'phases': job.phases,
            'testFailures': {
                'total': num_test_failures,
                'testGroups': self.serialize(test_failures, extended_serializers),
            },
            'logs': log_sources,
            'testGroups': test_groups,
            'previousRuns': previous_runs,
        })

        return self.respond(context)
Exemple #2
0
    def get(self, job_id):
        job = Job.query.options(
            subqueryload_all(Job.phases),
            joinedload('project', innerjoin=True),
        ).get(job_id)
        if job is None:
            return '', 404

        previous_runs = Job.query.filter(
            Job.project == job.project,
            Job.date_created < job.date_created,
            Job.status == Status.finished,
            Job.id != job.id,
        ).order_by(Job.date_created.desc())[:NUM_PREVIOUS_RUNS]

        test_failures = TestCase.query.filter(
            TestCase.job_id == job.id,
            TestCase.result == Result.failed,
        ).order_by(TestCase.name.asc())
        num_test_failures = test_failures.count()
        test_failures = test_failures[:25]

        if test_failures:
            failure_origins = find_failure_origins(job, test_failures)
            for test_failure in test_failures:
                test_failure.origin = failure_origins.get(test_failure)

        extended_serializers = {
            TestCase: TestCaseWithOriginSerializer(),
        }

        log_sources = list(
            LogSource.query.options(joinedload('step'), ).filter(
                LogSource.job_id == job.id, ).order_by(
                    LogSource.date_created.asc()))

        context = self.serialize(job)
        context.update({
            'phases': job.phases,
            'testFailures': {
                'total': num_test_failures,
                'tests': self.serialize(test_failures, extended_serializers),
            },
            'logs': log_sources,
            'previousRuns': previous_runs,
        })

        return self.respond(context)
    def get(self, job_id):
        job = Job.query.options(
            joinedload('project', innerjoin=True),
        ).get(job_id)
        if job is None:
            return '', 404

        previous_runs = Job.query.filter(
            Job.project == job.project,
            Job.date_created < job.date_created,
            Job.status == Status.finished,
            Job.id != job.id,
        ).order_by(Job.date_created.desc())[:NUM_PREVIOUS_RUNS]

        test_failures = TestCase.query.filter(
            TestCase.job_id == job.id,
            TestCase.result == Result.failed,
        ).order_by(TestCase.name.asc())
        num_test_failures = test_failures.count()
        test_failures = test_failures[:25]

        if test_failures:
            failure_origins = find_failure_origins(
                job.build, test_failures)
            for test_failure in test_failures:
                test_failure.origin = failure_origins.get(test_failure)

        extended_serializers = {
            TestCase: TestCaseWithOriginSerializer(),
        }

        log_sources = list(LogSource.query.options(
            joinedload('step'),
        ).filter(
            LogSource.job_id == job.id,
        ).order_by(LogSource.date_created.asc()))

        context = self.serialize(job)
        context.update({
            'testFailures': {
                'total': num_test_failures,
                'tests': self.serialize(test_failures, extended_serializers),
            },
            'logs': log_sources,
            'previousRuns': previous_runs,
        })

        return self.respond(context)
    def get(self, job_id):
        job = Job.query.options(
            joinedload('project', innerjoin=True),
        ).get(job_id)
        if job is None:
            return '', 404

        test_failures = TestCase.query.filter(
            TestCase.job_id == job.id,
            TestCase.result == Result.failed,
        ).order_by(TestCase.name.asc())
        num_test_failures = test_failures.count()
        test_failures = test_failures[:25]

        if test_failures:
            failure_origins = find_failure_origins(
                job.build, test_failures)
            for test_failure in test_failures:
                test_failure.origin = failure_origins.get(test_failure)

        extended_serializers = {
            TestCase: TestCaseWithOriginCrumbler(),
        }

        # Restricting to matching JobStep ids when querying LogSources
        # allows us to use the LogSource.step_id index, which makes
        # the query significantly faster.
        jobstep_ids = db.session.query(JobStep.id).filter(
            JobStep.job_id == job.id
        ).subquery()
        log_sources = list(LogSource.query.options(
            joinedload('step').joinedload('node'),
        ).filter(
            LogSource.job_id == job.id,
            LogSource.project_id == job.project_id,
            LogSource.step_id.in_(jobstep_ids),
        ).order_by(LogSource.date_created.asc()))

        context = self.serialize(job)
        context.update({
            'testFailures': {
                'total': num_test_failures,
                'tests': self.serialize(test_failures, extended_serializers),
            },
            'logs': log_sources,
        })

        return self.respond(context)
Exemple #5
0
    def test_simple(self):
        source = self.create_source(self.project)
        build_a = self.create_build(project=self.project,
                                    result=Result.passed,
                                    status=Status.finished,
                                    label='build a',
                                    date_created=datetime(
                                        2013, 9, 19, 22, 15, 22),
                                    source=source)
        job_a = self.create_job(build=build_a)
        build_b = self.create_build(project=self.project,
                                    result=Result.failed,
                                    status=Status.finished,
                                    label='build b',
                                    date_created=datetime(
                                        2013, 9, 19, 22, 15, 23),
                                    source=source)
        job_b = self.create_job(build=build_b)
        build_c = self.create_build(project=self.project,
                                    result=Result.failed,
                                    status=Status.finished,
                                    label='build c',
                                    date_created=datetime(
                                        2013, 9, 19, 22, 15, 24),
                                    source=source)
        job_c = self.create_job(build=build_c)
        build_d = self.create_build(project=self.project,
                                    result=Result.failed,
                                    status=Status.finished,
                                    label='build d',
                                    date_created=datetime(
                                        2013, 9, 19, 22, 15, 25),
                                    source=source)
        job_d = self.create_job(build=build_d)

        self.create_test(job_a, name='foo', result=Result.passed)
        self.create_test(job_a, name='bar', result=Result.passed)
        self.create_test(job_b, name='foo', result=Result.failed)
        self.create_test(job_b, name='bar', result=Result.passed)
        self.create_test(job_c, name='foo', result=Result.failed)
        self.create_test(job_c, name='bar', result=Result.failed)
        foo_d = self.create_test(job_d, name='foo', result=Result.failed)
        bar_d = self.create_test(job_d, name='bar', result=Result.failed)

        result = find_failure_origins(build_d, [foo_d, bar_d])
        assert result == {foo_d: build_b, bar_d: build_c}
Exemple #6
0
    def test_simple(self):
        project = self.create_project()
        source = self.create_source(project)
        build_a = self.create_build(
            project=project, result=Result.passed, status=Status.finished,
            label='build a', date_created=datetime(2013, 9, 19, 22, 15, 22),
            source=source)
        job_a = self.create_job(build=build_a)
        build_b = self.create_build(
            project=project, result=Result.failed, status=Status.finished,
            label='build b', date_created=datetime(2013, 9, 19, 22, 15, 23),
            source=source)
        job_b = self.create_job(build=build_b)
        build_c = self.create_build(
            project=project, result=Result.failed, status=Status.finished,
            label='build c', date_created=datetime(2013, 9, 19, 22, 15, 24),
            source=source)
        job_c = self.create_job(build=build_c)
        build_d = self.create_build(
            project=project, result=Result.failed, status=Status.finished,
            label='build d', date_created=datetime(2013, 9, 19, 22, 15, 25),
            source=source)
        job_d = self.create_job(build=build_d)

        self.create_test(job_a, name='foo', result=Result.passed)
        self.create_test(job_a, name='bar', result=Result.passed)
        self.create_test(job_b, name='foo', result=Result.failed)
        self.create_test(job_b, name='bar', result=Result.passed)
        self.create_test(job_c, name='foo', result=Result.failed)
        self.create_test(job_c, name='bar', result=Result.failed)
        foo_d = self.create_test(job_d, name='foo', result=Result.failed)
        bar_d = self.create_test(job_d, name='bar', result=Result.failed)

        result = find_failure_origins(build_d, [foo_d, bar_d])
        assert result == {
            foo_d: build_b,
            bar_d: build_c
        }
Exemple #7
0
    def get(self, job_id):
        job = Job.query.options(joinedload('project',
                                           innerjoin=True), ).get(job_id)
        if job is None:
            return '', 404

        test_failures = TestCase.query.filter(
            TestCase.job_id == job.id,
            TestCase.result == Result.failed,
        ).order_by(TestCase.name.asc())
        num_test_failures = test_failures.count()
        test_failures = test_failures[:25]

        if test_failures:
            failure_origins = find_failure_origins(job.build, test_failures)
            for test_failure in test_failures:
                test_failure.origin = failure_origins.get(test_failure)

        extended_serializers = {
            TestCase: TestCaseWithOriginCrumbler(),
        }

        log_sources = list(
            LogSource.query.options(joinedload('step'), ).filter(
                LogSource.job_id == job.id,
                LogSource.project_id == job.project_id,
            ).order_by(LogSource.date_created.asc()))

        context = self.serialize(job)
        context.update({
            'testFailures': {
                'total': num_test_failures,
                'tests': self.serialize(test_failures, extended_serializers),
            },
            'logs': log_sources,
        })

        return self.respond(context)
Exemple #8
0
    def get(self, job_id):
        job = Job.query.options(joinedload("project", innerjoin=True)).get(job_id)
        if job is None:
            return "", 404

        test_failures = TestCase.query.filter(TestCase.job_id == job.id, TestCase.result == Result.failed).order_by(
            TestCase.name.asc()
        )
        num_test_failures = test_failures.count()
        test_failures = test_failures[:25]

        if test_failures:
            failure_origins = find_failure_origins(job.build, test_failures)
            for test_failure in test_failures:
                test_failure.origin = failure_origins.get(test_failure)

        extended_serializers = {TestCase: TestCaseWithOriginSerializer()}

        log_sources = list(
            LogSource.query.options(joinedload("step"))
            .filter(LogSource.job_id == job.id)
            .order_by(LogSource.date_created.asc())
        )

        context = self.serialize(job)
        context.update(
            {
                "testFailures": {
                    "total": num_test_failures,
                    "tests": self.serialize(test_failures, extended_serializers),
                },
                "logs": log_sources,
            }
        )

        return self.respond(context)
Exemple #9
0
    def get(self, build_id):
        build = Build.query.options(
            joinedload('project', innerjoin=True),
            joinedload('author'),
            joinedload('source').joinedload('revision'),
            subqueryload_all('stats'),
        ).get(build_id)
        if build is None:
            return '', 404

        jobs = list(Job.query.filter(
            Job.build_id == build.id,
        ))

        # identify failures
        if not jobs:
            # If we have no jobs, the query to find failures becomes very expensive due to `in_([])` being
            # handled poorly, but since we know the result, we can just set it.
            test_failures = []
            num_test_failures = 0
        else:
            test_failures = TestCase.query.options(
                joinedload('job', innerjoin=True),
            ).filter(
                TestCase.job_id.in_([j.id for j in jobs]),
                TestCase.result == Result.failed,
            ).order_by(TestCase.name.asc())
            num_test_failures = test_failures.count()
            test_failures = test_failures[:25]

        failures_by_job = defaultdict(list)
        for failure in test_failures:
            failures_by_job[failure.job].append(failure)

        failure_origins = find_failure_origins(
            build, test_failures)
        for test_failure in test_failures:
            test_failure.origin = failure_origins.get(test_failure)

        seen_by = list(User.query.join(
            BuildSeen, BuildSeen.user_id == User.id,
        ).filter(
            BuildSeen.build_id == build.id,
        ))

        extended_serializers = {
            TestCase: TestCaseWithOriginCrumbler(),
        }

        event_list = list(Event.query.filter(
            Event.item_id == build.id,
            Event.type.in_(ALL_EVENT_TYPES)
        ).order_by(Event.date_created.desc()))

        context = self.serialize(build)
        context.update({
            'jobs': jobs,
            'seenBy': seen_by,
            'events': event_list,
            'failures': get_failure_reasons(build),
            'testFailures': {
                'total': num_test_failures,
                'tests': self.serialize(test_failures, extended_serializers),
            },
            'parents': self.serialize(get_parents_last_builds(build)),
            'containsAutogeneratedPlan': build_lib.contains_autogenerated_plan(build),
        })

        return self.respond(context)
Exemple #10
0
    def get(self, build_id):
        build = Build.query.options(
            joinedload("project", innerjoin=True), joinedload("author"), joinedload("source")
        ).get(build_id)
        if build is None:
            return "", 404

        previous_runs = (
            Build.query.filter(
                Build.project == build.project,
                Build.date_created < build.date_created,
                Build.status == Status.finished,
                Build.id != build.id,
                Build.patch == None,  # NOQA
            )
            .options(joinedload("source"), joinedload("author"))
            .order_by(Build.date_created.desc())[:NUM_PREVIOUS_RUNS]
        )

        if previous_runs:
            most_recent_run = previous_runs[0]
        else:
            most_recent_run = None

        jobs = list(Job.query.filter(Job.build_id == build.id))

        # identify failures
        test_failures = (
            TestGroup.query.options(joinedload("parent"), joinedload("job", innerjoin=True))
            .filter(
                TestGroup.job_id.in_([j.id for j in jobs]), TestGroup.result == Result.failed, TestGroup.num_leaves == 0
            )
            .order_by(TestGroup.name.asc())
        )
        num_test_failures = test_failures.count()
        test_failures = test_failures[:25]

        failures_by_job = defaultdict(list)
        for failure in test_failures:
            failures_by_job[failure.job].append(failure)

        failure_origins = find_failure_origins(build, test_failures)
        for test_failure in test_failures:
            test_failure.origin = failure_origins.get(test_failure)

        # identify added/removed tests
        if most_recent_run and build.status == Status.finished:
            changed_tests = find_changed_tests(build, most_recent_run)
        else:
            changed_tests = []

        seen_by = list(User.query.join(BuildSeen, BuildSeen.user_id == User.id).filter(BuildSeen.build_id == build.id))

        extended_serializers = {TestGroup: TestGroupWithOriginSerializer()}

        context = self.serialize(build)
        context.update(
            {
                "jobs": jobs,
                "previousRuns": previous_runs,
                "seenBy": seen_by,
                "testFailures": {
                    "total": num_test_failures,
                    "testGroups": self.serialize(test_failures, extended_serializers),
                },
                "testChanges": self.serialize(changed_tests, extended_serializers),
            }
        )

        return self.respond(context)
Exemple #11
0
    def get(self, build_id):
        build = Build.query.options(
            joinedload('project', innerjoin=True),
            joinedload('author'),
            joinedload('source'),
        ).get(build_id)
        if build is None:
            return '', 404

        previous_runs = Build.query.filter(
            Build.project == build.project,
            Build.date_created < build.date_created,
            Build.status == Status.finished,
            Build.id != build.id,
            Source.patch_id == None,  # NOQA
        ).join(
            Source, Build.source_id == Source.id,
        ).options(
            contains_eager('source'),
            joinedload('author'),
        ).order_by(Build.date_created.desc())[:NUM_PREVIOUS_RUNS]

        if previous_runs:
            most_recent_run = previous_runs[0]
        else:
            most_recent_run = None

        jobs = list(Job.query.filter(
            Job.build_id == build.id,
        ))

        # identify failures
        test_failures = TestCase.query.options(
            joinedload('job', innerjoin=True),
        ).filter(
            TestCase.job_id.in_([j.id for j in jobs]),
            TestCase.result == Result.failed,
        ).order_by(TestCase.name.asc())
        num_test_failures = test_failures.count()
        test_failures = test_failures[:25]

        failures_by_job = defaultdict(list)
        for failure in test_failures:
            failures_by_job[failure.job].append(failure)

        failure_origins = find_failure_origins(
            build, test_failures)
        for test_failure in test_failures:
            test_failure.origin = failure_origins.get(test_failure)

        # identify added/removed tests
        if most_recent_run and build.status == Status.finished:
            changed_tests = find_changed_tests(build, most_recent_run)
        else:
            changed_tests = []

        seen_by = list(User.query.join(
            BuildSeen, BuildSeen.user_id == User.id,
        ).filter(
            BuildSeen.build_id == build.id,
        ))

        extended_serializers = {
            TestCase: TestCaseWithOriginSerializer(),
        }

        event_list = list(Event.query.filter(
            Event.item_id == build.id,
        ).order_by(Event.date_created.desc()))

        context = self.serialize(build)
        context.update({
            'jobs': jobs,
            'previousRuns': previous_runs,
            'seenBy': seen_by,
            'events': event_list,
            'testFailures': {
                'total': num_test_failures,
                'tests': self.serialize(test_failures, extended_serializers),
            },
            'testChanges': self.serialize(changed_tests, extended_serializers),
        })

        return self.respond(context)
Exemple #12
0
    def get(self, build_id):
        build = Build.query.options(
            joinedload('project', innerjoin=True),
            joinedload('author'),
            joinedload('source').joinedload('revision'),
            subqueryload_all('stats'),
        ).get(build_id)
        if build is None:
            return '', 404

        try:
            most_recent_run = Build.query.filter(
                Build.project == build.project,
                Build.date_created < build.date_created,
                Build.status == Status.finished,
                Build.id != build.id,
                Source.patch_id == None,  # NOQA
            ).join(
                Source, Build.source_id == Source.id,
            ).options(
                contains_eager('source').joinedload('revision'),
                joinedload('author'),
            ).order_by(Build.date_created.desc())[0]
        except IndexError:
            most_recent_run = None

        jobs = list(Job.query.filter(
            Job.build_id == build.id,
        ))

        # identify failures
        test_failures = TestCase.query.options(
            joinedload('job', innerjoin=True),
        ).filter(
            TestCase.job_id.in_([j.id for j in jobs]),
            TestCase.result == Result.failed,
        ).order_by(TestCase.name.asc())
        num_test_failures = test_failures.count()
        test_failures = test_failures[:25]

        failures_by_job = defaultdict(list)
        for failure in test_failures:
            failures_by_job[failure.job].append(failure)

        failure_origins = find_failure_origins(
            build, test_failures)
        for test_failure in test_failures:
            test_failure.origin = failure_origins.get(test_failure)

        # identify added/removed tests
        if most_recent_run and build.status == Status.finished:
            changed_tests = find_changed_tests(build, most_recent_run)
        else:
            changed_tests = []

        seen_by = list(User.query.join(
            BuildSeen, BuildSeen.user_id == User.id,
        ).filter(
            BuildSeen.build_id == build.id,
        ))

        extended_serializers = {
            TestCase: TestCaseWithOriginCrumbler(),
        }

        event_list = list(Event.query.filter(
            Event.item_id == build.id,
        ).order_by(Event.date_created.desc()))

        context = self.serialize(build)
        context.update({
            'jobs': jobs,
            'seenBy': seen_by,
            'events': event_list,
            'failures': get_failure_reasons(build),
            'testFailures': {
                'total': num_test_failures,
                'tests': self.serialize(test_failures, extended_serializers),
            },
            'testChanges': self.serialize(changed_tests, extended_serializers),
            'parents': self.serialize(get_parents_last_builds(build)),
        })

        return self.respond(context)
Exemple #13
0
    def get(self, build_id):
        build = Build.query.options(
            joinedload('project', innerjoin=True),
            joinedload('author'),
            joinedload('source'),
        ).get(build_id)
        if build is None:
            return '', 404

        previous_runs = Build.query.filter(
            Build.project == build.project,
            Build.date_created < build.date_created,
            Build.status == Status.finished,
            Build.id != build.id,
            Build.patch == None,  # NOQA
        ).options(
            joinedload('source'),
            joinedload('author'),
        ).order_by(Build.date_created.desc())[:NUM_PREVIOUS_RUNS]

        if previous_runs:
            most_recent_run = previous_runs[0]
        else:
            most_recent_run = None

        jobs = list(Job.query.filter(
            Job.build_id == build.id,
        ))

        # identify failures
        test_failures = TestCase.query.options(
            joinedload('job', innerjoin=True),
        ).filter(
            TestCase.job_id.in_([j.id for j in jobs]),
            TestCase.result == Result.failed,
        ).order_by(TestCase.name.asc())
        num_test_failures = test_failures.count()
        test_failures = test_failures[:25]

        failures_by_job = defaultdict(list)
        for failure in test_failures:
            failures_by_job[failure.job].append(failure)

        failure_origins = find_failure_origins(
            build, test_failures)
        for test_failure in test_failures:
            test_failure.origin = failure_origins.get(test_failure)

        # identify added/removed tests
        if most_recent_run and build.status == Status.finished:
            changed_tests = find_changed_tests(build, most_recent_run)
        else:
            changed_tests = []

        seen_by = list(User.query.join(
            BuildSeen, BuildSeen.user_id == User.id,
        ).filter(
            BuildSeen.build_id == build.id,
        ))

        extended_serializers = {
            TestCase: TestCaseWithOriginSerializer(),
        }

        context = self.serialize(build)
        context.update({
            'jobs': jobs,
            'previousRuns': previous_runs,
            'seenBy': seen_by,
            'testFailures': {
                'total': num_test_failures,
                'tests': self.serialize(test_failures, extended_serializers),
            },
            'testChanges': self.serialize(changed_tests, extended_serializers),
        })

        return self.respond(context)
Exemple #14
0
    def get(self, build_id):
        build = Build.query.options(
            joinedload('project', innerjoin=True),
            joinedload('author'),
            joinedload('source').joinedload('revision'),
            subqueryload_all('stats'),
        ).get(build_id)
        if build is None:
            return '', 404

        try:
            most_recent_run = Build.query.filter(
                Build.project == build.project,
                Build.date_created < build.date_created,
                Build.status == Status.finished,
                Build.id != build.id,
                Source.patch_id == None,  # NOQA
            ).join(
                Source, Build.source_id == Source.id,
            ).options(
                contains_eager('source').joinedload('revision'),
                joinedload('author'),
            ).order_by(Build.date_created.desc())[0]
        except IndexError:
            most_recent_run = None

        jobs = list(Job.query.filter(
            Job.build_id == build.id,
        ))

        # identify failures
        if not jobs:
            # If we have no jobs, the query to find failures becomes very expensive due to `in_([])` being
            # handled poorly, but since we know the result, we can just set it.
            test_failures = []
            num_test_failures = 0
        else:
            test_failures = TestCase.query.options(
                joinedload('job', innerjoin=True),
            ).filter(
                TestCase.job_id.in_([j.id for j in jobs]),
                TestCase.result == Result.failed,
            ).order_by(TestCase.name.asc())
            num_test_failures = test_failures.count()
            test_failures = test_failures[:25]

        failures_by_job = defaultdict(list)
        for failure in test_failures:
            failures_by_job[failure.job].append(failure)

        failure_origins = find_failure_origins(
            build, test_failures)
        for test_failure in test_failures:
            test_failure.origin = failure_origins.get(test_failure)

        # identify added/removed tests
        if most_recent_run and build.status == Status.finished:
            changed_tests = find_changed_tests(build, most_recent_run)
        else:
            changed_tests = []

        seen_by = list(User.query.join(
            BuildSeen, BuildSeen.user_id == User.id,
        ).filter(
            BuildSeen.build_id == build.id,
        ))

        extended_serializers = {
            TestCase: TestCaseWithOriginCrumbler(),
        }

        event_list = list(Event.query.filter(
            Event.item_id == build.id,
        ).order_by(Event.date_created.desc()))

        context = self.serialize(build)
        context.update({
            'jobs': jobs,
            'seenBy': seen_by,
            'events': event_list,
            'failures': get_failure_reasons(build),
            'testFailures': {
                'total': num_test_failures,
                'tests': self.serialize(test_failures, extended_serializers),
            },
            'testChanges': self.serialize(changed_tests, extended_serializers),
            'parents': self.serialize(get_parents_last_builds(build)),
        })

        return self.respond(context)