summaryrefslogtreecommitdiffstats
path: root/test/fixtures_ws.py
diff options
context:
space:
mode:
Diffstat (limited to 'test/fixtures_ws.py')
-rw-r--r--test/fixtures_ws.py388
1 files changed, 388 insertions, 0 deletions
diff --git a/test/fixtures_ws.py b/test/fixtures_ws.py
new file mode 100644
index 0000000..ceee402
--- /dev/null
+++ b/test/fixtures_ws.py
@@ -0,0 +1,388 @@
+#
+# Wireshark tests
+#
+# Copyright (c) 2018 Peter Wu <peter@lekensteyn.nl>
+#
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+'''Fixtures that are specific to Wireshark.'''
+
+from contextlib import contextmanager
+import os
+import re
+import subprocess
+import sys
+import tempfile
+import types
+import pytest
+
+@pytest.fixture(scope='session')
+def capture_interface(request, cmd_dumpcap):
+ '''
+ Name of capture interface. Tests will be skipped if dumpcap is not
+ available or no Loopback interface is available.
+ '''
+ disabled = request.config.getoption('--disable-capture', default=False)
+ if disabled:
+ pytest.skip('Capture tests are disabled via --disable-capture')
+ proc = subprocess.Popen((cmd_dumpcap, '-D'), stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE, universal_newlines=True)
+ outs, errs = proc.communicate()
+ if proc.returncode != 0:
+ print('"dumpcap -D" exited with %d. stderr:\n%s' %
+ (proc.returncode, errs))
+ pytest.skip('Test requires capture privileges and an interface.')
+ # Matches: "lo (Loopback)" (Linux), "lo0 (Loopback)" (macOS) or
+ # "\Device\NPF_{...} (Npcap Loopback Adapter)" (Windows)
+ print('"dumpcap -D" output:\n%s' % (outs,))
+ m = re.search(r'^(\d+)\. .*\(.*Loopback.*\)', outs, re.MULTILINE|re.IGNORECASE)
+ if not m:
+ pytest.skip('Test requires a capture interface.')
+ iface = m.group(1)
+ # Interface found, check for capture privileges (needed for Linux).
+ try:
+ subprocess.check_output((cmd_dumpcap, '-L', '-i', iface),
+ stderr=subprocess.STDOUT,
+ universal_newlines=True)
+ return iface
+ except subprocess.CalledProcessError as e:
+ print('"dumpcap -L -i %s" exited with %d. Output:\n%s' % (iface,
+ e.returncode,
+ e.output))
+ pytest.skip('Test requires capture privileges.')
+
+
+@pytest.fixture(scope='session')
+def program_path(request):
+ '''
+ Path to the Wireshark binaries as set by the --program-path option, the
+ WS_BIN_PATH environment variable or (curdir)/run.
+ '''
+ curdir_run = os.path.join(os.curdir, 'run')
+ if sys.platform == 'win32':
+ build_type = request.config.getoption('--build-type')
+ curdir_run_config = os.path.join(curdir_run, build_type)
+ if os.path.exists(curdir_run_config):
+ curdir_run = curdir_run_config
+ paths = (
+ request.config.getoption('--program-path', default=None),
+ os.environ.get('WS_BIN_PATH'),
+ curdir_run,
+ )
+ for path in paths:
+ if type(path) == str and os.path.isdir(path):
+ return path
+ raise AssertionError('Missing directory with Wireshark binaries')
+
+
+@pytest.fixture(scope='session')
+def program(program_path, request):
+ skip_if_missing = request.config.getoption('--skip-missing-programs',
+ default='')
+ skip_if_missing = skip_if_missing.split(',') if skip_if_missing else []
+ dotexe = ''
+ if sys.platform.startswith('win32'):
+ dotexe = '.exe'
+
+ def resolver(name):
+ path = os.path.abspath(os.path.join(program_path, name + dotexe))
+ if not os.access(path, os.X_OK):
+ if skip_if_missing == ['all'] or name in skip_if_missing:
+ pytest.skip('Program %s is not available' % (name,))
+ raise AssertionError('Program %s is not available' % (name,))
+ return path
+ return resolver
+
+
+@pytest.fixture(scope='session')
+def cmd_capinfos(program):
+ return program('capinfos')
+
+
+@pytest.fixture(scope='session')
+def cmd_dumpcap(program):
+ return program('dumpcap')
+
+
+@pytest.fixture(scope='session')
+def cmd_mergecap(program):
+ return program('mergecap')
+
+
+@pytest.fixture(scope='session')
+def cmd_rawshark(program):
+ return program('rawshark')
+
+
+@pytest.fixture(scope='session')
+def cmd_tshark(program):
+ return program('tshark')
+
+
+@pytest.fixture(scope='session')
+def cmd_text2pcap(program):
+ return program('text2pcap')
+
+
+@pytest.fixture(scope='session')
+def cmd_editcap(program):
+ return program('editcap')
+
+
+@pytest.fixture(scope='session')
+def cmd_wireshark(program):
+ return program('wireshark')
+
+
+@pytest.fixture(scope='session')
+def wireshark_command(cmd_wireshark):
+ # Windows can always display the GUI and macOS can if we're in a login session.
+ # On Linux, headless mode is used, see QT_QPA_PLATFORM in the 'test_env' fixture.
+ if sys.platform == 'darwin' and 'SECURITYSESSIONID' not in os.environ:
+ pytest.skip('Wireshark GUI tests require loginwindow session')
+ if sys.platform not in ('win32', 'darwin', 'linux'):
+ if 'DISPLAY' not in os.environ:
+ pytest.skip('Wireshark GUI tests require DISPLAY')
+ return (cmd_wireshark, '-ogui.update.enabled:FALSE')
+
+
+@pytest.fixture(scope='session')
+def cmd_extcap(program):
+ def extcap_name(name):
+ if sys.platform == 'darwin':
+ return program(os.path.join('Wireshark.app/Contents/MacOS/extcap', name))
+ else:
+ return program(os.path.join('extcap', name))
+ return extcap_name
+
+
+@pytest.fixture(scope='session')
+def features(cmd_tshark, make_env):
+ '''Returns an object describing available features in tshark.'''
+ try:
+ tshark_v = subprocess.check_output(
+ (cmd_tshark, '--version'),
+ stderr=subprocess.PIPE,
+ universal_newlines=True,
+ env=make_env()
+ )
+ tshark_v = re.sub(r'\s+', ' ', tshark_v)
+ except subprocess.CalledProcessError as ex:
+ print('Failed to detect tshark features: %s' % (ex,))
+ tshark_v = ''
+ gcry_m = re.search(r'with +Gcrypt +([0-9]+)\.([0-9]+)', tshark_v)
+ gcry_ver = (int(gcry_m.group(1)),int(gcry_m.group(2)))
+ return types.SimpleNamespace(
+ have_x64='Compiled (64-bit)' in tshark_v,
+ have_lua='with Lua' in tshark_v,
+ have_lua_unicode='(with UfW patches)' in tshark_v,
+ have_nghttp2='with nghttp2' in tshark_v,
+ have_nghttp3='with nghttp3' in tshark_v,
+ have_kerberos='with Kerberos' in tshark_v,
+ have_gnutls='with GnuTLS' in tshark_v,
+ have_pkcs11='and PKCS #11 support' in tshark_v,
+ have_brotli='with brotli' in tshark_v,
+ have_plugins='binary plugins supported' in tshark_v,
+ )
+
+
+@pytest.fixture(scope='session')
+def dirs():
+ '''Returns fixed directories containing test input.'''
+ this_dir = os.path.dirname(__file__)
+ return types.SimpleNamespace(
+ baseline_dir=os.path.join(this_dir, 'baseline'),
+ capture_dir=os.path.join(this_dir, 'captures'),
+ config_dir=os.path.join(this_dir, 'config'),
+ key_dir=os.path.join(this_dir, 'keys'),
+ lua_dir=os.path.join(this_dir, 'lua'),
+ protobuf_lang_files_dir=os.path.join(this_dir, 'protobuf_lang_files'),
+ tools_dir=os.path.join(this_dir, '..', 'tools'),
+ )
+
+
+@pytest.fixture(scope='session')
+def capture_file(dirs):
+ '''Returns the path to a capture file.'''
+ def resolver(filename):
+ return os.path.join(dirs.capture_dir, filename)
+ return resolver
+
+@pytest.fixture
+def result_file(tmp_path):
+ '''Returns the path to a temporary file.'''
+ def result_file_real(filename):
+ return str(tmp_path / filename)
+ return result_file_real
+
+@pytest.fixture
+def home_path():
+ '''Per-test home directory, removed when finished.'''
+ with tempfile.TemporaryDirectory(prefix='wireshark-tests-home-') as dirname:
+ yield dirname
+
+
+@pytest.fixture
+def conf_path(home_path):
+ '''Path to the Wireshark configuration directory.'''
+ if sys.platform.startswith('win32'):
+ conf_path = os.path.join(home_path, 'Wireshark')
+ else:
+ conf_path = os.path.join(home_path, '.config', 'wireshark')
+ os.makedirs(conf_path)
+ return conf_path
+
+
+@pytest.fixture(scope='session')
+def make_env():
+ """A factory for a modified environment to ensure reproducible tests."""
+ def make_env_real(home=None):
+ env = os.environ.copy()
+ env['TZ'] = 'UTC'
+ home_env = 'APPDATA' if sys.platform.startswith('win32') else 'HOME'
+ if home:
+ env[home_env] = home
+ else:
+ # This directory is supposed not to be written and is used by
+ # "readonly" tests that do not read any other preferences.
+ env[home_env] = "/wireshark-tests-unused"
+ # XDG_CONFIG_HOME takes precedence over HOME, which we don't want.
+ try:
+ del env['XDG_CONFIG_HOME']
+ except KeyError:
+ pass
+ return env
+ return make_env_real
+
+
+@pytest.fixture
+def base_env(home_path, make_env, request):
+ """A modified environment to ensure reproducible tests. Tests can modify
+ this environment as they see fit."""
+ env = make_env(home=home_path)
+
+ return env
+
+
+@pytest.fixture
+def test_env(base_env, conf_path, request, dirs):
+ '''A process environment with a populated configuration directory.'''
+ # Populate our UAT files
+ uat_files = [
+ '80211_keys',
+ 'dtlsdecrypttablefile',
+ 'esp_sa',
+ 'ssl_keys',
+ 'c1222_decryption_table',
+ 'ikev1_decryption_table',
+ 'ikev2_decryption_table',
+ ]
+ # uat.c replaces backslashes...
+ key_dir_path = os.path.join(dirs.key_dir, '').replace('\\', '\\x5c')
+ for uat in uat_files:
+ template_file = os.path.join(dirs.config_dir, uat + '.tmpl')
+ out_file = os.path.join(conf_path, uat)
+ with open(template_file, 'r') as f:
+ template_contents = f.read()
+ cf_contents = template_contents.replace('TEST_KEYS_DIR', key_dir_path)
+ with open(out_file, 'w') as f:
+ f.write(cf_contents)
+
+ env = base_env
+ env['WIRESHARK_RUN_FROM_BUILD_DIRECTORY'] = '1'
+ env['WIRESHARK_QUIT_AFTER_CAPTURE'] = '1'
+
+ # Allow GUI tests to be run without opening windows nor requiring a Xserver.
+ # Set envvar QT_DEBUG_BACKINGSTORE=1 to save the window contents to a file
+ # in the current directory, output0000.png, output0001.png, etc. Note that
+ # this will overwrite existing files.
+ if sys.platform == 'linux':
+ # This option was verified working on Arch Linux with Qt 5.12.0-2 and
+ # Ubuntu 16.04 with libqt5gui5 5.5.1+dfsg-16ubuntu7.5. On macOS and
+ # Windows it unfortunately crashes (Qt 5.12.0).
+ env['QT_QPA_PLATFORM'] = 'minimal'
+
+ return env
+
+
+@pytest.fixture
+def test_env_80211_user_tk(base_env, conf_path, request, dirs):
+ '''A process environment with a populated configuration directory.'''
+ # Populate our UAT files
+ uat_files = [
+ '80211_keys',
+ ]
+ # uat.c replaces backslashes...
+ key_dir_path = os.path.join(dirs.key_dir, '').replace('\\', '\\x5c')
+ for uat in uat_files:
+ template_file = os.path.join(dirs.config_dir, uat + '.user_tk_tmpl')
+ out_file = os.path.join(conf_path, uat)
+ with open(template_file, 'r') as f:
+ template_contents = f.read()
+ cf_contents = template_contents.replace('TEST_KEYS_DIR', key_dir_path)
+ with open(out_file, 'w') as f:
+ f.write(cf_contents)
+
+ env = base_env
+ env['WIRESHARK_RUN_FROM_BUILD_DIRECTORY'] = '1'
+ env['WIRESHARK_QUIT_AFTER_CAPTURE'] = '1'
+
+ # Allow GUI tests to be run without opening windows nor requiring a Xserver.
+ # Set envvar QT_DEBUG_BACKINGSTORE=1 to save the window contents to a file
+ # in the current directory, output0000.png, output0001.png, etc. Note that
+ # this will overwrite existing files.
+ if sys.platform == 'linux':
+ # This option was verified working on Arch Linux with Qt 5.12.0-2 and
+ # Ubuntu 16.04 with libqt5gui5 5.5.1+dfsg-16ubuntu7.5. On macOS and
+ # Windows it unfortunately crashes (Qt 5.12.0).
+ env['QT_QPA_PLATFORM'] = 'minimal'
+
+ return env
+
+@pytest.fixture
+def unicode_env(home_path, make_env):
+ '''A Wireshark configuration directory with Unicode in its path.'''
+ home_env = 'APPDATA' if sys.platform.startswith('win32') else 'HOME'
+ uni_home = os.path.join(home_path, 'unicode-Ф-€-中-testcases')
+ env = make_env(home=uni_home)
+ if sys.platform == 'win32':
+ pluginsdir = os.path.join(uni_home, 'Wireshark', 'plugins')
+ else:
+ pluginsdir = os.path.join(uni_home, '.local/lib/wireshark/plugins')
+ os.makedirs(pluginsdir)
+ return types.SimpleNamespace(
+ path=lambda *args: os.path.join(uni_home, *args),
+ env=env,
+ pluginsdir=pluginsdir
+ )
+
+
+@pytest.fixture(scope='session')
+def make_screenshot():
+ '''Creates a screenshot and save it to a file. Intended for CI purposes.'''
+ def make_screenshot_real(filename):
+ try:
+ if sys.platform == 'darwin':
+ subprocess.check_call(['screencapture', filename])
+ else:
+ print("Creating a screenshot on this platform is not supported")
+ return
+ size = os.path.getsize(filename)
+ print("Created screenshot %s (%d bytes)" % (filename, size))
+ except (subprocess.CalledProcessError, OSError) as e:
+ print("Failed to take screenshot:", e)
+ return make_screenshot_real
+
+
+@pytest.fixture
+def make_screenshot_on_error(request, make_screenshot, result_file):
+ '''Writes a screenshot when a process times out.'''
+ @contextmanager
+ def make_screenshot_on_error_real():
+ try:
+ yield
+ except subprocess.TimeoutExpired:
+ filename = result_file('screenshot.png')
+ make_screenshot(filename)
+ raise
+ return make_screenshot_on_error_real