Merge lp:~cjwatson/launchpad/germinate-stale-files into lp:launchpad

Proposed by Colin Watson on 2012-05-21
Status: Merged
Approved by: Benji York on 2012-05-21
Approved revision: no longer in the source branch.
Merged at revision: 15280
Proposed branch: lp:~cjwatson/launchpad/germinate-stale-files
Merge into: lp:launchpad
Diff against target: 1019 lines (+352/-361)
4 files modified
lib/lp/archivepublisher/scripts/generate_extra_overrides.py (+36/-10)
lib/lp/archivepublisher/tests/publisher-config.txt (+0/-195)
lib/lp/archivepublisher/tests/test_config.py (+157/-2)
lib/lp/archivepublisher/tests/test_generate_extra_overrides.py (+159/-154)
To merge this branch: bzr merge lp:~cjwatson/launchpad/germinate-stale-files
Reviewer Review Type Date Requested Status
Benji York (community) code 2012-05-21 Approve on 2012-05-21
Review via email: mp+106626@code.launchpad.net

Commit Message

Remove stale files from config.germinateroot.

Description of the Change

== Summary ==

One of the problems identified in https://wiki.ubuntu.com/FoundationsTeam/ReplaceArchiveAdminShellAccess (and bug 1001517) is that stale files pile up in cocoplum:/srv/launchpad.net/ubuntu-archive/ubuntu-germinate/ from time to time.

== Proposed fix ==

Keep track of the current set of files written for the current series, and remove any not in that set.

== Implementation details ==

Easy, except for LoC. I did some refactoring of test_generate_extra_overrides, which was overdue anyway, but couldn't make it small enough to get the LoC delta non-positive; so I converted an unrelated but nearby doctest to unit tests, bringing me to -14.

== Tests ==

bin/test -vvct archivepublisher.tests.test_config -t archivepublisher.tests.test_generate_extra_overrides

== Demo and Q/A ==

On dogfood:

 cp -a /srv/launchpad.net/ubuntu-archive/ubuntu-germinate /srv/launchpad.net/ubuntu-archive/ubuntu-germinate.stale-files-backup
 touch /srv/launchpad.net/ubuntu-archive/ubuntu-germinate/nonexistentseed_ubuntu_quantal_i386

