From 45d031957f192c61d54d9e4ccad9e1d965fe3fe0 Mon Sep 17 00:00:00 2001 From: Martin Vrachev Date: Sat, 29 Aug 2020 18:59:30 +0300 Subject: [PATCH 1/7] Remove redundant "url" class member from tests Signed-off-by: Martin Vrachev --- tests/test_arbitrary_package_attack.py | 1 - tests/test_endless_data_attack.py | 1 - tests/test_extraneous_dependencies_attack.py | 1 - tests/test_indefinite_freeze_attack.py | 1 - tests/test_key_revocation_integration.py | 1 - tests/test_mix_and_match_attack.py | 1 - tests/test_multiple_repositories_integration.py | 2 -- tests/test_replay_attack.py | 1 - tests/test_slow_retrieval_attack.py | 1 - tests/test_updater.py | 3 --- tests/test_updater_root_rotation_integration.py | 1 - 11 files changed, 14 deletions(-) diff --git a/tests/test_arbitrary_package_attack.py b/tests/test_arbitrary_package_attack.py index 640b5596..7fb30c8e 100755 --- a/tests/test_arbitrary_package_attack.py +++ b/tests/test_arbitrary_package_attack.py @@ -86,7 +86,6 @@ def setUpClass(cls): 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) diff --git a/tests/test_endless_data_attack.py b/tests/test_endless_data_attack.py index b1977ec5..ce3a6c9e 100755 --- a/tests/test_endless_data_attack.py +++ b/tests/test_endless_data_attack.py @@ -88,7 +88,6 @@ def setUpClass(cls): 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) diff --git a/tests/test_extraneous_dependencies_attack.py b/tests/test_extraneous_dependencies_attack.py index c67a6a25..634b44cb 100755 --- a/tests/test_extraneous_dependencies_attack.py +++ b/tests/test_extraneous_dependencies_attack.py @@ -92,7 +92,6 @@ def setUpClass(cls): 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) diff --git a/tests/test_indefinite_freeze_attack.py b/tests/test_indefinite_freeze_attack.py index c94b288a..aee5efb7 100755 --- a/tests/test_indefinite_freeze_attack.py +++ b/tests/test_indefinite_freeze_attack.py @@ -100,7 +100,6 @@ def setUpClass(cls): 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) diff --git a/tests/test_key_revocation_integration.py b/tests/test_key_revocation_integration.py index 8e7aade0..a6b0bbfa 100755 --- a/tests/test_key_revocation_integration.py +++ b/tests/test_key_revocation_integration.py @@ -87,7 +87,6 @@ def setUpClass(cls): 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) diff --git a/tests/test_mix_and_match_attack.py b/tests/test_mix_and_match_attack.py index 131af35a..d751467b 100755 --- a/tests/test_mix_and_match_attack.py +++ b/tests/test_mix_and_match_attack.py @@ -91,7 +91,6 @@ def setUpClass(cls): 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) diff --git a/tests/test_multiple_repositories_integration.py b/tests/test_multiple_repositories_integration.py index 6db53682..69e25a2e 100755 --- a/tests/test_multiple_repositories_integration.py +++ b/tests/test_multiple_repositories_integration.py @@ -150,8 +150,6 @@ def setUp(self): 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) diff --git a/tests/test_replay_attack.py b/tests/test_replay_attack.py index e8c86242..c0f78419 100755 --- a/tests/test_replay_attack.py +++ b/tests/test_replay_attack.py @@ -91,7 +91,6 @@ def setUpClass(cls): 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) diff --git a/tests/test_slow_retrieval_attack.py b/tests/test_slow_retrieval_attack.py index 5273e168..9a70511f 100755 --- a/tests/test_slow_retrieval_attack.py +++ b/tests/test_slow_retrieval_attack.py @@ -107,7 +107,6 @@ def _start_slow_server(self, 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: # diff --git a/tests/test_updater.py b/tests/test_updater.py index f7f64e29..d07c8a59 100644 --- a/tests/test_updater.py +++ b/tests/test_updater.py @@ -110,7 +110,6 @@ def setUpClass(cls): 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) @@ -1891,8 +1890,6 @@ def setUp(self): 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) diff --git a/tests/test_updater_root_rotation_integration.py b/tests/test_updater_root_rotation_integration.py index 84ce59ac..47263a91 100755 --- a/tests/test_updater_root_rotation_integration.py +++ b/tests/test_updater_root_rotation_integration.py @@ -94,7 +94,6 @@ def setUpClass(cls): 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) From 6f02646408ffee8928b62981d0dbc109d3fc2ea7 Mon Sep 17 00:00:00 2001 From: Martin Vrachev Date: Thu, 3 Sep 2020 15:00:15 +0300 Subject: [PATCH 2/7] Log subproceses stdout and stderr in temp files Logging the stdout and stderr from the test subprocesses into temporary files clean the console from unnecessary messages from the server-side such as "code 404, message File not found" or "GET" queries. I have decided to create TestServerProcess class that will handle the server subprocess creation and redirection to a temporary file object. That way that code can be reused in more than 10 files. Also, I have cleaned some parts of the unit test to make them more readable and efficient with the new abstraction. The unit tests are executed in sequential order and that's why we can reuse one temporary file object for multiple tests. Signed-off-by: Martin Vrachev --- tests/test_arbitrary_package_attack.py | 28 ++--- tests/test_download.py | 111 +++++++---------- tests/test_endless_data_attack.py | 26 ++-- tests/test_extraneous_dependencies_attack.py | 25 ++-- tests/test_indefinite_freeze_attack.py | 25 ++-- tests/test_key_revocation_integration.py | 24 ++-- tests/test_mix_and_match_attack.py | 26 ++-- .../test_multiple_repositories_integration.py | 39 ++---- tests/test_proxy_use.py | 94 ++++++--------- tests/test_replay_attack.py | 26 ++-- tests/test_slow_retrieval_attack.py | 32 +++-- tests/test_updater.py | 113 +++++++----------- .../test_updater_root_rotation_integration.py | 24 ++-- tests/utils.py | 112 +++++++++++++++++ 14 files changed, 332 insertions(+), 373 deletions(-) diff --git a/tests/test_arbitrary_package_attack.py b/tests/test_arbitrary_package_attack.py index 7fb30c8e..1b9ae571 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 @@ -80,14 +78,7 @@ 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)) - - utils.wait_for_server('localhost', cls.SERVER_PORT) + cls.server_process_handler = utils.TestServerProcess(log=logger) @@ -100,11 +91,8 @@ def tearDownClass(cls): # 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() + # Kills the server subprocess and closes the temp file used for logging. + cls.server_process_handler.clean() @@ -140,8 +128,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. @@ -157,6 +145,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. @@ -165,6 +154,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_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 ce3a6c9e..eb78b45d 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 @@ -82,14 +80,7 @@ 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)) - - utils.wait_for_server('localhost', cls.SERVER_PORT) + cls.server_process_handler = utils.TestServerProcess(log=logger) @@ -102,11 +93,8 @@ def tearDownClass(cls): # 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() + # Kills the server subprocess and closes the temp file used for logging. + cls.server_process_handler.clean() @@ -142,8 +130,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. @@ -166,6 +154,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 634b44cb..6033da4a 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 @@ -86,14 +84,7 @@ 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)) - - utils.wait_for_server('localhost', cls.SERVER_PORT) + cls.server_process_handler = utils.TestServerProcess(log=logger) @@ -106,11 +97,8 @@ def tearDownClass(cls): # 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() + # Kills the server subprocess and closes the temp file used for logging. + cls.server_process_handler.clean() @@ -149,8 +137,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,7 +161,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 aee5efb7..b72cf383 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 @@ -94,14 +92,7 @@ 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)) - - utils.wait_for_server('localhost', cls.SERVER_PORT) + cls.server_process_handler = utils.TestServerProcess(log=logger) @@ -114,11 +105,8 @@ def tearDownClass(cls): # 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() + # Kills the server subprocess and closes the temp file used for logging. + cls.server_process_handler.clean() @@ -156,8 +144,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. @@ -180,6 +168,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 a6b0bbfa..df33a63a 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 @@ -81,14 +79,7 @@ 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)) - - utils.wait_for_server('localhost', cls.SERVER_PORT) + cls.server_process_handler = utils.TestServerProcess(log=logger) @@ -101,11 +92,8 @@ def tearDownClass(cls): # 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() + # Kills the server subprocess and closes the temp file used for logging. + cls.server_process_handler.clean() @@ -148,8 +136,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. @@ -178,6 +166,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 d751467b..f3ae8f76 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 @@ -85,14 +83,7 @@ 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)) - - utils.wait_for_server('localhost', cls.SERVER_PORT) + cls.server_process_handler = utils.TestServerProcess(log=logger) @@ -105,12 +96,8 @@ def tearDownClass(cls): # 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() - + # Kills the server subprocess and closes the temp file used for logging. + cls.server_process_handler.clean() @@ -149,8 +136,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 +160,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 69e25a2e..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,26 +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)) - - 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) @@ -180,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) @@ -198,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 c0f78419..a6730a6a 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 @@ -85,14 +83,7 @@ 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)) - - utils.wait_for_server('localhost', cls.SERVER_PORT) + cls.server_process_handler = utils.TestServerProcess(log=logger) @@ -105,11 +96,8 @@ def tearDownClass(cls): # 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() + # Kills the server subprocess and closes the temp file used for logging. + cls.server_process_handler.clean() @@ -148,8 +136,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. @@ -172,6 +160,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_slow_retrieval_attack.py b/tests/test_slow_retrieval_attack.py index 9a70511f..03061542 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 @@ -102,11 +101,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)) # NOTE: Following error is raised if a delay is not long enough: # @@ -116,17 +115,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): @@ -246,12 +240,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. @@ -275,7 +271,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() @@ -291,7 +287,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 @@ -319,7 +315,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 d07c8a59..d81b1ddd 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 @@ -104,14 +102,8 @@ 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)) - - utils.wait_for_server('localhost', cls.SERVER_PORT) + cls.server_process_handler = utils.TestServerProcess(log=logger, + server=cls.SIMPLE_SERVER_PATH) @@ -120,16 +112,12 @@ 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() - # 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) + # Kills the server subprocess and closes the temp file used for logging. + cls.server_process_handler.clean() def setUp(self): @@ -177,8 +165,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. @@ -207,7 +195,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. @@ -1090,16 +1079,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', @@ -1217,8 +1205,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() @@ -1386,15 +1374,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. @@ -1500,8 +1488,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() @@ -1511,15 +1499,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. @@ -1602,8 +1590,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() @@ -1871,28 +1859,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)) - - 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) @@ -1928,16 +1913,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 47263a91..8eb56d4d 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 @@ -88,14 +86,7 @@ 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)) - - utils.wait_for_server('localhost', cls.SERVER_PORT) + cls.server_process_handler = utils.TestServerProcess(log=logger) @@ -109,10 +100,8 @@ def tearDownClass(cls): # 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() + # Kills the server subprocess and closes the temp file used for logging. + cls.server_process_handler.clean() @@ -155,8 +144,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. @@ -185,7 +174,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..8aa62dab 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 @@ -97,3 +100,112 @@ 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.port, timeout) + except Exception as e: + self.__logger.error( + "Error while waiting for the server to start: " + repr(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() From 5b44dd880837fee00c277b58e28a6932be27b0ce Mon Sep 17 00:00:00 2001 From: Martin Vrachev Date: Wed, 9 Sep 2020 14:53:52 +0300 Subject: [PATCH 3/7] Remove unneceserry checks in server files Signed-off-by: Martin Vrachev --- tests/simple_https_server.py | 13 ++----------- tests/simple_server.py | 13 ++----------- 2 files changed, 4 insertions(+), 22 deletions(-) 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): From 02c67d19801d4ed35bbb036c4bb670f8ba66d06c Mon Sep 17 00:00:00 2001 From: Martin Vrachev Date: Wed, 9 Sep 2020 19:29:03 +0300 Subject: [PATCH 4/7] Remove a not used function Signed-off-by: Martin Vrachev --- tests/slow_retrieval_server.py | 6 ------ 1 file changed, 6 deletions(-) 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) From 7f885d2160a46c2878bcfa39aaf4441561c812c1 Mon Sep 17 00:00:00 2001 From: Martin Vrachev Date: Wed, 9 Sep 2020 21:48:53 +0300 Subject: [PATCH 5/7] Remove redundant comments from test classes I don't see a need to leave a comment about what setupClass, tearDownClass, setup and tearDown functions do. There is documentation that describes that. Additionally, the links referenced in the comments are from Python 2 is deprecated. Signed-off-by: Martin Vrachev --- tests/test_arbitrary_package_attack.py | 5 ----- tests/test_developer_tool.py | 4 +--- tests/test_endless_data_attack.py | 5 ----- tests/test_extraneous_dependencies_attack.py | 5 ----- tests/test_indefinite_freeze_attack.py | 5 ----- tests/test_key_revocation_integration.py | 5 ----- tests/test_mix_and_match_attack.py | 5 ----- tests/test_replay_attack.py | 5 ----- tests/test_repository_lib.py | 7 ------- tests/test_repository_tool.py | 21 ------------------- tests/test_slow_retrieval_attack.py | 5 ----- tests/test_updater.py | 5 ----- .../test_updater_root_rotation_integration.py | 5 ----- 13 files changed, 1 insertion(+), 81 deletions(-) diff --git a/tests/test_arbitrary_package_attack.py b/tests/test_arbitrary_package_attack.py index 1b9ae571..d858d83a 100755 --- a/tests/test_arbitrary_package_attack.py +++ b/tests/test_arbitrary_package_attack.py @@ -64,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. @@ -84,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) 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_endless_data_attack.py b/tests/test_endless_data_attack.py index eb78b45d..d64ffdcf 100755 --- a/tests/test_endless_data_attack.py +++ b/tests/test_endless_data_attack.py @@ -66,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. @@ -86,9 +84,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) diff --git a/tests/test_extraneous_dependencies_attack.py b/tests/test_extraneous_dependencies_attack.py index 6033da4a..c9700d12 100755 --- a/tests/test_extraneous_dependencies_attack.py +++ b/tests/test_extraneous_dependencies_attack.py @@ -70,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. @@ -90,9 +88,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) diff --git a/tests/test_indefinite_freeze_attack.py b/tests/test_indefinite_freeze_attack.py index b72cf383..1e20cce9 100755 --- a/tests/test_indefinite_freeze_attack.py +++ b/tests/test_indefinite_freeze_attack.py @@ -78,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. @@ -98,9 +96,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) diff --git a/tests/test_key_revocation_integration.py b/tests/test_key_revocation_integration.py index df33a63a..4a7ca179 100755 --- a/tests/test_key_revocation_integration.py +++ b/tests/test_key_revocation_integration.py @@ -65,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. @@ -85,9 +83,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_mix_and_match_attack.py b/tests/test_mix_and_match_attack.py index f3ae8f76..bec55dca 100755 --- a/tests/test_mix_and_match_attack.py +++ b/tests/test_mix_and_match_attack.py @@ -69,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. @@ -89,9 +87,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) diff --git a/tests/test_replay_attack.py b/tests/test_replay_attack.py index a6730a6a..ac0bc318 100755 --- a/tests/test_replay_attack.py +++ b/tests/test_replay_attack.py @@ -69,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. @@ -89,9 +87,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) diff --git a/tests/test_repository_lib.py b/tests/test_repository_lib.py index 0fa9a6ae..6461e255 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 03061542..d0d14712 100755 --- a/tests/test_slow_retrieval_attack.py +++ b/tests/test_slow_retrieval_attack.py @@ -72,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. @@ -84,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) diff --git a/tests/test_updater.py b/tests/test_updater.py index d81b1ddd..477ef96c 100644 --- a/tests/test_updater.py +++ b/tests/test_updater.py @@ -83,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. @@ -109,9 +107,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_updater_root_rotation_integration.py b/tests/test_updater_root_rotation_integration.py index 8eb56d4d..721fe32e 100755 --- a/tests/test_updater_root_rotation_integration.py +++ b/tests/test_updater_root_rotation_integration.py @@ -72,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. @@ -93,9 +91,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) From 2693620ee8a03c408c00d9f7e7e1257d59b6fba0 Mon Sep 17 00:00:00 2001 From: Martin Vrachev Date: Tue, 15 Sep 2020 17:06:08 +0300 Subject: [PATCH 6/7] Make TimeoutError message more comprehensive Signed-off-by: Martin Vrachev --- tests/utils.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tests/utils.py b/tests/utils.py index 8aa62dab..26751d8d 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -52,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 @@ -77,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): @@ -167,10 +168,8 @@ def __init__(self, log, server='simple_server.py', if timeout > 0: try: - wait_for_server('localhost', self.port, timeout) + wait_for_server('localhost', self.server, self.port, timeout) except Exception as e: - self.__logger.error( - "Error while waiting for the server to start: " + repr(e)) # Make sure that errors from the server side will be logged. self.flush_log() raise e From e2ccfdb21337992bf15043494e52b1498c83df89 Mon Sep 17 00:00:00 2001 From: Martin Vrachev Date: Thu, 24 Sep 2020 13:19:27 +0300 Subject: [PATCH 7/7] Reorder the tearDownClass cleanup Fixes an issue where rmtree tries to access and consequently remove a temp folder where the server has opened a file already. This results in error: "PermissionError: [WinError 32] The process cannot access the file because it is being used by another process" For reference read: https://github.com/theupdateframework/tuf/issues/1119 Signed-off-by: Martin Vrachev --- tests/test_arbitrary_package_attack.py | 5 +++-- tests/test_endless_data_attack.py | 5 +++-- tests/test_extraneous_dependencies_attack.py | 5 +++-- tests/test_indefinite_freeze_attack.py | 5 +++-- tests/test_key_revocation_integration.py | 5 +++-- tests/test_mix_and_match_attack.py | 5 +++-- tests/test_replay_attack.py | 5 +++-- tests/test_updater.py | 5 +++-- tests/test_updater_root_rotation_integration.py | 5 +++-- 9 files changed, 27 insertions(+), 18 deletions(-) diff --git a/tests/test_arbitrary_package_attack.py b/tests/test_arbitrary_package_attack.py index d858d83a..a4127c64 100755 --- a/tests/test_arbitrary_package_attack.py +++ b/tests/test_arbitrary_package_attack.py @@ -82,12 +82,13 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): + # 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) - # Kills the server subprocess and closes the temp file used for logging. - cls.server_process_handler.clean() diff --git a/tests/test_endless_data_attack.py b/tests/test_endless_data_attack.py index d64ffdcf..dd5b9016 100755 --- a/tests/test_endless_data_attack.py +++ b/tests/test_endless_data_attack.py @@ -84,12 +84,13 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): + # 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) - # Kills the server subprocess and closes the temp file used for logging. - cls.server_process_handler.clean() diff --git a/tests/test_extraneous_dependencies_attack.py b/tests/test_extraneous_dependencies_attack.py index c9700d12..c5f92c9e 100755 --- a/tests/test_extraneous_dependencies_attack.py +++ b/tests/test_extraneous_dependencies_attack.py @@ -88,12 +88,13 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): + # 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) - # Kills the server subprocess and closes the temp file used for logging. - cls.server_process_handler.clean() diff --git a/tests/test_indefinite_freeze_attack.py b/tests/test_indefinite_freeze_attack.py index 1e20cce9..55cc664b 100755 --- a/tests/test_indefinite_freeze_attack.py +++ b/tests/test_indefinite_freeze_attack.py @@ -96,12 +96,13 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): + # 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) - # Kills the server subprocess and closes the temp file used for logging. - cls.server_process_handler.clean() diff --git a/tests/test_key_revocation_integration.py b/tests/test_key_revocation_integration.py index 4a7ca179..bf13cfe5 100755 --- a/tests/test_key_revocation_integration.py +++ b/tests/test_key_revocation_integration.py @@ -83,12 +83,13 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): + # 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) - # Kills the server subprocess and closes the temp file used for logging. - cls.server_process_handler.clean() diff --git a/tests/test_mix_and_match_attack.py b/tests/test_mix_and_match_attack.py index bec55dca..9be6c54f 100755 --- a/tests/test_mix_and_match_attack.py +++ b/tests/test_mix_and_match_attack.py @@ -87,12 +87,13 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): + # 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) - # Kills the server subprocess and closes the temp file used for logging. - cls.server_process_handler.clean() diff --git a/tests/test_replay_attack.py b/tests/test_replay_attack.py index ac0bc318..58fdc13f 100755 --- a/tests/test_replay_attack.py +++ b/tests/test_replay_attack.py @@ -87,12 +87,13 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): + # 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) - # Kills the server subprocess and closes the temp file used for logging. - cls.server_process_handler.clean() diff --git a/tests/test_updater.py b/tests/test_updater.py index 477ef96c..8c76a96c 100644 --- a/tests/test_updater.py +++ b/tests/test_updater.py @@ -107,12 +107,13 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): + # 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) - # Kills the server subprocess and closes the temp file used for logging. - cls.server_process_handler.clean() def setUp(self): diff --git a/tests/test_updater_root_rotation_integration.py b/tests/test_updater_root_rotation_integration.py index 721fe32e..c101b496 100755 --- a/tests/test_updater_root_rotation_integration.py +++ b/tests/test_updater_root_rotation_integration.py @@ -91,12 +91,13 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): + # 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) - # Kills the server subprocess and closes the temp file used for logging. - cls.server_process_handler.clean()