Migration and failover: add iallocator and target_node slots
[ganeti-local] / test / ganeti.utils.process_unittest.py
1 #!/usr/bin/python
2 #
3
4 # Copyright (C) 2011 Google Inc.
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 # General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 # 02110-1301, USA.
20
21
22 """Script for testing ganeti.utils.process"""
23
24 import unittest
25 import tempfile
26 import shutil
27 import os
28 import stat
29 import time
30 import signal
31
32 from ganeti import constants
33 from ganeti import utils
34 from ganeti import errors
35
36 import testutils
37
38
39 class TestIsProcessAlive(unittest.TestCase):
40   """Testing case for IsProcessAlive"""
41
42   def testExists(self):
43     mypid = os.getpid()
44     self.assert_(utils.IsProcessAlive(mypid), "can't find myself running")
45
46   def testNotExisting(self):
47     pid_non_existing = os.fork()
48     if pid_non_existing == 0:
49       os._exit(0)
50     elif pid_non_existing < 0:
51       raise SystemError("can't fork")
52     os.waitpid(pid_non_existing, 0)
53     self.assertFalse(utils.IsProcessAlive(pid_non_existing),
54                      "nonexisting process detected")
55
56
57 class TestGetProcStatusPath(unittest.TestCase):
58   def test(self):
59     self.assert_("/1234/" in utils.process._GetProcStatusPath(1234))
60     self.assertNotEqual(utils.process._GetProcStatusPath(1),
61                         utils.process._GetProcStatusPath(2))
62
63
64 class TestIsProcessHandlingSignal(unittest.TestCase):
65   def setUp(self):
66     self.tmpdir = tempfile.mkdtemp()
67
68   def tearDown(self):
69     shutil.rmtree(self.tmpdir)
70
71   def testParseSigsetT(self):
72     parse_sigset_t_fn = utils.process._ParseSigsetT
73     self.assertEqual(len(parse_sigset_t_fn("0")), 0)
74     self.assertEqual(parse_sigset_t_fn("1"), set([1]))
75     self.assertEqual(parse_sigset_t_fn("1000a"), set([2, 4, 17]))
76     self.assertEqual(parse_sigset_t_fn("810002"), set([2, 17, 24, ]))
77     self.assertEqual(parse_sigset_t_fn("0000000180000202"),
78                      set([2, 10, 32, 33]))
79     self.assertEqual(parse_sigset_t_fn("0000000180000002"),
80                      set([2, 32, 33]))
81     self.assertEqual(parse_sigset_t_fn("0000000188000002"),
82                      set([2, 28, 32, 33]))
83     self.assertEqual(parse_sigset_t_fn("000000004b813efb"),
84                      set([1, 2, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 17,
85                           24, 25, 26, 28, 31]))
86     self.assertEqual(parse_sigset_t_fn("ffffff"), set(range(1, 25)))
87
88   def testGetProcStatusField(self):
89     for field in ["SigCgt", "Name", "FDSize"]:
90       for value in ["", "0", "cat", "  1234 KB"]:
91         pstatus = "\n".join([
92           "VmPeak: 999 kB",
93           "%s: %s" % (field, value),
94           "TracerPid: 0",
95           ])
96         result = utils.process._GetProcStatusField(pstatus, field)
97         self.assertEqual(result, value.strip())
98
99   def test(self):
100     sp = utils.PathJoin(self.tmpdir, "status")
101
102     utils.WriteFile(sp, data="\n".join([
103       "Name:   bash",
104       "State:  S (sleeping)",
105       "SleepAVG:       98%",
106       "Pid:    22250",
107       "PPid:   10858",
108       "TracerPid:      0",
109       "SigBlk: 0000000000010000",
110       "SigIgn: 0000000000384004",
111       "SigCgt: 000000004b813efb",
112       "CapEff: 0000000000000000",
113       ]))
114
115     self.assert_(utils.IsProcessHandlingSignal(1234, 10, status_path=sp))
116
117   def testNoSigCgt(self):
118     sp = utils.PathJoin(self.tmpdir, "status")
119
120     utils.WriteFile(sp, data="\n".join([
121       "Name:   bash",
122       ]))
123
124     self.assertRaises(RuntimeError, utils.IsProcessHandlingSignal,
125                       1234, 10, status_path=sp)
126
127   def testNoSuchFile(self):
128     sp = utils.PathJoin(self.tmpdir, "notexist")
129
130     self.assertFalse(utils.IsProcessHandlingSignal(1234, 10, status_path=sp))
131
132   @staticmethod
133   def _TestRealProcess():
134     signal.signal(signal.SIGUSR1, signal.SIG_DFL)
135     if utils.IsProcessHandlingSignal(os.getpid(), signal.SIGUSR1):
136       raise Exception("SIGUSR1 is handled when it should not be")
137
138     signal.signal(signal.SIGUSR1, lambda signum, frame: None)
139     if not utils.IsProcessHandlingSignal(os.getpid(), signal.SIGUSR1):
140       raise Exception("SIGUSR1 is not handled when it should be")
141
142     signal.signal(signal.SIGUSR1, signal.SIG_IGN)
143     if utils.IsProcessHandlingSignal(os.getpid(), signal.SIGUSR1):
144       raise Exception("SIGUSR1 is not handled when it should be")
145
146     signal.signal(signal.SIGUSR1, signal.SIG_DFL)
147     if utils.IsProcessHandlingSignal(os.getpid(), signal.SIGUSR1):
148       raise Exception("SIGUSR1 is handled when it should not be")
149
150     return True
151
152   def testRealProcess(self):
153     self.assert_(utils.RunInSeparateProcess(self._TestRealProcess))
154
155
156 class TestRunCmd(testutils.GanetiTestCase):
157   """Testing case for the RunCmd function"""
158
159   def setUp(self):
160     testutils.GanetiTestCase.setUp(self)
161     self.magic = time.ctime() + " ganeti test"
162     self.fname = self._CreateTempFile()
163     self.fifo_tmpdir = tempfile.mkdtemp()
164     self.fifo_file = os.path.join(self.fifo_tmpdir, "ganeti_test_fifo")
165     os.mkfifo(self.fifo_file)
166
167   def tearDown(self):
168     shutil.rmtree(self.fifo_tmpdir)
169     testutils.GanetiTestCase.tearDown(self)
170
171   def testOk(self):
172     """Test successful exit code"""
173     result = utils.RunCmd("/bin/sh -c 'exit 0'")
174     self.assertEqual(result.exit_code, 0)
175     self.assertEqual(result.output, "")
176
177   def testFail(self):
178     """Test fail exit code"""
179     result = utils.RunCmd("/bin/sh -c 'exit 1'")
180     self.assertEqual(result.exit_code, 1)
181     self.assertEqual(result.output, "")
182
183   def testStdout(self):
184     """Test standard output"""
185     cmd = 'echo -n "%s"' % self.magic
186     result = utils.RunCmd("/bin/sh -c '%s'" % cmd)
187     self.assertEqual(result.stdout, self.magic)
188     result = utils.RunCmd("/bin/sh -c '%s'" % cmd, output=self.fname)
189     self.assertEqual(result.output, "")
190     self.assertFileContent(self.fname, self.magic)
191
192   def testStderr(self):
193     """Test standard error"""
194     cmd = 'echo -n "%s"' % self.magic
195     result = utils.RunCmd("/bin/sh -c '%s' 1>&2" % cmd)
196     self.assertEqual(result.stderr, self.magic)
197     result = utils.RunCmd("/bin/sh -c '%s' 1>&2" % cmd, output=self.fname)
198     self.assertEqual(result.output, "")
199     self.assertFileContent(self.fname, self.magic)
200
201   def testCombined(self):
202     """Test combined output"""
203     cmd = 'echo -n "A%s"; echo -n "B%s" 1>&2' % (self.magic, self.magic)
204     expected = "A" + self.magic + "B" + self.magic
205     result = utils.RunCmd("/bin/sh -c '%s'" % cmd)
206     self.assertEqual(result.output, expected)
207     result = utils.RunCmd("/bin/sh -c '%s'" % cmd, output=self.fname)
208     self.assertEqual(result.output, "")
209     self.assertFileContent(self.fname, expected)
210
211   def testSignal(self):
212     """Test signal"""
213     result = utils.RunCmd(["python", "-c",
214                            "import os; os.kill(os.getpid(), 15)"])
215     self.assertEqual(result.signal, 15)
216     self.assertEqual(result.output, "")
217
218   def testTimeoutClean(self):
219     cmd = "trap 'exit 0' TERM; read < %s" % self.fifo_file
220     result = utils.RunCmd(["/bin/sh", "-c", cmd], timeout=0.2)
221     self.assertEqual(result.exit_code, 0)
222
223   def testTimeoutKill(self):
224     cmd = ["/bin/sh", "-c", "trap '' TERM; read < %s" % self.fifo_file]
225     timeout = 0.2
226     (out, err, status, ta) = \
227       utils.process._RunCmdPipe(cmd, {}, False, "/", False,
228                                 timeout, None, _linger_timeout=0.2)
229     self.assert_(status < 0)
230     self.assertEqual(-status, signal.SIGKILL)
231
232   def testTimeoutOutputAfterTerm(self):
233     cmd = "trap 'echo sigtermed; exit 1' TERM; read < %s" % self.fifo_file
234     result = utils.RunCmd(["/bin/sh", "-c", cmd], timeout=0.2)
235     self.assert_(result.failed)
236     self.assertEqual(result.stdout, "sigtermed\n")
237
238   def testListRun(self):
239     """Test list runs"""
240     result = utils.RunCmd(["true"])
241     self.assertEqual(result.signal, None)
242     self.assertEqual(result.exit_code, 0)
243     result = utils.RunCmd(["/bin/sh", "-c", "exit 1"])
244     self.assertEqual(result.signal, None)
245     self.assertEqual(result.exit_code, 1)
246     result = utils.RunCmd(["echo", "-n", self.magic])
247     self.assertEqual(result.signal, None)
248     self.assertEqual(result.exit_code, 0)
249     self.assertEqual(result.stdout, self.magic)
250
251   def testFileEmptyOutput(self):
252     """Test file output"""
253     result = utils.RunCmd(["true"], output=self.fname)
254     self.assertEqual(result.signal, None)
255     self.assertEqual(result.exit_code, 0)
256     self.assertFileContent(self.fname, "")
257
258   def testLang(self):
259     """Test locale environment"""
260     old_env = os.environ.copy()
261     try:
262       os.environ["LANG"] = "en_US.UTF-8"
263       os.environ["LC_ALL"] = "en_US.UTF-8"
264       result = utils.RunCmd(["locale"])
265       for line in result.output.splitlines():
266         key, value = line.split("=", 1)
267         # Ignore these variables, they're overridden by LC_ALL
268         if key == "LANG" or key == "LANGUAGE":
269           continue
270         self.failIf(value and value != "C" and value != '"C"',
271             "Variable %s is set to the invalid value '%s'" % (key, value))
272     finally:
273       os.environ = old_env
274
275   def testDefaultCwd(self):
276     """Test default working directory"""
277     self.failUnlessEqual(utils.RunCmd(["pwd"]).stdout.strip(), "/")
278
279   def testCwd(self):
280     """Test default working directory"""
281     self.failUnlessEqual(utils.RunCmd(["pwd"], cwd="/").stdout.strip(), "/")
282     self.failUnlessEqual(utils.RunCmd(["pwd"], cwd="/tmp").stdout.strip(),
283                          "/tmp")
284     cwd = os.getcwd()
285     self.failUnlessEqual(utils.RunCmd(["pwd"], cwd=cwd).stdout.strip(), cwd)
286
287   def testResetEnv(self):
288     """Test environment reset functionality"""
289     self.failUnlessEqual(utils.RunCmd(["env"], reset_env=True).stdout.strip(),
290                          "")
291     self.failUnlessEqual(utils.RunCmd(["env"], reset_env=True,
292                                       env={"FOO": "bar",}).stdout.strip(),
293                          "FOO=bar")
294
295   def testNoFork(self):
296     """Test that nofork raise an error"""
297     self.assertFalse(utils.process._no_fork)
298     utils.DisableFork()
299     try:
300       self.assertTrue(utils.process._no_fork)
301       self.assertRaises(errors.ProgrammerError, utils.RunCmd, ["true"])
302     finally:
303       utils.process._no_fork = False
304     self.assertFalse(utils.process._no_fork)
305
306   def testWrongParams(self):
307     """Test wrong parameters"""
308     self.assertRaises(errors.ProgrammerError, utils.RunCmd, ["true"],
309                       output="/dev/null", interactive=True)
310
311   def testNocloseFds(self):
312     """Test selective fd retention (noclose_fds)"""
313     temp = open(self.fname, "r+")
314     try:
315       temp.write("test")
316       temp.seek(0)
317       cmd = "read -u %d; echo $REPLY" % temp.fileno()
318       result = utils.RunCmd(["/bin/bash", "-c", cmd])
319       self.assertEqual(result.stdout.strip(), "")
320       temp.seek(0)
321       result = utils.RunCmd(["/bin/bash", "-c", cmd],
322                             noclose_fds=[temp.fileno()])
323       self.assertEqual(result.stdout.strip(), "test")
324     finally:
325       temp.close()
326
327
328 class TestRunParts(testutils.GanetiTestCase):
329   """Testing case for the RunParts function"""
330
331   def setUp(self):
332     self.rundir = tempfile.mkdtemp(prefix="ganeti-test", suffix=".tmp")
333
334   def tearDown(self):
335     shutil.rmtree(self.rundir)
336
337   def testEmpty(self):
338     """Test on an empty dir"""
339     self.failUnlessEqual(utils.RunParts(self.rundir, reset_env=True), [])
340
341   def testSkipWrongName(self):
342     """Test that wrong files are skipped"""
343     fname = os.path.join(self.rundir, "00test.dot")
344     utils.WriteFile(fname, data="")
345     os.chmod(fname, stat.S_IREAD | stat.S_IEXEC)
346     relname = os.path.basename(fname)
347     self.failUnlessEqual(utils.RunParts(self.rundir, reset_env=True),
348                          [(relname, constants.RUNPARTS_SKIP, None)])
349
350   def testSkipNonExec(self):
351     """Test that non executable files are skipped"""
352     fname = os.path.join(self.rundir, "00test")
353     utils.WriteFile(fname, data="")
354     relname = os.path.basename(fname)
355     self.failUnlessEqual(utils.RunParts(self.rundir, reset_env=True),
356                          [(relname, constants.RUNPARTS_SKIP, None)])
357
358   def testError(self):
359     """Test error on a broken executable"""
360     fname = os.path.join(self.rundir, "00test")
361     utils.WriteFile(fname, data="")
362     os.chmod(fname, stat.S_IREAD | stat.S_IEXEC)
363     (relname, status, error) = utils.RunParts(self.rundir, reset_env=True)[0]
364     self.failUnlessEqual(relname, os.path.basename(fname))
365     self.failUnlessEqual(status, constants.RUNPARTS_ERR)
366     self.failUnless(error)
367
368   def testSorted(self):
369     """Test executions are sorted"""
370     files = []
371     files.append(os.path.join(self.rundir, "64test"))
372     files.append(os.path.join(self.rundir, "00test"))
373     files.append(os.path.join(self.rundir, "42test"))
374
375     for fname in files:
376       utils.WriteFile(fname, data="")
377
378     results = utils.RunParts(self.rundir, reset_env=True)
379
380     for fname in sorted(files):
381       self.failUnlessEqual(os.path.basename(fname), results.pop(0)[0])
382
383   def testOk(self):
384     """Test correct execution"""
385     fname = os.path.join(self.rundir, "00test")
386     utils.WriteFile(fname, data="#!/bin/sh\n\necho -n ciao")
387     os.chmod(fname, stat.S_IREAD | stat.S_IEXEC)
388     (relname, status, runresult) = \
389       utils.RunParts(self.rundir, reset_env=True)[0]
390     self.failUnlessEqual(relname, os.path.basename(fname))
391     self.failUnlessEqual(status, constants.RUNPARTS_RUN)
392     self.failUnlessEqual(runresult.stdout, "ciao")
393
394   def testRunFail(self):
395     """Test correct execution, with run failure"""
396     fname = os.path.join(self.rundir, "00test")
397     utils.WriteFile(fname, data="#!/bin/sh\n\nexit 1")
398     os.chmod(fname, stat.S_IREAD | stat.S_IEXEC)
399     (relname, status, runresult) = \
400       utils.RunParts(self.rundir, reset_env=True)[0]
401     self.failUnlessEqual(relname, os.path.basename(fname))
402     self.failUnlessEqual(status, constants.RUNPARTS_RUN)
403     self.failUnlessEqual(runresult.exit_code, 1)
404     self.failUnless(runresult.failed)
405
406   def testRunMix(self):
407     files = []
408     files.append(os.path.join(self.rundir, "00test"))
409     files.append(os.path.join(self.rundir, "42test"))
410     files.append(os.path.join(self.rundir, "64test"))
411     files.append(os.path.join(self.rundir, "99test"))
412
413     files.sort()
414
415     # 1st has errors in execution
416     utils.WriteFile(files[0], data="#!/bin/sh\n\nexit 1")
417     os.chmod(files[0], stat.S_IREAD | stat.S_IEXEC)
418
419     # 2nd is skipped
420     utils.WriteFile(files[1], data="")
421
422     # 3rd cannot execute properly
423     utils.WriteFile(files[2], data="")
424     os.chmod(files[2], stat.S_IREAD | stat.S_IEXEC)
425
426     # 4th execs
427     utils.WriteFile(files[3], data="#!/bin/sh\n\necho -n ciao")
428     os.chmod(files[3], stat.S_IREAD | stat.S_IEXEC)
429
430     results = utils.RunParts(self.rundir, reset_env=True)
431
432     (relname, status, runresult) = results[0]
433     self.failUnlessEqual(relname, os.path.basename(files[0]))
434     self.failUnlessEqual(status, constants.RUNPARTS_RUN)
435     self.failUnlessEqual(runresult.exit_code, 1)
436     self.failUnless(runresult.failed)
437
438     (relname, status, runresult) = results[1]
439     self.failUnlessEqual(relname, os.path.basename(files[1]))
440     self.failUnlessEqual(status, constants.RUNPARTS_SKIP)
441     self.failUnlessEqual(runresult, None)
442
443     (relname, status, runresult) = results[2]
444     self.failUnlessEqual(relname, os.path.basename(files[2]))
445     self.failUnlessEqual(status, constants.RUNPARTS_ERR)
446     self.failUnless(runresult)
447
448     (relname, status, runresult) = results[3]
449     self.failUnlessEqual(relname, os.path.basename(files[3]))
450     self.failUnlessEqual(status, constants.RUNPARTS_RUN)
451     self.failUnlessEqual(runresult.output, "ciao")
452     self.failUnlessEqual(runresult.exit_code, 0)
453     self.failUnless(not runresult.failed)
454
455   def testMissingDirectory(self):
456     nosuchdir = utils.PathJoin(self.rundir, "no/such/directory")
457     self.assertEqual(utils.RunParts(nosuchdir), [])
458
459
460 class TestStartDaemon(testutils.GanetiTestCase):
461   def setUp(self):
462     self.tmpdir = tempfile.mkdtemp(prefix="ganeti-test")
463     self.tmpfile = os.path.join(self.tmpdir, "test")
464
465   def tearDown(self):
466     shutil.rmtree(self.tmpdir)
467
468   def testShell(self):
469     utils.StartDaemon("echo Hello World > %s" % self.tmpfile)
470     self._wait(self.tmpfile, 60.0, "Hello World")
471
472   def testShellOutput(self):
473     utils.StartDaemon("echo Hello World", output=self.tmpfile)
474     self._wait(self.tmpfile, 60.0, "Hello World")
475
476   def testNoShellNoOutput(self):
477     utils.StartDaemon(["pwd"])
478
479   def testNoShellNoOutputTouch(self):
480     testfile = os.path.join(self.tmpdir, "check")
481     self.failIf(os.path.exists(testfile))
482     utils.StartDaemon(["touch", testfile])
483     self._wait(testfile, 60.0, "")
484
485   def testNoShellOutput(self):
486     utils.StartDaemon(["pwd"], output=self.tmpfile)
487     self._wait(self.tmpfile, 60.0, "/")
488
489   def testNoShellOutputCwd(self):
490     utils.StartDaemon(["pwd"], output=self.tmpfile, cwd=os.getcwd())
491     self._wait(self.tmpfile, 60.0, os.getcwd())
492
493   def testShellEnv(self):
494     utils.StartDaemon("echo \"$GNT_TEST_VAR\"", output=self.tmpfile,
495                       env={ "GNT_TEST_VAR": "Hello World", })
496     self._wait(self.tmpfile, 60.0, "Hello World")
497
498   def testNoShellEnv(self):
499     utils.StartDaemon(["printenv", "GNT_TEST_VAR"], output=self.tmpfile,
500                       env={ "GNT_TEST_VAR": "Hello World", })
501     self._wait(self.tmpfile, 60.0, "Hello World")
502
503   def testOutputFd(self):
504     fd = os.open(self.tmpfile, os.O_WRONLY | os.O_CREAT)
505     try:
506       utils.StartDaemon(["pwd"], output_fd=fd, cwd=os.getcwd())
507     finally:
508       os.close(fd)
509     self._wait(self.tmpfile, 60.0, os.getcwd())
510
511   def testPid(self):
512     pid = utils.StartDaemon("echo $$ > %s" % self.tmpfile)
513     self._wait(self.tmpfile, 60.0, str(pid))
514
515   def testPidFile(self):
516     pidfile = os.path.join(self.tmpdir, "pid")
517     checkfile = os.path.join(self.tmpdir, "abort")
518
519     pid = utils.StartDaemon("while sleep 5; do :; done", pidfile=pidfile,
520                             output=self.tmpfile)
521     try:
522       fd = os.open(pidfile, os.O_RDONLY)
523       try:
524         # Check file is locked
525         self.assertRaises(errors.LockError, utils.LockFile, fd)
526
527         pidtext = os.read(fd, 100)
528       finally:
529         os.close(fd)
530
531       self.assertEqual(int(pidtext.strip()), pid)
532
533       self.assert_(utils.IsProcessAlive(pid))
534     finally:
535       # No matter what happens, kill daemon
536       utils.KillProcess(pid, timeout=5.0, waitpid=False)
537       self.failIf(utils.IsProcessAlive(pid))
538
539     self.assertEqual(utils.ReadFile(self.tmpfile), "")
540
541   def _wait(self, path, timeout, expected):
542     # Due to the asynchronous nature of daemon processes, polling is necessary.
543     # A timeout makes sure the test doesn't hang forever.
544     def _CheckFile():
545       if not (os.path.isfile(path) and
546               utils.ReadFile(path).strip() == expected):
547         raise utils.RetryAgain()
548
549     try:
550       utils.Retry(_CheckFile, (0.01, 1.5, 1.0), timeout)
551     except utils.RetryTimeout:
552       self.fail("Apparently the daemon didn't run in %s seconds and/or"
553                 " didn't write the correct output" % timeout)
554
555   def testError(self):
556     self.assertRaises(errors.OpExecError, utils.StartDaemon,
557                       ["./does-NOT-EXIST/here/0123456789"])
558     self.assertRaises(errors.OpExecError, utils.StartDaemon,
559                       ["./does-NOT-EXIST/here/0123456789"],
560                       output=os.path.join(self.tmpdir, "DIR/NOT/EXIST"))
561     self.assertRaises(errors.OpExecError, utils.StartDaemon,
562                       ["./does-NOT-EXIST/here/0123456789"],
563                       cwd=os.path.join(self.tmpdir, "DIR/NOT/EXIST"))
564     self.assertRaises(errors.OpExecError, utils.StartDaemon,
565                       ["./does-NOT-EXIST/here/0123456789"],
566                       output=os.path.join(self.tmpdir, "DIR/NOT/EXIST"))
567
568     fd = os.open(self.tmpfile, os.O_WRONLY | os.O_CREAT)
569     try:
570       self.assertRaises(errors.ProgrammerError, utils.StartDaemon,
571                         ["./does-NOT-EXIST/here/0123456789"],
572                         output=self.tmpfile, output_fd=fd)
573     finally:
574       os.close(fd)
575
576
577 class RunInSeparateProcess(unittest.TestCase):
578   def test(self):
579     for exp in [True, False]:
580       def _child():
581         return exp
582
583       self.assertEqual(exp, utils.RunInSeparateProcess(_child))
584
585   def testArgs(self):
586     for arg in [0, 1, 999, "Hello World", (1, 2, 3)]:
587       def _child(carg1, carg2):
588         return carg1 == "Foo" and carg2 == arg
589
590       self.assert_(utils.RunInSeparateProcess(_child, "Foo", arg))
591
592   def testPid(self):
593     parent_pid = os.getpid()
594
595     def _check():
596       return os.getpid() == parent_pid
597
598     self.failIf(utils.RunInSeparateProcess(_check))
599
600   def testSignal(self):
601     def _kill():
602       os.kill(os.getpid(), signal.SIGTERM)
603
604     self.assertRaises(errors.GenericError,
605                       utils.RunInSeparateProcess, _kill)
606
607   def testException(self):
608     def _exc():
609       raise errors.GenericError("This is a test")
610
611     self.assertRaises(errors.GenericError,
612                       utils.RunInSeparateProcess, _exc)
613
614
615 if __name__ == "__main__":
616   testutils.GanetiTestProgram()