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 3 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
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,14 @@ profile = "black"
split_on_trailing_comma = true
combine_as_imports = true
known_first_party = [
"tests",
"toga",
"toga_android",
"toga_cocoa",
"toga_dummy",
"toga_gtk",
"toga_iOS",
"toga_test",
"toga_web",
"toga_winforms",
]
Expand Down
69 changes: 69 additions & 0 deletions test/.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/
84 changes: 84 additions & 0 deletions test/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# This project was generated using template: https://github.com/beeware/briefcase-template and branch: v0.3.12
[tool.briefcase]
project_name = "Toga test"
bundle = "org.beeware"
version = "0.0.1"
url = "https://beeware.org"
license = "BSD license"
author = 'Tiberius Yak'
author_email = "[email protected]"

[tool.briefcase.app.toga-test]
formal_name = "Toga test"
description = "Toga test"
icon = "src/toga_test/resources/toga_test"
sources = [
'src/toga_test',
'src/tests',
]
requires = [
'../core',
'../dummy',
freakboy3742 marked this conversation as resolved.
Show resolved Hide resolved
'pytest==7.2.0',
'pytest-asyncio==0.20.2',
]


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

[tool.briefcase.app.toga-test.linux]
requires = [
'../gtk',
]

[tool.briefcase.app.toga-test.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.toga-test.linux.flatpak]
flatpak_runtime = 'org.gnome.Platform'
flatpak_runtime_version = '42'
flatpak_sdk = 'org.gnome.Sdk'

[tool.briefcase.app.toga-test.windows]
sources = [
'../winforms/test_probes',
]
mhsmith marked this conversation as resolved.
Show resolved Hide resolved
requires = [
'../winforms',
]

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

[tool.briefcase.app.toga-test.android]
requires = [
'../android'
]

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

[tool.briefcase.app.toga-test.web]
requires = [
'../web'
]
style_framework = "Bootstrap v4.6"
Empty file added test/src/tests/__init__.py
Empty file.
57 changes: 57 additions & 0 deletions test/src/tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import asyncio
import inspect
from dataclasses import dataclass

from pytest import fixture

import toga_test.app


@fixture(scope="session")
def app():
return toga_test.app.app


@fixture(scope="session")
def main_box(app):
return app.main_box


# Controls the event loop used by pytest-asyncio.
@fixture(scope="session")
def event_loop(app):
return ProxyEventLoop(app._impl.loop)


# Proxy which forwards all tasks to another event loop in a thread-safe manner. It
# implements only the methods used by pytest-asyncio.
@dataclass
class ProxyEventLoop(asyncio.AbstractEventLoop):
loop: object

# Used by ensure_future.
def create_task(self, coro):
return ProxyTask(coro)

def run_until_complete(self, future):
if inspect.iscoroutine(future):
coro = future
elif isinstance(future, ProxyTask):
coro = future.coro
else:
raise TypeError(f"Future type {type(future)} is not currently supported")
return asyncio.run_coroutine_threadsafe(coro, self.loop).result()

def close(self):
pass


@dataclass
class ProxyTask:
coro: object

# Used by ensure_future.
_source_traceback = None

def done(self):
return False
16 changes: 16 additions & 0 deletions test/src/tests/test_data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from toga.colors import rgba

# TODO: add non-ASCII strings.
TEXTS = ["", " ", "a", "ab", "abc", "hello world", "hello\nworld"]


# TODO: include None
components = [0, 1, 2, 128, 253, 254, 255]
alphas = [0.0, 0.01, 0.1, 0.5, 0.9, 0.99, 1.0]
COLORS = [
rgba(r, g, b, a)
for r in components
for g in components
for b in components
for a in alphas
]
9 changes: 9 additions & 0 deletions test/src/tests/test_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# TODO: rename to utils.py once optimizations are disabled in the stub app. Until then,
# assertions in files not named test_*.py will raise "PytestConfigWarning: assertions not
# in test modules or plugins will be ignored".


def set_get(obj, name, value):
"""Calls a setter, then verifies that the same value is returned by the getter."""
setattr(obj, name, value)
assert getattr(obj, name) == value
Empty file.
17 changes: 17 additions & 0 deletions test/src/tests/widgets/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from importlib import import_module

from pytest import fixture


@fixture
async def widget(main_box, new_widget):
main_box.add(new_widget)
yield new_widget
main_box.remove(new_widget)


@fixture
async def probe(main_box, widget):
name = type(widget).__name__
module = import_module(f"test_probes.widgets.probe_{name.lower()}")
return getattr(module, f"{name}Probe")(main_box, widget)
19 changes: 19 additions & 0 deletions test/src/tests/widgets/test_button.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from unittest.mock import Mock

from pytest import fixture

import toga


@fixture
async def new_widget():
Copy link
Member

Choose a reason for hiding this comment

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

It took me a while to work out what was going on here; 2 questions coming from this:

  1. Do all fixtures have to be async?
  2. Is a fixture the right model here - especially for the "broader layout"? Might it not be easier to have a simple_layout(widget) which puts the test widget into a simple box layout?

Copy link
Member Author

@mhsmith mhsmith Nov 28, 2022

Choose a reason for hiding this comment

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

Do all fixtures have to be async?

Yes, if they call any Toga APIs, because making them async is what causes them to run on the main thread.

Is a fixture the right model here - especially for the "broader layout"? Might it not be easier to have a simple_layout(widget) which puts the test widget into a simple box layout?

I'm not quite sure what you mean, but I'll adjust the fixture names and dependencies so it's clearer what each one is actually doing.

return toga.Button("")


async def test_press(widget, probe):
handler = Mock()
# TODO: can't use set_get, because getattr returns the wrapped handler, which is an
# implementation detail that we shouldn't expose.
setattr(widget, "on_press", handler)
probe.press()
handler.assert_called_once_with(widget)
44 changes: 44 additions & 0 deletions test/src/tests/widgets/test_label.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
from pytest import approx, fixture

import toga

from ..test_data import COLORS, TEXTS
from ..test_utils import set_get


@fixture
async def new_widget():
return toga.Label("")


async def test_text(widget, probe):
for text in TEXTS:
set_get(widget, "text", text)
assert probe.text == text


async def test_color(widget, probe):
for color in COLORS:
widget.style.color = color
for component in ["r", "g", "b"]:
assert getattr(probe.color, component) == getattr(color, component)
assert probe.color.a == approx(color.a, abs=(1 / 256))
mhsmith marked this conversation as resolved.
Show resolved Hide resolved


async def test_multiline(widget, probe):
def make_lines(n):
return "\n".join(f"line{i}" for i in range(n))

widget.text = make_lines(1)
line_height = probe.height

widget.text = make_lines(2)
assert probe.height == approx(line_height * 2, rel=0.1)
line_spacing = probe.height - (line_height * 2)
mhsmith marked this conversation as resolved.
Show resolved Hide resolved

for n in range(3, 10):
widget.text = make_lines(n)
assert probe.height == approx(
(line_height * n) + (line_spacing * (n - 1)),
rel=0.1,
)
Empty file added test/src/toga_test/__init__.py
Empty file.
4 changes: 4 additions & 0 deletions test/src/toga_test/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from toga_test.app import main

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