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