9ce60b173b
Change-Id: I9c09f55673187f6fee723fcd72798fb6b9958b03 Reviewed-on: https://code.wireshark.org/review/32745 Petri-Dish: Peter Wu <peter@lekensteyn.nl> Tested-by: Petri Dish Buildbot Reviewed-by: Peter Wu <peter@lekensteyn.nl>
338 lines
12 KiB
Python
338 lines
12 KiB
Python
#
|
|
# -*- coding: utf-8 -*-
|
|
# 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 fixtures
|
|
import subprocesstest
|
|
|
|
|
|
@fixtures.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:
|
|
fixtures.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))
|
|
fixtures.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)
|
|
if not m:
|
|
fixtures.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))
|
|
fixtures.skip('Test requires capture privileges.')
|
|
|
|
|
|
@fixtures.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':
|
|
curdir_run = os.path.join(curdir_run, 'RelWithDebInfo')
|
|
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')
|
|
|
|
|
|
@fixtures.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:
|
|
fixtures.skip('Program %s is not available' % (name,))
|
|
raise AssertionError('Program %s is not available' % (name,))
|
|
return path
|
|
return resolver
|
|
|
|
|
|
@fixtures.fixture(scope='session')
|
|
def cmd_capinfos(program):
|
|
return program('capinfos')
|
|
|
|
|
|
@fixtures.fixture(scope='session')
|
|
def cmd_dumpcap(program):
|
|
return program('dumpcap')
|
|
|
|
|
|
@fixtures.fixture(scope='session')
|
|
def cmd_mergecap(program):
|
|
return program('mergecap')
|
|
|
|
|
|
@fixtures.fixture(scope='session')
|
|
def cmd_rawshark(program):
|
|
return program('rawshark')
|
|
|
|
|
|
@fixtures.fixture(scope='session')
|
|
def cmd_tshark(program):
|
|
return program('tshark')
|
|
|
|
|
|
@fixtures.fixture(scope='session')
|
|
def cmd_text2pcap(program):
|
|
return program('text2pcap')
|
|
|
|
|
|
@fixtures.fixture(scope='session')
|
|
def cmd_editcap(program):
|
|
return program('editcap')
|
|
|
|
|
|
@fixtures.fixture(scope='session')
|
|
def cmd_wireshark(program):
|
|
return program('wireshark')
|
|
|
|
|
|
@fixtures.fixture(scope='session')
|
|
def wireshark_command(cmd_wireshark):
|
|
# Windows and macOS can always display the GUI. On Linux, headless mode is
|
|
# used, see QT_QPA_PLATFORM in the 'test_env' fixture.
|
|
if sys.platform not in ('win32', 'darwin', 'linux'):
|
|
if 'DISPLAY' not in os.environ:
|
|
fixtures.skip('Wireshark GUI tests requires DISPLAY')
|
|
return (cmd_wireshark, '-ogui.update.enabled:FALSE')
|
|
|
|
|
|
@fixtures.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)
|
|
return types.SimpleNamespace(
|
|
have_x64='Compiled (64-bit)' in tshark_v,
|
|
have_lua='with Lua' in tshark_v,
|
|
have_nghttp2='with nghttp2' in tshark_v,
|
|
have_kerberos='with MIT Kerberos' in tshark_v or 'with Heimdal Kerberos' in tshark_v,
|
|
have_libgcrypt16=gcry_m and float(gcry_m.group(1)) >= 1.6,
|
|
have_libgcrypt17=gcry_m and float(gcry_m.group(1)) >= 1.7,
|
|
have_gnutls='with GnuTLS' in tshark_v,
|
|
have_pkcs11='and PKCS #11 support' in tshark_v,
|
|
have_brotli='with brotli' in tshark_v,
|
|
)
|
|
|
|
|
|
@fixtures.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'),
|
|
tools_dir=os.path.join(this_dir, '..', 'tools'),
|
|
)
|
|
|
|
|
|
@fixtures.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
|
|
|
|
|
|
@fixtures.fixture
|
|
def home_path():
|
|
'''Per-test home directory, removed when finished.'''
|
|
with tempfile.TemporaryDirectory(prefix='wireshark-tests-home-') as dirname:
|
|
yield dirname
|
|
|
|
|
|
@fixtures.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
|
|
|
|
|
|
@fixtures.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"
|
|
return env
|
|
return make_env_real
|
|
|
|
|
|
@fixtures.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)
|
|
|
|
# Remove this if test instances no longer inherit from SubprocessTestCase?
|
|
if isinstance(request.instance, subprocesstest.SubprocessTestCase):
|
|
# Inject the test environment as default if it was not overridden.
|
|
request.instance.injected_test_env = env
|
|
return env
|
|
|
|
|
|
@fixtures.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'
|
|
|
|
# Remove this if test instances no longer inherit from SubprocessTestCase?
|
|
if isinstance(request.instance, subprocesstest.SubprocessTestCase):
|
|
# Inject the test environment as default if it was not overridden.
|
|
request.instance.injected_test_env = env
|
|
return env
|
|
|
|
|
|
@fixtures.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
|
|
)
|
|
|
|
|
|
@fixtures.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
|
|
|
|
|
|
@fixtures.fixture
|
|
def make_screenshot_on_error(request, make_screenshot):
|
|
'''Writes a screenshot when a process times out.'''
|
|
@contextmanager
|
|
def make_screenshot_on_error_real():
|
|
try:
|
|
yield
|
|
except subprocess.TimeoutExpired:
|
|
filename = request.instance.filename_from_id('screenshot.png')
|
|
make_screenshot(filename)
|
|
raise
|
|
return make_screenshot_on_error_real
|