Merge ~juliank/autopkgtest/+git/development:master into ~ubuntu-release/autopkgtest/+git/development:master

Proposed by Julian Andres Klode
Status: Merged
Merge reported by: Iain Lane
Merged at revision: 3c1043c40694e77c3c05fb3b07afcf35cc965fae
Proposed branch: ~juliank/autopkgtest/+git/development:master
Merge into: ~ubuntu-release/autopkgtest/+git/development:master
Diff against target: 1951 lines (+982/-127) (has conflicts)
18 files modified
.gitlab-ci.yml (+26/-0)
debian/changelog (+109/-5)
debian/control (+10/-3)
debian/copyright (+1/-1)
doc/README.package-tests.rst (+63/-3)
lib/VirtSubproc.py (+11/-2)
lib/adt_testbed.py (+137/-35)
lib/adtlog.py (+8/-0)
lib/autopkgtest_args.py (+8/-0)
lib/testdesc.py (+94/-21)
runner/autopkgtest (+44/-11)
runner/autopkgtest.1 (+23/-8)
tests/autopkgtest (+368/-19)
tests/testdesc (+18/-16)
virt/autopkgtest-virt-lxc (+12/-0)
virt/autopkgtest-virt-lxd (+12/-0)
virt/autopkgtest-virt-qemu (+30/-3)
virt/autopkgtest-virt-qemu.1 (+8/-0)
Conflict in virt/autopkgtest-virt-lxc
Conflict in virt/autopkgtest-virt-lxd
Reviewer Review Type Date Requested Status
Iain Lane Approve
Review via email: mp+355422@code.launchpad.net

Description of the change

Should force push instead of merge

To post a comment you must log in.
Revision history for this message
Julian Andres Klode (juliank) wrote :

The following commit has been dropped:

commit b442efb577be63f4f03fcc5d821a68dc553628a3 (ubuntu/master)
Author: Steve Langasek <email address hidden>
Date: Fri Aug 31 21:53:49 2018 -0700

    Don't pick uids between 500 and 999 for our container user

    these are system users under Debian Policy, and we now have a common user
    in this uid range, systemd-coredump, with a non-writable homedir. That
    messes up a lot of tests.

It was no longer necessary, upstream took it a bit further and made it pick in [1000,59999] rather than any >= 1000.

Revision history for this message
Iain Lane (laney) wrote :

