squad 1.73__py3-none-any.whl → 1.74__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
squad/api/rest.py CHANGED
@@ -3,7 +3,6 @@ import yaml
3
3
 
4
4
  from django.db.models import Q, F, Value as V, CharField, Prefetch
5
5
  from django.db.models.functions import Concat
6
- from django.db.models.query import prefetch_related_objects
7
6
  from django.db.utils import IntegrityError
8
7
  from django.core import exceptions as core_exceptions
9
8
  from django.core.exceptions import ValidationError
@@ -904,7 +903,10 @@ class BuildViewSet(NestedViewSetMixin, ModelViewSet):
904
903
 
905
904
  * `api/builds/<id>/failures_with_confidence` GET
906
905
 
907
- List of failing tests with confidence scores. List is paginated
906
+ List of failing tests with confidence scores. For each failure SQUAD will look back
907
+ N builds, where N is defined in project settings. List is paginated.
908
+
909
+ * releases_only - when active, look back only on builds with is_release=True
908
910
 
909
911
  * `api/builds/<id>/metrics` GET
910
912
 
@@ -1024,17 +1026,13 @@ class BuildViewSet(NestedViewSetMixin, ModelViewSet):
1024
1026
  result=False,
1025
1027
  ).exclude(
1026
1028
  has_known_issues=True,
1027
- ).only(
1028
- 'metadata__suite', 'metadata__name', 'metadata__id',
1029
1029
  ).order_by(
1030
- 'metadata__suite', 'metadata__name',
1031
- ).values_list(
1032
- 'metadata__suite', 'metadata__name', 'metadata__id', named=True,
1033
- )
1030
+ 'id', 'metadata__suite', 'metadata__name', 'environment__slug',
1031
+ ).distinct()
1034
1032
 
1035
1033
  page = self.paginate_queryset(failures)
1036
- fwc = failures_with_confidence(build.project, build, page)
1037
- prefetch_related_objects(fwc, "known_issues")
1034
+ releases_only = request.GET.get("releases_only")
1035
+ fwc = failures_with_confidence(build.project, build, page, releases_only=releases_only)
1038
1036
  serializer = FailuresWithConfidenceSerializer(fwc, many=True, context={'request': request})
1039
1037
  return self.get_paginated_response(serializer.data)
1040
1038
 
@@ -1393,6 +1391,17 @@ class ConfidenceSerializer(serializers.BaseSerializer):
1393
1391
 
1394
1392
  class FailuresWithConfidenceSerializer(TestSerializer):
1395
1393
  confidence = ConfidenceSerializer()
1394
+ status = None
1395
+
1396
+ class Meta:
1397
+ model = Test
1398
+ exclude = (
1399
+ 'known_issues',
1400
+ 'has_known_issues',
1401
+ 'result',
1402
+ 'url',
1403
+ 'suite',
1404
+ )
1396
1405
 
1397
1406
 
1398
1407
  class TestViewSet(NestedViewSetMixin, ModelViewSet):
@@ -19,6 +19,7 @@ from cryptography.hazmat.primitives import (
19
19
  from squad.ci.backend.null import Backend as BaseBackend
20
20
  from squad.ci.exceptions import FetchIssue, TemporaryFetchIssue
21
21
  from squad.ci.models import TestJob
22
+ from squad.core.models import TestRun
22
23
 
23
24
 
24
25
  logger = logging.getLogger('squad.ci.backend.tuxsuite')
@@ -146,6 +147,72 @@ class Backend(BaseBackend):
146
147
 
147
148
  return None
148
149
 
150
+ def set_build_name(self, test_job, job_url, results, metadata, settings):
151
+ """
152
+ Tuxsuite allows plans with builds and tests within.
153
+ Some of these plans also support "special tests", which are
154
+ kind a sanity test to run before spinning a heavy load of tests.
155
+
156
+ Here's the default plan hierarchy:
157
+ - build -> tests
158
+
159
+ Now with sanity tests in between:
160
+ - build -> sanity tests -> tests
161
+
162
+ SQUAD needs to get to the build level in
163
+ order to retrieve the build object and finally retrieve
164
+ its build name attribute
165
+ """
166
+
167
+ build_id = results['waiting_for']
168
+ if build_id is None:
169
+ return
170
+
171
+ items = build_id.split('#')
172
+ if len(items) == 2:
173
+ _type = items[0]
174
+ _id = items[1]
175
+ else:
176
+ _type = "BUILD"
177
+ _id = items[0]
178
+
179
+ test_id = results['uid']
180
+
181
+ try:
182
+ # Check if the target build or sanity test is fetched
183
+ job_id = self.generate_job_id(_type.lower(), results)
184
+ job_id = job_id.replace(test_id, _id)
185
+
186
+ candidate = TestRun.objects.get(
187
+ build=test_job.target_build,
188
+ job_id=job_id
189
+ )
190
+
191
+ build_name = candidate.metadata.get('build_name')
192
+ if build_name:
193
+ metadata['build_name'] = build_name
194
+ return
195
+
196
+ except TestRun.DoesNotExist:
197
+ pass
198
+
199
+ # It is a sanity test, an extra request is needed to get build id
200
+ if _type == 'TEST':
201
+ follow_test_url = job_url.replace(test_id, _id)
202
+ test_json = self.fetch_url(follow_test_url).json()
203
+ build_id = test_json.get('waiting_for')
204
+
205
+ build_id = build_id.replace('BUILD#', '')
206
+ build_url = job_url.replace(test_id, build_id).replace('/tests/', '/builds/')
207
+
208
+ build_metadata = self.fetch_url(build_url).json()
209
+
210
+ build_metadata_keys = settings.get('TEST_BUILD_METADATA_KEYS', [])
211
+ metadata.update({k: build_metadata.get(k) for k in build_metadata_keys})
212
+
213
+ if 'toolchain' in build_metadata_keys and 'kconfig' in build_metadata_keys and metadata['build_name'] in [None, '']:
214
+ metadata['build_name'] = self.generate_test_name(build_metadata)
215
+
149
216
  def parse_build_results(self, test_job, job_url, results, settings):
150
217
  required_keys = ['build_status', 'warnings_count', 'download_url', 'retry']
151
218
  self.__check_required_keys__(required_keys, results)
@@ -163,6 +230,7 @@ class Backend(BaseBackend):
163
230
  metadata_keys = settings.get('BUILD_METADATA_KEYS', [])
164
231
  metadata = {k: results.get(k) for k in metadata_keys}
165
232
  metadata['job_url'] = job_url
233
+ metadata['job_id'] = test_job.job_id
166
234
  metadata['config'] = urljoin(results.get('download_url') + '/', 'config')
167
235
  metadata['build_name'] = test_name
168
236
 
@@ -200,6 +268,7 @@ class Backend(BaseBackend):
200
268
  metadata_keys = settings.get('TEST_METADATA_KEYS', [])
201
269
  metadata = {k: results.get(k) for k in metadata_keys}
202
270
  metadata['job_url'] = job_url
271
+ metadata['job_id'] = test_job.job_id
203
272
 
204
273
  # Set job name
205
274
  try:
@@ -227,25 +296,8 @@ class Backend(BaseBackend):
227
296
  # Retrieve TuxRun log
228
297
  logs = self.fetch_url(job_url + '/', 'logs?format=txt').text
229
298
 
230
- # Fetch more metadata if available
231
- if results['waiting_for'] is not None:
232
- build_id = results['waiting_for']
233
-
234
- # Tuxsuite recently has added support for tests depending on other tests
235
- if build_id.startswith('BUILD#') or '#' not in build_id:
236
- _, _, test_id = self.parse_job_id(test_job.job_id)
237
- build_id = build_id.replace('BUILD#', '')
238
- build_url = job_url.replace(test_id, build_id).replace('/tests/', '/builds/')
239
-
240
- # TODO: check if we can save a few seconds by querying a testjob that
241
- # already contains build results
242
- build_metadata = self.fetch_url(build_url).json()
243
-
244
- build_metadata_keys = settings.get('TEST_BUILD_METADATA_KEYS', [])
245
- metadata.update({k: build_metadata.get(k) for k in build_metadata_keys})
246
-
247
- if 'toolchain' in build_metadata_keys and 'kconfig' in build_metadata_keys and metadata['build_name'] in [None, '']:
248
- metadata['build_name'] = self.generate_test_name(build_metadata)
299
+ # Follow up the chain and retrieve build name
300
+ self.set_build_name(test_job, job_url, results, metadata, settings)
249
301
 
250
302
  # Create a boot test
251
303
  boot_test_name = 'boot/' + (metadata.get('build_name') or 'boot')
squad/core/comparison.py CHANGED
@@ -393,7 +393,7 @@ class TestComparison(BaseComparison):
393
393
 
394
394
  tests = models.Test.objects.filter(test_run_id__in=test_runs_ids.keys()).annotate(
395
395
  suite_slug=F('suite__slug'),
396
- ).prefetch_related('metadata').defer('log')
396
+ ).prefetch_related('metadata').defer('log').order_by()
397
397
 
