source: mod_gnutls/test/runtest.py @ 99c61f9

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

Add configure option --enable-valgrind-test to run tests with Valgrind

Also includes suppressions for known issues not caused by mod_gnutls.

  • Property mode set to 100755
File size: 9.0 KB
Line 
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
23import tempfile
24from unittest import SkipTest
25
26import mgstest.hooks
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 '
49                                          f'test number {number}: '
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 '
55                              f'{number}!')
56        else:
57            return (found.path, found.name)
58
59def temp_logfile():
60    return tempfile.SpooledTemporaryFile(max_size=4096, mode='w+',
61                                         prefix='mod_gnutls', suffix=".log")
62
63
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
87def main(args):
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
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
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
113    valgrind_log = None
114    if args.valgrind:
115        valgrind_log = os.path.join('logs', f'valgrind-{testname}.log')
116
117    # Define the available services
118    apache = ApacheService(config=os.path.join(testdir, 'apache.conf'),
119                           pidfile=f'apache2{pidaffix}.pid',
120                           valgrind_log=valgrind_log,
121                           valgrind_suppress=args.valgrind_suppressions)
122    backend = ApacheService(config=os.path.join(testdir, 'backend.conf'),
123                            pidfile=f'backend{pidaffix}.pid')
124    ocsp = ApacheService(config=os.path.join(testdir, 'ocsp.conf'),
125                         pidfile=f'ocsp{pidaffix}.pid',
126                         check=check_ocsp_responder)
127    msva = TestService(start=['monkeysphere-validation-agent'],
128                       env={'GNUPGHOME': 'msva.gnupghome',
129                            'MSVA_KEYSERVER_POLICY': 'never'},
130                       condition=lambda: 'USE_MSVA' in os.environ,
131                       check=check_msva)
132
133    # background services: must be ready before the main apache
134    # instance is started
135    bg_services = [backend, ocsp, msva]
136
137    # If VERBOSE is enabled, log the HTTPD build configuration
138    if 'VERBOSE' in os.environ:
139        apache2 = os.environ.get('APACHE2', 'apache2')
140        subprocess.run([apache2, '-f', f'{srcdir}/base_apache.conf', '-V'],
141                       check=True)
142
143    # This hook may modify the environment as needed for the test.
144    cleanup_callback = None
145    try:
146        if plugin.prepare_env:
147            cleanup_callback = plugin.prepare_env()
148    except SkipTest as skip:
149        print(f'Skipping: {skip!s}')
150        sys.exit(77)
151
152    if 'USE_MSVA' in os.environ:
153        os.environ['MONKEYSPHERE_VALIDATION_AGENT_SOCKET'] = \
154            f'http://127.0.0.1:{os.environ["MSVA_PORT"]}'
155
156    with contextlib.ExitStack() as service_stack:
157        if cleanup_callback:
158            service_stack.callback(cleanup_callback)
159        service_stack.enter_context(lockfile('test.lock', nolock='MGS_NETNS_ACTIVE' in os.environ))
160        service_stack.enter_context(ocsp.run())
161        service_stack.enter_context(backend.run())
162        service_stack.enter_context(msva.run())
163
164        # TEST_SERVICE_MAX_WAIT is in milliseconds
165        wait_timeout = \
166            int(os.environ.get('TEST_SERVICE_MAX_WAIT', 10000)) / 1000
167        for s in bg_services:
168            if s.condition():
169                s.wait_ready(timeout=wait_timeout)
170
171        # special case: expected to fail in a few cases
172        service_stack.enter_context(apache.run())
173        failed = apache.wait_ready()
174        if os.path.exists(os.path.join(testdir, 'fail.server')):
175            if failed:
176                print('Apache server failed to start as expected',
177                      file=sys.stderr)
178            else:
179                raise TestExpectationFailed(
180                    'Server start did not fail as expected!')
181
182        # Set TEST_TARGET for the request. Might be replaced with a
183        # parameter later.
184        if 'TARGET_IP' in os.environ:
185            os.environ['TEST_TARGET'] = os.environ['TARGET_IP']
186        else:
187            os.environ['TEST_TARGET'] = os.environ['TEST_HOST']
188
189        # Run the test connections
190        if plugin.run_connection:
191            plugin.run_connection(testname,
192                                  conn_log=args.log_connection,
193                                  response_log=args.log_responses)
194        else:
195            with open(os.path.join(testdir, 'test.yml'), 'r') as test_conf:
196                run_test_conf(test_conf,
197                              float(os.environ.get('TEST_QUERY_TIMEOUT', 5.0)),
198                              conn_log=args.log_connection,
199                              response_log=args.log_responses)
200
201    # run extra checks the test's hooks.py might define
202    if plugin.post_check:
203        args.log_connection.seek(0)
204        args.log_responses.seek(0)
205        plugin.post_check(conn_log=args.log_connection,
206                          response_log=args.log_responses)
207
208
209
210if __name__ == "__main__":
211    import argparse
212    parser = argparse.ArgumentParser(
213        description='Run a mod_gnutls server test')
214    parser.add_argument('--test-number', type=int,
215                        required=True, help='load YAML test configuration')
216    parser.add_argument('--log-connection', type=argparse.FileType('w+'),
217                        default=temp_logfile(),
218                        help='write connection log to this file')
219    parser.add_argument('--log-responses', type=argparse.FileType('w+'),
220                        default=temp_logfile(),
221                        help='write HTTP responses to this file')
222    parser.add_argument('--valgrind', action='store_true',
223                        help='run primary Apache instance with Valgrind')
224    parser.add_argument('--valgrind-suppressions', action='append',
225                        default=[],
226                        help='use Valgrind suppressions file')
227
228    # enable bash completion if argcomplete is available
229    try:
230        import argcomplete
231        argcomplete.autocomplete(parser)
232    except ImportError:
233        pass
234
235    args = parser.parse_args()
236
237    with contextlib.ExitStack() as stack:
238        stack.enter_context(contextlib.closing(args.log_connection))
239        stack.enter_context(contextlib.closing(args.log_responses))
240        main(args)
Note: See TracBrowser for help on using the repository browser.