diff --git a/autopkgtest.py b/autopkgtest.py index bd012a3..7bf7113 100644 --- a/autopkgtest.py +++ b/autopkgtest.py @@ -351,7 +351,7 @@ class AutoPackageTest(object): self.requested_tests.setdefault(src, {}).setdefault( ver, {}).setdefault(arch, set()).add((trigsrc, trigver)) - def fetch_swift_results(self, swift_url, src, arch, trigger=None): + def fetch_swift_results(self, swift_url, src, arch): '''Download new results for source package/arch from swift''' # prepare query: get all runs with a timestamp later than latest_stamp @@ -387,14 +387,12 @@ class AutoPackageTest(object): for p in result_paths: self.fetch_one_result( - os.path.join(swift_url, 'autopkgtest-' + self.series, p, 'result.tar'), - src, arch, trigger) + os.path.join(swift_url, 'autopkgtest-' + self.series, p, 'result.tar'), src, arch) - def fetch_one_result(self, url, src, arch, trigger=None): + def fetch_one_result(self, url, src, arch): '''Download one result URL for source/arch - Remove matching pending_tests entries. If trigger is given (src, ver) - it is added to the triggers of that result. + Remove matching pending_tests entries. ''' try: f = urlopen(url) @@ -413,11 +411,7 @@ class AutoPackageTest(object): exitcode = int(tar.extractfile('exitcode').read().strip()) srcver = tar.extractfile('testpkg-version').read().decode().strip() (ressrc, ver) = srcver.split() - try: - testinfo = json.loads(tar.extractfile('testinfo.json').read().decode()) - except KeyError: - self.log_error('warning: %s does not have a testinfo.json' % url) - testinfo = {} + testinfo = json.loads(tar.extractfile('testinfo.json').read().decode()) except (KeyError, ValueError, tarfile.TarError) as e: self.log_error('%s is damaged, ignoring: %s' % (url, str(e))) # ignore this; this will leave an orphaned request in pending.txt @@ -431,13 +425,13 @@ class AutoPackageTest(object): return # parse recorded triggers in test result - if 'custom_environment' in testinfo: - for e in testinfo['custom_environment']: - if e.startswith('ADT_TEST_TRIGGERS='): - result_triggers = [tuple(i.split('/', 1)) for i in e.split('=', 1)[1].split() if '/' in i] - break + for e in testinfo.get('custom_environment', []): + if e.startswith('ADT_TEST_TRIGGERS='): + result_triggers = [tuple(i.split('/', 1)) for i in e.split('=', 1)[1].split() if '/' in i] + break else: - result_triggers = None + self.log_error('%s result has no ADT_TEST_TRIGGERS, ignoring') + return stamp = os.path.basename(os.path.dirname(url)) # allow some skipped tests, but nothing else @@ -446,68 +440,49 @@ class AutoPackageTest(object): self.log_verbose('Fetched test result for %s/%s/%s %s (triggers: %s): %s' % ( src, ver, arch, stamp, result_triggers, passed and 'pass' or 'fail')) - # remove matching test requests, remember triggers - satisfied_triggers = set() + # remove matching test requests for request_map in [self.requested_tests, self.pending_tests]: for pending_ver, pending_archinfo in request_map.get(src, {}).copy().items(): # don't consider newer requested versions if apt_pkg.version_compare(pending_ver, ver) > 0: continue - if result_triggers: - # explicitly recording/retrieving test triggers is the - # preferred (and robust) way of matching results to pending - # requests - for result_trigger in result_triggers: - satisfied_triggers.add(result_trigger) - try: - request_map[src][pending_ver][arch].remove(result_trigger) - self.log_verbose('-> matches pending request %s/%s/%s for trigger %s' % - (src, pending_ver, arch, str(result_trigger))) - except (KeyError, ValueError): - self.log_verbose('-> does not match any pending request for %s/%s/%s' % - (src, pending_ver, arch)) - else: - # ... but we still need to support results without - # testinfo.json and recorded triggers until we stop caring about - # existing wily and trusty results; match the latest result to all - # triggers for src that have at least the requested version + for result_trigger in result_triggers: try: - t = pending_archinfo[arch] - self.log_verbose('-> matches pending request %s/%s for triggers %s' % - (src, pending_ver, str(t))) - satisfied_triggers.update(t) - del request_map[src][pending_ver][arch] - except KeyError: - self.log_verbose('-> does not match any pending request for %s/%s' % - (src, pending_ver)) - - # FIXME: this is a hack that mostly applies to re-running tests - # manually without giving a trigger. Tests which don't get - # triggered by a particular kernel version are fine with that, so - # add some heuristic once we drop the above code. - if trigger: - satisfied_triggers.add(trigger) + request_map[src][pending_ver][arch].remove(result_trigger) + self.log_verbose('-> matches pending request %s/%s/%s for trigger %s' % + (src, pending_ver, arch, str(result_trigger))) + except (KeyError, ValueError): + self.log_verbose('-> does not match any pending request for %s/%s/%s' % + (src, pending_ver, arch)) # add this result src_arch_results = self.test_results.setdefault(src, {}).setdefault(arch, [stamp, {}, False]) - if passed: - # update ever_passed field, unless we got triggered from - # linux-meta*: we trigger separate per-kernel tests for reverse - # test dependencies, and we don't want to track per-trigger - # ever_passed. This would be wrong for everything except the - # kernel, and the kernel team tracks per-kernel regressions already - if not result_triggers or not result_triggers[0][0].startswith('linux-meta'): - src_arch_results[2] = True - if satisfied_triggers: - for trig in satisfied_triggers: - src_arch_results[1].setdefault(ver, {})[trig[0] + '/' + trig[1]] = passed - else: - # this result did not match any triggers? then we are in backwards - # compat mode for results without recorded triggers; update all - # results - for trig in src_arch_results[1].setdefault(ver, {}): - src_arch_results[1][ver][trig] = passed + trigmap = src_arch_results[1].setdefault(ver, {}) + for trig in result_triggers: + trig_idx = trig[0] + '/' + trig[1] + + # If a test runs because of its own package (newer version), ensure + # that we got a new enough version; FIXME: this should be done more + # generically by matching against testpkg-versions + if trig[0] == src and apt_pkg.version_compare(ver, trig[1]) < 0: + self.log_error('test trigger %s, but run for older version %s, ignoring' % + (trig_idx, ver)) + continue + + # passed results are always good, but don't clobber existing passed + # results with failures from re-runs + if passed or trig_idx not in trigmap: + trigmap[trig_idx] = passed + + # update ever_passed field, unless we got triggered from + # linux-meta*: we trigger separate per-kernel tests for reverse + # test dependencies, and we don't want to track per-trigger + # ever_passed. This would be wrong for everything except the + # kernel, and the kernel team tracks per-kernel regressions already + if passed and not result_triggers[0][0].startswith('linux-meta'): + src_arch_results[2] = True + # update latest_stamp if stamp > src_arch_results[0]: src_arch_results[0] = stamp @@ -640,7 +615,7 @@ class AutoPackageTest(object): if arch not in self.pending_tests.get(trigpkg, {}).get(trigver, {}): self.log_verbose('Checking for new results for failed %s on %s for trigger %s/%s' % (pkg, arch, trigpkg, trigver)) - self.fetch_swift_results(self.britney.options.adt_swift_url, pkg, arch, (trigpkg, trigver)) + self.fetch_swift_results(self.britney.options.adt_swift_url, pkg, arch) # update the results cache with open(self.results_cache_file + '.new', 'w') as f: diff --git a/tests/test_autopkgtest.py b/tests/test_autopkgtest.py index a8f9747..60afc8d 100755 --- a/tests/test_autopkgtest.py +++ b/tests/test_autopkgtest.py @@ -303,7 +303,7 @@ lightgreen 1 i386 green 2 res = json.load(f) self.assertEqual(res['green']['i386'], ['20150101_100200@', - {'1': {}, '2': {'green/2': True}}, + {'1': {'passedbefore/1': True}, '2': {'green/2': True}}, True]) self.assertEqual(res['lightgreen']['amd64'], ['20150101_100101@', @@ -369,47 +369,31 @@ lightgreen 1 i386 green 2 self.assertIn('darkgreen 1 amd64 green 2', self.pending_requests) self.assertIn('lightgreen 1 i386 green 2', self.pending_requests) - def test_multi_rdepends_with_tests_mixed_no_recorded_triggers(self): - '''Multiple reverse dependencies with tests (mixed results), no recorded triggers''' + def test_results_without_triggers(self): + '''Old results without recorded triggers''' - # green has passed before on i386 only, therefore ALWAYSFAILED on amd64 self.swift.set_results({'autopkgtest-series': { - 'series/i386/g/green/20150101_100000@': (0, 'green 1', tr('passedbefore/1')), + 'series/i386/d/darkgreen/20150101_100000@': (0, 'darkgreen 1'), + 'series/amd64/l/lightgreen/20150101_100100@': (0, 'lightgreen 1'), + 'series/amd64/l/lightgreen/20150101_100101@': (4, 'lightgreen 1'), + 'series/i386/g/green/20150101_100100@': (0, 'green 1', tr('passedbefore/1')), + 'series/i386/g/green/20150101_100200@': (0, 'green 2'), + 'series/amd64/g/green/20150101_100201@': (4, 'green 2'), }}) - # first run requests tests and marks them as pending + # none of the above results should be accepted self.do_test( [('libgreen1', {'Version': '2', 'Source': 'green', 'Depends': 'libc6'}, 'autopkgtest')], {'green': (False, {'green 2': {'amd64': 'RUNNING-ALWAYSFAILED', 'i386': 'RUNNING'}, 'lightgreen 1': {'amd64': 'RUNNING-ALWAYSFAILED', 'i386': 'RUNNING-ALWAYSFAILED'}, 'darkgreen 1': {'amd64': 'RUNNING-ALWAYSFAILED', 'i386': 'RUNNING-ALWAYSFAILED'}, }) - }, - {'green': [('old-version', '1'), ('new-version', '2')]}) - - # second run collects the results - self.swift.set_results({'autopkgtest-series': { - 'series/i386/d/darkgreen/20150101_100000@': (0, 'darkgreen 1'), - 'series/amd64/l/lightgreen/20150101_100100@': (0, 'lightgreen 1'), - 'series/amd64/l/lightgreen/20150101_100101@': (4, 'lightgreen 1'), - 'series/i386/g/green/20150101_100200@': (0, 'green 2'), - 'series/amd64/g/green/20150101_100201@': (4, 'green 2'), - }}) - - out = self.do_test( - [], - {'green': (False, {'green 2': {'amd64': 'ALWAYSFAIL', 'i386': 'PASS'}, - 'lightgreen 1': {'amd64': 'REGRESSION', 'i386': 'RUNNING-ALWAYSFAILED'}, - 'darkgreen 1': {'amd64': 'RUNNING-ALWAYSFAILED', 'i386': 'PASS'}, - }) }) - # not expecting any failures to retrieve from swift - self.assertNotIn('Failure', out, out) - # there should be some pending ones self.assertIn('darkgreen 1 amd64 green 2', self.pending_requests) self.assertIn('lightgreen 1 i386 green 2', self.pending_requests) + self.assertIn('green 2 i386 green 2', self.pending_requests) def test_multi_rdepends_with_tests_regression(self): '''Multiple reverse dependencies with tests (regression)''' @@ -1452,12 +1436,13 @@ fancy 1 i386 linux-meta-lts-grumpy 1 ('linux-image-64only', {'Source': 'linux-meta-64only', 'Architecture': 'amd64'}, None), ], {'linux-meta': (True, {'fancy 1': {'amd64': 'PASS', 'i386': 'PASS'}}), - # we don't have an explicit result for amd64, so the old one counts - 'linux-meta-lts-grumpy': (True, {'fancy 1': {'amd64': 'ALWAYSFAIL', 'i386': 'ALWAYSFAIL'}}), + # we don't have an explicit result for amd64 + 'linux-meta-lts-grumpy': (True, {'fancy 1': {'amd64': 'RUNNING-ALWAYSFAILED', 'i386': 'ALWAYSFAIL'}}), 'linux-meta-64only': (True, {'fancy 1': {'amd64': 'PASS'}}), }) - self.assertEqual(self.pending_requests, '') + self.assertEqual(self.pending_requests, + 'fancy 1 amd64 linux-meta-lts-grumpy 1\n') def test_kernel_triggered_tests(self): '''linux, lxc, glibc tests get triggered by linux-meta* uploads'''