Merge lp:~james-w/linaro-image-tools/hwpack-builder into lp:linaro-image-tools/11.11

Proposed by James Westby
Status: Merged
Merged at revision: 70
Proposed branch: lp:~james-w/linaro-image-tools/hwpack-builder
Merge into: lp:linaro-image-tools/11.11
Prerequisite: lp:~james-w/linaro-image-tools/package-fetcher-config-manager
Diff against target: 375 lines (+275/-45)
5 files modified
hwpack/builder.py (+42/-0)
hwpack/testing.py (+157/-0)
hwpack/tests/__init__.py (+1/-0)
hwpack/tests/test_builder.py (+74/-0)
hwpack/tests/test_hardwarepack.py (+1/-45)
To merge this branch: bzr merge lp:~james-w/linaro-image-tools/hwpack-builder
Reviewer Review Type Date Requested Status
Zygmunt Krynicki (community) Approve
Review via email: mp+35129@code.launchpad.net

Description of the change

Hi,

A larger branch this time.

This adds the class that orchestrates the different parts in order
to produce the hardware packs. It's fairly simple itself, and the
majority of the additions are test code and test helper code.

Thanks,

James

To post a comment you must log in.
Revision history for this message
Zygmunt Krynicki (zyga) wrote :

I just read the dif, didn't run the code yet. Looks very solid!

