Merge lp:~jamesodhunt/upstart/bug-1360208 into lp:upstart

Proposed by James Hunt
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
Reviewer Review Type Date Requested Status
Upstart Reviewers Pending
Review via email: mp+234869@code.launchpad.net

Description of the change

Fix for bug 1360208.

* 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.

To post a comment you must log in.
Revision history for this message
James Hunt (jamesodhunt) wrote :

To test the file bridge, run the unit tests:

    $ cd scripts && python3 -munittest

lp:~jamesodhunt/upstart/bug-1360208 updated
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

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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 = \

Subscribers

People subscribed via source and target branches