squad 1.73__py3-none-any.whl → 1.75__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,16 @@ 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
+ )
1396
1404
 
1397
1405
 
1398
1406
  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')
@@ -99,9 +100,14 @@ class Backend(BaseBackend):
99
100
 
100
101
  ('BUILD', 'linaro@anders', '1yPYGaOEPNwr2pCqBgONY43zORq')
101
102
 
103
+ The leading string determines the type of the tuxsuite object:
104
+ - BUILD
105
+ - OEBUILD
106
+ - TEST
107
+
102
108
  """
103
109
 
104
- regex = r'^(BUILD|TEST):([0-9a-z_\-]+@[0-9a-z_\-]+)#([a-zA-Z0-9]+)$'
110
+ regex = r'^(OEBUILD|BUILD|TEST):([0-9a-z_\-]+@[0-9a-z_\-]+)#([a-zA-Z0-9]+)$'
105
111
  matches = re.findall(regex, job_id)
106
112
  if len(matches) == 0:
107
113
  raise FetchIssue(f'Job id "{job_id}" does not match "{regex}"')
@@ -112,18 +118,19 @@ class Backend(BaseBackend):
112
118
  def generate_job_id(self, result_type, result):
113
119
  """
114
120
  The job id for TuxSuite results is generated using 3 pieces of info:
115
- 1. If it's either "BUILD" or "TEST" result;
121
+ 1. If it's either "BUILD", "OEBUILD" or "TEST" result;
116
122
  2. The TuxSuite project. Ex: "linaro/anders"
117
123
  3. The ksuid of the object. Ex: "1yPYGaOEPNwr2pfqBgONY43zORp"
118
124
 
119
125
  A couple examples for job_id are:
120
126
  - BUILD:linaro@anders#1yPYGaOEPNwr2pCqBgONY43zORq
127
+ - OEBUILD:linaro@lkft#2Wetiz7Qs0TbtfPgPT7hUObWqDK
121
128
  - TEST:arm@bob#1yPYGaOEPNwr2pCqBgONY43zORp
122
129
 
123
130
  Then it's up to SQUAD's TuxSuite backend to parse the job_id
124
131
  and fetch results properly.
