Add basic profiling capability for quteproc tests.
When --qute-profile-subprocs is given, we write a profile file for each qutebrowser invocation and also create prof/combined.pstats afterwards.
This commit is contained in:
parent
6d11e9ffd8
commit
02f367a308
1
.gitignore
vendored
1
.gitignore
vendored
@ -29,4 +29,5 @@ __pycache__
|
||||
/.cache
|
||||
/.testmondata
|
||||
/.hypothesis
|
||||
/prof
|
||||
TODO
|
||||
|
@ -151,6 +151,8 @@ def pytest_addoption(parser):
|
||||
help='Disable xvfb in tests.')
|
||||
parser.addoption('--qute-delay', action='store', default=0, type=int,
|
||||
help="Delay between qutebrowser commands.")
|
||||
parser.addoption('--qute-profile-subprocs', action='store_true',
|
||||
default=False, help="Run cProfile for subprocesses.")
|
||||
|
||||
|
||||
def pytest_configure(config):
|
||||
|
@ -21,6 +21,28 @@
|
||||
|
||||
"""Things needed for integration testing."""
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import pstats
|
||||
|
||||
from webserver import httpbin, httpbin_after_test, ssl_server
|
||||
from quteprocess import quteproc_process, quteproc, quteproc_new
|
||||
from testprocess import pytest_runtest_makereport
|
||||
|
||||
|
||||
def pytest_configure(config):
|
||||
"""Remove old profile files."""
|
||||
if config.getoption('--qute-profile-subprocs'):
|
||||
try:
|
||||
shutil.rmtree('prof')
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
|
||||
def pytest_unconfigure(config):
|
||||
"""Combine profiles."""
|
||||
if config.getoption('--qute-profile-subprocs'):
|
||||
stats = pstats.Stats()
|
||||
for fn in os.listdir('prof'):
|
||||
stats.add(os.path.join('prof', fn))
|
||||
stats.dump_stats(os.path.join('prof', 'combined.pstats'))
|
||||
|
@ -28,6 +28,7 @@ import datetime
|
||||
import logging
|
||||
import tempfile
|
||||
import contextlib
|
||||
import itertools
|
||||
|
||||
import yaml
|
||||
import pytest
|
||||
@ -39,6 +40,9 @@ from qutebrowser.utils import log, utils
|
||||
from helpers import utils as testutils
|
||||
|
||||
|
||||
instance_counter = itertools.count()
|
||||
|
||||
|
||||
def is_ignored_qt_message(message):
|
||||
"""Check if the message is listed in qt_log_ignore."""
|
||||
# pylint: disable=no-member
|
||||
@ -124,6 +128,9 @@ class QuteProc(testprocess.Process):
|
||||
basedir: The base directory for this instance.
|
||||
_focus_ready: Whether the main window got focused.
|
||||
_load_ready: Whether the about:blank page got loaded.
|
||||
_profile: If True, do profiling of the subprocesses.
|
||||
_instance_id: An unique ID for this QuteProc instance
|
||||
_run_counter: A counter to get an unique ID for each run.
|
||||
|
||||
Signals:
|
||||
got_error: Emitted when there was an error log line.
|
||||
@ -134,14 +141,17 @@ class QuteProc(testprocess.Process):
|
||||
KEYS = ['timestamp', 'loglevel', 'category', 'module', 'function', 'line',
|
||||
'message']
|
||||
|
||||
def __init__(self, httpbin, delay, parent=None):
|
||||
def __init__(self, httpbin, delay, *, profile=False, parent=None):
|
||||
super().__init__(parent)
|
||||
self._profile = profile
|
||||
self._delay = delay
|
||||
self._httpbin = httpbin
|
||||
self._ipc_socket = None
|
||||
self.basedir = None
|
||||
self._focus_ready = False
|
||||
self._load_ready = False
|
||||
self._instance_id = next(instance_counter)
|
||||
self._run_counter = itertools.count()
|
||||
|
||||
def _is_ready(self, what):
|
||||
"""Called by _parse_line if loading/focusing is done.
|
||||
@ -201,12 +211,28 @@ class QuteProc(testprocess.Process):
|
||||
|
||||
def _executable_args(self):
|
||||
if hasattr(sys, 'frozen'):
|
||||
if self._profile:
|
||||
raise Exception("Can't profile with sys.frozen!")
|
||||
executable = os.path.join(os.path.dirname(sys.executable),
|
||||
'qutebrowser')
|
||||
args = []
|
||||
else:
|
||||
executable = sys.executable
|
||||
args = ['-m', 'qutebrowser']
|
||||
if self._profile:
|
||||
profile_dir = os.path.join(os.getcwd(), 'prof')
|
||||
profile_id = '{}_{}'.format(self._instance_id,
|
||||
next(self._run_counter))
|
||||
profile_file = os.path.join(profile_dir,
|
||||
'{}.pstats'.format(profile_id))
|
||||
try:
|
||||
os.mkdir(profile_dir)
|
||||
except FileExistsError:
|
||||
pass
|
||||
args = [os.path.join('scripts', 'dev', 'run_profile.py'),
|
||||
'--profile-tool', 'none',
|
||||
'--profile-file', profile_file]
|
||||
else:
|
||||
args = ['-m', 'qutebrowser']
|
||||
return executable, args
|
||||
|
||||
def _default_args(self):
|
||||
@ -397,7 +423,8 @@ class QuteProc(testprocess.Process):
|
||||
def quteproc_process(qapp, httpbin, request):
|
||||
"""Fixture for qutebrowser process which is started once per file."""
|
||||
delay = request.config.getoption('--qute-delay')
|
||||
proc = QuteProc(httpbin, delay)
|
||||
profile = request.config.getoption('--qute-profile-subprocs')
|
||||
proc = QuteProc(httpbin, delay, profile=profile)
|
||||
proc.start()
|
||||
yield proc
|
||||
proc.terminate()
|
||||
@ -416,7 +443,8 @@ def quteproc(quteproc_process, httpbin, request):
|
||||
def quteproc_new(qapp, httpbin, request):
|
||||
"""Per-test qutebrowser process to test invocations."""
|
||||
delay = request.config.getoption('--qute-delay')
|
||||
proc = QuteProc(httpbin, delay)
|
||||
profile = request.config.getoption('--qute-profile-subprocs')
|
||||
proc = QuteProc(httpbin, delay, profile=profile)
|
||||
request.node._quteproc_log = proc.captured_log
|
||||
# Not calling before_test here as that would start the process
|
||||
yield proc
|
||||
|
Loading…
Reference in New Issue
Block a user