Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Python3.13 and iOS/macOS ARM64 testing to CI #2404

Merged
merged 11 commits into from
Feb 20, 2024
Merged
65 changes: 47 additions & 18 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ defaults:

# Cancel active CI runs for a PR before starting another run
concurrency:
group: ${{ github.ref }}
group: ${{ github.workflow}}-${{ github.ref }}
cancel-in-progress: true

jobs:
Expand Down Expand Up @@ -60,18 +60,37 @@ jobs:
- "winforms"

core:
runs-on: ${{ matrix.platform }}-latest
runs-on: ${{ matrix.platform }}
needs: [pre-commit, towncrier, package]
continue-on-error: ${{ matrix.experimental }}
strategy:
fail-fast: false
matrix:
platform: [ "macos", "ubuntu", "windows" ]
python-version: [ "3.8", "3.9", "3.10", "3.11", "3.12" ]
platform: [ "macos-12", "macos-14", "ubuntu-latest", "windows-latest" ]
python-version: [ "3.8", "3.12", "3.13-dev" ]
include:
- experimental: false
# - python-version: "3.13-dev"
# experimental: true
# Test Python 3.9-3.11 on Ubuntu only
- platform: "ubuntu-latest"
python-version: "3.9"
experimental: false
- platform: "ubuntu-latest"
python-version: "3.10"
experimental: false
- platform: "ubuntu-latest"
python-version: "3.11"
experimental: false
# Allow development Python to fail without failing entire job.
- python-version: "3.13-dev"
experimental: true
exclude:
# macos-14 (i.e. arm64) does not support Python 3.8
- platform: "macos-14"
python-version: "3.8"

# Pillow isn't available for Python 3.13 on Windows
- platform: "windows-latest"
python-version: "3.13-dev"
steps:
- uses: actions/checkout@v4.1.1
- name: Set up Python ${{ matrix.python-version }}
Expand Down Expand Up @@ -144,22 +163,29 @@ jobs:
strategy:
fail-fast: false
matrix:
backend: [ "macOS", "windows", "linux", "android", "iOS" ]
backend: [ "macOS-x86_64", "macOS-arm64", "windows", "linux", "android", "iOS" ]
include:
- pre-command:
briefcase-run-prefix:
briefcase-run-args:
setup-python: true

- backend: macOS
runs-on: macos-12
- backend: "macOS-x86_64"
platform: "macOS"
runs-on: "macos-12"
app-user-data-path: $HOME/Library/Application Support/org.beeware.toga.testbed

- backend: "macOS-arm64"
platform: "macOS"
runs-on: "macos-14"
app-user-data-path: $HOME/Library/Application Support/org.beeware.toga.testbed

# We use a fixed Ubuntu version rather than `-latest` because at some point,
# `-latest` will be updated, but it will be a soft changeover, which would cause
# the system Python version to become inconsistent from run to run.
- backend: linux
runs-on: ubuntu-22.04
- backend: "linux"
platform: "linux"
runs-on: "ubuntu-22.04"
# The package list should be the same as in tutorial-0.rst, and the BeeWare
# tutorial, plus blackbox to provide a window manager. We need a window
# manager that is reasonably lightweight, honors full screen mode, and
Expand All @@ -185,17 +211,20 @@ jobs:
setup-python: false # Use the system Python packages.
app-user-data-path: $HOME/.local/share/testbed

- backend: windows
runs-on: windows-latest
- backend: "windows"
platform: "windows"
runs-on: "windows-latest"
app-user-data-path: $HOME\AppData\Local\Tiberius Yak\Toga Testbed\Data

- backend: iOS
runs-on: macos-12
- backend: "iOS"
platform: "iOS"
runs-on: "macos-14"
briefcase-run-args: ' -d "iPhone SE (3rd generation)"'
app-user-data-path: $(xcrun simctl get_app_container booted org.beeware.toga.testbed data)/Documents

- backend: android
runs-on: ubuntu-latest
- backend: "android"
platform: "android"
runs-on: "ubuntu-latest"
briefcase-run-args: " -d '{\"avd\":\"beePhone\",\"skin\":\"pixel_3a\"}' --Xemulator=-no-window --Xemulator=-no-snapshot --Xemulator=-no-audio --Xemulator=-no-boot-anim --shutdown-on-exit"
pre-command: |
# check if virtualization is supported...
Expand Down Expand Up @@ -230,7 +259,7 @@ jobs:
- name: Test App
working-directory: testbed
timeout-minutes: 15
run: ${{ matrix.briefcase-run-prefix }} briefcase run ${{ matrix.backend }} --test ${{ matrix.briefcase-run-args }}
run: ${{ matrix.briefcase-run-prefix }} briefcase run ${{ matrix.platform }} --test ${{ matrix.briefcase-run-args }}

