source: mod_gnutls/test/runtest.py @ e2200db

asyncioproxy-ticket
Last change on this file since e2200db was e2200db, checked in by Fiona Klute <fiona.klute@…>, 20 months ago

Test suite: Always run Apache with "-DFOREGROUND"

This simplifies process management a lot.

  • Property mode set to 100755
File size: 8.8 KB
RevLine 
[05984a0]1#!/usr/bin/python3
2# PYTHON_ARGCOMPLETE_OK
3
4# Copyright 2019 Fiona Klute
5#
6# Licensed under the Apache License, Version 2.0 (the "License");
7# you may not use this file except in compliance with the License.
8# You may obtain a copy of the License at
9#
10#     http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS,
14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15# See the License for the specific language governing permissions and
16# limitations under the License.
17
18import contextlib
19import os
20import os.path
21import subprocess
22import sys
[92cf138]23import tempfile
[9a48691]24from unittest import SkipTest
[05984a0]25
[0909c92]26import mgstest.hooks
[05984a0]27from mgstest import lockfile, TestExpectationFailed
28from mgstest.services import ApacheService, TestService
29from mgstest.tests import run_test_conf
30
31
32
33def find_testdir(number, dir):
34    """Find the configuration directory for a test based on its
35    number. The given directory must contain exactly one directory
36    with a name matching "NUMBER_*", otherwise a LookupError is
37    raised.
38
39    """
40    with os.scandir(dir) as it:
41        found = None
42        for entry in it:
43            if entry.is_dir():
44                num = int(entry.name.split('_', maxsplit=1)[0])
45                if number == num:
46                    if found:
47                        # duplicate numbers are an error
48                        raise LookupError('Multiple directories found for '
[079859e]49                                          f'test number {number}: '
[05984a0]50                                          f'{found.name} and {entry.name}')
51                    else:
52                        found = entry
53        if found == None:
54            raise LookupError('No test directory found for test number '
[079859e]55                              f'{number}!')
[05984a0]56        else:
57            return (found.path, found.name)
58
[92cf138]59def temp_logfile():
60    return tempfile.SpooledTemporaryFile(max_size=4096, mode='w+',
61                                         prefix='mod_gnutls', suffix=".log")
62
63
[05984a0]64
65
66def check_ocsp_responder():
67    # Check if OCSP responder works
68    issuer_cert = 'authority/x509.pem'
69    check_cert = 'authority/server/x509.pem'
70    command = ['ocsptool', '--ask', '--nonce',
71               '--load-issuer', issuer_cert,
72               '--load-cert', check_cert]
73    return subprocess.run(command).returncode == 0
74
75def check_msva():
76    # Check if MSVA is up
77    cert_file = 'authority/client/x509.pem'
78    uid_file = 'authority/client/uid'
79    with open(uid_file, 'r') as file:
80        uid = file.read().strip()
81        command = ['msva-query-agent', 'https', uid, 'x509pem', 'client']
82        with open(cert_file, 'r') as cert:
83            return subprocess.run(command, stdin=cert).returncode == 0
84
85
86
[ac516aa]87def main(args):
[05984a0]88    # The Automake environment always provides srcdir, the default is
89    # for manual use.
90    srcdir = os.path.realpath(os.environ.get('srcdir', '.'))
91    # ensure environment srcdir is absolute
92    os.environ['srcdir'] = srcdir
93
94    # Find the configuration directory for the test in
95    # ${srcdir}/tests/, based on the test number.
96    testdir, testname = find_testdir(args.test_number,
97                                     os.path.join(srcdir, 'tests'))
98    print(f'Found test {testname}, test dir is {testdir}')
99    os.environ['TEST_NAME'] = testname
100
[0909c92]101    # Load test case hooks (if any)
102    plugin_path = os.path.join(testdir, 'hooks.py')
103    plugin = mgstest.hooks.load_hooks_plugin(plugin_path)
104
[05984a0]105    # PID file name varies depending on whether we're using
106    # namespaces.
107    #
108    # TODO: Check if having the different names is really necessary.
109    pidaffix = ''
110    if 'USE_TEST_NAMESPACE' in os.environ:
111        pidaffix = f'-{testname}'
112
[72ebe64]113    valgrind_log = None
114    if args.valgrind:
115        valgrind_log = os.path.join('logs', f'valgrind-{testname}.log')
116
[05984a0]117    # Define the available services
118    apache = ApacheService(config=os.path.join(testdir, 'apache.conf'),
[72ebe64]119                           pidfile=f'apache2{pidaffix}.pid',
120                           valgrind_log=valgrind_log)
[05984a0]121    backend = ApacheService(config=os.path.join(testdir, 'backend.conf'),
122                            pidfile=f'backend{pidaffix}.pid')
123    ocsp = ApacheService(config=os.path.join(testdir, 'ocsp.conf'),
124                         pidfile=f'ocsp{pidaffix}.pid',
125                         check=check_ocsp_responder)
126    msva = TestService(start=['monkeysphere-validation-agent'],
127                       env={'GNUPGHOME': 'msva.gnupghome',
128                            'MSVA_KEYSERVER_POLICY': 'never'},
129                       condition=lambda: 'USE_MSVA' in os.environ,
130                       check=check_msva)
131
132    # background services: must be ready before the main apache
133    # instance is started
134    bg_services = [backend, ocsp, msva]
135
[7e10018]136    # If VERBOSE is enabled, log the HTTPD build configuration
137    if 'VERBOSE' in os.environ:
138        apache2 = os.environ.get('APACHE2', 'apache2')
139        subprocess.run([apache2, '-f', f'{srcdir}/base_apache.conf', '-V'],
140                       check=True)
[05984a0]141
[8666b50]142    # This hook may modify the environment as needed for the test.
[60ed7d1]143    cleanup_callback = None
[9a48691]144    try:
145        if plugin.prepare_env:
[60ed7d1]146            cleanup_callback = plugin.prepare_env()
[9a48691]147    except SkipTest as skip:
148        print(f'Skipping: {skip!s}')
149        sys.exit(77)
[05984a0]150
151    if 'USE_MSVA' in os.environ:
152        os.environ['MONKEYSPHERE_VALIDATION_AGENT_SOCKET'] = \
153            f'http://127.0.0.1:{os.environ["MSVA_PORT"]}'
154
155    with contextlib.ExitStack() as service_stack:
[60ed7d1]156        if cleanup_callback:
157            service_stack.callback(cleanup_callback)
[05984a0]158        service_stack.enter_context(lockfile('test.lock', nolock='MGS_NETNS_ACTIVE' in os.environ))
159        service_stack.enter_context(ocsp.run())
160        service_stack.enter_context(backend.run())
161        service_stack.enter_context(msva.run())
162
163        # TEST_SERVICE_MAX_WAIT is in milliseconds
164        wait_timeout = \
165            int(os.environ.get('TEST_SERVICE_MAX_WAIT', 10000)) / 1000
166        for s in bg_services:
167            if s.condition():
168                s.wait_ready(timeout=wait_timeout)
169
170        # special case: expected to fail in a few cases
[e2200db]171        service_stack.enter_context(apache.run())
172        failed = apache.wait_ready()
173        if os.path.exists(os.path.join(testdir, 'fail.server')):
174            if failed:
[05984a0]175                print('Apache server failed to start as expected',
176                      file=sys.stderr)
177            else:
[e2200db]178                raise TestExpectationFailed(
179                    'Server start did not fail as expected!')
[05984a0]180
181        # Set TEST_TARGET for the request. Might be replaced with a
182        # parameter later.
183        if 'TARGET_IP' in os.environ:
184            os.environ['TEST_TARGET'] = os.environ['TARGET_IP']
185        else:
186            os.environ['TEST_TARGET'] = os.environ['TEST_HOST']
187
188        # Run the test connections
[4de8cd3]189        if plugin.run_connection:
190            plugin.run_connection(testname,
191                                  conn_log=args.log_connection,
192                                  response_log=args.log_responses)
193        else:
194            with open(os.path.join(testdir, 'test.yml'), 'r') as test_conf:
[0909c92]195                run_test_conf(test_conf,
196                              float(os.environ.get('TEST_QUERY_TIMEOUT', 5.0)),
[b457e67]197                              conn_log=args.log_connection,
198                              response_log=args.log_responses)
[05984a0]199
[dd91d9c]200    # run extra checks the test's hooks.py might define
201    if plugin.post_check:
[92cf138]202        args.log_connection.seek(0)
203        args.log_responses.seek(0)
[b457e67]204        plugin.post_check(conn_log=args.log_connection,
205                          response_log=args.log_responses)
[ac516aa]206
207
208
209if __name__ == "__main__":
210    import argparse
211    parser = argparse.ArgumentParser(
212        description='Run a mod_gnutls server test')
213    parser.add_argument('--test-number', type=int,
214                        required=True, help='load YAML test configuration')
[b457e67]215    parser.add_argument('--log-connection', type=argparse.FileType('w+'),
[92cf138]216                        default=temp_logfile(),
[ac516aa]217                        help='write connection log to this file')
[b457e67]218    parser.add_argument('--log-responses', type=argparse.FileType('w+'),
[92cf138]219                        default=temp_logfile(),
[ac516aa]220                        help='write HTTP responses to this file')
[72ebe64]221    parser.add_argument('--valgrind', action='store_true',
222                        help='run primary Apache instance with Valgrind')
[ac516aa]223
224    # enable bash completion if argcomplete is available
225    try:
226        import argcomplete
227        argcomplete.autocomplete(parser)
228    except ImportError:
229        pass
230
231    args = parser.parse_args()
232
[b457e67]233    with contextlib.ExitStack() as stack:
[92cf138]234        stack.enter_context(contextlib.closing(args.log_connection))
235        stack.enter_context(contextlib.closing(args.log_responses))
[b457e67]236        main(args)
Note: See TracBrowser for help on using the repository browser.