Skip to content

Commit 6ec3113

Browse files
committed
Split Android instrumentation testing pipeline
1 parent 6df5ea6 commit 6ec3113

File tree

5 files changed

+311
-1
lines changed

5 files changed

+311
-1
lines changed

.github/workflows/scripts-android.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,25 @@ jobs:
2323
run: ./scripts/build-android-port.sh -q -DskipTests
2424
- name: Build Hello Codename One Android app
2525
run: ./scripts/build-android-app.sh -q -DskipTests
26+
- name: Add Android instrumentation test
27+
run: ./scripts/add-android-instrumentation-test.sh
28+
- name: Run Android instrumentation tests on emulator
29+
run: ./scripts/run-android-instrumentation-tests.sh
30+
- name: Export Android emulator screenshot path
31+
if: always()
32+
run: |
33+
set -euo pipefail
34+
echo "ANDROID_SCREENSHOT=" >> "$GITHUB_ENV"
35+
if [ -f scripts/.android-build-info ]; then
36+
# shellcheck disable=SC1090
37+
source scripts/.android-build-info
38+
if [ -n "${SCREENSHOT_PATH:-}" ] && [ -f "$SCREENSHOT_PATH" ]; then
39+
echo "ANDROID_SCREENSHOT=$SCREENSHOT_PATH" >> "$GITHUB_ENV"
40+
fi
41+
fi
42+
- name: Upload Android emulator screenshot
43+
if: ${{ always() && env.ANDROID_SCREENSHOT != '' }}
44+
uses: actions/upload-artifact@v4
45+
with:
46+
name: android-emulator-screenshot
47+
path: ${{ env.ANDROID_SCREENSHOT }}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
#!/usr/bin/env bash
2+
# Inject an instrumentation test into the generated Codename One Android Gradle project.
3+
set -euo pipefail
4+
5+
log() { echo "[add-android-instrumentation-test] $1"; }
6+
7+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
8+
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
9+
cd "$REPO_ROOT"
10+
11+
TMPDIR="${TMPDIR:-/tmp}"
12+
TMPDIR="${TMPDIR%/}"
13+
14+
TOOLS_ENV_DIR="$TMPDIR/codenameone-tools/tools"
15+
ENV_FILE="$TOOLS_ENV_DIR/env.sh"
16+
if [ -f "$ENV_FILE" ]; then
17+
# shellcheck disable=SC1090
18+
source "$ENV_FILE"
19+
else
20+
log "Workspace tools not provisioned. Run scripts/setup-workspace.sh first." >&2
21+
exit 1
22+
fi
23+
24+
BUILD_INFO_FILE="$SCRIPT_DIR/.android-build-info"
25+
if [ ! -f "$BUILD_INFO_FILE" ]; then
26+
log "Android build metadata not found at $BUILD_INFO_FILE. Run scripts/build-android-app.sh first." >&2
27+
exit 1
28+
fi
29+
30+
# shellcheck disable=SC1090
31+
source "$BUILD_INFO_FILE"
32+
33+
required_vars=(GRADLE_PROJECT_DIR PACKAGE_NAME ARTIFACT_ID WORK_DIR)
34+
for var in "${required_vars[@]}"; do
35+
if [ -z "${!var:-}" ]; then
36+
log "Required build metadata '$var' is missing. Regenerate the Android project." >&2
37+
exit 1
38+
fi
39+
done
40+
41+
ANDROID_APP_MODULE_DIR="$GRADLE_PROJECT_DIR/app"
42+
if [ ! -d "$ANDROID_APP_MODULE_DIR" ]; then
43+
log "Android app module directory not found at $ANDROID_APP_MODULE_DIR" >&2
44+
exit 1
45+
fi
46+
47+
PACKAGE_PATH="${PACKAGE_NAME//.//}"
48+
ANDROID_TEST_DIR="$ANDROID_APP_MODULE_DIR/src/androidTest/java/$PACKAGE_PATH"
49+
mkdir -p "$ANDROID_TEST_DIR"
50+
51+
TEMPLATE_FILE="$SCRIPT_DIR/templates/AndroidPackageInstrumentationTest.java.tmpl"
52+
if [ ! -f "$TEMPLATE_FILE" ]; then
53+
log "Instrumentation test template not found at $TEMPLATE_FILE" >&2
54+
exit 1
55+
fi
56+
57+
TEST_FILE="$ANDROID_TEST_DIR/PackageNameInstrumentationTest.java"
58+
sed -e "s|@PACKAGE@|$PACKAGE_NAME|g" "$TEMPLATE_FILE" > "$TEST_FILE"
59+
log "Wrote instrumentation test to $TEST_FILE"
60+
61+
GRADLE_APP_BUILD_FILE="$ANDROID_APP_MODULE_DIR/build.gradle"
62+
if [ ! -f "$GRADLE_APP_BUILD_FILE" ]; then
63+
log "Gradle build file not found at $GRADLE_APP_BUILD_FILE" >&2
64+
exit 1
65+
fi
66+
67+
python3 - "$GRADLE_APP_BUILD_FILE" <<'PY'
68+
import re
69+
import sys
70+
from pathlib import Path
71+
72+
build_file = Path(sys.argv[1])
73+
contents = build_file.read_text()
74+
75+
missing_lines = []
76+
if "androidx.test.ext:junit" not in contents:
77+
missing_lines.append("androidTestImplementation 'androidx.test.ext:junit:1.1.5'")
78+
if "androidx.test.espresso:espresso-core" not in contents:
79+
missing_lines.append("androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'")
80+
81+
if missing_lines:
82+
match = re.search(r'dependencies\s*\{', contents)
83+
if not match:
84+
raise SystemExit("dependencies block not found in app build.gradle")
85+
insertion = ''.join(f"\n {line}" for line in missing_lines)
86+
idx = match.end()
87+
contents = contents[:idx] + insertion + contents[idx:]
88+
build_file.write_text(contents)
89+
PY
90+
log "Ensured Android test dependencies are declared in $GRADLE_APP_BUILD_FILE"
91+

