Skip to content

Commit

Permalink
Merge pull request #442 from MatthieuDartiailh/py310
Browse files Browse the repository at this point in the history
Start testing on Python 3.10 (and drop 3.6)
  • Loading branch information
MatthieuDartiailh committed Jul 27, 2021
2 parents 4780960 + 605855f commit 5477fc4
Show file tree
Hide file tree
Showing 12 changed files with 54 additions and 61 deletions.
41 changes: 17 additions & 24 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
python-version: [3.6, 3.7, 3.8, 3.9]
python-version: ['3.7', '3.8', '3.9', '3.10.0-beta.4']
qt-binding: [pyqt5, pyside2]
fail-fast: false
steps:
Expand All @@ -34,7 +34,7 @@ jobs:
# QT_DEBUG_PLUGINS=1)
run: |
sudo apt-get update --fix-missing
sudo apt-get install libfontconfig1-dev libfreetype6-dev libx11-dev libxext-dev libxfixes-dev libxi-dev libxrender-dev libxcb1-dev libx11-xcb-dev libxcb-glx0-dev libxcb-keysyms1-dev libxcb-image0-dev libxcb-shm0-dev libxcb-icccm4-dev libxcb-sync0-dev libxcb-xfixes0-dev libxcb-shape0-dev libxcb-randr0-dev libxcb-render-util0-dev libxcb-xinerama0-dev libxcb-xkb-dev libxkbcommon-x11-dev libxcb-xtest0-dev libegl1-mesa xvfb matchbox-window-manager --fix-missing
sudo apt-get install libfontconfig1-dev libfreetype6-dev libx11-dev libxext-dev libxfixes-dev libxi-dev libxrender-dev libxcb1-dev libx11-xcb-dev libxcb-glx0-dev libxcb-keysyms1-dev libxcb-image0-dev libxcb-shm0-dev libxcb-icccm4-dev libxcb-sync0-dev libxcb-xfixes0-dev libxcb-shape0-dev libxcb-randr0-dev libxcb-render-util0-dev libxcb-xinerama0-dev libxcb-xkb-dev libxkbcommon-x11-dev libxcb-xtest0-dev libegl1-mesa xvfb fluxbox --fix-missing
- name: Checkout branch
uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
Expand All @@ -44,19 +44,21 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install bytecode ply qtpy numpy
pip install ply
pip install https://github.com/MatthieuDartiailh/bytecode/tarball/main
pip install https://github.com/nucleic/cppy/tarball/main
pip install https://github.com/nucleic/atom/tarball/main
pip install https://github.com/nucleic/kiwi/tarball/main
- name: Install Qt bindings
if: matrix.python-version != '3.10.0-beta.4'
run: |
pip install numpy '${{matrix.qt-binding}}'
pip install numpy qtpy '${{matrix.qt-binding}}'
- name: Install extra dependencies
if: matrix.python-version == '3.7'
if: matrix.python-version == '3.9'
run: |
pip install matplotlib ipython qtconsole
- name: Install QScintilla
if: matrix.python-version == '3.7' && matrix.qt-binding == 'pyqt5'
if: matrix.python-version == '3.9' && matrix.qt-binding == 'pyqt5'
run: |
pip install Cython QScintilla
- name: Install project
Expand All @@ -66,38 +68,29 @@ jobs:
python setup.py develop
- name: Install pytest
run: |
pip install pytest pytest-cov pytest-qt
- name: Run tests (Windows, Mac) (Py 3.6)
if: matrix.os != 'ubuntu-latest' && matrix.python-version == '3.6'
run: python -m pytest tests --cov enaml --cov-report xml
- name: Run tests (Linux) (Py 3.6)
if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.6'
shell: bash -l {0}
pip install pytest pytest-cov
- name: Install pytest-qt
if: matrix.python-version != '3.10.0-beta.4'
run: |
export DISPLAY=:99.0
/sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -screen 0 1920x1200x24 -ac +extension GLX +render -noreset
sleep 3
matchbox-window-manager &
sleep 1
python -m pytest tests --cov enaml --cov-report xml
pip install pytest-qt
- name: Run tests (Windows, Mac)
if: matrix.os != 'ubuntu-latest' && matrix.python-version != '3.6'
if: matrix.os != 'ubuntu-latest'
run: python -X dev -m pytest tests --cov enaml --cov-report xml
- name: Run tests (Linux)
if: matrix.os == 'ubuntu-latest' && matrix.python-version != '3.6'
if: matrix.os == 'ubuntu-latest'
shell: bash -l {0}
run: |
export DISPLAY=:99.0
/sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -screen 0 1920x1200x24 -ac +extension GLX +render -noreset
sleep 3
matchbox-window-manager &
exec /usr/bin/startfluxbox &
sleep 1
python -X dev -m pytest tests --cov enaml --cov-report xml
# Windows does not have C coverage so the upload is a bit different
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v1
if: matrix.os == 'windows-latest'
if: (github.event_name != 'schedule' && matrix.os == 'windows-latest')
with:
token: ${{ secrets.CODECOV_TOKEN }}
file: ./coverage.xml
Expand All @@ -106,7 +99,7 @@ jobs:
fail_ci_if_error: true
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v1
if: matrix.os != 'windows-latest'
if: (github.event_name != 'schedule' && matrix.os != 'windows-latest')
with:
token: ${{ secrets.CODECOV_TOKEN }}
flags: unittests
Expand Down
6 changes: 4 additions & 2 deletions enaml/core/code_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -435,8 +435,10 @@ def insert_python_block(self, pydata, trim=True):
"""
code = compile(pydata, self.filename, mode='exec')
bc_code = bc.Bytecode.from_code(code)
if trim: # skip ReturnValue
bc_code = bc_code[:-1]
# Skip the LOAD_CONST RETURN_VALUE pair if it exists (on Python 3.10+
# if the module ends on a raise, that pair which is unreachable is ommitted)
if trim and bc_code[-1].name == "RETURN_VALUE":
bc_code = bc_code[:-2]
self.code_ops.extend(bc_code)

def insert_python_expr(self, pydata, trim=True):
Expand Down
13 changes: 1 addition & 12 deletions enaml/core/import_hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import struct
import sys
import types
from abc import ABCMeta, abstractmethod
from abc import ABCMeta, abstractmethod, abstractclassmethod
from collections import defaultdict, namedtuple
from zipfile import ZipFile

Expand Down Expand Up @@ -60,17 +60,6 @@ def make_file_info(src_path):
return EnamlFileInfo(src_path, cache_path, cache_dir)


class abstractclassmethod(classmethod):
""" A backport of the Python 3's abc.abstractclassmethod.
"""
__isabstractmethod__ = True

def __init__(self, func):
func.__isabstractmethod__ = True
super(abstractclassmethod, self).__init__(func)


#------------------------------------------------------------------------------
# Abstract Enaml Importer
#------------------------------------------------------------------------------
Expand Down
2 changes: 1 addition & 1 deletion enaml/src/colorext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ Color__reduce__( Color* self, void* context )
uint32_t r = ( self->argb >> 16 ) & 0xFF;
uint32_t g = ( self->argb >> 8 ) & 0xFF;
uint32_t b = self->argb & 0xFF;
return Py_BuildValue( "O (iiii)", obj->ob_type, r, g, b, a );
return Py_BuildValue( "O (iiii)", Py_TYPE( obj ), r, g, b, a );
}


Expand Down
4 changes: 2 additions & 2 deletions enaml/src/compiler_helpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,7 @@ type_check_expr( PyObject* mod, PyObject* args )
PyErr_Format(
PyExc_TypeError,
"expression value has invalid type '%s'",
value->ob_type->tp_name
Py_TYPE( value )->tp_name
);
return 0;
}
Expand Down Expand Up @@ -461,7 +461,7 @@ validate_spec( PyObject* mod, PyObject* args )
PyErr_Format(
"template parameter %d has unhashable type: '%s'",
PyInt_AS_LONG( index ),
spec->ob_type->tp_name
Py_TYPE( spec )->tp_name
);
return 0;
}
Expand Down
6 changes: 3 additions & 3 deletions enaml/src/dynamicscope.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ test_dynamic_attr( PyObject* obj, PyObject* name )
descr = cppy::xincref( _PyType_Lookup( tp, name ) );
if( descr )
{
descr_f = descr.get()->ob_type->tp_descr_get;
descr_f = descr.type()->tp_descr_get;
if( descr_f && PyDescr_IsData( descr.get() ) )
return 1;
}
Expand Down Expand Up @@ -178,7 +178,7 @@ load_dynamic_attr( PyObject* obj, PyObject* name, PyObject* tracer=0 )
descr = cppy::xincref( _PyType_Lookup( tp, name ) );
if( descr )
{
descr_f = descr.get()->ob_type->tp_descr_get;
descr_f = descr.type()->tp_descr_get;
if( descr_f && PyDescr_IsData( descr.get() ) )
{
cppy::ptr res(
Expand Down Expand Up @@ -257,7 +257,7 @@ set_dynamic_attr( PyObject* obj, PyObject* name, PyObject* value )
descr = cppy::xincref( _PyType_Lookup( tp, name ) );
if( descr )
{
descr_f = descr.get()->ob_type->tp_descr_set;
descr_f = descr.type()->tp_descr_set;
if( descr_f && PyDescr_IsData( descr.get() ) )
return descr_f( descr.get(), objptr.get(), value );
}
Expand Down
5 changes: 3 additions & 2 deletions tests/core/test_importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ def test_handling_wrong_locate_module_implementation(method):
"""


