Status: | Merged | ||||
---|---|---|---|---|---|
Merged at revision: | 354 | ||||
Proposed branch: | lp:~cjwatson/click/libclick | ||||
Merge into: | lp:click | ||||
Diff against target: |
9053 lines (+5629/-2337) 52 files modified
.bzrignore (+14/-0) Makefile.am (+1/-1) click/Makefile.am (+2/-0) click/commands/desktophook.py (+5/-4) click/commands/hook.py (+16/-8) click/commands/info.py (+9/-6) click/commands/install.py (+6/-2) click/commands/list.py (+13/-10) click/commands/pkgdir.py (+8/-7) click/commands/register.py (+13/-5) click/commands/unregister.py (+10/-5) click/database.py (+0/-323) click/hooks.py (+0/-438) click/install.py (+14/-11) click/osextras.py (+9/-1) click/paths.py.in (+0/-2) click/query.py (+0/-43) click/tests/Makefile.am (+10/-0) click/tests/__init__.py (+39/-0) click/tests/config.py.in (+20/-0) click/tests/gimock.py (+499/-0) click/tests/gimock_types.py (+89/-0) click/tests/helpers.py (+35/-6) click/tests/preload.h (+110/-0) click/tests/test_database.py (+283/-219) click/tests/test_hooks.py (+846/-713) click/tests/test_install.py (+26/-17) click/tests/test_osextras.py (+82/-40) click/tests/test_query.py (+52/-0) click/tests/test_user.py (+151/-129) click/user.py (+0/-344) configure.ac (+44/-0) debian/changelog (+4/-0) debian/control (+38/-2) debian/gir1.2-click-0.4.install (+1/-0) debian/libclick-0.4-0.install (+1/-0) debian/libclick-0.4-0.symbols (+87/-0) debian/libclick-0.4-dev.install (+4/-0) debian/rules (+9/-1) lib/Makefile.am (+1/-0) lib/click/Makefile.am (+98/-0) lib/click/click-0.4.pc.in (+27/-0) lib/click/click.sym (+85/-0) lib/click/database.vala (+602/-0) lib/click/hooks.vala (+1169/-0) lib/click/osextras.vala (+213/-0) lib/click/paths.vala.in (+46/-0) lib/click/posix-extra.vapi (+48/-0) lib/click/query.vala (+58/-0) lib/click/user.vala (+677/-0) lib/click/valac-wrapper.in (+51/-0) run-click (+4/-0) |
||||
To merge this branch: | bzr merge lp:~cjwatson/click/libclick | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Thomas Voß | Pending | ||
Review via email: mp+209105@code.launchpad.net |
Commit message
Description of the change
Hi Thomas. You volunteered your services as a reviewer. I still have a good deal of proofreading and testing to do on this, but I think it's reached the point where it's worth another reviewer having a look in parallel.
This is the first (and most probably largest) chunk of libclick. The core of it is a reasonably literal translation of several of click's Python modules to Vala. This part is fairly straightforward, though I took as much care as I could to keep the public ABI down to only what I'm prepared to keep stable; Vala helped but I couldn't get it to stop exposing some internal methods, so I resorted to libtool -export-symbols.
The really difficult bit was getting the test suite going (and I'd have had essentially the same issues with C). I wanted to satisfy the following constraints:
* Don't rewrite the test suite at the same time as the code. In fact I'm quite happy with the test suite staying in Python permanently, but I definitely wanted to be able to run the existing test suite over the new code to validate the translation, with only relatively minor per-test changes.
* Preserve the ability to insert mock functions.
* Don't require relinking the library to run tests against it. This is a popular strategy for inserting mock functions in C, but I'm not comfortable with this level of interference with the code under test.
After a good deal of experimentation, I managed to get an initial version of this working, by generating LD_PRELOADable objects on the fly, using gobject-
- 352. By Colin Watson
-
Keep to <80 columns.
- 353. By Colin Watson
-
Fix bug in Vala conversion of find_on_path: only regular files should count.
- 354. By Colin Watson
-
Remove unnecessary PosixExtra versions of setgrent and endgrent.
- 355. By Colin Watson
-
Fix infinite loop if Vala version of find_package_
directory reaches "/". - 356. By Colin Watson
-
lib/click/
database. vala: Clear errno before calling getpwnam so that we can detect errors. (We already did this elsewhere.) - 357. By Colin Watson
-
lib/click/
user.vala: Might as well use our "exists" helper here too. - 358. By Colin Watson
-
Use has_package_name in User.is_removable for brevity and to be closer to the original Python code.
- 359. By Colin Watson
-
Simplify Hooks.open_all slightly.
- 360. By Colin Watson
-
Relink library if lib/click/click.sym changes.
Colin Watson (cjwatson) wrote : | # |
- 361. By Colin Watson
-
merge trunk
- 362. By Colin Watson
-
Hook Exec lines must be run using /bin/sh; Shell.parse_argv isn't enough, as Exec may contain more than just a simple command.
- 363. By Colin Watson
-
Add a run-click script to run bin/click from the build tree; useful during development.
- 364. By Colin Watson
-
merge trunk
- 365. By Colin Watson
-
When creating a User, implicitly create a DB if none was provided. This is convenient for the common case of quick lookups in clients.
- 366. By Colin Watson
-
Remove "#include <gee.h>" from our external header file.
- 367. By Colin Watson
-
libclick-0.4-dev needs to depend on libglib2.0-dev for <glib.h> and <glib-object.h>.
Colin Watson (cjwatson) wrote : | # |
I chatted with Thomas about this today. He doesn't think he's going to want major rework, although would like to carry on reviewing. We agreed that I would go ahead and land this anyway (since libclick is blocking other things, and it would be good to try to squeeze it in before Qt 5.2 breaks landing for however many days), and if he finds issues that I need to bump soname for then so be it.
(Let me know if anything here is inaccurate.)
Preview Diff
1 | === modified file '.bzrignore' |
2 | --- .bzrignore 2014-03-05 16:36:20 +0000 |
3 | +++ .bzrignore 2014-03-06 07:04:44 +0000 |
4 | @@ -27,6 +27,8 @@ |
5 | dist |
6 | .tox |
7 | click/paths.py |
8 | +click/tests/config.py |
9 | +click/tests/preload.gir |
10 | build-aux/compile |
11 | build-aux/config.guess |
12 | build-aux/config.rpath |
13 | @@ -44,6 +46,9 @@ |
14 | debian/click |
15 | debian/click-doc |
16 | debian/files |
17 | +debian/gir1.2-click-0.4 |
18 | +debian/libclick-0.4-0 |
19 | +debian/libclick-0.4-dev |
20 | debian/packagekit-plugin-click |
21 | debian/python3-click |
22 | debian/tmp |
23 | @@ -51,6 +56,15 @@ |
24 | init/systemd/click-user-hooks.service |
25 | init/upstart/click-system-hooks.conf |
26 | init/upstart/click-user-hooks.conf |
27 | +lib/click/*.c |
28 | +lib/click/*.gir |
29 | +lib/click/*.typelib |
30 | +lib/click/click.h |
31 | +lib/click/click-0.4.pc |
32 | +lib/click/click-0.4.vapi |
33 | +lib/click/libclick_0_4_la_vala.stamp |
34 | +lib/click/paths.vala |
35 | +lib/click/valac-wrapper |
36 | m4/* |
37 | pk-plugin/com.ubuntu.click.policy |
38 | po/.intltool-merge-cache |
39 | |
40 | === modified file 'Makefile.am' |
41 | --- Makefile.am 2014-03-05 16:36:20 +0000 |
42 | +++ Makefile.am 2014-03-06 07:04:44 +0000 |
43 | @@ -1,4 +1,4 @@ |
44 | -SUBDIRS = click conf debhelper init po preload schroot |
45 | +SUBDIRS = lib preload click conf debhelper init po schroot |
46 | if PACKAGEKIT |
47 | SUBDIRS += pk-plugin |
48 | endif |
49 | |
50 | === modified file 'click/Makefile.am' |
51 | --- click/Makefile.am 2013-09-03 12:50:11 +0000 |
52 | +++ click/Makefile.am 2014-03-06 07:04:44 +0000 |
53 | @@ -1,3 +1,5 @@ |
54 | +SUBDIRS = tests |
55 | + |
56 | noinst_SCRIPTS = paths.py |
57 | CLEANFILES = $(noinst_SCRIPTS) |
58 | |
59 | |
60 | === modified file 'click/commands/desktophook.py' |
61 | --- click/commands/desktophook.py 2013-09-30 09:14:02 +0000 |
62 | +++ click/commands/desktophook.py 2014-03-06 07:04:44 +0000 |
63 | @@ -23,8 +23,9 @@ |
64 | from optparse import OptionParser |
65 | import os |
66 | |
67 | +from gi.repository import Click |
68 | + |
69 | from click import osextras |
70 | -from click.query import find_package_directory |
71 | |
72 | |
73 | COMMENT = \ |
74 | @@ -71,7 +72,7 @@ |
75 | |
76 | def read_hooks_for(path, package, app_name): |
77 | try: |
78 | - directory = find_package_directory(path) |
79 | + directory = Click.find_package_directory(path) |
80 | manifest_path = os.path.join( |
81 | directory, ".click", "info", "%s.manifest" % package) |
82 | with io.open(manifest_path, encoding="UTF-8") as manifest: |
83 | @@ -111,10 +112,10 @@ |
84 | # TODO: This is a very crude .desktop file mangler; we should instead |
85 | # implement proper (de)serialisation. |
86 | def write_desktop_file(target_path, source_path, profile): |
87 | - osextras.ensuredir(os.path.dirname(target_path)) |
88 | + Click.ensuredir(os.path.dirname(target_path)) |
89 | with io.open(source_path, encoding="UTF-8") as source, \ |
90 | io.open(target_path, "w", encoding="UTF-8") as target: |
91 | - source_dir = find_package_directory(source_path) |
92 | + source_dir = Click.find_package_directory(source_path) |
93 | written_comment = False |
94 | seen_path = False |
95 | for line in source: |
96 | |
97 | === modified file 'click/commands/hook.py' |
98 | --- click/commands/hook.py 2013-09-20 11:07:55 +0000 |
99 | +++ click/commands/hook.py 2014-03-06 07:04:44 +0000 |
100 | @@ -20,8 +20,7 @@ |
101 | from optparse import OptionParser |
102 | from textwrap import dedent |
103 | |
104 | -from click.database import ClickDB |
105 | -from click.hooks import ClickHook, run_system_hooks, run_user_hooks |
106 | +from gi.repository import Click |
107 | |
108 | |
109 | per_hook_subcommands = { |
110 | @@ -54,16 +53,25 @@ |
111 | if subcommand in per_hook_subcommands: |
112 | if len(args) < 2: |
113 | parser.error("need hook name") |
114 | - db = ClickDB(options.root) |
115 | + db = Click.DB() |
116 | + db.read() |
117 | + if options.root is not None: |
118 | + db.add(options.root) |
119 | name = args[1] |
120 | - hook = ClickHook.open(db, name) |
121 | + hook = Click.Hook.open(db, name) |
122 | getattr(hook, per_hook_subcommands[subcommand])() |
123 | elif subcommand == "run-system": |
124 | - db = ClickDB(options.root) |
125 | - run_system_hooks(db) |
126 | + db = Click.DB() |
127 | + db.read() |
128 | + if options.root is not None: |
129 | + db.add(options.root) |
130 | + Click.run_system_hooks(db) |
131 | elif subcommand == "run-user": |
132 | - db = ClickDB(options.root) |
133 | - run_user_hooks(db, user=options.user) |
134 | + db = Click.DB() |
135 | + db.read() |
136 | + if options.root is not None: |
137 | + db.add(options.root) |
138 | + Click.run_user_hooks(db, user_name=options.user) |
139 | else: |
140 | parser.error( |
141 | "unknown subcommand '%s' (known: install, remove, run-system," |
142 | |
143 | === modified file 'click/commands/info.py' |
144 | --- click/commands/info.py 2013-09-30 09:20:16 +0000 |
145 | +++ click/commands/info.py 2014-03-06 07:04:44 +0000 |
146 | @@ -24,18 +24,21 @@ |
147 | import os |
148 | import sys |
149 | |
150 | -from click.database import ClickDB |
151 | +from gi.repository import Click |
152 | + |
153 | from click.install import DebFile |
154 | -from click.user import ClickUser |
155 | |
156 | |
157 | def get_manifest(options, arg): |
158 | if "/" not in arg: |
159 | - db = ClickDB(options.root) |
160 | - registry = ClickUser(db, user=options.user) |
161 | - if arg in registry: |
162 | + db = Click.DB() |
163 | + db.read() |
164 | + if options.root is not None: |
165 | + db.add(options.root) |
166 | + registry = Click.User.for_user(db, name=options.user) |
167 | + if registry.has_package_name(arg): |
168 | manifest_path = os.path.join( |
169 | - registry.path(arg), ".click", "info", "%s.manifest" % arg) |
170 | + registry.get_path(arg), ".click", "info", "%s.manifest" % arg) |
171 | with io.open(manifest_path, encoding="UTF-8") as manifest: |
172 | return json.load(manifest) |
173 | |
174 | |
175 | === modified file 'click/commands/install.py' |
176 | --- click/commands/install.py 2013-10-22 10:44:43 +0000 |
177 | +++ click/commands/install.py 2014-03-06 07:04:44 +0000 |
178 | @@ -21,7 +21,8 @@ |
179 | import sys |
180 | from textwrap import dedent |
181 | |
182 | -from click.database import ClickDB |
183 | +from gi.repository import Click |
184 | + |
185 | from click.install import ClickInstaller, ClickInstallerError |
186 | |
187 | |
188 | @@ -45,7 +46,10 @@ |
189 | options, args = parser.parse_args(argv) |
190 | if len(args) < 1: |
191 | parser.error("need package file name") |
192 | - db = ClickDB(options.root) |
193 | + db = Click.DB() |
194 | + db.read() |
195 | + if options.root is not None: |
196 | + db.add(options.root) |
197 | package_path = args[0] |
198 | installer = ClickInstaller(db, options.force_missing_framework) |
199 | try: |
200 | |
201 | === modified file 'click/commands/list.py' |
202 | --- click/commands/list.py 2013-09-30 09:14:02 +0000 |
203 | +++ click/commands/list.py 2014-03-06 07:04:44 +0000 |
204 | @@ -23,22 +23,25 @@ |
205 | import os |
206 | import sys |
207 | |
208 | -from click.database import ClickDB |
209 | -from click.user import ClickUser |
210 | +from gi.repository import Click |
211 | |
212 | |
213 | def list_packages(options): |
214 | - db = ClickDB(options.root) |
215 | + db = Click.DB() |
216 | + db.read() |
217 | + if options.root is not None: |
218 | + db.add(options.root) |
219 | if options.all: |
220 | - for package, version, path, writeable in \ |
221 | - db.packages(all_versions=True): |
222 | - yield package, version, path, writeable |
223 | + for inst in db.get_packages(all_versions=True): |
224 | + yield ( |
225 | + inst.props.package, inst.props.version, inst.props.path, |
226 | + inst.props.writeable) |
227 | else: |
228 | - registry = ClickUser(db, user=options.user) |
229 | - for package, version in sorted(registry.items()): |
230 | + registry = Click.User.for_user(db, name=options.user) |
231 | + for package in sorted(registry.get_package_names()): |
232 | yield ( |
233 | - package, version, registry.path(package), |
234 | - registry.removable(package)) |
235 | + package, registry.get_version(package), |
236 | + registry.get_path(package), registry.is_removable(package)) |
237 | |
238 | |
239 | def run(argv): |
240 | |
241 | === modified file 'click/commands/pkgdir.py' |
242 | --- click/commands/pkgdir.py 2013-09-16 12:44:40 +0000 |
243 | +++ click/commands/pkgdir.py 2014-03-06 07:04:44 +0000 |
244 | @@ -21,9 +21,7 @@ |
245 | from optparse import OptionParser |
246 | import sys |
247 | |
248 | -from click.database import ClickDB |
249 | -from click.query import find_package_directory |
250 | -from click.user import ClickUser |
251 | +from gi.repository import Click |
252 | |
253 | |
254 | def run(argv): |
255 | @@ -39,12 +37,15 @@ |
256 | parser.error("need package name") |
257 | try: |
258 | if "/" in args[0]: |
259 | - print(find_package_directory(args[0])) |
260 | + print(Click.find_package_directory(args[0])) |
261 | else: |
262 | - db = ClickDB(options.root) |
263 | + db = Click.DB() |
264 | + db.read() |
265 | + if options.root is not None: |
266 | + db.add(options.root) |
267 | package_name = args[0] |
268 | - registry = ClickUser(db, user=options.user) |
269 | - print(registry.path(package_name)) |
270 | + registry = Click.User(db, name=options.user) |
271 | + print(registry.get_path(package_name)) |
272 | except Exception as e: |
273 | print(e, file=sys.stderr) |
274 | return 1 |
275 | |
276 | === modified file 'click/commands/register.py' |
277 | --- click/commands/register.py 2013-09-30 09:14:02 +0000 |
278 | +++ click/commands/register.py 2014-03-06 07:04:44 +0000 |
279 | @@ -19,8 +19,7 @@ |
280 | |
281 | from optparse import OptionParser |
282 | |
283 | -from click.database import ClickDB |
284 | -from click.user import ClickUser |
285 | +from gi.repository import Click, GLib |
286 | |
287 | |
288 | def run(argv): |
289 | @@ -38,11 +37,20 @@ |
290 | parser.error("need package name") |
291 | if len(args) < 2: |
292 | parser.error("need version") |
293 | - db = ClickDB(options.root) |
294 | + db = Click.DB() |
295 | + db.read() |
296 | + if options.root is not None: |
297 | + db.add(options.root) |
298 | package = args[0] |
299 | version = args[1] |
300 | - registry = ClickUser(db, user=options.user, all_users=options.all_users) |
301 | - old_version = registry.get(package) |
302 | + if options.all_users: |
303 | + registry = Click.User.for_all_users(db) |
304 | + else: |
305 | + registry = Click.User.for_user(db, name=options.user) |
306 | + try: |
307 | + old_version = registry.get_version(package) |
308 | + except GLib.GError: |
309 | + old_version = None |
310 | registry.set_version(package, version) |
311 | if old_version is not None: |
312 | db.maybe_remove(package, old_version) |
313 | |
314 | === modified file 'click/commands/unregister.py' |
315 | --- click/commands/unregister.py 2013-09-30 09:14:02 +0000 |
316 | +++ click/commands/unregister.py 2014-03-06 07:04:44 +0000 |
317 | @@ -21,8 +21,7 @@ |
318 | import os |
319 | import sys |
320 | |
321 | -from click.database import ClickDB |
322 | -from click.user import ClickUser |
323 | +from gi.repository import Click |
324 | |
325 | |
326 | def run(argv): |
327 | @@ -44,10 +43,16 @@ |
328 | "remove packages from disk") |
329 | if options.user is None and "SUDO_USER" in os.environ: |
330 | options.user = os.environ["SUDO_USER"] |
331 | - db = ClickDB(options.root) |
332 | + db = Click.DB() |
333 | + db.read() |
334 | + if options.root is not None: |
335 | + db.add(options.root) |
336 | package = args[0] |
337 | - registry = ClickUser(db, user=options.user, all_users=options.all_users) |
338 | - old_version = registry[package] |
339 | + if options.all_users: |
340 | + registry = Click.User.for_all_users(db) |
341 | + else: |
342 | + registry = Click.User.for_user(db, name=options.user) |
343 | + old_version = registry.get_version(package) |
344 | if len(args) >= 2 and old_version != args[1]: |
345 | print( |
346 | "Not removing %s %s; expected version %s" % |
347 | |
348 | === removed file 'click/database.py' |
349 | --- click/database.py 2013-12-10 14:33:19 +0000 |
350 | +++ click/database.py 1970-01-01 00:00:00 +0000 |
351 | @@ -1,323 +0,0 @@ |
352 | -# Copyright (C) 2013 Canonical Ltd. |
353 | -# Author: Colin Watson <cjwatson@ubuntu.com> |
354 | - |
355 | -# This program is free software: you can redistribute it and/or modify |
356 | -# it under the terms of the GNU General Public License as published by |
357 | -# the Free Software Foundation; version 3 of the License. |
358 | -# |
359 | -# This program is distributed in the hope that it will be useful, |
360 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
361 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
362 | -# GNU General Public License for more details. |
363 | -# |
364 | -# You should have received a copy of the GNU General Public License |
365 | -# along with this program. If not, see <http://www.gnu.org/licenses/>. |
366 | - |
367 | -"""Click databases.""" |
368 | - |
369 | -from __future__ import print_function |
370 | - |
371 | -__metaclass__ = type |
372 | -__all__ = [ |
373 | - "ClickDB", |
374 | - ] |
375 | - |
376 | - |
377 | -from collections import Sequence, defaultdict |
378 | -import io |
379 | -import json |
380 | -import os |
381 | -import pwd |
382 | -import shutil |
383 | -import subprocess |
384 | -import sys |
385 | - |
386 | -try: |
387 | - from configparser import Error as ConfigParserError |
388 | - if sys.version < "3.2": |
389 | - from configparser import SafeConfigParser as ConfigParser |
390 | - else: |
391 | - from configparser import ConfigParser |
392 | -except ImportError: |
393 | - from ConfigParser import Error as ConfigParserError |
394 | - from ConfigParser import SafeConfigParser as ConfigParser |
395 | - |
396 | -from click import osextras |
397 | -from click.paths import db_dir |
398 | - |
399 | - |
400 | -class ClickSingleDB: |
401 | - def __init__(self, root, master_db): |
402 | - self.root = root |
403 | - self.master_db = master_db |
404 | - |
405 | - def path(self, package, version): |
406 | - """Look up a package and version in only this database.""" |
407 | - try_path = os.path.join(self.root, package, version) |
408 | - if os.path.exists(try_path): |
409 | - return try_path |
410 | - else: |
411 | - raise KeyError( |
412 | - "%s %s does not exist in %s" % (package, version, self.root)) |
413 | - |
414 | - def packages(self, all_versions=False): |
415 | - """Return all current package versions in only this database. |
416 | - |
417 | - If all_versions=True, return all versions, not just current ones. |
418 | - """ |
419 | - for package in sorted(osextras.listdir_force(self.root)): |
420 | - if package == ".click": |
421 | - continue |
422 | - if all_versions: |
423 | - package_path = os.path.join(self.root, package) |
424 | - for version in sorted(osextras.listdir_force(package_path)): |
425 | - version_path = os.path.join(package_path, version) |
426 | - if (os.path.islink(version_path) or |
427 | - not os.path.isdir(version_path)): |
428 | - continue |
429 | - yield package, version, version_path |
430 | - else: |
431 | - current_path = os.path.join(self.root, package, "current") |
432 | - if os.path.islink(current_path): |
433 | - version = os.readlink(current_path) |
434 | - if "/" not in version: |
435 | - yield package, version, current_path |
436 | - |
437 | - def _app_running(self, package, app_name, version): |
438 | - app_id = "%s_%s_%s" % (package, app_name, version) |
439 | - command = ["upstart-app-pid", app_id] |
440 | - with open("/dev/null", "w") as devnull: |
441 | - return subprocess.call(command, stdout=devnull) == 0 |
442 | - |
443 | - def _any_app_running(self, package, version): |
444 | - if not osextras.find_on_path("upstart-app-pid"): |
445 | - return False |
446 | - manifest_path = os.path.join( |
447 | - self.path(package, version), ".click", "info", |
448 | - "%s.manifest" % package) |
449 | - try: |
450 | - with io.open(manifest_path, encoding="UTF-8") as manifest: |
451 | - manifest_json = json.load(manifest) |
452 | - for app_name in manifest_json.get("hooks", {}): |
453 | - if self._app_running(package, app_name, version): |
454 | - return True |
455 | - except Exception: |
456 | - pass |
457 | - return False |
458 | - |
459 | - def _remove_unless_running(self, package, version, verbose=False): |
460 | - # Circular imports. |
461 | - from click.hooks import package_remove_hooks |
462 | - from click.user import ClickUser, GC_IN_USE_USER |
463 | - |
464 | - if self._any_app_running(package, version): |
465 | - gc_in_use_user_db = ClickUser(self.master_db, user=GC_IN_USE_USER) |
466 | - gc_in_use_user_db.set_version(package, version) |
467 | - return |
468 | - |
469 | - version_path = self.path(package, version) |
470 | - if verbose: |
471 | - print("Removing %s" % version_path) |
472 | - package_remove_hooks(self, package, version) |
473 | - shutil.rmtree(version_path, ignore_errors=True) |
474 | - |
475 | - package_path = os.path.join(self.root, package) |
476 | - current_path = os.path.join(package_path, "current") |
477 | - if (os.path.islink(current_path) and |
478 | - os.readlink(current_path) == version): |
479 | - os.unlink(current_path) |
480 | - # TODO: Perhaps we should relink current to the latest remaining |
481 | - # version. However, that requires version comparison, and it's |
482 | - # not clear whether it's worth it given that current is mostly |
483 | - # superseded by user registration. |
484 | - if not os.listdir(package_path): |
485 | - os.rmdir(package_path) |
486 | - |
487 | - def maybe_remove(self, package, version): |
488 | - """Remove a package version if it is not in use. |
489 | - |
490 | - "In use" may mean registered for another user, or running. In the |
491 | - latter case we construct a fake registration so that we can tell the |
492 | - difference later between a package version that was in use at the |
493 | - time of removal and one that was never registered for any user. |
494 | - |
495 | - (This is unfortunately complex, and perhaps some day we can require |
496 | - that installations always have some kind of registration to avoid |
497 | - this complexity.) |
498 | - """ |
499 | - # Circular imports. |
500 | - from click.user import ClickUsers, GC_IN_USE_USER |
501 | - |
502 | - for user_name, user_db in ClickUsers(self.master_db).items(): |
503 | - if user_db.get(package) == version: |
504 | - if user_name == GC_IN_USE_USER: |
505 | - # Previously running; we'll check this again shortly. |
506 | - user_db.remove(package) |
507 | - else: |
508 | - # In use. |
509 | - return |
510 | - |
511 | - self._remove_unless_running(package, version) |
512 | - |
513 | - def gc(self, verbose=True): |
514 | - """Remove package versions with no user registrations. |
515 | - |
516 | - To avoid accidentally removing packages that were installed without |
517 | - ever having a user registration, we only garbage-collect packages |
518 | - that were not removed by ClickSingleDB.maybe_remove due to having a |
519 | - running application at the time. |
520 | - |
521 | - (This is unfortunately complex, and perhaps some day we can require |
522 | - that installations always have some kind of registration to avoid |
523 | - this complexity.) |
524 | - """ |
525 | - # Circular import. |
526 | - from click.user import ClickUser, ClickUsers, GC_IN_USE_USER |
527 | - |
528 | - user_reg = defaultdict(set) |
529 | - gc_in_use = defaultdict(set) |
530 | - for user_name, user_db in ClickUsers(self.master_db).items(): |
531 | - for package, version in user_db.items(): |
532 | - if user_name == GC_IN_USE_USER: |
533 | - gc_in_use[package].add(version) |
534 | - else: |
535 | - user_reg[package].add(version) |
536 | - |
537 | - gc_in_use_user_db = ClickUser(self.master_db, user=GC_IN_USE_USER) |
538 | - for package in sorted(osextras.listdir_force(self.root)): |
539 | - if package == ".click": |
540 | - continue |
541 | - package_path = os.path.join(self.root, package) |
542 | - for version in sorted(osextras.listdir_force(package_path)): |
543 | - if version in user_reg[package]: |
544 | - # In use. |
545 | - continue |
546 | - if version not in gc_in_use[package]: |
547 | - version_path = os.path.join(package_path, version) |
548 | - if verbose: |
549 | - print( |
550 | - "Not removing %s (never registered)." % |
551 | - version_path) |
552 | - continue |
553 | - gc_in_use_user_db.remove(package) |
554 | - self._remove_unless_running(package, version, verbose=verbose) |
555 | - |
556 | - def _clickpkg_paths(self): |
557 | - """Yield all paths which should be owned by clickpkg.""" |
558 | - if os.path.exists(self.root): |
559 | - yield self.root |
560 | - for package in osextras.listdir_force(self.root): |
561 | - if package == ".click": |
562 | - path = os.path.join(self.root, ".click") |
563 | - yield path |
564 | - log_path = os.path.join(path, "log") |
565 | - if os.path.exists(log_path): |
566 | - yield log_path |
567 | - users_path = os.path.join(path, "users") |
568 | - if os.path.exists(users_path): |
569 | - yield users_path |
570 | - else: |
571 | - path = os.path.join(self.root, package) |
572 | - for dirpath, dirnames, filenames in os.walk(path): |
573 | - yield dirpath |
574 | - for dirname in dirnames: |
575 | - dirname_path = os.path.join(dirpath, dirname) |
576 | - if os.path.islink(dirname_path): |
577 | - yield dirname_path |
578 | - for filename in filenames: |
579 | - yield os.path.join(dirpath, filename) |
580 | - |
581 | - def ensure_ownership(self): |
582 | - """Ensure correct ownership of files in the database. |
583 | - |
584 | - On a system that is upgraded by delivering a new system image rather |
585 | - than by package upgrades, it is possible for the clickpkg UID to |
586 | - change. The overlay database must then be adjusted to account for |
587 | - this. |
588 | - """ |
589 | - pw = pwd.getpwnam("clickpkg") |
590 | - try: |
591 | - st = os.stat(self.root) |
592 | - if st.st_uid == pw.pw_uid and st.st_gid == pw.pw_gid: |
593 | - return |
594 | - except OSError: |
595 | - return |
596 | - chown_kwargs = {} |
597 | - if sys.version >= "3.3" and os.chown in os.supports_follow_symlinks: |
598 | - chown_kwargs["follow_symlinks"] = False |
599 | - for path in self._clickpkg_paths(): |
600 | - os.chown(path, pw.pw_uid, pw.pw_gid, **chown_kwargs) |
601 | - |
602 | - |
603 | -class ClickDB(Sequence): |
604 | - def __init__(self, extra_root=None, use_system=True, override_db_dir=None): |
605 | - if override_db_dir is None: |
606 | - override_db_dir = db_dir |
607 | - self._db = [] |
608 | - if use_system: |
609 | - for entry in sorted(osextras.listdir_force(override_db_dir)): |
610 | - if not entry.endswith(".conf"): |
611 | - continue |
612 | - path = os.path.join(override_db_dir, entry) |
613 | - config = ConfigParser() |
614 | - try: |
615 | - config.read(path) |
616 | - root = config.get("Click Database", "root") |
617 | - except ConfigParserError as e: |
618 | - print(e, file=sys.stderr) |
619 | - continue |
620 | - self.add(root) |
621 | - if extra_root is not None: |
622 | - self.add(extra_root) |
623 | - |
624 | - def __getitem__(self, key): |
625 | - return self._db[key] |
626 | - |
627 | - def __len__(self): |
628 | - return len(self._db) |
629 | - |
630 | - def add(self, root): |
631 | - self._db.append(ClickSingleDB(root, self)) |
632 | - |
633 | - @property |
634 | - def overlay(self): |
635 | - """Return the directory where changes should be written.""" |
636 | - return self._db[-1].root |
637 | - |
638 | - def path(self, package, version): |
639 | - """Look up a package and version in all databases.""" |
640 | - for db in reversed(self._db): |
641 | - try: |
642 | - return db.path(package, version) |
643 | - except KeyError: |
644 | - pass |
645 | - else: |
646 | - raise KeyError( |
647 | - "%s %s does not exist in any database" % (package, version)) |
648 | - |
649 | - def packages(self, all_versions=False): |
650 | - """Return current package versions in all databases. |
651 | - |
652 | - If all_versions=True, return all versions, not just current ones. |
653 | - """ |
654 | - seen = set() |
655 | - for db in reversed(self._db): |
656 | - writeable = db is self._db[-1] |
657 | - for package, version, path in \ |
658 | - db.packages(all_versions=all_versions): |
659 | - if all_versions: |
660 | - seen_id = (package, version) |
661 | - else: |
662 | - seen_id = package |
663 | - if seen_id not in seen: |
664 | - yield package, version, path, writeable |
665 | - seen.add(seen_id) |
666 | - |
667 | - def maybe_remove(self, package, version): |
668 | - self._db[-1].maybe_remove(package, version) |
669 | - |
670 | - def gc(self, verbose=True): |
671 | - self._db[-1].gc(verbose=verbose) |
672 | - |
673 | - def ensure_ownership(self): |
674 | - self._db[-1].ensure_ownership() |
675 | |
676 | === removed file 'click/hooks.py' |
677 | --- click/hooks.py 2014-02-19 15:31:55 +0000 |
678 | +++ click/hooks.py 1970-01-01 00:00:00 +0000 |
679 | @@ -1,438 +0,0 @@ |
680 | -# Copyright (C) 2013 Canonical Ltd. |
681 | -# Author: Colin Watson <cjwatson@ubuntu.com> |
682 | - |
683 | -# This program is free software: you can redistribute it and/or modify |
684 | -# it under the terms of the GNU General Public License as published by |
685 | -# the Free Software Foundation; version 3 of the License. |
686 | -# |
687 | -# This program is distributed in the hope that it will be useful, |
688 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
689 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
690 | -# GNU General Public License for more details. |
691 | -# |
692 | -# You should have received a copy of the GNU General Public License |
693 | -# along with this program. If not, see <http://www.gnu.org/licenses/>. |
694 | - |
695 | -"""Click package hooks. |
696 | - |
697 | -See doc/hooks.rst for the draft specification. |
698 | -""" |
699 | - |
700 | -from __future__ import print_function |
701 | - |
702 | -__metaclass__ = type |
703 | -__all__ = [ |
704 | - "ClickHook", |
705 | - "package_install_hooks", |
706 | - "run_system_hooks", |
707 | - "run_user_hooks", |
708 | - ] |
709 | - |
710 | -from functools import partial |
711 | -import grp |
712 | -import io |
713 | -import json |
714 | -import os |
715 | -import pwd |
716 | -import re |
717 | -from string import Formatter |
718 | -import subprocess |
719 | - |
720 | -from debian.deb822 import Deb822 |
721 | - |
722 | -from click import osextras |
723 | -from click.paths import hooks_dir |
724 | -from click.user import ClickUser, ClickUsers |
725 | - |
726 | - |
727 | -def _read_manifest_hooks(db, package, version): |
728 | - if version is None: |
729 | - return {} |
730 | - try: |
731 | - manifest_path = os.path.join( |
732 | - db.path(package, version), ".click", "info", |
733 | - "%s.manifest" % package) |
734 | - with io.open(manifest_path, encoding="UTF-8") as manifest: |
735 | - return json.load(manifest).get("hooks", {}) |
736 | - except (KeyError, IOError): |
737 | - return {} |
738 | - |
739 | - |
740 | -class ClickPatternFormatter(Formatter): |
741 | - """A Formatter that handles simple $-expansions. |
742 | - |
743 | - `${key}` is replaced by the value of the `key` argument; `$$` is |
744 | - replaced by `$`. Any `$` character not followed by `{...}` or `$` is |
745 | - preserved intact. |
746 | - """ |
747 | - _expansion_re = re.compile(r"\$(?:\$|{(.*?)})") |
748 | - |
749 | - def parse(self, format_string): |
750 | - while True: |
751 | - match = self._expansion_re.search(format_string) |
752 | - if match is None: |
753 | - if format_string: |
754 | - yield format_string, None, None, None |
755 | - return |
756 | - start, end = match.span() |
757 | - if format_string[match.start():match.end()] == "$$": |
758 | - yield format_string[:match.start() + 1], None, None, None |
759 | - else: |
760 | - yield format_string[:match.start()], match.group(1), "", None |
761 | - format_string = format_string[match.end():] |
762 | - |
763 | - def get_field(self, field_name, args, kwargs): |
764 | - value = kwargs.get(field_name) |
765 | - if value is None: |
766 | - value = "" |
767 | - return value, field_name |
768 | - |
769 | - def possible_expansion(self, s, format_string, *args, **kwargs): |
770 | - """Check if s is a possible expansion. |
771 | - |
772 | - Any (keyword) arguments have the effect of binding some keys to |
773 | - fixed values; unspecified keys may take any value, and will bind |
774 | - greedily to the longest possible string. |
775 | - |
776 | - If s is a possible expansion, then this method returns a (possibly |
777 | - empty) dictionary mapping all the unspecified keys to their bound |
778 | - values. Otherwise, it returns None. |
779 | - """ |
780 | - ret = {} |
781 | - regex_pieces = [] |
782 | - group_names = [] |
783 | - for literal_text, field_name, format_spec, conversion in \ |
784 | - self.parse(format_string): |
785 | - if literal_text: |
786 | - regex_pieces.append(re.escape(literal_text)) |
787 | - if field_name is not None: |
788 | - if field_name in kwargs: |
789 | - regex_pieces.append(re.escape(kwargs[field_name])) |
790 | - else: |
791 | - regex_pieces.append("(.*)") |
792 | - group_names.append(field_name) |
793 | - match = re.match("^%s$" % "".join(regex_pieces), s) |
794 | - if match is None: |
795 | - return None |
796 | - for group in range(len(group_names)): |
797 | - ret[group_names[group]] = match.group(group + 1) |
798 | - return ret |
799 | - |
800 | - |
801 | -class ClickHook(Deb822): |
802 | - _formatter = ClickPatternFormatter() |
803 | - |
804 | - def __init__(self, db, name, sequence=None, fields=None, encoding="utf-8"): |
805 | - super(ClickHook, self).__init__( |
806 | - sequence=sequence, fields=fields, encoding=encoding) |
807 | - self.db = db |
808 | - self.name = name |
809 | - |
810 | - @classmethod |
811 | - def open(cls, db, name): |
812 | - try: |
813 | - with open(os.path.join(hooks_dir, "%s.hook" % name)) as f: |
814 | - return cls(db, name, f) |
815 | - except IOError: |
816 | - raise KeyError("No click hook '%s' installed" % name) |
817 | - |
818 | - @classmethod |
819 | - def open_all(cls, db, hook_name=None): |
820 | - for entry in osextras.listdir_force(hooks_dir): |
821 | - if not entry.endswith(".hook"): |
822 | - continue |
823 | - try: |
824 | - with open(os.path.join(hooks_dir, entry)) as f: |
825 | - hook = cls(db, entry[:-5], f) |
826 | - if hook_name is None or hook.hook_name == hook_name: |
827 | - yield hook |
828 | - except IOError: |
829 | - pass |
830 | - |
831 | - @property |
832 | - def user_level(self): |
833 | - return self.get("user-level", "no") == "yes" |
834 | - |
835 | - @property |
836 | - def single_version(self): |
837 | - return self.user_level or self.get("single-version", "no") == "yes" |
838 | - |
839 | - @property |
840 | - def hook_name(self): |
841 | - return self.get("hook-name", self.name) |
842 | - |
843 | - def short_app_id(self, package, app_name): |
844 | - # TODO: perhaps this check belongs further up the stack somewhere? |
845 | - if "_" in app_name or "/" in app_name: |
846 | - raise ValueError( |
847 | - "Application name '%s' may not contain _ or / characters" % |
848 | - app_name) |
849 | - return "%s_%s" % (package, app_name) |
850 | - |
851 | - def app_id(self, package, version, app_name): |
852 | - return "%s_%s" % (self.short_app_id(package, app_name), version) |
853 | - |
854 | - def _user_home(self, user): |
855 | - if user is None: |
856 | - return None |
857 | - # TODO: make robust against removed users |
858 | - # TODO: caching |
859 | - return pwd.getpwnam(user).pw_dir |
860 | - |
861 | - def pattern(self, package, version, app_name, user=None): |
862 | - app_id = self.app_id(package, version, app_name) |
863 | - kwargs = { |
864 | - "id": app_id, |
865 | - "user": user, |
866 | - "home": self._user_home(user), |
867 | - } |
868 | - if self.single_version: |
869 | - kwargs["short-id"] = self.short_app_id(package, app_name) |
870 | - return self._formatter.format(self["pattern"], **kwargs).rstrip(os.sep) |
871 | - |
872 | - def _drop_privileges(self, username): |
873 | - if os.geteuid() != 0: |
874 | - return |
875 | - pw = pwd.getpwnam(username) |
876 | - os.setgroups( |
877 | - [g.gr_gid for g in grp.getgrall() if username in g.gr_mem]) |
878 | - # Portability note: this assumes that we have [gs]etres[gu]id, which |
879 | - # is true on Linux but not necessarily elsewhere. If you need to |
880 | - # support something else, there are reasonably standard alternatives |
881 | - # involving other similar calls; see e.g. gnulib/lib/idpriv-drop.c. |
882 | - os.setresgid(pw.pw_gid, pw.pw_gid, pw.pw_gid) |
883 | - os.setresuid(pw.pw_uid, pw.pw_uid, pw.pw_uid) |
884 | - assert os.getresuid() == (pw.pw_uid, pw.pw_uid, pw.pw_uid) |
885 | - assert os.getresgid() == (pw.pw_gid, pw.pw_gid, pw.pw_gid) |
886 | - os.environ["HOME"] = pw.pw_dir |
887 | - os.umask(osextras.get_umask() | 0o002) |
888 | - |
889 | - def _run_commands_user(self, user=None): |
890 | - if self.user_level: |
891 | - return user |
892 | - else: |
893 | - return self["user"] |
894 | - |
895 | - def _run_commands(self, user=None): |
896 | - if "exec" in self: |
897 | - drop_privileges = partial( |
898 | - self._drop_privileges, self._run_commands_user(user=user)) |
899 | - subprocess.check_call( |
900 | - self["exec"], preexec_fn=drop_privileges, shell=True) |
901 | - if self.get("trigger", "no") == "yes": |
902 | - raise NotImplementedError("'Trigger: yes' not yet implemented") |
903 | - |
904 | - def _previous_entries(self, user=None): |
905 | - """Find entries that match the structure of our links.""" |
906 | - link_dir = os.path.dirname(self.pattern("", "", "", user=user)) |
907 | - # TODO: This only works if the app ID only appears, at most, in the |
908 | - # last component of the pattern path. |
909 | - for previous_entry in osextras.listdir_force(link_dir): |
910 | - previous_path = os.path.join(link_dir, previous_entry) |
911 | - previous_exp = self._formatter.possible_expansion( |
912 | - previous_path, self["pattern"], user=user, |
913 | - home=self._user_home(user)) |
914 | - if previous_exp is None or "id" not in previous_exp: |
915 | - continue |
916 | - previous_id = previous_exp["id"] |
917 | - try: |
918 | - previous_package, previous_app_name, previous_version = ( |
919 | - previous_id.split("_", 2)) |
920 | - yield ( |
921 | - previous_path, |
922 | - previous_package, previous_version, previous_app_name) |
923 | - except ValueError: |
924 | - continue |
925 | - |
926 | - def _install_link(self, package, version, app_name, relative_path, |
927 | - user=None, user_db=None): |
928 | - """Install a hook symlink. |
929 | - |
930 | - This should be called with dropped privileges if necessary. |
931 | - """ |
932 | - if self.user_level: |
933 | - target = os.path.join(user_db.path(package), relative_path) |
934 | - else: |
935 | - target = os.path.join( |
936 | - self.db.path(package, version), relative_path) |
937 | - link = self.pattern(package, version, app_name, user=user) |
938 | - if not os.path.islink(link) or os.readlink(link) != target: |
939 | - osextras.ensuredir(os.path.dirname(link)) |
940 | - osextras.symlink_force(target, link) |
941 | - |
942 | - def install_package(self, package, version, app_name, relative_path, |
943 | - user=None): |
944 | - if self.user_level: |
945 | - user_db = ClickUser(self.db, user=user) |
946 | - else: |
947 | - assert user is None |
948 | - |
949 | - # Remove previous versions if necessary. |
950 | - if self.single_version: |
951 | - for path, p_package, p_version, p_app_name in \ |
952 | - self._previous_entries(user=user): |
953 | - if (p_package == package and p_app_name == app_name and |
954 | - p_version != version): |
955 | - osextras.unlink_force(path) |
956 | - |
957 | - if self.user_level: |
958 | - with user_db._dropped_privileges(): |
959 | - self._install_link( |
960 | - package, version, app_name, relative_path, |
961 | - user=user, user_db=user_db) |
962 | - else: |
963 | - self._install_link(package, version, app_name, relative_path) |
964 | - self._run_commands(user=user) |
965 | - |
966 | - def remove_package(self, package, version, app_name, user=None): |
967 | - osextras.unlink_force( |
968 | - self.pattern(package, version, app_name, user=user)) |
969 | - self._run_commands(user=user) |
970 | - |
971 | - def _all_packages(self, user=None): |
972 | - """Return an iterable of all unpacked packages. |
973 | - |
974 | - If running a user-level hook, this returns (package, version, user) |
975 | - for the current version of each package registered for each user, or |
976 | - only for a single user if user is not None. |
977 | - |
978 | - If running a system-level hook, this returns (package, version, |
979 | - None) for each version of each unpacked package. |
980 | - """ |
981 | - if self.user_level: |
982 | - if user is not None: |
983 | - user_db = ClickUser(self.db, user=user) |
984 | - for package, version in user_db.items(): |
985 | - yield package, version, user |
986 | - else: |
987 | - for user_name, user_db in ClickUsers(self.db).items(): |
988 | - if user_name.startswith("@"): |
989 | - continue |
990 | - for package, version in user_db.items(): |
991 | - yield package, version, user_name |
992 | - else: |
993 | - for package, version, _, _ in self.db.packages(): |
994 | - yield package, version, None |
995 | - |
996 | - def _relevant_apps(self, user=None): |
997 | - """Return an iterable of all applications relevant for this hook.""" |
998 | - for package, version, user_name in self._all_packages(user=user): |
999 | - manifest = _read_manifest_hooks(self.db, package, version) |
1000 | - for app_name, hooks in manifest.items(): |
1001 | - if self.hook_name in hooks: |
1002 | - yield ( |
1003 | - package, version, app_name, user_name, |
1004 | - hooks[self.hook_name]) |
1005 | - |
1006 | - def install(self, user=None): |
1007 | - for package, version, app_name, user_name, relative_path in ( |
1008 | - self._relevant_apps(user=user)): |
1009 | - self.install_package( |
1010 | - package, version, app_name, relative_path, user=user_name) |
1011 | - |
1012 | - def remove(self, user=None): |
1013 | - for package, version, app_name, user_name, _ in ( |
1014 | - self._relevant_apps(user=user)): |
1015 | - self.remove_package(package, version, app_name, user=user_name) |
1016 | - |
1017 | - def sync(self, user=None): |
1018 | - if self.user_level: |
1019 | - user_db = ClickUser(self.db, user=user) |
1020 | - else: |
1021 | - assert user is None |
1022 | - |
1023 | - seen = set() |
1024 | - for package, version, app_name, user_name, relative_path in ( |
1025 | - self._relevant_apps(user=user)): |
1026 | - seen.add((package, version, app_name)) |
1027 | - if self.user_level: |
1028 | - with user_db._dropped_privileges(): |
1029 | - self._install_link( |
1030 | - package, version, app_name, relative_path, |
1031 | - user=user_name, user_db=user_db) |
1032 | - else: |
1033 | - self._install_link(package, version, app_name, relative_path) |
1034 | - for path, package, version, app_name in \ |
1035 | - self._previous_entries(user=user): |
1036 | - if (package, version, app_name) not in seen: |
1037 | - osextras.unlink_force(path) |
1038 | - self._run_commands(user=user) |
1039 | - |
1040 | - |
1041 | -def _app_hooks(hooks): |
1042 | - items = set() |
1043 | - for app_name in hooks: |
1044 | - for hook_name in hooks[app_name]: |
1045 | - items.add((app_name, hook_name)) |
1046 | - return items |
1047 | - |
1048 | - |
1049 | -def package_install_hooks(db, package, old_version, new_version, user=None): |
1050 | - """Run hooks following installation or upgrade of a Click package. |
1051 | - |
1052 | - If user is None, only run system-level hooks. If user is not None, only |
1053 | - run user-level hooks for that user. |
1054 | - """ |
1055 | - old_manifest = _read_manifest_hooks(db, package, old_version) |
1056 | - new_manifest = _read_manifest_hooks(db, package, new_version) |
1057 | - |
1058 | - # Remove any targets for single-version hooks that were in the old |
1059 | - # manifest but not the new one. |
1060 | - for app_name, hook_name in sorted( |
1061 | - _app_hooks(old_manifest) - _app_hooks(new_manifest)): |
1062 | - for hook in ClickHook.open_all(db, hook_name): |
1063 | - if hook.user_level != (user is not None): |
1064 | - continue |
1065 | - if hook.single_version: |
1066 | - hook.remove_package(package, old_version, app_name, user=user) |
1067 | - |
1068 | - for app_name, app_hooks in sorted(new_manifest.items()): |
1069 | - for hook_name, relative_path in sorted(app_hooks.items()): |
1070 | - for hook in ClickHook.open_all(db, hook_name): |
1071 | - if hook.user_level != (user is not None): |
1072 | - continue |
1073 | - hook.install_package( |
1074 | - package, new_version, app_name, relative_path, user=user) |
1075 | - |
1076 | - |
1077 | -def package_remove_hooks(db, package, old_version, user=None): |
1078 | - """Run hooks following removal of a Click package. |
1079 | - |
1080 | - If user is None, only run system-level hooks. If user is not None, only |
1081 | - run user-level hooks for that user. |
1082 | - """ |
1083 | - old_manifest = _read_manifest_hooks(db, package, old_version) |
1084 | - |
1085 | - for app_name, app_hooks in sorted(old_manifest.items()): |
1086 | - for hook_name in sorted(app_hooks): |
1087 | - for hook in ClickHook.open_all(db, hook_name): |
1088 | - if hook.user_level != (user is not None): |
1089 | - continue |
1090 | - hook.remove_package(package, old_version, app_name, user=user) |
1091 | - |
1092 | - |
1093 | -def run_system_hooks(db): |
1094 | - """Run system-level hooks for all installed packages. |
1095 | - |
1096 | - This is useful when starting up from images with preinstalled packages |
1097 | - which may not have had their system-level hooks run properly when |
1098 | - building the image. It is suitable for running at system startup. |
1099 | - """ |
1100 | - db.ensure_ownership() |
1101 | - for hook in ClickHook.open_all(db): |
1102 | - if not hook.user_level: |
1103 | - hook.sync() |
1104 | - |
1105 | - |
1106 | -def run_user_hooks(db, user=None): |
1107 | - """Run user-level hooks for all packages registered for a user. |
1108 | - |
1109 | - This is useful to catch up with packages that may have been preinstalled |
1110 | - and registered for all users. It is suitable for running at session |
1111 | - startup. |
1112 | - """ |
1113 | - if user is None: |
1114 | - user = pwd.getpwuid(os.getuid()).pw_name |
1115 | - for hook in ClickHook.open_all(db): |
1116 | - if hook.user_level: |
1117 | - hook.sync(user=user) |
1118 | |
1119 | === modified file 'click/install.py' |
1120 | --- click/install.py 2014-01-22 14:02:33 +0000 |
1121 | +++ click/install.py 2014-03-06 07:04:44 +0000 |
1122 | @@ -43,12 +43,10 @@ |
1123 | import apt_pkg |
1124 | from debian.debfile import DebFile as _DebFile |
1125 | from debian.debian_support import Version |
1126 | +from gi.repository import Click |
1127 | |
1128 | -from click import osextras |
1129 | -from click.hooks import package_install_hooks |
1130 | from click.paths import frameworks_dir, preload_path |
1131 | from click.preinst import static_preinst_matches |
1132 | -from click.user import ClickUser |
1133 | from click.versions import spec_version |
1134 | |
1135 | |
1136 | @@ -313,12 +311,14 @@ |
1137 | |
1138 | def install(self, path, user=None, all_users=False): |
1139 | package_name, package_version = self.audit(path, check_arch=True) |
1140 | - package_dir = os.path.join(self.db.overlay, package_name) |
1141 | + package_dir = os.path.join(self.db.props.overlay, package_name) |
1142 | inst_dir = os.path.join(package_dir, package_version) |
1143 | - assert os.path.dirname(os.path.dirname(inst_dir)) == self.db.overlay |
1144 | + assert ( |
1145 | + os.path.dirname(os.path.dirname(inst_dir)) == |
1146 | + self.db.props.overlay) |
1147 | |
1148 | - self._check_write_permissions(self.db.overlay) |
1149 | - root_click = os.path.join(self.db.overlay, ".click") |
1150 | + self._check_write_permissions(self.db.props.overlay) |
1151 | + root_click = os.path.join(self.db.props.overlay, ".click") |
1152 | if not os.path.exists(root_click): |
1153 | os.makedirs(root_click) |
1154 | if os.geteuid() == 0: |
1155 | @@ -348,7 +348,7 @@ |
1156 | if "LD_PRELOAD" in env: |
1157 | preloads.append(env["LD_PRELOAD"]) |
1158 | env["LD_PRELOAD"] = " ".join(preloads) |
1159 | - env["CLICK_BASE_DIR"] = self.db.overlay |
1160 | + env["CLICK_BASE_DIR"] = self.db.props.overlay |
1161 | env["CLICK_PACKAGE_PATH"] = path |
1162 | env["CLICK_PACKAGE_FD"] = str(fd.fileno()) |
1163 | env.pop("HOME", None) |
1164 | @@ -379,11 +379,11 @@ |
1165 | old_version = None |
1166 | else: |
1167 | old_version = None |
1168 | - package_install_hooks( |
1169 | + Click.package_install_hooks( |
1170 | self.db, package_name, old_version, package_version) |
1171 | |
1172 | new_path = os.path.join(package_dir, "current.new") |
1173 | - osextras.symlink_force(package_version, new_path) |
1174 | + Click.symlink_force(package_version, new_path) |
1175 | if os.geteuid() == 0: |
1176 | # shutil.chown would be more convenient, but it doesn't support |
1177 | # follow_symlinks=False in Python 3.3. |
1178 | @@ -393,7 +393,10 @@ |
1179 | os.rename(new_path, current_path) |
1180 | |
1181 | if user is not None or all_users: |
1182 | - registry = ClickUser(self.db, user=user, all_users=all_users) |
1183 | + if all_users: |
1184 | + registry = Click.User.for_all_users(self.db) |
1185 | + else: |
1186 | + registry = Click.User.for_user(self.db, name=user) |
1187 | registry.set_version(package_name, package_version) |
1188 | |
1189 | if old_version is not None: |
1190 | |
1191 | === modified file 'click/osextras.py' |
1192 | --- click/osextras.py 2013-09-04 15:59:18 +0000 |
1193 | +++ click/osextras.py 2014-03-06 07:04:44 +0000 |
1194 | @@ -13,7 +13,15 @@ |
1195 | # You should have received a copy of the GNU General Public License |
1196 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
1197 | |
1198 | -"""Extra OS-level utility functions.""" |
1199 | +"""Extra OS-level utility functions. |
1200 | + |
1201 | +Usually we can instead use the functions exported from |
1202 | +lib/click/osextras.vala via GObject Introspection. These pure-Python |
1203 | +versions are preserved so that they can be used from code that needs to be |
1204 | +maximally portable: for example, click.build is intended to be usable even |
1205 | +on systems that lack GObject, as long as they have a reasonably recent |
1206 | +version of Python. |
1207 | +""" |
1208 | |
1209 | __all__ = [ |
1210 | 'ensuredir', |
1211 | |
1212 | === modified file 'click/paths.py.in' |
1213 | --- click/paths.py.in 2013-09-03 12:50:11 +0000 |
1214 | +++ click/paths.py.in 2014-03-06 07:04:44 +0000 |
1215 | @@ -17,5 +17,3 @@ |
1216 | |
1217 | preload_path = "@pkglibdir@/libclickpreload.so" |
1218 | frameworks_dir = "@pkgdatadir@/frameworks" |
1219 | -hooks_dir = "@pkgdatadir@/hooks" |
1220 | -db_dir = "@sysconfdir@/click/databases" |
1221 | |
1222 | === removed file 'click/query.py' |
1223 | --- click/query.py 2013-07-23 18:30:48 +0000 |
1224 | +++ click/query.py 1970-01-01 00:00:00 +0000 |
1225 | @@ -1,43 +0,0 @@ |
1226 | -# Copyright (C) 2013 Canonical Ltd. |
1227 | -# Author: Colin Watson <cjwatson@ubuntu.com> |
1228 | - |
1229 | -# This program is free software: you can redistribute it and/or modify |
1230 | -# it under the terms of the GNU General Public License as published by |
1231 | -# the Free Software Foundation; version 3 of the License. |
1232 | -# |
1233 | -# This program is distributed in the hope that it will be useful, |
1234 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
1235 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1236 | -# GNU General Public License for more details. |
1237 | -# |
1238 | -# You should have received a copy of the GNU General Public License |
1239 | -# along with this program. If not, see <http://www.gnu.org/licenses/>. |
1240 | - |
1241 | -"""Query information about installed Click packages.""" |
1242 | - |
1243 | -from __future__ import print_function |
1244 | - |
1245 | -__metaclass__ = type |
1246 | -__all__ = [ |
1247 | - 'find_package_directory', |
1248 | - ] |
1249 | - |
1250 | -import os |
1251 | - |
1252 | - |
1253 | -def _walk_up(path): |
1254 | - while True: |
1255 | - yield path |
1256 | - newpath = os.path.dirname(path) |
1257 | - if newpath == path: |
1258 | - return |
1259 | - path = newpath |
1260 | - |
1261 | - |
1262 | -def find_package_directory(path): |
1263 | - for directory in _walk_up(os.path.realpath(path)): |
1264 | - if os.path.isdir(os.path.join(directory, ".click", "info")): |
1265 | - return directory |
1266 | - break |
1267 | - else: |
1268 | - raise Exception("No package directory found for %s" % path) |
1269 | |
1270 | === added file 'click/tests/Makefile.am' |
1271 | --- click/tests/Makefile.am 1970-01-01 00:00:00 +0000 |
1272 | +++ click/tests/Makefile.am 2014-03-06 07:04:44 +0000 |
1273 | @@ -0,0 +1,10 @@ |
1274 | +noinst_DATA = preload.gir |
1275 | +CLEANFILES = $(noinst_DATA) |
1276 | + |
1277 | +preload.gir: preload.h |
1278 | + PKG_CONFIG_PATH=$(top_builddir)/lib/click g-ir-scanner \ |
1279 | + -n preload --nsversion 0 -l c \ |
1280 | + --pkg glib-2.0 --pkg gee-0.8 --pkg click-0.4 \ |
1281 | + -I$(top_builddir)/lib/click -L$(top_builddir)/lib/click \ |
1282 | + --accept-unprefixed --warn-all \ |
1283 | + $< --output $@ |
1284 | |
1285 | === modified file 'click/tests/__init__.py' |
1286 | --- click/tests/__init__.py 2013-04-10 15:55:06 +0000 |
1287 | +++ click/tests/__init__.py 2014-03-06 07:04:44 +0000 |
1288 | @@ -0,0 +1,39 @@ |
1289 | +from __future__ import print_function |
1290 | + |
1291 | +import os |
1292 | +import sys |
1293 | + |
1294 | +from click.tests import config |
1295 | + |
1296 | + |
1297 | +def _append_env_path(envname, value): |
1298 | + if envname in os.environ: |
1299 | + if value in os.environ[envname].split(":"): |
1300 | + return False |
1301 | + os.environ[envname] = "%s:%s" % (os.environ[envname], value) |
1302 | + else: |
1303 | + os.environ[envname] = value |
1304 | + return True |
1305 | + |
1306 | + |
1307 | +# Don't do any of this in interactive mode. |
1308 | +if not hasattr(sys, "ps1"): |
1309 | + _lib_click_dir = os.path.join(config.abs_top_builddir, "lib", "click") |
1310 | + changed = False |
1311 | + if _append_env_path( |
1312 | + "LD_LIBRARY_PATH", os.path.join(_lib_click_dir, ".libs")): |
1313 | + changed = True |
1314 | + if _append_env_path("GI_TYPELIB_PATH", _lib_click_dir): |
1315 | + changed = True |
1316 | + if changed: |
1317 | + # We have to re-exec ourselves to get the dynamic loader to pick up |
1318 | + # the new value of LD_LIBRARY_PATH. |
1319 | + if "-m unittest" in sys.argv[0]: |
1320 | + # unittest does horrible things to sys.argv in the name of |
1321 | + # "usefulness", making the re-exec more painful than it needs to |
1322 | + # be. |
1323 | + os.execvp( |
1324 | + sys.executable, [sys.executable, "-m", "unittest"] + sys.argv[1:]) |
1325 | + else: |
1326 | + os.execvp(sys.executable, [sys.executable] + sys.argv) |
1327 | + os._exit(1) |
1328 | |
1329 | === added file 'click/tests/config.py.in' |
1330 | --- click/tests/config.py.in 1970-01-01 00:00:00 +0000 |
1331 | +++ click/tests/config.py.in 2014-03-06 07:04:44 +0000 |
1332 | @@ -0,0 +1,20 @@ |
1333 | +# Copyright (C) 2014 Canonical Ltd. |
1334 | +# Author: Colin Watson <cjwatson@ubuntu.com> |
1335 | + |
1336 | +# This program is free software: you can redistribute it and/or modify |
1337 | +# it under the terms of the GNU General Public License as published by |
1338 | +# the Free Software Foundation; version 3 of the License. |
1339 | +# |
1340 | +# This program is distributed in the hope that it will be useful, |
1341 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
1342 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1343 | +# GNU General Public License for more details. |
1344 | +# |
1345 | +# You should have received a copy of the GNU General Public License |
1346 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
1347 | + |
1348 | +abs_top_builddir = "@abs_top_builddir@" |
1349 | +STAT_OFFSET_UID = @STAT_OFFSET_UID@ |
1350 | +STAT_OFFSET_GID = @STAT_OFFSET_GID@ |
1351 | +STAT64_OFFSET_UID = @STAT64_OFFSET_UID@ |
1352 | +STAT64_OFFSET_GID = @STAT64_OFFSET_GID@ |
1353 | |
1354 | === added file 'click/tests/gimock.py' |
1355 | --- click/tests/gimock.py 1970-01-01 00:00:00 +0000 |
1356 | +++ click/tests/gimock.py 2014-03-06 07:04:44 +0000 |
1357 | @@ -0,0 +1,499 @@ |
1358 | +# Copyright (C) 2014 Canonical Ltd. |
1359 | +# Author: Colin Watson <cjwatson@ubuntu.com> |
1360 | + |
1361 | +# This program is free software: you can redistribute it and/or modify |
1362 | +# it under the terms of the GNU General Public License as published by |
1363 | +# the Free Software Foundation; version 3 of the License. |
1364 | +# |
1365 | +# This program is distributed in the hope that it will be useful, |
1366 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
1367 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1368 | +# GNU General Public License for more details. |
1369 | +# |
1370 | +# You should have received a copy of the GNU General Public License |
1371 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
1372 | + |
1373 | +"""Mock function support based on GObject Introspection. |
1374 | + |
1375 | +(Note to reviewers: I expect to rewrite this from scratch on my own time as |
1376 | +a more generalised set of Python modules for unit testing of C code, |
1377 | +although using similar core ideas. This is a first draft for the purpose of |
1378 | +getting Click's test suite to work expediently, rather than an interface I'm |
1379 | +prepared to commit to long-term.) |
1380 | + |
1381 | +Python is a versatile and concise language for writing tests, and GObject |
1382 | +Introspection (GI) makes it straightforward (often trivial) to bind native |
1383 | +code into Python. However, writing tests for native code quickly runs into |
1384 | +the problem of how to build mock functions. You might reasonably have code |
1385 | +that calls chown(), for instance, and want to test how it's called rather |
1386 | +than worrying about setting up a fakeroot-type environment where chown() |
1387 | +will work. The obvious solution is to use `LD_PRELOAD` wrappers, but there |
1388 | +are various problems to overcome in practice: |
1389 | + |
1390 | + * You can only set up a new `LD_PRELOAD` by going through the run-time |
1391 | + linker; you can't just set it for a single in-process test case. |
1392 | + * Generating the preloaded wrapper involves a fair bit of boilerplate code. |
1393 | + * Having to write per-test mock code in C is inconvenient, and makes it |
1394 | + difficult to get information back out of the mock (such as "how often was |
1395 | + this function called, and with what arguments?"). |
1396 | + |
1397 | +The first problem can be solved by a decorator that knows how to run |
1398 | +individual tests in a subprocess. This is made somewhat more inconvenient |
1399 | +by the fact that there is no way for a context manager's `__enter__` method |
1400 | +to avoid executing the context-managed block other than by throwing an |
1401 | +exception, which makes it hard to silently avoid executing the test case in |
1402 | +the parent process, but we can work around this at the cost of an extra line |
1403 | +of code per invocation. |
1404 | + |
1405 | +For the rest, a combination of GI itself and ctypes can help. We can use GI |
1406 | +to keep track of argument and return types of the mocked C functions in a |
1407 | +reasonably sane way, by parsing header files. We're operating in the other |
1408 | +direction from how GI is normally used, so PyGObject can't deal with |
1409 | +bridging the two calling conventions for us. ctypes can: but we still need |
1410 | +to be careful! We have to construct the callback functions in the child |
1411 | +process, ensure that we keep references to them, and inject function |
1412 | +pointers into the preloaded library via specially-named helper functions; |
1413 | +until those function pointers are set up we must make sure to call the libc |
1414 | +functions instead (since some of them might be called during Python |
1415 | +startup). |
1416 | + |
1417 | +The combination of all of this allows us to bridge C functions somewhat |
1418 | +transparently into Python. This lets you supply a Python function or method |
1419 | +as the mock replacement for a C library function, making it much simpler to |
1420 | +record state. |
1421 | + |
1422 | +It's still not perfect: |
1423 | + |
1424 | + * We're using GI in an upside-down kind of way, and we specifically need |
1425 | + GIR files rather than typelibs so that we can extract the original C |
1426 | + type, so some fiddling is required for each new function you want to |
1427 | + mock. |
1428 | + |
1429 | + * The subprocess arrangements are unavoidably slow and it's possible that |
1430 | + they may cause problems with some test runners. |
1431 | + |
1432 | + * Some C functions (such as `stat`) tend to have multiple underlying entry |
1433 | + points in the C library which must be preloaded independently. |
1434 | + |
1435 | + * You have to be careful about how your libraries are linked, because `ld |
1436 | + -Wl,-Bsymbolic-functions` prevents `LD_PRELOAD` working for intra-library |
1437 | + calls. |
1438 | + |
1439 | + * `ctypes should return composite types from callbacks |
1440 | + <http://bugs.python.org/issue5710>`_. The least awful approach for now |
1441 | + seems to be to construct the composite type in question, stash a |
1442 | + reference to it forever, and then return a pointer to it as a void *; we |
1443 | + can only get away with this because tests are by nature relatively |
1444 | + short-lived. |
1445 | + |
1446 | + * The ctypes module's handling of 64-bit pointers is basically just awful. |
1447 | + The right answer is probably to use a different callback-generation |
1448 | + framework entirely (maybe extending PyGObject so that we can get at the |
1449 | + pieces we need), but I've hacked around it for now. |
1450 | + |
1451 | + * It doesn't appear to be possible to install mock replacements for |
1452 | + functions that are called directly from Python code using their GI |
1453 | + wrappers. You can work around this by simply patching the GI wrapper |
1454 | + instead, using `mock.patch`. |
1455 | + |
1456 | +I think the benefits, in terms of local clarity of tests, are worth the |
1457 | +downsides. |
1458 | +""" |
1459 | + |
1460 | +from __future__ import print_function |
1461 | + |
1462 | +__metaclass__ = type |
1463 | +__all__ = ['GIMockTestCase'] |
1464 | + |
1465 | + |
1466 | +import contextlib |
1467 | +import ctypes |
1468 | +import fcntl |
1469 | +from functools import partial |
1470 | +import os |
1471 | +import pickle |
1472 | +import shutil |
1473 | +import subprocess |
1474 | +import sys |
1475 | +import tempfile |
1476 | +from textwrap import dedent |
1477 | +import traceback |
1478 | +import unittest |
1479 | +try: |
1480 | + from unittest import mock |
1481 | +except ImportError: |
1482 | + import mock |
1483 | +try: |
1484 | + import xml.etree.cElementTree as etree |
1485 | +except ImportError: |
1486 | + import xml.etree.ElementTree as etree |
1487 | + |
1488 | +from click.tests.gimock_types import Stat, Stat64 |
1489 | + |
1490 | + |
1491 | +# Borrowed from giscanner.girparser. |
1492 | +CORE_NS = "http://www.gtk.org/introspection/core/1.0" |
1493 | +C_NS = "http://www.gtk.org/introspection/c/1.0" |
1494 | +GLIB_NS = "http://www.gtk.org/introspection/glib/1.0" |
1495 | + |
1496 | + |
1497 | +def _corens(tag): |
1498 | + return '{%s}%s' % (CORE_NS, tag) |
1499 | + |
1500 | + |
1501 | +def _glibns(tag): |
1502 | + return '{%s}%s' % (GLIB_NS, tag) |
1503 | + |
1504 | + |
1505 | +def _cns(tag): |
1506 | + return '{%s}%s' % (C_NS, tag) |
1507 | + |
1508 | + |
1509 | +# Override some c:type annotations that g-ir-scanner gets a bit wrong. |
1510 | +_c_type_override = { |
1511 | + "passwd*": "struct passwd*", |
1512 | + "stat*": "struct stat*", |
1513 | + "stat64*": "struct stat64*", |
1514 | + } |
1515 | + |
1516 | + |
1517 | +# Mapping of GI type name -> ctypes type. |
1518 | +_typemap = { |
1519 | + "GError**": ctypes.c_void_p, |
1520 | + "gboolean": ctypes.c_bool, |
1521 | + "gint": ctypes.c_int, |
1522 | + "gint*": ctypes.POINTER(ctypes.c_int), |
1523 | + "gint32": ctypes.c_int32, |
1524 | + "gpointer": ctypes.c_void_p, |
1525 | + "guint": ctypes.c_uint, |
1526 | + "guint8**": ctypes.POINTER(ctypes.POINTER(ctypes.c_uint8)), |
1527 | + "guint32": ctypes.c_uint32, |
1528 | + "none": None, |
1529 | + "utf8": ctypes.c_char_p, |
1530 | + "utf8*": ctypes.POINTER(ctypes.c_char_p), |
1531 | + } |
1532 | + |
1533 | + |
1534 | +class GIMockTestCase(unittest.TestCase): |
1535 | + def setUp(self): |
1536 | + super(GIMockTestCase, self).setUp() |
1537 | + self._gimock_temp_dir = tempfile.mkdtemp(prefix="gimock") |
1538 | + self.addCleanup(shutil.rmtree, self._gimock_temp_dir) |
1539 | + self._preload_func_refs = [] |
1540 | + self._composite_refs = [] |
1541 | + self._delegate_funcs = {} |
1542 | + |
1543 | + def tearDown(self): |
1544 | + self._preload_func_refs = [] |
1545 | + self._composite_refs = [] |
1546 | + self._delegate_funcs = {} |
1547 | + |
1548 | + def _gir_get_type(self, obj): |
1549 | + ret = {} |
1550 | + arrayinfo = obj.find(_corens("array")) |
1551 | + if arrayinfo is not None: |
1552 | + typeinfo = arrayinfo.find(_corens("type")) |
1553 | + raw_ctype = arrayinfo.get(_cns("type")) |
1554 | + else: |
1555 | + typeinfo = obj.find(_corens("type")) |
1556 | + raw_ctype = typeinfo.get(_cns("type")) |
1557 | + gi_type = typeinfo.get("name") |
1558 | + if obj.get("direction", "in") == "out": |
1559 | + gi_type += "*" |
1560 | + if arrayinfo is not None: |
1561 | + gi_type += "*" |
1562 | + ret["gi"] = gi_type |
1563 | + ret["c"] = _c_type_override.get(raw_ctype, raw_ctype) |
1564 | + return ret |
1565 | + |
1566 | + def _parse_gir(self, path): |
1567 | + # A very, very crude GIR parser. We might have used |
1568 | + # giscanner.girparser, but it's not importable in Python 3 at the |
1569 | + # moment. |
1570 | + tree = etree.parse(path) |
1571 | + root = tree.getroot() |
1572 | + assert root.tag == _corens("repository") |
1573 | + assert root.get("version") == "1.2" |
1574 | + ns = root.find(_corens("namespace")) |
1575 | + assert ns is not None |
1576 | + funcs = {} |
1577 | + for func in ns.findall(_corens("function")): |
1578 | + name = func.get(_cns("identifier")) |
1579 | + # g-ir-scanner skips identifiers starting with "__", which we |
1580 | + # need in order to mock stat effectively. Work around this. |
1581 | + name = name.replace("under_under_", "__") |
1582 | + headers = None |
1583 | + for attr in func.findall(_corens("attribute")): |
1584 | + if attr.get("name") == "headers": |
1585 | + headers = attr.get("value") |
1586 | + break |
1587 | + rv = func.find(_corens("return-value")) |
1588 | + assert rv is not None |
1589 | + params = [] |
1590 | + paramnode = func.find(_corens("parameters")) |
1591 | + if paramnode is not None: |
1592 | + for param in paramnode.findall(_corens("parameter")): |
1593 | + params.append({ |
1594 | + "name": param.get("name"), |
1595 | + "type": self._gir_get_type(param), |
1596 | + }) |
1597 | + if func.get("throws", "0") == "1": |
1598 | + params.append({ |
1599 | + "name": "error", |
1600 | + "type": { "gi": "GError**", "c": "GError**" }, |
1601 | + }) |
1602 | + funcs[name] = { |
1603 | + "name": name, |
1604 | + "headers": headers, |
1605 | + "rv": self._gir_get_type(rv), |
1606 | + "params": params, |
1607 | + } |
1608 | + return funcs |
1609 | + |
1610 | + def _ctypes_type(self, gi_type): |
1611 | + return _typemap[gi_type["gi"]] |
1612 | + |
1613 | + def make_preloads(self, preloads): |
1614 | + rpreloads = [] |
1615 | + std_headers = set([ |
1616 | + "dlfcn.h", |
1617 | + # Not strictly needed, but convenient for ad-hoc debugging. |
1618 | + "stdio.h", |
1619 | + "stdint.h", |
1620 | + "stdlib.h", |
1621 | + "sys/types.h", |
1622 | + "unistd.h", |
1623 | + ]) |
1624 | + preload_headers = set() |
1625 | + funcs = self._parse_gir("click/tests/preload.gir") |
1626 | + for name, func in preloads.items(): |
1627 | + info = funcs[name] |
1628 | + rpreloads.append([info, func]) |
1629 | + headers = info["headers"] |
1630 | + if headers is not None: |
1631 | + preload_headers.update(headers.split(",")) |
1632 | + if "GIMOCK_SUBPROCESS" in os.environ: |
1633 | + return None, rpreloads |
1634 | + preloads_dir = os.path.join(self._gimock_temp_dir, "_preloads") |
1635 | + os.makedirs(preloads_dir) |
1636 | + c_path = os.path.join(preloads_dir, "gimockpreload.c") |
1637 | + with open(c_path, "w") as c: |
1638 | + print("#define _GNU_SOURCE", file=c) |
1639 | + for header in sorted(std_headers | preload_headers): |
1640 | + print("#include <%s>" % header, file=c) |
1641 | + print(file=c) |
1642 | + for info, _ in rpreloads: |
1643 | + conv = {} |
1644 | + conv["name"] = info["name"] |
1645 | + argtypes = [p["type"]["c"] for p in info["params"]] |
1646 | + argnames = [p["name"] for p in info["params"]] |
1647 | + conv["ret"] = info["rv"]["c"] |
1648 | + conv["bareproto"] = ", ".join(argtypes) |
1649 | + conv["proto"] = ", ".join( |
1650 | + "%s %s" % pair for pair in zip(argtypes, argnames)) |
1651 | + conv["args"] = ", ".join(argnames) |
1652 | + # The delegation scheme used here is needed because trying |
1653 | + # to pass pointers back and forward through ctypes is a |
1654 | + # recipe for having them truncated to 32 bits at the drop of |
1655 | + # a hat. This approach is less obvious but much safer. |
1656 | + print(dedent("""\ |
1657 | + typedef %(ret)s preloadtype_%(name)s (%(bareproto)s); |
1658 | + preloadtype_%(name)s *ctypes_%(name)s = (void *) 0; |
1659 | + preloadtype_%(name)s *real_%(name)s = (void *) 0; |
1660 | + static volatile int delegate_%(name)s = 0; |
1661 | + |
1662 | + extern void _gimock_init_%(name)s (preloadtype_%(name)s *f) |
1663 | + { |
1664 | + ctypes_%(name)s = f; |
1665 | + if (! real_%(name)s) { |
1666 | + /* Retry lookup in case the symbol wasn't |
1667 | + * resolvable until the program under test was |
1668 | + * loaded. |
1669 | + */ |
1670 | + dlerror (); |
1671 | + real_%(name)s = dlsym (RTLD_NEXT, \"%(name)s\"); |
1672 | + if (dlerror ()) _exit (1); |
1673 | + } |
1674 | + } |
1675 | + """) % conv, file=c) |
1676 | + if conv["ret"] == "void": |
1677 | + print(dedent("""\ |
1678 | + void %(name)s (%(proto)s) |
1679 | + { |
1680 | + if (ctypes_%(name)s) { |
1681 | + delegate_%(name)s = 0; |
1682 | + (*ctypes_%(name)s) (%(args)s); |
1683 | + if (! delegate_%(name)s) |
1684 | + return; |
1685 | + } |
1686 | + (*real_%(name)s) (%(args)s); |
1687 | + } |
1688 | + """) % conv, file=c) |
1689 | + else: |
1690 | + print(dedent("""\ |
1691 | + %(ret)s %(name)s (%(proto)s) |
1692 | + { |
1693 | + if (ctypes_%(name)s) { |
1694 | + %(ret)s ret; |
1695 | + delegate_%(name)s = 0; |
1696 | + ret = (*ctypes_%(name)s) (%(args)s); |
1697 | + if (! delegate_%(name)s) |
1698 | + return ret; |
1699 | + } |
1700 | + return (*real_%(name)s) (%(args)s); |
1701 | + } |
1702 | + """) % conv, file=c) |
1703 | + print(dedent("""\ |
1704 | + extern void _gimock_delegate_%(name)s (void) |
1705 | + { |
1706 | + delegate_%(name)s = 1; |
1707 | + } |
1708 | + """) % conv, file=c) |
1709 | + print(dedent("""\ |
1710 | + static void __attribute__ ((constructor)) |
1711 | + gimockpreload_init (void) |
1712 | + { |
1713 | + dlerror (); |
1714 | + """), file=c) |
1715 | + for info, _ in rpreloads: |
1716 | + name = info["name"] |
1717 | + print(" real_%s = dlsym (RTLD_NEXT, \"%s\");" % |
1718 | + (name, name), file=c) |
1719 | + print(" if (dlerror ()) _exit (1);", file=c) |
1720 | + print("}", file=c) |
1721 | + if "GIMOCK_PRELOAD_DEBUG" in os.environ: |
1722 | + with open(c_path) as c: |
1723 | + print(c.read()) |
1724 | + # TODO: Use libtool or similar rather than hardcoding gcc invocation. |
1725 | + lib_path = os.path.join(preloads_dir, "libgimockpreload.so") |
1726 | + cflags = subprocess.check_output([ |
1727 | + "pkg-config", "--cflags", "glib-2.0", "gee-0.8"], |
1728 | + universal_newlines=True).rstrip("\n").split() |
1729 | + subprocess.check_call([ |
1730 | + "gcc", "-O0", "-g", "-shared", "-fPIC", "-DPIC", "-I", "lib/click", |
1731 | + ] + cflags + [ |
1732 | + "-Wl,-soname", "-Wl,libgimockpreload.so", |
1733 | + c_path, "-ldl", "-o", lib_path, |
1734 | + ]) |
1735 | + return lib_path, rpreloads |
1736 | + |
1737 | + # Use as: |
1738 | + # with self.run_in_subprocess("func", ...) as (enter, preloads): |
1739 | + # enter() |
1740 | + # # test case body; preloads["func"] will be a mock.MagicMock |
1741 | + # # instance |
1742 | + @contextlib.contextmanager |
1743 | + def run_in_subprocess(self, *patches): |
1744 | + preloads = {} |
1745 | + for patch in patches: |
1746 | + preloads[patch] = mock.MagicMock() |
1747 | + if preloads: |
1748 | + lib_path, rpreloads = self.make_preloads(preloads) |
1749 | + else: |
1750 | + lib_path, rpreloads = None, None |
1751 | + |
1752 | + class ParentProcess(Exception): |
1753 | + pass |
1754 | + |
1755 | + def helper(lib_path, rpreloads): |
1756 | + if "GIMOCK_SUBPROCESS" in os.environ: |
1757 | + del os.environ["LD_PRELOAD"] |
1758 | + preload_lib = ctypes.cdll.LoadLibrary(lib_path) |
1759 | + delegate_cfunctype = ctypes.CFUNCTYPE(None) |
1760 | + for info, func in rpreloads: |
1761 | + signature = [info["rv"]] + [ |
1762 | + p["type"] for p in info["params"]] |
1763 | + signature = [self._ctypes_type(t) for t in signature] |
1764 | + cfunctype = ctypes.CFUNCTYPE(*signature) |
1765 | + init = getattr( |
1766 | + preload_lib, "_gimock_init_%s" % info["name"]) |
1767 | + cfunc = cfunctype(func) |
1768 | + self._preload_func_refs.append(cfunc) |
1769 | + init(cfunc) |
1770 | + delegate = getattr( |
1771 | + preload_lib, "_gimock_delegate_%s" % info["name"]) |
1772 | + self._delegate_funcs[info["name"]] = delegate_cfunctype( |
1773 | + delegate) |
1774 | + return |
1775 | + rfd, wfd = os.pipe() |
1776 | + # It would be cleaner to use subprocess.Popen(pass_fds=[wfd]), but |
1777 | + # that isn't available in Python 2.7. |
1778 | + if hasattr(os, "set_inheritable"): |
1779 | + os.set_inheritable(wfd, True) |
1780 | + else: |
1781 | + fcntl.fcntl(rfd, fcntl.F_SETFD, fcntl.FD_CLOEXEC) |
1782 | + args = [ |
1783 | + sys.executable, "-m", "unittest", |
1784 | + "%s.%s.%s" % ( |
1785 | + self.__class__.__module__, self.__class__.__name__, |
1786 | + self._testMethodName)] |
1787 | + env = os.environ.copy() |
1788 | + env["GIMOCK_SUBPROCESS"] = str(wfd) |
1789 | + if lib_path is not None: |
1790 | + env["LD_PRELOAD"] = lib_path |
1791 | + subp = subprocess.Popen(args, close_fds=False, env=env) |
1792 | + os.close(wfd) |
1793 | + reader = os.fdopen(rfd, "rb") |
1794 | + subp.communicate() |
1795 | + exctype = pickle.load(reader) |
1796 | + if exctype is not None and issubclass(exctype, AssertionError): |
1797 | + raise AssertionError("Subprocess failed a test!") |
1798 | + elif exctype is not None or subp.returncode != 0: |
1799 | + raise Exception("Subprocess returned an error!") |
1800 | + reader.close() |
1801 | + raise ParentProcess() |
1802 | + |
1803 | + try: |
1804 | + yield partial(helper, lib_path, rpreloads), preloads |
1805 | + if "GIMOCK_SUBPROCESS" in os.environ: |
1806 | + wfd = int(os.environ["GIMOCK_SUBPROCESS"]) |
1807 | + writer = os.fdopen(wfd, "wb") |
1808 | + pickle.dump(None, writer) |
1809 | + writer.flush() |
1810 | + os._exit(0) |
1811 | + except ParentProcess: |
1812 | + pass |
1813 | + except Exception as e: |
1814 | + if "GIMOCK_SUBPROCESS" in os.environ: |
1815 | + wfd = int(os.environ["GIMOCK_SUBPROCESS"]) |
1816 | + writer = os.fdopen(wfd, "wb") |
1817 | + # It would be better to use tblib to pickle the traceback so |
1818 | + # that we can re-raise it properly from the parent process. |
1819 | + # Until that's packaged and available to us, just print the |
1820 | + # traceback and send the exception type. |
1821 | + print() |
1822 | + traceback.print_exc() |
1823 | + pickle.dump(type(e), writer) |
1824 | + writer.flush() |
1825 | + os._exit(1) |
1826 | + else: |
1827 | + raise |
1828 | + |
1829 | + def make_pointer(self, composite): |
1830 | + # Store a reference to a composite type and return a pointer to it, |
1831 | + # working around http://bugs.python.org/issue5710. |
1832 | + self._composite_refs.append(composite) |
1833 | + return ctypes.addressof(composite) |
1834 | + |
1835 | + def make_string(self, s): |
1836 | + # As make_pointer, but for a string. |
1837 | + copied = ctypes.create_string_buffer(s.encode()) |
1838 | + self._composite_refs.append(copied) |
1839 | + return ctypes.addressof(copied) |
1840 | + |
1841 | + def convert_pointer(self, composite_type, address): |
1842 | + # Return a ctypes composite type instance at a given address. |
1843 | + return composite_type.from_address(address) |
1844 | + |
1845 | + def convert_stat_pointer(self, name, address): |
1846 | + # As convert_pointer, but for a "struct stat *" or "struct stat64 *" |
1847 | + # depending on the wrapped function name. |
1848 | + stat_type = {"__xstat": Stat, "__xstat64": Stat64} |
1849 | + return self.convert_pointer(stat_type[name], address) |
1850 | + |
1851 | + def delegate_to_original(self, name): |
1852 | + # Cause the wrapper function to delegate to the original version |
1853 | + # after the callback returns. (Note that the callback still needs |
1854 | + # to return something type-compatible with the declared result type, |
1855 | + # although the return value will otherwise be ignored.) |
1856 | + self._delegate_funcs[name]() |
1857 | |
1858 | === added file 'click/tests/gimock_types.py' |
1859 | --- click/tests/gimock_types.py 1970-01-01 00:00:00 +0000 |
1860 | +++ click/tests/gimock_types.py 2014-03-06 07:04:44 +0000 |
1861 | @@ -0,0 +1,89 @@ |
1862 | +# Copyright (C) 2014 Canonical Ltd. |
1863 | +# Author: Colin Watson <cjwatson@ubuntu.com> |
1864 | + |
1865 | +# This program is free software: you can redistribute it and/or modify |
1866 | +# it under the terms of the GNU General Public License as published by |
1867 | +# the Free Software Foundation; version 3 of the License. |
1868 | +# |
1869 | +# This program is distributed in the hope that it will be useful, |
1870 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
1871 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1872 | +# GNU General Public License for more details. |
1873 | +# |
1874 | +# You should have received a copy of the GNU General Public License |
1875 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
1876 | + |
1877 | +"""A collection of variously hacky ctypes definitions for use with gimock.""" |
1878 | + |
1879 | +import ctypes |
1880 | + |
1881 | +from click.tests.config import ( |
1882 | + STAT_OFFSET_GID, |
1883 | + STAT_OFFSET_UID, |
1884 | + STAT64_OFFSET_GID, |
1885 | + STAT64_OFFSET_UID, |
1886 | + ) |
1887 | + |
1888 | + |
1889 | +class Passwd(ctypes.Structure): |
1890 | + _fields_ = [ |
1891 | + ("pw_name", ctypes.c_char_p), |
1892 | + ("pw_passwd", ctypes.c_char_p), |
1893 | + ("pw_uid", ctypes.c_uint32), |
1894 | + ("pw_gid", ctypes.c_uint32), |
1895 | + ("pw_gecos", ctypes.c_char_p), |
1896 | + ("pw_dir", ctypes.c_char_p), |
1897 | + ("pw_shell", ctypes.c_char_p), |
1898 | + ] |
1899 | + |
1900 | + |
1901 | +# TODO: This is pretty awful. The layout of "struct stat" is complicated |
1902 | +# enough that we have to use offsetof() in configure to pick out the fields |
1903 | +# we care about. Fortunately, we only care about a couple of fields, and |
1904 | +# since this is an output parameter it doesn't matter if our structure is |
1905 | +# too short (if we cared about this then we could use AC_CHECK_SIZEOF to |
1906 | +# figure it out). |
1907 | +class Stat(ctypes.Structure): |
1908 | + _pack_ = 1 |
1909 | + _fields_ = [] |
1910 | + _fields_.append( |
1911 | + ("pad0", ctypes.c_ubyte * min(STAT_OFFSET_UID, STAT_OFFSET_GID))) |
1912 | + if STAT_OFFSET_UID < STAT_OFFSET_GID: |
1913 | + _fields_.append(("st_uid", ctypes.c_uint32)) |
1914 | + pad = (STAT_OFFSET_GID - STAT_OFFSET_UID - |
1915 | + ctypes.sizeof(ctypes.c_uint32)) |
1916 | + assert pad >= 0 |
1917 | + if pad > 0: |
1918 | + _fields_.append(("pad1", ctypes.c_ubyte * pad)) |
1919 | + _fields_.append(("st_gid", ctypes.c_uint32)) |
1920 | + else: |
1921 | + _fields_.append(("st_gid", ctypes.c_uint32)) |
1922 | + pad = (STAT_OFFSET_UID - STAT_OFFSET_GID - |
1923 | + ctypes.sizeof(ctypes.c_uint32)) |
1924 | + assert pad >= 0 |
1925 | + if pad > 0: |
1926 | + _fields_.append(("pad1", ctypes.c_ubyte * pad)) |
1927 | + _fields_.append(("st_uid", ctypes.c_uint32)) |
1928 | + |
1929 | + |
1930 | +class Stat64(ctypes.Structure): |
1931 | + _pack_ = 1 |
1932 | + _fields_ = [] |
1933 | + _fields_.append( |
1934 | + ("pad0", ctypes.c_ubyte * min(STAT64_OFFSET_UID, STAT64_OFFSET_GID))) |
1935 | + if STAT64_OFFSET_UID < STAT64_OFFSET_GID: |
1936 | + _fields_.append(("st_uid", ctypes.c_uint32)) |
1937 | + pad = (STAT64_OFFSET_GID - STAT64_OFFSET_UID - |
1938 | + ctypes.sizeof(ctypes.c_uint32)) |
1939 | + assert pad >= 0 |
1940 | + if pad > 0: |
1941 | + _fields_.append(("pad1", ctypes.c_ubyte * pad)) |
1942 | + _fields_.append(("st_gid", ctypes.c_uint32)) |
1943 | + else: |
1944 | + _fields_.append(("st_gid", ctypes.c_uint32)) |
1945 | + pad = (STAT64_OFFSET_UID - STAT64_OFFSET_GID - |
1946 | + ctypes.sizeof(ctypes.c_uint32)) |
1947 | + assert pad >= 0 |
1948 | + if pad > 0: |
1949 | + _fields_.append(("pad1", ctypes.c_ubyte * pad)) |
1950 | + _fields_.append(("st_uid", ctypes.c_uint32)) |
1951 | |
1952 | === modified file 'click/tests/helpers.py' |
1953 | --- click/tests/helpers.py 2014-03-01 23:28:24 +0000 |
1954 | +++ click/tests/helpers.py 2014-03-06 07:04:44 +0000 |
1955 | @@ -39,10 +39,12 @@ |
1956 | except ImportError: |
1957 | import mock |
1958 | |
1959 | -from click import osextras |
1960 | - |
1961 | - |
1962 | -class TestCase(unittest.TestCase): |
1963 | +from gi.repository import Click, GLib |
1964 | + |
1965 | +from click.tests import gimock |
1966 | + |
1967 | + |
1968 | +class TestCase(gimock.GIMockTestCase): |
1969 | def setUp(self): |
1970 | super(TestCase, self).setUp() |
1971 | self.temp_dir = None |
1972 | @@ -74,6 +76,33 @@ |
1973 | if not hasattr(unittest.TestCase, 'assertRaisesRegex'): |
1974 | assertRaisesRegex = unittest.TestCase.assertRaisesRegexp |
1975 | |
1976 | + def assertRaisesGError(self, domain_name, code, callableObj, |
1977 | + *args, **kwargs): |
1978 | + with self.assertRaises(GLib.GError) as cm: |
1979 | + callableObj(*args, **kwargs) |
1980 | + self.assertEqual(domain_name, cm.exception.domain) |
1981 | + self.assertEqual(code, cm.exception.code) |
1982 | + |
1983 | + def assertRaisesFileError(self, code, callableObj, *args, **kwargs): |
1984 | + self.assertRaisesGError( |
1985 | + "g-file-error-quark", code, callableObj, *args, **kwargs) |
1986 | + |
1987 | + def assertRaisesDatabaseError(self, code, callableObj, *args, **kwargs): |
1988 | + self.assertRaisesGError( |
1989 | + "click_database_error-quark", code, callableObj, *args, **kwargs) |
1990 | + |
1991 | + def assertRaisesHooksError(self, code, callableObj, *args, **kwargs): |
1992 | + self.assertRaisesGError( |
1993 | + "click_hooks_error-quark", code, callableObj, *args, **kwargs) |
1994 | + |
1995 | + def assertRaisesQueryError(self, code, callableObj, *args, **kwargs): |
1996 | + self.assertRaisesGError( |
1997 | + "click_query_error-quark", code, callableObj, *args, **kwargs) |
1998 | + |
1999 | + def assertRaisesUserError(self, code, callableObj, *args, **kwargs): |
2000 | + self.assertRaisesGError( |
2001 | + "click_user_error-quark", code, callableObj, *args, **kwargs) |
2002 | + |
2003 | |
2004 | if not hasattr(mock, "call"): |
2005 | # mock 0.7.2, the version in Ubuntu 12.04 LTS, lacks mock.ANY and |
2006 | @@ -228,14 +257,14 @@ |
2007 | |
2008 | @contextlib.contextmanager |
2009 | def mkfile(path, mode="w"): |
2010 | - osextras.ensuredir(os.path.dirname(path)) |
2011 | + Click.ensuredir(os.path.dirname(path)) |
2012 | with open(path, mode) as f: |
2013 | yield f |
2014 | |
2015 | |
2016 | @contextlib.contextmanager |
2017 | def mkfile_utf8(path, mode="w"): |
2018 | - osextras.ensuredir(os.path.dirname(path)) |
2019 | + Click.ensuredir(os.path.dirname(path)) |
2020 | if sys.version < "3": |
2021 | import codecs |
2022 | with codecs.open(path, mode, "UTF-8") as f: |
2023 | |
2024 | === added file 'click/tests/preload.h' |
2025 | --- click/tests/preload.h 1970-01-01 00:00:00 +0000 |
2026 | +++ click/tests/preload.h 2014-03-06 07:04:44 +0000 |
2027 | @@ -0,0 +1,110 @@ |
2028 | +#include <sys/stat.h> |
2029 | +#include <sys/types.h> |
2030 | + |
2031 | +#include <glib.h> |
2032 | + |
2033 | +#include "click.h" |
2034 | + |
2035 | +/** |
2036 | + * chown: |
2037 | + * |
2038 | + * Attributes: (headers unistd.h) |
2039 | + */ |
2040 | +extern int chown (const char *file, uid_t owner, gid_t group); |
2041 | + |
2042 | +/* Workaround for g-ir-scanner not picking up the type properly: mode_t is |
2043 | + * uint32_t on all glibc platforms. |
2044 | + */ |
2045 | +/** |
2046 | + * mkdir: |
2047 | + * @mode: (type guint32) |
2048 | + * |
2049 | + * Attributes: (headers sys/stat.h,sys/types.h) |
2050 | + */ |
2051 | +extern int mkdir (const char *pathname, mode_t mode); |
2052 | + |
2053 | +/** |
2054 | + * getpwnam: |
2055 | + * |
2056 | + * Attributes: (headers sys/types.h,pwd.h) |
2057 | + * Returns: (transfer none): |
2058 | + */ |
2059 | +extern struct passwd *getpwnam (const char *name); |
2060 | + |
2061 | +/** |
2062 | + * under_under_xstat: |
2063 | + * |
2064 | + * Attributes: (headers sys/types.h,sys/stat.h,unistd.h) |
2065 | + */ |
2066 | +extern int under_under_xstat (int ver, const char *pathname, struct stat *buf); |
2067 | + |
2068 | +/** |
2069 | + * under_under_xstat64: |
2070 | + * |
2071 | + * Attributes: (headers sys/types.h,sys/stat.h,unistd.h) |
2072 | + */ |
2073 | +extern int under_under_xstat64 (int ver, const char *pathname, struct stat64 *buf); |
2074 | + |
2075 | +const gchar *g_get_user_name (void); |
2076 | + |
2077 | +/** |
2078 | + * g_spawn_sync: |
2079 | + * @argv: (array zero-terminated=1): |
2080 | + * @envp: (array zero-terminated=1): |
2081 | + * @flags: (type gint) |
2082 | + * @child_setup: (type gpointer) |
2083 | + * @standard_output: (out) (array zero-terminated=1) (element-type guint8): |
2084 | + * @standard_error: (out) (array zero-terminated=1) (element-type guint8): |
2085 | + * @exit_status: (out): |
2086 | + * |
2087 | + * Attributes: (headers glib.h) |
2088 | + */ |
2089 | +gboolean g_spawn_sync (const gchar *working_directory, |
2090 | + gchar **argv, |
2091 | + gchar **envp, |
2092 | + GSpawnFlags flags, |
2093 | + GSpawnChildSetupFunc child_setup, |
2094 | + gpointer user_data, |
2095 | + gchar **standard_output, |
2096 | + gchar **standard_error, |
2097 | + gint *exit_status, |
2098 | + GError **error); |
2099 | + |
2100 | +/** |
2101 | + * click_find_on_path: |
2102 | + * |
2103 | + * Attributes: (headers glib.h) |
2104 | + */ |
2105 | +gboolean click_find_on_path (const gchar *command); |
2106 | + |
2107 | +/** |
2108 | + * click_get_db_dir: |
2109 | + * |
2110 | + * Attributes: (headers glib.h) |
2111 | + */ |
2112 | +gchar *click_get_db_dir (void); |
2113 | + |
2114 | +/** |
2115 | + * click_get_hooks_dir: |
2116 | + * |
2117 | + * Attributes: (headers glib.h) |
2118 | + */ |
2119 | +gchar *click_get_hooks_dir (void); |
2120 | + |
2121 | +/** |
2122 | + * click_get_user_home: |
2123 | + * |
2124 | + * Attributes: (headers glib.h) |
2125 | + */ |
2126 | +gchar *click_get_user_home (const gchar *user_name); |
2127 | + |
2128 | +/** |
2129 | + * click_package_install_hooks: |
2130 | + * @db: (type gpointer) |
2131 | + * |
2132 | + * Attributes: (headers glib.h,click.h) |
2133 | + */ |
2134 | +void click_package_install_hooks (ClickDB *db, const gchar *package, |
2135 | + const gchar *old_version, |
2136 | + const gchar *new_version, |
2137 | + const gchar *user_name, GError **error); |
2138 | |
2139 | === modified file 'click/tests/test_database.py' |
2140 | --- click/tests/test_database.py 2013-12-10 14:33:19 +0000 |
2141 | +++ click/tests/test_database.py 2014-03-06 07:04:44 +0000 |
2142 | @@ -24,45 +24,46 @@ |
2143 | ] |
2144 | |
2145 | |
2146 | +from functools import partial |
2147 | +from itertools import takewhile |
2148 | import json |
2149 | import os |
2150 | |
2151 | -from click.database import ClickDB |
2152 | -from click.tests.helpers import TestCase, mkfile, mock, touch |
2153 | - |
2154 | - |
2155 | -class MockPasswd: |
2156 | - def __init__(self, pw_uid, pw_gid): |
2157 | - self.pw_uid = pw_uid |
2158 | - self.pw_gid = pw_gid |
2159 | - |
2160 | - |
2161 | -class MockStatResult: |
2162 | - original_stat = os.stat |
2163 | - |
2164 | - def __init__(self, path, **override): |
2165 | - self.st = self.original_stat(path) |
2166 | - self.override = override |
2167 | - |
2168 | - def __getattr__(self, name): |
2169 | - if name in self.override: |
2170 | - return self.override[name] |
2171 | - else: |
2172 | - return getattr(self.st, name) |
2173 | +from gi.repository import Click |
2174 | + |
2175 | +from click.tests.gimock_types import Passwd |
2176 | +from click.tests.helpers import TestCase, mkfile, touch |
2177 | |
2178 | |
2179 | class TestClickSingleDB(TestCase): |
2180 | def setUp(self): |
2181 | super(TestClickSingleDB, self).setUp() |
2182 | self.use_temp_dir() |
2183 | - self.master_db = ClickDB(extra_root=self.temp_dir, use_system=False) |
2184 | - self.db = self.master_db._db[-1] |
2185 | + self.master_db = Click.DB() |
2186 | + self.master_db.add(self.temp_dir) |
2187 | + self.db = self.master_db.get(self.master_db.props.size - 1) |
2188 | + self.spawn_calls = [] |
2189 | + |
2190 | + def g_spawn_sync_side_effect(self, status_map, working_directory, argv, |
2191 | + envp, flags, child_setup, user_data, |
2192 | + standard_output, standard_error, exit_status, |
2193 | + error): |
2194 | + self.spawn_calls.append(list(takewhile(lambda x: x is not None, argv))) |
2195 | + if argv[0] in status_map: |
2196 | + exit_status[0] = status_map[argv[0]] |
2197 | + else: |
2198 | + self.delegate_to_original("g_spawn_sync") |
2199 | + return 0 |
2200 | + |
2201 | + def _installed_packages_tuplify(self, ip): |
2202 | + return [(p.props.package, p.props.version, p.props.path) for p in ip] |
2203 | |
2204 | def test_path(self): |
2205 | path = os.path.join(self.temp_dir, "a", "1.0") |
2206 | os.makedirs(path) |
2207 | - self.assertEqual(path, self.db.path("a", "1.0")) |
2208 | - self.assertRaises(KeyError, self.db.path, "a", "1.1") |
2209 | + self.assertEqual(path, self.db.get_path("a", "1.0")) |
2210 | + self.assertRaisesDatabaseError( |
2211 | + Click.DatabaseError.DOES_NOT_EXIST, self.db.get_path, "a", "1.1") |
2212 | |
2213 | def test_packages_current(self): |
2214 | os.makedirs(os.path.join(self.temp_dir, "a", "1.0")) |
2215 | @@ -76,7 +77,8 @@ |
2216 | self.assertEqual([ |
2217 | ("a", "1.1", a_current), |
2218 | ("b", "0.1", b_current), |
2219 | - ], list(self.db.packages())) |
2220 | + ], self._installed_packages_tuplify( |
2221 | + self.db.get_packages(all_versions=False))) |
2222 | |
2223 | def test_packages_all(self): |
2224 | os.makedirs(os.path.join(self.temp_dir, "a", "1.0")) |
2225 | @@ -90,120 +92,145 @@ |
2226 | ("a", "1.1", os.path.join(self.temp_dir, "a", "1.1")), |
2227 | ("b", "0.1", os.path.join(self.temp_dir, "b", "0.1")), |
2228 | ("c", "2.0", os.path.join(self.temp_dir, "c", "2.0")), |
2229 | - ], list(self.db.packages(all_versions=True))) |
2230 | - |
2231 | - @mock.patch("subprocess.call") |
2232 | - def test_app_running(self, mock_call): |
2233 | - mock_call.return_value = 0 |
2234 | - self.assertTrue(self.db._app_running("foo", "bar", "1.0")) |
2235 | - mock_call.assert_called_once_with( |
2236 | - ["upstart-app-pid", "foo_bar_1.0"], stdout=mock.ANY) |
2237 | - mock_call.return_value = 1 |
2238 | - self.assertFalse(self.db._app_running("foo", "bar", "1.0")) |
2239 | - |
2240 | - @mock.patch("click.osextras.find_on_path") |
2241 | - @mock.patch("subprocess.call") |
2242 | - def test_any_app_running(self, mock_call, mock_find_on_path): |
2243 | - manifest_path = os.path.join( |
2244 | - self.temp_dir, "a", "1.0", ".click", "info", "a.manifest") |
2245 | - with mkfile(manifest_path) as manifest: |
2246 | - json.dump({"hooks": {"a-app": {}}}, manifest) |
2247 | - mock_call.return_value = 0 |
2248 | - mock_find_on_path.return_value = False |
2249 | - self.assertFalse(self.db._any_app_running("a", "1.0")) |
2250 | - mock_find_on_path.return_value = True |
2251 | - self.assertTrue(self.db._any_app_running("a", "1.0")) |
2252 | - mock_call.assert_called_once_with( |
2253 | - ["upstart-app-pid", "a_a-app_1.0"], stdout=mock.ANY) |
2254 | - mock_call.return_value = 1 |
2255 | - self.assertFalse(self.db._any_app_running("a", "1.0")) |
2256 | - |
2257 | - @mock.patch("click.osextras.find_on_path") |
2258 | - @mock.patch("subprocess.call") |
2259 | - def test_maybe_remove_registered(self, mock_call, mock_find_on_path): |
2260 | - version_path = os.path.join(self.temp_dir, "a", "1.0") |
2261 | - manifest_path = os.path.join( |
2262 | - version_path, ".click", "info", "a.manifest") |
2263 | - with mkfile(manifest_path) as manifest: |
2264 | - json.dump({"hooks": {"a-app": {}}}, manifest) |
2265 | - user_path = os.path.join( |
2266 | - self.temp_dir, ".click", "users", "test-user", "a") |
2267 | - os.makedirs(os.path.dirname(user_path)) |
2268 | - os.symlink(version_path, user_path) |
2269 | - mock_call.return_value = 0 |
2270 | - mock_find_on_path.return_value = True |
2271 | - self.db.maybe_remove("a", "1.0") |
2272 | - self.assertTrue(os.path.exists(version_path)) |
2273 | - self.assertTrue(os.path.exists(user_path)) |
2274 | - |
2275 | - @mock.patch("click.osextras.find_on_path") |
2276 | - @mock.patch("subprocess.call") |
2277 | - def test_maybe_remove_running(self, mock_call, mock_find_on_path): |
2278 | - version_path = os.path.join(self.temp_dir, "a", "1.0") |
2279 | - manifest_path = os.path.join( |
2280 | - version_path, ".click", "info", "a.manifest") |
2281 | - with mkfile(manifest_path) as manifest: |
2282 | - json.dump({"hooks": {"a-app": {}}}, manifest) |
2283 | - mock_call.return_value = 0 |
2284 | - mock_find_on_path.return_value = True |
2285 | - self.db.maybe_remove("a", "1.0") |
2286 | - gcinuse_path = os.path.join( |
2287 | - self.temp_dir, ".click", "users", "@gcinuse", "a") |
2288 | - self.assertTrue(os.path.islink(gcinuse_path)) |
2289 | - self.assertEqual(version_path, os.readlink(gcinuse_path)) |
2290 | - self.assertTrue(os.path.exists(version_path)) |
2291 | - self.db.maybe_remove("a", "1.0") |
2292 | - self.assertTrue(os.path.islink(gcinuse_path)) |
2293 | - self.assertEqual(version_path, os.readlink(gcinuse_path)) |
2294 | - self.assertTrue(os.path.exists(version_path)) |
2295 | - |
2296 | - @mock.patch("click.osextras.find_on_path") |
2297 | - @mock.patch("subprocess.call") |
2298 | - def test_maybe_remove_not_running(self, mock_call, mock_find_on_path): |
2299 | - version_path = os.path.join(self.temp_dir, "a", "1.0") |
2300 | - manifest_path = os.path.join( |
2301 | - version_path, ".click", "info", "a.manifest") |
2302 | - with mkfile(manifest_path) as manifest: |
2303 | - json.dump({"hooks": {"a-app": {}}}, manifest) |
2304 | - current_path = os.path.join(self.temp_dir, "a", "current") |
2305 | - os.symlink("1.0", current_path) |
2306 | - mock_call.return_value = 1 |
2307 | - mock_find_on_path.return_value = True |
2308 | - self.db.maybe_remove("a", "1.0") |
2309 | - gcinuse_path = os.path.join( |
2310 | - self.temp_dir, ".click", "users", "@gcinuse", "a") |
2311 | - self.assertFalse(os.path.islink(gcinuse_path)) |
2312 | - self.assertFalse(os.path.exists(os.path.join(self.temp_dir, "a"))) |
2313 | - |
2314 | - @mock.patch("click.osextras.find_on_path") |
2315 | - @mock.patch("subprocess.call") |
2316 | - def test_gc(self, mock_call, mock_find_on_path): |
2317 | - a_path = os.path.join(self.temp_dir, "a", "1.0") |
2318 | - a_manifest_path = os.path.join(a_path, ".click", "info", "a.manifest") |
2319 | - with mkfile(a_manifest_path) as manifest: |
2320 | - json.dump({"hooks": {"a-app": {}}}, manifest) |
2321 | - b_path = os.path.join(self.temp_dir, "b", "1.0") |
2322 | - b_manifest_path = os.path.join(b_path, ".click", "info", "b.manifest") |
2323 | - with mkfile(b_manifest_path) as manifest: |
2324 | - json.dump({"hooks": {"b-app": {}}}, manifest) |
2325 | - c_path = os.path.join(self.temp_dir, "c", "1.0") |
2326 | - c_manifest_path = os.path.join(c_path, ".click", "info", "c.manifest") |
2327 | - with mkfile(c_manifest_path) as manifest: |
2328 | - json.dump({"hooks": {"c-app": {}}}, manifest) |
2329 | - a_user_path = os.path.join( |
2330 | - self.temp_dir, ".click", "users", "test-user", "a") |
2331 | - os.makedirs(os.path.dirname(a_user_path)) |
2332 | - os.symlink(a_path, a_user_path) |
2333 | - b_gcinuse_path = os.path.join( |
2334 | - self.temp_dir, ".click", "users", "@gcinuse", "b") |
2335 | - os.makedirs(os.path.dirname(b_gcinuse_path)) |
2336 | - os.symlink(b_path, b_gcinuse_path) |
2337 | - mock_call.return_value = 1 |
2338 | - mock_find_on_path.return_value = True |
2339 | - self.db.gc(verbose=False) |
2340 | - self.assertTrue(os.path.exists(a_path)) |
2341 | - self.assertFalse(os.path.exists(b_path)) |
2342 | - self.assertTrue(os.path.exists(c_path)) |
2343 | + ], self._installed_packages_tuplify( |
2344 | + self.db.get_packages(all_versions=True))) |
2345 | + |
2346 | + def test_app_running(self): |
2347 | + with self.run_in_subprocess("g_spawn_sync") as (enter, preloads): |
2348 | + enter() |
2349 | + preloads["g_spawn_sync"].side_effect = partial( |
2350 | + self.g_spawn_sync_side_effect, {b"upstart-app-pid": 0}) |
2351 | + self.assertTrue(self.db.app_running("foo", "bar", "1.0")) |
2352 | + self.assertEqual( |
2353 | + [[b"upstart-app-pid", b"foo_bar_1.0"]], self.spawn_calls) |
2354 | + preloads["g_spawn_sync"].side_effect = partial( |
2355 | + self.g_spawn_sync_side_effect, {b"upstart-app-pid": 1 << 8}) |
2356 | + self.assertFalse(self.db.app_running("foo", "bar", "1.0")) |
2357 | + |
2358 | + def test_any_app_running(self): |
2359 | + with self.run_in_subprocess( |
2360 | + "click_find_on_path", "g_spawn_sync", |
2361 | + ) as (enter, preloads): |
2362 | + enter() |
2363 | + manifest_path = os.path.join( |
2364 | + self.temp_dir, "a", "1.0", ".click", "info", "a.manifest") |
2365 | + with mkfile(manifest_path) as manifest: |
2366 | + json.dump({"hooks": {"a-app": {}}}, manifest) |
2367 | + preloads["g_spawn_sync"].side_effect = partial( |
2368 | + self.g_spawn_sync_side_effect, {b"upstart-app-pid": 0}) |
2369 | + preloads["click_find_on_path"].return_value = False |
2370 | + self.assertFalse(self.db.any_app_running("a", "1.0")) |
2371 | + preloads["click_find_on_path"].return_value = True |
2372 | + self.assertTrue(self.db.any_app_running("a", "1.0")) |
2373 | + self.assertEqual( |
2374 | + [[b"upstart-app-pid", b"a_a-app_1.0"]], self.spawn_calls) |
2375 | + preloads["g_spawn_sync"].side_effect = partial( |
2376 | + self.g_spawn_sync_side_effect, {b"upstart-app-pid": 1 << 8}) |
2377 | + self.assertFalse(self.db.any_app_running("a", "1.0")) |
2378 | + |
2379 | + def test_maybe_remove_registered(self): |
2380 | + with self.run_in_subprocess( |
2381 | + "click_find_on_path", "g_spawn_sync", |
2382 | + ) as (enter, preloads): |
2383 | + enter() |
2384 | + version_path = os.path.join(self.temp_dir, "a", "1.0") |
2385 | + manifest_path = os.path.join( |
2386 | + version_path, ".click", "info", "a.manifest") |
2387 | + with mkfile(manifest_path) as manifest: |
2388 | + json.dump({"hooks": {"a-app": {}}}, manifest) |
2389 | + user_path = os.path.join( |
2390 | + self.temp_dir, ".click", "users", "test-user", "a") |
2391 | + os.makedirs(os.path.dirname(user_path)) |
2392 | + os.symlink(version_path, user_path) |
2393 | + preloads["g_spawn_sync"].side_effect = partial( |
2394 | + self.g_spawn_sync_side_effect, {b"upstart-app-pid": 0}) |
2395 | + preloads["click_find_on_path"].return_value = True |
2396 | + self.db.maybe_remove("a", "1.0") |
2397 | + self.assertTrue(os.path.exists(version_path)) |
2398 | + self.assertTrue(os.path.exists(user_path)) |
2399 | + |
2400 | + def test_maybe_remove_running(self): |
2401 | + with self.run_in_subprocess( |
2402 | + "click_find_on_path", "g_spawn_sync", |
2403 | + ) as (enter, preloads): |
2404 | + enter() |
2405 | + version_path = os.path.join(self.temp_dir, "a", "1.0") |
2406 | + manifest_path = os.path.join( |
2407 | + version_path, ".click", "info", "a.manifest") |
2408 | + with mkfile(manifest_path) as manifest: |
2409 | + json.dump({"hooks": {"a-app": {}}}, manifest) |
2410 | + preloads["g_spawn_sync"].side_effect = partial( |
2411 | + self.g_spawn_sync_side_effect, {b"upstart-app-pid": 0}) |
2412 | + preloads["click_find_on_path"].return_value = True |
2413 | + self.db.maybe_remove("a", "1.0") |
2414 | + gcinuse_path = os.path.join( |
2415 | + self.temp_dir, ".click", "users", "@gcinuse", "a") |
2416 | + self.assertTrue(os.path.islink(gcinuse_path)) |
2417 | + self.assertEqual(version_path, os.readlink(gcinuse_path)) |
2418 | + self.assertTrue(os.path.exists(version_path)) |
2419 | + self.db.maybe_remove("a", "1.0") |
2420 | + self.assertTrue(os.path.islink(gcinuse_path)) |
2421 | + self.assertEqual(version_path, os.readlink(gcinuse_path)) |
2422 | + self.assertTrue(os.path.exists(version_path)) |
2423 | + |
2424 | + def test_maybe_remove_not_running(self): |
2425 | + with self.run_in_subprocess( |
2426 | + "click_find_on_path", "g_spawn_sync", |
2427 | + ) as (enter, preloads): |
2428 | + enter() |
2429 | + os.environ["TEST_QUIET"] = "1" |
2430 | + version_path = os.path.join(self.temp_dir, "a", "1.0") |
2431 | + manifest_path = os.path.join( |
2432 | + version_path, ".click", "info", "a.manifest") |
2433 | + with mkfile(manifest_path) as manifest: |
2434 | + json.dump({"hooks": {"a-app": {}}}, manifest) |
2435 | + current_path = os.path.join(self.temp_dir, "a", "current") |
2436 | + os.symlink("1.0", current_path) |
2437 | + preloads["g_spawn_sync"].side_effect = partial( |
2438 | + self.g_spawn_sync_side_effect, {b"upstart-app-pid": 1 << 8}) |
2439 | + preloads["click_find_on_path"].return_value = True |
2440 | + self.db.maybe_remove("a", "1.0") |
2441 | + gcinuse_path = os.path.join( |
2442 | + self.temp_dir, ".click", "users", "@gcinuse", "a") |
2443 | + self.assertFalse(os.path.islink(gcinuse_path)) |
2444 | + self.assertFalse(os.path.exists(os.path.join(self.temp_dir, "a"))) |
2445 | + |
2446 | + def test_gc(self): |
2447 | + with self.run_in_subprocess( |
2448 | + "click_find_on_path", "g_spawn_sync", |
2449 | + ) as (enter, preloads): |
2450 | + enter() |
2451 | + os.environ["TEST_QUIET"] = "1" |
2452 | + a_path = os.path.join(self.temp_dir, "a", "1.0") |
2453 | + a_manifest_path = os.path.join( |
2454 | + a_path, ".click", "info", "a.manifest") |
2455 | + with mkfile(a_manifest_path) as manifest: |
2456 | + json.dump({"hooks": {"a-app": {}}}, manifest) |
2457 | + b_path = os.path.join(self.temp_dir, "b", "1.0") |
2458 | + b_manifest_path = os.path.join( |
2459 | + b_path, ".click", "info", "b.manifest") |
2460 | + with mkfile(b_manifest_path) as manifest: |
2461 | + json.dump({"hooks": {"b-app": {}}}, manifest) |
2462 | + c_path = os.path.join(self.temp_dir, "c", "1.0") |
2463 | + c_manifest_path = os.path.join( |
2464 | + c_path, ".click", "info", "c.manifest") |
2465 | + with mkfile(c_manifest_path) as manifest: |
2466 | + json.dump({"hooks": {"c-app": {}}}, manifest) |
2467 | + a_user_path = os.path.join( |
2468 | + self.temp_dir, ".click", "users", "test-user", "a") |
2469 | + os.makedirs(os.path.dirname(a_user_path)) |
2470 | + os.symlink(a_path, a_user_path) |
2471 | + b_gcinuse_path = os.path.join( |
2472 | + self.temp_dir, ".click", "users", "@gcinuse", "b") |
2473 | + os.makedirs(os.path.dirname(b_gcinuse_path)) |
2474 | + os.symlink(b_path, b_gcinuse_path) |
2475 | + preloads["g_spawn_sync"].side_effect = partial( |
2476 | + self.g_spawn_sync_side_effect, {b"upstart-app-pid": 1 << 8}) |
2477 | + preloads["click_find_on_path"].return_value = True |
2478 | + self.db.gc() |
2479 | + self.assertTrue(os.path.exists(a_path)) |
2480 | + self.assertFalse(os.path.exists(b_path)) |
2481 | + self.assertTrue(os.path.exists(c_path)) |
2482 | |
2483 | def _make_ownership_test(self): |
2484 | path = os.path.join(self.temp_dir, "a", "1.0") |
2485 | @@ -215,60 +242,80 @@ |
2486 | os.symlink(path, user_path) |
2487 | touch(os.path.join(self.temp_dir, ".click", "log")) |
2488 | |
2489 | - def test_clickpkg_paths(self): |
2490 | - self._make_ownership_test() |
2491 | - self.assertCountEqual([ |
2492 | - self.temp_dir, |
2493 | - os.path.join(self.temp_dir, ".click"), |
2494 | - os.path.join(self.temp_dir, ".click", "log"), |
2495 | - os.path.join(self.temp_dir, ".click", "users"), |
2496 | - os.path.join(self.temp_dir, "a"), |
2497 | - os.path.join(self.temp_dir, "a", "1.0"), |
2498 | - os.path.join(self.temp_dir, "a", "1.0", ".click"), |
2499 | - os.path.join(self.temp_dir, "a", "1.0", ".click", "info"), |
2500 | - os.path.join( |
2501 | - self.temp_dir, "a", "1.0", ".click", "info", "a.manifest"), |
2502 | - os.path.join(self.temp_dir, "a", "current"), |
2503 | - ], list(self.db._clickpkg_paths())) |
2504 | - |
2505 | - @mock.patch("pwd.getpwnam") |
2506 | - @mock.patch("os.chown") |
2507 | - def test_ensure_ownership_quick_if_correct(self, mock_chown, |
2508 | - mock_getpwnam): |
2509 | - mock_getpwnam.return_value = MockPasswd(pw_uid=1, pw_gid=1) |
2510 | - self._make_ownership_test() |
2511 | - with mock.patch("os.stat") as mock_stat: |
2512 | - mock_stat.side_effect = ( |
2513 | - lambda path, *args, **kwargs: MockStatResult( |
2514 | - path, st_uid=1, st_gid=1)) |
2515 | - self.db.ensure_ownership() |
2516 | - self.assertFalse(mock_chown.called) |
2517 | - |
2518 | - @mock.patch("pwd.getpwnam") |
2519 | - @mock.patch("os.chown") |
2520 | - def test_ensure_ownership(self, mock_chown, mock_getpwnam): |
2521 | - mock_getpwnam.return_value = MockPasswd(pw_uid=1, pw_gid=1) |
2522 | - self._make_ownership_test() |
2523 | - with mock.patch("os.stat") as mock_stat: |
2524 | - mock_stat.side_effect = ( |
2525 | - lambda path, *args, **kwargs: MockStatResult( |
2526 | - path, st_uid=2, st_gid=2)) |
2527 | - self.db.ensure_ownership() |
2528 | - self.assertCountEqual([ |
2529 | - self.temp_dir, |
2530 | - os.path.join(self.temp_dir, ".click"), |
2531 | - os.path.join(self.temp_dir, ".click", "log"), |
2532 | - os.path.join(self.temp_dir, ".click", "users"), |
2533 | - os.path.join(self.temp_dir, "a"), |
2534 | - os.path.join(self.temp_dir, "a", "1.0"), |
2535 | - os.path.join(self.temp_dir, "a", "1.0", ".click"), |
2536 | - os.path.join(self.temp_dir, "a", "1.0", ".click", "info"), |
2537 | - os.path.join( |
2538 | - self.temp_dir, "a", "1.0", ".click", "info", "a.manifest"), |
2539 | - os.path.join(self.temp_dir, "a", "current"), |
2540 | - ], [args[0][0] for args in mock_chown.call_args_list]) |
2541 | - self.assertCountEqual( |
2542 | - [(1, 1)], set(args[0][1:] for args in mock_chown.call_args_list)) |
2543 | + def _set_stat_side_effect(self, preloads, side_effect, limit): |
2544 | + limit = limit.encode() |
2545 | + preloads["__xstat"].side_effect = ( |
2546 | + lambda ver, path, buf: side_effect( |
2547 | + "__xstat", limit, ver, path, buf)) |
2548 | + preloads["__xstat64"].side_effect = ( |
2549 | + lambda ver, path, buf: side_effect( |
2550 | + "__xstat64", limit, ver, path, buf)) |
2551 | + |
2552 | + def test_ensure_ownership_quick_if_correct(self): |
2553 | + def stat_side_effect(name, limit, ver, path, buf): |
2554 | + st = self.convert_stat_pointer(name, buf) |
2555 | + if path == limit: |
2556 | + st.st_uid = 1 |
2557 | + st.st_gid = 1 |
2558 | + return 0 |
2559 | + else: |
2560 | + self.delegate_to_original(name) |
2561 | + return -1 |
2562 | + |
2563 | + with self.run_in_subprocess( |
2564 | + "chown", "getpwnam", "__xstat", "__xstat64", |
2565 | + ) as (enter, preloads): |
2566 | + enter() |
2567 | + preloads["getpwnam"].side_effect = ( |
2568 | + lambda name: self.make_pointer(Passwd(pw_uid=1, pw_gid=1))) |
2569 | + self._set_stat_side_effect( |
2570 | + preloads, stat_side_effect, self.db.props.root) |
2571 | + |
2572 | + self._make_ownership_test() |
2573 | + self.db.ensure_ownership() |
2574 | + self.assertFalse(preloads["chown"].called) |
2575 | + |
2576 | + def test_ensure_ownership(self): |
2577 | + def stat_side_effect(name, limit, ver, path, buf): |
2578 | + st = self.convert_stat_pointer(name, buf) |
2579 | + if path == limit: |
2580 | + st.st_uid = 2 |
2581 | + st.st_gid = 2 |
2582 | + return 0 |
2583 | + else: |
2584 | + self.delegate_to_original(name) |
2585 | + return -1 |
2586 | + |
2587 | + with self.run_in_subprocess( |
2588 | + "chown", "getpwnam", "__xstat", "__xstat64", |
2589 | + ) as (enter, preloads): |
2590 | + enter() |
2591 | + preloads["getpwnam"].side_effect = ( |
2592 | + lambda name: self.make_pointer(Passwd(pw_uid=1, pw_gid=1))) |
2593 | + self._set_stat_side_effect( |
2594 | + preloads, stat_side_effect, self.db.props.root) |
2595 | + |
2596 | + self._make_ownership_test() |
2597 | + self.db.ensure_ownership() |
2598 | + expected_paths = [ |
2599 | + self.temp_dir, |
2600 | + os.path.join(self.temp_dir, ".click"), |
2601 | + os.path.join(self.temp_dir, ".click", "log"), |
2602 | + os.path.join(self.temp_dir, ".click", "users"), |
2603 | + os.path.join(self.temp_dir, "a"), |
2604 | + os.path.join(self.temp_dir, "a", "1.0"), |
2605 | + os.path.join(self.temp_dir, "a", "1.0", ".click"), |
2606 | + os.path.join(self.temp_dir, "a", "1.0", ".click", "info"), |
2607 | + os.path.join( |
2608 | + self.temp_dir, "a", "1.0", ".click", "info", "a.manifest"), |
2609 | + os.path.join(self.temp_dir, "a", "current"), |
2610 | + ] |
2611 | + self.assertCountEqual( |
2612 | + [path.encode() for path in expected_paths], |
2613 | + [args[0][0] for args in preloads["chown"].call_args_list]) |
2614 | + self.assertCountEqual( |
2615 | + [(1, 1)], |
2616 | + set(args[0][1:] for args in preloads["chown"].call_args_list)) |
2617 | |
2618 | |
2619 | class TestClickDB(TestCase): |
2620 | @@ -276,6 +323,11 @@ |
2621 | super(TestClickDB, self).setUp() |
2622 | self.use_temp_dir() |
2623 | |
2624 | + def _installed_packages_tuplify(self, ip): |
2625 | + return [ |
2626 | + (p.props.package, p.props.version, p.props.path, p.props.writeable) |
2627 | + for p in ip] |
2628 | + |
2629 | def test_read_configuration(self): |
2630 | with open(os.path.join(self.temp_dir, "a.conf"), "w") as a: |
2631 | print("[Click Database]", file=a) |
2632 | @@ -283,23 +335,27 @@ |
2633 | with open(os.path.join(self.temp_dir, "b.conf"), "w") as b: |
2634 | print("[Click Database]", file=b) |
2635 | print("root = /b", file=b) |
2636 | - db = ClickDB(extra_root="/c", override_db_dir=self.temp_dir) |
2637 | - self.assertEqual(3, len(db)) |
2638 | - self.assertEqual(["/a", "/b", "/c"], [d.root for d in db]) |
2639 | + db = Click.DB() |
2640 | + db.read(db_dir=self.temp_dir) |
2641 | + db.add("/c") |
2642 | + self.assertEqual(3, db.props.size) |
2643 | + self.assertEqual( |
2644 | + ["/a", "/b", "/c"], |
2645 | + [db.get(i).props.root for i in range(db.props.size)]) |
2646 | |
2647 | - def test_no_use_system(self): |
2648 | + def test_no_read(self): |
2649 | with open(os.path.join(self.temp_dir, "a.conf"), "w") as a: |
2650 | print("[Click Database]", file=a) |
2651 | print("root = /a", file=a) |
2652 | - db = ClickDB(use_system=False, override_db_dir=self.temp_dir) |
2653 | - self.assertEqual(0, len(db)) |
2654 | + db = Click.DB() |
2655 | + self.assertEqual(0, db.props.size) |
2656 | |
2657 | def test_add(self): |
2658 | - db = ClickDB(use_system=False) |
2659 | - self.assertEqual(0, len(db)) |
2660 | + db = Click.DB() |
2661 | + self.assertEqual(0, db.props.size) |
2662 | db.add("/new/root") |
2663 | - self.assertEqual(1, len(db)) |
2664 | - self.assertEqual(["/new/root"], [d.root for d in db]) |
2665 | + self.assertEqual(1, db.props.size) |
2666 | + self.assertEqual("/new/root", db.get(0).props.root) |
2667 | |
2668 | def test_overlay(self): |
2669 | with open(os.path.join(self.temp_dir, "00_custom.conf"), "w") as f: |
2670 | @@ -308,8 +364,9 @@ |
2671 | with open(os.path.join(self.temp_dir, "99_default.conf"), "w") as f: |
2672 | print("[Click Database]", file=f) |
2673 | print("root = /opt/click.ubuntu.com", file=f) |
2674 | - db = ClickDB(override_db_dir=self.temp_dir) |
2675 | - self.assertEqual("/opt/click.ubuntu.com", db.overlay) |
2676 | + db = Click.DB() |
2677 | + db.read(db_dir=self.temp_dir) |
2678 | + self.assertEqual("/opt/click.ubuntu.com", db.props.overlay) |
2679 | |
2680 | def test_path(self): |
2681 | with open(os.path.join(self.temp_dir, "a.conf"), "w") as a: |
2682 | @@ -318,21 +375,24 @@ |
2683 | with open(os.path.join(self.temp_dir, "b.conf"), "w") as b: |
2684 | print("[Click Database]", file=b) |
2685 | print("root = %s" % os.path.join(self.temp_dir, "b"), file=b) |
2686 | - db = ClickDB(override_db_dir=self.temp_dir) |
2687 | - self.assertRaises(KeyError, db.path, "pkg", "1.0") |
2688 | + db = Click.DB() |
2689 | + db.read(db_dir=self.temp_dir) |
2690 | + self.assertRaisesDatabaseError( |
2691 | + Click.DatabaseError.DOES_NOT_EXIST, db.get_path, "pkg", "1.0") |
2692 | os.makedirs(os.path.join(self.temp_dir, "a", "pkg", "1.0")) |
2693 | self.assertEqual( |
2694 | os.path.join(self.temp_dir, "a", "pkg", "1.0"), |
2695 | - db.path("pkg", "1.0")) |
2696 | - self.assertRaises(KeyError, db.path, "pkg", "1.1") |
2697 | + db.get_path("pkg", "1.0")) |
2698 | + self.assertRaisesDatabaseError( |
2699 | + Click.DatabaseError.DOES_NOT_EXIST, db.get_path, "pkg", "1.1") |
2700 | os.makedirs(os.path.join(self.temp_dir, "b", "pkg", "1.0")) |
2701 | self.assertEqual( |
2702 | os.path.join(self.temp_dir, "b", "pkg", "1.0"), |
2703 | - db.path("pkg", "1.0")) |
2704 | + db.get_path("pkg", "1.0")) |
2705 | os.makedirs(os.path.join(self.temp_dir, "b", "pkg", "1.1")) |
2706 | self.assertEqual( |
2707 | os.path.join(self.temp_dir, "b", "pkg", "1.1"), |
2708 | - db.path("pkg", "1.1")) |
2709 | + db.get_path("pkg", "1.1")) |
2710 | |
2711 | def test_packages_current(self): |
2712 | with open(os.path.join(self.temp_dir, "a.conf"), "w") as a: |
2713 | @@ -341,8 +401,9 @@ |
2714 | with open(os.path.join(self.temp_dir, "b.conf"), "w") as b: |
2715 | print("[Click Database]", file=b) |
2716 | print("root = %s" % os.path.join(self.temp_dir, "b"), file=b) |
2717 | - db = ClickDB(override_db_dir=self.temp_dir) |
2718 | - self.assertEqual([], list(db.packages())) |
2719 | + db = Click.DB() |
2720 | + db.read(db_dir=self.temp_dir) |
2721 | + self.assertEqual([], list(db.get_packages(all_versions=False))) |
2722 | os.makedirs(os.path.join(self.temp_dir, "a", "pkg1", "1.0")) |
2723 | os.symlink("1.0", os.path.join(self.temp_dir, "a", "pkg1", "current")) |
2724 | os.makedirs(os.path.join(self.temp_dir, "b", "pkg1", "1.1")) |
2725 | @@ -354,7 +415,8 @@ |
2726 | self.assertEqual([ |
2727 | ("pkg1", "1.1", pkg1_current, True), |
2728 | ("pkg2", "0.1", pkg2_current, True), |
2729 | - ], list(db.packages())) |
2730 | + ], self._installed_packages_tuplify( |
2731 | + db.get_packages(all_versions=False))) |
2732 | |
2733 | def test_packages_all(self): |
2734 | with open(os.path.join(self.temp_dir, "a.conf"), "w") as a: |
2735 | @@ -363,8 +425,9 @@ |
2736 | with open(os.path.join(self.temp_dir, "b.conf"), "w") as b: |
2737 | print("[Click Database]", file=b) |
2738 | print("root = %s" % os.path.join(self.temp_dir, "b"), file=b) |
2739 | - db = ClickDB(override_db_dir=self.temp_dir) |
2740 | - self.assertEqual([], list(db.packages())) |
2741 | + db = Click.DB() |
2742 | + db.read(db_dir=self.temp_dir) |
2743 | + self.assertEqual([], list(db.get_packages(all_versions=False))) |
2744 | os.makedirs(os.path.join(self.temp_dir, "a", "pkg1", "1.0")) |
2745 | os.symlink("1.0", os.path.join(self.temp_dir, "a", "pkg1", "current")) |
2746 | os.makedirs(os.path.join(self.temp_dir, "b", "pkg1", "1.1")) |
2747 | @@ -378,4 +441,5 @@ |
2748 | True), |
2749 | ("pkg1", "1.0", os.path.join(self.temp_dir, "a", "pkg1", "1.0"), |
2750 | False), |
2751 | - ], list(db.packages(all_versions=True))) |
2752 | + ], self._installed_packages_tuplify( |
2753 | + db.get_packages(all_versions=True))) |
2754 | |
2755 | === modified file 'click/tests/test_hooks.py' |
2756 | --- click/tests/test_hooks.py 2014-02-19 15:31:55 +0000 |
2757 | +++ click/tests/test_hooks.py 2014-03-06 07:04:44 +0000 |
2758 | @@ -27,824 +27,957 @@ |
2759 | ] |
2760 | |
2761 | |
2762 | -import contextlib |
2763 | +from functools import partial |
2764 | +from itertools import takewhile |
2765 | import json |
2766 | import os |
2767 | from textwrap import dedent |
2768 | |
2769 | -from click import hooks |
2770 | -from click.database import ClickDB |
2771 | -from click.hooks import ( |
2772 | - ClickHook, |
2773 | - ClickPatternFormatter, |
2774 | - package_install_hooks, |
2775 | - package_remove_hooks, |
2776 | - ) |
2777 | -from click.user import ClickUser |
2778 | -from click.tests.helpers import TestCase, mkfile, mkfile_utf8, mock |
2779 | - |
2780 | - |
2781 | -@contextlib.contextmanager |
2782 | -def temp_hooks_dir(new_dir): |
2783 | - old_dir = hooks.hooks_dir |
2784 | - try: |
2785 | - hooks.hooks_dir = new_dir |
2786 | - yield |
2787 | - finally: |
2788 | - hooks.hooks_dir = old_dir |
2789 | +from gi.repository import Click, GLib |
2790 | + |
2791 | +from click.tests.gimock_types import Passwd |
2792 | +from click.tests.helpers import TestCase, mkfile, mkfile_utf8 |
2793 | |
2794 | |
2795 | class TestClickPatternFormatter(TestCase): |
2796 | - def setUp(self): |
2797 | - super(TestClickPatternFormatter, self).setUp() |
2798 | - self.formatter = ClickPatternFormatter() |
2799 | + def _make_variant(self, **kwargs): |
2800 | + # pygobject's Variant creator can't handle maybe types, so we have |
2801 | + # to do this by hand. |
2802 | + builder = GLib.VariantBuilder.new(GLib.VariantType.new("a{sms}")) |
2803 | + for key, value in kwargs.items(): |
2804 | + entry = GLib.VariantBuilder.new(GLib.VariantType.new("{sms}")) |
2805 | + entry.add_value(GLib.Variant.new_string(key)) |
2806 | + entry.add_value(GLib.Variant.new_maybe( |
2807 | + GLib.VariantType.new("s"), |
2808 | + None if value is None else GLib.Variant.new_string(value))) |
2809 | + builder.add_value(entry.end()) |
2810 | + return builder.end() |
2811 | |
2812 | def test_expands_provided_keys(self): |
2813 | self.assertEqual( |
2814 | - "foo.bar", self.formatter.format("foo.${key}", key="bar")) |
2815 | + "foo.bar", |
2816 | + Click.pattern_format("foo.${key}", self._make_variant(key="bar"))) |
2817 | self.assertEqual( |
2818 | "foo.barbaz", |
2819 | - self.formatter.format( |
2820 | - "foo.${key1}${key2}", key1="bar", key2="baz")) |
2821 | + Click.pattern_format( |
2822 | + "foo.${key1}${key2}", |
2823 | + self._make_variant(key1="bar", key2="baz"))) |
2824 | |
2825 | def test_expands_missing_keys_to_empty_string(self): |
2826 | - self.assertEqual("xy", self.formatter.format("x${key}y")) |
2827 | + self.assertEqual( |
2828 | + "xy", Click.pattern_format("x${key}y", self._make_variant())) |
2829 | |
2830 | def test_preserves_unmatched_dollar(self): |
2831 | - self.assertEqual("$", self.formatter.format("$")) |
2832 | - self.assertEqual("$ {foo}", self.formatter.format("$ {foo}")) |
2833 | - self.assertEqual("x${y", self.formatter.format("${key}${y", key="x")) |
2834 | + self.assertEqual("$", Click.pattern_format("$", self._make_variant())) |
2835 | + self.assertEqual( |
2836 | + "$ {foo}", Click.pattern_format("$ {foo}", self._make_variant())) |
2837 | + self.assertEqual( |
2838 | + "x${y", |
2839 | + Click.pattern_format("${key}${y", self._make_variant(key="x"))) |
2840 | |
2841 | def test_double_dollar(self): |
2842 | - self.assertEqual("$", self.formatter.format("$$")) |
2843 | - self.assertEqual("${foo}", self.formatter.format("$${foo}")) |
2844 | - self.assertEqual("x$y", self.formatter.format("x$$${key}", key="y")) |
2845 | + self.assertEqual("$", Click.pattern_format("$$", self._make_variant())) |
2846 | + self.assertEqual( |
2847 | + "${foo}", Click.pattern_format("$${foo}", self._make_variant())) |
2848 | + self.assertEqual( |
2849 | + "x$y", |
2850 | + Click.pattern_format("x$$${key}", self._make_variant(key="y"))) |
2851 | |
2852 | def test_possible_expansion(self): |
2853 | self.assertEqual( |
2854 | {"id": "abc"}, |
2855 | - self.formatter.possible_expansion( |
2856 | - "x_abc_1", "x_${id}_${num}", num="1")) |
2857 | + Click.pattern_possible_expansion( |
2858 | + "x_abc_1", "x_${id}_${num}", |
2859 | + self._make_variant(num="1")).unpack()) |
2860 | self.assertIsNone( |
2861 | - self.formatter.possible_expansion( |
2862 | - "x_abc_1", "x_${id}_${num}", num="2")) |
2863 | + Click.pattern_possible_expansion( |
2864 | + "x_abc_1", "x_${id}_${num}", self._make_variant(num="2"))) |
2865 | |
2866 | |
2867 | class TestClickHookBase(TestCase): |
2868 | def setUp(self): |
2869 | super(TestClickHookBase, self).setUp() |
2870 | self.use_temp_dir() |
2871 | - self.db = ClickDB(self.temp_dir) |
2872 | + self.db = Click.DB() |
2873 | + self.db.add(self.temp_dir) |
2874 | + self.spawn_calls = [] |
2875 | + |
2876 | + def _setup_hooks_dir(self, preloads, hooks_dir=None): |
2877 | + if hooks_dir is None: |
2878 | + hooks_dir = self.temp_dir |
2879 | + preloads["click_get_hooks_dir"].side_effect = ( |
2880 | + lambda: self.make_string(hooks_dir)) |
2881 | + |
2882 | + def g_spawn_sync_side_effect(self, status_map, working_directory, argv, |
2883 | + envp, flags, child_setup, user_data, |
2884 | + standard_output, standard_error, exit_status, |
2885 | + error): |
2886 | + self.spawn_calls.append(list(takewhile(lambda x: x is not None, argv))) |
2887 | + if argv[0] in status_map: |
2888 | + exit_status[0] = status_map[argv[0]] |
2889 | + else: |
2890 | + self.delegate_to_original("g_spawn_sync") |
2891 | + return 0 |
2892 | |
2893 | |
2894 | class TestClickHookSystemLevel(TestClickHookBase): |
2895 | def test_open(self): |
2896 | - with mkfile(os.path.join(self.temp_dir, "test.hook")) as f: |
2897 | - print(dedent("""\ |
2898 | - Pattern: /usr/share/test/${id}.test |
2899 | - # Comment |
2900 | - Exec: test-update |
2901 | - User: root |
2902 | - """), file=f) |
2903 | - with temp_hooks_dir(self.temp_dir): |
2904 | - hook = ClickHook.open(self.db, "test") |
2905 | - self.assertCountEqual(["Pattern", "Exec", "User"], hook.keys()) |
2906 | - self.assertEqual("/usr/share/test/${id}.test", hook["pattern"]) |
2907 | - self.assertEqual("test-update", hook["exec"]) |
2908 | - self.assertFalse(hook.user_level) |
2909 | + with self.run_in_subprocess( |
2910 | + "click_get_hooks_dir") as (enter, preloads): |
2911 | + enter() |
2912 | + self._setup_hooks_dir(preloads) |
2913 | + with mkfile(os.path.join(self.temp_dir, "test.hook")) as f: |
2914 | + print(dedent("""\ |
2915 | + Pattern: /usr/share/test/${id}.test |
2916 | + # Comment |
2917 | + Exec: test-update |
2918 | + User: root |
2919 | + """), file=f) |
2920 | + hook = Click.Hook.open(self.db, "test") |
2921 | + self.assertCountEqual( |
2922 | + ["pattern", "exec", "user"], hook.get_fields()) |
2923 | + self.assertEqual( |
2924 | + "/usr/share/test/${id}.test", hook.get_field("pattern")) |
2925 | + self.assertEqual("test-update", hook.get_field("exec")) |
2926 | + self.assertFalse(hook.props.is_user_level) |
2927 | |
2928 | def test_hook_name_absent(self): |
2929 | - with mkfile(os.path.join(self.temp_dir, "test.hook")) as f: |
2930 | - print("Pattern: /usr/share/test/${id}.test", file=f) |
2931 | - with temp_hooks_dir(self.temp_dir): |
2932 | - hook = ClickHook.open(self.db, "test") |
2933 | - self.assertEqual("test", hook.hook_name) |
2934 | + with self.run_in_subprocess( |
2935 | + "click_get_hooks_dir") as (enter, preloads): |
2936 | + enter() |
2937 | + self._setup_hooks_dir(preloads) |
2938 | + with mkfile(os.path.join(self.temp_dir, "test.hook")) as f: |
2939 | + print("Pattern: /usr/share/test/${id}.test", file=f) |
2940 | + hook = Click.Hook.open(self.db, "test") |
2941 | + self.assertEqual("test", hook.get_hook_name()) |
2942 | |
2943 | def test_hook_name_present(self): |
2944 | - with mkfile(os.path.join(self.temp_dir, "test.hook")) as f: |
2945 | - print("Pattern: /usr/share/test/${id}.test", file=f) |
2946 | - print("Hook-Name: other", file=f) |
2947 | - with temp_hooks_dir(self.temp_dir): |
2948 | - hook = ClickHook.open(self.db, "test") |
2949 | - self.assertEqual("other", hook.hook_name) |
2950 | + with self.run_in_subprocess( |
2951 | + "click_get_hooks_dir") as (enter, preloads): |
2952 | + enter() |
2953 | + self._setup_hooks_dir(preloads) |
2954 | + with mkfile(os.path.join(self.temp_dir, "test.hook")) as f: |
2955 | + print("Pattern: /usr/share/test/${id}.test", file=f) |
2956 | + print("Hook-Name: other", file=f) |
2957 | + hook = Click.Hook.open(self.db, "test") |
2958 | + self.assertEqual("other", hook.get_hook_name()) |
2959 | |
2960 | def test_invalid_app_id(self): |
2961 | - with mkfile(os.path.join(self.temp_dir, "test.hook")) as f: |
2962 | - print(dedent("""\ |
2963 | - Pattern: /usr/share/test/${id}.test |
2964 | - # Comment |
2965 | - Exec: test-update |
2966 | - User: root |
2967 | - """), file=f) |
2968 | - with temp_hooks_dir(self.temp_dir): |
2969 | - hook = ClickHook.open(self.db, "test") |
2970 | - self.assertRaises( |
2971 | - ValueError, hook.app_id, "package", "0.1", "app_name") |
2972 | - self.assertRaises( |
2973 | - ValueError, hook.app_id, "package", "0.1", "app/name") |
2974 | + with self.run_in_subprocess( |
2975 | + "click_get_hooks_dir") as (enter, preloads): |
2976 | + enter() |
2977 | + self._setup_hooks_dir(preloads) |
2978 | + with mkfile(os.path.join(self.temp_dir, "test.hook")) as f: |
2979 | + print(dedent("""\ |
2980 | + Pattern: /usr/share/test/${id}.test |
2981 | + # Comment |
2982 | + Exec: test-update |
2983 | + User: root |
2984 | + """), file=f) |
2985 | + hook = Click.Hook.open(self.db, "test") |
2986 | + self.assertRaisesHooksError( |
2987 | + Click.HooksError.BAD_APP_NAME, hook.get_app_id, |
2988 | + "package", "0.1", "app_name") |
2989 | + self.assertRaisesHooksError( |
2990 | + Click.HooksError.BAD_APP_NAME, hook.get_app_id, |
2991 | + "package", "0.1", "app/name") |
2992 | |
2993 | def test_short_id_invalid(self): |
2994 | - with mkfile(os.path.join(self.temp_dir, "test.hook")) as f: |
2995 | - print("Pattern: /usr/share/test/${short-id}.test", file=f) |
2996 | - with temp_hooks_dir(self.temp_dir): |
2997 | - hook = ClickHook.open(self.db, "test") |
2998 | - # It would perhaps be better if unrecognised $-expansions raised |
2999 | - # KeyError, but they don't right now. |
3000 | - self.assertEqual( |
3001 | - "/usr/share/test/.test", |
3002 | - hook.pattern("package", "0.1", "app-name")) |
3003 | + with self.run_in_subprocess( |
3004 | + "click_get_hooks_dir") as (enter, preloads): |
3005 | + enter() |
3006 | + self._setup_hooks_dir(preloads) |
3007 | + with mkfile(os.path.join(self.temp_dir, "test.hook")) as f: |
3008 | + print("Pattern: /usr/share/test/${short-id}.test", file=f) |
3009 | + hook = Click.Hook.open(self.db, "test") |
3010 | + # It would perhaps be better if unrecognised $-expansions raised |
3011 | + # KeyError, but they don't right now. |
3012 | + self.assertEqual( |
3013 | + "/usr/share/test/.test", |
3014 | + hook.get_pattern("package", "0.1", "app-name")) |
3015 | |
3016 | def test_short_id_valid_with_single_version(self): |
3017 | - with mkfile(os.path.join(self.temp_dir, "test.hook")) as f: |
3018 | - print("Pattern: /usr/share/test/${short-id}.test", file=f) |
3019 | - print("Single-Version: yes", file=f) |
3020 | - with temp_hooks_dir(self.temp_dir): |
3021 | - hook = ClickHook.open(self.db, "test") |
3022 | - self.assertEqual( |
3023 | - "/usr/share/test/package_app-name.test", |
3024 | - hook.pattern("package", "0.1", "app-name")) |
3025 | - |
3026 | - @mock.patch("subprocess.check_call") |
3027 | - def test_run_commands(self, mock_check_call): |
3028 | - with mkfile(os.path.join(self.temp_dir, "test.hook")) as f: |
3029 | - print("Exec: test-update", file=f) |
3030 | - print("User: root", file=f) |
3031 | - with temp_hooks_dir(self.temp_dir): |
3032 | - hook = ClickHook.open(self.db, "test") |
3033 | - self.assertEqual("root", hook._run_commands_user(user=None)) |
3034 | - hook._run_commands(user=None) |
3035 | - mock_check_call.assert_called_once_with( |
3036 | - "test-update", preexec_fn=mock.ANY, shell=True) |
3037 | - |
3038 | - def test_previous_entries(self): |
3039 | - with mkfile(os.path.join(self.temp_dir, "test.hook")) as f: |
3040 | - print("Pattern: %s/${id}.test" % self.temp_dir, file=f) |
3041 | - link_one = os.path.join( |
3042 | - self.temp_dir, "org.example.package_test-app_1.0.test") |
3043 | - link_two = os.path.join( |
3044 | - self.temp_dir, "org.example.package_test-app_2.0.test") |
3045 | - os.symlink("dummy", link_one) |
3046 | - os.symlink("dummy", link_two) |
3047 | - os.symlink("dummy", os.path.join(self.temp_dir, "malformed")) |
3048 | - with temp_hooks_dir(self.temp_dir): |
3049 | - hook = ClickHook.open(self.db, "test") |
3050 | - self.assertCountEqual([ |
3051 | - (link_one, "org.example.package", "1.0", "test-app"), |
3052 | - (link_two, "org.example.package", "2.0", "test-app"), |
3053 | - ], list(hook._previous_entries())) |
3054 | + with self.run_in_subprocess( |
3055 | + "click_get_hooks_dir") as (enter, preloads): |
3056 | + enter() |
3057 | + self._setup_hooks_dir(preloads) |
3058 | + with mkfile(os.path.join(self.temp_dir, "test.hook")) as f: |
3059 | + print("Pattern: /usr/share/test/${short-id}.test", file=f) |
3060 | + print("Single-Version: yes", file=f) |
3061 | + hook = Click.Hook.open(self.db, "test") |
3062 | + self.assertEqual( |
3063 | + "/usr/share/test/package_app-name.test", |
3064 | + hook.get_pattern("package", "0.1", "app-name")) |
3065 | + |
3066 | + def test_run_commands(self): |
3067 | + with self.run_in_subprocess( |
3068 | + "click_get_hooks_dir", "g_spawn_sync") as (enter, preloads): |
3069 | + enter() |
3070 | + self._setup_hooks_dir(preloads) |
3071 | + preloads["g_spawn_sync"].side_effect = partial( |
3072 | + self.g_spawn_sync_side_effect, {b"/bin/sh": 0}) |
3073 | + with mkfile(os.path.join(self.temp_dir, "test.hook")) as f: |
3074 | + print("Exec: test-update", file=f) |
3075 | + print("User: root", file=f) |
3076 | + hook = Click.Hook.open(self.db, "test") |
3077 | + self.assertEqual( |
3078 | + "root", hook.get_run_commands_user(user_name=None)) |
3079 | + hook.run_commands(user_name=None) |
3080 | + self.assertEqual( |
3081 | + [[b"/bin/sh", b"-c", b"test-update"]], self.spawn_calls) |
3082 | |
3083 | def test_install_package(self): |
3084 | - with mkfile(os.path.join(self.temp_dir, "test.hook")) as f: |
3085 | - print("Pattern: %s/${id}.test" % self.temp_dir, file=f) |
3086 | - os.makedirs(os.path.join(self.temp_dir, "org.example.package", "1.0")) |
3087 | - with temp_hooks_dir(self.temp_dir): |
3088 | - hook = ClickHook.open(self.db, "test") |
3089 | - hook.install_package( |
3090 | - "org.example.package", "1.0", "test-app", "foo/bar") |
3091 | - symlink_path = os.path.join( |
3092 | - self.temp_dir, "org.example.package_test-app_1.0.test") |
3093 | - target_path = os.path.join( |
3094 | - self.temp_dir, "org.example.package", "1.0", "foo", "bar") |
3095 | - self.assertTrue(os.path.islink(symlink_path)) |
3096 | - self.assertEqual(target_path, os.readlink(symlink_path)) |
3097 | + with self.run_in_subprocess( |
3098 | + "click_get_hooks_dir") as (enter, preloads): |
3099 | + enter() |
3100 | + self._setup_hooks_dir(preloads) |
3101 | + with mkfile(os.path.join(self.temp_dir, "test.hook")) as f: |
3102 | + print("Pattern: %s/${id}.test" % self.temp_dir, file=f) |
3103 | + os.makedirs( |
3104 | + os.path.join(self.temp_dir, "org.example.package", "1.0")) |
3105 | + hook = Click.Hook.open(self.db, "test") |
3106 | + hook.install_package( |
3107 | + "org.example.package", "1.0", "test-app", "foo/bar") |
3108 | + symlink_path = os.path.join( |
3109 | + self.temp_dir, "org.example.package_test-app_1.0.test") |
3110 | + target_path = os.path.join( |
3111 | + self.temp_dir, "org.example.package", "1.0", "foo", "bar") |
3112 | + self.assertTrue(os.path.islink(symlink_path)) |
3113 | + self.assertEqual(target_path, os.readlink(symlink_path)) |
3114 | |
3115 | def test_install_package_trailing_slash(self): |
3116 | - with mkfile(os.path.join(self.temp_dir, "test.hook")) as f: |
3117 | - print("Pattern: %s/${id}/" % self.temp_dir, file=f) |
3118 | - os.makedirs(os.path.join(self.temp_dir, "org.example.package", "1.0")) |
3119 | - with temp_hooks_dir(self.temp_dir): |
3120 | - hook = ClickHook.open(self.db, "test") |
3121 | - hook.install_package("org.example.package", "1.0", "test-app", "foo") |
3122 | - symlink_path = os.path.join( |
3123 | - self.temp_dir, "org.example.package_test-app_1.0") |
3124 | - target_path = os.path.join( |
3125 | - self.temp_dir, "org.example.package", "1.0", "foo") |
3126 | - self.assertTrue(os.path.islink(symlink_path)) |
3127 | - self.assertEqual(target_path, os.readlink(symlink_path)) |
3128 | + with self.run_in_subprocess( |
3129 | + "click_get_hooks_dir") as (enter, preloads): |
3130 | + enter() |
3131 | + self._setup_hooks_dir(preloads) |
3132 | + with mkfile(os.path.join(self.temp_dir, "test.hook")) as f: |
3133 | + print("Pattern: %s/${id}/" % self.temp_dir, file=f) |
3134 | + os.makedirs( |
3135 | + os.path.join(self.temp_dir, "org.example.package", "1.0")) |
3136 | + hook = Click.Hook.open(self.db, "test") |
3137 | + hook.install_package( |
3138 | + "org.example.package", "1.0", "test-app", "foo") |
3139 | + symlink_path = os.path.join( |
3140 | + self.temp_dir, "org.example.package_test-app_1.0") |
3141 | + target_path = os.path.join( |
3142 | + self.temp_dir, "org.example.package", "1.0", "foo") |
3143 | + self.assertTrue(os.path.islink(symlink_path)) |
3144 | + self.assertEqual(target_path, os.readlink(symlink_path)) |
3145 | |
3146 | def test_upgrade(self): |
3147 | - with mkfile(os.path.join(self.temp_dir, "test.hook")) as f: |
3148 | - print("Pattern: %s/${id}.test" % self.temp_dir, file=f) |
3149 | - symlink_path = os.path.join( |
3150 | - self.temp_dir, "org.example.package_test-app_1.0.test") |
3151 | - os.symlink("old-target", symlink_path) |
3152 | - os.makedirs(os.path.join(self.temp_dir, "org.example.package", "1.0")) |
3153 | - with temp_hooks_dir(self.temp_dir): |
3154 | - hook = ClickHook.open(self.db, "test") |
3155 | - hook.install_package( |
3156 | - "org.example.package", "1.0", "test-app", "foo/bar") |
3157 | - target_path = os.path.join( |
3158 | - self.temp_dir, "org.example.package", "1.0", "foo", "bar") |
3159 | - self.assertTrue(os.path.islink(symlink_path)) |
3160 | - self.assertEqual(target_path, os.readlink(symlink_path)) |
3161 | + with self.run_in_subprocess( |
3162 | + "click_get_hooks_dir") as (enter, preloads): |
3163 | + enter() |
3164 | + self._setup_hooks_dir(preloads) |
3165 | + with mkfile(os.path.join(self.temp_dir, "test.hook")) as f: |
3166 | + print("Pattern: %s/${id}.test" % self.temp_dir, file=f) |
3167 | + symlink_path = os.path.join( |
3168 | + self.temp_dir, "org.example.package_test-app_1.0.test") |
3169 | + os.symlink("old-target", symlink_path) |
3170 | + os.makedirs( |
3171 | + os.path.join(self.temp_dir, "org.example.package", "1.0")) |
3172 | + hook = Click.Hook.open(self.db, "test") |
3173 | + hook.install_package( |
3174 | + "org.example.package", "1.0", "test-app", "foo/bar") |
3175 | + target_path = os.path.join( |
3176 | + self.temp_dir, "org.example.package", "1.0", "foo", "bar") |
3177 | + self.assertTrue(os.path.islink(symlink_path)) |
3178 | + self.assertEqual(target_path, os.readlink(symlink_path)) |
3179 | |
3180 | def test_remove_package(self): |
3181 | - with mkfile(os.path.join(self.temp_dir, "test.hook")) as f: |
3182 | - print("Pattern: %s/${id}.test" % self.temp_dir, file=f) |
3183 | - symlink_path = os.path.join( |
3184 | - self.temp_dir, "org.example.package_test-app_1.0.test") |
3185 | - os.symlink("old-target", symlink_path) |
3186 | - with temp_hooks_dir(self.temp_dir): |
3187 | - hook = ClickHook.open(self.db, "test") |
3188 | - hook.remove_package("org.example.package", "1.0", "test-app") |
3189 | - self.assertFalse(os.path.exists(symlink_path)) |
3190 | + with self.run_in_subprocess( |
3191 | + "click_get_hooks_dir") as (enter, preloads): |
3192 | + enter() |
3193 | + self._setup_hooks_dir(preloads) |
3194 | + with mkfile(os.path.join(self.temp_dir, "test.hook")) as f: |
3195 | + print("Pattern: %s/${id}.test" % self.temp_dir, file=f) |
3196 | + symlink_path = os.path.join( |
3197 | + self.temp_dir, "org.example.package_test-app_1.0.test") |
3198 | + os.symlink("old-target", symlink_path) |
3199 | + hook = Click.Hook.open(self.db, "test") |
3200 | + hook.remove_package("org.example.package", "1.0", "test-app") |
3201 | + self.assertFalse(os.path.exists(symlink_path)) |
3202 | |
3203 | def test_install(self): |
3204 | - with mkfile(os.path.join(self.temp_dir, "hooks", "new.hook")) as f: |
3205 | - print("Pattern: %s/${id}.new" % self.temp_dir, file=f) |
3206 | - with mkfile_utf8(os.path.join( |
3207 | - self.temp_dir, "test-1", "1.0", ".click", "info", |
3208 | - "test-1.manifest")) as f: |
3209 | - json.dump({ |
3210 | - "maintainer": |
3211 | - b"Unic\xc3\xb3de <unicode@example.org>".decode("UTF-8"), |
3212 | - "hooks": {"test1-app": {"new": "target-1"}}, |
3213 | - }, f, ensure_ascii=False) |
3214 | - os.symlink("1.0", os.path.join(self.temp_dir, "test-1", "current")) |
3215 | - with mkfile_utf8(os.path.join( |
3216 | - self.temp_dir, "test-2", "2.0", ".click", "info", |
3217 | - "test-2.manifest")) as f: |
3218 | - json.dump({ |
3219 | - "maintainer": |
3220 | - b"Unic\xc3\xb3de <unicode@example.org>".decode("UTF-8"), |
3221 | - "hooks": {"test1-app": {"new": "target-2"}}, |
3222 | - }, f, ensure_ascii=False) |
3223 | - os.symlink("2.0", os.path.join(self.temp_dir, "test-2", "current")) |
3224 | - with temp_hooks_dir(os.path.join(self.temp_dir, "hooks")): |
3225 | - hook = ClickHook.open(self.db, "new") |
3226 | - hook.install() |
3227 | - path_1 = os.path.join(self.temp_dir, "test-1_test1-app_1.0.new") |
3228 | - self.assertTrue(os.path.lexists(path_1)) |
3229 | - self.assertEqual( |
3230 | - os.path.join(self.temp_dir, "test-1", "1.0", "target-1"), |
3231 | - os.readlink(path_1)) |
3232 | - path_2 = os.path.join(self.temp_dir, "test-2_test1-app_2.0.new") |
3233 | - self.assertTrue(os.path.lexists(path_2)) |
3234 | - self.assertEqual( |
3235 | - os.path.join(self.temp_dir, "test-2", "2.0", "target-2"), |
3236 | - os.readlink(path_2)) |
3237 | + with self.run_in_subprocess( |
3238 | + "click_get_hooks_dir") as (enter, preloads): |
3239 | + enter() |
3240 | + self._setup_hooks_dir( |
3241 | + preloads, hooks_dir=os.path.join(self.temp_dir, "hooks")) |
3242 | + with mkfile(os.path.join(self.temp_dir, "hooks", "new.hook")) as f: |
3243 | + print("Pattern: %s/${id}.new" % self.temp_dir, file=f) |
3244 | + with mkfile_utf8(os.path.join( |
3245 | + self.temp_dir, "test-1", "1.0", ".click", "info", |
3246 | + "test-1.manifest")) as f: |
3247 | + json.dump({ |
3248 | + "maintainer": |
3249 | + b"Unic\xc3\xb3de <unicode@example.org>".decode( |
3250 | + "UTF-8"), |
3251 | + "hooks": {"test1-app": {"new": "target-1"}}, |
3252 | + }, f, ensure_ascii=False) |
3253 | + os.symlink("1.0", os.path.join(self.temp_dir, "test-1", "current")) |
3254 | + with mkfile_utf8(os.path.join( |
3255 | + self.temp_dir, "test-2", "2.0", ".click", "info", |
3256 | + "test-2.manifest")) as f: |
3257 | + json.dump({ |
3258 | + "maintainer": |
3259 | + b"Unic\xc3\xb3de <unicode@example.org>".decode( |
3260 | + "UTF-8"), |
3261 | + "hooks": {"test1-app": {"new": "target-2"}}, |
3262 | + }, f, ensure_ascii=False) |
3263 | + os.symlink("2.0", os.path.join(self.temp_dir, "test-2", "current")) |
3264 | + hook = Click.Hook.open(self.db, "new") |
3265 | + hook.install() |
3266 | + path_1 = os.path.join(self.temp_dir, "test-1_test1-app_1.0.new") |
3267 | + self.assertTrue(os.path.lexists(path_1)) |
3268 | + self.assertEqual( |
3269 | + os.path.join(self.temp_dir, "test-1", "1.0", "target-1"), |
3270 | + os.readlink(path_1)) |
3271 | + path_2 = os.path.join(self.temp_dir, "test-2_test1-app_2.0.new") |
3272 | + self.assertTrue(os.path.lexists(path_2)) |
3273 | + self.assertEqual( |
3274 | + os.path.join(self.temp_dir, "test-2", "2.0", "target-2"), |
3275 | + os.readlink(path_2)) |
3276 | |
3277 | def test_remove(self): |
3278 | - with mkfile(os.path.join(self.temp_dir, "hooks", "old.hook")) as f: |
3279 | - print("Pattern: %s/${id}.old" % self.temp_dir, file=f) |
3280 | - with mkfile(os.path.join( |
3281 | - self.temp_dir, "test-1", "1.0", ".click", "info", |
3282 | - "test-1.manifest")) as f: |
3283 | - json.dump({"hooks": {"test1-app": {"old": "target-1"}}}, f) |
3284 | - os.symlink("1.0", os.path.join(self.temp_dir, "test-1", "current")) |
3285 | - path_1 = os.path.join(self.temp_dir, "test-1_test1-app_1.0.old") |
3286 | - os.symlink( |
3287 | - os.path.join(self.temp_dir, "test-1", "1.0", "target-1"), path_1) |
3288 | - with mkfile(os.path.join( |
3289 | - self.temp_dir, "test-2", "2.0", ".click", "info", |
3290 | - "test-2.manifest")) as f: |
3291 | - json.dump({"hooks": {"test2-app": {"old": "target-2"}}}, f) |
3292 | - os.symlink("2.0", os.path.join(self.temp_dir, "test-2", "current")) |
3293 | - path_2 = os.path.join(self.temp_dir, "test-2_test2-app_2.0.old") |
3294 | - os.symlink( |
3295 | - os.path.join(self.temp_dir, "test-2", "2.0", "target-2"), path_2) |
3296 | - with temp_hooks_dir(os.path.join(self.temp_dir, "hooks")): |
3297 | - hook = ClickHook.open(self.db, "old") |
3298 | - hook.remove() |
3299 | - self.assertFalse(os.path.exists(path_1)) |
3300 | - self.assertFalse(os.path.exists(path_2)) |
3301 | + with self.run_in_subprocess( |
3302 | + "click_get_hooks_dir") as (enter, preloads): |
3303 | + enter() |
3304 | + self._setup_hooks_dir( |
3305 | + preloads, hooks_dir=os.path.join(self.temp_dir, "hooks")) |
3306 | + with mkfile(os.path.join(self.temp_dir, "hooks", "old.hook")) as f: |
3307 | + print("Pattern: %s/${id}.old" % self.temp_dir, file=f) |
3308 | + with mkfile(os.path.join( |
3309 | + self.temp_dir, "test-1", "1.0", ".click", "info", |
3310 | + "test-1.manifest")) as f: |
3311 | + json.dump({"hooks": {"test1-app": {"old": "target-1"}}}, f) |
3312 | + os.symlink("1.0", os.path.join(self.temp_dir, "test-1", "current")) |
3313 | + path_1 = os.path.join(self.temp_dir, "test-1_test1-app_1.0.old") |
3314 | + os.symlink( |
3315 | + os.path.join(self.temp_dir, "test-1", "1.0", "target-1"), |
3316 | + path_1) |
3317 | + with mkfile(os.path.join( |
3318 | + self.temp_dir, "test-2", "2.0", ".click", "info", |
3319 | + "test-2.manifest")) as f: |
3320 | + json.dump({"hooks": {"test2-app": {"old": "target-2"}}}, f) |
3321 | + os.symlink("2.0", os.path.join(self.temp_dir, "test-2", "current")) |
3322 | + path_2 = os.path.join(self.temp_dir, "test-2_test2-app_2.0.old") |
3323 | + os.symlink( |
3324 | + os.path.join(self.temp_dir, "test-2", "2.0", "target-2"), |
3325 | + path_2) |
3326 | + hook = Click.Hook.open(self.db, "old") |
3327 | + hook.remove() |
3328 | + self.assertFalse(os.path.exists(path_1)) |
3329 | + self.assertFalse(os.path.exists(path_2)) |
3330 | |
3331 | def test_sync(self): |
3332 | - with mkfile(os.path.join(self.temp_dir, "hooks", "test.hook")) as f: |
3333 | - print("Pattern: %s/${id}.test" % self.temp_dir, file=f) |
3334 | - with mkfile(os.path.join( |
3335 | - self.temp_dir, "test-1", "1.0", ".click", "info", |
3336 | - "test-1.manifest")) as f: |
3337 | - json.dump({"hooks": {"test1-app": {"test": "target-1"}}}, f) |
3338 | - os.symlink("1.0", os.path.join(self.temp_dir, "test-1", "current")) |
3339 | - with mkfile(os.path.join( |
3340 | - self.temp_dir, "test-2", "1.1", ".click", "info", |
3341 | - "test-2.manifest")) as f: |
3342 | - json.dump({"hooks": {"test2-app": {"test": "target-2"}}}, f) |
3343 | - os.symlink("1.1", os.path.join(self.temp_dir, "test-2", "current")) |
3344 | - path_1 = os.path.join(self.temp_dir, "test-1_test1-app_1.0.test") |
3345 | - os.symlink( |
3346 | - os.path.join(self.temp_dir, "test-1", "1.0", "target-1"), path_1) |
3347 | - path_2 = os.path.join(self.temp_dir, "test-2_test2-app_1.1.test") |
3348 | - path_3 = os.path.join(self.temp_dir, "test-3_test3-app_1.0.test") |
3349 | - os.symlink( |
3350 | - os.path.join(self.temp_dir, "test-3", "1.0", "target-3"), path_3) |
3351 | - with temp_hooks_dir(os.path.join(self.temp_dir, "hooks")): |
3352 | - hook = ClickHook.open(self.db, "test") |
3353 | - hook.sync() |
3354 | - self.assertTrue(os.path.lexists(path_1)) |
3355 | - self.assertEqual( |
3356 | - os.path.join(self.temp_dir, "test-1", "1.0", "target-1"), |
3357 | - os.readlink(path_1)) |
3358 | - self.assertTrue(os.path.lexists(path_2)) |
3359 | - self.assertEqual( |
3360 | - os.path.join(self.temp_dir, "test-2", "1.1", "target-2"), |
3361 | - os.readlink(path_2)) |
3362 | - self.assertFalse(os.path.lexists(path_3)) |
3363 | + with self.run_in_subprocess( |
3364 | + "click_get_hooks_dir") as (enter, preloads): |
3365 | + enter() |
3366 | + self._setup_hooks_dir( |
3367 | + preloads, hooks_dir=os.path.join(self.temp_dir, "hooks")) |
3368 | + with mkfile(os.path.join( |
3369 | + self.temp_dir, "hooks", "test.hook")) as f: |
3370 | + print("Pattern: %s/${id}.test" % self.temp_dir, file=f) |
3371 | + with mkfile(os.path.join( |
3372 | + self.temp_dir, "test-1", "1.0", ".click", "info", |
3373 | + "test-1.manifest")) as f: |
3374 | + json.dump({"hooks": {"test1-app": {"test": "target-1"}}}, f) |
3375 | + os.symlink("1.0", os.path.join(self.temp_dir, "test-1", "current")) |
3376 | + with mkfile(os.path.join( |
3377 | + self.temp_dir, "test-2", "1.1", ".click", "info", |
3378 | + "test-2.manifest")) as f: |
3379 | + json.dump({"hooks": {"test2-app": {"test": "target-2"}}}, f) |
3380 | + os.symlink("1.1", os.path.join(self.temp_dir, "test-2", "current")) |
3381 | + path_1 = os.path.join(self.temp_dir, "test-1_test1-app_1.0.test") |
3382 | + os.symlink( |
3383 | + os.path.join(self.temp_dir, "test-1", "1.0", "target-1"), |
3384 | + path_1) |
3385 | + path_2 = os.path.join(self.temp_dir, "test-2_test2-app_1.1.test") |
3386 | + path_3 = os.path.join(self.temp_dir, "test-3_test3-app_1.0.test") |
3387 | + os.symlink( |
3388 | + os.path.join(self.temp_dir, "test-3", "1.0", "target-3"), |
3389 | + path_3) |
3390 | + hook = Click.Hook.open(self.db, "test") |
3391 | + hook.sync() |
3392 | + self.assertTrue(os.path.lexists(path_1)) |
3393 | + self.assertEqual( |
3394 | + os.path.join(self.temp_dir, "test-1", "1.0", "target-1"), |
3395 | + os.readlink(path_1)) |
3396 | + self.assertTrue(os.path.lexists(path_2)) |
3397 | + self.assertEqual( |
3398 | + os.path.join(self.temp_dir, "test-2", "1.1", "target-2"), |
3399 | + os.readlink(path_2)) |
3400 | + self.assertFalse(os.path.lexists(path_3)) |
3401 | |
3402 | |
3403 | class TestClickHookUserLevel(TestClickHookBase): |
3404 | def test_open(self): |
3405 | - with mkfile(os.path.join(self.temp_dir, "test.hook")) as f: |
3406 | - print(dedent("""\ |
3407 | - User-Level: yes |
3408 | - Pattern: ${home}/.local/share/test/${id}.test |
3409 | - # Comment |
3410 | - Exec: test-update |
3411 | - """), file=f) |
3412 | - with temp_hooks_dir(self.temp_dir): |
3413 | - hook = ClickHook.open(self.db, "test") |
3414 | - self.assertCountEqual(["User-Level", "Pattern", "Exec"], hook.keys()) |
3415 | - self.assertEqual( |
3416 | - "${home}/.local/share/test/${id}.test", hook["pattern"]) |
3417 | - self.assertEqual("test-update", hook["exec"]) |
3418 | - self.assertTrue(hook.user_level) |
3419 | + with self.run_in_subprocess( |
3420 | + "click_get_hooks_dir") as (enter, preloads): |
3421 | + enter() |
3422 | + self._setup_hooks_dir(preloads) |
3423 | + with mkfile(os.path.join(self.temp_dir, "test.hook")) as f: |
3424 | + print(dedent("""\ |
3425 | + User-Level: yes |
3426 | + Pattern: ${home}/.local/share/test/${id}.test |
3427 | + # Comment |
3428 | + Exec: test-update |
3429 | + """), file=f) |
3430 | + hook = Click.Hook.open(self.db, "test") |
3431 | + self.assertCountEqual( |
3432 | + ["user-level", "pattern", "exec"], hook.get_fields()) |
3433 | + self.assertEqual( |
3434 | + "${home}/.local/share/test/${id}.test", |
3435 | + hook.get_field("pattern")) |
3436 | + self.assertEqual("test-update", hook.get_field("exec")) |
3437 | + self.assertTrue(hook.props.is_user_level) |
3438 | |
3439 | def test_hook_name_absent(self): |
3440 | - with mkfile(os.path.join(self.temp_dir, "test.hook")) as f: |
3441 | - print("User-Level: yes", file=f) |
3442 | - print("Pattern: ${home}/.local/share/test/${id}.test", file=f) |
3443 | - with temp_hooks_dir(self.temp_dir): |
3444 | - hook = ClickHook.open(self.db, "test") |
3445 | - self.assertEqual("test", hook.hook_name) |
3446 | + with self.run_in_subprocess( |
3447 | + "click_get_hooks_dir") as (enter, preloads): |
3448 | + enter() |
3449 | + self._setup_hooks_dir(preloads) |
3450 | + with mkfile(os.path.join(self.temp_dir, "test.hook")) as f: |
3451 | + print("User-Level: yes", file=f) |
3452 | + print("Pattern: ${home}/.local/share/test/${id}.test", file=f) |
3453 | + hook = Click.Hook.open(self.db, "test") |
3454 | + self.assertEqual("test", hook.get_hook_name()) |
3455 | |
3456 | def test_hook_name_present(self): |
3457 | - with mkfile(os.path.join(self.temp_dir, "test.hook")) as f: |
3458 | - print("User-Level: yes", file=f) |
3459 | - print("Pattern: ${home}/.local/share/test/${id}.test", file=f) |
3460 | - print("Hook-Name: other", file=f) |
3461 | - with temp_hooks_dir(self.temp_dir): |
3462 | - hook = ClickHook.open(self.db, "test") |
3463 | - self.assertEqual("other", hook.hook_name) |
3464 | + with self.run_in_subprocess( |
3465 | + "click_get_hooks_dir") as (enter, preloads): |
3466 | + enter() |
3467 | + self._setup_hooks_dir(preloads) |
3468 | + with mkfile(os.path.join(self.temp_dir, "test.hook")) as f: |
3469 | + print("User-Level: yes", file=f) |
3470 | + print("Pattern: ${home}/.local/share/test/${id}.test", file=f) |
3471 | + print("Hook-Name: other", file=f) |
3472 | + hook = Click.Hook.open(self.db, "test") |
3473 | + self.assertEqual("other", hook.get_hook_name()) |
3474 | |
3475 | def test_invalid_app_id(self): |
3476 | - with mkfile(os.path.join(self.temp_dir, "test.hook")) as f: |
3477 | - print(dedent("""\ |
3478 | - User-Level: yes |
3479 | - Pattern: ${home}/.local/share/test/${id}.test |
3480 | - # Comment |
3481 | - Exec: test-update |
3482 | - """), file=f) |
3483 | - with temp_hooks_dir(self.temp_dir): |
3484 | - hook = ClickHook.open(self.db, "test") |
3485 | - self.assertRaises( |
3486 | - ValueError, hook.app_id, "package", "0.1", "app_name") |
3487 | - self.assertRaises( |
3488 | - ValueError, hook.app_id, "package", "0.1", "app/name") |
3489 | - |
3490 | - @mock.patch("pwd.getpwnam") |
3491 | - def test_short_id_valid(self, mock_getpwnam): |
3492 | - class MockPasswd: |
3493 | - def __init__(self, pw_dir): |
3494 | - self.pw_dir = pw_dir |
3495 | - |
3496 | - mock_getpwnam.return_value = MockPasswd(pw_dir="/mock") |
3497 | - with mkfile(os.path.join(self.temp_dir, "test.hook")) as f: |
3498 | - print("User-Level: yes", file=f) |
3499 | - print( |
3500 | - "Pattern: ${home}/.local/share/test/${short-id}.test", file=f) |
3501 | - with temp_hooks_dir(self.temp_dir): |
3502 | - hook = ClickHook.open(self.db, "test") |
3503 | - self.assertEqual( |
3504 | - "/mock/.local/share/test/package_app-name.test", |
3505 | - hook.pattern("package", "0.1", "app-name", user="mock")) |
3506 | - |
3507 | - @mock.patch("subprocess.check_call") |
3508 | - def test_run_commands(self, mock_check_call): |
3509 | - with mkfile(os.path.join(self.temp_dir, "test.hook")) as f: |
3510 | - print("User-Level: yes", file=f) |
3511 | - print("Exec: test-update", file=f) |
3512 | - with temp_hooks_dir(self.temp_dir): |
3513 | - hook = ClickHook.open(self.db, "test") |
3514 | - self.assertEqual( |
3515 | - "test-user", hook._run_commands_user(user="test-user")) |
3516 | - hook._run_commands(user="test-user") |
3517 | - mock_check_call.assert_called_once_with( |
3518 | - "test-update", preexec_fn=mock.ANY, shell=True) |
3519 | - |
3520 | - @mock.patch("click.hooks.ClickHook._user_home") |
3521 | - def test_previous_entries(self, mock_user_home): |
3522 | - with mkfile(os.path.join(self.temp_dir, "test.hook")) as f: |
3523 | - print("User-Level: yes", file=f) |
3524 | - print("Pattern: %s/${id}.test" % self.temp_dir, file=f) |
3525 | - link_one = os.path.join( |
3526 | - self.temp_dir, "org.example.package_test-app_1.0.test") |
3527 | - link_two = os.path.join( |
3528 | - self.temp_dir, "org.example.package_test-app_2.0.test") |
3529 | - os.symlink("dummy", link_one) |
3530 | - os.symlink("dummy", link_two) |
3531 | - os.symlink("dummy", os.path.join(self.temp_dir, "malformed")) |
3532 | - with temp_hooks_dir(self.temp_dir): |
3533 | - hook = ClickHook.open(self.db, "test") |
3534 | - self.assertCountEqual([ |
3535 | - (link_one, "org.example.package", "1.0", "test-app"), |
3536 | - (link_two, "org.example.package", "2.0", "test-app"), |
3537 | - ], list(hook._previous_entries(user="test-user"))) |
3538 | - |
3539 | - @mock.patch("click.hooks.ClickHook._user_home") |
3540 | - def test_install_package(self, mock_user_home): |
3541 | - mock_user_home.return_value = "/home/test-user" |
3542 | - with temp_hooks_dir(self.temp_dir): |
3543 | + with self.run_in_subprocess( |
3544 | + "click_get_hooks_dir") as (enter, preloads): |
3545 | + enter() |
3546 | + self._setup_hooks_dir(preloads) |
3547 | + with mkfile(os.path.join(self.temp_dir, "test.hook")) as f: |
3548 | + print(dedent("""\ |
3549 | + User-Level: yes |
3550 | + Pattern: ${home}/.local/share/test/${id}.test |
3551 | + # Comment |
3552 | + Exec: test-update |
3553 | + """), file=f) |
3554 | + hook = Click.Hook.open(self.db, "test") |
3555 | + self.assertRaisesHooksError( |
3556 | + Click.HooksError.BAD_APP_NAME, hook.get_app_id, |
3557 | + "package", "0.1", "app_name") |
3558 | + self.assertRaisesHooksError( |
3559 | + Click.HooksError.BAD_APP_NAME, hook.get_app_id, |
3560 | + "package", "0.1", "app/name") |
3561 | + |
3562 | + def test_short_id_valid(self): |
3563 | + with self.run_in_subprocess( |
3564 | + "click_get_hooks_dir", "getpwnam") as (enter, preloads): |
3565 | + enter() |
3566 | + self._setup_hooks_dir(preloads) |
3567 | + preloads["getpwnam"].side_effect = ( |
3568 | + lambda name: self.make_pointer(Passwd(pw_dir=b"/mock"))) |
3569 | + with mkfile(os.path.join(self.temp_dir, "test.hook")) as f: |
3570 | + print("User-Level: yes", file=f) |
3571 | + print( |
3572 | + "Pattern: ${home}/.local/share/test/${short-id}.test", |
3573 | + file=f) |
3574 | + hook = Click.Hook.open(self.db, "test") |
3575 | + self.assertEqual( |
3576 | + "/mock/.local/share/test/package_app-name.test", |
3577 | + hook.get_pattern( |
3578 | + "package", "0.1", "app-name", user_name="mock")) |
3579 | + |
3580 | + def test_run_commands(self): |
3581 | + with self.run_in_subprocess( |
3582 | + "click_get_hooks_dir", "g_spawn_sync") as (enter, preloads): |
3583 | + enter() |
3584 | + self._setup_hooks_dir(preloads) |
3585 | + preloads["g_spawn_sync"].side_effect = partial( |
3586 | + self.g_spawn_sync_side_effect, {b"/bin/sh": 0}) |
3587 | + with mkfile(os.path.join(self.temp_dir, "test.hook")) as f: |
3588 | + print("User-Level: yes", file=f) |
3589 | + print("Exec: test-update", file=f) |
3590 | + hook = Click.Hook.open(self.db, "test") |
3591 | + self.assertEqual( |
3592 | + "test-user", hook.get_run_commands_user(user_name="test-user")) |
3593 | + hook.run_commands(user_name="test-user") |
3594 | + self.assertEqual( |
3595 | + [[b"/bin/sh", b"-c", b"test-update"]], self.spawn_calls) |
3596 | + |
3597 | + def test_install_package(self): |
3598 | + with self.run_in_subprocess( |
3599 | + "click_get_hooks_dir", "click_get_user_home", |
3600 | + ) as (enter, preloads): |
3601 | + enter() |
3602 | + self._setup_hooks_dir(preloads) |
3603 | + preloads["click_get_user_home"].return_value = "/home/test-user" |
3604 | os.makedirs(os.path.join( |
3605 | self.temp_dir, "org.example.package", "1.0")) |
3606 | - user_db = ClickUser(self.db, user="test-user") |
3607 | + user_db = Click.User.for_user(self.db, "test-user") |
3608 | user_db.set_version("org.example.package", "1.0") |
3609 | with mkfile(os.path.join(self.temp_dir, "test.hook")) as f: |
3610 | print("User-Level: yes", file=f) |
3611 | print("Pattern: %s/${id}.test" % self.temp_dir, file=f) |
3612 | - hook = ClickHook.open(self.db, "test") |
3613 | - hook.install_package( |
3614 | - "org.example.package", "1.0", "test-app", "foo/bar", |
3615 | - user="test-user") |
3616 | - symlink_path = os.path.join( |
3617 | - self.temp_dir, "org.example.package_test-app_1.0.test") |
3618 | - target_path = os.path.join( |
3619 | - self.temp_dir, ".click", "users", "test-user", |
3620 | - "org.example.package", "foo", "bar") |
3621 | - self.assertTrue(os.path.islink(symlink_path)) |
3622 | - self.assertEqual(target_path, os.readlink(symlink_path)) |
3623 | + hook = Click.Hook.open(self.db, "test") |
3624 | + hook.install_package( |
3625 | + "org.example.package", "1.0", "test-app", "foo/bar", |
3626 | + user_name="test-user") |
3627 | + symlink_path = os.path.join( |
3628 | + self.temp_dir, "org.example.package_test-app_1.0.test") |
3629 | + target_path = os.path.join( |
3630 | + self.temp_dir, ".click", "users", "test-user", |
3631 | + "org.example.package", "foo", "bar") |
3632 | + self.assertTrue(os.path.islink(symlink_path)) |
3633 | + self.assertEqual(target_path, os.readlink(symlink_path)) |
3634 | |
3635 | - @mock.patch("click.hooks.ClickHook._user_home") |
3636 | - def test_install_package_trailing_slash(self, mock_user_home): |
3637 | - mock_user_home.return_value = "/home/test-user" |
3638 | - with temp_hooks_dir(self.temp_dir): |
3639 | + def test_install_package_trailing_slash(self): |
3640 | + with self.run_in_subprocess( |
3641 | + "click_get_hooks_dir", "click_get_user_home", |
3642 | + ) as (enter, preloads): |
3643 | + enter() |
3644 | + self._setup_hooks_dir(preloads) |
3645 | + preloads["click_get_user_home"].return_value = "/home/test-user" |
3646 | os.makedirs(os.path.join( |
3647 | self.temp_dir, "org.example.package", "1.0")) |
3648 | - user_db = ClickUser(self.db, user="test-user") |
3649 | + user_db = Click.User.for_user(self.db, "test-user") |
3650 | user_db.set_version("org.example.package", "1.0") |
3651 | with mkfile(os.path.join(self.temp_dir, "test.hook")) as f: |
3652 | print("User-Level: yes", file=f) |
3653 | print("Pattern: %s/${id}/" % self.temp_dir, file=f) |
3654 | - hook = ClickHook.open(self.db, "test") |
3655 | - hook.install_package( |
3656 | - "org.example.package", "1.0", "test-app", "foo", user="test-user") |
3657 | - symlink_path = os.path.join( |
3658 | - self.temp_dir, "org.example.package_test-app_1.0") |
3659 | - target_path = os.path.join( |
3660 | - self.temp_dir, ".click", "users", "test-user", |
3661 | - "org.example.package", "foo") |
3662 | - self.assertTrue(os.path.islink(symlink_path)) |
3663 | - self.assertEqual(target_path, os.readlink(symlink_path)) |
3664 | + hook = Click.Hook.open(self.db, "test") |
3665 | + hook.install_package( |
3666 | + "org.example.package", "1.0", "test-app", "foo", |
3667 | + user_name="test-user") |
3668 | + symlink_path = os.path.join( |
3669 | + self.temp_dir, "org.example.package_test-app_1.0") |
3670 | + target_path = os.path.join( |
3671 | + self.temp_dir, ".click", "users", "test-user", |
3672 | + "org.example.package", "foo") |
3673 | + self.assertTrue(os.path.islink(symlink_path)) |
3674 | + self.assertEqual(target_path, os.readlink(symlink_path)) |
3675 | |
3676 | - @mock.patch("click.hooks.ClickHook._user_home") |
3677 | - def test_install_package_removes_previous(self, mock_user_home): |
3678 | - mock_user_home.return_value = "/home/test-user" |
3679 | - with temp_hooks_dir(self.temp_dir): |
3680 | + def test_install_package_removes_previous(self): |
3681 | + with self.run_in_subprocess( |
3682 | + "click_get_hooks_dir", "click_get_user_home", |
3683 | + ) as (enter, preloads): |
3684 | + enter() |
3685 | + self._setup_hooks_dir(preloads) |
3686 | + preloads["click_get_user_home"].return_value = "/home/test-user" |
3687 | os.makedirs(os.path.join( |
3688 | self.temp_dir, "org.example.package", "1.0")) |
3689 | os.makedirs(os.path.join( |
3690 | self.temp_dir, "org.example.package", "1.1")) |
3691 | - user_db = ClickUser(self.db, user="test-user") |
3692 | + user_db = Click.User.for_user(self.db, "test-user") |
3693 | user_db.set_version("org.example.package", "1.0") |
3694 | with mkfile(os.path.join(self.temp_dir, "test.hook")) as f: |
3695 | print("User-Level: yes", file=f) |
3696 | print("Pattern: %s/${id}.test" % self.temp_dir, file=f) |
3697 | - hook = ClickHook.open(self.db, "test") |
3698 | - hook.install_package( |
3699 | - "org.example.package", "1.0", "test-app", "foo/bar", |
3700 | - user="test-user") |
3701 | - hook.install_package( |
3702 | - "org.example.package", "1.1", "test-app", "foo/bar", |
3703 | - user="test-user") |
3704 | - old_symlink_path = os.path.join( |
3705 | - self.temp_dir, "org.example.package_test-app_1.0.test") |
3706 | - symlink_path = os.path.join( |
3707 | - self.temp_dir, "org.example.package_test-app_1.1.test") |
3708 | - self.assertFalse(os.path.islink(old_symlink_path)) |
3709 | - self.assertTrue(os.path.islink(symlink_path)) |
3710 | - target_path = os.path.join( |
3711 | - self.temp_dir, ".click", "users", "test-user", |
3712 | - "org.example.package", "foo", "bar") |
3713 | - self.assertEqual(target_path, os.readlink(symlink_path)) |
3714 | + hook = Click.Hook.open(self.db, "test") |
3715 | + hook.install_package( |
3716 | + "org.example.package", "1.0", "test-app", "foo/bar", |
3717 | + user_name="test-user") |
3718 | + hook.install_package( |
3719 | + "org.example.package", "1.1", "test-app", "foo/bar", |
3720 | + user_name="test-user") |
3721 | + old_symlink_path = os.path.join( |
3722 | + self.temp_dir, "org.example.package_test-app_1.0.test") |
3723 | + symlink_path = os.path.join( |
3724 | + self.temp_dir, "org.example.package_test-app_1.1.test") |
3725 | + self.assertFalse(os.path.islink(old_symlink_path)) |
3726 | + self.assertTrue(os.path.islink(symlink_path)) |
3727 | + target_path = os.path.join( |
3728 | + self.temp_dir, ".click", "users", "test-user", |
3729 | + "org.example.package", "foo", "bar") |
3730 | + self.assertEqual(target_path, os.readlink(symlink_path)) |
3731 | |
3732 | - @mock.patch("click.hooks.ClickHook._user_home") |
3733 | - def test_upgrade(self, mock_user_home): |
3734 | - mock_user_home.return_value = "/home/test-user" |
3735 | - symlink_path = os.path.join( |
3736 | - self.temp_dir, "org.example.package_test-app_1.0.test") |
3737 | - os.symlink("old-target", symlink_path) |
3738 | - with temp_hooks_dir(self.temp_dir): |
3739 | + def test_upgrade(self): |
3740 | + with self.run_in_subprocess( |
3741 | + "click_get_hooks_dir", "click_get_user_home", |
3742 | + ) as (enter, preloads): |
3743 | + enter() |
3744 | + self._setup_hooks_dir(preloads) |
3745 | + preloads["click_get_user_home"].return_value = "/home/test-user" |
3746 | + symlink_path = os.path.join( |
3747 | + self.temp_dir, "org.example.package_test-app_1.0.test") |
3748 | + os.symlink("old-target", symlink_path) |
3749 | os.makedirs(os.path.join( |
3750 | self.temp_dir, "org.example.package", "1.0")) |
3751 | - user_db = ClickUser(self.db, user="test-user") |
3752 | + user_db = Click.User.for_user(self.db, "test-user") |
3753 | user_db.set_version("org.example.package", "1.0") |
3754 | with mkfile(os.path.join(self.temp_dir, "test.hook")) as f: |
3755 | print("User-Level: yes", file=f) |
3756 | print("Pattern: %s/${id}.test" % self.temp_dir, file=f) |
3757 | - hook = ClickHook.open(self.db, "test") |
3758 | - hook.install_package( |
3759 | - "org.example.package", "1.0", "test-app", "foo/bar", |
3760 | - user="test-user") |
3761 | - target_path = os.path.join( |
3762 | - self.temp_dir, ".click", "users", "test-user", |
3763 | - "org.example.package", "foo", "bar") |
3764 | - self.assertTrue(os.path.islink(symlink_path)) |
3765 | - self.assertEqual(target_path, os.readlink(symlink_path)) |
3766 | - |
3767 | - @mock.patch("click.hooks.ClickHook._user_home") |
3768 | - def test_remove_package(self, mock_user_home): |
3769 | - mock_user_home.return_value = "/home/test-user" |
3770 | - with mkfile(os.path.join(self.temp_dir, "test.hook")) as f: |
3771 | - print("User-Level: yes", file=f) |
3772 | - print("Pattern: %s/${id}.test" % self.temp_dir, file=f) |
3773 | - symlink_path = os.path.join( |
3774 | - self.temp_dir, "org.example.package_test-app_1.0.test") |
3775 | - os.symlink("old-target", symlink_path) |
3776 | - with temp_hooks_dir(self.temp_dir): |
3777 | - hook = ClickHook.open(self.db, "test") |
3778 | - hook.remove_package( |
3779 | - "org.example.package", "1.0", "test-app", user="test-user") |
3780 | - self.assertFalse(os.path.exists(symlink_path)) |
3781 | - |
3782 | - @mock.patch("click.hooks.ClickHook._user_home") |
3783 | - def test_install(self, mock_user_home): |
3784 | - mock_user_home.return_value = "/home/test-user" |
3785 | - with mkfile(os.path.join(self.temp_dir, "hooks", "new.hook")) as f: |
3786 | - print("User-Level: yes", file=f) |
3787 | - print("Pattern: %s/${id}.new" % self.temp_dir, file=f) |
3788 | - user_db = ClickUser(self.db, user="test-user") |
3789 | - with mkfile_utf8(os.path.join( |
3790 | - self.temp_dir, "test-1", "1.0", ".click", "info", |
3791 | - "test-1.manifest")) as f: |
3792 | - json.dump({ |
3793 | - "maintainer": |
3794 | - b"Unic\xc3\xb3de <unicode@example.org>".decode("UTF-8"), |
3795 | - "hooks": {"test1-app": {"new": "target-1"}}, |
3796 | - }, f, ensure_ascii=False) |
3797 | - user_db.set_version("test-1", "1.0") |
3798 | - with mkfile_utf8(os.path.join( |
3799 | - self.temp_dir, "test-2", "2.0", ".click", "info", |
3800 | - "test-2.manifest")) as f: |
3801 | - json.dump({ |
3802 | - "maintainer": |
3803 | - b"Unic\xc3\xb3de <unicode@example.org>".decode("UTF-8"), |
3804 | - "hooks": {"test1-app": {"new": "target-2"}}, |
3805 | - }, f, ensure_ascii=False) |
3806 | - user_db.set_version("test-2", "2.0") |
3807 | - with temp_hooks_dir(os.path.join(self.temp_dir, "hooks")): |
3808 | - hook = ClickHook.open(self.db, "new") |
3809 | - hook.install() |
3810 | - path_1 = os.path.join(self.temp_dir, "test-1_test1-app_1.0.new") |
3811 | - self.assertTrue(os.path.lexists(path_1)) |
3812 | - self.assertEqual( |
3813 | - os.path.join( |
3814 | - self.temp_dir, ".click", "users", "test-user", "test-1", |
3815 | - "target-1"), |
3816 | - os.readlink(path_1)) |
3817 | - path_2 = os.path.join(self.temp_dir, "test-2_test1-app_2.0.new") |
3818 | - self.assertTrue(os.path.lexists(path_2)) |
3819 | - self.assertEqual( |
3820 | - os.path.join( |
3821 | - self.temp_dir, ".click", "users", "test-user", "test-2", |
3822 | - "target-2"), |
3823 | - os.readlink(path_2)) |
3824 | - |
3825 | - os.unlink(path_1) |
3826 | - os.unlink(path_2) |
3827 | - hook.install(user="another-user") |
3828 | - self.assertFalse(os.path.lexists(path_1)) |
3829 | - self.assertFalse(os.path.lexists(path_2)) |
3830 | - |
3831 | - hook.install(user="test-user") |
3832 | - self.assertTrue(os.path.lexists(path_1)) |
3833 | - self.assertEqual( |
3834 | - os.path.join( |
3835 | - self.temp_dir, ".click", "users", "test-user", "test-1", |
3836 | - "target-1"), |
3837 | - os.readlink(path_1)) |
3838 | - self.assertTrue(os.path.lexists(path_2)) |
3839 | - self.assertEqual( |
3840 | - os.path.join( |
3841 | - self.temp_dir, ".click", "users", "test-user", "test-2", |
3842 | - "target-2"), |
3843 | - os.readlink(path_2)) |
3844 | - |
3845 | - @mock.patch("click.hooks.ClickHook._user_home") |
3846 | - def test_remove(self, mock_user_home): |
3847 | - mock_user_home.return_value = "/home/test-user" |
3848 | - with mkfile(os.path.join(self.temp_dir, "hooks", "old.hook")) as f: |
3849 | - print("User-Level: yes", file=f) |
3850 | - print("Pattern: %s/${id}.old" % self.temp_dir, file=f) |
3851 | - user_db = ClickUser(self.db, user="test-user") |
3852 | - with mkfile(os.path.join( |
3853 | - self.temp_dir, "test-1", "1.0", ".click", "info", |
3854 | - "test-1.manifest")) as f: |
3855 | - json.dump({"hooks": {"test1-app": {"old": "target-1"}}}, f) |
3856 | - user_db.set_version("test-1", "1.0") |
3857 | - os.symlink("1.0", os.path.join(self.temp_dir, "test-1", "current")) |
3858 | - path_1 = os.path.join(self.temp_dir, "test-1_test1-app_1.0.old") |
3859 | - os.symlink(os.path.join(user_db.path("test-1"), "target-1"), path_1) |
3860 | - with mkfile(os.path.join( |
3861 | - self.temp_dir, "test-2", "2.0", ".click", "info", |
3862 | - "test-2.manifest")) as f: |
3863 | - json.dump({"hooks": {"test2-app": {"old": "target-2"}}}, f) |
3864 | - user_db.set_version("test-2", "2.0") |
3865 | - path_2 = os.path.join(self.temp_dir, "test-2_test2-app_2.0.old") |
3866 | - os.symlink(os.path.join(user_db.path("test-2"), "target-2"), path_2) |
3867 | - with temp_hooks_dir(os.path.join(self.temp_dir, "hooks")): |
3868 | - hook = ClickHook.open(self.db, "old") |
3869 | - hook.remove() |
3870 | - self.assertFalse(os.path.exists(path_1)) |
3871 | - self.assertFalse(os.path.exists(path_2)) |
3872 | - |
3873 | - @mock.patch("click.hooks.ClickHook._user_home") |
3874 | - def test_sync(self, mock_user_home): |
3875 | - mock_user_home.return_value = "/home/test-user" |
3876 | - with mkfile(os.path.join(self.temp_dir, "hooks", "test.hook")) as f: |
3877 | - print("User-Level: yes", file=f) |
3878 | - print("Pattern: %s/${id}.test" % self.temp_dir, file=f) |
3879 | - user_db = ClickUser(self.db, user="test-user") |
3880 | - with mkfile(os.path.join( |
3881 | - self.temp_dir, "test-1", "1.0", ".click", "info", |
3882 | - "test-1.manifest")) as f: |
3883 | - json.dump({"hooks": {"test1-app": {"test": "target-1"}}}, f) |
3884 | - user_db.set_version("test-1", "1.0") |
3885 | - with mkfile(os.path.join( |
3886 | - self.temp_dir, "test-2", "1.1", ".click", "info", |
3887 | - "test-2.manifest")) as f: |
3888 | - json.dump({"hooks": {"test2-app": {"test": "target-2"}}}, f) |
3889 | - user_db.set_version("test-2", "1.1") |
3890 | - path_1 = os.path.join(self.temp_dir, "test-1_test1-app_1.0.test") |
3891 | - os.symlink( |
3892 | - os.path.join( |
3893 | - self.temp_dir, ".click", "users", "test-user", "test-1", |
3894 | - "target-1"), |
3895 | - path_1) |
3896 | - path_2 = os.path.join(self.temp_dir, "test-2_test2-app_1.1.test") |
3897 | - path_3 = os.path.join(self.temp_dir, "test-3_test3-app_1.0.test") |
3898 | - os.symlink( |
3899 | - os.path.join( |
3900 | - self.temp_dir, ".click", "users", "test-user", "test-3", |
3901 | - "target-3"), |
3902 | - path_3) |
3903 | - with temp_hooks_dir(os.path.join(self.temp_dir, "hooks")): |
3904 | - hook = ClickHook.open(self.db, "test") |
3905 | - hook.sync(user="test-user") |
3906 | - self.assertTrue(os.path.lexists(path_1)) |
3907 | - self.assertEqual( |
3908 | - os.path.join( |
3909 | - self.temp_dir, ".click", "users", "test-user", "test-1", |
3910 | - "target-1"), |
3911 | - os.readlink(path_1)) |
3912 | - self.assertTrue(os.path.lexists(path_2)) |
3913 | - self.assertEqual( |
3914 | - os.path.join( |
3915 | - self.temp_dir, ".click", "users", "test-user", "test-2", |
3916 | - "target-2"), |
3917 | - os.readlink(path_2)) |
3918 | - self.assertFalse(os.path.lexists(path_3)) |
3919 | + hook = Click.Hook.open(self.db, "test") |
3920 | + hook.install_package( |
3921 | + "org.example.package", "1.0", "test-app", "foo/bar", |
3922 | + user_name="test-user") |
3923 | + target_path = os.path.join( |
3924 | + self.temp_dir, ".click", "users", "test-user", |
3925 | + "org.example.package", "foo", "bar") |
3926 | + self.assertTrue(os.path.islink(symlink_path)) |
3927 | + self.assertEqual(target_path, os.readlink(symlink_path)) |
3928 | + |
3929 | + def test_remove_package(self): |
3930 | + with self.run_in_subprocess( |
3931 | + "click_get_hooks_dir", "click_get_user_home", |
3932 | + ) as (enter, preloads): |
3933 | + enter() |
3934 | + self._setup_hooks_dir(preloads) |
3935 | + preloads["click_get_user_home"].return_value = "/home/test-user" |
3936 | + with mkfile(os.path.join(self.temp_dir, "test.hook")) as f: |
3937 | + print("User-Level: yes", file=f) |
3938 | + print("Pattern: %s/${id}.test" % self.temp_dir, file=f) |
3939 | + symlink_path = os.path.join( |
3940 | + self.temp_dir, "org.example.package_test-app_1.0.test") |
3941 | + os.symlink("old-target", symlink_path) |
3942 | + hook = Click.Hook.open(self.db, "test") |
3943 | + hook.remove_package( |
3944 | + "org.example.package", "1.0", "test-app", |
3945 | + user_name="test-user") |
3946 | + self.assertFalse(os.path.exists(symlink_path)) |
3947 | + |
3948 | + def test_install(self): |
3949 | + with self.run_in_subprocess( |
3950 | + "click_get_hooks_dir", "click_get_user_home", |
3951 | + ) as (enter, preloads): |
3952 | + enter() |
3953 | + self._setup_hooks_dir(preloads) |
3954 | + preloads["click_get_user_home"].return_value = "/home/test-user" |
3955 | + with mkfile(os.path.join(self.temp_dir, "hooks", "new.hook")) as f: |
3956 | + print("User-Level: yes", file=f) |
3957 | + print("Pattern: %s/${id}.new" % self.temp_dir, file=f) |
3958 | + user_db = Click.User.for_user(self.db, "test-user") |
3959 | + with mkfile_utf8(os.path.join( |
3960 | + self.temp_dir, "test-1", "1.0", ".click", "info", |
3961 | + "test-1.manifest")) as f: |
3962 | + json.dump({ |
3963 | + "maintainer": |
3964 | + b"Unic\xc3\xb3de <unicode@example.org>".decode( |
3965 | + "UTF-8"), |
3966 | + "hooks": {"test1-app": {"new": "target-1"}}, |
3967 | + }, f, ensure_ascii=False) |
3968 | + user_db.set_version("test-1", "1.0") |
3969 | + with mkfile_utf8(os.path.join( |
3970 | + self.temp_dir, "test-2", "2.0", ".click", "info", |
3971 | + "test-2.manifest")) as f: |
3972 | + json.dump({ |
3973 | + "maintainer": |
3974 | + b"Unic\xc3\xb3de <unicode@example.org>".decode( |
3975 | + "UTF-8"), |
3976 | + "hooks": {"test1-app": {"new": "target-2"}}, |
3977 | + }, f, ensure_ascii=False) |
3978 | + user_db.set_version("test-2", "2.0") |
3979 | + self._setup_hooks_dir( |
3980 | + preloads, hooks_dir=os.path.join(self.temp_dir, "hooks")) |
3981 | + hook = Click.Hook.open(self.db, "new") |
3982 | + hook.install() |
3983 | + path_1 = os.path.join(self.temp_dir, "test-1_test1-app_1.0.new") |
3984 | + self.assertTrue(os.path.lexists(path_1)) |
3985 | + self.assertEqual( |
3986 | + os.path.join( |
3987 | + self.temp_dir, ".click", "users", "test-user", "test-1", |
3988 | + "target-1"), |
3989 | + os.readlink(path_1)) |
3990 | + path_2 = os.path.join(self.temp_dir, "test-2_test1-app_2.0.new") |
3991 | + self.assertTrue(os.path.lexists(path_2)) |
3992 | + self.assertEqual( |
3993 | + os.path.join( |
3994 | + self.temp_dir, ".click", "users", "test-user", "test-2", |
3995 | + "target-2"), |
3996 | + os.readlink(path_2)) |
3997 | + |
3998 | + os.unlink(path_1) |
3999 | + os.unlink(path_2) |
4000 | + hook.install(user_name="another-user") |
4001 | + self.assertFalse(os.path.lexists(path_1)) |
4002 | + self.assertFalse(os.path.lexists(path_2)) |
4003 | + |
4004 | + hook.install(user_name="test-user") |
4005 | + self.assertTrue(os.path.lexists(path_1)) |
4006 | + self.assertEqual( |
4007 | + os.path.join( |
4008 | + self.temp_dir, ".click", "users", "test-user", "test-1", |
4009 | + "target-1"), |
4010 | + os.readlink(path_1)) |
4011 | + self.assertTrue(os.path.lexists(path_2)) |
4012 | + self.assertEqual( |
4013 | + os.path.join( |
4014 | + self.temp_dir, ".click", "users", "test-user", "test-2", |
4015 | + "target-2"), |
4016 | + os.readlink(path_2)) |
4017 | + |
4018 | + def test_remove(self): |
4019 | + with self.run_in_subprocess( |
4020 | + "click_get_hooks_dir", "click_get_user_home", |
4021 | + ) as (enter, preloads): |
4022 | + enter() |
4023 | + self._setup_hooks_dir(preloads) |
4024 | + preloads["click_get_user_home"].return_value = "/home/test-user" |
4025 | + with mkfile(os.path.join(self.temp_dir, "hooks", "old.hook")) as f: |
4026 | + print("User-Level: yes", file=f) |
4027 | + print("Pattern: %s/${id}.old" % self.temp_dir, file=f) |
4028 | + user_db = Click.User.for_user(self.db, "test-user") |
4029 | + with mkfile(os.path.join( |
4030 | + self.temp_dir, "test-1", "1.0", ".click", "info", |
4031 | + "test-1.manifest")) as f: |
4032 | + json.dump({"hooks": {"test1-app": {"old": "target-1"}}}, f) |
4033 | + user_db.set_version("test-1", "1.0") |
4034 | + os.symlink("1.0", os.path.join(self.temp_dir, "test-1", "current")) |
4035 | + path_1 = os.path.join(self.temp_dir, "test-1_test1-app_1.0.old") |
4036 | + os.symlink( |
4037 | + os.path.join(user_db.get_path("test-1"), "target-1"), path_1) |
4038 | + with mkfile(os.path.join( |
4039 | + self.temp_dir, "test-2", "2.0", ".click", "info", |
4040 | + "test-2.manifest")) as f: |
4041 | + json.dump({"hooks": {"test2-app": {"old": "target-2"}}}, f) |
4042 | + user_db.set_version("test-2", "2.0") |
4043 | + path_2 = os.path.join(self.temp_dir, "test-2_test2-app_2.0.old") |
4044 | + os.symlink( |
4045 | + os.path.join(user_db.get_path("test-2"), "target-2"), path_2) |
4046 | + self._setup_hooks_dir( |
4047 | + preloads, hooks_dir=os.path.join(self.temp_dir, "hooks")) |
4048 | + hook = Click.Hook.open(self.db, "old") |
4049 | + hook.remove() |
4050 | + self.assertFalse(os.path.exists(path_1)) |
4051 | + self.assertFalse(os.path.exists(path_2)) |
4052 | + |
4053 | + def test_sync(self): |
4054 | + with self.run_in_subprocess( |
4055 | + "click_get_hooks_dir", "click_get_user_home", |
4056 | + ) as (enter, preloads): |
4057 | + enter() |
4058 | + preloads["click_get_user_home"].return_value = "/home/test-user" |
4059 | + self._setup_hooks_dir(preloads) |
4060 | + with mkfile( |
4061 | + os.path.join(self.temp_dir, "hooks", "test.hook")) as f: |
4062 | + print("User-Level: yes", file=f) |
4063 | + print("Pattern: %s/${id}.test" % self.temp_dir, file=f) |
4064 | + user_db = Click.User.for_user(self.db, "test-user") |
4065 | + with mkfile(os.path.join( |
4066 | + self.temp_dir, "test-1", "1.0", ".click", "info", |
4067 | + "test-1.manifest")) as f: |
4068 | + json.dump({"hooks": {"test1-app": {"test": "target-1"}}}, f) |
4069 | + user_db.set_version("test-1", "1.0") |
4070 | + with mkfile(os.path.join( |
4071 | + self.temp_dir, "test-2", "1.1", ".click", "info", |
4072 | + "test-2.manifest")) as f: |
4073 | + json.dump({"hooks": {"test2-app": {"test": "target-2"}}}, f) |
4074 | + user_db.set_version("test-2", "1.1") |
4075 | + path_1 = os.path.join(self.temp_dir, "test-1_test1-app_1.0.test") |
4076 | + os.symlink( |
4077 | + os.path.join( |
4078 | + self.temp_dir, ".click", "users", "test-user", "test-1", |
4079 | + "target-1"), |
4080 | + path_1) |
4081 | + path_2 = os.path.join(self.temp_dir, "test-2_test2-app_1.1.test") |
4082 | + path_3 = os.path.join(self.temp_dir, "test-3_test3-app_1.0.test") |
4083 | + os.symlink( |
4084 | + os.path.join( |
4085 | + self.temp_dir, ".click", "users", "test-user", "test-3", |
4086 | + "target-3"), |
4087 | + path_3) |
4088 | + self._setup_hooks_dir( |
4089 | + preloads, hooks_dir=os.path.join(self.temp_dir, "hooks")) |
4090 | + hook = Click.Hook.open(self.db, "test") |
4091 | + hook.sync(user_name="test-user") |
4092 | + self.assertTrue(os.path.lexists(path_1)) |
4093 | + self.assertEqual( |
4094 | + os.path.join( |
4095 | + self.temp_dir, ".click", "users", "test-user", "test-1", |
4096 | + "target-1"), |
4097 | + os.readlink(path_1)) |
4098 | + self.assertTrue(os.path.lexists(path_2)) |
4099 | + self.assertEqual( |
4100 | + os.path.join( |
4101 | + self.temp_dir, ".click", "users", "test-user", "test-2", |
4102 | + "target-2"), |
4103 | + os.readlink(path_2)) |
4104 | + self.assertFalse(os.path.lexists(path_3)) |
4105 | |
4106 | |
4107 | class TestPackageInstallHooks(TestClickHookBase): |
4108 | def test_removes_old_hooks(self): |
4109 | - hooks_dir = os.path.join(self.temp_dir, "hooks") |
4110 | - with mkfile(os.path.join(hooks_dir, "unity.hook")) as f: |
4111 | - print("Pattern: %s/unity/${id}.scope" % self.temp_dir, file=f) |
4112 | - print("Single-Version: yes", file=f) |
4113 | - with mkfile(os.path.join(hooks_dir, "yelp-docs.hook")) as f: |
4114 | - print("Pattern: %s/yelp/docs-${id}.txt" % self.temp_dir, file=f) |
4115 | - print("Single-Version: yes", file=f) |
4116 | - print("Hook-Name: yelp", file=f) |
4117 | - with mkfile(os.path.join(hooks_dir, "yelp-other.hook")) as f: |
4118 | - print("Pattern: %s/yelp/other-${id}.txt" % self.temp_dir, file=f) |
4119 | - print("Single-Version: yes", file=f) |
4120 | - print("Hook-Name: yelp", file=f) |
4121 | - os.mkdir(os.path.join(self.temp_dir, "unity")) |
4122 | - unity_path = os.path.join(self.temp_dir, "unity", "test_app_1.0.scope") |
4123 | - os.symlink("dummy", unity_path) |
4124 | - os.mkdir(os.path.join(self.temp_dir, "yelp")) |
4125 | - yelp_docs_path = os.path.join( |
4126 | - self.temp_dir, "yelp", "docs-test_app_1.0.txt") |
4127 | - os.symlink("dummy", yelp_docs_path) |
4128 | - yelp_other_path = os.path.join( |
4129 | - self.temp_dir, "yelp", "other-test_app_1.0.txt") |
4130 | - os.symlink("dummy", yelp_other_path) |
4131 | - package_dir = os.path.join(self.temp_dir, "test") |
4132 | - with mkfile(os.path.join( |
4133 | - package_dir, "1.0", ".click", "info", "test.manifest")) as f: |
4134 | - json.dump( |
4135 | - {"hooks": {"app": {"yelp": "foo.txt", "unity": "foo.scope"}}}, |
4136 | - f) |
4137 | - with mkfile(os.path.join( |
4138 | - package_dir, "1.1", ".click", "info", "test.manifest")) as f: |
4139 | - json.dump({}, f) |
4140 | - with temp_hooks_dir(hooks_dir): |
4141 | - package_install_hooks(self.db, "test", "1.0", "1.1") |
4142 | - self.assertFalse(os.path.lexists(unity_path)) |
4143 | - self.assertFalse(os.path.lexists(yelp_docs_path)) |
4144 | - self.assertFalse(os.path.lexists(yelp_other_path)) |
4145 | + with self.run_in_subprocess( |
4146 | + "click_get_hooks_dir") as (enter, preloads): |
4147 | + enter() |
4148 | + hooks_dir = os.path.join(self.temp_dir, "hooks") |
4149 | + self._setup_hooks_dir(preloads, hooks_dir=hooks_dir) |
4150 | + with mkfile(os.path.join(hooks_dir, "unity.hook")) as f: |
4151 | + print("Pattern: %s/unity/${id}.scope" % self.temp_dir, file=f) |
4152 | + print("Single-Version: yes", file=f) |
4153 | + with mkfile(os.path.join(hooks_dir, "yelp-docs.hook")) as f: |
4154 | + print("Pattern: %s/yelp/docs-${id}.txt" % self.temp_dir, |
4155 | + file=f) |
4156 | + print("Single-Version: yes", file=f) |
4157 | + print("Hook-Name: yelp", file=f) |
4158 | + with mkfile(os.path.join(hooks_dir, "yelp-other.hook")) as f: |
4159 | + print("Pattern: %s/yelp/other-${id}.txt" % self.temp_dir, |
4160 | + file=f) |
4161 | + print("Single-Version: yes", file=f) |
4162 | + print("Hook-Name: yelp", file=f) |
4163 | + os.mkdir(os.path.join(self.temp_dir, "unity")) |
4164 | + unity_path = os.path.join( |
4165 | + self.temp_dir, "unity", "test_app_1.0.scope") |
4166 | + os.symlink("dummy", unity_path) |
4167 | + os.mkdir(os.path.join(self.temp_dir, "yelp")) |
4168 | + yelp_docs_path = os.path.join( |
4169 | + self.temp_dir, "yelp", "docs-test_app_1.0.txt") |
4170 | + os.symlink("dummy", yelp_docs_path) |
4171 | + yelp_other_path = os.path.join( |
4172 | + self.temp_dir, "yelp", "other-test_app_1.0.txt") |
4173 | + os.symlink("dummy", yelp_other_path) |
4174 | + package_dir = os.path.join(self.temp_dir, "test") |
4175 | + with mkfile(os.path.join( |
4176 | + package_dir, "1.0", ".click", "info", |
4177 | + "test.manifest")) as f: |
4178 | + json.dump( |
4179 | + {"hooks": {"app": {"yelp": "foo.txt", "unity": "foo.scope"}}}, |
4180 | + f) |
4181 | + with mkfile(os.path.join( |
4182 | + package_dir, "1.1", ".click", "info", |
4183 | + "test.manifest")) as f: |
4184 | + json.dump({}, f) |
4185 | + Click.package_install_hooks(self.db, "test", "1.0", "1.1") |
4186 | + self.assertFalse(os.path.lexists(unity_path)) |
4187 | + self.assertFalse(os.path.lexists(yelp_docs_path)) |
4188 | + self.assertFalse(os.path.lexists(yelp_other_path)) |
4189 | |
4190 | def test_installs_new_hooks(self): |
4191 | - hooks_dir = os.path.join(self.temp_dir, "hooks") |
4192 | - with mkfile(os.path.join(hooks_dir, "a.hook")) as f: |
4193 | - print("Pattern: %s/a/${id}.a" % self.temp_dir, file=f) |
4194 | - with mkfile(os.path.join(hooks_dir, "b-1.hook")) as f: |
4195 | - print("Pattern: %s/b/1-${id}.b" % self.temp_dir, file=f) |
4196 | - print("Hook-Name: b", file=f) |
4197 | - with mkfile(os.path.join(hooks_dir, "b-2.hook")) as f: |
4198 | - print("Pattern: %s/b/2-${id}.b" % self.temp_dir, file=f) |
4199 | - print("Hook-Name: b", file=f) |
4200 | - os.mkdir(os.path.join(self.temp_dir, "a")) |
4201 | - os.mkdir(os.path.join(self.temp_dir, "b")) |
4202 | - package_dir = os.path.join(self.temp_dir, "test") |
4203 | - with mkfile(os.path.join( |
4204 | - package_dir, "1.0", ".click", "info", "test.manifest")) as f: |
4205 | - json.dump({"hooks": {}}, f) |
4206 | - with mkfile(os.path.join( |
4207 | - package_dir, "1.1", ".click", "info", "test.manifest")) as f: |
4208 | - json.dump({"hooks": {"app": {"a": "foo.a", "b": "foo.b"}}}, f) |
4209 | - with temp_hooks_dir(hooks_dir): |
4210 | - package_install_hooks(self.db, "test", "1.0", "1.1") |
4211 | - self.assertTrue(os.path.lexists( |
4212 | - os.path.join(self.temp_dir, "a", "test_app_1.1.a"))) |
4213 | - self.assertTrue(os.path.lexists( |
4214 | - os.path.join(self.temp_dir, "b", "1-test_app_1.1.b"))) |
4215 | - self.assertTrue(os.path.lexists( |
4216 | - os.path.join(self.temp_dir, "b", "2-test_app_1.1.b"))) |
4217 | + with self.run_in_subprocess( |
4218 | + "click_get_hooks_dir") as (enter, preloads): |
4219 | + enter() |
4220 | + hooks_dir = os.path.join(self.temp_dir, "hooks") |
4221 | + self._setup_hooks_dir(preloads, hooks_dir=hooks_dir) |
4222 | + with mkfile(os.path.join(hooks_dir, "a.hook")) as f: |
4223 | + print("Pattern: %s/a/${id}.a" % self.temp_dir, file=f) |
4224 | + with mkfile(os.path.join(hooks_dir, "b-1.hook")) as f: |
4225 | + print("Pattern: %s/b/1-${id}.b" % self.temp_dir, file=f) |
4226 | + print("Hook-Name: b", file=f) |
4227 | + with mkfile(os.path.join(hooks_dir, "b-2.hook")) as f: |
4228 | + print("Pattern: %s/b/2-${id}.b" % self.temp_dir, file=f) |
4229 | + print("Hook-Name: b", file=f) |
4230 | + os.mkdir(os.path.join(self.temp_dir, "a")) |
4231 | + os.mkdir(os.path.join(self.temp_dir, "b")) |
4232 | + package_dir = os.path.join(self.temp_dir, "test") |
4233 | + with mkfile(os.path.join( |
4234 | + package_dir, "1.0", ".click", "info", |
4235 | + "test.manifest")) as f: |
4236 | + json.dump({"hooks": {}}, f) |
4237 | + with mkfile(os.path.join( |
4238 | + package_dir, "1.1", ".click", "info", |
4239 | + "test.manifest")) as f: |
4240 | + json.dump({"hooks": {"app": {"a": "foo.a", "b": "foo.b"}}}, f) |
4241 | + Click.package_install_hooks(self.db, "test", "1.0", "1.1") |
4242 | + self.assertTrue(os.path.lexists( |
4243 | + os.path.join(self.temp_dir, "a", "test_app_1.1.a"))) |
4244 | + self.assertTrue(os.path.lexists( |
4245 | + os.path.join(self.temp_dir, "b", "1-test_app_1.1.b"))) |
4246 | + self.assertTrue(os.path.lexists( |
4247 | + os.path.join(self.temp_dir, "b", "2-test_app_1.1.b"))) |
4248 | |
4249 | def test_upgrades_existing_hooks(self): |
4250 | - hooks_dir = os.path.join(self.temp_dir, "hooks") |
4251 | - with mkfile(os.path.join(hooks_dir, "a.hook")) as f: |
4252 | - print("Pattern: %s/a/${id}.a" % self.temp_dir, file=f) |
4253 | - print("Single-Version: yes", file=f) |
4254 | - with mkfile(os.path.join(hooks_dir, "b-1.hook")) as f: |
4255 | - print("Pattern: %s/b/1-${id}.b" % self.temp_dir, file=f) |
4256 | - print("Single-Version: yes", file=f) |
4257 | - print("Hook-Name: b", file=f) |
4258 | - with mkfile(os.path.join(hooks_dir, "b-2.hook")) as f: |
4259 | - print("Pattern: %s/b/2-${id}.b" % self.temp_dir, file=f) |
4260 | - print("Single-Version: yes", file=f) |
4261 | - print("Hook-Name: b", file=f) |
4262 | - with mkfile(os.path.join(hooks_dir, "c.hook")) as f: |
4263 | - print("Pattern: %s/c/${id}.c" % self.temp_dir, file=f) |
4264 | - print("Single-Version: yes", file=f) |
4265 | - os.mkdir(os.path.join(self.temp_dir, "a")) |
4266 | - a_path = os.path.join(self.temp_dir, "a", "test_app_1.0.a") |
4267 | - os.symlink("dummy", a_path) |
4268 | - os.mkdir(os.path.join(self.temp_dir, "b")) |
4269 | - b_irrelevant_path = os.path.join( |
4270 | - self.temp_dir, "b", "1-test_other-app_1.0.b") |
4271 | - os.symlink("dummy", b_irrelevant_path) |
4272 | - b_1_path = os.path.join(self.temp_dir, "b", "1-test_app_1.0.b") |
4273 | - os.symlink("dummy", b_1_path) |
4274 | - b_2_path = os.path.join(self.temp_dir, "b", "2-test_app_1.0.b") |
4275 | - os.symlink("dummy", b_2_path) |
4276 | - os.mkdir(os.path.join(self.temp_dir, "c")) |
4277 | - package_dir = os.path.join(self.temp_dir, "test") |
4278 | - with mkfile(os.path.join( |
4279 | - package_dir, "1.0", ".click", "info", "test.manifest")) as f: |
4280 | - json.dump({"hooks": {"app": {"a": "foo.a", "b": "foo.b"}}}, f) |
4281 | - with mkfile(os.path.join( |
4282 | - package_dir, "1.1", ".click", "info", "test.manifest")) as f: |
4283 | - json.dump( |
4284 | - {"hooks": { |
4285 | - "app": {"a": "foo.a", "b": "foo.b", "c": "foo.c"}} |
4286 | - }, f) |
4287 | - with temp_hooks_dir(hooks_dir): |
4288 | - package_install_hooks(self.db, "test", "1.0", "1.1") |
4289 | - self.assertFalse(os.path.lexists(a_path)) |
4290 | - self.assertTrue(os.path.lexists(b_irrelevant_path)) |
4291 | - self.assertFalse(os.path.lexists(b_1_path)) |
4292 | - self.assertFalse(os.path.lexists(b_2_path)) |
4293 | - self.assertTrue(os.path.lexists( |
4294 | - os.path.join(self.temp_dir, "a", "test_app_1.1.a"))) |
4295 | - self.assertTrue(os.path.lexists( |
4296 | - os.path.join(self.temp_dir, "b", "1-test_app_1.1.b"))) |
4297 | - self.assertTrue(os.path.lexists( |
4298 | - os.path.join(self.temp_dir, "b", "2-test_app_1.1.b"))) |
4299 | - self.assertTrue(os.path.lexists( |
4300 | - os.path.join(self.temp_dir, "c", "test_app_1.1.c"))) |
4301 | + with self.run_in_subprocess( |
4302 | + "click_get_hooks_dir") as (enter, preloads): |
4303 | + enter() |
4304 | + hooks_dir = os.path.join(self.temp_dir, "hooks") |
4305 | + self._setup_hooks_dir(preloads, hooks_dir=hooks_dir) |
4306 | + with mkfile(os.path.join(hooks_dir, "a.hook")) as f: |
4307 | + print("Pattern: %s/a/${id}.a" % self.temp_dir, file=f) |
4308 | + print("Single-Version: yes", file=f) |
4309 | + with mkfile(os.path.join(hooks_dir, "b-1.hook")) as f: |
4310 | + print("Pattern: %s/b/1-${id}.b" % self.temp_dir, file=f) |
4311 | + print("Single-Version: yes", file=f) |
4312 | + print("Hook-Name: b", file=f) |
4313 | + with mkfile(os.path.join(hooks_dir, "b-2.hook")) as f: |
4314 | + print("Pattern: %s/b/2-${id}.b" % self.temp_dir, file=f) |
4315 | + print("Single-Version: yes", file=f) |
4316 | + print("Hook-Name: b", file=f) |
4317 | + with mkfile(os.path.join(hooks_dir, "c.hook")) as f: |
4318 | + print("Pattern: %s/c/${id}.c" % self.temp_dir, file=f) |
4319 | + print("Single-Version: yes", file=f) |
4320 | + os.mkdir(os.path.join(self.temp_dir, "a")) |
4321 | + a_path = os.path.join(self.temp_dir, "a", "test_app_1.0.a") |
4322 | + os.symlink("dummy", a_path) |
4323 | + os.mkdir(os.path.join(self.temp_dir, "b")) |
4324 | + b_irrelevant_path = os.path.join( |
4325 | + self.temp_dir, "b", "1-test_other-app_1.0.b") |
4326 | + os.symlink("dummy", b_irrelevant_path) |
4327 | + b_1_path = os.path.join(self.temp_dir, "b", "1-test_app_1.0.b") |
4328 | + os.symlink("dummy", b_1_path) |
4329 | + b_2_path = os.path.join(self.temp_dir, "b", "2-test_app_1.0.b") |
4330 | + os.symlink("dummy", b_2_path) |
4331 | + os.mkdir(os.path.join(self.temp_dir, "c")) |
4332 | + package_dir = os.path.join(self.temp_dir, "test") |
4333 | + with mkfile(os.path.join( |
4334 | + package_dir, "1.0", ".click", "info", |
4335 | + "test.manifest")) as f: |
4336 | + json.dump({"hooks": {"app": {"a": "foo.a", "b": "foo.b"}}}, f) |
4337 | + with mkfile(os.path.join( |
4338 | + package_dir, "1.1", ".click", "info", |
4339 | + "test.manifest")) as f: |
4340 | + json.dump( |
4341 | + {"hooks": { |
4342 | + "app": {"a": "foo.a", "b": "foo.b", "c": "foo.c"}} |
4343 | + }, f) |
4344 | + Click.package_install_hooks(self.db, "test", "1.0", "1.1") |
4345 | + self.assertFalse(os.path.lexists(a_path)) |
4346 | + self.assertTrue(os.path.lexists(b_irrelevant_path)) |
4347 | + self.assertFalse(os.path.lexists(b_1_path)) |
4348 | + self.assertFalse(os.path.lexists(b_2_path)) |
4349 | + self.assertTrue(os.path.lexists( |
4350 | + os.path.join(self.temp_dir, "a", "test_app_1.1.a"))) |
4351 | + self.assertTrue(os.path.lexists( |
4352 | + os.path.join(self.temp_dir, "b", "1-test_app_1.1.b"))) |
4353 | + self.assertTrue(os.path.lexists( |
4354 | + os.path.join(self.temp_dir, "b", "2-test_app_1.1.b"))) |
4355 | + self.assertTrue(os.path.lexists( |
4356 | + os.path.join(self.temp_dir, "c", "test_app_1.1.c"))) |
4357 | |
4358 | |
4359 | class TestPackageRemoveHooks(TestClickHookBase): |
4360 | def test_removes_hooks(self): |
4361 | - hooks_dir = os.path.join(self.temp_dir, "hooks") |
4362 | - with mkfile(os.path.join(hooks_dir, "unity.hook")) as f: |
4363 | - print("Pattern: %s/unity/${id}.scope" % self.temp_dir, file=f) |
4364 | - with mkfile(os.path.join(hooks_dir, "yelp-docs.hook")) as f: |
4365 | - print("Pattern: %s/yelp/docs-${id}.txt" % self.temp_dir, file=f) |
4366 | - print("Hook-Name: yelp", file=f) |
4367 | - with mkfile(os.path.join(hooks_dir, "yelp-other.hook")) as f: |
4368 | - print("Pattern: %s/yelp/other-${id}.txt" % self.temp_dir, file=f) |
4369 | - print("Hook-Name: yelp", file=f) |
4370 | - os.mkdir(os.path.join(self.temp_dir, "unity")) |
4371 | - unity_path = os.path.join(self.temp_dir, "unity", "test_app_1.0.scope") |
4372 | - os.symlink("dummy", unity_path) |
4373 | - os.mkdir(os.path.join(self.temp_dir, "yelp")) |
4374 | - yelp_docs_path = os.path.join( |
4375 | - self.temp_dir, "yelp", "docs-test_app_1.0.txt") |
4376 | - os.symlink("dummy", yelp_docs_path) |
4377 | - yelp_other_path = os.path.join( |
4378 | - self.temp_dir, "yelp", "other-test_app_1.0.txt") |
4379 | - os.symlink("dummy", yelp_other_path) |
4380 | - package_dir = os.path.join(self.temp_dir, "test") |
4381 | - with mkfile(os.path.join( |
4382 | - package_dir, "1.0", ".click", "info", "test.manifest")) as f: |
4383 | - json.dump( |
4384 | - {"hooks": {"app": {"yelp": "foo.txt", "unity": "foo.scope"}}}, |
4385 | - f) |
4386 | - with temp_hooks_dir(hooks_dir): |
4387 | - package_remove_hooks(self.db, "test", "1.0") |
4388 | - self.assertFalse(os.path.lexists(unity_path)) |
4389 | - self.assertFalse(os.path.lexists(yelp_docs_path)) |
4390 | - self.assertFalse(os.path.lexists(yelp_other_path)) |
4391 | + with self.run_in_subprocess( |
4392 | + "click_get_hooks_dir") as (enter, preloads): |
4393 | + enter() |
4394 | + hooks_dir = os.path.join(self.temp_dir, "hooks") |
4395 | + self._setup_hooks_dir(preloads, hooks_dir=hooks_dir) |
4396 | + with mkfile(os.path.join(hooks_dir, "unity.hook")) as f: |
4397 | + print("Pattern: %s/unity/${id}.scope" % self.temp_dir, file=f) |
4398 | + with mkfile(os.path.join(hooks_dir, "yelp-docs.hook")) as f: |
4399 | + print("Pattern: %s/yelp/docs-${id}.txt" % self.temp_dir, |
4400 | + file=f) |
4401 | + print("Hook-Name: yelp", file=f) |
4402 | + with mkfile(os.path.join(hooks_dir, "yelp-other.hook")) as f: |
4403 | + print("Pattern: %s/yelp/other-${id}.txt" % self.temp_dir, |
4404 | + file=f) |
4405 | + print("Hook-Name: yelp", file=f) |
4406 | + os.mkdir(os.path.join(self.temp_dir, "unity")) |
4407 | + unity_path = os.path.join( |
4408 | + self.temp_dir, "unity", "test_app_1.0.scope") |
4409 | + os.symlink("dummy", unity_path) |
4410 | + os.mkdir(os.path.join(self.temp_dir, "yelp")) |
4411 | + yelp_docs_path = os.path.join( |
4412 | + self.temp_dir, "yelp", "docs-test_app_1.0.txt") |
4413 | + os.symlink("dummy", yelp_docs_path) |
4414 | + yelp_other_path = os.path.join( |
4415 | + self.temp_dir, "yelp", "other-test_app_1.0.txt") |
4416 | + os.symlink("dummy", yelp_other_path) |
4417 | + package_dir = os.path.join(self.temp_dir, "test") |
4418 | + with mkfile(os.path.join( |
4419 | + package_dir, "1.0", ".click", "info", |
4420 | + "test.manifest")) as f: |
4421 | + json.dump( |
4422 | + {"hooks": { |
4423 | + "app": {"yelp": "foo.txt", "unity": "foo.scope"}} |
4424 | + }, f) |
4425 | + Click.package_remove_hooks(self.db, "test", "1.0") |
4426 | + self.assertFalse(os.path.lexists(unity_path)) |
4427 | + self.assertFalse(os.path.lexists(yelp_docs_path)) |
4428 | + self.assertFalse(os.path.lexists(yelp_other_path)) |
4429 | |
4430 | === modified file 'click/tests/test_install.py' |
4431 | --- click/tests/test_install.py 2014-01-22 14:02:33 +0000 |
4432 | +++ click/tests/test_install.py 2014-03-06 07:04:44 +0000 |
4433 | @@ -34,10 +34,10 @@ |
4434 | from unittest import skipUnless |
4435 | |
4436 | from debian.deb822 import Deb822 |
4437 | +from gi.repository import Click |
4438 | |
4439 | -from click import install, osextras |
4440 | +from click import install |
4441 | from click.build import ClickBuilder |
4442 | -from click.database import ClickDB |
4443 | from click.install import ( |
4444 | ClickInstaller, |
4445 | ClickInstallerAuditError, |
4446 | @@ -68,7 +68,8 @@ |
4447 | def setUp(self): |
4448 | super(TestClickInstaller, self).setUp() |
4449 | self.use_temp_dir() |
4450 | - self.db = ClickDB(self.temp_dir) |
4451 | + self.db = Click.DB() |
4452 | + self.db.add(self.temp_dir) |
4453 | |
4454 | def make_fake_package(self, control_fields=None, manifest=None, |
4455 | control_scripts=None, data_files=None): |
4456 | @@ -90,7 +91,7 @@ |
4457 | for name, contents in control_scripts.items(): |
4458 | with mkfile(os.path.join(control_dir, name)) as script: |
4459 | script.write(contents) |
4460 | - osextras.ensuredir(data_dir) |
4461 | + Click.ensuredir(data_dir) |
4462 | for name, path in data_files.items(): |
4463 | if path is None: |
4464 | touch(os.path.join(data_dir, name)) |
4465 | @@ -108,11 +109,11 @@ |
4466 | old_dir = install.frameworks_dir |
4467 | try: |
4468 | install.frameworks_dir = os.path.join(self.temp_dir, "frameworks") |
4469 | - osextras.ensuredir(install.frameworks_dir) |
4470 | + Click.ensuredir(install.frameworks_dir) |
4471 | touch(os.path.join(install.frameworks_dir, "%s.framework" % name)) |
4472 | yield |
4473 | finally: |
4474 | - osextras.unlink_force( |
4475 | + Click.unlink_force( |
4476 | os.path.join(install.frameworks_dir, "%s.framework" % name)) |
4477 | install.frameworks_dir = old_dir |
4478 | |
4479 | @@ -342,7 +343,7 @@ |
4480 | @skipUnless( |
4481 | os.path.exists(ClickInstaller(None)._preload_path()), |
4482 | "preload bits not built; installing packages will fail") |
4483 | - @mock.patch("click.install.package_install_hooks") |
4484 | + @mock.patch("gi.repository.Click.package_install_hooks") |
4485 | def test_install(self, mock_package_install_hooks): |
4486 | path = self.make_fake_package( |
4487 | control_fields={ |
4488 | @@ -361,7 +362,8 @@ |
4489 | control_scripts={"preinst": static_preinst}, |
4490 | data_files={"foo": None}) |
4491 | root = os.path.join(self.temp_dir, "root") |
4492 | - db = ClickDB(root) |
4493 | + db = Click.DB() |
4494 | + db.add(root) |
4495 | installer = ClickInstaller(db) |
4496 | with self.make_framework("ubuntu-sdk-13.10"), \ |
4497 | mock_quiet_subprocess_call(): |
4498 | @@ -425,7 +427,9 @@ |
4499 | control_scripts={"preinst": static_preinst}, |
4500 | data_files={"foo": None}) |
4501 | root = os.path.join(self.temp_dir, "root") |
4502 | - installer = ClickInstaller(ClickDB(root)) |
4503 | + db = Click.DB() |
4504 | + db.add(root) |
4505 | + installer = ClickInstaller(db) |
4506 | with self.make_framework("ubuntu-sdk-13.10"), \ |
4507 | mock.patch("subprocess.call") as mock_call: |
4508 | mock_call.side_effect = call_side_effect |
4509 | @@ -437,8 +441,9 @@ |
4510 | @skipUnless( |
4511 | os.path.exists(ClickInstaller(None)._preload_path()), |
4512 | "preload bits not built; installing packages will fail") |
4513 | - @mock.patch("click.install.package_install_hooks") |
4514 | + @mock.patch("gi.repository.Click.package_install_hooks") |
4515 | def test_upgrade(self, mock_package_install_hooks): |
4516 | + os.environ["TEST_QUIET"] = "1" |
4517 | path = self.make_fake_package( |
4518 | control_fields={ |
4519 | "Package": "test-package", |
4520 | @@ -460,7 +465,8 @@ |
4521 | inst_dir = os.path.join(package_dir, "current") |
4522 | os.makedirs(os.path.join(package_dir, "1.0")) |
4523 | os.symlink("1.0", inst_dir) |
4524 | - db = ClickDB(root) |
4525 | + db = Click.DB() |
4526 | + db.add(root) |
4527 | installer = ClickInstaller(db) |
4528 | with self.make_framework("ubuntu-sdk-13.10"), \ |
4529 | mock_quiet_subprocess_call(): |
4530 | @@ -494,7 +500,7 @@ |
4531 | @skipUnless( |
4532 | os.path.exists(ClickInstaller(None)._preload_path()), |
4533 | "preload bits not built; installing packages will fail") |
4534 | - @mock.patch("click.install.package_install_hooks") |
4535 | + @mock.patch("gi.repository.Click.package_install_hooks") |
4536 | def test_world_readable(self, mock_package_install_hooks): |
4537 | owner_only_file = os.path.join(self.temp_dir, "owner-only-file") |
4538 | touch(owner_only_file) |
4539 | @@ -521,7 +527,8 @@ |
4540 | "world-readable-dir": owner_only_dir, |
4541 | }) |
4542 | root = os.path.join(self.temp_dir, "root") |
4543 | - db = ClickDB(root) |
4544 | + db = Click.DB() |
4545 | + db.add(root) |
4546 | installer = ClickInstaller(db) |
4547 | with self.make_framework("ubuntu-sdk-13.10"), \ |
4548 | mock_quiet_subprocess_call(): |
4549 | @@ -538,7 +545,7 @@ |
4550 | @skipUnless( |
4551 | os.path.exists(ClickInstaller(None)._preload_path()), |
4552 | "preload bits not built; installing packages will fail") |
4553 | - @mock.patch("click.install.package_install_hooks") |
4554 | + @mock.patch("gi.repository.Click.package_install_hooks") |
4555 | @mock.patch("click.install.ClickInstaller._dpkg_architecture") |
4556 | def test_single_architecture(self, mock_dpkg_architecture, |
4557 | mock_package_install_hooks): |
4558 | @@ -560,7 +567,8 @@ |
4559 | }, |
4560 | control_scripts={"preinst": static_preinst}) |
4561 | root = os.path.join(self.temp_dir, "root") |
4562 | - db = ClickDB(root) |
4563 | + db = Click.DB() |
4564 | + db.add(root) |
4565 | installer = ClickInstaller(db) |
4566 | with self.make_framework("ubuntu-sdk-13.10"), \ |
4567 | mock_quiet_subprocess_call(): |
4568 | @@ -571,7 +579,7 @@ |
4569 | @skipUnless( |
4570 | os.path.exists(ClickInstaller(None)._preload_path()), |
4571 | "preload bits not built; installing packages will fail") |
4572 | - @mock.patch("click.install.package_install_hooks") |
4573 | + @mock.patch("gi.repository.Click.package_install_hooks") |
4574 | @mock.patch("click.install.ClickInstaller._dpkg_architecture") |
4575 | def test_multiple_architectures(self, mock_dpkg_architecture, |
4576 | mock_package_install_hooks): |
4577 | @@ -593,7 +601,8 @@ |
4578 | }, |
4579 | control_scripts={"preinst": static_preinst}) |
4580 | root = os.path.join(self.temp_dir, "root") |
4581 | - db = ClickDB(root) |
4582 | + db = Click.DB() |
4583 | + db.add(root) |
4584 | installer = ClickInstaller(db) |
4585 | with self.make_framework("ubuntu-sdk-13.10"), \ |
4586 | mock_quiet_subprocess_call(): |
4587 | |
4588 | === modified file 'click/tests/test_osextras.py' |
4589 | --- click/tests/test_osextras.py 2013-09-04 15:59:18 +0000 |
4590 | +++ click/tests/test_osextras.py 2014-03-06 07:04:44 +0000 |
4591 | @@ -17,35 +17,34 @@ |
4592 | |
4593 | from __future__ import print_function |
4594 | __all__ = [ |
4595 | - 'TestOSExtras', |
4596 | + 'TestOSExtrasNative', |
4597 | + 'TestOSExtrasPython', |
4598 | ] |
4599 | |
4600 | |
4601 | import os |
4602 | |
4603 | +from gi.repository import Click, GLib |
4604 | + |
4605 | from click import osextras |
4606 | -from click.tests.helpers import TestCase, touch |
4607 | - |
4608 | - |
4609 | -class TestOSExtras(TestCase): |
4610 | - def setUp(self): |
4611 | - super(TestOSExtras, self).setUp() |
4612 | - self.use_temp_dir() |
4613 | - |
4614 | +from click.tests.helpers import TestCase, mock, touch |
4615 | + |
4616 | + |
4617 | +class TestOSExtrasBaseMixin: |
4618 | def test_ensuredir_previously_missing(self): |
4619 | new_dir = os.path.join(self.temp_dir, "dir") |
4620 | - osextras.ensuredir(new_dir) |
4621 | + self.mod.ensuredir(new_dir) |
4622 | self.assertTrue(os.path.isdir(new_dir)) |
4623 | |
4624 | def test_ensuredir_previously_present(self): |
4625 | new_dir = os.path.join(self.temp_dir, "dir") |
4626 | os.mkdir(new_dir) |
4627 | - osextras.ensuredir(new_dir) |
4628 | + self.mod.ensuredir(new_dir) |
4629 | self.assertTrue(os.path.isdir(new_dir)) |
4630 | |
4631 | def test_find_on_path_missing_environment(self): |
4632 | os.environ.pop("PATH", None) |
4633 | - self.assertFalse(osextras.find_on_path("ls")) |
4634 | + self.assertFalse(self.mod.find_on_path("ls")) |
4635 | |
4636 | def test_find_on_path_present_executable(self): |
4637 | bin_dir = os.path.join(self.temp_dir, "bin") |
4638 | @@ -53,69 +52,112 @@ |
4639 | touch(program) |
4640 | os.chmod(program, 0o755) |
4641 | os.environ["PATH"] = bin_dir |
4642 | - self.assertTrue(osextras.find_on_path("program")) |
4643 | + self.assertTrue(self.mod.find_on_path("program")) |
4644 | |
4645 | def test_find_on_path_present_not_executable(self): |
4646 | bin_dir = os.path.join(self.temp_dir, "bin") |
4647 | touch(os.path.join(bin_dir, "program")) |
4648 | os.environ["PATH"] = bin_dir |
4649 | - self.assertFalse(osextras.find_on_path("program")) |
4650 | - |
4651 | - def test_listdir_directory_present(self): |
4652 | - new_dir = os.path.join(self.temp_dir, "dir") |
4653 | - touch(os.path.join(new_dir, "file")) |
4654 | - self.assertEqual(["file"], osextras.listdir_force(new_dir)) |
4655 | - |
4656 | - def test_listdir_directory_missing(self): |
4657 | - new_dir = os.path.join(self.temp_dir, "dir") |
4658 | - self.assertEqual([], osextras.listdir_force(new_dir)) |
4659 | - |
4660 | - def test_listdir_oserror(self): |
4661 | - not_dir = os.path.join(self.temp_dir, "file") |
4662 | - touch(not_dir) |
4663 | - self.assertRaises(OSError, osextras.listdir_force, not_dir) |
4664 | + self.assertFalse(self.mod.find_on_path("program")) |
4665 | + |
4666 | + def test_find_on_path_requires_regular_file(self): |
4667 | + bin_dir = os.path.join(self.temp_dir, "bin") |
4668 | + self.mod.ensuredir(os.path.join(bin_dir, "subdir")) |
4669 | + os.environ["PATH"] = bin_dir |
4670 | + self.assertFalse(self.mod.find_on_path("subdir")) |
4671 | |
4672 | def test_unlink_file_present(self): |
4673 | path = os.path.join(self.temp_dir, "file") |
4674 | touch(path) |
4675 | - osextras.unlink_force(path) |
4676 | + self.mod.unlink_force(path) |
4677 | self.assertFalse(os.path.exists(path)) |
4678 | |
4679 | def test_unlink_file_missing(self): |
4680 | path = os.path.join(self.temp_dir, "file") |
4681 | - osextras.unlink_force(path) |
4682 | + self.mod.unlink_force(path) |
4683 | self.assertFalse(os.path.exists(path)) |
4684 | |
4685 | - def test_unlink_oserror(self): |
4686 | - path = os.path.join(self.temp_dir, "dir") |
4687 | - os.mkdir(path) |
4688 | - self.assertRaises(OSError, osextras.unlink_force, path) |
4689 | - |
4690 | def test_symlink_file_present(self): |
4691 | path = os.path.join(self.temp_dir, "link") |
4692 | touch(path) |
4693 | - osextras.symlink_force("source", path) |
4694 | + self.mod.symlink_force("source", path) |
4695 | self.assertTrue(os.path.islink(path)) |
4696 | self.assertEqual("source", os.readlink(path)) |
4697 | |
4698 | def test_symlink_link_present(self): |
4699 | path = os.path.join(self.temp_dir, "link") |
4700 | os.symlink("old", path) |
4701 | - osextras.symlink_force("source", path) |
4702 | + self.mod.symlink_force("source", path) |
4703 | self.assertTrue(os.path.islink(path)) |
4704 | self.assertEqual("source", os.readlink(path)) |
4705 | |
4706 | def test_symlink_missing(self): |
4707 | path = os.path.join(self.temp_dir, "link") |
4708 | - osextras.symlink_force("source", path) |
4709 | + self.mod.symlink_force("source", path) |
4710 | self.assertTrue(os.path.islink(path)) |
4711 | self.assertEqual("source", os.readlink(path)) |
4712 | |
4713 | def test_umask(self): |
4714 | old_mask = os.umask(0o040) |
4715 | try: |
4716 | - self.assertEqual(0o040, osextras.get_umask()) |
4717 | + self.assertEqual(0o040, self.mod.get_umask()) |
4718 | os.umask(0o002) |
4719 | - self.assertEqual(0o002, osextras.get_umask()) |
4720 | + self.assertEqual(0o002, self.mod.get_umask()) |
4721 | finally: |
4722 | os.umask(old_mask) |
4723 | + |
4724 | + |
4725 | +class TestOSExtrasNative(TestCase, TestOSExtrasBaseMixin): |
4726 | + def setUp(self): |
4727 | + super(TestOSExtrasNative, self).setUp() |
4728 | + self.use_temp_dir() |
4729 | + self.mod = Click |
4730 | + |
4731 | + def test_dir_read_name_directory_present(self): |
4732 | + new_dir = os.path.join(self.temp_dir, "dir") |
4733 | + touch(os.path.join(new_dir, "file")) |
4734 | + d = Click.Dir.open(new_dir, 0) |
4735 | + self.assertEqual("file", d.read_name()) |
4736 | + self.assertIsNone(d.read_name()) |
4737 | + |
4738 | + def test_dir_read_name_directory_missing(self): |
4739 | + new_dir = os.path.join(self.temp_dir, "dir") |
4740 | + d = Click.Dir.open(new_dir, 0) |
4741 | + self.assertIsNone(d.read_name()) |
4742 | + |
4743 | + def test_dir_open_error(self): |
4744 | + not_dir = os.path.join(self.temp_dir, "file") |
4745 | + touch(not_dir) |
4746 | + self.assertRaisesFileError( |
4747 | + GLib.FileError.NOTDIR, Click.Dir.open, not_dir, 0) |
4748 | + |
4749 | + def test_unlink_error(self): |
4750 | + path = os.path.join(self.temp_dir, "dir") |
4751 | + os.mkdir(path) |
4752 | + self.assertRaisesFileError(mock.ANY, self.mod.unlink_force, path) |
4753 | + |
4754 | + |
4755 | +class TestOSExtrasPython(TestCase, TestOSExtrasBaseMixin): |
4756 | + def setUp(self): |
4757 | + super(TestOSExtrasPython, self).setUp() |
4758 | + self.use_temp_dir() |
4759 | + self.mod = osextras |
4760 | + |
4761 | + def test_listdir_directory_present(self): |
4762 | + new_dir = os.path.join(self.temp_dir, "dir") |
4763 | + touch(os.path.join(new_dir, "file")) |
4764 | + self.assertEqual(["file"], osextras.listdir_force(new_dir)) |
4765 | + |
4766 | + def test_listdir_directory_missing(self): |
4767 | + new_dir = os.path.join(self.temp_dir, "dir") |
4768 | + self.assertEqual([], osextras.listdir_force(new_dir)) |
4769 | + |
4770 | + def test_listdir_oserror(self): |
4771 | + not_dir = os.path.join(self.temp_dir, "file") |
4772 | + touch(not_dir) |
4773 | + self.assertRaises(OSError, osextras.listdir_force, not_dir) |
4774 | + |
4775 | + def test_unlink_oserror(self): |
4776 | + path = os.path.join(self.temp_dir, "dir") |
4777 | + os.mkdir(path) |
4778 | + self.assertRaises(OSError, self.mod.unlink_force, path) |
4779 | |
4780 | === added file 'click/tests/test_query.py' |
4781 | --- click/tests/test_query.py 1970-01-01 00:00:00 +0000 |
4782 | +++ click/tests/test_query.py 2014-03-06 07:04:44 +0000 |
4783 | @@ -0,0 +1,52 @@ |
4784 | +# Copyright (C) 2014 Canonical Ltd. |
4785 | +# Author: Colin Watson <cjwatson@ubuntu.com> |
4786 | + |
4787 | +# This program is free software: you can redistribute it and/or modify |
4788 | +# it under the terms of the GNU General Public License as published by |
4789 | +# the Free Software Foundation; version 3 of the License. |
4790 | +# |
4791 | +# This program is distributed in the hope that it will be useful, |
4792 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
4793 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
4794 | +# GNU General Public License for more details. |
4795 | +# |
4796 | +# You should have received a copy of the GNU General Public License |
4797 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
4798 | + |
4799 | +"""Unit tests for click.query.""" |
4800 | + |
4801 | +from __future__ import print_function |
4802 | +__all__ = [ |
4803 | + 'TestQuery', |
4804 | + ] |
4805 | + |
4806 | + |
4807 | +import os |
4808 | + |
4809 | +from gi.repository import Click |
4810 | + |
4811 | +from click.tests.helpers import TestCase, touch |
4812 | + |
4813 | + |
4814 | +class TestQuery(TestCase): |
4815 | + def setUp(self): |
4816 | + super(TestQuery, self).setUp() |
4817 | + self.use_temp_dir() |
4818 | + |
4819 | + def test_find_package_directory_missing(self): |
4820 | + path = os.path.join(self.temp_dir, "nonexistent") |
4821 | + self.assertRaisesQueryError( |
4822 | + Click.QueryError.PATH, Click.find_package_directory, path) |
4823 | + |
4824 | + def test_find_package_directory(self): |
4825 | + info = os.path.join(self.temp_dir, ".click", "info") |
4826 | + path = os.path.join(self.temp_dir, "file") |
4827 | + Click.ensuredir(info) |
4828 | + touch(path) |
4829 | + pkgdir = Click.find_package_directory(path) |
4830 | + self.assertEqual(self.temp_dir, pkgdir) |
4831 | + |
4832 | + def test_find_package_directory_outside(self): |
4833 | + self.assertRaisesQueryError( |
4834 | + Click.QueryError.NO_PACKAGE_DIR, Click.find_package_directory, |
4835 | + "/bin") |
4836 | |
4837 | === modified file 'click/tests/test_user.py' |
4838 | --- click/tests/test_user.py 2014-03-01 23:54:08 +0000 |
4839 | +++ click/tests/test_user.py 2014-03-06 07:04:44 +0000 |
4840 | @@ -25,24 +25,26 @@ |
4841 | |
4842 | import os |
4843 | |
4844 | -from click.database import ClickDB |
4845 | +from gi.repository import Click |
4846 | + |
4847 | from click.tests.helpers import TestCase |
4848 | -from click.user import ClickUser |
4849 | |
4850 | |
4851 | class TestClickUser(TestCase): |
4852 | def setUp(self): |
4853 | super(TestClickUser, self).setUp() |
4854 | self.use_temp_dir() |
4855 | - self.db = ClickDB(self.temp_dir, use_system=False) |
4856 | + self.db = Click.DB() |
4857 | + self.db.add(self.temp_dir) |
4858 | |
4859 | def _setUpMultiDB(self): |
4860 | - self.multi_db = ClickDB(use_system=False) |
4861 | + self.multi_db = Click.DB() |
4862 | self.multi_db.add(os.path.join(self.temp_dir, "custom")) |
4863 | self.multi_db.add(os.path.join(self.temp_dir, "click")) |
4864 | user_dbs = [ |
4865 | - os.path.join(d.root, ".click", "users", "user") |
4866 | - for d in self.multi_db |
4867 | + os.path.join( |
4868 | + self.multi_db.get(i).props.root, ".click", "users", "user") |
4869 | + for i in range(self.multi_db.props.size) |
4870 | ] |
4871 | a_1_0 = os.path.join(self.temp_dir, "custom", "a", "1.0") |
4872 | os.makedirs(a_1_0) |
4873 | @@ -58,80 +60,90 @@ |
4874 | os.makedirs(user_dbs[1]) |
4875 | os.symlink(a_1_1, os.path.join(user_dbs[1], "a")) |
4876 | os.symlink(c_0_1, os.path.join(user_dbs[1], "c")) |
4877 | - return user_dbs, ClickUser(self.multi_db, "user") |
4878 | - |
4879 | - def test_overlay_db(self): |
4880 | + return user_dbs, Click.User.for_user(self.multi_db, "user") |
4881 | + |
4882 | + def test_new_no_db(self): |
4883 | + with self.run_in_subprocess( |
4884 | + "click_get_db_dir", "g_get_user_name") as (enter, preloads): |
4885 | + enter() |
4886 | + preloads["click_get_db_dir"].side_effect = ( |
4887 | + lambda: self.make_string(self.temp_dir)) |
4888 | + preloads["g_get_user_name"].side_effect = ( |
4889 | + lambda: self.make_string("test-user")) |
4890 | + db_root = os.path.join(self.temp_dir, "db") |
4891 | + os.makedirs(db_root) |
4892 | + with open(os.path.join(self.temp_dir, "db.conf"), "w") as f: |
4893 | + print("[Click Database]", file=f) |
4894 | + print("root = %s" % db_root, file=f) |
4895 | + registry = Click.User.for_user() |
4896 | + self.assertEqual( |
4897 | + os.path.join(db_root, ".click", "users", "test-user"), |
4898 | + registry.get_overlay_db()) |
4899 | + |
4900 | + def test_get_overlay_db(self): |
4901 | self.assertEqual( |
4902 | os.path.join(self.temp_dir, ".click", "users", "user"), |
4903 | - ClickUser(self.db, "user").overlay_db) |
4904 | - |
4905 | - def test_iter_missing(self): |
4906 | - db = ClickDB( |
4907 | - os.path.join(self.temp_dir, "nonexistent"), use_system=False) |
4908 | - registry = ClickUser(db) |
4909 | - self.assertEqual([], list(registry)) |
4910 | - |
4911 | - def test_iter(self): |
4912 | - registry = ClickUser(self.db, "user") |
4913 | - os.makedirs(registry.overlay_db) |
4914 | - os.symlink("/1.0", os.path.join(registry.overlay_db, "a")) |
4915 | - os.symlink("/1.1", os.path.join(registry.overlay_db, "b")) |
4916 | - self.assertCountEqual(["a", "b"], list(registry)) |
4917 | - |
4918 | - def test_iter_multiple_root(self): |
4919 | - _, registry = self._setUpMultiDB() |
4920 | - self.assertCountEqual(["a", "b", "c"], list(registry)) |
4921 | - |
4922 | - def test_len_missing(self): |
4923 | - db = ClickDB( |
4924 | - os.path.join(self.temp_dir, "nonexistent"), use_system=False) |
4925 | - registry = ClickUser(db) |
4926 | - self.assertEqual(0, len(registry)) |
4927 | - |
4928 | - def test_len(self): |
4929 | - registry = ClickUser(self.db, "user") |
4930 | - os.makedirs(registry.overlay_db) |
4931 | - os.symlink("/1.0", os.path.join(registry.overlay_db, "a")) |
4932 | - os.symlink("/1.1", os.path.join(registry.overlay_db, "b")) |
4933 | - self.assertEqual(2, len(registry)) |
4934 | - |
4935 | - def test_len_multiple_root(self): |
4936 | - _, registry = self._setUpMultiDB() |
4937 | - self.assertEqual(3, len(registry)) |
4938 | - |
4939 | - def test_getitem_missing(self): |
4940 | - registry = ClickUser(self.db, "user") |
4941 | - self.assertRaises(KeyError, registry.__getitem__, "a") |
4942 | - |
4943 | - def test_getitem(self): |
4944 | - registry = ClickUser(self.db, "user") |
4945 | - os.makedirs(registry.overlay_db) |
4946 | - os.symlink("/1.0", os.path.join(registry.overlay_db, "a")) |
4947 | - self.assertEqual("1.0", registry["a"]) |
4948 | - |
4949 | - def test_getitem_multiple_root(self): |
4950 | - _, registry = self._setUpMultiDB() |
4951 | - self.assertEqual("1.1", registry["a"]) |
4952 | - self.assertEqual("2.0", registry["b"]) |
4953 | - self.assertEqual("0.1", registry["c"]) |
4954 | + Click.User.for_user(self.db, "user").get_overlay_db()) |
4955 | + |
4956 | + def test_get_package_names_missing(self): |
4957 | + db = Click.DB() |
4958 | + db.add(os.path.join(self.temp_dir, "nonexistent")) |
4959 | + registry = Click.User.for_user(db) |
4960 | + self.assertEqual([], list(registry.get_package_names())) |
4961 | + |
4962 | + def test_get_package_names(self): |
4963 | + registry = Click.User.for_user(self.db, "user") |
4964 | + os.makedirs(registry.get_overlay_db()) |
4965 | + os.symlink("/1.0", os.path.join(registry.get_overlay_db(), "a")) |
4966 | + os.symlink("/1.1", os.path.join(registry.get_overlay_db(), "b")) |
4967 | + self.assertCountEqual(["a", "b"], list(registry.get_package_names())) |
4968 | + |
4969 | + def test_get_package_names_multiple_root(self): |
4970 | + _, registry = self._setUpMultiDB() |
4971 | + self.assertCountEqual( |
4972 | + ["a", "b", "c"], list(registry.get_package_names())) |
4973 | + |
4974 | + def test_get_version_missing(self): |
4975 | + registry = Click.User.for_user(self.db, "user") |
4976 | + self.assertRaisesUserError( |
4977 | + Click.UserError.NO_SUCH_PACKAGE, registry.get_version, "a") |
4978 | + self.assertFalse(registry.has_package_name("a")) |
4979 | + |
4980 | + def test_get_version(self): |
4981 | + registry = Click.User.for_user(self.db, "user") |
4982 | + os.makedirs(registry.get_overlay_db()) |
4983 | + os.symlink("/1.0", os.path.join(registry.get_overlay_db(), "a")) |
4984 | + self.assertEqual("1.0", registry.get_version("a")) |
4985 | + self.assertTrue(registry.has_package_name("a")) |
4986 | + |
4987 | + def test_get_version_multiple_root(self): |
4988 | + _, registry = self._setUpMultiDB() |
4989 | + self.assertEqual("1.1", registry.get_version("a")) |
4990 | + self.assertEqual("2.0", registry.get_version("b")) |
4991 | + self.assertEqual("0.1", registry.get_version("c")) |
4992 | + self.assertTrue(registry.has_package_name("a")) |
4993 | + self.assertTrue(registry.has_package_name("b")) |
4994 | + self.assertTrue(registry.has_package_name("c")) |
4995 | |
4996 | def test_set_version_missing_target(self): |
4997 | - registry = ClickUser(self.db, "user") |
4998 | - self.assertRaises(KeyError, registry.set_version, "a", "1.0") |
4999 | + registry = Click.User.for_user(self.db, "user") |
5000 | + self.assertRaisesDatabaseError( |
There are several bugs I'd want to tackle after this, collected here:
https:/ /bugs.launchpad .net/ubuntu/ +source/ click/+ bugs?field. tag=libclick