Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Android Instrumentation test workflow #1

Merged
merged 27 commits into from
Jul 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
1ed3702
Add workflow from colorfilters
TWiStErRob Jul 10, 2023
2b9a04f
Add documentation
TWiStErRob Jul 10, 2023
66bd5b9
Add CI
TWiStErRob Jul 10, 2023
782f963
Add ability to customize names and better descriptions
TWiStErRob Jul 10, 2023
dbc14e5
Try type: choice
TWiStErRob Jul 10, 2023
ae52eb8
Restore type
TWiStErRob Jul 10, 2023
d2316a7
Add --no-install, don't be a smartypants, npx
TWiStErRob Jul 10, 2023
414028d
.
TWiStErRob Jul 10, 2023
750e9a9
Only run CI if yml files changed
TWiStErRob Jul 10, 2023
2577952
Remove filter
TWiStErRob Jul 10, 2023
3c81ae2
Generalize for Inventory
TWiStErRob Jul 10, 2023
748142e
Merge branch 'main' into instrumentation
TWiStErRob Jul 10, 2023
15d823b
Try constant
TWiStErRob Jul 10, 2023
ecc7ee8
Try again
TWiStErRob Jul 10, 2023
4a6958a
Fix syntax
TWiStErRob Jul 10, 2023
c83e21f
Remove code injection possibilities https://github.com/actions/github…
TWiStErRob Jul 10, 2023
0389468
Fix emoji name
TWiStErRob Jul 10, 2023
92d860d
Add example usage
TWiStErRob Jul 10, 2023
5216d54
Remove unused secrets
TWiStErRob Jul 10, 2023
52031a1
Bit more air in inputs
TWiStErRob Jul 10, 2023
2a42e3e
Bit more air in validate
TWiStErRob Jul 10, 2023
510017f
Fix JS
TWiStErRob Jul 10, 2023
86786d1
Mimic name as show in example in readme
TWiStErRob Jul 10, 2023
f1ebf6b
Add permission to post status
TWiStErRob Jul 11, 2023
d64bc7d
Review clean up
TWiStErRob Jul 11, 2023
ec77b44
Add a warning if the URL is missing, sometimes scans "just" fail.
TWiStErRob Jul 11, 2023
96f9609
Merge branch 'main' into instrumentation
TWiStErRob Jul 11, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,16 @@ jobs:
workflow-file:
- ci.yml # self-validation.
- validate.yml
- instrumentation.yml

steps:
- name: "Checkout ${{ github.ref }} branch in ${{ github.repository }} repository."
uses: actions/checkout@v3

- name: "Install action-validator"
- name: "Install action-validator."
working-directory: action-validator
run: npm install

- name: "Validate workflow file."
working-directory: action-validator
run: npx action-validator ${{ github.workspace }}/.github/workflows/${{ matrix.workflow-file }}
run: npx --no-install action-validator ${{ github.workspace }}/.github/workflows/${{ matrix.workflow-file }}
257 changes: 257 additions & 0 deletions .github/workflows/instrumentation.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,257 @@
name: "🧪 Instrumentation Test"

on:
workflow_call:
inputs:

script:
required: true
type: string
description: |
Lines of script to execute while the emulator is running.
Usually this is a `gradlew connectedCheck` command in some form.
Multiple lines are allowed, but due to reactivecircus/android-emulator-runner's behavior
each line will be executed separately, so no backslashes at end of lines.
Note: in yaml, you can still use > to write multiple lines that end up as a single-line string.

android-api:
required: true
type: number
description: |
Android SDK API level to run the tests on.
See https://apilevels.com/.
Potential values:
- 10
- 14
- ...
- 34 (2023)

android-image-type:
required: false
type: string
default: ${{ inputs.android-api >= 32 && 'google_apis' || 'default' }}
description: |
Android emulator image type to run the tests on.
Run `sdkmanager --list | grep "system-images;android-"` to see what's available.
Some types for certain API levels are not available: https://issuetracker.google.com/issues/267458959
Potential values:
- default
- google_apis
- google_apis_playstore
- google-tv
- android-desktop
- android-tv

android-image-arch:
required: false
type: string
default: ${{ inputs.android-api >= 21 && 'x86_64' || 'x86' }}
description: |
Android emulator image architecture.
Run `sdkmanager --list | grep "system-images;android-"` to see what's available.

Nowadays with AndroidX libraries having API 14 as minimum,
API 15 is the first possible emulator that works well on GitHub Actions' architectures:
> system-images;android-10;default;armeabi-v7a
> system-images;android-10;default;x86
> system-images;android-14;default;armeabi-v7a
> system-images;android-15;default;armeabi-v7a
> system-images;android-15;default;x86 <-- this one!