398
398
  for test in tests:
399
399
  build, env = test_runs_ids.get(test.test_run_id)
@@ -539,6 +539,9 @@ class TestComparison(BaseComparison):
539
539
  # No baseline is present, then no comparison is needed
540
540
  return
541
541
 
542
+ baseline = self.builds[0]
543
+ target = self.builds[1]
544
+
542
545
  query = self.base_sql.copy()
543
546
  query['select'].append('target.result')
544
547
  query['select'].append('target.has_known_issues')
@@ -549,42 +552,54 @@ class TestComparison(BaseComparison):
549
552
  tests = [t for t in models.Test.objects.raw(sql)]
550
553
  prefetch_related_objects(tests, 'metadata', 'suite')
551
554
 
552
- env_ids = []
555
+ env_ids = [t.environment_id for t in tests]
556
+ envs = {e.id: e for e in models.Environment.objects.filter(id__in=env_ids).all()}
557
+ envs_slugs = sorted({e.slug for e in envs.values()})
558
+
559
+ for build in self.builds:
560
+ self.environments[build] = envs_slugs
561
+
553
562
  fixed_tests = defaultdict(set)
554
563
  regressions = defaultdict(set)
555
564
  fixes = defaultdict(set)
556
565
 
557
566
  for test in tests:
558
567
  env_id = test.environment_id
568
+
559
569
  full_name = test.full_name
570
+ if full_name not in self.results:
571
+ self.results[full_name] = OrderedDict()
560
572
 
561
- env_ids.append(env_id)
573
+ baseline_key = (baseline, envs[env_id].slug)
574
+ target_key = (target, envs[env_id].slug)
562
575
 
563
576
  if test.status == 'fail':
564
577
  regressions[env_id].add(full_name)
578
+ self.results[full_name][target_key] = 'fail'
579
+ self.results[full_name][baseline_key] = 'pass'
565
580
  elif test.status == 'pass':
566
581
  fixes[env_id].add(full_name)
567
582
  fixed_tests[env_id].add(test.metadata_id)
583
+ self.results[full_name][target_key] = 'pass'
584
+ self.results[full_name][baseline_key] = 'fail'
568
585
 
569
- environments = {e.id: e for e in models.Environment.objects.filter(id__in=env_ids).all()}
586
+ self.results = OrderedDict(sorted(self.results.items()))
570
587
 
571
588
  for env_id in regressions.keys():
572
- self.__regressions__[environments[env_id].slug] = list(regressions[env_id])
589
+ self.__regressions__[envs[env_id].slug] = list(regressions[env_id])
573
590
 
574
591
  # It's not a fix if baseline test is intermittent for a given environment:
575
592
  # - test.has_known_issues == True and
576
593
  # - test.known_issues[env].intermittent == True
577
- fixed_tests_environment_slugs = [environments[env_id] for env_id in fixed_tests.keys()]
594
+ fixed_tests_environment_slugs = [envs[env_id] for env_id in fixed_tests.keys()]
578
595
  intermittent_fixed_tests = self.__intermittent_fixed_tests__(fixed_tests, fixed_tests_environment_slugs)
579
596
  for env_id in fixes.keys():
580
- env_slug = environments[env_id].slug
597
+ env_slug = envs[env_id].slug
581
598
  test_list = [test for test in fixes[env_id] if (test, env_slug) not in intermittent_fixed_tests]
582
599
  if len(test_list):
583
600
  self.__fixes__[env_slug] = test_list
584
601
 
585
- baseline = self.builds[0]
586
- target = self.builds[1]
587
- for env in environments.values():
602
+ for env in envs.values():
588
603
  if env.slug in self.__regressions__:
589
604
  for test in self.__regressions__[env.slug]:
590
605
  self.__diff__[test][target][env.slug] = False
squad/core/failures.py CHANGED
@@ -1,32 +1,28 @@
1
+ from django.db.models import prefetch_related_objects
2
+
1
3
  from squad.core.models import Test
2
4
 
3
5
 
4
- def failures_with_confidence(project, build, failures):
6
+ def failures_with_confidence(project, build, failures, releases_only=False):
5
7
  limit = project.build_confidence_count
6
8
  threshold = project.build_confidence_threshold
7
9
 
8
- # First, get a list of failures x environments
9
- with_envs = build.tests.filter(
10
- metadata_id__in=[f.metadata__id for f in failures],
11
- result=False,
12
- ).exclude(
13
- has_known_issues=True
14
- ).prefetch_related(
15
- "metadata",
16
- "environment",
17
- ).order_by(
18
- "metadata__suite",
19
- "metadata__name",
20
- )
10
+ prefetch_related_objects(failures, "metadata")
11
+
12
+ queryset = project.builds.filter(id__lt=build.id)
13
+ if releases_only:
14
+ queryset = queryset.filter(is_release=True)
15
+ builds = queryset.order_by('-id').all()[:limit]
16
+ builds_ids = [b.id for b in builds]
21
17
 
22
18
  # Find previous `limit` tests that contain this test x environment
23
- for failure in with_envs:
19
+ for failure in failures:
24
20
  history = Test.objects.filter(
25
- build_id__lt=build.id,
21
+ build_id__in=builds_ids,
26
22
  metadata_id=failure.metadata_id,
27
23
  environment_id=failure.environment_id,
28
- ).order_by('-build_id').defer("log")[:limit]
24
+ ).only("result").order_by()
29
25
 
30
26
  failure.set_confidence(threshold, history)
31
27
 
32
- return with_envs
28
+ return failures
squad/core/history.py CHANGED
@@ -1,9 +1,9 @@
1
- from collections import OrderedDict
1
+ from collections import defaultdict
2
2
  from django.core.paginator import Paginator
3
3
 
4
4
  from squad.core.queries import test_confidence
5
5
  from squad.core.utils import parse_name
6
- from squad.core.models import Test, SuiteMetadata, KnownIssue
6
+ from squad.core.models import SuiteMetadata, KnownIssue, Environment
7
7
 
8
8
 
9
9
  class TestResult(object):
@@ -11,20 +11,20 @@ class TestResult(object):
11
11
  __test__ = False
12
12
 
13
13
  class TestRunStatus(object):
14
- def __init__(self, test_run, suite):
15
- self.test_run = test_run
14
+ def __init__(self, test_run_id, suite):
15
+ self.test_run_id = test_run_id
16
16
  self.suite = suite
17
17
 
18
- def __init__(self, test, suite, metadata, known_issues, is_duplicate=False):
18
+ def __init__(self, test, suite, metadata, known_issues, is_duplicate=False, list_of_duplicates=None):
19
19
  self.test = test
