Skip to content

Commit

Permalink
test: fix performance issues when all tests run
Browse files Browse the repository at this point in the history
Use pytest-xdist plugin to run the tests concurrently in different
processes. Splitting the execution across multiple workers significantly
reduces the time to run the tests.

Syrupy does not support xdist:
syrupy-project/syrupy#535. Move to pytest-snapshot
since that is the only snapshot library that supports xdist.

Update snapshot reporter plugin to work with pytest-snapshot.
  • Loading branch information
olivierwilkinson committed Mar 10, 2022
1 parent ddbc0f4 commit 6c20113
Show file tree
Hide file tree
Showing 46 changed files with 120 additions and 76 deletions.
8 changes: 1 addition & 7 deletions conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,11 @@

import pytest
from PIL import ImageFont
from syrupy.extensions.image import PNGImageSnapshotExtension

from pt_miniscreen.state import ScheduledAppEvent
from pt_miniscreen.utils import get_image_file_path

pytest_plugins = ("syrupy", "tests.plugins.snapshot_reporter")
pytest_plugins = ("pytest_snapshot", "tests.plugins.snapshot_reporter")


@pytest.fixture()
Expand Down Expand Up @@ -70,8 +69,3 @@ def app(patch_packages, patch_font, patch_images):
@pytest.fixture
def miniscreen(app):
yield app.miniscreen


@pytest.fixture
def image_snapshot(snapshot):
return snapshot.use_extension(PNGImageSnapshotExtension)
3 changes: 3 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,6 @@ profile = black

[pep8]
max-line-length = 150

[tool:pytest]
addopts = -n auto
Binary file not shown.
24 changes: 12 additions & 12 deletions tests/hud_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,45 +12,45 @@ def setup(mocker):
)


def test_hud_navigation(miniscreen, image_snapshot):
assert miniscreen.device.display_image == image_snapshot(name="overview")
def test_hud_navigation(miniscreen, snapshot):
snapshot.assert_match(miniscreen.device.display_image, "overview.png")

miniscreen.down_button.release()
sleep(1)
assert miniscreen.device.display_image == image_snapshot(name="system")
snapshot.assert_match(miniscreen.device.display_image, "system.png")

miniscreen.select_button.release()
sleep(1)
assert miniscreen.device.display_image == image_snapshot(name="enter-system")
snapshot.assert_match(miniscreen.device.display_image, "enter-system.png")

miniscreen.cancel_button.release()
sleep(1)
assert miniscreen.device.display_image == image_snapshot(name="leave-system")
snapshot.assert_match(miniscreen.device.display_image, "leave-system.png")

miniscreen.down_button.release()
sleep(1)
assert miniscreen.device.display_image == image_snapshot(name="network")
snapshot.assert_match(miniscreen.device.display_image, "network.png")

miniscreen.select_button.release()
sleep(1)
assert miniscreen.device.display_image == image_snapshot(name="enter-network")
snapshot.assert_match(miniscreen.device.display_image, "enter-network.png")

miniscreen.cancel_button.release()
sleep(1)
assert miniscreen.device.display_image == image_snapshot(name="leave-network")
snapshot.assert_match(miniscreen.device.display_image, "leave-network.png")

miniscreen.down_button.release()
sleep(1)
assert miniscreen.device.display_image == image_snapshot(name="settings")
snapshot.assert_match(miniscreen.device.display_image, "settings.png")

miniscreen.select_button.release()
sleep(1)
assert miniscreen.device.display_image == image_snapshot(name="enter-settings")
snapshot.assert_match(miniscreen.device.display_image, "enter-settings.png")

miniscreen.cancel_button.release()
sleep(1)
assert miniscreen.device.display_image == image_snapshot(name="leave-settings")
snapshot.assert_match(miniscreen.device.display_image, "leave-settings.png")

miniscreen.up_button.release()
sleep(1)
assert miniscreen.device.display_image == image_snapshot(name="scroll-up")
snapshot.assert_match(miniscreen.device.display_image, "scroll-up.png")
24 changes: 12 additions & 12 deletions tests/network_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ def setup(miniscreen):
sleep(1)


