Merge lp:~jamesodhunt/ubuntu/vivid/ubuntu-core-upgrader/add-functional-tests into lp:ubuntu/vivid/ubuntu-core-upgrader
- Vivid (15.04)
- add-functional-tests
- Merge into vivid
Proposed by
James Hunt
Status: | Merged |
---|---|
Merged at revision: | 18 |
Proposed branch: | lp:~jamesodhunt/ubuntu/vivid/ubuntu-core-upgrader/add-functional-tests |
Merge into: | lp:ubuntu/vivid/ubuntu-core-upgrader |
Diff against target: |
1174 lines (+936/-170) 5 files modified
debian/changelog (+8/-0) functional/test_upgrader.py (+624/-0) ubuntucoreupgrader/tests/test_upgrader.py (+5/-169) ubuntucoreupgrader/tests/utils.py (+292/-0) ubuntucoreupgrader/upgrader.py (+7/-1) |
To merge this branch: | bzr merge lp:~jamesodhunt/ubuntu/vivid/ubuntu-core-upgrader/add-functional-tests |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Michael Vogt | Pending | ||
Review via email: mp+251723@code.launchpad.net |
Commit message
Description of the change
* ubuntucoreupgra
- get_file_
* functional/
To post a comment you must log in.
- 18. By James Hunt
-
* Fix-up changelog.
- 19. By James Hunt
-
* Moved common test code into ubuntucoreupgra
der/tests/ utils.py.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'debian/changelog' | |||
2 | --- debian/changelog 2015-03-04 11:34:20 +0000 | |||
3 | +++ debian/changelog 2015-03-04 13:53:47 +0000 | |||
4 | @@ -1,3 +1,11 @@ | |||
5 | 1 | ubuntu-core-upgrader (0.7.6) UNRELEASED; urgency=low | ||
6 | 2 | |||
7 | 3 | * ubuntucoreupgrader/upgrader.py: | ||
8 | 4 | - get_file_contents(): Fix to avoid leaking the fd. | ||
9 | 5 | * functional/test_upgrader.py: Basic set of functional tests. | ||
10 | 6 | |||
11 | 7 | -- James Hunt <james.hunt@ubuntu.com> Wed, 04 Mar 2015 11:52:19 +0000 | ||
12 | 8 | |||
13 | 1 | ubuntu-core-upgrader (0.7.5) vivid; urgency=low | 9 | ubuntu-core-upgrader (0.7.5) vivid; urgency=low |
14 | 2 | 10 | ||
15 | 3 | [ Michael Vogt ] | 11 | [ Michael Vogt ] |
16 | 4 | 12 | ||
17 | === added directory 'functional' | |||
18 | === added file 'functional/__init__.py' | |||
19 | === added file 'functional/test_upgrader.py' | |||
20 | --- functional/test_upgrader.py 1970-01-01 00:00:00 +0000 | |||
21 | +++ functional/test_upgrader.py 2015-03-04 13:53:47 +0000 | |||
22 | @@ -0,0 +1,624 @@ | |||
23 | 1 | #!/usr/bin/python3 | ||
24 | 2 | # -*- coding: utf-8 -*- | ||
25 | 3 | # -------------------------------------------------------------------- | ||
26 | 4 | # Copyright © 2014-2015 Canonical Ltd. | ||
27 | 5 | # | ||
28 | 6 | # This program is free software: you can redistribute it and/or modify | ||
29 | 7 | # it under the terms of the GNU General Public License as published by | ||
30 | 8 | # the Free Software Foundation, version 3 of the License, or | ||
31 | 9 | # (at your option) any later version. | ||
32 | 10 | # | ||
33 | 11 | # This program is distributed in the hope that it will be useful, | ||
34 | 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
35 | 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
36 | 14 | # GNU General Public License for more details. | ||
37 | 15 | # | ||
38 | 16 | # You should have received a copy of the GNU General Public License | ||
39 | 17 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
40 | 18 | # -------------------------------------------------------------------- | ||
41 | 19 | |||
42 | 20 | # -------------------------------------------------------------------- | ||
43 | 21 | # Functional tests for the snappy upgrader. | ||
44 | 22 | # -------------------------------------------------------------------- | ||
45 | 23 | |||
46 | 24 | import sys | ||
47 | 25 | import os | ||
48 | 26 | import logging | ||
49 | 27 | import tarfile | ||
50 | 28 | import unittest | ||
51 | 29 | import shutil | ||
52 | 30 | |||
53 | 31 | from unittest.mock import patch | ||
54 | 32 | |||
55 | 33 | from ubuntucoreupgrader.upgrader import ( | ||
56 | 34 | Upgrader, | ||
57 | 35 | parse_args, | ||
58 | 36 | ) | ||
59 | 37 | |||
60 | 38 | base_dir = os.path.abspath(os.path.dirname(__file__)) | ||
61 | 39 | module_dir = os.path.normpath(os.path.realpath(base_dir + os.sep + '..')) | ||
62 | 40 | sys.path.append(base_dir) | ||
63 | 41 | |||
64 | 42 | from ubuntucoreupgrader.tests.utils import * | ||
65 | 43 | |||
66 | 44 | CMD_FILE = 'ubuntu_command' | ||
67 | 45 | |||
68 | 46 | |||
69 | 47 | def call_upgrader(command_file, root_dir, update): | ||
70 | 48 | ''' | ||
71 | 49 | Invoke the upgrader. | ||
72 | 50 | |||
73 | 51 | :param command_file: commands file to drive the upgrader. | ||
74 | 52 | :param root_dir: Test directory to apply the upgrade to. | ||
75 | 53 | :param update: UpdateTree object. | ||
76 | 54 | ''' | ||
77 | 55 | |||
78 | 56 | args = [] | ||
79 | 57 | args += ['--root-dir', root_dir] | ||
80 | 58 | args += ['--debug', '1'] | ||
81 | 59 | |||
82 | 60 | # don't delete the archive and command files. | ||
83 | 61 | # The tests clean up after themselves so they will get removed then, | ||
84 | 62 | # but useful to have them around to diagnose test failures. | ||
85 | 63 | args.append('--leave-files') | ||
86 | 64 | |||
87 | 65 | args.append(command_file) | ||
88 | 66 | commands = file_to_list(command_file) | ||
89 | 67 | |||
90 | 68 | cache_dir = make_tmp_dir() | ||
91 | 69 | |||
92 | 70 | def mock_get_cache_dir(): | ||
93 | 71 | cache_dir = update.tmp_dir | ||
94 | 72 | sys_dir = os.path.join(cache_dir, 'system') | ||
95 | 73 | os.makedirs(sys_dir, exist_ok=True) | ||
96 | 74 | return cache_dir | ||
97 | 75 | |||
98 | 76 | upgrader = Upgrader(parse_args(args), commands, []) | ||
99 | 77 | upgrader.get_cache_dir = mock_get_cache_dir | ||
100 | 78 | upgrader.run() | ||
101 | 79 | |||
102 | 80 | shutil.rmtree(cache_dir) | ||
103 | 81 | |||
104 | 82 | |||
105 | 83 | def create_device_file(path, type='c', major=-1, minor=-1): | ||
106 | 84 | ''' | ||
107 | 85 | Create a device file. | ||
108 | 86 | |||
109 | 87 | :param path: full path to device file. | ||
110 | 88 | :param type: 'c' or 'b' (character or block). | ||
111 | 89 | :param major: major number. | ||
112 | 90 | :param minor: minor number. | ||
113 | 91 | |||
114 | 92 | XXX: This doesn't actually create a device node, | ||
115 | 93 | it simply creates a regular empty file whilst ensuring the filename | ||
116 | 94 | gives the impression that it is a device file. | ||
117 | 95 | |||
118 | 96 | This hackery is done for the following reasons: | ||
119 | 97 | |||
120 | 98 | - non-priv users cannot create device nodes (and the tests run as a | ||
121 | 99 | non-priv user). | ||
122 | 100 | - the removed file in the upgrade tar file does not actually specify the | ||
123 | 101 | _type_ of the files to remove. Hence, we can pretend that the file | ||
124 | 102 | to remove is a device file since the upgrader cannot know for sure | ||
125 | 103 | (it can check the existing on-disk file, but that isn't conclusive | ||
126 | 104 | since the admin may have manually modified a file, or the server | ||
127 | 105 | may have generated an invalid remove file - the upgrader cannot | ||
128 | 106 | know for sure. | ||
129 | 107 | ''' | ||
130 | 108 | assert (os.path.dirname(path).endswith('/dev')) | ||
131 | 109 | |||
132 | 110 | append_file(path, 'fake-device file') | ||
133 | 111 | |||
134 | 112 | |||
135 | 113 | def create_directory(path): | ||
136 | 114 | ''' | ||
137 | 115 | Create a directory. | ||
138 | 116 | ''' | ||
139 | 117 | os.makedirs(path, mode=TEST_DIR_MODE, exist_ok=False) | ||
140 | 118 | |||
141 | 119 | |||
142 | 120 | def create_sym_link(source, dest): | ||
143 | 121 | ''' | ||
144 | 122 | Create a symbolic link. | ||
145 | 123 | |||
146 | 124 | :param source: existing file to link to. | ||
147 | 125 | :param dest: name for the sym link. | ||
148 | 126 | ''' | ||
149 | 127 | dirname = os.path.dirname(dest) | ||
150 | 128 | os.makedirs(dirname, mode=TEST_DIR_MODE, exist_ok=True) | ||
151 | 129 | |||
152 | 130 | os.symlink(source, dest) | ||
153 | 131 | |||
154 | 132 | |||
155 | 133 | def create_hard_link(source, dest): | ||
156 | 134 | ''' | ||
157 | 135 | Create a hard link. | ||
158 | 136 | |||
159 | 137 | :param source: existing file to link to. | ||
160 | 138 | :param dest: name for the hard link. | ||
161 | 139 | ''' | ||
162 | 140 | os.link(source, dest) | ||
163 | 141 | |||
164 | 142 | |||
165 | 143 | def is_sym_link_broken(path): | ||
166 | 144 | ''' | ||
167 | 145 | :param path: symbolic link to check. | ||
168 | 146 | :return: True if the specified path is a broken symbolic link, | ||
169 | 147 | else False. | ||
170 | 148 | ''' | ||
171 | 149 | try: | ||
172 | 150 | os.lstat(path) | ||
173 | 151 | os.stat(path) | ||
174 | 152 | except: | ||
175 | 153 | return True | ||
176 | 154 | return False | ||
177 | 155 | |||
178 | 156 | |||
179 | 157 | def make_command_file(path, update_list): | ||
180 | 158 | ''' | ||
181 | 159 | Create a command file that the upgrader processes. | ||
182 | 160 | |||
183 | 161 | :param path: full path to file to create, | ||
184 | 162 | :param update_list: list of update archives to include. | ||
185 | 163 | ''' | ||
186 | 164 | l = [] | ||
187 | 165 | |||
188 | 166 | for file in update_list: | ||
189 | 167 | l.append('update {} {}.asc'.format(file, file)) | ||
190 | 168 | |||
191 | 169 | # flatten | ||
192 | 170 | contents = "\n".join(l) + '\n' | ||
193 | 171 | |||
194 | 172 | append_file(path, contents) | ||
195 | 173 | |||
196 | 174 | |||
197 | 175 | def file_to_list(path): | ||
198 | 176 | ''' | ||
199 | 177 | Convert the specified file into a list and return it. | ||
200 | 178 | ''' | ||
201 | 179 | lines = [] | ||
202 | 180 | |||
203 | 181 | with open(path, 'r') as f: | ||
204 | 182 | lines = f.readlines() | ||
205 | 183 | |||
206 | 184 | lines = [line.rstrip() for line in lines] | ||
207 | 185 | |||
208 | 186 | return lines | ||
209 | 187 | |||
210 | 188 | |||
211 | 189 | class UpgraderFileRemovalTestCase(UbuntuCoreUpgraderTestCase): | ||
212 | 190 | ''' | ||
213 | 191 | Test how the upgrader handles the removals file. | ||
214 | 192 | ''' | ||
215 | 193 | |||
216 | 194 | def test_remove_file(self): | ||
217 | 195 | ''' | ||
218 | 196 | Ensure the upgrader can remove a regular file. | ||
219 | 197 | ''' | ||
220 | 198 | |||
221 | 199 | file = 'a-regular-file' | ||
222 | 200 | |||
223 | 201 | self.update.add_to_removed_file([file]) | ||
224 | 202 | |||
225 | 203 | archive = self.update.create_archive(self.TARFILE) | ||
226 | 204 | self.assertTrue(os.path.exists(archive)) | ||
227 | 205 | |||
228 | 206 | cmd_file = os.path.join(self.update.tmp_dir, CMD_FILE) | ||
229 | 207 | make_command_file(cmd_file, [self.TARFILE]) | ||
230 | 208 | |||
231 | 209 | file_path = os.path.join(self.victim_dir, file) | ||
232 | 210 | create_file(file_path, 'foo bar') | ||
233 | 211 | |||
234 | 212 | self.assertTrue(os.path.exists(file_path)) | ||
235 | 213 | self.assertTrue(os.path.isfile(file_path)) | ||
236 | 214 | |||
237 | 215 | call_upgrader(cmd_file, self.victim_dir, self.update) | ||
238 | 216 | |||
239 | 217 | self.assertFalse(os.path.exists(file_path)) | ||
240 | 218 | |||
241 | 219 | def test_remove_directory(self): | ||
242 | 220 | ''' | ||
243 | 221 | Ensure the upgrader can remove a directory. | ||
244 | 222 | ''' | ||
245 | 223 | dir = 'a-directory' | ||
246 | 224 | |||
247 | 225 | self.update.add_to_removed_file([dir]) | ||
248 | 226 | |||
249 | 227 | archive = self.update.create_archive(self.TARFILE) | ||
250 | 228 | self.assertTrue(os.path.exists(archive)) | ||
251 | 229 | |||
252 | 230 | cmd_file = os.path.join(self.update.tmp_dir, CMD_FILE) | ||
253 | 231 | make_command_file(cmd_file, [self.TARFILE]) | ||
254 | 232 | |||
255 | 233 | dir_path = os.path.join(self.victim_dir, dir) | ||
256 | 234 | create_directory(dir_path) | ||
257 | 235 | |||
258 | 236 | self.assertTrue(os.path.exists(dir_path)) | ||
259 | 237 | self.assertTrue(os.path.isdir(dir_path)) | ||
260 | 238 | |||
261 | 239 | call_upgrader(cmd_file, self.victim_dir, self.update) | ||
262 | 240 | |||
263 | 241 | self.assertFalse(os.path.exists(dir_path)) | ||
264 | 242 | |||
265 | 243 | def test_remove_sym_link_file(self): | ||
266 | 244 | ''' | ||
267 | 245 | Ensure the upgrader can remove a symbolic link to a file. | ||
268 | 246 | ''' | ||
269 | 247 | src = 'the-source-file' | ||
270 | 248 | link = 'the-symlink-file' | ||
271 | 249 | |||
272 | 250 | self.update.add_to_removed_file([link]) | ||
273 | 251 | |||
274 | 252 | archive = self.update.create_archive(self.TARFILE) | ||
275 | 253 | self.assertTrue(os.path.exists(archive)) | ||
276 | 254 | |||
277 | 255 | cmd_file = os.path.join(self.update.tmp_dir, CMD_FILE) | ||
278 | 256 | make_command_file(cmd_file, [self.TARFILE]) | ||
279 | 257 | |||
280 | 258 | src_file_path = os.path.join(self.victim_dir, src) | ||
281 | 259 | link_file_path = os.path.join(self.victim_dir, link) | ||
282 | 260 | |||
283 | 261 | create_file(src_file_path, 'foo bar') | ||
284 | 262 | |||
285 | 263 | self.assertTrue(os.path.exists(src_file_path)) | ||
286 | 264 | self.assertTrue(os.path.isfile(src_file_path)) | ||
287 | 265 | self.assertFalse(os.path.islink(src_file_path)) | ||
288 | 266 | |||
289 | 267 | create_sym_link(src_file_path, link_file_path) | ||
290 | 268 | self.assertTrue(os.path.exists(link_file_path)) | ||
291 | 269 | self.assertTrue(os.path.islink(link_file_path)) | ||
292 | 270 | |||
293 | 271 | call_upgrader(cmd_file, self.victim_dir, self.update) | ||
294 | 272 | |||
295 | 273 | # original file should still be there | ||
296 | 274 | self.assertTrue(os.path.exists(src_file_path)) | ||
297 | 275 | self.assertTrue(os.path.isfile(src_file_path)) | ||
298 | 276 | self.assertFalse(os.path.islink(src_file_path)) | ||
299 | 277 | |||
300 | 278 | # link should have gone | ||
301 | 279 | self.assertFalse(os.path.exists(link_file_path)) | ||
302 | 280 | |||
303 | 281 | def test_remove_sym_link_directory(self): | ||
304 | 282 | ''' | ||
305 | 283 | Ensure the upgrader can remove a symbolic link to a directory. | ||
306 | 284 | ''' | ||
307 | 285 | dir = 'the-source-directory' | ||
308 | 286 | link = 'the-symlink-file' | ||
309 | 287 | |||
310 | 288 | self.update.add_to_removed_file([link]) | ||
311 | 289 | |||
312 | 290 | archive = self.update.create_archive(self.TARFILE) | ||
313 | 291 | self.assertTrue(os.path.exists(archive)) | ||
314 | 292 | |||
315 | 293 | cmd_file = os.path.join(self.update.tmp_dir, CMD_FILE) | ||
316 | 294 | make_command_file(cmd_file, [self.TARFILE]) | ||
317 | 295 | |||
318 | 296 | src_dir_path = os.path.join(self.victim_dir, dir) | ||
319 | 297 | link_file_path = os.path.join(self.victim_dir, link) | ||
320 | 298 | |||
321 | 299 | create_directory(src_dir_path) | ||
322 | 300 | |||
323 | 301 | self.assertTrue(os.path.exists(src_dir_path)) | ||
324 | 302 | self.assertTrue(os.path.isdir(src_dir_path)) | ||
325 | 303 | self.assertFalse(os.path.islink(src_dir_path)) | ||
326 | 304 | |||
327 | 305 | create_sym_link(src_dir_path, link_file_path) | ||
328 | 306 | self.assertTrue(os.path.exists(link_file_path)) | ||
329 | 307 | self.assertTrue(os.path.islink(link_file_path)) | ||
330 | 308 | |||
331 | 309 | call_upgrader(cmd_file, self.victim_dir, self.update) | ||
332 | 310 | |||
333 | 311 | # original directory should still be there | ||
334 | 312 | self.assertTrue(os.path.exists(src_dir_path)) | ||
335 | 313 | self.assertTrue(os.path.isdir(src_dir_path)) | ||
336 | 314 | self.assertFalse(os.path.islink(src_dir_path)) | ||
337 | 315 | |||
338 | 316 | # link should have gone | ||
339 | 317 | self.assertFalse(os.path.exists(link_file_path)) | ||
340 | 318 | |||
341 | 319 | def test_remove_hardlink(self): | ||
342 | 320 | ''' | ||
343 | 321 | Ensure the upgrader can remove a hard link to a file. | ||
344 | 322 | ''' | ||
345 | 323 | src = 'the-source-file' | ||
346 | 324 | link = 'the-hardlink-file' | ||
347 | 325 | |||
348 | 326 | self.update.add_to_removed_file([link]) | ||
349 | 327 | |||
350 | 328 | archive = self.update.create_archive(self.TARFILE) | ||
351 | 329 | self.assertTrue(os.path.exists(archive)) | ||
352 | 330 | |||
353 | 331 | cmd_file = os.path.join(self.update.tmp_dir, CMD_FILE) | ||
354 | 332 | make_command_file(cmd_file, [self.TARFILE]) | ||
355 | 333 | |||
356 | 334 | src_file_path = os.path.join(self.victim_dir, src) | ||
357 | 335 | link_file_path = os.path.join(self.victim_dir, link) | ||
358 | 336 | |||
359 | 337 | create_file(src_file_path, 'foo bar') | ||
360 | 338 | |||
361 | 339 | src_inode = os.stat(src_file_path).st_ino | ||
362 | 340 | |||
363 | 341 | self.assertTrue(os.path.exists(src_file_path)) | ||
364 | 342 | self.assertTrue(os.path.isfile(src_file_path)) | ||
365 | 343 | |||
366 | 344 | create_hard_link(src_file_path, link_file_path) | ||
367 | 345 | self.assertTrue(os.path.exists(link_file_path)) | ||
368 | 346 | |||
369 | 347 | link_inode = os.stat(link_file_path).st_ino | ||
370 | 348 | |||
371 | 349 | self.assertTrue(src_inode == link_inode) | ||
372 | 350 | |||
373 | 351 | call_upgrader(cmd_file, self.victim_dir, self.update) | ||
374 | 352 | |||
375 | 353 | # original file should still be there | ||
376 | 354 | self.assertTrue(os.path.exists(src_file_path)) | ||
377 | 355 | self.assertTrue(os.path.isfile(src_file_path)) | ||
378 | 356 | |||
379 | 357 | # Inode should not have changed. | ||
380 | 358 | self.assertTrue(os.stat(src_file_path).st_ino == src_inode) | ||
381 | 359 | |||
382 | 360 | # link should have gone | ||
383 | 361 | self.assertFalse(os.path.exists(link_file_path)) | ||
384 | 362 | |||
385 | 363 | def test_remove_device_file(self): | ||
386 | 364 | ''' | ||
387 | 365 | Ensure the upgrader can deal with a device file. | ||
388 | 366 | |||
389 | 367 | XXX: Note that The upgrader currently "deals" with them by | ||
390 | 368 | XXX: ignoring them :-) | ||
391 | 369 | |||
392 | 370 | ''' | ||
393 | 371 | file = '/dev/a-fake-device' | ||
394 | 372 | |||
395 | 373 | self.update.add_to_removed_file([file]) | ||
396 | 374 | |||
397 | 375 | archive = self.update.create_archive(self.TARFILE) | ||
398 | 376 | self.assertTrue(os.path.exists(archive)) | ||
399 | 377 | |||
400 | 378 | cmd_file = os.path.join(self.update.tmp_dir, CMD_FILE) | ||
401 | 379 | make_command_file(cmd_file, [self.TARFILE]) | ||
402 | 380 | |||
403 | 381 | file_path = '{}{}'.format(self.victim_dir, file) | ||
404 | 382 | |||
405 | 383 | create_device_file(file_path) | ||
406 | 384 | |||
407 | 385 | self.assertTrue(os.path.exists(file_path)) | ||
408 | 386 | |||
409 | 387 | # sigh - we can't assert the that filetype is a char/block | ||
410 | 388 | # device because it won't be :) | ||
411 | 389 | self.assertTrue(os.path.isfile(file_path)) | ||
412 | 390 | |||
413 | 391 | call_upgrader(cmd_file, self.victim_dir, self.update) | ||
414 | 392 | |||
415 | 393 | self.assertFalse(os.path.exists(file_path)) | ||
416 | 394 | |||
417 | 395 | |||
418 | 396 | class UpgraderFileAddTestCase(UbuntuCoreUpgraderTestCase): | ||
419 | 397 | ''' | ||
420 | 398 | Test how the upgrader handles adding new files. | ||
421 | 399 | ''' | ||
422 | 400 | |||
423 | 401 | def test_create_file(self): | ||
424 | 402 | ''' | ||
425 | 403 | Ensure the upgrader can create a regular file. | ||
426 | 404 | ''' | ||
427 | 405 | file = 'created-regular-file' | ||
428 | 406 | |||
429 | 407 | file_path = os.path.join(self.update.system_dir, file) | ||
430 | 408 | |||
431 | 409 | create_file(file_path, 'foo bar') | ||
432 | 410 | |||
433 | 411 | archive = self.update.create_archive(self.TARFILE) | ||
434 | 412 | self.assertTrue(os.path.exists(archive)) | ||
435 | 413 | |||
436 | 414 | cmd_file = os.path.join(self.update.tmp_dir, CMD_FILE) | ||
437 | 415 | make_command_file(cmd_file, [self.TARFILE]) | ||
438 | 416 | |||
439 | 417 | file_path = os.path.join(self.victim_dir, file) | ||
440 | 418 | self.assertFalse(os.path.exists(file_path)) | ||
441 | 419 | |||
442 | 420 | call_upgrader(cmd_file, self.victim_dir, self.update) | ||
443 | 421 | |||
444 | 422 | self.assertTrue(os.path.exists(file_path)) | ||
445 | 423 | self.assertTrue(os.path.isfile(file_path)) | ||
446 | 424 | |||
447 | 425 | def test_create_directory(self): | ||
448 | 426 | ''' | ||
449 | 427 | Ensure the upgrader can create a directory. | ||
450 | 428 | ''' | ||
451 | 429 | dir = 'created-directory' | ||
452 | 430 | |||
453 | 431 | dir_path = os.path.join(self.update.system_dir, dir) | ||
454 | 432 | |||
455 | 433 | create_directory(dir_path) | ||
456 | 434 | |||
457 | 435 | archive = self.update.create_archive(self.TARFILE) | ||
458 | 436 | self.assertTrue(os.path.exists(archive)) | ||
459 | 437 | |||
460 | 438 | cmd_file = os.path.join(self.update.tmp_dir, CMD_FILE) | ||
461 | 439 | make_command_file(cmd_file, [self.TARFILE]) | ||
462 | 440 | |||
463 | 441 | dir_path = os.path.join(self.victim_dir, dir) | ||
464 | 442 | self.assertFalse(os.path.exists(dir_path)) | ||
465 | 443 | |||
466 | 444 | call_upgrader(cmd_file, self.victim_dir, self.update) | ||
467 | 445 | |||
468 | 446 | self.assertTrue(os.path.exists(dir_path)) | ||
469 | 447 | self.assertTrue(os.path.isdir(dir_path)) | ||
470 | 448 | |||
471 | 449 | def test_create_absolute_sym_link_to_file(self): | ||
472 | 450 | ''' | ||
473 | 451 | Ensure the upgrader can create a symbolic link to a file (which | ||
474 | 452 | already exists and is not included in the update archive). | ||
475 | 453 | ''' | ||
476 | 454 | src = 'the-source-file' | ||
477 | 455 | link = 'the-symlink-file' | ||
478 | 456 | |||
479 | 457 | # the file the link points to should *NOT* be below the 'system/' | ||
480 | 458 | # directory (since there isn't one post-unpack). | ||
481 | 459 | |||
482 | 460 | # an absolute sym-link target path | ||
483 | 461 | src_file_path = '/{}'.format(src) | ||
484 | 462 | link_file_path = os.path.join(self.update.system_dir, link) | ||
485 | 463 | |||
486 | 464 | victim_src_file_path = os.path.normpath('{}/{}' | ||
487 | 465 | .format(self.victim_dir, | ||
488 | 466 | src_file_path)) | ||
489 | 467 | victim_link_file_path = os.path.join(self.victim_dir, link) | ||
490 | 468 | |||
491 | 469 | # Create a broken sym link ('/system/<link> -> /<src>') | ||
492 | 470 | create_sym_link(src_file_path, link_file_path) | ||
493 | 471 | |||
494 | 472 | self.assertTrue(os.path.lexists(link_file_path)) | ||
495 | 473 | self.assertTrue(os.path.islink(link_file_path)) | ||
496 | 474 | self.assertTrue(is_sym_link_broken(link_file_path)) | ||
497 | 475 | |||
498 | 476 | archive = self.update.create_archive(self.TARFILE) | ||
499 | 477 | self.assertTrue(os.path.exists(archive)) | ||
500 | 478 | |||
501 | 479 | cmd_file = os.path.join(self.update.tmp_dir, CMD_FILE) | ||
502 | 480 | make_command_file(cmd_file, [self.TARFILE]) | ||
503 | 481 | |||
504 | 482 | create_file(victim_src_file_path, 'foo') | ||
505 | 483 | |||
506 | 484 | self.assertTrue(os.path.exists(victim_src_file_path)) | ||
507 | 485 | self.assertTrue(os.path.isfile(victim_src_file_path)) | ||
508 | 486 | |||
509 | 487 | self.assertFalse(os.path.exists(victim_link_file_path)) | ||
510 | 488 | |||
511 | 489 | call_upgrader(cmd_file, self.victim_dir, self.update) | ||
512 | 490 | |||
513 | 491 | self.assertTrue(os.path.exists(victim_src_file_path)) | ||
514 | 492 | self.assertTrue(os.path.isfile(victim_src_file_path)) | ||
515 | 493 | |||
516 | 494 | # upgrader should have created the link in the victim directory | ||
517 | 495 | self.assertTrue(os.path.lexists(victim_link_file_path)) | ||
518 | 496 | self.assertTrue(os.path.islink(victim_link_file_path)) | ||
519 | 497 | self.assertFalse(is_sym_link_broken(victim_link_file_path)) | ||
520 | 498 | |||
521 | 499 | def test_create_relative_sym_link_to_file(self): | ||
522 | 500 | ''' | ||
523 | 501 | Ensure the upgrader can create a symbolic link to a file (which | ||
524 | 502 | already exists and is not included in the update archive). | ||
525 | 503 | ''' | ||
526 | 504 | src = 'a/b/c/the-source-file' | ||
527 | 505 | link = 'a/d/e/the-symlink-file' | ||
528 | 506 | |||
529 | 507 | # a relative sym-link target path | ||
530 | 508 | # ##src_file_path = '../../b/c/{}'.format(src) | ||
531 | 509 | src_file_path = '../../b/c/the-source-file'.format(src) | ||
532 | 510 | |||
533 | 511 | # the file the link points to should *NOT* be below the 'system/' | ||
534 | 512 | # directory (since there isn't one post-unpack). | ||
535 | 513 | |||
536 | 514 | link_file_path = os.path.join(self.update.system_dir, link) | ||
537 | 515 | |||
538 | 516 | victim_src_file_path = os.path.normpath('{}/{}' | ||
539 | 517 | .format(self.victim_dir, | ||
540 | 518 | src)) | ||
541 | 519 | victim_link_file_path = os.path.join(self.victim_dir, link) | ||
542 | 520 | |||
543 | 521 | create_sym_link(src_file_path, link_file_path) | ||
544 | 522 | |||
545 | 523 | self.assertTrue(os.path.lexists(link_file_path)) | ||
546 | 524 | self.assertTrue(os.path.islink(link_file_path)) | ||
547 | 525 | self.assertTrue(is_sym_link_broken(link_file_path)) | ||
548 | 526 | |||
549 | 527 | archive = self.update.create_archive(self.TARFILE) | ||
550 | 528 | self.assertTrue(os.path.exists(archive)) | ||
551 | 529 | |||
552 | 530 | cmd_file = os.path.join(self.update.tmp_dir, CMD_FILE) | ||
553 | 531 | make_command_file(cmd_file, [self.TARFILE]) | ||
554 | 532 | |||
555 | 533 | create_file(victim_src_file_path, 'foo') | ||
556 | 534 | |||
557 | 535 | self.assertTrue(os.path.exists(victim_src_file_path)) | ||
558 | 536 | self.assertTrue(os.path.isfile(victim_src_file_path)) | ||
559 | 537 | |||
560 | 538 | self.assertFalse(os.path.exists(victim_link_file_path)) | ||
561 | 539 | |||
562 | 540 | call_upgrader(cmd_file, self.victim_dir, self.update) | ||
563 | 541 | |||
564 | 542 | self.assertTrue(os.path.exists(victim_src_file_path)) | ||
565 | 543 | self.assertTrue(os.path.isfile(victim_src_file_path)) | ||
566 | 544 | |||
567 | 545 | # upgrader should have created the link in the victim directory | ||
568 | 546 | self.assertTrue(os.path.lexists(victim_link_file_path)) | ||
569 | 547 | self.assertTrue(os.path.islink(victim_link_file_path)) | ||
570 | 548 | self.assertFalse(is_sym_link_broken(victim_link_file_path)) | ||
571 | 549 | |||
572 | 550 | def test_create_broken_sym_link_file(self): | ||
573 | 551 | ''' | ||
574 | 552 | Ensure the upgrader can create a broken symbolic link | ||
575 | 553 | (one that points to a non-existent file). | ||
576 | 554 | ''' | ||
577 | 555 | src = 'the-source-file' | ||
578 | 556 | link = 'the-symlink-file' | ||
579 | 557 | |||
580 | 558 | # the file the link points to should *NOT* be below the 'system/' | ||
581 | 559 | # directory (since there isn't one post-unpack). | ||
582 | 560 | src_file_path = src | ||
583 | 561 | |||
584 | 562 | link_file_path = os.path.join(self.update.system_dir, link) | ||
585 | 563 | |||
586 | 564 | # Create a broken sym link ('/system/<link> -> /<src>') | ||
587 | 565 | create_sym_link(src_file_path, link_file_path) | ||
588 | 566 | |||
589 | 567 | self.assertTrue(os.path.lexists(link_file_path)) | ||
590 | 568 | self.assertTrue(os.path.islink(link_file_path)) | ||
591 | 569 | self.assertTrue(is_sym_link_broken(link_file_path)) | ||
592 | 570 | |||
593 | 571 | archive = self.update.create_archive(self.TARFILE) | ||
594 | 572 | self.assertTrue(os.path.exists(archive)) | ||
595 | 573 | |||
596 | 574 | cmd_file = os.path.join(self.update.tmp_dir, CMD_FILE) | ||
597 | 575 | make_command_file(cmd_file, [self.TARFILE]) | ||
598 | 576 | |||
599 | 577 | victim_src_file_path = os.path.join(self.victim_dir, src) | ||
600 | 578 | victim_link_file_path = os.path.join(self.victim_dir, link) | ||
601 | 579 | |||
602 | 580 | self.assertFalse(os.path.exists(victim_src_file_path)) | ||
603 | 581 | self.assertFalse(os.path.exists(victim_link_file_path)) | ||
604 | 582 | |||
605 | 583 | call_upgrader(cmd_file, self.victim_dir, self.update) | ||
606 | 584 | |||
607 | 585 | # source still shouldn't exist | ||
608 | 586 | self.assertFalse(os.path.exists(victim_src_file_path)) | ||
609 | 587 | |||
610 | 588 | # upgrader should have created the link in the victim directory | ||
611 | 589 | self.assertTrue(os.path.lexists(victim_link_file_path)) | ||
612 | 590 | self.assertTrue(os.path.islink(victim_link_file_path)) | ||
613 | 591 | self.assertTrue(is_sym_link_broken(victim_link_file_path)) | ||
614 | 592 | |||
615 | 593 | |||
616 | 594 | def main(): | ||
617 | 595 | kwargs = {} | ||
618 | 596 | format = \ | ||
619 | 597 | '%(asctime)s:' \ | ||
620 | 598 | '%(filename)s:' \ | ||
621 | 599 | '%(name)s:' \ | ||
622 | 600 | '%(funcName)s:' \ | ||
623 | 601 | '%(levelname)s:' \ | ||
624 | 602 | '%(message)s' | ||
625 | 603 | |||
626 | 604 | kwargs['format'] = format | ||
627 | 605 | |||
628 | 606 | # We want to see what's happening | ||
629 | 607 | kwargs['level'] = logging.DEBUG | ||
630 | 608 | |||
631 | 609 | logging.basicConfig(**kwargs) | ||
632 | 610 | |||
633 | 611 | unittest.main( | ||
634 | 612 | testRunner=unittest.TextTestRunner( | ||
635 | 613 | stream=sys.stdout, | ||
636 | 614 | verbosity=2, | ||
637 | 615 | |||
638 | 616 | # don't keep running tests if one fails | ||
639 | 617 | # (... who _wouldn't_ want this???) | ||
640 | 618 | failfast=True | ||
641 | 619 | ), | ||
642 | 620 | |||
643 | 621 | ) | ||
644 | 622 | |||
645 | 623 | if __name__ == '__main__': | ||
646 | 624 | main() | ||
647 | 0 | 625 | ||
648 | === modified file 'ubuntucoreupgrader/tests/test_upgrader.py' | |||
649 | --- ubuntucoreupgrader/tests/test_upgrader.py 2015-03-04 11:27:28 +0000 | |||
650 | +++ ubuntucoreupgrader/tests/test_upgrader.py 2015-03-04 13:53:47 +0000 | |||
651 | @@ -29,6 +29,7 @@ | |||
652 | 29 | import unittest | 29 | import unittest |
653 | 30 | import os | 30 | import os |
654 | 31 | import shutil | 31 | import shutil |
655 | 32 | import sys | ||
656 | 32 | 33 | ||
657 | 33 | from unittest.mock import patch | 34 | from unittest.mock import patch |
658 | 34 | 35 | ||
659 | @@ -38,7 +39,10 @@ | |||
660 | 38 | parse_args, | 39 | parse_args, |
661 | 39 | ) | 40 | ) |
662 | 40 | 41 | ||
664 | 41 | script_name = os.path.basename(__file__) | 42 | base_dir = os.path.abspath(os.path.dirname(__file__)) |
665 | 43 | sys.path.append(base_dir) | ||
666 | 44 | |||
667 | 45 | from ubuntucoreupgrader.tests.utils import * | ||
668 | 42 | 46 | ||
669 | 43 | # file mode to use for creating test directories. | 47 | # file mode to use for creating test directories. |
670 | 44 | TEST_DIR_MODE = 0o750 | 48 | TEST_DIR_MODE = 0o750 |
671 | @@ -50,19 +54,6 @@ | |||
672 | 50 | return parse_args([]) | 54 | return parse_args([]) |
673 | 51 | 55 | ||
674 | 52 | 56 | ||
675 | 53 | def make_tmp_dir(tag=None): | ||
676 | 54 | ''' | ||
677 | 55 | Create a temporary directory. | ||
678 | 56 | ''' | ||
679 | 57 | |||
680 | 58 | if tag: | ||
681 | 59 | prefix = '{}-{}-'.format(script_name, tag) | ||
682 | 60 | else: | ||
683 | 61 | prefix = script_name | ||
684 | 62 | |||
685 | 63 | return tempfile.mkdtemp(prefix=prefix) | ||
686 | 64 | |||
687 | 65 | |||
688 | 66 | class UpgradeTestCase(unittest.TestCase): | 57 | class UpgradeTestCase(unittest.TestCase): |
689 | 67 | 58 | ||
690 | 68 | def test_tar_generator_unpack_assets(self): | 59 | def test_tar_generator_unpack_assets(self): |
691 | @@ -172,28 +163,6 @@ | |||
692 | 172 | shutil.rmtree(cache_dir) | 163 | shutil.rmtree(cache_dir) |
693 | 173 | 164 | ||
694 | 174 | 165 | ||
695 | 175 | def append_file(path, contents): | ||
696 | 176 | ''' | ||
697 | 177 | Append to a regular file (create it doesn't exist). | ||
698 | 178 | ''' | ||
699 | 179 | |||
700 | 180 | dirname = os.path.dirname(path) | ||
701 | 181 | os.makedirs(dirname, mode=TEST_DIR_MODE, exist_ok=True) | ||
702 | 182 | |||
703 | 183 | with open(path, 'a') as fh: | ||
704 | 184 | fh.writelines(contents) | ||
705 | 185 | |||
706 | 186 | if not contents.endswith('\n'): | ||
707 | 187 | fh.write('\n') | ||
708 | 188 | |||
709 | 189 | |||
710 | 190 | def create_file(path, contents): | ||
711 | 191 | ''' | ||
712 | 192 | Create a regular file. | ||
713 | 193 | ''' | ||
714 | 194 | append_file(path, contents) | ||
715 | 195 | |||
716 | 196 | |||
717 | 197 | def touch_file(path): | 166 | def touch_file(path): |
718 | 198 | ''' | 167 | ''' |
719 | 199 | Create an empty file (creating any necessary intermediate | 168 | Create an empty file (creating any necessary intermediate |
720 | @@ -223,139 +192,6 @@ | |||
721 | 223 | return l | 192 | return l |
722 | 224 | 193 | ||
723 | 225 | 194 | ||
724 | 226 | class UpdateTree(): | ||
725 | 227 | ''' | ||
726 | 228 | Representation of a directory tree that will be converted into an | ||
727 | 229 | update archive. | ||
728 | 230 | ''' | ||
729 | 231 | TEST_REMOVED_FILE = 'removed' | ||
730 | 232 | TEST_SYSTEM_DIR = 'system/' | ||
731 | 233 | |||
732 | 234 | def __init__(self): | ||
733 | 235 | |||
734 | 236 | # Directory tree used to construct the tar file from. | ||
735 | 237 | # Also used to hold the TEST_REMOVED_FILE file. | ||
736 | 238 | self.dir = make_tmp_dir(tag='UpdateTree-tar-source') | ||
737 | 239 | |||
738 | 240 | self.removed_file = os.path.join(self.dir, self.TEST_REMOVED_FILE) | ||
739 | 241 | |||
740 | 242 | # Directory to place create/modify files into. | ||
741 | 243 | self.system_dir = os.path.join(self.dir, self.TEST_SYSTEM_DIR) | ||
742 | 244 | |||
743 | 245 | # Directory used to write the generated tarfile to. | ||
744 | 246 | # This directory should also be used to write the command file | ||
745 | 247 | # to. | ||
746 | 248 | self.tmp_dir = make_tmp_dir(tag='UpdateTree-cache') | ||
747 | 249 | |||
748 | 250 | def destroy(self): | ||
749 | 251 | if os.path.exists(self.dir): | ||
750 | 252 | shutil.rmtree(self.dir) | ||
751 | 253 | |||
752 | 254 | if os.path.exists(self.tmp_dir): | ||
753 | 255 | shutil.rmtree(self.tmp_dir) | ||
754 | 256 | |||
755 | 257 | def tar_filter(self, member): | ||
756 | 258 | ''' | ||
757 | 259 | Function to filter the tarinfo members before creating the | ||
758 | 260 | archive. | ||
759 | 261 | ''' | ||
760 | 262 | # members are created with relative paths (no leading slash) | ||
761 | 263 | path = os.sep + member.name | ||
762 | 264 | |||
763 | 265 | if member.name == '/.': | ||
764 | 266 | return None | ||
765 | 267 | |||
766 | 268 | i = path.find(self.dir) | ||
767 | 269 | assert(i == 0) | ||
768 | 270 | |||
769 | 271 | # remove the temporary directory elements | ||
770 | 272 | # (+1 for the os.sep we added above) | ||
771 | 273 | member.name = path[len(self.dir)+1:] | ||
772 | 274 | |||
773 | 275 | return member | ||
774 | 276 | |||
775 | 277 | def create_archive(self, name): | ||
776 | 278 | ''' | ||
777 | 279 | Create an archive with the specified name from the UpdateTree | ||
778 | 280 | object. Also creates a fake signature file alongside the archive | ||
779 | 281 | file since this is currently required by the upgrader (although | ||
780 | 282 | it is not validated). | ||
781 | 283 | |||
782 | 284 | :param name: name of tarfile. | ||
783 | 285 | :param name: full path to xz archive to create. | ||
784 | 286 | :return full path to tar file with name @name. | ||
785 | 287 | ''' | ||
786 | 288 | |||
787 | 289 | tar_path = os.path.join(self.tmp_dir, name) | ||
788 | 290 | tar = tarfile.open(tar_path, 'w:xz') | ||
789 | 291 | |||
790 | 292 | # We can't just add recursively since that would attempt to add | ||
791 | 293 | # the parent directory. However, the real update tars don't | ||
792 | 294 | # include that, and attempting to ignore the parent directory | ||
793 | 295 | # results in an empty archive. So, walk the tree and add | ||
794 | 296 | # file-by-file. | ||
795 | 297 | for path, names, files in os.walk(self.dir): | ||
796 | 298 | for file in files: | ||
797 | 299 | full = os.path.join(path, file) | ||
798 | 300 | tar.add(full, recursive=False, filter=self.tar_filter) | ||
799 | 301 | if not files and not names: | ||
800 | 302 | # add (empty) directories | ||
801 | 303 | tar.add(path, recursive=False, filter=self.tar_filter) | ||
802 | 304 | |||
803 | 305 | tar.close() | ||
804 | 306 | |||
805 | 307 | signature = '{}.asc'.format(tar_path) | ||
806 | 308 | |||
807 | 309 | with open(signature, 'w') as fh: | ||
808 | 310 | fh.write('fake signature file') | ||
809 | 311 | |||
810 | 312 | return tar_path | ||
811 | 313 | |||
812 | 314 | |||
813 | 315 | class UbuntuCoreUpgraderTestCase(unittest.TestCase): | ||
814 | 316 | ''' | ||
815 | 317 | Base class for Upgrader tests. | ||
816 | 318 | ''' | ||
817 | 319 | |||
818 | 320 | TARFILE = 'update.tar.xz' | ||
819 | 321 | |||
820 | 322 | # result of last test run. Hack to deal with fact that even if a | ||
821 | 323 | # test fails, unittest still calls .tearDown() (whomever thought | ||
822 | 324 | # that was a good idea...?) | ||
823 | 325 | currentResult = None | ||
824 | 326 | |||
825 | 327 | def setUp(self): | ||
826 | 328 | ''' | ||
827 | 329 | Test setup. | ||
828 | 330 | ''' | ||
829 | 331 | # Create an object to hold the tree that will be converted into | ||
830 | 332 | # an upgrade archive. | ||
831 | 333 | self.update = UpdateTree() | ||
832 | 334 | |||
833 | 335 | # The directory which will have the update archive applied to | ||
834 | 336 | # it. | ||
835 | 337 | self.victim_dir = make_tmp_dir(tag='victim') | ||
836 | 338 | |||
837 | 339 | def tearDown(self): | ||
838 | 340 | ''' | ||
839 | 341 | Test cleanup. | ||
840 | 342 | ''' | ||
841 | 343 | |||
842 | 344 | if not self.currentResult.wasSuccessful(): | ||
843 | 345 | # Do not clean up - the only sane option if a test fails. | ||
844 | 346 | return | ||
845 | 347 | |||
846 | 348 | self.update.destroy() | ||
847 | 349 | self.update = None | ||
848 | 350 | |||
849 | 351 | shutil.rmtree(self.victim_dir) | ||
850 | 352 | self.victim_dir = None | ||
851 | 353 | |||
852 | 354 | def run(self, result=None): | ||
853 | 355 | self.currentResult = result | ||
854 | 356 | unittest.TestCase.run(self, result) | ||
855 | 357 | |||
856 | 358 | |||
857 | 359 | def mock_get_root_partitions_by_label(): | 195 | def mock_get_root_partitions_by_label(): |
858 | 360 | matches = [] | 196 | matches = [] |
859 | 361 | 197 | ||
860 | 362 | 198 | ||
861 | === added file 'ubuntucoreupgrader/tests/utils.py' | |||
862 | --- ubuntucoreupgrader/tests/utils.py 1970-01-01 00:00:00 +0000 | |||
863 | +++ ubuntucoreupgrader/tests/utils.py 2015-03-04 13:53:47 +0000 | |||
864 | @@ -0,0 +1,292 @@ | |||
865 | 1 | # -*- coding: utf-8 -*- | ||
866 | 2 | # -------------------------------------------------------------------- | ||
867 | 3 | # Copyright © 2014-2015 Canonical Ltd. | ||
868 | 4 | # | ||
869 | 5 | # This program is free software: you can redistribute it and/or modify | ||
870 | 6 | # it under the terms of the GNU General Public License as published by | ||
871 | 7 | # the Free Software Foundation, version 3 of the License, or | ||
872 | 8 | # (at your option) any later version. | ||
873 | 9 | # | ||
874 | 10 | # This program is distributed in the hope that it will be useful, | ||
875 | 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
876 | 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
877 | 13 | # GNU General Public License for more details. | ||
878 | 14 | # | ||
879 | 15 | # You should have received a copy of the GNU General Public License | ||
880 | 16 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
881 | 17 | # -------------------------------------------------------------------- | ||
882 | 18 | |||
883 | 19 | import os | ||
884 | 20 | import tempfile | ||
885 | 21 | import tarfile | ||
886 | 22 | import shutil | ||
887 | 23 | import unittest | ||
888 | 24 | |||
889 | 25 | from unittest.mock import patch | ||
890 | 26 | |||
891 | 27 | # file mode to use for creating test directories. | ||
892 | 28 | TEST_DIR_MODE = 0o750 | ||
893 | 29 | |||
894 | 30 | script_name = os.path.basename(__file__) | ||
895 | 31 | |||
896 | 32 | |||
897 | 33 | def make_tmp_dir(tag=None): | ||
898 | 34 | ''' | ||
899 | 35 | Create a temporary directory. | ||
900 | 36 | ''' | ||
901 | 37 | |||
902 | 38 | if tag: | ||
903 | 39 | prefix = '{}-{}-'.format(script_name, tag) | ||
904 | 40 | else: | ||
905 | 41 | prefix = script_name | ||
906 | 42 | |||
907 | 43 | return tempfile.mkdtemp(prefix=prefix) | ||
908 | 44 | |||
909 | 45 | |||
910 | 46 | def append_file(path, contents): | ||
911 | 47 | ''' | ||
912 | 48 | Append to a regular file (create it doesn't exist). | ||
913 | 49 | ''' | ||
914 | 50 | |||
915 | 51 | dirname = os.path.dirname(path) | ||
916 | 52 | os.makedirs(dirname, mode=TEST_DIR_MODE, exist_ok=True) | ||
917 | 53 | |||
918 | 54 | with open(path, 'a') as fh: | ||
919 | 55 | fh.writelines(contents) | ||
920 | 56 | |||
921 | 57 | if not contents.endswith('\n'): | ||
922 | 58 | fh.write('\n') | ||
923 | 59 | |||
924 | 60 | |||
925 | 61 | def create_file(path, contents): | ||
926 | 62 | ''' | ||
927 | 63 | Create a regular file. | ||
928 | 64 | ''' | ||
929 | 65 | append_file(path, contents) | ||
930 | 66 | |||
931 | 67 | |||
932 | 68 | def mock_get_root_partitions_by_label(): | ||
933 | 69 | ''' | ||
934 | 70 | Fake disk partition details for testing. | ||
935 | 71 | ''' | ||
936 | 72 | matches = [] | ||
937 | 73 | |||
938 | 74 | matches.append(('system-a', '/dev/sda3', '/')) | ||
939 | 75 | matches.append(('system-b', '/dev/sda4', '/writable/cache/system')) | ||
940 | 76 | |||
941 | 77 | return matches | ||
942 | 78 | |||
943 | 79 | |||
944 | 80 | def mock_make_mount_private(target): | ||
945 | 81 | ''' | ||
946 | 82 | NOP implementation. | ||
947 | 83 | ''' | ||
948 | 84 | pass | ||
949 | 85 | |||
950 | 86 | |||
951 | 87 | class UpdateTree(): | ||
952 | 88 | ''' | ||
953 | 89 | Representation of a directory tree that will be converted into an | ||
954 | 90 | update archive. | ||
955 | 91 | ''' | ||
956 | 92 | TEST_REMOVED_FILE = 'removed' | ||
957 | 93 | TEST_SYSTEM_DIR = 'system/' | ||
958 | 94 | |||
959 | 95 | def __init__(self): | ||
960 | 96 | |||
961 | 97 | # Directory tree used to construct the tar file from. | ||
962 | 98 | # Also used to hold the TEST_REMOVED_FILE file. | ||
963 | 99 | self.dir = make_tmp_dir(tag='UpdateTree-tar-source') | ||
964 | 100 | |||
965 | 101 | self.removed_file = os.path.join(self.dir, self.TEST_REMOVED_FILE) | ||
966 | 102 | |||
967 | 103 | # Directory to place create/modify files into. | ||
968 | 104 | self.system_dir = os.path.join(self.dir, self.TEST_SYSTEM_DIR) | ||
969 | 105 | |||
970 | 106 | # Directory used to write the generated tarfile to. | ||
971 | 107 | # This directory should also be used to write the command file | ||
972 | 108 | # to. | ||
973 | 109 | self.tmp_dir = make_tmp_dir(tag='UpdateTree-cache') | ||
974 | 110 | |||
975 | 111 | def destroy(self): | ||
976 | 112 | if os.path.exists(self.dir): | ||
977 | 113 | shutil.rmtree(self.dir) | ||
978 | 114 | |||
979 | 115 | if os.path.exists(self.tmp_dir): | ||
980 | 116 | shutil.rmtree(self.tmp_dir) | ||
981 | 117 | |||
982 | 118 | def add_to_removed_file(self, removed_files): | ||
983 | 119 | ''' | ||
984 | 120 | Add the specified list of files to the removed file. | ||
985 | 121 | |||
986 | 122 | The 'removed' file is simply a file with a well-known name that | ||
987 | 123 | contains a list of files (one per line) to be removed from a | ||
988 | 124 | system before the rest of the update archive is unpacked. | ||
989 | 125 | |||
990 | 126 | :param removed_files: list of file names to add to the removed file. | ||
991 | 127 | |||
992 | 128 | ''' | ||
993 | 129 | # all files listed in the removed list must be system files | ||
994 | 130 | final = list(map(lambda a: | ||
995 | 131 | '{}{}'.format(self.TEST_SYSTEM_DIR, a), removed_files)) | ||
996 | 132 | |||
997 | 133 | contents = "".join(final) | ||
998 | 134 | append_file(self.removed_file, contents) | ||
999 | 135 | |||
1000 | 136 | def tar_filter(self, member): | ||
1001 | 137 | ''' | ||
1002 | 138 | Function to filter the tarinfo members before creating the | ||
1003 | 139 | archive. | ||
1004 | 140 | ''' | ||
1005 | 141 | # members are created with relative paths (no leading slash) | ||
1006 | 142 | path = os.sep + member.name | ||
1007 | 143 | |||
1008 | 144 | if member.name == '/.': | ||
1009 | 145 | return None | ||
1010 | 146 | |||
1011 | 147 | i = path.find(self.dir) | ||
1012 | 148 | assert(i == 0) | ||
1013 | 149 | |||
1014 | 150 | # remove the temporary directory elements | ||
1015 | 151 | # (+1 for the os.sep we added above) | ||
1016 | 152 | member.name = path[len(self.dir)+1:] | ||
1017 | 153 | |||
1018 | 154 | return member | ||
1019 | 155 | |||
1020 | 156 | def create_archive(self, name): | ||
1021 | 157 | ''' | ||
1022 | 158 | Create an archive with the specified name from the UpdateTree | ||
1023 | 159 | object. Also creates a fake signature file alongside the archive | ||
1024 | 160 | file since this is currently required by the upgrader (although | ||
1025 | 161 | it is not validated). | ||
1026 | 162 | |||
1027 | 163 | :param name: name of tarfile. | ||
1028 | 164 | :param name: full path to xz archive to create. | ||
1029 | 165 | :return full path to tar file with name @name. | ||
1030 | 166 | ''' | ||
1031 | 167 | |||
1032 | 168 | self.tar_path = os.path.join(self.tmp_dir, name) | ||
1033 | 169 | tar = tarfile.open(self.tar_path, 'w:xz') | ||
1034 | 170 | |||
1035 | 171 | # We can't just add recursively since that would attempt to add | ||
1036 | 172 | # the parent directory. However, the real update tars don't | ||
1037 | 173 | # include that, and attempting to ignore the parent directory | ||
1038 | 174 | # results in an empty archive. So, walk the tree and add | ||
1039 | 175 | # file-by-file. | ||
1040 | 176 | for path, names, files in os.walk(self.dir): | ||
1041 | 177 | for file in files: | ||
1042 | 178 | full = os.path.join(path, file) | ||
1043 | 179 | tar.add(full, recursive=False, filter=self.tar_filter) | ||
1044 | 180 | if not files and not names: | ||
1045 | 181 | # add (empty) directories | ||
1046 | 182 | tar.add(path, recursive=False, filter=self.tar_filter) | ||
1047 | 183 | |||
1048 | 184 | tar.close() | ||
1049 | 185 | |||
1050 | 186 | signature = '{}.asc'.format(self.tar_path) | ||
1051 | 187 | |||
1052 | 188 | with open(signature, 'w') as fh: | ||
1053 | 189 | fh.write('fake signature file') | ||
1054 | 190 | |||
1055 | 191 | return self.tar_path | ||
1056 | 192 | |||
1057 | 193 | |||
1058 | 194 | class UbuntuCoreUpgraderTestCase(unittest.TestCase): | ||
1059 | 195 | ''' | ||
1060 | 196 | Base class for Upgrader tests. | ||
1061 | 197 | |||
1062 | 198 | Most of the tests follow a standard pattern: | ||
1063 | 199 | |||
1064 | 200 | 1) Create an UpdateTree object: | ||
1065 | 201 | |||
1066 | 202 | update = UpdateTree() | ||
1067 | 203 | |||
1068 | 204 | This creates 2 temporary directories: | ||
1069 | 205 | |||
1070 | 206 | - self.system dir: Used as to generate an update archive from. | ||
1071 | 207 | |||
1072 | 208 | - self.tmp_dir: Used to write the generated archive file to. The | ||
1073 | 209 | intention is that this directory should also be used to hold | ||
1074 | 210 | the command file. | ||
1075 | 211 | |||
1076 | 212 | 2) Removal tests call update.add_to_removed_file(file) to add a | ||
1077 | 213 | particular file to the removals file in the update archive. | ||
1078 | 214 | |||
1079 | 215 | 3) Create/Modify tests create files below update.system_dir. | ||
1080 | 216 | |||
1081 | 217 | 4) Create an update archive (which includes the removals file | ||
1082 | 218 | and all files below update.system_dir): | ||
1083 | 219 | |||
1084 | 220 | archive = update.create_archive(self.TARFILE) | ||
1085 | 221 | |||
1086 | 222 | 5) Create a command file (which tells the upgrader what to do | ||
1087 | 223 | and which archive files to apply): | ||
1088 | 224 | |||
1089 | 225 | make_command_file(...) | ||
1090 | 226 | |||
1091 | 227 | 6) Create a victim directory. This is a temporary directory where | ||
1092 | 228 | the upgrade will happen. | ||
1093 | 229 | |||
1094 | 230 | 7) Start the upgrade: | ||
1095 | 231 | |||
1096 | 232 | call_upgrader(...) | ||
1097 | 233 | |||
1098 | 234 | 8) Perform checks on the victim directory to ensure that upgrade | ||
1099 | 235 | did what was expected. | ||
1100 | 236 | |||
1101 | 237 | ''' | ||
1102 | 238 | |||
1103 | 239 | TARFILE = 'update.tar.xz' | ||
1104 | 240 | |||
1105 | 241 | # result of last test run. Hack to deal with fact that even if a | ||
1106 | 242 | # test fails, unittest still calls .tearDown() (whomever thought | ||
1107 | 243 | # that was a good idea...?) | ||
1108 | 244 | currentResult = None | ||
1109 | 245 | |||
1110 | 246 | def setUp(self): | ||
1111 | 247 | ''' | ||
1112 | 248 | Test setup. | ||
1113 | 249 | ''' | ||
1114 | 250 | # Create an object to hold the tree that will be converted into | ||
1115 | 251 | # an upgrade archive. | ||
1116 | 252 | self.update = UpdateTree() | ||
1117 | 253 | |||
1118 | 254 | # The directory which will have the update archive applied to | ||
1119 | 255 | # it. | ||
1120 | 256 | self.victim_dir = make_tmp_dir(tag='victim') | ||
1121 | 257 | |||
1122 | 258 | self.patch_get_root_partitions_by_label = \ | ||
1123 | 259 | patch('ubuntucoreupgrader.upgrader.get_root_partitions_by_label', | ||
1124 | 260 | mock_get_root_partitions_by_label) | ||
1125 | 261 | |||
1126 | 262 | self.patch_make_mount_private = \ | ||
1127 | 263 | patch('ubuntucoreupgrader.upgrader.make_mount_private', | ||
1128 | 264 | mock_make_mount_private) | ||
1129 | 265 | |||
1130 | 266 | self.mock_get_root_partitions_by_label = \ | ||
1131 | 267 | self.patch_get_root_partitions_by_label.start() | ||
1132 | 268 | |||
1133 | 269 | self.mock_make_mount_private = \ | ||
1134 | 270 | self.patch_make_mount_private.start() | ||
1135 | 271 | |||
1136 | 272 | def tearDown(self): | ||
1137 | 273 | ''' | ||
1138 | 274 | Test cleanup. | ||
1139 | 275 | ''' | ||
1140 | 276 | |||
1141 | 277 | if not self.currentResult.wasSuccessful(): | ||
1142 | 278 | # Do not clean up - the only sane option if a test fails. | ||
1143 | 279 | return | ||
1144 | 280 | |||
1145 | 281 | self.update.destroy() | ||
1146 | 282 | self.update = None | ||
1147 | 283 | |||
1148 | 284 | shutil.rmtree(self.victim_dir) | ||
1149 | 285 | self.victim_dir = None | ||
1150 | 286 | |||
1151 | 287 | self.patch_get_root_partitions_by_label.stop() | ||
1152 | 288 | self.patch_make_mount_private.stop() | ||
1153 | 289 | |||
1154 | 290 | def run(self, result=None): | ||
1155 | 291 | self.currentResult = result | ||
1156 | 292 | unittest.TestCase.run(self, result) | ||
1157 | 0 | 293 | ||
1158 | === modified file 'ubuntucoreupgrader/upgrader.py' | |||
1159 | --- ubuntucoreupgrader/upgrader.py 2015-03-04 11:27:28 +0000 | |||
1160 | +++ ubuntucoreupgrader/upgrader.py 2015-03-04 13:53:47 +0000 | |||
1161 | @@ -1455,7 +1455,13 @@ | |||
1162 | 1455 | tar.extract(path=tmpdir, member=tar.getmember(file)) | 1455 | tar.extract(path=tmpdir, member=tar.getmember(file)) |
1163 | 1456 | 1456 | ||
1164 | 1457 | path = os.path.join(tmpdir, file) | 1457 | path = os.path.join(tmpdir, file) |
1166 | 1458 | lines = [line.rstrip() for line in open(path, 'r')] | 1458 | |
1167 | 1459 | lines = [] | ||
1168 | 1460 | |||
1169 | 1461 | with open(path, 'r') as f: | ||
1170 | 1462 | lines = f.readlines() | ||
1171 | 1463 | |||
1172 | 1464 | lines = [line.rstrip() for line in lines] | ||
1173 | 1459 | 1465 | ||
1174 | 1460 | shutil.rmtree(tmpdir) | 1466 | shutil.rmtree(tmpdir) |
1175 | 1461 | 1467 |