Merge lp:~jamesodhunt/upstart/fix-system-reexec-test into lp:upstart
- fix-system-reexec-test
- Merge into trunk
Status: | Merged |
---|---|
Merged at revision: | 1550 |
Proposed branch: | lp:~jamesodhunt/upstart/fix-system-reexec-test |
Merge into: | lp:upstart |
Diff against target: |
443 lines (+207/-51) 4 files modified
ChangeLog (+21/-0) scripts/pyupstart.py (+114/-32) scripts/tests/test_pyupstart_session_init.py (+50/-1) scripts/tests/test_pyupstart_system_init.py (+22/-18) |
To merge this branch: | bzr merge lp:~jamesodhunt/upstart/fix-system-reexec-test |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Dimitri John Ledkov | Approve | ||
Review via email: mp+185238@code.launchpad.net |
Commit message
Description of the change
Various fixes and improvements to the system and session tests:
* scripts/
- dbus_encode(): Comments.
- Upstart:
- Upstart:
job configuration file on object destruction.
- Upstart:
existing job configuration file.
- Job::__init__(): New 'retain' and 'reuse_path' options.
- Job::get_
- Job::get_
- JobInstance:
* scripts/
- test_pid1_reexec():
- Retain the job configuration to allow the object to be recreated
after re-exec.
* scripts/
- test_session_
test_
Preview Diff
1 | === modified file 'ChangeLog' | |||
2 | --- ChangeLog 2013-09-05 16:19:06 +0000 | |||
3 | +++ ChangeLog 2013-09-12 10:58:02 +0000 | |||
4 | @@ -1,3 +1,24 @@ | |||
5 | 1 | 2013-09-12 James Hunt <james.hunt@ubuntu.com> | ||
6 | 2 | |||
7 | 3 | * scripts/pyupstart.py: | ||
8 | 4 | - dbus_encode(): Comments. | ||
9 | 5 | - Upstart::_idle_create_job_cb(): Pass retain option. | ||
10 | 6 | - Upstart::job_create(): Allow specification of retain option to keep | ||
11 | 7 | job configuration file on object destruction. | ||
12 | 8 | - Upstart::job_recreate(): New method to vivify a Job object using an | ||
13 | 9 | existing job configuration file. | ||
14 | 10 | - Job::__init__(): New 'retain' and 'reuse_path' options. | ||
15 | 11 | - Job::get_instance(): New method to obtain a Jobs JobInstance. | ||
16 | 12 | - Job::get_dbus_instance(): Renamed from get_instance(). | ||
17 | 13 | - JobInstance::destroy(): NOP for 'retain'ed jobs. | ||
18 | 14 | * scripts/tests/test_pyupstart_system_init.py: | ||
19 | 15 | - test_pid1_reexec(): | ||
20 | 16 | - Retain the job configuration to allow the object to be recreated | ||
21 | 17 | after re-exec. | ||
22 | 18 | * scripts/tests/test_pyupstart_session_init.py: | ||
23 | 19 | - test_session_init_reexec: Add job creation for parity with | ||
24 | 20 | test_pid1_reexec(). | ||
25 | 21 | |||
26 | 1 | 2013-09-05 James Hunt <james.hunt@ubuntu.com> | 22 | 2013-09-05 James Hunt <james.hunt@ubuntu.com> |
27 | 2 | 23 | ||
28 | 3 | * util/tests/test_initctl.c: test_quiesce(): | 24 | * util/tests/test_initctl.c: test_quiesce(): |
29 | 4 | 25 | ||
30 | === modified file 'scripts/pyupstart.py' | |||
31 | --- scripts/pyupstart.py 2013-07-30 08:25:59 +0000 | |||
32 | +++ scripts/pyupstart.py 2013-09-12 10:58:02 +0000 | |||
33 | @@ -91,8 +91,13 @@ | |||
34 | 91 | Note that in the special case of the specified string being None | 91 | Note that in the special case of the specified string being None |
35 | 92 | or the nul string, it is encoded as '_'. | 92 | or the nul string, it is encoded as '_'. |
36 | 93 | 93 | ||
39 | 94 | Example: 'hello-world' would be encoded as 'hello_2dworld' since | 94 | Examples: |
40 | 95 | '-' is 2d in hex. | 95 | |
41 | 96 | 'hello-world' would be encoded as 'hello_2dworld' since | ||
42 | 97 | '-' is 2d in hex (resulting in '_2d'). | ||
43 | 98 | |||
44 | 99 | Similarly, '_2f' would be the encoding for '/' since that character | ||
45 | 100 | has hex value 2f. | ||
46 | 96 | 101 | ||
47 | 97 | """ | 102 | """ |
48 | 98 | if not str: | 103 | if not str: |
49 | @@ -244,7 +249,8 @@ | |||
50 | 244 | the main loop starts. | 249 | the main loop starts. |
51 | 245 | 250 | ||
52 | 246 | """ | 251 | """ |
54 | 247 | self.new_job = Job(self, self.test_dir, self.test_dir_name, name, body=body) | 252 | self.new_job = Job(self, self.test_dir, self.test_dir_name, |
55 | 253 | name, body=body, retain=self.retain) | ||
56 | 248 | 254 | ||
57 | 249 | # deregister | 255 | # deregister |
58 | 250 | return False | 256 | return False |
59 | @@ -377,13 +383,15 @@ | |||
60 | 377 | """ | 383 | """ |
61 | 378 | raise NotImplementedError('method must be implemented by subclass') | 384 | raise NotImplementedError('method must be implemented by subclass') |
62 | 379 | 385 | ||
64 | 380 | def job_create(self, name, body): | 386 | def job_create(self, name, body, retain=False): |
65 | 381 | """ | 387 | """ |
66 | 382 | Create a Job Configuration File. | 388 | Create a Job Configuration File. |
67 | 383 | 389 | ||
68 | 384 | @name: Name to give the job. | 390 | @name: Name to give the job. |
69 | 385 | @body: String representation of configuration file, or list of | 391 | @body: String representation of configuration file, or list of |
70 | 386 | strings. | 392 | strings. |
71 | 393 | @retain: if True, don't remove the Job Configuration File when | ||
72 | 394 | object is cleaned up. | ||
73 | 387 | 395 | ||
74 | 388 | Strategy: | 396 | Strategy: |
75 | 389 | 397 | ||
76 | @@ -421,6 +429,8 @@ | |||
77 | 421 | self.job_seen = False | 429 | self.job_seen = False |
78 | 422 | self.new_job = None | 430 | self.new_job = None |
79 | 423 | 431 | ||
80 | 432 | self.retain = retain | ||
81 | 433 | |||
82 | 424 | # construct the D-Bus path for the new job | 434 | # construct the D-Bus path for the new job |
83 | 425 | job_path = '{}/{}'.format(self.test_dir_name, name) | 435 | job_path = '{}/{}'.format(self.test_dir_name, name) |
84 | 426 | 436 | ||
85 | @@ -452,6 +462,27 @@ | |||
86 | 452 | 462 | ||
87 | 453 | return self.new_job | 463 | return self.new_job |
88 | 454 | 464 | ||
89 | 465 | def job_recreate(self, name, conf_path): | ||
90 | 466 | """ | ||
91 | 467 | Create a job object from an existing Job Configuration File. | ||
92 | 468 | |||
93 | 469 | @name: Name prefix of existing job configuration file. | ||
94 | 470 | @conf_path: Full path to *existing* Job Configuration File. | ||
95 | 471 | """ | ||
96 | 472 | |||
97 | 473 | assert (name) | ||
98 | 474 | assert (conf_path) | ||
99 | 475 | |||
100 | 476 | job_path = '{}/{}'.format(self.test_dir_name, name) | ||
101 | 477 | self.job_object_path = '{}/{}/{}'.format( | ||
102 | 478 | OBJECT_PATH, 'jobs', dbus_encode(job_path) | ||
103 | 479 | ) | ||
104 | 480 | |||
105 | 481 | self.new_job = Job(self, self.test_dir, self.test_dir_name, | ||
106 | 482 | name, body=None, reuse_path=conf_path) | ||
107 | 483 | self.jobs.append(self.new_job) | ||
108 | 484 | return self.new_job | ||
109 | 485 | |||
110 | 455 | 486 | ||
111 | 456 | class Job: | 487 | class Job: |
112 | 457 | """ | 488 | """ |
113 | @@ -470,7 +501,8 @@ | |||
114 | 470 | 501 | ||
115 | 471 | """ | 502 | """ |
116 | 472 | 503 | ||
118 | 473 | def __init__(self, upstart, dir_name, subdir_name, job_name, body=None): | 504 | def __init__(self, upstart, dir_name, subdir_name, job_name, |
119 | 505 | body=None, reuse_path=None, retain=False): | ||
120 | 474 | """ | 506 | """ |
121 | 475 | @upstart: Upstart() parent object. | 507 | @upstart: Upstart() parent object. |
122 | 476 | @dir_name: Full path to job configuration files directory. | 508 | @dir_name: Full path to job configuration files directory. |
123 | @@ -479,6 +511,10 @@ | |||
124 | 479 | @job_name: Name of job. | 511 | @job_name: Name of job. |
125 | 480 | @body: Contents of job configuration file (either a string, or a | 512 | @body: Contents of job configuration file (either a string, or a |
126 | 481 | list of strings). | 513 | list of strings). |
127 | 514 | @reuse_path: If set and @body is None, (re)create the job object | ||
128 | 515 | using the existing specified job configuration file path. | ||
129 | 516 | @retain: If True, don't delete the Job Configuration File on | ||
130 | 517 | object destruction. | ||
131 | 482 | """ | 518 | """ |
132 | 483 | 519 | ||
133 | 484 | self.logger = logging.getLogger(self.__class__.__name__) | 520 | self.logger = logging.getLogger(self.__class__.__name__) |
134 | @@ -491,6 +527,8 @@ | |||
135 | 491 | self.name = job_name | 527 | self.name = job_name |
136 | 492 | self.job_dir = dir_name | 528 | self.job_dir = dir_name |
137 | 493 | self.body = body | 529 | self.body = body |
138 | 530 | self.reuse_path = reuse_path | ||
139 | 531 | self.retain = retain | ||
140 | 494 | 532 | ||
141 | 495 | self.instance_name = None | 533 | self.instance_name = None |
142 | 496 | 534 | ||
143 | @@ -499,19 +537,30 @@ | |||
144 | 499 | 537 | ||
145 | 500 | self.properties = None | 538 | self.properties = None |
146 | 501 | 539 | ||
149 | 502 | if not self.body: | 540 | # need some way to create the job |
150 | 503 | raise UpstartException('No body specified') | 541 | if not self.body and not self.reuse_path: |
151 | 542 | raise UpstartException('No body or reusable path specified') | ||
152 | 504 | 543 | ||
154 | 505 | self.conffile = os.path.join(self.job_dir, self.name + '.conf') | 544 | if self.reuse_path: |
155 | 545 | self.conffile = self.reuse_path | ||
156 | 546 | else: | ||
157 | 547 | self.conffile = os.path.join(self.job_dir, self.name + '.conf') | ||
158 | 506 | 548 | ||
159 | 507 | if self.body and isinstance(self.body, str): | 549 | if self.body and isinstance(self.body, str): |
160 | 508 | # Assume body cannot be a bytes object. | 550 | # Assume body cannot be a bytes object. |
161 | 509 | body = body.splitlines() | 551 | body = body.splitlines() |
162 | 510 | 552 | ||
167 | 511 | with open(self.conffile, 'w', encoding='utf-8') as fh: | 553 | if not self.body and self.reuse_path: |
168 | 512 | for line in body: | 554 | # Just check conf file exists |
169 | 513 | print(line.strip(), file=fh) | 555 | if not os.path.exists(self.conffile): |
170 | 514 | print(file=fh) | 556 | raise UpstartException( |
171 | 557 | 'File {} does not exist for reuse'.format(self.conffile)) | ||
172 | 558 | else: | ||
173 | 559 | # Create conf file | ||
174 | 560 | with open(self.conffile, 'w', encoding='utf-8') as fh: | ||
175 | 561 | for line in body: | ||
176 | 562 | print(line.strip(), file=fh) | ||
177 | 563 | print(file=fh) | ||
178 | 515 | 564 | ||
179 | 516 | self.valid = True | 565 | self.valid = True |
180 | 517 | 566 | ||
181 | @@ -533,7 +582,8 @@ | |||
182 | 533 | for instance in self.instances: | 582 | for instance in self.instances: |
183 | 534 | instance.destroy() | 583 | instance.destroy() |
184 | 535 | 584 | ||
186 | 536 | os.remove(self.conffile) | 585 | if not self.retain: |
187 | 586 | os.remove(self.conffile) | ||
188 | 537 | except FileNotFoundError: | 587 | except FileNotFoundError: |
189 | 538 | pass | 588 | pass |
190 | 539 | 589 | ||
191 | @@ -551,19 +601,44 @@ | |||
192 | 551 | if env is None: | 601 | if env is None: |
193 | 552 | env = [] | 602 | env = [] |
194 | 553 | instance_path = self.interface.Start(dbus.Array(env, 's'), wait) | 603 | instance_path = self.interface.Start(dbus.Array(env, 's'), wait) |
208 | 554 | instance_name = instance_path.replace("%s/" % self.object_path, '') | 604 | |
209 | 555 | 605 | instance_name = instance_path.replace("%s/" % self.object_path, '') | |
210 | 556 | # store the D-Bus encoded instance name ('_' for single-instance jobs) | 606 | |
211 | 557 | if instance_name not in self.instance_names: | 607 | # store the D-Bus encoded instance name ('_' for single-instance jobs) |
212 | 558 | self.instance_names.append(instance_name) | 608 | if instance_name not in self.instance_names: |
213 | 559 | 609 | self.instance_names.append(instance_name) | |
214 | 560 | instance = JobInstance(self, instance_name, instance_path) | 610 | |
215 | 561 | self.instances.append(instance) | 611 | instance = JobInstance(self, instance_name, instance_path) |
216 | 562 | return instance | 612 | self.instances.append(instance) |
217 | 563 | 613 | return instance | |
218 | 564 | def _get_instance(self, name): | 614 | |
219 | 565 | """ | 615 | def get_instance(self): |
220 | 566 | Retrieve job instance and its properties. | 616 | """ |
221 | 617 | Returns: JobInstance of calling Job. | ||
222 | 618 | """ | ||
223 | 619 | |||
224 | 620 | # construct the D-Bus path for the new job | ||
225 | 621 | job_path = '{}/{}'.format(self.upstart.test_dir_name, self.name) | ||
226 | 622 | |||
227 | 623 | instance_path = '{}/{}/{}/{}'.format( | ||
228 | 624 | OBJECT_PATH, 'jobs', dbus_encode(job_path), | ||
229 | 625 | |||
230 | 626 | # XXX: LIMITATION - only support default instance. | ||
231 | 627 | '_' | ||
232 | 628 | ) | ||
233 | 629 | instance_name = instance_path.replace("%s/" % self.object_path, '') | ||
234 | 630 | |||
235 | 631 | # store the D-Bus encoded instance name ('_' for single-instance jobs) | ||
236 | 632 | if instance_name not in self.instance_names: | ||
237 | 633 | self.instance_names.append(instance_name) | ||
238 | 634 | |||
239 | 635 | instance = JobInstance(self, instance_name, instance_path) | ||
240 | 636 | self.instances.append(instance) | ||
241 | 637 | return instance | ||
242 | 638 | |||
243 | 639 | def _get_dbus_instance(self, name): | ||
244 | 640 | """ | ||
245 | 641 | Retrieve D-Bus job instance and its properties. | ||
246 | 567 | 642 | ||
247 | 568 | @name: D-Bus encoded instance name. | 643 | @name: D-Bus encoded instance name. |
248 | 569 | 644 | ||
249 | @@ -586,7 +661,7 @@ | |||
250 | 586 | """ | 661 | """ |
251 | 587 | 662 | ||
252 | 588 | for name in self.instance_names: | 663 | for name in self.instance_names: |
254 | 589 | instance = self._get_instance(name) | 664 | instance = self._get_dbus_instance(name) |
255 | 590 | try: | 665 | try: |
256 | 591 | instance.Stop(wait) | 666 | instance.Stop(wait) |
257 | 592 | except dbus.exceptions.DBusException: | 667 | except dbus.exceptions.DBusException: |
258 | @@ -600,7 +675,7 @@ | |||
259 | 600 | @wait: if False, stop job instances asynchronously. | 675 | @wait: if False, stop job instances asynchronously. |
260 | 601 | """ | 676 | """ |
261 | 602 | for name in self.instance_names: | 677 | for name in self.instance_names: |
263 | 603 | instance = self._get_instance(name) | 678 | instance = self._get_dbus_instance(name) |
264 | 604 | instance.Restart(wait) | 679 | instance.Restart(wait) |
265 | 605 | 680 | ||
266 | 606 | def instance_object_paths(self): | 681 | def instance_object_paths(self): |
267 | @@ -629,9 +704,13 @@ | |||
268 | 629 | 704 | ||
269 | 630 | assert(name in self.instance_names) | 705 | assert(name in self.instance_names) |
270 | 631 | 706 | ||
272 | 632 | instance = self._get_instance(name) | 707 | instance = self._get_dbus_instance(name) |
273 | 708 | assert (instance) | ||
274 | 633 | 709 | ||
275 | 634 | properties = dbus.Interface(instance, FREEDESKTOP_PROPERTIES) | 710 | properties = dbus.Interface(instance, FREEDESKTOP_PROPERTIES) |
276 | 711 | assert (properties) | ||
277 | 712 | |||
278 | 713 | # don't assert as there may not be any processes | ||
279 | 635 | procs = properties.Get(INSTANCE_INTERFACE_NAME, 'processes') | 714 | procs = properties.Get(INSTANCE_INTERFACE_NAME, 'processes') |
280 | 636 | 715 | ||
281 | 637 | pid_map = {} | 716 | pid_map = {} |
282 | @@ -845,10 +924,13 @@ | |||
283 | 845 | def destroy(self): | 924 | def destroy(self): |
284 | 846 | """ | 925 | """ |
285 | 847 | Stop the instance and cleanup. | 926 | Stop the instance and cleanup. |
286 | 927 | |||
287 | 928 | Note: If the instance specified retain when created, this will | ||
288 | 929 | be a NOP. | ||
289 | 848 | """ | 930 | """ |
293 | 849 | self.stop() | 931 | if not self.job.retain: |
294 | 850 | self.logfile.destroy() | 932 | self.stop() |
295 | 851 | 933 | self.logfile.destroy() | |
296 | 852 | 934 | ||
297 | 853 | class SystemInit(Upstart): | 935 | class SystemInit(Upstart): |
298 | 854 | 936 | ||
299 | 855 | 937 | ||
300 | === modified file 'scripts/tests/test_pyupstart_session_init.py' | |||
301 | --- scripts/tests/test_pyupstart_session_init.py 2013-08-06 16:54:28 +0000 | |||
302 | +++ scripts/tests/test_pyupstart_session_init.py 2013-09-12 10:58:02 +0000 | |||
303 | @@ -229,6 +229,23 @@ | |||
304 | 229 | # Ensure no stateful-reexec already performed. | 229 | # Ensure no stateful-reexec already performed. |
305 | 230 | self.assertFalse(re.search('state-fd', output)) | 230 | self.assertFalse(re.search('state-fd', output)) |
306 | 231 | 231 | ||
307 | 232 | version = self.upstart.version() | ||
308 | 233 | self.assertTrue(version) | ||
309 | 234 | |||
310 | 235 | # create a job and start it, marking it such that the .conf file | ||
311 | 236 | # will be retained when object becomes unusable (after re-exec). | ||
312 | 237 | job = self.upstart.job_create('sleeper', 'exec sleep 123', retain=True) | ||
313 | 238 | self.assertTrue(job) | ||
314 | 239 | |||
315 | 240 | # Used when recreating the job | ||
316 | 241 | conf_path = job.conffile | ||
317 | 242 | |||
318 | 243 | inst = job.start() | ||
319 | 244 | self.assertTrue(inst) | ||
320 | 245 | pids = job.pids() | ||
321 | 246 | self.assertEqual(len(pids), 1) | ||
322 | 247 | pid = pids['main'] | ||
323 | 248 | |||
324 | 232 | # Trigger re-exec and catch the D-Bus exception resulting | 249 | # Trigger re-exec and catch the D-Bus exception resulting |
325 | 233 | # from disconnection from Session Init when it severs client | 250 | # from disconnection from Session Init when it severs client |
326 | 234 | # connections. | 251 | # connections. |
327 | @@ -248,8 +265,40 @@ | |||
328 | 248 | # instantaneous, try a few times. | 265 | # instantaneous, try a few times. |
329 | 249 | self.upstart.polling_connect(force=True) | 266 | self.upstart.polling_connect(force=True) |
330 | 250 | 267 | ||
331 | 268 | # Since the parent job was created with 'retain', this is actually | ||
332 | 269 | # a NOP but is added to denote that the old instance is dead. | ||
333 | 270 | inst.destroy() | ||
334 | 271 | |||
335 | 251 | # check that we can still operate on the re-exec'd Upstart | 272 | # check that we can still operate on the re-exec'd Upstart |
337 | 252 | self.assertTrue(self.upstart.version()) | 273 | version_postexec = self.upstart.version() |
338 | 274 | self.assertTrue(version_postexec) | ||
339 | 275 | self.assertEqual(version, version_postexec) | ||
340 | 276 | |||
341 | 277 | # Ensure the job is still running with the same PID | ||
342 | 278 | os.kill(pid, 0) | ||
343 | 279 | |||
344 | 280 | # XXX: The re-exec will have severed the D-Bus connection to | ||
345 | 281 | # Upstart. Hence, revivify the job with some magic. | ||
346 | 282 | job = self.upstart.job_recreate('sleeper', conf_path) | ||
347 | 283 | self.assertTrue(job) | ||
348 | 284 | |||
349 | 285 | # Recreate the instance | ||
350 | 286 | inst = job.get_instance() | ||
351 | 287 | self.assertTrue(inst) | ||
352 | 288 | |||
353 | 289 | self.assertTrue(job.running('_')) | ||
354 | 290 | pids = job.pids() | ||
355 | 291 | self.assertEqual(len(pids), 1) | ||
356 | 292 | self.assertTrue(pids['main']) | ||
357 | 293 | |||
358 | 294 | # The pid should not have changed after a restart | ||
359 | 295 | self.assertEqual(pid, pids['main']) | ||
360 | 296 | |||
361 | 297 | job.stop() | ||
362 | 298 | |||
363 | 299 | # Ensure the pid has gone | ||
364 | 300 | with self.assertRaises(ProcessLookupError): | ||
365 | 301 | os.kill(pid, 0) | ||
366 | 253 | 302 | ||
367 | 254 | self.stop_session_init() | 303 | self.stop_session_init() |
368 | 255 | 304 | ||
369 | 256 | 305 | ||
370 | === modified file 'scripts/tests/test_pyupstart_system_init.py' | |||
371 | --- scripts/tests/test_pyupstart_system_init.py 2013-08-06 16:54:28 +0000 | |||
372 | +++ scripts/tests/test_pyupstart_system_init.py 2013-09-12 10:58:02 +0000 | |||
373 | @@ -63,9 +63,12 @@ | |||
374 | 63 | 63 | ||
375 | 64 | # create a job and start it, marking it such that the .conf file | 64 | # create a job and start it, marking it such that the .conf file |
376 | 65 | # will be retained when object becomes unusable (after re-exec). | 65 | # will be retained when object becomes unusable (after re-exec). |
378 | 66 | job = self.upstart.job_create('sleeper', 'exec sleep 123') | 66 | job = self.upstart.job_create('sleeper', 'exec sleep 123', retain=True) |
379 | 67 | self.assertTrue(job) | 67 | self.assertTrue(job) |
380 | 68 | 68 | ||
381 | 69 | # Used when recreating the job | ||
382 | 70 | conf_path = job.conffile | ||
383 | 71 | |||
384 | 69 | inst = job.start() | 72 | inst = job.start() |
385 | 70 | self.assertTrue(inst) | 73 | self.assertTrue(inst) |
386 | 71 | pids = job.pids() | 74 | pids = job.pids() |
387 | @@ -80,6 +83,10 @@ | |||
388 | 80 | # try a few times. | 83 | # try a few times. |
389 | 81 | self.upstart.polling_connect(force=True) | 84 | self.upstart.polling_connect(force=True) |
390 | 82 | 85 | ||
391 | 86 | # Since the parent job was created with 'retain', this is actually | ||
392 | 87 | # a NOP but is added to denote that the old instance is dead. | ||
393 | 88 | inst.destroy() | ||
394 | 89 | |||
395 | 83 | # check that we can still operate on the re-exec'd Upstart | 90 | # check that we can still operate on the re-exec'd Upstart |
396 | 84 | version_postexec = self.upstart.version() | 91 | version_postexec = self.upstart.version() |
397 | 85 | self.assertTrue(version_postexec) | 92 | self.assertTrue(version_postexec) |
398 | @@ -88,32 +95,29 @@ | |||
399 | 88 | # Ensure the job is still running with the same PID | 95 | # Ensure the job is still running with the same PID |
400 | 89 | os.kill(pid, 0) | 96 | os.kill(pid, 0) |
401 | 90 | 97 | ||
402 | 98 | # XXX: The re-exec will have severed the D-Bus connection to | ||
403 | 99 | # Upstart. Hence, revivify the job with some magic. | ||
404 | 100 | job = self.upstart.job_recreate('sleeper', conf_path) | ||
405 | 101 | self.assertTrue(job) | ||
406 | 102 | |||
407 | 103 | # Recreate the instance | ||
408 | 104 | inst = job.get_instance() | ||
409 | 105 | self.assertTrue(inst) | ||
410 | 106 | |||
411 | 91 | self.assertTrue(job.running('_')) | 107 | self.assertTrue(job.running('_')) |
412 | 92 | |||
413 | 93 | pids = job.pids() | 108 | pids = job.pids() |
414 | 94 | self.assertEqual(len(pids), 1) | 109 | self.assertEqual(len(pids), 1) |
415 | 95 | self.assertTrue(pids['main']) | 110 | self.assertTrue(pids['main']) |
416 | 96 | 111 | ||
418 | 97 | # Ensure pid remains the same | 112 | # The pid should not have changed after a restart |
419 | 98 | self.assertEqual(pid, pids['main']) | 113 | self.assertEqual(pid, pids['main']) |
420 | 99 | 114 | ||
421 | 100 | # Exceptions will be caught by the unittest framework | ||
422 | 101 | inst.stop() | ||
423 | 102 | |||
424 | 103 | job.start() | ||
425 | 104 | |||
426 | 105 | pids = job.pids() | ||
427 | 106 | self.assertEqual(len(pids), 1) | ||
428 | 107 | self.assertTrue(pids['main']) | ||
429 | 108 | |||
430 | 109 | os.kill(pids['main'], 0) | ||
431 | 110 | self.assertTrue(job.running('_')) | ||
432 | 111 | |||
433 | 112 | # The pid should have changed after a restart | ||
434 | 113 | self.assertNotEqual(pid, pids['main']) | ||
435 | 114 | |||
436 | 115 | job.stop() | 115 | job.stop() |
437 | 116 | 116 | ||
438 | 117 | # Ensure the pid has gone | ||
439 | 118 | with self.assertRaises(ProcessLookupError): | ||
440 | 119 | os.kill(pid, 0) | ||
441 | 120 | |||
442 | 117 | # Clean up | 121 | # Clean up |
443 | 118 | self.upstart.destroy() | 122 | self.upstart.destroy() |
444 | 119 | 123 |
What "::" is in C++ is "." in python =)
pyflakes 3 .
pep8 .
give plenty of warnings, but that's a pre-existing (health) condition.