def test_wifi(miniscreen, image_snapshot, internal_ip, mocker):
assert miniscreen.device.display_image == image_snapshot(name="disconnected")
def test_wifi(miniscreen, snapshot, internal_ip, mocker):
snapshot.assert_match(miniscreen.device.display_image, "disconnected.png")

internal_ip(page="wifi", interface="wlan0", ip="192.168.192.168")
mocker.patch(
Expand All @@ -34,29 +34,29 @@ def test_wifi(miniscreen, image_snapshot, internal_ip, mocker):
"pt_miniscreen.hotspots.wifi_strength.get_network_strength", return_value="80%"
)
sleep(2)
assert miniscreen.device.display_image == image_snapshot(name="connected")
snapshot.assert_match(miniscreen.device.display_image, "connected.png")


def test_ethernet(miniscreen, image_snapshot, internal_ip):
def test_ethernet(miniscreen, snapshot, internal_ip):
# scroll to ethernet page
miniscreen.down_button.release()
sleep(1)

assert miniscreen.device.display_image == image_snapshot(name="disconnected")
snapshot.assert_match(miniscreen.device.display_image, "disconnected.png")

internal_ip(page="ethernet", interface="eth0", ip="10.255.10.255")
sleep(2)
assert miniscreen.device.display_image == image_snapshot(name="connected")
snapshot.assert_match(miniscreen.device.display_image, "connected.png")


def test_ap(miniscreen, image_snapshot, mocker):
def test_ap(miniscreen, snapshot, mocker):
# scroll to ap page
miniscreen.down_button.release()
sleep(1)
miniscreen.down_button.release()
sleep(1)

assert miniscreen.device.display_image == image_snapshot(name="disconnected")
snapshot.assert_match(miniscreen.device.display_image, "disconnected.png")

mocker.patch(
"pt_miniscreen.pages.network.ap.get_ap_mode_status",
Expand All @@ -67,10 +67,10 @@ def test_ap(miniscreen, image_snapshot, mocker):
},
)
sleep(2)
assert miniscreen.device.display_image == image_snapshot(name="connected")
snapshot.assert_match(miniscreen.device.display_image, "connected.png")


def test_usb(miniscreen, image_snapshot, internal_ip):
def test_usb(miniscreen, snapshot, internal_ip):
# scroll to usb page
miniscreen.down_button.release()
sleep(1)
Expand All @@ -79,8 +79,8 @@ def test_usb(miniscreen, image_snapshot, internal_ip):
miniscreen.down_button.release()
sleep(1)

assert miniscreen.device.display_image == image_snapshot(name="disconnected")
snapshot.assert_match(miniscreen.device.display_image, "disconnected.png")

internal_ip(page="usb", interface="ptusb0", ip="192.168.0.1")
sleep(2)
assert miniscreen.device.display_image == image_snapshot(name="connected")
snapshot.assert_match(miniscreen.device.display_image, "connected.png")
20 changes: 10 additions & 10 deletions tests/overview_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,44 +26,44 @@ def patch(_interface, ip=""):
return patch


def test_overview_battery(battery, miniscreen, image_snapshot):
def test_overview_battery(battery, miniscreen, snapshot):
battery.is_charging = True
battery.when_charging()
sleep(1.5)
assert miniscreen.device.display_image == image_snapshot(name="charging")
snapshot.assert_match(miniscreen.device.display_image, "charging.png")

battery.is_full = True
battery.capacity = 100
battery.when_full()
sleep(1.5)
assert miniscreen.device.display_image == image_snapshot(name="full")
snapshot.assert_match(miniscreen.device.display_image, "full.png")

battery.is_charging = False
battery.is_full = False
battery.capacity = 99
battery.when_discharging()
sleep(1.5)
assert miniscreen.device.display_image == image_snapshot(name="discharging")
snapshot.assert_match(miniscreen.device.display_image, "discharging.png")

battery.capacity = 98
battery.on_capacity_change(98)
sleep(1.5)
assert miniscreen.device.display_image == image_snapshot(name="capacity-change")
snapshot.assert_match(miniscreen.device.display_image, "capacity-change.png")