Then do a full publisher run, and verify that (a) /srv/launchpad.net/ubuntu-archive/ubuntu-germinate/nonexistentseed_ubuntu_quantal_i386 is removed; (b) no other files were removed relative to the backup; (c) many other *_*_quantal_* files are created (since we haven't had a publisher run on dogfood since the last DB restore).

To post a comment you must log in.
Benji York (benji) wrote :

Looks good.

The with/pass in test_generate_extra_overrides.py (line 877 of the diff)
confused me for a second. I wonder if an old-fashioned open().close()
wouldn't be easier to understand at first blush. Or maybe even a
"touch" helper function.

review: Approve (code)
Colin Watson (cjwatson) wrote :

Maybe I'm weird, but I prefer the with/pass pattern myself; however, a
helper function makes sense, and perhaps can be moved somewhere more
common in future. Done.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/archivepublisher/scripts/generate_extra_overrides.py'
2--- lib/lp/archivepublisher/scripts/generate_extra_overrides.py 2012-01-03 17:08:40 +0000
3+++ lib/lp/archivepublisher/scripts/generate_extra_overrides.py 2012-05-21 19:07:38 +0000
4@@ -1,4 +1,4 @@
5-# Copyright 2011 Canonical Ltd. This software is licensed under the
6+# Copyright 2011-2012 Canonical Ltd. This software is licensed under the
7 # GNU Affero General Public License version 3 (see the file LICENSE).
8
9 """Generate extra overrides using Germinate."""
10@@ -9,6 +9,7 @@
11 ]
12
13 from functools import partial
14+import glob
15 import logging
16 from optparse import OptionValueError
17 import os
18@@ -130,8 +131,9 @@
19
20 self.germinate_logger = logging.getLogger("germinate")
21 self.germinate_logger.setLevel(logging.INFO)
22- log_file = os.path.join(self.config.germinateroot, "germinate.output")
23- handler = logging.FileHandler(log_file, mode="w")
24+ self.log_file = os.path.join(
25+ self.config.germinateroot, "germinate.output")
26+ handler = logging.FileHandler(self.log_file, mode="w")
27 handler.setFormatter(GerminateFormatter())
28 self.germinate_logger.addHandler(handler)
29 self.germinate_logger.propagate = False
30@@ -186,8 +188,12 @@
31 self.config.germinateroot,
32 "%s_%s_%s_%s" % (base, flavour, series_name, arch))
33
34+ def recordOutput(self, path, seed_outputs):
35+ if seed_outputs is not None:
36+ seed_outputs.add(os.path.basename(path))
37+
38 def writeGerminateOutput(self, germinator, structure, flavour,
39- series_name, arch):
40+ series_name, arch, seed_outputs=None):
41 """Write dependency-expanded output files.
42
43 These files are a reduced subset of those written by the germinate
44@@ -198,18 +204,22 @@
45 # The structure file makes it possible to figure out how the other
46 # output files relate to each other.
47 structure.write(path("structure"))
48+ self.recordOutput(path("structure"), seed_outputs)
49
50 # "all" and "all.sources" list the full set of binary and source
51 # packages respectively for a given flavour/suite/architecture
52 # combination.
53 germinator.write_all_list(structure, path("all"))
54+ self.recordOutput(path("all"), seed_outputs)
55 germinator.write_all_source_list(structure, path("all.sources"))
56+ self.recordOutput(path("all.sources"), seed_outputs)
57
58 # Write the dependency-expanded output for each seed. Several of
59 # these are used by archive administration tools, and others are
60 # useful for debugging, so it's best to just write them all.
61 for seedname in structure.names:
62 germinator.write_full_list(structure, path(seedname), seedname)
63+ self.recordOutput(path(seedname), seed_outputs)
64
65 def parseTaskHeaders(self, seedtext):
66 """Parse a seed for Task headers.
67@@ -263,15 +273,17 @@
68 package, arch, key, value)
69
70 def germinateArchFlavour(self, override_file, germinator, series_name,
71- arch, flavour, structure, primary_flavour):
72+ arch, flavour, structure, primary_flavour,
73+ seed_outputs=None):
74 """Germinate seeds on a single flavour for a single architecture."""
75 # Expand dependencies.
76 germinator.plant_seeds(structure)
77 germinator.grow(structure)
78 germinator.add_extras(structure)
79
80- self.writeGerminateOutput(germinator, structure, flavour, series_name,
81- arch)
82+ self.writeGerminateOutput(
83+ germinator, structure, flavour, series_name, arch,
84+ seed_outputs=seed_outputs)
85
86 write_overrides = partial(
87 self.writeOverrides, override_file, germinator, structure, arch)
88@@ -295,7 +307,7 @@
89 write_overrides("build-essential", "Build-Essential", "yes")
90
91 def germinateArch(self, override_file, series_name, components, arch,
92- flavours, structures):
93+ flavours, structures, seed_outputs=None):
94 """Germinate seeds on all flavours for a single architecture."""
95 germinator = Germinator(arch)
96
97@@ -316,7 +328,19 @@
98
99 self.germinateArchFlavour(
100 override_file, germinator, series_name, arch, flavour,
101- structures[flavour], flavour == flavours[0])
102+ structures[flavour], flavour == flavours[0],
103+ seed_outputs=seed_outputs)
104+
105+ def removeStaleOutputs(self, series_name, seed_outputs):
106+ """Remove stale outputs for a series.
107+
108+ Any per-seed outputs not in seed_outputs are considered stale.
109+ """
110+ all_outputs = glob.glob(
111+ os.path.join(self.config.germinateroot, "*_*_%s_*" % series_name))
112+ for output in all_outputs:
113+ if os.path.basename(output) not in seed_outputs:
114+ os.remove(output)
115
116 def generateExtraOverrides(self, series_name, components, architectures,
117 flavours, seed_bases=None):
118@@ -324,6 +348,7 @@
119 series_name, flavours, seed_bases=seed_bases)
120
121 if structures:
122+ seed_outputs = set()
123 override_path = os.path.join(
124 self.config.miscroot,
125 "more-extra.override.%s.main" % series_name)
126@@ -331,7 +356,8 @@
127 for arch in architectures:
128 self.germinateArch(
129 override_file, series_name, components, arch,
130- flavours, structures)
131+ flavours, structures, seed_outputs=seed_outputs)
132+ self.removeStaleOutputs(series_name, seed_outputs)
133
134 def process(self, seed_bases=None):
135 """Do the bulk of the work."""
136
137=== removed file 'lib/lp/archivepublisher/tests/publisher-config.txt'
138--- lib/lp/archivepublisher/tests/publisher-config.txt 2011-12-29 05:29:36 +0000
139+++ lib/lp/archivepublisher/tests/publisher-config.txt 1970-01-01 00:00:00 +0000
140@@ -1,195 +0,0 @@
141-Publisher Configuration Provider
142-================================
143-
144-The config module has a function to provide a modified publisher configuration
145-that provides the right paths for its publication according to the given
146-archive.
147-
148-We will use a helper function for dumping publisher configurations.
149-
150- >>> config_attributes = [
151- ... 'distroroot',
152- ... 'archiveroot',
153- ... 'poolroot',
154- ... 'distsroot',
155- ... 'overrideroot',
156- ... 'cacheroot',
157- ... 'miscroot',
158- ... 'germinateroot',
159- ... 'temproot',
160- ... ]
161-
162- >>> def dump_config(config):
163- ... for attr_name in config_attributes:
164- ... print '%s: %s' % (attr_name, getattr(config, attr_name))
165-
166-
167-Primary
168--------
169-
170- >>> from lp.registry.interfaces.distribution import IDistributionSet
171- >>> ubuntutest = getUtility(IDistributionSet)['ubuntutest']
172-
173- >>> from lp.archivepublisher.config import getPubConfig
174- >>> primary_config = getPubConfig(ubuntutest.main_archive)
175-
176- >>> dump_config(primary_config)
177- distroroot: /var/tmp/archive
178- archiveroot: /var/tmp/archive/ubuntutest
179- poolroot: /var/tmp/archive/ubuntutest/pool
180- distsroot: /var/tmp/archive/ubuntutest/dists
181- overrideroot: /var/tmp/archive/ubuntutest-overrides
182- cacheroot: /var/tmp/archive/ubuntutest-cache
183- miscroot: /var/tmp/archive/ubuntutest-misc
184- germinateroot: /var/tmp/archive/ubuntutest-germinate
185- temproot: /var/tmp/archive/ubuntutest-temp
186-
187-
188-PPAs
189-----
190-
191-Adjust Celso's PPA to point to a configured distribution and built its
192-publisher configuration.
193-
194- >>> # cprov 20061127: We should *never* be able to change a PPA
195- >>> # distribution, however 'ubuntu' is not prepared for publication, thus
196- >>> # we have to override the PPA to 'ubuntutest' in order to perform the
197- >>> # tests.
198-
199- >>> from lp.registry.interfaces.person import IPersonSet
200- >>> cprov = getUtility(IPersonSet).getByName('cprov')
201- >>> cprov_archive = cprov.archive
202- >>> cprov_archive.distribution = ubuntutest
203-
204- >>> ppa_config = getPubConfig(cprov_archive)
205-
206-The base Archive publication location is set in the current Launchpad
207-configuration file:
208-
209- >>> from lp.services.config import config
210- >>> ppa_config.distroroot == config.personalpackagearchive.root
211- True
212-
213-A PPA repository topology will follow:
214-
215-<PPA_BASE_DIR>/<PERSONNAME>/<DISTRIBUTION>
216-
217-And some paths are not used for PPA workflow, so they are set to
218-None, so they won't get created:
219-
220- >>> dump_config(ppa_config)
221- distroroot: /var/tmp/ppa.test/
222- archiveroot: /var/tmp/ppa.test/cprov/ppa/ubuntutest
223- poolroot: /var/tmp/ppa.test/cprov/ppa/ubuntutest/pool
224- distsroot: /var/tmp/ppa.test/cprov/ppa/ubuntutest/dists
225- overrideroot: None
226- cacheroot: None
227- miscroot: None
228- germinateroot: None
229- temproot: /var/tmp/archive/ubuntutest-temp
230-
231-There is a separate location for private PPAs that is used if the
232-archive is marked as private:
233-
234- >>> (config.personalpackagearchive.private_root !=
235- ... config.personalpackagearchive.root)
236- True
237-
238- >>> cprov_private_ppa = factory.makeArchive(
239- ... owner=cprov, name='myprivateppa',
240- ... distribution=cprov_archive.distribution)
241- >>> cprov_private_ppa.private = True
242- >>> cprov_private_ppa.buildd_secret = "secret"
243- >>> p3a_config = getPubConfig(cprov_private_ppa)
244-
245- >>> (p3a_config.distroroot ==
246- ... config.personalpackagearchive.private_root)
247- True
248-
249- >>> dump_config(p3a_config)
250- distroroot: /var/tmp/ppa
251- archiveroot: /var/tmp/ppa/cprov/myprivateppa/ubuntutest
252- poolroot: /var/tmp/ppa/cprov/myprivateppa/ubuntutest/pool
253- distsroot: /var/tmp/ppa/cprov/myprivateppa/ubuntutest/dists
254- overrideroot: None
255- cacheroot: None
256- miscroot: None
257- germinateroot: None
258- temproot: /var/tmp/archive/ubuntutest-temp
259-
260-
261-Partner
262--------
263-
264-The publisher config for PARTNER contains only 'partner' in its
265-components. This prevents non-partner being published in the partner
266-archive.
267-
268- >>> from lp.soyuz.interfaces.archive import IArchiveSet
269- >>> partner_archive = getUtility(IArchiveSet).getByDistroAndName(
270- ... ubuntutest, 'partner')
271-
272- >>> partner_config = getPubConfig(partner_archive)
273-
274- >>> dump_config(partner_config)
275- distroroot: /var/tmp/archive
276- archiveroot: /var/tmp/archive/ubuntutest-partner
277- poolroot: /var/tmp/archive/ubuntutest-partner/pool
278- distsroot: /var/tmp/archive/ubuntutest-partner/dists
279- overrideroot: None
280- cacheroot: None
281- miscroot: None
282- germinateroot: None
283- temproot: /var/tmp/archive/ubuntutest-temp
284-
285-
286-DEBUG
287------
288-
289-The publisher configuration for DEBUG archives points to directories
290-besides PRIMARY repository ones, but the distribution part is
291-modified to be clearly different than the PRIMARY one.
292-
293- >>> from lp.soyuz.enums import ArchivePurpose
294- >>> debug_archive = getUtility(IArchiveSet).new(
295- ... purpose=ArchivePurpose.DEBUG, owner=ubuntutest.owner,
296- ... distribution=ubuntutest)
297-
298- >>> debug_config = getPubConfig(debug_archive)
299-
300- >>> dump_config(debug_config)
301- distroroot: /var/tmp/archive
302- archiveroot: /var/tmp/archive/ubuntutest-debug
303- poolroot: /var/tmp/archive/ubuntutest-debug/pool
304- distsroot: /var/tmp/archive/ubuntutest-debug/dists
305- overrideroot: None
306- cacheroot: None
307- miscroot: None
308- germinateroot: None
309- temproot: /var/tmp/archive/ubuntutest-temp
310-
311-
312-COPY
313-----
314-
315-In the case of copy archives (used for rebuild testing) the archiveroot
316-is of the form distroroot/distroname-archivename/distroname
317-
318- >>> copy_archive = getUtility(IArchiveSet).new(
319- ... purpose=ArchivePurpose.COPY, owner=ubuntutest.owner,
320- ... distribution=ubuntutest, name="rebuildtest99")
321-
322- >>> copy_config = getPubConfig(copy_archive)
323-
324- >>> dump_config(copy_config)
325- distroroot: /var/tmp/archive
326- archiveroot: /var/tmp/archive/ubuntutest-rebuildtest99/ubuntutest
327- poolroot: /var/tmp/archive/ubuntutest-rebuildtest99/ubuntutest/pool
328- distsroot: /var/tmp/archive/ubuntutest-rebuildtest99/ubuntutest/dists
329- overrideroot:
330- /var/tmp/archive/ubuntutest-rebuildtest99/ubuntutest-overrides
331- cacheroot: /var/tmp/archive/ubuntutest-rebuildtest99/ubuntutest-cache
332- miscroot: /var/tmp/archive/ubuntutest-rebuildtest99/ubuntutest-misc
333- germinateroot:
334- /var/tmp/archive/ubuntutest-rebuildtest99/ubuntutest-germinate
335- temproot: /var/tmp/archive/ubuntutest-rebuildtest99/ubuntutest-temp
336
337=== modified file 'lib/lp/archivepublisher/tests/test_config.py'
338--- lib/lp/archivepublisher/tests/test_config.py 2012-01-01 02:58:52 +0000
339+++ lib/lp/archivepublisher/tests/test_config.py 2012-05-21 19:07:38 +0000
340@@ -1,11 +1,20 @@
341-# Copyright 2011 Canonical Ltd. This software is licensed under the
342+# Copyright 2011-2012 Canonical Ltd. This software is licensed under the
343 # GNU Affero General Public License version 3 (see the file LICENSE).
344
345-"""Test publisher configs handling."""
346+"""Test publisher configs handling.
347+
348+Publisher configuration provides archive-dependent filesystem paths.
349+"""
350
351 __metaclass__ = type
352
353+from zope.component import getUtility
354+
355 from lp.archivepublisher.config import getPubConfig
356+from lp.registry.interfaces.distribution import IDistributionSet
357+from lp.services.config import config
358+from lp.soyuz.enums import ArchivePurpose
359+from lp.soyuz.interfaces.archive import IArchiveSet
360 from lp.testing import TestCaseWithFactory
361 from lp.testing.layers import ZopelessDatabaseLayer
362
363@@ -14,6 +23,152 @@
364
365 layer = ZopelessDatabaseLayer
366
367+ def setUp(self):
368+ super(TestGetPubConfig, self).setUp()
369+ self.ubuntutest = getUtility(IDistributionSet)['ubuntutest']
370+ self.root = "/var/tmp/archive"
371+
372 def test_getPubConfig_returns_None_if_no_publisherconfig_found(self):
373 archive = self.factory.makeDistribution(no_pubconf=True).main_archive
374 self.assertEqual(None, getPubConfig(archive))
375+
376+ def test_primary_config(self):
377+ # Primary archive configuration is correct.
378+ primary_config = getPubConfig(self.ubuntutest.main_archive)
379+ self.assertEqual(self.root, primary_config.distroroot)
380+ archiveroot = self.root + "/ubuntutest"
381+ self.assertEqual(archiveroot, primary_config.archiveroot)
382+ self.assertEqual(archiveroot + "/pool", primary_config.poolroot)
383+ self.assertEqual(archiveroot + "/dists", primary_config.distsroot)
384+ self.assertEqual(
385+ archiveroot + "-overrides", primary_config.overrideroot)
386+ self.assertEqual(archiveroot + "-cache", primary_config.cacheroot)
387+ self.assertEqual(archiveroot + "-misc", primary_config.miscroot)
388+ self.assertEqual(
389+ archiveroot + "-germinate", primary_config.germinateroot)
390+ self.assertEqual(
391+ self.root + "/ubuntutest-temp", primary_config.temproot)
392+
393+ def test_partner_config(self):
394+ # Partner archive configuration is correct.
395+ # The publisher config for PARTNER contains only 'partner' in its
396+ # components. This prevents non-partner being published in the
397+ # partner archive.
398+ partner_archive = getUtility(IArchiveSet).getByDistroAndName(
399+ self.ubuntutest, "partner")
400+ partner_config = getPubConfig(partner_archive)
401+ self.root = "/var/tmp/archive"
402+ self.assertEqual(self.root, partner_config.distroroot)
403+ archiveroot = self.root + "/ubuntutest-partner"
404+ self.assertEqual(archiveroot, partner_config.archiveroot)
405+ self.assertEqual(archiveroot + "/pool", partner_config.poolroot)
406+ self.assertEqual(archiveroot + "/dists", partner_config.distsroot)
407+ self.assertIsNone(partner_config.overrideroot)
408+ self.assertIsNone(partner_config.cacheroot)
409+ self.assertIsNone(partner_config.miscroot)
410+ self.assertIsNone(partner_config.germinateroot)
411+ self.assertEqual(
412+ self.root + "/ubuntutest-temp", partner_config.temproot)
413+
414+ def test_debug_config(self):
415+ # The publisher configuration for DEBUG archives points to
416+ # directories beside PRIMARY repository ones, but the distribution
417+ # part is modified to be clearly different than the PRIMARY one.
418+ debug_archive = getUtility(IArchiveSet).new(
419+ purpose=ArchivePurpose.DEBUG, owner=self.ubuntutest.owner,
420+ distribution=self.ubuntutest)
421+ debug_config = getPubConfig(debug_archive)
422+ self.assertEqual(self.root, debug_config.distroroot)
423+ archiveroot = self.root + "/ubuntutest-debug"
424+ self.assertEqual(archiveroot, debug_config.archiveroot)
425+ self.assertEqual(archiveroot + "/pool", debug_config.poolroot)
426+ self.assertEqual(archiveroot + "/dists", debug_config.distsroot)
427+ self.assertIsNone(debug_config.overrideroot)
428+ self.assertIsNone(debug_config.cacheroot)
429+ self.assertIsNone(debug_config.miscroot)
430+ self.assertIsNone(debug_config.germinateroot)
431+ self.assertEqual(self.root + "/ubuntutest-temp", debug_config.temproot)
432+
433+ def test_copy_config(self):
434+ # In the case of copy archives (used for rebuild testing) the
435+ # archiveroot is of the form
436+ # DISTROROOT/DISTRONAME-ARCHIVENAME/DISTRONAME.
437+ copy_archive = getUtility(IArchiveSet).new(
438+ purpose=ArchivePurpose.COPY, owner=self.ubuntutest.owner,
439+ distribution=self.ubuntutest, name="rebuildtest99")
440+ copy_config = getPubConfig(copy_archive)
441+ self.assertEqual(self.root, copy_config.distroroot)
442+ archiveroot = self.root + "/ubuntutest-rebuildtest99/ubuntutest"
443+ self.assertEqual(archiveroot, copy_config.archiveroot)
444+ self.assertEqual(archiveroot + "/pool", copy_config.poolroot)
445+ self.assertEqual(archiveroot + "/dists", copy_config.distsroot)
446+ self.assertEqual(
447+ archiveroot + "-overrides", copy_config.overrideroot)
448+ self.assertEqual(archiveroot + "-cache", copy_config.cacheroot)
449+ self.assertEqual(archiveroot + "-misc", copy_config.miscroot)
450+ self.assertEqual(
451+ archiveroot + "-germinate", copy_config.germinateroot)
452+ self.assertEqual(archiveroot + "-temp", copy_config.temproot)
453+
454+
455+class TestGetPubConfigPPA(TestCaseWithFactory):
456+
457+ layer = ZopelessDatabaseLayer
458+
459+ def setUp(self):
460+ super(TestGetPubConfigPPA, self).setUp()
461+ self.ubuntutest = getUtility(IDistributionSet)['ubuntutest']
462+ self.ppa = self.factory.makeArchive(
463+ distribution=self.ubuntutest, purpose=ArchivePurpose.PPA)
464+ self.ppa_config = getPubConfig(self.ppa)
465+
466+ def test_ppa_root_matches_config(self):
467+ # The base publication location is set by Launchpad configuration.
468+ self.assertEqual(
469+ config.personalpackagearchive.root, self.ppa_config.distroroot)
470+
471+ def test_ppa_config(self):
472+ # PPA configuration matches the PPA repository topology:
473+ # <PPA_BASE_DIR>/<PERSONNAME>/<DISTRIBUTION>
474+ # Some paths are not used in the PPA workflow, so they are set to
475+ # None in order that they won't get created.
476+ self.assertEqual("/var/tmp/ppa.test/", self.ppa_config.distroroot)
477+ archiveroot = "%s%s/%s/ubuntutest" % (
478+ self.ppa_config.distroroot, self.ppa.owner.name, self.ppa.name)
479+ self.assertEqual(archiveroot, self.ppa_config.archiveroot)
480+ self.assertEqual(archiveroot + "/pool", self.ppa_config.poolroot)
481+ self.assertEqual(archiveroot + "/dists", self.ppa_config.distsroot)
482+ self.assertIsNone(self.ppa_config.overrideroot)
483+ self.assertIsNone(self.ppa_config.cacheroot)
484+ self.assertIsNone(self.ppa_config.miscroot)
485+ self.assertIsNone(self.ppa_config.germinateroot)
486+ self.assertEqual(
487+ "/var/tmp/archive/ubuntutest-temp", self.ppa_config.temproot)
488+
489+ def test_private_ppa_separate_root(self):
490+ # Private PPAs are published to a different location.
491+ self.assertNotEqual(
492+ config.personalpackagearchive.private_root,
493+ config.personalpackagearchive.root)
494+
495+ def test_private_ppa_config(self):
496+ # Private PPA configuration uses the separate base location.
497+ p3a = self.factory.makeArchive(
498+ owner=self.ppa.owner, name="myprivateppa",
499+ distribution=self.ubuntutest, purpose=ArchivePurpose.PPA)
500+ p3a.private = True
501+ p3a.buildd_secret = "secret"
502+ p3a_config = getPubConfig(p3a)
503+ self.assertEqual(
504+ config.personalpackagearchive.private_root, p3a_config.distroroot)
505+ archiveroot = "%s/%s/%s/ubuntutest" % (
506+ p3a_config.distroroot, p3a.owner.name, p3a.name)
507+ self.assertEqual(archiveroot, p3a_config.archiveroot)
508+ self.assertEqual(archiveroot + "/pool", p3a_config.poolroot)
509+ self.assertEqual(archiveroot + "/dists", p3a_config.distsroot)
510+ self.assertIsNone(p3a_config.overrideroot)
511+ self.assertIsNone(p3a_config.cacheroot)
512+ self.assertIsNone(p3a_config.miscroot)
513+ self.assertIsNone(p3a_config.germinateroot)
514+ self.assertEqual(
515+ "/var/tmp/archive/ubuntutest-temp", p3a_config.temproot)
516
517=== modified file 'lib/lp/archivepublisher/tests/test_generate_extra_overrides.py'
518--- lib/lp/archivepublisher/tests/test_generate_extra_overrides.py 2012-01-03 17:08:40 +0000
519+++ lib/lp/archivepublisher/tests/test_generate_extra_overrides.py 2012-05-21 19:07:38 +0000
520@@ -1,10 +1,11 @@
521-# Copyright 2011 Canonical Ltd. This software is licensed under the
522+# Copyright 2011-2012 Canonical Ltd. This software is licensed under the
523 # GNU Affero General Public License version 3 (see the file LICENSE).
524
525 """Test for the `generate-extra-overrides` script."""
526
527 __metaclass__ = type
528
529+from functools import partial
530 import logging
531 from optparse import OptionValueError
532 import os
533@@ -46,6 +47,12 @@
534 return handle.read()
535
536
537+def touch(path):
538+ """Create an empty file at path."""
539+ with open_for_writing(path, "a"):
540+ pass
541+
542+
543 class TestAtomicFile(TestCaseWithFactory):
544 """Tests for the AtomicFile helper class."""
545
546@@ -113,6 +120,24 @@
547 script.distribution = distribution
548 return script
549
550+ def setUpDistroAndScript(self, series_statuses=["DEVELOPMENT"], **kwargs):
551+ """Helper wrapping distro and script setup."""
552+ self.distro = self.makeDistro()
553+ self.distroseries = [
554+ self.factory.makeDistroSeries(
555+ distribution=self.distro, status=SeriesStatus.items[status])
556+ for status in series_statuses]
557+ self.script = self.makeScript(self.distro, **kwargs)
558+
559+ def setUpComponent(self, component=None):
560+ """Create a component and attach it to all distroseries."""
561+ if component is None:
562+ component = self.factory.makeComponent()
563+ for distroseries in self.distroseries:
564+ self.factory.makeComponentSelection(
565+ distroseries=distroseries, component=component)
566+ return component
567+
568 def makePackage(self, component, dases, **kwargs):
569 """Create a published source and binary package for testing."""
570 package = self.factory.makeDistributionSourcePackage(
571@@ -175,11 +200,8 @@
572 self.seeddir, "%s.%s" % (flavour, series_name), seed_name)
573
574 def makeSeedStructure(self, flavour, series_name, seed_names,
575- seed_inherit=None):
576+ seed_inherit={}):
577 """Create a simple seed structure file."""
578- if seed_inherit is None:
579- seed_inherit = {}
580-
581 structure_path = self.composeSeedPath(
582 flavour, series_name, "STRUCTURE")
583 with open_for_writing(structure_path, "w") as structure:
584@@ -246,130 +268,87 @@
585 def test_looks_up_distro(self):
586 # The script looks up and keeps the distribution named on the
587 # command line.
588- distro = self.makeDistro()
589- self.factory.makeDistroSeries(distro)
590- script = self.makeScript(distro)
591- self.assertEqual(distro, script.distribution)
592+ self.setUpDistroAndScript()
593+ self.assertEqual(self.distro, self.script.distribution)
594
595 def test_prefers_development_distro_series(self):
596 # The script prefers a DEVELOPMENT series for the named
597 # distribution over CURRENT and SUPPORTED series.
598- distro = self.makeDistro()
599- self.factory.makeDistroSeries(distro, status=SeriesStatus.SUPPORTED)
600- self.factory.makeDistroSeries(distro, status=SeriesStatus.CURRENT)
601- development_distroseries = self.factory.makeDistroSeries(
602- distro, status=SeriesStatus.DEVELOPMENT)
603- script = self.makeScript(distro)
604- observed_series = [series.name for series in script.series]
605- self.assertEqual([development_distroseries.name], observed_series)
606+ self.setUpDistroAndScript(["SUPPORTED", "CURRENT", "DEVELOPMENT"])
607+ self.assertEqual([self.distroseries[2]], self.script.series)
608
609 def test_permits_frozen_distro_series(self):
610 # If there is no DEVELOPMENT series, a FROZEN one will do.
611- distro = self.makeDistro()
612- self.factory.makeDistroSeries(distro, status=SeriesStatus.SUPPORTED)
613- self.factory.makeDistroSeries(distro, status=SeriesStatus.CURRENT)
614- frozen_distroseries = self.factory.makeDistroSeries(
615- distro, status=SeriesStatus.FROZEN)
616- script = self.makeScript(distro)
617- observed_series = [series.name for series in script.series]
618- self.assertEqual([frozen_distroseries.name], observed_series)
619+ self.setUpDistroAndScript(["SUPPORTED", "CURRENT", "FROZEN"])
620+ self.assertEqual([self.distroseries[2]], self.script.series)
621
622 def test_requires_development_frozen_distro_series(self):
623 # If there is no DEVELOPMENT or FROZEN series, the script fails.
624- distro = self.makeDistro()
625- self.factory.makeDistroSeries(distro, status=SeriesStatus.SUPPORTED)
626- self.factory.makeDistroSeries(distro, status=SeriesStatus.CURRENT)
627- script = self.makeScript(distro, run_setup=False)
628- self.assertRaises(LaunchpadScriptFailure, script.processOptions)
629+ self.setUpDistroAndScript(["SUPPORTED", "CURRENT"], run_setup=False)
630+ self.assertRaises(LaunchpadScriptFailure, self.script.processOptions)
631
632 def test_multiple_development_frozen_distro_series(self):
633 # If there are multiple DEVELOPMENT or FROZEN series, they are all
634 # used.
635- distro = self.makeDistro()
636- development_distroseries_one = self.factory.makeDistroSeries(
637- distro, status=SeriesStatus.DEVELOPMENT)
638- development_distroseries_two = self.factory.makeDistroSeries(
639- distro, status=SeriesStatus.DEVELOPMENT)
640- frozen_distroseries_one = self.factory.makeDistroSeries(
641- distro, status=SeriesStatus.FROZEN)
642- frozen_distroseries_two = self.factory.makeDistroSeries(
643- distro, status=SeriesStatus.FROZEN)
644- script = self.makeScript(distro)
645- expected_series = [
646- development_distroseries_one.name,
647- development_distroseries_two.name,
648- frozen_distroseries_one.name,
649- frozen_distroseries_two.name,
650- ]
651- observed_series = [series.name for series in script.series]
652- self.assertContentEqual(expected_series, observed_series)
653+ self.setUpDistroAndScript(
654+ ["DEVELOPMENT", "DEVELOPMENT", "FROZEN", "FROZEN"])
655+ self.assertContentEqual(self.distroseries, self.script.series)
656
657 def test_components_exclude_partner(self):
658 # If a 'partner' component exists, it is excluded.
659- distro = self.makeDistro()
660- distroseries = self.factory.makeDistroSeries(distro)
661- self.factory.makeComponentSelection(
662- distroseries=distroseries, component="main")
663- self.factory.makeComponentSelection(
664- distroseries=distroseries, component="partner")
665- script = self.makeScript(distro)
666- self.assertEqual(1, len(script.series))
667- self.assertEqual(["main"], script.getComponents(script.series[0]))
668+ self.setUpDistroAndScript()
669+ self.setUpComponent(component="main")
670+ self.setUpComponent(component="partner")
671+ self.assertEqual(1, len(self.script.series))
672+ self.assertEqual(
673+ ["main"], self.script.getComponents(self.script.series[0]))
674
675 def test_compose_output_path_in_germinateroot(self):
676 # Output files are written to the correct locations under
677 # germinateroot.
678- distro = self.makeDistro()
679- distroseries = self.factory.makeDistroSeries(distro)
680- script = self.makeScript(distro)
681+ self.setUpDistroAndScript()
682 flavour = self.factory.getUniqueString()
683 arch = self.factory.getUniqueString()
684 base = self.factory.getUniqueString()
685- output = script.composeOutputPath(
686- flavour, distroseries.name, arch, base)
687+ output = self.script.composeOutputPath(
688+ flavour, self.distroseries[0].name, arch, base)
689 self.assertEqual(
690 "%s/%s_%s_%s_%s" % (
691- script.config.germinateroot, base, flavour, distroseries.name,
692- arch),
693+ self.script.config.germinateroot, base, flavour,
694+ self.distroseries[0].name, arch),
695 output)
696
697 def test_make_seed_structures_missing_seeds(self):
698 # makeSeedStructures ignores missing seeds.
699- distro = self.makeDistro()
700- distroseries = self.factory.makeDistroSeries(distribution=distro)
701- series_name = distroseries.name
702- script = self.makeScript(distro)
703+ self.setUpDistroAndScript()
704+ series_name = self.distroseries[0].name
705 flavour = self.factory.getUniqueString()
706
707- structures = script.makeSeedStructures(
708+ structures = self.script.makeSeedStructures(
709 series_name, [flavour], seed_bases=["file://%s" % self.seeddir])
710 self.assertEqual({}, structures)
711
712 def test_make_seed_structures_empty_seed_structure(self):
713 # makeSeedStructures ignores an empty seed structure.
714- distro = self.makeDistro()
715- distroseries = self.factory.makeDistroSeries(distribution=distro)
716- series_name = distroseries.name
717- script = self.makeScript(distro)
718+ self.setUpDistroAndScript()
719+ series_name = self.distroseries[0].name
720 flavour = self.factory.getUniqueString()
721 self.makeSeedStructure(flavour, series_name, [])
722
723- structures = script.makeSeedStructures(
724+ structures = self.script.makeSeedStructures(
725 series_name, [flavour], seed_bases=["file://%s" % self.seeddir])
726 self.assertEqual({}, structures)
727
728 def test_make_seed_structures_valid_seeds(self):
729 # makeSeedStructures reads valid seeds successfully.
730- distro = self.makeDistro()
731- distroseries = self.factory.makeDistroSeries(distribution=distro)
732- series_name = distroseries.name
733- script = self.makeScript(distro)
734+ self.setUpDistroAndScript()
735+ series_name = self.distroseries[0].name
736 flavour = self.factory.getUniqueString()
737 seed = self.factory.getUniqueString()
738 self.makeSeedStructure(flavour, series_name, [seed])
739 self.makeSeed(flavour, series_name, seed, [])
740
741- structures = script.makeSeedStructures(
742+ structures = self.script.makeSeedStructures(
743 series_name, [flavour], seed_bases=["file://%s" % self.seeddir])
744 self.assertIn(flavour, structures)
745
746@@ -390,18 +369,15 @@
747 def test_germinate_output(self):
748 # A single call to germinateArch produces output for all flavours on
749 # one architecture.
750- distro = self.makeDistro()
751- distroseries = self.factory.makeDistroSeries(distribution=distro)
752- series_name = distroseries.name
753- component = self.factory.makeComponent()
754- self.factory.makeComponentSelection(
755- distroseries=distroseries, component=component)
756- das = self.factory.makeDistroArchSeries(distroseries=distroseries)
757+ self.setUpDistroAndScript()
758+ series_name = self.distroseries[0].name
759+ component = self.setUpComponent()
760+ das = self.factory.makeDistroArchSeries(
761+ distroseries=self.distroseries[0])
762 arch = das.architecturetag
763 one = self.makePackage(component, [das])
764 two = self.makePackage(component, [das])
765- script = self.makeScript(distro)
766- self.makeIndexFiles(script, distroseries)
767+ self.makeIndexFiles(self.script, self.distroseries[0])
768
769 flavour_one = self.factory.getUniqueString()
770 flavour_two = self.factory.getUniqueString()
771@@ -412,51 +388,49 @@
772 self.makeSeed(flavour_two, series_name, seed, [two.name])
773
774 overrides = self.fetchGerminatedOverrides(
775- script, distroseries, arch, [flavour_one, flavour_two])
776+ self.script, self.distroseries[0], arch,
777+ [flavour_one, flavour_two])
778 self.assertEqual([], overrides)
779
780 seed_dir_one = os.path.join(
781 self.seeddir, "%s.%s" % (flavour_one, series_name))
782 self.assertFilesEqual(
783 os.path.join(seed_dir_one, "STRUCTURE"),
784- script.composeOutputPath(
785+ self.script.composeOutputPath(
786 flavour_one, series_name, arch, "structure"))
787- self.assertTrue(file_exists(script.composeOutputPath(
788+ self.assertTrue(file_exists(self.script.composeOutputPath(
789 flavour_one, series_name, arch, "all")))
790- self.assertTrue(file_exists(script.composeOutputPath(
791+ self.assertTrue(file_exists(self.script.composeOutputPath(
792 flavour_one, series_name, arch, "all.sources")))
793- self.assertTrue(file_exists(script.composeOutputPath(
794+ self.assertTrue(file_exists(self.script.composeOutputPath(
795 flavour_one, series_name, arch, seed)))
796
797 seed_dir_two = os.path.join(
798 self.seeddir, "%s.%s" % (flavour_two, series_name))
799 self.assertFilesEqual(
800 os.path.join(seed_dir_two, "STRUCTURE"),
801- script.composeOutputPath(
802+ self.script.composeOutputPath(
803 flavour_two, series_name, arch, "structure"))
804- self.assertTrue(file_exists(script.composeOutputPath(
805+ self.assertTrue(file_exists(self.script.composeOutputPath(
806 flavour_two, series_name, arch, "all")))
807- self.assertTrue(file_exists(script.composeOutputPath(
808+ self.assertTrue(file_exists(self.script.composeOutputPath(
809 flavour_two, series_name, arch, "all.sources")))
810- self.assertTrue(file_exists(script.composeOutputPath(
811+ self.assertTrue(file_exists(self.script.composeOutputPath(
812 flavour_two, series_name, arch, seed)))
813
814 def test_germinate_output_task(self):
815 # germinateArch produces Task extra overrides.
816- distro = self.makeDistro()
817- distroseries = self.factory.makeDistroSeries(distribution=distro)
818- series_name = distroseries.name
819- component = self.factory.makeComponent()
820- self.factory.makeComponentSelection(
821- distroseries=distroseries, component=component)
822- das = self.factory.makeDistroArchSeries(distroseries=distroseries)
823+ self.setUpDistroAndScript()
824+ series_name = self.distroseries[0].name
825+ component = self.setUpComponent()
826+ das = self.factory.makeDistroArchSeries(
827+ distroseries=self.distroseries[0])
828 arch = das.architecturetag
829 one = self.makePackage(component, [das])
830 two = self.makePackage(component, [das], depends=one.name)
831 three = self.makePackage(component, [das])
832 self.makePackage(component, [das])
833- script = self.makeScript(distro)
834- self.makeIndexFiles(script, distroseries)
835+ self.makeIndexFiles(self.script, self.distroseries[0])
836
837 flavour = self.factory.getUniqueString()
838 seed_one = self.factory.getUniqueString()
839@@ -470,7 +444,7 @@
840 headers=["Task-Description: two"])
841
842 overrides = self.fetchGerminatedOverrides(
843- script, distroseries, arch, [flavour])
844+ self.script, self.distroseries[0], arch, [flavour])
845 expected_overrides = [
846 "%s/%s Task %s" % (one.name, arch, seed_one),
847 "%s/%s Task %s" % (two.name, arch, seed_one),
848@@ -560,17 +534,14 @@
849
850 def test_germinate_output_build_essential(self):
851 # germinateArch produces Build-Essential extra overrides.
852- distro = self.makeDistro()
853- distroseries = self.factory.makeDistroSeries(distribution=distro)
854- series_name = distroseries.name
855- component = self.factory.makeComponent()
856- self.factory.makeComponentSelection(
857- distroseries=distroseries, component=component)
858- das = self.factory.makeDistroArchSeries(distroseries=distroseries)
859+ self.setUpDistroAndScript()
860+ series_name = self.distroseries[0].name
861+ component = self.setUpComponent()
862+ das = self.factory.makeDistroArchSeries(
863+ distroseries=self.distroseries[0])
864 arch = das.architecturetag
865 package = self.makePackage(component, [das])
866- script = self.makeScript(distro)
867- self.makeIndexFiles(script, distroseries)
868+ self.makeIndexFiles(self.script, self.distroseries[0])
869
870 flavour = self.factory.getUniqueString()
871 seed = "build-essential"
872@@ -578,55 +549,89 @@
873 self.makeSeed(flavour, series_name, seed, [package.name])
874
875 overrides = self.fetchGerminatedOverrides(
876- script, distroseries, arch, [flavour])
877+ self.script, self.distroseries[0], arch, [flavour])
878 self.assertContentEqual(
879 ["%s/%s Build-Essential yes" % (package.name, arch)], overrides)
880
881+ def test_removes_only_stale_files(self):
882+ # removeStaleOutputs removes only stale germinate output files.
883+ self.setUpDistroAndScript()
884+ series_name = self.distroseries[0].name
885+ seed_old_file = "old_flavour_%s_i386" % series_name
886+ seed_new_file = "new_flavour_%s_i386" % series_name
887+ other_file = "other-file"
888+ output = partial(os.path.join, self.script.config.germinateroot)
889+ for base in (seed_old_file, seed_new_file, other_file):
890+ touch(output(base))
891+ self.script.removeStaleOutputs(series_name, set([seed_new_file]))
892+ self.assertFalse(os.path.exists(output(seed_old_file)))
893+ self.assertTrue(os.path.exists(output(seed_new_file)))
894+ self.assertTrue(os.path.exists(output(other_file)))
895+
896 def test_process_missing_seeds(self):
897 # The script ignores series with no seed structures.
898- distro = self.makeDistro()
899- distroseries_one = self.factory.makeDistroSeries(distribution=distro)
900- distroseries_two = self.factory.makeDistroSeries(distribution=distro)
901- component = self.factory.makeComponent()
902- self.factory.makeComponentSelection(
903- distroseries=distroseries_one, component=component)
904- self.factory.makeComponentSelection(
905- distroseries=distroseries_two, component=component)
906- self.factory.makeDistroArchSeries(distroseries=distroseries_one)
907- self.factory.makeDistroArchSeries(distroseries=distroseries_two)
908 flavour = self.factory.getUniqueString()
909- script = self.makeScript(distro, extra_args=[flavour])
910- self.makeIndexFiles(script, distroseries_two)
911+ self.setUpDistroAndScript(
912+ ["DEVELOPMENT", "DEVELOPMENT"], extra_args=[flavour])
913+ self.setUpComponent()
914+ self.factory.makeDistroArchSeries(distroseries=self.distroseries[0])
915+ self.factory.makeDistroArchSeries(distroseries=self.distroseries[1])
916+ self.makeIndexFiles(self.script, self.distroseries[1])
917 seed = self.factory.getUniqueString()
918- self.makeSeedStructure(flavour, distroseries_two.name, [seed])
919- self.makeSeed(flavour, distroseries_two.name, seed, [])
920+ self.makeSeedStructure(flavour, self.distroseries[1].name, [seed])
921+ self.makeSeed(flavour, self.distroseries[1].name, seed, [])
922
923- script.process(seed_bases=["file://%s" % self.seeddir])
924+ self.script.process(seed_bases=["file://%s" % self.seeddir])
925 self.assertFalse(os.path.exists(os.path.join(
926- script.config.miscroot,
927- "more-extra.override.%s.main" % distroseries_one.name)))
928+ self.script.config.miscroot,
929+ "more-extra.override.%s.main" % self.distroseries[0].name)))
930 self.assertTrue(os.path.exists(os.path.join(
931- script.config.miscroot,
932- "more-extra.override.%s.main" % distroseries_two.name)))
933+ self.script.config.miscroot,
934+ "more-extra.override.%s.main" % self.distroseries[1].name)))
935+
936+ def test_process_removes_only_stale_files(self):
937+ # The script removes only stale germinate output files.
938+ flavour = self.factory.getUniqueString()
939+ self.setUpDistroAndScript(extra_args=[flavour])
940+ series_name = self.distroseries[0].name
941+ self.setUpComponent()
942+ das = self.factory.makeDistroArchSeries(
943+ distroseries=self.distroseries[0])
944+ arch = das.architecturetag
945+ self.makeIndexFiles(self.script, self.distroseries[0])
946+
947+ seed_old = self.factory.getUniqueString()
948+ seed_new = self.factory.getUniqueString()
949+ self.makeSeedStructure(flavour, series_name, [seed_old])
950+ self.makeSeed(flavour, series_name, seed_old, [])
951+ self.script.process(seed_bases=["file://%s" % self.seeddir])
952+ output = partial(
953+ self.script.composeOutputPath, flavour, series_name, arch)
954+ self.assertTrue(os.path.exists(output(seed_old)))
955+ self.makeSeedStructure(flavour, series_name, [seed_new])
956+ self.makeSeed(flavour, series_name, seed_new, [])
957+ self.script.process(seed_bases=["file://%s" % self.seeddir])
958+ self.assertTrue(os.path.exists(os.path.join(self.script.log_file)))
959+ self.assertTrue(os.path.exists(output("structure")))
960+ self.assertTrue(os.path.exists(output("all")))
961+ self.assertTrue(os.path.exists(output("all.sources")))
962+ self.assertTrue(os.path.exists(output(seed_new)))
963+ self.assertFalse(os.path.exists(output(seed_old)))
964
965 def test_main(self):
966 # If run end-to-end, the script generates override files containing
967 # output for all architectures, and sends germinate's log output to
968 # a file.
969- distro = self.makeDistro()
970- distroseries = self.factory.makeDistroSeries(distribution=distro)
971- series_name = distroseries.name
972- component = self.factory.makeComponent()
973- self.factory.makeComponentSelection(
974- distroseries=distroseries, component=component)
975- das_one = self.factory.makeDistroArchSeries(distroseries=distroseries)
976- arch_one = das_one.architecturetag
977- das_two = self.factory.makeDistroArchSeries(distroseries=distroseries)
978- arch_two = das_two.architecturetag
979+ flavour = self.factory.getUniqueString()
980+ self.setUpDistroAndScript(extra_args=[flavour])
981+ series_name = self.distroseries[0].name
982+ component = self.setUpComponent()
983+ das_one = self.factory.makeDistroArchSeries(
984+ distroseries=self.distroseries[0])
985+ das_two = self.factory.makeDistroArchSeries(
986+ distroseries=self.distroseries[0])
987 package = self.makePackage(component, [das_one, das_two])
988- flavour = self.factory.getUniqueString()
989- script = self.makeScript(distro, extra_args=[flavour])
990- self.makeIndexFiles(script, distroseries)
991+ self.makeIndexFiles(self.script, self.distroseries[0])
992
993 seed = self.factory.getUniqueString()
994 self.makeSeedStructure(flavour, series_name, [seed])
995@@ -634,19 +639,19 @@
996 flavour, series_name, seed, [package.name],
997 headers=["Task-Description: task"])
998
999- script.process(seed_bases=["file://%s" % self.seeddir])
1000+ self.script.process(seed_bases=["file://%s" % self.seeddir])
1001 override_path = os.path.join(
1002- script.config.miscroot,
1003+ self.script.config.miscroot,
1004 "more-extra.override.%s.main" % series_name)
1005 expected_overrides = [
1006- "%s/%s Task %s" % (package.name, arch_one, seed),
1007- "%s/%s Task %s" % (package.name, arch_two, seed),
1008+ "%s/%s Task %s" % (package.name, das_one.architecturetag, seed),
1009+ "%s/%s Task %s" % (package.name, das_two.architecturetag, seed),
1010 ]
1011 self.assertContentEqual(
1012 expected_overrides, file_contents(override_path).splitlines())
1013
1014 log_file = os.path.join(
1015- script.config.germinateroot, "germinate.output")
1016+ self.script.config.germinateroot, "germinate.output")
1017 self.assertIn("Downloading file://", file_contents(log_file))
1018
1019 def test_run_script(self):