From fe2d5fc2679501eb1083f4429202f841f0ae12e5 Mon Sep 17 00:00:00 2001 From: Enrico Minack Date: Tue, 27 Feb 2024 09:57:38 +0100 Subject: [PATCH] OS-specific composite actions (#576) --- .github/workflows/publish.yml | 198 +++++++++++++++++++-- README.md | 79 +++++++-- action.yml | 3 +- composite/action.yml | 27 ++- linux/action.yml | 301 ++++++++++++++++++++++++++++++++ macos/action.yml | 301 ++++++++++++++++++++++++++++++++ python/test/test_action_yml.py | 68 +++++--- windows/action.yml | 306 +++++++++++++++++++++++++++++++++ windows/bash/action.yml | 305 ++++++++++++++++++++++++++++++++ 9 files changed, 1526 insertions(+), 62 deletions(-) create mode 100644 linux/action.yml create mode 100644 macos/action.yml create mode 100644 windows/action.yml create mode 100644 windows/bash/action.yml diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 010dae01..030f9dea 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -215,7 +215,7 @@ jobs: with: sarif_file: ${{ steps.scan.outputs.sarif }} - publish-composite: + publish-linux: name: Publish Test Results (${{ matrix.os-label }} python ${{ matrix.python }}) runs-on: ${{ matrix.os }} permissions: @@ -224,10 +224,72 @@ jobs: strategy: fail-fast: false - max-parallel: 3 + max-parallel: 2 + matrix: + # https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources + include: + - os: ubuntu-latest + os-label: Linux + python: "3.8" + - os: ubuntu-latest + os-label: Linux + python: "venv" + - os: ubuntu-latest + os-label: Linux + python: "installed" + - os: ubuntu-20.04 + os-label: Linux 20.04 + python: "installed" + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Python + if: matrix.python != 'installed' && matrix.python != 'venv' + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python }} + + - name: Install virtualenv + if: matrix.python == 'venv' + run: python3 -m pip install virtualenv + shell: bash + + - name: Download Artifacts + uses: actions/download-artifact@v4 + with: + path: artifacts + + - name: Publish Test Results + id: test-results + uses: ./linux + with: + check_name: Test Results (${{ matrix.os-label }} python ${{ matrix.python }}) + files: artifacts/**/*.xml + json_file: "tests.json" + json_suite_details: true + json_test_case_results: true + report_suite_logs: "any" + + - name: JSON output + uses: ./misc/action/json-output + with: + json: '${{ steps.test-results.outputs.json }}' + json_file: 'tests.json' + + publish-macos: + name: Publish Test Results (${{ matrix.os-label }} python ${{ matrix.python }}) + runs-on: ${{ matrix.os }} + permissions: + checks: write + pull-requests: write + + strategy: + fail-fast: false + max-parallel: 2 matrix: # https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources - # test *-latest and newer (because newer eventually become 'latest' and should be tested to work before that) include: - os: macos-latest os-label: macOS @@ -248,19 +310,56 @@ jobs: os-label: macOS 14 python: "installed" - - os: ubuntu-latest - os-label: Linux - python: "3.8" - - os: ubuntu-latest - os-label: Linux - python: "venv" - - os: ubuntu-latest - os-label: Linux - python: "installed" - - os: ubuntu-20.04 - os-label: Linux 20.04 - python: "installed" + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Python + if: matrix.python != 'installed' && matrix.python != 'venv' + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python }} + + - name: Install virtualenv + if: matrix.python == 'venv' + run: python3 -m pip install virtualenv + shell: bash + + - name: Download Artifacts + uses: actions/download-artifact@v4 + with: + path: artifacts + + - name: Publish Test Results + id: test-results + uses: ./macos + with: + check_name: Test Results (${{ matrix.os-label }} python ${{ matrix.python }}) + files: artifacts/**/*.xml + json_file: "tests.json" + json_suite_details: true + json_test_case_results: true + report_suite_logs: "any" + + - name: JSON output + uses: ./misc/action/json-output + with: + json: '${{ steps.test-results.outputs.json }}' + json_file: 'tests.json' + + publish-windows: + name: Publish Test Results (${{ matrix.os-label }} python ${{ matrix.python }}) + runs-on: ${{ matrix.os }} + permissions: + checks: write + pull-requests: write + + strategy: + fail-fast: false + max-parallel: 2 + matrix: + # https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources + include: - os: windows-latest os-label: Windows python: "installed" @@ -283,8 +382,7 @@ jobs: - name: Install virtualenv if: matrix.python == 'venv' - run: | - python3 -m pip install virtualenv + run: python3 -m pip install virtualenv shell: bash - name: Download Artifacts @@ -294,9 +392,73 @@ jobs: - name: Publish Test Results id: test-results - uses: ./composite + uses: ./windows with: check_name: Test Results (${{ matrix.os-label }} python ${{ matrix.python }}) + files: artifacts\**\*.xml + json_file: "tests.json" + json_suite_details: true + json_test_case_results: true + report_suite_logs: "any" + + - name: JSON output + uses: ./misc/action/json-output + with: + json: '${{ steps.test-results.outputs.json }}' + json_file: 'tests.json' + + - name: Publish Test Results (Bash) + id: test-results-bash + uses: ./windows/bash + with: + check_name: Test Results (${{ matrix.os-label }} bash python ${{ matrix.python }}) + files: artifacts\**\*.xml + json_file: "tests.json" + json_suite_details: true + json_test_case_results: true + report_suite_logs: "any" + + - name: JSON output (Bash) + uses: ./misc/action/json-output + with: + json: '${{ steps.test-results-bash.outputs.json }}' + json_file: 'tests.json' + + publish-composite: + name: Publish Test Results (${{ matrix.os-label }} composite) + runs-on: ${{ matrix.os }} + permissions: + checks: write + pull-requests: write + + strategy: + fail-fast: false + max-parallel: 1 + matrix: + # https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources + # test *-latest and newer (because newer eventually become 'latest' and should be tested to work before that) + include: + - os: macos-latest + os-label: macOS + - os: ubuntu-latest + os-label: Linux + - os: windows-latest + os-label: Windows + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Download Artifacts + uses: actions/download-artifact@v4 + with: + path: artifacts + + - name: Publish Test Results + id: test-results + uses: ./composite + with: + check_name: Test Results (${{ matrix.os-label }} composite python ${{ matrix.python }}) files: | artifacts/**/*.xml artifacts\**\*.xml diff --git a/README.md b/README.md index 9f5d0329..104b9589 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ publishes the results on GitHub. It supports [JSON (Dart, Mocha), TRX (MSTest, V and runs on Linux, macOS and Windows. You can use this action with ![Ubuntu Linux](misc/badge-ubuntu.svg) runners (e.g. `runs-on: ubuntu-latest`) -or ![ARM Linux](misc/badge-arm.svg) self-hosted runners: +or ![ARM Linux](misc/badge-arm.svg) self-hosted runners that support Docker: ```yaml - name: Publish Test Results @@ -36,12 +36,34 @@ or ![ARM Linux](misc/badge-arm.svg) self-hosted runners: See the [notes on running this action with absolute paths](#running-with-absolute-paths) if you cannot use relative test result file paths. -Use this for ![macOS](misc/badge-macos.svg) (e.g. `runs-on: macos-latest`) -and ![Windows](misc/badge-windows.svg) (e.g. `runs-on: windows-latest`) runners: +Use this for ![macOS](misc/badge-macos.svg) (e.g. `runs-on: macos-latest`) runners: +```yaml +- name: Publish Test Results + uses: EnricoMi/publish-unit-test-result-action/macos@v2 + if: always() + with: + files: | + test-results/**/*.xml + test-results/**/*.trx + test-results/**/*.json +``` +… and ![Windows](misc/badge-windows.svg) (e.g. `runs-on: windows-latest`) runners: ```yaml - name: Publish Test Results - uses: EnricoMi/publish-unit-test-result-action/composite@v2 + uses: EnricoMi/publish-unit-test-result-action/windows@v2 + if: always() + with: + files: | + test-results\**\*.xml + test-results\**\*.trx + test-results\**\*.json +``` + +For **self-hosted** Linux GitHub Actions runners **without Docker** installed, please use: +```yaml +- name: Publish Test Results + uses: EnricoMi/publish-unit-test-result-action/linux@v2 if: always() with: files: | @@ -50,7 +72,7 @@ and ![Windows](misc/badge-windows.svg) (e.g. `runs-on: windows-latest`) runners: test-results/**/*.json ``` -See the [notes on running this action as a composite action](#running-as-a-composite-action) if you run it on Windows or macOS. +See the [notes on running this action as a non-Docker action](#running-as-a-non-docker-action). If you see the `"Resource not accessible by integration"` error, you have to grant additional [permissions](#permissions), or [setup the support for pull requests from fork repositories and branches created by Dependabot](#support-fork-repositories-and-dependabot-branches). @@ -273,7 +295,7 @@ The list of most notable options: |Option|Default Value|Description| |:-----|:-----:|:----------| -|`files`|_no default_|File patterns of test result files. Relative paths are known to work best, while the composite action [also works with absolute paths](#running-with-absolute-paths). Supports `*`, `**`, `?`, and `[]` character ranges. Use multiline string for multiple patterns. Patterns starting with `!` exclude the matching files. There have to be at least one pattern starting without a `!`.| +|`files`|_no default_|File patterns of test result files. Relative paths are known to work best, while the non-Docker action [also works with absolute paths](#running-with-absolute-paths). Supports `*`, `**`, `?`, and `[]` character ranges. Use multiline string for multiple patterns. Patterns starting with `!` exclude the matching files. There have to be at least one pattern starting without a `!`.| |`check_name`|`"Test Results"`|An alternative name for the check result. Required to be unique for each instance in one workflow.| |`comment_title`|same as `check_name`|An alternative name for the pull request comment.| |`comment_mode`|`always`|The action posts comments to pull requests that are associated with the commit. Set to:
`always` - always comment
`changes` - comment when changes w.r.t. the target branch exist
`changes in failures` - when changes in the number of failures and errors exist
`changes in errors` - when changes in the number of (only) errors exist
`failures` - when failures or errors exist
`errors` - when (only) errors exist
`off` - to not create pull request comments.| @@ -783,10 +805,14 @@ You can then use the badge via this URL: https://gist.githubusercontent.com/{use ## Running with absolute paths It is known that this action works best with relative paths (e.g. `test-results/**/*.xml`), -but most absolute paths (e.g. `/tmp/test-results/**/*.xml`) require to use the composite variant -of this action (`uses: EnricoMi/publish-unit-test-result-action/composite@v2`). +but most absolute paths (e.g. `/tmp/test-results/**/*.xml`) require to use the non-Docker variant +of this action: -If you have to use absolute paths with the non-composite variant of this action (`uses: EnricoMi/publish-unit-test-result-action@v2`), + uses: EnricoMi/publish-unit-test-result-action/linux@v2 + uses: EnricoMi/publish-unit-test-result-action/macos@v2 + uses: EnricoMi/publish-unit-test-result-action/windows@v2 + +If you have to use absolute paths with the Docker variant of this action (`uses: EnricoMi/publish-unit-test-result-action@v2`), you have to copy files to a relative path first, and then use the relative path: ```yaml @@ -806,14 +832,18 @@ you have to copy files to a relative path first, and then use the relative path: test-results/**/*.json ``` -Using the non-composite variant of this action is recommended as it starts up much quicker. +Using the Docker variant of this action is recommended as it starts up much quicker. -## Running as a composite action +## Running as a non-Docker action -Running this action as a composite action allows to run it on various operating systems as it -does not require Docker. The composite action, however, requires a Python3 environment to be setup -on the action runner. All GitHub-hosted runners (Ubuntu, Windows Server and macOS) provide a suitable -Python3 environment out-of-the-box. +Running this action as below allows to run it on action runners that do not provide Docker: + + uses: EnricoMi/publish-unit-test-result-action/linux@v2 + uses: EnricoMi/publish-unit-test-result-action/macos@v2 + uses: EnricoMi/publish-unit-test-result-action/windows@v2 + +These actions, however, require a Python3 environment to be setup on the action runner. +All GitHub-hosted runners (Ubuntu, Windows Server and macOS) provide a suitable Python3 environment out-of-the-box. Self-hosted runners may require setting up a Python environment first: @@ -824,6 +854,19 @@ Self-hosted runners may require setting up a Python environment first: python-version: 3.8 ``` -Self-hosted runners for Windows require Bash shell to be installed. Easiest way to have one is by installing -Git for Windows, which comes with Git BASH. Make sure that the location of `bash.exe` is part of the `PATH` -environment variable seen by the self-hosted runner. +Start-up of the action is faster with `virtualenv` or `venv`, as well as `wheel` packages are installed. + +## Running as a composite action + +Running this action via: + + uses: EnricoMi/publish-unit-test-result-action/composite@v2 + +is **deprecated**, please use an action appropriate for your operating system and shell: + +- Linux (Bash shell): `uses: EnricoMi/publish-unit-test-result-action/linux@v2` +- macOS (Bash shell): `uses: EnricoMi/publish-unit-test-result-action/macos@v2` +- Windows (PowerShell): `uses: EnricoMi/publish-unit-test-result-action/windows@v2` +- Windows (Bash shell): `uses: EnricoMi/publish-unit-test-result-action/windows/bash@v2` + +These are non-Docker variations of this action. For details, see section ["Running as a non-Docker action"](#running-as-a-non-docker-action) above. diff --git a/action.yml b/action.yml index 0481ceb1..4d5e35f6 100644 --- a/action.yml +++ b/action.yml @@ -42,7 +42,7 @@ inputs: default: 'false' required: false files: - description: 'File patterns of test result files. Relative paths are known to work best, while the composite action also works with absolute paths. Supports "*", "**", "?", and "[]" character ranges. Use multiline string for multiple patterns. Patterns starting with "!" exclude the matching files. There have to be at least one pattern starting without a "!".' + description: 'File patterns of test result files. Relative paths are known to work best, while the non-Docker action also works with absolute paths. Supports "*", "**", "?", and "[]" character ranges. Use multiline string for multiple patterns. Patterns starting with "!" exclude the matching files. There have to be at least one pattern starting without a "!".' required: false junit_files: description: 'Deprecated, use "files" option instead.' @@ -142,6 +142,7 @@ inputs: description: 'Prior to v2.6.0, the action used the "/search/issues" REST API to find pull requests related to a commit. If you need to restore that behaviour, set this to "true". Defaults to "false".' default: 'false' required: false + outputs: json: description: "Test results as JSON" diff --git a/composite/action.yml b/composite/action.yml index cda1fed0..e16c0ad5 100644 --- a/composite/action.yml +++ b/composite/action.yml @@ -42,7 +42,7 @@ inputs: default: 'false' required: false files: - description: 'File patterns of test result files. Relative paths are known to work best, while the composite action also works with absolute paths. Supports "*", "**", "?", and "[]" character ranges. Use multiline string for multiple patterns. Patterns starting with "!" exclude the matching files. There have to be at least one pattern starting without a "!".' + description: 'File patterns of test result files. Relative paths are known to work best, while the non-Docker action also works with absolute paths. Supports "*", "**", "?", and "[]" character ranges. Use multiline string for multiple patterns. Patterns starting with "!" exclude the matching files. There have to be at least one pattern starting without a "!".' required: false junit_files: description: 'Deprecated, use "files" option instead.' @@ -151,10 +151,18 @@ outputs: runs: using: 'composite' steps: + - name: Deprecation warning + run: | + # Print deprecation warning + echo "::warning::Running this action via 'uses: EnricoMi/publish-unit-test-result-action/composite@v2 is deprecated! For details, see: https://github.com/EnricoMi/publish-unit-test-result-action/tree/v2#running-as-a-composite-action" + shell: bash + - name: Check for Python3 id: python run: | + # Check for Python3 echo '::group::Check for Python3' + # we check version here just to execute `python3` with an argument # on Windows, there is a `python3.exe` that is a proxy to trigger installation from app store # command `which python3` finds that, but `python3 -V` does not return the version on stdout @@ -174,12 +182,14 @@ runs: echo "Python that creates venv: $PYTHON_BIN" echo "PYTHON_BIN=$PYTHON_BIN" >> "$GITHUB_ENV" echo "version=$("$PYTHON_BIN" -V)" >> "$GITHUB_OUTPUT" + echo '::endgroup::' shell: bash - name: Detect OS id: os run: | + # Detect OS case "$RUNNER_OS" in Linux*) echo "pip-cache=~/.cache/pip" >> "$GITHUB_OUTPUT" @@ -208,7 +218,9 @@ runs: env: PIP_OPTIONS: ${{ steps.os.outputs.pip-options }} run: | + # Create virtualenv echo '::group::Create virtualenv' + echo "Python that creates venv: $PYTHON_BIN" echo "Creating virtual environment" @@ -218,12 +230,16 @@ runs: if ! "$PYTHON_BIN" -m pip install $PIP_OPTIONS virtualenv && [ -n "$PIP_OPTIONS" ] then echo "Installing virtualenv package with PIP options '$PIP_OPTIONS' failed, now trying without" - "$PYTHON_BIN" -m pip install virtualenv + if ! "$PYTHON_BIN" -m pip install virtualenv + then + echo "::error::Installing virtualenv package failed" + exit 1 + fi fi if ! "$PYTHON_BIN" -m virtualenv enricomi-publish-action-venv then - echo "Still cannot create venv after installing virtualenv package" + echo "::error::Cannot create venv after installing virtualenv package" exit 1 fi fi @@ -238,21 +254,26 @@ runs: PYTHON_VENV="$("$PYTHON_VENV" -c 'import sys; print(sys.executable)')" echo "Python in venv: $PYTHON_VENV" echo "PYTHON_VENV=$PYTHON_VENV" >> "$GITHUB_ENV" + echo '::endgroup::' shell: bash - name: Install Python dependencies run: | + # Install Python dependencies echo '::group::Install Python dependencies' + # make sure wheel is installed, which improves installing our dependencies "$PYTHON_VENV" -m pip install wheel "$PYTHON_VENV" -m pip install -r "$GITHUB_ACTION_PATH/../python/requirements.txt" + echo '::endgroup::' shell: bash - name: Publish Test Results id: test-results run: | + # Publish Test Results echo '::group::Publish Test Results' "$PYTHON_VENV" "$GITHUB_ACTION_PATH/../python/publish_test_results.py" echo '::endgroup::' diff --git a/linux/action.yml b/linux/action.yml new file mode 100644 index 00000000..b57869a5 --- /dev/null +++ b/linux/action.yml @@ -0,0 +1,301 @@ +name: 'Publish Test Results' +author: 'EnricoMi' +description: 'Publishes JUnit, NUnit, XUnit, TRX, JSON test results on GitHub for .NET, Dart, Java, JS, Jest, Mocha, Python, Scala, …' + +inputs: + github_token: + description: 'GitHub API Access Token.' + default: ${{ github.token }} + required: false + github_token_actor: + description: 'The name of the GitHub app that owns the GitHub API Access Token (see github_token). Used to identify pull request comments created by this action during earlier runs. Has to be set when `github_token` is set to a GitHub app installation token (other than GitHub actions). Otherwise, existing comments will not be updated, but new comments created. Note: this does not change the bot name of the pull request comments. Defaults to "github-actions".' + default: 'github-actions' + required: false + github_retries: + description: 'Requests to the GitHub API are retried this number of times. The value must be a positive integer or zero.' + default: '10' + required: false + commit: + description: 'Commit SHA to which test results are published. Only needed if the value of GITHUB_SHA does not work for you.' + required: false + check_name: + description: 'Name of the created check run.' + default: 'Test Results' + required: false + comment_title: + description: 'An alternative title for the pull request comment. Defaults to value of check_name input.' + required: false + comment_mode: + description: 'The action posts comments to pull requests that are associated with the commit. Set to "always" - always comment, "changes" - comment when changes w.r.t. the target branch exist, "changes in failures" - when changes in the number of failures and errors exist, "changes in errors" - when changes in the number of (only) errors exist, "failures" - when failures or errors exist, "errors" - when (only) errors exist, "off" - to not create pull request comments.' + default: 'always' + required: false + fail_on: + description: 'The created test result check run has failure state if any test fails or test errors occur. Never fails when set to "nothing", fails only on errors when set to "errors". Default is "test failures".' + default: 'test failures' + required: false + action_fail: + description: 'When set "true", the action itself fails when tests have failed (see option fail_on).' + default: 'false' + required: false + action_fail_on_inconclusive: + description: 'When set "true", the action itself fails when tests are inconclusive (no test results).' + default: 'false' + required: false + files: + description: 'File patterns of test result files. Relative paths are known to work best, while the non-Docker action also works with absolute paths. Supports "*", "**", "?", and "[]" character ranges. Use multiline string for multiple patterns. Patterns starting with "!" exclude the matching files. There have to be at least one pattern starting without a "!".' + required: false + junit_files: + description: 'Deprecated, use "files" option instead.' + required: false + nunit_files: + description: 'Deprecated, use "files" option instead.' + required: false + xunit_files: + description: 'Deprecated, use "files" option instead.' + required: false + trx_files: + description: 'Deprecated, use "files" option instead.' + required: false + time_unit: + description: 'Time values in the test result files have this unit. Supports "seconds" and "milliseconds".' + default: 'seconds' + required: false + test_file_prefix: + description: 'Paths in the test result files should be relative to the git repository for annotations to work best. This prefix is added to (if starting with "+"), or remove from (if starting with "-") test file paths. Examples: "+src/" or "-/opt/actions-runner".' + required: false + report_individual_runs: + description: 'Individual runs of the same test may see different failures. Reports all individual failures when set "true" or the first only otherwise.' + required: false + report_suite_logs: + description: 'In addition to reporting regular test logs, also report test suite logs. These are logs provided on suite level, not individual test level. Set to "info" for normal output, "error" for error output, "any" for both, or "none" for no suite logs at all. Defaults to "none".' + default: 'none' + required: false + deduplicate_classes_by_file_name: + description: 'De-duplicates classes with same name by their file name when set "true", combines test results for those classes otherwise.' + required: false + large_files: + description: 'Support for large files is enabled when set to "true". Defaults to "false", unless ignore_runs is "true".' + required: false + ignore_runs: + description: 'Does not collect test run information from the test result files, which is useful for very large files. This disables any check run annotations.' + default: 'false' + required: false + check_run: + description: 'Set to "true", the results are published as a check run, but it may not be associated with the workflow that ran this action.' + default: 'true' + required: false + job_summary: + description: 'Set to "true", the results are published as part of the job summary page of the workflow run.' + default: 'true' + required: false + compare_to_earlier_commit: + description: 'Test results are compared to results of earlier commits to highlight changes: "false" - disable comparison, "true" - compare across commits.' + default: 'true' + required: false + pull_request_build: + description: 'As part of pull requests, GitHub builds a merge commit, which combines the commit and the target branch. If tests ran on the actual pushed commit, then set this to "commit". Defaults to "merge".' + default: 'merge' + required: false + event_file: + description: 'An alternative event file to use. Useful to replace a "workflow_run" event file with the actual source event file.' + required: false + event_name: + description: 'An alternative event name to use. Useful to replace a "workflow_run" event name with the actual source event name: github.event.workflow_run.event.' + required: false + test_changes_limit: + description: 'Limits the number of removed or skipped tests reported on pull request comments. This report can be disabled with a value of 0. The default is 10.' + required: false + check_run_annotations: + description: 'Adds additional information to the check run. This is a comma-separated list of any of the following values: "all tests" - list all found tests, "skipped tests" - list all skipped tests. Set to "none" to add no extra annotations at all.' + default: 'all tests, skipped tests' + required: false + check_run_annotations_branch: + description: 'Adds check run annotations only on given branches. Comma-separated list of branch names allowed, asterisk "*" matches all branches. Defaults to event.repository.default_branch or "main, master".' + required: false + seconds_between_github_reads: + description: 'Sets the number of seconds the action waits between concurrent read requests to the GitHub API. This throttles the API usage to avoid abuse rate limits: https://docs.github.com/en/rest/overview/resources-in-the-rest-api#abuse-rate-limits.' + default: '0.25' + required: false + seconds_between_github_writes: + description: 'Sets the number of seconds the action waits between concurrent write requests to the GitHub API. This throttles the API usage to avoid abuse rate limits: https://docs.github.com/en/rest/overview/resources-in-the-rest-api#abuse-rate-limits.' + default: '2.0' + required: false + secondary_rate_limit_wait_seconds: + description: 'Sets the number of seconds to wait before retrying secondary rate limit errors. If not set, the default defined in the PyGithub library is used (currently 60 seconds).' + required: false + json_file: + description: 'Results are written to this JSON file.' + required: false + json_thousands_separator: + description: 'Formatted numbers in JSON use this character to separate groups of thousands. Common values are "," or ".". Defaults to punctuation space (\u2008).' + default: ' ' + required: false + json_suite_details: + description: 'Write out all suite details to the JSON file. Setting this to "true" can greatly increase the size of the output. Defaults to "false".' + default: 'false' + required: false + json_test_case_results: + description: 'Write out all individual test case results to the JSON file. Setting this to "true" can greatly increase the size of the output. Defaults to "false".' + default: 'false' + required: false + search_pull_requests: + description: 'Prior to v2.6.0, the action used the "/search/issues" REST API to find pull requests related to a commit. If you need to restore that behaviour, set this to "true". Defaults to "false".' + default: 'false' + required: false + +outputs: + json: + description: "Test results as JSON" + value: ${{ steps.test-results.outputs.json }} + +runs: + using: 'composite' + steps: + - name: Check for Python3 + id: python + run: | + # Check for Python3 + echo '::group::Check for Python3' + + # we check version here just to execute `python3` with an argument + # on Windows, there is a `python3.exe` that is a proxy to trigger installation from app store + # command `which python3` finds that, but `python3 -V` does not return the version on stdout + if ! which python3 || [[ "$(python3 -V)" != *"python 3."* && "$(python3 -V)" != *"Python 3."* ]] + then + if ! which python || [[ "$(python -V)" != *"python 3."* && "$(python -V)" != *"Python 3."* ]] + then + echo "::error::No python3 interpreter found. Please setup python before running this action. You could use https://github.com/actions/setup-python." + exit 1 + fi + + PYTHON_BIN="$(python -c 'import sys; print(sys.executable)')" + else + PYTHON_BIN="$(python3 -c 'import sys; print(sys.executable)')" + fi + + echo "Python that creates venv: $PYTHON_BIN" + echo "PYTHON_BIN=$PYTHON_BIN" >> "$GITHUB_ENV" + echo "version=$("$PYTHON_BIN" -V)" >> "$GITHUB_OUTPUT" + + echo '::endgroup::' + shell: bash + + - name: Restore PIP packages cache + uses: actions/cache/restore@v4 + id: cache + continue-on-error: true + with: + path: '~/.cache/pip' + key: enricomi-publish-action-${{ runner.os }}-${{ runner.arch }}-pip-${{ steps.python.outputs.version }}-70a313922fdbeb7398ec60313d908b11 + + - name: Create virtualenv + id: venv + continue-on-error: true + run: | + # Create virtualenv + echo '::group::Create virtualenv' + + echo "Python that creates venv: $PYTHON_BIN" + + echo "Creating virtual environment" + if ! "$PYTHON_BIN" -m virtualenv enricomi-publish-action-venv && ! "$PYTHON_BIN" -m venv enricomi-publish-action-venv + then + echo "Looks like there is neither virtualenv nor venv package installed" + if ! "$PYTHON_BIN" -m pip install virtualenv + then + echo "::error::Installing virtualenv package failed" + exit 1 + fi + + if ! "$PYTHON_BIN" -m virtualenv enricomi-publish-action-venv + then + echo "::error::Cannot create venv after installing virtualenv package" + exit 1 + fi + fi + + echo "Finding Python interpreter in venv" + PYTHON_VENV="enricomi-publish-action-venv/bin/python" + PYTHON_VENV="$("$PYTHON_VENV" -c 'import sys; print(sys.executable)')" + echo "Python in venv: $PYTHON_VENV" + echo "PYTHON_VENV=$PYTHON_VENV" >> "$GITHUB_ENV" + + echo '::endgroup::' + shell: bash + + - name: Install Python dependencies + run: | + # Install Python dependencies + echo '::group::Install Python dependencies' + + # make sure wheel is installed, which improves installing our dependencies + "$PYTHON_VENV" -m pip install wheel + "$PYTHON_VENV" -m pip install -r "$GITHUB_ACTION_PATH/../python/requirements.txt" + + echo '::endgroup::' + shell: bash + + - name: Publish Test Results + id: test-results + run: | + # Publish Test Results + echo '::group::Publish Test Results' + "$PYTHON_VENV" "$GITHUB_ACTION_PATH/../python/publish_test_results.py" + echo '::endgroup::' + env: + GITHUB_TOKEN: ${{ inputs.github_token }} + GITHUB_TOKEN_ACTOR: ${{ inputs.github_token_actor }} + GITHUB_RETRIES: ${{ inputs.github_retries }} + COMMIT: ${{ inputs.commit }} + CHECK_NAME: ${{ inputs.check_name }} + COMMENT_TITLE: ${{ inputs.comment_title }} + COMMENT_MODE: ${{ inputs.comment_mode }} + FAIL_ON: ${{ inputs.fail_on }} + ACTION_FAIL: ${{ inputs.action_fail }} + ACTION_FAIL_ON_INCONCLUSIVE: ${{ inputs.action_fail_on_inconclusive }} + FILES: ${{ inputs.files }} + JUNIT_FILES: ${{ inputs.junit_files }} + NUNIT_FILES: ${{ inputs.nunit_files }} + XUNIT_FILES: ${{ inputs.xunit_files }} + TRX_FILES: ${{ inputs.trx_files }} + TIME_UNIT: ${{ inputs.time_unit }} + TEST_FILE_PREFIX: ${{ inputs.test_file_prefix }} + REPORT_INDIVIDUAL_RUNS: ${{ inputs.report_individual_runs }} + REPORT_SUITE_LOGS: ${{ inputs.report_suite_logs }} + DEDUPLICATE_CLASSES_BY_FILE_NAME: ${{ inputs.deduplicate_classes_by_file_name }} + LARGE_FILES: ${{ inputs.large_files }} + IGNORE_RUNS: ${{ inputs.ignore_runs }} + COMPARE_TO_EARLIER_COMMIT: ${{ inputs.compare_to_earlier_commit }} + PULL_REQUEST_BUILD: ${{ inputs.pull_request_build }} + EVENT_FILE: ${{ inputs.event_file }} + EVENT_NAME: ${{ inputs.event_name }} + TEST_CHANGES_LIMIT: ${{ inputs.test_changes_limit }} + CHECK_RUN_ANNOTATIONS: ${{ inputs.check_run_annotations }} + CHECK_RUN_ANNOTATIONS_BRANCH: ${{ inputs.check_run_annotations_branch }} + SECONDS_BETWEEN_GITHUB_READS: ${{ inputs.seconds_between_github_reads }} + SECONDS_BETWEEN_GITHUB_WRITES: ${{ inputs.seconds_between_github_writes }} + SECONDARY_RATE_LIMIT_WAIT_SECONDS: ${{ inputs.secondary_rate_limit_wait_seconds }} + JSON_FILE: ${{ inputs.json_file }} + JSON_THOUSANDS_SEPARATOR: ${{ inputs.json_thousands_separator }} + JSON_SUITE_DETAILS: ${{ inputs.json_suite_details }} + JSON_TEST_CASE_RESULTS: ${{ inputs.json_test_case_results }} + CHECK_RUN: ${{ inputs.check_run }} + JOB_SUMMARY: ${{ inputs.job_summary }} + SEARCH_PULL_REQUESTS: ${{ inputs.search_pull_requests }} + # not documented + ROOT_LOG_LEVEL: ${{ inputs.root_log_level }} + # not documented + LOG_LEVEL: ${{ inputs.log_level }} + shell: bash + + - name: Save PIP packages cache + uses: actions/cache/save@v4 + if: ( success() || failure() ) && ! steps.cache.outputs.cache-hit + continue-on-error: true + with: + path: '~/.cache/pip' + key: ${{ steps.cache.outputs.cache-primary-key }} + +branding: + icon: 'check-circle' + color: 'green' diff --git a/macos/action.yml b/macos/action.yml new file mode 100644 index 00000000..439d7891 --- /dev/null +++ b/macos/action.yml @@ -0,0 +1,301 @@ +name: 'Publish Test Results' +author: 'EnricoMi' +description: 'Publishes JUnit, NUnit, XUnit, TRX, JSON test results on GitHub for .NET, Dart, Java, JS, Jest, Mocha, Python, Scala, …' + +inputs: + github_token: + description: 'GitHub API Access Token.' + default: ${{ github.token }} + required: false + github_token_actor: + description: 'The name of the GitHub app that owns the GitHub API Access Token (see github_token). Used to identify pull request comments created by this action during earlier runs. Has to be set when `github_token` is set to a GitHub app installation token (other than GitHub actions). Otherwise, existing comments will not be updated, but new comments created. Note: this does not change the bot name of the pull request comments. Defaults to "github-actions".' + default: 'github-actions' + required: false + github_retries: + description: 'Requests to the GitHub API are retried this number of times. The value must be a positive integer or zero.' + default: '10' + required: false + commit: + description: 'Commit SHA to which test results are published. Only needed if the value of GITHUB_SHA does not work for you.' + required: false + check_name: + description: 'Name of the created check run.' + default: 'Test Results' + required: false + comment_title: + description: 'An alternative title for the pull request comment. Defaults to value of check_name input.' + required: false + comment_mode: + description: 'The action posts comments to pull requests that are associated with the commit. Set to "always" - always comment, "changes" - comment when changes w.r.t. the target branch exist, "changes in failures" - when changes in the number of failures and errors exist, "changes in errors" - when changes in the number of (only) errors exist, "failures" - when failures or errors exist, "errors" - when (only) errors exist, "off" - to not create pull request comments.' + default: 'always' + required: false + fail_on: + description: 'The created test result check run has failure state if any test fails or test errors occur. Never fails when set to "nothing", fails only on errors when set to "errors". Default is "test failures".' + default: 'test failures' + required: false + action_fail: + description: 'When set "true", the action itself fails when tests have failed (see option fail_on).' + default: 'false' + required: false + action_fail_on_inconclusive: + description: 'When set "true", the action itself fails when tests are inconclusive (no test results).' + default: 'false' + required: false + files: + description: 'File patterns of test result files. Relative paths are known to work best, while the non-Docker action also works with absolute paths. Supports "*", "**", "?", and "[]" character ranges. Use multiline string for multiple patterns. Patterns starting with "!" exclude the matching files. There have to be at least one pattern starting without a "!".' + required: false + junit_files: + description: 'Deprecated, use "files" option instead.' + required: false + nunit_files: + description: 'Deprecated, use "files" option instead.' + required: false + xunit_files: + description: 'Deprecated, use "files" option instead.' + required: false + trx_files: + description: 'Deprecated, use "files" option instead.' + required: false + time_unit: + description: 'Time values in the test result files have this unit. Supports "seconds" and "milliseconds".' + default: 'seconds' + required: false + test_file_prefix: + description: 'Paths in the test result files should be relative to the git repository for annotations to work best. This prefix is added to (if starting with "+"), or remove from (if starting with "-") test file paths. Examples: "+src/" or "-/opt/actions-runner".' + required: false + report_individual_runs: + description: 'Individual runs of the same test may see different failures. Reports all individual failures when set "true" or the first only otherwise.' + required: false + report_suite_logs: + description: 'In addition to reporting regular test logs, also report test suite logs. These are logs provided on suite level, not individual test level. Set to "info" for normal output, "error" for error output, "any" for both, or "none" for no suite logs at all. Defaults to "none".' + default: 'none' + required: false + deduplicate_classes_by_file_name: + description: 'De-duplicates classes with same name by their file name when set "true", combines test results for those classes otherwise.' + required: false + large_files: + description: 'Support for large files is enabled when set to "true". Defaults to "false", unless ignore_runs is "true".' + required: false + ignore_runs: + description: 'Does not collect test run information from the test result files, which is useful for very large files. This disables any check run annotations.' + default: 'false' + required: false + check_run: + description: 'Set to "true", the results are published as a check run, but it may not be associated with the workflow that ran this action.' + default: 'true' + required: false + job_summary: + description: 'Set to "true", the results are published as part of the job summary page of the workflow run.' + default: 'true' + required: false + compare_to_earlier_commit: + description: 'Test results are compared to results of earlier commits to highlight changes: "false" - disable comparison, "true" - compare across commits.' + default: 'true' + required: false + pull_request_build: + description: 'As part of pull requests, GitHub builds a merge commit, which combines the commit and the target branch. If tests ran on the actual pushed commit, then set this to "commit". Defaults to "merge".' + default: 'merge' + required: false + event_file: + description: 'An alternative event file to use. Useful to replace a "workflow_run" event file with the actual source event file.' + required: false + event_name: + description: 'An alternative event name to use. Useful to replace a "workflow_run" event name with the actual source event name: github.event.workflow_run.event.' + required: false + test_changes_limit: + description: 'Limits the number of removed or skipped tests reported on pull request comments. This report can be disabled with a value of 0. The default is 10.' + required: false + check_run_annotations: + description: 'Adds additional information to the check run. This is a comma-separated list of any of the following values: "all tests" - list all found tests, "skipped tests" - list all skipped tests. Set to "none" to add no extra annotations at all.' + default: 'all tests, skipped tests' + required: false + check_run_annotations_branch: + description: 'Adds check run annotations only on given branches. Comma-separated list of branch names allowed, asterisk "*" matches all branches. Defaults to event.repository.default_branch or "main, master".' + required: false + seconds_between_github_reads: + description: 'Sets the number of seconds the action waits between concurrent read requests to the GitHub API. This throttles the API usage to avoid abuse rate limits: https://docs.github.com/en/rest/overview/resources-in-the-rest-api#abuse-rate-limits.' + default: '0.25' + required: false + seconds_between_github_writes: + description: 'Sets the number of seconds the action waits between concurrent write requests to the GitHub API. This throttles the API usage to avoid abuse rate limits: https://docs.github.com/en/rest/overview/resources-in-the-rest-api#abuse-rate-limits.' + default: '2.0' + required: false + secondary_rate_limit_wait_seconds: + description: 'Sets the number of seconds to wait before retrying secondary rate limit errors. If not set, the default defined in the PyGithub library is used (currently 60 seconds).' + required: false + json_file: + description: 'Results are written to this JSON file.' + required: false + json_thousands_separator: + description: 'Formatted numbers in JSON use this character to separate groups of thousands. Common values are "," or ".". Defaults to punctuation space (\u2008).' + default: ' ' + required: false + json_suite_details: + description: 'Write out all suite details to the JSON file. Setting this to "true" can greatly increase the size of the output. Defaults to "false".' + default: 'false' + required: false + json_test_case_results: + description: 'Write out all individual test case results to the JSON file. Setting this to "true" can greatly increase the size of the output. Defaults to "false".' + default: 'false' + required: false + search_pull_requests: + description: 'Prior to v2.6.0, the action used the "/search/issues" REST API to find pull requests related to a commit. If you need to restore that behaviour, set this to "true". Defaults to "false".' + default: 'false' + required: false + +outputs: + json: + description: "Test results as JSON" + value: ${{ steps.test-results.outputs.json }} + +runs: + using: 'composite' + steps: + - name: Check for Python3 + id: python + run: | + # Check for Python3 + echo '::group::Check for Python3' + + # we check version here just to execute `python3` with an argument + # on Windows, there is a `python3.exe` that is a proxy to trigger installation from app store + # command `which python3` finds that, but `python3 -V` does not return the version on stdout + if ! which python3 || [[ "$(python3 -V)" != *"python 3."* && "$(python3 -V)" != *"Python 3."* ]] + then + if ! which python || [[ "$(python -V)" != *"python 3."* && "$(python -V)" != *"Python 3."* ]] + then + echo "::error::No python3 interpreter found. Please setup python before running this action. You could use https://github.com/actions/setup-python." + exit 1 + fi + + PYTHON_BIN="$(python -c 'import sys; print(sys.executable)')" + else + PYTHON_BIN="$(python3 -c 'import sys; print(sys.executable)')" + fi + + echo "Python that creates venv: $PYTHON_BIN" + echo "PYTHON_BIN=$PYTHON_BIN" >> "$GITHUB_ENV" + echo "version=$("$PYTHON_BIN" -V)" >> "$GITHUB_OUTPUT" + + echo '::endgroup::' + shell: bash + + - name: Restore PIP packages cache + uses: actions/cache/restore@v4 + id: cache + continue-on-error: true + with: + path: '~/Library/Caches/pip' + key: enricomi-publish-action-${{ runner.os }}-${{ runner.arch }}-pip-${{ steps.python.outputs.version }}-70a313922fdbeb7398ec60313d908b11 + + - name: Create virtualenv + id: venv + continue-on-error: true + run: | + # Create virtualenv + echo '::group::Create virtualenv' + + echo "Python that creates venv: $PYTHON_BIN" + + echo "Creating virtual environment" + if ! "$PYTHON_BIN" -m virtualenv enricomi-publish-action-venv && ! "$PYTHON_BIN" -m venv enricomi-publish-action-venv + then + echo "Looks like there is neither virtualenv nor venv package installed" + if ! "$PYTHON_BIN" -m pip install virtualenv + then + echo "::error::Installing virtualenv package failed" + exit 1 + fi + + if ! "$PYTHON_BIN" -m virtualenv enricomi-publish-action-venv + then + echo "::error::Cannot create venv after installing virtualenv package" + exit 1 + fi + fi + + echo "Finding Python interpreter in venv" + PYTHON_VENV="enricomi-publish-action-venv/bin/python" + PYTHON_VENV="$("$PYTHON_VENV" -c 'import sys; print(sys.executable)')" + echo "Python in venv: $PYTHON_VENV" + echo "PYTHON_VENV=$PYTHON_VENV" >> "$GITHUB_ENV" + + echo '::endgroup::' + shell: bash + + - name: Install Python dependencies + run: | + # Install Python dependencies + echo '::group::Install Python dependencies' + + # make sure wheel is installed, which improves installing our dependencies + "$PYTHON_VENV" -m pip install wheel + "$PYTHON_VENV" -m pip install -r "$GITHUB_ACTION_PATH/../python/requirements.txt" + + echo '::endgroup::' + shell: bash + + - name: Publish Test Results + id: test-results + run: | + # Publish Test Results + echo '::group::Publish Test Results' + "$PYTHON_VENV" "$GITHUB_ACTION_PATH/../python/publish_test_results.py" + echo '::endgroup::' + env: + GITHUB_TOKEN: ${{ inputs.github_token }} + GITHUB_TOKEN_ACTOR: ${{ inputs.github_token_actor }} + GITHUB_RETRIES: ${{ inputs.github_retries }} + COMMIT: ${{ inputs.commit }} + CHECK_NAME: ${{ inputs.check_name }} + COMMENT_TITLE: ${{ inputs.comment_title }} + COMMENT_MODE: ${{ inputs.comment_mode }} + FAIL_ON: ${{ inputs.fail_on }} + ACTION_FAIL: ${{ inputs.action_fail }} + ACTION_FAIL_ON_INCONCLUSIVE: ${{ inputs.action_fail_on_inconclusive }} + FILES: ${{ inputs.files }} + JUNIT_FILES: ${{ inputs.junit_files }} + NUNIT_FILES: ${{ inputs.nunit_files }} + XUNIT_FILES: ${{ inputs.xunit_files }} + TRX_FILES: ${{ inputs.trx_files }} + TIME_UNIT: ${{ inputs.time_unit }} + TEST_FILE_PREFIX: ${{ inputs.test_file_prefix }} + REPORT_INDIVIDUAL_RUNS: ${{ inputs.report_individual_runs }} + REPORT_SUITE_LOGS: ${{ inputs.report_suite_logs }} + DEDUPLICATE_CLASSES_BY_FILE_NAME: ${{ inputs.deduplicate_classes_by_file_name }} + LARGE_FILES: ${{ inputs.large_files }} + IGNORE_RUNS: ${{ inputs.ignore_runs }} + COMPARE_TO_EARLIER_COMMIT: ${{ inputs.compare_to_earlier_commit }} + PULL_REQUEST_BUILD: ${{ inputs.pull_request_build }} + EVENT_FILE: ${{ inputs.event_file }} + EVENT_NAME: ${{ inputs.event_name }} + TEST_CHANGES_LIMIT: ${{ inputs.test_changes_limit }} + CHECK_RUN_ANNOTATIONS: ${{ inputs.check_run_annotations }} + CHECK_RUN_ANNOTATIONS_BRANCH: ${{ inputs.check_run_annotations_branch }} + SECONDS_BETWEEN_GITHUB_READS: ${{ inputs.seconds_between_github_reads }} + SECONDS_BETWEEN_GITHUB_WRITES: ${{ inputs.seconds_between_github_writes }} + SECONDARY_RATE_LIMIT_WAIT_SECONDS: ${{ inputs.secondary_rate_limit_wait_seconds }} + JSON_FILE: ${{ inputs.json_file }} + JSON_THOUSANDS_SEPARATOR: ${{ inputs.json_thousands_separator }} + JSON_SUITE_DETAILS: ${{ inputs.json_suite_details }} + JSON_TEST_CASE_RESULTS: ${{ inputs.json_test_case_results }} + CHECK_RUN: ${{ inputs.check_run }} + JOB_SUMMARY: ${{ inputs.job_summary }} + SEARCH_PULL_REQUESTS: ${{ inputs.search_pull_requests }} + # not documented + ROOT_LOG_LEVEL: ${{ inputs.root_log_level }} + # not documented + LOG_LEVEL: ${{ inputs.log_level }} + shell: bash + + - name: Save PIP packages cache + uses: actions/cache/save@v4 + if: ( success() || failure() ) && ! steps.cache.outputs.cache-hit + continue-on-error: true + with: + path: '~/Library/Caches/pip' + key: ${{ steps.cache.outputs.cache-primary-key }} + +branding: + icon: 'check-circle' + color: 'green' diff --git a/python/test/test_action_yml.py b/python/test/test_action_yml.py index 23529e9e..c99338c7 100644 --- a/python/test/test_action_yml.py +++ b/python/test/test_action_yml.py @@ -24,10 +24,25 @@ def test_action_version(self): self.assertEqual(__version__, version, 'version in action.yml must match __version__ in python/publish/__init__.py') def test_composite_action(self): + self.do_test_composite_action('composite') + + def test_linux_action(self): + self.do_test_composite_action('linux') + + def test_macos_action(self): + self.do_test_composite_action('macos') + + def test_windows_action(self): + self.do_test_composite_action('windows') + + def test_windows_bash_action(self): + self.do_test_composite_action('windows/bash') + + def do_test_composite_action(self, action: str): with open(project_root / 'action.yml', encoding='utf-8') as r: dockerfile_action = yaml.safe_load(r) - with open(project_root / 'composite/action.yml', encoding='utf-8') as r: + with open(project_root / f'{action}/action.yml', encoding='utf-8') as r: composite_action = yaml.safe_load(r) self.assertIn('runs', dockerfile_action) @@ -43,26 +58,35 @@ def test_composite_action(self): self.assertEqual(dockerfile_action_wo_runs, composite_action_wo_runs) self.assertIn(('using', 'composite'), composite_action.get('runs', {}).items()) - # check cache key hash is up-to-date in composite action - # this md5 is linux-based (on Windows, git uses different newlines, which changes the hash) - if sys.platform != 'win32': - with open(project_root / 'python' / 'requirements.txt', mode='rb') as r: - expected_hash = hashlib.md5(r.read()).hexdigest() - cache_hash = next(step.get('with', {}).get('key', '').split('-')[-1] - for step in composite_action.get('runs', {}).get('steps', []) - if step.get('uses', '').startswith('actions/cache/restore@')) - self.assertEqual(expected_hash, cache_hash, msg='Changing python/requirements.txt requires ' - 'to update the MD5 hash in composite/action.yaml') - - def test_composite_inputs(self): - with open(project_root / 'composite/action.yml', encoding='utf-8') as r: - action = yaml.safe_load(r) - + # check inputs forwarded to action # these are not documented in the action.yml files but still needs to be forwarded extra_inputs = ['files', 'root_log_level', 'log_level'] - expected = {key.upper(): f'${{{{ inputs.{key} }}}}' for key in list(action.get('inputs', {}).keys()) + extra_inputs} - - steps = action.get('runs', {}).get('steps', []) - step = next((step for step in steps if step.get('name') == 'Publish Test Results'), {}) - inputs = {key.upper(): value for key, value in step.get('env', {}).items()} - self.assertEqual(expected, inputs) + expected = {key.upper(): f'${{{{ inputs.{key} }}}}' + for key in list(composite_action.get('inputs', {}).keys()) + extra_inputs} + + steps = composite_action.get('runs', {}).get('steps', []) + steps = [step for step in steps if step.get('name') == 'Publish Test Results'] + self.assertTrue(len(steps) > 0) + for step in steps: + self.assertIn('env', step, step.get('name')) + inputs = {key.upper(): value for key, value in step.get('env', {}).items()} + self.assertEqual(expected, inputs) + + # the 'composite' composite action is just a proxy to the os-specific actions, so there is no caching + if action != 'composite': + # check cache key hash is up-to-date in composite action + # this md5 is linux-based (on Windows, git uses different newlines, which changes the hash) + if sys.platform != 'win32': + with open(project_root / 'python' / 'requirements.txt', mode='rb') as r: + expected_hash = hashlib.md5(r.read()).hexdigest() + cache_hash = next(step.get('with', {}).get('key', '').split('-')[-1] + for step in composite_action.get('runs', {}).get('steps', []) + if step.get('uses', '').startswith('actions/cache/restore@')) + self.assertEqual(expected_hash, cache_hash, msg='Changing python/requirements.txt requires ' + 'to update the MD5 hash in composite/action.yaml') + + def test_proxy_action(self): + # TODO + # run RUNNER_OS=Linux|Windows|macOS GITHUB_ACTION_PATH=... composite/proxy.sh + # and compare with python/test/files/proxy.yml + pass diff --git a/windows/action.yml b/windows/action.yml new file mode 100644 index 00000000..c04b810d --- /dev/null +++ b/windows/action.yml @@ -0,0 +1,306 @@ +name: 'Publish Test Results' +author: 'EnricoMi' +description: 'Publishes JUnit, NUnit, XUnit, TRX, JSON test results on GitHub for .NET, Dart, Java, JS, Jest, Mocha, Python, Scala, …' + +inputs: + github_token: + description: 'GitHub API Access Token.' + default: ${{ github.token }} + required: false + github_token_actor: + description: 'The name of the GitHub app that owns the GitHub API Access Token (see github_token). Used to identify pull request comments created by this action during earlier runs. Has to be set when `github_token` is set to a GitHub app installation token (other than GitHub actions). Otherwise, existing comments will not be updated, but new comments created. Note: this does not change the bot name of the pull request comments. Defaults to "github-actions".' + default: 'github-actions' + required: false + github_retries: + description: 'Requests to the GitHub API are retried this number of times. The value must be a positive integer or zero.' + default: '10' + required: false + commit: + description: 'Commit SHA to which test results are published. Only needed if the value of GITHUB_SHA does not work for you.' + required: false + check_name: + description: 'Name of the created check run.' + default: 'Test Results' + required: false + comment_title: + description: 'An alternative title for the pull request comment. Defaults to value of check_name input.' + required: false + comment_mode: + description: 'The action posts comments to pull requests that are associated with the commit. Set to "always" - always comment, "changes" - comment when changes w.r.t. the target branch exist, "changes in failures" - when changes in the number of failures and errors exist, "changes in errors" - when changes in the number of (only) errors exist, "failures" - when failures or errors exist, "errors" - when (only) errors exist, "off" - to not create pull request comments.' + default: 'always' + required: false + fail_on: + description: 'The created test result check run has failure state if any test fails or test errors occur. Never fails when set to "nothing", fails only on errors when set to "errors". Default is "test failures".' + default: 'test failures' + required: false + action_fail: + description: 'When set "true", the action itself fails when tests have failed (see option fail_on).' + default: 'false' + required: false + action_fail_on_inconclusive: + description: 'When set "true", the action itself fails when tests are inconclusive (no test results).' + default: 'false' + required: false + files: + description: 'File patterns of test result files. Relative paths are known to work best, while the non-Docker action also works with absolute paths. Supports "*", "**", "?", and "[]" character ranges. Use multiline string for multiple patterns. Patterns starting with "!" exclude the matching files. There have to be at least one pattern starting without a "!".' + required: false + junit_files: + description: 'Deprecated, use "files" option instead.' + required: false + nunit_files: + description: 'Deprecated, use "files" option instead.' + required: false + xunit_files: + description: 'Deprecated, use "files" option instead.' + required: false + trx_files: + description: 'Deprecated, use "files" option instead.' + required: false + time_unit: + description: 'Time values in the test result files have this unit. Supports "seconds" and "milliseconds".' + default: 'seconds' + required: false + test_file_prefix: + description: 'Paths in the test result files should be relative to the git repository for annotations to work best. This prefix is added to (if starting with "+"), or remove from (if starting with "-") test file paths. Examples: "+src/" or "-/opt/actions-runner".' + required: false + report_individual_runs: + description: 'Individual runs of the same test may see different failures. Reports all individual failures when set "true" or the first only otherwise.' + required: false + report_suite_logs: + description: 'In addition to reporting regular test logs, also report test suite logs. These are logs provided on suite level, not individual test level. Set to "info" for normal output, "error" for error output, "any" for both, or "none" for no suite logs at all. Defaults to "none".' + default: 'none' + required: false + deduplicate_classes_by_file_name: + description: 'De-duplicates classes with same name by their file name when set "true", combines test results for those classes otherwise.' + required: false + large_files: + description: 'Support for large files is enabled when set to "true". Defaults to "false", unless ignore_runs is "true".' + required: false + ignore_runs: + description: 'Does not collect test run information from the test result files, which is useful for very large files. This disables any check run annotations.' + default: 'false' + required: false + check_run: + description: 'Set to "true", the results are published as a check run, but it may not be associated with the workflow that ran this action.' + default: 'true' + required: false + job_summary: + description: 'Set to "true", the results are published as part of the job summary page of the workflow run.' + default: 'true' + required: false + compare_to_earlier_commit: + description: 'Test results are compared to results of earlier commits to highlight changes: "false" - disable comparison, "true" - compare across commits.' + default: 'true' + required: false + pull_request_build: + description: 'As part of pull requests, GitHub builds a merge commit, which combines the commit and the target branch. If tests ran on the actual pushed commit, then set this to "commit". Defaults to "merge".' + default: 'merge' + required: false + event_file: + description: 'An alternative event file to use. Useful to replace a "workflow_run" event file with the actual source event file.' + required: false + event_name: + description: 'An alternative event name to use. Useful to replace a "workflow_run" event name with the actual source event name: github.event.workflow_run.event.' + required: false + test_changes_limit: + description: 'Limits the number of removed or skipped tests reported on pull request comments. This report can be disabled with a value of 0. The default is 10.' + required: false + check_run_annotations: + description: 'Adds additional information to the check run. This is a comma-separated list of any of the following values: "all tests" - list all found tests, "skipped tests" - list all skipped tests. Set to "none" to add no extra annotations at all.' + default: 'all tests, skipped tests' + required: false + check_run_annotations_branch: + description: 'Adds check run annotations only on given branches. Comma-separated list of branch names allowed, asterisk "*" matches all branches. Defaults to event.repository.default_branch or "main, master".' + required: false + seconds_between_github_reads: + description: 'Sets the number of seconds the action waits between concurrent read requests to the GitHub API. This throttles the API usage to avoid abuse rate limits: https://docs.github.com/en/rest/overview/resources-in-the-rest-api#abuse-rate-limits.' + default: '0.25' + required: false + seconds_between_github_writes: + description: 'Sets the number of seconds the action waits between concurrent write requests to the GitHub API. This throttles the API usage to avoid abuse rate limits: https://docs.github.com/en/rest/overview/resources-in-the-rest-api#abuse-rate-limits.' + default: '2.0' + required: false + secondary_rate_limit_wait_seconds: + description: 'Sets the number of seconds to wait before retrying secondary rate limit errors. If not set, the default defined in the PyGithub library is used (currently 60 seconds).' + required: false + json_file: + description: 'Results are written to this JSON file.' + required: false + json_thousands_separator: + description: 'Formatted numbers in JSON use this character to separate groups of thousands. Common values are "," or ".". Defaults to punctuation space (\u2008).' + default: ' ' + required: false + json_suite_details: + description: 'Write out all suite details to the JSON file. Setting this to "true" can greatly increase the size of the output. Defaults to "false".' + default: 'false' + required: false + json_test_case_results: + description: 'Write out all individual test case results to the JSON file. Setting this to "true" can greatly increase the size of the output. Defaults to "false".' + default: 'false' + required: false + search_pull_requests: + description: 'Prior to v2.6.0, the action used the "/search/issues" REST API to find pull requests related to a commit. If you need to restore that behaviour, set this to "true". Defaults to "false".' + default: 'false' + required: false + +outputs: + json: + description: "Test results as JSON" + value: ${{ steps.test-results.outputs.json }} + +runs: + using: 'composite' + steps: + - name: Check for Python3 + id: python + run: | + # Check for Python3 + Write-Output "::group::Check for Python3" + try { + # we check version here just to execute `python3` with an argument + # on Windows, there is a `python3.exe` that is a proxy to trigger installation from app store + # command `which python3` finds that, but `python3 -V` does not return the version on stdout + if ( !(Get-Command python3 -ErrorAction SilentlyContinue) -Or -Not ((python3 -V 2> $null) -Like "Python 3.*") ) { + if ( !(Get-Command python -ErrorAction SilentlyContinue) -Or -Not ((python -V 2> $null) -Like "Python 3.*") ) { + Write-Output "::error::No python3 interpreter found. Please setup python before running this action. You could use https://github.com/actions/setup-python." + Exit 1 + } + + $PYTHON_BIN = python -c "import sys; print(sys.executable)" + } else { + $PYTHON_BIN = python3 -c "import sys; print(sys.executable)" + } + Write-Output "Python that creates venv: $PYTHON_BIN" + "PYTHON_BIN=$PYTHON_BIN" | Out-File -FilePath $env:GITHUB_ENV -Append + + $VERSION = Invoke-Expression -Command "& '$PYTHON_BIN' -V" + Write-Output "Python version: $VERSION" + "version=$VERSION" | Out-File -FilePath $env:GITHUB_OUTPUT -Append + } finally { + Write-Output "::endgroup::" + } + shell: pwsh + + - name: Restore PIP packages cache + uses: actions/cache/restore@v4 + id: cache + continue-on-error: true + with: + path: '~\AppData\Local\pip\Cache' + key: enricomi-publish-action-${{ runner.os }}-${{ runner.arch }}-pip-${{ steps.python.outputs.version }}-70a313922fdbeb7398ec60313d908b11 + + - name: Create virtualenv + id: venv + continue-on-error: true + run: | + # Create virtualenv + Write-Output "::group::Create virtualenv" + try { + Write-Output "Python that creates venv: $env:PYTHON_BIN" + + Write-Output "Creating virtual environment" + if ( -Not (Invoke-Expression -Command "& '$env:PYTHON_BIN' -m virtualenv enricomi-publish-action-venv" 2> $null) -And -Not (Invoke-Expression -Command "& '$env:PYTHON_BIN' -m venv enricomi-publish-action-venv" 2> $null) ) { + Write-Output "Looks like there is neither virtualenv nor venv package installed" + if ( -Not (Invoke-Expression -Command "& '$env:PYTHON_BIN' -m pip install --user virtualenv" 2> $null) ) { + Write-Output "Installing virtualenv package with PIP option '--user' failed, now trying without" + if ( -Not (Invoke-Expression -Command "& '$env:PYTHON_BIN' -m pip install virtualenv" 2> $null) ) { + Write-Output "::error::Installing virtualenv package failed" + Exit 1 + } + } + + if ( -Not (Invoke-Expression -Command "& '$env:PYTHON_BIN' -m virtualenv enricomi-publish-action-venv" 2> $null) ) { + Write-Output "::error::Cannot create venv after installing virtualenv package" + Exit 1 + } + } + + Write-Output "Finding Python interpreter in venv" + $PYTHON_VENV = enricomi-publish-action-venv\Scripts\python -c "import sys; print(sys.executable)" + Write-Output "Python in venv: $PYTHON_VENV" + "PYTHON_VENV=$PYTHON_VENV" | Out-File -FilePath $env:GITHUB_ENV -Append + } finally { + Write-Output "::endgroup::" + } + shell: pwsh + + - name: Install Python dependencies + run: | + # Install Python dependencies + Write-Output "::group::Install Python dependencies" + try { + # make sure wheel is installed, which improves installing our dependencies + Invoke-Expression -Command "& '$env:PYTHON_VENV' -m pip install wheel" + Invoke-Expression -Command "& '$env:PYTHON_VENV' -m pip install -r '$env.GITHUB_ACTION_PATH\..\python\requirements.txt'" + } finally { + Write-Output "::endgroup::" + } + shell: pwsh + + - name: Publish Test Results + id: test-results + run: | + # Publish Test Results + Write-Output "::group::Publish Test Results" + try { + Invoke-Expression -Command "& '$env:PYTHON_VENV' '$env:GITHUB_ACTION_PATH\..\python\publish_test_results.py'" + } finally { + Write-Output "::endgroup::" + } + env: + GITHUB_TOKEN: ${{ inputs.github_token }} + GITHUB_TOKEN_ACTOR: ${{ inputs.github_token_actor }} + GITHUB_RETRIES: ${{ inputs.github_retries }} + COMMIT: ${{ inputs.commit }} + CHECK_NAME: ${{ inputs.check_name }} + COMMENT_TITLE: ${{ inputs.comment_title }} + COMMENT_MODE: ${{ inputs.comment_mode }} + FAIL_ON: ${{ inputs.fail_on }} + ACTION_FAIL: ${{ inputs.action_fail }} + ACTION_FAIL_ON_INCONCLUSIVE: ${{ inputs.action_fail_on_inconclusive }} + FILES: ${{ inputs.files }} + JUNIT_FILES: ${{ inputs.junit_files }} + NUNIT_FILES: ${{ inputs.nunit_files }} + XUNIT_FILES: ${{ inputs.xunit_files }} + TRX_FILES: ${{ inputs.trx_files }} + TIME_UNIT: ${{ inputs.time_unit }} + TEST_FILE_PREFIX: ${{ inputs.test_file_prefix }} + REPORT_INDIVIDUAL_RUNS: ${{ inputs.report_individual_runs }} + REPORT_SUITE_LOGS: ${{ inputs.report_suite_logs }} + DEDUPLICATE_CLASSES_BY_FILE_NAME: ${{ inputs.deduplicate_classes_by_file_name }} + LARGE_FILES: ${{ inputs.large_files }} + IGNORE_RUNS: ${{ inputs.ignore_runs }} + COMPARE_TO_EARLIER_COMMIT: ${{ inputs.compare_to_earlier_commit }} + PULL_REQUEST_BUILD: ${{ inputs.pull_request_build }} + EVENT_FILE: ${{ inputs.event_file }} + EVENT_NAME: ${{ inputs.event_name }} + TEST_CHANGES_LIMIT: ${{ inputs.test_changes_limit }} + CHECK_RUN_ANNOTATIONS: ${{ inputs.check_run_annotations }} + CHECK_RUN_ANNOTATIONS_BRANCH: ${{ inputs.check_run_annotations_branch }} + SECONDS_BETWEEN_GITHUB_READS: ${{ inputs.seconds_between_github_reads }} + SECONDS_BETWEEN_GITHUB_WRITES: ${{ inputs.seconds_between_github_writes }} + SECONDARY_RATE_LIMIT_WAIT_SECONDS: ${{ inputs.secondary_rate_limit_wait_seconds }} + JSON_FILE: ${{ inputs.json_file }} + JSON_THOUSANDS_SEPARATOR: ${{ inputs.json_thousands_separator }} + JSON_SUITE_DETAILS: ${{ inputs.json_suite_details }} + JSON_TEST_CASE_RESULTS: ${{ inputs.json_test_case_results }} + CHECK_RUN: ${{ inputs.check_run }} + JOB_SUMMARY: ${{ inputs.job_summary }} + SEARCH_PULL_REQUESTS: ${{ inputs.search_pull_requests }} + # not documented + ROOT_LOG_LEVEL: ${{ inputs.root_log_level }} + # not documented + LOG_LEVEL: ${{ inputs.log_level }} + shell: pwsh + + - name: Save PIP packages cache + uses: actions/cache/save@v4 + if: ( success() || failure() ) && ! steps.cache.outputs.cache-hit + continue-on-error: true + with: + path: '~\AppData\Local\pip\Cache' + key: ${{ steps.cache.outputs.cache-primary-key }} + +branding: + icon: 'check-circle' + color: 'green' diff --git a/windows/bash/action.yml b/windows/bash/action.yml new file mode 100644 index 00000000..77925df4 --- /dev/null +++ b/windows/bash/action.yml @@ -0,0 +1,305 @@ +name: 'Publish Test Results' +author: 'EnricoMi' +description: 'Publishes JUnit, NUnit, XUnit, TRX, JSON test results on GitHub for .NET, Dart, Java, JS, Jest, Mocha, Python, Scala, …' + +inputs: + github_token: + description: 'GitHub API Access Token.' + default: ${{ github.token }} + required: false + github_token_actor: + description: 'The name of the GitHub app that owns the GitHub API Access Token (see github_token). Used to identify pull request comments created by this action during earlier runs. Has to be set when `github_token` is set to a GitHub app installation token (other than GitHub actions). Otherwise, existing comments will not be updated, but new comments created. Note: this does not change the bot name of the pull request comments. Defaults to "github-actions".' + default: 'github-actions' + required: false + github_retries: + description: 'Requests to the GitHub API are retried this number of times. The value must be a positive integer or zero.' + default: '10' + required: false + commit: + description: 'Commit SHA to which test results are published. Only needed if the value of GITHUB_SHA does not work for you.' + required: false + check_name: + description: 'Name of the created check run.' + default: 'Test Results' + required: false + comment_title: + description: 'An alternative title for the pull request comment. Defaults to value of check_name input.' + required: false + comment_mode: + description: 'The action posts comments to pull requests that are associated with the commit. Set to "always" - always comment, "changes" - comment when changes w.r.t. the target branch exist, "changes in failures" - when changes in the number of failures and errors exist, "changes in errors" - when changes in the number of (only) errors exist, "failures" - when failures or errors exist, "errors" - when (only) errors exist, "off" - to not create pull request comments.' + default: 'always' + required: false + fail_on: + description: 'The created test result check run has failure state if any test fails or test errors occur. Never fails when set to "nothing", fails only on errors when set to "errors". Default is "test failures".' + default: 'test failures' + required: false + action_fail: + description: 'When set "true", the action itself fails when tests have failed (see option fail_on).' + default: 'false' + required: false + action_fail_on_inconclusive: + description: 'When set "true", the action itself fails when tests are inconclusive (no test results).' + default: 'false' + required: false + files: + description: 'File patterns of test result files. Relative paths are known to work best, while the non-Docker action also works with absolute paths. Supports "*", "**", "?", and "[]" character ranges. Use multiline string for multiple patterns. Patterns starting with "!" exclude the matching files. There have to be at least one pattern starting without a "!".' + required: false + junit_files: + description: 'Deprecated, use "files" option instead.' + required: false + nunit_files: + description: 'Deprecated, use "files" option instead.' + required: false + xunit_files: + description: 'Deprecated, use "files" option instead.' + required: false + trx_files: + description: 'Deprecated, use "files" option instead.' + required: false + time_unit: + description: 'Time values in the test result files have this unit. Supports "seconds" and "milliseconds".' + default: 'seconds' + required: false + test_file_prefix: + description: 'Paths in the test result files should be relative to the git repository for annotations to work best. This prefix is added to (if starting with "+"), or remove from (if starting with "-") test file paths. Examples: "+src/" or "-/opt/actions-runner".' + required: false + report_individual_runs: + description: 'Individual runs of the same test may see different failures. Reports all individual failures when set "true" or the first only otherwise.' + required: false + report_suite_logs: + description: 'In addition to reporting regular test logs, also report test suite logs. These are logs provided on suite level, not individual test level. Set to "info" for normal output, "error" for error output, "any" for both, or "none" for no suite logs at all. Defaults to "none".' + default: 'none' + required: false + deduplicate_classes_by_file_name: + description: 'De-duplicates classes with same name by their file name when set "true", combines test results for those classes otherwise.' + required: false + large_files: + description: 'Support for large files is enabled when set to "true". Defaults to "false", unless ignore_runs is "true".' + required: false + ignore_runs: + description: 'Does not collect test run information from the test result files, which is useful for very large files. This disables any check run annotations.' + default: 'false' + required: false + check_run: + description: 'Set to "true", the results are published as a check run, but it may not be associated with the workflow that ran this action.' + default: 'true' + required: false + job_summary: + description: 'Set to "true", the results are published as part of the job summary page of the workflow run.' + default: 'true' + required: false + compare_to_earlier_commit: + description: 'Test results are compared to results of earlier commits to highlight changes: "false" - disable comparison, "true" - compare across commits.' + default: 'true' + required: false + pull_request_build: + description: 'As part of pull requests, GitHub builds a merge commit, which combines the commit and the target branch. If tests ran on the actual pushed commit, then set this to "commit". Defaults to "merge".' + default: 'merge' + required: false + event_file: + description: 'An alternative event file to use. Useful to replace a "workflow_run" event file with the actual source event file.' + required: false + event_name: + description: 'An alternative event name to use. Useful to replace a "workflow_run" event name with the actual source event name: github.event.workflow_run.event.' + required: false + test_changes_limit: + description: 'Limits the number of removed or skipped tests reported on pull request comments. This report can be disabled with a value of 0. The default is 10.' + required: false + check_run_annotations: + description: 'Adds additional information to the check run. This is a comma-separated list of any of the following values: "all tests" - list all found tests, "skipped tests" - list all skipped tests. Set to "none" to add no extra annotations at all.' + default: 'all tests, skipped tests' + required: false + check_run_annotations_branch: + description: 'Adds check run annotations only on given branches. Comma-separated list of branch names allowed, asterisk "*" matches all branches. Defaults to event.repository.default_branch or "main, master".' + required: false + seconds_between_github_reads: + description: 'Sets the number of seconds the action waits between concurrent read requests to the GitHub API. This throttles the API usage to avoid abuse rate limits: https://docs.github.com/en/rest/overview/resources-in-the-rest-api#abuse-rate-limits.' + default: '0.25' + required: false + seconds_between_github_writes: + description: 'Sets the number of seconds the action waits between concurrent write requests to the GitHub API. This throttles the API usage to avoid abuse rate limits: https://docs.github.com/en/rest/overview/resources-in-the-rest-api#abuse-rate-limits.' + default: '2.0' + required: false + secondary_rate_limit_wait_seconds: + description: 'Sets the number of seconds to wait before retrying secondary rate limit errors. If not set, the default defined in the PyGithub library is used (currently 60 seconds).' + required: false + json_file: + description: 'Results are written to this JSON file.' + required: false + json_thousands_separator: + description: 'Formatted numbers in JSON use this character to separate groups of thousands. Common values are "," or ".". Defaults to punctuation space (\u2008).' + default: ' ' + required: false + json_suite_details: + description: 'Write out all suite details to the JSON file. Setting this to "true" can greatly increase the size of the output. Defaults to "false".' + default: 'false' + required: false + json_test_case_results: + description: 'Write out all individual test case results to the JSON file. Setting this to "true" can greatly increase the size of the output. Defaults to "false".' + default: 'false' + required: false + search_pull_requests: + description: 'Prior to v2.6.0, the action used the "/search/issues" REST API to find pull requests related to a commit. If you need to restore that behaviour, set this to "true". Defaults to "false".' + default: 'false' + required: false + +outputs: + json: + description: "Test results as JSON" + value: ${{ steps.test-results.outputs.json }} + +runs: + using: 'composite' + steps: + - name: Check for Python3 + id: python + run: | + # Check for Python3 + echo '::group::Check for Python3' + + # we check version here just to execute `python3` with an argument + # on Windows, there is a `python3.exe` that is a proxy to trigger installation from app store + # command `which python3` finds that, but `python3 -V` does not return the version on stdout + if ! which python3 || [[ "$(python3 -V)" != *"python 3."* && "$(python3 -V)" != *"Python 3."* ]] + then + if ! which python || [[ "$(python -V)" != *"python 3."* && "$(python -V)" != *"Python 3."* ]] + then + echo "::error::No python3 interpreter found. Please setup python before running this action. You could use https://github.com/actions/setup-python." + exit 1 + fi + + PYTHON_BIN="$(python -c 'import sys; print(sys.executable)')" + else + PYTHON_BIN="$(python3 -c 'import sys; print(sys.executable)')" + fi + + echo "Python that creates venv: $PYTHON_BIN" + echo "PYTHON_BIN=$PYTHON_BIN" >> "$GITHUB_ENV" + echo "version=$("$PYTHON_BIN" -V)" >> "$GITHUB_OUTPUT" + + echo '::endgroup::' + shell: bash + + - name: Restore PIP packages cache + uses: actions/cache/restore@v4 + id: cache + continue-on-error: true + with: + path: '~\AppData\Local\pip\Cache' + key: enricomi-publish-action-${{ runner.os }}-${{ runner.arch }}-pip-${{ steps.python.outputs.version }}-70a313922fdbeb7398ec60313d908b11 + + - name: Create virtualenv + id: venv + continue-on-error: true + run: | + # Create virtualenv + echo '::group::Create virtualenv' + + echo "Python that creates venv: $PYTHON_BIN" + + echo "Creating virtual environment" + if ! "$PYTHON_BIN" -m virtualenv enricomi-publish-action-venv && ! "$PYTHON_BIN" -m venv enricomi-publish-action-venv + then + echo "Looks like there is neither virtualenv nor venv package installed" + if ! "$PYTHON_BIN" -m pip install --user virtualenv + then + echo "Installing virtualenv package with PIP options '--user' failed, now trying without" + if ! "$PYTHON_BIN" -m pip install virtualenv + then + echo "::error::Installing virtualenv package failed" + exit 1 + fi + fi + + if ! "$PYTHON_BIN" -m virtualenv enricomi-publish-action-venv + then + echo "::error::Cannot create venv after installing virtualenv package" + exit 1 + fi + fi + + echo "Finding Python interpreter in venv" + PYTHON_VENV="enricomi-publish-action-venv\\Scripts\\python" + PYTHON_VENV="$("$PYTHON_VENV" -c 'import sys; print(sys.executable)')" + echo "Python in venv: $PYTHON_VENV" + echo "PYTHON_VENV=$PYTHON_VENV" >> "$GITHUB_ENV" + + echo '::endgroup::' + shell: bash + + - name: Install Python dependencies + run: | + # Install Python dependencies + echo '::group::Install Python dependencies' + + # make sure wheel is installed, which improves installing our dependencies + "$PYTHON_VENV" -m pip install wheel + "$PYTHON_VENV" -m pip install -r "$GITHUB_ACTION_PATH/../../python/requirements.txt" + + echo '::endgroup::' + shell: bash + + - name: Publish Test Results + id: test-results + run: | + # Publish Test Results + echo '::group::Publish Test Results' + "$PYTHON_VENV" "$GITHUB_ACTION_PATH/../../python/publish_test_results.py" + echo '::endgroup::' + env: + GITHUB_TOKEN: ${{ inputs.github_token }} + GITHUB_TOKEN_ACTOR: ${{ inputs.github_token_actor }} + GITHUB_RETRIES: ${{ inputs.github_retries }} + COMMIT: ${{ inputs.commit }} + CHECK_NAME: ${{ inputs.check_name }} + COMMENT_TITLE: ${{ inputs.comment_title }} + COMMENT_MODE: ${{ inputs.comment_mode }} + FAIL_ON: ${{ inputs.fail_on }} + ACTION_FAIL: ${{ inputs.action_fail }} + ACTION_FAIL_ON_INCONCLUSIVE: ${{ inputs.action_fail_on_inconclusive }} + FILES: ${{ inputs.files }} + JUNIT_FILES: ${{ inputs.junit_files }} + NUNIT_FILES: ${{ inputs.nunit_files }} + XUNIT_FILES: ${{ inputs.xunit_files }} + TRX_FILES: ${{ inputs.trx_files }} + TIME_UNIT: ${{ inputs.time_unit }} + TEST_FILE_PREFIX: ${{ inputs.test_file_prefix }} + REPORT_INDIVIDUAL_RUNS: ${{ inputs.report_individual_runs }} + REPORT_SUITE_LOGS: ${{ inputs.report_suite_logs }} + DEDUPLICATE_CLASSES_BY_FILE_NAME: ${{ inputs.deduplicate_classes_by_file_name }} + LARGE_FILES: ${{ inputs.large_files }} + IGNORE_RUNS: ${{ inputs.ignore_runs }} + COMPARE_TO_EARLIER_COMMIT: ${{ inputs.compare_to_earlier_commit }} + PULL_REQUEST_BUILD: ${{ inputs.pull_request_build }} + EVENT_FILE: ${{ inputs.event_file }} + EVENT_NAME: ${{ inputs.event_name }} + TEST_CHANGES_LIMIT: ${{ inputs.test_changes_limit }} + CHECK_RUN_ANNOTATIONS: ${{ inputs.check_run_annotations }} + CHECK_RUN_ANNOTATIONS_BRANCH: ${{ inputs.check_run_annotations_branch }} + SECONDS_BETWEEN_GITHUB_READS: ${{ inputs.seconds_between_github_reads }} + SECONDS_BETWEEN_GITHUB_WRITES: ${{ inputs.seconds_between_github_writes }} + SECONDARY_RATE_LIMIT_WAIT_SECONDS: ${{ inputs.secondary_rate_limit_wait_seconds }} + JSON_FILE: ${{ inputs.json_file }} + JSON_THOUSANDS_SEPARATOR: ${{ inputs.json_thousands_separator }} + JSON_SUITE_DETAILS: ${{ inputs.json_suite_details }} + JSON_TEST_CASE_RESULTS: ${{ inputs.json_test_case_results }} + CHECK_RUN: ${{ inputs.check_run }} + JOB_SUMMARY: ${{ inputs.job_summary }} + SEARCH_PULL_REQUESTS: ${{ inputs.search_pull_requests }} + # not documented + ROOT_LOG_LEVEL: ${{ inputs.root_log_level }} + # not documented + LOG_LEVEL: ${{ inputs.log_level }} + shell: bash + + - name: Save PIP packages cache + uses: actions/cache/save@v4 + if: ( success() || failure() ) && ! steps.cache.outputs.cache-hit + continue-on-error: true + with: + path: '~\AppData\Local\pip\Cache' + key: ${{ steps.cache.outputs.cache-primary-key }} + +branding: + icon: 'check-circle' + color: 'green'