From 8b83306d5425a1c1e3b498d03e5a2d3c201c38fe Mon Sep 17 00:00:00 2001 From: Zihang Chen Date: Thu, 24 Jul 2014 16:46:50 +0530 Subject: [PATCH] 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/ChangeLog | 143 ++++++++ testenv/README | 77 ++-- testenv/Test--https.py | 6 +- testenv/Test--spider-r.py | 3 +- testenv/Test-Content-disposition-2.py | 3 +- testenv/Test-Content-disposition.py | 3 +- testenv/Test-Head.py | 3 +- testenv/Test-O.py | 3 +- testenv/Test-Parallel-Proto.py | 6 +- testenv/Test-Post.py | 3 +- testenv/Test-Proto.py | 6 +- testenv/Test-auth-basic-fail.py | 3 +- testenv/Test-auth-basic.py | 3 +- testenv/Test-auth-both.py | 3 +- testenv/Test-auth-digest.py | 3 +- testenv/Test-auth-no-challenge-url.py | 3 +- testenv/Test-auth-no-challenge.py | 3 +- testenv/Test-auth-retcode.py | 3 +- testenv/Test-auth-with-content-disposition.py | 3 +- testenv/Test-c-full.py | 3 +- testenv/Test-cookie-401.py | 3 +- testenv/Test-cookie-domain-mismatch.py | 3 +- testenv/Test-cookie-expires.py | 3 +- testenv/Test-cookie.py | 3 +- testenv/WgetTest.py | 337 ------------------ testenv/conf/__init__.py | 47 +++ testenv/conf/authentication.py | 9 + testenv/conf/expect_header.py | 7 + testenv/conf/expected_files.py | 42 +++ testenv/conf/expected_ret_code.py | 16 + testenv/conf/files_crawled.py | 18 + testenv/conf/hook_sample.py | 15 + testenv/conf/local_files.py | 12 + testenv/conf/reject_header.py | 7 + testenv/conf/response.py | 7 + testenv/conf/rule_sample.py | 10 + testenv/conf/send_header.py | 7 + testenv/conf/server_conf.py | 11 + testenv/conf/server_files.py | 15 + testenv/conf/urls.py | 10 + testenv/conf/wget_commands.py | 10 + testenv/exc/__init__.py | 0 testenv/exc/test_failed.py | 7 + testenv/misc/__init__.py | 0 .../colour_terminal.py} | 8 + testenv/misc/wget_file.py | 16 + testenv/server/__init__.py | 0 testenv/server/ftp/__init__.py | 0 .../ftp/ftp_server.py} | 0 testenv/server/http/__init__.py | 0 .../http/http_server.py} | 0 testenv/test/__init__.py | 0 testenv/test/base_test.py | 226 ++++++++++++ testenv/test/http_test.py | 45 +++ 54 files changed, 793 insertions(+), 384 deletions(-) delete mode 100644 testenv/WgetTest.py create mode 100644 testenv/conf/__init__.py create mode 100644 testenv/conf/authentication.py create mode 100644 testenv/conf/expect_header.py create mode 100644 testenv/conf/expected_files.py create mode 100644 testenv/conf/expected_ret_code.py create mode 100644 testenv/conf/files_crawled.py create mode 100644 testenv/conf/hook_sample.py create mode 100644 testenv/conf/local_files.py create mode 100644 testenv/conf/reject_header.py create mode 100644 testenv/conf/response.py create mode 100644 testenv/conf/rule_sample.py create mode 100644 testenv/conf/send_header.py create mode 100644 testenv/conf/server_conf.py create mode 100644 testenv/conf/server_files.py create mode 100644 testenv/conf/urls.py create mode 100644 testenv/conf/wget_commands.py create mode 100644 testenv/exc/__init__.py create mode 100644 testenv/exc/test_failed.py create mode 100644 testenv/misc/__init__.py rename testenv/{ColourTerm.py => misc/colour_terminal.py} (68%) create mode 100644 testenv/misc/wget_file.py create mode 100644 testenv/server/__init__.py create mode 100644 testenv/server/ftp/__init__.py rename testenv/{FTPServer.py => server/ftp/ftp_server.py} (100%) create mode 100644 testenv/server/http/__init__.py rename testenv/{HTTPServer.py => server/http/http_server.py} (100%) create mode 100644 testenv/test/__init__.py create mode 100644 testenv/test/base_test.py create mode 100644 testenv/test/http_test.py diff --git a/testenv/ChangeLog b/testenv/ChangeLog index a3f71c0d..22b24687 100644 --- a/testenv/ChangeLog +++ b/testenv/ChangeLog @@ -1,3 +1,146 @@ +2014-03-13 Zihang Chen + + * base_test.py: + (CommonMethods): Rename to BaseTest. + (BaseTest): Implement __init__ method where the class-wide variables are + initialized. Also variable names like `xxx_list` is renamed to its plural + form, e.g. `server_list` => `servers`. + (BaseTest.init_test_env): Remove name argument due to its unnecessarity. + (BaseTest.get_test_dir): Because the path of the test directory is needed + in multiple methods, this method is implemented. + (BaseTest.get_domain_addr): Rewrite the return statement utilizing str + formatting (which is more Pythonic). + (BaseTest.get_cmd_line): Rename to gen_cmd_line. Change the variables with + capitcal characters to lower ones. Also, the nested for loop is rewritten + to a plain loop using the zip function. + (BaseTest.__gen_local_filesys): Rename to gen_local_fs_snapshot. Move to + ExpectedFiles in conf/expected_files.py and is marked as a static + method. Refactor to a less verbose implementation. + (BaseTest._check_downloaded_files): Rename to __call__ to agree with the + invocation in test case classes. Move to ExpectedFiles in + conf/expected_files.py. + (BaseTest.get_server_rules): Refactor to a more Pythonic form utilizing + dict.items() and is marked static. + (BaseTest.stop_server): (new method) an abstract method which should stop + the currently using servers. + (BaseTest.instantiate_server_by): (new method) an abstract method which + should instantiate a server instance according to the given argument. + (BaseTest.__enter__): (new method) method which initialize the context + manager + (BaseTest.__exit__): (new method) method that finilize the context manager + and deal with the exceptions during the execution of the with statement, + subclasses can override this method for extensibility + * http_test.py: + (HTTPTest.__init__): Add call to super.__init__. Default values of + pre_hook, test_params, post_hook are set to None to avoid a subtle bug of + Python. Argument servers is renamed to protocols. + (HTTPTest.Server_setup): Move to BaseTest and rename to server_setup. + Calls to pre_hook_call, call_test, post_hook_call are removed. + (HTTPTest.hook_call, pre_hook_call, call_test, post_hook_call): Move to + BaseTest for that both HTTP test cases and FTP test cases may use these + methods. + (HTTPTest.init_HTTP_Server, init_HTTPS_Server): Merge and rename to + instantiate_server_by to implement the abstract method in BaseTest. + (HTTPTest.stop_HTTP_Server): Rename to stop_server to implement the + abstract method in BaseTest. Also, pull out the part where remaining + requests are gathered into a new method request_remaining. + (BaseTest.act_retcode): Rename to ret_code because ExpectedRetCode is + moved out from BaseTest, so the name act_retcode is actually a bit + verbose. + * conf/expected_ret_code.py: + (ExpectedRetCode.__call__): Rewrite the str into a more readable form. + * conf/files_crawled.py: + (FilesCrawled.__call__): Refactor this method into a more Pythonic form + utilizing the zip function. + * conf/local_files.py: + (LocalFiles__call__): Rewrite this method with the recommended with + statement. + * conf/server_conf.py: + (ServerConf.__call__): Rewrite this method due to BaseTest.server_list is + renamed to BaseTest.servers. + * conf/server_files.py: + (ServerFiles.__call__): Refactor the nested for loop into a plain one + utilizing the zip function. + * conf/urls.py: + (URLs): Rename url_list to urls. + * conf/wget_commands.py: + (WgetCommands): Rename command_list to commands, rename test_obj.options + to test_obj.wget_options. + * Test--https.py, Test-Proto.py, Test-Parallel-Proto.py: Argument servers + is changed to protocols due to change in the signature of + HTTPTest.__init__. + +2014-03-13 Zihang Chen + + * test: (new package) package for test case classes + * WgetTest.py: Split into test/base_test.py and test/http_test.py. + * Test-*.py: Optimize the imports according to changes of WgetTest.py + +2014-03-13 Zihang Chen + + * server: (new package) package for the server classes + * server.http: (new package) package for HTTP server + * server.ftp: (new package) package for FTP server + * HTTPServer.py: Move to server/http/http_server.py. Also change the + CERTFILE to '../certs/wget-cert.pem'. + * FTPServer.py: Move to server/ftp/ftp_server.py. + * WgetTest.py: Optimize import respect to the server classes. + +2014-03-13 Zihang Chen + + * conf: (new package) package for rule classes and hook methods + * WgetTest.py: + (CommonMethods.Authentication): Move to conf/authentication.py. + (CommonMethods.ExpectHeader): Move to conf/expect_header.py. + (CommonMethods.RejectHeader): Move to conf/reject_header.py. + (CommonMethods.Response): Move to conf/response.py. + (CommonMethods.SendHeader): Move to conf/send_header.py. + (CommonMethods.ServerFiles): Move to conf/server_files.py. + (CommonMethods.LocalFiles): Move to conf/local_files.py. + (CommonMethods.ServerConf): Move to conf/server_conf.py. + (CommonMethods.WgetCommands): Move to conf/wget_commands.py. + (CommonMethods.Urls): Move to conf/urls.py. + (CommonMethods.ExpectedRetcode): Move to conf/expected_retcode.py. + (CommonMethods.ExpectedFiles): Move to conf/expected_files.py. + (CommonMethods.FilesCrawled): Move to conf/files_crawled.py. + (CommonMethods.__check_downloaded_files): Rename to + _check_downloaded_files, so that the method is callable from outside the + class. + (CommomMethods.get_server_rules): Modify so that it utilizes the conf + package. + (HTTPTest): Add a method hook_call(configs, name) to reduce duplications + in pre_hook_call, call_test and post_hook_call utilizing the conf package. + * conf/hook_sample.py: (new file) sample for hooks + * conf/rule_sample.py: (new file) sample for rules + * REAMDE: Update sections about customizing rules and hooks. + +2014-03-13 Zihang Chen + + * exc: (new package) package for miscellaneous exceptions + * WgetTest.py: Move TestFailed to exc/test_failed.py. + +2014-03-13 Zihang Chen + + * Test-Proto.py: Fix a typo (line 71: server to servers). + +2014-03-13 Zihang Chen + + * WgetTest.py: Move WgetFile to package misc. + * README: Modify documentation respect to WgetFile. + * Test-*.py: Optimize imports about WgetFile. + +2014-03-13 Zihang Chen + + * misc: (new package) package for miscellaneous modules + * ColourTerm.py: Move to package misc and rename to colour_terminal.py, + add print_color functions to reduce the use of string literals like + "BLUE", "RED" etc. + * WgetTest.py: + (CommonMethods.Server_setup): Change invocation to printer to print_blue. + (CommonMethods.FilesCrawled): Change invocation to printer to print_red. + (HTTPTest.__init__): Change invocations to printer to print_red and + print_green respectively. + 2014-01-02 Darshit Shah * Makefile.am: Add new Test--https.py to list of tests and EXTRA_DIST. Also replace all tabs with spaces in file for conformity. diff --git a/testenv/README b/testenv/README index 09f226a9..6cd52a4a 100644 --- a/testenv/README +++ b/testenv/README @@ -10,24 +10,34 @@ Run the './configure' command to generate the Makefile and then run 'make check' to execute the Test Suite. Use the '-j n' option with 'make check' to execute n tests simultaneously. -File List: +Structure: ================================================================================ - * HTTPServer.py: This file contains a custom, programmatically configurable - HTTP Server for testing Wget. It runs an instance of Python's http.server - module. + * server: This package contains custom programmatically configurable servers + (both HTTP and FTP) for testing Wget. The HTTP server runs an instance of + Python's http.server module. The FTP server is to be implemented. - * WgetTest.py: This file contains various functions and global variables for - each instance of the server that is initiated. It includes functions to - start and stop the server, to initialze the test environment and to cleanup - after a test. + * test: This package contains the test case classes for HTTP and FTP. The + test case classes includes methods for initializing and cleaning up of the + test environment. * Test-Proto.py: This is a prototype Test Case file. The file defines all the acceptable elements and their uses. Typically, one must copy this file and edit it for writing Test Cases. - * ColourTerm.py: A custom library for printing coloured output to the - terminal. Currently it only supports 4 colours in a *nix environment. + * exc: This package contains custom exception classes used in this test + suite. + + * conf: This package contains the configuration classes for servers to be + configured with. + + * misc: This package contains several helper modules used in this test + suite. + - colour_terminal.py: A custom module for printing coloured output to + the terminal. Currently it only supports 4 colours in a *nix + environment. + - wget_file.py: Module which contains WgetFile, which is a file data + container object. Working: ================================================================================ @@ -93,7 +103,8 @@ effort to get accustomed to. All Test Files MUST begin with the following Three Lines: #!/usr/bin/python3 from sys import exit -from WgetTest import {HTTPTest|FTPTest}, WgetFile +from WgetTest import {HTTPTest|FTPTest} +from misc.wget_file import WgetFile It is recommended that a small description of the Test Case is provided next. This would be very helpful to future contributors. @@ -101,7 +112,7 @@ Next, is the const variable, TEST_NAME that defines the name of the Test. Each File in the Test must be represented as a WgetFile object. The WgetFile Class has the following prototype: -WgetFile (String name, String contents, String timestamp, dict rules) +WgetFile (str name, str contents, str timestamp, dict rules) None except name is a mandatory paramter, one may pass only those parameters that are required by the File object. @@ -138,10 +149,11 @@ Both, the HTTPTest and FTPTest modules have the same prototype: pre_hook, test_options, post_hook, - servers + protocols } -name expects the string name, and is usually passed the TEST_NAME variable, -the three hooks, expect python dictionary objects and servers is an integer. +name should be a string, and is usually passed to the TEST_NAME variable, +the three hooks should be Python dict objects and protocols should be a list of +protocols, like [HTTP, HTTPS]. Valid File Rules: ================================================================================ @@ -190,7 +202,8 @@ The test_options dictionary defines the commands to be used when the Test is executed. The currently supported options are: * Urls : A list of the filenames that Wget must attempt to - download. The complete URL will be created and passed to Wget automatically. + download. The complete URL will be created and passed to Wget + automatically. (alias URLs) * WgetCommands : A string consisting of the various commandline switches sent to Wget upon invokation. Any data placed between {{ }} in this string will be replaced with the contents of self. before being passed to @@ -207,7 +220,7 @@ hooks are usually used to run checks on the data, files downloaded, return code, etc. The following hooks are currently supported: * ExpectedRetcode : This is an integer value of the ReturnCode with which - Wget is expected to exit. + Wget is expected to exit. (alias ExpectedRetCode) * ExpectedFiles : This is a list of WgetFile objects of the files that must exist locally on disk in the Test directory. * FilesCrawled : This requires a list of the Requests that the server is @@ -223,11 +236,31 @@ recommended method for writing new Test Case files is to copy Test-Proto.py and modify it to ones needs. In case you require any functionality that is not currently defined in List of -Rules defined above, you should add the required code in WgetTest.py. -In most cases, one requires a new Rule to be added for the Server to follow. -In such a case, create a new Class in WgetTest.py with the same name as the Rule -and define an __init__ () function to handle the data. A method must also be -defined in HTTPTest / FTPTest modules to handle the said Rule. +Rules defined above, you should implement a new class in the conf package. The +file name doesn't matter (though it's better to give it an appropriate name). +The new rule or hook class should be like this: +============================================ +from conf import rule + + +@rule() +class MyNewRule: + def __init__(self, rule_arg): + self.rule_arg = rule_arg + # your rule initialization code goes here +============================================ +from conf import hook + + +@hook() +class MyNewHook: + def __init__(self, hook_arg): + self.hook_arg = hook_arg + # your hook initialization code goes here + + def __call__(self, test_obj): + # your hook code goes here +============================================ Once a new Test File is created, it must be added to the TESTS variable in Makefile.am. This way the Test will be executed on running a 'make check'. diff --git a/testenv/Test--https.py b/testenv/Test--https.py index 17252b6b..55f417be 100755 --- a/testenv/Test--https.py +++ b/testenv/Test--https.py @@ -1,6 +1,8 @@ #!/usr/bin/env python3 from sys import exit -from WgetTest import HTTPTest, WgetFile, HTTPS, HTTP +from test.http_test import HTTPTest +from test.base_test import HTTP, HTTPS +from misc.wget_file import WgetFile """ This test ensures that Wget can download files from HTTPS Servers @@ -45,7 +47,7 @@ err = HTTPTest ( pre_hook=pre_test, test_params=test_options, post_hook=post_test, - servers=Servers + protocols=Servers ).begin () exit (err) diff --git a/testenv/Test--spider-r.py b/testenv/Test--spider-r.py index b770a9f2..df023d3a 100755 --- a/testenv/Test--spider-r.py +++ b/testenv/Test--spider-r.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 from sys import exit -from WgetTest import HTTPTest, WgetFile +from test.http_test import HTTPTest +from misc.wget_file import WgetFile """ This test executed Wget in Spider mode with recursive retrieval. diff --git a/testenv/Test-Content-disposition-2.py b/testenv/Test-Content-disposition-2.py index c2512e16..3bf49405 100755 --- a/testenv/Test-Content-disposition-2.py +++ b/testenv/Test-Content-disposition-2.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 from sys import exit -from WgetTest import HTTPTest, WgetFile +from test.http_test import HTTPTest +from misc.wget_file import WgetFile """ This test ensures that Wget parses the Content-Disposition header diff --git a/testenv/Test-Content-disposition.py b/testenv/Test-Content-disposition.py index 0a81dea2..ce245994 100755 --- a/testenv/Test-Content-disposition.py +++ b/testenv/Test-Content-disposition.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 from sys import exit -from WgetTest import HTTPTest, WgetFile +from test.http_test import HTTPTest +from misc.wget_file import WgetFile """ This test ensures that Wget parses the Content-Disposition header diff --git a/testenv/Test-Head.py b/testenv/Test-Head.py index 49aaa411..e3562529 100755 --- a/testenv/Test-Head.py +++ b/testenv/Test-Head.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 from sys import exit -from WgetTest import HTTPTest, WgetFile +from test.http_test import HTTPTest +from misc.wget_file import WgetFile """ This test ensures that Wget correctly handles responses to HEAD requests diff --git a/testenv/Test-O.py b/testenv/Test-O.py index 613fbcdb..784a229d 100755 --- a/testenv/Test-O.py +++ b/testenv/Test-O.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 from sys import exit -from WgetTest import HTTPTest, WgetFile +from test.http_test import HTTPTest +from misc.wget_file import WgetFile """ This test ensures that Wget correctly handles the -O command for output diff --git a/testenv/Test-Parallel-Proto.py b/testenv/Test-Parallel-Proto.py index e7aae2ef..b5e42bbe 100755 --- a/testenv/Test-Parallel-Proto.py +++ b/testenv/Test-Parallel-Proto.py @@ -1,6 +1,8 @@ #!/usr/bin/env python3 from sys import exit -from WgetTest import HTTPTest, WgetFile, HTTP, HTTPS +from test.http_test import HTTPTest +from misc.constants import HTTP, HTTPS +from misc.wget_file import WgetFile """ This is a Prototype Test File for multiple servers. @@ -46,7 +48,7 @@ err = HTTPTest ( pre_hook=pre_test, test_params=test_options, post_hook=post_test, - servers=Servers + protocols=Servers ).begin () exit (err) diff --git a/testenv/Test-Post.py b/testenv/Test-Post.py index 632326fd..8983454b 100755 --- a/testenv/Test-Post.py +++ b/testenv/Test-Post.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 from sys import exit -from WgetTest import HTTPTest, WgetFile +from test.http_test import HTTPTest +from misc.wget_file import WgetFile """ Simple test for HTTP POST Requests usiong the --method command diff --git a/testenv/Test-Proto.py b/testenv/Test-Proto.py index eaafdc13..d26b2bb3 100755 --- a/testenv/Test-Proto.py +++ b/testenv/Test-Proto.py @@ -1,6 +1,8 @@ #!/usr/bin/env python3 from sys import exit -from WgetTest import HTTPTest, WgetFile, HTTP, HTTPS +from test.http_test import HTTPTest +from misc.constants import HTTP, HTTPS +from misc.wget_file import WgetFile """ This is a Prototype Test File. @@ -67,7 +69,7 @@ err = HTTPTest ( pre_hook=pre_test, test_params=test_options, post_hook=post_test, - server=Servers + protocols=Servers ).begin () exit (err) diff --git a/testenv/Test-auth-basic-fail.py b/testenv/Test-auth-basic-fail.py index 894e96dd..263f7566 100755 --- a/testenv/Test-auth-basic-fail.py +++ b/testenv/Test-auth-basic-fail.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 from sys import exit -from WgetTest import HTTPTest, WgetFile +from test.http_test import HTTPTest +from misc.wget_file import WgetFile """ This test ensures that Wget returns the correct exit code when Basic diff --git a/testenv/Test-auth-basic.py b/testenv/Test-auth-basic.py index 96141e08..102bf8c6 100755 --- a/testenv/Test-auth-basic.py +++ b/testenv/Test-auth-basic.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 from sys import exit -from WgetTest import HTTPTest, WgetFile +from test.http_test import HTTPTest +from misc.wget_file import WgetFile """ This test ensures Wget's Basic Authorization Negotiation. diff --git a/testenv/Test-auth-both.py b/testenv/Test-auth-both.py index 98371348..2da2840f 100755 --- a/testenv/Test-auth-both.py +++ b/testenv/Test-auth-both.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 from sys import exit -from WgetTest import HTTPTest, WgetFile +from test.http_test import HTTPTest +from misc.wget_file import WgetFile """ This test ensures Wget's Basic Authorization Negotiation. diff --git a/testenv/Test-auth-digest.py b/testenv/Test-auth-digest.py index a66b2c97..f6d28c7c 100755 --- a/testenv/Test-auth-digest.py +++ b/testenv/Test-auth-digest.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 from sys import exit -from WgetTest import HTTPTest, WgetFile +from test.http_test import HTTPTest +from misc.wget_file import WgetFile """ This test ensures Wget's Digest Authorization Negotiation. diff --git a/testenv/Test-auth-no-challenge-url.py b/testenv/Test-auth-no-challenge-url.py index eb88ac5f..c39ebaa9 100755 --- a/testenv/Test-auth-no-challenge-url.py +++ b/testenv/Test-auth-no-challenge-url.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 from sys import exit -from WgetTest import HTTPTest, WgetFile +from test.http_test import HTTPTest +from misc.wget_file import WgetFile """ This test ensures Wget's Basic Authorization Negotiation, when credentials diff --git a/testenv/Test-auth-no-challenge.py b/testenv/Test-auth-no-challenge.py index 774bd59a..f02c0307 100755 --- a/testenv/Test-auth-no-challenge.py +++ b/testenv/Test-auth-no-challenge.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 from sys import exit -from WgetTest import HTTPTest, WgetFile +from test.http_test import HTTPTest +from misc.wget_file import WgetFile """ This test ensures Wget's Basic Authorization Negotiation, when the diff --git a/testenv/Test-auth-retcode.py b/testenv/Test-auth-retcode.py index adbcbb05..8719bd01 100755 --- a/testenv/Test-auth-retcode.py +++ b/testenv/Test-auth-retcode.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 from sys import exit -from WgetTest import HTTPTest, WgetFile +from test.http_test import HTTPTest +from misc.wget_file import WgetFile """ This test ensures that Wget returns the correct return code when sent diff --git a/testenv/Test-auth-with-content-disposition.py b/testenv/Test-auth-with-content-disposition.py index 50e08ba2..f74a9592 100755 --- a/testenv/Test-auth-with-content-disposition.py +++ b/testenv/Test-auth-with-content-disposition.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 from sys import exit -from WgetTest import HTTPTest, WgetFile +from test.http_test import HTTPTest +from misc.wget_file import WgetFile """ This test ensures that Wget handles Content-Disposition correctly when diff --git a/testenv/Test-c-full.py b/testenv/Test-c-full.py index 87ffc529..3cdcd768 100755 --- a/testenv/Test-c-full.py +++ b/testenv/Test-c-full.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 from sys import exit -from WgetTest import HTTPTest, WgetFile +from test.http_test import HTTPTest +from misc.wget_file import WgetFile """ Test Wget's response when the file requested already exists on disk with diff --git a/testenv/Test-cookie-401.py b/testenv/Test-cookie-401.py index 9ca96415..9488c34e 100755 --- a/testenv/Test-cookie-401.py +++ b/testenv/Test-cookie-401.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 from sys import exit -from WgetTest import HTTPTest, WgetFile +from test.http_test import HTTPTest +from misc.wget_file import WgetFile """ This test ensures that Wget stores the cookie even in the event of a diff --git a/testenv/Test-cookie-domain-mismatch.py b/testenv/Test-cookie-domain-mismatch.py index ae108d99..92487f43 100755 --- a/testenv/Test-cookie-domain-mismatch.py +++ b/testenv/Test-cookie-domain-mismatch.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 from sys import exit -from WgetTest import HTTPTest, WgetFile +from test.http_test import HTTPTest +from misc.wget_file import WgetFile """ This test ensures that Wget identifies bad servers trying to set cookies diff --git a/testenv/Test-cookie-expires.py b/testenv/Test-cookie-expires.py index 2ae3bf90..48a93b99 100755 --- a/testenv/Test-cookie-expires.py +++ b/testenv/Test-cookie-expires.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 from sys import exit -from WgetTest import HTTPTest, WgetFile +from test.http_test import HTTPTest +from misc.wget_file import WgetFile """ This test ensures that Wget handles Cookie expiry dates correctly. diff --git a/testenv/Test-cookie.py b/testenv/Test-cookie.py index 7f5b0932..b70316d8 100755 --- a/testenv/Test-cookie.py +++ b/testenv/Test-cookie.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 from sys import exit -from WgetTest import HTTPTest, WgetFile +from test.http_test import HTTPTest +from misc.wget_file import WgetFile """ This test ensures that Wget's cookie jar support works correctly. diff --git a/testenv/WgetTest.py b/testenv/WgetTest.py deleted file mode 100644 index 6470d936..00000000 --- a/testenv/WgetTest.py +++ /dev/null @@ -1,337 +0,0 @@ -import os -import shutil -import shlex -import sys -import traceback -import HTTPServer -import re -import time -from subprocess import call -from ColourTerm import printer -from difflib import unified_diff - -HTTP = "HTTP" -HTTPS = "HTTPS" - -""" A Custom Exception raised by the Test Environment. """ - -class TestFailed (Exception): - - def __init__ (self, error): - self.error = error - - -""" Class that defines methods common to both HTTP and FTP Tests. """ - -class CommonMethods: - TestFailed = TestFailed - - def init_test_env (self, name): - testDir = name + "-test" - try: - os.mkdir (testDir) - except FileExistsError: - shutil.rmtree (testDir) - os.mkdir (testDir) - os.chdir (testDir) - self.tests_passed = True - - def get_domain_addr (self, addr): - self.port = str (addr[1]) - return addr[0] + ":" + str(addr[1]) + "/" - - def exec_wget (self, options, urls, domain_list): - cmd_line = self.get_cmd_line (options, urls, domain_list) - params = shlex.split (cmd_line) - print (params) - if os.getenv ("SERVER_WAIT"): - time.sleep (float (os.getenv ("SERVER_WAIT"))) - try: - retcode = call (params) - except FileNotFoundError as filenotfound: - raise TestFailed ( - "The Wget Executable does not exist at the expected path") - return retcode - - def get_cmd_line (self, options, urls, domain_list): - TEST_PATH = os.path.abspath (".") - WGET_PATH = os.path.join (TEST_PATH, "..", "..", "src", "wget") - WGET_PATH = os.path.abspath (WGET_PATH) - cmd_line = WGET_PATH + " " + options + " " - for i in range (0, self.servers): - for url in urls[i]: - protocol = "http://" if self.server_types[i] is "HTTP" else "https://" - cmd_line += protocol + domain_list[i] + url + " " -# for url in urls: -# cmd_line += domain_list[0] + url + " " - print (cmd_line) - return cmd_line - - def __test_cleanup (self): - testDir = self.name + "-test" - os.chdir ('..') - try: - if os.getenv ("NO_CLEANUP") is None: - shutil.rmtree (testDir) - except Exception as ae: - 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 - - """ Methods to check if the Test Case passes or not. """ - - def __gen_local_filesys (self): - file_sys = dict () - for parent, dirs, files in os.walk ('.'): - for name in files: - onefile = dict () - # Create the full path to file, removing the leading ./ - # Might not work on non-unix systems. Someone please test. - filepath = os.path.join (parent, name) - file_handle = open (filepath, 'r') - file_content = file_handle.read () - onefile['content'] = file_content - filepath = filepath[2:] - file_sys[filepath] = onefile - file_handle.close () - return file_sys - - - def __check_downloaded_files (self, exp_filesys): - local_filesys = self.__gen_local_filesys () - for files in exp_filesys: - if files.name in local_filesys: - local_file = local_filesys.pop (files.name) - if files.content != local_file ['content']: - for line in unified_diff (local_file['content'], files.content, fromfile="Actual", tofile="Expected"): - sys.stderr.write (line) - raise TestFailed ("Contents of " + files.name + " do not match") - else: - raise TestFailed ("Expected file " + files.name + " not found") - if local_filesys: - print (local_filesys) - raise TestFailed ("Extra files downloaded.") - - 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 - - - """ Test Rule Definitions """ - """ This should really be taken out soon. All this extra stuff to ensure - re-use of old code is crap. Someone needs to re-write it. The new rework - branch is much better written, but integrating it requires effort. - All these classes should never exist. The whole server needs to modified. - """ - - class Authentication: - def __init__ (self, auth_obj): - self.auth_type = auth_obj['Type'] - self.auth_user = auth_obj['User'] - self.auth_pass = auth_obj['Pass'] - - class ExpectHeader: - def __init__ (self, header_obj): - self.headers = header_obj - - class RejectHeader: - def __init__ (self, header_obj): - self.headers = header_obj - - class Response: - def __init__ (self, retcode): - self.response_code = retcode - - class SendHeader: - def __init__ (self, header_obj): - self.headers = header_obj - - def get_server_rules (self, 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 = dict () - for rule in file_obj.rules: - r_obj = getattr (self, rule) (file_obj.rules[rule]) - server_rules[rule] = r_obj - return server_rules - - """ Pre-Test Hook Function Calls """ - - def ServerFiles (self, server_files): - for i in range (0, self.servers): - file_list = dict () - server_rules = dict () - for file_obj in server_files[i]: - content = self._replace_substring (file_obj.content) - file_list[file_obj.name] = content - rule_obj = self.get_server_rules (file_obj) - server_rules[file_obj.name] = rule_obj - self.server_list[i].server_conf (file_list, server_rules) - - def LocalFiles (self, local_files): - for file_obj in local_files: - file_handler = open (file_obj.name, "w") - file_handler.write (file_obj.content) - file_handler.close () - - def ServerConf (self, server_settings): - for i in range (0, self.servers): - self.server_list[i].server_sett (server_settings) - - """ Test Option Function Calls """ - - def WgetCommands (self, command_list): - self.options = self._replace_substring (command_list) - - def Urls (self, url_list): - self.urls = url_list - - """ Post-Test Hook Function Calls """ - - def ExpectedRetcode (self, retcode): - if self.act_retcode != retcode: - pr = "Return codes do not match.\nExpected: " + str(retcode) + "\nActual: " + str(self.act_retcode) - raise TestFailed (pr) - - def ExpectedFiles (self, exp_filesys): - self.__check_downloaded_files (exp_filesys) - - def FilesCrawled (self, Request_Headers): - for i in range (0, self.servers): - headers = set(Request_Headers[i]) - o_headers = self.Request_remaining[i] - header_diff = headers.symmetric_difference (o_headers) - if len(header_diff) is not 0: - printer ("RED", str (header_diff)) - raise TestFailed ("Not all files were crawled correctly") - - -""" Class for HTTP Tests. """ - -class HTTPTest (CommonMethods): - -# Temp Notes: It is expected that when pre-hook functions are executed, only an empty test-dir exists. -# pre-hook functions are executed just prior to the call to Wget is made. -# post-hook functions will be executed immediately after the call to Wget returns. - - def __init__ ( - self, - name="Unnamed Test", - pre_hook=dict(), - test_params=dict(), - post_hook=dict(), - servers=[HTTP] - ): - try: - self.Server_setup (name, pre_hook, test_params, post_hook, servers) - except TestFailed as tf: - printer ("RED", "Error: " + tf.error) - self.tests_passed = False - except Exception as ae: - printer ("RED", "Unhandled Exception Caught.") - print ( ae.__str__ ()) - traceback.print_exc () - self.tests_passed = False - else: - printer ("GREEN", "Test Passed") - finally: - self._exit_test () - - def Server_setup (self, name, pre_hook, test_params, post_hook, servers): - self.name = name - self.server_types = servers - self.servers = len (servers) - printer ("BLUE", "Running Test " + self.name) - self.init_test_env (name) - self.server_list = list() - self.domain_list = list() - for server_type in servers: - server_inst = getattr (self, "init_" + server_type + "_Server") () - self.server_list.append (server_inst) - domain = self.get_domain_addr (server_inst.server_address) - self.domain_list.append (domain) - #self.server = self.init_HTTP_Server () - #self.domain = self.get_domain_addr (self.server.server_address) - - self.pre_hook_call (pre_hook) - self.call_test (test_params) - self.post_hook_call (post_hook) - - def pre_hook_call (self, pre_hook): - for pre_hook_func in pre_hook: - try: - assert hasattr (self, pre_hook_func) - except AssertionError as ae: - self.stop_HTTP_Server () - raise TestFailed ("Pre Test Function " + pre_hook_func + " not defined.") - getattr (self, pre_hook_func) (pre_hook[pre_hook_func]) - - def call_test (self, test_params): - for test_func in test_params: - try: - assert hasattr (self, test_func) - except AssertionError as ae: - self.stop_HTTP_Server () - raise TestFailed ("Test Option " + test_func + " unknown.") - getattr (self, test_func) (test_params[test_func]) - - try: - self.act_retcode = self.exec_wget (self.options, self.urls, self.domain_list) - except TestFailed as tf: - self.stop_HTTP_Server () - raise TestFailed (tf.__str__ ()) - self.stop_HTTP_Server () - - def post_hook_call (self, post_hook): - for post_hook_func in post_hook: - try: - assert hasattr (self, post_hook_func) - except AssertionError as ae: - raise TestFailed ("Post Test Function " + post_hook_func + " not defined.") - getattr (self, post_hook_func) (post_hook[post_hook_func]) - - def init_HTTP_Server (self): - server = HTTPServer.HTTPd () - server.start () - return server - - def init_HTTPS_Server (self): - server = HTTPServer.HTTPSd () - server.start () - return server - - def stop_HTTP_Server (self): - self.Request_remaining = list () - for server in self.server_list: - server_req = server.server_inst.get_req_headers () - self.Request_remaining.append (server_req) - server.server_inst.shutdown () - -""" WgetFile is a File Data Container object """ - -class WgetFile: - - def __init__ ( - self, - name, - content="Test Contents", - timestamp=None, - rules=dict() - ): - self.name = name - self.content = content - self.timestamp = timestamp - self.rules = rules - -# vim: set ts=4 sts=4 sw=4 tw=80 et : diff --git a/testenv/conf/__init__.py b/testenv/conf/__init__.py new file mode 100644 index 00000000..156e9b60 --- /dev/null +++ b/testenv/conf/__init__.py @@ -0,0 +1,47 @@ +import os + +# this file implements the mechanism of conf class auto-registration, +# don't modify this file if you have no idea what you're doing + +def gen_hook(): + hook_table = {} + + class Wrapper: + """ + Decorator class which implements the conf class registration. + """ + def __init__(self, alias=None): + self.alias = alias + + def __call__(self, cls): + # register the class object with the name of the class + hook_table[cls.__name__] = cls + if self.alias: + # also register the alias of the class + hook_table[self.alias] = cls + + return cls + + def find_hook(name): + if name in hook_table: + return hook_table[name] + else: + raise AttributeError + + return Wrapper, find_hook + +_register, find_conf = gen_hook() +hook = rule = _register + +__all__ = ['hook', 'rule'] + +for module in os.listdir(os.path.dirname(__file__)): + # import every module under this package except __init__.py, + # so that the decorator `register` applies + # (nothing happens if the script is not loaded) + if module != '__init__.py' and module.endswith('.py'): + module_name = module[:-3] + mod = __import__('%s.%s' % (__name__, module_name), + globals(), + locals()) + __all__.append(module_name) diff --git a/testenv/conf/authentication.py b/testenv/conf/authentication.py new file mode 100644 index 00000000..58cbaff0 --- /dev/null +++ b/testenv/conf/authentication.py @@ -0,0 +1,9 @@ +from conf import rule + + +@rule() +class Authentication: + def __init__ (self, auth_obj): + self.auth_type = auth_obj['Type'] + self.auth_user = auth_obj['User'] + self.auth_pass = auth_obj['Pass'] diff --git a/testenv/conf/expect_header.py b/testenv/conf/expect_header.py new file mode 100644 index 00000000..87b0e24d --- /dev/null +++ b/testenv/conf/expect_header.py @@ -0,0 +1,7 @@ +from conf import rule + + +@rule() +class ExpectHeader: + def __init__(self, header_obj): + self.headers = header_obj diff --git a/testenv/conf/expected_files.py b/testenv/conf/expected_files.py new file mode 100644 index 00000000..a8b2ee19 --- /dev/null +++ b/testenv/conf/expected_files.py @@ -0,0 +1,42 @@ +from difflib import unified_diff +import os +import sys +from conf import hook +from exc.test_failed import TestFailed + + +@hook() +class ExpectedFiles: + def __init__(self, expected_fs): + self.expected_fs = expected_fs + + @staticmethod + def gen_local_fs_snapshot(): + snapshot = {} + for parent, dirs, files in os.walk('.'): + for name in files: + f = {'content': ''} + file_path = os.path.join(parent, name) + with open(file_path) as fp: + f['content'] = fp.read() + snapshot[file_path[2:]] = f + + return snapshot + + def __call__(self, test_obj): + local_fs = self.gen_local_fs_snapshot() + for file in self.expected_fs: + if file.name in local_fs: + local_file = local_fs.pop(file.name) + if file.content != local_file['content']: + for line in unified_diff(local_file['content'], + file.content, + fromfile='Actual', + tofile='Expected'): + print(line, file=sys.stderr) + raise TestFailed('Contents of %s do not match.' % file.name) + else: + raise TestFailed('Expected file %s not found.' % file.name) + if local_fs: + print(local_fs) + raise TestFailed('Extra files downloaded.') diff --git a/testenv/conf/expected_ret_code.py b/testenv/conf/expected_ret_code.py new file mode 100644 index 00000000..5f53f8c1 --- /dev/null +++ b/testenv/conf/expected_ret_code.py @@ -0,0 +1,16 @@ +from exc.test_failed import TestFailed +from conf import hook + + +@hook(alias='ExpectedRetcode') +class ExpectedRetCode: + def __init__(self, expected_ret_code): + self.expected_ret_code = expected_ret_code + + def __call__(self, test_obj): + if test_obj.ret_code != self.expected_ret_code: + failure = "Return codes do not match.\n" \ + "Expected: %s\n" \ + "Actual: %s" % (self.expected_ret_code, + test_obj.ret_code) + raise TestFailed(failure) diff --git a/testenv/conf/files_crawled.py b/testenv/conf/files_crawled.py new file mode 100644 index 00000000..ad6d0f17 --- /dev/null +++ b/testenv/conf/files_crawled.py @@ -0,0 +1,18 @@ +from misc.colour_terminal import print_red +from conf import hook +from exc.test_failed import TestFailed + + +@hook() +class FilesCrawled: + def __init__(self, request_headers): + self.request_headers = request_headers + + def __call__(self, test_obj): + for headers, remaining in zip(map(set, self.request_headers), + test_obj.request_remaining()): + diff = headers.symmetric_difference(remaining) + + if diff: + print_red(diff) + raise TestFailed('Not all files were crawled correctly.') diff --git a/testenv/conf/hook_sample.py b/testenv/conf/hook_sample.py new file mode 100644 index 00000000..f48942f0 --- /dev/null +++ b/testenv/conf/hook_sample.py @@ -0,0 +1,15 @@ +from exc.test_failed import TestFailed +from conf import hook + +# this file is a hook example + +@hook(alias='SampleHookAlias') +class SampleHook: + def __init__(self, sample_hook_arg): + # do conf initialization here + self.arg = sample_hook_arg + + def __call__(self, test_obj): + # implement hook here + # if you need the test case instance, refer to test_obj + pass diff --git a/testenv/conf/local_files.py b/testenv/conf/local_files.py new file mode 100644 index 00000000..1eb3e4e5 --- /dev/null +++ b/testenv/conf/local_files.py @@ -0,0 +1,12 @@ +from conf import hook + + +@hook() +class LocalFiles: + def __init__(self, local_files): + self.local_files = local_files + + def __call__(self, _): + for f in self.local_files: + with open(f.name, 'w') as fp: + fp.write(f.content) diff --git a/testenv/conf/reject_header.py b/testenv/conf/reject_header.py new file mode 100644 index 00000000..1f451456 --- /dev/null +++ b/testenv/conf/reject_header.py @@ -0,0 +1,7 @@ +from conf import rule + + +@rule() +class RejectHeader: + def __init__ (self, header_obj): + self.headers = header_obj diff --git a/testenv/conf/response.py b/testenv/conf/response.py new file mode 100644 index 00000000..23d55de5 --- /dev/null +++ b/testenv/conf/response.py @@ -0,0 +1,7 @@ +from conf import rule + + +@rule() +class Response: + def __init__(self, ret_code): + self.response_code = ret_code diff --git a/testenv/conf/rule_sample.py b/testenv/conf/rule_sample.py new file mode 100644 index 00000000..6345a3c5 --- /dev/null +++ b/testenv/conf/rule_sample.py @@ -0,0 +1,10 @@ +from conf import rule + + +@rule(alias='SampleRuleAlias') +class SampleRule: + def __init__(self, rule): + # do rule initialization here + # you may also need to implement a method the same name of this + # class in server/protocol/protocol_server.py to apply this rule. + self.rule = rule diff --git a/testenv/conf/send_header.py b/testenv/conf/send_header.py new file mode 100644 index 00000000..61dbc0ec --- /dev/null +++ b/testenv/conf/send_header.py @@ -0,0 +1,7 @@ +from conf import rule + + +@rule() +class SendHeader: + def __init__(self, header_obj): + self.headers = header_obj diff --git a/testenv/conf/server_conf.py b/testenv/conf/server_conf.py new file mode 100644 index 00000000..c287bd70 --- /dev/null +++ b/testenv/conf/server_conf.py @@ -0,0 +1,11 @@ +from conf import hook + + +@hook() +class ServerConf: + def __init__(self, server_settings): + self.server_settings = server_settings + + def __call__(self, test_obj): + for server in test_obj.servers: + server.server_sett(self.server_settings) diff --git a/testenv/conf/server_files.py b/testenv/conf/server_files.py new file mode 100644 index 00000000..bf6c1633 --- /dev/null +++ b/testenv/conf/server_files.py @@ -0,0 +1,15 @@ +from conf import hook + + +@hook() +class ServerFiles: + def __init__(self, server_files): + self.server_files = server_files + + def __call__(self, test_obj): + for server, files in zip(test_obj.servers, self.server_files): + rules = {f.name: test_obj.get_server_rules(f) + for f in files} + files = {f.name: test_obj._replace_substring(f.content) + for f in files} + server.server_conf(files, rules) diff --git a/testenv/conf/urls.py b/testenv/conf/urls.py new file mode 100644 index 00000000..60015867 --- /dev/null +++ b/testenv/conf/urls.py @@ -0,0 +1,10 @@ +from conf import hook + + +@hook(alias='Urls') +class URLs: + def __init__(self, urls): + self.urls = urls + + def __call__(self, test_obj): + test_obj.urls = self.urls diff --git a/testenv/conf/wget_commands.py b/testenv/conf/wget_commands.py new file mode 100644 index 00000000..a326bb56 --- /dev/null +++ b/testenv/conf/wget_commands.py @@ -0,0 +1,10 @@ +from conf import hook + + +@hook() +class WgetCommands: + def __init__(self, commands): + self.commands = commands + + def __call__(self, test_obj): + test_obj.wget_options = test_obj._replace_substring(self.commands) diff --git a/testenv/exc/__init__.py b/testenv/exc/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/testenv/exc/test_failed.py b/testenv/exc/test_failed.py new file mode 100644 index 00000000..de5e02a2 --- /dev/null +++ b/testenv/exc/test_failed.py @@ -0,0 +1,7 @@ + +class TestFailed(Exception): + + """ A Custom Exception raised by the Test Environment. """ + + def __init__ (self, error): + self.error = error diff --git a/testenv/misc/__init__.py b/testenv/misc/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/testenv/ColourTerm.py b/testenv/misc/colour_terminal.py similarity index 68% rename from testenv/ColourTerm.py rename to testenv/misc/colour_terminal.py index d8f67692..206ffa30 100644 --- a/testenv/ColourTerm.py +++ b/testenv/misc/colour_terminal.py @@ -1,3 +1,4 @@ +from functools import partial import platform from os import getenv @@ -20,4 +21,11 @@ def printer (color, string): else: print (string) + +print_blue = partial(printer, 'BLUE') +print_red = partial(printer, 'RED') +print_green = partial(printer, 'GREEN') +print_purple = partial(printer, 'PURPLE') +print_yellow = partial(printer, 'YELLOW') + # vim: set ts=8 sw=3 tw=0 et : diff --git a/testenv/misc/wget_file.py b/testenv/misc/wget_file.py new file mode 100644 index 00000000..027dcedf --- /dev/null +++ b/testenv/misc/wget_file.py @@ -0,0 +1,16 @@ + +class WgetFile: + + """ WgetFile is a File Data Container object """ + + def __init__ ( + self, + name, + content="Test Contents", + timestamp=None, + rules=None + ): + self.name = name + self.content = content + self.timestamp = timestamp + self.rules = rules or {} diff --git a/testenv/server/__init__.py b/testenv/server/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/testenv/server/ftp/__init__.py b/testenv/server/ftp/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/testenv/FTPServer.py b/testenv/server/ftp/ftp_server.py similarity index 100% rename from testenv/FTPServer.py rename to testenv/server/ftp/ftp_server.py diff --git a/testenv/server/http/__init__.py b/testenv/server/http/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/testenv/HTTPServer.py b/testenv/server/http/http_server.py similarity index 100% rename from testenv/HTTPServer.py rename to testenv/server/http/http_server.py diff --git a/testenv/test/__init__.py b/testenv/test/__init__.py new file mode 100644 index 00000000..e69de29b 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 diff --git a/testenv/test/http_test.py b/testenv/test/http_test.py new file mode 100644 index 00000000..fe2254de --- /dev/null +++ b/testenv/test/http_test.py @@ -0,0 +1,45 @@ +from misc.colour_terminal import print_green +from server.http.http_server import HTTPd, HTTPSd +from test.base_test import BaseTest, HTTP, HTTPS + + +class HTTPTest(BaseTest): + + """ Class for HTTP Tests. """ + + # Temp Notes: It is expected that when pre-hook functions are executed, only an empty test-dir exists. + # pre-hook functions are executed just prior to the call to Wget is made. + # post-hook functions will be executed immediately after the call to Wget returns. + + def __init__(self, + name="Unnamed Test", + pre_hook=None, + test_params=None, + post_hook=None, + protocols=(HTTP,)): + super(HTTPTest, self).__init__(name, + pre_hook, + test_params, + post_hook, + protocols) + with self: + # if any exception occurs, self.__exit__ will be immediately called + self.server_setup() + self.do_test() + print_green('Test Passed.') + + def instantiate_server_by(self, protocol): + server = {HTTP: HTTPd, + HTTPS: HTTPSd}[protocol]() + server.start() + + return server + + def request_remaining(self): + return [s.server_inst.get_req_headers() + for s in self.servers] + + def stop_server(self): + for server in self.servers: + server.server_inst.shutdown() +# vim: set ts=4 sts=4 sw=4 tw=80 et :