Merge lp:~james-w/linaro-image-tools/hwpack-builder into lp:linaro-image-tools/11.11
- hwpack-builder
- Merge into trunk
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Zygmunt Krynicki (community) | Approve | ||
Review via email: mp+35129@code.launchpad.net |
Commit message
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.
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): |
I just read the dif, didn't run the code yet. Looks very solid!
I like how matchers work, need to try them out