125
132
  """
126
- _type = "TEST" if result_type == "test" else "BUILD"
133
+ _type = result_type.upper()
127
134
  project = result["project"].replace("/", "@")
128
135
  uid = result["uid"]
129
136
  return f"{_type}:{project}#{uid}"
@@ -146,6 +153,72 @@ class Backend(BaseBackend):
146
153
 
147
154
  return None
148
155
 
156
+ def set_build_name(self, test_job, job_url, results, metadata, settings):
157
+ """
158
+ Tuxsuite allows plans with builds and tests within.
159
+ Some of these plans also support "special tests", which are
160
+ kind a sanity test to run before spinning a heavy load of tests.
161
+
162
+ Here's the default plan hierarchy:
163
+ - build -> tests
164
+
165
+ Now with sanity tests in between:
166
+ - build -> sanity tests -> tests
167
+
168
+ SQUAD needs to get to the build level in
169
+ order to retrieve the build object and finally retrieve
170
+ its build name attribute
171
+ """
172
+
173
+ build_id = results['waiting_for']
174
+ if build_id is None:
175
+ return
176
+
177
+ items = build_id.split('#')
178
+ if len(items) == 2:
179
+ _type = items[0]
180
+ _id = items[1]
181
+ else:
182
+ _type = "BUILD"
183
+ _id = items[0]
184
+
185
+ test_id = results['uid']
186
+
187
+ try:
188
+ # Check if the target build or sanity test is fetched
189
+ job_id = self.generate_job_id(_type.lower(), results)
190
+ job_id = job_id.replace(test_id, _id)
191
+
192
+ candidate = TestRun.objects.get(
193
+ build=test_job.target_build,
194
+ job_id=job_id
195
+ )
196
+
197
+ build_name = candidate.metadata.get('build_name')
198
+ if build_name:
199
+ metadata['build_name'] = build_name
200
+ return
201
+
202
+ except TestRun.DoesNotExist:
203
+ pass
204
+
205
+ # It is a sanity test, an extra request is needed to get build id
206
+ if _type == 'TEST':
207
+ follow_test_url = job_url.replace(test_id, _id)
208
+ test_json = self.fetch_url(follow_test_url).json()
209
+ build_id = test_json.get('waiting_for')
210
+
211
+ build_id = build_id.replace('BUILD#', '')
212
+ build_url = job_url.replace(test_id, build_id).replace('/tests/', '/builds/')
213
+
214
+ build_metadata = self.fetch_url(build_url).json()
215
+
216
+ build_metadata_keys = settings.get('TEST_BUILD_METADATA_KEYS', [])
217
+ metadata.update({k: build_metadata.get(k) for k in build_metadata_keys})
218
+
219
+ if 'toolchain' in build_metadata_keys and 'kconfig' in build_metadata_keys and metadata['build_name'] in [None, '']:
220
+ metadata['build_name'] = self.generate_test_name(build_metadata)
221
+
149
222
  def parse_build_results(self, test_job, job_url, results, settings):
150
223
  required_keys = ['build_status', 'warnings_count', 'download_url', 'retry']
151
224
  self.__check_required_keys__(required_keys, results)
@@ -163,6 +236,7 @@ class Backend(BaseBackend):
163
236
  metadata_keys = settings.get('BUILD_METADATA_KEYS', [])
164
237
  metadata = {k: results.get(k) for k in metadata_keys}
165
238
  metadata['job_url'] = job_url
239
+ metadata['job_id'] = test_job.job_id
166
240
  metadata['config'] = urljoin(results.get('download_url') + '/', 'config')
167
241
  metadata['build_name'] = test_name
168
242
 
@@ -189,6 +263,30 @@ class Backend(BaseBackend):
189
263
 
190
264
  return status, completed, metadata, tests, metrics, logs
191
265
 
266
+ def parse_oebuild_results(self, test_job, job_url, results, settings):
267
+ required_keys = ['download_url', 'result']
268
+ self.__check_required_keys__(required_keys, results)
269
+
270
+ # Make metadata
271
+ metadata_keys = settings.get('OEBUILD_METADATA_KEYS', [])
272
+ metadata = {k: results.get(k) for k in metadata_keys}
273
+ metadata['job_url'] = job_url
274
+ metadata['job_id'] = test_job.job_id
275
+
276
+ sources = results.get('sources')
277
+ if sources:
278
+ metadata['sources'] = sources
279
+
280
+ # Create tests and metrics
281
+ tests = {}
282
+ metrics = {}
283
+ completed = True
284
+ status = 'Complete'
285
+ tests['build/build'] = 'pass' if results['result'] == 'pass' else 'fail'
286
+ logs = self.fetch_url(results['download_url'], 'build.log').text
287
+
288
+ return status, completed, metadata, tests, metrics, logs
289
+
192
290
  def parse_test_results(self, test_job, job_url, results, settings):
193
291
  status = 'Complete'
194
292
  completed = True
@@ -200,6 +298,7 @@ class Backend(BaseBackend):
200
298
  metadata_keys = settings.get('TEST_METADATA_KEYS', [])
201
299
  metadata = {k: results.get(k) for k in metadata_keys}
202
300
  metadata['job_url'] = job_url
301
+ metadata['job_id'] = test_job.job_id
203
302
 
204
303
  # Set job name
205
304
  try:
@@ -227,25 +326,8 @@ class Backend(BaseBackend):
227
326
  # Retrieve TuxRun log
228
327
  logs = self.fetch_url(job_url + '/', 'logs?format=txt').text
229
328
 
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)
329
+ # Follow up the chain and retrieve build name
330
+ self.set_build_name(test_job, job_url, results, metadata, settings)
249
331
 
250
332
  # Create a boot test
251
333
  boot_test_name = 'boot/' + (metadata.get('build_name') or 'boot')
squad/ci/models.py CHANGED
@@ -4,6 +4,7 @@ import traceback
4
4
  import yaml
5
5
  from io import StringIO
6
6
  from django.db import models, transaction, DatabaseError
7
+ from django.db.models import Q
7
8
  from django.utils import timezone
8
9
  from dateutil.relativedelta import relativedelta
9
10
 
@@ -66,6 +67,19 @@ class Backend(models.Model):
66
67
  yield test_job
67
68
 
68
69
  def fetch(self, job_id):
70
+ # Job statuses can be one of:
71
+ # * None
72
+ # * Submitted
73
+ # * Scheduling
74
+ # * Scheduled
75
+ # * Running
76
+ # * Complete
77
+ # * Incomplete
78
+ # * Canceled
79
+ # * Fetching
80
+ # Only jobs in 'Complete', 'Canceled' and 'Incomplete' are eligible for fetching
81
+
82
+ job_status = None
69
83
  with transaction.atomic():
70
84
  try:
71
85
  test_job = TestJob.objects.select_for_update(nowait=True).get(pk=job_id)
@@ -91,6 +105,8 @@ class Backend(models.Model):
91
105
  test_job.save()
92
106
  return
93
107
 
108
+ job_status = test_job.job_status
109
+ test_job.job_status = 'Fetching'
94
110
  test_job.fetched = True
95
111
  test_job.fetched_at = timezone.now()
96
112
  test_job.save()
@@ -130,10 +146,16 @@ class Backend(models.Model):
130
146
  except DuplicatedTestJob as exception:
131
147
  logger.error('Failed to fetch test_job(%d): "%s"' % (test_job.id, str(exception)))
132
148
 
149
+ if test_job.testrun:
150
+ self.__postprocess_testjob__(test_job)
151
+
152
+ # Removed the 'Fetching' job_status only after eventual plugins
153
+ # are finished, this garantees extra tests and metadata to
154
+ # be in SQUAD before the build is considered finished
155
+ test_job.job_status = job_status
133
156
  test_job.save()
134
157
 
135
158
  if test_job.testrun:
136
- self.__postprocess_testjob__(test_job)
137
159
  UpdateProjectStatus()(test_job.testrun)
138
160
 
139
161
  def __postprocess_testjob__(self, test_job):
@@ -177,9 +199,16 @@ class Backend(models.Model):
177
199
  return '%s (%s)' % (self.name, self.implementation_type)
178
200
 
179
201
 
202
+ class TestJobManager(models.Manager):
203
+
204
+ def pending(self):
205
+ return self.filter(Q(fetched=False) | Q(job_status='Fetching'))
206
+
207
+
180
208
  class TestJob(models.Model):
181
209
 
182
210
  __test__ = False
211
+ objects = TestJobManager()
183
212
 
184
213
  # input - internal
185
214
  backend = models.ForeignKey(Backend, related_name='test_jobs', on_delete=models.CASCADE)
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
@@ -584,10 +584,11 @@ class Build(models.Model):
584
584
  def important_metadata(self):
585
585
  wanted = (self.project.important_metadata_keys or '').splitlines()
586
586
  m = self.metadata
587
+ metadata = self.metadata
587
588
  if len(wanted):
588
- return {k: m[k] for k in wanted if k in m}
589
- else:
590
- return self.metadata
589
+ metadata = {k: m[k] for k in wanted if k in m}
590
+
591
+ return metadata
591
592
 
592
593
  @property
593
594
  def has_extra_metadata(self):
@@ -619,7 +620,7 @@ class Build(models.Model):
619
620
  # dependency on squad.ci, what in theory violates our architecture.
620
621
  testjobs = self.test_jobs
621
622
  if testjobs.count() > 0:
622
- if testjobs.filter(fetched=False).count() > 0:
623
+ if testjobs.pending().count() > 0:
623
624
  # a build that has pending CI jobs is NOT finished
624
625
  reasons.append("There are unfinished CI jobs")
625
626
  else:
@@ -1025,7 +1026,7 @@ class Test(models.Model):
1025
1026
 
1026
1027
  @property
1027
1028
  def passes(self):
1028
- return sum(1 for t in self.tests if t.status == "pass")
1029
+ return sum(1 for t in self.tests if t.result)
1029
1030
 
1030
1031
  @property
1031
1032
  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):