- name: Upload logs
uses: actions/upload-artifact@v4.3.1
Expand Down
1 change: 1 addition & 0 deletions android/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ classifiers = [
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3 :: Only",
"Topic :: Software Development",
"Topic :: Software Development :: User Interfaces",
Expand Down
1 change: 1 addition & 0 deletions changes/2383.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Issues with running the testbed on macOS 14 (Sonoma) were resolved.
1 change: 1 addition & 0 deletions changes/2404.feature.1.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Support for Python 3.13 was added.
1 change: 1 addition & 0 deletions changes/2404.feature.2.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Toga's release processes now include automated testing on ARM64.
1 change: 1 addition & 0 deletions cocoa/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ classifiers = [
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3 :: Only",
"Topic :: Software Development",
"Topic :: Software Development :: User Interfaces",
Expand Down
12 changes: 10 additions & 2 deletions cocoa/src/toga_cocoa/widgets/detailedlist.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ class TogaList(NSTableView):
interface = objc_property(object, weak=True)
impl = objc_property(object, weak=True)

@objc_method
def didCloseMenu_withEvent_(self, menu, event) -> None:
# When the menu closes, drop the reference to the menu object.
self.impl._popup = None

@objc_method
def menuForEvent_(self, event):
if self.impl.primary_action_enabled or self.impl.secondary_action_enabled:
Expand Down Expand Up @@ -49,9 +54,12 @@ def menuForEvent_(self, event):
)
secondary_action_item.tag = row

return popup
else:
return None
popup = None

# Preserve a Python reference to the popup for testing purposes.
self.impl._popup = popup
return popup

@objc_method
def primaryActionOnRow_(self, menuitem):
Expand Down
7 changes: 6 additions & 1 deletion cocoa/src/toga_cocoa/widgets/internal/refresh.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,12 @@ def constrainScrollPoint_(self, proposedNewOrigin: NSPoint) -> NSPoint:
argtypes=[NSPoint],
)