@pytest.yield_fixture()
@pytest.fixture()
def enaml_module(tmpdir):
"""Create an enaml module in a tempdir and add it to sys.path.
Expand Down Expand Up @@ -145,14 +145,15 @@ def test_handling_importing_a_bugged_module(enaml_module):
with open(path, 'a') as f:
f.write('\nraise RuntimeError()')

assert name not in sys.modules
with imports():
with pytest.raises(RuntimeError):
importlib.import_module(name)

assert name not in sys.modules


@pytest.yield_fixture
@pytest.fixture
def enaml_importer():
"""Standard enaml importer whose state is restored after testing.
Expand Down
3 changes: 0 additions & 3 deletions tests/test_encodings.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,10 @@ def test_bytes_literal():
enamldef MyWindow(Window): win:
attr test = b'I also do bytes with\\nescapes: \\785 and \\x12'
attr test2 = b'And handle unicode escapes \\u0320 gracefully'
""")
myWindow = compile_source(source, 'MyWindow')()
bytes_literal = b'I also do bytes with\nescapes: \785 and \x12'
assert bytes_literal == myWindow.test
bytes_literal = b'And handle unicode escapes \u0320 gracefully'
assert bytes_literal == myWindow.test2


def test_raw_bytes_literal1():
Expand Down
16 changes: 8 additions & 8 deletions tests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ def run_pending_tasks(qtbot, timeout=1000):
"""
def check_pending_tasks():
assert not qtbot.enaml_app.has_pending_tasks()
qtbot.wait_until(check_pending_tasks, TIMEOUT)
qtbot.wait_until(check_pending_tasks, timeout=TIMEOUT)


