Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,13 @@ jobs:
- name: Install dependencies
run: pip install -r requirements-dev.txt

- name: Install Xvfb for the UI tests
run: sudo apt-get update && sudo apt-get install -y xvfb

- name: Run tests
run: pytest
run: xvfb-run -a pytest
env:
REQUIRE_UI_TESTS: "1"

lint:
runs-on: ubuntu-latest
Expand Down
1 change: 1 addition & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,5 +168,6 @@ pip install -r requirements-dev.txt
| Type checks | `mypy` |

The UI tests are skipped when no display is available. On Linux they can be run with `xvfb-run -a pytest`.
CI sets `REQUIRE_UI_TESTS=1`, which makes them fail instead of being skipped.

Pull requests target the `dev` branch.
2 changes: 1 addition & 1 deletion renovate.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:base"
"config:recommended"
],
"rebaseWhen": "conflicted",
"labels": [
Expand Down
6 changes: 2 additions & 4 deletions test/tkinter_floor_test.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
"""Unit tests for the TkinterFloor class."""

import importlib.util
import unittest

import numpy

from floors.floor import Floor
from generators.generator import Generator
from rooms.teleport_room import TeleportRoom
from ui_test_support import requires_tkinter

_HAS_TKINTER = importlib.util.find_spec("tkinter") is not None


@unittest.skipUnless(_HAS_TKINTER, "tkinter is not available")
@requires_tkinter()
class TkinterFloorTest(unittest.TestCase):
def setUp(self) -> None:
# pylint: disable=import-outside-toplevel
Expand Down
19 changes: 2 additions & 17 deletions test/tkinter_generator_test.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,15 @@
"""Unit tests for the TkinterGenerator class."""

import importlib.util
import tempfile
import time
import unittest
from unittest import mock

from ui_test_support import requires_display
from utils import globals as my_globals

_HAS_TKINTER = importlib.util.find_spec("tkinter") is not None


def _has_display() -> bool:
if not _HAS_TKINTER:
return False
# pylint: disable=import-outside-toplevel
import tkinter

try:
tkinter.Tk().destroy()
except tkinter.TclError:
return False
return True


@unittest.skipUnless(_has_display(), "tkinter or a display is not available")
@requires_display()
class TkinterGeneratorTest(unittest.TestCase):
def setUp(self) -> None:
# pylint: disable=import-outside-toplevel
Expand Down
6 changes: 2 additions & 4 deletions test/tkinter_teleport_room_test.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
"""Unit tests for the TkinterTeleportRoom class."""

import importlib.util
import unittest

from ui_test_support import requires_tkinter
from utils.room_type import RoomType

_HAS_TKINTER = importlib.util.find_spec("tkinter") is not None


@unittest.skipUnless(_HAS_TKINTER, "tkinter is not available")
@requires_tkinter()
class TkinterTeleportRoomTest(unittest.TestCase):
def setUp(self) -> None:
# pylint: disable=import-outside-toplevel
Expand Down
60 changes: 60 additions & 0 deletions test/ui_test_support.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
"""
Helpers to skip the UI tests when tkinter or a display is not available.

Set the environment variable REQUIRE_UI_TESTS=1 to make these tests fail
instead of being skipped, e.g. in CI where they must always run.
"""

import importlib.util
import os
import unittest
from typing import Callable, TypeVar

T = TypeVar("T")

_REQUIRED = os.environ.get("REQUIRE_UI_TESTS") == "1"


def has_tkinter() -> bool:
"""
Returns if tkinter can be imported.
"""
return importlib.util.find_spec("tkinter") is not None


def has_display() -> bool:
"""
Returns if tkinter can open a window.
"""
if not has_tkinter():
return False
# pylint: disable=import-outside-toplevel
import tkinter

try:
tkinter.Tk().destroy()
except tkinter.TclError:
return False
return True


def _require(available: bool, reason: str) -> Callable[[T], T]:
if available:
return lambda test: test
if _REQUIRED:
raise RuntimeError(f"UI tests are required but {reason}")
return unittest.skip(reason)


def requires_tkinter() -> Callable[[T], T]:
"""
Skips the decorated tests if tkinter is not available.
"""
return _require(has_tkinter(), "tkinter is not available")


def requires_display() -> Callable[[T], T]:
"""
Skips the decorated tests if tkinter cannot open a window.
"""
return _require(has_display(), "tkinter or a display is not available")
Loading