Skip to content

Commit

Permalink
avocado.main(): avoid an infinite fork loop bomb
Browse files Browse the repository at this point in the history
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.

This adresses issue avocado-framework#961.

Signed-off-by: Cleber Rosa <crosa@redhat.com>
  • Loading branch information
clebergnu committed Jan 27, 2016
1 parent b9b0be8 commit 944b89c
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 1 deletion.
10 changes: 10 additions & 0 deletions avocado/core/job.py
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,16 @@ class TestProgram(object):
"""

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.progName = os.path.basename(sys.argv[0])
self.parseArgs(sys.argv[1:])
Expand Down
68 changes: 67 additions & 1 deletion selftests/functional/test_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,6 @@ def test(self):
main()
"""


NOT_A_TEST = """
def hello():
print('Hello World!')
Expand All @@ -100,6 +99,40 @@ def hello():
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):

Expand Down Expand Up @@ -161,6 +194,39 @@ def test_load_not_a_test(self):
def test_load_not_a_test_not_exec(self):
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 "
"--job-timeout=5s %s" % (self.tmpdir, mytest))
process.run(cmd_line)

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 "
"--job-timeout=5s %s" % (self.tmpdir, mytest))
process.run(cmd_line)

def tearDown(self):
shutil.rmtree(self.tmpdir)

Expand Down

0 comments on commit 944b89c

Please sign in to comment.