scripts/build-android-app.sh

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -282,4 +282,16 @@ export JAVA_HOME="$ORIGINAL_JAVA_HOME"
282282

283283
APK_PATH=$(find "$GRADLE_PROJECT_DIR" -path "*/outputs/apk/debug/*.apk" | head -n 1 || true)
284284
[ -n "$APK_PATH" ] || { ba_log "Gradle build completed but no APK was found" >&2; exit 1; }
285-
ba_log "Successfully built Android APK at $APK_PATH"
285+
ba_log "Successfully built Android APK at $APK_PATH"
286+
287+
BUILD_INFO_FILE="$SCRIPT_DIR/.android-build-info"
288+
ba_log "Recording Android build metadata for downstream stages at $BUILD_INFO_FILE"
289+
{
290+
printf "APP_DIR=%q\n" "$APP_DIR"
291+
printf "GRADLE_PROJECT_DIR=%q\n" "$GRADLE_PROJECT_DIR"
292+
printf "PACKAGE_NAME=%q\n" "$PACKAGE_NAME"
293+
printf "ARTIFACT_ID=%q\n" "$ARTIFACT_ID"
294+
printf "WORK_DIR=%q\n" "$WORK_DIR"
295+
printf "APK_PATH=%q\n" "$APK_PATH"
296+
} > "$BUILD_INFO_FILE"
297+
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
#!/usr/bin/env bash
2+
# Boot an Android emulator, execute instrumentation tests, and capture a screenshot artifact.
3+
set -euo pipefail
4+
5+
log() { echo "[run-android-instrumentation-tests] $1"; }
6+
7+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
8+
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
9+
cd "$REPO_ROOT"
10+
11+
TMPDIR="${TMPDIR:-/tmp}"
12+
TMPDIR="${TMPDIR%/}"
13+
TOOLS_ENV_DIR="$TMPDIR/codenameone-tools/tools"
14+
ENV_FILE="$TOOLS_ENV_DIR/env.sh"
15+
if [ -f "$ENV_FILE" ]; then
16+
# shellcheck disable=SC1090
17+
source "$ENV_FILE"
18+
else
19+
log "Workspace tools not provisioned. Run scripts/setup-workspace.sh first." >&2
20+
exit 1
21+
fi
22+
23+
BUILD_INFO_FILE="$SCRIPT_DIR/.android-build-info"
24+
if [ ! -f "$BUILD_INFO_FILE" ]; then
25+
log "Android build metadata not found at $BUILD_INFO_FILE. Run scripts/build-android-app.sh first." >&2
26+
exit 1
27+
fi
28+
29+
# shellcheck disable=SC1090
30+
source "$BUILD_INFO_FILE"
31+
32+
required_vars=(APP_DIR GRADLE_PROJECT_DIR PACKAGE_NAME ARTIFACT_ID WORK_DIR APK_PATH)
33+
for var in "${required_vars[@]}"; do
34+
if [ -z "${!var:-}" ]; then
35+
log "Required build metadata '$var' is missing. Regenerate the Android project." >&2
36+
exit 1
37+
fi
38+
done
39+
40+
ANDROID_SDK_ROOT="${ANDROID_SDK_ROOT:-${ANDROID_HOME:-}}"
41+
if [ -z "$ANDROID_SDK_ROOT" ]; then
42+
if [ -d "/usr/local/lib/android/sdk" ]; then
43+
ANDROID_SDK_ROOT="/usr/local/lib/android/sdk"
44+
elif [ -d "$HOME/Android/Sdk" ]; then
45+
ANDROID_SDK_ROOT="$HOME/Android/Sdk"
46+
fi
47+
fi
48+
if [ -z "$ANDROID_SDK_ROOT" ] || [ ! -d "$ANDROID_SDK_ROOT" ]; then
49+
log "Android SDK not found. Set ANDROID_SDK_ROOT or ANDROID_HOME to a valid installation." >&2
50+
exit 1
51+
fi
52+
export ANDROID_SDK_ROOT ANDROID_HOME="$ANDROID_SDK_ROOT"
53+
54+
find_tool() {
55+
local binary="$1"
56+
shift
57+
for dir in "$@"; do
58+
if [ -x "$dir/$binary" ]; then
59+
printf '%s' "$dir/$binary"
60+
return 0
61+
fi
62+
done
63+
return 1
64+
}
65+
66+
SDKMANAGER=$(find_tool sdkmanager \
67+
"$ANDROID_SDK_ROOT/cmdline-tools/latest/bin" \
68+
"$ANDROID_SDK_ROOT/cmdline-tools/bin" \
69+
"$ANDROID_SDK_ROOT/tools/bin" || true)
70+
AVDMANAGER=$(find_tool avdmanager \
71+
"$ANDROID_SDK_ROOT/cmdline-tools/latest/bin" \
72+
"$ANDROID_SDK_ROOT/cmdline-tools/bin" \
73+
"$ANDROID_SDK_ROOT/tools/bin" || true)
74+
EMU_BIN="$ANDROID_SDK_ROOT/emulator/emulator"
75+
ADB_BIN="$ANDROID_SDK_ROOT/platform-tools/adb"
76+
77+
if [ -z "$SDKMANAGER" ] || [ -z "$AVDMANAGER" ] || [ ! -x "$EMU_BIN" ] || [ ! -x "$ADB_BIN" ]; then
78+
log "Required Android command-line tools were not found." >&2
79+
log "SDKMANAGER=$SDKMANAGER AVDMANAGER=$AVDMANAGER EMULATOR=$EMU_BIN ADB=$ADB_BIN" >&2
80+
exit 1
81+
fi
82+
83+
log "Accepting Android SDK licenses"
84+
yes | "$SDKMANAGER" --licenses >/dev/null 2>&1 || true
85+
86+
log "Installing Android 35 ARM system image"
87+
yes | "$SDKMANAGER" --install \
88+
"platform-tools" \
89+
"platforms;android-35" \
90+
"emulator" \
91+
"system-images;android-35;google_apis;arm64-v8a" >/dev/null
92+
93+
AVD_NAME="cn1-api35-arm"
94+
if ! "$AVDMANAGER" list avd | grep -q "Name: $AVD_NAME"; then
95+
log "Creating AVD $AVD_NAME"
96+
printf 'no\n' | "$AVDMANAGER" create avd -n "$AVD_NAME" -k "system-images;android-35;google_apis;arm64-v8a" -d pixel >/dev/null
97+
fi
98+
99+
log "Starting AVD $AVD_NAME in headless mode"
100+
"$EMU_BIN" -avd "$AVD_NAME" -no-boot-anim -no-audio -no-snapshot -no-window -gpu swiftshader_indirect -netfast >/tmp/$AVD_NAME.log 2>&1 &
101+
EMU_PID=$!
102+
cleanup() {
103+
log "Cleaning up emulator"
104+
"$ADB_BIN" emu kill >/dev/null 2>&1 || true
105+
kill "$EMU_PID" >/dev/null 2>&1 || true
106+
}
107+
trap cleanup EXIT
108+
109+
log "Waiting for emulator to boot"
110+
"$ADB_BIN" wait-for-device
111+
BOOT_COMPLETED="0"
112+
for _ in $(seq 1 120); do
113+
BOOT_COMPLETED=$("$ADB_BIN" shell getprop sys.boot_completed 2>/dev/null | tr -d '\r') || true
114+
if [ "$BOOT_COMPLETED" = "1" ]; then
115+
break
116+
fi
117+
sleep 5
118+
done
119+
120+
if [ "$BOOT_COMPLETED" != "1" ]; then
121+
log "Emulator did not boot in the allotted time" >&2
122+
exit 1
123+
fi
124+
125+
log "Executing connected Android tests"
126+
chmod +x "$GRADLE_PROJECT_DIR/gradlew"
127+
ORIGINAL_JAVA_HOME="$JAVA_HOME"
128+
export JAVA_HOME="$JAVA17_HOME"
129+
(
130+
cd "$GRADLE_PROJECT_DIR"
131+
./gradlew --no-daemon connectedDebugAndroidTest
132+
)
133+
export JAVA_HOME="$ORIGINAL_JAVA_HOME"
134+
135+
log "Launching application to capture screenshot"
136+
"$ADB_BIN" shell monkey -p "$PACKAGE_NAME" -c android.intent.category.LAUNCHER 1 >/dev/null 2>&1 || true
137+
sleep 10
138+
139+
ARTIFACTS_DIR="$WORK_DIR/artifacts"
140+
mkdir -p "$ARTIFACTS_DIR"
141+
SCREENSHOT_PATH="$ARTIFACTS_DIR/${ARTIFACT_ID}-emulator.png"
142+
log "Capturing screenshot at $SCREENSHOT_PATH"
143+
"$ADB_BIN" exec-out screencap -p > "$SCREENSHOT_PATH"
144+
log "Screenshot captured"
145+
146+
persist_build_info() {
147+
{
148+
printf "APP_DIR=%q\n" "$APP_DIR"
149+
printf "GRADLE_PROJECT_DIR=%q\n" "$GRADLE_PROJECT_DIR"
150+
printf "PACKAGE_NAME=%q\n" "$PACKAGE_NAME"
151+
printf "ARTIFACT_ID=%q\n" "$ARTIFACT_ID"
152+
printf "WORK_DIR=%q\n" "$WORK_DIR"
153+
printf "APK_PATH=%q\n" "$APK_PATH"
154+
printf "SCREENSHOT_PATH=%q\n" "$SCREENSHOT_PATH"
155+
} > "$BUILD_INFO_FILE"
156+
}
157+
158+
persist_build_info
159+
log "Updated build metadata with screenshot information"
160+
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package @PACKAGE@;
2+
3+
import static org.junit.Assert.assertEquals;
4+
5+
import android.content.Context;
6+
7+
import androidx.test.ext.junit.runners.AndroidJUnit4;
8+
import androidx.test.platform.app.InstrumentationRegistry;
9+
10+
import org.junit.Test;
11+
import org.junit.runner.RunWith;
12+
13+
/**
14+
* Basic instrumentation test that verifies the generated application's package name.
15+
*/
16+
@RunWith(AndroidJUnit4.class)
17+
public class PackageNameInstrumentationTest {
18+
19+
@Test
20+
public void appContextMatchesPackage() {
21+
Context targetContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
22+
assertEquals("@PACKAGE@", targetContext.getPackageName());
23+
}
24+
}
25+

0 commit comments

Comments
 (0)