From d8b6a32538c8c909cac3546de38d6374dae7ae55 Mon Sep 17 00:00:00 2001 From: Andre Miras Date: Tue, 10 Sep 2019 23:14:24 +0200 Subject: [PATCH 01/17] Also (`tox`) test against Python3.7, fixes #13 And switch to Python3.7 as default from `Makefile`. --- .travis.yml | 2 +- CHANGELOG.md | 4 ++++ Makefile | 3 +-- tox.ini | 7 +------ 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index 763bf0c..a40c4d5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,7 +9,7 @@ env: global: - DISPLAY=:99.0 matrix: - - TAG=zbarcam-linux DOCKERFILE=dockerfiles/Dockerfile-linux COMMAND='tox' + - TAG=xcamera-linux DOCKERFILE=dockerfiles/Dockerfile-linux COMMAND='make test' before_install: - sudo apt update -qq > /dev/null diff --git a/CHANGELOG.md b/CHANGELOG.md index 8abc0fe..2ae91f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Change Log +## [Unreleased] + + - Add Python3.7 support, refs #13 + ## [2019.0911] - Move to new garden layout diff --git a/Makefile b/Makefile index d1bf10a..503df2b 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,6 @@ VENV_NAME=venv PIP=$(VENV_NAME)/bin/pip TOX=`which tox` -GARDEN=$(VENV_NAME)/bin/garden PYTHON=$(VENV_NAME)/bin/python ISORT=$(VENV_NAME)/bin/isort FLAKE8=$(VENV_NAME)/bin/flake8 @@ -18,7 +17,7 @@ SYSTEM_DEPENDENCIES= \ virtualenv OS=$(shell lsb_release -si) PYTHON_MAJOR_VERSION=3 -PYTHON_MINOR_VERSION=6 +PYTHON_MINOR_VERSION=7 PYTHON_VERSION=$(PYTHON_MAJOR_VERSION).$(PYTHON_MINOR_VERSION) PYTHON_WITH_VERSION=python$(PYTHON_VERSION) diff --git a/tox.ini b/tox.ini index 7622c6f..32103f1 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = pep8,isort-check,py36 +envlist = pep8,isort-check,py36,py37 # no setup.py to be ran skipsdist = True @@ -17,8 +17,3 @@ commands = flake8 src/ tests/ setup.py setup_meta.py [testenv:isort-check] commands = isort --check-only --recursive --diff src/ tests/ setup.py setup_meta.py - - -[flake8] -ignore = - E501, # Line too long (82 > 79 characters) From 6892c9cac88c5399bc6e7783f8eb1191b7d6744f Mon Sep 17 00:00:00 2001 From: Andre Miras Date: Tue, 10 Sep 2019 23:57:04 +0200 Subject: [PATCH 02/17] Publish `xcamera` demo binary, fixes #9 Moves demo to the published module folder. Hooks it from `src/main.py` as it's still the one picked up by `buildozer`. Also adds missing `opencv-python` install dep. --- CHANGELOG.md | 1 + README.md | 2 +- setup.py | 4 +++ src/kivy_garden/xcamera/main.py | 43 +++++++++++++++++++++++++++++++++ src/main.py | 40 ++++-------------------------- 5 files changed, 54 insertions(+), 36 deletions(-) create mode 100644 src/kivy_garden/xcamera/main.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ae91f4..8d82faa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## [Unreleased] + - Publish `xcamera` demo binary, refs #9 - Add Python3.7 support, refs #13 ## [2019.0911] diff --git a/README.md b/README.md index fdf81e2..80aa50b 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ pip install xcamera ``` ## Demo -A full working demo is available in [src/main.py](https://github.com/kivy-garden/xcamera/blob/master/src/main.py). +A full working demo is available in [src/kivy_garden/xcamera/main.py](https://github.com/kivy-garden/xcamera/blob/develop/src/main.py). You can run it via: ```sh make run diff --git a/setup.py b/setup.py index 64d7e41..639edb8 100644 --- a/setup.py +++ b/setup.py @@ -25,8 +25,12 @@ def read(fname): 'kivy_garden.xcamera.data': ['*.ttf', '*.wav'], }, 'package_dir': {'': 'src'}, + 'entry_points': { + 'console_scripts': ['xcamera=kivy_garden.xcamera.main:main'], + }, 'install_requires': [ 'kivy', + 'opencv-python', ], } diff --git a/src/kivy_garden/xcamera/main.py b/src/kivy_garden/xcamera/main.py new file mode 100644 index 0000000..c522662 --- /dev/null +++ b/src/kivy_garden/xcamera/main.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python +from kivy.app import App +from kivy.lang import Builder + +kv = """ +#:import XCamera kivy_garden.xcamera.XCamera + +FloatLayout: + orientation: 'vertical' + + XCamera: + id: xcamera + on_picture_taken: app.picture_taken(*args) + + BoxLayout: + orientation: 'horizontal' + size_hint: 1, None + height: sp(50) + + Button: + text: 'Set landscape' + on_release: xcamera.force_landscape() + + Button: + text: 'Restore orientation' + on_release: xcamera.restore_orientation() +""" + + +class CameraApp(App): + def build(self): + return Builder.load_string(kv) + + def picture_taken(self, obj, filename): + print('Picture taken and saved to {}'.format(filename)) + + +def main(): + CameraApp().run() + + +if __name__ == '__main__': + main() diff --git a/src/main.py b/src/main.py index e192666..61c7612 100755 --- a/src/main.py +++ b/src/main.py @@ -1,38 +1,8 @@ -from kivy.app import App -from kivy.lang import Builder - -kv = """ -#:import XCamera kivy_garden.xcamera.XCamera - -FloatLayout: - orientation: 'vertical' - - XCamera: - id: xcamera - on_picture_taken: app.picture_taken(*args) - - BoxLayout: - orientation: 'horizontal' - size_hint: 1, None - height: sp(50) - - Button: - text: 'Set landscape' - on_release: xcamera.force_landscape() - - Button: - text: 'Restore orientation' - on_release: xcamera.restore_orientation() """ - - -class CameraApp(App): - def build(self): - return Builder.load_string(kv) - - def picture_taken(self, obj, filename): - print('Picture taken and saved to {}'.format(filename)) - +This is the file being picked up by `buildozer` as it's expecting a `main.py` +in the source directory. +""" +from kivy_garden.xcamera.main import main if __name__ == '__main__': - CameraApp().run() + main() From b370f4a9950d018704451fe21149ee3529d53232 Mon Sep 17 00:00:00 2001 From: Andre Miras Date: Wed, 11 Sep 2019 01:05:32 +0200 Subject: [PATCH 03/17] Fixes broken screenshot on PyPI Uses full path so it works on other side rendering the `README.md`. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 80aa50b..f8167f2 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ functionality. In particular: Screenshot: -![screenshot](/screenshot.png?raw=True "Screenshot") +![screenshot](https://raw.githubusercontent.com/kivy-garden/xcamera/develop/screenshot.png?raw=True "Screenshot") Notes: From b753db1f7d6c9423a6ce9f9bf2be77ba0db7f735 Mon Sep 17 00:00:00 2001 From: Andre Miras Date: Sun, 15 Sep 2019 13:24:47 +0200 Subject: [PATCH 04/17] linter depends on test venv --- Makefile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 503df2b..94af440 100644 --- a/Makefile +++ b/Makefile @@ -47,13 +47,13 @@ run: run/linux test: $(TOX) -lint/isort-check: virtualenv +lint/isort-check: virtualenv/test $(ISORT) --check-only --recursive --diff $(SOURCES) -lint/isort-fix: virtualenv +lint/isort-fix: virtualenv/test $(ISORT) --recursive $(SOURCES) -lint/flake8: virtualenv +lint/flake8: virtualenv/test $(FLAKE8) $(SOURCES) lint: lint/isort-check lint/flake8 @@ -67,7 +67,7 @@ docs: release/clean: rm -rf dist/ build/ -release/build: release/clean +release/build: release/clean virtualenv $(PYTHON) setup.py sdist bdist_wheel $(PYTHON) setup_meta.py sdist bdist_wheel $(TWINE) check dist/* From b003aca82746b48ee417bd5629e7d4dc91e71a04 Mon Sep 17 00:00:00 2001 From: Andre Miras Date: Sun, 15 Sep 2019 13:25:03 +0200 Subject: [PATCH 05/17] DRY on tox.ini --- tox.ini | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tox.ini b/tox.ini index 32103f1..fa5950f 100644 --- a/tox.ini +++ b/tox.ini @@ -6,14 +6,14 @@ skipsdist = True [testenv] setenv = PYTHONPATH = {toxinidir}/src/ + SOURCES = src/ tests/ setup.py setup_meta.py deps = -r{toxinidir}/requirements.txt -r{toxinidir}/requirements/requirements-test.txt commands = pytest tests/ [testenv:pep8] -commands = flake8 src/ tests/ setup.py setup_meta.py +commands = flake8 {env:SOURCES} [testenv:isort-check] -commands = - isort --check-only --recursive --diff src/ tests/ setup.py setup_meta.py +commands = isort --check-only --recursive --diff {env:SOURCES} From 5ba68876be9875281dea2e98b63190e99d555b21 Mon Sep 17 00:00:00 2001 From: Andre Miras Date: Sun, 15 Sep 2019 13:25:28 +0200 Subject: [PATCH 06/17] Package is Python3 only --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 639edb8..0c27560 100644 --- a/setup.py +++ b/setup.py @@ -28,6 +28,7 @@ def read(fname): 'entry_points': { 'console_scripts': ['xcamera=kivy_garden.xcamera.main:main'], }, + 'python_requires': '>=3', 'install_requires': [ 'kivy', 'opencv-python', From cd018146b0a03902c102fb9a4d4324f6382efd8f Mon Sep 17 00:00:00 2001 From: Andre Miras Date: Thu, 26 Sep 2019 22:24:18 +0200 Subject: [PATCH 07/17] Revamps Dockerfile and Makefile - simplifies `.travis.yml` file (DISPLAY not yet needed) - moves `docker build` and `docker run` commands to `Makefile` --- .travis.yml | 17 ++--------------- Makefile | 9 +++++++++ 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/.travis.yml b/.travis.yml index a40c4d5..05bc730 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,21 +5,8 @@ language: generic services: - docker -env: - global: - - DISPLAY=:99.0 - matrix: - - TAG=xcamera-linux DOCKERFILE=dockerfiles/Dockerfile-linux COMMAND='make test' - -before_install: - - sudo apt update -qq > /dev/null - - sudo apt install --yes --no-install-recommends xvfb - install: - - docker build --tag=$TAG --file=$DOCKERFILE --build-arg CI . - -before_script: - - sh -e /etc/init.d/xvfb start + - make docker/build script: - - travis_wait docker run -e DISPLAY -e CI -v /tmp/.X11-unix:/tmp/.X11-unix $TAG $COMMAND + - make docker/run/test diff --git a/Makefile b/Makefile index 94af440..35624b1 100644 --- a/Makefile +++ b/Makefile @@ -82,3 +82,12 @@ clean: release/clean docs/clean clean/all: clean rm -rf $(VENV_NAME) .tox/ + +docker/build: + docker build --tag=xcamera-linux --file=dockerfiles/Dockerfile-linux . + +docker/run/test: + docker run xcamera-linux 'make test' + +docker/run/shell: + docker run -it --rm xcamera-linux From 6774782f2f7b04cfc55d067c5f44ed2693e58936 Mon Sep 17 00:00:00 2001 From: Andre Miras Date: Thu, 26 Sep 2019 22:57:59 +0200 Subject: [PATCH 08/17] Second revamp of Dockerfile and Makefile - uses `Makefile` as single source of truth for system dependencies - adds `docker/run/app` target to run app through Docker --- Makefile | 52 +++++++++++++++++++++++------------- dockerfiles/Dockerfile-linux | 27 ++++--------------- 2 files changed, 38 insertions(+), 41 deletions(-) diff --git a/Makefile b/Makefile index 35624b1..6f8717b 100644 --- a/Makefile +++ b/Makefile @@ -1,18 +1,31 @@ -VENV_NAME=venv -PIP=$(VENV_NAME)/bin/pip +VIRTUAL_ENV ?= venv +PIP=$(VIRTUAL_ENV)/bin/pip TOX=`which tox` -PYTHON=$(VENV_NAME)/bin/python -ISORT=$(VENV_NAME)/bin/isort -FLAKE8=$(VENV_NAME)/bin/flake8 +PYTHON=$(VIRTUAL_ENV)/bin/python +ISORT=$(VIRTUAL_ENV)/bin/isort +FLAKE8=$(VIRTUAL_ENV)/bin/flake8 TWINE=`which twine` SOURCES=src/ tests/ setup.py setup_meta.py # using full path so it can be used outside the root dir SPHINXBUILD=$(shell realpath venv/bin/sphinx-build) DOCS_DIR=doc SYSTEM_DEPENDENCIES= \ - libpython$(PYTHON_VERSION)-dev \ + build-essential \ + ccache \ + cmake \ + curl \ libsdl2-dev \ + libsdl2-image-dev \ + libsdl2-mixer-dev \ + libsdl2-ttf-dev \ + libpython3.6-dev \ + libpython$(PYTHON_VERSION)-dev \ libzbar-dev \ + pkg-config \ + python3.6 \ + python3.6-dev \ + python3.7 \ + python3.7-dev \ tox \ virtualenv OS=$(shell lsb_release -si) @@ -24,26 +37,24 @@ PYTHON_WITH_VERSION=python$(PYTHON_VERSION) all: system_dependencies virtualenv -venv: - test -d venv || virtualenv -p $(PYTHON_WITH_VERSION) venv +system_dependencies: +ifeq ($(OS), Ubuntu) + sudo apt install --yes --no-install-recommends $(SYSTEM_DEPENDENCIES) +endif -virtualenv: venv +$(VIRTUAL_ENV): + virtualenv -p $(PYTHON_WITH_VERSION) $(VIRTUAL_ENV) $(PIP) install Cython==0.28.6 $(PIP) install -r requirements.txt +virtualenv: $(VIRTUAL_ENV) + virtualenv/test: virtualenv $(PIP) install -r requirements/requirements-test.txt -system_dependencies: -ifeq ($(OS), Ubuntu) - sudo apt install --yes --no-install-recommends $(SYSTEM_DEPENDENCIES) -endif - -run/linux: virtualenv +run: virtualenv $(PYTHON) src/main.py --debug -run: run/linux - test: $(TOX) @@ -81,7 +92,7 @@ clean: release/clean docs/clean find . -type d -name "*.egg-info" -exec rm -r {} + clean/all: clean - rm -rf $(VENV_NAME) .tox/ + rm -rf $(VIRTUAL_ENV) .tox/ docker/build: docker build --tag=xcamera-linux --file=dockerfiles/Dockerfile-linux . @@ -89,5 +100,8 @@ docker/build: docker/run/test: docker run xcamera-linux 'make test' +docker/run/app: + docker run -e DISPLAY -v /tmp/.X11-unix:/tmp/.X11-unix --device=/dev/video0:/dev/video0 xcamera-linux 'make run' + docker/run/shell: - docker run -it --rm xcamera-linux + docker run -e DISPLAY -v /tmp/.X11-unix:/tmp/.X11-unix --device=/dev/video0:/dev/video0 -it --rm xcamera-linux diff --git a/dockerfiles/Dockerfile-linux b/dockerfiles/Dockerfile-linux index b8cef3e..886d202 100644 --- a/dockerfiles/Dockerfile-linux +++ b/dockerfiles/Dockerfile-linux @@ -7,7 +7,7 @@ # docker run xcamera-linux 'make test' # For running UI: # xhost +"local:docker@" -# docker run -e DISPLAY=$DISPLAY -v /tmp/.X11-unix:/tmp/.X11-unix xcamera-linux 'make uitest' +# docker run -e DISPLAY -v /tmp/.X11-unix:/tmp/.X11-unix --device=/dev/video0:/dev/video0 xcamera-linux 'make run' # Or for interactive shell: # docker run -it --rm xcamera-linux FROM ubuntu:18.04 @@ -26,37 +26,20 @@ ENV LANG="en_US.UTF-8" \ # install system dependencies RUN apt install --yes --no-install-recommends \ - build-essential \ - ccache \ - cmake \ - curl \ - libsdl2-dev \ - libsdl2-image-dev \ - libsdl2-mixer-dev \ - libsdl2-ttf-dev \ - libpython3.6-dev \ - libpython3.7-dev \ - libzbar-dev \ lsb-release \ make \ - pkg-config \ - python3.6 \ - python3.6-dev \ - python3.7 \ - python3.7-dev \ - sudo \ - tox \ - virtualenv + sudo # prepare non root env RUN useradd --create-home --shell /bin/bash ${USER} # with sudo access and no password RUN usermod -append --groups sudo ${USER} RUN echo "%sudo ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers +# gives access to video so the camera can be accessed within the container +RUN gpasswd --add ${USER} video USER ${USER} WORKDIR ${WORK_DIR} COPY . ${WORK_DIR} - -# RUN make +RUN sudo make system_dependencies && make virtualenv ENTRYPOINT ["./dockerfiles/start.sh"] From ae035eb62772fbb51c3a8b6df91c6e1f21957225 Mon Sep 17 00:00:00 2001 From: Andre Miras Date: Sat, 28 Sep 2019 16:03:34 +0200 Subject: [PATCH 09/17] Enables coverage testing and coveralls.io report --- .dockerignore | 1 - CHANGELOG.md | 1 + Makefile | 9 ++++++--- README.md | 1 + dockerfiles/env.list | 9 +++++++++ requirements/requirements-test.txt | 2 ++ tox.ini | 3 ++- 7 files changed, 21 insertions(+), 5 deletions(-) create mode 100644 dockerfiles/env.list diff --git a/.dockerignore b/.dockerignore index 38d67d5..9a59c66 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,6 +1,5 @@ bin/ venv/ -.git/ .buildozer/ .pytest_cache/ .tox/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d82faa..a931cd9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## [Unreleased] + - Setup coverage testing - Publish `xcamera` demo binary, refs #9 - Add Python3.7 support, refs #13 diff --git a/Makefile b/Makefile index 6f8717b..a259be0 100644 --- a/Makefile +++ b/Makefile @@ -14,6 +14,7 @@ SYSTEM_DEPENDENCIES= \ ccache \ cmake \ curl \ + git \ libsdl2-dev \ libsdl2-image-dev \ libsdl2-mixer-dev \ @@ -32,6 +33,7 @@ OS=$(shell lsb_release -si) PYTHON_MAJOR_VERSION=3 PYTHON_MINOR_VERSION=7 PYTHON_VERSION=$(PYTHON_MAJOR_VERSION).$(PYTHON_MINOR_VERSION) +PYTHON_MAJOR_MINOR=$(PYTHON_MAJOR_VERSION)$(PYTHON_MINOR_VERSION) PYTHON_WITH_VERSION=python$(PYTHON_VERSION) @@ -57,6 +59,7 @@ run: virtualenv test: $(TOX) + @if test -n "$$CI"; then .tox/py$(PYTHON_MAJOR_MINOR)/bin/coveralls; fi; \ lint/isort-check: virtualenv/test $(ISORT) --check-only --recursive --diff $(SOURCES) @@ -98,10 +101,10 @@ docker/build: docker build --tag=xcamera-linux --file=dockerfiles/Dockerfile-linux . docker/run/test: - docker run xcamera-linux 'make test' + docker run --env-file dockerfiles/env.list xcamera-linux 'make test' docker/run/app: - docker run -e DISPLAY -v /tmp/.X11-unix:/tmp/.X11-unix --device=/dev/video0:/dev/video0 xcamera-linux 'make run' + docker run --env-file dockerfiles/env.list -v /tmp/.X11-unix:/tmp/.X11-unix --device=/dev/video0:/dev/video0 xcamera-linux 'make run' docker/run/shell: - docker run -e DISPLAY -v /tmp/.X11-unix:/tmp/.X11-unix --device=/dev/video0:/dev/video0 -it --rm xcamera-linux + docker run --env-file dockerfiles/env.list -v /tmp/.X11-unix:/tmp/.X11-unix --device=/dev/video0:/dev/video0 -it --rm xcamera-linux diff --git a/README.md b/README.md index f8167f2..3f41d3d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # XCamera: Android-optimized camera widget [![Build Status](https://travis-ci.com/kivy-garden/xcamera.svg?branch=develop)](https://travis-ci.com/kivy-garden/xcamera) +[![Coverage Status](https://coveralls.io/repos/github/kivy-garden/xcamera/badge.svg?branch=develop)](https://coveralls.io/github/kivy-garden/xcamera?branch=develop) [![PyPI version](https://badge.fury.io/py/xcamera.svg)](https://badge.fury.io/py/xcamera) XCamera is a widget which extends the standard Kivy Camera widget with more diff --git a/dockerfiles/env.list b/dockerfiles/env.list new file mode 100644 index 0000000..732d46e --- /dev/null +++ b/dockerfiles/env.list @@ -0,0 +1,9 @@ +# used by coveralls.io, refs: +# https://coveralls-python.readthedocs.io/en/latest/usage/tox.html#travisci +CI +TRAVIS +TRAVIS_BRANCH +TRAVIS_JOB_ID +TRAVIS_PULL_REQUEST +# used for running UI tests +DISPLAY diff --git a/requirements/requirements-test.txt b/requirements/requirements-test.txt index 7424ff6..09ff3bd 100644 --- a/requirements/requirements-test.txt +++ b/requirements/requirements-test.txt @@ -1,3 +1,5 @@ +coveralls flake8 isort pytest +pytest-cov diff --git a/tox.ini b/tox.ini index fa5950f..6703a46 100644 --- a/tox.ini +++ b/tox.ini @@ -10,7 +10,8 @@ setenv = deps = -r{toxinidir}/requirements.txt -r{toxinidir}/requirements/requirements-test.txt -commands = pytest tests/ +commands = + pytest --cov=src/ tests/ [testenv:pep8] commands = flake8 {env:SOURCES} From d06dc077c35b2dc96415ced044669d35670363cc Mon Sep 17 00:00:00 2001 From: Andre Miras Date: Sat, 28 Sep 2019 19:00:37 +0200 Subject: [PATCH 10/17] Increases coverage test from 70% to 91% Actually test the `XCamera` class and util functions. Note that we need the X virtual framebuffer as we actually run some UI. --- .travis.yml | 10 ++++ Makefile | 6 +- src/kivy_garden/xcamera/xcamera.py | 9 +-- tests/kivy_garden/xcamera/test_xcamera.py | 70 +++++++++++++++++++++++ tests/test_main.py | 14 +++++ tox.ini | 3 + 6 files changed, 107 insertions(+), 5 deletions(-) create mode 100644 tests/kivy_garden/xcamera/test_xcamera.py create mode 100644 tests/test_main.py diff --git a/.travis.yml b/.travis.yml index 05bc730..2b9d4c6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,8 +5,18 @@ language: generic services: - docker +env: + - DISPLAY=:99.0 + +before_install: + - sudo apt update -qq > /dev/null + - sudo apt install --yes --no-install-recommends xvfb + install: - make docker/build +before_script: + - sh -e /etc/init.d/xvfb start + script: - make docker/run/test diff --git a/Makefile b/Makefile index a259be0..d59b654 100644 --- a/Makefile +++ b/Makefile @@ -4,6 +4,7 @@ TOX=`which tox` PYTHON=$(VIRTUAL_ENV)/bin/python ISORT=$(VIRTUAL_ENV)/bin/isort FLAKE8=$(VIRTUAL_ENV)/bin/flake8 +PYTEST=$(VIRTUAL_ENV)/bin/pytest TWINE=`which twine` SOURCES=src/ tests/ setup.py setup_meta.py # using full path so it can be used outside the root dir @@ -61,6 +62,9 @@ test: $(TOX) @if test -n "$$CI"; then .tox/py$(PYTHON_MAJOR_MINOR)/bin/coveralls; fi; \ +pytest: virtualenv/test + PYTHONPATH=src $(PYTEST) tests/ + lint/isort-check: virtualenv/test $(ISORT) --check-only --recursive --diff $(SOURCES) @@ -101,7 +105,7 @@ docker/build: docker build --tag=xcamera-linux --file=dockerfiles/Dockerfile-linux . docker/run/test: - docker run --env-file dockerfiles/env.list xcamera-linux 'make test' + docker run --env-file dockerfiles/env.list -v /tmp/.X11-unix:/tmp/.X11-unix xcamera-linux 'make test' docker/run/app: docker run --env-file dockerfiles/env.list -v /tmp/.X11-unix:/tmp/.X11-unix --device=/dev/video0:/dev/video0 xcamera-linux 'make run' diff --git a/src/kivy_garden/xcamera/xcamera.py b/src/kivy_garden/xcamera/xcamera.py index c4362a1..da618eb 100644 --- a/src/kivy_garden/xcamera/xcamera.py +++ b/src/kivy_garden/xcamera/xcamera.py @@ -22,6 +22,10 @@ def darker(color, factor=0.5): return r, g, b, a +def get_filename(): + return datetime.datetime.now().strftime('%Y-%m-%d %H.%M.%S.jpg') + + class XCameraIconButton(ButtonBehavior, Label): pass @@ -40,13 +44,10 @@ def on_picture_taken(self, filename): This event is fired every time a picture has been taken """ - def get_filename(self): - return datetime.datetime.now().strftime('%Y-%m-%d %H.%M.%S.jpg') - def shoot(self): def on_success(filename): self.dispatch('on_picture_taken', filename) - filename = self.get_filename() + filename = get_filename() if self.directory: filename = os.path.join(self.directory, filename) take_picture(self, filename, on_success) diff --git a/tests/kivy_garden/xcamera/test_xcamera.py b/tests/kivy_garden/xcamera/test_xcamera.py new file mode 100644 index 0000000..a0c7b75 --- /dev/null +++ b/tests/kivy_garden/xcamera/test_xcamera.py @@ -0,0 +1,70 @@ +import os +from unittest import mock + +from kivy_garden.xcamera import platform_api, xcamera + + +def get_camera_class(): + """ + Continuous integration providers don't have a camera available. + """ + if os.environ.get('CI', False): + Camera = None + else: + from kivy.core.camera import Camera + return Camera + + +def get_xcamera(): + """ + Helper function to return a (potentially patched) XCamera instance. + """ + Camera = get_camera_class() + # overrides to `None` as initializing it twice seems to freeze tests + Camera = None + # uses the `wraps` parameter to conditionally enable/disable mock + with mock.patch('kivy.uix.camera.CoreCamera', wraps=Camera): + camera = xcamera.XCamera() + return camera + + +class TestBase: + + def test_darker(self): + red = 0.1 + green = 0.2 + blue = 0.3 + alpha = 0.4 + color = (red, green, blue, alpha) + new_color = xcamera.darker(color) + assert new_color == (0.05, 0.1, 0.15, 0.4) + + def test_getfilename(self): + assert xcamera.get_filename().endswith('.jpg') + + +class TestXCamera: + + def test_shoot(self): + camera = get_xcamera() + with mock.patch( + 'kivy_garden.xcamera.xcamera.take_picture') as m_take_picture: + camera.shoot() + assert m_take_picture.mock_calls == [ + mock.call(camera, mock.ANY, mock.ANY)] + + def test_force_landscape(self): + camera = get_xcamera() + assert camera._previous_orientation is None + camera.force_landscape() + assert camera._previous_orientation == platform_api.LANDSCAPE + + def test_restore_orientation(self): + camera = get_xcamera() + assert camera._previous_orientation is None + camera.restore_orientation() + assert camera._previous_orientation is None + camera.force_landscape() + assert camera._previous_orientation == platform_api.LANDSCAPE + camera.restore_orientation() + assert camera._previous_orientation == platform_api.LANDSCAPE diff --git a/tests/test_main.py b/tests/test_main.py new file mode 100644 index 0000000..f27fcd9 --- /dev/null +++ b/tests/test_main.py @@ -0,0 +1,14 @@ +from unittest import mock + +from main import main + + +class TestMain: + """ + Tests the `main` module. + """ + + def test_main(self): + with mock.patch('kivy_garden.xcamera.main.CameraApp.run') as m_play: + main() + assert m_play.mock_calls == [mock.call()] diff --git a/tox.ini b/tox.ini index 6703a46..2ffc2dc 100644 --- a/tox.ini +++ b/tox.ini @@ -7,6 +7,9 @@ skipsdist = True setenv = PYTHONPATH = {toxinidir}/src/ SOURCES = src/ tests/ setup.py setup_meta.py +passenv = + CI + DISPLAY deps = -r{toxinidir}/requirements.txt -r{toxinidir}/requirements/requirements-test.txt From ac30e3712d1d8ef6892619e7c4f0d67c71f9ab0a Mon Sep 17 00:00:00 2001 From: Andre Miras Date: Sat, 28 Sep 2019 20:08:57 +0200 Subject: [PATCH 11/17] buildozer init --- .gitignore | 1 - buildozer.spec | 305 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 305 insertions(+), 1 deletion(-) create mode 100644 buildozer.spec diff --git a/.gitignore b/.gitignore index 5aa99e0..c6a16e0 100644 --- a/.gitignore +++ b/.gitignore @@ -31,7 +31,6 @@ var/ # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest -*.spec # Installer logs pip-log.txt diff --git a/buildozer.spec b/buildozer.spec new file mode 100644 index 0000000..d9ef6a2 --- /dev/null +++ b/buildozer.spec @@ -0,0 +1,305 @@ +[app] + +# (str) Title of your application +title = My Application + +# (str) Package name +package.name = myapp + +# (str) Package domain (needed for android/ios packaging) +package.domain = org.test + +# (str) Source code where the main.py live +source.dir = . + +# (list) Source files to include (let empty to include all the files) +source.include_exts = py,png,jpg,kv,atlas + +# (list) List of inclusions using pattern matching +#source.include_patterns = assets/*,images/*.png + +# (list) Source files to exclude (let empty to not exclude anything) +#source.exclude_exts = spec + +# (list) List of directory to exclude (let empty to not exclude anything) +#source.exclude_dirs = tests, bin + +# (list) List of exclusions using pattern matching +#source.exclude_patterns = license,images/*/*.jpg + +# (str) Application versioning (method 1) +version = 0.1 + +# (str) Application versioning (method 2) +# version.regex = __version__ = ['"](.*)['"] +# version.filename = %(source.dir)s/main.py + +# (list) Application requirements +# comma separated e.g. requirements = sqlite3,kivy +requirements = python3,kivy + +# (str) Custom source folders for requirements +# Sets custom source for any requirements with recipes +# requirements.source.kivy = ../../kivy + +# (list) Garden requirements +#garden_requirements = + +# (str) Presplash of the application +#presplash.filename = %(source.dir)s/data/presplash.png + +# (str) Icon of the application +#icon.filename = %(source.dir)s/data/icon.png + +# (str) Supported orientation (one of landscape, sensorLandscape, portrait or all) +orientation = portrait + +# (list) List of service to declare +#services = NAME:ENTRYPOINT_TO_PY,NAME2:ENTRYPOINT2_TO_PY + +# +# OSX Specific +# + +# +# author = © Copyright Info + +# change the major version of python used by the app +osx.python_version = 3 + +# Kivy version to use +osx.kivy_version = 1.9.1 + +# +# Android specific +# + +# (bool) Indicate if the application should be fullscreen or not +fullscreen = 0 + +# (string) Presplash background color (for new android toolchain) +# Supported formats are: #RRGGBB #AARRGGBB or one of the following names: +# red, blue, green, black, white, gray, cyan, magenta, yellow, lightgray, +# darkgray, grey, lightgrey, darkgrey, aqua, fuchsia, lime, maroon, navy, +# olive, purple, silver, teal. +#android.presplash_color = #FFFFFF + +# (list) Permissions +#android.permissions = INTERNET + +# (int) Target Android API, should be as high as possible. +#android.api = 27 + +# (int) Minimum API your APK will support. +#android.minapi = 21 + +# (int) Android SDK version to use +#android.sdk = 20 + +# (str) Android NDK version to use +#android.ndk = 17c + +# (int) Android NDK API to use. This is the minimum API your app will support, it should usually match android.minapi. +#android.ndk_api = 21 + +# (bool) Use --private data storage (True) or --dir public storage (False) +#android.private_storage = True + +# (str) Android NDK directory (if empty, it will be automatically downloaded.) +#android.ndk_path = + +# (str) Android SDK directory (if empty, it will be automatically downloaded.) +#android.sdk_path = + +# (str) ANT directory (if empty, it will be automatically downloaded.) +#android.ant_path = + +# (bool) If True, then skip trying to update the Android sdk +# This can be useful to avoid excess Internet downloads or save time +# when an update is due and you just want to test/build your package +# android.skip_update = False + +# (bool) If True, then automatically accept SDK license +# agreements. This is intended for automation only. If set to False, +# the default, you will be shown the license when first running +# buildozer. +# android.accept_sdk_license = False + +# (str) Android entry point, default is ok for Kivy-based app +#android.entrypoint = org.renpy.android.PythonActivity + +# (list) Pattern to whitelist for the whole project +#android.whitelist = + +# (str) Path to a custom whitelist file +#android.whitelist_src = + +# (str) Path to a custom blacklist file +#android.blacklist_src = + +# (list) List of Java .jar files to add to the libs so that pyjnius can access +# their classes. Don't add jars that you do not need, since extra jars can slow +# down the build process. Allows wildcards matching, for example: +# OUYA-ODK/libs/*.jar +#android.add_jars = foo.jar,bar.jar,path/to/more/*.jar + +# (list) List of Java files to add to the android project (can be java or a +# directory containing the files) +#android.add_src = + +# (list) Android AAR archives to add (currently works only with sdl2_gradle +# bootstrap) +#android.add_aars = + +# (list) Gradle dependencies to add (currently works only with sdl2_gradle +# bootstrap) +#android.gradle_dependencies = + +# (list) Java classes to add as activities to the manifest. +#android.add_activites = com.example.ExampleActivity + +# (str) OUYA Console category. Should be one of GAME or APP +# If you leave this blank, OUYA support will not be enabled +#android.ouya.category = GAME + +# (str) Filename of OUYA Console icon. It must be a 732x412 png image. +#android.ouya.icon.filename = %(source.dir)s/data/ouya_icon.png + +# (str) XML file to include as an intent filters in tag +#android.manifest.intent_filters = + +# (str) launchMode to set for the main activity +#android.manifest.launch_mode = standard + +# (list) Android additional libraries to copy into libs/armeabi +#android.add_libs_armeabi = libs/android/*.so +#android.add_libs_armeabi_v7a = libs/android-v7/*.so +#android.add_libs_arm64_v8a = libs/android-v8/*.so +#android.add_libs_x86 = libs/android-x86/*.so +#android.add_libs_mips = libs/android-mips/*.so + +# (bool) Indicate whether the screen should stay on +# Don't forget to add the WAKE_LOCK permission if you set this to True +#android.wakelock = False + +# (list) Android application meta-data to set (key=value format) +#android.meta_data = + +# (list) Android library project to add (will be added in the +# project.properties automatically.) +#android.library_references = + +# (list) Android shared libraries which will be added to AndroidManifest.xml using tag +#android.uses_library = + +# (str) Android logcat filters to use +#android.logcat_filters = *:S python:D + +# (bool) Copy library instead of making a libpymodules.so +#android.copy_libs = 1 + +# (str) The Android arch to build for, choices: armeabi-v7a, arm64-v8a, x86, x86_64 +android.arch = armeabi-v7a + +# +# Python for android (p4a) specific +# + +# (str) python-for-android fork to use, defaults to upstream (kivy) +#p4a.fork = kivy + +# (str) python-for-android branch to use, defaults to master +#p4a.branch = master + +# (str) python-for-android git clone directory (if empty, it will be automatically cloned from github) +#p4a.source_dir = + +# (str) The directory in which python-for-android should look for your own build recipes (if any) +#p4a.local_recipes = + +# (str) Filename to the hook for p4a +#p4a.hook = + +# (str) Bootstrap to use for android builds +# p4a.bootstrap = sdl2 + +# (int) port number to specify an explicit --port= p4a argument (eg for bootstrap flask) +#p4a.port = + + +# +# iOS specific +# + +# (str) Path to a custom kivy-ios folder +#ios.kivy_ios_dir = ../kivy-ios +# Alternately, specify the URL and branch of a git checkout: +ios.kivy_ios_url = https://github.com/kivy/kivy-ios +ios.kivy_ios_branch = master + +# Another platform dependency: ios-deploy +# Uncomment to use a custom checkout +#ios.ios_deploy_dir = ../ios_deploy +# Or specify URL and branch +ios.ios_deploy_url = https://github.com/phonegap/ios-deploy +ios.ios_deploy_branch = 1.7.0 + +# (str) Name of the certificate to use for signing the debug version +# Get a list of available identities: buildozer ios list_identities +#ios.codesign.debug = "iPhone Developer: ()" + +# (str) Name of the certificate to use for signing the release version +#ios.codesign.release = %(ios.codesign.debug)s + + +[buildozer] + +# (int) Log level (0 = error only, 1 = info, 2 = debug (with command output)) +log_level = 2 + +# (int) Display warning if buildozer is run as root (0 = False, 1 = True) +warn_on_root = 1 + +# (str) Path to build artifact storage, absolute or relative to spec file +# build_dir = ./.buildozer + +# (str) Path to build output (i.e. .apk, .ipa) storage +# bin_dir = ./bin + +# ----------------------------------------------------------------------------- +# List as sections +# +# You can define all the "list" as [section:key]. +# Each line will be considered as a option to the list. +# Let's take [app] / source.exclude_patterns. +# Instead of doing: +# +#[app] +#source.exclude_patterns = license,data/audio/*.wav,data/images/original/* +# +# This can be translated into: +# +#[app:source.exclude_patterns] +#license +#data/audio/*.wav +#data/images/original/* +# + + +# ----------------------------------------------------------------------------- +# Profiles +# +# You can extend section / key with a profile +# For example, you want to deploy a demo version of your application without +# HD content. You could first change the title to add "(demo)" in the name +# and extend the excluded directories to remove the HD content. +# +#[app@demo] +#title = My Application (demo) +# +#[app:source.exclude_patterns@demo] +#images/hd/* +# +# Then, invoke the command line with the "demo" profile: +# +#buildozer --profile demo android debug From 27689d73f941209609c6c4369220373bafde2e48 Mon Sep 17 00:00:00 2001 From: Andre Miras Date: Sat, 28 Sep 2019 20:56:34 +0200 Subject: [PATCH 12/17] Updates buildozer.spec, fixes Android build --- .gitignore | 2 ++ buildozer.spec | 35 +++++++++++++++++++---------------- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/.gitignore b/.gitignore index c6a16e0..7951ec8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ # Custom *.swp +.buildozer/ +bin/ # Byte-compiled / optimized / DLL files __pycache__/ diff --git a/buildozer.spec b/buildozer.spec index d9ef6a2..a19cd6d 100644 --- a/buildozer.spec +++ b/buildozer.spec @@ -1,16 +1,16 @@ [app] # (str) Title of your application -title = My Application +title = XCamera # (str) Package name -package.name = myapp +package.name = xcamera # (str) Package domain (needed for android/ios packaging) -package.domain = org.test +package.domain = com.github.kivy # (str) Source code where the main.py live -source.dir = . +source.dir = src # (list) Source files to include (let empty to include all the files) source.include_exts = py,png,jpg,kv,atlas @@ -28,15 +28,18 @@ source.include_exts = py,png,jpg,kv,atlas #source.exclude_patterns = license,images/*/*.jpg # (str) Application versioning (method 1) -version = 0.1 +# version = 0.1 # (str) Application versioning (method 2) -# version.regex = __version__ = ['"](.*)['"] -# version.filename = %(source.dir)s/main.py +version.regex = __version__ = ['"](.*)['"] +version.filename = %(source.dir)s/kivy_garden/xcamera/version.py # (list) Application requirements # comma separated e.g. requirements = sqlite3,kivy -requirements = python3,kivy +requirements = + android, + kivy==1.11.1, + python3 # (str) Custom source folders for requirements # Sets custom source for any requirements with recipes @@ -85,22 +88,22 @@ fullscreen = 0 #android.presplash_color = #FFFFFF # (list) Permissions -#android.permissions = INTERNET +android.permissions = CAMERA # (int) Target Android API, should be as high as possible. -#android.api = 27 +android.api = 27 # (int) Minimum API your APK will support. -#android.minapi = 21 +android.minapi = 21 # (int) Android SDK version to use -#android.sdk = 20 +android.sdk = 20 # (str) Android NDK version to use -#android.ndk = 17c +android.ndk = 19b # (int) Android NDK API to use. This is the minimum API your app will support, it should usually match android.minapi. -#android.ndk_api = 21 +android.ndk_api = 21 # (bool) Use --private data storage (True) or --dir public storage (False) #android.private_storage = True @@ -123,7 +126,7 @@ fullscreen = 0 # agreements. This is intended for automation only. If set to False, # the default, you will be shown the license when first running # buildozer. -# android.accept_sdk_license = False +android.accept_sdk_license = True # (str) Android entry point, default is ok for Kivy-based app #android.entrypoint = org.renpy.android.PythonActivity @@ -209,7 +212,7 @@ android.arch = armeabi-v7a #p4a.fork = kivy # (str) python-for-android branch to use, defaults to master -#p4a.branch = master +p4a.branch = develop # (str) python-for-android git clone directory (if empty, it will be automatically cloned from github) #p4a.source_dir = From 99fce81444a5f5f5314a793d68078e13e70556a6 Mon Sep 17 00:00:00 2001 From: Andre Miras Date: Sat, 28 Sep 2019 21:56:24 +0200 Subject: [PATCH 13/17] Runtime permission check, fixes #5 Also adds a new `on_camera_ready()` event. --- CHANGELOG.md | 1 + buildozer.spec | 2 +- src/kivy_garden/xcamera/xcamera.py | 60 +++++++++++++++++++++++++++++- 3 files changed, 60 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a931cd9..3b819ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## [Unreleased] - Setup coverage testing + - Runtime camera permission check, refs #5 - Publish `xcamera` demo binary, refs #9 - Add Python3.7 support, refs #13 diff --git a/buildozer.spec b/buildozer.spec index a19cd6d..f58f3c8 100644 --- a/buildozer.spec +++ b/buildozer.spec @@ -13,7 +13,7 @@ package.domain = com.github.kivy source.dir = src # (list) Source files to include (let empty to include all the files) -source.include_exts = py,png,jpg,kv,atlas +source.include_exts = py,png,jpg,kv,atlas,ttf,wav # (list) List of inclusions using pattern matching #source.include_patterns = assets/*,images/*.png diff --git a/src/kivy_garden/xcamera/xcamera.py b/src/kivy_garden/xcamera/xcamera.py index da618eb..f08871d 100644 --- a/src/kivy_garden/xcamera/xcamera.py +++ b/src/kivy_garden/xcamera/xcamera.py @@ -1,12 +1,14 @@ import datetime import os +from kivy.clock import mainthread from kivy.lang import Builder from kivy.properties import ObjectProperty from kivy.resources import resource_add_path from kivy.uix.behaviors import ButtonBehavior from kivy.uix.camera import Camera from kivy.uix.label import Label +from kivy.utils import platform from .platform_api import LANDSCAPE, set_orientation, take_picture @@ -26,6 +28,33 @@ def get_filename(): return datetime.datetime.now().strftime('%Y-%m-%d %H.%M.%S.jpg') +def is_android(): + return platform == 'android' + + +def check_camera_permission(): + """ + Android runtime `CAMERA` permission check. + """ + if not is_android(): + return True + from android.permissions import Permission, check_permission + permission = Permission.CAMERA + return check_permission(permission) + + +def check_request_camera_permission(callback=None): + """ + Android runtime `CAMERA` permission check & request. + """ + had_permission = check_camera_permission() + if not had_permission: + from android.permissions import Permission, request_permissions + permissions = [Permission.CAMERA] + request_permissions(permissions, callback) + return had_permission + + class XCameraIconButton(ButtonBehavior, Label): pass @@ -33,16 +62,43 @@ class XCameraIconButton(ButtonBehavior, Label): class XCamera(Camera): directory = ObjectProperty(None) _previous_orientation = None - __events__ = ('on_picture_taken',) + __events__ = ('on_picture_taken', 'on_camera_ready') def __init__(self, **kwargs): Builder.load_file(os.path.join(ROOT, "xcamera.kv")) super().__init__(**kwargs) + def _on_index(self, *largs): + """ + Overrides `kivy.uix.camera.Camera._on_index()` to make sure + `camera.open()` is not called unless Android `CAMERA` permission is + granted, refs #5. + """ + @mainthread + def on_permissions_callback(permissions, grant_results): + """ + On camera permission callback calls parent `_on_index()` method. + """ + if all(grant_results): + self._on_index_dispatch(*largs) + if check_request_camera_permission(callback=on_permissions_callback): + self._on_index_dispatch(*largs) + + def _on_index_dispatch(self, *largs): + super()._on_index(*largs) + self.dispatch('on_camera_ready') + def on_picture_taken(self, filename): """ - This event is fired every time a picture has been taken + This event is fired every time a picture has been taken. + """ + pass + + def on_camera_ready(self): + """ + Fired when the camera is ready. """ + pass def shoot(self): def on_success(filename): From 5480b55225a2d5d2ecd4dc564b98066302cb127d Mon Sep 17 00:00:00 2001 From: Andre Miras Date: Sat, 28 Sep 2019 22:39:23 +0200 Subject: [PATCH 14/17] Unit tests `check_camera_permission()` refs #5 Increases coverage from 86% to 88%. Note that the `pytest` `Makefile` target will output coverage report to HTML while `tox` will keep as command line. --- Makefile | 3 ++- tests/kivy_garden/xcamera/test_xcamera.py | 25 +++++++++++++++++++++++ tox.ini | 2 +- 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index d59b654..54a8883 100644 --- a/Makefile +++ b/Makefile @@ -63,7 +63,7 @@ test: @if test -n "$$CI"; then .tox/py$(PYTHON_MAJOR_MINOR)/bin/coveralls; fi; \ pytest: virtualenv/test - PYTHONPATH=src $(PYTEST) tests/ + PYTHONPATH=src $(PYTEST) --cov src/ --cov-report html tests/ lint/isort-check: virtualenv/test $(ISORT) --check-only --recursive --diff $(SOURCES) @@ -97,6 +97,7 @@ clean: release/clean docs/clean py3clean src/ find . -type d -name "__pycache__" -exec rm -r {} + find . -type d -name "*.egg-info" -exec rm -r {} + + rm -rf htmlcov/ clean/all: clean rm -rf $(VIRTUAL_ENV) .tox/ diff --git a/tests/kivy_garden/xcamera/test_xcamera.py b/tests/kivy_garden/xcamera/test_xcamera.py index a0c7b75..278aa40 100644 --- a/tests/kivy_garden/xcamera/test_xcamera.py +++ b/tests/kivy_garden/xcamera/test_xcamera.py @@ -28,6 +28,15 @@ def get_xcamera(): return camera +def patch_is_android(): + return mock.patch('kivy_garden.xcamera.xcamera.is_android') + + +def patch_android_permissions(m_android_permissions): + return mock.patch.dict( + 'sys.modules', {'android.permissions': m_android_permissions}) + + class TestBase: def test_darker(self): @@ -42,6 +51,22 @@ def test_darker(self): def test_getfilename(self): assert xcamera.get_filename().endswith('.jpg') + def test_check_camera_permission(self): + """ + Makes sure `check_permission()` is called if `is_android()` is `True`. + """ + m_android_permissions = mock.Mock() + with patch_is_android() as m_is_android, \ + patch_android_permissions(m_android_permissions): + m_is_android.return_value = True + m_android_permissions.check_permission.return_value = True + assert xcamera.check_camera_permission() is True + assert m_is_android.mock_calls == [mock.call()] + assert m_android_permissions.method_calls == [ + mock.call.check_permission( + m_android_permissions.Permission.CAMERA), + ] + class TestXCamera: diff --git a/tox.ini b/tox.ini index 2ffc2dc..ffc84d2 100644 --- a/tox.ini +++ b/tox.ini @@ -14,7 +14,7 @@ deps = -r{toxinidir}/requirements.txt -r{toxinidir}/requirements/requirements-test.txt commands = - pytest --cov=src/ tests/ + pytest --cov src/ tests/ [testenv:pep8] commands = flake8 {env:SOURCES} From c5dde90c510ff6acd6de9c26fa55849f141414b6 Mon Sep 17 00:00:00 2001 From: Andre Miras Date: Sat, 28 Sep 2019 23:03:33 +0200 Subject: [PATCH 15/17] Unit tests `check_request_camera_permission()` refs #5 Increases coverage from 88% to 91%. --- tests/kivy_garden/xcamera/test_xcamera.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/kivy_garden/xcamera/test_xcamera.py b/tests/kivy_garden/xcamera/test_xcamera.py index 278aa40..94037e7 100644 --- a/tests/kivy_garden/xcamera/test_xcamera.py +++ b/tests/kivy_garden/xcamera/test_xcamera.py @@ -37,6 +37,10 @@ def patch_android_permissions(m_android_permissions): 'sys.modules', {'android.permissions': m_android_permissions}) +def patch_check_camera_permission(): + return mock.patch('kivy_garden.xcamera.xcamera.check_camera_permission') + + class TestBase: def test_darker(self): @@ -67,6 +71,22 @@ def test_check_camera_permission(self): m_android_permissions.Permission.CAMERA), ] + def test_check_request_camera_permission(self): + """ + Checks if `request_permissions()` is called when + `check_camera_permission()` is `False`. + """ + m_android_permissions = mock.Mock() + callback = mock.Mock() + with patch_check_camera_permission() as m_check_camera_permission, \ + patch_android_permissions(m_android_permissions): + m_check_camera_permission.return_value = False + xcamera.check_request_camera_permission(callback) + assert m_android_permissions.method_calls == [ + mock.call.request_permissions( + [m_android_permissions.Permission.CAMERA], callback), + ] + class TestXCamera: From a30e83ca390cff3da0c786a0c07421598efcb268 Mon Sep 17 00:00:00 2001 From: Andre Miras Date: Sun, 29 Sep 2019 00:30:08 +0200 Subject: [PATCH 16/17] Updates README.md - Install & Usage - Develop & Contribute --- README.md | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 3f41d3d..635e1cf 100644 --- a/README.md +++ b/README.md @@ -36,9 +36,19 @@ Notes: new button to allow the user to manually select the preferred size. Pull requests are welcome :) -## Install +## Install & Usage +[xcamera is available on PyPI](https://pypi.org/project/xcamera/). +Therefore it can be installed via `pip`. ```sh -pip install xcamera +pip3 install --user xcamera +``` +Once installed, the demo should be available in your `PATH` and can be ran from the command line. +```sh +xcamera +``` +And the widget can be imported via: +```python +from kivy_garden.xcamera import XCamera ``` ## Demo @@ -48,7 +58,7 @@ You can run it via: make run ``` -## Contribute +## Develop & Contribute To play with the project, install system dependencies and Python requirements using the [Makefile](Makefile). ```sh make @@ -57,3 +67,16 @@ Then verify everything is OK by running tests. ```sh make test ``` +If you're familiar with `Docker`, the project can also run in a fully isolated container. +First build the image. +```sh +make docker/build +``` +Then you can run tests within the container. +```sh +make docker/run/test +``` +Or the application itself. +```sh +make docker/run/app +``` From 15477f5c9bd82bbc29d3624ca3dd36b6ad8afa05 Mon Sep 17 00:00:00 2001 From: Andre Miras Date: Sun, 29 Sep 2019 00:40:52 +0200 Subject: [PATCH 17/17] 2019.0928 --- CHANGELOG.md | 2 +- src/kivy_garden/xcamera/version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b819ad..880838a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Change Log -## [Unreleased] +## [2019.0928] - Setup coverage testing - Runtime camera permission check, refs #5 diff --git a/src/kivy_garden/xcamera/version.py b/src/kivy_garden/xcamera/version.py index 76f8958..c33baeb 100644 --- a/src/kivy_garden/xcamera/version.py +++ b/src/kivy_garden/xcamera/version.py @@ -1 +1 @@ -__version__ = '2019.0911' +__version__ = '2019.0928'