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 dummy as a known platform, and use it in tests #1534

Merged
merged 48 commits into from
Oct 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
547a36b
Add as a known platform, and use it in tests
bruno-rino Jul 27, 2022
0bce426
Use TestCase's setUp and tearDown to set and reset the the current fa…
bruno-rino Jul 29, 2022
79bfdd4
Fix flake8 issues
bruno-rino Aug 16, 2022
c368522
Add test for platform selection
bruno-rino Aug 16, 2022
b94be8e
Updated tests for CI environment, where the native platform factory i…
bruno-rino Aug 17, 2022
24ed0ed
Check availablity of the native platform factory before it is overriden
bruno-rino Aug 17, 2022
dbbea6e
Fix flake8 issues
bruno-rino Aug 17, 2022
2f6bca4
Trying to keep codecov happy by using skipTest
bruno-rino Aug 17, 2022
ad9db8a
Mock toga_gtk for the CI environment benefit
bruno-rino Aug 21, 2022
a4d8be6
Fix flake8 issues
bruno-rino Aug 21, 2022
eca2d97
Exploiting distutils extension points for toga backends
bruno-rino Aug 25, 2022
78ce15b
Merge branch 'main' into add_dummy_platform
bruno-rino Aug 25, 2022
ab675ab
Remove most "factory=toga_dummy.factory" parameters from tests
bruno-rino Aug 25, 2022
f9b8c61
Bump the dependency on importlib_metadata.
freakboy3742 Aug 26, 2022
dd9a93b
Add missing "[options.entry_points]" section title
bruno-rino Aug 26, 2022
d98fe31
web backends are deployment shims, rather than full "toga.backends"
bruno-rino Aug 26, 2022
0d92fca
More precise error messages
bruno-rino Aug 26, 2022
8e83571
Merge branch 'main' into add_dummy_platform
bruno-rino Sep 6, 2022
c3396bf
remove references to toga_dummy
bruno-rino Sep 6, 2022
30d9789
Improve test coverage
bruno-rino Sep 6, 2022
ae95371
Deprecate custom factory argument from get_platform_factory
bruno-rino Sep 7, 2022
439defa
Deprecate factory argument from bind() methods
bruno-rino Sep 7, 2022
095e5a3
Override the toga platform to use in tests
bruno-rino Sep 8, 2022
ae79748
Deprecate factory argument from widget constructors
bruno-rino Sep 8, 2022
630e4f8
Merge branch 'main' into add_dummy_platform
bruno-rino Sep 8, 2022
91a1279
Override the toga platform in all tests that use toga_dummy.utils.Tes…
bruno-rino Sep 9, 2022
27cde92
Fix testbed apps when no backend is specified
bruno-rino Sep 9, 2022
d2225a7
Fix typo
bruno-rino Sep 9, 2022
b5e3540
Temporarily disable the platform override
bruno-rino Sep 12, 2022
74d90b4
Fix flake8 issues
bruno-rino Sep 12, 2022
4497d4c
Make the platform overridde more concise and explicit
bruno-rino Sep 12, 2022
8c3aad2
Allow platform overridde via environment variable
bruno-rino Sep 12, 2022
9b4bd77
The platform override in TestCase is opt-in
bruno-rino Sep 16, 2022
866c81f
Turn up verbosity on tests, and be explicit about the test platform.
freakboy3742 Sep 27, 2022
6fa5f04
Clean up error messages for platform use.
freakboy3742 Sep 27, 2022
db45e6e
Use a different mechanism to set environment variables.
freakboy3742 Sep 27, 2022
49051fb
Change the environment variable override to target backends instead o…
bruno-rino Sep 29, 2022
70ee010
Set the TOGA_BACKEND environment variable to run tests
bruno-rino Sep 29, 2022
1443cc6
Merge remote-tracking branch 'origin/main' into add_dummy_platform
bruno-rino Sep 29, 2022
520a02b
Merge branch 'main' into add_dummy_platform
mhsmith Oct 20, 2022
b133746
Fix tests
mhsmith Oct 20, 2022
5aa5439
Merge branch 'pre-commit' into add_dummy_platform
mhsmith Oct 21, 2022
01289b9
Apply pre-commit
mhsmith Oct 21, 2022
66b4687
Merge branch 'main' into add_dummy_platform
freakboy3742 Oct 21, 2022
4fd31f0
Merge branch 'main' into add_dummy_platform
freakboy3742 Oct 26, 2022
1a853a7
Minor cleanups to messaging and options in platform selection.
freakboy3742 Oct 26, 2022
192fb9a
Update contribution guide to remove some redundant instructions.
freakboy3742 Oct 26, 2022
9491665
Merge branch 'main' into add_dummy_platform
freakboy3742 Oct 26, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -87,12 +87,12 @@ jobs:
- runs-on: ubuntu-latest
- python-version: "3.7" # Should be env.min_python_version (https://github.com/actions/runner/issues/480)
- pre-command:
- test-command: pytest
- test-command: pytest -v
- backend: cocoa
runs-on: macos-latest
- backend: gtk
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"
test-command: "xvfb-run -a -s '-screen 0 2048x1536x24' pytest"
test-command: "xvfb-run -a -s '-screen 0 2048x1536x24' pytest -v"
- backend: iOS
runs-on: macos-latest
- backend: winforms
Expand Down Expand Up @@ -124,4 +124,4 @@ jobs:
- name: Test
run: |
cd src/${{ matrix.backend }}
${{ matrix.test-command }}
TOGA_BACKEND=toga_${{ matrix.backend }} ${{ matrix.test-command }}
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
version = '.'.join(release.split('.')[:2])

