Skip to content

Commit e6b50d6

Browse files
requested changes
Signed-off-by: Omkar Sarkar <omkarsarkar24@gmail.com>
1 parent e72707f commit e6b50d6

8 files changed

Lines changed: 85 additions & 63 deletions

.github/workflows/pytest.yml

Lines changed: 48 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,17 @@
11
name: Pytest
22

3-
# https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#packaging-workflow-data-as-artifacts
4-
53
on:
64
pull_request:
75
paths:
8-
- '**/*.py' # Watch for changes in any Python files
9-
- 'pyproject.toml' # Watch for changes in the pyproject.toml file
6+
- '**/*.py'
7+
- 'pyproject.toml'
108
- '.github/workflows/pytest.yml'
119
push:
1210
branches:
13-
- master # Only run on push to master branch
11+
- master
1412
paths:
15-
- '**/*.py' # Watch for changes in any Python files
16-
- 'pyproject.toml' # Watch for changes in the pyproject.toml file
13+
- '**/*.py'
14+
- 'pyproject.toml'
1715
- '.github/workflows/pytest.yml'
1816
workflow_dispatch:
1917
release:
@@ -29,28 +27,32 @@ jobs:
2927
strategy:
3028
fail-fast: false
3129
matrix:
32-
# os: [ubuntu-latest, macos-latest, windows-latest]
33-
# python-version: ["3.9", "3.10", "3.11", "3.12", "pypy3.9", "pypy3.10"]
34-
os: [ubuntu-latest]
35-
python-version: ["3.9", "3.14", "3.14t"]
30+
include:
31+
- os: ubuntu-latest
32+
python-version: "3.9" # to make sure the minimum AMC supported python version does not break
33+
- os: ubuntu-latest
34+
python-version: "3.14" # to make sure the latest AMC supported python version does not break
35+
- os: macos-latest
36+
python-version: "3.14" # The macOS .dmg file uses the latest python version
3637

3738
steps:
3839
- name: Harden the runner (Audit all outbound calls)
40+
if: matrix.os != 'windows-latest'
3941
uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
4042
with:
4143
egress-policy: audit
4244

4345
- name: Checkout
4446
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
4547

46-
# https://docs.astral.sh/uv/guides/integration/github/
4748
- name: Install uv and set the Python version
4849
uses: astral-sh/setup-uv@eac588ad8def6316056a12d4907a9d4d84ff7a3b # v7.3.0
4950
with:
5051
python-version: ${{ matrix.python-version }}
5152
activate-environment: true
5253

