diff --git a/tests/webapp/api/test_jobs_api.py b/tests/webapp/api/test_jobs_api.py index 7bf7a2c07..4415a8525 100644 --- a/tests/webapp/api/test_jobs_api.py +++ b/tests/webapp/api/test_jobs_api.py @@ -211,3 +211,34 @@ def test_job_detail_not_found(webapp, jm): expect_errors=True ) assert resp.status_int == 404 + + +def test_job_error_lines(webapp, eleven_jobs_stored, jm, failure_lines, classified_failures): + """ + test retrieving failure lines + """ + job = jm.get_job(1)[0] + + resp = webapp.get( + reverse("jobs-failure-lines", + kwargs={"project": jm.project, "pk": job["id"]}) + ) + assert resp.status_int == 200 + + failures = resp.json + assert isinstance(failures, list) + + exp_failure_keys = ["id", "job_guid", "repository", "action", "line", + "test", "subtest", "status", "expected", "message", + "signature", "level", "created", "modified", "matches"] + + assert set(failures[0].keys()) == set(exp_failure_keys) + + matches = failures[0]["matches"] + assert isinstance(matches, list) + + exp_matches_keys = ["id", "matcher", "score", "is_best"] + + assert set(matches[0].keys()) == set(exp_matches_keys) + + jm.disconnect() diff --git a/treeherder/webapp/api/jobs.py b/treeherder/webapp/api/jobs.py index 6c3fcf11e..832b20404 100644 --- a/treeherder/webapp/api/jobs.py +++ b/treeherder/webapp/api/jobs.py @@ -5,6 +5,8 @@ from rest_framework.response import Response from rest_framework.reverse import reverse from treeherder.model.derived import ArtifactsModel +from treeherder.model.models import FailureLine +from treeherder.webapp.api import serializers from treeherder.webapp.api import permissions from treeherder.webapp.api.permissions import IsStaffOrReadOnly from treeherder.webapp.api.utils import UrlQueryFilter, get_option, with_jobs @@ -166,6 +168,25 @@ class JobsViewSet(viewsets.ViewSet): else: return Response("No job with id: {0}".format(pk), 404) + @detail_route(methods=['get']) + @with_jobs + def failure_lines(self, request, project, jm, pk=None): + """ + Get a list of test failure lines for the job + """ + job = jm.get_job(pk) + if job: + queryset = FailureLine.objects.filter( + job_guid=job[0]['job_guid'] + ).prefetch_related( + "matches", "matches__matcher" + ) + failure_lines = [serializers.FailureLineNoStackSerializer(obj).data + for obj in queryset] + return Response(failure_lines) + else: + return Response("No job with id: {0}".format(pk), 404) + @with_jobs def create(self, request, project, jm): """ diff --git a/treeherder/webapp/api/refdata.py b/treeherder/webapp/api/refdata.py index c99f070ec..4aa306c8f 100644 --- a/treeherder/webapp/api/refdata.py +++ b/treeherder/webapp/api/refdata.py @@ -178,3 +178,11 @@ class ExclusionProfileViewSet(viewsets.ModelViewSet): if "author" not in request.DATA: request.DATA["author"] = request.user.id return super(ExclusionProfileViewSet, self).create(request, *args, **kwargs) + + +class MatcherViewSet(viewsets.ReadOnlyModelViewSet): + queryset = models.Matcher.objects.all() + serializer_class = th_serializers.MatcherSerializer + + class Meta: + model = models.Matcher diff --git a/treeherder/webapp/api/serializers.py b/treeherder/webapp/api/serializers.py index 8c52bafe4..7bbb11a3e 100644 --- a/treeherder/webapp/api/serializers.py +++ b/treeherder/webapp/api/serializers.py @@ -120,3 +120,27 @@ class BugscacheSerializer(serializers.ModelSerializer): class Meta: model = models.Bugscache + + +class MatcherSerializer(serializers.ModelSerializer): + + class Meta: + model = models.Matcher + + +class FailureMatchSerializer(serializers.ModelSerializer): + + class Meta: + model = models.FailureMatch + exclude = ['classified_failure', + 'failure_line'] + + +class FailureLineNoStackSerializer(serializers.ModelSerializer): + matches = FailureMatchSerializer(many=True) + + class Meta: + model = models.FailureLine + exclude = ['stack', + 'stackwalk_stdout', + 'stackwalk_stderr'] diff --git a/treeherder/webapp/api/urls.py b/treeherder/webapp/api/urls.py index c4da936dc..4a3786d54 100644 --- a/treeherder/webapp/api/urls.py +++ b/treeherder/webapp/api/urls.py @@ -85,6 +85,7 @@ default_router.register(r'failureclassification', refdata.FailureClassificationV default_router.register(r'user', refdata.UserViewSet, base_name='user') default_router.register(r'exclusion-profile', refdata.ExclusionProfileViewSet) default_router.register(r'job-exclusion', refdata.JobExclusionViewSet) +default_router.register(r'matcher', refdata.MatcherViewSet) urlpatterns = patterns( '',