From 7b8a397089673ab54595743f4471a7e33c92f5e2 Mon Sep 17 00:00:00 2001 From: Matthew Einhorn Date: Thu, 23 May 2019 22:19:21 -0400 Subject: [PATCH] Add tests for pyinstaller. --- appveyor.yml | 4 +- kivy/tests/conftest.py | 17 +++ kivy/tests/pyinstaller/simple_widget/main.py | 12 ++ .../tests/pyinstaller/simple_widget/main.spec | 37 ++++++ .../simple_widget/project/__init__.py | 0 .../simple_widget/project/widget.py | 12 ++ kivy/tests/pyinstaller/test_pyinstaller.py | 115 ++++++++++++++++++ kivy/tests/pyinstaller/video_widget/main.py | 6 + kivy/tests/pyinstaller/video_widget/main.spec | 50 ++++++++ .../video_widget/project/__init__.py | 30 +++++ 10 files changed, 281 insertions(+), 2 deletions(-) create mode 100644 kivy/tests/conftest.py create mode 100644 kivy/tests/pyinstaller/simple_widget/main.py create mode 100644 kivy/tests/pyinstaller/simple_widget/main.spec create mode 100644 kivy/tests/pyinstaller/simple_widget/project/__init__.py create mode 100644 kivy/tests/pyinstaller/simple_widget/project/widget.py create mode 100644 kivy/tests/pyinstaller/test_pyinstaller.py create mode 100644 kivy/tests/pyinstaller/video_widget/main.py create mode 100644 kivy/tests/pyinstaller/video_widget/main.spec create mode 100644 kivy/tests/pyinstaller/video_widget/project/__init__.py diff --git a/appveyor.yml b/appveyor.yml index 64054ab90..4e1f75d01 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -67,7 +67,7 @@ build_script: Check-Error - $PYTHONPATH = "$env:APPVEYOR_BUILD_FOLDER;$PYTHONPATH" + $env:PYTHONPATH = "$env:APPVEYOR_BUILD_FOLDER;$PYTHONPATH" echo "Build folder: $env:APPVEYOR_BUILD_FOLDER. Wheel folder: $env:WHEEL_DIR." @@ -155,7 +155,7 @@ build_script: Check-Error } - pip install mock cython pygments docutils pytest kivy_deps.glew_dev kivy_deps.glew kivy_deps.gstreamer_dev kivy_deps.sdl2_dev kivy_deps.sdl2 + pip install mock cython pygments docutils pytest pyinstaller kivy_deps.glew_dev kivy_deps.glew kivy_deps.gstreamer_dev kivy_deps.sdl2_dev kivy_deps.sdl2 pip --no-cache-dir install kivy_deps.gstreamer diff --git a/kivy/tests/conftest.py b/kivy/tests/conftest.py new file mode 100644 index 000000000..d0a260231 --- /dev/null +++ b/kivy/tests/conftest.py @@ -0,0 +1,17 @@ +import pytest + + +def pytest_runtest_makereport(item, call): + # from https://docs.pytest.org/en/latest/example/simple.html + if "incremental" in item.keywords: + if call.excinfo is not None: + parent = item.parent + parent._previousfailed = item + + +def pytest_runtest_setup(item): + # from https://docs.pytest.org/en/latest/example/simple.html + if "incremental" in item.keywords: + previousfailed = getattr(item.parent, "_previousfailed", None) + if previousfailed is not None: + pytest.xfail("previous test failed (%s)" % previousfailed.name) diff --git a/kivy/tests/pyinstaller/simple_widget/main.py b/kivy/tests/pyinstaller/simple_widget/main.py new file mode 100644 index 000000000..d3a7a769c --- /dev/null +++ b/kivy/tests/pyinstaller/simple_widget/main.py @@ -0,0 +1,12 @@ + + +from project.widget import MyWidget + +if __name__ == '__main__': + w = MyWidget() + + assert w.x == w.y + w.y = 868 + assert w.x == 868 + w.y = 370 + assert w.x == 370 diff --git a/kivy/tests/pyinstaller/simple_widget/main.spec b/kivy/tests/pyinstaller/simple_widget/main.spec new file mode 100644 index 000000000..2435f98c5 --- /dev/null +++ b/kivy/tests/pyinstaller/simple_widget/main.spec @@ -0,0 +1,37 @@ +# -*- mode: python -*- + +block_cipher = None +from kivy_deps import sdl2, glew +from kivy.tools.packaging.pyinstaller_hooks import runtime_hooks, hookspath +import os + + +a = Analysis(['main.py'], + pathex=[os.environ['__KIVY_PYINSTALLER_DIR']], + binaries=[], + datas=[], + hiddenimports=[], + hookspath=[os.environ['__KIVY_PYINSTALLER_DIR']], + runtime_hooks=runtime_hooks(), + excludes=['numpy', 'ffpyplayer'], + win_no_prefer_redirects=False, + win_private_assemblies=False, + cipher=block_cipher) +pyz = PYZ(a.pure, a.zipped_data, + cipher=block_cipher) +exe = EXE(pyz, + a.scripts, + exclude_binaries=True, + name='main', + debug=False, + strip=False, + upx=True, + console=True ) +coll = COLLECT(exe, + a.binaries, + a.zipfiles, + a.datas, + *[Tree(p) for p in (sdl2.dep_bins + glew.dep_bins)], + strip=False, + upx=True, + name='main') diff --git a/kivy/tests/pyinstaller/simple_widget/project/__init__.py b/kivy/tests/pyinstaller/simple_widget/project/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/kivy/tests/pyinstaller/simple_widget/project/widget.py b/kivy/tests/pyinstaller/simple_widget/project/widget.py new file mode 100644 index 000000000..7d1767821 --- /dev/null +++ b/kivy/tests/pyinstaller/simple_widget/project/widget.py @@ -0,0 +1,12 @@ +from kivy.uix.widget import Widget + + +class MyWidget(Widget): + + def __init__(self, **kwargs): + super(MyWidget, self).__init__(**kwargs) + + def callback(*l): + self.x = self.y + self.fbind('y', callback) + callback() diff --git a/kivy/tests/pyinstaller/test_pyinstaller.py b/kivy/tests/pyinstaller/test_pyinstaller.py new file mode 100644 index 000000000..03f580275 --- /dev/null +++ b/kivy/tests/pyinstaller/test_pyinstaller.py @@ -0,0 +1,115 @@ +import pytest +import os +import subprocess +import sys +import shutil + +if sys.platform != 'win32': + pytestmark = pytest.mark.skip( + "PyInstaller is currently only tested on Windows") +else: + try: + import PyInstaller + except ImportError: + pytestmark = pytest.mark.skip("PyInstaller is not available") + + +@pytest.mark.incremental +class PyinstallerBase(object): + + pinstall_path = '' + + env = None + + @classmethod + def setup_class(cls): + cls.env = cls.get_env() + + @classmethod + def get_env(cls): + env = os.environ.copy() + env['__KIVY_PYINSTALLER_DIR'] = cls.pinstall_path + + if 'PYTHONPATH' not in env: + env['PYTHONPATH'] = cls.pinstall_path + else: + env['PYTHONPATH'] = cls.pinstall_path + os.sep + env['PYTHONPATH'] + return env + + @classmethod + def get_run_env(cls): + return os.environ.copy() + + def test_project(self): + try: + # check that the project works normally before packaging + subprocess.check_output( + [sys.executable or 'python', + os.path.join(self.pinstall_path, 'main.py')], + stderr=subprocess.STDOUT, env=self.env) + except subprocess.CalledProcessError as e: + print(e.output.decode('utf8')) + raise + + def test_packaging(self): + dist = os.path.join(self.pinstall_path, 'dist') + build = os.path.join(self.pinstall_path, 'build') + try: + # create pyinstaller package + subprocess.check_output( + [sys.executable or 'python', '-m', 'PyInstaller', + os.path.join(self.pinstall_path, 'main.spec'), + '--distpath', dist, '--workpath', build], + stderr=subprocess.STDOUT, env=self.env) + except subprocess.CalledProcessError as e: + print(e.output.decode('utf8')) + raise + + def test_packaged_project(self): + try: + # test package + subprocess.check_output( + os.path.join(self.pinstall_path, 'dist', 'main', 'main'), + stderr=subprocess.STDOUT, env=self.get_run_env()) + except subprocess.CalledProcessError as e: + print(e.output.decode('utf8')) + raise + + @classmethod + def teardown_class(cls): + shutil.rmtree( + os.path.join(cls.pinstall_path, '__pycache__'), + ignore_errors=True) + shutil.rmtree( + os.path.join(cls.pinstall_path, 'build'), ignore_errors=True) + shutil.rmtree( + os.path.join(cls.pinstall_path, 'dist'), ignore_errors=True) + shutil.rmtree( + os.path.join(cls.pinstall_path, 'project', '__pycache__'), + ignore_errors=True) + + +class TestSimpleWidget(PyinstallerBase): + + pinstall_path = os.path.join(os.path.dirname(__file__), 'simple_widget') + + +class TestVideoWidget(PyinstallerBase): + + pinstall_path = os.path.join(os.path.dirname(__file__), 'video_widget') + + @classmethod + def get_env(cls): + env = super(TestVideoWidget, cls).get_env() + env['__KIVY_VIDEO_TEST_FNAME'] = os.path.abspath(os.path.join( + os.path.dirname(__file__), "..", "..", "..", "examples", + "widgets", "cityCC0.mpg")) + return env + + @classmethod + def get_run_env(cls): + env = super(TestVideoWidget, cls).get_run_env() + env['__KIVY_VIDEO_TEST_FNAME'] = os.path.abspath(os.path.join( + os.path.dirname(__file__), "..", "..", "..", "examples", + "widgets", "cityCC0.mpg")) + return env diff --git a/kivy/tests/pyinstaller/video_widget/main.py b/kivy/tests/pyinstaller/video_widget/main.py new file mode 100644 index 000000000..e5a29762f --- /dev/null +++ b/kivy/tests/pyinstaller/video_widget/main.py @@ -0,0 +1,6 @@ +from project import VideoApp + +if __name__ == '__main__': + from kivy.core.video import Video + assert Video is not None + VideoApp().run() diff --git a/kivy/tests/pyinstaller/video_widget/main.spec b/kivy/tests/pyinstaller/video_widget/main.spec new file mode 100644 index 000000000..960b742f9 --- /dev/null +++ b/kivy/tests/pyinstaller/video_widget/main.spec @@ -0,0 +1,50 @@ +# -*- mode: python -*- + +block_cipher = None +from kivy_deps import sdl2, glew +from kivy.tools.packaging.pyinstaller_hooks import runtime_hooks, hookspath +import os + +deps = list(sdl2.dep_bins + glew.dep_bins) +try: + import ffpyplayer + deps.extend(ffpyplayer.dep_bins) +except ImportError: + pass +try: + from kivy_deps import gstreamer + deps.extend(gstreamer.dep_bins) +except ImportError: + pass +print('deps are: ', deps) + + +a = Analysis(['main.py'], + pathex=[os.environ['__KIVY_PYINSTALLER_DIR']], + binaries=[], + datas=[], + hiddenimports=[], + hookspath=[os.environ['__KIVY_PYINSTALLER_DIR']], + runtime_hooks=runtime_hooks(), + excludes=['numpy',], + win_no_prefer_redirects=False, + win_private_assemblies=False, + cipher=block_cipher) +pyz = PYZ(a.pure, a.zipped_data, + cipher=block_cipher) +exe = EXE(pyz, + a.scripts, + exclude_binaries=True, + name='main', + debug=False, + strip=False, + upx=True, + console=True ) +coll = COLLECT(exe, + a.binaries, + a.zipfiles, + a.datas, + *[Tree(p) for p in deps], + strip=False, + upx=True, + name='main') diff --git a/kivy/tests/pyinstaller/video_widget/project/__init__.py b/kivy/tests/pyinstaller/video_widget/project/__init__.py new file mode 100644 index 000000000..2302cd3ef --- /dev/null +++ b/kivy/tests/pyinstaller/video_widget/project/__init__.py @@ -0,0 +1,30 @@ +from kivy.app import App +from kivy.uix.videoplayer import VideoPlayer +from kivy.clock import Clock +import os + + +class VideoApp(App): + + player = None + + def build(self): + self.player = player = VideoPlayer( + source=os.environ['__KIVY_VIDEO_TEST_FNAME'], volume=0) + + self.player.fbind('position', self.check_position) + Clock.schedule_once(self.start_player, 0) + Clock.schedule_once(self.stop_player, 5) + return player + + def start_player(self, *args): + self.player.state = 'play' + + def check_position(self, *args): + if self.player.position > 0.1: + self.stop_player() + + def stop_player(self, *args): + assert self.player.duration > 0 + assert self.player.position > 0 + self.stop()