Skip to content

Commit b478cd2

Browse files
committed
Add missing CI tests (full matrix, sphinx, runtime tests)
1 parent fb20014 commit b478cd2

6 files changed

Lines changed: 114 additions & 52 deletions

File tree

.github/workflows/ci.yml

Lines changed: 73 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,30 +21,97 @@ on:
2121
- "setup.cfg"
2222
- "**/*.py"
2323

24+
concurrency:
25+
# Cancel previous runs for the same PR
26+
# Don't cancel successive pushes to master
27+
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
28+
cancel-in-progress: true
29+
2430
jobs:
2531
mypy:
2632
runs-on: ${{ matrix.os }}
33+
timeout-minutes: &timeout-minutes 25
2734
strategy:
35+
# mypy is os and python-version sensitive. Test on all supported combinations
2836
matrix:
29-
os: [windows-latest, ubuntu-latest, macos-latest]
30-
python-version: ["3.10"]
37+
# Arm runners are faster (as long as the same wheels are available)
38+
os: [windows-11-arm, ubuntu-24.04-arm, macos-latest]
39+
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
40+
# actions/setup-python doesn't support Python < 3.11 on ARM
41+
exclude:
42+
- python-version: "3.9"
43+
os: windows-11-arm
44+
- python-version: "3.10"
45+
os: windows-11-arm
46+
include:
47+
- python-version: "3.9"
48+
os: windows-latest
49+
- python-version: "3.10"
50+
os: windows-latest
3151
fail-fast: false
3252
steps:
33-
- uses: actions/checkout@v4
53+
- uses: actions/checkout@v6
3454
- uses: actions/setup-python@v6
3555
with:
3656
python-version: ${{ matrix.python-version }}
57+
cache: "pip"
3758
- run: pip install . --group=dev
3859
- run: mypy . --python-version=${{ matrix.python-version }}
3960

61+
tests:
62+
runs-on: ${{ matrix.os }}
63+
timeout-minutes: *timeout-minutes
64+
strategy:
65+
# Test on all supported runtime combinations
66+
matrix:
67+
# Arm runners are faster (as long as the same wheels are available)
68+
# PyWinCtl doesn't have any code that should act differently per architecture
69+
os: [windows-11-arm, ubuntu-24.04-arm, macos-latest]
70+
# TODO: Run tests in parallel on free-threaded python to catch free-threading issues
71+
# See: https://py-free-threading.github.io/testing/
72+
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
73+
# actions/setup-python doesn't support Python < 3.11 on ARM
74+
exclude:
75+
- python-version: "3.9"
76+
os: windows-11-arm
77+
- python-version: "3.10"
78+
os: windows-11-arm
79+
include:
80+
- python-version: "3.9"
81+
os: windows-latest
82+
- python-version: "3.10"
83+
os: windows-latest
84+
fail-fast: false
85+
steps:
86+
- uses: actions/checkout@v6
87+
- uses: actions/setup-python@v6
88+
with:
89+
python-version: ${{ matrix.python-version }}
90+
cache: "pip"
91+
- run: pip install . --group=dev
92+
- name: Install Linux Packages
93+
if: ${{ startsWith(matrix.os, 'ubuntu') }}
94+
run: |
95+
sudo apt update
96+
sudo apt install gedit xvfb x11-xserver-utils openbox dbus-x11
97+
- name: Run tests (Linux)
98+
if: ${{ startsWith(matrix.os, 'ubuntu') }}
99+
working-directory: tests
100+
run: xvfb-run --server-args="-screen 0 1280x1024x24" bash -c "openbox & sleep 1 && dbus-launch --exit-with-session python test_pywinctl.py"
101+
- name: Run tests (Windows & macOS)
102+
run: python test_pywinctl.py
103+
if: ${{ !startsWith(matrix.os, 'ubuntu') }}
104+
working-directory: tests
105+
40106
sphinx:
41-
runs-on: ubuntu-22.04 # Keep in sync with build.os in .readthedocs.yaml
107+
runs-on: ubuntu-24.04-arm # Keep in sync with build.os in .readthedocs.yaml
108+
timeout-minutes: *timeout-minutes
42109
steps:
43-
- uses: actions/checkout@v4
110+
- uses: actions/checkout@v6
44111
- uses: actions/setup-python@v6
45112
with:
46113
python-version: "3.11" # Keep in sync with build.tools.python in .readthedocs.yaml
47114
- run: pip install . --group=docs
48115
- name: Build docs
49116
# TODO: Add --fail-on-warning, but still too many warnings right now
50-
run: sphinx-build --keep-going -b html docs/source docs/_build/html
117+
run: sphinx-build --keep-going --builder html docs/source docs/_build/html

.readthedocs.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ version: 2
66

77
# Set the OS, Python version and other tools you might need
88
build:
9-
os: ubuntu-22.04 # Keep in sync with runs-on in .github/workflows/docs.yml
9+
os: ubuntu-24.04 # Keep in sync with runs-on in .github/workflows/ci.yml
1010
tools:
11-
python: "3.11" # Keep in sync with python-version in .github/workflows/docs.yml
11+
python: "3.11" # Keep in sync with python-version in .github/workflows/ci.yml
1212
# You can also specify other tool versions:
1313
# nodejs: "20"
1414
# rust: "1.70"

