From 8b83306d5425a1c1e3b498d03e5a2d3c201c38fe Mon Sep 17 00:00:00 2001 From: Zihang Chen Date: Thu, 24 Jul 2014 16:46:50 +0530 Subject: Refactor the Python based test suite This is a squashed commit of the following from parallel-wget: ecd6977 Refactor mainly the test cases classes d26c8eb Create package test for test case classes 507383d Move server classes to package server.protocol 195393b Create package conf where rules and hooks are put 42e482a Create package exc and move TestFailed to exc 82f44f3 Fix a typo in Test-Proto.py 31e5f33 From WgetTest.py move WgetFile to misc 422171d Create package misc, move ColourTerm.py to misc --- testenv/test/base_test.py | 226 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 226 insertions(+) create mode 100644 testenv/test/base_test.py (limited to 'testenv/test/base_test.py') diff --git a/testenv/test/base_test.py b/testenv/test/base_test.py new file mode 100644 index 00000000..1c21c18d --- /dev/null +++ b/testenv/test/base_test.py @@ -0,0 +1,226 @@ +import os +import shutil +import shlex +import traceback +import re +import time +from subprocess import call +from misc.colour_terminal import print_red, print_blue +from exc.test_failed import TestFailed +import conf + +HTTP = "HTTP" +HTTPS = "HTTPS" + + +class BaseTest: + + """ + Class that defines methods common to both HTTP and FTP Tests. + Note that this is an abstract class, subclasses must implement + * stop_server() + * instantiate_server_by(protocol) + """ + + def __init__(self, name, pre_hook, test_params, post_hook, protocols): + """ + Define the class-wide variables (or attributes). + Attributes should not be defined outside __init__. + """ + self.name = name + self.pre_configs = pre_hook or {} # if pre_hook == None, then + # {} (an empty dict object) is + # passed to self.pre_configs + self.test_params = test_params or {} + self.post_configs = post_hook or {} + self.protocols = protocols + + self.servers = [] + self.domains = [] + self.port = -1 + + self.wget_options = '' + self.urls = [] + + self.tests_passed = True + self.init_test_env() + + self.ret_code = 0 + + def get_test_dir(self): + return self.name + '-test' + + def init_test_env(self): + test_dir = self.get_test_dir() + try: + os.mkdir(test_dir) + except FileExistsError: + shutil.rmtree(test_dir) + os.mkdir(test_dir) + os.chdir(test_dir) + + def get_domain_addr(self, addr): + # TODO if there's a multiple number of ports, wouldn't it be + # overridden to the port of the last invocation? + self.port = str(addr[1]) + + return '%s:%s' % (addr[0], self.port) + + def server_setup(self): + print_blue("Running Test %s" % self.name) + for protocol in self.protocols: + instance = self.instantiate_server_by(protocol) + self.servers.append(instance) + + # servers instantiated by different protocols may differ in + # ports and etc. + # so we should record different domains respect to servers. + domain = self.get_domain_addr(instance.server_address) + self.domains.append(domain) + + def exec_wget(self): + cmd_line = self.gen_cmd_line() + params = shlex.split(cmd_line) + print(params) + + if os.getenv("SERVER_WAIT"): + time.sleep(float(os.getenv("SERVER_WAIT"))) + + try: + ret_code = call(params) + except FileNotFoundError: + raise TestFailed("The Wget Executable does not exist at the " + "expected path.") + + return ret_code + + def gen_cmd_line(self): + test_path = os.path.abspath(".") + wget_path = os.path.abspath(os.path.join(test_path, + "..", '..', 'src', "wget")) + + cmd_line = '%s %s ' % (wget_path, self.wget_options) + for protocol, urls, domain in zip(self.protocols, + self.urls, + self.domains): + # zip is function for iterating multiple lists at the same time. + # e.g. for item1, item2 in zip([1, 5, 3], + # ['a', 'e', 'c']): + # print(item1, item2) + # generates the following output: + # 1 a + # 5 e + # 3 c + protocol = protocol.lower() + for url in urls: + cmd_line += '%s://%s/%s ' % (protocol, domain, url) + + print(cmd_line) + + return cmd_line + + def __test_cleanup(self): + os.chdir('..') + try: + if not os.getenv("NO_CLEANUP"): + shutil.rmtree(self.get_test_dir()) + except: + print ("Unknown Exception while trying to remove Test Environment.") + + def _exit_test (self): + self.__test_cleanup() + + def begin (self): + return 0 if self.tests_passed else 100 + + def call_test(self): + self.hook_call(self.test_params, 'Test Option') + + try: + self.ret_code = self.exec_wget() + except TestFailed as e: + raise e + finally: + self.stop_server() + + def do_test(self): + self.pre_hook_call() + self.call_test() + self.post_hook_call() + + def hook_call(self, configs, name): + for conf_name, conf_arg in configs.items(): + try: + # conf.find_conf(conf_name) returns the required conf class, + # then the class is instantiated with conf_arg, then the + # conf instance is called with this test instance itself to + # invoke the desired hook + conf.find_conf(conf_name)(conf_arg)(self) + except AttributeError: + self.stop_server() + raise TestFailed("%s %s not defined." % + (name, conf_name)) + + def pre_hook_call(self): + self.hook_call(self.pre_configs, 'Pre Test Function') + + def post_hook_call(self): + self.hook_call(self.post_configs, 'Post Test Function') + + def _replace_substring (self, string): + pattern = re.compile ('\{\{\w+\}\}') + match_obj = pattern.search (string) + if match_obj is not None: + rep = match_obj.group() + temp = getattr (self, rep.strip ('{}')) + string = string.replace (rep, temp) + return string + + def instantiate_server_by(self, protocol): + """ + Subclasses must override this method to actually instantiate servers + for test cases. + """ + raise NotImplementedError + + def stop_server(self): + """ + Subclasses must implement this method in order to stop certain + servers of different types. + """ + raise NotImplementedError + + @staticmethod + def get_server_rules(file_obj): + """ + The handling of expect header could be made much better when the + options are parsed in a true and better fashion. For an example, + see the commented portion in Test-basic-auth.py. + """ + server_rules = {} + for rule_name, rule in file_obj.rules.items(): + server_rules[rule_name] = conf.find_conf(rule_name)(rule) + return server_rules + + def __enter__(self): + """ + Initialization for with statement. + """ + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + """ + If the with statement got executed with no exception raised, then + exc_type, exc_val, exc_tb are all None. + """ + if exc_val: + self.tests_passed = False + if exc_type is TestFailed: + print_red('Error: %s.' % exc_val.error) + else: + print_red('Unhandled exception caught.') + print(exc_val) + traceback.print_tb(exc_tb) + self.__test_cleanup() + + return True -- cgit v1.2.1 From be78cba9e5fe5688ddf8ede2e42061035f0fca13 Mon Sep 17 00:00:00 2001 From: Darshit Shah Date: Wed, 23 Jul 2014 18:42:43 +0530 Subject: Support running tests through valgrind --- testenv/test/base_test.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'testenv/test/base_test.py') diff --git a/testenv/test/base_test.py b/testenv/test/base_test.py index 1c21c18d..369370a3 100644 --- a/testenv/test/base_test.py +++ b/testenv/test/base_test.py @@ -99,7 +99,11 @@ class BaseTest: wget_path = os.path.abspath(os.path.join(test_path, "..", '..', 'src', "wget")) - cmd_line = '%s %s ' % (wget_path, self.wget_options) + if os.getenv("VALGRIND_TESTS"): + valgrind_test = "valgrind --error-exitcode=301 --leak-check=full" + else: + valgrind_test = "" + cmd_line = '%s %s %s ' % (valgrind_test, wget_path, self.wget_options) for protocol, urls, domain in zip(self.protocols, self.urls, self.domains): -- cgit v1.2.1 From 03f8babefe4372acb2580873a637d51bc2086a2b Mon Sep 17 00:00:00 2001 From: Darshit Shah Date: Sat, 26 Jul 2014 23:45:57 +0530 Subject: Group common switches in test suite together Some command line switches are passed to Wget unconditionally. These switches should exist in a single place instead of being redundantly defined in each test file. We add the following two switches by default here: 1. --debug: This causes wget to be most verbose and output a lot of debugging information. Hence, if a test fails, the test log should provide valuable information. 2. --no-config: Users may have their own wgetrc files on the system. However, for the tests, we want Wget to run with vanilla settings. Hence, disbale loading any config files. --- testenv/test/base_test.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'testenv/test/base_test.py') diff --git a/testenv/test/base_test.py b/testenv/test/base_test.py index 369370a3..db65b5a9 100644 --- a/testenv/test/base_test.py +++ b/testenv/test/base_test.py @@ -98,12 +98,13 @@ class BaseTest: test_path = os.path.abspath(".") wget_path = os.path.abspath(os.path.join(test_path, "..", '..', 'src', "wget")) + wget_options = '--debug --no-config %s' % self.wget_options if os.getenv("VALGRIND_TESTS"): valgrind_test = "valgrind --error-exitcode=301 --leak-check=full" else: valgrind_test = "" - cmd_line = '%s %s %s ' % (valgrind_test, wget_path, self.wget_options) + cmd_line = '%s %s %s ' % (valgrind_test, wget_path, wget_options) for protocol, urls, domain in zip(self.protocols, self.urls, self.domains): -- cgit v1.2.1 From f8e9a64ec7445d22f67f1817043d5807c4766556 Mon Sep 17 00:00:00 2001 From: Darshit Shah Date: Fri, 8 Aug 2014 11:24:08 +0530 Subject: Documentation and code cleanup in test suite Add (lots) of documentation for various parts of the test suite in the form of Python docstrings. Also, clean up some of the redundant code and fix indentation issues. --- testenv/test/base_test.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'testenv/test/base_test.py') diff --git a/testenv/test/base_test.py b/testenv/test/base_test.py index db65b5a9..8bf8ea5f 100644 --- a/testenv/test/base_test.py +++ b/testenv/test/base_test.py @@ -28,9 +28,9 @@ class BaseTest: Attributes should not be defined outside __init__. """ self.name = name - self.pre_configs = pre_hook or {} # if pre_hook == None, then - # {} (an empty dict object) is - # passed to self.pre_configs + self.pre_configs = pre_hook or {} # if pre_hook == None, then + # {} (an empty dict object) is + # passed to self.pre_configs self.test_params = test_params or {} self.post_configs = post_hook or {} self.protocols = protocols -- cgit v1.2.1