From d891e486e7c62ac8c6793b3a959cbf38eacc211a Mon Sep 17 00:00:00 2001 From: Stefan Allius <122395479+s-allius@users.noreply.github.com> Date: Fri, 9 Aug 2024 17:45:53 +0200 Subject: [PATCH] Code coverage for SonarCloud (#150) * cleanup code and unit tests * add test coverage for SonarCloud * configure SonarCloud * update changelog --- .coveragerc | 3 +- .github/workflows/python-app.yml | 22 ++++++++-- .github/workflows/sonarcloud.yml | 65 ------------------------------ .sonarlint/connectedMode.json | 4 ++ .vscode/settings.json | 6 ++- CHANGELOG.md | 1 + app/tests/test_config.py | 5 +-- system_tests/test_tcp_socket.py | 11 +---- system_tests/test_tcp_socket_v2.py | 14 +------ 9 files changed, 36 insertions(+), 95 deletions(-) delete mode 100644 .github/workflows/sonarcloud.yml create mode 100644 .sonarlint/connectedMode.json diff --git a/.coveragerc b/.coveragerc index 6b08179..890dd1b 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,2 +1,3 @@ [run] -branch = True \ No newline at end of file +branch = True +relative_files = True \ No newline at end of file diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index 0817aa5..2719e1c 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -8,7 +8,7 @@ on: branches: [ "main", "dev-*", "*/issue*" ] paths-ignore: - '**.md' # Do no build on *.md changes - - '**.yml' # Do no build on *.yml changes + # - '**.yml' # Do no build on *.yml changes - '**.yaml' # Do no build on *.yaml changes - '**.yuml' # Do no build on *.yuml changes - '**.svg' # Do no build on *.svg changes @@ -18,10 +18,11 @@ on: - '**.dockerfile' # Do no build on *.dockerfile changes - '**.sh' # Do no build on *.sh changes pull_request: - branches: [ "main" ] + branches: [ "main", "dev-*" ] permissions: contents: read + pull-requests: read # allows SonarCloud to decorate PRs with analysis results jobs: build: @@ -53,4 +54,19 @@ jobs: flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics - name: Test with pytest run: | - python -m pytest app + pip install pytest pytest-cov + #pytest app --doctest-modules --junitxml=junit/test-results.xml --cov=com --cov-report=xml --cov-report=html + python -m pytest app --cov=app/src --cov-report=xml + - name: Analyze with SonarCloud + uses: SonarSource/sonarcloud-github-action@v2.2.0 + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + with: + projectBaseDir: . + args: + -Dsonar.projectKey=s-allius_tsun-gen3-proxy + -Dsonar.organization=s-allius + -Dsonar.python.version=3.12 + -Dsonar.python.coverage.reportPaths=coverage.xml + -Dsonar.tests=system_tests,app/tests + -Dsonar.source=app/src diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml deleted file mode 100644 index 54d5379..0000000 --- a/.github/workflows/sonarcloud.yml +++ /dev/null @@ -1,65 +0,0 @@ -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. - -# This workflow helps you trigger a SonarCloud analysis of your code and populates -# GitHub Code Scanning alerts with the vulnerabilities found. -# Free for open source project. - -# 1. Login to SonarCloud.io using your GitHub account - -# 2. Import your project on SonarCloud -# * Add your GitHub organization first, then add your repository as a new project. -# * Please note that many languages are eligible for automatic analysis, -# which means that the analysis will start automatically without the need to set up GitHub Actions. -# * This behavior can be changed in Administration > Analysis Method. -# -# 3. Follow the SonarCloud in-product tutorial -# * a. Copy/paste the Project Key and the Organization Key into the args parameter below -# (You'll find this information in SonarCloud. Click on "Information" at the bottom left) -# -# * b. Generate a new token and add it to your Github repository's secrets using the name SONAR_TOKEN -# (On SonarCloud, click on your avatar on top-right > My account > Security -# or go directly to https://sonarcloud.io/account/security/) - -# Feel free to take a look at our documentation (https://docs.sonarcloud.io/getting-started/github/) -# or reach out to our community forum if you need some help (https://community.sonarsource.com/c/help/sc/9) - -name: SonarCloud analysis - -on: - push: - branches: [ "main", "dev-*" ] - pull_request: - branches: [ "main", "dev-*" ] - workflow_dispatch: - -permissions: - pull-requests: read # allows SonarCloud to decorate PRs with analysis results - -jobs: - Analysis: - runs-on: ubuntu-latest - - steps: - - name: Analyze with SonarCloud - - # You can pin the exact commit or the version. - # uses: SonarSource/sonarcloud-github-action@v2.2.0 - uses: SonarSource/sonarcloud-github-action@4006f663ecaf1f8093e8e4abb9227f6041f52216 - env: - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} # Generate a token on Sonarcloud.io, add it to the secrets of this repo with the name SONAR_TOKEN (Settings > Secrets > Actions > add new repository secret) - with: - # Additional arguments for the SonarScanner CLI - args: - # Unique keys of your project and organization. You can find them in SonarCloud > Information (bottom-left menu) - # mandatory - -Dsonar.projectKey=s-allius_tsun-gen3-proxy - -Dsonar.organization=s-allius - # -Dsonar.sources=tsun-gen3-proxy/app/src - # -Dsonar.tests=tsun-gen3-proxy/app/tests,tsun-gen3-proxy/system_tests - # Adds more detail to both client and server-side analysis logs, activating DEBUG mode for the scanner, and adding client-side environment variables and system properties to the server-side log of analysis report processing. - #-Dsonar.verbose= # optional, default is false - # When you need the analysis to take place in a directory other than the one from which it was launched, default is . - projectBaseDir: . diff --git a/.sonarlint/connectedMode.json b/.sonarlint/connectedMode.json new file mode 100644 index 0000000..4a9cc82 --- /dev/null +++ b/.sonarlint/connectedMode.json @@ -0,0 +1,4 @@ +{ + "sonarCloudOrganization": "s-allius", + "projectKey": "s-allius_tsun-gen3-proxy" +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 42fb2c2..dd2d0cf 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -11,5 +11,9 @@ "python.testing.pytestEnabled": true, "flake8.args": [ "--extend-exclude=app/tests/*.py system_tests/*.py" - ] + ], + "sonarlint.connectedMode.project": { + "connectionId": "s-allius", + "projectKey": "s-allius_tsun-gen3-proxy" + } } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index d79d606..4b10f0d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [unreleased] +- add SonarQube and code coverage support - don't send MODBUS request when state is note up; adapt timeouts [#141](https://github.com/s-allius/tsun-gen3-proxy/issues/141) - build multi arch images with sboms [#144](https://github.com/s-allius/tsun-gen3-proxy/issues/144) - add timestamp to MQTT topics [#138](https://github.com/s-allius/tsun-gen3-proxy/issues/138) diff --git a/app/tests/test_config.py b/app/tests/test_config.py index 6465c83..260f965 100644 --- a/app/tests/test_config.py +++ b/app/tests/test_config.py @@ -20,7 +20,7 @@ def test_empty_config(): Config.conf_schema.validate(cnf) assert False except SchemaMissingKeyError: - assert True + pass def test_default_config(): with open("app/config/default_config.toml", "rb") as f: @@ -28,7 +28,6 @@ def test_default_config(): try: validated = Config.conf_schema.validate(cnf) - assert True except: assert False assert validated == {'gen3plus': {'at_acl': {'mqtt': {'allow': ['AT+'], 'block': []}, 'tsun': {'allow': ['AT+Z', 'AT+UPURL', 'AT+SUPDATE'], 'block': []}}}, 'tsun': {'enabled': True, 'host': 'logger.talent-monitoring.com', 'port': 5005}, 'solarman': {'enabled': True, 'host': 'iot.talent-monitoring.com', 'port': 10000}, 'mqtt': {'host': 'mqtt', 'port': 1883, 'user': None, 'passwd': None}, 'ha': {'auto_conf_prefix': 'homeassistant', 'discovery_prefix': 'homeassistant', 'entity_prefix': 'tsun', 'proxy_node_id': 'proxy', 'proxy_unique_id': 'P170000000000001'}, 'inverters': {'allow_all': True, 'R170000000000001': {'node_id': '', 'modbus_polling': True, 'monitor_sn': 0, 'suggested_area': ''}, 'Y170000000000001': {'modbus_polling': True, 'monitor_sn': 2000000000, 'node_id': '', 'suggested_area': ''}}} @@ -45,7 +44,6 @@ def test_full_config(): 'Y170000000000001': {'modbus_polling': True, 'monitor_sn': 2000000000, 'node_id': '', 'suggested_area': ''}}} try: validated = Config.conf_schema.validate(cnf) - assert True except: assert False assert validated == {'gen3plus': {'at_acl': {'mqtt': {'allow': ['AT+'], 'block': []}, 'tsun': {'allow': ['AT+Z', 'AT+UPURL', 'AT+SUPDATE'], 'block': []}}}, 'tsun': {'enabled': True, 'host': 'logger.talent-monitoring.com', 'port': 5005}, 'solarman': {'enabled': True, 'host': 'iot.talent-monitoring.com', 'port': 10000}, 'mqtt': {'host': 'mqtt', 'port': 1883, 'user': None, 'passwd': None}, 'ha': {'auto_conf_prefix': 'homeassistant', 'discovery_prefix': 'homeassistant', 'entity_prefix': 'tsun', 'proxy_node_id': 'proxy', 'proxy_unique_id': 'P170000000000001'}, 'inverters': {'allow_all': True, 'R170000000000001': {'node_id': '', 'modbus_polling': True, 'monitor_sn': 0, 'pv1': {'manufacturer': 'man1','type': 'type1'},'pv2': {'manufacturer': 'man2','type': 'type2'},'pv3': {'manufacturer': 'man3','type': 'type3'}, 'suggested_area': ''}, 'Y170000000000001': {'modbus_polling': True, 'monitor_sn': 2000000000, 'node_id': '', 'suggested_area': ''}}} @@ -63,7 +61,6 @@ def test_mininum_config(): try: validated = Config.conf_schema.validate(cnf) - assert True except: assert False assert validated == {'gen3plus': {'at_acl': {'mqtt': {'allow': ['AT+'], 'block': []}, 'tsun': {'allow': ['AT+Z', 'AT+UPURL', 'AT+SUPDATE'], 'block': []}}}, 'tsun': {'enabled': True, 'host': 'logger.talent-monitoring.com', 'port': 5005}, 'solarman': {'enabled': True, 'host': 'iot.talent-monitoring.com', 'port': 10000}, 'mqtt': {'host': 'mqtt', 'port': 1883, 'user': None, 'passwd': None}, 'ha': {'auto_conf_prefix': 'homeassistant', 'discovery_prefix': 'homeassistant', 'entity_prefix': 'tsun', 'proxy_node_id': 'proxy', 'proxy_unique_id': 'P170000000000001'}, 'inverters': {'allow_all': True, 'R170000000000001': {'node_id': '', 'modbus_polling': True, 'monitor_sn': 0, 'suggested_area': ''}}} diff --git a/system_tests/test_tcp_socket.py b/system_tests/test_tcp_socket.py index e2b64f8..00ec70f 100644 --- a/system_tests/test_tcp_socket.py +++ b/system_tests/test_tcp_socket.py @@ -1,8 +1,6 @@ # test_with_pytest.py and scapy # import pytest, socket, time -#from scapy.all import * -#from scapy.layers.inet import IP, TCP, TCP_client def get_sn() -> bytes: return b'R170000000000001' @@ -120,9 +118,7 @@ def MsgOtaUpdateReq(): # Over the air update request from talent cloud @pytest.fixture(scope="session") def ClientConnection(): - #host = '172.16.30.7' host = 'logger.talent-monitoring.com' - #host = '127.0.0.1' port = 5005 with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.connect((host, port)) @@ -132,9 +128,7 @@ def ClientConnection(): s.close() def tempClientConnection(): - #host = '172.16.30.7' host = 'logger.talent-monitoring.com' - #host = '127.0.0.1' port = 5005 with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.connect((host, port)) @@ -148,7 +142,6 @@ def test_open_close(): pass except: assert False - assert True def test_send_contact_info1(ClientConnection, MsgContactInfo, MsgContactResp): s = ClientConnection @@ -166,7 +159,7 @@ def test_send_contact_info2(ClientConnection, MsgContactInfo2, MsgContactInfo, M s.sendall(MsgContactInfo2) data = s.recv(1024) except TimeoutError: - assert True + pass else: assert False @@ -198,7 +191,7 @@ def test_send_contact_resp(ClientConnection, MsgContactResp): s.sendall(MsgContactResp) data = s.recv(1024) except TimeoutError: - assert True + pass else: assert data == b'' diff --git a/system_tests/test_tcp_socket_v2.py b/system_tests/test_tcp_socket_v2.py index 5e978de..b3521a8 100644 --- a/system_tests/test_tcp_socket_v2.py +++ b/system_tests/test_tcp_socket_v2.py @@ -3,9 +3,6 @@ import pytest, socket, time, os from dotenv import load_dotenv -#from scapy.all import * -#from scapy.layers.inet import IP, TCP, TCP_client - load_dotenv() SOLARMAN_SNR = os.getenv('SOLARMAN_SNR', '00000080') @@ -111,10 +108,7 @@ def MsgInvalidInfo(): # Contact Info message wrong start byte @pytest.fixture(scope="session") def ClientConnection(): - #host = '172.16.30.7' host = 'logger.talent-monitoring.com' - #host = 'iot.talent-monitoring.com' - #host = '127.0.0.1' port = 10000 with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.connect((host, port)) @@ -131,10 +125,7 @@ def checkResponse(data, Msg): def tempClientConnection(): - #host = '172.16.30.7' host = 'logger.talent-monitoring.com' - #host = 'iot.talent-monitoring.com' - #host = '127.0.0.1' port = 10000 with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.connect((host, port)) @@ -145,11 +136,10 @@ def tempClientConnection(): def test_open_close(): try: - for s in tempClientConnection(): - pass + for _ in tempClientConnection(): + pass # test generator tempClientConnection() except: assert False - assert True def test_conn_msg(ClientConnection,MsgContactInfo, MsgContactResp): s = ClientConnection