def test_overview_network(miniscreen, image_snapshot, internal_ip):
def test_overview_network(miniscreen, snapshot, internal_ip):
internal_ip("wlan0", "192.168.192.168")
sleep(4.5)
assert miniscreen.device.display_image == image_snapshot(name="wifi")
snapshot.assert_match(miniscreen.device.display_image, "wifi.png")

internal_ip("eth0", "10.255.10.255")
sleep(4.5)
assert miniscreen.device.display_image == image_snapshot(name="ethernet")
snapshot.assert_match(miniscreen.device.display_image, "ethernet.png")

internal_ip("ptusb0", "172.31.172.31")
sleep(4.5)
assert miniscreen.device.display_image == image_snapshot(name="usb")
snapshot.assert_match(miniscreen.device.display_image, "usb.png")

internal_ip("lo", "127.0.0.1")
sleep(4.5)
assert miniscreen.device.display_image == image_snapshot(name="localhost")
snapshot.assert_match(miniscreen.device.display_image, "localhost.png")
86 changes: 66 additions & 20 deletions tests/plugins/snapshot_reporter.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,78 @@
import pickle

import pytest
from imgcat import imgcat


@pytest.hookimpl(trylast=True)
def pytest_terminal_summary(terminalreporter, exitstatus, config) -> None:
if exitstatus == 0:
return
def is_master(config):
return not hasattr(config, "workerinput")

terminalreporter.write_sep("-", "snapshot diff")

syrupy_report = terminalreporter.config._syrupy.report
for assertion in syrupy_report.assertions:
for execution in assertion.executions.values():
if execution.success:
continue
def pytest_configure(config):
plugin = SnapshotReporter(config)
config.pluginmanager.register(plugin)

terminalreporter.write_line(execution.snapshot_location)

terminalreporter.write_line("Expected:")
if execution.recalled_data:
imgcat(execution.recalled_data)
else:
terminalreporter.write_line("None")
class SnapshotReporter:
def __init__(self, config):
self.config = config
self.is_master = is_master(config)
self.failed_snapshots = []

def track_snapshot(self, snapshot):
def track_assert(fn):
def tracked(value, snapshot_name):
try:
fn(value, snapshot_name)
except AssertionError as e:
path = snapshot._snapshot_path(snapshot_name)
self.failed_snapshots.append(
{
"path": str(path),
"received": value,
"expected": path.read_bytes() if path.is_file() else None,
}
)

raise e

return tracked

snapshot.assert_match = track_assert(snapshot.assert_match)
snapshot.assert_match_dir = track_assert(snapshot.assert_match_dir)

@pytest.fixture
def snapshot(self, snapshot):
self.track_snapshot(snapshot)
yield snapshot

@pytest.hookimpl(hookwrapper=True, trylast=True)
def pytest_sessionfinish(self, session, exitstatus):
yield
if not self.is_master:
self.config.workeroutput["failed_snapshots"] = pickle.dumps(
self.failed_snapshots
)

def pytest_testnodedown(self, node, error):
worker_snapshots = pickle.loads(node.workeroutput["failed_snapshots"])
self.failed_snapshots.extend(worker_snapshots)

@pytest.hookimpl(trylast=True)
def pytest_terminal_summary(self, terminalreporter, exitstatus):
if exitstatus == 0:
return

terminalreporter.write_sep("=", "snapshot diff")

for failed_snapshot in self.failed_snapshots:
terminalreporter.write_sep("-", failed_snapshot["path"])

terminalreporter.write_line("Received:")
if execution.asserted_data:
imgcat(execution.asserted_data)
imgcat(failed_snapshot["received"])

terminalreporter.write_line("Expected:")
if failed_snapshot["expected"]:
imgcat(failed_snapshot["expected"])
else:
terminalreporter.write_line("None")

terminalreporter.write_line("\n")
3 changes: 2 additions & 1 deletion tests/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
pytest
pytest-cov
pytest-mock
syrupy>=1.7.4
pytest-xdist
pytest-snapshot>=0.8.1
Pillow>=8.1.2
imgcat>=0.5.0
psutil>=5.8.0
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 6c20113

Please sign in to comment.