diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..73393b4 --- /dev/null +++ b/.flake8 @@ -0,0 +1,5 @@ +[flake8] +ignore = E203 +exclude = .git,__pycache__,docs/source/conf.py,old,build,dist,systemtest-library/.eggs,venv +max-line-length = 130 + diff --git a/.github/workflows/installation-test.yml b/.github/workflows/installation-test.yml new file mode 100644 index 0000000..fb18552 --- /dev/null +++ b/.github/workflows/installation-test.yml @@ -0,0 +1,39 @@ +name: Installation test +on: + push: + paths: + - 'requirements.txt' + - 'pytest.ini' + - '.github/workflows/installation-test.yml' + +# This allows a subsequently queued workflow run to interrupt previous runs +concurrency: + group: e2e-'${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}' + cancel-in-progress: true + +jobs: + python-install: + strategy: + matrix: + os: ["ubuntu-20.04", "ubuntu-22.04"] + python-ver: ["3.7", "3.8", "3.9", "3.10", "3.11"] + exclude: + - os: ubuntu-22.04 + python-ver: 3.6 + runs-on: ${{ matrix.os }} + steps: + - name: Check out code + uses: actions/checkout@v3 + - name: Install Python ${{ matrix.python-ver}} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-ver}} + - name: Display Python version + run: python -c "import sys; print(sys.version)" + - name: Installation steps on ${{ matrix.os }} w ${{ matrix.python-ver}} + run: | + pip install --upgrade pip + pip install --upgrade cython + pip install -U -r requirements.txt + - name: Run collect only + run: pytest --collect-only diff --git a/.github/workflows/pr-checker.yml b/.github/workflows/pr-checker.yml new file mode 100644 index 0000000..8764c0e --- /dev/null +++ b/.github/workflows/pr-checker.yml @@ -0,0 +1,98 @@ +name: PR-checker +on: push + +# This allows a subsequently queued workflow run to interrupt previous runs +concurrency: + group: izuma-system-tests-'${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}' + cancel-in-progress: true + +jobs: + static-checks: + runs-on: ubuntu-latest + env: + SUMMARY_FILE: summary.log + steps: + - name: Check out code + uses: actions/checkout@v4 + - name: Install Python + uses: actions/setup-python@v4 + with: + python-version: '3.9' + + - name: Clone scripts-internal + uses: actions/checkout@v4 + with: + repository: PelionIoT/scripts-internal + token: ${{ secrets.ACCESS_TOKEN }} + + # Need to run this 1st, so that the other log files do not cause unnecessary findings + - name: Run misspell + if: always() + run: | + curl -L -o ./install-misspell.sh https://git.io/misspell + sh ./install-misspell.sh + bin/misspell -i mosquitto . >misspell.log + echo "## Summary" >>$SUMMARY_FILE + echo "### misspell" >>$SUMMARY_FILE + cat misspell.log >>$SUMMARY_FILE + bin/misspell -error -i mosquitto . + lines=$(wc -l < "misspell.log") + if [[ $lines -gt 0 ]]; then + echo "Misspell has findings, fail." + echo "TEST_FAIL=true" >> $GITHUB_ENV + exit 1 + else + echo "No findings." >>$SUMMARY_FILE + fi + + - name: Run pylint (findings may not increase) + run: | + sudo apt-get install pylint + pylint --version + pylint --exit-zero --persistent=n tests/ izuma_systest_lib/ >pylint.log + echo "### pylint" >>$SUMMARY_FILE + pylintstats=$(scripts-internal/ci/more-lines-checker.sh ${{ github.event.repository.default_branch }} ${{ github.ref_name }} "pylint --exit-zero --persistent=n systemtest-library test_cases" | tail -n2) + echo "$pylintstats" >>$SUMMARY_FILE + pylintscore=$(tail -2 pylint.log |head -1) + echo "$pylintscore" >>$SUMMARY_FILE + echo "$pylintstats" + if [[ $pylintstats == *"Oh no"* ]]; then + # More findings than earlier + echo "TEST_FAIL=true" >> $GITHUB_ENV + exit 1 + fi + - name: Run flake8 + if: always() + run: | + sudo apt-get install flake8 + flake8 --version + echo "Starting flake8..." + flake8 --exit-zero --exclude=izuma_systest_lib/.eggs,scripts-internal --max-line-length=130 >flake8.log + echo "### flake8" >>$SUMMARY_FILE + lines=$(wc -l < "flake8.log") + if [[ $lines -gt 0 ]]; then + echo "Flake8 has findings, fail." + echo "TEST_FAIL=true" >> $GITHUB_ENV + exit 1 + else + echo "No findings." >>$SUMMARY_FILE + fi + + - name: Archive production artifacts + if: always() + uses: actions/upload-artifact@v3 + with: + name: Findings logs + path: | + *.log + - name: Summary + if: always() + run: cat $SUMMARY_FILE >>$GITHUB_STEP_SUMMARY + + - name: Set whole job status based on found fails + if: always() + run: | + if [ "$TEST_FAIL" = "true" ]; then + echo "Some test has failed, fail the job." + exit 1 # You can choose to exit with success (0) to mark this step as successful but skipped. + fi diff --git a/changelog.md b/changelog.md index d6eec20..e16b4ac 100644 --- a/changelog.md +++ b/changelog.md @@ -1,10 +1,15 @@ # Izuma E2E Edge Python Test Suite Change log +## 1.2.2 +- Fixed misspellings. +- Updated `aiohttp` to 3.8.6 and `requests` to 2.31.0. +- Minor `pylint` fixes. + ## 1.2.1 - Add marker `cpu_notif_test` to CPU resource notification test. This allows you to skip that test with `pytest -m "not cpu_notif_test"`. You can skip that and reboot test with `-m 'not (reboot_test or cpu_notif_test)'`. - Add marker `reboot_test` to LwM2M test that resets device. This allows you to skip that test with `pytest -m "not reboot_test"`. - Move `pytest.ini` from `tests`-folder to root, this way to the custom marker gets taken into account. -- Upgrade `pytest` to 7.3.1 and `pytest-html` 3.2.0 to resolve Python 3.10 compatiblity issue. +- Upgrade `pytest` to 7.3.1 and `pytest-html` 3.2.0 to resolve Python 3.10 compatibility issue. - Add *.xml files to `.gitignore`. ## 1.2.0 diff --git a/izuma_systest_lib/cloud/libraries/config_management.py b/izuma_systest_lib/cloud/libraries/config_management.py index 10970fe..3d2263f 100644 --- a/izuma_systest_lib/cloud/libraries/config_management.py +++ b/izuma_systest_lib/cloud/libraries/config_management.py @@ -24,7 +24,7 @@ class EdgeConfigManagementAPI: """ - A class that provides edge config managment. + A class that provides edge config management. https://github.com/PelionIoT/wigwag-cloud-relay-configs/blob/master/swagger.yaml """ diff --git a/izuma_systest_lib/edge/remote_terminal.py b/izuma_systest_lib/edge/remote_terminal.py index 35503fc..5dc4a32 100644 --- a/izuma_systest_lib/edge/remote_terminal.py +++ b/izuma_systest_lib/edge/remote_terminal.py @@ -35,7 +35,7 @@ def __init__(self, api_key, url): :param url: Remote terminal url """ # Create loop explicitly, Python 3.10 warns, 3.11 stops working.. - if self.loop == None: + if self.loop is None: self.loop = asyncio.new_event_loop() asyncio.set_event_loop(self.loop) log.debug(f"RemoteTerminal __init__ self.loop created {self.loop}") diff --git a/izuma_systest_lib/fixtures/edge_fixtures.py b/izuma_systest_lib/fixtures/edge_fixtures.py index e7ee774..67d1b9b 100644 --- a/izuma_systest_lib/fixtures/edge_fixtures.py +++ b/izuma_systest_lib/fixtures/edge_fixtures.py @@ -66,7 +66,7 @@ def kube_config_file_path(tc_config_data): utils.execute_local_command('kubectl config use-context edge-k8s') log.debug('Temporary kubernetes configuration successfully created and in use') else: - log.debug('No need to create temporary kubernetes konfiguration') + log.debug('No need to create temporary kubernetes configuration') yield kube_config_path # Remove temporary configuration when running outside Jenkins @@ -80,7 +80,7 @@ def edge(kube_config_file_path, tc_config_data): """ Initializes connection to edge via RaaS or locally (subprocess) based on configuration. Release connection when not needed anymore - :param kube_config_file_path: Kubernetes konfiguration file path + :param kube_config_file_path: Kubernetes configuration file path :param tc_config_data: Test case config :return: resource """ @@ -106,7 +106,7 @@ def kubectl(edge): # pylint: disable=unused-argument def kaas(kube_config_file_path): """ Open connection to the kubernets as a service (KAAS) using kubernets python sdk - :param kube_config_file_path: Kubernetes konfiguration file path + :param kube_config_file_path: Kubernetes configuration file path :return: api_client """ custom_configuration = Configuration() diff --git a/izuma_systest_lib/tools.py b/izuma_systest_lib/tools.py index 587f266..7d8a936 100644 --- a/izuma_systest_lib/tools.py +++ b/izuma_systest_lib/tools.py @@ -100,7 +100,7 @@ def _assert_status(expected_resp, response): Status code comparison :param expected_resp: Expected response, one value or list of values :param response: Request response - :return: Comparision result + :return: Comparison result """ if isinstance(expected_resp, list): return response.status_code in expected_resp diff --git a/pylintrc b/pylintrc new file mode 100644 index 0000000..59b928d --- /dev/null +++ b/pylintrc @@ -0,0 +1,344 @@ +[MASTER] +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Profiled execution. +profile=no + +# Add files or directories to the blacklist. They should be base names, not +# paths. +#ignore= + +# Pickle collected data for later comparisons. +persistent=yes + +# Deprecated. It was used to include message's id in output. Use --msg-template + +# instead. +include-ids=no + +# Deprecated. It was used to include symbolic ids of messages in output. Use +# --msg-template instead. +symbols=no + +# Use multiple processes to speed up Pylint. +jobs=4 + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +unsafe-load-any-extension=no + +# Allow optimization of some AST trees. This will activate a peephole AST +# optimizer, which will apply various small optimizations. For instance, it can +# be used to obtain the result of joining multiple strings with the addition +# operator. Joining a lot of strings can lead to a maximum recursion error in +# Pylint and this flag can prevent that. It has one side effect, the resulting +# AST will be different than the one from reality. +optimize-ast=no + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED +confidence= + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once).You can also use "--disable=all" to +# disable everything first and then reenable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use"--disable=all --enable=classes +# --disable=W" +# Disabled: Suppressed %s (from line %d), Useless suppression of %s, +# Unnecessary parens after %r keyword, consider using f-string +disable=fixme,C0116,C0114,C0115,W1202,E0401,C0111,C0209 + +[REPORTS] + +# Set the output format. Available formats are text, parseable, colorized, msvs +# (visual studio) and html. You can also give a reporter class, eg +# mypackage.mymodule.MyReporterClass. +output-format=parseable + +# Put messages in a separate file for each module / package specified on the +# command line instead of printing them on stdout. Reports (if any) will be +# written in a file name "pylint_global.[txt|html]". +files-output=no + +# Tells whether to display a full report or only the messages +reports=yes + +# Python expression which should return a note less than 10 (10 is the highest +# note). You have access to the variables errors warning, statement which +# respectively contain the number of errors / warnings messages and the total +# number of statements analyzed. This is used by the global evaluation report +# (RP0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Add a comment according to your evaluation note. This is used by the global +# evaluation report (RP0004). +comment=yes + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details +#msg-template= + + +[BASIC] + +# List of builtins function names that should not be used, separated by a comma +bad-functions=map,filter,input + +# Good variable names which should always be accepted, separated by a comma +good-names=i,j,k,r,ex,Run,_,cc,e,ep,rc,ws,ts,te,iq,oq,it,ot,p,f + +# Bad variable names which should always be refused, separated by a comma +bad-names=foo,bar,baz,toto,tutu,tata + +# Include a hint for the correct naming format with invalid-name +include-naming-hint=no + +# Regular expression matching correct function names +function-rgx=[a-z_][a-z0-9_]{2,70}$ + +# Naming hint for function names +function-name-hint=[a-z_][a-z0-9_]{2,60}$ + +# Regular expression matching correct variable names +variable-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming hint for variable names +variable-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression matching correct constant names +const-rgx=(([a-z_][a-z0-9_]*)|([A-Z_][A-Z0-9_]*)|(__.*__))$ + +# Naming hint for constant names +const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$ + +# Regular expression matching correct attribute names +attr-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming hint for attribute names +attr-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression matching correct argument names +argument-rgx=[a-z_][a-z0-9_]{2,70}$ + +# Naming hint for argument names +argument-name-hint=[a-z_][a-z0-9_]{2,60}$ + +# Regular expression matching correct class attribute names +class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ + +# Naming hint for class attribute names +class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ + +# Regular expression matching correct inline iteration names +inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ + +# Naming hint for inline iteration names +inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$ + +# Regular expression matching correct class names +class-rgx=[A-Z_][a-zA-Z0-9]+$ + +# Naming hint for class names +class-name-hint=[A-Z_][a-zA-Z0-9]+$ + +# Regular expression matching correct module names +module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ + +# Naming hint for module names +module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ + +# Regular expression matching correct method names +method-rgx=[a-z_][a-z0-9_]{2,60}$ + +# Naming hint for method names +method-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=__.*__ + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + + +[FORMAT] + +# Maximum number of characters on a single line. +max-line-length=130 + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + +# List of optional constructs for which whitespace checking is disabled +no-space-check=trailing-comma,dict-separator + +# Maximum number of lines in a module +max-module-lines=2000 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + + +[LOGGING] + +# Logging modules to check that the string format arguments are in logging + +# function parameter format +logging-modules=logging + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME,XXX,TODO + + +[SIMILARITIES] + +# Minimum lines number of a similarity. +min-similarity-lines=4 + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes + +# Ignore imports when computing similarities. +ignore-imports=no + + +[SPELLING] +# Tells whether to store unknown words to indicated private dictionary in +# --spelling-private-dict-file option instead of raising a message. +spelling-store-unknown-words=no + + +[TYPECHECK] + +# Tells whether missing members accessed in mixin class should be ignored. A +# mixin class is detected if its name ends with "mixin" (case insensitive). +ignore-mixin-members=yes + +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis +ignored-modules= + +# List of classes names for which member attributes should not be checked +# (useful for classes with attributes dynamically set). +ignored-classes=SQLObject + +# When zope mode is activated, add a predefined set of Zope acquired attributes +# to generated-members. +zope=no + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E0201 when accessed. Python regular +# expressions are accepted. +generated-members=REQUEST,acl_users,aq_parent + + +[VARIABLES] + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# A regular expression matching the name of dummy variables (i.e. expectedly +# not used). +dummy-variables-rgx=_$|dummy + +# List of strings which can identify a callback function by name. A callback + +# name must start or end with one of those strings. +callbacks=cb_,_cb + + +[CLASSES] + +# List of interface methods to ignore, separated by a comma. This is used for +# instance to not check methods defines in Zope's Interface base class. +ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__,__new__,setUp + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=mcs + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict,_fields,_replace,_source,_make + + +[DESIGN] + +# Maximum number of arguments for function / method +max-args=13 + +# Argument names that match this expression will be ignored. Default to name + +# with leading underscore +ignored-argument-names=_.* + +# Maximum number of locals for function / method body +max-locals=30 + +# Maximum number of return / yield for function / method body +max-returns=6 + +# Maximum number of branch for function / method body +max-branches=20 + +# Maximum number of statements in function / method body +max-statements=90 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of attributes for a class (see R0902). +max-attributes=15 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=0 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=40 + + +[IMPORTS] + +# Deprecated modules which should not be used, separated by a comma +deprecated-modules=regsub,TERMIOS,Bastion,rexec + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "Exception" +overgeneral-exceptions=Exception + diff --git a/pytest.ini b/pytest.ini index 0624ffe..5d7d5ca 100644 --- a/pytest.ini +++ b/pytest.ini @@ -14,4 +14,4 @@ junit_family = xunit1 junit_logging = all markers = reboot_test: this test will reboot device (deselect with '-m "not reboot_test"') - cpu_notif_test: CPU temperature notification test (deselect with '-m "not cpu_notif_test"') \ No newline at end of file + cpu_notif_test: CPU temperature notification test (deselect with '-m "not cpu_notif_test"') diff --git a/requirements.txt b/requirements.txt index af369e3..d67bbfa 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ +cython==3.0.4 pytest==7.3.1 pytest-rerunfailures==10.2 pytest-html==3.2.0 @@ -6,6 +7,6 @@ pytest-stress==1.0.1 kubernetes==10.0.1 Jinja2==3.1.2 pyyaml==6.0 -requests==2.28.1 +requests==2.31.0 ws4py==0.5.1 -aiohttp==3.8.1 +aiohttp==3.8.6 diff --git a/tests/test_edge_online.py b/tests/test_edge_online.py index 000012e..4b273d5 100644 --- a/tests/test_edge_online.py +++ b/tests/test_edge_online.py @@ -32,12 +32,8 @@ import logging import time import pytest -import os import json -from izuma_systest_lib.edge.kaas import Kaas -from izuma_systest_lib.edge.kubectl import Kubectl -from izuma_systest_lib.tools import execute_with_retry from izuma_systest_lib.cloud import connect_handler from izuma_systest_lib.tools import execute_local_command @@ -47,7 +43,7 @@ log = logging.getLogger(__name__) -def test_LwM2M_OK(edge, cloud_api, websocket): +def test_lwm2m_ok(edge, cloud_api, websocket): """ Verify if LwM2M connection is OK to devicey by reading epoch time resource. @@ -56,7 +52,6 @@ def test_LwM2M_OK(edge, cloud_api, websocket): :param websocket: websocket for async notifications """ - EPOCH_BASE_DELTA = 0 resource = '/3/0/13' payload = {'method': 'GET', 'uri': resource} resp = connect_handler.send_async_device_and_wait_for_response(cloud_api, @@ -81,7 +76,8 @@ def test_LwM2M_OK(edge, cloud_api, websocket): log.debug("Received /3/0/13 epoch time {epoch_received} 10 s later.") assert epoch_delta >= 10, f"Epoch time delta {epoch_delta} not >= 10 s." -def test_Kube_is_OK(edge): + +def test_kube_is_ok(edge): """ Verify if Edge Kubernetes is in Ready -state. @@ -113,9 +109,10 @@ def test_Kube_is_OK(edge): ) assert False, f"ERROR - can't find device ID {edge.device_id} from kubectl get nodes." -def test_Terminal_OK(edge): + +def test_terminal_ok(edge): """ - Verify if Edge Terminal is responding to echo command. + Verify if Edge terminal is responding to echo command. :param edge: Edge -structure """ @@ -125,20 +122,19 @@ def test_Terminal_OK(edge): assert "terminal_is_alive" == edge.execute_remote_terminal("echo terminal_is_alive"), "Remote terminal not OK." -def test_Edge_Online(edge, cloud_api, websocket): +def test_edge_online(edge, cloud_api, websocket): """ Verify if Edge is online using the 3 tests above. You can run this test in repeat: - pytest --count=10 --config_path=config-rpi3.json tests/test_edge_online.py::test_Edge_Online + pytest --count=10 --config_path=config-rpi3.json tests/test_edge_online.py::test_edge_online :param edge: edge structure with device ID etc. :param cloud_api: cloud API struct :param websocket: websocket for async notifications """ - test_LwM2M_OK(edge, cloud_api, websocket) - test_Terminal_OK(edge) - test_Kube_is_OK(edge) - + test_lwm2m_ok(edge, cloud_api, websocket) + test_terminal_ok(edge) + test_kube_is_ok(edge) diff --git a/tests/test_lwm2m_resources.py b/tests/test_lwm2m_resources.py index 9638525..3baaaa1 100644 --- a/tests/test_lwm2m_resources.py +++ b/tests/test_lwm2m_resources.py @@ -24,9 +24,9 @@ # ---------------------------------------------------------------------------- import logging -import pytest import random import base64 +import pytest from izuma_systest_lib.cloud import connect_handler diff --git a/tests/test_websocket_notifications.py b/tests/test_websocket_notifications.py index 5ebf8f6..8885f12 100644 --- a/tests/test_websocket_notifications.py +++ b/tests/test_websocket_notifications.py @@ -26,11 +26,11 @@ # ---------------------------------------------------------------------------- -import pytest import logging import base64 -from izuma_systest_lib.cloud import connect_handler import time +from izuma_systest_lib.cloud import connect_handler +import pytest log = logging.getLogger(__name__) log.setLevel(logging.INFO) @@ -56,6 +56,7 @@ def test_registration_update_notification(edge, cloud_api, websocket): assert data, 'Registration update not received from websocket notification channel' + # Add marker - this reboot test is not always that useful, so having the marker # Allows you to skip it with pytest -m "not reboot_test" @pytest.mark.reboot_test