Potential values:
- armeabi-v7a
- arm64-v8a
- x86
- x86_64

timeout-minutes:
required: false
type: number
default: 30
description: |
Number of minutes to wait for the `inputs.script` to finish running on the emulator.
Might be slightly less because installing, configuring and starting up the emulator might take minutes.

java-version:
required: false
type: number
default: 17
description: |
See actions/setup-java's java-version. Will use zulu distribution.

junit5:
required: false
type: boolean
default: false
description: |
Whether the instrumentation is running JUnit 5 or not.
Android-JUnit 5 uses Java 8, so it's only possible to use from API 26 onwards.
This makes no behavioral change yet to any of the steps (yet).

name-artifact-fine-grained:
required: false
type: string
default: 'Instrumentation Test Results {0}'
description: |
Format string for the name of the artifact to be uploaded to GitHub.
- Use {0} to represent `android-api`.
- Use {1} to represent `android-image-type`.
- Use {2} to represent `android-image-arch`.

name-artifact-merged:
required: false
type: string
default: 'Instrumentation Merged Results {0}'
description: |
Format string for the name of the artifact to be uploaded to GitHub.
- Use {0} to represent `android-api`.
- Use {1} to represent `android-image-type`.
- Use {2} to represent `android-image-arch`.

name-check-results:
required: false
type: string
default: '🔔 Test: Instrumentation Results {0}'
description: |
Format string for the name of the check suite created in the workflow run.
- Use {0} to represent `android-api`.
- Use {1} to represent `android-image-type`.
- Use {2} to represent `android-image-arch`.

name-check-gradle:
required: false
type: string
default: 'CI / Instrumentation Tests on / {0} / API {0} / Gradle'
description: |
Format string for the name of the check suite created in the workflow run.
- Use {0} to represent `android-api`.
- Use {1} to represent `android-image-type`.
- Use {2} to represent `android-image-arch`.


jobs:

android:
name: "API ${{ inputs.android-api }}"
runs-on: macos-latest
timeout-minutes: ${{ inputs.timeout-minutes }}

permissions:
# actions/checkout
contents: read
# EnricoMi/publish-unit-test-result-action -> https://github.com/EnricoMi/publish-unit-test-result-action#permissions
checks: write
# actions/github-script calling github.createCommitStatus()
statuses: write

steps:

- name: "Set up Java ${{ inputs.java-version }}."
uses: actions/setup-java@v3
with:
java-version: ${{ inputs.java-version }}
distribution: zulu

- name: "Checkout ${{ github.ref }} branch in ${{ github.repository }} repository."
uses: actions/checkout@v3
with:
submodules: recursive

- name: "Run Instrumentation Tests on emulator."
id: gradle
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: ${{ inputs.android-api }}
target: ${{ inputs.android-image-type }}
arch: ${{ inputs.android-image-arch }}
profile: pixel
# Ensure that /storage/sdcard exists, without this the stock DocumentsUI will be non-functional.
sdcard-path-or-size: 100M
script: |
adb devices -l
${{ inputs.script }}

- name: "Publish 'Gradle' result and Build Scan URL."
if: (success() || failure()) && steps.gradle != null && steps.gradle.outputs.result-success != null
uses: actions/github-script@v6
env:
PARAM_BUILD_SCAN_URL: ${{ steps.gradle.outputs.build-scan-url }}
PARAM_GRADLE_RESULT_SUCCESS: ${{ steps.gradle.outputs.result-success }}
PARAM_GRADLE_RESULT_TEXT: ${{ steps.gradle.outputs.result-text }}
PARAM_CHECK_NAME_GRADLE: ${{ format(inputs.name-check-gradle, inputs.android-api, inputs.android-image-type, inputs.android-image-arch) }}
with:
debug: ${{ secrets.ACTIONS_STEP_DEBUG || false }}
script: |
const buildScanUrl = process.env.PARAM_BUILD_SCAN_URL || undefined; // Empty string is falsy.
const success = process.env.PARAM_GRADLE_RESULT_SUCCESS === "true";
const description = process.env.PARAM_GRADLE_RESULT_TEXT;
const statusName = process.env.PARAM_CHECK_NAME_GRADLE;
if (!buildScanUrl) {
// > Publishing build scan...
// > Publishing build scan failed due to network error 'java.net.SocketException: Unexpected end of file from server' (2 retries remaining)...
// > Publishing build scan failed due to network error 'java.net.SocketException: Unexpected end of file from server' (1 retry remaining)...
// >
// > A network error occurred.
// >
// > If you require assistance with this problem, please report it via https://gradle.com/help/plugin and include the following information via copy/paste.
// >
// > ----------
// > Gradle version: 8.2.1
// > Plugin version: 3.13.4
// > Request URL: https://status.gradle.com
// > Request ID: 1eef0fbd-9780-4d90-8705-d1582251572f
// > Exception: java.net.SocketException: Unexpected end of file from server
// > ----------
core.warning(`No build scan URL found, ${statusName} will have no link.`);
}
await github.rest.repos.createCommitStatus({
owner: context.repo.owner,
repo: context.repo.repo,
sha: context.sha,
state: success ? "success" : "failure",
// Remove Emojis from status name, as they are not supported by GitHub HERE.
// > HttpError: Validation Failed: {"resource":"Status","code":"custom",
// > "field":"context","message":"context doesn't accept 4-byte Unicode"}
// Ensure that no "space space" is left behind.
context: statusName.replace(/ \p{Emoji_Presentation}+ /gu, ' '),
// Truncate to be sure it fits:
// > HttpError: Validation Failed: {"resource":"Status","code":"custom",
// > "field":"description","message":"description is too long (maximum is 140 characters)"}
// Max length is 140, give some space for potential Unicode.
description: description.length > 130
? `${description.substring(0, 130)}…`
: description,
target_url: buildScanUrl,
});