53-
- name: Install system dependencies for GUI testing
54+
- name: Install system dependencies for GUI testing on linux
55+
if: matrix.os == 'ubuntu-latest'
5456
run: |
5557
sudo apt-get update
5658
# Only install system Tcl/Tk for Python < 3.13 (newer versions bundle their own)
@@ -62,35 +64,32 @@ jobs:
6264
# Only install tools, not Tcl/Tk libraries
6365
sudo apt-get install -y scrot xdotool x11-utils gnome-screenshot
6466
fi
67+
sudo apt-get install -y xvfb libx11-6 libxrender1 libxext6 libsm6
6568
python3 --version && python3 -c "import tkinter; print(tkinter.TclVersion, tkinter.TkVersion)"
6669
67-
- name: Ensure Tcl/Tk search paths
70+
- name: Install system dependencies for GUI testing on macOS
71+
if: matrix.os == 'macos-latest'
6872
run: |
69-
# Only set Tcl/Tk paths for Python < 3.13
70-
if [[ "${{ matrix.python-version }}" < "3.13" ]]; then
71-
echo "Setting Tcl/Tk paths for Python ${{ matrix.python-version }}"
72-
echo "TCL_LIBRARY=/usr/share/tcltk/tcl8.6" >> $GITHUB_ENV
73-
echo "TK_LIBRARY=/usr/share/tcltk/tk8.6" >> $GITHUB_ENV
74-
else
75-
echo "Python ${{ matrix.python-version }} uses bundled Tcl/Tk, no custom paths needed"
76-
fi
73+
brew install tcl-tk
74+
echo "LDFLAGS=-L$(brew --prefix tcl-tk)/lib" >> $GITHUB_ENV
75+
echo "CPPFLAGS=-I$(brew --prefix tcl-tk)/include" >> $GITHUB_ENV
76+
echo "PKG_CONFIG_PATH=$(brew --prefix tcl-tk)/lib/pkgconfig" >> $GITHUB_ENV
77+
python3 -c "import tkinter; print('Tk OK:', tkinter.TclVersion, tkinter.TkVersion)"
7778
7879
- name: Install dependencies and application
79-
# without --editable, the coverage report is not generated correctly
8080
run: |
8181
uv pip install --editable .[dev,ci_headless_tests]
8282
8383
- name: Download ArduCopter SITL (if available)
84+
if: matrix.os == 'ubuntu-latest'
8485
run: |
85-
# Create cache key based on current quarter (YYYY-Q)
8686
CURRENT_YEAR=$(date +%Y)
8787
CURRENT_MONTH=$(date +%m)
8888
QUARTER=$(( (CURRENT_MONTH-1)/3 + 1 ))
8989
CACHE_KEY="${CURRENT_YEAR}-Q${QUARTER}"
9090
9191
echo "Cache key: ${CACHE_KEY}"
9292
93-
# Check if we have cached SITL files for this quarter
9493
if [ -d "sitl-cache/${CACHE_KEY}" ] && [ -f "sitl-cache/${CACHE_KEY}/arducopter" ]; then
9594
echo "Using cached SITL files from ${CACHE_KEY}"
9695
mkdir -p sitl/
@@ -99,20 +98,16 @@ jobs:
9998
echo "Downloading fresh SITL files for ${CACHE_KEY}"
10099
mkdir -p sitl/ sitl-cache/${CACHE_KEY}/
101100
102-
# Download latest ArduCopter SITL from official firmware server
103101
curl -L -o sitl/arducopter https://firmware.ardupilot.org/Copter/latest/SITL_x86_64_linux_gnu/arducopter
104102
curl -L -o sitl/firmware-version.txt https://firmware.ardupilot.org/Copter/latest/SITL_x86_64_linux_gnu/firmware-version.txt
105103
curl -L -o sitl/git-version.txt https://firmware.ardupilot.org/Copter/latest/SITL_x86_64_linux_gnu/git-version.txt
106104
107-
# Cache the downloaded files
108105
cp sitl/* sitl-cache/${CACHE_KEY}/
109106
fi
110107
111-
# Make executable and verify
112108
chmod +x sitl/arducopter
113109
ls -la sitl/
114110
115-
# Set environment variables
116111
echo "SITL_BINARY=$(pwd)/sitl/arducopter" >> $GITHUB_ENV
117112
echo "SITL_AVAILABLE=true" >> $GITHUB_ENV
118113
echo "SITL version: $(cat sitl/git-version.txt)"
@@ -132,19 +127,25 @@ jobs:
132127
continue-on-error: false
133128
run: |
134129
export LIBGL_ALWAYS_SOFTWARE=1
135-
export DISPLAY=:99
136-
# disable X authentication
137-
export XAUTHORITY=/dev/null
138-
# disable access control restrictions
139-
Xvfb :99 -screen 0 1024x768x16 -ac &
140-
# ensure Xvfb is fully started before running tests
141-
sleep 2
142-
if [ "$SITL_AVAILABLE" = "true" ]; then
143-
echo "Running tests with SITL support"
144-
uv run pytest --cov=ardupilot_methodic_configurator --cov-report=xml:tests/coverage.xml --md=tests/results-${{ matrix.python-version }}.md --junit-xml=tests/results-junit.xml -m "sitl or not sitl"
130+
131+
if [[ "${{ matrix.os }}" == "ubuntu-latest" ]]; then
132+
export DISPLAY=:99
133+
export XAUTHORITY=/dev/null
134+
Xvfb :99 -screen 0 1024x768x16 -ac &
135+
sleep 2
136+
137+
if [ "$SITL_AVAILABLE" = "true" ]; then
138+
FINAL_MARKER="sitl or not sitl"
139+
else
140+
FINAL_MARKER="not sitl"
141+
fi
142+
143+
echo "Running Ubuntu tests with markers: $FINAL_MARKER"
144+
uv run pytest --cov=ardupilot_methodic_configurator --cov-report=xml:tests/coverage.xml --md=tests/results-${{ matrix.python-version }}.md --junit-xml=tests/results-junit.xml -m "$FINAL_MARKER"
145+
145146
else
146-
echo "Running tests without SITL (mocked tests only)"
147-
uv run pytest --cov=ardupilot_methodic_configurator --cov-report=xml:tests/coverage.xml --md=tests/results-${{ matrix.python-version }}.md --junit-xml=tests/results-junit.xml -m "not sitl"
147+
echo "Running macOS headless tests (skipping GUI files entirely)"
148+
uv run pytest --cov=ardupilot_methodic_configurator --cov-report=xml:tests/coverage.xml --md=tests/results-${{ matrix.python-version }}.md --junit-xml=tests/results-junit.xml -m "not gui" --ignore-glob="tests/acceptance_*.py" --ignore-glob="tests/*frontend*.py" --ignore-glob="tests/*tkinter*.py"
148149
fi
149150
150151
- name: Fix coverage paths
@@ -156,27 +157,24 @@ jobs:
156157

157158
- name: Display test results as GitHub job summary
158159
run: cat tests/results-${{ matrix.python-version }}.md >> $GITHUB_STEP_SUMMARY
159-
# Use always() to always run this step to publish test results when there are test failures
160160
if: ${{ always() }}
161161

162162
- name: Upload coverage xml report
163+
if: matrix.os == 'ubuntu-latest' && always()
163164
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
164165
with:
165166
name: coverage-${{ matrix.python-version }}-xml
166167
path: tests/*.xml
167168
retention-days: 1
168-
# Use always() to always run this step to publish test results when there are test failures
169-
if: ${{ always() }}
170169

171170
- name: Upload coverage report
171+
if: matrix.os == 'ubuntu-latest' && always()
172172
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
173173
with:
174174
name: coverage-${{ matrix.python-version }}
175175
path: .coverage
176176
include-hidden-files: true
177177
retention-days: 1
178-
# Use always() to always run this step to publish test results when there are test failures
179-
if: ${{ always() }}
180178

181179
upload_coverage_to_coveralls:
182180
if: (github.event_name == 'push' && github.ref == 'refs/heads/master') && (success() || failure())
@@ -203,12 +201,10 @@ jobs:
203201
github-token: ${{ secrets.GITHUB_TOKEN }}
204202
files: coverage.xml
205203

206-
# TODO: create a badge that presents the result of the Upload coverage xml report step
207-
208204
check_coverage:
209205
if: success() || failure()
210206
runs-on: ubuntu-latest
211-
needs: pytest # This will ensure this job runs after 'pytest'
207+
needs: pytest
212208

213209
steps:
214210
- name: Harden the runner (Audit all outbound calls)
@@ -224,11 +220,10 @@ jobs:
224220
with:
225221
name: coverage-3.9
226222

227-
# https://docs.astral.sh/uv/guides/integration/github/
228223
- name: Install uv and set the Python version
229224
uses: astral-sh/setup-uv@eac588ad8def6316056a12d4907a9d4d84ff7a3b # v7.3.0
230225
with:
231-
python-version: '3.9' # Match with the coverage report Python version
226+
python-version: '3.9'
232227
activate-environment: true
233228

234229
- name: Install dependencies
@@ -237,7 +232,6 @@ jobs:
237232
238233
- name: Check coverage
239234
run: |
240-
# Check if pytest job failed
241235
if [ "${{ needs.pytest.result }}" == "failure" ]; then
242236
echo "Pytest failed - failing coverage check"
243237
exit 1
@@ -248,11 +242,9 @@ jobs:
248242
if: always()
249243
name: "Publish Tests Results"
250244
runs-on: ubuntu-latest
251-
needs: pytest # This will ensure this job runs after 'pytest'
245+
needs: pytest
252246
permissions:
253247
checks: write
254-
255-
# only needed unless run with comment_mode: off
256248
pull-requests: write
257249

258250
steps:
@@ -296,7 +288,6 @@ jobs:
296288
path: badge.svg
297289

298290
- name: Upload badge to Gist
299-
# Upload only for master branch
300291
if: >
301292
github.event_name == 'workflow_run' && github.event.workflow_run.head_branch == 'master' ||
302293
github.event_name != 'workflow_run' && github.ref == 'refs/heads/master'
@@ -309,7 +300,7 @@ jobs:
309300
add_coverage_to_pullrequest:
310301
if: github.event_name == 'pull_request' && (success() || failure())
311302
runs-on: ubuntu-latest
312-
needs: pytest # This will ensure this job runs after 'pytest'
303+
needs: pytest
313304
permissions:
314305
contents: read
315306
pull-requests: write

pytest.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@ markers =
1414
integration: mark a test as an integration test
1515
slow: mark a test as slow running
1616
sitl: mark a test as requiring SITL (real ArduPilot simulation)
17+
gui: requires real GUI (tkinter, windows, cocoa)

tests/acceptance_battery_monitor.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
)
3131
from ardupilot_methodic_configurator.plugin_factory import plugin_factory
3232

33+
pytestmark = pytest.mark.gui
34+
3335
# pylint: disable=redefined-outer-name,protected-access,too-many-lines
3436

3537

tests/conftest.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,3 +498,11 @@ def sitl_flight_controller(sitl_manager: SITLManager) -> Generator[FlightControl
498498

499499
# Cleanup connection but keep SITL running for subsequent tests
500500
fc.disconnect()
501+
502+
503+
@pytest.fixture(autouse=True)
504+
def mock_center_window_for_macos_only() -> None:
505+
"""Only mock center_window on macOS to prevent Tkinter crashes. Let Linux run normally."""
506+
if platform.system() == "Darwin":
507+
patcher = patch("ardupilot_methodic_configurator.frontend_tkinter_base_window.BaseWindow.center_window")
508+
patcher.start()

tests/gui_frontend_tkinter_parameter_editor.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ def test_show_about_window(self, mocker) -> None: # pylint: disable=too-many-lo
106106
# Check window properties
107107
assert about_window.title() == "About"
108108
# Check that geometry contains the expected size (position may vary)
109+
about_window.update_idletasks()
109110
geometry = about_window.geometry()
110111
assert "650x340" in geometry
111112

tests/test_backend_mavftp.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,7 @@
1717

1818
# from unittest.mock import patch
1919
from io import StringIO
20-
21-
from pymavlink import mavutil
20+
from unittest.mock import MagicMock
2221

2322
# from ardupilot_methodic_configurator.backend_mavftp import ERR_NoErrorCodeInPayload
2423
# from ardupilot_methodic_configurator.backend_mavftp import ERR_NoErrorCodeInNack
@@ -65,10 +64,10 @@ def setUp(self) -> None:
6564
logger.setLevel(logging.DEBUG)
6665

6766
# Mock mavutil.mavlink_connection to simulate a connection
68-
self.mock_master = mavutil.mavlink_connection(device="udp:localhost:14550", source_system=1)
67+
self.mock_master = MagicMock()
6968

7069
# Initialize MAVFTP instance for testing
71-
self.mav_ftp = MAVFTP(self.mock_master, target_system=1, target_component=1)
70+
self.mav_ftp = MAVFTP(self.mock_master, 1, 0)
7271

7372
def tearDown(self) -> None:
7473
self.log_stream.seek(0)

tests/test_frontend_tkinter_rich_text.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -184,8 +184,14 @@ def tearDown(self) -> None:
184184
def test_get_widget_font_family_and_size(self) -> None:
185185
label = ttk.Label(self.root, text="Test")
186186
family, size = get_widget_font_family_and_size(label)
187-
expected_family = ["Segoe UI"] if platform_system() == "Windows" else ["Helvetica", "sans-serif"]
188-
expected_size = [9] if platform_system() == "Windows" else [-12, 10]
187+
expected_family = (
188+
["Segoe UI"]
189+
if platform_system() == "Windows"
190+
else [".AppleSystemUIFont"]
191+
if platform_system() == "Darwin"
192+
else ["Helvetica", "sans-serif"]
193+
)
194+
expected_size = [9] if platform_system() == "Windows" else [13] if platform_system() == "Darwin" else [-12, 10]
189195
assert isinstance(family, str)
190196
assert isinstance(size, int)
191197
assert family in expected_family

0 commit comments

Comments
 (0)