diff --git a/tests/simple_https_server.py b/tests/simple_https_server.py index 4cb9d860..8e4c1dda 100755 --- a/tests/simple_https_server.py +++ b/tests/simple_https_server.py @@ -48,20 +48,11 @@ keyfile = os.path.join('ssl_certs', 'ssl_cert.key') certfile = os.path.join('ssl_certs', 'ssl_cert.crt') -def _generate_random_port(): - return random.randint(30000, 45000) - if len(sys.argv) > 1: - try: - PORT = int(sys.argv[1]) - if PORT < 30000 or PORT > 45000: - raise ValueError - - except ValueError: - PORT = _generate_random_port() + PORT = int(sys.argv[1]) else: - PORT = _generate_random_port() + PORT = random.randint(30000, 45000) if len(sys.argv) > 2: diff --git a/tests/simple_server.py b/tests/simple_server.py index 746b5ae4..8c19acc5 100755 --- a/tests/simple_server.py +++ b/tests/simple_server.py @@ -41,20 +41,11 @@ PORT = 0 -def _port_gen(): - return random.randint(30000, 45000) - if len(sys.argv) > 1: - try: - PORT = int(sys.argv[1]) - if PORT < 30000 or PORT > 45000: - raise ValueError - - except ValueError: - PORT = _port_gen() + PORT = int(sys.argv[1]) else: - PORT = _port_gen() + PORT = random.randint(30000, 45000) class QuietHTTPRequestHandler(SimpleHTTPRequestHandler): diff --git a/tests/slow_retrieval_server.py b/tests/slow_retrieval_server.py index 6930c80a..61e5c474 100755 --- a/tests/slow_retrieval_server.py +++ b/tests/slow_retrieval_server.py @@ -85,12 +85,6 @@ def do_GET(self): -def get_random_port(): - port = random.randint(30000, 45000) - return port - - - def run(port, test_mode): server_address = ('localhost', port) httpd = HTTPServer_Test(server_address, Handler, test_mode) diff --git a/tests/test_arbitrary_package_attack.py b/tests/test_arbitrary_package_attack.py index 640b5596..a4127c64 100755 --- a/tests/test_arbitrary_package_attack.py +++ b/tests/test_arbitrary_package_attack.py @@ -38,10 +38,8 @@ import os import tempfile -import random import shutil import json -import subprocess import logging import unittest import sys @@ -66,8 +64,6 @@ class TestArbitraryPackageAttack(unittest_toolbox.Modified_TestCase): @classmethod def setUpClass(cls): - # setUpClass() is called before any of the test cases are executed. - # Create a temporary directory to store the repository, metadata, and target # files. 'temporary_directory' must be deleted in TearDownModule() so that # temporary files are always removed, even when exceptions occur. @@ -80,32 +76,19 @@ def setUpClass(cls): # the pre-generated metadata files have a specific structure, such # as a delegated role 'targets/role1', three target files, five key files, # etc. - cls.SERVER_PORT = random.randint(30000, 45000) - command = ['python', 'simple_server.py', str(cls.SERVER_PORT)] - cls.server_process = subprocess.Popen(command) - logger.info('Server process started.') - logger.info('Server process id: ' + str(cls.server_process.pid)) - logger.info('Serving on port: ' + str(cls.SERVER_PORT)) - cls.url = 'http://localhost:' + str(cls.SERVER_PORT) + os.path.sep - - utils.wait_for_server('localhost', cls.SERVER_PORT) + cls.server_process_handler = utils.TestServerProcess(log=logger) @classmethod def tearDownClass(cls): - # tearDownModule() is called after all the test cases have run. - # http://docs.python.org/2/library/unittest.html#class-and-module-fixtures + # Kills the server subprocess and closes the temp file used for logging. + cls.server_process_handler.clean() # Remove the temporary repository directory, which should contain all the # metadata, targets, and key files generated of all the test cases. shutil.rmtree(cls.temporary_directory) - # Kill the SimpleHTTPServer process. - if cls.server_process.returncode is None: - logger.info('Server process ' + str(cls.server_process.pid) + ' terminated.') - cls.server_process.kill() - cls.server_process.wait() @@ -141,8 +124,8 @@ def setUp(self): # Set the url prefix required by the 'tuf/client/updater.py' updater. # 'path/to/tmp/repository' -> 'localhost:8001/tmp/repository'. repository_basepath = self.repository_directory[len(os.getcwd()):] - url_prefix = \ - 'http://localhost:' + str(self.SERVER_PORT) + repository_basepath + url_prefix = 'http://localhost:' \ + + str(self.server_process_handler.port) + repository_basepath # Setting 'tuf.settings.repository_directory' with the temporary client # directory copied from the original repository files. @@ -158,6 +141,7 @@ def setUp(self): self.repository_mirrors) + def tearDown(self): # Modified_TestCase.tearDown() automatically deletes temporary files and # directories that may have been created during each test case. @@ -166,6 +150,11 @@ def tearDown(self): tuf.roledb.clear_roledb(clear_all=True) tuf.keydb.clear_keydb(clear_all=True) + # Logs stdout and stderr from the sever subprocess. + self.server_process_handler.flush_log() + + + def test_without_tuf(self): # Verify that a target file replaced with a malicious version is downloaded # by a non-TUF client (i.e., a non-TUF client that does not verify hashes, diff --git a/tests/test_developer_tool.py b/tests/test_developer_tool.py index 6867104b..2a34786b 100755 --- a/tests/test_developer_tool.py +++ b/tests/test_developer_tool.py @@ -55,13 +55,11 @@ class TestProject(unittest.TestCase): def setUpClass(cls): cls.tmp_dir = tempfile.mkdtemp(dir = os.getcwd()) + @classmethod def tearDownClass(cls): shutil.rmtree(cls.tmp_dir) - def setUp(self): - # called before every test case - pass def tearDown(self): # called after every test case diff --git a/tests/test_download.py b/tests/test_download.py index 91f00db6..276c49b8 100755 --- a/tests/test_download.py +++ b/tests/test_download.py @@ -36,8 +36,6 @@ import hashlib import logging import os -import random -import subprocess import sys import unittest @@ -73,15 +71,11 @@ def setUp(self): self.target_data_length = len(self.target_data) # Launch a SimpleHTTPServer (serves files in the current dir). - self.PORT = random.randint(30000, 45000) - self.server_proc = popen_python(['simple_server.py', str(self.PORT)]) - logger.info('\n\tServer process started.') - logger.info('\tServer process id: ' + str(self.server_proc.pid)) - logger.info('\tServing on port: ' + str(self.PORT)) - junk, rel_target_filepath = os.path.split(target_filepath) - self.url = 'http://localhost:'+str(self.PORT)+'/'+rel_target_filepath + self.server_process_handler = utils.TestServerProcess(log=logger) - utils.wait_for_server('localhost', self.PORT) + rel_target_filepath = os.path.basename(target_filepath) + self.url = 'http://localhost:' \ + + str(self.server_process_handler.port) + '/' + rel_target_filepath # Computing hash of target file data. m = hashlib.md5() @@ -93,11 +87,11 @@ def setUp(self): # Stop server process and perform clean up. def tearDown(self): unittest_toolbox.Modified_TestCase.tearDown(self) - if self.server_proc.returncode is None: - logger.info('\tServer process '+str(self.server_proc.pid)+' terminated.') - self.server_proc.kill() - # Drop return values of communicate() - self.server_proc.communicate() + + # Logs stdout and stderr from the server subprocess and then it + # kills it and closes the temp file used for logging. + self.server_process_handler.clean() + self.target_fileobj.close() @@ -176,14 +170,17 @@ def test_download_url_to_tempfileobj_and_urls(self): download_file, self.random_string(), self.target_data_length) + url = 'http://localhost:' \ + + str(self.server_process_handler.port) + '/' + self.random_string() self.assertRaises(requests.exceptions.HTTPError, download_file, - 'http://localhost:' + str(self.PORT) + '/' + self.random_string(), + url, self.target_data_length) - + url1 = 'http://localhost:' \ + + str(self.server_process_handler.port + 1) + '/' + self.random_string() self.assertRaises(requests.exceptions.ConnectionError, download_file, - 'http://localhost:' + str(self.PORT+1) + '/' + self.random_string(), + url1, self.target_data_length) # Specify an unsupported URI scheme. @@ -258,27 +255,30 @@ def test_https_connection(self): # 3: run with an HTTPS certificate with an unexpected hostname # 4: run with an HTTPS certificate that is expired # Be sure to offset from the port used in setUp to avoid collision. - port1 = str(self.PORT + 1) - port2 = str(self.PORT + 2) - port3 = str(self.PORT + 3) - port4 = str(self.PORT + 4) - good_https_server_proc = popen_python( - ['simple_https_server.py', port1, good_cert_fname]) - good2_https_server_proc = popen_python( - ['simple_https_server.py', port2, good2_cert_fname]) - bad_https_server_proc = popen_python( - ['simple_https_server.py', port3, bad_cert_fname]) - expd_https_server_proc = popen_python( - ['simple_https_server.py', port4, expired_cert_fname]) - for port in range(self.PORT + 1, self.PORT + 5): - utils.wait_for_server('localhost', port) + port1 = self.server_process_handler.port + 1 + port2 = self.server_process_handler.port + 2 + port3 = self.server_process_handler.port + 3 + port4 = self.server_process_handler.port + 4 - relative_target_fpath = os.path.basename(target_filepath) - good_https_url = 'https://localhost:' + port1 + '/' + relative_target_fpath - good2_https_url = good_https_url.replace(':' + port1, ':' + port2) - bad_https_url = good_https_url.replace(':' + port1, ':' + port3) - expired_https_url = good_https_url.replace(':' + port1, ':' + port4) + good_https_server_handler = utils.TestServerProcess(log=logger, + server='simple_https_server.py', port=port1, + extra_cmd_args=[good_cert_fname]) + good2_https_server_handler = utils.TestServerProcess(log=logger, + server='simple_https_server.py', port=port2, + extra_cmd_args=[good2_cert_fname]) + bad_https_server_handler = utils.TestServerProcess(log=logger, + server='simple_https_server.py', port=port3, + extra_cmd_args=[bad_cert_fname]) + expd_https_server_handler = utils.TestServerProcess(log=logger, + server='simple_https_server.py', port=port4, + extra_cmd_args=[expired_cert_fname]) + + suffix = '/' + os.path.basename(target_filepath) + good_https_url = 'https://localhost:' + str(port1) + suffix + good2_https_url = 'https://localhost:' + str(port2) + suffix + bad_https_url = 'https://localhost:' + str(port3) + suffix + expired_https_url = 'https://localhost:' + str(port4) + suffix # Download the target file using an HTTPS connection. @@ -354,36 +354,15 @@ def test_https_connection(self): download.unsafe_download(good2_https_url, target_data_length).close() finally: - for proc in [ - good_https_server_proc, - good2_https_server_proc, - bad_https_server_proc, - expd_https_server_proc]: - if proc.returncode is None: - logger.info('Terminating server process ' + str(proc.pid)) - proc.kill() - # drop return values - proc.communicate() - - - - - -# TODO: Move this to a common test module (tests/common.py?) -# and strip it test_proxy_use.py and test_download.py. -def popen_python(command_arg_list): - """ - Run subprocess.Popen() to produce a process running a Python interpreter. - Uses the same Python interpreter that the current process is using, via - sys.executable. - """ - assert sys.executable, 'Test cannot function: unable to determine ' \ - 'current Python interpreter via sys.executable.' - - return subprocess.Popen( - [sys.executable] + command_arg_list, stderr=subprocess.PIPE) - + for proc_handler in [ + good_https_server_handler, + good2_https_server_handler, + bad_https_server_handler, + expd_https_server_handler]: + # Logs stdout and stderr from the server subprocess and then it + # kills it and closes the temp file used for logging. + proc_handler.clean() diff --git a/tests/test_endless_data_attack.py b/tests/test_endless_data_attack.py index b1977ec5..dd5b9016 100755 --- a/tests/test_endless_data_attack.py +++ b/tests/test_endless_data_attack.py @@ -41,10 +41,8 @@ import os import tempfile -import random import shutil import json -import subprocess import logging import unittest import sys @@ -68,8 +66,6 @@ class TestEndlessDataAttack(unittest_toolbox.Modified_TestCase): @classmethod def setUpClass(cls): - # setUpClass() is called before any of the test cases are executed. - # Create a temporary directory to store the repository, metadata, and target # files. 'temporary_directory' must be deleted in TearDownModule() so that # temporary files are always removed, even when exceptions occur. @@ -82,32 +78,19 @@ def setUpClass(cls): # the pre-generated metadata files have a specific structure, such # as a delegated role 'targets/role1', three target files, five key files, # etc. - cls.SERVER_PORT = random.randint(30000, 45000) - command = ['python', 'simple_server.py', str(cls.SERVER_PORT)] - cls.server_process = subprocess.Popen(command) - logger.info('Server process started.') - logger.info('Server process id: '+str(cls.server_process.pid)) - logger.info('Serving on port: '+str(cls.SERVER_PORT)) - cls.url = 'http://localhost:'+str(cls.SERVER_PORT) + os.path.sep - - utils.wait_for_server('localhost', cls.SERVER_PORT) + cls.server_process_handler = utils.TestServerProcess(log=logger) @classmethod def tearDownClass(cls): - # tearDownModule() is called after all the test cases have run. - # http://docs.python.org/2/library/unittest.html#class-and-module-fixtures + # Kills the server subprocess and closes the temp file used for logging. + cls.server_process_handler.clean() # Remove the temporary repository directory, which should contain all the # metadata, targets, and key files generated of all the test cases. shutil.rmtree(cls.temporary_directory) - # Kill the SimpleHTTPServer process. - if cls.server_process.returncode is None: - logger.info('Server process '+str(cls.server_process.pid)+' terminated.') - cls.server_process.kill() - cls.server_process.wait() @@ -143,8 +126,8 @@ def setUp(self): # Set the url prefix required by the 'tuf/client/updater.py' updater. # 'path/to/tmp/repository' -> 'localhost:8001/tmp/repository'. repository_basepath = self.repository_directory[len(os.getcwd()):] - url_prefix = \ - 'http://localhost:' + str(self.SERVER_PORT) + repository_basepath + url_prefix = 'http://localhost:' \ + + str(self.server_process_handler.port) + repository_basepath # Setting 'tuf.settings.repository_directory' with the temporary client # directory copied from the original repository files. @@ -167,6 +150,10 @@ def tearDown(self): tuf.roledb.clear_roledb(clear_all=True) tuf.keydb.clear_keydb(clear_all=True) + # Logs stdout and stderr from the sever subprocess. + self.server_process_handler.flush_log() + + def test_without_tuf(self): # Verify that a target file replaced with a larger malicious version (to diff --git a/tests/test_extraneous_dependencies_attack.py b/tests/test_extraneous_dependencies_attack.py index c67a6a25..c5f92c9e 100755 --- a/tests/test_extraneous_dependencies_attack.py +++ b/tests/test_extraneous_dependencies_attack.py @@ -44,10 +44,8 @@ import os import tempfile -import random import shutil import json -import subprocess import logging import unittest import sys @@ -72,8 +70,6 @@ class TestExtraneousDependenciesAttack(unittest_toolbox.Modified_TestCase): @classmethod def setUpClass(cls): - # setUpClass() is called before any of the test cases are executed. - # Create a temporary directory to store the repository, metadata, and target # files. 'temporary_directory' must be deleted in TearDownModule() so that # temporary files are always removed, even when exceptions occur. @@ -86,32 +82,19 @@ def setUpClass(cls): # the pre-generated metadata files have a specific structure, such # as a delegated role 'targets/role1', three target files, five key files, # etc. - cls.SERVER_PORT = random.randint(30000, 45000) - command = ['python', 'simple_server.py', str(cls.SERVER_PORT)] - cls.server_process = subprocess.Popen(command) - logger.info('Server process started.') - logger.info('Server process id: '+str(cls.server_process.pid)) - logger.info('Serving on port: '+str(cls.SERVER_PORT)) - cls.url = 'http://localhost:'+str(cls.SERVER_PORT) + os.path.sep - - utils.wait_for_server('localhost', cls.SERVER_PORT) + cls.server_process_handler = utils.TestServerProcess(log=logger) @classmethod def tearDownClass(cls): - # tearDownModule() is called after all the test cases have run. - # http://docs.python.org/2/library/unittest.html#class-and-module-fixtures + # Kills the server subprocess and closes the temp file used for logging. + cls.server_process_handler.clean() # Remove the temporary repository directory, which should contain all the # metadata, targets, and key files generated of all the test cases. shutil.rmtree(cls.temporary_directory) - # Kill the SimpleHTTPServer process. - if cls.server_process.returncode is None: - logger.info('Server process '+str(cls.server_process.pid)+' terminated.') - cls.server_process.kill() - cls.server_process.wait() @@ -150,8 +133,8 @@ def setUp(self): # Set the url prefix required by the 'tuf/client/updater.py' updater. # 'path/to/tmp/repository' -> 'localhost:8001/tmp/repository'. repository_basepath = self.repository_directory[len(os.getcwd()):] - url_prefix = \ - 'http://localhost:' + str(self.SERVER_PORT) + repository_basepath + url_prefix = 'http://localhost:' \ + + str(self.server_process_handler.port) + repository_basepath # Setting 'tuf.settings.repository_directory' with the temporary client # directory copied from the original repository files. @@ -174,7 +157,8 @@ def tearDown(self): tuf.roledb.clear_roledb(clear_all=True) tuf.keydb.clear_keydb(clear_all=True) - + # Logs stdout and stderr from the sever subprocess. + self.server_process_handler.flush_log() def test_with_tuf(self): diff --git a/tests/test_indefinite_freeze_attack.py b/tests/test_indefinite_freeze_attack.py index c94b288a..55cc664b 100755 --- a/tests/test_indefinite_freeze_attack.py +++ b/tests/test_indefinite_freeze_attack.py @@ -45,12 +45,10 @@ from __future__ import unicode_literals import os -import random import time import tempfile import shutil import json -import subprocess import logging import unittest import sys @@ -80,8 +78,6 @@ class TestIndefiniteFreezeAttack(unittest_toolbox.Modified_TestCase): @classmethod def setUpClass(cls): - # setUpClass() is called before any of the test cases are executed. - # Create a temporary directory to store the repository, metadata, and target # files. 'temporary_directory' must be deleted in TearDownModule() so that # temporary files are always removed, even when exceptions occur. @@ -94,32 +90,19 @@ def setUpClass(cls): # the pre-generated metadata files have a specific structure, such # as a delegated role 'targets/role1', three target files, five key files, # etc. - cls.SERVER_PORT = random.randint(30000, 45000) - command = ['python', 'simple_server.py', str(cls.SERVER_PORT)] - cls.server_process = subprocess.Popen(command) - logger.info('Server process started.') - logger.info('Server process id: '+str(cls.server_process.pid)) - logger.info('Serving on port: '+str(cls.SERVER_PORT)) - cls.url = 'http://localhost:'+str(cls.SERVER_PORT) + os.path.sep - - utils.wait_for_server('localhost', cls.SERVER_PORT) + cls.server_process_handler = utils.TestServerProcess(log=logger) @classmethod def tearDownClass(cls): - # tearDownModule() is called after all the test cases have run. - # http://docs.python.org/2/library/unittest.html#class-and-module-fixtures + # Kills the server subprocess and closes the temp file used for logging. + cls.server_process_handler.clean() # Remove the temporary repository directory, which should contain all the # metadata, targets, and key files generated of all the test cases. shutil.rmtree(cls.temporary_directory) - # Kill the SimpleHTTPServer process. - if cls.server_process.returncode is None: - logger.info('Server process '+str(cls.server_process.pid)+' terminated.') - cls.server_process.kill() - cls.server_process.wait() @@ -157,8 +140,8 @@ def setUp(self): # Set the url prefix required by the 'tuf/client/updater.py' updater. # 'path/to/tmp/repository' -> 'localhost:8001/tmp/repository'. repository_basepath = self.repository_directory[len(os.getcwd()):] - url_prefix = \ - 'http://localhost:' + str(self.SERVER_PORT) + repository_basepath + url_prefix = 'http://localhost:' \ + + str(self.server_process_handler.port) + repository_basepath # Setting 'tuf.settings.repository_directory' with the temporary client # directory copied from the original repository files. @@ -181,6 +164,9 @@ def tearDown(self): tuf.roledb.clear_roledb(clear_all=True) tuf.keydb.clear_keydb(clear_all=True) + # Logs stdout and stderr from the sever subprocess. + self.server_process_handler.flush_log() + def test_without_tuf(self): # Without TUF, Test 1 and Test 2 are functionally equivalent, so we skip diff --git a/tests/test_key_revocation_integration.py b/tests/test_key_revocation_integration.py index 8e7aade0..bf13cfe5 100755 --- a/tests/test_key_revocation_integration.py +++ b/tests/test_key_revocation_integration.py @@ -41,8 +41,6 @@ import shutil import tempfile import logging -import random -import subprocess import unittest import sys @@ -67,8 +65,6 @@ class TestKeyRevocation(unittest_toolbox.Modified_TestCase): @classmethod def setUpClass(cls): - # setUpClass() is called before tests in an individual class are executed. - # Create a temporary directory to store the repository, metadata, and target # files. 'temporary_directory' must be deleted in TearDownModule() so that # temporary files are always removed, even when exceptions occur. @@ -81,32 +77,19 @@ def setUpClass(cls): # 'test_key_revocation.py' assume the pre-generated metadata files have a # specific structure, such as a delegated role, three target files, five # key files, etc. - cls.SERVER_PORT = random.randint(30000, 45000) - command = ['python', 'simple_server.py', str(cls.SERVER_PORT)] - cls.server_process = subprocess.Popen(command) - logger.info('\n\tServer process started.') - logger.info('\tServer process id: '+str(cls.server_process.pid)) - logger.info('\tServing on port: '+str(cls.SERVER_PORT)) - cls.url = 'http://localhost:'+str(cls.SERVER_PORT) + os.path.sep - - utils.wait_for_server('localhost', cls.SERVER_PORT) + cls.server_process_handler = utils.TestServerProcess(log=logger) @classmethod def tearDownClass(cls): - # tearDownModule() is called after all the tests have run. - # http://docs.python.org/2/library/unittest.html#class-and-module-fixtures + # Kills the server subprocess and closes the temp file used for logging. + cls.server_process_handler.clean() # Remove the temporary repository directory, which should contain all the # metadata, targets, and key files generated for the test cases. shutil.rmtree(cls.temporary_directory) - # Kill the SimpleHTTPServer process. - if cls.server_process.returncode is None: - logger.info('\tServer process '+str(cls.server_process.pid)+' terminated.') - cls.server_process.kill() - cls.server_process.wait() @@ -149,8 +132,8 @@ def setUp(self): # 'path/to/tmp/repository' -> 'localhost:8001/tmp/repository'. repository_basepath = self.repository_directory[len(os.getcwd()):] - url_prefix = \ - 'http://localhost:' + str(self.SERVER_PORT) + repository_basepath + url_prefix = 'http://localhost:' \ + + str(self.server_process_handler.port) + repository_basepath # Setting 'tuf.settings.repository_directory' with the temporary client # directory copied from the original repository files. @@ -179,6 +162,8 @@ def tearDown(self): tuf.roledb.clear_roledb(clear_all=True) tuf.keydb.clear_keydb(clear_all=True) + # Logs stdout and stderr from the sever subprocess. + self.server_process_handler.flush_log() # UNIT TESTS. diff --git a/tests/test_mix_and_match_attack.py b/tests/test_mix_and_match_attack.py index 131af35a..9be6c54f 100755 --- a/tests/test_mix_and_match_attack.py +++ b/tests/test_mix_and_match_attack.py @@ -40,9 +40,7 @@ import os import tempfile -import random import shutil -import subprocess import logging import unittest import sys @@ -71,8 +69,6 @@ class TestMixAndMatchAttack(unittest_toolbox.Modified_TestCase): @classmethod def setUpClass(cls): - # setUpClass() is called before any of the test cases are executed. - # Create a temporary directory to store the repository, metadata, and # target files. 'temporary_directory' must be deleted in TearDownModule() # so that temporary files are always removed, even when exceptions occur. @@ -85,33 +81,19 @@ def setUpClass(cls): # the pre-generated metadata files have a specific structure, such # as a delegated role 'targets/role1', three target files, five key files, # etc. - cls.SERVER_PORT = random.randint(30000, 45000) - command = ['python', 'simple_server.py', str(cls.SERVER_PORT)] - cls.server_process = subprocess.Popen(command) - logger.info('Server process started.') - logger.info('Server process id: '+str(cls.server_process.pid)) - logger.info('Serving on port: '+str(cls.SERVER_PORT)) - cls.url = 'http://localhost:'+str(cls.SERVER_PORT) + os.path.sep - - utils.wait_for_server('localhost', cls.SERVER_PORT) + cls.server_process_handler = utils.TestServerProcess(log=logger) @classmethod def tearDownClass(cls): - # tearDownModule() is called after all the test cases have run. - # http://docs.python.org/2/library/unittest.html#class-and-module-fixtures + # Kills the server subprocess and closes the temp file used for logging. + cls.server_process_handler.clean() # Remove the temporary repository directory, which should contain all the # metadata, targets, and key files generated of all the test cases. shutil.rmtree(cls.temporary_directory) - # Kill the SimpleHTTPServer process. - if cls.server_process.returncode is None: - logger.info('Server process '+str(cls.server_process.pid)+' terminated.') - cls.server_process.kill() - cls.server_process.wait() - @@ -150,8 +132,8 @@ def setUp(self): # Set the url prefix required by the 'tuf/client/updater.py' updater. # 'path/to/tmp/repository' -> 'localhost:8001/tmp/repository'. repository_basepath = self.repository_directory[len(os.getcwd()):] - url_prefix = \ - 'http://localhost:' + str(self.SERVER_PORT) + repository_basepath + url_prefix = 'http://localhost:' \ + + str(self.server_process_handler.port) + repository_basepath # Setting 'tuf.settings.repository_directory' with the temporary client # directory copied from the original repository files. @@ -174,6 +156,9 @@ def tearDown(self): tuf.roledb.clear_roledb(clear_all=True) tuf.keydb.clear_keydb(clear_all=True) + # Logs stdout and stderr from the sever subprocess. + self.server_process_handler.flush_log() + def test_with_tuf(self): # Scenario: diff --git a/tests/test_multiple_repositories_integration.py b/tests/test_multiple_repositories_integration.py index 6db53682..58a55e77 100755 --- a/tests/test_multiple_repositories_integration.py +++ b/tests/test_multiple_repositories_integration.py @@ -32,7 +32,6 @@ import os import tempfile import random -import subprocess import logging import shutil import unittest @@ -133,28 +132,19 @@ def setUp(self): # has been changed when executing a subprocess. SIMPLE_SERVER_PATH = os.path.join(os.getcwd(), 'simple_server.py') - command = ['python', SIMPLE_SERVER_PATH, str(self.SERVER_PORT)] - command2 = ['python', SIMPLE_SERVER_PATH, str(self.SERVER_PORT2)] - - self.server_process = subprocess.Popen(command, - cwd=self.repository_directory) + # Creates a subprocess running server and uses temp file for logging. + self.server_process_handler = utils.TestServerProcess(log=logger, + port=self.SERVER_PORT, server=SIMPLE_SERVER_PATH, + popen_cwd=self.repository_directory) logger.debug('Server process started.') - logger.debug('Server process id: ' + str(self.server_process.pid)) - logger.debug('Serving on port: ' + str(self.SERVER_PORT)) - - self.server_process2 = subprocess.Popen(command2, - cwd=self.repository_directory2) + # Creates a subprocess running server and uses temp file for logging. + self.server_process_handler2 = utils.TestServerProcess(log=logger, + port=self.SERVER_PORT2, server=SIMPLE_SERVER_PATH, + popen_cwd=self.repository_directory2) logger.debug('Server process 2 started.') - logger.debug('Server 2 process id: ' + str(self.server_process2.pid)) - logger.debug('Serving 2 on port: ' + str(self.SERVER_PORT2)) - self.url = 'http://localhost:' + str(self.SERVER_PORT) + os.path.sep - self.url2 = 'http://localhost:' + str(self.SERVER_PORT2) + os.path.sep - - utils.wait_for_server('localhost', self.SERVER_PORT) - utils.wait_for_server('localhost', self.SERVER_PORT2) url_prefix = 'http://localhost:' + str(self.SERVER_PORT) url_prefix2 = 'http://localhost:' + str(self.SERVER_PORT2) @@ -182,16 +172,10 @@ def tearDown(self): # directories that may have been created during each test case. unittest_toolbox.Modified_TestCase.tearDown(self) - # Kill the SimpleHTTPServer process. - if self.server_process.returncode is None: - logger.info('Server process ' + str(self.server_process.pid) + ' terminated.') - self.server_process.kill() - self.server_process.wait() - - if self.server_process2.returncode is None: - logger.info('Server 2 process ' + str(self.server_process2.pid) + ' terminated.') - self.server_process2.kill() - self.server_process2.wait() + # Logs stdout and stderr from the server subprocesses and then it + # kills them and closes the temp files used for logging. + self.server_process_handler.clean() + self.server_process_handler2.clean() # updater.Updater() populates the roledb with the name "test_repository1" tuf.roledb.clear_roledb(clear_all=True) @@ -200,7 +184,6 @@ def tearDown(self): shutil.rmtree(self.temporary_directory) - def test_update(self): self.assertEqual('test_repository1', str(self.repository_updater)) self.assertEqual('test_repository2', str(self.repository_updater2)) diff --git a/tests/test_proxy_use.py b/tests/test_proxy_use.py index 27e09882..5c98361f 100644 --- a/tests/test_proxy_use.py +++ b/tests/test_proxy_use.py @@ -37,10 +37,8 @@ import logging import os -import random -import subprocess -import sys import unittest +import sys import tuf import tuf.download as download @@ -78,25 +76,23 @@ def setUpClass(cls): " (proxy_server.py is Python2 only)") # Launch a simple HTTP server (serves files in the current dir). - cls.http_port = random.randint(30000, 45000) - cls.http_server_proc = popen_python( - ['simple_server.py', str(cls.http_port)]) + cls.http_server_handler = utils.TestServerProcess(log=logger) # Launch an HTTPS server (serves files in the current dir). - cls.https_port = cls.http_port + 1 - cls.https_server_proc = popen_python( - ['simple_https_server.py', str(cls.https_port)]) - + cls.https_server_handler = utils.TestServerProcess(log=logger, + server='simple_https_server.py', + port=cls.http_server_handler.port + 1) # Launch an HTTP proxy server derived from inaz2/proxy2. # This one is able to handle HTTP CONNECT requests, and so can pass HTTPS # requests on to the target server. - cls.http_proxy_port = cls.http_port + 2 - cls.http_proxy_proc = popen_python( - ['proxy_server.py', str(cls.http_proxy_port)]) + cls.http_proxy_handler = utils.TestServerProcess(log=logger, + server='proxy_server.py', + port=cls.http_server_handler.port + 2) + # Note that the HTTP proxy server's address uses http://, regardless of the # type of connection used with the target server. - cls.http_proxy_addr = 'http://127.0.0.1:' + str(cls.http_proxy_port) + cls.http_proxy_addr = 'http://127.0.0.1:' + str(cls.http_proxy_handler.port) # Launch an HTTPS proxy server, also derived from inaz2/proxy2. @@ -111,18 +107,15 @@ def setUpClass(cls): # 3rd arg: (optional) certificate file for telling the proxy what target # server certs to accept in its HTTPS connection to the target server. # This is only relevant if the proxy is in intercept mode. - cls.https_proxy_port = cls.http_port + 3 - cls.https_proxy_proc = popen_python( - ['proxy_server.py', str(cls.https_proxy_port), 'intercept', - os.path.join('ssl_certs', 'ssl_cert.crt')]) + good_cert_fpath = os.path.join('ssl_certs', 'ssl_cert.crt') + cls.https_proxy_handler = utils.TestServerProcess(log=logger, + server='proxy_server.py', + port=cls.http_server_handler.port + 3, + extra_cmd_args=['intercept', good_cert_fpath]) + # Note that the HTTPS proxy server's address uses https://, regardless of # the type of connection used with the target server. - cls.https_proxy_addr = 'https://localhost:' + str(cls.https_proxy_port) - - utils.wait_for_server('localhost', cls.http_port) - utils.wait_for_server('localhost', cls.https_port) - utils.wait_for_server('localhost', cls.http_proxy_port) - utils.wait_for_server('localhost', cls.https_proxy_port) + cls.https_proxy_addr = 'https://localhost:' + str(cls.https_proxy_handler.port) @@ -135,17 +128,15 @@ def tearDownClass(cls): """ unittest_toolbox.Modified_TestCase.tearDownClass() - for proc in [ - cls.http_server_proc, - cls.https_server_proc, - cls.http_proxy_proc, - cls.https_proxy_proc, + for proc_handler in [ + cls.http_server_handler, + cls.https_server_handler, + cls.http_proxy_handler, + cls.https_proxy_handler, ]: - if proc.returncode is None: - logger.info('\tTerminating process ' + str(proc.pid) + ' in cleanup.') - proc.kill() - # Drop return values of communicate() - proc.communicate() + + # Kill the SimpleHTTPServer process. + proc_handler.clean() @@ -163,16 +154,16 @@ def setUp(self): # and its url on the server. current_dir = os.getcwd() target_filepath = self.make_temp_data_file(directory=current_dir) - rel_target_filepath = os.path.basename(target_filepath) with open(target_filepath, 'r') as target_file_object: self.target_data_length = len(target_file_object.read()) + suffix = '/' + os.path.basename(target_filepath) self.url = \ - 'http://localhost:' + str(self.http_port) + '/' + rel_target_filepath + 'http://localhost:' + str(self.http_server_handler.port) + suffix self.url_https = \ - 'https://localhost:' + str(self.https_port) + '/' + rel_target_filepath + 'https://localhost:' + str(self.https_server_handler.port) + suffix @@ -187,6 +178,15 @@ def tearDown(self): self.restore_all_modified_env_values() + for proc_handler in [ + self.http_server_handler, + self.https_server_handler, + self.http_proxy_handler, + self.https_proxy_handler, + ]: + + # Logs stdout and stderr from the sever subprocess. + proc_handler.flush_log() @@ -352,32 +352,12 @@ def restore_env_value(self, key): - - def restore_all_modified_env_values(self): for key in self.old_env_values: self.restore_env_value(key) - - -# TODO: Move this to a common test module (tests/common.py?) -# and strip it test_proxy_use.py and test_download.py. -def popen_python(command_arg_list): - """ - Run subprocess.Popen() to produce a process running a Python interpreter. - Uses the same Python interpreter that the current process is using, via - sys.executable. - """ - assert sys.executable, 'Test cannot function: unable to determine ' \ - 'current Python interpreter via sys.executable.' - - return subprocess.Popen( - [sys.executable] + command_arg_list, stderr=subprocess.PIPE) - - - # Run unit test. if __name__ == '__main__': utils.configure_test_logging(sys.argv) diff --git a/tests/test_replay_attack.py b/tests/test_replay_attack.py index e8c86242..58fdc13f 100755 --- a/tests/test_replay_attack.py +++ b/tests/test_replay_attack.py @@ -40,10 +40,8 @@ import os import tempfile -import random import datetime import shutil -import subprocess import logging import unittest import sys @@ -71,8 +69,6 @@ class TestReplayAttack(unittest_toolbox.Modified_TestCase): @classmethod def setUpClass(cls): - # setUpClass() is called before any of the test cases are executed. - # Create a temporary directory to store the repository, metadata, and target # files. 'temporary_directory' must be deleted in TearDownModule() so that # temporary files are always removed, even when exceptions occur. @@ -85,32 +81,19 @@ def setUpClass(cls): # the pre-generated metadata files have a specific structure, such # as a delegated role 'targets/role1', three target files, five key files, # etc. - cls.SERVER_PORT = random.randint(30000, 45000) - command = ['python', 'simple_server.py', str(cls.SERVER_PORT)] - cls.server_process = subprocess.Popen(command) - logger.info('Server process started.') - logger.info('Server process id: '+str(cls.server_process.pid)) - logger.info('Serving on port: '+str(cls.SERVER_PORT)) - cls.url = 'http://localhost:'+str(cls.SERVER_PORT) + os.path.sep - - utils.wait_for_server('localhost', cls.SERVER_PORT) + cls.server_process_handler = utils.TestServerProcess(log=logger) @classmethod def tearDownClass(cls): - # tearDownModule() is called after all the test cases have run. - # http://docs.python.org/2/library/unittest.html#class-and-module-fixtures + # Kills the server subprocess and closes the temp file used for logging. + cls.server_process_handler.clean() # Remove the temporary repository directory, which should contain all the # metadata, targets, and key files generated of all the test cases. shutil.rmtree(cls.temporary_directory) - # Kill the SimpleHTTPServer process. - if cls.server_process.returncode is None: - logger.info('Server process '+str(cls.server_process.pid)+' terminated.') - cls.server_process.kill() - cls.server_process.wait() @@ -149,8 +132,8 @@ def setUp(self): # Set the url prefix required by the 'tuf/client/updater.py' updater. # 'path/to/tmp/repository' -> 'localhost:8001/tmp/repository'. repository_basepath = self.repository_directory[len(os.getcwd()):] - url_prefix = \ - 'http://localhost:' + str(self.SERVER_PORT) + repository_basepath + url_prefix = 'http://localhost:' \ + + str(self.server_process_handler.port) + repository_basepath # Setting 'tuf.settings.repository_directory' with the temporary client # directory copied from the original repository files. @@ -173,6 +156,10 @@ def tearDown(self): tuf.roledb.clear_roledb(clear_all=True) tuf.keydb.clear_keydb(clear_all=True) + # Logs stdout and stderr from the sever subprocess. + self.server_process_handler.flush_log() + + def test_without_tuf(self): # Scenario: diff --git a/tests/test_repository_lib.py b/tests/test_repository_lib.py index 9575226f..e1367874 100755 --- a/tests/test_repository_lib.py +++ b/tests/test_repository_lib.py @@ -70,9 +70,6 @@ class TestRepositoryToolFunctions(unittest.TestCase): @classmethod def setUpClass(cls): - - # setUpClass() is called before tests in an individual class are executed. - # Create a temporary directory to store the repository, metadata, and target # files. 'temporary_directory' must be deleted in TearDownClass() so that # temporary files are always removed, even when exceptions occur. @@ -84,10 +81,6 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): - - # tearDownModule() is called after all the tests have run. - # http://docs.python.org/2/library/unittest.html#class-and-module-fixtures - # Remove the temporary repository directory, which should contain all the # metadata, targets, and key files generated for the test cases. tuf.roledb.clear_roledb(clear_all=True) diff --git a/tests/test_repository_tool.py b/tests/test_repository_tool.py index 09095411..9f955f72 100755 --- a/tests/test_repository_tool.py +++ b/tests/test_repository_tool.py @@ -58,9 +58,6 @@ class TestRepository(unittest.TestCase): @classmethod def setUpClass(cls): - - # setUpClass() is called before tests in an individual class are executed. - # Create a temporary directory to store the repository, metadata, and target # files. 'temporary_directory' must be deleted in TearDownClass() so that # temporary files are always removed, even when exceptions occur. @@ -69,10 +66,6 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): - - # tearDownModule() is called after all the tests have run. - # http://docs.python.org/2/library/unittest.html#class-and-module-fixtures - # Remove the temporary repository directory, which should contain all the # metadata, targets, and key files generated for the test cases. shutil.rmtree(cls.temporary_directory) @@ -1160,9 +1153,6 @@ def test_init(self): class TestTargets(unittest.TestCase): @classmethod def setUpClass(cls): - - # setUpClass() is called before tests in an individual class are executed. - # Create a temporary directory to store the repository, metadata, and target # files. 'temporary_directory' must be deleted in TearDownClass() so that # temporary files are always removed, even when exceptions occur. @@ -1172,10 +1162,6 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): - - # tearDownModule() is called after all the tests have run. - # http://docs.python.org/2/library/unittest.html#class-and-module-fixtures - # Remove the temporary repository directory, which should contain all the # metadata, targets, and key files generated for the test cases. shutil.rmtree(cls.temporary_directory) @@ -1922,9 +1908,6 @@ def test_check_path(self): class TestRepositoryToolFunctions(unittest.TestCase): @classmethod def setUpClass(cls): - - # setUpClass() is called before tests in an individual class are executed. - # Create a temporary directory to store the repository, metadata, and target # files. 'temporary_directory' must be deleted in TearDownClass() so that # temporary files are always removed, even when exceptions occur. @@ -1934,10 +1917,6 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): - - # tearDownModule() is called after all the tests have run. - # http://docs.python.org/2/library/unittest.html#class-and-module-fixtures - # Remove the temporary repository directory, which should contain all the # metadata, targets, and key files generated for the test cases. shutil.rmtree(cls.temporary_directory) diff --git a/tests/test_slow_retrieval_attack.py b/tests/test_slow_retrieval_attack.py index 5273e168..d0d14712 100755 --- a/tests/test_slow_retrieval_attack.py +++ b/tests/test_slow_retrieval_attack.py @@ -49,7 +49,6 @@ import random import time import shutil -import subprocess import logging import unittest import sys @@ -73,8 +72,6 @@ class TestSlowRetrievalAttack(unittest_toolbox.Modified_TestCase): @classmethod def setUpClass(cls): - # setUpClass() is called before any of the test cases are executed. - # Create a temporary directory to store the repository, metadata, and target # files. 'temporary_directory' must be deleted in TearDownModule() so that # temporary files are always removed, even when exceptions occur. @@ -85,9 +82,6 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): - # tearDownModule() is called after all the test cases have run. - # http://docs.python.org/2/library/unittest.html#class-and-module-fixtures - # Remove the temporary repository directory, which should contain all the # metadata, targets, and key files generated of all the test cases. shutil.rmtree(cls.temporary_directory) @@ -102,12 +96,11 @@ def _start_slow_server(self, mode): # the pre-generated metadata files have a specific structure, such # as a delegated role 'targets/role1', three target files, five key files, # etc. - command = ['python', 'slow_retrieval_server.py', str(self.SERVER_PORT), mode] - server_process = subprocess.Popen(command) + self.server_process_handler = utils.TestServerProcess(log=logger, + server='slow_retrieval_server.py', port=self.SERVER_PORT, + timeout=0, extra_cmd_args=[mode]) + logger.info('Slow Retrieval Server process started.') - logger.info('Server process id: '+str(server_process.pid)) - logger.info('Serving on port: '+str(self.SERVER_PORT)) - url = 'http://localhost:'+str(self.SERVER_PORT) + os.path.sep # NOTE: Following error is raised if a delay is not long enough: # @@ -117,17 +110,12 @@ def _start_slow_server(self, mode): # increasing this to 3s, sadly. time.sleep(3) - return server_process - - def _stop_slow_server(self, server_process): - # Kill the SimpleHTTPServer process. - if server_process.returncode is None: - logger.info('Server process '+str(server_process.pid)+' terminated.') - server_process.kill() - server_process.wait() - + def _stop_slow_server(self): + # Logs stdout and stderr from the server subprocess and then it + # kills it and closes the temp file used for logging. + self.server_process_handler.clean() def setUp(self): @@ -247,12 +235,14 @@ def tearDown(self): tuf.keydb.clear_keydb(clear_all=True) + + def test_with_tuf_mode_1(self): # Simulate a slow retrieval attack. # 'mode_1': When download begins,the server blocks the download for a long # time by doing nothing before it sends the first byte of data. - server_process = self._start_slow_server('mode_1') + self._start_slow_server('mode_1') # Verify that the TUF client detects replayed metadata and refuses to # continue the update process. @@ -276,7 +266,7 @@ def test_with_tuf_mode_1(self): self.fail('TUF did not prevent a slow retrieval attack.') finally: - self._stop_slow_server(server_process) + self._stop_slow_server() @@ -292,7 +282,7 @@ def test_with_tuf_mode_2(self): # 'mode_2': During the download process, the server blocks the download # by sending just several characters every few seconds. - server_process = self._start_slow_server('mode_2') + self._start_slow_server('mode_2') client_filepath = os.path.join(self.client_directory, 'file1.txt') original_average_download_speed = tuf.settings.MIN_AVERAGE_DOWNLOAD_SPEED tuf.settings.MIN_AVERAGE_DOWNLOAD_SPEED = 3 @@ -320,7 +310,7 @@ def test_with_tuf_mode_2(self): self.fail('TUF did not prevent a slow retrieval attack.') finally: - self._stop_slow_server(server_process) + self._stop_slow_server() tuf.settings.MIN_AVERAGE_DOWNLOAD_SPEED = original_average_download_speed diff --git a/tests/test_updater.py b/tests/test_updater.py index f7f64e29..8c76a96c 100644 --- a/tests/test_updater.py +++ b/tests/test_updater.py @@ -55,8 +55,6 @@ import copy import tempfile import logging -import random -import subprocess import errno import sys import unittest @@ -85,8 +83,6 @@ class TestUpdater(unittest_toolbox.Modified_TestCase): @classmethod def setUpClass(cls): - # setUpClass() is called before tests in an individual class are executed. - # Create a temporary directory to store the repository, metadata, and target # files. 'temporary_directory' must be deleted in TearDownModule() so that # temporary files are always removed, even when exceptions occur. @@ -104,28 +100,15 @@ def setUpClass(cls): # assume the pre-generated metadata files have a specific structure, such # as a delegated role 'targets/role1', three target files, five key files, # etc. - cls.SERVER_PORT = random.randint(30000, 45000) - command = ['python', cls.SIMPLE_SERVER_PATH, str(cls.SERVER_PORT)] - cls.server_process = subprocess.Popen(command) - logger.info('\n\tServer process started.') - logger.info('\tServer process id: '+str(cls.server_process.pid)) - logger.info('\tServing on port: '+str(cls.SERVER_PORT)) - cls.url = 'http://localhost:'+str(cls.SERVER_PORT) + os.path.sep - - utils.wait_for_server('localhost', cls.SERVER_PORT) + cls.server_process_handler = utils.TestServerProcess(log=logger, + server=cls.SIMPLE_SERVER_PATH) @classmethod def tearDownClass(cls): - # tearDownModule() is called after all the tests have run. - # http://docs.python.org/2/library/unittest.html#class-and-module-fixtures - - # Kill the SimpleHTTPServer process. - if cls.server_process.returncode is None: - logger.info('\tServer process ' + str(cls.server_process.pid) + ' terminated.') - cls.server_process.kill() - cls.server_process.wait() + # Kills the server subprocess and closes the temp file used for logging. + cls.server_process_handler.clean() # Remove the temporary repository directory, which should contain all the # metadata, targets, and key files generated for the test cases @@ -178,8 +161,8 @@ def setUp(self): # 'path/to/tmp/repository' -> 'localhost:8001/tmp/repository'. repository_basepath = self.repository_directory[len(os.getcwd()):] - url_prefix = \ - 'http://localhost:' + str(self.SERVER_PORT) + repository_basepath + url_prefix = 'http://localhost:' \ + + str(self.server_process_handler.port) + repository_basepath # Setting 'tuf.settings.repository_directory' with the temporary client # directory copied from the original repository files. @@ -208,7 +191,8 @@ def tearDown(self): tuf.roledb.clear_roledb(clear_all=True) tuf.keydb.clear_keydb(clear_all=True) - + # Logs stdout and stderr from the sever subprocess. + self.server_process_handler.flush_log() # UNIT TESTS. @@ -1091,16 +1075,15 @@ def test_6_get_one_valid_targetinfo(self): # Unlike some of the other tests, start up a fresh server here. # The SimpleHTTPServer started in the setupclass has a tendency to # timeout in Windows after a few tests. - SERVER_PORT = random.randint(30000, 45000) - command = ['python', self.SIMPLE_SERVER_PATH, str(SERVER_PORT)] - server_process = subprocess.Popen(command) - utils.wait_for_server('localhost', SERVER_PORT) + # Creates a subprocess running server and uses temp file for logging. + server_process_handler = utils.TestServerProcess(log=logger, + server=self.SIMPLE_SERVER_PATH) # 'path/to/tmp/repository' -> 'localhost:8001/tmp/repository'. repository_basepath = self.repository_directory[len(os.getcwd()):] - url_prefix = \ - 'http://localhost:' + str(SERVER_PORT) + repository_basepath + url_prefix = 'http://localhost:' \ + + str(self.server_process_handler.port) + repository_basepath self.repository_mirrors = {'mirror1': {'url_prefix': url_prefix, 'metadata_path': 'metadata', 'targets_path': 'targets', @@ -1218,8 +1201,8 @@ def test_6_get_one_valid_targetinfo(self): self.repository_updater.get_one_valid_targetinfo, '/foo/foo1.1.tar.gz') - server_process.kill() - server_process.wait() + # Kills the server subprocess and closes the temp file used for logging. + server_process_handler.clean() @@ -1387,15 +1370,15 @@ def test_7_updated_targets(self): # Unlike some of the other tests, start up a fresh server here. # The SimpleHTTPServer started in the setupclass has a tendency to # timeout in Windows after a few tests. - SERVER_PORT = random.randint(30000, 45000) - command = ['python', self.SIMPLE_SERVER_PATH, str(SERVER_PORT)] - server_process = subprocess.Popen(command) - utils.wait_for_server('localhost', SERVER_PORT) + + # Creates a subprocess running server and uses temp file for logging. + server_process_handler = utils.TestServerProcess(log=logger, + server=self.SIMPLE_SERVER_PATH) # 'path/to/tmp/repository' -> 'localhost:8001/tmp/repository'. repository_basepath = self.repository_directory[len(os.getcwd()):] - url_prefix = \ - 'http://localhost:' + str(SERVER_PORT) + repository_basepath + url_prefix = 'http://localhost:' \ + + str(self.server_process_handler.port) + repository_basepath # Setting 'tuf.settings.repository_directory' with the temporary client # directory copied from the original repository files. @@ -1501,8 +1484,8 @@ def test_7_updated_targets(self): self.repository_updater.updated_targets(all_targets, destination_directory) self.assertEqual(len(updated_targets), 1) - server_process.kill() - server_process.wait() + # Kills the server subprocess and closes the temp file used for logging. + server_process_handler.clean() @@ -1512,15 +1495,15 @@ def test_8_remove_obsolete_targets(self): # Unlike some of the other tests, start up a fresh server here. # The SimpleHTTPServer started in the setupclass has a tendency to # timeout in Windows after a few tests. - SERVER_PORT = random.randint(30000, 45000) - command = ['python', self.SIMPLE_SERVER_PATH, str(SERVER_PORT)] - server_process = subprocess.Popen(command) - utils.wait_for_server('localhost', SERVER_PORT) + + # Creates a subprocess running server and uses temp file for logging. + server_process_handler = utils.TestServerProcess(log=logger, + server=self.SIMPLE_SERVER_PATH) # 'path/to/tmp/repository' -> 'localhost:8001/tmp/repository'. repository_basepath = self.repository_directory[len(os.getcwd()):] - url_prefix = \ - 'http://localhost:' + str(SERVER_PORT) + repository_basepath + url_prefix = 'http://localhost:' \ + + str(self.server_process_handler.port) + repository_basepath # Setting 'tuf.settings.repository_directory' with the temporary client # directory copied from the original repository files. @@ -1603,8 +1586,8 @@ def test_8_remove_obsolete_targets(self): del self.repository_updater.metadata['previous']['targets'] self.repository_updater.remove_obsolete_targets(destination_directory) - server_process.kill() - server_process.wait() + # Kills the server subprocess and closes the temp file used for logging. + server_process_handler.clean() @@ -1872,30 +1855,25 @@ def setUp(self): # the pre-generated metadata files have a specific structure, such # as a delegated role 'targets/role1', three target files, five key files, # etc. + + # The ports are harcoded because the urls to the repositories are harcoded + # in map.json. self.SERVER_PORT = 30001 self.SERVER_PORT2 = 30002 - command = ['python', self.SIMPLE_SERVER_PATH, str(self.SERVER_PORT)] - command2 = ['python', self.SIMPLE_SERVER_PATH, str(self.SERVER_PORT2)] - - self.server_process = subprocess.Popen(command, - cwd=self.repository_directory) + # Creates a subprocess running server and uses temp file for logging. + self.server_process_handler = utils.TestServerProcess(log=logger, + server=self.SIMPLE_SERVER_PATH, port=self.SERVER_PORT, + popen_cwd=self.repository_directory) logger.debug('Server process started.') - logger.debug('Server process id: ' + str(self.server_process.pid)) - logger.debug('Serving on port: ' + str(self.SERVER_PORT)) - self.server_process2 = subprocess.Popen(command2, - cwd=self.repository_directory2) + # Creates a subprocess running server and uses temp file for logging. + self.server_process_handler2 = utils.TestServerProcess(log=logger, + server=self.SIMPLE_SERVER_PATH, port=self.SERVER_PORT2, + popen_cwd=self.repository_directory2) logger.debug('Server process 2 started.') - logger.debug('Server 2 process id: ' + str(self.server_process2.pid)) - logger.debug('Serving 2 on port: ' + str(self.SERVER_PORT2)) - self.url = 'http://localhost:' + str(self.SERVER_PORT) + os.path.sep - self.url2 = 'http://localhost:' + str(self.SERVER_PORT2) + os.path.sep - - utils.wait_for_server('localhost', self.SERVER_PORT) - utils.wait_for_server('localhost', self.SERVER_PORT2) url_prefix = 'http://localhost:' + str(self.SERVER_PORT) url_prefix2 = 'http://localhost:' + str(self.SERVER_PORT2) @@ -1931,16 +1909,10 @@ def tearDown(self): # directories that may have been created during each test case. unittest_toolbox.Modified_TestCase.tearDown(self) - # Kill the SimpleHTTPServer process. - if self.server_process.returncode is None: - logger.info('Server process ' + str(self.server_process.pid) + ' terminated.') - self.server_process.kill() - self.server_process.wait() - - if self.server_process2.returncode is None: - logger.info('Server 2 process ' + str(self.server_process2.pid) + ' terminated.') - self.server_process2.kill() - self.server_process2.wait() + # Logs stdout and stderr from the server subprocesses and then it + # kills them and closes the temp files used for logging. + self.server_process_handler.clean() + self.server_process_handler2.clean() # updater.Updater() populates the roledb with the name "test_repository1" tuf.roledb.clear_roledb(clear_all=True) diff --git a/tests/test_updater_root_rotation_integration.py b/tests/test_updater_root_rotation_integration.py index 84ce59ac..c101b496 100755 --- a/tests/test_updater_root_rotation_integration.py +++ b/tests/test_updater_root_rotation_integration.py @@ -45,8 +45,6 @@ import shutil import tempfile import logging -import random -import subprocess import unittest import filecmp import sys @@ -74,8 +72,6 @@ class TestUpdater(unittest_toolbox.Modified_TestCase): @classmethod def setUpClass(cls): - # setUpClass() is called before tests in an individual class are executed. - # Create a temporary directory to store the repository, metadata, and target # files. 'temporary_directory' must be deleted in TearDownModule() so that # temporary files are always removed, even when exceptions occur. @@ -88,32 +84,20 @@ def setUpClass(cls): # assume the pre-generated metadata files have a specific structure, such # as a delegated role 'targets/role1', three target files, five key files, # etc. - cls.SERVER_PORT = random.randint(30000, 45000) - command = ['python', 'simple_server.py', str(cls.SERVER_PORT)] - cls.server_process = subprocess.Popen(command) - logger.info('\n\tServer process started.') - logger.info('\tServer process id: '+str(cls.server_process.pid)) - logger.info('\tServing on port: '+str(cls.SERVER_PORT)) - cls.url = 'http://localhost:'+str(cls.SERVER_PORT) + os.path.sep - - utils.wait_for_server('localhost', cls.SERVER_PORT) + cls.server_process_handler = utils.TestServerProcess(log=logger) @classmethod def tearDownClass(cls): - # tearDownModule() is called after all the tests have run. - # http://docs.python.org/2/library/unittest.html#class-and-module-fixtures + # Kills the server subprocess and closes the temp file used for logging. + cls.server_process_handler.clean() # Remove the temporary repository directory, which should contain all the # metadata, targets, and key files generated for the test cases. shutil.rmtree(cls.temporary_directory) - # Kill the SimpleHTTPServer process. - if cls.server_process.returncode is None: - logger.info('\tServer process ' + str(cls.server_process.pid) + ' terminated.') - cls.server_process.kill() @@ -156,8 +140,8 @@ def setUp(self): # 'path/to/tmp/repository' -> 'localhost:8001/tmp/repository'. repository_basepath = self.repository_directory[len(os.getcwd()):] - url_prefix = \ - 'http://localhost:' + str(self.SERVER_PORT) + repository_basepath + url_prefix = 'http://localhost:' \ + + str(self.server_process_handler.port) + repository_basepath # Setting 'tuf.settings.repository_directory' with the temporary client # directory copied from the original repository files. @@ -186,7 +170,8 @@ def tearDown(self): tuf.roledb.clear_roledb(clear_all=True) tuf.keydb.clear_keydb(clear_all=True) - + # Logs stdout and stderr from the sever subprocess. + self.server_process_handler.flush_log() # UNIT TESTS. diff --git a/tests/utils.py b/tests/utils.py index d0ad75d5..26751d8d 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -25,6 +25,9 @@ import logging import socket import time +import subprocess +import tempfile +import random import tuf.log @@ -49,7 +52,7 @@ def __str__(self): # but the current blocking connect() seems to work fast on Linux and seems # to at least work on Windows (ECONNREFUSED unfortunately has a 2 second # timeout on Windows) -def wait_for_server(host, port, timeout=10): +def wait_for_server(host, server, port, timeout=10): start = time.time() remaining_timeout = timeout succeeded = False @@ -74,7 +77,8 @@ def wait_for_server(host, port, timeout=10): remaining_timeout = timeout - (time.time() - start) if not succeeded: - raise TimeoutError + raise TimeoutError("Could not connect to the " + server \ + + " on port " + str(port) + " !") def configure_test_logging(argv): @@ -97,3 +101,110 @@ def configure_test_logging(argv): logging.basicConfig(level=loglevel) tuf.log.set_log_level(loglevel) + + +class TestServerProcess(): + """ + + Creates a child process with the subprocess.Popen object and + TempFile object used for logging. + + + log: + Logger which will be used for logging. + + server: + Path to the server to run in the subprocess. + Default is "simpler_server.py". + + port: + The port used to access the server. If none is provided, + then one will be generated. + Default is None. + + timeout: + Time in seconds in which the server should start or otherwise + TimeoutError error will be raised. + If 0 is given, no check if the server has started will be done. + Default is 10. + + popen_cwd: + Current working directory used when instancing a + subprocess.Popen object. + Default is "." + + extra_cmd_args: + List of additional arguments for the command + which will start the subprocess. + More precisely "python -u ". + Default is empty list. + """ + + + def __init__(self, log, server='simple_server.py', + port=None, timeout=10, popen_cwd=".", + extra_cmd_args=[]): + + # Create temporary log file used for logging stdout and stderr + # of the subprocess. In the mode "r+"" stands for reading and writing + # and "t" stands for text mode. + self.__temp_log_file = tempfile.TemporaryFile(mode='r+t') + + self.server = server + self.port = port or random.randint(30000, 45000) + self.__logger = log + + # The "-u" option forces stdin, stdout and stderr to be unbuffered. + command = ['python', '-u', server, str(self.port)] + extra_cmd_args + + # We are reusing one server subprocess in multiple unit tests, but we are + # collecting the logs per test. + self.__server_process = subprocess.Popen(command, + stdout=self.__temp_log_file, stderr=subprocess.STDOUT, cwd=popen_cwd) + + self.__logger.info('Server process with process id ' \ + + str(self.__server_process.pid) + " serving on port " \ + + str(self.port) + ' started.') + + if timeout > 0: + try: + wait_for_server('localhost', self.server, self.port, timeout) + except Exception as e: + # Make sure that errors from the server side will be logged. + self.flush_log() + raise e + + + + def flush_log(self): + """Logs contents from TempFile, truncates buffer""" + + # Seek is needed to move the pointer to the beginning of the file, because + # the subprocess could have read and/or write and thus moved the pointer. + self.__temp_log_file.seek(0) + log_message = self.__temp_log_file.read() + + if len(log_message) > 0: + title = "Test server (" + self.server + ") output:" + message = [title] + log_message.splitlines() + self.__logger.info('\n| '.join(message)) + + # Make sure the file is empty before the next test logs new information. + self.__temp_log_file.truncate(0) + + + + def clean(self): + """Kills the subprocess and closes the TempFile. + Calls flush_log to check for logged information, but not yet flushed.""" + + # If there is anything logged, flush it before closing the resourses. + self.flush_log() + + self.__temp_log_file.close() + + if self.__server_process.returncode is None: + self.__logger.info('Server process ' + str(self.__server_process.pid) + + ' terminated.') + self.__server_process.kill() + self.__server_process.wait()