Github CI/CD stuff part 1
How to get automated tests running on github for a kotlin multiplatform project
I decided I should get this working and it would make a nice little blog I figured. If you want the recipe, just skip to the last section!
Story Mode:
So I asked my handy chatgpt to whip me up a github automation file and tell me where to put it. Apparently it goes in .github/workflows/anyoldname.yml
And it didn’t work, so I asked for a hello-world test and got:
name: Test GitHub Runners
on:
push:
branches:
- main # Set this to your default branch if it's not 'main' jobs:
jobs:
hello_world_job:
name: Hello World
runs-on: ubuntu-latest # Specifies the runner environment
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Run a one-line script
run: echo "Hello, GitHub Actions!"
as an aside, have you noticed that LLM AIs comment the strangest things? I really don’t need the line runs-on: ubuntu-latest
to be commented with # specifies the runner environment.
LLMs give us the best and worst of the internets programming all in one. I also don’t see much difference between github copilot, jetbrains ai assist, and chat gpt. I like AI assist the best for intellij just because of usability issues. Github Copilot is a close second, and ChatGPT you have to copy and past everything into it - pain.
Anyway, I make a couple of changes to this:
name: Test GitHub Runners
on: [push, pull_request]
jobs:
hello_world_job:
name: Hello World
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Run a one-line script
run: echo "Hello, GitHub Actions!"
I want to run on every push
, and on pull_requests
for now. Actually, pull requests probably want their own workflow because I’d like to do a test merge, and then run the tests. So lets just do push for now, but any branch.
excellent, it ran! but it is complaining about a couple of things I’m using, should be v4 instead of v2 apparently. Change that and repush: and no warnings. Good!
Next step, lets see if we can run the desktop tests. First we need to setup a jdk.
- name: Set up JDK
uses: actions/setup-java@v4
with:
distribution: 'adopt'
java-version: '21'
That worked, though again ai gave me the wrong versions of the actions. Next: build. Oddly, the ai despite looking at my files goes off on a tangent and tries to use maven to build instead of gradle. Easy enough to fix.
- name: Build Project
run: ./gradlew build
Ok, now we need to run some tests. I’m going to start with the desktopTests. They include the commonTests so it is a nice clean spot to run. How do you run them from the command line? I believe you can just run ./gradlew desktopTest - it compiles some stuff and runs some things. No output, but you can see that it makes some files in the build reports and test-results directories so lets go with that.
- name: Run Tests
run: ./gradlew desktopTest
It worked, and so now we need a report - because the success or failure of the tests seem to disappear in the aether - or maybe it would fail if the tests fail. Interesting idea, I should test that.
Anyway, lets add a test summary section.
- name: Create Test Summary
uses: test-summary/action@v2
with:
paths: '**/test-results/**/*.xml'
show: "pass, fail, skip
Not bad! Now for the really interesting question, can we get the android unit tests to run? On my system I can run ,/gradlew composeApp:testDebugUnitTest and it will run them. composeApp:androidUnitTest does not work, would have thought it would.
- name: Run Android Unit Tests
run: ./gradlew composeApp:testDebugUnitTest
yup! That worked too. Wait a minute:
The same number of tests. Did they not run? No, they ran. `test a kotlin assertion in android unit test: KotlinAndroidUnitTest`
is an android unit test, as are a couple of others. It seems that they are running when I am running the desktop test. Not sure why… Anyway, I will leave this in to be clear. The next question is can I run the androidInstrumentedTest
target. This is particularly interesting because you need to setup the android sdk and accept licenses and such on your personal computer to make this all work. However, there is an android emulator runner action in github. It has a lot of setup, but seems to have all the instructions and some examples. So I add:
- name: Enable KVM group perms
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
- name: Run Android Emulator Tests
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 28
script: ./gradlew connectedAndroidTest
but I still have 15 passed tests, no failed. Looking in the output for the task, I see:
[EmulatorConsole]: Failed to start Emulator console for 5554.
Turns out minimum api-level for this runner is 29- so I up that and I get:
/usr/bin/sh -c \sdkmanager --install 'system-images;android-30;default;x86' --channel=0 > /dev/null
Warning: Errors during XML parse:
Warning: Additionally, the fallback loader failed to parse the XML.
Warning: Failed to find package 'system-images;android-30;default;x86'
So the emulator is not starting. Looks like the default is for an x86
, so adding an arch: x86_64
to the command
- name: Run Android Emulator Tests
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 30
script: ./gradlew connectedAndroidTest
arch: x86_64
it runs, but still only 15 passed tests. Looks like we needed to specify the paths as well:
- name: Create Test Summary
uses: test-summary/action@v2
with:
paths: '**/*est-results/**/*.xml'
show: "pass, fail, skip"
note I took the lower-case “t” out of the path. That is because android instrumented tests end up in some directory called androidTest-results
so this path pattern meets both and gathers all the results up!
So in summary:
you need a github workflow file, it goes in .github/workflows/someName.yml
and here is a file that will run all the tests for android and desktop and common on any branch push or pull-request. I named it main.yml
:
name: Test GitHub Runners
on: push
jobs:
build-and-test:
name: Kotlin Multiplatform Project Build
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up JDK
uses: actions/setup-java@v4
with:
distribution: 'adopt'
java-version: '21'
- name: Build Project
run: ./gradlew build
- name: Run Desktop Tests
run: ./gradlew composeApp:desktopTest
- name: Run Android Unit Tests
run: ./gradlew composeApp:testDebugUnitTest
- name: Enable KVM group perms
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
- name: Run Android Emulator Tests
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 30
script: ./gradlew connectedAndroidTest
arch: x86_64
- name: Create Test Summary
uses: test-summary/action@v2
with:
paths: '**/*est-results/**/*.xml'
show: "pass, fail, skip"
There we go, runs everything. I think there are some caching things one can do to speed it up. I’ll maybe look at that later.
Note I’ve committed these changes to my github kotlin multiplatform testing project
I hope this helps someone out!