CHANGES.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
0.4.02, 2026/06/16 -- ALL: Fixed WatchDog.stop() not joining the worker thread. Known to have been causing Xlib race conditions on Linux
12
0.4.01, 2024/09/22 -- ALL: Added getAllWindowsDict() general function. Added getPID() method.
23
LINUX: Added bad window filter to check for window.id == 0
34
0.4, 2023/10/11 -- ALL: Added getMonitor() as alias for getDisplay()

setup.cfg

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
description_file = README.md
33

44
[mypy]
5-
python_version = 3.9
65
mypy_path = src/, typings/
76
exclude = build
87
strict = True
@@ -19,5 +18,3 @@ warn_unused_ignores = False
1918
disable_error_code =
2019
# https://github.com/python/mypy/issues/6232 (redefinition with correct type)
2120
attr-defined, assignment,
22-
# https://github.com/python/mypy/issues/13975 (@property mistaken as Callable)
23-
comparison-overlap, truthy-function

src/pywinctl/_main.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -582,6 +582,7 @@ def stop(self):
582582
"""
583583
if self._watchdog:
584584
self._watchdog.kill()
585+
self._watchdog.join()
585586

586587
def isAlive(self):
587588
"""Check if watchdog is running

tests/test_pywinctl.py

Lines changed: 37 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,16 @@
55
import subprocess
66
import sys
77
import time
8+
from typing import TypedDict
89

910
import pywinctl
1011

1112

13+
class GetWindowKwargs(TypedDict):
14+
title: str
15+
condition: int # TODO: Consider making pywinctl.Re an IntEnum
16+
17+
1218
def test_basic():
1319

1420
print("PLATFORM:", sys.platform)
@@ -26,52 +32,41 @@ def test_basic():
2632
print()
2733

2834
if sys.platform == "win32":
29-
subprocess.Popen('notepad')
30-
time.sleep(2)
31-
32-
testWindows = [pywinctl.getActiveWindow()]
33-
assert len(testWindows) == 1
34-
35-
npw = testWindows[0]
36-
wait = True
37-
timelap = 0.50
38-
39-
basic_test(npw, wait, timelap)
40-
35+
process = "notepad"
36+
get_window_kwargs: GetWindowKwargs = {
37+
"title": "Notepad",
38+
"condition": pywinctl.Re.ENDSWITH,
39+
}
4140
elif sys.platform == "linux":
42-
subprocess.Popen('gedit')
43-
time.sleep(2)
44-
45-
testWindows = [pywinctl.getActiveWindow()]
46-
assert len(testWindows) == 1
47-
48-
npw = testWindows[0]
49-
wait = True
50-
timelap = 0.50
51-
52-
basic_test(npw, wait, timelap)
53-
41+
process = "gedit"
42+
get_window_kwargs: GetWindowKwargs = {
43+
"title": "gedit",
44+
"condition": pywinctl.Re.ENDSWITH,
45+
}
5446
elif sys.platform == "darwin":
5547
if not pywinctl.checkPermissions(activate=True):
5648
exit()
57-
subprocess.Popen(['touch', 'test.py'])
58-
time.sleep(2)
59-
subprocess.Popen(['open', '-a', 'TextEdit', 'test.py'])
60-
time.sleep(5)
61-
62-
testWindows = pywinctl.getWindowsWithTitle('test.py')
63-
assert len(testWindows) == 1
64-
65-
npw = testWindows[0]
66-
wait = True
67-
timelap = 0.50
49+
process = ["open", "-a", "TextEdit", __file__]
50+
get_window_kwargs: GetWindowKwargs = {
51+
"title": "test_pywinctl.py",
52+
"condition": pywinctl.Re.IS,
53+
}
54+
else:
55+
raise NotImplementedError(
56+
"PyWinCtl currently does not support this platform. "
57+
+ "If you have useful knowledge, please contribute! https://github.com/Kalmat/PyWinCtl"
58+
)
6859

69-
basic_test(npw, wait, timelap)
70-
subprocess.Popen(['rm', 'test.py'])
60+
subprocess.Popen(process)
7161

72-
else:
73-
raise NotImplementedError('PyWinCtl currently does not support this platform. If you have useful knowledge, please contribute! https://github.com/Kalmat/pywinctl')
62+
testWindows: list[pywinctl.Window] = []
63+
deadline = time.time() + 15
64+
while not testWindows and time.time() < deadline:
65+
time.sleep(0.5)
66+
testWindows = pywinctl.getWindowsWithTitle(**get_window_kwargs)
67+
assert len(testWindows) == 1
7468

69+
basic_test(npw=testWindows[0], wait=True, timelap=0.50)
7570

7671
def basic_test(npw: pywinctl.Window | None, wait: bool, timelap: float):
7772
assert npw is not None
@@ -249,8 +244,9 @@ def activeCB(isActive):
249244
print("PARENT INFO")
250245
parent = npw.getParent()
251246
if parent:
252-
print("WINDOW PARENT:", parent, npw.isChild(parent))
253-
assert npw.isChild(parent)
247+
is_child = npw.isChild(parent)
248+
print("WINDOW PARENT:", parent, is_child)
249+
assert is_child
254250
children = npw.getChildren()
255251
for child in children:
256252
if child and isinstance(child, int):

0 commit comments

Comments
 (0)