Done, thanks (to pings on #debian-release).

I re-rebased, so this probably won't get marked as merged automatically.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
2new file mode 100644
3index 0000000..f28fa86
4--- /dev/null
5+++ b/.gitlab-ci.yml
6@@ -0,0 +1,26 @@
7+quicktests:
8+ stage: test
9+ image: debian:sid
10+ script:
11+ - apt-get update
12+ - apt-get install -y autodep8 libdpkg-perl pycodestyle pyflakes3 python3-debian
13+ - tests/autopkgtest_args
14+ - tests/pycodestyle
15+ - tests/pyflakes
16+ - tests/testdesc
17+
18+tests-sid:
19+ stage: test
20+ image: debian:sid
21+ script:
22+ - apt-get update
23+ - apt-get install -y apt-utils autodep8 build-essential debhelper libdpkg-perl procps python3 python3-debian
24+ - tests/autopkgtest NullRunner NullRunnerRoot ChrootRunner
25+
26+tests-stretch:
27+ stage: test
28+ image: debian:stretch
29+ script:
30+ - apt-get update
31+ - apt-get install -y apt-utils autodep8 build-essential debhelper libdpkg-perl procps python3 python3-debian
32+ - tests/autopkgtest NullRunner NullRunnerRoot ChrootRunner
33diff --git a/debian/changelog b/debian/changelog
34index 212d827..ec56090 100644
35--- a/debian/changelog
36+++ b/debian/changelog
37@@ -1,3 +1,107 @@
38+autopkgtest (5.5) unstable; urgency=medium
39+
40+ [ Antonio Terceiro ]
41+ * Different implementation to make sure to never pass an empty string as
42+ username to `su`
43+ * When using systemd, reboot via systemd-run
44+ * test_breaks_testbed: allow output on stderr
45+
46+ [ Paul Gevers ]
47+ * Fix whitespace in old parts of d/changelog
48+ * Add gitlab pipelines to autopkgtest
49+ * Fixes to be conform pycodestyle
50+ * tests: replace encoding='utf-8' by universal_newlines=True for
51+ python3.5 (stretch)
52+ * Fix missing empty line in commit 30470603
53+ * Add option to disable the apt fallback when dependencies can't be
54+ installed due to pinning
55+ * Make autopkgtest Build-Depends-Arch aware (Closes: #903398)
56+ * Add skip-not-installable restriction (Closes: #905311)
57+ * Drop ancient X-Python3-Version (thanks lintian)
58+
59+ [ Ian Jackson ]
60+ * doc/README.package-tests.rst: document hint-testsuite-triggers
61+ (Closes: #905310)
62+
63+ [ Simon McVittie ]
64+ * runner: Document exit status 14 as possible
65+ * tests: Don't rely on being able to install B-D as non-root (Closes:
66+ #905677)
67+ * tests: Skip test_tree_build_needed_success if necessary
68+ * runner: Exit 8 if every test was skipped or otherwise ignored
69+ (Closes: #901804)
70+
71+ -- Paul Gevers <elbrus@debian.org> Fri, 10 Aug 2018 20:27:40 +0200
72+
73+autopkgtest (5.4.2) unstable; urgency=medium
74+
75+ [ Martin Pitt ]
76+ * doc: Document "test-name" feature
77+ * doc/README.package-tests.rst: Fix formatting of previous commit
78+
79+ [ Niko Tyni ]
80+ * Fix versioned provides test dependency handling
81+ * Refactor _synthesize_deps() parameters
82+ * Properly handle virtual packages in alternative test dependencies
83+ (Closes: #903975)
84+ * Add test cases for alternative test dependencies improvements
85+
86+ [ Antonio Terceiro ]
87+ * Always pass a non-empty user string to `su` (Closes: #904870)
88+
89+ -- Antonio Terceiro <terceiro@debian.org> Sun, 29 Jul 2018 12:13:06 -0300
90+
91+autopkgtest (5.4.1) unstable; urgency=medium
92+
93+ * README.package-tests.rst: explain that architecture qualifiers are
94+ allowed in Test-Depends
95+ * SchrootRunner: add testcases for arch qualifiers in Test-Depends
96+ * Fix regression on arch-specific packages with '@' in Test-Depends
97+ (Closes: #903000)
98+
99+ -- Paul Gevers <elbrus@debian.org> Fri, 06 Jul 2018 20:05:39 +0200
100+
101+autopkgtest (5.4) unstable; urgency=medium
102+
103+ [ Simon McVittie ]
104+ * README.package-tests.rst: Document AUTOPKGTEST_NORMAL_USER
105+ * d/tests/lxd: Don't assume all test-runners set AUTOPKGTEST_NORMAL_USER
106+ * qemu: Only set up base image device if requested (Closes: #892023, #862362)
107+ * qemu: Document --baseimage
108+ * qemu: Update test for --baseimage no longer being the default
109+ * qemu, lxc, lxd: Look for a user account in the 1000-59999 range
110+ (Closes: #897170)
111+ * qemu: Add a shortcut for running tests on an EFI-booted image (Closes:
112+ #898340)
113+ * doc: Describe how to parse Features, Restrictions, Classes
114+ * Add support for flaky tests (Closes: #851558)
115+ * Add support for tests declaring themselves to have been skipped
116+ * autopkgtest(1): Document FLAKY as a possible summary status
117+
118+ [ Balint Reczey ]
119+ * Fix bashism in retrying apt update
120+
121+ [ Paul Gevers ]
122+ * Enable testing to continue after badpkg (Closes: #832751)
123+ * adt_testbed.py fix missed piece of regular expression in commit a7e1dad
124+ * d/tests/autopkgtest Update SchrootRunner
125+ * runner/autopkgtest: Drop Ubuntu 12.04 fallback
126+ * manpage: make ordering consistent with --help (Closes: #901643)
127+
128+ [ Julian Andres Klode ]
129+ * ssh-setup/nova: Add support for keystone v3 auth (LP: #1767433)
130+
131+ [ Rafael Laboissiere ]
132+ * Set Maintainer email address to team+ci@tracker.debian.org
133+
134+ [ Niko Tyni ]
135+ * Add a couple of testcases for versioned provides support
136+ * Ensure synthesized test dependencies are not satisfied by versioned Provides
137+ (Closes: #867081)
138+ * Remove the old '(>= 0)' hack for ensuring '@' pulls in real packages
139+
140+ -- Paul Gevers <elbrus@debian.org> Mon, 02 Jul 2018 11:50:21 +0200
141+
142 autopkgtest (5.3.1) unstable; urgency=medium
143
144 [ Iain Lane ]
145@@ -3398,11 +3502,11 @@ autopkgtest (1.0.4) gutsy; urgency=low
146 * adt-run:
147 - Options for setting timeouts.
148 - Increase default timeouts 100s, 3ks, 10ks, 100ks.
149-
150+
151 * hosts/chinstrap/*:
152 Screen-scrape Launchpad to get existing bugs and generate a suitable
153- suppression file so we file a bug iff there isn't one already.
154-
155+ suppression file so we file a bug iff there isn't one already.
156+
157 * hosts/cadmium/*:
158 New directory for convenience scripts etc on Canonical buildd.
159
160@@ -3541,7 +3645,7 @@ autopkgtest (0.8.2) gutsy unstable; urgency=low
161 * Fix handling of pre-built source trees.
162 * Fix cleanup handling not to delete tmpdir before resetting testbed's
163 apt.
164-
165+
166 -- Ian Jackson <ian@davenant.greenend.org.uk> Fri, 27 Apr 2007 16:06:15 +0100
167
168 autopkgtest (0.8.1) feisty; urgency=low
169@@ -3571,7 +3675,7 @@ autopkgtest (0.8.0) feisty; urgency=low
170 * Longer timeouts by default.
171 * Print `adt-run: trace' for trace output.
172 * Show all apt stdout (including dpkg stdout) in contemporaneous trace.
173-
174+
175 -- Ian Jackson <iwj@ubuntu.com> Tue, 3 Apr 2007 20:08:13 +0100
176
177 autopkgtest (0.7.2) feisty; urgency=low
178diff --git a/debian/control b/debian/control
179index eed730e..d1a838b 100644
180--- a/debian/control
181+++ b/debian/control
182@@ -1,5 +1,5 @@
183 Source: autopkgtest
184-Maintainer: Debian CI team <debian-ci@lists.debian.org>
185+Maintainer: Debian CI team <team+ci@tracker.debian.org>
186 Uploaders: Ian Jackson <ijackson@chiark.greenend.org.uk>, Martin Pitt <mpitt@debian.org>, Antonio Terceiro <terceiro@debian.org>, Paul Gevers <elbrus@debian.org>
187 Section: devel
188 Priority: optional
189@@ -14,7 +14,6 @@ Build-Depends: debhelper (>= 9),
190 pycodestyle | pep8,
191 Vcs-Git: https://salsa.debian.org/ci-team/autopkgtest.git
192 Vcs-Browser: https://salsa.debian.org/ci-team/autopkgtest
193-X-Python3-Version: >= 3.1
194
195 Package: autopkgtest
196 Architecture: all
197@@ -25,7 +24,15 @@ Depends: python3,
198 procps,
199 ${misc:Depends}
200 Recommends: autodep8
201-Suggests: schroot, lxc, lxd-client, qemu-system, qemu-utils
202+Suggests:
203+ lxc,
204+ lxd-client,
205+ ovmf,
206+ qemu-efi-aarch64,
207+ qemu-efi-arm,
208+ qemu-system,
209+ qemu-utils,
210+ schroot,
211 Breaks: debci (<< 1.7~)
212 Description: automatic as-installed testing for Debian packages
213 autopkgtest runs tests on binary packages. The tests are run on the
214diff --git a/debian/copyright b/debian/copyright
215index d592b1c..7b35827 100644
216--- a/debian/copyright
217+++ b/debian/copyright
218@@ -1,4 +1,4 @@
219-Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
220+Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
221
222 Files: *
223 Copyright: Copyright (C) 2006-2014 Canonical Ltd.
224diff --git a/doc/README.package-tests.rst b/doc/README.package-tests.rst
225index b212597..4cb60ba 100644
226--- a/doc/README.package-tests.rst
227+++ b/doc/README.package-tests.rst
228@@ -107,14 +107,19 @@ Restrictions: restriction-name [, another-restriction-name ...]
229 declare unknown restrictions will be skipped. See below for the
230 defined restrictions.
231
232+ Restrictions are separated by commas and/or whitespace.
233+
234 Features: feature-name [, another-feature-name ...]
235 Declares some additional capabilities or good properties of the
236 tests defined in this stanza. Any unknown features declared will be
237 completely ignored. See below for the defined features.
238
239+ Features are separated by commas and/or whitespace.
240+
241 Depends: dpkg dependency field syntax
242 Declares that the specified packages must be installed for the test
243- to go ahead. This supports all features of dpkg dependencies (see
244+ to go ahead. This supports all features of dpkg dependencies, including
245+ the architecture qualifiers (see
246 https://www.debian.org/doc/debian-policy/ch-relationships.html),
247 plus the following extensions:
248
249@@ -126,7 +131,7 @@ Depends: dpkg dependency field syntax
250 once and without a version restriction).
251
252 ``@builddeps@`` will be replaced by the package's
253- ``Build-Depends:``, ``Build-Depends-Indep:``, and
254+ ``Build-Depends:``, ``Build-Depends-Indep:``, ``Build-Depends-Arch:``, and
255 ``build-essential``. This is useful if you have many build
256 dependencies which are only necessary for running the test suite and
257 you don't want to replicate them in the test ``Depends:``. However,
258@@ -164,6 +169,8 @@ Classes: class-1 [, class-2 ...]
259 This is purely an informational field for autopkgtest itself and
260 will be ignored.
261
262+ Classes are separated by commas and/or whitespace.
263+
264 Any unknown fields will cause the whole stanza to be skipped.
265
266 Defined restrictions
267@@ -241,10 +248,63 @@ needs-recommends
268 Enable installation of recommended packages in apt for the test
269 dependencies. This does not affect build dependencies.
270
271+flaky
272+ The test is expected to fail intermittently, and is not suitable for
273+ gating continuous integration. This indicates a bug in either the
274+ package under test, a dependency or the test itself, but such bugs
275+ can be difficult to fix, and it is often difficult to know when the
276+ bug has been fixed without running the test for a while. If a
277+ ``flaky`` test succeeds, it will be treated like any other
278+ successful test, but if it fails it will be treated as though it
279+ had been skipped.
280+
281+skippable
282+ The test might need to be skipped for reasons that cannot be
283+ described by an existing restriction such as isolation-machine or
284+ breaks-testbed, but must instead be detected at runtime. If the
285+ test exits with status 77 (a convention borrowed from Automake), it
286+ will be treated as though it had been skipped. If it exits with any
287+ other status, its success or failure will be derived from the exit
288+ status and stderr as usual. Test authors must be careful to ensure
289+ that ``skippable`` tests never exit with status 77 for reasons that
290+ should be treated as a failure.
291+
292+skip-not-installable
293+ This test might have test dependencies that can't be fulfilled on all
294+ architectures. Therefore, when apt-get installs the dependencies, it will
295+ fail. Don't treat this as a test failure, but instead treat it as if the
296+ test was skipped.
297+
298+hint-testsuite-triggers
299+ This test exists purely as a hint to suggest when rerunning the
300+ tests is likely to be useful. Specifically, it exists to
301+ influence the way dpkg-source generates the Testsuite-Triggers
302+ .dsc header from test metadata: the Depends for this test are
303+ to be added to Testsuite-Triggers. (Just as they are for any other
304+ test.)
305+
306+ The test with the hint-testsuite-triggers restriction should not
307+ actually be run. Future systems which understand per-test update
308+ triggering should treat the Depends of the test with this
309+ restriction, as triggering packages for all tests in this
310+ debian/tests/control.
311+
312+ The packages listed as Depends for this test are usually indirect
313+ dependencies, updates to which are considered to pose a risk of
314+ regressions in other tests defined in this package.
315+
316+ There is currently no way to specify this hint on a per-test
317+ basis; but in any case the debian.org machinery is not able to
318+ think about triggering individual tests.
319+
320 Defined features
321 ----------------
322
323-There are no currently defined Features.
324+test-name
325+ Set an explicit test name for the log heading and the ``summary`` file
326+ for a ``Test-Command:`` inline test. When not given, these are
327+ enumerated like ``command1``.
328+
329
330 Source package header
331 ---------------------
332diff --git a/lib/VirtSubproc.py b/lib/VirtSubproc.py
333index c12d0c6..671a21d 100644
334--- a/lib/VirtSubproc.py
335+++ b/lib/VirtSubproc.py
336@@ -337,8 +337,17 @@ def cmd_reboot(c, ce):
337 if len(c) > 1 and c[1] == 'prepare-only':
338 adtlog.info('state saved, waiting for testbed to reboot...')
339 else:
340- execute_timeout(None, 30, auxverb +
341- ['sh', '-c', '(sleep 3; reboot) >/dev/null 2>&1 &'])
342+ (systemd_check, out, err) = execute_timeout(
343+ None, 10, auxverb + ['test', '-d', '/run/systemd/system'],
344+ stdout=subprocess.PIPE, stderr=subprocess.PIPE
345+ )
346+ if systemd_check == 0:
347+ reboot_cmd = ['systemd-run', '--no-block', '--quiet', 'sh', '-c', 'sleep 3; reboot']
348+ else:
349+ reboot_cmd = ['sh', '-c', '(sleep 3; reboot) >/dev/null 2>&1 &']
350+
351+ adtlog.debug('rebooting with command: %s' % reboot_cmd)
352+ execute_timeout(None, 30, auxverb + reboot_cmd)
353 caller.hook_wait_reboot()
354
355 # restore downtmp
356diff --git a/lib/adt_testbed.py b/lib/adt_testbed.py
357index b52f5b8..6f228a4 100644
358--- a/lib/adt_testbed.py
359+++ b/lib/adt_testbed.py
360@@ -47,7 +47,8 @@ class Testbed:
361 def __init__(self, vserver_argv, output_dir, user,
362 setup_commands=[], setup_commands_boot=[], add_apt_pockets=[],
363 copy_files=[], pin_packages=[], add_apt_sources=[],
364- add_apt_releases=[], apt_default_release=None):
365+ add_apt_releases=[], apt_default_release=None,
366+ enable_apt_fallback=True):
367 self.sp = None
368 self.lastsend = None
369 self.scratch = None
370@@ -77,6 +78,7 @@ class Testbed:
371 self.last_reboot_marker = ''
372 self.eatmydata_prefix = []
373 self.apt_pin_for_releases = []
374+ self.enable_apt_fallback = enable_apt_fallback
375 self.nproc = None
376 self.cpu_model = None
377 self.cpu_flags = None
378@@ -88,6 +90,10 @@ class Testbed:
379
380 adtlog.debug('testbed init')
381
382+ @property
383+ def su_user(self):
384+ return self.user or 'root'
385+
386 def start(self):
387 # are we running from a checkout?
388 root_dir = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
389@@ -389,7 +395,7 @@ class Testbed:
390 self._opened(pl)
391 self.modified = False
392
393- def install_deps(self, deps_new, recommends, shell_on_failure=False):
394+ def install_deps(self, deps_new, recommends, shell_on_failure=False, synth_deps=[]):
395 '''Install dependencies into testbed'''
396 adtlog.debug('install_deps: deps_new=%s, recommends=%s' % (deps_new, recommends))
397
398@@ -397,7 +403,7 @@ class Testbed:
399 self.recommends_installed = recommends
400 if not deps_new:
401 return
402- self.satisfy_dependencies_string(', '.join(deps_new), 'install-deps', recommends, shell_on_failure=shell_on_failure)
403+ self.satisfy_dependencies_string(', '.join(deps_new), 'install-deps', recommends, shell_on_failure=shell_on_failure, synth_deps=synth_deps)
404
405 def needs_reset(self):
406 # show what caused a reset
407@@ -412,7 +418,9 @@ class Testbed:
408 raise _type(m)
409
410 def badpkg(self, m):
411- self.bomb(m, adtlog.BadPackageError)
412+ _type = adtlog.BadPackageError
413+ adtlog.debug('%s %s' % (_type.__name__, m))
414+ raise _type(m)
415
416 def send(self, string):
417 try:
418@@ -541,7 +549,60 @@ class Testbed:
419 adtlog.AutopkgtestError)
420 return out
421
422- def install_apt(self, deps, recommends=False, shell_on_failure=False):
423+ def is_real_package_installed(self, package):
424+ '''Check if a non-virtual package is installed in the testbed'''
425+ (rc, out, err) = self.execute(['dpkg-query', '--show', '-f', '${Status}', package],
426+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
427+ if rc != 0:
428+ if 'no packages found' in err:
429+ return False
430+ self.badpkg('Failed to run dpkg-query: %s (exit code %d)' % (err, rc))
431+ if out == 'install ok installed':
432+ return True
433+ return False
434+
435+ def is_virtual_package_installed(self, package):
436+ '''Check if a package is installed in the testbed'''
437+ (rc, out, err) = self.execute(['dpkg-query', '--show', '-f', '${Status} ${Provides}\n', '*'],
438+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
439+ if rc != 0:
440+ self.badpkg('Failed to run dpkg-query: %s (exit code %d)' % (err, rc))
441+ return False
442+ if not out:
443+ return False
444+ for line in out.splitlines():
445+ prefix = 'install ok installed '
446+ if not line.startswith(prefix):
447+ continue
448+ line = line[len(prefix):]
449+ for p in line.split(','):
450+ (p, _, _) = p.lstrip().partition(' ') # ' foo (== 1.0)' => 'foo'
451+ if p == package:
452+ return True
453+ return False
454+
455+ def _run_apt_install(self, what, prefix, recommends):
456+ '''actually run apt-get install'''
457+
458+ # capture status-fd to stderr
459+ (rc, _, serr) = self.execute(['/bin/sh', '-ec', '%s apt-get install '
460+ '--assume-yes %s '
461+ '-o APT::Status-Fd=3 '
462+ '-o APT::Install-Recommends=%s '
463+ '-o Dpkg::Options::=--force-confnew '
464+ '-o Debug::pkgProblemResolver=true 3>&2 2>&1' %
465+ (prefix, what, recommends)],
466+ kind='install', stderr=subprocess.PIPE)
467+ if rc != 0:
468+ adtlog.debug('apt-get install %s failed; status-fd:\n%s' % (what, serr))
469+ # check if apt failed during package download, which might be a
470+ # transient error, so retry
471+ if 'dlstatus:' in serr and 'pmstatus:' not in serr:
472+ raise adtlog.AptDownloadError
473+ else:
474+ raise adtlog.AptPermanentError
475+
476+ def install_apt(self, deps, recommends=False, shell_on_failure=False, synth_deps=[]):
477 '''Install dependencies with apt-get into testbed
478
479 This requires root privileges and a writable file system.
480@@ -575,28 +636,60 @@ Description: satisfy autopkgtest test dependencies
481 download_fail_retries = 3
482 while True:
483 self.check_exec(['dpkg', '--unpack', deb.tb], stdout=subprocess.PIPE)
484- # capture status-fd to stderr
485- (rc, _, serr) = self.execute(['/bin/sh', '-ec', '%s apt-get install '
486- '--assume-yes --fix-broken '
487- '-o APT::Status-Fd=3 '
488- '-o APT::Install-Recommends=%s '
489- '-o Dpkg::Options::=--force-confnew '
490- '-o Debug::pkgProblemResolver=true 3>&2 2>&1' %
491- (' '.join(self.eatmydata_prefix), recommends)],
492- kind='install', stderr=subprocess.PIPE)
493- if rc != 0:
494- adtlog.debug('apt-get install failed; status-fd:\n%s' % serr)
495- # check if apt failed during package download, which might be a
496- # transient error, so retry
497- if 'dlstatus:' in serr and 'pmstatus:' not in serr:
498- download_fail_retries -= 1
499- if download_fail_retries > 0:
500- adtlog.warning('apt failed to download packages, retrying in 10s...')
501- time.sleep(10)
502- continue
503+
504+ # if '@' was provided as a test dependency (as seen in synth_deps),
505+ # we explicitly install the corresponding binary packages in case
506+ # the dependencies on those were satisfied by versioned Provides
507+
508+ rc = 0
509+ try:
510+ self._run_apt_install('--fix-broken', ' '.join(self.eatmydata_prefix), recommends)
511+ need_explicit_install = []
512+ for dep in synth_deps:
513+ if dep != list(dep):
514+ # simple test dependency (no alternatives)
515+ if self.is_real_package_installed(dep):
516+ continue
517+ if self.is_virtual_package_installed(dep):
518+ need_explicit_install.append(dep)
519+ continue
520+ adtlog.warning('package %s is not installed though it should be' % dep)
521 else:
522- self.bomb('apt repeatedly failed to download packages')
523+ # figure out which test dependency alternative got installed
524+ installed_virtual_packages = []
525+ found_a_real_package = False
526+ for pkg in dep:
527+ if self.is_real_package_installed(pkg):
528+ # no need to install anything from this set of alternatives
529+ found_a_real_package = True
530+ break
531+ if self.is_virtual_package_installed(pkg):
532+ installed_virtual_packages.append(pkg)
533+ if found_a_real_package:
534+ continue
535+ if not installed_virtual_packages:
536+ adtlog.warning('no alternative in %s is installed though one should be' % dep)
537+ if len(installed_virtual_packages) > 1:
538+ adtlog.warning('more than one test dependency alternative in %s installed as a virtual package, installing the first one (%s) as the real package' % (dep, installed_virtual_packages[0]))
539+ need_explicit_install.append(installed_virtual_packages[0])
540+
541+ if need_explicit_install:
542+ adtlog.debug('installing real packages of test dependencies: %s' % need_explicit_install)
543+ self._run_apt_install(' '.join(need_explicit_install), ' '.join(self.eatmydata_prefix), recommends)
544+
545+ # check if apt failed during package download, which might be a
546+ # transient error, so retry
547+ except adtlog.AptDownloadError:
548+ download_fail_retries -= 1
549+ if download_fail_retries > 0:
550+ adtlog.warning('apt failed to download packages, retrying in 10s...')
551+ time.sleep(10)
552+ continue
553+ else:
554+ self.bomb('apt repeatedly failed to download packages')
555
556+ except adtlog.AptPermanentError:
557+ rc = -1
558 if shell_on_failure:
559 self.run_shell()
560 else:
561@@ -607,7 +700,7 @@ Description: satisfy autopkgtest test dependencies
562 stderr=subprocess.PIPE)[0]
563
564 if rc != 0:
565- if self.apt_pin_for_releases:
566+ if self.apt_pin_for_releases and self.enable_apt_fallback:
567 release = self.apt_pin_for_releases.pop()
568 adtlog.warning('Test dependencies are unsatisfiable with using apt pinning. '
569 'Retrying with using all packages from %s' % release)
570@@ -618,10 +711,17 @@ Description: satisfy autopkgtest test dependencies
571
572 if shell_on_failure:
573 self.run_shell()
574- self.badpkg('Test dependencies are unsatisfiable. A common reason is '
575- 'that your testbed is out of date with respect to the '
576- 'archive, and you need to use a current testbed or run '
577- 'apt-get update or use -U.')
578+ if self.enable_apt_fallback:
579+ self.badpkg('Test dependencies are unsatisfiable. A common reason is '
580+ 'that your testbed is out of date with respect to the '
581+ 'archive, and you need to use a current testbed or run '
582+ 'apt-get update or use -U.')
583+ else:
584+ self.badpkg('Test dependencies are unsatisfiable. A common reason is '
585+ 'that the requested apt pinning prevented dependencies '
586+ 'from the non-default suite to be installed. In that '
587+ 'case you need to add those dependencies to the pinning '
588+ 'list.')
589 break
590
591 # remove autopkgtest-satdep to avoid confusing tests, but avoid marking our
592@@ -842,7 +942,7 @@ fi
593 # we don't want su -l here which resets the environment from
594 # self.execute(); so emulate the parts that we want
595 # FIXME: move "run as user" as an argument of execute()/check_exec() and run with -l
596- self.check_exec(['su', '--shell=/bin/sh', self.user, '-c',
597+ self.check_exec(['su', '--shell=/bin/sh', self.su_user, '-c',
598 ('export USER=%s;' % self.user) +
599 '. /etc/profile >/dev/null 2>&1 || true; '
600 ' . ~/.profile >/dev/null 2>&1 || true; '
601@@ -915,7 +1015,7 @@ fi
602 self.bomb('Failed to update click AppArmor rules')
603
604 def satisfy_dependencies_string(self, deps, what, recommends=False,
605- build_dep=False, shell_on_failure=False):
606+ build_dep=False, shell_on_failure=False, synth_deps=[]):
607 '''Install dependencies from a string into the testbed'''
608
609 adtlog.debug('%s: satisfying %s' % (what, deps))
610@@ -957,7 +1057,7 @@ fi
611 adtlog.debug('can use apt-get on testbed: %s' % can_apt_get)
612
613 if can_apt_get:
614- self.install_apt(deps, recommends, shell_on_failure)
615+ self.install_apt(deps, recommends, shell_on_failure, synth_deps)
616 else:
617 self.install_tmp(deps, recommends)
618
619@@ -1069,7 +1169,7 @@ fi
620 # we don't want -l here which resets the environment from
621 # self.execute(); so emulate the parts that we want
622 # FIXME: move "run as user" as an argument of execute()/check_exec() and run with -l
623- test_argv = ['su', '-s', '/bin/bash', self.user, '-c']
624+ test_argv = ['su', '-s', '/bin/bash', self.su_user, '-c']
625
626 if 'rw-build-tree' in test.restrictions:
627 self.check_exec(['chown', '-R', self.user, tree.tb])
628@@ -1160,6 +1260,8 @@ fi
629
630 if timeout:
631 test.failed('timed out')
632+ elif rc == 77 and 'skippable' in test.restrictions:
633+ test.set_skipped('exit status 77 and marked as skippable')
634 elif rc != 0:
635 test.failed('non-zero exit status %d' % rc)
636 elif se_size != 0 and 'allow-stderr' not in test.restrictions:
637@@ -1265,7 +1367,7 @@ fi
638 if self.default_release is None:
639
640 script = 'SRCS=$(ls /etc/apt/sources.list /etc/apt/sources.list.d/*.list 2>/dev/null|| true); '
641- script += '''sed -rn '/^(deb|deb-src) (http|https|file):/ { s/\[.*\] +//; s/^[^ ]+ +[^ ]* +([^ -]+) +.*$/\\1/p }' $SRCS | head -n1'''
642+ script += '''sed -rn '/^(deb|deb-src) +(\[.*\] *)?(http|https|file):/ { s/\[.*\] +//; s/^[^ ]+ +[^ ]* +([^ -]+) +.*$/\\1/p }' $SRCS | head -n1'''
643 self.default_release = self.check_exec(['sh', '-ec', script], stdout=True).strip()
644
645 return self.default_release
646diff --git a/lib/adtlog.py b/lib/adtlog.py
647index 8f18386..4405c76 100644
648--- a/lib/adtlog.py
649+++ b/lib/adtlog.py
650@@ -125,3 +125,11 @@ def bomb(m):
651
652 def badpkg(m):
653 raise BadPackageError(m)
654+
655+
656+class AptPermanentError(Exception):
657+ pass
658+
659+
660+class AptDownloadError(Exception):
661+ pass
662diff --git a/lib/autopkgtest_args.py b/lib/autopkgtest_args.py
663index e681bc0..0e8e0f3 100644
664--- a/lib/autopkgtest_args.py
665+++ b/lib/autopkgtest_args.py
666@@ -339,6 +339,14 @@ for details.'''
667 help='Set "parallel=N" DEB_BUILD_OPTION for building '
668 'packages (default: number of available processors)')
669 g_misc.add_argument(
670+ '--no-apt-fallback',
671+ dest='enable_apt_fallback',
672+ action='store_false',
673+ default=True,
674+ help='Disable the apt fallback which is used with --apt-pocket or '
675+ '--pin-packages in case installation of dependencies fails due to '
676+ 'strict pinning')
677+ g_misc.add_argument(
678 '-h', '--help', action='help', default=argparse.SUPPRESS,
679 help='show this help message and exit')
680
681diff --git a/lib/testdesc.py b/lib/testdesc.py
682index c6bb3ed..dfc9ee3 100644
683--- a/lib/testdesc.py
684+++ b/lib/testdesc.py
685@@ -42,7 +42,9 @@ import adtlog
686
687 known_restrictions = ['rw-build-tree', 'breaks-testbed', 'needs-root',
688 'build-needed', 'allow-stderr', 'isolation-container',
689- 'isolation-machine', 'needs-recommends', 'needs-reboot']
690+ 'isolation-machine', 'needs-recommends', 'needs-reboot',
691+ 'flaky', 'skippable',
692+ 'skip-not-installable']
693
694
695 class Unsupported(Exception):
696@@ -80,7 +82,7 @@ class Test:
697 actions.
698 '''
699 def __init__(self, name, path, command, restrictions, features, depends,
700- clicks, installed_clicks):
701+ clicks, installed_clicks, synth_depends):
702 '''Create new test description
703
704 A test must have either "path" or "command", the respective other value
705@@ -93,6 +95,7 @@ class Test:
706 @depends: string list of test dependencies (packages)
707 @clicks: path list of click packages to install for this test
708 @installed_clicks: names of already installed clicks for this test
709+ @synth_depends: string list of synthesized test dependencies (packages)
710 '''
711 if '/' in name:
712 raise Unsupported(name, 'test name may not contain / character')
713@@ -111,8 +114,10 @@ class Test:
714 self.depends = depends
715 self.clicks = clicks
716 self.installed_clicks = installed_clicks
717+ self.synth_depends = synth_depends
718 # None while test hasn't run yet; True: pass, False: fail
719 self.result = None
720+ self.skipped = False
721 adtlog.debug('Test defined: name %s path %s command "%s" '
722 'restrictions %s features %s depends %s clicks %s '
723 'installed clicks %s' %
724@@ -125,11 +130,23 @@ class Test:
725 self.result = True
726 adtlog.report(self.name, 'PASS')
727
728+ def set_skipped(self, reason):
729+ '''Mark test as skipped'''
730+ # This isn't called skipped() to avoid clashing with the boolean
731+ # attribute.
732+
733+ self.skipped = True
734+ self.result = True
735+ adtlog.report(self.name, 'SKIP ' + reason)
736+
737 def failed(self, reason):
738 '''Mark test as failed'''
739
740 self.result = False
741- adtlog.report(self.name, 'FAIL ' + reason)
742+ if 'flaky' in self.restrictions:
743+ adtlog.report(self.name, 'FLAKY ' + reason)
744+ else:
745+ adtlog.report(self.name, 'FAIL ' + reason)
746
747 def check_testbed_compat(self, caps):
748 '''Check for restrictions incompatible with test bed capabilities.
749@@ -221,6 +238,7 @@ def _debian_check_unknown_fields(name, record):
750
751 def _debian_packages_from_source(srcdir):
752 packages = []
753+ packages_no_arch = []
754
755 for st in parse_rfc822(os.path.join(srcdir, 'debian/control')):
756 if 'Package' not in st:
757@@ -232,11 +250,12 @@ def _debian_packages_from_source(srcdir):
758 continue
759 arch = st['Architecture']
760 if arch in ('all', 'any'):
761- packages.append('%s (>= 0~)' % st['Package'])
762+ packages.append(st['Package'])
763 else:
764- packages.append('%s (>= 0~) [%s]' % (st['Package'], arch))
765+ packages.append('%s [%s]' % (st['Package'], arch))
766+ packages_no_arch.append(st['Package'])
767
768- return packages
769+ return (packages, packages_no_arch)
770
771
772 def _debian_build_deps_from_source(srcdir, testbed_arch):
773@@ -246,6 +265,8 @@ def _debian_build_deps_from_source(srcdir, testbed_arch):
774 deps += st['Build-depends']
775 if 'Build-depends-indep' in st:
776 deps += ', ' + st['Build-depends-indep']
777+ if 'Build-depends-arch' in st:
778+ deps += ', ' + st['Build-depends-arch']
779
780 # resolve arch specific dependencies and build profiles
781 perl = subprocess.Popen(['perl', '-'], stdin=subprocess.PIPE,
782@@ -274,7 +295,7 @@ def _debian_build_deps_from_source(srcdir, testbed_arch):
783 dep_re = re.compile(
784 r'(?P<package>[a-z0-9+-.]+)(?::native)?\s*'
785 r'(\((?P<relation><<|<=|>=|=|>>)\s*(?P<version>[^\)]*)\))?'
786- r'(\s*\[[[a-z0-9+-.! ]+\])?$')
787+ r'(\s*\[(?P<arch>[[a-z0-9+-.! ]+)\])?$')
788
789
790 def _debian_check_dep(testname, dep):
791@@ -296,6 +317,38 @@ def _debian_check_dep(testname, dep):
792 # too old python-debian, skip the check
793 pass
794
795+ return (m.group('package'), m.group('version'))
796+
797+
798+def _synthesize_deps(dep, testbed_arch):
799+ '''Ensure that apt can install synthesized Depends
800+
801+ Test Depends may have architecture qualifiers we need to check,
802+ because apt command-line can't handle those and can't do the check.
803+ (Policy says the architecture qualifier should not be in the binary
804+ control file). We'll ignore the version here.
805+ '''
806+
807+ dep = dep.strip()
808+ m = dep_re.match(dep)
809+
810+ arch_matches = 'y'
811+ if m.group('arch') is not None:
812+ arch_matches = False
813+ perl = subprocess.Popen(['perl', '-'], stdin=subprocess.PIPE,
814+ stdout=subprocess.PIPE)
815+ code = '''use Dpkg::Deps;
816+ print deps_parse('foo [%s]')->arch_is_concerned('%s') ? 'y' : 'n';
817+ ''' % (m.group('arch'), testbed_arch)
818+ arch_matches = perl.communicate(code.encode('UTF-8'))[0].decode('UTF-8').strip()
819+ if perl.returncode != 0:
820+ raise InvalidControl('source', 'Invalid (Test-)Depends architecture qualifiers')
821+
822+ if arch_matches == 'y':
823+ return m.group('package')
824+ else:
825+ return None
826+
827
828 def _parse_debian_depends(testname, dep_str, srcdir, testbed_arch):
829 '''Parse Depends: line in a Debian package
830@@ -307,6 +360,8 @@ def _parse_debian_depends(testname, dep_str, srcdir, testbed_arch):
831 dependencies.
832 '''
833 deps = []
834+ synthdeps = []
835+ (my_packages, my_packages_no_arch) = _debian_packages_from_source(srcdir)
836 for alt_group_str in dep_str.split(','):
837 alt_group_str = alt_group_str.strip()
838 if not alt_group_str:
839@@ -314,19 +369,35 @@ def _parse_debian_depends(testname, dep_str, srcdir, testbed_arch):
840 continue
841 adtlog.debug('processing dependency %s' % alt_group_str)
842 if alt_group_str == '@':
843- for d in _debian_packages_from_source(srcdir):
844+ for d in my_packages:
845 adtlog.debug('synthesised dependency %s' % d)
846 deps.append(d)
847+ s = _synthesize_deps(d, testbed_arch)
848+ if s:
849+ synthdeps.append(s)
850 elif alt_group_str == '@builddeps@':
851 for d in _debian_build_deps_from_source(srcdir, testbed_arch):
852 adtlog.debug('synthesised dependency %s' % d)
853 deps.append(d)
854 else:
855+ synthdep_alternatives = []
856 for dep in alt_group_str.split('|'):
857- _debian_check_dep(testname, dep)
858+ (pkg, version) = _debian_check_dep(testname, dep)
859+ if pkg not in my_packages_no_arch:
860+ synthdep_alternatives = []
861+ break
862+ s = _synthesize_deps(dep, testbed_arch)
863+ if s:
864+ synthdep_alternatives.append(s)
865+ if synthdep_alternatives:
866+ adtlog.debug('marked alternatives %s as a synthesised dependency' % synthdep_alternatives)
867+ if len(synthdep_alternatives) > 1:
868+ synthdeps.append(synthdep_alternatives)
869+ else:
870+ synthdeps.append(synthdep_alternatives[0])
871 deps.append(alt_group_str)
872
873- return deps
874+ return (deps, synthdeps)
875
876
877 def _autodep8(srcdir):
878@@ -411,10 +482,11 @@ def parse_debian_source(srcdir, testbed_caps, testbed_arch, control_path=None,
879
880 if 'Tests' in record:
881 test_names = record['Tests'].replace(',', ' ').split()
882- depends = _parse_debian_depends(test_names[0],
883- record.get('Depends', '@'),
884- srcdir,
885- testbed_arch)
886+ (depends, synth_depends) = _parse_debian_depends(
887+ test_names[0],
888+ record.get('Depends', '@'),
889+ srcdir,
890+ testbed_arch)
891 if 'Test-command' in record:
892 raise InvalidControl('*', 'Only one of "Tests" or '
893 '"Test-Command" may be given')
894@@ -426,15 +498,16 @@ def parse_debian_source(srcdir, testbed_caps, testbed_arch, control_path=None,
895 _debian_check_unknown_fields(test_names[0], record)
896 for n in test_names:
897 test = Test(n, os.path.join(test_dir, n), None,
898- restrictions, features, depends, [], [])
899+ restrictions, features, depends, [], [], synth_depends)
900 test.check_testbed_compat(testbed_caps)
901 tests.append(test)
902 elif 'Test-command' in record:
903 command = record['Test-command']
904- depends = _parse_debian_depends(command,
905- record.get('Depends', '@'),
906- srcdir,
907- testbed_arch)
908+ (depends, synth_depends) = _parse_debian_depends(
909+ command,
910+ record.get('Depends', '@'),
911+ srcdir,
912+ testbed_arch)
913 if feature_test_name is None:
914 command_counter += 1
915 name = 'command%i' % command_counter
916@@ -442,7 +515,7 @@ def parse_debian_source(srcdir, testbed_caps, testbed_arch, control_path=None,
917 name = feature_test_name
918 _debian_check_unknown_fields(name, record)
919 test = Test(name, None, command, restrictions, features,
920- depends, [], [])
921+ depends, [], [], synth_depends)
922 test.check_testbed_compat(testbed_caps)
923 tests.append(test)
924 else:
925@@ -532,7 +605,7 @@ def parse_click_manifest(manifest, testbed_caps, clickdeps, use_installed,
926 try:
927 test = Test(name, desc.get('path'), desc.get('command'),
928 desc.get('restrictions', []), desc.get('features', []),
929- desc.get('depends', []), clickdeps, installed_clicks)
930+ desc.get('depends', []), clickdeps, installed_clicks, [])
931 test.check_testbed_compat(testbed_caps)
932 tests.append(test)
933 except Unsupported as u:
934diff --git a/runner/autopkgtest b/runner/autopkgtest
935index 90a3fd9..24fd84a 100755
936--- a/runner/autopkgtest
937+++ b/runner/autopkgtest
938@@ -138,27 +138,60 @@ def setup_trace():
939 def run_tests(tests, tree):
940 global errorcode, testbed
941
942+ # We should not get here if we have had an error other than skipping
943+ # tests
944+ assert errorcode in (0, 2), errorcode
945+
946 if not tests:
947 # if we have skipped tests, don't claim that we don't have any
948- if not errorcode & 2:
949+ if errorcode == 0:
950 adtlog.report('*', 'SKIP no tests in this package')
951- errorcode |= 8
952+
953+ errorcode = 8
954 return
955
956+ any_passed = False
957+
958 for t in tests:
959 # Set up clean test bed with given dependencies
960 adtlog.info('test %s: preparing testbed' % t.name)
961 testbed.reset(t.depends, 'needs-recommends' in t.restrictions)
962 binaries.publish()
963- testbed.install_deps(t.depends, 'needs-recommends' in t.restrictions, opts.shell_fail)
964+ doTest = True
965+ try:
966+ testbed.install_deps(t.depends, 'needs-recommends' in t.restrictions, opts.shell_fail, t.synth_depends)
967+ except adtlog.BadPackageError as e:
968+ if 'skip-not-installable' in t.restrictions:
969+ errorcode |= 2
970+ adtlog.report(t.name, 'SKIP installation fails and skip-not-installable set')
971+ else:
972+ errorcode |= 12
973+ adtlog.report(t.name, 'FAIL badpkg')
974+ adtlog.preport('blame: ' + ' '.join(blamed))
975+ adtlog.preport('badpkg: ' + str(e))
976+ doTest = False
977+
978+ if doTest:
979+ testbed.run_test(tree, t, opts.env, opts.shell_fail, opts.shell,
980+ opts.build_parallel)
981+ if t.skipped:
982+ errorcode |= 2
983+ elif not t.result:
984+ if 'flaky' in t.restrictions:
985+ errorcode |= 2
986+ else:
987+ errorcode |= 4
988+ else:
989+ any_passed = True
990
991- testbed.run_test(tree, t, opts.env, opts.shell_fail, opts.shell,
992- opts.build_parallel)
993- if not t.result:
994- errorcode |= 4
995 if 'breaks-testbed' in t.restrictions:
996 testbed.needs_reset()
997
998+ if errorcode in (0, 2) and not any_passed:
999+ # If we have skipped or ignored every test, set the same exit
1000+ # status as if we didn't have any tests
1001+ errorcode = 8
1002+
1003 testbed.needs_reset()
1004
1005
1006@@ -362,11 +395,9 @@ def build_source(kind, arg, built_binaries):
1007 #
1008 # apt-get source is terribly noisy; only show what gets downloaded
1009 #
1010- # apt in Ubuntu 12.04 does not yet understand --only-source, so add a fallback
1011- #
1012 # very old source packages don't have Package-List: yet, fall back to Binary:
1013 # (Binary: is generally not sufficient as it gets truncated for long lists)
1014- create_command = ('pkgs=$(apt-cache showsrc --only-source %(src)s || [ $? != 100 ] || apt-cache showsrc %(src)s); '
1015+ create_command = ('pkgs=$(apt-cache showsrc --only-source %(src)s); '
1016 'pkgs=$(echo "$pkgs\n" | awk "/^Package: / { if (\$2 != \\"%(src)s\\") { skippar=1; next; } else { skippar=0}} { if (skippar) next; } /^Binary:/ { sub(/^Binary:/, \\"\\"); gsub(/,/, \\"\\"); split(\$0,oldpkgs)}; /^Package-List:/ { inlist=1; have_pl=1; delete thissrc; if (\$2) thissrc[\$2] = 1; next } (/^ / && inlist == 1) { thissrc[\$1] = 1; next } { if (!inlist) next; inlist=0; if (intersect) {for (p in pkgs) { if (!(p in thissrc)) delete pkgs[p]; else remaining=1}; if (!remaining) {for (p in thissrc) {pkgs[p] = 1}} } else { for (p in thissrc) { pkgs[p] = 1}; intersect=1 } } END {if (have_pl) { for (p in pkgs) print p } else {for (p in oldpkgs) print oldpkgs[p]} }");'
1017 ' [ -n "$pkgs" ] || exit 1; '
1018 ' for pkg in $pkgs; do'
1019@@ -460,6 +491,7 @@ def build_source(kind, arg, built_binaries):
1020 d = deb822.Deb822(sequence=f)
1021 bd = d.get('Build-Depends', '')
1022 bdi = d.get('Build-Depends-Indep', '')
1023+ bda = d.get('Build-Depends-Arch', '')
1024
1025 # determine build command and build-essential packages
1026 build_essential = ['build-essential']
1027@@ -472,7 +504,7 @@ def build_source(kind, arg, built_binaries):
1028 if testbed.user or 'root-on-testbed' not in testbed.caps:
1029 build_essential += ['fakeroot']
1030
1031- testbed.satisfy_dependencies_string(bd + ', ' + bdi + ', ' + ', '.join(build_essential), arg,
1032+ testbed.satisfy_dependencies_string(bd + ', ' + bdi + ', ' + bda + ', ' + ', '.join(build_essential), arg,
1033 build_dep=True, shell_on_failure=opts.shell_fail)
1034
1035 # keep patches applied for tests
1036@@ -650,6 +682,7 @@ def main():
1037 setup_commands_boot=opts.setup_commands_boot,
1038 add_apt_pockets=opts.apt_pocket,
1039 copy_files=opts.copy,
1040+ enable_apt_fallback=opts.enable_apt_fallback,
1041 add_apt_sources=getattr(opts, 'add_apt_sources', []),
1042 add_apt_releases=getattr(opts, 'add_apt_releases', []),
1043 pin_packages=opts.pin_packages,
1044diff --git a/runner/autopkgtest.1 b/runner/autopkgtest.1
1045index 168a7a0..2a98c32 100644
1046--- a/runner/autopkgtest.1
1047+++ b/runner/autopkgtest.1
1048@@ -3,8 +3,9 @@
1049 autopkgtest \- test an installed binary package using the source package's tests
1050 .SH SYNOPSIS
1051 .B autopkgtest
1052-.IR [ options ...]
1053-.IR "testsrc " [ testbinary "...]"
1054+.RI [ options ...]
1055+.RI [ testbinary ...]
1056+.I testsrc
1057 .B \-\-
1058 .I virt\-server
1059 .RI [ virt\-server\-arg ...]
1060@@ -442,12 +443,15 @@ individual servers for how to use them.
1061 .SH OUTPUT FORMAT
1062 During a normal test run, one line is printed for each test. This
1063 consists of a short string identifying the test, some horizontal
1064-whitespace, and either
1065-.B PASS
1066-or
1067-.BR FAIL " reason"
1068+whitespace, and one of
1069+.BR PASS ,
1070+.B FAIL
1071+.IR reason ,
1072+.B SKIP
1073+.IR reason ,
1074 or
1075-.BR SKIP " reason"
1076+.B FLAKY
1077+.I reason
1078 where the pass/fail indication is separated by any reason by some
1079 horizontal whitespace.
1080
1081@@ -455,6 +459,15 @@ The string to identify the test consists of a short alphanumeric
1082 string invented by \fBautopkgtest\fR to distinguish different command-line
1083 arguments, the \fIargid\fR, followed by a hyphen and the test name.
1084
1085+.B SKIP
1086+indicates that a test was not run, or that the test code was started
1087+but detected that the test could not complete, for instance because a
1088+required resource was not available.
1089+
1090+.B FLAKY
1091+indicates that a test would ordinarily have failed, but because this
1092+particular test is known to be unreliable, the failure was ignored.
1093+
1094 Sometimes a
1095 .B SKIP
1096 will be reported when the name of the test is not known or not
1097@@ -520,7 +533,7 @@ argument value.
1098 .SH EXIT STATUS
1099 0 all tests passed
1100 .br
1101-2 at least one test skipped
1102+2 at least one test was skipped (or at least one flaky test failed)
1103 .br
1104 4 at least one test failed
1105 .br
1106@@ -530,6 +543,8 @@ argument value.
1107 .br
1108 12 erroneous package
1109 .br
1110+14 erroneous package and at least one test skipped
1111+.br
1112 16 testbed failure
1113 .br
1114 20 other unexpected failures including bad usage
1115diff --git a/tests/autopkgtest b/tests/autopkgtest
1116index aa6e431..ceb0ee6 100755
1117--- a/tests/autopkgtest
1118+++ b/tests/autopkgtest
1119@@ -64,6 +64,12 @@ have_ubuntu_device_flash = subprocess.call(['which', 'ubuntu-device-flash'],
1120 have_autodep8 = subprocess.call(['which', 'autodep8'], stdout=subprocess.PIPE) == 0
1121 have_git = subprocess.call(['which', 'git'], stdout=subprocess.PIPE) == 0
1122
1123+host_arch = subprocess.run(['dpkg-architecture', '-q', 'DEB_HOST_ARCH'],
1124+ stdout=subprocess.PIPE, universal_newlines=True).stdout.strip()
1125+
1126+host_os = subprocess.run(['dpkg-architecture', '-q', 'DEB_HOST_ARCH_OS'],
1127+ stdout=subprocess.PIPE, universal_newlines=True).stdout.strip()
1128+
1129
1130 class AdtTestCase(unittest.TestCase):
1131 '''Base class with common test setup'''
1132@@ -158,6 +164,18 @@ class AdtTestCase(unittest.TestCase):
1133 (out, err) = p.communicate()
1134 return (p.returncode, out.decode('UTF-8', 'replace'), err.decode('UTF-8', 'replace'))
1135
1136+ @property
1137+ def has_isolation_machine(self):
1138+ return self.virt_args[0] in ['qemu', 'null']
1139+
1140+ @property
1141+ def has_isolation_container(self):
1142+ return self.virt_args[0] not in ['chroot', 'schroot']
1143+
1144+ @property
1145+ def can_revert_full_system(self):
1146+ return self.virt_args[0] not in ['null', 'chroot', 'schroot']
1147+
1148
1149 class DebTestsAll:
1150 '''Common deb tests for all runners'''
1151@@ -189,21 +207,35 @@ class DebTestsAll:
1152 # should log package version
1153 self.assertIn('testing package testpkg version 1\n', err)
1154
1155+ @unittest.skipIf((os.getuid() == 0 and
1156+ os.path.exists('/usr/share/doc/aspell-doc')),
1157+ 'needs aspell-doc uninstalled if run as root')
1158 def test_tree_build_needed_success(self):
1159 '''source tree, build-needed restriction, test success'''
1160
1161 p = self.build_src('Tests: pass\nDepends: coreutils\nRestrictions: build-needed\n',
1162 {'pass': '#!/bin/sh -e\n./test_built | grep -q "built script OK"\n'
1163 './test_abspath | grep -q "built script OK"\necho GOOD'})
1164- # add Build-Depends-Indep: and a build profile package
1165- subprocess.check_call(['sed', '-i', '/^Build-Depends:/ a\Build-Depends-Indep: dpkg-dev, nonexisting <cross>',
1166- os.path.join(p, 'debian', 'control')])
1167+
1168+ if os.getuid() == 0:
1169+ # Add Build-Depends-Indep:, Build-Depends-Arch and a build
1170+ # profile package. We can only do this if we're root, because
1171+ # packages that we unpack non-system-wide as non-root are
1172+ # invisible to dpkg-checkbuilddeps so the build would fail.
1173+ subprocess.check_call(['sed', '-i', '-e',
1174+ '/^Build-Depends:/ a\Build-Depends-Indep: dpkg-dev, nonexisting <cross>',
1175+ '-e',
1176+ '/^Build-Depends:/ a\Build-Depends-Arch: aspell-doc',
1177+ os.path.join(p, 'debian', 'control')])
1178
1179 (code, out, err) = self.runtest(['--no-built-binaries', p])
1180 # test should succeed
1181 self.assertEqual(code, 0, out + err)
1182 self.assertRegex(out, 'pass\s+PASS', out)
1183
1184+ if os.getuid() == 0:
1185+ self.assertIn('Unpacking aspell-doc', out)
1186+
1187 # should build package
1188 self.assertIn('dh build', err)
1189
1190@@ -222,26 +254,34 @@ class DebTestsAll:
1191
1192 (code, out, err) = self.runtest(['-B', p])
1193
1194- if self.virt_args[0] in ['qemu', 'null']:
1195- self.assertEqual(code, 0, out + err)
1196+ if self.has_isolation_machine:
1197 self.assertRegex(out, 'im\s+PASS', out)
1198 self.assertIn('\nmachine ok\n', out)
1199 else:
1200- self.assertEqual(code, 2, out + err)
1201 self.assertRegex(out, 'im\s+SKIP .*machine', out)
1202 self.assertNotIn('machine ok', out)
1203
1204- if self.virt_args[0] in ['chroot', 'schroot']:
1205+ if not self.has_isolation_container:
1206 self.assertRegex(out, 'ic\s+SKIP .*container', out)
1207 self.assertNotIn('container ok', out)
1208 else:
1209 self.assertRegex(out, 'ic\s+PASS', out)
1210 self.assertIn('container ok\n', out)
1211
1212+ if self.has_isolation_machine and self.has_isolation_container:
1213+ # all tests run
1214+ self.assertEqual(code, 0, out + err)
1215+ elif self.has_isolation_machine or self.has_isolation_container:
1216+ # one test run, one skipped
1217+ self.assertEqual(code, 2, out + err)
1218+ else:
1219+ # all skipped
1220+ self.assertEqual(code, 8, out + err)
1221+
1222 def test_breaks_testbed(self):
1223 '''breaks-testbed restriction'''
1224
1225- p = self.build_src('Tests: zap boom\nDepends:\nRestrictions: needs-root breaks-testbed',
1226+ p = self.build_src('Tests: zap boom\nDepends:\nRestrictions: allow-stderr needs-root breaks-testbed',
1227 {'zap': '#!/bin/sh\ntouch /zap\n'
1228 '[ -d "$HOME" ] || echo "HOME not set" >&2\n'
1229 'apt-get install -y aspell-doc\n',
1230@@ -249,9 +289,9 @@ class DebTestsAll:
1231
1232 (code, out, err) = self.runtest(['--no-built-binaries', p, '-d'])
1233
1234- if self.virt_args[0] in ['null', 'chroot', 'schroot']:
1235+ if not self.can_revert_full_system:
1236 # test should be skipped as these runners doesn't provide revert-full-system
1237- self.assertEqual(code, 2, err)
1238+ self.assertEqual(code, 8, err)
1239 self.assertRegex(out, 'zap\s+SKIP Test breaks testbed')
1240 # FIXME: we should see this too!
1241 # self.assertRegex(out, 'boom\s+SKIP Test breaks testbed')
1242@@ -264,6 +304,22 @@ class DebTestsAll:
1243 self.assertRegex(out, 'boom\s+PASS')
1244 self.assertIn('Unpacking aspell-doc', out)
1245
1246+ def test_flaky_success(self):
1247+ '''A flaky test succeeds'''
1248+ p = self.build_src('Tests: unreproducible\nRestrictions: flaky\nDepends: coreutils\n',
1249+ {'unreproducible': '#!/bin/sh\necho I am fine\n'})
1250+ (code, out, err) = self.runtest(['-d', '--no-built-binaries', p])
1251+ self.assertEqual(code, 0, err)
1252+ self.assertRegex(out, 'unreproducible\s+PASS', out)
1253+
1254+ def test_skippable_success(self):
1255+ '''A skippable test succeeds'''
1256+ p = self.build_src('Tests: needs-magic\nRestrictions: skippable\nDepends: coreutils\n',
1257+ {'needs-magic': '#!/bin/sh\necho I am fine\n'})
1258+ (code, out, err) = self.runtest(['-d', '--no-built-binaries', p])
1259+ self.assertEqual(code, 0, err)
1260+ self.assertRegex(out, 'needs-magic\s+PASS', out)
1261+
1262
1263 class DebTestsFailureModes:
1264 '''Common deb tests for handling various failure modes
1265@@ -359,6 +415,59 @@ class DebTestsFailureModes:
1266 # no restricted dependencies functionality as they are already installed
1267 self.assertNotIn('will only work for some packages', err)
1268
1269+ def test_flaky_fail(self):
1270+ '''A flaky test fails'''
1271+ p = self.build_src('Tests: unreproducible\nRestrictions: flaky\nDepends: coreutils\n',
1272+ {'unreproducible': '#!/bin/sh\nexit 1\n'})
1273+ (code, out, err) = self.runtest(['-d', '--no-built-binaries', p])
1274+ self.assertEqual(code, 8, err)
1275+ self.assertRegex(out, 'unreproducible\s+FLAKY', out)
1276+
1277+ def test_flaky_fail_mixed(self):
1278+ '''A flaky test fails, but another test passes'''
1279+ p = self.build_src('Tests: unreproducible ok\nRestrictions: flaky\nDepends: coreutils\n',
1280+ {'unreproducible': '#!/bin/sh\nexit 1\n',
1281+ 'ok': '#!/bin/sh\n'})
1282+ (code, out, err) = self.runtest(['-d', '--no-built-binaries', p])
1283+ self.assertEqual(code, 2, err)
1284+ self.assertRegex(out, 'unreproducible\s+FLAKY', out)
1285+ self.assertRegex(out, 'ok\s+PASS', out)
1286+
1287+ def test_skippable_skipped(self):
1288+ '''A skippable test skips itself'''
1289+ p = self.build_src('Tests: needs-magic\nRestrictions: skippable\nDepends: coreutils\n',
1290+ {'needs-magic': '#!/bin/sh\necho Cannot run >&2\nexit 77\n'})
1291+ (code, out, err) = self.runtest(['-d', '--no-built-binaries', p])
1292+ self.assertEqual(code, 8, err)
1293+ self.assertRegex(out, 'needs-magic\s+SKIP', out)
1294+
1295+ def test_skippable_skipped_mixed(self):
1296+ '''A skippable test skips itself, but another test passes'''
1297+ p = self.build_src('Tests: needs-magic ok\nRestrictions: skippable\nDepends: coreutils\n',
1298+ {'needs-magic': '#!/bin/sh\necho Cannot run >&2\nexit 77\n',
1299+ 'ok': '#!/bin/sh\n'})
1300+ (code, out, err) = self.runtest(['-d', '--no-built-binaries', p])
1301+ self.assertEqual(code, 2, err)
1302+ self.assertRegex(out, 'needs-magic\s+SKIP', out)
1303+ self.assertRegex(out, 'ok\s+PASS', out)
1304+
1305+ def test_skippable_fail(self):
1306+ '''A skippable test fails'''
1307+ p = self.build_src('Tests: needs-magic\nRestrictions: skippable\nDepends: coreutils\n',
1308+ {'needs-magic': '#!/bin/sh\nexit 1\n'})
1309+ (code, out, err) = self.runtest(['-d', '--no-built-binaries', p])
1310+ self.assertEqual(code, 4, err)
1311+ self.assertRegex(out, 'needs-magic\s+FAIL non-zero exit status 1', out)
1312+
1313+ def test_unskippable(self):
1314+ '''A non-skippable test fails with an exit status that happens
1315+ to match the skippable API.'''
1316+ p = self.build_src('Tests: no-magic\nDepends: coreutils\n',
1317+ {'no-magic': '#!/bin/sh\nexit 77\n'})
1318+ (code, out, err) = self.runtest(['-d', '--no-built-binaries', p])
1319+ self.assertEqual(code, 4, err)
1320+ self.assertRegex(out, 'no-magic\s+FAIL non-zero exit status 77', out)
1321+
1322
1323 class NullRunner(AdtTestCase, DebTestsAll, DebTestsFailureModes):
1324 def __init__(self, *args, **kwargs):
1325@@ -1399,8 +1508,69 @@ if ($pid) { # parent
1326
1327 p = self.build_src('Test-Command: true\nDepends:\nRestrictions: needs-reboot', {})
1328 (code, out, err) = self.runtest(['-d', '-B', p])
1329+ self.assertEqual(code, 8, err)
1330+ self.assertRegex(out, 'command1\s+SKIP Test needs to reboot testbed but testbed does not provide reboot capability')
1331+
1332+ def test_reboot_mixed(self):
1333+ '''needs-reboot test gets skipped but another test is still run'''
1334+
1335+ p = self.build_src('Test-Command: true 1\nDepends:\nRestrictions: needs-reboot\n'
1336+ '\n'
1337+ 'Test-Command: true 2\nDepends:\n',
1338+ {})
1339+ (code, out, err) = self.runtest(['-d', '-B', p])
1340 self.assertEqual(code, 2, err)
1341 self.assertRegex(out, 'command1\s+SKIP Test needs to reboot testbed but testbed does not provide reboot capability')
1342+ self.assertRegex(out, 'command2\s+PASS')
1343+
1344+ def test_broken_test_deps(self):
1345+ '''unsatisfiable test dependencies'''
1346+
1347+ p = self.build_src('Tests: p\nDepends: unknown, libc6 (>= 99:99)\n',
1348+ {'p': '#!/bin/sh -e\ntrue'})
1349+
1350+ (code, out, err) = self.runtest(['--no-built-binaries', p])
1351+ self.assertEqual(code, 12, err)
1352+ self.assertIn('Test dependencies are unsatisfiable', err)
1353+ self.assertIn('Test dependencies are unsatisfiable', out)
1354+
1355+ def test_continue_with_other_tests(self):
1356+ '''Install failure should continue with next test'''
1357+
1358+ p = self.build_src('Test-Command: false\nDepends:\n\nTest-Command: true\nDepends:unknown\n\nTest-Command: true\nDepends:\n', {})
1359+
1360+ summary = os.path.join(self.workdir, 'summary.log')
1361+
1362+ (code, out, err) = self.runtest(['--debug', '--no-built-binaries', p, '--summary=' + summary])
1363+ self.assertEqual(code, 12, err)
1364+ self.assertRegex(out, 'command1\s+FAIL')
1365+ self.assertRegex(out, 'command2\s+FAIL')
1366+ self.assertRegex(out, 'command3\s+PASS')
1367+
1368+ def test_skip_not_installable(self):
1369+ '''Test skip-not-installable restriction'''
1370+ p = self.build_src('Tests: notinstallable\n'
1371+ 'Restrictions: skip-not-installable\n'
1372+ 'Depends: nonexisting\n',
1373+ {'notinstallable': '#!/bin/sh\necho But I am fine\n'})
1374+ (code, out, err) = self.runtest(['-d', '--no-built-binaries', p])
1375+ self.assertEqual(code, 8, err)
1376+ self.assertRegex(out, 'notinstallable\s+SKIP installation fails', out)
1377+
1378+ def test_skip_not_installable_mixed(self):
1379+ '''Test skip-not-installable restriction combined with a passing test'''
1380+ p = self.build_src('Tests: notinstallable\n'
1381+ 'Restrictions: skip-not-installable\n'
1382+ 'Depends: nonexisting\n'
1383+ '\n'
1384+ 'Tests: ok\n'
1385+ 'Depends:\n',
1386+ {'notinstallable': '#!/bin/sh\necho But I am fine\n',
1387+ 'ok': '#!/bin/sh\nexit 0\n'})
1388+ (code, out, err) = self.runtest(['-d', '--no-built-binaries', p])
1389+ self.assertEqual(code, 2, err)
1390+ self.assertRegex(out, 'notinstallable\s+SKIP installation fails', out)
1391+ self.assertRegex(out, 'ok\s+PASS', out)
1392
1393
1394 @unittest.skipIf(os.getuid() > 0,
1395@@ -1629,7 +1799,8 @@ Restrictions: needs-root
1396
1397 # add extra build dependencies to testpkg
1398 subprocess.check_call(['sed', '-i', '/^Build-Depends:/ s/:.*/: bdep1 , bdep2,\\n bdep3, # moo\\n#comment\\n bdep4\\n'
1399- 'Build-Depends-Indep: bdep5/',
1400+ 'Build-Depends-Indep: bdep5\\n'
1401+ 'Build-Depends-Arch: bdep6/',
1402 os.path.join(p, 'debian', 'control')])
1403
1404 # run this under C locale to test that UTF-8 debian/control is still
1405@@ -1639,13 +1810,14 @@ Restrictions: needs-root
1406 self.assertEqual(code, 0, err)
1407 self.assertRegex(out, 'pass\s+PASS')
1408
1409- self.assertIn('synthesised dependency testpkg (>= 0~)\n', err)
1410+ self.assertIn('synthesised dependency testpkg\n', err)
1411 self.assertIn('processing dependency testdep1\n', err)
1412 self.assertIn('synthesised dependency bdep1\n', err)
1413 self.assertIn('synthesised dependency bdep2\n', err)
1414 self.assertIn('synthesised dependency bdep3\n', err)
1415 self.assertIn('synthesised dependency bdep4\n', err)
1416 self.assertIn('synthesised dependency bdep5\n', err)
1417+ self.assertIn('synthesised dependency bdep6\n', err)
1418 self.assertIn('synthesised dependency build-essential\n', err)
1419 self.assertIn('processing dependency testdep2\n', err)
1420
1421@@ -3094,9 +3266,9 @@ class SchrootRunner(AdtTestCase, DebTestsVirtFS, DebTestsFailureModes):
1422 (code, out, err) = self.runtest(['--no-built-binaries', p])
1423 self.assertEqual(code, 12, err)
1424 self.assertRegex(out, '[dD]epends.*unknown', err)
1425- self.assertRegex(err, 'Test dependencies are unsatisfiable', err)
1426+ self.assertIn('Test dependencies are unsatisfiable', err)
1427 self.assertRegex(out, 'blame: .*/testpkg', out)
1428- self.assertRegex(out, 'Test dependencies are unsatisfiable', out)
1429+ self.assertIn('Test dependencies are unsatisfiable', out)
1430
1431 def test_install_failure(self):
1432 '''test dependency install failure'''
1433@@ -3110,7 +3282,7 @@ class SchrootRunner(AdtTestCase, DebTestsVirtFS, DebTestsFailureModes):
1434 self.assertEqual(code, 12, err)
1435 self.assertIn('BROKEN POSTINST', out)
1436 self.assertRegex(out, 'blame: .*/testpkg', out)
1437- self.assertRegex(out, 'badpkg: Test dependencies are unsatisfiable', out)
1438+ self.assertIn('badpkg: Test dependencies are unsatisfiable', out)
1439
1440 def do_apt_pocket_test(self, pocket_args, ver_checks,
1441 add_deb_src=False, expect_success=True,
1442@@ -3197,7 +3369,7 @@ class SchrootRunner(AdtTestCase, DebTestsVirtFS, DebTestsFailureModes):
1443 # FIXME: our current apt pinning does not resolve this properly, so
1444 # this will re-try with the entirety of -proposed
1445 self.assertIn('WARNING: Test dependencies are unsatisfiable with using apt pinning.', err)
1446- self.assertIn('Retrying with using all packages from proposed\n', err)
1447+ self.assertIn('Retrying with using all packages from testy-proposed\n', err)
1448
1449 def test_apt_pocket_multi_nonexisting_dep(self):
1450 '''--apt-pocket with multiple tests and a nonexisting dependency'''
1451@@ -3219,7 +3391,16 @@ Depends: nonexisting'''
1452 self.assertRegex(out, 'command1\s+PASS', out)
1453 self.assertNotIn(out, 'command2')
1454 self.assertIn('WARNING: Test dependencies are unsatisfiable with using apt pinning.', err)
1455- self.assertIn('ERROR: erroneous package: Test dependencies are unsatisfiable', err)
1456+ self.assertIn('badpkg: Test dependencies are unsatisfiable', err)
1457+
1458+ def test_apt_pocket_no_fallback_fail(self):
1459+ '''--apt-pocket selecting package from -proposed with dep from -proposed with --no-apt-fallback'''
1460+
1461+ (code, out, err) = self.do_apt_pocket_test(
1462+ ['--apt-pocket=proposed=vanilla', '--no-apt-fallback'],
1463+ '[ "$V" = 2 ]; [ "$C" = 2 ]; [ "$M" = 2 ]; [ "$H" = 2 ]; [ "$L" = 2 ]',
1464+ expect_success=False)
1465+ self.assertIn('Test dependencies are unsatisfiable.', err)
1466
1467 def test_apt_pocket_nbs(self):
1468 '''--apt-pocket with only NBS binaries in unstable'''
1469@@ -3273,7 +3454,7 @@ Depends: nonexisting'''
1470 '--apt-upgrade',
1471 '--debug', 'flavor'])
1472 # we cannot actually download the package, but it should get as far as trying
1473- self.assertTrue('apt-get source -q --only-source flavor=3' in err, err)
1474+ self.assertTrue('apt-get source -d -q --only-source flavor=3' in err, err)
1475 self.assertEqual(code, 12, err)
1476
1477 def test_git_source_build(self):
1478@@ -3281,7 +3462,7 @@ Depends: nonexisting'''
1479
1480 (code, out, err) = self.runtest(
1481 ['--env=DEB_BUILD_OPTIONS=nocheck',
1482- 'git://anonscm.debian.org/autopkgtest/autopkgtest.git'])
1483+ 'https://salsa.debian.org/ci-team/autopkgtest.git'])
1484 # test should succeed, but skip the Lxd test (if present)
1485 self.assertIn(code, [0, 2], err)
1486 self.assertRegex(err, 'autopkgtest\s+PASS')
1487@@ -3359,6 +3540,174 @@ Depends: nonexisting'''
1488 # should build package
1489 self.assertIn('dh build', err)
1490
1491+ def _build_testprovider(self, provision, arch='all'):
1492+ '''mangle a 'testprovider' package from the testpkg sources'''
1493+
1494+ pr = self.build_src('Tests: pass',
1495+ {},
1496+ 'testprovider')
1497+
1498+ subprocess.check_call(['sed', '-i', 's/testpkg/testprovider/g'] +
1499+ [os.path.join(pr, 'debian', f) for f in ('control', 'changelog', 'links')])
1500+
1501+ # fix conflicting files
1502+ subprocess.check_call(['sed', '-i', 's,/usr/bin,/usr/share/testprovider,'] +
1503+ [os.path.join(pr, 'Makefile')])
1504+
1505+ # add a (possibly versioned) Provides
1506+ subprocess.check_call(['sed', '-i', '/^Depends:/ a\Provides: %s' % provision,
1507+ os.path.join(pr, 'debian', 'control')])
1508+
1509+ if arch != 'all':
1510+ subprocess.check_call(['sed', '-i', 's/Architecture: all/Architecture: %s/' % arch,
1511+ os.path.join(pr, 'debian', 'control')])
1512+ subprocess.check_call(['dpkg-buildpackage', '-b', '-us', '-uc', '-tc'],
1513+ stdout=subprocess.PIPE, stderr=subprocess.PIPE,
1514+ cwd=pr)
1515+
1516+ shutil.rmtree(pr)
1517+
1518+ if arch == 'all':
1519+ deb = os.path.join(os.path.dirname(pr), 'testprovider_1_all.deb')
1520+ else:
1521+ deb = os.path.join(os.path.dirname(pr), 'testprovider_1_' + host_arch + '.deb')
1522+ return deb
1523+
1524+ def _test_provides(self, provision, test_dependency, arch='all', control_extra='', expect_failure=False):
1525+ '''internal function to test different combinations of provides support'''
1526+
1527+ testprovider_deb = self._build_testprovider(provision, arch)
1528+
1529+ # we check that the testprovider package isn't enough to satisfy
1530+ # testpkg's test-dependency on itself via 'test_dependency'
1531+ # even though testprovider Provides: a versioned testpkg
1532+ p = self.build_src('Tests: pass\nDepends: testprovider, %s' % test_dependency,
1533+ {'pass': '#!/bin/sh -e\ndpkg-query -W -f\${Status} testpkg | grep -q ^install'})
1534+
1535+ if arch != 'all':
1536+ subprocess.check_call(['sed', '-i', 's/Architecture: all/Architecture: %s/' % arch,
1537+ os.path.join(p, 'debian', 'control')])
1538+
1539+ extradebs = []
1540+ if control_extra:
1541+ with open(os.path.join(p, 'debian', 'control'), 'a') as f:
1542+ f.write('\n' + control_extra)
1543+ extradebs = [os.path.join(os.path.dirname(p), pkg + '_1_all.deb')
1544+ for pkg in re.findall('^Package: (.+)', control_extra, re.MULTILINE)]
1545+
1546+ subprocess.check_call(['dpkg-buildpackage', '-b', '-us', '-uc', '-tc'],
1547+ stdout=subprocess.PIPE, stderr=subprocess.PIPE,
1548+ cwd=p)
1549+
1550+ if arch == 'all':
1551+ testpkg_deb = os.path.join(os.path.dirname(p), 'testpkg_1_all.deb')
1552+ else:
1553+ testpkg_deb = os.path.join(os.path.dirname(p), 'testpkg_1_' + host_arch + '.deb')
1554+
1555+ failed = False
1556+ (code, out, err) = self.runtest([p, testpkg_deb, testprovider_deb] + extradebs)
1557+ try:
1558+ self.assertEqual(code, 0, out + err)
1559+ self.assertRegex(out, 'pass\s+PASS', out)
1560+ except BaseException:
1561+ if not expect_failure:
1562+ raise
1563+ failed = True
1564+
1565+ shutil.rmtree(p)
1566+
1567+ # now check that testpkg gets installed even if testprovider already is
1568+ p = self.build_src('Tests: pass\nDepends: %s' % test_dependency,
1569+ {'pass': '#!/bin/sh -e\ndpkg-query -W -f\${Status} testpkg | grep -q ^install'})
1570+ if control_extra:
1571+ with open(os.path.join(p, 'debian', 'control'), 'a') as f:
1572+ f.write('\n' + control_extra)
1573+
1574+ tb_deb = '/tmp/provider.deb'
1575+
1576+ (code, out, err) = self.runtest(['--copy', '%s:%s' % (testprovider_deb, tb_deb),
1577+ '--setup-commands', 'dpkg -i %s' % tb_deb, p])
1578+ try:
1579+ self.assertEqual(code, 0, out + err)
1580+ self.assertRegex(out, 'pass\s+PASS', out)
1581+ except BaseException:
1582+ if not expect_failure:
1583+ raise
1584+ failed = True
1585+
1586+ self.assertTrue(failed or not expect_failure)
1587+
1588+ shutil.rmtree(p)
1589+
1590+ def test_unversioned_provides_with_at(self):
1591+ '''test that unversioned provides do not satisfy test dependencies on "@"'''
1592+ self._test_provides('testpkg', '@')
1593+
1594+ def test_unversioned_provides_with_unversioned_explicit(self):
1595+ '''test that unversioned provides do not satisfy unversioned test dependencies on binary package from the same source'''
1596+ self._test_provides('testpkg', 'testpkg')
1597+
1598+ # this is somewhat silly but included for completeness
1599+ def test_unversioned_provides_with_versioned_explicit(self):
1600+ '''test that unversioned provides do not satisfy versioned test dependencies on binary package from the same source'''
1601+ self._test_provides('testpkg', 'testpkg (>= 0.5)')
1602+
1603+ def test_versioned_provides_with_at(self):
1604+ '''test that versioned provides do not satisfy test dependencies on "@"'''
1605+ self._test_provides('testpkg (= 0.5)', '@')
1606+
1607+ def test_versioned_provides_with_unversioned_explicit(self):
1608+ '''test that versioned provides do not satisfy unversioned test dependencies on binary package from the same source'''
1609+ self._test_provides('testpkg (= 0.5)', 'testpkg')
1610+
1611+ def test_versioned_provides_with_versioned_explicit(self):
1612+ '''test that versioned provides do not satisfy versioned test dependencies on binary package from the same source'''
1613+ self._test_provides('testpkg (= 0.5)', 'testpkg (>= 0.5)')
1614+
1615+ def test_versioned_provides_with_at_arch_any(self):
1616+ '''test that versioned provides do not satisfy test dependencies on "@" with arch:any binary packages'''
1617+ self._test_provides('testpkg (= 0.5)', '@', arch='any')
1618+
1619+ def test_versioned_provides_with_at_arch_multiple(self):
1620+ '''test that versioned provides do not satisfy test dependencies on "@" with <os>-any + hurd-any binary packages'''
1621+ self._test_provides('testpkg (= 0.5)', '@', arch=host_os + '-any hurd-any')
1622+
1623+ def test_versioned_provides_with_unversioned_explicit_on_not_hurd(self):
1624+ '''test that versioned provides do not satisfy test dependencies on !hurd-any'''
1625+ self._test_provides('testpkg (= 0.5)', 'testpkg [!hurd-any]')
1626+
1627+ def test_versioned_provides_with_unversioned_explicit_on_host_arch(self):
1628+ '''test that versioned provides do not satisfy test dependencies on <host_arch>'''
1629+ self._test_provides('testpkg (= 0.5)', 'testpkg [' + host_arch + ']')
1630+
1631+ def test_unversioned_provides_with_conflicting_alternatives(self):
1632+ '''test that unversioned provides do not satisfy test dependencies on conflicting alternatives from the same source'''
1633+ testpkg2 = '\nPackage: testpkg2\nArchitecture: all\nDescription: test package 2\nConflicts: testpkg'
1634+ self._test_provides('testpkg', 'testpkg | testpkg2', control_extra=testpkg2)
1635+
1636+ def test_versioned_provides_with_conflicting_alternatives(self):
1637+ '''test that versioned provides do not satisfy test dependencies on conflicting alternatives from the same source'''
1638+ testpkg2 = '\nPackage: testpkg2\nArchitecture: all\nDescription: test package 2\nConflicts: testpkg'
1639+ self._test_provides('testpkg (= 0.5)', 'testpkg | testpkg2', control_extra=testpkg2)
1640+
1641+ def test_unversioned_provides_with_nonconflicting_alternatives(self):
1642+ '''test that unversioned provides do not satisfy test dependencies on not conflicting alternatives from the same source'''
1643+ testpkg2 = '\nPackage: testpkg2\nArchitecture: all\nDescription: test package 2'
1644+ self._test_provides('testpkg', 'testpkg | testpkg2', control_extra=testpkg2)
1645+
1646+ def test_versioned_provides_with_nonconflicting_alternatives(self):
1647+ '''test that versioned provides do not satisfy test dependencies on not conflicting alternatives from the same source'''
1648+ testpkg2 = '\nPackage: testpkg2\nArchitecture: all\nDescription: test package 2'
1649+ self._test_provides('testpkg (= 0.5)', 'testpkg | testpkg2', control_extra=testpkg2)
1650+
1651+ def test_unversioned_provides_with_unrelated_alternatives(self):
1652+ '''test that unversioned provides satisfy test dependencies on alternatives not all from the same source'''
1653+ self._test_provides('testpkg', 'testpkg | dpkg', expect_failure=True)
1654+
1655+ def test_versioned_provides_with_unrelated_alternatives(self):
1656+ '''test that versioned provides satisfy test dependencies on alternatives not all from the same source'''
1657+ self._test_provides('testpkg (= 0.5)', 'testpkg | dpkg', expect_failure=True)
1658+
1659
1660 @unittest.skipUnless('AUTOPKGTEST_TEST_SCHROOT_CLICK' in os.environ,
1661 'Set $AUTOPKGTEST_TEST_SCHROOT_CLICK to an existing schroot')
1662diff --git a/tests/testdesc b/tests/testdesc
1663index b749018..4a51f44 100755
1664--- a/tests/testdesc
1665+++ b/tests/testdesc
1666@@ -108,7 +108,7 @@ class Test(unittest.TestCase):
1667 '''valid Test instantiation with path'''
1668
1669 t = testdesc.Test('foo', 'tests/do_foo', None, ['needs-root'],
1670- ['unknown_feature'], ['coreutils >= 7'], [], [])
1671+ ['unknown_feature'], ['coreutils >= 7'], [], [], [])
1672 self.assertEqual(t.name, 'foo')
1673 self.assertEqual(t.path, 'tests/do_foo')
1674 self.assertEqual(t.command, None)
1675@@ -120,7 +120,7 @@ class Test(unittest.TestCase):
1676 '''valid Test instantiation with command'''
1677
1678 t = testdesc.Test('foo', None, 'echo hi', ['needs-root'],
1679- ['unknown_feature'], ['coreutils >= 7'], [], [])
1680+ ['unknown_feature'], ['coreutils >= 7'], [], [], [])
1681 self.assertEqual(t.name, 'foo')
1682 self.assertEqual(t.path, None)
1683 self.assertEqual(t.command, 'echo hi')
1684@@ -130,7 +130,7 @@ class Test(unittest.TestCase):
1685 '''Test with invalid name'''
1686
1687 with self.assertRaises(testdesc.Unsupported) as cm:
1688- testdesc.Test('foo/bar', 'do_foo', None, [], [], [], [], [])
1689+ testdesc.Test('foo/bar', 'do_foo', None, [], [], [], [], [], [])
1690 self.assertIn('may not contain /', str(cm.exception))
1691
1692 def test_unknown_restriction(self):
1693@@ -138,28 +138,28 @@ class Test(unittest.TestCase):
1694
1695 with self.assertRaises(testdesc.Unsupported) as cm:
1696 testdesc.Test('foo', 'tests/do_foo', None, ['needs-red'], [], [],
1697- [], [])
1698+ [], [], [])
1699 self.assertIn('unknown restriction needs-red', str(cm.exception))
1700
1701 def test_neither_path_nor_command(self):
1702 '''Test without path nor command'''
1703
1704 with self.assertRaises(testdesc.InvalidControl) as cm:
1705- testdesc.Test('foo', None, None, [], [], [], [], [])
1706+ testdesc.Test('foo', None, None, [], [], [], [], [], [])
1707 self.assertIn('either path or command', str(cm.exception))
1708
1709 def test_both_path_and_command(self):
1710 '''Test with path and command'''
1711
1712 with self.assertRaises(testdesc.InvalidControl) as cm:
1713- testdesc.Test('foo', 'do_foo', 'echo hi', [], [], [], [], [])
1714+ testdesc.Test('foo', 'do_foo', 'echo hi', [], [], [], [], [], [])
1715 self.assertIn('either path or command', str(cm.exception))
1716
1717 def test_capabilities_compat(self):
1718 '''Test compatibility with testbed capabilities'''
1719
1720 t = testdesc.Test('foo', 'tests/do_foo', None,
1721- ['needs-root', 'isolation-container'], [], [], [], [])
1722+ ['needs-root', 'isolation-container'], [], [], [], [], [])
1723
1724 self.assertRaises(testdesc.Unsupported,
1725 t.check_testbed_compat, ['isolation-container'])
1726@@ -221,7 +221,7 @@ class Debian(unittest.TestCase):
1727 for t in ts:
1728 self.assertEqual(t.restrictions, [])
1729 self.assertEqual(t.features, [])
1730- self.assertEqual(t.depends, ['one (>= 0~)', 'two (>= 0~)'])
1731+ self.assertEqual(t.depends, ['one', 'two'])
1732 self.assertFalse(skipped)
1733
1734 def test_arch_specific(self):
1735@@ -238,8 +238,8 @@ class Debian(unittest.TestCase):
1736 self.assertEqual(ts[0].restrictions, [])
1737 self.assertEqual(ts[0].features, [])
1738 self.assertEqual(ts[0].depends,
1739- ['one (>= 0~)', 'two (>= 0~) [linux-any]',
1740- 'three (>= 0~) [c64 vax]'])
1741+ ['one', 'two [linux-any]',
1742+ 'three [c64 vax]'])
1743 self.assertFalse(skipped)
1744
1745 def test_test_name_feature(self):
1746@@ -375,10 +375,12 @@ class Debian(unittest.TestCase):
1747 'Tests: t\nDepends: @, @builddeps@, foo (>= 7)',
1748 'Source: nums\nBuild-Depends: bd1, bd2 [armhf], bd3:native (>= 7) | bd4 [linux-any]\n'
1749 'Build-Depends-Indep: bdi1, bdi2 [amd64]\n'
1750+ 'Build-Depends-Arch: bda1, bda2 [amd64]\n'
1751 '\n'
1752 'Package: one\nArchitecture: any')
1753- self.assertEqual(ts[0].depends, ['one (>= 0~)', 'bd1', 'bd3:native (>= 7) | bd4',
1754- 'bdi1', 'bdi2', 'build-essential', 'foo (>= 7)'])
1755+ self.assertEqual(ts[0].depends, ['one', 'bd1', 'bd3:native (>= 7) | bd4',
1756+ 'bdi1', 'bdi2', 'bda1', 'bda2',
1757+ 'build-essential', 'foo (>= 7)'])
1758 self.assertFalse(skipped)
1759
1760 @unittest.skipUnless(have_dpkg_build_profiles,
1761@@ -391,7 +393,7 @@ class Debian(unittest.TestCase):
1762 'Source: nums\nBuild-Depends: bd1, bd2 <!check>, bd3 <!cross>, bdnotme <stage1> <cross>\n'
1763 '\n'
1764 'Package: one\nArchitecture: any')
1765- self.assertEqual(ts[0].depends, ['one (>= 0~)', 'bd1', 'bd2', 'bd3', 'build-essential'])
1766+ self.assertEqual(ts[0].depends, ['one', 'bd1', 'bd2', 'bd3', 'build-essential'])
1767 self.assertFalse(skipped)
1768
1769 def test_complex_deps(self):
1770@@ -401,7 +403,7 @@ class Debian(unittest.TestCase):
1771 'Tests: t\nDepends: @,\n foo (>= 7) [linux-any],\n'
1772 ' bd3:native (>= 4) | bd4 [armhf megacpu],\n',
1773 'Source: nums\n\nPackage: one\nArchitecture: any')
1774- self.assertEqual(ts[0].depends, ['one (>= 0~)', 'foo (>= 7) [linux-any]',
1775+ self.assertEqual(ts[0].depends, ['one', 'foo (>= 7) [linux-any]',
1776 'bd3:native (>= 4) | bd4 [armhf megacpu]'])
1777 self.assertFalse(skipped)
1778
1779@@ -433,7 +435,7 @@ class Debian(unittest.TestCase):
1780 ' , bd2\n'
1781 '\n'
1782 'Package: one\nArchitecture: any')
1783- self.assertEqual(ts[0].depends, ['one (>= 0~)', 'bd1', 'bd2', 'build-essential'])
1784+ self.assertEqual(ts[0].depends, ['one', 'bd1', 'bd2', 'build-essential'])
1785 self.assertFalse(skipped)
1786
1787 @patch('adtlog.report')
1788@@ -565,7 +567,7 @@ class Debian(unittest.TestCase):
1789 self.assertGreaterEqual(len(ts), 1)
1790 self.assertIn('pkg-perl-autopkgtest', ts[0].command)
1791 self.assertIn('pkg-perl-autopkgtest', ts[0].depends)
1792- self.assertIn('libfoo-perl (>= 0~)', ts[0].depends)
1793+ self.assertIn('libfoo-perl', ts[0].depends)
1794 else:
1795 self.assertEqual(len(ts), 0)
1796
1797diff --git a/virt/autopkgtest-virt-lxc b/virt/autopkgtest-virt-lxc
1798index 7a9a682..a2ecd76 100755
1799--- a/virt/autopkgtest-virt-lxc
1800+++ b/virt/autopkgtest-virt-lxc
1801@@ -139,10 +139,18 @@ def determine_normal_user(lxc_name):
1802
1803 global capabilities, normal_user
1804
1805+<<<<<<< virt/autopkgtest-virt-lxc
1806 # get the first UID >= 1000
1807 cmd = ['lxc-attach', '--name', lxc_name, '--', 'sh', '-c',
1808 'getent passwd | sort -t: -nk3 | '
1809 "awk -F: '{if ($3 >= 1000) { print $1; exit } }'"]
1810+=======
1811+ # get the first UID in the Debian Policy §9.2.2 "dynamically allocated
1812+ # user account" range
1813+ cmd = ['lxc-attach', '--name', lxc_name, '--', 'sh', '-c',
1814+ 'getent passwd | sort -t: -nk3 | '
1815+ "awk -F: '{if ($3 >= 1000 && $3 <= 59999) { print $1; exit } }'"]
1816+>>>>>>> virt/autopkgtest-virt-lxc
1817 out = VirtSubproc.execute_timeout(None, 10, sudoify(cmd),
1818 stdout=subprocess.PIPE)[1].strip()
1819 if out:
1820@@ -150,7 +158,11 @@ def determine_normal_user(lxc_name):
1821 capabilities.append('suggested-normal-user=' + normal_user)
1822 adtlog.debug('determine_normal_user: got user "%s"' % normal_user)
1823 else:
1824+<<<<<<< virt/autopkgtest-virt-lxc
1825 adtlog.debug('determine_normal_user: no uid >= 1000 available')
1826+=======
1827+ adtlog.debug('determine_normal_user: no uid in [1000,59999] available')
1828+>>>>>>> virt/autopkgtest-virt-lxc
1829
1830
1831 def start_lxc1():
1832diff --git a/virt/autopkgtest-virt-lxd b/virt/autopkgtest-virt-lxd
1833index 46394da..f51ca34 100755
1834--- a/virt/autopkgtest-virt-lxd
1835+++ b/virt/autopkgtest-virt-lxd
1836@@ -115,10 +115,18 @@ def determine_normal_user():
1837
1838 global capabilities, normal_user
1839
1840+<<<<<<< virt/autopkgtest-virt-lxd
1841 # get the first UID >= 1000
1842 cmd = ['lxc', 'exec', container_name, '--', 'sh', '-c',
1843 'getent passwd | sort -t: -nk3 | '
1844 "awk -F: '{if ($3 >= 1000) { print $1; exit } }'"]
1845+=======
1846+ # get the first UID in the Debian Policy §9.2.2 "dynamically allocated
1847+ # user account" range
1848+ cmd = ['lxc', 'exec', container_name, '--', 'sh', '-c',
1849+ 'getent passwd | sort -t: -nk3 | '
1850+ "awk -F: '{if ($3 >= 1000 && $3 <= 59999) { print $1; exit } }'"]
1851+>>>>>>> virt/autopkgtest-virt-lxd
1852 out = VirtSubproc.execute_timeout(None, 10, cmd,
1853 stdout=subprocess.PIPE)[1].strip()
1854 if out:
1855@@ -126,7 +134,11 @@ def determine_normal_user():
1856 capabilities.append('suggested-normal-user=' + normal_user)
1857 adtlog.debug('determine_normal_user: got user "%s"' % normal_user)
1858 else:
1859+<<<<<<< virt/autopkgtest-virt-lxd
1860 adtlog.debug('determine_normal_user: no uid >= 1000 available')
1861+=======
1862+ adtlog.debug('determine_normal_user: no uid in [1000,59999] available')
1863+>>>>>>> virt/autopkgtest-virt-lxd
1864
1865
1866 def hook_open():
1867diff --git a/virt/autopkgtest-virt-qemu b/virt/autopkgtest-virt-qemu
1868index afb82e7..416c4c8 100755
1869--- a/virt/autopkgtest-virt-qemu
1870+++ b/virt/autopkgtest-virt-qemu
1871@@ -90,6 +90,8 @@ def parse_args():
1872 help='Pass through arguments to QEMU command.')
1873 parser.add_argument('--baseimage', action='store_true', default=False,
1874 help='Provide a read-only copy of the base image at /dev/baseimage')
1875+ parser.add_argument('--efi', action='store_true', default=False,
1876+ help='Use OVMF or AAVMF to boot virtual machine using EFI (default: BIOS)')
1877 parser.add_argument('image', nargs='+',
1878 help='disk image to add to the VM (in order)')
1879
1880@@ -494,10 +496,11 @@ def determine_normal_user(shared_dir):
1881 normal_user = args.user
1882 return
1883
1884- # get the first UID >= 500
1885+ # get the first UID in the Debian Policy §9.2.2 "dynamically allocated
1886+ # user account" range
1887 term = VirtSubproc.get_unix_socket(os.path.join(workdir, 'ttyS1'))
1888 term.send(b"getent passwd | sort -t: -nk3 | "
1889- b"awk -F: '{if ($3 >= 500) { print $1; exit } }'"
1890+ b"awk -F: '{if ($3 >= 1000 && $3 <= 59999) { print $1; exit } }'"
1891 b"> /run/autopkgtest/shared/normal_user\n")
1892 with VirtSubproc.timeout(5, 'timed out on determining normal user'):
1893 outfile = os.path.join(shared_dir, 'normal_user')
1894@@ -509,7 +512,7 @@ def determine_normal_user(shared_dir):
1895 normal_user = out.strip()
1896 adtlog.debug('determine_normal_user: got user "%s"' % normal_user)
1897 else:
1898- adtlog.debug('determine_normal_user: no uid >= 500 available')
1899+ adtlog.debug('determine_normal_user: no uid in [1000,59999] available')
1900
1901
1902 def hook_open():
1903@@ -544,6 +547,30 @@ def hook_open():
1904 '-virtfs',
1905 'local,id=autopkgtest,path=%s,security_model=none,mount_tag=autopkgtest' % shareddir,
1906 '-drive', 'file=%s,cache=unsafe,if=virtio,index=0' % overlay]
1907+
1908+ if args.efi:
1909+ code = None
1910+ data = None
1911+
1912+ if 'qemu-system-x86_64' in args.qemu_command or \
1913+ 'qemu-system-i386' in args.qemu_command:
1914+ code = '/usr/share/OVMF/OVMF_CODE.fd'
1915+ data = '/usr/share/OVMF/OVMF_VARS.fd'
1916+ elif 'qemu-system-aarch64' in args.qemu_command:
1917+ code = '/usr/share/AAVMF/AAVMF_CODE.fd'
1918+ data = '/usr/share/AAVMF/AAVMF_VARS.fd'
1919+ elif 'qemu-system-arm' in args.qemu_command:
1920+ code = '/usr/share/AAVMF/AAVMF32_CODE.fd'
1921+ data = '/usr/share/AAVMF/AAVMF32_VARS.fd'
1922+ else:
1923+ VirtSubproc.bomb('Unknown architecture for EFI boot')
1924+
1925+ shutil.copy(data, '%s/efivars.fd' % workdir)
1926+ argv.append('-drive')
1927+ argv.append('if=pflash,format=raw,read-only,file=' + code)
1928+ argv.append('-drive')
1929+ argv.append('if=pflash,format=raw,file=%s/efivars.fd' % workdir)
1930+
1931 for i, image in enumerate(args.image[1:]):
1932 argv.append('-drive')
1933 argv.append('file=%s,if=virtio,index=%i,readonly' % (image, i + 1))
1934diff --git a/virt/autopkgtest-virt-qemu.1 b/virt/autopkgtest-virt-qemu.1
1935index cdfe190..2137725 100644
1936--- a/virt/autopkgtest-virt-qemu.1
1937+++ b/virt/autopkgtest-virt-qemu.1
1938@@ -110,6 +110,14 @@ will not be accessible between calling
1939 and the next boot, thus make sure to stop accessing it before.
1940
1941 .TP
1942+.B \-\-efi
1943+Configure QEMU with OVMF or AAVMF firmware images suitable for booting
1944+an x86, ARM or ARM64 virtual machine using EFI. This requires the
1945+ovmf, qemu-efi-arm or qemu-efi-aarch64 package. The correct firmware
1946+image is guessed from the
1947+.BR --qemu-command .
1948+
1949+.TP
1950 .BI "--qemu-options=" arguments
1951 Pass through arguments to QEMU command; e. g. --qemu-options='-readconfig qemu.cfg'
1952

Subscribers

People subscribed via source and target branches