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

Test app #1687

Merged
merged 34 commits into from
Dec 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
d8e33fd
Test app WIP
mhsmith Nov 15, 2022
5ce684a
Fix threading and discovery
mhsmith Nov 21, 2022
90f1873
Split common and backend test code, add more tests
mhsmith Nov 23, 2022
71d7487
Cleanups
mhsmith Nov 28, 2022
0481126
Split test and production code
mhsmith Nov 28, 2022
06dc8ac
Add Android probes
mhsmith Nov 28, 2022
296b424
Add Slider tests
mhsmith Nov 29, 2022
fcd6b9a
Remove `tests` from isort known_first_party list
mhsmith Nov 29, 2022
0d65ecf
Add test_probes to MANIFEST.in
mhsmith Nov 29, 2022
da146a0
Work around https://github.com/pypa/twine/issues/940, and pin all oth…
mhsmith Nov 29, 2022
ad710a9
Remove stray comment
mhsmith Nov 29, 2022
d50e9fd
Remove `dummy` from test app requirements
mhsmith Nov 30, 2022
0593b52
Replace global `app` variable with `toga.App.app`
mhsmith Nov 30, 2022
2c97ac3
Split "utils" and "common" modules into more specific names
mhsmith Nov 30, 2022
87287de
Rename test_probes to tests_backend, and probe_{name}.py to {name}.py
mhsmith Dec 3, 2022
8beba38
Generalize probe constructors / Create a new container for each test
mhsmith Dec 3, 2022
c3cf437
Reduce colors list
mhsmith Dec 3, 2022
94594ab
Update backends' MANIFEST.in / Update twine
mhsmith Dec 3, 2022
8a62cec
Rename toga-test to testbed.
freakboy3742 Dec 5, 2022
9e6c692
Backfill all required icons.
freakboy3742 Dec 5, 2022
5d2dd6c
Add a reminder to use --test.
freakboy3742 Dec 5, 2022
dd8739c
Add a CI configuration to run testbed.
freakboy3742 Dec 5, 2022
1ddcb53
Add workaround for Rubicon thread starvation problem.
freakboy3742 Dec 5, 2022
20130d1
Only use the workaround on iOS.
freakboy3742 Dec 5, 2022
5d9982a
Clarify the role played by main_loop() on iOS.
freakboy3742 Dec 6, 2022
018fb41
Ensure paths are correct before running the test suite.
freakboy3742 Dec 6, 2022
3ae99ae
Merge pull request #1 from freakboy3742/testbed
mhsmith Dec 6, 2022
a9580ce
Remove Codecov
mhsmith Dec 6, 2022
426ae43
Remove questionable comment
mhsmith Dec 6, 2022
89f99b8
Add **kwargs to event handlers
mhsmith Dec 6, 2022
30f4c82
Make test skip conditions more specific
mhsmith Dec 6, 2022
347b112
Move native container assertions from probe constructor into a separa…
mhsmith Dec 6, 2022
9d6c88c
Add xvfb-run to GTK testbed CI
mhsmith Dec 7, 2022
30295c1
Fix typo
mhsmith Dec 7, 2022
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
63 changes: 55 additions & 8 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -96,21 +96,13 @@ jobs:
run: |
tox -e py-core
cd core
python -m coverage xml
mv .coverage .coverage.${{ matrix.platform }}.${{ matrix.python-version }}
- name: Store coverage data
uses: actions/upload-artifact@v3
with:
name: core-coverage-data
path: "core/.coverage.*"
if-no-files-found: error
- name: Upload coverage data to CodeCov
uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}
file: core/coverage.xml
flags: unittests
fail_ci_if_error: true

core-coverage:
name: Combine & check core coverage.
Expand Down Expand Up @@ -190,3 +182,58 @@ jobs:
TOGA_INSTALL_COMMAND: 'bash -c "pip install ../{core,dummy,${{ matrix.backend }}}/dist/*.whl"'
run: |
tox -e py-${{ matrix.backend }}

testbed:
needs: core
strategy:
fail-fast: false
matrix:
backend: ["macOS", "windows", "linux", "android", "iOS"]
include:
- pre-command:
briefcase-run-prefix:
briefcase-run-args:

- backend: macOS
runs-on: macos-12

