From e35dd28de9404b7b846e721fa95d77dc7e050092 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Arnauts?= Date: Thu, 26 Mar 2020 11:31:28 +0100 Subject: [PATCH] Improve CI tests (#14) --- .gitattributes | 10 ++++ .github/workflows/addon-check.yml | 41 +++++++++++++ .github/workflows/ci.yml | 58 +++++++++--------- Makefile | 7 +-- README.md | 4 +- requirements.txt | 2 +- resources/lib/viervijfzes/auth.py | 4 +- resources/lib/viervijfzes/auth_awsidp.py | 3 - resources/lib/viervijfzes/content.py | 61 +++---------------- test/run.py | 40 +++++++++++++ test/userdata/addon_settings.json | 3 +- test/xbmc.py | 76 ++++++++++++------------ 12 files changed, 173 insertions(+), 136 deletions(-) create mode 100644 .gitattributes create mode 100644 .github/workflows/addon-check.yml create mode 100755 test/run.py diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..405dfb6 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,10 @@ +.github/ export-ignore +test/ export-ignore +.coverage export-ignore +.gitattributes export-ignore +.gitignore export-ignore +.pylintrc export-ignore +codecov.yml export-ignore +Makefile export-ignore +requirements.txt export-ignore +tox.ini export-ignore diff --git a/.github/workflows/addon-check.yml b/.github/workflows/addon-check.yml new file mode 100644 index 0000000..efec0fb --- /dev/null +++ b/.github/workflows/addon-check.yml @@ -0,0 +1,41 @@ +name: Kodi + +on: + push: + branches: + - master + pull_request: + branches: + - master + +jobs: + tests: + name: Kodi Add-on checker + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + kodi-branch: [leia, matrix] + steps: + - name: Check out ${{ github.sha }} from repository ${{ github.repository }} + uses: actions/checkout@v2 + with: + path: ${{ github.repository }} + - name: Set up Python 3.8 + uses: actions/setup-python@v1 + with: + python-version: 3.8 + - name: Install dependencies + run: | + sudo apt-get install libxml2-utils xmlstarlet + python -m pip install --upgrade pip + pip install kodi-addon-checker + - name: Remove unwanted files + run: awk '/export-ignore/ { print $1 }' .gitattributes | xargs rm -rf -- + working-directory: ${{ github.repository }} + - name: Rewrite addon.xml for Matrix + run: xmlstarlet ed -L -u '/addon/requires/import[@addon="xbmc.python"]/@version' -v "3.0.0" addon.xml + working-directory: ${{ github.repository }} + if: matrix.kodi-branch == 'matrix' + - name: Run kodi-addon-checker + run: kodi-addon-checker --branch=${{ matrix.kodi-branch }} ${{ github.repository }}/ diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 98c2abf..66bfeaf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,41 +2,26 @@ name: CI on: push: - branches: [ master ] + branches: + - master pull_request: - branches: [ master ] + branches: + - master jobs: - - check-addon: - name: Run kodi-addon-checker - runs-on: ubuntu-latest - strategy: - matrix: - python-version: [3.8] - steps: - - uses: actions/checkout@v2 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v1 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - sudo apt-get install libxml2-utils - python -m pip install --upgrade pip - pip install kodi-addon-checker - - name: Run kodi-addon-checker - run: | - make check-addon - tests: - name: Run unit tests + name: Unit tests runs-on: ubuntu-latest + env: + PYTHONIOENCODING: utf-8 + PYTHONPATH: ${{ github.workspace }}/resources/lib:${{ github.workspace }}/test strategy: + fail-fast: false matrix: python-version: [2.7, 3.5, 3.6, 3.7, 3.8] steps: - - uses: actions/checkout@v2 + - name: Check out ${{ github.sha }} from repository ${{ github.repository }} + uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v1 with: @@ -44,19 +29,30 @@ jobs: - name: Install dependencies run: | sudo apt-get install gettext + sudo pip install coverage --install-option="--install-scripts=/usr/bin" python -m pip install --upgrade pip pip install -r requirements.txt - - name: Run checks + - name: Run pylint run: | make check-pylint + - name: Run tox + run: | make check-tox + - name: Check translations + run: | make check-translations - - name: Run tests + - name: Run unit tests env: - PYTHONIOENCODING: utf-8 ADDON_USERNAME: ${{ secrets.ADDON_USERNAME }} ADDON_PASSWORD: ${{ secrets.ADDON_PASSWORD }} run: | - make test - - name: Upload code coverage + coverage run -m unittest discover + - name: Run addon + run: | + coverage run -a test/run.py / + - name: Run add-on service + run: | + coverage run -a service_entry.py + - name: Upload code coverage to CodeCov uses: codecov/codecov-action@v1 + continue-on-error: true \ No newline at end of file diff --git a/Makefile b/Makefile index 3535dca..6b8f0e5 100644 --- a/Makefile +++ b/Makefile @@ -45,12 +45,7 @@ test: test-unit test-unit: @echo ">>> Running unit tests" -ifdef GITHUB_ACTIONS - @coverage run -m unittest discover - @coverage xml -else - @$(PYTHON) -m unittest discover -v -b -f -endif + @$(PYTHON) -m unittest discover -v -b -f clean: @find . -name '*.pyc' -type f -delete diff --git a/README.md b/README.md index 3b98ea0..c4d719f 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,9 @@ [![License: GPLv3](https://img.shields.io/badge/License-GPLv3-yellow.svg)](https://opensource.org/licenses/GPL-3.0) [![Contributors](https://img.shields.io/github/contributors/add-ons/plugin.video.viervijfzes.svg)](https://github.com/add-ons/plugin.video.viervijfzes/graphs/contributors) -# VIER / VIJF / ZES Kodi add-on +# VIER VIJF ZES Kodi add-on -*plugin.video.viervijfzes* is een Kodi add-on om de video-on-demand content van [vier.be](https://vier.be/), [vijf.be](https://vijf.be/) en [zestv.be](https://zestv.be/) te bekijken. +*plugin.video.viervijfzes* is een Kodi add-on om de video-on-demand content van [vier.be](https://www.vier.be/), [vijf.be](https://www.vijf.be/) en [zestv.be](https://www.zestv.be/) te bekijken. > Note: Je moet eerst een account aanmaken op één van bovenstaande websites. diff --git a/requirements.txt b/requirements.txt index 0a5a135..09d61b6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -codecov +coverage git+git://github.com/emilsvennesson/script.module.inputstreamhelper.git@master#egg=inputstreamhelper polib pylint diff --git a/resources/lib/viervijfzes/auth.py b/resources/lib/viervijfzes/auth.py index 7341fe8..5c96e58 100644 --- a/resources/lib/viervijfzes/auth.py +++ b/resources/lib/viervijfzes/auth.py @@ -46,7 +46,7 @@ class AuthApi: if self._id_token and self._expiry > now: # We have a valid id token in memory, use it - _LOGGER.debug('Got an id token from memory: %s', self._id_token) + _LOGGER.debug('Got an id token from memory') return self._id_token if self._refresh_token: @@ -56,7 +56,6 @@ class AuthApi: try: self._id_token = self._refresh(self._refresh_token) self._expiry = now + 3600 - _LOGGER.debug('Got an id token by refreshing: %s', self._id_token) except (InvalidLoginException, AuthenticationException) as exc: _LOGGER.error('Error logging in: %s', str(exc)) self._id_token = None @@ -71,7 +70,6 @@ class AuthApi: self._id_token = id_token self._refresh_token = refresh_token self._expiry = now + 3600 - _LOGGER.debug('Got an id token by logging in: %s', self._id_token) # Store new tokens in cache if not kodiutils.exists(self._cache_dir): diff --git a/resources/lib/viervijfzes/auth_awsidp.py b/resources/lib/viervijfzes/auth_awsidp.py index eb5fd2e..001ff65 100644 --- a/resources/lib/viervijfzes/auth_awsidp.py +++ b/resources/lib/viervijfzes/auth_awsidp.py @@ -90,7 +90,6 @@ class AwsIdp: auth_response = self._session.post(self.url, auth_data, headers=auth_headers) auth_response_json = json.loads(auth_response.text) challenge_parameters = auth_response_json.get("ChallengeParameters") - _LOGGER.debug(challenge_parameters) challenge_name = auth_response_json.get("ChallengeName") if not challenge_name == "PASSWORD_VERIFIER": @@ -105,7 +104,6 @@ class AwsIdp: } auth_response = self._session.post(self.url, challenge_data, headers=challenge_headers) auth_response_json = json.loads(auth_response.text) - _LOGGER.debug("Got response: %s", auth_response_json) if "message" in auth_response_json: raise InvalidLoginException(auth_response_json.get("message")) @@ -348,7 +346,6 @@ class AwsIdp: format_string = "{} {} %-d %H:%M:%S UTC %Y".format(days[time_now.weekday()], months[time_now.month]) time_string = datetime.datetime.utcnow().strftime(format_string) - _LOGGER.debug("AWS Auth Timestamp: %s", time_string) return time_string def __str__(self): diff --git a/resources/lib/viervijfzes/content.py b/resources/lib/viervijfzes/content.py index 596146e..6a5a0ba 100644 --- a/resources/lib/viervijfzes/content.py +++ b/resources/lib/viervijfzes/content.py @@ -146,58 +146,6 @@ class ContentApi: self._session = requests.session() self._auth = auth - def get_notifications(self): - """ Get a list of notifications for your account. - :rtype list[dict] - """ - response = self._get_url(self.API_ENDPOINT + '/notifications', authentication=True) - data = json.loads(response) - return data - - def get_content_tree(self, channel): - """ Get a list of all the content. - :type channel: str - :rtype list[dict] - """ - if channel not in self.SITE_APIS: - raise Exception('Unknown channel %s' % channel) - - response = self._get_url(self.SITE_APIS[channel] + '/content_tree', authentication=True) - data = json.loads(response) - return data - - def get_stream_by_uuid(self, uuid): - """ Get the stream URL to use for this video. - :type uuid: str - :rtype str - """ - response = self._get_url(self.API_ENDPOINT + '/content/%s' % uuid, authentication=True) - data = json.loads(response) - return data['video']['S'] - - def get_programs_new(self, channel): - """ Get a list of all programs of the specified channel. - :type channel: str - :rtype list[Program] - """ - if channel not in CHANNELS: - raise Exception('Unknown channel %s' % channel) - - # Request all content from this channel - content_tree = self.get_content_tree(channel) - - programs = [] - for uuid in content_tree['programs']: - try: - program = self.get_program_by_uuid(uuid) - program.channel = channel - programs.append(program) - except UnavailableException: - # Some programs are not available, but do occur in the content tree - pass - - return programs - def get_programs(self, channel): """ Get a list of all programs of the specified channel. :type channel: str @@ -332,6 +280,15 @@ class ContentApi: return None + def get_stream_by_uuid(self, uuid): + """ Get the stream URL to use for this video. + :type uuid: str + :rtype str + """ + response = self._get_url(self.API_ENDPOINT + '/content/%s' % uuid, authentication=True) + data = json.loads(response) + return data['video']['S'] + @staticmethod def _parse_program_data(data): """ Parse the Program JSON. diff --git a/test/run.py b/test/run.py new file mode 100755 index 0000000..a773d32 --- /dev/null +++ b/test/run.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, Dag Wieers (@dagwieers) +# GNU General Public License v3.0 (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" Run any Kodi VTM GO plugin:// URL on the commandline """ + +# pylint: disable=invalid-name + +from __future__ import absolute_import, division, print_function, unicode_literals +import os +import sys + +# Add current working directory to import paths +cwd = os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(os.path.realpath(__file__))), os.pardir)) +sys.path.insert(0, cwd) +from resources.lib import addon # noqa: E402 pylint: disable=wrong-import-position + +xbmc = __import__('xbmc') +xbmcaddon = __import__('xbmcaddon') +xbmcgui = __import__('xbmcgui') +xbmcplugin = __import__('xbmcplugin') +xbmcvfs = __import__('xbmcvfs') + +if len(sys.argv) <= 1: + print("%s: URI argument missing\nTry '%s plugin://plugin.video.viervijfzes/' to test." % (sys.argv[0], sys.argv[0])) + sys.exit(1) + +# Also support bare paths like /recent/2 +if not sys.argv[1].startswith('plugin://'): + sys.argv[1] = 'plugin://plugin.video.viervijfzes' + sys.argv[1] + +# Split path and args +try: + path, args = sys.argv[1].split('?', 1) +except ValueError: + path, args = sys.argv[1], '' + +print('** Running URI %s with args %s' % (path, args)) +plugin = addon.routing +plugin.run([path, 0, args]) diff --git a/test/userdata/addon_settings.json b/test/userdata/addon_settings.json index a1a1a3e..01694d7 100644 --- a/test/userdata/addon_settings.json +++ b/test/userdata/addon_settings.json @@ -1,6 +1,7 @@ { "plugin.video.viervijfzes": { - "_comment": "do-not-add-username-and-password-here" + "_comment": "do-not-add-username-and-password-here", + "metadata_update": "true" }, "plugin.video.youtube": { }, diff --git a/test/xbmc.py b/test/xbmc.py index 1f1efdb..1ec7252 100644 --- a/test/xbmc.py +++ b/test/xbmc.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # Copyright: (c) 2019, Dag Wieers (@dagwieers) # GNU General Public License v3.0 (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) -''' This file implements the Kodi xbmc module, either using stubs or alternative functionality ''' +""" This file implements the Kodi xbmc module, either using stubs or alternative functionality """ # pylint: disable=invalid-name,no-self-use,unused-argument @@ -60,113 +60,115 @@ def from_unicode(text, encoding='utf-8'): class Keyboard: - ''' A stub implementation of the xbmc Keyboard class ''' + """ A stub implementation of the xbmc Keyboard class """ def __init__(self, line='', heading=''): - ''' A stub constructor for the xbmc Keyboard class ''' + """ A stub constructor for the xbmc Keyboard class """ def doModal(self, autoclose=0): - ''' A stub implementation for the xbmc Keyboard class doModal() method ''' + """ A stub implementation for the xbmc Keyboard class doModal() method """ def isConfirmed(self): - ''' A stub implementation for the xbmc Keyboard class isConfirmed() method ''' + """ A stub implementation for the xbmc Keyboard class isConfirmed() method """ return True def getText(self): - ''' A stub implementation for the xbmc Keyboard class getText() method ''' + """ A stub implementation for the xbmc Keyboard class getText() method """ return 'test' class Monitor: - ''' A stub implementation of the xbmc Monitor class ''' + """A stub implementation of the xbmc Monitor class""" def __init__(self, line='', heading=''): - ''' A stub constructor for the xbmc Monitor class ''' + """A stub constructor for the xbmc Monitor class""" + self._deadline = time.time() + 10 # 10 seconds def abortRequested(self): - ''' A stub implementation for the xbmc Keyboard class abortRequested() method ''' - return False + """A stub implementation for the xbmc Keyboard class abortRequested() method""" + return time.time() > self._deadline def waitForAbort(self, timeout=None): - ''' A stub implementation for the xbmc Keyboard class waitForAbort() method ''' + """A stub implementation for the xbmc Keyboard class waitForAbort() method""" + time.sleep(0.5) return False class Player: - ''' A stub implementation of the xbmc Player class ''' + """ A stub implementation of the xbmc Player class """ def __init__(self): self._count = 0 def play(self, item='', listitem=None, windowed=False, startpos=-1): - ''' A stub implementation for the xbmc Player class play() method ''' + """ A stub implementation for the xbmc Player class play() method """ return def isPlaying(self): - ''' A stub implementation for the xbmc Player class isPlaying() method ''' + """ A stub implementation for the xbmc Player class isPlaying() method """ # Return True four times out of five self._count += 1 return bool(self._count % 5 != 0) def setSubtitles(self, subtitleFile): - ''' A stub implementation for the xbmc Player class setSubtitles() method ''' + """ A stub implementation for the xbmc Player class setSubtitles() method """ return def showSubtitles(self, visible): - ''' A stub implementation for the xbmc Player class showSubtitles() method ''' + """ A stub implementation for the xbmc Player class showSubtitles() method """ return def getTotalTime(self): - ''' A stub implementation for the xbmc Player class getTotalTime() method ''' + """ A stub implementation for the xbmc Player class getTotalTime() method """ return 0 def getTime(self): - ''' A stub implementation for the xbmc Player class getTime() method ''' + """ A stub implementation for the xbmc Player class getTime() method """ return 0 def getVideoInfoTag(self): - ''' A stub implementation for the xbmc Player class getVideoInfoTag() method ''' + """ A stub implementation for the xbmc Player class getVideoInfoTag() method """ return VideoInfoTag() def getPlayingFile(self): - ''' A stub implementation for the xbmc Player class getPlayingFile() method ''' + """ A stub implementation for the xbmc Player class getPlayingFile() method """ return '' class VideoInfoTag: - ''' A stub implementation of the xbmc VideoInfoTag class ''' + """ A stub implementation of the xbmc VideoInfoTag class """ def __init__(self): - ''' A stub constructor for the xbmc VideoInfoTag class ''' + """ A stub constructor for the xbmc VideoInfoTag class """ def getSeason(self): - ''' A stub implementation for the xbmc VideoInfoTag class getSeason() method ''' + """ A stub implementation for the xbmc VideoInfoTag class getSeason() method """ return 0 def getEpisode(self): - ''' A stub implementation for the xbmc VideoInfoTag class getEpisode() method ''' + """ A stub implementation for the xbmc VideoInfoTag class getEpisode() method """ return 0 def getTVShowTitle(self): - ''' A stub implementation for the xbmc VideoInfoTag class getTVShowTitle() method ''' + """ A stub implementation for the xbmc VideoInfoTag class getTVShowTitle() method """ return '' def getPlayCount(self): - ''' A stub implementation for the xbmc VideoInfoTag class getPlayCount() method ''' + """ A stub implementation for the xbmc VideoInfoTag class getPlayCount() method """ return 0 def getRating(self): - ''' A stub implementation for the xbmc VideoInfoTag class getRating() method ''' + """ A stub implementation for the xbmc VideoInfoTag class getRating() method """ return 0 def executebuiltin(string, wait=False): # pylint: disable=unused-argument - ''' A stub implementation of the xbmc executebuiltin() function ''' + """ A stub implementation of the xbmc executebuiltin() function """ return def executeJSONRPC(jsonrpccommand): - ''' A reimplementation of the xbmc executeJSONRPC() function ''' + """ A reimplementation of the xbmc executeJSONRPC() function """ command = json.loads(jsonrpccommand) if command.get('method') == 'Settings.GetSettingValue': key = command.get('params').get('setting') @@ -184,19 +186,19 @@ def executeJSONRPC(jsonrpccommand): def getCondVisibility(string): - ''' A reimplementation of the xbmc getCondVisibility() function ''' + """ A reimplementation of the xbmc getCondVisibility() function """ if string == 'system.platform.android': return False return True def getInfoLabel(key): - ''' A reimplementation of the xbmc getInfoLabel() function ''' + """ A reimplementation of the xbmc getInfoLabel() function """ return INFO_LABELS.get(key) def getLocalizedString(msgctxt): - ''' A reimplementation of the xbmc getLocalizedString() function ''' + """ A reimplementation of the xbmc getLocalizedString() function """ for entry in PO: if entry.msgctxt == '#%s' % msgctxt: return entry.msgstr or entry.msgid @@ -206,12 +208,12 @@ def getLocalizedString(msgctxt): def getRegion(key): - ''' A reimplementation of the xbmc getRegion() function ''' + """ A reimplementation of the xbmc getRegion() function """ return REGIONS.get(key) def log(msg, level=LOGINFO): - ''' A reimplementation of the xbmc log() function ''' + """ A reimplementation of the xbmc log() function """ if level in (LOGERROR, LOGFATAL): print('\033[31;1m%s: \033[32;0m%s\033[0;39m' % (LOG_MAPPING.get(level), to_unicode(msg))) if level == LOGFATAL: @@ -223,17 +225,17 @@ def log(msg, level=LOGINFO): def setContent(self, content): - ''' A stub implementation of the xbmc setContent() function ''' + """ A stub implementation of the xbmc setContent() function """ return def sleep(seconds): - ''' A reimplementation of the xbmc sleep() function ''' + """ A reimplementation of the xbmc sleep() function """ time.sleep(seconds) def translatePath(path): - ''' A stub implementation of the xbmc translatePath() function ''' + """ A stub implementation of the xbmc translatePath() function """ if path.startswith('special://home'): return path.replace('special://home', os.path.join(os.getcwd(), 'test/')) if path.startswith('special://masterprofile'):