# Fix the autodoc import issues
os.environ['TOGA_PLATFORM'] = 'dummy'
os.environ['TOGA_BACKEND'] = 'toga_dummy'

autoclass_content = 'both'

Expand Down
102 changes: 26 additions & 76 deletions docs/how-to/contribute.rst
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,7 @@ Start by running the core test suite:
.. code-block:: bash

(venv) $ cd src/core
(venv) $ python setup.py test
(venv) $ TOGA_BACKEND=toga_dummy python setup.py test
...
----------------------------------------------------------------------
Ran 181 tests in 0.343s
Expand All @@ -376,7 +376,7 @@ Start by running the core test suite:
.. code-block:: bash

(venv) $ cd src/core
(venv) $ python setup.py test
(venv) $ TOGA_BACKEND=toga_dummy python setup.py test
...
----------------------------------------------------------------------
Ran 181 tests in 0.343s
Expand All @@ -388,20 +388,30 @@ Start by running the core test suite:
.. code-block:: doscon

(venv) C:\...>cd src/core
(venv) C:\...>set TOGA_BACKEND=toga_dummy
(venv) C:\...>python setup.py test
(venv) C:\...>set TOGA_BACKEND=
...
----------------------------------------------------------------------
Ran 181 tests in 0.343s

OK (skipped=1)

You should get some output indicating that tests have been run. You shouldnt
You should get some output indicating that tests have been run. You shouldn't
ever get any FAIL or ERROR test results. We run our full test suite before
merging every patch. If that process discovers any problems, we dont merge
the patch. If you do find a test error or failure, either theres something
odd in your test environment, or youve found an edge case that we havent
merging every patch. If that process discovers any problems, we don't merge
the patch. If you do find a test error or failure, either there's something
odd in your test environment, or you've found an edge case that we haven't
seen before - either way, let us know!

Note that when we run the test suite, we set the environment variable
``TOGA_BACKEND``. Under normal operation, Toga will automatically choose the
appropriate backend for your platform. However, when running the tests for
the core platform, we need to use a special "dummy" backend. This dummy backend
satisfies the interface contract of a Toga backend, but doesn't acutally
display any widgets. This allows us to test the behavior of the core library
independent of the behavior of a specific backend.