- backend: linux
runs-on: ubuntu-latest
pre-command: "sudo apt-get update -y && sudo apt-get install -y python3-gi python3-gi-cairo gir1.2-gtk-3.0 python3-dev libgirepository1.0-dev libcairo2-dev pkg-config"
briefcase-run-prefix: 'xvfb-run -a -s "-screen 0 2048x1536x24"'

- backend: windows
runs-on: windows-latest

- backend: iOS
runs-on: macos-12
briefcase-run-args: ' -d "iPhone SE (3rd generation)"'

- backend: android
runs-on: macos-12
briefcase-run-args: " -d '{\"avd\":\"beePhone\"}' --Xemulator=-no-window --Xemulator=-no-snapshot --Xemulator=-no-audio --Xemulator=-no-boot-anim --shutdown-on-exit"

runs-on: ${{ matrix.runs-on }}
steps:
- uses: actions/checkout@v3.1.0
with:
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v4.3.0
with:
python-version: "3.X"
- name: Install dependencies
run: |
${{ matrix.pre-command }}
# Use the development version of Briefcase
pip install git+https://github.com/beeware/briefcase.git
- name: Test App
run: |
cd testbed
${{ matrix.briefcase-run-prefix }} briefcase run ${{ matrix.backend }} --test ${{ matrix.briefcase-run-args }}
- uses: actions/upload-artifact@v3
name: Upload logs
if: failure()
with:
name: testbed-failure-logs-${{ matrix.backend }}
path: testbed/logs/*
1 change: 1 addition & 0 deletions android/MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ include LICENSE
include README.rst
include tox.ini
recursive-include tests *.py
recursive-include tests_backend *.py
Empty file.
Empty file.
43 changes: 43 additions & 0 deletions android/tests_backend/widgets/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from pytest import skip


class SimpleProbe:
def __init__(self, widget):
self.native = widget._impl.native
assert isinstance(self.native, self.native_class)

def assert_container(self, container):
container_native = container._impl.native
for i in range(container_native.getChildCount()):
child = container_native.getChildAt(i)
if child is self.native:
break
else:
raise AssertionError(f"cannot find {self.native} in {container_native}")

@property
def enabled(self):
return self.native.isEnabled()

@property
def background_color(self):
skip("not implemented: background_color")

@property
def color(self):
skip("not implemented: color")

@property
def hidden(self):
skip("not implemented: hidden")

@property
def width(self):
return self.native.getWidth()

@property
def height(self):
return self.native.getHeight()

def press(self):
self.native.performClick()
8 changes: 8 additions & 0 deletions android/tests_backend/widgets/button.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from java import jclass

from .label import LabelProbe


# On Android, a Button is just a TextView with a state-dependent background image.
class ButtonProbe(LabelProbe):
native_class = jclass("android.widget.Button")
16 changes: 16 additions & 0 deletions android/tests_backend/widgets/label.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from java import jclass

from .base import SimpleProbe
from .properties import toga_color


class LabelProbe(SimpleProbe):
native_class = jclass("android.widget.TextView")

@property
def color(self):
return toga_color(self.native.getCurrentTextColor())

@property
def text(self):
return str(self.native.getText())
15 changes: 15 additions & 0 deletions android/tests_backend/widgets/properties.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from java import jint

from android.graphics import Color
from toga.colors import rgba


def toga_color(color_int):
# Select the `int` overloads rather than the `long` ones.
color_int = jint(color_int)
return rgba(
Color.red(color_int),
Color.green(color_int),
Color.blue(color_int),
Color.alpha(color_int) / 255,
)
24 changes: 24 additions & 0 deletions android/tests_backend/widgets/slider.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from java import jclass

from android.os import Build

from .base import SimpleProbe


class SliderProbe(SimpleProbe):
native_class = jclass("android.widget.SeekBar")

@property
def position(self):
return (self.native.getProgress() - self._min) / (self._max - self._min)

def change(self, position):
self.native.setProgress(self._min + round(position * (self._max - self._min)))

@property
def _min(self):
return 0 if (Build.VERSION.SDK_INT < 26) else self.native.getMin()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this imply we need to run the tests on (at least) 2 Android SDK versions (24 and 33, I'm guessing)? If I'm understanding correctly, we would only need to build it once, but we'd need to use 2 simulators?

Copy link
Member Author

@mhsmith mhsmith Dec 6, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It won't make any difference here, because Toga doesn't currently use the native minimum value property. I just added this code to the probe in case that ever changes.

But as a general principle yes, I think it would be a good idea to run Android tests against both the minimum and maximum API levels we support, and that's what I always do before a Chaquopy release.

If I'm understanding correctly, we would only need to build it once, but we'd need to use 2 simulators?

That's correct.


@property
def _max(self):
return self.native.getMax()
1 change: 1 addition & 0 deletions changes/1687.misc.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added a backend test app.
6 changes: 4 additions & 2 deletions iOS/src/toga_iOS/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,10 @@ def open_document(self, fileURL):
pass

def main_loop(self):
# Main loop is a no-op on iOS; the app loop is integrated with the
# main iOS event loop.
# Main loop is non-blocking on iOS. The app loop is integrated with the
# main iOS event loop, so this call will return; however, it will leave
# the app in a state such that asyncio events will be scheduled on the
# iOS event loop.
self.loop.run_forever_cooperatively(lifecycle=iOSLifecycle())

def set_main_window(self, window):
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ known_first_party = [
"toga_dummy",
"toga_gtk",
"toga_iOS",
"toga_test",
"toga_web",
"toga_winforms",
]
Expand Down
69 changes: 69 additions & 0 deletions testbed/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# OSX useful to ignore
*.DS_Store
.AppleDouble
.LSOverride

# Thumbnails
._*

# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent

# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk

# C extensions
*.so

# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg

# IntelliJ Idea family of suites
.idea
*.iml
## File-based project format:
*.ipr
*.iws
## mpeltonen/sbt-idea plugin
.idea_modules/

# Briefcase build directories
iOS/
macOS/
windows/
android/
linux/
django/

# Briefcase log files
logs/
90 changes: 90 additions & 0 deletions testbed/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# This project was generated using template: https://github.com/beeware/briefcase-template and branch: v0.3.12
[tool.briefcase]
project_name = "Toga Testbed"
bundle = "org.beeware.toga"
version = "0.0.1"
url = "https://beeware.org"
license = "BSD license"
author = 'Tiberius Yak'
author_email = "tiberius@beeware.org"

[tool.briefcase.app.testbed]
formal_name = "Toga Testbed"
description = "A testbed for Toga visual tests"
icon = "src/testbed/resources/testbed"
sources = [
'src/testbed',
]
test_sources = [
'tests',
]
requires = [
'../core',
]
test_requires = [
'pytest==7.2.0',
'pytest-asyncio==0.20.2',
]


[tool.briefcase.app.testbed.macOS]
requires = [
'../cocoa',
'std-nslog~=1.0.0'
]

[tool.briefcase.app.testbed.linux]
requires = [
'../gtk',
]

[tool.briefcase.app.testbed.linux.appimage]
system_requires = [
'gir1.2-webkit-3.0',
'libcairo2-dev',
'libgirepository1.0-dev',
'libgtk-3-dev',
'libpango1.0-dev',
'librsvg2-dev',
'libwebkitgtk-3.0-0',
]
linuxdeploy_plugins = [
'DEPLOY_GTK_VERSION=3 gtk',
]

[tool.briefcase.app.testbed.linux.flatpak]
flatpak_runtime = 'org.gnome.Platform'
flatpak_runtime_version = '42'
flatpak_sdk = 'org.gnome.Sdk'

[tool.briefcase.app.testbed.windows]
test_sources = [
'../winforms/tests_backend',
]
requires = [
'../winforms',
]

# Mobile deployments
[tool.briefcase.app.testbed.iOS]
requires = [
'../iOS',
'std-nslog~=1.0.0'
]

[tool.briefcase.app.testbed.android]
test_sources = [
'../android/tests_backend',
]
requires = [
'../android'
]

# TODO: replace with extractPackages
build_gradle_extra_content = "android.defaultConfig.python.pyc.src false"

[tool.briefcase.app.testbed.web]
requires = [
'../web'
]
style_framework = "Bootstrap v4.6"
Empty file added testbed/src/testbed/__init__.py
Empty file.
4 changes: 4 additions & 0 deletions testbed/src/testbed/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from testbed.app import main

if __name__ == "__main__":
main().main_loop()
Loading