def get_window(qtbot, cls=Window, timeout=1000):
Expand Down Expand Up @@ -113,7 +113,7 @@ def get_window(qtbot, cls=Window, timeout=1000):
def check_window_presence():
assert [w for w in Window.windows if isinstance(w, cls)]

qtbot.wait_until(check_window_presence, TIMEOUT)
qtbot.wait_until(check_window_presence, timeout=TIMEOUT)
for w in Window.windows:
if isinstance(w, cls):
return w
Expand Down Expand Up @@ -144,7 +144,7 @@ def get_popup(qtbot, cls=PopupView, timeout=1000):
def check_popup_presence():
assert [p for p in PopupView.popup_views if isinstance(p, cls)]

qtbot.wait_until(check_popup_presence, TIMEOUT)
qtbot.wait_until(check_popup_presence, timeout=TIMEOUT)
for p in PopupView.popup_views:
if isinstance(p, cls):
return p
Expand All @@ -160,7 +160,7 @@ def wait_for_window_displayed(qtbot, window, timeout=1000):
if not window.proxy_is_active or not window.proxy.widget:
msg = 'Window must be activated before waiting for display'
raise RuntimeError(msg)
qtbot.wait_for_window_shown(window.proxy.widget)
qtbot.wait_exposed(window.proxy.widget, timeout=timeout)


