From e6a8cea17b2f23d0ba1fc706008c57315ade7c61 Mon Sep 17 00:00:00 2001 From: Swetha Swaminathan Date: Mon, 2 Dec 2024 17:47:40 +0530 Subject: [PATCH 01/52] First draft --- .github/workflows/integration_test_run.yaml | 28 +++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/.github/workflows/integration_test_run.yaml b/.github/workflows/integration_test_run.yaml index 267ef33ba..0183e071c 100644 --- a/.github/workflows/integration_test_run.yaml +++ b/.github/workflows/integration_test_run.yaml @@ -294,16 +294,20 @@ jobs: module="-k ${{ matrix.modules }}" fi echo "MODULE=$module" >> $GITHUB_ENV + - name: Install Allure Dependencies + run: | + python -m pip install --upgrade pip + pip install allure-pytest>=2.8.18 - name: Run k8s integration tests working-directory: ${{ inputs.working-directory }} if: ${{ inputs.provider == 'microk8s' }} run: | - tox -e ${{ inputs.test-tox-env }} -- --model testing --keep-models ${{ env.SERIES }} ${{ env.MODULE }} ${{ env.ARGS }} ${{ inputs.extra-arguments }} ${{ secrets.INTEGRATION_TEST_ARGS }} + tox -e ${{ inputs.test-tox-env }} -- --model testing --keep-models ${{ env.SERIES }} ${{ env.MODULE }} ${{ env.ARGS }} ${{ inputs.extra-arguments }} ${{ secrets.INTEGRATION_TEST_ARGS }} --alluredir=allure-results - name: Run lxd integration tests working-directory: ${{ inputs.working-directory }} if: ${{ inputs.provider == 'lxd' }} run: | - tox -e ${{ inputs.test-tox-env }} -- --keep-models ${{ env.SERIES }} ${{ env.MODULE }} ${{ env.ARGS }} ${{ inputs.extra-arguments }} ${{ secrets.INTEGRATION_TEST_ARGS }} + tox -e ${{ inputs.test-tox-env }} -- --keep-models ${{ env.SERIES }} ${{ env.MODULE }} ${{ env.ARGS }} ${{ inputs.extra-arguments }} ${{ secrets.INTEGRATION_TEST_ARGS }} --alluredir=allure-results - name: Tmate debugging session (self-hosted) if: ${{ failure() && (inputs.tmate-debug || runner.debug) && inputs.self-hosted-runner }} uses: canonical/action-tmate@main @@ -354,3 +358,23 @@ jobs: target: ${{ inputs.zap-target-protocol }}://${{ env.ZAP_TARGET }}:${{ inputs.zap-target-port }}/ cmd_options: ${{ inputs.zap-cmd-options }} rules_file_name: ${{ inputs.zap-rules-file-name }} + - name: Get Allure history + uses: actions/checkout@v3 + if: always() + continue-on-error: true + with: + ref: gh-pages + path: gh-pages + - name: Allure Report action from marketplace + uses: simple-elf/allure-report-action@v1.7 + if: always() + with: + allure_results: allure-results + allure_history: allure-history + - name: Deploy report to Github Pages + if: always() + uses: peaceiris/actions-gh-pages@v2 + env: + PERSONAL_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PUBLISH_BRANCH: gh-pages + PUBLISH_DIR: allure-history From c39ab8b9004c832dd745a4042d819fb3268296a8 Mon Sep 17 00:00:00 2001 From: Swetha Swaminathan Date: Mon, 2 Dec 2024 20:40:03 +0530 Subject: [PATCH 02/52] Edits --- .github/workflows/integration_test_run.yaml | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/.github/workflows/integration_test_run.yaml b/.github/workflows/integration_test_run.yaml index 66b69513f..1ff8abeb7 100644 --- a/.github/workflows/integration_test_run.yaml +++ b/.github/workflows/integration_test_run.yaml @@ -152,6 +152,9 @@ on: description: Rules file to ignore any alerts from the ZAP scan type: string +permissions: + contents: write + jobs: integration-test: name: Integration tests @@ -294,20 +297,24 @@ jobs: module="-k ${{ matrix.modules }}" fi echo "MODULE=$module" >> $GITHUB_ENV - - name: Install Allure Dependencies + - name: (beta) Get Allure option + id: allure-option + shell: python run: | - python -m pip install --upgrade pip - pip install allure-pytest>=2.8.18 + import os + output = "option=--alluredir=allure-results" + with open(os.environ["GITHUB_OUTPUT"], "a") as file: + file.write(output) - name: Run k8s integration tests working-directory: ${{ inputs.working-directory }} if: ${{ inputs.provider == 'microk8s' }} run: | - tox -e ${{ inputs.test-tox-env }} -- --model testing --keep-models ${{ env.SERIES }} ${{ env.MODULE }} ${{ env.ARGS }} ${{ inputs.extra-arguments }} ${{ secrets.INTEGRATION_TEST_ARGS }} --alluredir=allure-results + tox -e ${{ inputs.test-tox-env }} -- --model testing --keep-models ${{ env.SERIES }} ${{ env.MODULE }} ${{ env.ARGS }} ${{ inputs.extra-arguments }} ${{ secrets.INTEGRATION_TEST_ARGS }} ${{ steps.allure-option.outputs.option }} - name: Run lxd integration tests working-directory: ${{ inputs.working-directory }} if: ${{ inputs.provider == 'lxd' }} run: | - tox -e ${{ inputs.test-tox-env }} -- --keep-models ${{ env.SERIES }} ${{ env.MODULE }} ${{ env.ARGS }} ${{ inputs.extra-arguments }} ${{ secrets.INTEGRATION_TEST_ARGS }} --alluredir=allure-results + tox -e ${{ inputs.test-tox-env }} -- --keep-models ${{ env.SERIES }} ${{ env.MODULE }} ${{ env.ARGS }} ${{ inputs.extra-arguments }} ${{ secrets.INTEGRATION_TEST_ARGS }} ${{ steps.allure-option.outputs.option }} - name: Tmate debugging session (self-hosted) if: ${{ failure() && (inputs.tmate-debug || runner.debug) && inputs.self-hosted-runner }} uses: canonical/action-tmate@main From b2208a0cb079053b3b6187ff5c44b5127d4cc832 Mon Sep 17 00:00:00 2001 From: Swetha Swaminathan Date: Wed, 4 Dec 2024 10:50:44 +0530 Subject: [PATCH 03/52] upload allure results --- .github/workflows/integration_test_run.yaml | 29 ++++++--------------- 1 file changed, 8 insertions(+), 21 deletions(-) diff --git a/.github/workflows/integration_test_run.yaml b/.github/workflows/integration_test_run.yaml index 1ff8abeb7..d28295a28 100644 --- a/.github/workflows/integration_test_run.yaml +++ b/.github/workflows/integration_test_run.yaml @@ -309,12 +309,12 @@ jobs: working-directory: ${{ inputs.working-directory }} if: ${{ inputs.provider == 'microk8s' }} run: | - tox -e ${{ inputs.test-tox-env }} -- --model testing --keep-models ${{ env.SERIES }} ${{ env.MODULE }} ${{ env.ARGS }} ${{ inputs.extra-arguments }} ${{ secrets.INTEGRATION_TEST_ARGS }} ${{ steps.allure-option.outputs.option }} + tox -e ${{ inputs.test-tox-env }} -- --model testing --keep-models ${{ env.SERIES }} ${{ env.MODULE }} ${{ env.ARGS }} ${{ steps.allure-option.outputs.option }} ${{ inputs.extra-arguments }} ${{ secrets.INTEGRATION_TEST_ARGS }} - name: Run lxd integration tests working-directory: ${{ inputs.working-directory }} if: ${{ inputs.provider == 'lxd' }} run: | - tox -e ${{ inputs.test-tox-env }} -- --keep-models ${{ env.SERIES }} ${{ env.MODULE }} ${{ env.ARGS }} ${{ inputs.extra-arguments }} ${{ secrets.INTEGRATION_TEST_ARGS }} ${{ steps.allure-option.outputs.option }} + tox -e ${{ inputs.test-tox-env }} -- --keep-models ${{ env.SERIES }} ${{ env.MODULE }} ${{ env.ARGS }} ${{ steps.allure-option.outputs.option }} ${{ inputs.extra-arguments }} ${{ secrets.INTEGRATION_TEST_ARGS }} - name: Tmate debugging session (self-hosted) if: ${{ failure() && (inputs.tmate-debug || runner.debug) && inputs.self-hosted-runner }} uses: canonical/action-tmate@main @@ -367,23 +367,10 @@ jobs: target: ${{ inputs.zap-target-protocol }}://${{ env.ZAP_TARGET }}:${{ inputs.zap-target-port }}/ cmd_options: ${{ inputs.zap-cmd-options }} rules_file_name: ${{ inputs.zap-rules-file-name }} - - name: Get Allure history - uses: actions/checkout@v3 - if: always() - continue-on-error: true + - name: Upload Allure results + timeout-minutes: 3 + uses: actions/upload-artifact@v4 with: - ref: gh-pages - path: gh-pages - - name: Allure Report action from marketplace - uses: simple-elf/allure-report-action@v1.7 - if: always() - with: - allure_results: allure-results - allure_history: allure-history - - name: Deploy report to Github Pages - if: always() - uses: peaceiris/actions-gh-pages@v2 - env: - PERSONAL_TOKEN: ${{ secrets.GITHUB_TOKEN }} - PUBLISH_BRANCH: gh-pages - PUBLISH_DIR: allure-history + name: allure-results-${{ env.MODULE }} + path: allure-results/ + if-no-files-found: error From b9ad38ab044aeed838939fed8c3502aa0c89ae06 Mon Sep 17 00:00:00 2001 From: Swetha Swaminathan Date: Wed, 4 Dec 2024 14:11:08 +0530 Subject: [PATCH 04/52] added allure-pytest --- .github/workflows/integration_test_run.yaml | 14 +++++++------- .../integration/test-rock/requirements.txt | 1 + .../integration/test-upload-charm/requirements.txt | 1 + 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/.github/workflows/integration_test_run.yaml b/.github/workflows/integration_test_run.yaml index d28295a28..5e2c1676e 100644 --- a/.github/workflows/integration_test_run.yaml +++ b/.github/workflows/integration_test_run.yaml @@ -315,6 +315,13 @@ jobs: if: ${{ inputs.provider == 'lxd' }} run: | tox -e ${{ inputs.test-tox-env }} -- --keep-models ${{ env.SERIES }} ${{ env.MODULE }} ${{ env.ARGS }} ${{ steps.allure-option.outputs.option }} ${{ inputs.extra-arguments }} ${{ secrets.INTEGRATION_TEST_ARGS }} + - name: Upload Allure results + timeout-minutes: 3 + uses: actions/upload-artifact@v4 + with: + name: allure-results-${{ matrix.modules }} + path: allure-results/ + if-no-files-found: error - name: Tmate debugging session (self-hosted) if: ${{ failure() && (inputs.tmate-debug || runner.debug) && inputs.self-hosted-runner }} uses: canonical/action-tmate@main @@ -367,10 +374,3 @@ jobs: target: ${{ inputs.zap-target-protocol }}://${{ env.ZAP_TARGET }}:${{ inputs.zap-target-port }}/ cmd_options: ${{ inputs.zap-cmd-options }} rules_file_name: ${{ inputs.zap-rules-file-name }} - - name: Upload Allure results - timeout-minutes: 3 - uses: actions/upload-artifact@v4 - with: - name: allure-results-${{ env.MODULE }} - path: allure-results/ - if-no-files-found: error diff --git a/tests/workflows/integration/test-rock/requirements.txt b/tests/workflows/integration/test-rock/requirements.txt index 2d81d3bb6..0d05e6c9d 100644 --- a/tests/workflows/integration/test-rock/requirements.txt +++ b/tests/workflows/integration/test-rock/requirements.txt @@ -1 +1,2 @@ ops +allure-pytest>=2.8.18 diff --git a/tests/workflows/integration/test-upload-charm/requirements.txt b/tests/workflows/integration/test-upload-charm/requirements.txt index 56f5f6428..f9239509b 100644 --- a/tests/workflows/integration/test-upload-charm/requirements.txt +++ b/tests/workflows/integration/test-upload-charm/requirements.txt @@ -1 +1,2 @@ ops >= 1.5.0 +allure-pytest>=2.8.18 \ No newline at end of file From 9d26d45cfbd54bb9533e0942dfb24fba6bb1382d Mon Sep 17 00:00:00 2001 From: Swetha Swaminathan Date: Wed, 4 Dec 2024 14:38:50 +0530 Subject: [PATCH 05/52] debug lines --- .github/workflows/integration_test_run.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/integration_test_run.yaml b/.github/workflows/integration_test_run.yaml index 5e2c1676e..401ce1d1b 100644 --- a/.github/workflows/integration_test_run.yaml +++ b/.github/workflows/integration_test_run.yaml @@ -315,8 +315,14 @@ jobs: if: ${{ inputs.provider == 'lxd' }} run: | tox -e ${{ inputs.test-tox-env }} -- --keep-models ${{ env.SERIES }} ${{ env.MODULE }} ${{ env.ARGS }} ${{ steps.allure-option.outputs.option }} ${{ inputs.extra-arguments }} ${{ secrets.INTEGRATION_TEST_ARGS }} + - name: Check Allure directory + working-directory: ${{ inputs.working-directory }} + run: | + ls -la + ls -la allure-results/ - name: Upload Allure results timeout-minutes: 3 + if: always() && !cancelled() uses: actions/upload-artifact@v4 with: name: allure-results-${{ matrix.modules }} From 4f456bccd78ffaccc13b11028a47ab897f943f36 Mon Sep 17 00:00:00 2001 From: Swetha Swaminathan Date: Wed, 4 Dec 2024 14:53:16 +0530 Subject: [PATCH 06/52] debug lines --- .github/workflows/integration_test_run.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/integration_test_run.yaml b/.github/workflows/integration_test_run.yaml index 401ce1d1b..3f518f377 100644 --- a/.github/workflows/integration_test_run.yaml +++ b/.github/workflows/integration_test_run.yaml @@ -309,19 +309,22 @@ jobs: working-directory: ${{ inputs.working-directory }} if: ${{ inputs.provider == 'microk8s' }} run: | + pwd tox -e ${{ inputs.test-tox-env }} -- --model testing --keep-models ${{ env.SERIES }} ${{ env.MODULE }} ${{ env.ARGS }} ${{ steps.allure-option.outputs.option }} ${{ inputs.extra-arguments }} ${{ secrets.INTEGRATION_TEST_ARGS }} - name: Run lxd integration tests working-directory: ${{ inputs.working-directory }} if: ${{ inputs.provider == 'lxd' }} run: | + pwd tox -e ${{ inputs.test-tox-env }} -- --keep-models ${{ env.SERIES }} ${{ env.MODULE }} ${{ env.ARGS }} ${{ steps.allure-option.outputs.option }} ${{ inputs.extra-arguments }} ${{ secrets.INTEGRATION_TEST_ARGS }} - name: Check Allure directory - working-directory: ${{ inputs.working-directory }} run: | + pwd ls -la ls -la allure-results/ - name: Upload Allure results timeout-minutes: 3 + working-directory: ${{ inputs.working-directory }} if: always() && !cancelled() uses: actions/upload-artifact@v4 with: From 399c7dcf5ca0f8b2a4cb06ea74d4831795f08143 Mon Sep 17 00:00:00 2001 From: Swetha Swaminathan Date: Wed, 4 Dec 2024 15:00:01 +0530 Subject: [PATCH 07/52] debug lines --- .github/workflows/integration_test_run.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/integration_test_run.yaml b/.github/workflows/integration_test_run.yaml index 3f518f377..511180631 100644 --- a/.github/workflows/integration_test_run.yaml +++ b/.github/workflows/integration_test_run.yaml @@ -324,12 +324,11 @@ jobs: ls -la allure-results/ - name: Upload Allure results timeout-minutes: 3 - working-directory: ${{ inputs.working-directory }} if: always() && !cancelled() uses: actions/upload-artifact@v4 with: name: allure-results-${{ matrix.modules }} - path: allure-results/ + path: ${{ inputs.working-directory }}/allure-results/ if-no-files-found: error - name: Tmate debugging session (self-hosted) if: ${{ failure() && (inputs.tmate-debug || runner.debug) && inputs.self-hosted-runner }} From 59044071c837201e595e6d0acbb61bad32a047f6 Mon Sep 17 00:00:00 2001 From: Swetha Swaminathan Date: Wed, 4 Dec 2024 15:18:22 +0530 Subject: [PATCH 08/52] debug lines --- .github/workflows/integration_test_run.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/integration_test_run.yaml b/.github/workflows/integration_test_run.yaml index 511180631..307173b46 100644 --- a/.github/workflows/integration_test_run.yaml +++ b/.github/workflows/integration_test_run.yaml @@ -328,7 +328,7 @@ jobs: uses: actions/upload-artifact@v4 with: name: allure-results-${{ matrix.modules }} - path: ${{ inputs.working-directory }}/allure-results/ + path: ${{ inputs.working-directory }}allure-results/ if-no-files-found: error - name: Tmate debugging session (self-hosted) if: ${{ failure() && (inputs.tmate-debug || runner.debug) && inputs.self-hosted-runner }} From 7434888446449cac3558f0d629add6e693b6eb72 Mon Sep 17 00:00:00 2001 From: Swetha Swaminathan Date: Wed, 4 Dec 2024 15:29:26 +0530 Subject: [PATCH 09/52] debug lines --- .github/workflows/integration_test_run.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/integration_test_run.yaml b/.github/workflows/integration_test_run.yaml index 307173b46..2a145b859 100644 --- a/.github/workflows/integration_test_run.yaml +++ b/.github/workflows/integration_test_run.yaml @@ -328,7 +328,7 @@ jobs: uses: actions/upload-artifact@v4 with: name: allure-results-${{ matrix.modules }} - path: ${{ inputs.working-directory }}allure-results/ + path: allure-results/ if-no-files-found: error - name: Tmate debugging session (self-hosted) if: ${{ failure() && (inputs.tmate-debug || runner.debug) && inputs.self-hosted-runner }} From facf0c1ebe4071ea0293d4fccd5fff885ccba10d Mon Sep 17 00:00:00 2001 From: Swetha Swaminathan Date: Wed, 4 Dec 2024 22:22:05 +0530 Subject: [PATCH 10/52] getting integ to run --- .github/workflows/integration_test_run.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/integration_test_run.yaml b/.github/workflows/integration_test_run.yaml index 2a145b859..b8e78ea4b 100644 --- a/.github/workflows/integration_test_run.yaml +++ b/.github/workflows/integration_test_run.yaml @@ -302,7 +302,7 @@ jobs: shell: python run: | import os - output = "option=--alluredir=allure-results" + output = "option=--alluredir=${{ inputs.working-directory }}allure-results" with open(os.environ["GITHUB_OUTPUT"], "a") as file: file.write(output) - name: Run k8s integration tests @@ -318,6 +318,7 @@ jobs: pwd tox -e ${{ inputs.test-tox-env }} -- --keep-models ${{ env.SERIES }} ${{ env.MODULE }} ${{ env.ARGS }} ${{ steps.allure-option.outputs.option }} ${{ inputs.extra-arguments }} ${{ secrets.INTEGRATION_TEST_ARGS }} - name: Check Allure directory + working-directory: ${{ inputs.working-directory }} run: | pwd ls -la @@ -328,7 +329,7 @@ jobs: uses: actions/upload-artifact@v4 with: name: allure-results-${{ matrix.modules }} - path: allure-results/ + path: ${{ inputs.working-directory }}allure-results/ if-no-files-found: error - name: Tmate debugging session (self-hosted) if: ${{ failure() && (inputs.tmate-debug || runner.debug) && inputs.self-hosted-runner }} From 76b8b13648d7351c476dc63b83361cd4fd9707f1 Mon Sep 17 00:00:00 2001 From: Swetha Swaminathan Date: Wed, 4 Dec 2024 22:38:26 +0530 Subject: [PATCH 11/52] getting integ to run --- .github/workflows/integration_test_run.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/integration_test_run.yaml b/.github/workflows/integration_test_run.yaml index b8e78ea4b..1defb4b70 100644 --- a/.github/workflows/integration_test_run.yaml +++ b/.github/workflows/integration_test_run.yaml @@ -302,7 +302,7 @@ jobs: shell: python run: | import os - output = "option=--alluredir=${{ inputs.working-directory }}allure-results" + output = "option=--alluredir=allure-results" with open(os.environ["GITHUB_OUTPUT"], "a") as file: file.write(output) - name: Run k8s integration tests From fe89621026fc1c0ed557a11473e38441049151c7 Mon Sep 17 00:00:00 2001 From: Swetha Swaminathan Date: Thu, 5 Dec 2024 10:16:34 +0530 Subject: [PATCH 12/52] getting integ to run --- .github/workflows/allure_report.yaml | 89 +++++++++++++++++++++ .github/workflows/integration_test.yaml | 5 ++ .github/workflows/integration_test_run.yaml | 6 +- .github/workflows/workflow_test.yaml | 26 ++++++ 4 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/allure_report.yaml diff --git a/.github/workflows/allure_report.yaml b/.github/workflows/allure_report.yaml new file mode 100644 index 000000000..ab9e7a89c --- /dev/null +++ b/.github/workflows/allure_report.yaml @@ -0,0 +1,89 @@ +name: Allure Report Generation + +on: [workflow_call] + +jobs: + allure-report: + name: (beta) Publish Allure report + runs-on: ubuntu-latest + timeout-minutes: 5 + if: always() && !cancelled() + steps: + - name: Download Allure from allure-framwork + # Following instructions from https://allurereport.org/docs/gettingstarted-installation/#install-via-the-system-package-manager-for-linux + run: gh release download --repo allure-framework/allure2 --pattern 'allure_*.deb' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Install Allure + run: | + sudo apt-get update + sudo apt-get install ./allure_*.deb -y + # For first run, manually create branch with no history + # (e.g. + # git checkout --orphan gh-pages-beta + # git rm -rf . + # touch .nojekyll + # git add .nojekyll + # git commit -m "Initial commit" + # git push origin gh-pages-beta + # ) + - name: Checkout GitHub pages branch + uses: actions/checkout@v4 + with: + ref: gh-pages + path: repo/ + - name: Download first test results + uses: actions/download-artifact@v4 + with: + path: allure-results/ + pattern: allure-results* + merge-multiple: true + - name: Load test report history + run: | + if [[ -d repo/_latest/history/ ]] + then + echo 'Loading history' + cp -r repo/_latest/history/ allure-results/ + fi + - name: Create executor.json + shell: python + run: | + # Reverse engineered from https://github.com/simple-elf/allure-report-action/blob/eca283b643d577c69b8e4f048dd6cd8eb8457cfd/entrypoint.sh + import json + + DATA = { + "name": "GitHub Actions", + "type": "github", + "buildOrder": ${{ github.run_number }}, # TODO future improvement: use run ID + "buildName": "Run ${{ github.run_id }}", + "buildUrl": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}", + "reportUrl": "../${{ github.run_number }}/", + } + with open("allure-results/executor.json", "w") as file: + json.dump(DATA, file) + - name: Generate Allure report + run: allure generate + - name: Create index.html + shell: python + run: | + DATA = f""" + + + + """ + with open("repo/index.html", "w") as file: + file.write(DATA) + - name: Update GitHub pages branch + working-directory: repo/ + # TODO future improvement: commit message + run: | + mkdir '${{ github.run_number }}' + rm -f _latest + ln -s '${{ github.run_number }}' _latest + cp -r ../allure-report/. _latest/ + git add . + git config user.name "GitHub Actions" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git commit -m "Allure report ${{ github.run_number }}" + # Uses token set in checkout step + git push origin gh-pages \ No newline at end of file diff --git a/.github/workflows/integration_test.yaml b/.github/workflows/integration_test.yaml index 18fa972cf..7edff53c0 100644 --- a/.github/workflows/integration_test.yaml +++ b/.github/workflows/integration_test.yaml @@ -10,6 +10,10 @@ on: description: Label for building the charm type: string default: ubuntu-latest + calling-job-name: + description: Name of the job that is calling this reusable workflow + type: string + required: true charmcraft-channel: description: Charmcraft channel to use for the integration test type: string @@ -316,6 +320,7 @@ jobs: if: always() && needs.plan.result == 'success' && (needs.build.result == 'success' || toJSON(fromJSON(needs.plan.outputs.plan).build) == '[]') secrets: inherit with: + calling-job-name: ${{ inputs.calling-job-name}} channel: ${{ inputs.channel }} charmcraft-ref: ${{ inputs.charmcraft-ref }} charmcraft-repository: ${{ inputs.charmcraft-repository }} diff --git a/.github/workflows/integration_test_run.yaml b/.github/workflows/integration_test_run.yaml index 1defb4b70..ecc2095a7 100644 --- a/.github/workflows/integration_test_run.yaml +++ b/.github/workflows/integration_test_run.yaml @@ -6,6 +6,10 @@ name: Run integration tests on: workflow_call: inputs: + calling-job-name: + description: Name of the job that is calling this reusable workflow + type: string + required: true charmcraft-ref: description: Used in conjunction with charmcraft-repository to pull and build charmcraft from source instead of using snapstore version. type: string @@ -328,7 +332,7 @@ jobs: if: always() && !cancelled() uses: actions/upload-artifact@v4 with: - name: allure-results-${{ matrix.modules }} + name: allure-results-${{ inputs.calling-job-name }}-${{ matrix.modules }} path: ${{ inputs.working-directory }}allure-results/ if-no-files-found: error - name: Tmate debugging session (self-hosted) diff --git a/.github/workflows/workflow_test.yaml b/.github/workflows/workflow_test.yaml index 631b45114..c6cb6697d 100644 --- a/.github/workflows/workflow_test.yaml +++ b/.github/workflows/workflow_test.yaml @@ -13,6 +13,7 @@ jobs: with: working-directory: "tests/workflows/integration/test-upload-charm/" self-hosted-runner: false + calling-job-name: simple simple-self-hosted: uses: ./.github/workflows/test.yaml secrets: inherit @@ -20,6 +21,7 @@ jobs: working-directory: "tests/workflows/integration/test-upload-charm/" self-hosted-runner: true self-hosted-runner-label: "edge" + calling-job-name: simple-self-hosted integration: uses: ./.github/workflows/integration_test.yaml secrets: inherit @@ -27,6 +29,7 @@ jobs: working-directory: "tests/workflows/integration/test-upload-charm/" trivy-image-config: "tests/workflows/integration/test-upload-charm/trivy.yaml" trivy-severity-config: "CRITICAL,HIGH" + calling-job-name: integration integration-juju3: uses: ./.github/workflows/integration_test.yaml secrets: inherit @@ -38,6 +41,7 @@ jobs: channel: 1.30-strict/stable provider: microk8s test-tox-env: "integration-juju3.1" + calling-job-name: integration-juju3 integration-artifact: uses: ./.github/workflows/integration_test.yaml secrets: inherit @@ -51,6 +55,7 @@ jobs: test-tox-env: "integration-juju3.1" upload-image: artifact microk8s-addons: "dns ingress rbac storage registry" + calling-job-name: integration-artifact integration-self-hosted: uses: ./.github/workflows/integration_test.yaml secrets: inherit @@ -59,12 +64,14 @@ jobs: trivy-image-config: "tests/workflows/integration/test-upload-charm/trivy.yaml" self-hosted-runner: true self-hosted-runner-label: "edge" + calling-job-name: integration-self-hosted integration-rock: uses: ./.github/workflows/integration_test.yaml secrets: inherit with: working-directory: "tests/workflows/integration/test-rock/" trivy-image-config: "tests/workflows/integration/test-rock/trivy.yaml" + calling-job-name: integration-rock integration-rock-artifact: uses: ./.github/workflows/integration_test.yaml secrets: inherit @@ -73,6 +80,7 @@ jobs: upload-image: artifact microk8s-addons: "dns ingress rbac storage registry" trivy-image-config: "tests/workflows/integration/test-rock/trivy.yaml" + calling-job-name: integration-rock-artifact integration-craft: uses: ./.github/workflows/integration_test.yaml secrets: inherit @@ -83,6 +91,7 @@ jobs: charmcraft-ref: main rockcraft-repository: canonical/rockcraft rockcraft-ref: main + calling-job-name: integration-craft publish: uses: ./.github/workflows/publish_charm.yaml secrets: inherit @@ -93,6 +102,7 @@ jobs: integration-test-workflow-file: workflow_test.yaml working-directory: tests/workflows/integration/test-upload-charm/ workflow-run-id: ${{ github.run_id }} + calling-job-name: publish publish-artifact: uses: ./.github/workflows/publish_charm.yaml secrets: inherit @@ -103,6 +113,7 @@ jobs: integration-test-workflow-file: workflow_test.yaml working-directory: tests/workflows/integration/test-upload-charm/ workflow-run-id: ${{ github.run_id }} + calling-job-name: publish-artifact check: runs-on: ubuntu-latest if: always() && !cancelled() @@ -132,3 +143,18 @@ jobs: [ '${{ needs.integration-craft.result }}' = 'success' ] || (echo integration-craft failed && false) [ '${{ needs.publish.result }}' != 'failure' ] || (echo publish failed && false) [ '${{ needs.publish-artifact.result }}' != 'failure' ] || (echo publish failed && false) + allure-report: + if: always() && !cancelled() + needs: + - simple + - simple-self-hosted + - integration + - integration-juju3 + - integration-artifact + - integration-self-hosted + - integration-rock + - integration-rock-artifact + - integration-craft + - publish + - publish-artifact + uses: ./.github/workflows/allure_report.yaml From f7c81be81fd8c156f6f7a73167a7adf8117caaac Mon Sep 17 00:00:00 2001 From: Swetha Swaminathan Date: Thu, 5 Dec 2024 10:17:35 +0530 Subject: [PATCH 13/52] getting integ to run --- .github/workflows/integration_test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/integration_test.yaml b/.github/workflows/integration_test.yaml index 7edff53c0..763f64ac3 100644 --- a/.github/workflows/integration_test.yaml +++ b/.github/workflows/integration_test.yaml @@ -320,7 +320,7 @@ jobs: if: always() && needs.plan.result == 'success' && (needs.build.result == 'success' || toJSON(fromJSON(needs.plan.outputs.plan).build) == '[]') secrets: inherit with: - calling-job-name: ${{ inputs.calling-job-name}} + calling-job-name: ${{ inputs.calling-job-name }} channel: ${{ inputs.channel }} charmcraft-ref: ${{ inputs.charmcraft-ref }} charmcraft-repository: ${{ inputs.charmcraft-repository }} From 06ae0c627ce2e0bc6a98cb6710258e563f14c8de Mon Sep 17 00:00:00 2001 From: Swetha Swaminathan Date: Thu, 5 Dec 2024 10:20:30 +0530 Subject: [PATCH 14/52] getting integ to run --- .github/workflows/workflow_test.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/workflow_test.yaml b/.github/workflows/workflow_test.yaml index c6cb6697d..b0a8a62e1 100644 --- a/.github/workflows/workflow_test.yaml +++ b/.github/workflows/workflow_test.yaml @@ -13,7 +13,6 @@ jobs: with: working-directory: "tests/workflows/integration/test-upload-charm/" self-hosted-runner: false - calling-job-name: simple simple-self-hosted: uses: ./.github/workflows/test.yaml secrets: inherit @@ -21,7 +20,6 @@ jobs: working-directory: "tests/workflows/integration/test-upload-charm/" self-hosted-runner: true self-hosted-runner-label: "edge" - calling-job-name: simple-self-hosted integration: uses: ./.github/workflows/integration_test.yaml secrets: inherit From b46d2bef5530509a2a079f07f1143bd3f62d54d7 Mon Sep 17 00:00:00 2001 From: Swetha Swaminathan Date: Thu, 5 Dec 2024 10:21:22 +0530 Subject: [PATCH 15/52] getting integ to run --- .github/workflows/workflow_test.yaml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.github/workflows/workflow_test.yaml b/.github/workflows/workflow_test.yaml index b0a8a62e1..ed45ce1cd 100644 --- a/.github/workflows/workflow_test.yaml +++ b/.github/workflows/workflow_test.yaml @@ -100,7 +100,6 @@ jobs: integration-test-workflow-file: workflow_test.yaml working-directory: tests/workflows/integration/test-upload-charm/ workflow-run-id: ${{ github.run_id }} - calling-job-name: publish publish-artifact: uses: ./.github/workflows/publish_charm.yaml secrets: inherit @@ -111,7 +110,6 @@ jobs: integration-test-workflow-file: workflow_test.yaml working-directory: tests/workflows/integration/test-upload-charm/ workflow-run-id: ${{ github.run_id }} - calling-job-name: publish-artifact check: runs-on: ubuntu-latest if: always() && !cancelled() @@ -144,8 +142,6 @@ jobs: allure-report: if: always() && !cancelled() needs: - - simple - - simple-self-hosted - integration - integration-juju3 - integration-artifact @@ -153,6 +149,4 @@ jobs: - integration-rock - integration-rock-artifact - integration-craft - - publish - - publish-artifact uses: ./.github/workflows/allure_report.yaml From 0c6c4c97630fce0be6a98320ea43e19941ada60e Mon Sep 17 00:00:00 2001 From: Swetha Swaminathan Date: Thu, 5 Dec 2024 10:23:19 +0530 Subject: [PATCH 16/52] getting integ to run --- .github/workflows/workflow_test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/workflow_test.yaml b/.github/workflows/workflow_test.yaml index ed45ce1cd..dea46379c 100644 --- a/.github/workflows/workflow_test.yaml +++ b/.github/workflows/workflow_test.yaml @@ -89,7 +89,7 @@ jobs: charmcraft-ref: main rockcraft-repository: canonical/rockcraft rockcraft-ref: main - calling-job-name: integration-craft + calling-job-name: integration-craft publish: uses: ./.github/workflows/publish_charm.yaml secrets: inherit From cba281994e53937ae48bd6b95613b2b8a2b1e4e8 Mon Sep 17 00:00:00 2001 From: Swetha Swaminathan Date: Thu, 5 Dec 2024 14:57:47 +0530 Subject: [PATCH 17/52] getting integ to run --- .github/workflows/allure_report.yaml | 9 ++++--- .github/workflows/integration_test_run.yaml | 28 ++++++--------------- 2 files changed, 14 insertions(+), 23 deletions(-) diff --git a/.github/workflows/allure_report.yaml b/.github/workflows/allure_report.yaml index ab9e7a89c..f8b33384b 100644 --- a/.github/workflows/allure_report.yaml +++ b/.github/workflows/allure_report.yaml @@ -1,10 +1,13 @@ +# Copyright 2024 Canonical Ltd. +# See LICENSE file for licensing details. + name: Allure Report Generation on: [workflow_call] jobs: allure-report: - name: (beta) Publish Allure report + name: Publish Allure report runs-on: ubuntu-latest timeout-minutes: 5 if: always() && !cancelled() @@ -20,12 +23,12 @@ jobs: sudo apt-get install ./allure_*.deb -y # For first run, manually create branch with no history # (e.g. - # git checkout --orphan gh-pages-beta + # git checkout --orphan gh-pages # git rm -rf . # touch .nojekyll # git add .nojekyll # git commit -m "Initial commit" - # git push origin gh-pages-beta + # git push origin gh-pages # ) - name: Checkout GitHub pages branch uses: actions/checkout@v4 diff --git a/.github/workflows/integration_test_run.yaml b/.github/workflows/integration_test_run.yaml index ecc2095a7..0e074a5cc 100644 --- a/.github/workflows/integration_test_run.yaml +++ b/.github/workflows/integration_test_run.yaml @@ -291,50 +291,38 @@ jobs: args="${{ steps.plan-integration.outputs.args }}" echo "ARGS=$args" >> $GITHUB_ENV + + allure_artifact_suffix=$(uuidgen) series="" if [ ! -z ${{ matrix.series }} ]; then series="--series ${{ matrix.series }}" + allure_artifact_suffix=$(allure_artifact_suffix)-${{ matrix.series }} fi echo "SERIES=$series" >> $GITHUB_ENV module="" if [ ! -z ${{ matrix.modules }} ]; then module="-k ${{ matrix.modules }}" + allure_artifact_suffix=$(allure_artifact_suffix)-${{ matrix.modules }} fi echo "MODULE=$module" >> $GITHUB_ENV - - name: (beta) Get Allure option - id: allure-option - shell: python - run: | - import os - output = "option=--alluredir=allure-results" - with open(os.environ["GITHUB_OUTPUT"], "a") as file: - file.write(output) + echo "ALLURE_ARTIFACT_SUFFIX=$allure_artifact_suffix" >> $GITHUB_ENV - name: Run k8s integration tests working-directory: ${{ inputs.working-directory }} if: ${{ inputs.provider == 'microk8s' }} run: | - pwd - tox -e ${{ inputs.test-tox-env }} -- --model testing --keep-models ${{ env.SERIES }} ${{ env.MODULE }} ${{ env.ARGS }} ${{ steps.allure-option.outputs.option }} ${{ inputs.extra-arguments }} ${{ secrets.INTEGRATION_TEST_ARGS }} + tox -e ${{ inputs.test-tox-env }} -- --model testing --keep-models ${{ env.SERIES }} ${{ env.MODULE }} ${{ env.ARGS }} --alluredir=allure-results ${{ inputs.extra-arguments }} ${{ secrets.INTEGRATION_TEST_ARGS }} - name: Run lxd integration tests working-directory: ${{ inputs.working-directory }} if: ${{ inputs.provider == 'lxd' }} run: | - pwd - tox -e ${{ inputs.test-tox-env }} -- --keep-models ${{ env.SERIES }} ${{ env.MODULE }} ${{ env.ARGS }} ${{ steps.allure-option.outputs.option }} ${{ inputs.extra-arguments }} ${{ secrets.INTEGRATION_TEST_ARGS }} - - name: Check Allure directory - working-directory: ${{ inputs.working-directory }} - run: | - pwd - ls -la - ls -la allure-results/ + tox -e ${{ inputs.test-tox-env }} -- --keep-models ${{ env.SERIES }} ${{ env.MODULE }} ${{ env.ARGS }} --alluredir=allure-results ${{ inputs.extra-arguments }} ${{ secrets.INTEGRATION_TEST_ARGS }} - name: Upload Allure results timeout-minutes: 3 if: always() && !cancelled() uses: actions/upload-artifact@v4 with: - name: allure-results-${{ inputs.calling-job-name }}-${{ matrix.modules }} + name: allure-results-${{ env.ALLURE_ARTIFACT_SUFFIX }} path: ${{ inputs.working-directory }}allure-results/ - if-no-files-found: error - name: Tmate debugging session (self-hosted) if: ${{ failure() && (inputs.tmate-debug || runner.debug) && inputs.self-hosted-runner }} uses: canonical/action-tmate@main From fec66c7a1d381609daa8ad9924ada34986eb6118 Mon Sep 17 00:00:00 2001 From: Swetha Swaminathan Date: Fri, 6 Dec 2024 19:18:32 +0530 Subject: [PATCH 18/52] adding default result --- .github/workflows/allure_report.yaml | 17 ++- .github/workflows/integration_test.yaml | 5 - .github/workflows/integration_test_run.yaml | 88 +++++++------ .github/workflows/workflow_test.yaml | 7 - python/cli/README.md | 3 + .../__init__.py | 0 .../allure_add_default_for_missing_results.py | 49 +++++++ python/cli/pyproject.toml | 21 +++ .../allure_pytest_collection_report/README.md | 1 + .../__init__.py | 0 .../_plugin.py | 120 ++++++++++++++++++ .../pyproject.toml | 23 ++++ 12 files changed, 281 insertions(+), 53 deletions(-) create mode 100644 python/cli/README.md create mode 100644 python/cli/allure_add_default_for_missing_results/__init__.py create mode 100644 python/cli/allure_add_default_for_missing_results/allure_add_default_for_missing_results.py create mode 100644 python/cli/pyproject.toml create mode 100644 python/pytest_plugins/allure_pytest_collection_report/README.md create mode 100644 python/pytest_plugins/allure_pytest_collection_report/__init__.py create mode 100644 python/pytest_plugins/allure_pytest_collection_report/_plugin.py create mode 100644 python/pytest_plugins/allure_pytest_collection_report/pyproject.toml diff --git a/.github/workflows/allure_report.yaml b/.github/workflows/allure_report.yaml index f8b33384b..7afb2bba1 100644 --- a/.github/workflows/allure_report.yaml +++ b/.github/workflows/allure_report.yaml @@ -12,7 +12,7 @@ jobs: timeout-minutes: 5 if: always() && !cancelled() steps: - - name: Download Allure from allure-framwork + - name: Download Allure # Following instructions from https://allurereport.org/docs/gettingstarted-installation/#install-via-the-system-package-manager-for-linux run: gh release download --repo allure-framework/allure2 --pattern 'allure_*.deb' env: @@ -35,12 +35,25 @@ jobs: with: ref: gh-pages path: repo/ - - name: Download first test results + - name: Download default test results + uses: actions/download-artifact@v4 + with: + path: allure-collection-default-results/ + pattern: allure-default-results* + merge-multiple: true + - name: Download actual test results uses: actions/download-artifact@v4 with: path: allure-results/ pattern: allure-results* merge-multiple: true + - name: Install CLI + run: pipx install git+https://github.com/canonical/operator-workflows@ISD-2620-allure#subdirectory=python/cli + - name: Combine Allure default results & actual results + # For every test: if actual result available, use that. Otherwise, use default result + # So that, if actual result not available, Allure report will show "unknown"/"failed" test result + # instead of omitting the test + run: allure-add-default-for-missing-results --allure-results-dir=allure-results --allure-collection-default-results-dir=allure-collection-default-results - name: Load test report history run: | if [[ -d repo/_latest/history/ ]] diff --git a/.github/workflows/integration_test.yaml b/.github/workflows/integration_test.yaml index 763f64ac3..18fa972cf 100644 --- a/.github/workflows/integration_test.yaml +++ b/.github/workflows/integration_test.yaml @@ -10,10 +10,6 @@ on: description: Label for building the charm type: string default: ubuntu-latest - calling-job-name: - description: Name of the job that is calling this reusable workflow - type: string - required: true charmcraft-channel: description: Charmcraft channel to use for the integration test type: string @@ -320,7 +316,6 @@ jobs: if: always() && needs.plan.result == 'success' && (needs.build.result == 'success' || toJSON(fromJSON(needs.plan.outputs.plan).build) == '[]') secrets: inherit with: - calling-job-name: ${{ inputs.calling-job-name }} channel: ${{ inputs.channel }} charmcraft-ref: ${{ inputs.charmcraft-ref }} charmcraft-repository: ${{ inputs.charmcraft-repository }} diff --git a/.github/workflows/integration_test_run.yaml b/.github/workflows/integration_test_run.yaml index 0e074a5cc..bd4bf9696 100644 --- a/.github/workflows/integration_test_run.yaml +++ b/.github/workflows/integration_test_run.yaml @@ -6,10 +6,6 @@ name: Run integration tests on: workflow_call: inputs: - calling-job-name: - description: Name of the job that is calling this reusable workflow - type: string - required: true charmcraft-ref: description: Used in conjunction with charmcraft-repository to pull and build charmcraft from source instead of using snapstore version. type: string @@ -156,9 +152,6 @@ on: description: Rules file to ignore any alerts from the ZAP scan type: string -permissions: - contents: write - jobs: integration-test: name: Integration tests @@ -178,6 +171,55 @@ jobs: )) || inputs.runs-on }} steps: + - name: Plan Integration + uses: canonical/operator-workflows/internal/plan-integration@main + id: plan-integration + with: + plan: ${{ inputs.plan }} + - name: Integration tests variable setting + working-directory: ${{ inputs.working-directory }}/${{ inputs.charm-directory }} + run: | + CHARM_NAME="$([ -f metadata.yaml ] && yq '.name' metadata.yaml || echo UNKNOWN)" + if [ "$CHARM_NAME" == "UNKNOWN" ]; then + CHARM_NAME="$([ -f charmcraft.yaml ] && yq '.name' charmcraft.yaml || echo UNKNOWN)" + fi + echo "CHARM_NAME=$CHARM_NAME" >> $GITHUB_ENV + + args="${{ steps.plan-integration.outputs.args }}" + echo "ARGS=$args" >> $GITHUB_ENV + + allure_artifact_suffix=$(uuidgen) + series="" + if [ ! -z ${{ matrix.series }} ]; then + series="--series ${{ matrix.series }}" + allure_artifact_suffix=$(allure_artifact_suffix)-${{ matrix.series }} + fi + echo "SERIES=$series" >> $GITHUB_ENV + module="" + if [ ! -z ${{ matrix.modules }} ]; then + module="-k ${{ matrix.modules }}" + allure_artifact_suffix=$(allure_artifact_suffix)-${{ matrix.modules }} + fi + echo "MODULE=$module" >> $GITHUB_ENV + echo "ALLURE_ARTIFACT_SUFFIX=$allure_artifact_suffix" >> $GITHUB_ENV + - name: Install pytest plugin for Allure + run: pip install git+https://github.com/canonical/operator-workflows@ISD-2620-allure#subdirectory=python/pytest_plugins/allure_pytest_collection_report + - name: Collect tests for Allure (k8s) + working-directory: ${{ inputs.working-directory }} + if: ${{ inputs.provider == 'microk8s' }} + run: | + tox -e ${{ inputs.test-tox-env }} -- --model testing --keep-models ${{ env.SERIES }} ${{ env.MODULE }} ${{ env.ARGS }} --allure-collection-dir=allure-default ${{ inputs.extra-arguments }} ${{ secrets.INTEGRATION_TEST_ARGS }} + - name: Collect tests for Allure (lxd) + working-directory: ${{ inputs.working-directory }} + if: ${{ inputs.provider == 'lxd' }} + run: | + tox -e ${{ inputs.test-tox-env }} -- --keep-models ${{ env.SERIES }} ${{ env.MODULE }} ${{ env.ARGS }} --allure-collection-dir=allure-default ${{ inputs.extra-arguments }} ${{ secrets.INTEGRATION_TEST_ARGS }} + - name: Upload Default Allure results + timeout-minutes: 3 + uses: actions/upload-artifact@v4 + with: + name: allure-default-results-${{ env.ALLURE_ARTIFACT_SUFFIX }} + path: allure-default/ - name: Setup operator environment uses: charmed-kubernetes/actions-operator@main with: @@ -274,38 +316,6 @@ jobs: - run: sudo apt install skopeo -y - - name: Plan Integration - uses: canonical/operator-workflows/internal/plan-integration@main - id: plan-integration - with: - plan: ${{ inputs.plan }} - - - name: Integration tests variable setting - working-directory: ${{ inputs.working-directory }}/${{ inputs.charm-directory }} - run: | - CHARM_NAME="$([ -f metadata.yaml ] && yq '.name' metadata.yaml || echo UNKNOWN)" - if [ "$CHARM_NAME" == "UNKNOWN" ]; then - CHARM_NAME="$([ -f charmcraft.yaml ] && yq '.name' charmcraft.yaml || echo UNKNOWN)" - fi - echo "CHARM_NAME=$CHARM_NAME" >> $GITHUB_ENV - - args="${{ steps.plan-integration.outputs.args }}" - echo "ARGS=$args" >> $GITHUB_ENV - - allure_artifact_suffix=$(uuidgen) - series="" - if [ ! -z ${{ matrix.series }} ]; then - series="--series ${{ matrix.series }}" - allure_artifact_suffix=$(allure_artifact_suffix)-${{ matrix.series }} - fi - echo "SERIES=$series" >> $GITHUB_ENV - module="" - if [ ! -z ${{ matrix.modules }} ]; then - module="-k ${{ matrix.modules }}" - allure_artifact_suffix=$(allure_artifact_suffix)-${{ matrix.modules }} - fi - echo "MODULE=$module" >> $GITHUB_ENV - echo "ALLURE_ARTIFACT_SUFFIX=$allure_artifact_suffix" >> $GITHUB_ENV - name: Run k8s integration tests working-directory: ${{ inputs.working-directory }} if: ${{ inputs.provider == 'microk8s' }} diff --git a/.github/workflows/workflow_test.yaml b/.github/workflows/workflow_test.yaml index dea46379c..f9b9b7c56 100644 --- a/.github/workflows/workflow_test.yaml +++ b/.github/workflows/workflow_test.yaml @@ -27,7 +27,6 @@ jobs: working-directory: "tests/workflows/integration/test-upload-charm/" trivy-image-config: "tests/workflows/integration/test-upload-charm/trivy.yaml" trivy-severity-config: "CRITICAL,HIGH" - calling-job-name: integration integration-juju3: uses: ./.github/workflows/integration_test.yaml secrets: inherit @@ -39,7 +38,6 @@ jobs: channel: 1.30-strict/stable provider: microk8s test-tox-env: "integration-juju3.1" - calling-job-name: integration-juju3 integration-artifact: uses: ./.github/workflows/integration_test.yaml secrets: inherit @@ -53,7 +51,6 @@ jobs: test-tox-env: "integration-juju3.1" upload-image: artifact microk8s-addons: "dns ingress rbac storage registry" - calling-job-name: integration-artifact integration-self-hosted: uses: ./.github/workflows/integration_test.yaml secrets: inherit @@ -62,14 +59,12 @@ jobs: trivy-image-config: "tests/workflows/integration/test-upload-charm/trivy.yaml" self-hosted-runner: true self-hosted-runner-label: "edge" - calling-job-name: integration-self-hosted integration-rock: uses: ./.github/workflows/integration_test.yaml secrets: inherit with: working-directory: "tests/workflows/integration/test-rock/" trivy-image-config: "tests/workflows/integration/test-rock/trivy.yaml" - calling-job-name: integration-rock integration-rock-artifact: uses: ./.github/workflows/integration_test.yaml secrets: inherit @@ -78,7 +73,6 @@ jobs: upload-image: artifact microk8s-addons: "dns ingress rbac storage registry" trivy-image-config: "tests/workflows/integration/test-rock/trivy.yaml" - calling-job-name: integration-rock-artifact integration-craft: uses: ./.github/workflows/integration_test.yaml secrets: inherit @@ -89,7 +83,6 @@ jobs: charmcraft-ref: main rockcraft-repository: canonical/rockcraft rockcraft-ref: main - calling-job-name: integration-craft publish: uses: ./.github/workflows/publish_charm.yaml secrets: inherit diff --git a/python/cli/README.md b/python/cli/README.md new file mode 100644 index 000000000..5b6554569 --- /dev/null +++ b/python/cli/README.md @@ -0,0 +1,3 @@ +The `allure_add_default_for_missing_results` cli combines the Allure default results & actual results before generating the report. + +Currently, if the result of a test is not available (for reasons such as setup failure), allure omits the test. This CLI ensures that for every test, if actual result is available, it will use that. Otherwise, it uses the default result `unknown`. \ No newline at end of file diff --git a/python/cli/allure_add_default_for_missing_results/__init__.py b/python/cli/allure_add_default_for_missing_results/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python/cli/allure_add_default_for_missing_results/allure_add_default_for_missing_results.py b/python/cli/allure_add_default_for_missing_results/allure_add_default_for_missing_results.py new file mode 100644 index 000000000..9d9de9255 --- /dev/null +++ b/python/cli/allure_add_default_for_missing_results/allure_add_default_for_missing_results.py @@ -0,0 +1,49 @@ +import argparse +import dataclasses +import json +import pathlib + + +@dataclasses.dataclass(frozen=True) +class Result: + test_case_id: str + path: pathlib.Path + + def __eq__(self, other): + if not isinstance(other, type(self)): + return False + return self.test_case_id == other.test_case_id + + +def main(): + """Combine Allure default results & actual results + + For every test: if actual result available, use that. Otherwise, use default result + + So that, if actual result not available, Allure report will show "unknown"/"failed" test result + instead of omitting the test + """ + parser = argparse.ArgumentParser() + parser.add_argument("--allure-results-dir", required=True) + parser.add_argument("--allure-collection-default-results-dir", required=True) + args = parser.parse_args() + + actual_results = pathlib.Path(args.allure_results_dir) + default_results = pathlib.Path(args.allure_collection_default_results_dir) + + results: dict[pathlib.Path, set[Result]] = { + actual_results: set(), + default_results: set(), + } + for directory, results_ in results.items(): + for path in directory.glob("*-result.json"): + with path.open("r") as file: + id_ = json.load(file)["testCaseId"] + results_.add(Result(id_, path)) + + actual_results.mkdir(exist_ok=True) + + missing_results = results[default_results] - results[actual_results] + for default_result in missing_results: + # Move to `actual_results` directory + default_result.path.rename(actual_results / default_result.path.name) diff --git a/python/cli/pyproject.toml b/python/cli/pyproject.toml new file mode 100644 index 000000000..257a49600 --- /dev/null +++ b/python/cli/pyproject.toml @@ -0,0 +1,21 @@ +[tool.poetry] +name = "workflows-cli" +# Version unused; repository has its own versioning system. (See .github/workflows/__release.yaml) +version = "0.1.0" +description = "" +license = "Apache-2.0" +authors = ["Carl Csaposs "] +readme = "README.md" + +[tool.poetry.scripts] +allure-add-default-for-missing-results = "workflows_cli.allure_add_default_for_missing_results:main" + +[tool.poetry.dependencies] +python = "^3.10" +pyyaml = "^6.0.1" +requests = "^2.31.0" + + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" \ No newline at end of file diff --git a/python/pytest_plugins/allure_pytest_collection_report/README.md b/python/pytest_plugins/allure_pytest_collection_report/README.md new file mode 100644 index 000000000..e3a5e1b8b --- /dev/null +++ b/python/pytest_plugins/allure_pytest_collection_report/README.md @@ -0,0 +1 @@ +The `allure-pytest` plugin marks all the test statuses as unknown during collection time. \ No newline at end of file diff --git a/python/pytest_plugins/allure_pytest_collection_report/__init__.py b/python/pytest_plugins/allure_pytest_collection_report/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python/pytest_plugins/allure_pytest_collection_report/_plugin.py b/python/pytest_plugins/allure_pytest_collection_report/_plugin.py new file mode 100644 index 000000000..b96f762b7 --- /dev/null +++ b/python/pytest_plugins/allure_pytest_collection_report/_plugin.py @@ -0,0 +1,120 @@ +# Upstream feature request to replace this plugin: +# https://github.com/allure-framework/allure-python/issues/821 + +import allure_commons.logger +import allure_commons.model2 +import allure_commons.types +import allure_commons.utils +import allure_pytest.listener +import allure_pytest.utils + + +def pytest_addoption(parser): + parser.addoption( + "--allure-collection-dir", + help="Generate default Allure results (used by GitHub Actions) in \ + this directory for tests that are missing Allure results", + ) + + +def pytest_configure(config): + if config.option.allure_collection_dir: + config.option.collectonly = True + + +def pytest_collection_finish(session): + report_dir = session.config.option.allure_collection_dir + if not report_dir: + return + + # Copied from `allure_pytest.listener.AllureListener._cache` + _cache = allure_pytest.listener.ItemCache() + # Modified from `allure_pytest.plugin.pytest_configure` + file_logger = allure_commons.logger.AllureFileLogger(report_dir) + + for item in session.items: + # Modified from + # `allure_pytest.listener.AllureListener.pytest_runtest_protocol` + uuid = _cache.push(item.nodeid) + test_result = allure_commons.model2.TestResult(name=item.name, uuid=uuid) + + # Copied from `allure_pytest.listener.AllureListener.pytest_runtest_setup` + params = ( + allure_pytest.listener.AllureListener._AllureListener__get_pytest_params( + item + ) + ) + test_result.name = allure_pytest.utils.allure_name(item, params) + full_name = allure_pytest.utils.allure_full_name(item) + test_result.fullName = full_name + test_result.testCaseId = allure_commons.utils.md5(full_name) + test_result.description = allure_pytest.utils.allure_description(item) + test_result.descriptionHtml = allure_pytest.utils.allure_description_html(item) + current_param_names = [param.name for param in test_result.parameters] + test_result.parameters.extend( + [ + allure_commons.model2.Parameter( + name=name, value=allure_commons.utils.represent(value) + ) + for name, value in params.items() + if name not in current_param_names + ] + ) + + # Copied from `allure_pytest.listener.AllureListener.pytest_runtest_teardown` + listener = allure_pytest.listener.AllureListener + test_result.historyId = allure_pytest.utils.get_history_id( + test_result.fullName, + test_result.parameters, + original_values=listener._AllureListener__get_pytest_params(item), + ) + test_result.labels.extend( + [ + allure_commons.model2.Label(name=name, value=value) + for name, value in allure_pytest.utils.allure_labels(item) + ] + ) + test_result.labels.extend( + [ + allure_commons.model2.Label( + name=allure_commons.types.LabelType.TAG, value=value + ) + for value in allure_pytest.utils.pytest_markers(item) + ] + ) + allure_pytest.listener.AllureListener._AllureListener__apply_default_suites( + None, item, test_result + ) + test_result.labels.append( + allure_commons.model2.Label( + name=allure_commons.types.LabelType.HOST, + value=allure_commons.utils.host_tag(), + ) + ) + test_result.labels.append( + allure_commons.model2.Label( + name=allure_commons.types.LabelType.FRAMEWORK, value="pytest" + ) + ) + test_result.labels.append( + allure_commons.model2.Label( + name=allure_commons.types.LabelType.LANGUAGE, + value=allure_commons.utils.platform_label(), + ) + ) + test_result.labels.append( + allure_commons.model2.Label( + name="package", value=allure_pytest.utils.allure_package(item) + ) + ) + test_result.links.extend( + [ + allure_commons.model2.Link(link_type, url, name) + for link_type, url, name in allure_pytest.utils.allure_links(item) + ] + ) + + # Modified from `allure_pytest.listener.AllureListener.pytest_runtest_protocol` + test_result.status = allure_commons.model2.Status.UNKNOWN + # Modified from `allure_commons.reporter.AllureReporter.close_test` + file_logger.report_result(test_result) diff --git a/python/pytest_plugins/allure_pytest_collection_report/pyproject.toml b/python/pytest_plugins/allure_pytest_collection_report/pyproject.toml new file mode 100644 index 000000000..03e6074fe --- /dev/null +++ b/python/pytest_plugins/allure_pytest_collection_report/pyproject.toml @@ -0,0 +1,23 @@ +[tool.poetry] +name = "allure_pytest_collection_report" +# Version unused; repository has its own versioning system. (See .github/workflows/__release.yaml) +version = "0.1.0" +description = "" +authors = ["Carl Csaposs "] +readme = "README.md" +classifiers = [ + "Framework :: Pytest", +] + +[tool.poetry.plugins."pytest11"] +allure_collection_report = "allure_pytest_collection_report._plugin" + +[tool.poetry.dependencies] +python = "^3.8" +pytest = "*" +allure-pytest = ">=2.13.5" + + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" \ No newline at end of file From a8f2e7c3a183333d8a6c0a5b0c463016aa0e3112 Mon Sep 17 00:00:00 2001 From: Swetha Swaminathan Date: Fri, 6 Dec 2024 20:58:35 +0530 Subject: [PATCH 19/52] adding default result --- .github/workflows/integration_test_run.yaml | 38 +++++++++---------- .licenserc.yaml | 1 + .../__init__.py | 2 + .../allure_add_default_for_missing_results.py | 2 + .../__init__.py | 2 + .../_plugin.py | 2 + 6 files changed, 26 insertions(+), 21 deletions(-) diff --git a/.github/workflows/integration_test_run.yaml b/.github/workflows/integration_test_run.yaml index bd4bf9696..cc2f2995b 100644 --- a/.github/workflows/integration_test_run.yaml +++ b/.github/workflows/integration_test_run.yaml @@ -171,23 +171,9 @@ jobs: )) || inputs.runs-on }} steps: - - name: Plan Integration - uses: canonical/operator-workflows/internal/plan-integration@main - id: plan-integration - with: - plan: ${{ inputs.plan }} - name: Integration tests variable setting working-directory: ${{ inputs.working-directory }}/${{ inputs.charm-directory }} run: | - CHARM_NAME="$([ -f metadata.yaml ] && yq '.name' metadata.yaml || echo UNKNOWN)" - if [ "$CHARM_NAME" == "UNKNOWN" ]; then - CHARM_NAME="$([ -f charmcraft.yaml ] && yq '.name' charmcraft.yaml || echo UNKNOWN)" - fi - echo "CHARM_NAME=$CHARM_NAME" >> $GITHUB_ENV - - args="${{ steps.plan-integration.outputs.args }}" - echo "ARGS=$args" >> $GITHUB_ENV - allure_artifact_suffix=$(uuidgen) series="" if [ ! -z ${{ matrix.series }} ]; then @@ -204,16 +190,11 @@ jobs: echo "ALLURE_ARTIFACT_SUFFIX=$allure_artifact_suffix" >> $GITHUB_ENV - name: Install pytest plugin for Allure run: pip install git+https://github.com/canonical/operator-workflows@ISD-2620-allure#subdirectory=python/pytest_plugins/allure_pytest_collection_report - - name: Collect tests for Allure (k8s) - working-directory: ${{ inputs.working-directory }} - if: ${{ inputs.provider == 'microk8s' }} - run: | - tox -e ${{ inputs.test-tox-env }} -- --model testing --keep-models ${{ env.SERIES }} ${{ env.MODULE }} ${{ env.ARGS }} --allure-collection-dir=allure-default ${{ inputs.extra-arguments }} ${{ secrets.INTEGRATION_TEST_ARGS }} - - name: Collect tests for Allure (lxd) + - name: Collect tests for Allure working-directory: ${{ inputs.working-directory }} if: ${{ inputs.provider == 'lxd' }} run: | - tox -e ${{ inputs.test-tox-env }} -- --keep-models ${{ env.SERIES }} ${{ env.MODULE }} ${{ env.ARGS }} --allure-collection-dir=allure-default ${{ inputs.extra-arguments }} ${{ secrets.INTEGRATION_TEST_ARGS }} + tox -e ${{ inputs.test-tox-env }} -- --keep-models ${{ env.SERIES }} ${{ env.MODULE }} --allure-collection-dir=allure-default ${{ inputs.extra-arguments }} ${{ secrets.INTEGRATION_TEST_ARGS }} - name: Upload Default Allure results timeout-minutes: 3 uses: actions/upload-artifact@v4 @@ -315,7 +296,22 @@ jobs: VAULT_APPROLE_SECRET_ID: ${{ secrets.VAULT_APPROLE_SECRET_ID }} - run: sudo apt install skopeo -y + - name: Plan Integration + uses: canonical/operator-workflows/internal/plan-integration@main + id: plan-integration + with: + plan: ${{ inputs.plan }} + - name: Charm name setting + working-directory: ${{ inputs.working-directory }}/${{ inputs.charm-directory }} + run: | + CHARM_NAME="$([ -f metadata.yaml ] && yq '.name' metadata.yaml || echo UNKNOWN)" + if [ "$CHARM_NAME" == "UNKNOWN" ]; then + CHARM_NAME="$([ -f charmcraft.yaml ] && yq '.name' charmcraft.yaml || echo UNKNOWN)" + fi + echo "CHARM_NAME=$CHARM_NAME" >> $GITHUB_ENV + args="${{ steps.plan-integration.outputs.args }}" + echo "ARGS=$args" >> $GITHUB_ENV - name: Run k8s integration tests working-directory: ${{ inputs.working-directory }} if: ${{ inputs.provider == 'microk8s' }} diff --git a/.licenserc.yaml b/.licenserc.yaml index 291d80eb6..ca8c7a317 100644 --- a/.licenserc.yaml +++ b/.licenserc.yaml @@ -32,4 +32,5 @@ header: - 'dist/**' - 'internal/**' - 'tests/workflows/integration/**' + - 'python/**/**/pyproject.toml' comment: on-failure diff --git a/python/cli/allure_add_default_for_missing_results/__init__.py b/python/cli/allure_add_default_for_missing_results/__init__.py index e69de29bb..e3979c0f6 100644 --- a/python/cli/allure_add_default_for_missing_results/__init__.py +++ b/python/cli/allure_add_default_for_missing_results/__init__.py @@ -0,0 +1,2 @@ +# Copyright 2024 Canonical Ltd. +# See LICENSE file for licensing details. diff --git a/python/cli/allure_add_default_for_missing_results/allure_add_default_for_missing_results.py b/python/cli/allure_add_default_for_missing_results/allure_add_default_for_missing_results.py index 9d9de9255..14d2b6bad 100644 --- a/python/cli/allure_add_default_for_missing_results/allure_add_default_for_missing_results.py +++ b/python/cli/allure_add_default_for_missing_results/allure_add_default_for_missing_results.py @@ -1,3 +1,5 @@ +# Copyright 2024 Canonical Ltd. +# See LICENSE file for licensing details. import argparse import dataclasses import json diff --git a/python/pytest_plugins/allure_pytest_collection_report/__init__.py b/python/pytest_plugins/allure_pytest_collection_report/__init__.py index e69de29bb..e3979c0f6 100644 --- a/python/pytest_plugins/allure_pytest_collection_report/__init__.py +++ b/python/pytest_plugins/allure_pytest_collection_report/__init__.py @@ -0,0 +1,2 @@ +# Copyright 2024 Canonical Ltd. +# See LICENSE file for licensing details. diff --git a/python/pytest_plugins/allure_pytest_collection_report/_plugin.py b/python/pytest_plugins/allure_pytest_collection_report/_plugin.py index b96f762b7..1832d0575 100644 --- a/python/pytest_plugins/allure_pytest_collection_report/_plugin.py +++ b/python/pytest_plugins/allure_pytest_collection_report/_plugin.py @@ -1,3 +1,5 @@ +# Copyright 2024 Canonical Ltd. +# See LICENSE file for licensing details. # Upstream feature request to replace this plugin: # https://github.com/allure-framework/allure-python/issues/821 From bd05b70c023d581d3438cfebdfeba6b3cec8a1ec Mon Sep 17 00:00:00 2001 From: Swetha Swaminathan Date: Fri, 6 Dec 2024 21:07:56 +0530 Subject: [PATCH 20/52] adding default result --- .github/workflows/integration_test_run.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/integration_test_run.yaml b/.github/workflows/integration_test_run.yaml index cc2f2995b..900a6af1f 100644 --- a/.github/workflows/integration_test_run.yaml +++ b/.github/workflows/integration_test_run.yaml @@ -172,7 +172,6 @@ jobs: }} steps: - name: Integration tests variable setting - working-directory: ${{ inputs.working-directory }}/${{ inputs.charm-directory }} run: | allure_artifact_suffix=$(uuidgen) series="" From 72aec57c993add818aa8613e58434d1b2e364480 Mon Sep 17 00:00:00 2001 From: Swetha Swaminathan Date: Fri, 6 Dec 2024 21:24:32 +0530 Subject: [PATCH 21/52] adding default result --- .../{ => allure_pytest_collection_report}/__init__.py | 0 .../{ => allure_pytest_collection_report}/_plugin.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename python/pytest_plugins/allure_pytest_collection_report/{ => allure_pytest_collection_report}/__init__.py (100%) rename python/pytest_plugins/allure_pytest_collection_report/{ => allure_pytest_collection_report}/_plugin.py (100%) diff --git a/python/pytest_plugins/allure_pytest_collection_report/__init__.py b/python/pytest_plugins/allure_pytest_collection_report/allure_pytest_collection_report/__init__.py similarity index 100% rename from python/pytest_plugins/allure_pytest_collection_report/__init__.py rename to python/pytest_plugins/allure_pytest_collection_report/allure_pytest_collection_report/__init__.py diff --git a/python/pytest_plugins/allure_pytest_collection_report/_plugin.py b/python/pytest_plugins/allure_pytest_collection_report/allure_pytest_collection_report/_plugin.py similarity index 100% rename from python/pytest_plugins/allure_pytest_collection_report/_plugin.py rename to python/pytest_plugins/allure_pytest_collection_report/allure_pytest_collection_report/_plugin.py From bb9a889d6f1cc952d6701e06a0714dc20c1f3d6f Mon Sep 17 00:00:00 2001 From: Swetha Swaminathan Date: Fri, 6 Dec 2024 21:33:28 +0530 Subject: [PATCH 22/52] adding default result --- .github/workflows/integration_test_run.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/integration_test_run.yaml b/.github/workflows/integration_test_run.yaml index 900a6af1f..aeebc37d5 100644 --- a/.github/workflows/integration_test_run.yaml +++ b/.github/workflows/integration_test_run.yaml @@ -191,7 +191,6 @@ jobs: run: pip install git+https://github.com/canonical/operator-workflows@ISD-2620-allure#subdirectory=python/pytest_plugins/allure_pytest_collection_report - name: Collect tests for Allure working-directory: ${{ inputs.working-directory }} - if: ${{ inputs.provider == 'lxd' }} run: | tox -e ${{ inputs.test-tox-env }} -- --keep-models ${{ env.SERIES }} ${{ env.MODULE }} --allure-collection-dir=allure-default ${{ inputs.extra-arguments }} ${{ secrets.INTEGRATION_TEST_ARGS }} - name: Upload Default Allure results From 4ddf4a2b14d8e675f8e80566a42122bbfc1c017a Mon Sep 17 00:00:00 2001 From: Swetha Swaminathan Date: Mon, 9 Dec 2024 10:59:00 +0530 Subject: [PATCH 23/52] adding default result --- .github/workflows/integration_test_run.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/integration_test_run.yaml b/.github/workflows/integration_test_run.yaml index aeebc37d5..e7463a2d0 100644 --- a/.github/workflows/integration_test_run.yaml +++ b/.github/workflows/integration_test_run.yaml @@ -171,6 +171,7 @@ jobs: )) || inputs.runs-on }} steps: + - uses: actions/checkout@v4.2.2 - name: Integration tests variable setting run: | allure_artifact_suffix=$(uuidgen) From 9726ee0a4bd6222db6ed9f7d551d8dd6385c8ede Mon Sep 17 00:00:00 2001 From: Swetha Swaminathan Date: Mon, 9 Dec 2024 11:17:08 +0530 Subject: [PATCH 24/52] adding default result --- .github/workflows/integration_test_run.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/integration_test_run.yaml b/.github/workflows/integration_test_run.yaml index e7463a2d0..7813477d7 100644 --- a/.github/workflows/integration_test_run.yaml +++ b/.github/workflows/integration_test_run.yaml @@ -190,6 +190,9 @@ jobs: echo "ALLURE_ARTIFACT_SUFFIX=$allure_artifact_suffix" >> $GITHUB_ENV - name: Install pytest plugin for Allure run: pip install git+https://github.com/canonical/operator-workflows@ISD-2620-allure#subdirectory=python/pytest_plugins/allure_pytest_collection_report + - name: Install tox + run: | + pip install tox - name: Collect tests for Allure working-directory: ${{ inputs.working-directory }} run: | From ee58e40189ecac9d3d2a6d9ab3c0eb08c255b0cd Mon Sep 17 00:00:00 2001 From: Swetha Swaminathan Date: Mon, 9 Dec 2024 11:26:44 +0530 Subject: [PATCH 25/52] adding default result --- .github/workflows/integration_test_run.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/integration_test_run.yaml b/.github/workflows/integration_test_run.yaml index 7813477d7..5d869db82 100644 --- a/.github/workflows/integration_test_run.yaml +++ b/.github/workflows/integration_test_run.yaml @@ -189,6 +189,7 @@ jobs: echo "MODULE=$module" >> $GITHUB_ENV echo "ALLURE_ARTIFACT_SUFFIX=$allure_artifact_suffix" >> $GITHUB_ENV - name: Install pytest plugin for Allure + working-directory: ${{ inputs.working-directory }} run: pip install git+https://github.com/canonical/operator-workflows@ISD-2620-allure#subdirectory=python/pytest_plugins/allure_pytest_collection_report - name: Install tox run: | From cbc8acefdc2503a3141ad6f2e74b0852f1b8d338 Mon Sep 17 00:00:00 2001 From: Swetha Swaminathan Date: Mon, 9 Dec 2024 11:40:47 +0530 Subject: [PATCH 26/52] adding default result --- .github/workflows/integration_test.yaml | 2 +- .github/workflows/integration_test_run.yaml | 3 --- tests/workflows/integration/test-upload-charm/tox.ini | 1 + 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/integration_test.yaml b/.github/workflows/integration_test.yaml index 18fa972cf..bc721d136 100644 --- a/.github/workflows/integration_test.yaml +++ b/.github/workflows/integration_test.yaml @@ -312,7 +312,7 @@ jobs: integration-test: name: Integration tests uses: ./.github/workflows/integration_test_run.yaml - needs: [ plan, build ] + needs: [ plan ] if: always() && needs.plan.result == 'success' && (needs.build.result == 'success' || toJSON(fromJSON(needs.plan.outputs.plan).build) == '[]') secrets: inherit with: diff --git a/.github/workflows/integration_test_run.yaml b/.github/workflows/integration_test_run.yaml index 5d869db82..ad5cf518e 100644 --- a/.github/workflows/integration_test_run.yaml +++ b/.github/workflows/integration_test_run.yaml @@ -188,9 +188,6 @@ jobs: fi echo "MODULE=$module" >> $GITHUB_ENV echo "ALLURE_ARTIFACT_SUFFIX=$allure_artifact_suffix" >> $GITHUB_ENV - - name: Install pytest plugin for Allure - working-directory: ${{ inputs.working-directory }} - run: pip install git+https://github.com/canonical/operator-workflows@ISD-2620-allure#subdirectory=python/pytest_plugins/allure_pytest_collection_report - name: Install tox run: | pip install tox diff --git a/tests/workflows/integration/test-upload-charm/tox.ini b/tests/workflows/integration/test-upload-charm/tox.ini index 1a868e5b6..1698dee21 100644 --- a/tests/workflows/integration/test-upload-charm/tox.ini +++ b/tests/workflows/integration/test-upload-charm/tox.ini @@ -104,6 +104,7 @@ commands = [testenv:integration] description = Run integration tests deps = + git+https://github.com/canonical/operator-workflows@ISD-2620-allure#subdirectory=python/pytest_plugins/allure_pytest_collection_report juju==2.9.42.4 pytest pytest-operator From e31c15001a26b31d996777ffb60859710468996b Mon Sep 17 00:00:00 2001 From: Swetha Swaminathan Date: Mon, 9 Dec 2024 11:42:24 +0530 Subject: [PATCH 27/52] adding default result --- .github/workflows/integration_test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/integration_test.yaml b/.github/workflows/integration_test.yaml index bc721d136..284abc6a9 100644 --- a/.github/workflows/integration_test.yaml +++ b/.github/workflows/integration_test.yaml @@ -313,7 +313,7 @@ jobs: name: Integration tests uses: ./.github/workflows/integration_test_run.yaml needs: [ plan ] - if: always() && needs.plan.result == 'success' && (needs.build.result == 'success' || toJSON(fromJSON(needs.plan.outputs.plan).build) == '[]') + if: always() secrets: inherit with: channel: ${{ inputs.channel }} From cb3a9cca42d60fc651d98e7abbdd3b47da34d977 Mon Sep 17 00:00:00 2001 From: Swetha Swaminathan Date: Mon, 9 Dec 2024 11:58:59 +0530 Subject: [PATCH 28/52] adding default result --- tests/workflows/integration/test-upload-charm/requirements.txt | 3 ++- tests/workflows/integration/test-upload-charm/tox.ini | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/workflows/integration/test-upload-charm/requirements.txt b/tests/workflows/integration/test-upload-charm/requirements.txt index f9239509b..3c7718578 100644 --- a/tests/workflows/integration/test-upload-charm/requirements.txt +++ b/tests/workflows/integration/test-upload-charm/requirements.txt @@ -1,2 +1,3 @@ ops >= 1.5.0 -allure-pytest>=2.8.18 \ No newline at end of file +allure-pytest>=2.8.18 +git+https://github.com/canonical/operator-workflows@ISD-2620-allure#subdirectory=python/pytest_plugins/allure_pytest_collection_report \ No newline at end of file diff --git a/tests/workflows/integration/test-upload-charm/tox.ini b/tests/workflows/integration/test-upload-charm/tox.ini index 1698dee21..1a868e5b6 100644 --- a/tests/workflows/integration/test-upload-charm/tox.ini +++ b/tests/workflows/integration/test-upload-charm/tox.ini @@ -104,7 +104,6 @@ commands = [testenv:integration] description = Run integration tests deps = - git+https://github.com/canonical/operator-workflows@ISD-2620-allure#subdirectory=python/pytest_plugins/allure_pytest_collection_report juju==2.9.42.4 pytest pytest-operator From 8da2658d609ef9b1ce16314910a18be862bf062c Mon Sep 17 00:00:00 2001 From: Swetha Swaminathan Date: Mon, 9 Dec 2024 12:01:20 +0530 Subject: [PATCH 29/52] adding default result --- .github/workflows/integration_test.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/integration_test.yaml b/.github/workflows/integration_test.yaml index 284abc6a9..18fa972cf 100644 --- a/.github/workflows/integration_test.yaml +++ b/.github/workflows/integration_test.yaml @@ -312,8 +312,8 @@ jobs: integration-test: name: Integration tests uses: ./.github/workflows/integration_test_run.yaml - needs: [ plan ] - if: always() + needs: [ plan, build ] + if: always() && needs.plan.result == 'success' && (needs.build.result == 'success' || toJSON(fromJSON(needs.plan.outputs.plan).build) == '[]') secrets: inherit with: channel: ${{ inputs.channel }} From 3baff1f340a09c7a2121329aceb2e72022ddce03 Mon Sep 17 00:00:00 2001 From: Swetha Swaminathan Date: Mon, 9 Dec 2024 12:38:04 +0530 Subject: [PATCH 30/52] adding default result --- tests/workflows/integration/test-upload-charm/requirements.txt | 3 +-- .../integration/test-upload-charm/test-requirements.txt | 1 + tests/workflows/integration/test-upload-charm/tox.ini | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 tests/workflows/integration/test-upload-charm/test-requirements.txt diff --git a/tests/workflows/integration/test-upload-charm/requirements.txt b/tests/workflows/integration/test-upload-charm/requirements.txt index 3c7718578..f9239509b 100644 --- a/tests/workflows/integration/test-upload-charm/requirements.txt +++ b/tests/workflows/integration/test-upload-charm/requirements.txt @@ -1,3 +1,2 @@ ops >= 1.5.0 -allure-pytest>=2.8.18 -git+https://github.com/canonical/operator-workflows@ISD-2620-allure#subdirectory=python/pytest_plugins/allure_pytest_collection_report \ No newline at end of file +allure-pytest>=2.8.18 \ No newline at end of file diff --git a/tests/workflows/integration/test-upload-charm/test-requirements.txt b/tests/workflows/integration/test-upload-charm/test-requirements.txt new file mode 100644 index 000000000..829504a07 --- /dev/null +++ b/tests/workflows/integration/test-upload-charm/test-requirements.txt @@ -0,0 +1 @@ +git+https://github.com/canonical/operator-workflows@ISD-2620-allure#subdirectory=python/pytest_plugins/allure_pytest_collection_report \ No newline at end of file diff --git a/tests/workflows/integration/test-upload-charm/tox.ini b/tests/workflows/integration/test-upload-charm/tox.ini index 1a868e5b6..d8a614c4c 100644 --- a/tests/workflows/integration/test-upload-charm/tox.ini +++ b/tests/workflows/integration/test-upload-charm/tox.ini @@ -112,6 +112,7 @@ deps = macaroonbakery==1.3.2 websockets<14.0 # https://github.com/juju/python-libjuju/issues/1184 -r{toxinidir}/requirements.txt + -r{toxinidir}/test-requirements.txt commands = pytest -v --tb native --ignore={[vars]tst_path}unit --log-cli-level=INFO -s {posargs} @@ -126,5 +127,6 @@ deps = macaroonbakery==1.3.2 websockets<14.0 # https://github.com/juju/python-libjuju/issues/1184 -r{toxinidir}/requirements.txt + -r{toxinidir}/test-requirements.txt commands = pytest -v --tb native --ignore={[vars]tst_path}unit --log-cli-level=INFO -s {posargs} From d7eee35878d63a1d4b6c95b80f99939f46f2ef5d Mon Sep 17 00:00:00 2001 From: Swetha Swaminathan Date: Mon, 9 Dec 2024 13:51:56 +0530 Subject: [PATCH 31/52] adding default result --- python/cli/pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/cli/pyproject.toml b/python/cli/pyproject.toml index 257a49600..2fc63df2c 100644 --- a/python/cli/pyproject.toml +++ b/python/cli/pyproject.toml @@ -1,5 +1,5 @@ [tool.poetry] -name = "workflows-cli" +name = "allure_add_default_for_missing_results" # Version unused; repository has its own versioning system. (See .github/workflows/__release.yaml) version = "0.1.0" description = "" @@ -8,7 +8,7 @@ authors = ["Carl Csaposs "] readme = "README.md" [tool.poetry.scripts] -allure-add-default-for-missing-results = "workflows_cli.allure_add_default_for_missing_results:main" +allure-add-default-for-missing-results = "allure_add_default_for_missing_results.allure_add_default_for_missing_results:main" [tool.poetry.dependencies] python = "^3.10" From c691921bd68a821def88a9b402497776f85dfa18 Mon Sep 17 00:00:00 2001 From: Swetha Swaminathan Date: Mon, 9 Dec 2024 16:02:11 +0530 Subject: [PATCH 32/52] adding default result --- .github/workflows/integration_test_run.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/integration_test_run.yaml b/.github/workflows/integration_test_run.yaml index ad5cf518e..e18c12fcb 100644 --- a/.github/workflows/integration_test_run.yaml +++ b/.github/workflows/integration_test_run.yaml @@ -200,7 +200,7 @@ jobs: uses: actions/upload-artifact@v4 with: name: allure-default-results-${{ env.ALLURE_ARTIFACT_SUFFIX }} - path: allure-default/ + path: ${{ inputs.working-directory }}allure-default/ - name: Setup operator environment uses: charmed-kubernetes/actions-operator@main with: From b2ef7aedefcf70251d7b8e2f9543f11436b5d19d Mon Sep 17 00:00:00 2001 From: Swetha Swaminathan Date: Mon, 9 Dec 2024 21:28:59 +0530 Subject: [PATCH 33/52] testing operator workflows --- .github/workflows/integration_test_allure.md | 4 ++++ .github/workflows/integration_test_run.yaml | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/integration_test_allure.md diff --git a/.github/workflows/integration_test_allure.md b/.github/workflows/integration_test_allure.md new file mode 100644 index 000000000..8fcb4a803 --- /dev/null +++ b/.github/workflows/integration_test_allure.md @@ -0,0 +1,4 @@ +# Allure Report Integration + +[Allure Report](https://allurereport.org/) for [integration_test_run.yaml](https://github.com/canonical/operator-workflows?tab=readme-ov-file#integration-test-workflow-canonicaloperator-workflowsgithubworkflowsintegration_testyamlmain) + diff --git a/.github/workflows/integration_test_run.yaml b/.github/workflows/integration_test_run.yaml index e18c12fcb..432083aa4 100644 --- a/.github/workflows/integration_test_run.yaml +++ b/.github/workflows/integration_test_run.yaml @@ -178,13 +178,13 @@ jobs: series="" if [ ! -z ${{ matrix.series }} ]; then series="--series ${{ matrix.series }}" - allure_artifact_suffix=$(allure_artifact_suffix)-${{ matrix.series }} + allure_artifact_suffix="$allure_artifact_suffix"-${{ matrix.series }} fi echo "SERIES=$series" >> $GITHUB_ENV module="" if [ ! -z ${{ matrix.modules }} ]; then module="-k ${{ matrix.modules }}" - allure_artifact_suffix=$(allure_artifact_suffix)-${{ matrix.modules }} + allure_artifact_suffix="$allure_artifact_suffix"-${{ matrix.modules }} fi echo "MODULE=$module" >> $GITHUB_ENV echo "ALLURE_ARTIFACT_SUFFIX=$allure_artifact_suffix" >> $GITHUB_ENV From ba02d1995e7b8440827259a4c948871bce679c16 Mon Sep 17 00:00:00 2001 From: Swetha Swaminathan Date: Tue, 10 Dec 2024 11:29:39 +0530 Subject: [PATCH 34/52] testing --- .github/workflows/allure_report.yaml | 13 +--- .github/workflows/image.png | Bin 0 -> 81077 bytes .github/workflows/integration_test_allure.md | 65 +++++++++++++++++++ 3 files changed, 67 insertions(+), 11 deletions(-) create mode 100644 .github/workflows/image.png diff --git a/.github/workflows/allure_report.yaml b/.github/workflows/allure_report.yaml index 7afb2bba1..b2883cf09 100644 --- a/.github/workflows/allure_report.yaml +++ b/.github/workflows/allure_report.yaml @@ -13,7 +13,7 @@ jobs: if: always() && !cancelled() steps: - name: Download Allure - # Following instructions from https://allurereport.org/docs/gettingstarted-installation/#install-via-the-system-package-manager-for-linux + # Following instructions from https://allurereport.org/docs/install-for-linux/#install-from-a-deb-package run: gh release download --repo allure-framework/allure2 --pattern 'allure_*.deb' env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -21,15 +21,6 @@ jobs: run: | sudo apt-get update sudo apt-get install ./allure_*.deb -y - # For first run, manually create branch with no history - # (e.g. - # git checkout --orphan gh-pages - # git rm -rf . - # touch .nojekyll - # git add .nojekyll - # git commit -m "Initial commit" - # git push origin gh-pages - # ) - name: Checkout GitHub pages branch uses: actions/checkout@v4 with: @@ -48,7 +39,7 @@ jobs: pattern: allure-results* merge-multiple: true - name: Install CLI - run: pipx install git+https://github.com/canonical/operator-workflows@ISD-2620-allure#subdirectory=python/cli + run: pipx install git+https://github.com/canonical/operator-workflows@${{ github.ref_name }}#subdirectory=python/cli - name: Combine Allure default results & actual results # For every test: if actual result available, use that. Otherwise, use default result # So that, if actual result not available, Allure report will show "unknown"/"failed" test result diff --git a/.github/workflows/image.png b/.github/workflows/image.png new file mode 100644 index 0000000000000000000000000000000000000000..4822691beb99c90c70c7426e21f2b095cb9862a3 GIT binary patch literal 81077 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 SZ0QCDAn=2.8.18 +``` + +Create a seperate file `test-requirements.txt` in the same location and add the following - + +``` +git+https://github.com/canonical/operator-workflows@main#subdirectory=python/pytest_plugins/allure_pytest_collection_report +``` + +Add the following line under the dependencies `deps` for the integration test inside `tox.ini` - + +``` +-r{toxinidir}/test-requirements.txt +``` + +## 2. Calling the allure-workflow + +Add the following lines at the end of the workflow that runs the integrations tests by calling the reuable workflow [integration-test.yaml](https://github.com/canonical/operator-workflows/blob/main/.github/workflows/integration_test.yaml): + +``` + allure-report: + if: always() && !cancelled() + needs: + - [list of jobs with tests you would like to visualize] + uses: canonical/operator-workflows/.github/workflows/allure_report.yaml@main +``` + +**NOTE:** If the workflow is being called inside a matrix with the same test modules run with different parameters, the allure report will only display the results of the last combination. + +## 3. Changing branch permissions + +**NOTE:** For this, you would require admin access to the repository. + +- Go to the repository's **Settings > Branches** and next to Branch protection rules, select **Add rule** +- Enter the branch name **gh-pages** and select **Allow force pushes** and click **Save changes** + +## 4. Github pages branch + +- Create `gh-pages` branch: + +``` +# For first run, manually create branch with no history + # (e.g. + # git checkout --orphan gh-pages + # git rm -rf . + # touch .nojekyll + # git add .nojekyll + # git commit -m "Initial commit" + # git push origin gh-pages + # ) + ``` + + - Enable gh pages publishing at ** Settings > Pages ** and set branch name as `gh-pages`: + + ![alt text](image.png) + + + Example: \ No newline at end of file From 08e2a7d308c206f66a6cda9f92e3c22767319897 Mon Sep 17 00:00:00 2001 From: Swetha Swaminathan Date: Tue, 10 Dec 2024 12:41:55 +0530 Subject: [PATCH 35/52] testing # --- .github/workflows/allure_report.yaml | 2 +- python/cli/pyproject.toml | 1 - .../allure_pytest_collection_report/pyproject.toml | 1 - .../integration/test-upload-charm/test-requirements.txt | 1 - tests/workflows/integration/test-upload-charm/tox.ini | 2 +- 5 files changed, 2 insertions(+), 5 deletions(-) delete mode 100644 tests/workflows/integration/test-upload-charm/test-requirements.txt diff --git a/.github/workflows/allure_report.yaml b/.github/workflows/allure_report.yaml index b2883cf09..999fa1d43 100644 --- a/.github/workflows/allure_report.yaml +++ b/.github/workflows/allure_report.yaml @@ -39,7 +39,7 @@ jobs: pattern: allure-results* merge-multiple: true - name: Install CLI - run: pipx install git+https://github.com/canonical/operator-workflows@${{ github.ref_name }}#subdirectory=python/cli + run: pipx install git+https://github.com/canonical/operator-workflows@ISD-2620-allure#subdirectory=python/cli - name: Combine Allure default results & actual results # For every test: if actual result available, use that. Otherwise, use default result # So that, if actual result not available, Allure report will show "unknown"/"failed" test result diff --git a/python/cli/pyproject.toml b/python/cli/pyproject.toml index 2fc63df2c..ad7ef259b 100644 --- a/python/cli/pyproject.toml +++ b/python/cli/pyproject.toml @@ -1,6 +1,5 @@ [tool.poetry] name = "allure_add_default_for_missing_results" -# Version unused; repository has its own versioning system. (See .github/workflows/__release.yaml) version = "0.1.0" description = "" license = "Apache-2.0" diff --git a/python/pytest_plugins/allure_pytest_collection_report/pyproject.toml b/python/pytest_plugins/allure_pytest_collection_report/pyproject.toml index 03e6074fe..7c721432c 100644 --- a/python/pytest_plugins/allure_pytest_collection_report/pyproject.toml +++ b/python/pytest_plugins/allure_pytest_collection_report/pyproject.toml @@ -1,6 +1,5 @@ [tool.poetry] name = "allure_pytest_collection_report" -# Version unused; repository has its own versioning system. (See .github/workflows/__release.yaml) version = "0.1.0" description = "" authors = ["Carl Csaposs "] diff --git a/tests/workflows/integration/test-upload-charm/test-requirements.txt b/tests/workflows/integration/test-upload-charm/test-requirements.txt deleted file mode 100644 index 829504a07..000000000 --- a/tests/workflows/integration/test-upload-charm/test-requirements.txt +++ /dev/null @@ -1 +0,0 @@ -git+https://github.com/canonical/operator-workflows@ISD-2620-allure#subdirectory=python/pytest_plugins/allure_pytest_collection_report \ No newline at end of file diff --git a/tests/workflows/integration/test-upload-charm/tox.ini b/tests/workflows/integration/test-upload-charm/tox.ini index d8a614c4c..4dfdf4616 100644 --- a/tests/workflows/integration/test-upload-charm/tox.ini +++ b/tests/workflows/integration/test-upload-charm/tox.ini @@ -112,7 +112,7 @@ deps = macaroonbakery==1.3.2 websockets<14.0 # https://github.com/juju/python-libjuju/issues/1184 -r{toxinidir}/requirements.txt - -r{toxinidir}/test-requirements.txt + git+https://github.com/canonical/operator-workflows@ISD-2620-allure\#subdirectory=python/pytest_plugins/allure_pytest_collection_report commands = pytest -v --tb native --ignore={[vars]tst_path}unit --log-cli-level=INFO -s {posargs} From 3bf15ca19e9e627a408b77f2e8bb32e1401b2dfd Mon Sep 17 00:00:00 2001 From: Swetha Swaminathan Date: Tue, 10 Dec 2024 13:54:21 +0530 Subject: [PATCH 36/52] testing --- .github/workflows/image.png | Bin 81077 -> 0 bytes .github/workflows/integration_test_allure.md | 14 ++++---------- .../integration/test-upload-charm/tox.ini | 2 +- 3 files changed, 5 insertions(+), 11 deletions(-) delete mode 100644 .github/workflows/image.png diff --git a/.github/workflows/image.png b/.github/workflows/image.png deleted file mode 100644 index 4822691beb99c90c70c7426e21f2b095cb9862a3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 81077 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 SZ0QCDAn=2.8.18 ``` -Create a seperate file `test-requirements.txt` in the same location and add the following - +Add the following line under the dependencies (`deps`) in the integration section inside `tox.ini` - ``` -git+https://github.com/canonical/operator-workflows@main#subdirectory=python/pytest_plugins/allure_pytest_collection_report -``` - -Add the following line under the dependencies `deps` for the integration test inside `tox.ini` - - -``` --r{toxinidir}/test-requirements.txt +git+https://github.com/canonical/operator-workflows@main\#subdirectory=python/pytest_plugins/allure_pytest_collection_report ``` ## 2. Calling the allure-workflow @@ -36,6 +30,8 @@ Add the following lines at the end of the workflow that runs the integrations te uses: canonical/operator-workflows/.github/workflows/allure_report.yaml@main ``` +Here's an [example for the above](https://github.com/canonical/github-runner-operator/pull/412). + **NOTE:** If the workflow is being called inside a matrix with the same test modules run with different parameters, the allure report will only display the results of the last combination. ## 3. Changing branch permissions @@ -63,7 +59,5 @@ Add the following lines at the end of the workflow that runs the integrations te - Enable gh pages publishing at ** Settings > Pages ** and set branch name as `gh-pages`: - ![alt text](image.png) - Example: \ No newline at end of file diff --git a/tests/workflows/integration/test-upload-charm/tox.ini b/tests/workflows/integration/test-upload-charm/tox.ini index 4dfdf4616..b6353b92e 100644 --- a/tests/workflows/integration/test-upload-charm/tox.ini +++ b/tests/workflows/integration/test-upload-charm/tox.ini @@ -127,6 +127,6 @@ deps = macaroonbakery==1.3.2 websockets<14.0 # https://github.com/juju/python-libjuju/issues/1184 -r{toxinidir}/requirements.txt - -r{toxinidir}/test-requirements.txt + git+https://github.com/canonical/operator-workflows@ISD-2620-allure\#subdirectory=python/pytest_plugins/allure_pytest_collection_report commands = pytest -v --tb native --ignore={[vars]tst_path}unit --log-cli-level=INFO -s {posargs} From b3c486acadcff8dfbde7b024fc63a160c80a1eeb Mon Sep 17 00:00:00 2001 From: Swetha Swaminathan Date: Tue, 10 Dec 2024 13:56:54 +0530 Subject: [PATCH 37/52] testing --- .github/workflows/integration_test_run.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/integration_test_run.yaml b/.github/workflows/integration_test_run.yaml index 432083aa4..24d733922 100644 --- a/.github/workflows/integration_test_run.yaml +++ b/.github/workflows/integration_test_run.yaml @@ -190,7 +190,7 @@ jobs: echo "ALLURE_ARTIFACT_SUFFIX=$allure_artifact_suffix" >> $GITHUB_ENV - name: Install tox run: | - pip install tox + pipx install tox - name: Collect tests for Allure working-directory: ${{ inputs.working-directory }} run: | From 177f6f4d8f2e6adeb9fe3e980691d239570b233f Mon Sep 17 00:00:00 2001 From: Swetha Swaminathan Date: Tue, 10 Dec 2024 14:01:26 +0530 Subject: [PATCH 38/52] testing --- .github/workflows/workflow_test.yaml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/workflow_test.yaml b/.github/workflows/workflow_test.yaml index f9b9b7c56..88425cab4 100644 --- a/.github/workflows/workflow_test.yaml +++ b/.github/workflows/workflow_test.yaml @@ -103,6 +103,14 @@ jobs: integration-test-workflow-file: workflow_test.yaml working-directory: tests/workflows/integration/test-upload-charm/ workflow-run-id: ${{ github.run_id }} + allure-report: + if: always() && !cancelled() + needs: + - integration + - integration-juju3 + - integration-artifact + - integration-self-hosted + uses: ./.github/workflows/allure_report.yaml check: runs-on: ubuntu-latest if: always() && !cancelled() @@ -119,6 +127,7 @@ jobs: - integration-craft - publish - publish-artifact + - allure-report steps: - run: | [ '${{ needs.simple.result }}' = 'success' ] || (echo simple failed && false) @@ -132,14 +141,5 @@ jobs: [ '${{ needs.integration-craft.result }}' = 'success' ] || (echo integration-craft failed && false) [ '${{ needs.publish.result }}' != 'failure' ] || (echo publish failed && false) [ '${{ needs.publish-artifact.result }}' != 'failure' ] || (echo publish failed && false) - allure-report: - if: always() && !cancelled() - needs: - - integration - - integration-juju3 - - integration-artifact - - integration-self-hosted - - integration-rock - - integration-rock-artifact - - integration-craft - uses: ./.github/workflows/allure_report.yaml + [ '${{ needs.allure-report.result }}' != 'failure' ] || (echo allure-report failed && false) + From 53e0331547d6dd1c4e22687ecd8a7fb9c40f77db Mon Sep 17 00:00:00 2001 From: swetha1654 Date: Tue, 10 Dec 2024 14:14:48 +0530 Subject: [PATCH 39/52] Update integration_test_allure.md Signed-off-by: swetha1654 --- .github/workflows/integration_test_allure.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/integration_test_allure.md b/.github/workflows/integration_test_allure.md index 58f27a473..9d1f0ad74 100644 --- a/.github/workflows/integration_test_allure.md +++ b/.github/workflows/integration_test_allure.md @@ -59,5 +59,6 @@ Here's an [example for the above](https://github.com/canonical/github-runner-ope - Enable gh pages publishing at ** Settings > Pages ** and set branch name as `gh-pages`: +image - Example: \ No newline at end of file + [Example PR for steps 1 & 2](https://github.com/canonical/github-runner-operator/pull/412/files#) From d8b4254f020f3dc41ecc6523c739bdb307b593a7 Mon Sep 17 00:00:00 2001 From: Swetha Swaminathan Date: Tue, 10 Dec 2024 16:21:40 +0530 Subject: [PATCH 40/52] remove python folder --- .github/workflows/allure_report.yaml | 4 +- .github/workflows/integration_test_run.yaml | 2 +- python/cli/README.md | 3 - .../__init__.py | 2 - .../allure_add_default_for_missing_results.py | 51 -------- python/cli/pyproject.toml | 20 --- .../allure_pytest_collection_report/README.md | 1 - .../__init__.py | 2 - .../_plugin.py | 122 ------------------ .../pyproject.toml | 22 ---- .../integration/test-upload-charm/tox.ini | 4 +- 11 files changed, 5 insertions(+), 228 deletions(-) delete mode 100644 python/cli/README.md delete mode 100644 python/cli/allure_add_default_for_missing_results/__init__.py delete mode 100644 python/cli/allure_add_default_for_missing_results/allure_add_default_for_missing_results.py delete mode 100644 python/cli/pyproject.toml delete mode 100644 python/pytest_plugins/allure_pytest_collection_report/README.md delete mode 100644 python/pytest_plugins/allure_pytest_collection_report/allure_pytest_collection_report/__init__.py delete mode 100644 python/pytest_plugins/allure_pytest_collection_report/allure_pytest_collection_report/_plugin.py delete mode 100644 python/pytest_plugins/allure_pytest_collection_report/pyproject.toml diff --git a/.github/workflows/allure_report.yaml b/.github/workflows/allure_report.yaml index 999fa1d43..06c6f85f3 100644 --- a/.github/workflows/allure_report.yaml +++ b/.github/workflows/allure_report.yaml @@ -39,7 +39,7 @@ jobs: pattern: allure-results* merge-multiple: true - name: Install CLI - run: pipx install git+https://github.com/canonical/operator-workflows@ISD-2620-allure#subdirectory=python/cli + run: pipx install git+https://github.com/canonical/data-platform-workflows@v24.0.0#subdirectory=python/cli - name: Combine Allure default results & actual results # For every test: if actual result available, use that. Otherwise, use default result # So that, if actual result not available, Allure report will show "unknown"/"failed" test result @@ -93,4 +93,4 @@ jobs: git config user.email "41898282+github-actions[bot]@users.noreply.github.com" git commit -m "Allure report ${{ github.run_number }}" # Uses token set in checkout step - git push origin gh-pages \ No newline at end of file + git push origin gh-pages diff --git a/.github/workflows/integration_test_run.yaml b/.github/workflows/integration_test_run.yaml index 24d733922..432083aa4 100644 --- a/.github/workflows/integration_test_run.yaml +++ b/.github/workflows/integration_test_run.yaml @@ -190,7 +190,7 @@ jobs: echo "ALLURE_ARTIFACT_SUFFIX=$allure_artifact_suffix" >> $GITHUB_ENV - name: Install tox run: | - pipx install tox + pip install tox - name: Collect tests for Allure working-directory: ${{ inputs.working-directory }} run: | diff --git a/python/cli/README.md b/python/cli/README.md deleted file mode 100644 index 5b6554569..000000000 --- a/python/cli/README.md +++ /dev/null @@ -1,3 +0,0 @@ -The `allure_add_default_for_missing_results` cli combines the Allure default results & actual results before generating the report. - -Currently, if the result of a test is not available (for reasons such as setup failure), allure omits the test. This CLI ensures that for every test, if actual result is available, it will use that. Otherwise, it uses the default result `unknown`. \ No newline at end of file diff --git a/python/cli/allure_add_default_for_missing_results/__init__.py b/python/cli/allure_add_default_for_missing_results/__init__.py deleted file mode 100644 index e3979c0f6..000000000 --- a/python/cli/allure_add_default_for_missing_results/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -# Copyright 2024 Canonical Ltd. -# See LICENSE file for licensing details. diff --git a/python/cli/allure_add_default_for_missing_results/allure_add_default_for_missing_results.py b/python/cli/allure_add_default_for_missing_results/allure_add_default_for_missing_results.py deleted file mode 100644 index 14d2b6bad..000000000 --- a/python/cli/allure_add_default_for_missing_results/allure_add_default_for_missing_results.py +++ /dev/null @@ -1,51 +0,0 @@ -# Copyright 2024 Canonical Ltd. -# See LICENSE file for licensing details. -import argparse -import dataclasses -import json -import pathlib - - -@dataclasses.dataclass(frozen=True) -class Result: - test_case_id: str - path: pathlib.Path - - def __eq__(self, other): - if not isinstance(other, type(self)): - return False - return self.test_case_id == other.test_case_id - - -def main(): - """Combine Allure default results & actual results - - For every test: if actual result available, use that. Otherwise, use default result - - So that, if actual result not available, Allure report will show "unknown"/"failed" test result - instead of omitting the test - """ - parser = argparse.ArgumentParser() - parser.add_argument("--allure-results-dir", required=True) - parser.add_argument("--allure-collection-default-results-dir", required=True) - args = parser.parse_args() - - actual_results = pathlib.Path(args.allure_results_dir) - default_results = pathlib.Path(args.allure_collection_default_results_dir) - - results: dict[pathlib.Path, set[Result]] = { - actual_results: set(), - default_results: set(), - } - for directory, results_ in results.items(): - for path in directory.glob("*-result.json"): - with path.open("r") as file: - id_ = json.load(file)["testCaseId"] - results_.add(Result(id_, path)) - - actual_results.mkdir(exist_ok=True) - - missing_results = results[default_results] - results[actual_results] - for default_result in missing_results: - # Move to `actual_results` directory - default_result.path.rename(actual_results / default_result.path.name) diff --git a/python/cli/pyproject.toml b/python/cli/pyproject.toml deleted file mode 100644 index ad7ef259b..000000000 --- a/python/cli/pyproject.toml +++ /dev/null @@ -1,20 +0,0 @@ -[tool.poetry] -name = "allure_add_default_for_missing_results" -version = "0.1.0" -description = "" -license = "Apache-2.0" -authors = ["Carl Csaposs "] -readme = "README.md" - -[tool.poetry.scripts] -allure-add-default-for-missing-results = "allure_add_default_for_missing_results.allure_add_default_for_missing_results:main" - -[tool.poetry.dependencies] -python = "^3.10" -pyyaml = "^6.0.1" -requests = "^2.31.0" - - -[build-system] -requires = ["poetry-core"] -build-backend = "poetry.core.masonry.api" \ No newline at end of file diff --git a/python/pytest_plugins/allure_pytest_collection_report/README.md b/python/pytest_plugins/allure_pytest_collection_report/README.md deleted file mode 100644 index e3a5e1b8b..000000000 --- a/python/pytest_plugins/allure_pytest_collection_report/README.md +++ /dev/null @@ -1 +0,0 @@ -The `allure-pytest` plugin marks all the test statuses as unknown during collection time. \ No newline at end of file diff --git a/python/pytest_plugins/allure_pytest_collection_report/allure_pytest_collection_report/__init__.py b/python/pytest_plugins/allure_pytest_collection_report/allure_pytest_collection_report/__init__.py deleted file mode 100644 index e3979c0f6..000000000 --- a/python/pytest_plugins/allure_pytest_collection_report/allure_pytest_collection_report/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -# Copyright 2024 Canonical Ltd. -# See LICENSE file for licensing details. diff --git a/python/pytest_plugins/allure_pytest_collection_report/allure_pytest_collection_report/_plugin.py b/python/pytest_plugins/allure_pytest_collection_report/allure_pytest_collection_report/_plugin.py deleted file mode 100644 index 1832d0575..000000000 --- a/python/pytest_plugins/allure_pytest_collection_report/allure_pytest_collection_report/_plugin.py +++ /dev/null @@ -1,122 +0,0 @@ -# Copyright 2024 Canonical Ltd. -# See LICENSE file for licensing details. -# Upstream feature request to replace this plugin: -# https://github.com/allure-framework/allure-python/issues/821 - -import allure_commons.logger -import allure_commons.model2 -import allure_commons.types -import allure_commons.utils -import allure_pytest.listener -import allure_pytest.utils - - -def pytest_addoption(parser): - parser.addoption( - "--allure-collection-dir", - help="Generate default Allure results (used by GitHub Actions) in \ - this directory for tests that are missing Allure results", - ) - - -def pytest_configure(config): - if config.option.allure_collection_dir: - config.option.collectonly = True - - -def pytest_collection_finish(session): - report_dir = session.config.option.allure_collection_dir - if not report_dir: - return - - # Copied from `allure_pytest.listener.AllureListener._cache` - _cache = allure_pytest.listener.ItemCache() - # Modified from `allure_pytest.plugin.pytest_configure` - file_logger = allure_commons.logger.AllureFileLogger(report_dir) - - for item in session.items: - # Modified from - # `allure_pytest.listener.AllureListener.pytest_runtest_protocol` - uuid = _cache.push(item.nodeid) - test_result = allure_commons.model2.TestResult(name=item.name, uuid=uuid) - - # Copied from `allure_pytest.listener.AllureListener.pytest_runtest_setup` - params = ( - allure_pytest.listener.AllureListener._AllureListener__get_pytest_params( - item - ) - ) - test_result.name = allure_pytest.utils.allure_name(item, params) - full_name = allure_pytest.utils.allure_full_name(item) - test_result.fullName = full_name - test_result.testCaseId = allure_commons.utils.md5(full_name) - test_result.description = allure_pytest.utils.allure_description(item) - test_result.descriptionHtml = allure_pytest.utils.allure_description_html(item) - current_param_names = [param.name for param in test_result.parameters] - test_result.parameters.extend( - [ - allure_commons.model2.Parameter( - name=name, value=allure_commons.utils.represent(value) - ) - for name, value in params.items() - if name not in current_param_names - ] - ) - - # Copied from `allure_pytest.listener.AllureListener.pytest_runtest_teardown` - listener = allure_pytest.listener.AllureListener - test_result.historyId = allure_pytest.utils.get_history_id( - test_result.fullName, - test_result.parameters, - original_values=listener._AllureListener__get_pytest_params(item), - ) - test_result.labels.extend( - [ - allure_commons.model2.Label(name=name, value=value) - for name, value in allure_pytest.utils.allure_labels(item) - ] - ) - test_result.labels.extend( - [ - allure_commons.model2.Label( - name=allure_commons.types.LabelType.TAG, value=value - ) - for value in allure_pytest.utils.pytest_markers(item) - ] - ) - allure_pytest.listener.AllureListener._AllureListener__apply_default_suites( - None, item, test_result - ) - test_result.labels.append( - allure_commons.model2.Label( - name=allure_commons.types.LabelType.HOST, - value=allure_commons.utils.host_tag(), - ) - ) - test_result.labels.append( - allure_commons.model2.Label( - name=allure_commons.types.LabelType.FRAMEWORK, value="pytest" - ) - ) - test_result.labels.append( - allure_commons.model2.Label( - name=allure_commons.types.LabelType.LANGUAGE, - value=allure_commons.utils.platform_label(), - ) - ) - test_result.labels.append( - allure_commons.model2.Label( - name="package", value=allure_pytest.utils.allure_package(item) - ) - ) - test_result.links.extend( - [ - allure_commons.model2.Link(link_type, url, name) - for link_type, url, name in allure_pytest.utils.allure_links(item) - ] - ) - - # Modified from `allure_pytest.listener.AllureListener.pytest_runtest_protocol` - test_result.status = allure_commons.model2.Status.UNKNOWN - # Modified from `allure_commons.reporter.AllureReporter.close_test` - file_logger.report_result(test_result) diff --git a/python/pytest_plugins/allure_pytest_collection_report/pyproject.toml b/python/pytest_plugins/allure_pytest_collection_report/pyproject.toml deleted file mode 100644 index 7c721432c..000000000 --- a/python/pytest_plugins/allure_pytest_collection_report/pyproject.toml +++ /dev/null @@ -1,22 +0,0 @@ -[tool.poetry] -name = "allure_pytest_collection_report" -version = "0.1.0" -description = "" -authors = ["Carl Csaposs "] -readme = "README.md" -classifiers = [ - "Framework :: Pytest", -] - -[tool.poetry.plugins."pytest11"] -allure_collection_report = "allure_pytest_collection_report._plugin" - -[tool.poetry.dependencies] -python = "^3.8" -pytest = "*" -allure-pytest = ">=2.13.5" - - -[build-system] -requires = ["poetry-core"] -build-backend = "poetry.core.masonry.api" \ No newline at end of file diff --git a/tests/workflows/integration/test-upload-charm/tox.ini b/tests/workflows/integration/test-upload-charm/tox.ini index b6353b92e..04a96ee43 100644 --- a/tests/workflows/integration/test-upload-charm/tox.ini +++ b/tests/workflows/integration/test-upload-charm/tox.ini @@ -112,7 +112,7 @@ deps = macaroonbakery==1.3.2 websockets<14.0 # https://github.com/juju/python-libjuju/issues/1184 -r{toxinidir}/requirements.txt - git+https://github.com/canonical/operator-workflows@ISD-2620-allure\#subdirectory=python/pytest_plugins/allure_pytest_collection_report + git+https://github.com/canonical/data-platform-workflows@v24.0.0\#subdirectory=python/pytest_plugins/allure_pytest_collection_report commands = pytest -v --tb native --ignore={[vars]tst_path}unit --log-cli-level=INFO -s {posargs} @@ -127,6 +127,6 @@ deps = macaroonbakery==1.3.2 websockets<14.0 # https://github.com/juju/python-libjuju/issues/1184 -r{toxinidir}/requirements.txt - git+https://github.com/canonical/operator-workflows@ISD-2620-allure\#subdirectory=python/pytest_plugins/allure_pytest_collection_report + git+https://github.com/canonical/data-platform-workflows@v24.0.0\#subdirectory=python/pytest_plugins/allure_pytest_collection_report commands = pytest -v --tb native --ignore={[vars]tst_path}unit --log-cli-level=INFO -s {posargs} From 4c74d546e0f31968bcba5d5755afd85540236691 Mon Sep 17 00:00:00 2001 From: Swetha Swaminathan Date: Tue, 10 Dec 2024 21:44:05 +0530 Subject: [PATCH 41/52] address review comments --- .github/workflows/allure_report.yaml | 22 ++++++++++--------- .github/workflows/integration_test_run.yaml | 2 +- .licenserc.yaml | 1 - .../how-to}/integration_test_allure.md | 2 +- 4 files changed, 14 insertions(+), 13 deletions(-) rename {.github/workflows => docs/how-to}/integration_test_allure.md (95%) diff --git a/.github/workflows/allure_report.yaml b/.github/workflows/allure_report.yaml index 06c6f85f3..c4a78fc79 100644 --- a/.github/workflows/allure_report.yaml +++ b/.github/workflows/allure_report.yaml @@ -21,16 +21,18 @@ jobs: run: | sudo apt-get update sudo apt-get install ./allure_*.deb -y - - name: Checkout GitHub pages branch + - name: Checkout GitHub pages branch uses: actions/checkout@v4 with: ref: gh-pages - path: repo/ - - name: Download default test results + path: repo/ + # The `gh-pages` branch is used to host the Allure report site. + # With every workflow run, the workflow creates a new folder with the run_number and stores the test results there + - name: Download fallback test results uses: actions/download-artifact@v4 with: - path: allure-collection-default-results/ - pattern: allure-default-results* + path: allure-collection-fallback-results/ + pattern: allure-fallback-results* merge-multiple: true - name: Download actual test results uses: actions/download-artifact@v4 @@ -40,11 +42,11 @@ jobs: merge-multiple: true - name: Install CLI run: pipx install git+https://github.com/canonical/data-platform-workflows@v24.0.0#subdirectory=python/cli - - name: Combine Allure default results & actual results - # For every test: if actual result available, use that. Otherwise, use default result + - name: Combine Allure fallback results & actual results + # For every test: if actual result available, use that. Otherwise, use fallback result # So that, if actual result not available, Allure report will show "unknown"/"failed" test result # instead of omitting the test - run: allure-add-default-for-missing-results --allure-results-dir=allure-results --allure-collection-default-results-dir=allure-collection-default-results + run: allure-add-default-for-missing-results --allure-results-dir=allure-results --allure-collection-fallback-results-dir=allure-collection-fallback-results - name: Load test report history run: | if [[ -d repo/_latest/history/ ]] @@ -82,7 +84,6 @@ jobs: file.write(DATA) - name: Update GitHub pages branch working-directory: repo/ - # TODO future improvement: commit message run: | mkdir '${{ github.run_number }}' rm -f _latest @@ -90,7 +91,8 @@ jobs: cp -r ../allure-report/. _latest/ git add . git config user.name "GitHub Actions" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + # user.email obtained from https://github.com/actions/checkout/issues/13#issuecomment-724415212 + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" git commit -m "Allure report ${{ github.run_number }}" # Uses token set in checkout step git push origin gh-pages diff --git a/.github/workflows/integration_test_run.yaml b/.github/workflows/integration_test_run.yaml index 432083aa4..d02e3b42f 100644 --- a/.github/workflows/integration_test_run.yaml +++ b/.github/workflows/integration_test_run.yaml @@ -199,7 +199,7 @@ jobs: timeout-minutes: 3 uses: actions/upload-artifact@v4 with: - name: allure-default-results-${{ env.ALLURE_ARTIFACT_SUFFIX }} + name: allure-fallback-results-${{ env.ALLURE_ARTIFACT_SUFFIX }} path: ${{ inputs.working-directory }}allure-default/ - name: Setup operator environment uses: charmed-kubernetes/actions-operator@main diff --git a/.licenserc.yaml b/.licenserc.yaml index ca8c7a317..291d80eb6 100644 --- a/.licenserc.yaml +++ b/.licenserc.yaml @@ -32,5 +32,4 @@ header: - 'dist/**' - 'internal/**' - 'tests/workflows/integration/**' - - 'python/**/**/pyproject.toml' comment: on-failure diff --git a/.github/workflows/integration_test_allure.md b/docs/how-to/integration_test_allure.md similarity index 95% rename from .github/workflows/integration_test_allure.md rename to docs/how-to/integration_test_allure.md index 9d1f0ad74..4320144be 100644 --- a/.github/workflows/integration_test_allure.md +++ b/docs/how-to/integration_test_allure.md @@ -39,7 +39,7 @@ Here's an [example for the above](https://github.com/canonical/github-runner-ope **NOTE:** For this, you would require admin access to the repository. - Go to the repository's **Settings > Branches** and next to Branch protection rules, select **Add rule** -- Enter the branch name **gh-pages** and select **Allow force pushes** and click **Save changes** +- Enter the branch name **gh-pages** and click **Save changes** (Ensure that "require signed commits" is unchecked) ## 4. Github pages branch From 163d73506b36ff21bac87bcf0ba93c3048497a5f Mon Sep 17 00:00:00 2001 From: Swetha Swaminathan Date: Wed, 11 Dec 2024 10:11:20 +0530 Subject: [PATCH 42/52] address review comments --- docs/how-to/integration_test_allure.md | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/docs/how-to/integration_test_allure.md b/docs/how-to/integration_test_allure.md index 4320144be..b3f3b43b1 100644 --- a/docs/how-to/integration_test_allure.md +++ b/docs/how-to/integration_test_allure.md @@ -1,24 +1,22 @@ -# Allure Report Integration +# How to set up Allure Reports for integration tests -[Allure Report](https://allurereport.org/) for [integration_test_run.yaml](https://github.com/canonical/operator-workflows?tab=readme-ov-file#integration-test-workflow-canonicaloperator-workflowsgithubworkflowsintegration_testyamlmain) +This how-to guide describes how to integrate [Allure Reports](https://allurereport.org/) into your code repository's [integration_test_run.yaml](https://github.com/canonical/operator-workflows?tab=readme-ov-file#integration-test-workflow-canonicaloperator-workflowsgithubworkflowsintegration_testyamlmain). -In order to integrate Allure with your repository, perform the following actions: +## Adding allure-pytest and pytest collection plugin -## 1. Adding allure-pytest and pytest collection plugin - -Please add the following into the `requirements.txt` that is called by the integration test - +Include the following snippet in the `requirements.txt` that is called by the integration test: ``` allure-pytest>=2.8.18 ``` -Add the following line under the dependencies (`deps`) in the integration section inside `tox.ini` - +Add the following line under the dependencies (`deps`) in the integration section inside `tox.ini`: ``` git+https://github.com/canonical/operator-workflows@main\#subdirectory=python/pytest_plugins/allure_pytest_collection_report ``` -## 2. Calling the allure-workflow +## Calling the allure-workflow Add the following lines at the end of the workflow that runs the integrations tests by calling the reuable workflow [integration-test.yaml](https://github.com/canonical/operator-workflows/blob/main/.github/workflows/integration_test.yaml): @@ -30,18 +28,18 @@ Add the following lines at the end of the workflow that runs the integrations te uses: canonical/operator-workflows/.github/workflows/allure_report.yaml@main ``` -Here's an [example for the above](https://github.com/canonical/github-runner-operator/pull/412). +For an example of this implementation, see [the GitHub runner repository](https://github.com/canonical/github-runner-operator/pull/412). **NOTE:** If the workflow is being called inside a matrix with the same test modules run with different parameters, the allure report will only display the results of the last combination. -## 3. Changing branch permissions +## Changing branch permissions -**NOTE:** For this, you would require admin access to the repository. +**NOTE:** For this step, you need admin access to the repository. - Go to the repository's **Settings > Branches** and next to Branch protection rules, select **Add rule** - Enter the branch name **gh-pages** and click **Save changes** (Ensure that "require signed commits" is unchecked) -## 4. Github pages branch +## Github pages branch - Create `gh-pages` branch: @@ -57,8 +55,8 @@ Here's an [example for the above](https://github.com/canonical/github-runner-ope # ) ``` - - Enable gh pages publishing at ** Settings > Pages ** and set branch name as `gh-pages`: + - Enable GitHub pages publishing at ** Settings > Pages ** and set branch name as `gh-pages`: image - [Example PR for steps 1 & 2](https://github.com/canonical/github-runner-operator/pull/412/files#) +For an example of the first two steps, see [the GitHub runner repository](https://github.com/canonical/github-runner-operator/pull/412). From b285aaf02663c4336b53ffbd10dbc006324ddaa7 Mon Sep 17 00:00:00 2001 From: Swetha Swaminathan Date: Wed, 11 Dec 2024 10:12:10 +0530 Subject: [PATCH 43/52] address review comments --- .github/workflows/allure_report.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/allure_report.yaml b/.github/workflows/allure_report.yaml index c4a78fc79..cff2139e9 100644 --- a/.github/workflows/allure_report.yaml +++ b/.github/workflows/allure_report.yaml @@ -46,7 +46,7 @@ jobs: # For every test: if actual result available, use that. Otherwise, use fallback result # So that, if actual result not available, Allure report will show "unknown"/"failed" test result # instead of omitting the test - run: allure-add-default-for-missing-results --allure-results-dir=allure-results --allure-collection-fallback-results-dir=allure-collection-fallback-results + run: allure-add-default-for-missing-results --allure-results-dir=allure-results --allure-collection-default-results-dir=allure-collection-fallback-results - name: Load test report history run: | if [[ -d repo/_latest/history/ ]] From aeac64bb9d2cea3f4e7889db579ca1688e838787 Mon Sep 17 00:00:00 2001 From: Swetha Swaminathan Date: Wed, 11 Dec 2024 10:18:35 +0530 Subject: [PATCH 44/52] address review comments --- docs/how-to/integration_test_allure.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/how-to/integration_test_allure.md b/docs/how-to/integration_test_allure.md index b3f3b43b1..b1721ab43 100644 --- a/docs/how-to/integration_test_allure.md +++ b/docs/how-to/integration_test_allure.md @@ -18,19 +18,19 @@ git+https://github.com/canonical/operator-workflows@main\#subdirectory=python/py ## Calling the allure-workflow -Add the following lines at the end of the workflow that runs the integrations tests by calling the reuable workflow [integration-test.yaml](https://github.com/canonical/operator-workflows/blob/main/.github/workflows/integration_test.yaml): +To call the reusable workflow [allure_report.yaml](https://github.com/canonical/operator-workflows/blob/main/.github/workflows/allure_report.yaml), add the following lines at the end of the workflow that runs the integrations tests: ``` allure-report: if: always() && !cancelled() needs: - - [list of jobs with tests you would like to visualize] + - [list of jobs that calls integration_test workflow whose tests you would like to visualize] uses: canonical/operator-workflows/.github/workflows/allure_report.yaml@main ``` For an example of this implementation, see [the GitHub runner repository](https://github.com/canonical/github-runner-operator/pull/412). -**NOTE:** If the workflow is being called inside a matrix with the same test modules run with different parameters, the allure report will only display the results of the last combination. +**NOTE:** If the workflow is being called inside a matrix with the same test modules run with different parameters, the Allure Report will only display the results of the last combination. ## Changing branch permissions From 1fde840fbf8e833123f980d4854a6b1e7eeac83a Mon Sep 17 00:00:00 2001 From: Swetha Swaminathan Date: Wed, 11 Dec 2024 13:07:03 +0530 Subject: [PATCH 45/52] update tox and add check allure --- .github/workflows/allure_report.yaml | 1 + .github/workflows/integration_test_run.yaml | 33 ++++++++++++++++++++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/.github/workflows/allure_report.yaml b/.github/workflows/allure_report.yaml index cff2139e9..10a0cb799 100644 --- a/.github/workflows/allure_report.yaml +++ b/.github/workflows/allure_report.yaml @@ -58,6 +58,7 @@ jobs: shell: python run: | # Reverse engineered from https://github.com/simple-elf/allure-report-action/blob/eca283b643d577c69b8e4f048dd6cd8eb8457cfd/entrypoint.sh + # Not using the original action due to security concerns over using 3rd party github actions and the risk of running arbitrary code import json DATA = { diff --git a/.github/workflows/integration_test_run.yaml b/.github/workflows/integration_test_run.yaml index d02e3b42f..6be32d0f1 100644 --- a/.github/workflows/integration_test_run.yaml +++ b/.github/workflows/integration_test_run.yaml @@ -190,12 +190,43 @@ jobs: echo "ALLURE_ARTIFACT_SUFFIX=$allure_artifact_suffix" >> $GITHUB_ENV - name: Install tox run: | - pip install tox + if which tox &> /dev/null; then + echo "tox is already installed." + tox --version + fi + + pip_path=$(which pip 2>/dev/null) + SYSTEM_PIP_PATH="/usr/bin/pip" + if [ -n "$pip_path" ] && [ "$pip_path" != "$SYSTEM_PIP_PATH" ]; then + echo "Pip is available and not system-managed. Installing tox" + pip install tox + fi + + if which pipx &> /dev/null; then + echo "Pipx is available. Installing tox" + pipx install tox + fi + + echo "Neither pip nor pipx are available. Installing pipx via apt..." + sudo apt-get update -yqq + sudo apt-get install -yqq pipx + pipx ensurepath + sudo pipx ensurepath + + echo "Installing tox with pipx..." + pipx install tox + - name: Check Allure + run: | + tox -e ${{ inputs.test-tox-env }} --notest --list-dependencies 2>&1 | grep -q allure \ + && echo 'ENABLE_ALLURE=true' >> $GITHUB_ENV \ + || echo 'ENABLE_ALLURE=false' >> $GITHUB_ENV - name: Collect tests for Allure + if: ${{ env.ENABLE_ALLURE == true }} working-directory: ${{ inputs.working-directory }} run: | tox -e ${{ inputs.test-tox-env }} -- --keep-models ${{ env.SERIES }} ${{ env.MODULE }} --allure-collection-dir=allure-default ${{ inputs.extra-arguments }} ${{ secrets.INTEGRATION_TEST_ARGS }} - name: Upload Default Allure results + if: ${{ env.ENABLE_ALLURE == true }} timeout-minutes: 3 uses: actions/upload-artifact@v4 with: From a0da65291066f159f497d0e18da44f80b634edb7 Mon Sep 17 00:00:00 2001 From: Swetha Swaminathan Date: Wed, 11 Dec 2024 13:07:52 +0530 Subject: [PATCH 46/52] update tox and add check allure --- .github/workflows/integration_test_run.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/integration_test_run.yaml b/.github/workflows/integration_test_run.yaml index 6be32d0f1..0b3afcfa5 100644 --- a/.github/workflows/integration_test_run.yaml +++ b/.github/workflows/integration_test_run.yaml @@ -221,12 +221,12 @@ jobs: && echo 'ENABLE_ALLURE=true' >> $GITHUB_ENV \ || echo 'ENABLE_ALLURE=false' >> $GITHUB_ENV - name: Collect tests for Allure - if: ${{ env.ENABLE_ALLURE == true }} + if: ${{ env.ENABLE_ALLURE == "true" }} working-directory: ${{ inputs.working-directory }} run: | tox -e ${{ inputs.test-tox-env }} -- --keep-models ${{ env.SERIES }} ${{ env.MODULE }} --allure-collection-dir=allure-default ${{ inputs.extra-arguments }} ${{ secrets.INTEGRATION_TEST_ARGS }} - name: Upload Default Allure results - if: ${{ env.ENABLE_ALLURE == true }} + if: ${{ env.ENABLE_ALLURE == "true" }} timeout-minutes: 3 uses: actions/upload-artifact@v4 with: From 4202ba7c12bf979846e0fe798315080dc0b4180a Mon Sep 17 00:00:00 2001 From: Swetha Swaminathan Date: Wed, 11 Dec 2024 13:22:54 +0530 Subject: [PATCH 47/52] update tox and add check allure --- docs/how-to/integration_test_allure.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/how-to/integration_test_allure.md b/docs/how-to/integration_test_allure.md index b1721ab43..1ce16787b 100644 --- a/docs/how-to/integration_test_allure.md +++ b/docs/how-to/integration_test_allure.md @@ -24,7 +24,7 @@ To call the reusable workflow [allure_report.yaml](https://github.com/canonical/ allure-report: if: always() && !cancelled() needs: - - [list of jobs that calls integration_test workflow whose tests you would like to visualize] + - [list of jobs that call integration_test workflow whose tests you would like to visualize] uses: canonical/operator-workflows/.github/workflows/allure_report.yaml@main ``` @@ -36,6 +36,8 @@ For an example of this implementation, see [the GitHub runner repository](https: **NOTE:** For this step, you need admin access to the repository. +If your repository is configured to have signed commits for all branches by default, you need to create a seperate protection rule for the `gh-pages` branch with the signed commits disabled. + - Go to the repository's **Settings > Branches** and next to Branch protection rules, select **Add rule** - Enter the branch name **gh-pages** and click **Save changes** (Ensure that "require signed commits" is unchecked) From cb41e5200aa9cbf877c0651c5a619657e93502c8 Mon Sep 17 00:00:00 2001 From: Swetha Swaminathan Date: Wed, 11 Dec 2024 13:25:37 +0530 Subject: [PATCH 48/52] update tox and add check allure --- .github/workflows/integration_test_run.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/integration_test_run.yaml b/.github/workflows/integration_test_run.yaml index 0b3afcfa5..98fa9c794 100644 --- a/.github/workflows/integration_test_run.yaml +++ b/.github/workflows/integration_test_run.yaml @@ -221,12 +221,12 @@ jobs: && echo 'ENABLE_ALLURE=true' >> $GITHUB_ENV \ || echo 'ENABLE_ALLURE=false' >> $GITHUB_ENV - name: Collect tests for Allure - if: ${{ env.ENABLE_ALLURE == "true" }} + if: env.ENABLE_ALLURE == "true" working-directory: ${{ inputs.working-directory }} run: | tox -e ${{ inputs.test-tox-env }} -- --keep-models ${{ env.SERIES }} ${{ env.MODULE }} --allure-collection-dir=allure-default ${{ inputs.extra-arguments }} ${{ secrets.INTEGRATION_TEST_ARGS }} - name: Upload Default Allure results - if: ${{ env.ENABLE_ALLURE == "true" }} + if: env.ENABLE_ALLURE == "true" timeout-minutes: 3 uses: actions/upload-artifact@v4 with: From 89c33f4e2b8cc3edd3de00ec4a82a604097276be Mon Sep 17 00:00:00 2001 From: Swetha Swaminathan Date: Wed, 11 Dec 2024 13:26:53 +0530 Subject: [PATCH 49/52] update tox and add check allure --- .github/workflows/integration_test_run.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/integration_test_run.yaml b/.github/workflows/integration_test_run.yaml index 98fa9c794..86097956a 100644 --- a/.github/workflows/integration_test_run.yaml +++ b/.github/workflows/integration_test_run.yaml @@ -221,12 +221,12 @@ jobs: && echo 'ENABLE_ALLURE=true' >> $GITHUB_ENV \ || echo 'ENABLE_ALLURE=false' >> $GITHUB_ENV - name: Collect tests for Allure - if: env.ENABLE_ALLURE == "true" + if: env.ENABLE_ALLURE == 'true' working-directory: ${{ inputs.working-directory }} run: | tox -e ${{ inputs.test-tox-env }} -- --keep-models ${{ env.SERIES }} ${{ env.MODULE }} --allure-collection-dir=allure-default ${{ inputs.extra-arguments }} ${{ secrets.INTEGRATION_TEST_ARGS }} - name: Upload Default Allure results - if: env.ENABLE_ALLURE == "true" + if: env.ENABLE_ALLURE == 'true' timeout-minutes: 3 uses: actions/upload-artifact@v4 with: From 90c4f66ad76878f1fa02a216332414bd26bd8737 Mon Sep 17 00:00:00 2001 From: Swetha Swaminathan Date: Wed, 11 Dec 2024 13:48:26 +0530 Subject: [PATCH 50/52] test --- .github/workflows/integration_test_run.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/integration_test_run.yaml b/.github/workflows/integration_test_run.yaml index 86097956a..84bd1fcf4 100644 --- a/.github/workflows/integration_test_run.yaml +++ b/.github/workflows/integration_test_run.yaml @@ -216,7 +216,9 @@ jobs: echo "Installing tox with pipx..." pipx install tox - name: Check Allure + working-directory: ${{ inputs.working-directory }} run: | + tox -e ${{ inputs.test-tox-env }} --notest --list-dependencies 2>&1 tox -e ${{ inputs.test-tox-env }} --notest --list-dependencies 2>&1 | grep -q allure \ && echo 'ENABLE_ALLURE=true' >> $GITHUB_ENV \ || echo 'ENABLE_ALLURE=false' >> $GITHUB_ENV From 27f038a4361bab005882536a89747c891bd50bab Mon Sep 17 00:00:00 2001 From: Swetha Swaminathan Date: Wed, 11 Dec 2024 14:52:23 +0530 Subject: [PATCH 51/52] test --- .github/workflows/integration_test_run.yaml | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/workflows/integration_test_run.yaml b/.github/workflows/integration_test_run.yaml index 84bd1fcf4..dc26c2552 100644 --- a/.github/workflows/integration_test_run.yaml +++ b/.github/workflows/integration_test_run.yaml @@ -349,12 +349,20 @@ jobs: working-directory: ${{ inputs.working-directory }} if: ${{ inputs.provider == 'microk8s' }} run: | - tox -e ${{ inputs.test-tox-env }} -- --model testing --keep-models ${{ env.SERIES }} ${{ env.MODULE }} ${{ env.ARGS }} --alluredir=allure-results ${{ inputs.extra-arguments }} ${{ secrets.INTEGRATION_TEST_ARGS }} + if [ "$ENABLE_ALLURE" == "true" ]; then + tox -e ${{ inputs.test-tox-env }} -- --model testing --keep-models ${{ env.SERIES }} ${{ env.MODULE }} ${{ env.ARGS }} --alluredir=allure-results ${{ inputs.extra-arguments }} ${{ secrets.INTEGRATION_TEST_ARGS }} + else + tox -e ${{ inputs.test-tox-env }} -- --model testing --keep-models ${{ env.SERIES }} ${{ env.MODULE }} ${{ env.ARGS }} ${{ inputs.extra-arguments }} ${{ secrets.INTEGRATION_TEST_ARGS }} + fi - name: Run lxd integration tests working-directory: ${{ inputs.working-directory }} if: ${{ inputs.provider == 'lxd' }} run: | - tox -e ${{ inputs.test-tox-env }} -- --keep-models ${{ env.SERIES }} ${{ env.MODULE }} ${{ env.ARGS }} --alluredir=allure-results ${{ inputs.extra-arguments }} ${{ secrets.INTEGRATION_TEST_ARGS }} + if [ "$ENABLE_ALLURE" == "true" ]; then + tox -e ${{ inputs.test-tox-env }} -- --keep-models ${{ env.SERIES }} ${{ env.MODULE }} ${{ env.ARGS }} --alluredir=allure-results ${{ inputs.extra-arguments }} ${{ secrets.INTEGRATION_TEST_ARGS }} + else + tox -e ${{ inputs.test-tox-env }} -- --keep-models ${{ env.SERIES }} ${{ env.MODULE }} ${{ env.ARGS }} ${{ inputs.extra-arguments }} ${{ secrets.INTEGRATION_TEST_ARGS }} + fi - name: Upload Allure results timeout-minutes: 3 if: always() && !cancelled() From d4b0c2511bcb9f20c630ff3a945d6a551bf083d7 Mon Sep 17 00:00:00 2001 From: Swetha Swaminathan Date: Wed, 11 Dec 2024 15:14:15 +0530 Subject: [PATCH 52/52] test --- .github/workflows/integration_test_run.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/integration_test_run.yaml b/.github/workflows/integration_test_run.yaml index dc26c2552..0f8566307 100644 --- a/.github/workflows/integration_test_run.yaml +++ b/.github/workflows/integration_test_run.yaml @@ -365,7 +365,7 @@ jobs: fi - name: Upload Allure results timeout-minutes: 3 - if: always() && !cancelled() + if: env.ENABLE_ALLURE == 'true' && always() uses: actions/upload-artifact@v4 with: name: allure-results-${{ env.ALLURE_ARTIFACT_SUFFIX }}