if self.superview and self.superview.is_refreshing:
# FIXME: This has been marked no-cover so that ARM64 testing can be enabled;
# ARM64 CI can only run on Sonoma, and it looks like Sonoma has turned off
# scroll elasticity by default, which prevents pull-to-refresh from working.
# See Toga#2412 for details. If that ticket is closed, it should be possible
# to remove this this no-cover.
if self.superview and self.superview.is_refreshing: # pragma: no cover
return NSMakePoint(
constrained.x,
max(
Expand Down
37 changes: 13 additions & 24 deletions cocoa/tests_backend/widgets/detailedlist.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,39 +179,28 @@ async def refresh_action(self, active=True):
while self.scroll_position < 0:
await asyncio.sleep(0.01)

async def _perform_action(self, row, offset):
async def _perform_action(self, row, index):
point = self.row_position(row)
# First click to show menu

# Click to show menu
await self.mouse_event(
NSEventType.RightMouseDown,
point,
delay=0.1,
)
await self.redraw("Action menu has been displayed")

# Pick a point a little to the right of the point where the menu was displayed,
# and slightly lower (in reversed y coordinates) to select a menu item.
point2 = NSPoint(point.x + 10, point.y - offset)
await self.mouse_event(
NSEventType.LeftMouseDown,
point2,
delay=0.1,
)
await self.mouse_event(
NSEventType.LeftMouseUp,
point2,
delay=0.1,
)
popup = self.impl._popup
if popup:
popup.performActionForItemAtIndex(index)
popup.cancelTracking()

# Wait until the popup menu is fully disposed.
while self.impl._popup is not None:
await self.redraw("Action has been selected", delay=0.1)

async def perform_primary_action(self, row, active=True):
# 10px is enough to select the first menu item. It doesn't matter whether the
# action is active or not; if the action is inactive, it will either press the
# wrong action, or press empty space.
await self._perform_action(row, 10)
await self._perform_action(row, 0)

async def perform_secondary_action(self, row, active=True):
# 30px is enough to select the second menu item. However the secondary action
# will be in position 1 on the menu if the primary action is disabled. It
# doesn't matter whether the action is active or not; if the action is inactive,
# it will either press the wrong action, or press empty space.
await self._perform_action(row, 30 if self.impl.primary_action_enabled else 10)
await self._perform_action(row, 1 if self.impl.primary_action_enabled else 0)
7 changes: 2 additions & 5 deletions cocoa/tests_backend/widgets/selection.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,5 @@ async def select_item(self):
# Use a short delaly instead.
await self.mouse_event(NSEventType.LeftMouseDown, point, delay=0.1)

# macOS coordinate systems are backwards, so to select the item *above*
# the current selection, you need to *add* height.
point.y = point.y + self.height
await self.mouse_event(NSEventType.LeftMouseDown, point, delay=0.1)
await self.mouse_event(NSEventType.LeftMouseUp, point, delay=0.1)
self.native.menu.performActionForItemAtIndex(1)
self.native.menu.cancelTracking()
1 change: 1 addition & 0 deletions core/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ classifiers = [
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3 :: Only",
"Topic :: Software Development",
"Topic :: Software Development :: User Interfaces",
Expand Down
1 change: 1 addition & 0 deletions demo/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ classifiers = [
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3 :: Only",
"Topic :: Software Development",
"Topic :: Software Development :: User Interfaces",
Expand Down
1 change: 1 addition & 0 deletions dummy/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ classifiers = [
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3 :: Only",
"Topic :: Software Development",
"Topic :: Software Development :: User Interfaces",
Expand Down
1 change: 1 addition & 0 deletions gtk/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ classifiers = [
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3 :: Only",
"Topic :: Software Development",
"Topic :: Software Development :: User Interfaces",
Expand Down
1 change: 1 addition & 0 deletions iOS/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ classifiers = [
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3 :: Only",
"Topic :: Software Development",
"Topic :: Software Development :: User Interfaces",
Expand Down
20 changes: 14 additions & 6 deletions iOS/tests_backend/widgets/detailedlist.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import asyncio
import platform
import os

from rubicon.objc.api import Block
from rubicon.objc.api import Block, ObjCClass

from toga_iOS.libs import (
NSIndexPath,
Expand All @@ -13,6 +13,8 @@

from .base import SimpleProbe, UIControlEventValueChanged

UIDevice = ObjCClass("UIDevice")


class DetailedListProbe(SimpleProbe):
native_class = UITableView
Expand Down Expand Up @@ -50,11 +52,17 @@ def assert_cell_content(self, row, title, subtitle, icon=None):
@property
def max_scroll_position(self):
max_value = int(self.native.contentSize.height - self.native.frame.size.height)
# The max value is a little higher on iOS 17.
# Not sure where the 34 extra pixels are coming from. It appears to be
# a constant, independent of the number of rows of data.
if int(platform.release().split(".")[0]) >= 17:
# The max value is a dependent on the device; devices that don't have a physical
# button report as being a little larger. A physical device will give you the
# model identifier as part of UIDevice.currentDevice.model; however, simulators
# return "iPhone" as the model, so we need to check the environment as well to
# reliably get the device identifier. "iPhone14,6" is an iPhone SE 3rd edition.
# As of Feb 2023, it's the only device currently sold that has a physical
# button.
model = os.getenv("SIMULATOR_MODEL_IDENTIFIER", UIDevice.currentDevice.model)
if model != "iPhone14,6":
max_value += 34

return max(0, max_value)

@property
Expand Down
1 change: 1 addition & 0 deletions nursery/curses/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ classifiers = [
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3 :: Only",
"Topic :: Software Development",
"Topic :: Software Development :: User Interfaces",
Expand Down
1 change: 1 addition & 0 deletions nursery/qt/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ classifiers = [
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3 :: Only",
"Topic :: Software Development",
"Topic :: Software Development :: User Interfaces",
Expand Down
1 change: 1 addition & 0 deletions nursery/tvOS/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ classifiers = [
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3 :: Only",
"Topic :: Software Development",
"Topic :: Software Development :: User Interfaces",
Expand Down
1 change: 1 addition & 0 deletions nursery/uwp/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ classifiers = [
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3 :: Only",
"Topic :: Software Development",
"Topic :: Software Development :: User Interfaces",
Expand Down
1 change: 1 addition & 0 deletions nursery/watchOS/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ classifiers = [
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3 :: Only",
"Topic :: Software Development",
"Topic :: Software Development :: User Interfaces",
Expand Down
1 change: 1 addition & 0 deletions textual/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ classifiers = [
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3 :: Only",
"Topic :: Software Development",
"Topic :: Software Development :: User Interfaces",
Expand Down
1 change: 1 addition & 0 deletions toga/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ classifiers = [
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3 :: Only",
"Topic :: Software Development",
"Topic :: Software Development :: User Interfaces",
Expand Down
2 changes: 1 addition & 1 deletion tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ extend-ignore =
E203,

# The leading comma generates the "py" environment.
[testenv:py{,38,39,310,311,312}]
[testenv:py{,38,39,310,311,312,313}]
skip_install = True
setenv =
TOGA_BACKEND = toga_dummy
Expand Down
Loading
Loading