- name: "Upload '${{ format(inputs.name-artifact-fine-grained, inputs.android-api, inputs.android-image-type, inputs.android-image-arch) }}' artifact."
if: success() || failure()
uses: actions/upload-artifact@v3
with:
name: '${{ format(inputs.name-artifact-fine-grained, inputs.android-api, inputs.android-image-type, inputs.android-image-arch) }}'
# Exclude names with colon: https://issuetracker.google.com/issues/223643506
path: |
${{ github.workspace }}/**/build/reports/androidTests/connected/
${{ github.workspace }}/**/build/outputs/androidTest-results/connected/
${{ github.workspace }}/**/build/outputs/connected_android_test_additional_output/
!**/*:*

- name: "Upload '${{ format(inputs.name-artifact-merged, inputs.android-api, inputs.android-image-type, inputs.android-image-arch) }}' artifact."
if: success() || failure()
uses: actions/upload-artifact@v3
with:
name: '${{ format(inputs.name-artifact-merged, inputs.android-api, inputs.android-image-type, inputs.android-image-arch) }}'
# Exclude names with colon: https://issuetracker.google.com/issues/223643506
path: |
${{ github.workspace }}/build/androidTest-results/
!**/*:*

- name: "Publish '${{ format(inputs.name-check-results, inputs.android-api, inputs.android-image-type, inputs.android-image-arch) }}' check suite."
if: success() || failure()
uses: EnricoMi/publish-unit-test-result-action/composite@v2
with:
check_name: '${{ format(inputs.name-check-results, inputs.android-api, inputs.android-image-type, inputs.android-image-arch) }}'
comment_mode: off
report_individual_runs: true
test_changes_limit: 0
junit_files: ${{ github.workspace }}/**/build/outputs/androidTest-results/connected/TEST-*.xml
4 changes: 4 additions & 0 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,18 @@ on:
workflow_call

jobs:

gradle-wrapper:
name: "Gradle Wrapper"
runs-on: ubuntu-latest
timeout-minutes: 2

permissions:
# actions/checkout
contents: read

steps:

- name: "Checkout ${{ github.ref }} branch in ${{ github.repository }} repository."
uses: actions/checkout@v3
with:
Expand Down
47 changes: 47 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,52 @@ Reusable GitHub Actions workflows for my repositories.
## [validate.yml](.github/workflows/validate.yml)
Gradle wrapper validation.

```yaml
jobs:
validate:
name: "🦺 Validation"
uses: TWiStErRob/github-workflows/.github/workflows/validate.yml@v1
```

## [instrumentation.yml](.github/workflows/instrumentation.yml)
Run Android instrumentation tests on an emulator and upload artifacts.

```yaml
jobs:
instrumentation:
name: "🧪 Instrumentation Tests on" # / API ${{ matrix.api }} will be appended by used workflow.
needs: validate
uses: ./.github/workflows/instrumentation.yml
```

```yaml
name: "🧪 Instrumentation Test Matrix"

on:
workflow_call

jobs:
instrumentation:
name: "${{ matrix.api }}"

uses: TWiStErRob/github-workflows/.github/workflows/instrumentation.yml@v1
with:
android-api: ${{ matrix.api }}
script: >
./gradlew
--no-daemon
--continue
--stacktrace
:app:connectedCheck

strategy:
fail-fast: false
matrix:
# The API level, see https://apilevels.com/.
api:
- 29
- 33
```

## Related docs
* https://docs.github.com/en/actions/using-workflows/reusing-workflows