提交 08103fb4 编写于 作者: C Cleber Rosa

avocado.main(): avoid an infinite fork loop bomb

Because the current implementation of avocado.main() creates a job
and runs "sys.argv[0]" to implement standalone mode, it ends up
running itself over and over.

This simple proposed fix, prevents avocado.main() from running
itself again if called from itself. Since they are on different
processes, the mechanism chosen to do this is to set an environment
variable, that will be seen by the next process.

Also, by exiting from main() with an error code, the test first
level test will fail. This will let the user know that the chosen
approach (SIMPLE tests written in Python and calling main()) are
not worthy of a PASS.

The functional tests make use of Python's standard library utilities
(subprocess module) directly for running Avocado because of current
issues with Avocado's own process utility module.

This adresses issue #961.
Signed-off-by: NCleber Rosa <crosa@redhat.com>
上级 457cb58c
...@@ -540,6 +540,16 @@ class TestProgram(object): ...@@ -540,6 +540,16 @@ class TestProgram(object):
""" """
def __init__(self): def __init__(self):
# Avoid fork loop/bomb when running a test via avocado.main() that
# calls avocado.main() itself
if os.environ.get('AVOCADO_STANDALONE_IN_MAIN', False):
sys.stderr.write('AVOCADO_STANDALONE_IN_MAIN environment variable '
'found. This means that this code is being '
'called recursively. Exiting to avoid an infinite'
' fork loop.\n')
sys.exit(exit_codes.AVOCADO_FAIL)
os.environ['AVOCADO_STANDALONE_IN_MAIN'] = 'True'
self.defaultTest = sys.argv[0] self.defaultTest = sys.argv[0]
self.progName = os.path.basename(sys.argv[0]) self.progName = os.path.basename(sys.argv[0])
self.parseArgs(sys.argv[1:]) self.parseArgs(sys.argv[1:])
......
import os import os
import sys import sys
import subprocess
import time import time
import tempfile import tempfile
import shutil import shutil
import signal
if sys.version_info[:2] == (2, 6): if sys.version_info[:2] == (2, 6):
import unittest2 as unittest import unittest2 as unittest
else: else:
import unittest import unittest
from avocado.core import exit_codes
from avocado.utils import script from avocado.utils import script
from avocado.utils import process from avocado.utils import process
...@@ -82,7 +85,6 @@ if __name__ == "__main__": ...@@ -82,7 +85,6 @@ if __name__ == "__main__":
main() main()
""" """
NOT_A_TEST = """ NOT_A_TEST = """
def hello(): def hello():
print('Hello World!') print('Hello World!')
...@@ -100,6 +102,40 @@ SIMPLE_TEST = """#!/bin/sh ...@@ -100,6 +102,40 @@ SIMPLE_TEST = """#!/bin/sh
true true
""" """
AVOCADO_SIMPLE_PYTHON_LIKE_MULTIPLE_FILES = """#!/usr/bin/env python
# A simple test (executable bit set when saved to file) that looks like
# an Avocado instrumented test, with base class on separate file
from avocado import Test
from avocado import main
from test2 import *
class BasicTestSuite(SuperTest):
def test1(self):
self.xxx()
self.assertTrue(True)
if __name__ == '__main__':
main()
"""
AVOCADO_SIMPLE_PYTHON_LIKE_MULTIPLE_FILES_LIB = """
#!/usr/bin/python
from avocado import Test
class SuperTest(Test):
def xxx(self):
print "ahoj"
"""
AVOCADO_TEST_SIMPLE_USING_MAIN = """#!/usr/bin/env python
from avocado import main
if __name__ == "__main__":
main()
"""
class LoaderTestFunctional(unittest.TestCase): class LoaderTestFunctional(unittest.TestCase):
...@@ -117,6 +153,18 @@ class LoaderTestFunctional(unittest.TestCase): ...@@ -117,6 +153,18 @@ class LoaderTestFunctional(unittest.TestCase):
self.assertIn('%s: %s' % (exp_str, count), result.stdout) self.assertIn('%s: %s' % (exp_str, count), result.stdout)
test_script.remove() test_script.remove()
def _run_with_timeout(self, cmd_line, timeout):
current_time = time.time()
deadline = current_time + timeout
test_process = subprocess.Popen(cmd_line, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
preexec_fn=os.setsid)
while not test_process.poll():
if time.time() > deadline:
os.killpg(os.getpgid(test_process.pid), signal.SIGKILL)
self.fail("Failed to run test under %s seconds" % timeout)
time.sleep(0.05)
self.assertEquals(test_process.returncode, exit_codes.AVOCADO_TESTS_FAIL)
def test_simple(self): def test_simple(self):
self._test('simpletest.sh', SIMPLE_TEST, 'SIMPLE', 0775) self._test('simpletest.sh', SIMPLE_TEST, 'SIMPLE', 0775)
...@@ -161,6 +209,47 @@ class LoaderTestFunctional(unittest.TestCase): ...@@ -161,6 +209,47 @@ class LoaderTestFunctional(unittest.TestCase):
def test_load_not_a_test_not_exec(self): def test_load_not_a_test_not_exec(self):
self._test('notatest.py', NOT_A_TEST, 'NOT_A_TEST') self._test('notatest.py', NOT_A_TEST, 'NOT_A_TEST')
def test_runner_simple_python_like_multiple_files(self):
mylib = script.TemporaryScript(
'test2.py',
AVOCADO_SIMPLE_PYTHON_LIKE_MULTIPLE_FILES_LIB,
'avocado_simpletest_functional',
0644)
mylib.save()
mytest = script.Script(
os.path.join(os.path.dirname(mylib.path), 'test.py'),
AVOCADO_SIMPLE_PYTHON_LIKE_MULTIPLE_FILES)
os.chdir(basedir)
mytest.save()
cmd_line = "./scripts/avocado list -V %s" % mytest
result = process.run(cmd_line)
self.assertIn('SIMPLE: 1', result.stdout)
# job should be able to finish under 5 seconds. If this fails, it's
# possible that we hit the "simple test fork bomb" bug
cmd_line = ['./scripts/avocado',
'run',
'--sysinfo=off',
'--job-results-dir',
"%s" % self.tmpdir,
"%s" % mytest]
self._run_with_timeout(cmd_line, 5)
def test_simple_using_main(self):
mytest = script.TemporaryScript("simple_using_main.py",
AVOCADO_TEST_SIMPLE_USING_MAIN,
'avocado_simpletest_functional')
mytest.save()
os.chdir(basedir)
# job should be able to finish under 5 seconds. If this fails, it's
# possible that we hit the "simple test fork bomb" bug
cmd_line = ['./scripts/avocado',
'run',
'--sysinfo=off',
'--job-results-dir',
"%s" % self.tmpdir,
"%s" % mytest]
self._run_with_timeout(cmd_line, 5)
def tearDown(self): def tearDown(self):
shutil.rmtree(self.tmpdir) shutil.rmtree(self.tmpdir)
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册