Merge lp:~jamesodhunt/upstart/bug-1360208 into lp:upstart
- bug-1360208
- Merge into trunk
Status: | Needs review |
---|---|
Proposed branch: | lp:~jamesodhunt/upstart/bug-1360208 |
Merge into: | lp:upstart |
Diff against target: |
1966 lines (+943/-321) 5 files modified
ChangeLog (+37/-0) extra/upstart-file-bridge.c (+403/-164) scripts/pyupstart.py (+46/-20) scripts/tests/test_pyupstart_session_init.py (+368/-57) scripts/tests/test_pyupstart_system_init.py (+89/-80) |
To merge this branch: | bzr merge lp:~jamesodhunt/upstart/bug-1360208 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Upstart Reviewers | Pending | ||
Review via email: mp+234869@code.launchpad.net |
Commit message
Description of the change
Fix for bug 1360208.
* extra/upstart-
- Converted original_path() macro to a function.
- WatchedFile: Added more details on this crucial type.
- file_filter(): Removed as it was too simplistic and duplicating the
work of the individual handlers in determining whether a path should
be considered.
- create_handler(): Simplified.
- modify_handler(): Simplified.
- delete_handler(): Simplified.
- handle_event(): Now deals with globs and handles tilde+glob jobs
(LP: #1360208).
- handle_glob(): New function that allows main handlers to be simplified.
- Added lots of debug for '--debug'.
- expand_path(): Only check password database if $HOME not set. This
allows the tests to use a fake $HOME to check that tilde expansion
works without modifying the users actual $HOME.
- New utility functions:
- file_exists()
- remove_
* scripts/
- Add missing file header.
- pep8 formatting changes.
* scripts/
- test_init_
- Force the file bridge to run with a fake $HOME below /tmp to
allow testing jobs with paths that require tilde expansion.
- Run file bridge in foreground to capture debug output.
- Change tests to check for values of all variables the file-event(7)
sets.
- New tests for:
- glob file job.
- tilde file job.
- glob and tilde file job.
* scripts/
- 1662. By James Hunt
-
* Sync with lp:upstart.
Unmerged revisions
- 1662. By James Hunt
-
* Sync with lp:upstart.
- 1661. By James Hunt
-
* extra/upstart-
file-bridge. c:
- Converted original_path() macro to a function.
- WatchedFile: Added more details on this crucial type.
- file_filter(): Removed as it was too simplistic and duplicating the
work of the individual handlers in determining whether a path should
be considered.
- create_handler(): Simplified.
- modify_handler(): Simplified.
- delete_handler(): Simplified.
- handle_event(): Now deals with globs and handles tilde+glob jobs
(LP: #1360208).
- handle_glob(): New function that allows main handlers to be simplified.
- Added lots of debug for '--debug'.
- expand_path(): Only check password database if $HOME not set. This
allows the tests to use a fake $HOME to check that tilde expansion
works without modifying the users actual $HOME.
- New utility functions:
- file_exists()
- remove_trailing_ slashes( )
* scripts/pyupstart. py:
- Add missing file header.
- pep8 formatting changes.
* scripts/tests/test_ pyupstart_ session_ init.py: TestFileBridge:
- test_init_start_file_ bridge( ):
- Force the file bridge to run with a fake $HOME below /tmp to
allow testing jobs with paths that require tilde expansion.
- Run file bridge in foreground to capture debug output.
- Change tests to check for values of all variables the file-event(7)
sets.
- New tests for:
- glob file job.
- tilde file job.
- glob and tilde file job.
* scripts/tests/test_ pyupstart_ system_ init.py: pep8 formatting changes.
Preview Diff
1 | === modified file 'ChangeLog' |
2 | --- ChangeLog 2014-09-08 15:18:43 +0000 |
3 | +++ ChangeLog 2014-10-14 08:13:33 +0000 |
4 | @@ -1,3 +1,40 @@ |
5 | +2014-09-16 James Hunt <james.hunt@ubuntu.com> |
6 | + |
7 | + * extra/upstart-file-bridge.c: |
8 | + - Converted original_path() macro to a function. |
9 | + - WatchedFile: Added more details on this crucial type. |
10 | + - file_filter(): Removed as it was too simplistic and duplicating the |
11 | + work of the individual handlers in determining whether a path should |
12 | + be considered. |
13 | + - create_handler(): Simplified. |
14 | + - modify_handler(): Simplified. |
15 | + - delete_handler(): Simplified. |
16 | + - handle_event(): Now deals with globs and handles tilde+glob jobs |
17 | + (LP: #1360208). |
18 | + - handle_glob(): New function that allows main handlers to be simplified. |
19 | + - Added lots of debug for '--debug'. |
20 | + - expand_path(): Only check password database if $HOME not set. This |
21 | + allows the tests to use a fake $HOME to check that tilde expansion |
22 | + works without modifying the users actual $HOME. |
23 | + - New utility functions: |
24 | + - file_exists() |
25 | + - remove_trailing_slashes() |
26 | + * scripts/pyupstart.py: |
27 | + - Add missing file header. |
28 | + - pep8 formatting changes. |
29 | + * scripts/tests/test_pyupstart_session_init.py: TestFileBridge: |
30 | + - test_init_start_file_bridge(): |
31 | + - Force the file bridge to run with a fake $HOME below /tmp to |
32 | + allow testing jobs with paths that require tilde expansion. |
33 | + - Run file bridge in foreground to capture debug output. |
34 | + - Change tests to check for values of all variables the file-event(7) |
35 | + sets. |
36 | + - New tests for: |
37 | + - glob file job. |
38 | + - tilde file job. |
39 | + - glob and tilde file job. |
40 | + * scripts/tests/test_pyupstart_system_init.py: pep8 formatting changes. |
41 | + |
42 | 2014-09-08 James Hunt <james.hunt@ubuntu.com> |
43 | |
44 | * util/telinit.c: Remove UPSTART_TELINIT_U_NO_WAIT check as it |
45 | |
46 | === modified file 'extra/upstart-file-bridge.c' |
47 | --- extra/upstart-file-bridge.c 2013-11-03 00:19:02 +0000 |
48 | +++ extra/upstart-file-bridge.c 2014-10-14 08:13:33 +0000 |
49 | @@ -1,6 +1,6 @@ |
50 | /* upstart |
51 | * |
52 | - * Copyright © 2013 Canonical Ltd. |
53 | + * Copyright © 2013-2014 Canonical Ltd. |
54 | * Author: James Hunt <james.hunt@canonical.com>. |
55 | * |
56 | * This program is free software; you can redistribute it and/or modify |
57 | @@ -173,22 +173,6 @@ |
58 | #define GLOB_CHARS "*?[]" |
59 | |
60 | /** |
61 | - * original_path: |
62 | - * |
63 | - * @file: WatchedFile: |
64 | - * |
65 | - * Obtain the appropriate WatchedFile path: the original if the |
66 | - * path underwent expansion or is a directory, else the initial |
67 | - * unexpanded path. |
68 | - * |
69 | - * Required for emitting events since jobs need the unexpanded path to |
70 | - * allow their start/stop condition to match even if the path has |
71 | - * subsequently been expanded by this bridge. |
72 | - **/ |
73 | -#define original_path(file) \ |
74 | - (file->original ? file->original : file->path) |
75 | - |
76 | -/** |
77 | * skip_slashes: |
78 | * |
79 | * @path: pointer to path string. |
80 | @@ -253,9 +237,10 @@ |
81 | * WatchedFile: |
82 | * |
83 | * @entry: list header, |
84 | - * @path: full path to file being watched (or a glob), |
85 | - * @original: original (relative) path as specified by job |
86 | - * (or NULL if path expansion was not necessary), |
87 | + * @path: full path to file being watched (including a glob pattern |
88 | + * if @glob is set), |
89 | + * @original: original (relative) path as specified by job without glob |
90 | + * pattern (or NULL if path expansion was not necessary), |
91 | * @glob: glob file pattern (or NULL if globbing disabled), |
92 | * @dir: TRUE if @path is a directory, |
93 | * @events: mask of inotify events file is interested in, |
94 | @@ -265,6 +250,27 @@ |
95 | * |
96 | * For directories, @original contains the full path specified by the |
97 | * job and @path will contain @original, less any trailing slashes. |
98 | + * |
99 | + * Note: This type contains a representation of the 'start on file' |
100 | + * Upstart stanza. |
101 | + * |
102 | + * For example, if a job specified the following (but without the space |
103 | + * between '/' and '*'): |
104 | + * |
105 | + * start on file FILE=~/.cache/foo/ *.log EVENT=create |
106 | + * |
107 | + * The WatchedFile object would contain: |
108 | + * |
109 | + * path : "/home/user/.cache/foo/ *.log" (again without the space). |
110 | + * original : "~/.cache/foo/" |
111 | + * glob : "*.log" |
112 | + * dir : FALSE |
113 | + * events : IN_CREATE |
114 | + * |
115 | + * If file "/home/user/.cache/foo/bar.log" is created, an event will be |
116 | + * emitted as below (again without the space between '/' and '*'): |
117 | + * |
118 | + * file FILE=~/.cache/foo/ *.log EVENT=create MATCH=/home/user/.cache/foo/bar.log |
119 | **/ |
120 | typedef struct watched_file { |
121 | NihList entry; |
122 | @@ -306,8 +312,6 @@ |
123 | static Job *job_new (const char *class_path) |
124 | __attribute__ ((warn_unused_result)); |
125 | |
126 | -static int file_filter (WatchedDir *dir, const char *path, int is_dir); |
127 | - |
128 | static void create_handler (WatchedDir *dir, NihWatch *watch, |
129 | const char *path, struct stat *statbuf); |
130 | |
131 | @@ -327,34 +331,47 @@ |
132 | |
133 | static void emit_event_error (void *data, NihDBusMessage *message); |
134 | static int emit_event (const char *path, uint32_t event_type, |
135 | - const char *match); |
136 | + const char *match); |
137 | |
138 | static FileEvent *file_event_new (void *parent, const char *path, |
139 | uint32_t event, const char *match); |
140 | |
141 | static void upstart_disconnected (DBusConnection *connection); |
142 | |
143 | -static void handle_event (NihHash *handled, const char *path, |
144 | - uint32_t event, const char *match); |
145 | +static void handle_event (NihHash *handled, WatchedFile *file, |
146 | + uint32_t event, const char *match); |
147 | + |
148 | +static int handle_glob (WatchedFile *file, const char *path, |
149 | + NihHash *handled, uint32_t event) |
150 | + __attribute__ ((warn_unused_result)); |
151 | |
152 | static int job_destroy (Job *job); |
153 | |
154 | -static char * find_first_parent (const char *path) |
155 | +static char *find_first_parent (const char *path) |
156 | __attribute__ ((warn_unused_result)); |
157 | |
158 | -void watched_dir_init (void); |
159 | +static void watched_dir_init (void); |
160 | |
161 | static void ensure_watched (Job *job, WatchedFile *file); |
162 | |
163 | static int string_match (const char *a, const char *b) |
164 | __attribute__ ((warn_unused_result)); |
165 | |
166 | -char * expand_path (const void *parent, const char *path) |
167 | +static int file_exists (const char *path) |
168 | + __attribute__ ((warn_unused_result)); |
169 | + |
170 | +static void remove_trailing_slashes (char *path); |
171 | + |
172 | +static char *expand_path (const void *parent, const char *path) |
173 | __attribute__ ((warn_unused_result)); |
174 | |
175 | static int path_valid (const char *path) |
176 | __attribute__ ((warn_unused_result)); |
177 | |
178 | +static char *original_path (void *parent, const WatchedFile *file) |
179 | + __attribute__ ((warn_unused_result)); |
180 | + |
181 | + |
182 | /** |
183 | * daemonise: |
184 | * |
185 | @@ -798,8 +815,11 @@ |
186 | } |
187 | } |
188 | |
189 | - if (! path_valid (path)) |
190 | + if (! path_valid (path)) { |
191 | + nih_warn ("%s: %s", |
192 | + _("Path is not valid"), path); |
193 | return; |
194 | + } |
195 | |
196 | dirpart = NIH_MUST (nih_strdup (NULL, path)); |
197 | dir = dirname (dirpart); |
198 | @@ -810,7 +830,8 @@ |
199 | len2 = strlen (dir); |
200 | |
201 | if (strcspn (dir, GLOB_CHARS) < len2) { |
202 | - nih_warn ("%s: %s", job->path, _("Directory globbing not supported")); |
203 | + nih_warn ("%s: %s", job->path, |
204 | + _("Directory globbing not supported")); |
205 | return; |
206 | } |
207 | |
208 | @@ -898,44 +919,6 @@ |
209 | } |
210 | |
211 | /** |
212 | - * file_filter: |
213 | - * |
214 | - * @dir: WatchedDir, |
215 | - * @path: full path to file to consider, |
216 | - * @is_dir: TRUE if @path is a directory, else FALSE. |
217 | - * |
218 | - * Watch handler function to sift the wheat from the chaff. |
219 | - * |
220 | - * Returns: TRUE if @path should be ignored, FALSE otherwise. |
221 | - **/ |
222 | -int |
223 | -file_filter (WatchedDir *dir, |
224 | - const char *path, |
225 | - int is_dir) |
226 | -{ |
227 | - nih_assert (dir); |
228 | - nih_assert (path); |
229 | - |
230 | - skip_slashes (path); |
231 | - |
232 | - NIH_HASH_FOREACH_SAFE (dir->files, iter) { |
233 | - WatchedFile *file = (WatchedFile *)iter; |
234 | - |
235 | - if (strstr (file->path, path) == file->path) { |
236 | - /* Either an exact match or path is a child of the watched file. |
237 | - * Paths in the latter category will be inspected more closely by |
238 | - * the handlers. |
239 | - */ |
240 | - return FALSE; |
241 | - } else if ((file->dir || file->glob) && strstr (path, file->path) == path) { |
242 | - return FALSE; |
243 | - } |
244 | - } |
245 | - |
246 | - return TRUE; |
247 | -} |
248 | - |
249 | -/** |
250 | * create_handler: |
251 | * |
252 | * @dir: WatchedDir, |
253 | @@ -955,6 +938,7 @@ |
254 | char *p; |
255 | int add_dir = FALSE; |
256 | int empty; |
257 | + int ignored = TRUE; |
258 | |
259 | /* Hash of events already emitted (required to avoid sending |
260 | * same event multiple times). |
261 | @@ -972,6 +956,10 @@ |
262 | nih_assert (path); |
263 | nih_assert (statbuf); |
264 | |
265 | + nih_debug ("Considering new %s path '%s'", |
266 | + S_ISDIR (statbuf->st_mode) ? "directory" : "file", |
267 | + path); |
268 | + |
269 | skip_slashes (path); |
270 | |
271 | /* path should be a file below the WatchedDir */ |
272 | @@ -981,62 +969,71 @@ |
273 | handled = NIH_MUST (nih_hash_string_new (NULL, 0)); |
274 | |
275 | NIH_HASH_FOREACH_SAFE (dir->files, iter) { |
276 | - WatchedFile *file = (WatchedFile *)iter; |
277 | + WatchedFile *file = (WatchedFile *)iter; |
278 | |
279 | if (file->dir) { |
280 | if (! strcmp (file->path, dir->path)) { |
281 | /* Watch is on the directory itself and a file within that |
282 | - * watched directory was created, hence emit the _directory_ |
283 | + * watched directory was created, hence signal the _directory_ |
284 | * was modified. |
285 | */ |
286 | - if (file->events & IN_MODIFY) |
287 | - handle_event (handled, original_path (file), IN_MODIFY, path); |
288 | + if (file->events & IN_MODIFY) { |
289 | + handle_event (handled, file, IN_MODIFY, path); |
290 | + |
291 | + ignored = FALSE; |
292 | + } |
293 | } else if (! strcmp (file->path, path)) { |
294 | /* Directory has been created */ |
295 | - handle_event (handled, original_path (file), IN_CREATE, NULL); |
296 | + handle_event (handled, file, IN_CREATE, NULL); |
297 | + |
298 | add_dir = TRUE; |
299 | + ignored = FALSE; |
300 | nih_list_add (&entries, &file->entry); |
301 | } |
302 | } else if (file->glob) { |
303 | - nih_local char *full_path = NULL; |
304 | - |
305 | - /* reconstruct the full path */ |
306 | - full_path = NIH_MUST (nih_sprintf (NULL, "%s/%s", file->path, file->glob)); |
307 | - |
308 | - if (! fnmatch (full_path, path, FNM_PATHNAME) && (file->events & IN_CREATE)) |
309 | - handle_event (handled, full_path, IN_CREATE, path); |
310 | + if (handle_glob (file, path, handled, IN_CREATE)) |
311 | + ignored = FALSE; |
312 | } else { |
313 | + /* Basic file watch */ |
314 | + |
315 | if (! strcmp (file->path, path) && (file->events & IN_CREATE)) { |
316 | /* exact match, so emit event */ |
317 | - handle_event (handled, file->path, IN_CREATE, NULL); |
318 | + handle_event (handled, file, IN_CREATE, NULL); |
319 | |
320 | - } else if ((p=strstr (file->path, path)) && p == file->path |
321 | - && S_ISDIR (statbuf->st_mode)) { |
322 | - /* The created file is actually a directory |
323 | - * more specific that the current watch |
324 | - * directory associated with @file. |
325 | - * |
326 | - * As such, we can make the watch on @file more |
327 | - * specific by dropping the old watch, creating |
328 | - * a new WatchedDir for @path and adding @file |
329 | - * to the new WatchedDir's files hash. |
330 | - * |
331 | - * This has to be handled carefully due to NIH |
332 | - * list/hash handling constraints. First, the |
333 | - * new directory is marked as needing to be |
334 | - * added to the directory hash and secondly we |
335 | - * add the WatchedFile to a list representing |
336 | - * all WatchedFiles that need to be added for |
337 | - * the new path. |
338 | - */ |
339 | - add_dir = TRUE; |
340 | - nih_list_add (&entries, &file->entry); |
341 | + ignored = FALSE; |
342 | } |
343 | } |
344 | + |
345 | + if ((p=strstr (file->path, path)) && p == file->path |
346 | + && S_ISDIR (statbuf->st_mode)) { |
347 | + /* The created file is actually a directory |
348 | + * more specific that the current watch |
349 | + * directory associated with @file. |
350 | + * |
351 | + * As such, we can make the watch on @file more |
352 | + * specific by dropping the old watch, creating |
353 | + * a new WatchedDir for @path and adding @file |
354 | + * to the new WatchedDir's files hash. |
355 | + * |
356 | + * This has to be handled carefully due to NIH |
357 | + * list/hash handling constraints. First, the |
358 | + * new directory is marked as needing to be |
359 | + * added to the directory hash and secondly we |
360 | + * add the WatchedFile to a list representing |
361 | + * all WatchedFiles that need to be added for |
362 | + * the new path. |
363 | + */ |
364 | + add_dir = TRUE; |
365 | + nih_list_add (&entries, &file->entry); |
366 | + } |
367 | } |
368 | |
369 | - if (! add_dir) |
370 | + if (! add_dir) { |
371 | + nih_debug ("%s path '%s'", |
372 | + ! ignored ? "Handled" : "Ignored", |
373 | + path); |
374 | return; |
375 | + } |
376 | |
377 | /* we should have atleast 1 file to add to the new watch */ |
378 | nih_assert (! NIH_LIST_EMPTY (&entries)); |
379 | @@ -1048,6 +1045,8 @@ |
380 | return; |
381 | } |
382 | |
383 | + nih_debug ("Creating watch on more specific path '%s'", path); |
384 | + |
385 | /* Add all list entries to the newly-created WatchedDir */ |
386 | NIH_LIST_FOREACH_SAFE (&entries, iter) { |
387 | WatchedFile *file = (WatchedFile *)iter; |
388 | @@ -1063,6 +1062,7 @@ |
389 | |
390 | if (empty) { |
391 | /* Remove the old directory watch */ |
392 | + nih_debug ("Removing old watch on path '%s'", dir->path); |
393 | nih_free (dir); |
394 | } |
395 | } |
396 | @@ -1084,6 +1084,7 @@ |
397 | struct stat *statbuf) |
398 | { |
399 | nih_local NihHash *handled = NULL; |
400 | + int ignored = TRUE; |
401 | |
402 | nih_assert (dir); |
403 | nih_assert (watch); |
404 | @@ -1093,12 +1094,16 @@ |
405 | /* path should be a file below the WatchedDir */ |
406 | nih_assert (strstr (path, dir->path) == path); |
407 | |
408 | + nih_debug ("Considering modified %s path '%s'", |
409 | + S_ISDIR (statbuf->st_mode) ? "directory" : "file", |
410 | + path); |
411 | + |
412 | skip_slashes (path); |
413 | |
414 | handled = NIH_MUST (nih_hash_string_new (NULL, 0)); |
415 | |
416 | NIH_HASH_FOREACH_SAFE (dir->files, iter) { |
417 | - WatchedFile *file = (WatchedFile *)iter; |
418 | + WatchedFile *file = (WatchedFile *)iter; |
419 | |
420 | if (! (file->events & IN_MODIFY)) |
421 | continue; |
422 | @@ -1109,26 +1114,35 @@ |
423 | * watched directory was modified, hence emit the _directory_ |
424 | * was modified. |
425 | */ |
426 | - handle_event (handled, original_path (file), IN_MODIFY, path); |
427 | + handle_event (handled, file, IN_MODIFY, path); |
428 | + |
429 | + ignored = FALSE; |
430 | } |
431 | } else if (file->glob) { |
432 | - nih_local char *full_path = NULL; |
433 | - |
434 | - /* reconstruct the full path */ |
435 | - full_path = NIH_MUST (nih_sprintf (NULL, "%s/%s", file->path, file->glob)); |
436 | - |
437 | - if (! fnmatch (full_path, path, FNM_PATHNAME) && (file->events & IN_MODIFY)) |
438 | - handle_event (handled, full_path, IN_MODIFY, path); |
439 | + if (handle_glob (file, path, handled, IN_MODIFY)) |
440 | + ignored = FALSE; |
441 | } else { |
442 | + /* Basic file watch */ |
443 | + |
444 | if (! strcmp (file->path, path)) { |
445 | /* exact match, so emit event */ |
446 | - handle_event (handled, original_path (file), IN_MODIFY, NULL); |
447 | + handle_event (handled, file, IN_MODIFY, NULL); |
448 | + |
449 | + ignored = FALSE; |
450 | } else if (file->dir && strstr (path, file->path) == path) { |
451 | /* file in watched directory modified, so emit event */ |
452 | - handle_event (handled, path, IN_MODIFY, NULL); |
453 | + |
454 | + handle_event (handled, file, IN_MODIFY, NULL); |
455 | + |
456 | + ignored = FALSE; |
457 | } |
458 | } |
459 | } |
460 | + |
461 | + nih_debug ("%s path '%s'", |
462 | + (! ignored) ? "Handled" : "Ignored", |
463 | + path); |
464 | + |
465 | } |
466 | |
467 | /** |
468 | @@ -1151,6 +1165,7 @@ |
469 | struct stat statbuf; |
470 | int rm_dir = FALSE; |
471 | nih_local NihHash *handled = NULL; |
472 | + int ignored = TRUE; |
473 | |
474 | /* List of existing WatchedFiles that need to be added against |
475 | * @path (since @path either exactly matches their path, or |
476 | @@ -1165,64 +1180,79 @@ |
477 | /* path should be a file below the WatchedDir */ |
478 | nih_assert (strstr (path, dir->path) == path); |
479 | |
480 | + nih_debug ("Considering deleted path '%s'", path); |
481 | + |
482 | skip_slashes (path); |
483 | |
484 | nih_list_init (&entries); |
485 | handled = NIH_MUST (nih_hash_string_new (NULL, 0)); |
486 | |
487 | NIH_HASH_FOREACH_SAFE (dir->files, iter) { |
488 | - WatchedFile *file = (WatchedFile *)iter; |
489 | + WatchedFile *file = (WatchedFile *)iter; |
490 | |
491 | if (file->dir) { |
492 | if (! strcmp (file->path, path)) { |
493 | /* Directory itself was deleted */ |
494 | - handle_event (handled, original_path (file), IN_DELETE, NULL); |
495 | + handle_event (handled, file, IN_DELETE, NULL); |
496 | + |
497 | + ignored = FALSE; |
498 | } else if (! strcmp (file->path, dir->path)) { |
499 | /* Watch is on the directory itself and a file within that |
500 | * watched directory was deleted, hence emit the directory was |
501 | * modified. |
502 | */ |
503 | - if (file->events & IN_MODIFY) |
504 | - handle_event (handled, original_path (file), IN_MODIFY, path); |
505 | + if (file->events & IN_MODIFY) { |
506 | + handle_event (handled, file, IN_MODIFY, path); |
507 | + |
508 | + ignored = FALSE; |
509 | + } |
510 | } |
511 | } else if (file->glob) { |
512 | - nih_local char *full_path = NULL; |
513 | - |
514 | - /* reconstruct the full path */ |
515 | - full_path = NIH_MUST (nih_sprintf (NULL, "%s/%s", file->path, file->glob)); |
516 | - |
517 | - if (! fnmatch (full_path, path, FNM_PATHNAME) && (file->events & IN_DELETE)) |
518 | - handle_event (handled, full_path, IN_DELETE, path); |
519 | + if (handle_glob (file, path, handled, IN_DELETE)) |
520 | + ignored = FALSE; |
521 | } else { |
522 | + /* Basic file watch */ |
523 | + |
524 | if (! strcmp (file->path, path) && (file->events & IN_DELETE)) { |
525 | - handle_event (handled, original_path (file), IN_DELETE, NULL); |
526 | - } else if ((p=strstr (file->path, path)) && p == file->path) { |
527 | - /* Create a new directory watch for all |
528 | - * WatchedFiles whose immediate parent directory |
529 | - * matches @path (in other words, |
530 | - * make the watch looking after a WatchedFile |
531 | - * less specific). This has to be handled |
532 | - * carefully due to NIH list/hash handling |
533 | - * constraints. First, the new directory is |
534 | - * marked as needing to be added to the |
535 | - * directory hash and secondly we add the |
536 | - * WatchedFile to a list representing all |
537 | - * WatchedFiles that need to be added for the |
538 | - * new path. |
539 | - */ |
540 | - rm_dir = TRUE; |
541 | - nih_list_add (&entries, &file->entry); |
542 | - } else if (file->dir && strstr (path, file->path) == path && (file->events & IN_DELETE)) { |
543 | + handle_event (handled, file, IN_DELETE, NULL); |
544 | + |
545 | + ignored = FALSE; |
546 | + } |
547 | + } |
548 | + |
549 | + if ((p=strstr (file->path, path)) && p == file->path) { |
550 | + /* Create a new directory watch for all |
551 | + * WatchedFiles whose immediate parent directory |
552 | + * matches @path (in other words, |
553 | + * make the watch looking after a WatchedFile |
554 | + * less specific). This has to be handled |
555 | + * carefully due to NIH list/hash handling |
556 | + * constraints. First, the new directory is |
557 | + * marked as needing to be added to the |
558 | + * directory hash and secondly we add the |
559 | + * WatchedFile to a list representing all |
560 | + * WatchedFiles that need to be added for the |
561 | + * new path. |
562 | + */ |
563 | + rm_dir = TRUE; |
564 | + nih_list_add (&entries, &file->entry); |
565 | + } else if (file->dir && strstr (path, file->path) == path |
566 | + && (file->events & IN_DELETE)) { |
567 | /* file in watched directory deleted, so emit event */ |
568 | - handle_event (handled, path, IN_DELETE, NULL); |
569 | - } |
570 | + handle_event (handled, file, IN_DELETE, NULL); |
571 | + |
572 | + ignored = FALSE; |
573 | } |
574 | } |
575 | |
576 | - if (! rm_dir) |
577 | + if (! rm_dir) { |
578 | + nih_debug ("%s path '%s'", |
579 | + ! ignored ? "Handled" : "Ignored", |
580 | + path); |
581 | return; |
582 | + } |
583 | |
584 | - /* Remove the old directory watch */ |
585 | + nih_debug ("Removing old watch on path '%s'", dir->path); |
586 | nih_free (dir); |
587 | |
588 | nih_assert (! NIH_LIST_EMPTY (&entries)); |
589 | @@ -1252,6 +1282,8 @@ |
590 | _("Failed to watch directory"), parent); |
591 | return; |
592 | } |
593 | + |
594 | + nih_debug ("Creating watch on less specific path '%s'", new_dir->path); |
595 | } |
596 | |
597 | /* Add all list entries to the newly-created WatchedDir. */ |
598 | @@ -1345,6 +1377,28 @@ |
599 | entry = NIH_MUST (nih_list_entry_new (job)); |
600 | entry->data = file; |
601 | nih_list_add (&job->files, &entry->entry); |
602 | + |
603 | + /* Show the path the job specified, not the actual path since |
604 | + * that could confuse. |
605 | + */ |
606 | + if (file->glob) { |
607 | + nih_debug ("Watching %s glob '%s/%s' via '%s'", |
608 | + file->dir ? "directory" : "file", |
609 | + |
610 | + file->original |
611 | + ? file->original |
612 | + : file->path, |
613 | + |
614 | + file->glob, |
615 | + path); |
616 | + } else { |
617 | + nih_debug ("Watching %s '%s' via '%s'", |
618 | + file->dir ? "directory" : "file", |
619 | + file->original |
620 | + ? file->original |
621 | + : file->path, |
622 | + path); |
623 | + } |
624 | } |
625 | |
626 | /** |
627 | @@ -1352,7 +1406,7 @@ |
628 | * |
629 | * Initialise the watched_dirs hash table. |
630 | **/ |
631 | -void |
632 | +static void |
633 | watched_dir_init (void) |
634 | { |
635 | if (! watched_dirs) |
636 | @@ -1384,9 +1438,16 @@ |
637 | event_type == IN_MODIFY || |
638 | event_type == IN_DELETE); |
639 | |
640 | + nih_debug ("Emitting %s event for path '%s'", |
641 | + event_type == IN_CREATE ? "create" : |
642 | + event_type == IN_MODIFY ? "modify" : |
643 | + "delete", |
644 | + path); |
645 | + |
646 | env = NIH_MUST (nih_str_array_new (NULL)); |
647 | |
648 | var = NIH_MUST (nih_sprintf (NULL, "FILE=%s", path)); |
649 | + |
650 | NIH_MUST (nih_str_array_addp (&env, NULL, &env_len, var)); |
651 | |
652 | var = NIH_MUST (nih_sprintf (NULL, "EVENT=%s", |
653 | @@ -1514,7 +1575,7 @@ |
654 | */ |
655 | dir->watch = nih_watch_new (dir, watched_path, |
656 | FALSE, TRUE, |
657 | - (NihFileFilter)file_filter, |
658 | + NULL, |
659 | (NihCreateHandler)create_handler, |
660 | (NihModifyHandler)modify_handler, |
661 | (NihDeleteHandler)delete_handler, |
662 | @@ -1798,31 +1859,46 @@ |
663 | * handle_event: |
664 | * |
665 | * @handled: hash of FileEvents already handled, |
666 | - * @file_event: FileEvent to consider. |
667 | + * @file: WatchedFile to handle the event for, |
668 | + * @event: FileEvent to consider, |
669 | + * @match: full path to file that event refers to. |
670 | * |
671 | * Determine if @file_event has already been handled; if not emit the |
672 | * event and record its details in @handled. |
673 | **/ |
674 | static void |
675 | -handle_event (NihHash *handled, |
676 | - const char *path, |
677 | - uint32_t event, |
678 | - const char *match) |
679 | +handle_event (NihHash *handled, |
680 | + WatchedFile *file, |
681 | + uint32_t event, |
682 | + const char *match) |
683 | { |
684 | - FileEvent *file_event; |
685 | + FileEvent *file_event; |
686 | + nih_local char *original = NULL; |
687 | |
688 | nih_assert (handled); |
689 | - nih_assert (path); |
690 | + nih_assert (file); |
691 | nih_assert (event); |
692 | |
693 | - file_event = (FileEvent *)nih_hash_search (handled, path, NULL); |
694 | + original = original_path (NULL, file); |
695 | + |
696 | + if (! original) { |
697 | + nih_warn (_("Failed to determine original path")); |
698 | + return; |
699 | + } |
700 | + |
701 | + file_event = (FileEvent *)nih_hash_search (handled, original, NULL); |
702 | |
703 | while (file_event) { |
704 | if ((file_event->event & event) && string_match (file_event->match, match)) { |
705 | + nih_debug ("Ignoring %s event for already-handled path '%s'", |
706 | + event == IN_CREATE ? "create" : |
707 | + event == IN_MODIFY ? "modify" : |
708 | + "delete", |
709 | + original); |
710 | return; |
711 | } |
712 | |
713 | - file_event = (FileEvent *)nih_hash_search (handled, path, |
714 | + file_event = (FileEvent *)nih_hash_search (handled, original, |
715 | &file_event->entry); |
716 | } |
717 | |
718 | @@ -1831,10 +1907,68 @@ |
719 | /* Event has not yet been handled, so emit it and record fact |
720 | * it's now been handled. |
721 | */ |
722 | - file_event = NIH_MUST (file_event_new (handled, path, event, match)); |
723 | + file_event = NIH_MUST (file_event_new (handled, original, event, match)); |
724 | nih_hash_add (handled, &file_event->entry); |
725 | |
726 | - emit_event (path, event, match); |
727 | + if (file->original && file->original[0] == '~') { |
728 | + /* glob paths that also need to undergo expansion need |
729 | + * careful handling. |
730 | + */ |
731 | + if (file->glob) { |
732 | + nih_local char *emit_path = NULL; |
733 | + |
734 | + emit_path = NIH_MUST (nih_sprintf (NULL, "%s/%s", |
735 | + file->original, file->glob)); |
736 | + |
737 | + emit_event (emit_path, event, match); |
738 | + } else { |
739 | + emit_event (file->original, event, match); |
740 | + } |
741 | + } else { |
742 | + emit_event (original, event, match); |
743 | + } |
744 | +} |
745 | + |
746 | +/** |
747 | + * handle_glob: |
748 | + * |
749 | + * @file: WatchedFile that contains a glob, |
750 | + * @path: Full path to file (contains no globs), |
751 | + * @handled: hash of FileEvent's, |
752 | + * @event: file event type. |
753 | + * |
754 | + * Emit an event if @path matches the WatchedFile @file which must have |
755 | + * a glob set. |
756 | + * |
757 | + * Returns: TRUE if an event was emitted, else FALSE. |
758 | + **/ |
759 | +static int |
760 | +handle_glob (WatchedFile *file, |
761 | + const char *path, |
762 | + NihHash *handled, |
763 | + uint32_t event) |
764 | +{ |
765 | + nih_local char *original = NULL; |
766 | + |
767 | + nih_assert (file); |
768 | + nih_assert (file->glob); |
769 | + nih_assert (path); |
770 | + nih_assert (handled); |
771 | + nih_assert (event); |
772 | + |
773 | + original = original_path (NULL, file); |
774 | + |
775 | + if (! original) { |
776 | + nih_warn (_("Failed to determine original path")); |
777 | + return FALSE; |
778 | + } |
779 | + |
780 | + if (! fnmatch (original, path, FNM_PATHNAME) && (file->events & event)) { |
781 | + handle_event (handled, file, event, path); |
782 | + return TRUE; |
783 | + } |
784 | + |
785 | + return FALSE; |
786 | } |
787 | |
788 | /** |
789 | @@ -1863,6 +1997,51 @@ |
790 | } |
791 | |
792 | /** |
793 | + * file_exists: |
794 | + * @path: file to check. |
795 | + * |
796 | + * Determine if specified file exists. |
797 | + * |
798 | + * Returns: TRUE if @path exists, else FALSE. |
799 | + **/ |
800 | +static int |
801 | +file_exists (const char *path) |
802 | +{ |
803 | + struct stat st; |
804 | + |
805 | + nih_assert (path); |
806 | + |
807 | + return ! stat (path, &st); |
808 | +} |
809 | + |
810 | +/** |
811 | + * remove_trailing_slashes: |
812 | + * |
813 | + * @path: path. |
814 | + * |
815 | + * Remove contiguous runs of slashes from the end of @path. |
816 | + **/ |
817 | +static void |
818 | +remove_trailing_slashes (char *path) |
819 | +{ |
820 | + size_t len; |
821 | + char *end; |
822 | + |
823 | + assert (path); |
824 | + |
825 | + len = strlen (path); |
826 | + assert (len > 0); |
827 | + |
828 | + end = path + (len-1); |
829 | + |
830 | + while (*end == '/') { |
831 | + *end = '\0'; |
832 | + end--; |
833 | + } |
834 | +} |
835 | + |
836 | + |
837 | +/** |
838 | * expand_path: |
839 | * |
840 | * @parent: parent, |
841 | @@ -1875,11 +2054,13 @@ |
842 | * |
843 | * Returns: Newly-allocated fully-expanded path, or NULL on error. |
844 | **/ |
845 | -char * |
846 | +static char * |
847 | expand_path (const void *parent, const char *path) |
848 | { |
849 | - char *new; |
850 | - const char *p; |
851 | + char *new; |
852 | + const char *p; |
853 | + const char *e; |
854 | + nih_local char *path_env = NULL; |
855 | |
856 | nih_assert (path); |
857 | |
858 | @@ -1894,12 +2075,25 @@ |
859 | /* absolute path so nothing to do */ |
860 | nih_assert (path[0] != '/'); |
861 | |
862 | + e = getenv ("HOME"); |
863 | + |
864 | + if (e) { |
865 | + path_env = NIH_MUST (nih_strdup (NULL, e)); |
866 | + remove_trailing_slashes (path_env); |
867 | + } |
868 | + |
869 | p = path; |
870 | |
871 | if (strstr (path, "~/") == path || strstr (path, "./") == path) |
872 | p += 2; |
873 | |
874 | - new = nih_sprintf (parent, "%s/%s", home_dir, p); |
875 | + /* Note that $HOME is only honoured if it exists. |
876 | + */ |
877 | + new = nih_sprintf (parent, "%s/%s", |
878 | + path_env && file_exists (path_env) |
879 | + ? path_env |
880 | + : home_dir, |
881 | + p); |
882 | |
883 | return new; |
884 | } |
885 | @@ -1951,3 +2145,48 @@ |
886 | |
887 | return TRUE; |
888 | } |
889 | + |
890 | +/** |
891 | + * original_path: |
892 | + * |
893 | + * @parent: parent for returned string. |
894 | + * @file: WatchedFile. |
895 | + * |
896 | + * Obtain the appropriate WatchedFile path. |
897 | + * |
898 | + * - If the watched file contains a glob, the returned path will be |
899 | + * |
900 | + * the original if the |
901 | + * path underwent expansion or is a directory, else the initial |
902 | + * unexpanded path. |
903 | + * |
904 | + * Required for emitting events since jobs need the unexpanded path to |
905 | + * allow their start/stop condition to match even if the path has |
906 | + * subsequently been expanded by this bridge. |
907 | + * |
908 | + * Returns: newly-allocated string representing of original path. |
909 | + **/ |
910 | +static char * |
911 | +original_path (void *parent, const WatchedFile *file) |
912 | +{ |
913 | + char *path = NULL; |
914 | + |
915 | + nih_assert (file); |
916 | + |
917 | + if (file->original && file->original[0] == '~') { |
918 | + path = nih_strdup (parent, file->path); |
919 | + } else if (file->glob) { |
920 | + path = nih_sprintf (parent, "%s/%s", |
921 | + file->original |
922 | + ? file->original |
923 | + : file->path, |
924 | + file->glob); |
925 | + } else { |
926 | + path = nih_strdup (parent, |
927 | + file->original |
928 | + ? file->original |
929 | + : file->path); |
930 | + } |
931 | + |
932 | + return path; |
933 | +} |
934 | |
935 | === modified file 'scripts/pyupstart.py' |
936 | --- scripts/pyupstart.py 2014-04-04 14:16:52 +0000 |
937 | +++ scripts/pyupstart.py 2014-10-14 08:13:33 +0000 |
938 | @@ -1,5 +1,25 @@ |
939 | #!/usr/bin/python3 |
940 | -#--------------------------------------------------------------------- |
941 | +# -*- coding: utf-8 -*- |
942 | +# -------------------------------------------------------------------- |
943 | +# Copyright © 2013-2014 Canonical Ltd. |
944 | +# |
945 | +# Author: James Hunt <james.hunt@canonical.com> |
946 | +# |
947 | +# This program is free software; you can redistribute it and/or modify |
948 | +# it under the terms of the GNU General Public License version 2, as |
949 | +# published by the Free Software Foundation. |
950 | +# |
951 | +# This program is distributed in the hope that it will be useful, |
952 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
953 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
954 | +# GNU General Public License for more details. |
955 | +# |
956 | +# You should have received a copy of the GNU General Public License along |
957 | +# with this program; if not, write to the Free Software Foundation, Inc., |
958 | +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
959 | +# -------------------------------------------------------------------- |
960 | + |
961 | +# -------------------------------------------------------------------- |
962 | # = Limitations = |
963 | # |
964 | # - Override files are not currently supported. |
965 | @@ -8,7 +28,7 @@ |
966 | # where to create the override but be aware that after creating a |
967 | # '.override' file, you must wait until Upstart has re-parsed the job. |
968 | # |
969 | -#--------------------------------------------------------------------- |
970 | +# -------------------------------------------------------------------- |
971 | |
972 | """ |
973 | Upstart test module. |
974 | @@ -55,12 +75,12 @@ |
975 | |
976 | SESSION_DIR_FMT = 'upstart/sessions' |
977 | |
978 | -BUS_NAME = 'com.ubuntu.Upstart' |
979 | -INTERFACE_NAME = 'com.ubuntu.Upstart0_6' |
980 | -JOB_INTERFACE_NAME = 'com.ubuntu.Upstart0_6.Job' |
981 | -INSTANCE_INTERFACE_NAME = 'com.ubuntu.Upstart0_6.Instance' |
982 | -OBJECT_PATH = '/com/ubuntu/Upstart' |
983 | -FREEDESKTOP_PROPERTIES = 'org.freedesktop.DBus.Properties' |
984 | +BUS_NAME = 'com.ubuntu.Upstart' |
985 | +INTERFACE_NAME = 'com.ubuntu.Upstart0_6' |
986 | +JOB_INTERFACE_NAME = 'com.ubuntu.Upstart0_6.Job' |
987 | +INSTANCE_INTERFACE_NAME = 'com.ubuntu.Upstart0_6.Instance' |
988 | +OBJECT_PATH = '/com/ubuntu/Upstart' |
989 | +FREEDESKTOP_PROPERTIES = 'org.freedesktop.DBus.Properties' |
990 | |
991 | # Maximum number of seconds to wait for Upstart to detect a new job |
992 | # has been created |
993 | @@ -79,7 +99,6 @@ |
994 | # Maximum number of seconds to wait for Upstart to create a logfile |
995 | LOGFILE_WAIT_SECS = 5 |
996 | |
997 | -#--------------------------------------------------------------------- |
998 | |
999 | def get_init(): |
1000 | """ |
1001 | @@ -93,6 +112,7 @@ |
1002 | assert (os.path.exists(binary)) |
1003 | return binary |
1004 | |
1005 | + |
1006 | def get_initctl(): |
1007 | """ |
1008 | Return full path to an appropriate initctl binary. |
1009 | @@ -105,6 +125,7 @@ |
1010 | assert (os.path.exists(binary)) |
1011 | return binary |
1012 | |
1013 | + |
1014 | def get_file_bridge(): |
1015 | """ |
1016 | Return full path to an appropriate upstart-file-bridge binary. |
1017 | @@ -117,6 +138,7 @@ |
1018 | assert (os.path.exists(binary)) |
1019 | return binary |
1020 | |
1021 | + |
1022 | def dbus_encode(str): |
1023 | """ |
1024 | Simulate nih_dbus_path() which Upstart uses to convert |
1025 | @@ -288,7 +310,7 @@ |
1026 | |
1027 | """ |
1028 | self.new_job = Job(self, self.test_dir, self.test_dir_name, |
1029 | - name, body=body, retain=self.retain) |
1030 | + name, body=body, retain=self.retain) |
1031 | |
1032 | # deregister |
1033 | return False |
1034 | @@ -517,7 +539,7 @@ |
1035 | ) |
1036 | |
1037 | self.new_job = Job(self, self.test_dir, self.test_dir_name, |
1038 | - name, body=None, reuse_path=conf_path) |
1039 | + name, body=None, reuse_path=conf_path) |
1040 | self.jobs.append(self.new_job) |
1041 | return self.new_job |
1042 | |
1043 | @@ -540,7 +562,7 @@ |
1044 | """ |
1045 | |
1046 | def __init__(self, upstart, dir_name, subdir_name, job_name, |
1047 | - body=None, reuse_path=None, retain=False): |
1048 | + body=None, reuse_path=None, retain=False): |
1049 | """ |
1050 | @upstart: Upstart() parent object. |
1051 | @dir_name: Full path to job configuration files directory. |
1052 | @@ -591,8 +613,8 @@ |
1053 | if not self.body and self.reuse_path: |
1054 | # Just check conf file exists |
1055 | if not os.path.exists(self.conffile): |
1056 | - raise UpstartException( |
1057 | - 'File {} does not exist for reuse'.format(self.conffile)) |
1058 | + raise UpstartException('File {} does not exist for reuse' |
1059 | + .format(self.conffile)) |
1060 | else: |
1061 | # Create conf file |
1062 | with open(self.conffile, 'w', encoding='utf-8') as fh: |
1063 | @@ -970,6 +992,7 @@ |
1064 | self.stop() |
1065 | self.logfile.destroy() |
1066 | |
1067 | + |
1068 | class SystemInit(Upstart): |
1069 | |
1070 | def __init__(self): |
1071 | @@ -1009,8 +1032,9 @@ |
1072 | |
1073 | args = [get_initctl(), 'list-sessions'] |
1074 | |
1075 | - for line in subprocess.check_output(args, |
1076 | - universal_newlines=True).splitlines(): |
1077 | + lines = subprocess.check_output(args, |
1078 | + universal_newlines=True).splitlines() |
1079 | + for line in lines: |
1080 | pid, socket = line.split() |
1081 | sessions[pid] = socket |
1082 | |
1083 | @@ -1076,9 +1100,10 @@ |
1084 | self.log_dir = \ |
1085 | tempfile.mkdtemp(prefix="%s-logdir-%d-" % (NAME, pid)) |
1086 | |
1087 | - args.extend([init_binary, '--user', |
1088 | - '--confdir', self.conf_dir, |
1089 | - '--logdir', self.log_dir]) |
1090 | + args.extend([init_binary, |
1091 | + '--user', |
1092 | + '--confdir', self.conf_dir, |
1093 | + '--logdir', self.log_dir]) |
1094 | |
1095 | if extra: |
1096 | args.extend(extra) |
1097 | @@ -1091,7 +1116,8 @@ |
1098 | mask = pyinotify.IN_CREATE |
1099 | notifier = \ |
1100 | pyinotify.Notifier(watch_manager, InotifyHandler()) |
1101 | - session = os.path.join(os.environ['XDG_RUNTIME_DIR'], SESSION_DIR_FMT) |
1102 | + session = os.path.join(os.environ['XDG_RUNTIME_DIR'], |
1103 | + SESSION_DIR_FMT) |
1104 | watch_manager.add_watch(session, mask) |
1105 | |
1106 | notifier.process_events() |
1107 | |
1108 | === modified file 'scripts/tests/test_pyupstart_session_init.py' |
1109 | --- scripts/tests/test_pyupstart_session_init.py 2014-03-05 10:41:18 +0000 |
1110 | +++ scripts/tests/test_pyupstart_session_init.py 2014-10-14 08:13:33 +0000 |
1111 | @@ -1,6 +1,6 @@ |
1112 | #!/usr/bin/python3 |
1113 | # -*- coding: utf-8 -*- |
1114 | -#--------------------------------------------------------------------- |
1115 | +# -------------------------------------------------------------------- |
1116 | # Copyright 2013 Canonical Ltd. |
1117 | # |
1118 | # Author: James Hunt <james.hunt@canonical.com> |
1119 | @@ -17,15 +17,15 @@ |
1120 | # You should have received a copy of the GNU General Public License along |
1121 | # with this program; if not, write to the Free Software Foundation, Inc., |
1122 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
1123 | -#--------------------------------------------------------------------- |
1124 | +# -------------------------------------------------------------------- |
1125 | |
1126 | -#--------------------------------------------------------------------- |
1127 | +# -------------------------------------------------------------------- |
1128 | # Description: Session-level Upstart tests for the pyupstart module. |
1129 | # |
1130 | # Notes: Should be run both as a non-privileged user and then again |
1131 | # as the root user; in both cases, an Upstart user session |
1132 | # will be created. |
1133 | -#--------------------------------------------------------------------- |
1134 | +# -------------------------------------------------------------------- |
1135 | |
1136 | import os |
1137 | import sys |
1138 | @@ -45,6 +45,7 @@ |
1139 | |
1140 | import unittest |
1141 | |
1142 | + |
1143 | class TestSessionUpstart(unittest.TestCase): |
1144 | |
1145 | FILE_BRIDGE_CONF = 'upstart-file-bridge.conf' |
1146 | @@ -59,27 +60,41 @@ |
1147 | # variable since this is required by the Session Init. |
1148 | xdg_runtime_dir = os.environ.get('XDG_RUNTIME_DIR', None) |
1149 | if not xdg_runtime_dir or not os.path.exists(xdg_runtime_dir): |
1150 | - tmp_xdg_runtime_dir = tempfile.mkdtemp(prefix='tmp-xdg-runtime-dir') |
1151 | + tmp_xdg_runtime_dir = \ |
1152 | + tempfile.mkdtemp(prefix='tmp-xdg-runtime-dir') |
1153 | os.environ['XDG_RUNTIME_DIR'] = tmp_xdg_runtime_dir |
1154 | - print('INFO: User has no XDG_RUNTIME_DIR so created one: {}'.format(tmp_xdg_runtime_dir)) |
1155 | - |
1156 | - |
1157 | - self.file_bridge_conf = '{}{}{}'.format(bridge_session_conf_dir, os.sep, self.FILE_BRIDGE_CONF) |
1158 | - self.reexec_conf = '{}{}{}'.format(bridge_session_conf_dir, os.sep, self.REEXEC_CONF) |
1159 | + print('INFO: User has no XDG_RUNTIME_DIR so created one: {}' |
1160 | + .format(tmp_xdg_runtime_dir)) |
1161 | + |
1162 | + self.file_bridge_conf = '{}{}{}'.format(bridge_session_conf_dir, |
1163 | + os.sep, |
1164 | + self.FILE_BRIDGE_CONF) |
1165 | + |
1166 | + self.reexec_conf = '{}{}{}'.format(bridge_session_conf_dir, |
1167 | + os.sep, |
1168 | + self.REEXEC_CONF) |
1169 | |
1170 | # Prefer to use the installed job files if available and the |
1171 | # appropriate environment variable is set since they are going |
1172 | # to be more current and appropriate for the environment under |
1173 | # test. |
1174 | if os.environ.get('UPSTART_TEST_USE_INSTALLED_CONF', None): |
1175 | - tmp = '{}{}{}'.format(DEFAULT_SESSION_INSTALL_PATH, os.sep, self.FILE_BRIDGE_CONF) |
1176 | + tmp = '{}{}{}'.format(DEFAULT_SESSION_INSTALL_PATH, |
1177 | + os.sep, |
1178 | + self.FILE_BRIDGE_CONF) |
1179 | if os.path.exists(tmp): |
1180 | - print('INFO: UPSTART_TEST_USE_INSTALLED_CONF set - using {} rather than {}'.format(tmp, self.file_bridge_conf)) |
1181 | + print('INFO: UPSTART_TEST_USE_INSTALLED_CONF set - ' |
1182 | + 'using {} rather than {}' |
1183 | + .format(tmp, self.file_bridge_conf)) |
1184 | self.file_bridge_conf = tmp |
1185 | |
1186 | - tmp = '{}{}{}'.format(DEFAULT_SESSION_INSTALL_PATH, os.sep, self.REEXEC_CONF) |
1187 | + tmp = '{}{}{}'.format(DEFAULT_SESSION_INSTALL_PATH, |
1188 | + os.sep, |
1189 | + self.REEXEC_CONF) |
1190 | if os.path.exists(tmp): |
1191 | - print('INFO: UPSTART_TEST_USE_INSTALLED_CONF set - using {} rather than {}'.format(tmp, self.reexec_conf)) |
1192 | + print('INFO: UPSTART_TEST_USE_INSTALLED_CONF set - ' |
1193 | + 'using {} rather than {}' |
1194 | + .format(tmp, self.reexec_conf)) |
1195 | self.reexec_conf = tmp |
1196 | |
1197 | self.assertTrue(os.path.exists(self.file_bridge_conf)) |
1198 | @@ -135,24 +150,50 @@ |
1199 | |
1200 | self.upstart = None |
1201 | |
1202 | + |
1203 | class TestFileBridge(TestSessionUpstart): |
1204 | |
1205 | def test_init_start_file_bridge(self): |
1206 | self.start_session_init() |
1207 | |
1208 | + # Set $HOME to a temporary directory to allow us to create jobs |
1209 | + # that contain "~"'s that will be expanded as expected but which |
1210 | + # do not need to live under the users real home directory. |
1211 | + fake_home_dir = tempfile.mkdtemp() |
1212 | + |
1213 | + args = [get_initctl(), |
1214 | + 'set-env', |
1215 | + '--global', |
1216 | + 'HOME={}'.format(fake_home_dir)] |
1217 | + |
1218 | + subprocess.check_call(args) |
1219 | + |
1220 | + args = [get_initctl(), 'get-env', '--global', 'HOME'] |
1221 | + |
1222 | + # check Upstart is now using the fake $HOME value |
1223 | + lines = subprocess.check_output(args, |
1224 | + universal_newlines=True).splitlines() |
1225 | + |
1226 | + for line in lines: |
1227 | + assert(line == fake_home_dir) |
1228 | + self.logger.warning('Set \'HOME={}\' for testing' |
1229 | + .format(fake_home_dir)) |
1230 | + |
1231 | # Create upstart-file-bridge.conf |
1232 | # |
1233 | # Note that we do not use the bundled user job due to our |
1234 | # requirement for a different start condition and different |
1235 | # command options. |
1236 | - cmd = '{} --daemon --user --debug'.format(get_file_bridge()) |
1237 | + # |
1238 | + # Also, we run in the foreground to ensure debug output is |
1239 | + # captured. |
1240 | + cmd = '{} --user --debug'.format(get_file_bridge()) |
1241 | lines = """ |
1242 | start on startup |
1243 | stop on session-end |
1244 | |
1245 | emits file |
1246 | |
1247 | - expect daemon |
1248 | respawn |
1249 | exec {} |
1250 | """.format(cmd) |
1251 | @@ -171,46 +212,136 @@ |
1252 | os.kill(pid, 0) |
1253 | |
1254 | target_dir = tempfile.mkdtemp() |
1255 | - file = target_dir + os.sep + 'foo' |
1256 | - dir = target_dir + os.sep + 'bar' |
1257 | + |
1258 | + self.logger.warning('Session Init file watch directory: {}' |
1259 | + .format(target_dir)) |
1260 | + |
1261 | + file = target_dir + os.sep + 'foo.bar' |
1262 | + file2 = target_dir + os.sep + 'hello.baz' |
1263 | + glob_file = target_dir + os.sep + '*.baz' |
1264 | + dir = target_dir + os.sep + 'qux' |
1265 | + |
1266 | + tilde_file = '~' + os.sep + 'foo.bar' |
1267 | + tilde_file_expanded = fake_home_dir + os.sep + 'foo.bar' |
1268 | + |
1269 | + tilde_glob_file = '~' + os.sep + '*.baz' |
1270 | + tilde_glob_file_expanded = fake_home_dir + os.sep + 'glob.baz' |
1271 | + |
1272 | + tilde_dir = '~' + dir |
1273 | + |
1274 | + # We remove all the special characters that are used in the |
1275 | + # 'start on' stanza to avoid upstart detecting them as shell |
1276 | + # meta-characters and passing the exec command through a shell |
1277 | + # (which will then perform expansion and defeat our simple |
1278 | + # string checking). |
1279 | + |
1280 | + vars_msg = r"FILE='$FILE', EVENT='$EVENT', MATCH='$MATCH'" |
1281 | + |
1282 | + self.logger.warning('Session Init conf directory: {}' |
1283 | + .format(self.upstart.conf_dir)) |
1284 | + |
1285 | + self.logger.warning('Session Init log directory: {}' |
1286 | + .format(self.upstart.log_dir)) |
1287 | + |
1288 | + # ----------------------------------------------------------- |
1289 | + # file jobs |
1290 | + |
1291 | + # ----------------------------- |
1292 | + # create jobs |
1293 | |
1294 | # Create a job that makes use of the file event to watch a |
1295 | # file in a newly-created directory. |
1296 | - file_msg = 'got file %s' % file |
1297 | lines = [] |
1298 | lines.append('start on file FILE=%s EVENT=create' % file) |
1299 | - lines.append('exec echo %s' % file_msg) |
1300 | - create_file_job = self.upstart.job_create('wait-for-file-creation', lines) |
1301 | + lines.append('exec echo "%s"' % vars_msg) |
1302 | + create_file_job = self.upstart.job_create('file-create', lines) |
1303 | self.assertTrue(create_file_job) |
1304 | |
1305 | + # glob create file job |
1306 | + lines = [] |
1307 | + lines.append('start on file FILE=%s EVENT=create' % glob_file) |
1308 | + lines.append('exec echo "%s"' % vars_msg) |
1309 | + create_glob_file_job = \ |
1310 | + self.upstart.job_create('glob-file-job-create', lines) |
1311 | + self.assertTrue(create_glob_file_job) |
1312 | + |
1313 | + # tilde create file job |
1314 | + lines = [] |
1315 | + lines.append('start on file FILE=%s EVENT=create' % tilde_file) |
1316 | + lines.append('exec echo "%s"' % vars_msg) |
1317 | + tilde_file_create_job = \ |
1318 | + self.upstart.job_create('tilde-file-job-create', lines) |
1319 | + self.assertTrue(tilde_file_create_job) |
1320 | + |
1321 | + # tilde glob create file job |
1322 | + lines = [] |
1323 | + lines.append('start on file FILE=%s EVENT=create' % tilde_glob_file) |
1324 | + lines.append('exec echo "%s"' % vars_msg) |
1325 | + tilde_glob_file_create_job = \ |
1326 | + self.upstart.job_create('tilde-glob-file-job-create', lines) |
1327 | + self.assertTrue(tilde_glob_file_create_job) |
1328 | + |
1329 | + # ----------------------------- |
1330 | + # modify jobs |
1331 | + |
1332 | # Create job that waits for a file modification |
1333 | lines = [] |
1334 | lines.append('start on file FILE=%s EVENT=modify' % file) |
1335 | - lines.append('exec echo %s' % file_msg) |
1336 | - modify_file_job = self.upstart.job_create('wait-for-file-modify', lines) |
1337 | + lines.append('exec echo "%s"' % vars_msg) |
1338 | + modify_file_job = self.upstart.job_create('file-modify', lines) |
1339 | self.assertTrue(modify_file_job) |
1340 | |
1341 | + # glob modify file job |
1342 | + lines = [] |
1343 | + lines.append('start on file FILE=%s EVENT=modify' % glob_file) |
1344 | + lines.append('exec echo "%s"' % vars_msg) |
1345 | + modify_glob_file_job = \ |
1346 | + self.upstart.job_create('glob-file-job-modify', lines) |
1347 | + self.assertTrue(modify_glob_file_job) |
1348 | + |
1349 | + # tilde modify file job |
1350 | + lines = [] |
1351 | + lines.append('start on file FILE=%s EVENT=modify' % tilde_file) |
1352 | + lines.append('exec echo "%s"' % vars_msg) |
1353 | + tilde_file_modify_job = \ |
1354 | + self.upstart.job_create('tilde-file-job-modify', lines) |
1355 | + self.assertTrue(tilde_file_modify_job) |
1356 | + |
1357 | + # tilde glob modify file job |
1358 | + lines = [] |
1359 | + lines.append('start on file FILE=%s EVENT=modify' % tilde_glob_file) |
1360 | + lines.append('exec echo "%s"' % vars_msg) |
1361 | + tilde_glob_file_modify_job = \ |
1362 | + self.upstart.job_create('tilde-glob-file-job-modify', lines) |
1363 | + self.assertTrue(tilde_glob_file_modify_job) |
1364 | + |
1365 | + # ----------------------------- |
1366 | + # delete jobs |
1367 | + |
1368 | # Create another job that triggers when the same file is deleted |
1369 | lines = [] |
1370 | lines.append('start on file FILE=%s EVENT=delete' % file) |
1371 | - lines.append('exec echo %s' % file_msg) |
1372 | - delete_file_job = self.upstart.job_create('wait-for-file-deletion', lines) |
1373 | + lines.append('exec echo "%s"' % vars_msg) |
1374 | + delete_file_job = self.upstart.job_create('file-delete', lines) |
1375 | self.assertTrue(delete_file_job) |
1376 | |
1377 | + # ----------------------------------------------------------- |
1378 | + # directory jobs |
1379 | + |
1380 | # Create job that triggers on directory creation |
1381 | - dir_msg = 'got directory %s' % dir |
1382 | lines = [] |
1383 | # XXX: note the trailing slash to force a directory watch |
1384 | lines.append('start on file FILE=%s/ EVENT=create' % dir) |
1385 | - lines.append('exec echo %s' % dir_msg) |
1386 | - create_dir_job = self.upstart.job_create('wait-for-dir-creation', lines) |
1387 | + lines.append('exec echo "%s"' % vars_msg) |
1388 | + create_dir_job = \ |
1389 | + self.upstart.job_create('wait-for-dir-creation', lines) |
1390 | self.assertTrue(create_dir_job) |
1391 | |
1392 | # Create job that triggers on directory modification |
1393 | lines = [] |
1394 | # XXX: note the trailing slash to force a directory watch |
1395 | lines.append('start on file FILE=%s/ EVENT=modify' % dir) |
1396 | - lines.append('exec echo %s' % dir_msg) |
1397 | + lines.append('exec echo "%s"' % vars_msg) |
1398 | modify_dir_job = self.upstart.job_create('wait-for-dir-modify', lines) |
1399 | self.assertTrue(modify_dir_job) |
1400 | |
1401 | @@ -218,38 +349,72 @@ |
1402 | lines = [] |
1403 | # XXX: note the trailing slash to force a directory watch |
1404 | lines.append('start on file FILE=%s/ EVENT=delete' % dir) |
1405 | - lines.append('exec echo %s' % dir_msg) |
1406 | + lines.append('exec echo "%s"' % vars_msg) |
1407 | delete_dir_job = self.upstart.job_create('wait-for-dir-delete', lines) |
1408 | self.assertTrue(delete_dir_job) |
1409 | |
1410 | - # Create empty file |
1411 | - open(file, 'w').close() |
1412 | + # ----------------------------------------------------------- |
1413 | + # Trigger file bridge |
1414 | + |
1415 | + # Create file |
1416 | + with open(file, 'w') as f: |
1417 | + f.write('created\n') |
1418 | |
1419 | # Create directory |
1420 | os.mkdir(dir) |
1421 | |
1422 | + # ----------------------------------------------------------- |
1423 | # No need to start the jobs of course as the file-bridge handles that! |
1424 | |
1425 | # Identify full path to job logfiles |
1426 | create_file_job_logfile = create_file_job.logfile_name(dbus_encode('')) |
1427 | self.assertTrue(create_file_job_logfile) |
1428 | |
1429 | - modify_file_job_logfile = modify_file_job.logfile_name(dbus_encode('')) |
1430 | + create_glob_file_job_logfile = \ |
1431 | + create_glob_file_job.logfile_name(dbus_encode('')) |
1432 | + self.assertTrue(create_glob_file_job_logfile) |
1433 | + |
1434 | + create_tilde_file_job_logfile = \ |
1435 | + tilde_file_create_job.logfile_name(dbus_encode('')) |
1436 | + self.assertTrue(create_tilde_file_job_logfile) |
1437 | + |
1438 | + create_tilde_glob_file_job_logfile = \ |
1439 | + tilde_glob_file_create_job.logfile_name(dbus_encode('')) |
1440 | + self.assertTrue(create_tilde_glob_file_job_logfile) |
1441 | + |
1442 | + modify_file_job_logfile = \ |
1443 | + modify_file_job.logfile_name(dbus_encode('')) |
1444 | self.assertTrue(modify_file_job_logfile) |
1445 | |
1446 | - delete_file_job_logfile = delete_file_job.logfile_name(dbus_encode('')) |
1447 | + modify_glob_file_job_logfile = \ |
1448 | + modify_glob_file_job.logfile_name(dbus_encode('')) |
1449 | + self.assertTrue(modify_glob_file_job_logfile) |
1450 | + |
1451 | + tilde_file_modify_job_logfile = \ |
1452 | + tilde_file_modify_job.logfile_name(dbus_encode('')) |
1453 | + self.assertTrue(tilde_file_modify_job_logfile) |
1454 | + |
1455 | + tilde_glob_file_modify_job_logfile = \ |
1456 | + tilde_glob_file_modify_job.logfile_name(dbus_encode('')) |
1457 | + self.assertTrue(tilde_glob_file_modify_job_logfile) |
1458 | + |
1459 | + delete_file_job_logfile = \ |
1460 | + delete_file_job.logfile_name(dbus_encode('')) |
1461 | self.assertTrue(delete_file_job_logfile) |
1462 | |
1463 | - create_dir_job_logfile = create_dir_job.logfile_name(dbus_encode('')) |
1464 | + create_dir_job_logfile = \ |
1465 | + create_dir_job.logfile_name(dbus_encode('')) |
1466 | self.assertTrue(create_dir_job_logfile) |
1467 | |
1468 | - modify_dir_job_logfile = modify_dir_job.logfile_name(dbus_encode('')) |
1469 | + modify_dir_job_logfile = \ |
1470 | + modify_dir_job.logfile_name(dbus_encode('')) |
1471 | self.assertTrue(modify_dir_job_logfile) |
1472 | |
1473 | - delete_dir_job_logfile = delete_dir_job.logfile_name(dbus_encode('')) |
1474 | + delete_dir_job_logfile = \ |
1475 | + delete_dir_job.logfile_name(dbus_encode('')) |
1476 | self.assertTrue(delete_dir_job_logfile) |
1477 | |
1478 | - #-------------------- |
1479 | + # ------------------- |
1480 | |
1481 | # Wait for the create file job to run and produce output |
1482 | self.assertTrue(wait_for_file(create_file_job_logfile)) |
1483 | @@ -258,9 +423,73 @@ |
1484 | with open(create_file_job_logfile, 'r', encoding='utf-8') as f: |
1485 | lines = f.readlines() |
1486 | self.assertTrue(len(lines) == 1) |
1487 | - self.assertEqual(file_msg, lines[0].rstrip()) |
1488 | - |
1489 | - #-------------------- |
1490 | + expected = "FILE='{}', EVENT='{}', MATCH='{}'" \ |
1491 | + .format(file, 'create', '') |
1492 | + self.assertTrue(expected == lines[0].rstrip()) |
1493 | + |
1494 | + # ----------------------------------------------------------- |
1495 | + |
1496 | + # Create 2nd empty file |
1497 | + with open(file2, 'w') as f: |
1498 | + f.write('created\n') |
1499 | + |
1500 | + # ----------------------------------------------------------- |
1501 | + |
1502 | + # Wait for the tilde create file job to run and produce output |
1503 | + self.assertTrue(wait_for_file(create_glob_file_job_logfile)) |
1504 | + |
1505 | + # Check the output |
1506 | + with open(create_glob_file_job_logfile, 'r', encoding='utf-8') as f: |
1507 | + lines = f.readlines() |
1508 | + self.assertTrue(len(lines) == 1) |
1509 | + |
1510 | + expected = "FILE='{}', EVENT='{}', MATCH='{}'" \ |
1511 | + .format(glob_file, 'create', file2) |
1512 | + self.assertTrue(expected == lines[0].rstrip()) |
1513 | + |
1514 | + # ----------------------------------------------------------- |
1515 | + |
1516 | + # create tilde file |
1517 | + with open(tilde_file_expanded, 'w') as f: |
1518 | + f.write('created\n') |
1519 | + |
1520 | + # ----------------------------------------------------------- |
1521 | + |
1522 | + # Wait for the tilde create file job to run and produce output |
1523 | + self.assertTrue(wait_for_file(create_tilde_file_job_logfile)) |
1524 | + |
1525 | + # Check the output |
1526 | + with open(create_tilde_file_job_logfile, 'r', encoding='utf-8') as f: |
1527 | + lines = f.readlines() |
1528 | + self.assertTrue(len(lines) == 1) |
1529 | + |
1530 | + expected = "FILE='{}', EVENT='{}', MATCH='{}'" \ |
1531 | + .format(tilde_file, 'create', '') |
1532 | + self.assertTrue(expected == lines[0].rstrip()) |
1533 | + |
1534 | + # ----------------------------------------------------------- |
1535 | + |
1536 | + # create tilde glob file |
1537 | + with open(tilde_glob_file_expanded, 'w') as f: |
1538 | + f.write('created\n') |
1539 | + |
1540 | + # ----------------------------------------------------------- |
1541 | + |
1542 | + # Wait for the tilde glob create file job to run and produce output |
1543 | + self.assertTrue(wait_for_file(create_tilde_glob_file_job_logfile)) |
1544 | + |
1545 | + # Check the output |
1546 | + with open(create_tilde_glob_file_job_logfile, |
1547 | + 'r', |
1548 | + encoding='utf-8') as f: |
1549 | + lines = f.readlines() |
1550 | + self.assertTrue(len(lines) == 1) |
1551 | + |
1552 | + expected = "FILE='{}', EVENT='{}', MATCH='{}'" \ |
1553 | + .format(tilde_glob_file, 'create', tilde_glob_file_expanded) |
1554 | + self.assertTrue(expected == lines[0].rstrip()) |
1555 | + |
1556 | + # ------------------- |
1557 | |
1558 | # Wait for the create directory job to run and produce output |
1559 | self.assertTrue(wait_for_file(create_dir_job_logfile)) |
1560 | @@ -269,12 +498,17 @@ |
1561 | with open(create_dir_job_logfile, 'r', encoding='utf-8') as f: |
1562 | lines = f.readlines() |
1563 | self.assertTrue(len(lines) == 1) |
1564 | - self.assertEqual(dir_msg, lines[0].rstrip()) |
1565 | - |
1566 | - #-------------------- |
1567 | + |
1568 | + expected = "FILE='{}', EVENT='{}', MATCH='{}'" \ |
1569 | + .format(dir + '/', 'create', '') |
1570 | + |
1571 | + self.assertTrue(expected == lines[0].rstrip()) |
1572 | + |
1573 | + # ------------------- |
1574 | |
1575 | # Modify the file |
1576 | - open(file, 'w').close() |
1577 | + with open(file, 'a') as f: |
1578 | + f.write('appended\n') |
1579 | |
1580 | # Wait for the create file job to run and produce output |
1581 | self.assertTrue(wait_for_file(modify_file_job_logfile)) |
1582 | @@ -283,11 +517,59 @@ |
1583 | with open(modify_file_job_logfile, 'r', encoding='utf-8') as f: |
1584 | lines = f.readlines() |
1585 | self.assertTrue(len(lines) == 1) |
1586 | - self.assertEqual(file_msg, lines[0].rstrip()) |
1587 | - |
1588 | - #-------------------- |
1589 | + expected = "FILE='{}', EVENT='{}', MATCH='{}'" \ |
1590 | + .format(file, 'modify', '') |
1591 | + self.assertTrue(expected == lines[0].rstrip()) |
1592 | + |
1593 | + with open(file2, 'a') as f: |
1594 | + f.write('appended\n') |
1595 | + |
1596 | + self.assertTrue(wait_for_file(modify_glob_file_job_logfile)) |
1597 | + |
1598 | + # Check the output |
1599 | + with open(modify_glob_file_job_logfile, 'r', encoding='utf-8') as f: |
1600 | + lines = f.readlines() |
1601 | + self.assertTrue(len(lines) == 1) |
1602 | + |
1603 | + expected = "FILE='{}', EVENT='{}', MATCH='{}'" \ |
1604 | + .format(glob_file, 'modify', file2) |
1605 | + self.assertTrue(expected == lines[0].rstrip()) |
1606 | + |
1607 | + # modify tilde file |
1608 | + with open(tilde_file_expanded, 'a') as f: |
1609 | + f.write('modified\n') |
1610 | + |
1611 | + self.assertTrue(wait_for_file(tilde_file_modify_job_logfile)) |
1612 | + |
1613 | + # Check the output |
1614 | + with open(tilde_file_modify_job_logfile, 'r', encoding='utf-8') as f: |
1615 | + lines = f.readlines() |
1616 | + self.assertTrue(len(lines) == 1) |
1617 | + |
1618 | + expected = "FILE='{}', EVENT='{}', MATCH='{}'" \ |
1619 | + .format(tilde_file, 'modify', '') |
1620 | + self.assertTrue(expected == lines[0].rstrip()) |
1621 | + |
1622 | + # create tilde glob file |
1623 | + with open(tilde_glob_file_expanded, 'a') as f: |
1624 | + f.write('modified\n') |
1625 | + |
1626 | + self.assertTrue(wait_for_file(tilde_glob_file_modify_job_logfile)) |
1627 | + |
1628 | + # Check the output |
1629 | + with open(tilde_glob_file_modify_job_logfile, |
1630 | + 'r', |
1631 | + encoding='utf-8') as f: |
1632 | + lines = f.readlines() |
1633 | + self.assertTrue(len(lines) == 1) |
1634 | + |
1635 | + expected = "FILE='{}', EVENT='{}', MATCH='{}'" \ |
1636 | + .format(tilde_glob_file, 'modify', tilde_glob_file_expanded) |
1637 | + self.assertTrue(expected == lines[0].rstrip()) |
1638 | + |
1639 | + # ------------------- |
1640 | + |
1641 | # Modify the directory by creating a new file in it. |
1642 | - |
1643 | dir_file = dir + os.sep + 'baz' |
1644 | open(dir_file, 'w').close() |
1645 | |
1646 | @@ -298,9 +580,12 @@ |
1647 | with open(modify_dir_job_logfile, 'r', encoding='utf-8') as f: |
1648 | lines = f.readlines() |
1649 | self.assertTrue(len(lines) == 1) |
1650 | - self.assertEqual(dir_msg, lines[0].rstrip()) |
1651 | - |
1652 | - #-------------------- |
1653 | + |
1654 | + expected = "FILE='{}', EVENT='{}', MATCH='{}'" \ |
1655 | + .format(dir + '/', 'modify', dir_file) |
1656 | + self.assertTrue(expected == lines[0].rstrip()) |
1657 | + |
1658 | + # ------------------- |
1659 | |
1660 | os.remove(dir_file) |
1661 | os.rmdir(dir) |
1662 | @@ -312,8 +597,12 @@ |
1663 | with open(delete_dir_job_logfile, 'r', encoding='utf-8') as f: |
1664 | lines = f.readlines() |
1665 | self.assertTrue(len(lines) == 1) |
1666 | - self.assertEqual(dir_msg, lines[0].rstrip()) |
1667 | - #-------------------- |
1668 | + |
1669 | + expected = "FILE='{}', EVENT='{}', MATCH='{}'" \ |
1670 | + .format(dir + '/', 'delete', '') |
1671 | + self.assertTrue(expected == lines[0].rstrip()) |
1672 | + |
1673 | + # ------------------- |
1674 | |
1675 | shutil.rmtree(target_dir) |
1676 | |
1677 | @@ -324,13 +613,31 @@ |
1678 | with open(delete_file_job_logfile, 'r', encoding='utf-8') as f: |
1679 | lines = f.readlines() |
1680 | self.assertTrue(len(lines) == 1) |
1681 | - self.assertEqual(file_msg, lines[0].rstrip()) |
1682 | - |
1683 | - #-------------------- |
1684 | + |
1685 | + expected = "FILE='{}', EVENT='{}', MATCH='{}'" \ |
1686 | + .format(file, 'delete', '') |
1687 | + self.assertTrue(expected == lines[0].rstrip()) |
1688 | + |
1689 | + # ------------------- |
1690 | + |
1691 | + if os.environ.get('UPSTART_TEST_NO_CLEANUP'): |
1692 | + sys.exit("UPSTART_TEST_NO_CLEANUP set\n" |
1693 | + "Files left in '{}' and '{}'" |
1694 | + .format(self.upstart.conf_dir, |
1695 | + self.upstart.log_dir)) |
1696 | |
1697 | os.remove(create_file_job_logfile) |
1698 | + os.remove(create_glob_file_job_logfile) |
1699 | + os.remove(create_tilde_file_job_logfile) |
1700 | + os.remove(create_tilde_glob_file_job_logfile) |
1701 | + |
1702 | os.remove(modify_file_job_logfile) |
1703 | + os.remove(modify_glob_file_job_logfile) |
1704 | + os.remove(tilde_file_modify_job_logfile) |
1705 | + os.remove(tilde_glob_file_modify_job_logfile) |
1706 | + |
1707 | os.remove(delete_file_job_logfile) |
1708 | + |
1709 | os.remove(create_dir_job_logfile) |
1710 | os.remove(modify_dir_job_logfile) |
1711 | os.remove(delete_dir_job_logfile) |
1712 | @@ -338,6 +645,7 @@ |
1713 | file_bridge.stop() |
1714 | self.stop_session_init() |
1715 | |
1716 | + |
1717 | class TestSessionInitReExec(TestSessionUpstart): |
1718 | |
1719 | def test_session_init_reexec(self): |
1720 | @@ -487,6 +795,7 @@ |
1721 | |
1722 | self.stop_session_init() |
1723 | |
1724 | + |
1725 | class TestSessionInit(TestSessionUpstart): |
1726 | |
1727 | def test_session_init(self): |
1728 | @@ -508,6 +817,7 @@ |
1729 | inst.stop() |
1730 | self.stop_session_init() |
1731 | |
1732 | + |
1733 | class TestState(TestSessionUpstart): |
1734 | |
1735 | def test_state(self): |
1736 | @@ -539,6 +849,7 @@ |
1737 | self.assertEqual(path, full_job_path) |
1738 | self.stop_session_init() |
1739 | |
1740 | + |
1741 | def main(): |
1742 | kwargs = {} |
1743 | format = \ |
1744 | |
1745 | === modified file 'scripts/tests/test_pyupstart_system_init.py' |
1746 | --- scripts/tests/test_pyupstart_system_init.py 2014-03-10 13:43:50 +0000 |
1747 | +++ scripts/tests/test_pyupstart_system_init.py 2014-10-14 08:13:33 +0000 |
1748 | @@ -1,6 +1,6 @@ |
1749 | #!/usr/bin/python3 |
1750 | # -*- coding: utf-8 -*- |
1751 | -#--------------------------------------------------------------------- |
1752 | +# -------------------------------------------------------------------- |
1753 | # Copyright © 2013 Canonical Ltd. |
1754 | # |
1755 | # Author: James Hunt <james.hunt@canonical.com> |
1756 | @@ -17,13 +17,13 @@ |
1757 | # You should have received a copy of the GNU General Public License along |
1758 | # with this program; if not, write to the Free Software Foundation, Inc., |
1759 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
1760 | -#--------------------------------------------------------------------- |
1761 | +# -------------------------------------------------------------------- |
1762 | |
1763 | -#--------------------------------------------------------------------- |
1764 | +# -------------------------------------------------------------------- |
1765 | # Description: System-level Upstart tests for the pyupstart module. |
1766 | # |
1767 | # Notes: Can only be run as the root user. |
1768 | -#--------------------------------------------------------------------- |
1769 | +# -------------------------------------------------------------------- |
1770 | |
1771 | import os |
1772 | import sys |
1773 | @@ -40,6 +40,7 @@ |
1774 | |
1775 | import unittest |
1776 | |
1777 | + |
1778 | class TestSystemUpstart(unittest.TestCase): |
1779 | def setUp(self): |
1780 | if os.geteuid(): |
1781 | @@ -52,86 +53,92 @@ |
1782 | |
1783 | def tearDown(self): |
1784 | # Ensure no state file exists |
1785 | - state_file = '{}{}{}'.format(SYSTEM_LOG_DIR, os.sep, UPSTART_STATE_FILE) |
1786 | + state_file = '{}{}{}'.format(SYSTEM_LOG_DIR, |
1787 | + os.sep, |
1788 | + UPSTART_STATE_FILE) |
1789 | self.assertFalse(os.path.exists(state_file)) |
1790 | |
1791 | + |
1792 | class TestSystemInitReExec(TestSystemUpstart): |
1793 | |
1794 | def test_pid1_reexec(self): |
1795 | - version = self.upstart.version() |
1796 | - self.assertTrue(version) |
1797 | - |
1798 | - # Create an invalid job to ensure this causes no problems for |
1799 | - # the re-exec. Note that we cannot use job_create() since |
1800 | - # that validates the syntax of the .conf file). |
1801 | - # |
1802 | - # We create this file before any other to allow time for Upstart |
1803 | - # to _attempt to parse it_ by the time the re-exec is initiated. |
1804 | - invalid_conf = "{}/invalid.conf".format(self.upstart.test_dir) |
1805 | - with open(invalid_conf, 'w', encoding='utf-8') as fh: |
1806 | - print("invalid", file=fh) |
1807 | - |
1808 | - # create a job and start it, marking it such that the .conf file |
1809 | - # will be retained when object becomes unusable (after re-exec). |
1810 | - job = self.upstart.job_create('connected-job', ['exec upstart-udev-bridge', 'respawn'], retain=True) |
1811 | - self.assertTrue(job) |
1812 | - |
1813 | - # Used when recreating the job |
1814 | - conf_path = job.conffile |
1815 | - |
1816 | - inst = job.start() |
1817 | - self.assertTrue(inst) |
1818 | - pids = job.pids() |
1819 | - self.assertEqual(len(pids), 1) |
1820 | - pid = pids['main'] |
1821 | - |
1822 | - self.upstart.reexec() |
1823 | - |
1824 | - # PID 1 Upstart is now in the process of starting, but we need to |
1825 | - # reconnect to it via D-Bus since it cannot yet retain client |
1826 | - # connections. However, since the re-exec won't be instantaneous, |
1827 | - # try a few times. |
1828 | - self.upstart.polling_connect(force=True) |
1829 | - |
1830 | - # Since the parent job was created with 'retain', this is actually |
1831 | - # a NOP but is added to denote that the old instance is dead. |
1832 | - inst.destroy() |
1833 | - |
1834 | - # check that we can still operate on the re-exec'd Upstart |
1835 | - version_postexec = self.upstart.version() |
1836 | - self.assertTrue(version_postexec) |
1837 | - self.assertEqual(version, version_postexec) |
1838 | - |
1839 | - # Ensure the job is still running with the same PID |
1840 | - self.assertRaises(ProcessLookupError, os.kill, pid, 0) |
1841 | - |
1842 | - # XXX: The re-exec will have severed the D-Bus connection to |
1843 | - # Upstart. Hence, revivify the job with some magic. |
1844 | - job = self.upstart.job_recreate('connected-job', conf_path) |
1845 | - self.assertTrue(job) |
1846 | - |
1847 | - # Recreate the instance |
1848 | - inst = job.get_instance() |
1849 | - self.assertTrue(inst) |
1850 | - |
1851 | - self.assertTrue(job.running('_')) |
1852 | - pids = job.pids() |
1853 | - self.assertEqual(len(pids), 1) |
1854 | - self.assertTrue(pids['main']) |
1855 | - |
1856 | - # The pid _must_ have changed after a restart |
1857 | - self.assertNotEqual(pid, pids['main']) |
1858 | - |
1859 | - job.stop() |
1860 | - |
1861 | - # Ensure the pid has gone |
1862 | - with self.assertRaises(ProcessLookupError): |
1863 | - os.kill(pid, 0) |
1864 | - |
1865 | - os.remove(invalid_conf) |
1866 | - |
1867 | - # Clean up |
1868 | - self.upstart.destroy() |
1869 | + version = self.upstart.version() |
1870 | + self.assertTrue(version) |
1871 | + |
1872 | + # Create an invalid job to ensure this causes no problems for |
1873 | + # the re-exec. Note that we cannot use job_create() since |
1874 | + # that validates the syntax of the .conf file). |
1875 | + # |
1876 | + # We create this file before any other to allow time for Upstart |
1877 | + # to _attempt to parse it_ by the time the re-exec is initiated. |
1878 | + invalid_conf = "{}/invalid.conf".format(self.upstart.test_dir) |
1879 | + with open(invalid_conf, 'w', encoding='utf-8') as fh: |
1880 | + print("invalid", file=fh) |
1881 | + |
1882 | + # create a job and start it, marking it such that the .conf file |
1883 | + # will be retained when object becomes unusable (after re-exec). |
1884 | + job = self.upstart.job_create('connected-job', |
1885 | + ['exec upstart-udev-bridge', 'respawn'], |
1886 | + retain=True) |
1887 | + self.assertTrue(job) |
1888 | + |
1889 | + # Used when recreating the job |
1890 | + conf_path = job.conffile |
1891 | + |
1892 | + inst = job.start() |
1893 | + self.assertTrue(inst) |
1894 | + pids = job.pids() |
1895 | + self.assertEqual(len(pids), 1) |
1896 | + pid = pids['main'] |
1897 | + |
1898 | + self.upstart.reexec() |
1899 | + |
1900 | + # PID 1 Upstart is now in the process of starting, but we need to |
1901 | + # reconnect to it via D-Bus since it cannot yet retain client |
1902 | + # connections. However, since the re-exec won't be instantaneous, |
1903 | + # try a few times. |
1904 | + self.upstart.polling_connect(force=True) |
1905 | + |
1906 | + # Since the parent job was created with 'retain', this is actually |
1907 | + # a NOP but is added to denote that the old instance is dead. |
1908 | + inst.destroy() |
1909 | + |
1910 | + # check that we can still operate on the re-exec'd Upstart |
1911 | + version_postexec = self.upstart.version() |
1912 | + self.assertTrue(version_postexec) |
1913 | + self.assertEqual(version, version_postexec) |
1914 | + |
1915 | + # Ensure the job is still running with the same PID |
1916 | + self.assertRaises(ProcessLookupError, os.kill, pid, 0) |
1917 | + |
1918 | + # XXX: The re-exec will have severed the D-Bus connection to |
1919 | + # Upstart. Hence, revivify the job with some magic. |
1920 | + job = self.upstart.job_recreate('connected-job', conf_path) |
1921 | + self.assertTrue(job) |
1922 | + |
1923 | + # Recreate the instance |
1924 | + inst = job.get_instance() |
1925 | + self.assertTrue(inst) |
1926 | + |
1927 | + self.assertTrue(job.running('_')) |
1928 | + pids = job.pids() |
1929 | + self.assertEqual(len(pids), 1) |
1930 | + self.assertTrue(pids['main']) |
1931 | + |
1932 | + # The pid _must_ have changed after a restart |
1933 | + self.assertNotEqual(pid, pids['main']) |
1934 | + |
1935 | + job.stop() |
1936 | + |
1937 | + # Ensure the pid has gone |
1938 | + with self.assertRaises(ProcessLookupError): |
1939 | + os.kill(pid, 0) |
1940 | + |
1941 | + os.remove(invalid_conf) |
1942 | + |
1943 | + # Clean up |
1944 | + self.upstart.destroy() |
1945 | + |
1946 | |
1947 | class TestSystemInitChrootSession(TestSystemUpstart): |
1948 | CHROOT_ENVVAR = 'UPSTART_TEST_CHROOT_PATH' |
1949 | @@ -140,7 +147,8 @@ |
1950 | chroot_path = os.environ.get(self.CHROOT_ENVVAR, None) |
1951 | |
1952 | if not chroot_path: |
1953 | - raise unittest.SkipTest('{} variable not set'.format(self.CHROOT_ENVVAR)) |
1954 | + raise unittest.SkipTest('{} variable not set' |
1955 | + .format(self.CHROOT_ENVVAR)) |
1956 | |
1957 | # Ensure the chroot exists |
1958 | self.assertTrue(os.path.exists(chroot_path)) |
1959 | @@ -167,6 +175,7 @@ |
1960 | self.upstart.polling_connect(force=True) |
1961 | self.assertTrue(self.upstart.version()) |
1962 | |
1963 | + |
1964 | def main(): |
1965 | kwargs = {} |
1966 | format = \ |
To test the file bridge, run the unit tests:
$ cd scripts && python3 -munittest