20
20
  self.suite = suite
21
21
  self.known_issues = known_issues
22
22
  if is_duplicate:
23
- self.status, self.confidence_score = test_confidence(test)
23
+ self.status, self.confidence_score = test_confidence(None, list_of_duplicates=list_of_duplicates)
24
24
  else:
25
25
  self.status, self.confidence_score = (test.status, None)
26
- self.test_run = test.test_run
27
- self.test_run_status = self.TestRunStatus(self.test_run, self.suite)
26
+ self.test_run_id = test.test_run_id
27
+ self.test_run_status = self.TestRunStatus(self.test_run_id, self.suite)
28
28
  self.info = {
29
29
  "test_description": metadata.description if metadata else '',
30
30
  "test_instructions": metadata.instructions_to_reproduce if metadata else '',
@@ -51,11 +51,6 @@ class TestHistory(object):
51
51
 
52
52
  self.top = builds[0]
53
53
 
54
- environments = OrderedDict()
55
- results = OrderedDict()
56
- for build in builds:
57
- results[build] = {}
58
-
59
54
  issues_by_env = {}
60
55
  for issue in KnownIssue.active_by_project_and_test(project, full_test_name).all():
61
56
  for env in issue.environments.all():
@@ -65,16 +60,27 @@ class TestHistory(object):
65
60
 
66
61
  suite = project.suites.prefetch_related('metadata').get(slug=suite_slug)
67
62
  metadata = SuiteMetadata.objects.get(kind='test', suite=suite_slug, name=test_name)
68
- tests = Test.objects.filter(build__in=builds, metadata_id=metadata.id).prefetch_related('build', 'environment', 'test_run', 'metadata').order_by()
69
- for test in tests:
70
- build = test.build
71
- environment = test.environment
72
- environments[environment] = True
73
- known_issues = issues_by_env.get(environment.id)
74
- is_duplicate = False
75
- if environment in results[build]:
76
- is_duplicate = True
77
- results[build][environment] = TestResult(test, suite, metadata, known_issues, is_duplicate)
78
-
79
- self.environments = sorted(environments.keys(), key=lambda env: env.slug)
80
- self.results = results
63
+
64
+ results = defaultdict()
65
+ environments_ids = set()
66
+ for build in builds:
67
+ results[build] = defaultdict(list)
68
+ for test in build.tests.filter(metadata=metadata).order_by():
69
+ test.metadata = metadata
70
+ test.suite = suite
71
+ results[build][test.environment_id].append(test)
72
+ environments_ids.add(test.environment_id)
73
+
74
+ results_without_duplicates = defaultdict()
75
+ for build in results:
76
+ results_without_duplicates[build] = defaultdict()
77
+ for env in results[build]:
78
+ tests = results[build][env]
79
+
80
+ is_duplicate = len(tests) > 1
81
+ known_issues = issues_by_env.get(tests[0].environment_id)
82
+ result = TestResult(tests[0], suite, metadata, known_issues, is_duplicate, list_of_duplicates=tests)
83
+ results_without_duplicates[build][env] = result
84
+
85
+ self.environments = Environment.objects.filter(id__in=environments_ids).order_by('slug')
86
+ self.results = results_without_duplicates
squad/core/models.py CHANGED
@@ -1025,7 +1025,7 @@ class Test(models.Model):
1025
1025
 
1026
1026
  @property
1027
1027
  def passes(self):
1028
- return sum(1 for t in self.tests if t.status == "pass")
1028
+ return sum(1 for t in self.tests if t.result)
1029
1029
 
1030
1030
  @property
1031
1031
  def score(self):
squad/core/queries.py CHANGED
@@ -199,7 +199,7 @@ def test_confidence(test, list_of_duplicates=None):
199
199
  return {value: count for value, count in data.items() if count == max_count}
200
200
 
201
201
  if test:
202
- duplicates = models.Test.objects.filter(metadata=test.metadata, environment=test.environment, build=test.build).order_by()
202
+ duplicates = models.Test.objects.filter(metadata_id=test.metadata_id, environment_id=test.environment_id, build_id=test.build_id).order_by()
203
203
  else:
204
204
  duplicates = list_of_duplicates
205
205
 
@@ -116,11 +116,11 @@ def compare_builds(request):
116
116
  baseline = get_object_or_404(project.builds, version=baseline_build)
117
117
  target = get_object_or_404(project.builds, version=target_build)
118
118
 
119
- comparison_class = __get_comparison_class(comparison_type)
120
- comparison = comparison_class.compare_builds(baseline, target)
121
-
122
- if comparison_type == 'test' and len(transitions):
123
- comparison.apply_transitions([t for t, checked in transitions.items() if checked])
119
+ if comparison_type == 'test':
120
+ comparison = TestComparison(baseline, target, regressions_and_fixes_only=True)
121
+ else:
122
+ comparison_class = __get_comparison_class(comparison_type)
123
+ comparison = comparison_class.compare_builds(baseline, target)
124
124
 
125
125
  comparison.results = __paginate(comparison.results, request)
126
126
 
@@ -1,9 +1,5 @@
1
1
  {% if comparison %}
2
2
 
3
- {% if comparison_type == 'test' %}
4
- {% include "squad/_results_transitions_filter.jinja2" %}
5
- {% endif %}
6
-
7
3
  <table class='test-results'>
8
4
  <thead>
9
5
  <tr>
@@ -103,11 +103,6 @@
103
103
  {{ _('Tests') }}
104
104
  </a>
105
105
  </li>
106
- <li role="presentation" {% if url_name == 'failures' %}class="active"{% endif %}>
107
- <a href="{{build_section_url(build, 'failures')}}">
108
- {{ _('Test failures') }}
109
- </a>
110
- </li>
111
106
  <li role="presentation" {% if url_name == 'build_metrics' %}class="active"{% endif %}>
112
107
  <a href="{{build_section_url(build, 'build_metrics')}}">
113
108
  {{ _('Metrics') }}
@@ -39,7 +39,7 @@
39
39
  <td><a href="{{project_url(build)}}">{{build.version}}</a></td>
40
40
  <td>{{build.datetime|date}}</td>
41
41
  {% for environment in history.environments %}
42
- {% with result=results[environment] %}
42
+ {% with result=results[environment.id] %}
43
43
  {% if result %}
44
44
  {% with known_issues=result.known_issues %}
45
45
  <td class='{{result.status|slugify}}'>
@@ -82,14 +82,12 @@ def testrun_suite_test_details_history_url(group, project, build, status, test):
82
82
 
83
83
 
84
84
  def testrun_suite_or_test_url(group, project, build, status, kind, test=None):
85
- testrun = status.test_run.id
86
- suite = status.suite
87
85
  args = (
88
86
  group.slug,
89
87
  project.slug,
90
88
  build.version,
91
- testrun,
92
- suite.slug.replace('/', '$'),
89
+ status.test_run_id,
90
+ status.suite.slug.replace('/', '$'),
93
91
  )
94
92
  if test:
95
93
  if isinstance(test, Test):
squad/frontend/tests.py CHANGED
@@ -1,11 +1,12 @@
1
1
  import json
2
2
 
3
- from django.db.models import Prefetch
3
+ from collections import defaultdict
4
+
4
5
  from django.shortcuts import render, get_object_or_404
5
6
  from django.http import Http404
6
7
 
7
8
  from squad.http import auth
8
- from squad.core.models import Test, Suite, TestRun
9
+ from squad.core.models import Test, Suite, SuiteMetadata, Environment
9
10
  from squad.core.history import TestHistory
10
11
  from squad.core.queries import test_confidence
11
12
  from squad.frontend.views import get_build
@@ -46,23 +47,39 @@ class TestResultTable(list):
46
47
 
47
48
  def __init__(self):
48
49
  self.environments = None
50
+ self.filters = {
51
+ 'environment': None,
52
+ 'suite': None,
53
+ }
49
54
  self.paginator = self
50
55
  self.paginator.num_pages = 0
51
56
  self.number = 0
52
57
  self.all_tests = []
53
58
 
54
- def __get_all_tests__(self, build, search):
59
+ def __get_all_tests__(self, build, search, env=None, suite=None):
55
60
 
56
61
  queryset = Test.objects.filter(build=build)
57
62
  if search:
58
63
  queryset = queryset.filter(metadata__name__icontains=search)
59
64
 
60
- self.all_tests = queryset.only('result', 'has_known_issues', 'suite_id', 'metadata_id').order_by()
65
+ if env:
66
+ environment = Environment.objects.filter(project=build.project, slug=env)
67
+ if environment.exists():
68
+ self.filters['environment'] = environment.first()
69
+ queryset = queryset.filter(environment=self.filters['environment'])
70
+
71
+ if suite:
72
+ suite_ = Suite.objects.filter(project=build.project, slug=suite)
73
+ if suite_.exists():
74
+ self.filters['suite'] = suite_.first()
75
+ queryset = queryset.filter(suite=self.filters['suite'])
76
+
77
+ self.all_tests = queryset.only('result', 'has_known_issues', 'metadata_id').order_by()
61
78
 
62
79
  # count how many unique tests are represented in the given build, and sets
63
80
  # pagination data
64
81
  def __count_pages__(self, per_page):
65
- distinct_tests = set([(test.suite_id, test.metadata_id) for test in self.all_tests])
82
+ distinct_tests = set([test.metadata_id for test in self.all_tests])
66
83
  count = len(distinct_tests)
67
84
  self.num_pages = count // per_page
68
85
  if count % per_page > 0:
@@ -79,50 +96,48 @@ class TestResultTable(list):
79
96
  """
80
97
  offset = (page - 1) * per_page
81
98
 
82
- stats = {}
99
+ stats = defaultdict(lambda: {'pass': 0, 'fail': 0, 'xfail': 0, 'skip': 0})
83
100
  for test in self.all_tests:
84
- key = (test.suite_id, test.metadata_id)
85
- if key not in stats:
86
- stats[key] = {'pass': 0, 'fail': 0, 'xfail': 0, 'skip': 0, 'ids': []}
87
- stats[key][test.status] += 1
88
- stats[key]['ids'].append(test.id)
101
+ stats[test.metadata_id][test.status] += 1
89
102
 
90
103
  def keyfunc(item):
91
- name = item[0]
104
+ metadata_id = item[0]
92
105
  statuses = item[1]
93
- return tuple((-statuses[k] for k in ['fail', 'xfail', 'skip', 'pass'])) + (name,)
106
+ return tuple((-statuses[k] for k in ['fail', 'xfail', 'skip', 'pass'])) + (metadata_id,)
94
107
 
95
108
  ordered = sorted(stats.items(), key=keyfunc)
96
109
  tests_in_page = ordered[offset:offset + per_page]
97
-
98
- tests_ids = []
99
- for test in tests_in_page:
100
- tests_ids += test[1]['ids']
101
-
102
- return tests_ids
110
+ metadata_ids = [t[0] for t in tests_in_page]
111
+ return metadata_ids
103
112
 
104
113
  @classmethod
105
- def get(cls, build, page, search, per_page=50):
114
+ def get(cls, build, page, search, per_page=50, env=None, suite=None):
106
115
  table = cls()
107
- table.__get_all_tests__(build, search)
116
+ table.__get_all_tests__(build, search, env=env, suite=suite)
108
117
  table.number = page
109
118
  table.__count_pages__(per_page)
110
119
 
111
- table.environments = set([t.environment for t in build.test_runs.prefetch_related('environment').all()])
120
+ if table.filters['environment']:
121
+ table.environments = {table.filters['environment']}
122
+ else:
123
+ table.environments = set([t.environment for t in build.test_runs.prefetch_related('environment').all()])
124
+
125
+ queryset = build.tests
126
+ if table.filters['environment']:
127
+ queryset = queryset.filter(environment=table.filters['environment'])
112
128
 
113
- tests = Test.objects.filter(
114
- id__in=table.__get_page_filter__(page, per_page)
129
+ if table.filters['suite']:
130
+ queryset = queryset.filter(suite=table.filters['suite'])
131
+
132
+ tests = queryset.filter(
133
+ metadata_id__in=table.__get_page_filter__(page, per_page),
115
134
  ).prefetch_related(
116
- Prefetch('test_run', queryset=TestRun.objects.only('environment')),
117
135
  'suite__metadata',
118
136
  'metadata',
119
137
  )
120
138
 
121
- memo = {}
139
+ memo = defaultdict(lambda: defaultdict(list))
122
140
  for test in tests:
123
- memo.setdefault(test.full_name, {})
124
- if test.environment_id not in memo[test.full_name]:
125
- memo[test.full_name][test.environment_id] = []
126
141
  memo[test.full_name][test.environment_id].append(test)
127
142
 
128
143
  # handle duplicates
@@ -149,7 +164,7 @@ class TestResultTable(list):
149
164
  memo[full_name][env_id].append(info)
150
165
 
151
166
  if 'test_metadata' not in memo[full_name].keys():
152
- memo[full_name]['test_metadata'] = (test.test_run, test.suite, test.name)
167
+ memo[full_name]['test_metadata'] = (test.test_run_id, test.suite, test.name)
153
168
 
154
169
  for test_full_name, results in memo.items():
155
170
  test_result = TestResult(test_full_name)
@@ -173,13 +188,15 @@ def tests(request, group_slug, project_slug, build_version):
173
188
  except ValueError:
174
189
  page = 1
175
190
 
191
+ env = request.GET.get('environment')
192
+ suite = request.GET.get('suite')
176
193
  search = request.GET.get('search', '')
177
194
 
178
195
  context = {
179
196
  "project": project,
180
197
  "build": build,
181
198
  "search": search,
182
- "results": TestResultTable.get(build, page, search),
199
+ "results": TestResultTable.get(build, page, search, env=env, suite=suite),
183
200
  }
184
201
 
185
202
  return render(request, 'squad/tests.jinja2', context)
@@ -191,12 +208,12 @@ def legacy_test_history(request, group_slug, project_slug, full_test_name):
191
208
 
192
209
 
193
210
  @auth
194
- def test_history(request, group_slug, project_slug, build_version=None, testrun=None, suite_slug=None, test_name=None):
211
+ def test_history(request, group_slug, project_slug, build_version=None, testrun_id=None, suite_slug=None, test_name=None):
195
212
  project = request.project
196
213
  context = {"project": project}
197
- if build_version and testrun and suite_slug:
214
+ if build_version and testrun_id and suite_slug:
198
215
  build = get_build(project, build_version)
199
- test_run = get_object_or_404(build.test_runs, pk=testrun)
216
+ test_run = get_object_or_404(build.test_runs, pk=testrun_id)
200
217
  suite_slug = suite_slug.replace('$', '/')
201
218
  suite = get_object_or_404(project.suites, slug=suite_slug)
202
219
  status = get_object_or_404(test_run.status, suite=suite)
@@ -204,6 +221,7 @@ def test_history(request, group_slug, project_slug, build_version=None, testrun=
204
221
  context.update({"build": build, "status": status, "test": test_name})
205
222
  else:
206
223
  full_test_name = test_name.replace('$', '/')
224
+
207
225
  try:
208
226
  page = int(request.GET.get('page', '1'))
209
227
  except ValueError:
@@ -216,5 +234,5 @@ def test_history(request, group_slug, project_slug, build_version=None, testrun=
216
234
  history = TestHistory(project, full_test_name, top=top, page=page)
217
235
  context.update({"history": history})
218
236
  return render(request, 'squad/test_history.jinja2', context)
219
- except Suite.DoesNotExist:
220
- raise Http404("No such suite for test: %s")
237
+ except (Suite.DoesNotExist, SuiteMetadata.DoesNotExist) as e:
238
+ raise Http404(f"Test not found: {e}")
squad/frontend/urls.py CHANGED
@@ -7,7 +7,6 @@ from django.shortcuts import redirect
7
7
  from . import views
8
8
  from . import badges
9
9
  from . import comparison
10
- from . import failures
11
10
  from . import metrics
12
11
  from . import tests
13
12
  from . import ci
@@ -45,7 +44,6 @@ urlpatterns = [
45
44
  url(r'^(%s)/(%s)/build/([^/]+)/api/$' % group_and_project, views.build_api, name='build_api'),
46
45
  url(r'^(%s)/(%s)/build/([^/]+)/badge$' % group_and_project, badges.build_badge, name='build_badge'),
47
46
  url(r'^(%s)/(%s)/build/([^/]+)/tests/$' % group_and_project, tests.tests, name='tests'),
48
- url(r'^(%s)/(%s)/build/([^/]+)/failures/$' % group_and_project, failures.failures, name='failures'),
49
47
  url(r'^(%s)/(%s)/build/([^/]+)/metrics/$' % group_and_project, metrics.build_metrics, name='build_metrics'),
50
48
  url(r'^(%s)/(%s)/build/([^/]+)/testjobs/$' % group_and_project, ci.testjobs, name='testjobs'),
51
49
  url(r'^(%s)/(%s)/build/([^/]+)/metadata/$' % group_and_project, views.build_metadata, name='build_metadata'),
squad/frontend/views.py CHANGED
@@ -455,13 +455,13 @@ def __test_run_suite_context__(request, group_slug, project_slug, build_version,
455
455
 
456
456
 
457
457
  @auth
458
- def test_run_suite_tests(request, group_slug, project_slug, build_version, testrun, suite_slug):
458
+ def test_run_suite_tests(request, group_slug, project_slug, build_version, testrun_id, suite_slug):
459
459
  context = __test_run_suite_context__(
460
460
  request,
461
461
  group_slug,
462
462
  project_slug,
463
463
  build_version,
464
- testrun,
464
+ testrun_id,
465
465
  suite_slug
466
466
  )
467
467
 
@@ -479,13 +479,13 @@ def test_run_suite_tests(request, group_slug, project_slug, build_version, testr
479
479
 
480
480
 
481
481
  @auth
482
- def test_run_suite_test_details(request, group_slug, project_slug, build_version, testrun, suite_slug, test_name):
482
+ def test_run_suite_test_details(request, group_slug, project_slug, build_version, testrun_id, suite_slug, test_name):
483
483
  context = __test_run_suite_context__(
484
484
  request,
485
485
  group_slug,
486
486
  project_slug,
487
487
  build_version,
488
- testrun,
488
+ testrun_id,
489
489
  suite_slug
490
490
  )
491
491
  test_name = test_name.replace("$", "/")
@@ -523,13 +523,13 @@ def test_run_suite_test_details(request, group_slug, project_slug, build_version
523
523
 
524
524
 
525
525
  @auth
526
- def test_run_suite_metrics(request, group_slug, project_slug, build_version, testrun, suite_slug):
526
+ def test_run_suite_metrics(request, group_slug, project_slug, build_version, testrun_id, suite_slug):
527
527
  context = __test_run_suite_context__(
528
528
  request,
529
529
  group_slug,
530
530
  project_slug,
531
531
  build_version,
532
- testrun,
532
+ testrun_id,
533
533
  suite_slug
534
534
  )
535
535
  all_metrics = context['status'].metrics.prefetch_related(
squad/version.py CHANGED
@@ -1 +1 @@
1
- __version__ = '1.73'
1
+ __version__ = '1.74'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: squad
3
- Version: 1.73
3
+ Version: 1.74
4
4
  Summary: Software Quality Dashboard
5
5
  Home-page: https://github.com/Linaro/squad
6
6
  Author: Antonio Terceiro
@@ -10,14 +10,14 @@ squad/manage.py,sha256=Z-LXT67p0R-IzwJ9fLIAacEZmU0VUjqDOSg7j2ZSxJ4,1437
10
10
  squad/settings.py,sha256=8IuKnLnrgaTcotaLA-LIbDFVgmAiPqzMQfl5h3F0-58,14097
11
11
  squad/socialaccount.py,sha256=vySqPwQ3qVVpahuJ-Snln8K--yzRL3bw4Nx27AsB39A,789
12
12
  squad/urls.py,sha256=JiEfVW8YlzLPE52c2aHzdn5kVVKK4o22w8h5KOA6QhQ,2776
13
- squad/version.py,sha256=sHskipjTMgFJdgmMxqr6ik42QdKNmzMsDIUsY8847_Y,21
13
+ squad/version.py,sha256=hk3bRYhpJeAKv63C_jpKjb80t-uOh4QIP5iQeZZmnU4,21
14
14
  squad/wsgi.py,sha256=SF8T0cQ0OPVyuYjO5YXBIQzvSXQHV0M2BTmd4gP1rPs,387
15
15
  squad/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
16
  squad/api/apps.py,sha256=Trk72p-iV1uGn0o5mdJn5HARUoHGbfgO49jwXvpkmdQ,141
17
17
  squad/api/ci.py,sha256=g9ZodQW3A1scNci3K3EGprxSyW3rL9xn-qBYxd-_8ns,6462
18
18
  squad/api/data.py,sha256=obKDV0-neEvj5lPF9VED2gy_hpfhGtLJABYvSY38ing,2379
19
19
  squad/api/filters.py,sha256=Zvp8DCJmiNquFWqvfVseEAAMYYPiT95RUjqKdzcqSnw,6917
20
- squad/api/rest.py,sha256=xtlQicNBrrHfGlHDyAslddomEQnGoRKYMZYXa6ij7Iw,75733
20
+ squad/api/rest.py,sha256=d9KBEjX7ru8MZqOWptIwMnC1t_kmm6Tqp1xv61LntUE,75953
21
21
  squad/api/urls.py,sha256=rmsdaL1uOCVSZ5x1redup9RliICmijaBjRK5ObsTkG8,1343
22
22
  squad/api/utils.py,sha256=Sa8QFId3_oSqD2UOoY3Kuh54LLDLPNMq2sub5ktd6Fs,1160
23
23
  squad/api/views.py,sha256=yGLUp6RtNI5vuae6cOStMuUpSia46LcEVam3eMXmEqY,3885
@@ -32,7 +32,7 @@ squad/ci/backend/__init__.py,sha256=yhpotXT9F4IdAOXvGQ3-17eOHAFwoaqf9SnMX17ab30,
32
32
  squad/ci/backend/fake.py,sha256=zzOXGesDCW9xiMQvXGD_jqCQF32yEd7hPM8DgfZxUok,2159
33
33
  squad/ci/backend/lava.py,sha256=5KEXFwKGjzXPPe7_rzMGF7YShYCLwmooTc8tQUpGBSM,33076
34
34
  squad/ci/backend/null.py,sha256=vP77Xj7roruehSjX8fJs7xK2aWxgaUA2id3P8nHNrEY,4949
35
- squad/ci/backend/tuxsuite.py,sha256=_mEE46-J2pGKZtq2sECe97EkjmeaSyiyoTr0q8mJ-ys,14090
35
+ squad/ci/backend/tuxsuite.py,sha256=wILo_rUfIYtL3yfctr-uX8eMUmcfJdfq15xEEXVM64o,15552
36
36
  squad/ci/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
37
37
  squad/ci/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
38
38
  squad/ci/management/commands/create_tuxsuite_boot_tests.py,sha256=JvjNusebLX71eyz9d-kaeCyekYSpzc1eXoeIqWK9ygo,4045
@@ -76,14 +76,14 @@ squad/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
76
76
  squad/core/admin.py,sha256=tFyRu_rhHN-ABJkEApaT4YtRYa4qxNe-8jou2H3Q0P4,7897
77
77
  squad/core/apps.py,sha256=Bl-4Yg0joKjygdifQG0ROIz4m5bHiPqytQ3G82uyzWc,143
78
78
  squad/core/callback.py,sha256=QhMf3ILkR2HAnqv1B2OW1pQEhrrIp4xRA9HhD6ywe1A,3066
79
- squad/core/comparison.py,sha256=pxEaOi7QCfZFudq1W6y0orWj86BqFJmRBdxJ8J2dcRc,23788
79
+ squad/core/comparison.py,sha256=LR3-Unv0CTmakFCDzF_h8fm2peTJzkv79mQWNau1iwI,24429
80
80
  squad/core/data.py,sha256=2zw56v7iYRTUc7wlhuUNgwIIMmK2w84hi-amR9J7EPU,2236
81
- squad/core/failures.py,sha256=CkLFGiKz1_BKWVRTkP-poQqsCePIqgVSIw6GKZjZ1eM,943
82
- squad/core/history.py,sha256=bCPqlpWnucTz78L4txRLfP51u5-4syYllZb1T_NwxUs,3112
83
- squad/core/models.py,sha256=ppfURnWBPYVEjc494mRE935Pb_Q2a_1PRZquY7YCFGs,60353
81
+ squad/core/failures.py,sha256=X6lJVghM2fOrd-RfuHeLlezW2pt7owDZ8eX-Kn_Qrt0,918
82
+ squad/core/history.py,sha256=APIgJ1fXAGyxoNgxVMn02kJzXhLR1x2SG4UyCcTyUEQ,3467
83
+ squad/core/models.py,sha256=wJ-0e5JNPxBendEMOk9YdxLH03tvikDAeGjzDcuSCxw,60343
84
84
  squad/core/notification.py,sha256=rOpO6F63w7_5l9gQgWBBEk-MFBjp7x_hVzoVIVyDze0,10030
85
85
  squad/core/plugins.py,sha256=PzgKrRvnwoe4ynLh0aRGAsL0CngHSu8dose37CO5ESg,5992
86
- squad/core/queries.py,sha256=pXTG5SVKNYkKXGOm_glUfyGym1dzDpA52dQ8gcUMorY,7680
86
+ squad/core/queries.py,sha256=78fhIJZWXIlDryewYAt96beK1VJad66Ufu8cg3dHh4w,7698
87
87
  squad/core/statistics.py,sha256=xyTHuhdBjcJ4AozZESjTzSD3dBmmCDgLpbg5XpeyO_M,1056
88
88
  squad/core/utils.py,sha256=L6c-suSBbNFBVEZF7GF8DcjD5lmXZLy7QO0T_2j9LCk,4893
89
89
  squad/core/locale/django.pot,sha256=XycSJyEaEpozGBS9zu7QTNQbffZC0D9eSJ-AwXaVZx4,2282
@@ -294,20 +294,19 @@ squad/frontend/apps.py,sha256=lKMd_HrIna5OZrfeWXndcGoIDR2KFmBFnMoGHtCGE4E,151
294
294
  squad/frontend/badges.py,sha256=rEgjkJQKZQT1mL9j9s47okTQO-J55eU8mDNHShijovY,5878
295
295
  squad/frontend/build_settings.py,sha256=_Hqw5npczuU01Zi6FGAiSbrtMMzK9eAXv-cX5U5btto,909
296
296
  squad/frontend/ci.py,sha256=lfglUArCj5iYRLZgC6mDgEN_k-dDqfCezXW3j2Fn_Uc,2244
297
- squad/frontend/comparison.py,sha256=iGmi5obsV5AALiuCgIvMWG3l6ZxsdO-npTS1EY9L10M,4547
297
+ squad/frontend/comparison.py,sha256=tZQOcXOOTU787VbZ2ueuXpWGUjiFSeq-O7fepSM0g-I,4547
298
298
  squad/frontend/extract.py,sha256=p88JGuBvaC4AMDkJi7lqzbj5ZGh6h2LSlV7tcXbmxDc,8491
299
- squad/frontend/failures.py,sha256=L71acQR_cDzu1Tqu9FtKWS0ABeOcQd-YBMsu63x9hUw,1945
300
299
  squad/frontend/forms.py,sha256=StPdrHsFsEoBKEOF6bBampLXbVXrZcEDkbuI4II1dCA,753
301
300
  squad/frontend/group_settings.py,sha256=mV0kJEfRo41AEbOZMxWXY1MpYNqSgWVqac0dUdwkGyk,6232
302
301
  squad/frontend/metrics.py,sha256=nGrfFHLG6g_DUYVJCIDlFLvLWhMrqJxX1Z0cckPBJlg,1149
303
302
  squad/frontend/project_settings.py,sha256=TtWz8h8Goeb3pccLy9jLUibeHqyqkdK8phL7_Vh_d0I,5045
304
303
  squad/frontend/queries.py,sha256=NxQF2woAf9A4Wk_ozHzZXOGmr2as-j7hqfvmsfJ-ojc,967
305
304
  squad/frontend/setup.py,sha256=NF9VunY1HJGB2HsHJss-go7EGmqr__JASddxiBCvmeQ,169
306
- squad/frontend/tests.py,sha256=-x7y454ha9l49UUrQkK1xrRbkBhPBMQYzb8lPKplVbQ,7877
307
- squad/frontend/urls.py,sha256=2DX52mnyKeMQljmi-iqrYLRLjodDJ50HxvxH9ba-99A,5010
305
+ squad/frontend/tests.py,sha256=q64Z6DyS6TiJTCzF6SXw4wZfvXR8c0A4t-f0ib1jJ0w,8713
306
+ squad/frontend/urls.py,sha256=biWauxwXR5j9kOfrSUqkv1Iqz-elB2aNViS9_UFoLzQ,4882
308
307
  squad/frontend/user_settings.py,sha256=IBogLusn-WNQiXqc_cF23s_tAWZ6wYVL6bbCfah_Aco,4189
309
308
  squad/frontend/utils.py,sha256=DeH58CJUI1dovpQrj3a-DcxNzM0cxsnBDOF0mrC4Qws,1364
310
- squad/frontend/views.py,sha256=u4A0OfNrT9_J5K-XUyS_Lb_K9uhi2p5aihtFJ1bSxf8,25677
309
+ squad/frontend/views.py,sha256=n4Xll_gRBHOLV1pki6DZpXgkYsNGWhtiu2fruGEZ9iI,25695
311
310
  squad/frontend/locale/django.pot,sha256=ENSJCBcxS2gWg5byPCMxU8gvTNR6qtJdsSi9emUUOvY,30178
312
311
  squad/frontend/locale/pl/LC_MESSAGES/django.po,sha256=yKoZEDP72mn_5WSNDP8zh5Pl3Z5Z9OGuoDG159NPvEI,36297
313
312
  squad/frontend/locale/pt/LC_MESSAGES/django.po,sha256=8srHMXuXV0ZuUM284LEwTb5sXWEMzlCmul1lKXsDZc8,36375
@@ -357,7 +356,7 @@ squad/frontend/templates/squad/_pagination.jinja2,sha256=bA5h-LtVjaz3HhdU2m3nsQG
357
356
  squad/frontend/templates/squad/_permissions.jinja2,sha256=cZy_2JJTy3ePSxZVArnt-_PfRlp5vCTrXtvMFL1b5xA,895
358
357
  squad/frontend/templates/squad/_project_list.jinja2,sha256=NLIEkUxA-na4UE05V27xVNZWPd3ER7SgN1Iisphhtuw,3988
359
358
  squad/frontend/templates/squad/_regressions_and_fixes.jinja2,sha256=il0FYsrvzjbKoPp0EH0rYdQNdPBqTFPo6KnwMhV9hrs,1263
360
- squad/frontend/templates/squad/_results_table.jinja2,sha256=u9-ja_DLGBo0_RfYQvQ3uqLlQ6r1xNHi39X3I48ww7k,2751
359
+ squad/frontend/templates/squad/_results_table.jinja2,sha256=riSxp2L_2c_8WRp6SR3lGl_SY_KwjO-TWmosZnQ-gVs,2644
361
360
  squad/frontend/templates/squad/_results_transitions_filter.jinja2,sha256=6h9PI9OCM6CeGVfGOkMc0kelZ0maQRQVnk2UOKw22H0,1680
362
361
  squad/frontend/templates/squad/_subscribe.jinja2,sha256=WawVotX1CgxEg8EoPNYzZvJi0k6jfhOIN-p1rGR7pWg,920
363
362
  squad/frontend/templates/squad/_test_results_envbox.jinja2,sha256=vmMh3AtJppRgNQfkfGC11mXqBskJNODzxShflInIzR0,5157
@@ -369,7 +368,7 @@ squad/frontend/templates/squad/_test_run_test.jinja2,sha256=S3ZgmbJGsN4I7dQimvSr
369
368
  squad/frontend/templates/squad/_unfinished_build.jinja2,sha256=U1dWITSX2LLxKquBee0jU8d76ssh6dFc4sxxFX921Cw,164
370
369
  squad/frontend/templates/squad/_user_menu.jinja2,sha256=7pIdeUEInR9FejvxIqtcxbrIpbLt460uokcVspNRogI,1256
371
370
  squad/frontend/templates/squad/base.jinja2,sha256=tY7Emy9BSDHirxm4QN2jDSQbkfD1nCsrHEcEzOefw88,4335
372
- squad/frontend/templates/squad/build-nav.jinja2,sha256=sjSYg8482HZ6prOYBIrUvKj2jmzOBwbBRC0PudED2Es,9006
371
+ squad/frontend/templates/squad/build-nav.jinja2,sha256=w4j2eevYQSkNe1YEIdo6nKHbHnLub_1iRWrTYNzwZi0,8800
373
372
  squad/frontend/templates/squad/build.jinja2,sha256=RJe6cDQpQVqO3IMdS-61wWiq4p5y1TTyr3FU0lk3kzo,6190
374
373
  squad/frontend/templates/squad/build_callbacks.jinja2,sha256=eNN455gEodq3EmAQ4LplmlXcKCCmSoKA88UScubqjX8,2493
375
374
  squad/frontend/templates/squad/build_metadata.jinja2,sha256=1yiNjM9OANusiVKg1M10bdxG_tGfvAU9nD-5cf5sdUM,312
@@ -379,7 +378,6 @@ squad/frontend/templates/squad/builds.jinja2,sha256=fgncIoQ4S5A0SBh4WKGey80PxDhu
379
378
  squad/frontend/templates/squad/compare.jinja2,sha256=-jlpzpSHqNp-A-GDlsqyGEPgmGMyGNKtubUCaIUX3FI,3879
380
379
  squad/frontend/templates/squad/compare_builds.jinja2,sha256=5fYnzM9LAJ5fMYv1fpjXYpEOsGidO4ItVXHGSNBoD0Q,3830
381
380
  squad/frontend/templates/squad/compare_projects.jinja2,sha256=UYr2PCzrHxCcjW63lULKjgKG6T1rSPr1Hzbd1rNM5Uk,3246
382
- squad/frontend/templates/squad/failures.jinja2,sha256=SWJpC1EO-GCdHu2Zy2djI9IJhDBBvEc-7W_HV6WhQQs,2951
383
381
  squad/frontend/templates/squad/group-nav.jinja2,sha256=5cA9FZRSrlcGjdMT_NpmrHZEEjyUiM8rUha7pJgCKFs,969
384
382
  squad/frontend/templates/squad/group.jinja2,sha256=APntfdkRqdgA2sGgx2xvVU1guVZbJNi0wqHw45rYyds,336
385
383
  squad/frontend/templates/squad/index.jinja2,sha256=jtVWPDRr4l0CPWvmV60NGoNinCCr7eHXy6xiSQy-yYA,1901
@@ -388,7 +386,7 @@ squad/frontend/templates/squad/login.jinja2,sha256=NPp20MpmgoGxWOschCUxcZMJKdnkV
388
386
  squad/frontend/templates/squad/metrics.jinja2,sha256=CH6Dw539PwlA5TI8rRWgQyQ7nbVMjI3eJO2fUOVoYbE,3158
389
387
  squad/frontend/templates/squad/project-nav.jinja2,sha256=AHN7r5TMvJ-NwEo_u3vlJg34J1njsuII32SgQuTfiwA,1526
390
388
  squad/frontend/templates/squad/project.jinja2,sha256=dQp1yyB2cZ6cmMT7bY8vl9v8-u-qd6elarEDAhnh7FU,618
391
- squad/frontend/templates/squad/test_history.jinja2,sha256=dM5h5M1OwZXeQ_kpl1Z1GsqhxqD6-tTz6O7gibct8bE,5721
389
+ squad/frontend/templates/squad/test_history.jinja2,sha256=mGAIKtR8dsn8QDmxgYLuCw8_keq_tsKOU0U05fkFkbQ,5724
392
390
  squad/frontend/templates/squad/test_run.jinja2,sha256=smxFEC7XnHu28Wj7iC2WQrGjpuPiqsxASpflbyYGG_A,1176
393
391
  squad/frontend/templates/squad/test_run_suite_metrics.jinja2,sha256=WGjlObw7ZTGoomTmON0O2QRHHdmEBOYf9xMSTWP83F4,1780
394
392
  squad/frontend/templates/squad/test_run_suite_test_details.jinja2,sha256=R6Md26LsUJGrvMfCsFNO67eMgMfyMZdrmF5MS7ydtHo,5478
@@ -419,7 +417,7 @@ squad/frontend/templates/squad/user_settings/profile.jinja2,sha256=l32GBmn3n5Dh0
419
417
  squad/frontend/templates/squad/user_settings/projects.jinja2,sha256=AsMPhI96V5Gt_SjeKKIne9P1pGH8tS_ik8uyFA_aKMQ,935
420
418
  squad/frontend/templates/squad/user_settings/subscriptions.jinja2,sha256=jhmkNMIdXUx2vswuBkaJwM6VpAe4ecV_bRH3Sg0wfT4,2054
421
419
  squad/frontend/templatetags/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
422
- squad/frontend/templatetags/squad.py,sha256=NYUTUgpRHFfeSep4S2mj0oqYqaZ9oWqUNme5L5OPjX4,7786
420
+ squad/frontend/templatetags/squad.py,sha256=QFIogzfhubm1gA45hZNNfizFpqp7BxElz4Mt1yxAHDQ,7746
423
421
  squad/plugins/__init__.py,sha256=9BSzy2jFIoDpWlhD7odPPrLdW4CC3btBhdFCvB651dM,152
424
422
  squad/plugins/example.py,sha256=BKpwd315lHRIuNXJPteibpwfnI6C5eXYHYdFYBtVmsI,89
425
423
  squad/plugins/gerrit.py,sha256=CqO2KnFQzu9utr_TQ-sGr1wg3ln0B-bS2-c0_i8T5-c,7009
@@ -430,9 +428,9 @@ squad/run/__main__.py,sha256=DOl8JOi4Yg7DdtwnUeGqtYBJ6P2k-D2psAEuYOjWr8w,66
430
428
  squad/run/listener.py,sha256=jBeOQhPGb4EdIREB1QsCzYuumsfJ-TqJPd3nR-0m59g,200
431
429
  squad/run/scheduler.py,sha256=CDJG3q5C0GuQuxwlMOfWTSSJpDdwbR6rzpbJfuA0xuw,277
432
430
  squad/run/worker.py,sha256=jtML0h5qKDuSbpJ6_rpWP4MT_rsGA7a24AhwGxBquzk,594
433
- squad-1.73.dist-info/COPYING,sha256=jOtLnuWt7d5Hsx6XXB2QxzrSe2sWWh3NgMfFRetluQM,35147
434
- squad-1.73.dist-info/METADATA,sha256=NwC6BioPY-C7FkHCTZQJfvFpdYgkcZ4wVAnsOM5ht-s,1245
435
- squad-1.73.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92
436
- squad-1.73.dist-info/entry_points.txt,sha256=apCDQydHZtvqV334ql6NhTJUAJeZRdtAm0TVcbbAi5Q,194
437
- squad-1.73.dist-info/top_level.txt,sha256=_x9uqE1XppiiytmVTl_qNgpnXus6Gsef69HqfliE7WI,6
438
- squad-1.73.dist-info/RECORD,,
431
+ squad-1.74.dist-info/COPYING,sha256=jOtLnuWt7d5Hsx6XXB2QxzrSe2sWWh3NgMfFRetluQM,35147
432
+ squad-1.74.dist-info/METADATA,sha256=6eAK6LZ4Cba9SY_SyjKNnlGeg-aB-pepP-slkiBz0pk,1245
433
+ squad-1.74.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92
434
+ squad-1.74.dist-info/entry_points.txt,sha256=apCDQydHZtvqV334ql6NhTJUAJeZRdtAm0TVcbbAi5Q,194
435
+ squad-1.74.dist-info/top_level.txt,sha256=_x9uqE1XppiiytmVTl_qNgpnXus6Gsef69HqfliE7WI,6
436
+ squad-1.74.dist-info/RECORD,,
@@ -1,65 +0,0 @@
1
- from django.core.paginator import InvalidPage, Paginator
2
- from django.http import Http404
3
- from django.shortcuts import render
4
-
5
- from squad.core.failures import failures_with_confidence
6
- from squad.core.models import Test
7
- from squad.http import auth
8
- from squad.frontend.views import get_build
9
-
10
-
11
- @auth
12
- def failures(request, group_slug, project_slug, build_version):
13
- project = request.project
14
- build = get_build(project, build_version)
15
- environments = project.environments.order_by("slug")
16
-
17
- failures_ids = build.tests.filter(
18
- result=False,
19
- ).exclude(
20
- has_known_issues=True,
21
- ).only(
22
- 'id'
23
- ).distinct('metadata_id').order_by('-metadata_id')
24
-
25
- failures = Test.objects.filter(id__in=failures_ids).only(
26
- 'metadata__suite', 'metadata__name', 'metadata__id',
27
- ).order_by(
28
- 'metadata__suite', 'metadata__name',
29
- ).distinct().values_list(
30
- 'metadata__suite', 'metadata__name', 'metadata__id', named=True,
31
- )
32
-
33
- search = request.GET.get('search', '')
34
- if search:
35
- failures = failures.filter(metadata__name__contains=search)
36
-
37
- try:
38
- page_num = request.GET.get('page', 1)
39
- paginator = Paginator(failures, 25)
40
- paginator.count = failures_ids.count()
41
- page = paginator.page(page_num)
42
- except InvalidPage as ip:
43
- raise Http404(('Invalid page (%(page_number)s): %(message)s') % {
44
- 'page_number': page_num,
45
- 'message': str(ip),
46
- })
47
-
48
- fwc = failures_with_confidence(project, build, page)
49
- rows = {}
50
- for t in fwc:
51
- if t.environment.slug not in rows:
52
- rows[t.environment.slug] = {}
53
-
54
- rows[t.environment.slug][t.full_name] = t
55
-
56
- context = {
57
- "project": project,
58
- "build": build,
59
- "environments": environments,
60
- "page": page,
61
- "rows": rows,
62
- "search": search,
63
- }
64
-
65
- return render(request, 'squad/failures.jinja2', context)
@@ -1,91 +0,0 @@
1
- {% extends "squad/base.jinja2" %}
2
-
3
- {% block content %}
4
-
5
- <div ng-app='Build'>
6
- {% include "squad/build-nav.jinja2" %}
7
- </div>
8
-
9
- <h2>{{ _('All test failures') }}</h2>
10
-
11
- {% with items=page %}
12
- {% include "squad/_pagination.jinja2" %}
13
- {% endwith %}
14
-
15
- <div>
16
- <div class='row row-bordered'>
17
- <div class='col-md-12 col-sm-12 filter'>
18
- <a id="searchLink"><button type='button' class='btn btn-primary fa fa-search'></button></a>
19
- <input name='search' id='search' type='text' placeholder='{{ _('Filter results ...') }}' value='{{search}}' />
20
- </div>
21
- </div>
22
-
23
- <table class='test-results'>
24
- <thead>
25
- <th>{{ _('Test') }}</th>
26
- {% for e in environments %}
27
- <th>{{ e.slug }}</th>
28
- {% endfor %}
29
- </thead>
30
- {% if rows|length == 0 %}
31
- <tr>
32
- <td colspan="{{environments|length|add(1)}}" class="alert alert-warning">
33
- <em>{{ _('This build has no failures yet.') }}</em>
34
- </td>
35
- </tr>
36
- {% else %}
37
- {% for i in page %}
38
- {% set fn = i.metadata__suite + "/" + i.metadata__name %}
39
- <tr>
40
- <td>{{ fn }}</td>
41
- {% for e in environments %}
42
- {% if e.slug in rows and fn in rows[e.slug]: %}
43
- <td class="{{ rows[e.slug][fn].status }}">
44
- {{ rows[e.slug][fn].confidence.score }}&nbsp;
45
- <span data-toggle="tooltip" title="{{ _('Click to display confidence info') }}">
46
- <button class="fa fa-info-circle popover-regressions-fixes"></button>
47
- <span title="{{ _('Confidence') }}" class="hidden">
48
- Pass: {{ rows[e.slug][fn].confidence.passes }}
49
- Count: {{ rows[e.slug][fn].confidence.count }}
50
- Threshold: {{ rows[e.slug][fn].confidence.threshold }}
51
- </span>
52
- </span>
53
- </td>
54
- {% else %}
55
- <td>&nbsp;</td>
56
- {% endif %}
57
- {% endfor %}
58
- <tr>
59
- {% endfor %}
60
- {% endif %}
61
- </table>
62
-
63
- </div>
64
-
65
- {% with items=page %}
66
- {% include "squad/_pagination.jinja2" %}
67
- {% endwith %}
68
-
69
- {% endblock %}
70
-
71
- {% block javascript %}
72
- <script type="module" src='{{static("squad/build.js")}}'></script>
73
- <script type="text/javascript" src='{{static("squad/table.js")}}'></script>
74
- <script type="text/javascript">
75
- $('[data-toggle="tooltip"]').tooltip();
76
- function loadSearchURL(pageParam, search) {
77
- searchURL = pageParam + "&search=" + search;
78
- window.location = searchURL;
79
- }
80
- $("#search").keypress(function(e) {
81
- if(e.which == 13) {
82
- window.location = "?page=1&search=" + $("#search").val();
83
- return false;
84
- }
85
- });
86
- $("#searchLink").click(function(event) {
87
- window.location = "?page=1&search=" + $("#search").val();
88
- return false;
89
- });
90
- </script>
91
- {% endblock %}
File without changes
File without changes