Although the tests should all pass, the test suite itself is still
incomplete. There are many aspects of the Toga Core API that aren't currently
tested (or aren't tested thoroughly). To work out what *isn't* tested, we're
Expand All @@ -421,7 +431,7 @@ ask coverage to generate a report of the data that was gathered:
.. code-block:: bash

(venv) $ pip install coverage
(venv) $ coverage run setup.py test
(venv) $ TOGA_BACKEND=toga_dummy coverage run setup.py test
(venv) $ coverage report -m --include="toga/*"
Name Stmts Miss Cover Missing
------------------------------------------------------------------
Expand All @@ -437,7 +447,7 @@ ask coverage to generate a report of the data that was gathered:
.. code-block:: bash

(venv) $ pip install coverage
(venv) $ coverage run setup.py test
(venv) $ TOGA_BACKEND=toga_dummy coverage run setup.py test
(venv) $ coverage report -m --include="toga/*"
Name Stmts Miss Cover Missing
------------------------------------------------------------------
Expand All @@ -453,7 +463,9 @@ ask coverage to generate a report of the data that was gathered:
.. code-block:: doscon

(venv) C:\...>pip install coverage
(venv) C:\...>set TOGA_BACKEND=toga_dummy
(venv) C:\...>coverage run setup.py test
(venv) C:\...>set TOGA_BACKEND=
(venv) C:\...>coverage report -m --include=toga/*
Name Stmts Miss Cover Missing
------------------------------------------------------------------
Expand Down Expand Up @@ -488,7 +500,7 @@ expect to see something like:

.. code-block:: bash

(venv) $ coverage run setup.py test
(venv) $ TOGA_BACKEND=toga_dummy coverage run setup.py test
running test
...
----------------------------------------------------------------------
Expand All @@ -509,7 +521,7 @@ expect to see something like:

.. code-block:: bash

(venv) $ coverage run setup.py test
(venv) $ TOGA_BACKEND=toga_dummy coverage run setup.py test
running test
...
----------------------------------------------------------------------
Expand All @@ -530,7 +542,9 @@ expect to see something like:

.. code-block:: doscon

(venv) C:\...>set TOGA_BACKEND=toga_dummy
(venv) C:\...>coverage run setup.py test
(venv) C:\...>set TOGA_BACKEND=
running test
...
----------------------------------------------------------------------
Expand All @@ -554,70 +568,6 @@ in the coverage results.
Submit a pull request for your work, and you're done! Congratulations, you're
a contributor to Toga!

How does this all work?
=======================

Since you're writing tests for a GUI toolkit, you might be wondering why you
haven't seen a GUI yet. The Toga Core package contains the API definitions for
the Toga widget kit. This is completely platform agnostic - it just provides
an interface, and defers actually drawing anything on the screen to the
platform backends.

When you run the test suite, the test runner uses a "dummy" backend - a
platform backend that *implements* the full API, but doesn’t actually *do*
anything (i.e., when you say display a button, it creates an object, but
doesn’t actually display a button).

In this way, it's possible to for the Toga Core tests to exercise every API
entry point in the Toga Core package, verify that data is stored correctly on
the interface layer, and sent through to the right endpoints in the Dummy
backend. If the *dummy* backend is invoked correctly, then any other backend
will be handled correctly, too.

One error you might see...
--------------------------

When you're running these tests - especially when you submit your PR, and the
tests run on our continuous integration (CI) server - it's possible you might get
an error that reads::

ModuleNotFoundError: No module named 'toga_gtk'.

If this happens, you've found an bug in the way the widget you're testing
has been constructed.

The Core API is designed to be platform independent. When a widget is created,
it calls upon a "factory" to instantiate the underlying platform-dependent
implementation. When a Toga application starts running, it will try to guess
the right factory to use based on the environment where the code is running.
So, if you run your code on a Mac, it will use the Cocoa factory; if you're on
a Linux box, it will use the GTK factory.

However, when writing tests, we want to use the "dummy" factory. The Dummy
factory isn't the "native" platform anywhere - it's just a placeholder. As a
result, the dummy factory won't be used unless you specifically request it -
which means every widget has to honor that request.

Most Toga widgets create their platform-specific implementation when they are
created. As a result, most Toga widgets should accept a ``factory`` argument -
and that factory should be used to instantiate any widget implementations or
sub-widgets.

However, *some* widgets - like Icon - are "late loaded" - the implementation
isn't created until the widget is actually *used*. Late loaded widgets don't
accept a ``factory`` when they're created - but they *do* have an `_impl()`
method that accepts a factory.

If these factory arguments aren't being passed around correctly, then a test
suite will attempt to create a widget, but will fall back to the platform-
default factory, rather than the "dummy" factory. If you've installed the
appropriate platform default backend, you won't (necessarily) get an error,
but your tests won't use the dummy backend. On our CI server, we deliberately
don't install a platform backend so we can find these errors.

If you get the ``ModuleNotFoundError``, you need to audit the code to find out
where a widget is being created without a factory being specified.

It's not just about coverage!
=============================

Expand All @@ -629,8 +579,8 @@ purpose it was intended!

As you develop tests and improve coverage, you should be checking that the
core module is internally **consistent** as well. If you notice any method
names that arent internally consistent (e.g., something called ``on_select``
in one module, but called ``on_selected`` in another), or where the data isnt
names that aren't internally consistent (e.g., something called ``on_select``
in one module, but called ``on_selected`` in another), or where the data isn't
being handled consistently (one widget updates then refreshes, but another
widget refreshes then updates), flag it and bring it to our attention by
raising a ticket. Or, if you're confident that you know what needs to be done,
Expand Down
4 changes: 4 additions & 0 deletions nursery/curses/setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ include =
toga_curses
toga_curses.*

[options.entry_points]
toga.backends =
terminal = toga_curses

[flake8]
exclude=\
.eggs/*,\
Expand Down
6 changes: 6 additions & 0 deletions nursery/qt/setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ include =
toga_qt
toga_qt.*

[options.entry_points]
toga.backends =
linux = toga_qt
macOS = toga_qt
windows = toga_qt

[flake8]
exclude=\
.eggs/*,\
Expand Down
4 changes: 4 additions & 0 deletions nursery/tvOS/setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ include =
toga_tvOS
toga_tvOS.*

[options.entry_points]
toga.backends =
tvOS = toga_tvOS

[flake8]
exclude=\
.eggs/*,\
Expand Down
4 changes: 4 additions & 0 deletions nursery/uwp/setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ include =
toga_uwp
toga_uwp.*

[options.entry_points]
toga.backends =
windows = toga_uwp

[flake8]
exclude=\
.eggs/*,\
Expand Down
4 changes: 4 additions & 0 deletions nursery/watchOS/setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ include =
toga_watchOS
toga_watchOS.*

[options.entry_points]
toga.backends =
watchOS = toga_watchOS

[flake8]
exclude=\
.eggs/*,\
Expand Down
4 changes: 4 additions & 0 deletions src/android/setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ package_dir =
python_requires = >= 3.6
zip_safe = False

[options.entry_points]
toga.backends =
android = toga_android

[options.packages.find]
where = src

Expand Down
2 changes: 1 addition & 1 deletion src/android/src/toga_android/widgets/button.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def set_enabled(self, value):

def set_font(self, font):
if font:
font_impl = font.bind(self.interface.factory)
font_impl = font.bind()
self.native.setTextSize(TypedValue.COMPLEX_UNIT_SP, font_impl.get_size())
self.native.setTypeface(font_impl.get_typeface(), font_impl.get_style())

Expand Down
2 changes: 1 addition & 1 deletion src/android/src/toga_android/widgets/detailedlist.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ def _make_row(self, container, i):
icon_image_view = ImageView(self._native_activity)
icon = self.interface.data[i].icon
if icon is not None:
icon.bind(self.interface.factory)
icon.bind()
bitmap = BitmapFactory.decodeFile(str(icon._impl.path))
icon_image_view.setImageBitmap(bitmap)
icon_layout_params = RelativeLayout__LayoutParams(
Expand Down
2 changes: 1 addition & 1 deletion src/android/src/toga_android/widgets/label.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def set_text(self, value):

def set_font(self, font):
if font:
font_impl = font.bind(self.interface.factory)
font_impl = font.bind()
self.native.setTextSize(TypedValue.COMPLEX_UNIT_SP, font_impl.get_size())
self.native.setTypeface(font_impl.get_typeface(), font_impl.get_style())

Expand Down
2 changes: 1 addition & 1 deletion src/android/src/toga_android/widgets/multilinetextinput.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ def set_color(self, color):

def set_font(self, font):
if font:
font_impl = font.bind(self.interface.factory)
font_impl = font.bind()
self.native.setTextSize(TypedValue.COMPLEX_UNIT_SP, font_impl.get_size())
self.native.setTypeface(font_impl.get_typeface(), font_impl.get_style())

Expand Down
2 changes: 1 addition & 1 deletion src/android/src/toga_android/widgets/numberinput.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ def set_alignment(self, value):

def set_font(self, font):
if font:
font_impl = font.bind(self.interface.factory)
font_impl = font.bind()
self.native.setTextSize(TypedValue.COMPLEX_UNIT_SP, font_impl.get_size())
self.native.setTypeface(font_impl.get_typeface(), font_impl.get_style())

Expand Down
2 changes: 1 addition & 1 deletion src/android/src/toga_android/widgets/switch.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def get_value(self):

def set_font(self, font):
if font:
font_impl = font.bind(self.interface.factory)
font_impl = font.bind()
self.native.setTextSize(TypedValue.COMPLEX_UNIT_SP, font_impl.get_size())
self.native.setTypeface(font_impl.get_typeface(), font_impl.get_style())

Expand Down
2 changes: 1 addition & 1 deletion src/android/src/toga_android/widgets/table.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ def remove_column(self, accessor):

def set_font(self, font):
if font:
self._font_impl = font.bind(self.interface.factory)
self._font_impl = font.bind()
if self.interface.data is not None:
self.change_source(self.interface.data)

Expand Down
2 changes: 1 addition & 1 deletion src/android/src/toga_android/widgets/textinput.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ def set_alignment(self, value):

def set_font(self, font):
if font:
font_impl = font.bind(self.interface.factory)
font_impl = font.bind()
self.native.setTextSize(TypedValue.COMPLEX_UNIT_SP, font_impl.get_size())
self.native.setTypeface(font_impl.get_typeface(), font_impl.get_style())

Expand Down
4 changes: 4 additions & 0 deletions src/cocoa/setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ package_dir =
python_requires = >= 3.6
zip_safe = False

[options.entry_points]
toga.backends =
macOS = toga_cocoa

[options.packages.find]
where = src

Expand Down
4 changes: 2 additions & 2 deletions src/cocoa/src/toga_cocoa/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ def create(self):
self.native = NSApplication.sharedApplication
self.native.setActivationPolicy(NSApplicationActivationPolicyRegular)

icon = self.interface.icon.bind(self.interface.factory)
icon = self.interface.icon.bind()
self.native.setApplicationIconImage_(icon.native)

self.resource_path = os.path.dirname(os.path.dirname(NSBundle.mainBundle.bundlePath))
Expand Down Expand Up @@ -350,7 +350,7 @@ def set_main_window(self, window):
def show_about_dialog(self):
options = NSMutableDictionary.alloc().init()

options[NSAboutPanelOptionApplicationIcon] = self.interface.icon.bind(self.interface.factory).native
options[NSAboutPanelOptionApplicationIcon] = self.interface.icon.bind().native

if self.interface.name is not None:
options[NSAboutPanelOptionApplicationName] = self.interface.name
Expand Down
2 changes: 1 addition & 1 deletion src/cocoa/src/toga_cocoa/widgets/button.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def create(self):

def set_font(self, font):
if font:
self.native.font = font.bind(self.interface.factory).native
self.native.font = font.bind().native

def set_text(self, text):
self.native.title = self.interface.text
Expand Down