class EventObserver(Atom):
Expand All @@ -184,7 +184,7 @@ def wait_for_destruction(qtbot, widget):
return
obs = EventObserver()
widget.observe('destroyed', obs.callback)
qtbot.wait_until(obs.assert_called, TIMEOUT)
qtbot.wait_until(obs.assert_called, timeout=TIMEOUT)


def close_window_or_popup(qtbot, window_or_popup):
Expand All @@ -197,7 +197,7 @@ def close_window_or_popup(qtbot, window_or_popup):
obs = EventObserver()
window_or_popup.observe('destroyed', obs.callback)
window_or_popup.close()
qtbot.wait_until(obs.assert_called, TIMEOUT)
qtbot.wait_until(obs.assert_called, timeout=TIMEOUT)


@contextmanager
Expand Down Expand Up @@ -263,7 +263,7 @@ def __call__(self):
finally:
if not self.skip_answer:
getattr(dial, self.op)()
self.bot.wait_until(obs.assert_called, TIMEOUT)
self.bot.wait_until(obs.assert_called, timeout=TIMEOUT)

def was_called(self):
"""Assert the scheduler was called.
Expand Down Expand Up @@ -304,7 +304,7 @@ def handle_dialog(qtbot, op='accept', handler=lambda qtbot, window: window,
except Exception:
raise
else:
qtbot.wait_until(sch.was_called, TIMEOUT)
qtbot.wait_until(sch.was_called, timeout=TIMEOUT)


@contextmanager
Expand Down
5 changes: 5 additions & 0 deletions tests/widgets/test_focus_tracker.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"""
import os
import sys

import pytest

Expand Down Expand Up @@ -44,6 +45,10 @@
"""

@pytest.mark.skipif("TRAVIS" in os.environ, reason='Skip on Travis')
@pytest.mark.skipif(
"CI" in os.environ and sys.platform.startswith("linux"),
reason='Skip on linux CI where setting up a windows manager is a nightmare'
)
@pytest.mark.skipif(not is_qt_available(), reason='Requires a Qt binding')
def test_focus_tracking(enaml_qtbot, enaml_sleep):
"""Test moving the focus forward in the presence of a custom focus traversal.
Expand Down
5 changes: 5 additions & 0 deletions tests/widgets/test_focus_traversal.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"""
import os
import sys

import pytest

Expand Down Expand Up @@ -87,6 +88,10 @@


@pytest.mark.skipif("TRAVIS" in os.environ, reason='Skip on Travis')
@pytest.mark.skipif(
"CI" in os.environ and sys.platform.startswith("linux"),
reason='Skip on linux CI where setting up a windows manager is a nightmare'
)
@pytest.mark.skipif(not is_qt_available(), reason='Requires a Qt binding')
@pytest.mark.parametrize("widgets, mods",
[(["f4", "f3", "f6", "f2", "f7", "f5", "f1"], [False]*7),
Expand Down
9 changes: 5 additions & 4 deletions tests/widgets/test_image_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,11 @@ def image_path(name):
dirname = os.path.dirname(r"%s")
return os.path.join(dirname, 'images', name)
IMAGES = {
'None': None,
'Image A': Image(data=open(image_path('img1.png'), 'rb').read()),
}
with open(image_path('img1.png'), 'rb') as f:
IMAGES = {
'None': None,
'Image A': Image(data=f.read()),
}
enamldef Main(Window):
Expand Down

0 comments on commit 5477fc4

Please sign in to comment.