I like how matchers work, need to try them out

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'hwpack/builder.py'
2--- hwpack/builder.py 1970-01-01 00:00:00 +0000
3+++ hwpack/builder.py 2010-09-10 16:11:07 +0000
4@@ -0,0 +1,42 @@
5+import errno
6+
7+from hwpack.config import Config
8+from hwpack.hardwarepack import HardwarePack, Metadata
9+from hwpack.packages import PackageFetcher
10+
11+
12+class ConfigFileMissing(Exception):
13+
14+ def __init__(self, filename):
15+ self.filename = filename
16+ super(ConfigFileMissing, self).__init__(
17+ "No such config file: '%s'" % self.filename)
18+
19+
20+class HardwarePackBuilder(object):
21+
22+ def __init__(self, config_path, version):
23+ try:
24+ with open(config_path) as fp:
25+ self.config = Config(fp)
26+ except IOError, e:
27+ if e.errno == errno.ENOENT:
28+ raise ConfigFileMissing(config_path)
29+ raise
30+ self.config.validate()
31+ self.version = version
32+
33+ def build(self):
34+ for architecture in self.config.architectures:
35+ metadata = Metadata.from_config(
36+ self.config, self.version, architecture)
37+ hwpack = HardwarePack(metadata)
38+ sources = self.config.sources
39+ hwpack.add_apt_sources(sources)
40+ fetcher = PackageFetcher(
41+ sources.values(), architecture=architecture)
42+ with fetcher:
43+ packages = fetcher.fetch_packages(self.config.packages)
44+ hwpack.add_packages(packages)
45+ with open(hwpack.filename(), 'w') as f:
46+ hwpack.to_file(f)
47
48=== modified file 'hwpack/testing.py'
49--- hwpack/testing.py 2010-09-02 15:30:36 +0000
50+++ hwpack/testing.py 2010-09-10 16:11:07 +0000
51@@ -7,8 +7,10 @@
52 import tarfile
53
54 from testtools import TestCase
55+from testtools.matchers import Matcher, Mismatch
56
57 from hwpack.better_tarfile import writeable_tarfile
58+from hwpack.tarfile_matchers import TarfileHasFile
59 from hwpack.packages import get_packages_file, FetchedPackage
60
61
62@@ -136,3 +138,158 @@
63 self.addCleanup(fixture.tearDown)
64 fixture.setUp()
65 return fixture
66+
67+
68+class ConfigFileFixture(object):
69+
70+ def __init__(self, contents):
71+ self.contents = contents
72+ self.filename = None
73+
74+ def setUp(self):
75+ fh, self.filename = tempfile.mkstemp(prefix="hwpack-test-config-")
76+ with os.fdopen(fh, 'w') as f:
77+ f.write(self.contents)
78+
79+ def tearDown(self):
80+ if self.filename is not None and os.path.exists(self.filename):
81+ os.unlink(self.filename)
82+
83+
84+class ChdirToTempdirFixture(object):
85+
86+ def __init__(self):
87+ self._orig_dir = None
88+ self.tempdir = None
89+
90+ def setUp(self):
91+ self.tearDown()
92+ self._orig_dir = os.getcwd()
93+ self.tempdir = tempfile.mkdtemp(prefix="hwpack-tests-")
94+ os.chdir(self.tempdir)
95+
96+ def tearDown(self):
97+ if self._orig_dir is not None:
98+ os.chdir(self._orig_dir)
99+ self._orig_dir = None
100+ if self.tempdir is not None and os.path.exists(self.tempdir):
101+ shutil.rmtree(self.tempdir)
102+ self.tempdir = None
103+
104+
105+class MismatchesAll(Mismatch):
106+ """A mismatch with many child mismatches."""
107+
108+ def __init__(self, mismatches):
109+ self.mismatches = mismatches
110+
111+ def describe(self):
112+ descriptions = ["Differences: ["]
113+ for mismatch in self.mismatches:
114+ descriptions.append(mismatch.describe())
115+ descriptions.append("]\n")
116+ return '\n'.join(descriptions)
117+
118+
119+class MatchesAll(object):
120+
121+ def __init__(self, *matchers):
122+ self.matchers = matchers
123+
124+ def __str__(self):
125+ return 'MatchesAll(%s)' % ', '.join(map(str, self.matchers))
126+
127+ def match(self, matchee):
128+ results = []
129+ for matcher in self.matchers:
130+ mismatch = matcher.match(matchee)
131+ if mismatch is not None:
132+ results.append(mismatch)
133+ if results:
134+ return MismatchesAll(results)
135+ else:
136+ return None
137+
138+
139+class HardwarePackHasFile(TarfileHasFile):
140+ """A subclass of TarfileHasFile specific to hardware packs.
141+
142+ We default to a set of attributes expected for files in a hardware
143+ pack.
144+ """
145+
146+ def __init__(self, path, **kwargs):
147+ """Create a HardwarePackHasFile matcher.
148+
149+ The kwargs are the keyword arguments taken by TarfileHasFile.
150+ If they are not given then defaults will be checked:
151+ - The type should be a regular file
152+ - If the content is given then the size will be checked
153+ to ensure it indicates the length of the content
154+ correctly.
155+ - the mode is appropriate for the type. If the type is
156+ regular file this is 0644, otherwise if it is
157+ a directory then it is 0755.
158+ - the linkname should be the empty string.
159+ - the uid and gid should be 1000
160+ - the uname and gname should be "user" and "group"
161+ respectively.
162+
163+ :param path: the path that should be present.
164+ :type path: str
165+ """
166+ kwargs.setdefault("type", tarfile.REGTYPE)
167+ if "content" in kwargs:
168+ kwargs.setdefault("size", len(kwargs["content"]))
169+ if kwargs["type"] == tarfile.DIRTYPE:
170+ kwargs.setdefault("mode", 0755)
171+ else:
172+ kwargs.setdefault("mode", 0644)
173+ kwargs.setdefault("linkname", "")
174+ kwargs.setdefault("uid", 1000)
175+ kwargs.setdefault("gid", 1000)
176+ kwargs.setdefault("uname", "user")
177+ kwargs.setdefault("gname", "group")
178+ # TODO: mtime checking
179+ super(HardwarePackHasFile, self).__init__(path, **kwargs)
180+
181+
182+class IsHardwarePack(Matcher):
183+
184+ def __init__(self, metadata, packages, sources):
185+ self.metadata = metadata
186+ self.packages = packages
187+ self.sources = sources
188+
189+ def match(self, path):
190+ tf = tarfile.open(name=path, mode="r:gz")
191+ try:
192+ matchers = []
193+ matchers.append(HardwarePackHasFile("FORMAT", content="1.0\n"))
194+ matchers.append(HardwarePackHasFile(
195+ "metadata", content=str(self.metadata)))
196+ manifest = ""
197+ for package in self.packages:
198+ manifest += "%s=%s\n" % (package.name, package.version)
199+ matchers.append(HardwarePackHasFile("manifest", content=manifest))
200+ matchers.append(HardwarePackHasFile("pkgs", type=tarfile.DIRTYPE))
201+ for package in self.packages:
202+ matchers.append(HardwarePackHasFile(
203+ "pkgs/%s" % package.filename,
204+ content=package.content.read()))
205+ matchers.append(HardwarePackHasFile(
206+ "pkgs/Packages", content=get_packages_file(self.packages)))
207+ matchers.append(HardwarePackHasFile(
208+ "sources.list.d", type=tarfile.DIRTYPE))
209+ for source_id, sources_entry in self.sources.items():
210+ matchers.append(HardwarePackHasFile(
211+ "sources.list.d/%s" % source_id,
212+ content="deb " + sources_entry + "\n"))
213+ matchers.append(HardwarePackHasFile(
214+ "sources.list.d.gpg", type=tarfile.DIRTYPE))
215+ return MatchesAll(*matchers).match(tf)
216+ finally:
217+ tf.close()
218+
219+ def __str__(self):
220+ return "Is a valid hardware pack."
221
222=== modified file 'hwpack/tests/__init__.py'
223--- hwpack/tests/__init__.py 2010-09-01 20:33:33 +0000
224+++ hwpack/tests/__init__.py 2010-09-10 16:11:07 +0000
225@@ -3,6 +3,7 @@
226 def test_suite():
227 module_names = ['hwpack.tests.test_config',
228 'hwpack.tests.test_better_tarfile',
229+ 'hwpack.tests.test_builder',
230 'hwpack.tests.test_hardwarepack',
231 'hwpack.tests.test_packages',
232 'hwpack.tests.test_tarfile_matchers',
233
234=== added file 'hwpack/tests/test_builder.py'
235--- hwpack/tests/test_builder.py 1970-01-01 00:00:00 +0000
236+++ hwpack/tests/test_builder.py 2010-09-10 16:11:07 +0000
237@@ -0,0 +1,74 @@
238+import os
239+
240+from testtools import TestCase
241+
242+from hwpack.builder import ConfigFileMissing, HardwarePackBuilder
243+from hwpack.config import HwpackConfigError
244+from hwpack.hardwarepack import Metadata
245+from hwpack.testing import (
246+ AptSourceFixture,
247+ ChdirToTempdirFixture,
248+ ConfigFileFixture,
249+ DummyFetchedPackage,
250+ IsHardwarePack,
251+ TestCaseWithFixtures,
252+ )
253+
254+
255+class ConfigFileMissingTests(TestCase):
256+
257+ def test_str(self):
258+ exc = ConfigFileMissing("path")
259+ self.assertEqual("No such config file: 'path'", str(exc))
260+
261+
262+class HardwarePackBuilderTests(TestCaseWithFixtures):
263+
264+ def setUp(self):
265+ super(HardwarePackBuilderTests, self).setUp()
266+ self.useFixture(ChdirToTempdirFixture())
267+
268+ def test_raises_on_missing_configuration(self):
269+ e = self.assertRaises(
270+ ConfigFileMissing, HardwarePackBuilder, "nonexistant", "1.0")
271+ self.assertEqual("nonexistant", e.filename)
272+
273+ def test_validates_configuration(self):
274+ config = self.useFixture(ConfigFileFixture(''))
275+ self.assertRaises(
276+ HwpackConfigError, HardwarePackBuilder, config.filename, "1.0")
277+
278+ def test_builds_one_pack_per_arch(self):
279+ available_package = DummyFetchedPackage("foo", "1.1")
280+ source = self.useFixture(AptSourceFixture([available_package]))
281+ config = self.useFixture(ConfigFileFixture(
282+ '[hwpack]\nname=ahwpack\npackages=foo\narchitectures=i386 armel\n'
283+ '\n[ubuntu]\nsources-entry=%s\n' % source.sources_entry))
284+ builder = HardwarePackBuilder(config.filename, "1.0")
285+ builder.build()
286+ self.assertTrue(os.path.isfile("hwpack_ahwpack_1.0_i386.tar.gz"))
287+ self.assertTrue(os.path.isfile("hwpack_ahwpack_1.0_armel.tar.gz"))
288+
289+ def test_builds_correct_contents(self):
290+ hwpack_name = "ahwpack"
291+ hwpack_version = "1.0"
292+ architecture = "armel"
293+ package_name = "foo"
294+ source_id = "ubuntu"
295+ available_package = DummyFetchedPackage(
296+ package_name, "1.1", architecture=architecture)
297+ source = self.useFixture(AptSourceFixture([available_package]))
298+ config = self.useFixture(ConfigFileFixture(
299+ '[hwpack]\nname=%s\npackages=%s\narchitectures=%s\n'
300+ '\n[%s]\nsources-entry=%s\n'
301+ % (hwpack_name, package_name, architecture,
302+ source_id, source.sources_entry)))
303+ builder = HardwarePackBuilder(config.filename, hwpack_version)
304+ builder.build()
305+ metadata = Metadata(hwpack_name, hwpack_version, architecture)
306+ self.assertThat(
307+ "hwpack_%s_%s_%s.tar.gz" % (hwpack_name, hwpack_version,
308+ architecture),
309+ IsHardwarePack(
310+ metadata, [available_package],
311+ {source_id: source.sources_entry}))
312
313=== modified file 'hwpack/tests/test_hardwarepack.py'
314--- hwpack/tests/test_hardwarepack.py 2010-09-10 16:11:07 +0000
315+++ hwpack/tests/test_hardwarepack.py 2010-09-10 16:11:07 +0000
316@@ -5,8 +5,7 @@
317
318 from hwpack.hardwarepack import HardwarePack, Metadata
319 from hwpack.packages import get_packages_file
320-from hwpack.tarfile_matchers import TarfileHasFile
321-from hwpack.testing import DummyFetchedPackage
322+from hwpack.testing import DummyFetchedPackage, HardwarePackHasFile
323
324
325 class MetadataTests(TestCase):
326@@ -90,49 +89,6 @@
327 self.assertEqual("i386", metadata.architecture)
328
329
330-class HardwarePackHasFile(TarfileHasFile):
331- """A subclass of TarfileHasFile specific to hardware packs.
332-
333- We default to a set of attributes expected for files in a hardware
334- pack.
335- """
336-
337- def __init__(self, path, **kwargs):
338- """Create a HardwarePackHasFile matcher.
339-
340- The kwargs are the keyword arguments taken by TarfileHasFile.
341- If they are not given then defaults will be checked:
342- - The type should be a regular file
343- - If the content is given then the size will be checked
344- to ensure it indicates the length of the content
345- correctly.
346- - the mode is appropriate for the type. If the type is
347- regular file this is 0644, otherwise if it is
348- a directory then it is 0755.
349- - the linkname should be the empty string.
350- - the uid and gid should be 1000
351- - the uname and gname should be "user" and "group"
352- respectively.
353-
354- :param path: the path that should be present.
355- :type path: str
356- """
357- kwargs.setdefault("type", tarfile.REGTYPE)
358- if "content" in kwargs:
359- kwargs.setdefault("size", len(kwargs["content"]))
360- if kwargs["type"] == tarfile.DIRTYPE:
361- kwargs.setdefault("mode", 0755)
362- else:
363- kwargs.setdefault("mode", 0644)
364- kwargs.setdefault("linkname", "")
365- kwargs.setdefault("uid", 1000)
366- kwargs.setdefault("gid", 1000)
367- kwargs.setdefault("uname", "user")
368- kwargs.setdefault("gname", "group")
369- # TODO: mtime checking
370- super(HardwarePackHasFile, self).__init__(path, **kwargs)
371-
372-
373 class HardwarePackTests(TestCase):
374
375 def setUp(self):

Subscribers

People subscribed via source and target branches