Skip to content

Commit cda8e16

Browse files
committed
Switched to androidx testing
1 parent 4151c2a commit cda8e16

File tree

2 files changed

+254
-86
lines changed

2 files changed

+254
-86
lines changed

scripts/build-android-app.sh

Lines changed: 142 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,7 @@ xmlstarlet sel -N "$NS" -t -c "/mvn:project/mvn:build/mvn:plugins" -n "$ROOT_POM
201201
[ -f "$APP_DIR/build.sh" ] && chmod +x "$APP_DIR/build.sh"
202202

203203
SETTINGS_FILE="$APP_DIR/common/codenameone_settings.properties"
204+
echo "codename1.arg.android.useAndroidX=true" >> "$SETTINGS_FILE"
204205
[ -f "$SETTINGS_FILE" ] || { ba_log "codenameone_settings.properties not found at $SETTINGS_FILE" >&2; exit 1; }
205206

206207
# --- Read settings ---
@@ -266,112 +267,168 @@ if [ -z "$GRADLE_PROJECT_DIR" ]; then
266267
fi
267268

268269
ba_log "Configuring instrumentation test sources in $GRADLE_PROJECT_DIR"
270+
271+
# Ensure AndroidX flags in gradle.properties
272+
# --- BEGIN: robust Gradle patch for AndroidX tests ---
273+
GRADLE_PROPS="$GRADLE_PROJECT_DIR/gradle.properties"
274+
grep -q '^android.useAndroidX=' "$GRADLE_PROPS" 2>/dev/null || echo 'android.useAndroidX=true' >> "$GRADLE_PROPS"
275+
grep -q '^android.enableJetifier=' "$GRADLE_PROPS" 2>/dev/null || echo 'android.enableJetifier=true' >> "$GRADLE_PROPS"
276+
269277
APP_BUILD_GRADLE="$GRADLE_PROJECT_DIR/app/build.gradle"
270-
if [ -f "$APP_BUILD_GRADLE" ]; then
271-
python3 - "$APP_BUILD_GRADLE" <<'PYTHON'
272-
import pathlib
273-
import re
274-
import sys
275-
276-
path = pathlib.Path(sys.argv[1])
277-
text = path.read_text()
278-
modified = False
279-
280-
if "android.test.InstrumentationTestRunner" not in text:
281-
def add_runner(match):
282-
prefix = match.group(0)
283-
return prefix + "\n testInstrumentationRunner \"android.test.InstrumentationTestRunner\""
284-
285-
new_text, count = re.subn(r"(defaultConfig\s*\{)", add_runner, text, count=1, flags=re.MULTILINE)
286-
if count:
287-
text = new_text
288-
modified = True
289-
else:
290-
raise SystemExit("defaultConfig block not found while adding instrumentation runner")
278+
ROOT_BUILD_GRADLE="$GRADLE_PROJECT_DIR/build.gradle"
291279

292-
libraries = [
293-
"useLibrary 'android.test.base'",
294-
"useLibrary 'android.test.mock'",
295-
"useLibrary 'android.test.runner'",
296-
]
280+
# Ensure repos in both root and app
281+
for F in "$ROOT_BUILD_GRADLE" "$APP_BUILD_GRADLE"; do
282+
if [ -f "$F" ]; then
283+
if ! grep -qE '^\s*repositories\s*{' "$F"; then
284+
cat >> "$F" <<'EOS'
297285
298-
missing_libraries = [lib for lib in libraries if lib not in text]
299-
if missing_libraries:
300-
match = re.search(r"^(\s*android\s*\{)", text, flags=re.MULTILINE)
301-
if not match:
302-
raise SystemExit("android block not found while adding instrumentation libraries")
303-
line = match.group(1)
304-
indent = re.match(r"^(\s*)", line).group(1)
305-
insertion = "".join(f"\n{indent} {lib}" for lib in missing_libraries)
306-
text = text[: match.end()] + insertion + text[match.end():]
307-
modified = True
308-
309-
if modified:
310-
if not text.endswith("\n"):
311-
text += "\n"
312-
path.write_text(text)
313-
PYTHON
314-
ba_log "Ensured instrumentation runner and libraries are declared"
315-
else
316-
ba_log "Warning: Gradle build file not found at $APP_BUILD_GRADLE; skipping instrumentation dependency configuration" >&2
317-
fi
286+
repositories {
287+
google()
288+
mavenCentral()
289+
}
290+
EOS
291+
else
292+
grep -q 'google()' "$F" || sed -E -i '0,/repositories[[:space:]]*\{/s//repositories {\n google()\n mavenCentral()/' "$F"
293+
grep -q 'mavenCentral()' "$F" || sed -E -i '0,/repositories[[:space:]]*\{/s//repositories {\n google()\n mavenCentral()/' "$F"
294+
fi
295+
fi
296+
done
297+
298+
# Edit app/build.gradle
299+
python3 - "$APP_BUILD_GRADLE" <<'PY'
300+
import sys, re, pathlib
301+
p = pathlib.Path(sys.argv[1]); txt = p.read_text(); orig = txt; changed = False
302+
303+
def strip_block(name, s):
304+
return re.sub(rf'(?ms)^\s*{name}\s*\{{.*?\}}\s*', '', s)
305+
306+
module_view = strip_block('buildscript', strip_block('pluginManagement', txt))
307+
308+
# 1) android { compileSdkVersion/targetSdkVersion }
309+
def ensure_sdk(body):
310+
# If android { ... } exists, update/insert inside defaultConfig and the android block
311+
if re.search(r'(?m)^\s*android\s*\{', body):
312+
# compileSdkVersion
313+
if re.search(r'(?m)^\s*compileSdkVersion\s+\d+', body) is None:
314+
body = re.sub(r'(?m)(^\s*android\s*\{)', r'\1\n compileSdkVersion 33', body, count=1)
315+
else:
316+
body = re.sub(r'(?m)^\s*compileSdkVersion\s+\d+', ' compileSdkVersion 33', body)
317+
# targetSdkVersion
318+
if re.search(r'(?ms)^\s*defaultConfig\s*\{.*?^\s*\}', body):
319+
dc = re.search(r'(?ms)^\s*defaultConfig\s*\{.*?^\s*\}', body)
320+
block = dc.group(0)
321+
if re.search(r'(?m)^\s*targetSdkVersion\s+\d+', block):
322+
block2 = re.sub(r'(?m)^\s*targetSdkVersion\s+\d+', ' targetSdkVersion 33', block)
323+
else:
324+
block2 = re.sub(r'(\{\s*)', r'\1\n targetSdkVersion 33', block, count=1)
325+
body = body[:dc.start()] + block2 + body[dc.end():]
326+
else:
327+
body = re.sub(r'(?m)(^\s*android\s*\{)', r'\1\n defaultConfig {\n targetSdkVersion 33\n }', body, count=1)
328+
else:
329+
# No android block at all: add minimal
330+
body += '\n\nandroid {\n compileSdkVersion 33\n defaultConfig { targetSdkVersion 33 }\n}\n'
331+
return body
332+
333+
txt2 = ensure_sdk(txt)
334+
if txt2 != txt: txt = txt2; module_view = strip_block('buildscript', strip_block('pluginManagement', txt)); changed = True
335+
336+
# 2) testInstrumentationRunner -> AndroidX
337+
if "androidx.test.runner.AndroidJUnitRunner" not in module_view:
338+
t2, n = re.subn(r'(?m)^\s*testInstrumentationRunner\s*".*?"\s*$', ' testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"', txt)
339+
if n == 0:
340+
t2, n = re.subn(r'(?m)(^\s*defaultConfig\s*\{)', r'\1\n testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"', txt, count=1)
341+
if n == 0:
342+
t2, n = re.subn(r'(?ms)(^\s*android\s*\{)', r'\1\n defaultConfig {\n testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"\n }', txt, count=1)
343+
if n: txt = t2; module_view = strip_block('buildscript', strip_block('pluginManagement', txt)); changed = True
344+
345+
# 3) remove legacy useLibrary lines
346+
t2, n = re.subn(r'(?m)^\s*useLibrary\s+\'android\.test\.(base|mock|runner)\'\s*$', '', txt)
347+
if n: txt = t2; module_view = strip_block('buildscript', strip_block('pluginManagement', txt)); changed = True
348+
349+
# 4) deps: choose androidTestImplementation vs androidTestCompile
350+
uses_modern = re.search(r'(?m)^\s*(implementation|api|testImplementation|androidTestImplementation)\b', module_view) is not None
351+
conf = "androidTestImplementation" if uses_modern else "androidTestCompile"
352+
need = [
353+
("androidx.test.ext:junit:1.1.5", conf), # AndroidJUnit4
354+
("androidx.test:runner:1.5.2", conf),
355+
("androidx.test:core:1.5.0", conf),
356+
("androidx.test.services:storage:1.4.2", conf),
357+
]
358+
to_add = [(c, k) for (c, k) in need if c not in module_view]
359+
360+
if to_add:
361+
block = "\n\ndependencies {\n" + "".join([f" {k} \"{c}\"\n" for c, k in to_add]) + "}\n"
362+
txt = txt.rstrip() + block
363+
changed = True
364+
365+
if changed and txt != orig:
366+
if not txt.endswith("\n"): txt += "\n"
367+
p.write_text(txt)
368+
print(f"Patched app/build.gradle (SDK=33; deps via {conf})")
369+
else:
370+
print("No changes needed in app/build.gradle")
371+
PY
372+
# --- END: robust Gradle patch ---
373+
374+
echo "----- app/build.gradle tail -----"
375+
tail -n 80 "$APP_BUILD_GRADLE" | sed 's/^/| /'
376+
echo "---------------------------------"
318377

319378
TEST_SRC_DIR="$GRADLE_PROJECT_DIR/app/src/androidTest/java/${PACKAGE_PATH}"
320379
mkdir -p "$TEST_SRC_DIR"
321380
TEST_CLASS="$TEST_SRC_DIR/HelloCodenameOneInstrumentedTest.java"
322-
cat >"$TEST_CLASS" <<EOF
323-
package $PACKAGE_NAME;
324-
325-
import android.content.Context;
326-
import android.test.InstrumentationTestCase;
381+
cat >"$TEST_CLASS" <<'EOF'
382+
package @PACKAGE@;
327383
328384
import android.app.Instrumentation;
329-
import android.os.ParcelFileDescriptor;
385+
import android.graphics.Bitmap;
386+
import android.os.Build;
330387
import android.util.Base64;
331388
332-
import java.io.FileInputStream;
333-
import java.io.IOException;
334-
import java.io.InputStream;
389+
import androidx.test.core.app.ApplicationProvider;
390+
import androidx.test.ext.junit.runners.AndroidJUnit4;
391+
import androidx.test.platform.app.InstrumentationRegistry;
335392
336-
public class HelloCodenameOneInstrumentedTest extends InstrumentationTestCase {
393+
import org.junit.Test;
394+
import org.junit.runner.RunWith;
337395
338-
public void testUseAppContext() {
339-
Context appContext = getInstrumentation().getTargetContext();
340-
assertEquals("$PACKAGE_NAME", appContext.getPackageName());
341-
}
396+
import java.io.ByteArrayOutputStream;
397+
398+
import static org.junit.Assert.assertEquals;
399+
400+
@RunWith(AndroidJUnit4.class)
401+
public class HelloCodenameOneInstrumentedTest {
342402
343-
private static final int CHUNK = 200_000;
403+
@Test
404+
public void testUseAppContext_andEmitScreenshot() throws Exception {
405+
String expectedPkg = "@PACKAGE@";
406+
String actualPkg = ApplicationProvider.getApplicationContext().getPackageName();
407+
assertEquals(expectedPkg, actualPkg);
344408
345-
public static void printPngToStdout(Instrumentation inst) {
346-
try {
347-
ParcelFileDescriptor pfd = inst.getUiAutomation().executeShellCommand("screencap -p");
348-
byte[] png;
349-
try (InputStream in = new FileInputStream(pfd.getFileDescriptor())) {
350-
png = readAll(in);
409+
// Take a screenshot using UiAutomation (API 29+)
410+
Instrumentation inst = InstrumentationRegistry.getInstrumentation();
411+
Bitmap bmp = null;
412+
if (Build.VERSION.SDK_INT >= 29) {
413+
bmp = inst.getUiAutomation().takeScreenshot();
351414
}
352-
String b64 = Base64.encodeToString(png, Base64.NO_WRAP);
353-
System.out.println("<<CN1_SCREENSHOT_BEGIN>>");
354-
for (int i = 0; i < b64.length(); i += CHUNK) {
355-
int end = Math.min(i + CHUNK, b64.length());
356-
System.out.println(b64.substring(i, end));
415+
if (bmp == null) {
416+
System.out.println("<<CN1_SCREENSHOT_BEGIN>><<NULL>><<CN1_SCREENSHOT_END>>");
417+
return;
357418
}
358-
System.out.println("<<CN1_SCREENSHOT_END>>");
359-
System.out.flush();
360-
} catch (IOException err) {
361-
err.printStackTrace();
362-
throw new RuntimeException(err);
363-
}
364-
}
365419
366-
private static byte[] readAll(InputStream in) throws IOException {
367-
byte[] buf = new byte[64 * 1024];
368-
int n;
369-
java.io.ByteArrayOutputStream out = new java.io.ByteArrayOutputStream();
370-
while ((n = in.read(buf)) != -1) out.write(buf, 0, n);
371-
return out.toByteArray();
420+
// Encode to PNG & Base64 (no wrap)
421+
ByteArrayOutputStream png = new ByteArrayOutputStream(1024 * 1024);
422+
bmp.compress(Bitmap.CompressFormat.PNG, 100, png);
423+
String b64 = Base64.encodeToString(png.toByteArray(), Base64.NO_WRAP);
424+
425+
// Emit to STDOUT so Gradle’s connected test XML/log captures it
426+
System.out.println("<<CN1_SCREENSHOT_BEGIN>>" + b64 + "<<CN1_SCREENSHOT_END>>");
427+
System.out.flush();
372428
}
373429
}
374430
EOF
431+
sed -i "s|@PACKAGE@|$PACKAGE_NAME|g" "$TEST_CLASS"
375432
ba_log "Created instrumentation test at $TEST_CLASS"
376433

377434
DEFAULT_ANDROID_TEST="$GRADLE_PROJECT_DIR/app/src/androidTest/java/com/example/myapplication2/ExampleInstrumentedTest.java"

scripts/run-android-instrumentation-tests.sh

Lines changed: 112 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,92 @@ RESULTS_ROOT="$GRADLE_PROJECT_DIR/app/build/outputs/androidTest-results/connecte
129129
ARTIFACTS_DIR="${ARTIFACTS_DIR:-${GITHUB_WORKSPACE:-$REPO_ROOT}/artifacts}"
130130
mkdir -p "$ARTIFACTS_DIR"
131131

132+
SCREENSHOT_OUT="$ARTIFACTS_DIR/emulator-screenshot.png"
133+
134+
HOST_OUTPUT_ROOT="$GRADLE_PROJECT_DIR/app/build/outputs"
135+
FOUND="$(find "$HOST_OUTPUT_ROOT" -type f -name 'emulator-screenshot.png' -print -quit 2>/dev/null || true)"
136+
137+
# Resolve applicationId from (in order):
138+
# - connected test logcat filename under outputs
139+
# - Gradle :app:properties
140+
# - AndroidManifest.xml
141+
# - adb package list (heuristic)
142+
resolve_app_id() {
143+
local results_root="$GRADLE_PROJECT_DIR/app/build/outputs/androidTest-results/connected"
144+
145+
# A) From logcat filename: logcat-<package>.<Class>-<method>.txt
146+
local logcat_file rest pkg
147+
logcat_file="$(find "$results_root" -type f -name 'logcat-*.txt' -print -quit 2>/dev/null || true)"
148+
if [ -n "$logcat_file" ]; then
149+
rest="$(basename "$logcat_file")" # logcat-com.pkg.Class-method.txt
150+
rest="${rest#logcat-}" # com.pkg.Class-method.txt
151+
rest="${rest%%-*}" # com.pkg.Class
152+
pkg="${rest%.*}" # com.pkg
153+
if [[ "$pkg" == *.* ]]; then
154+
printf '%s\n' "$pkg"
155+
return 0
156+
fi
157+
fi
158+
159+
# B) Gradle :app:properties (quiet) — run with Java 17 like the build
160+
(
161+
cd "$GRADLE_PROJECT_DIR"
162+
local save="${JAVA_HOME:-}"
163+
export JAVA_HOME="$JAVA17_HOME"
164+
./gradlew -q :app:properties 2>/dev/null | awk -F= '/^applicationId=/{print $2;found=1} END{exit found?0:1}'
165+
export JAVA_HOME="$save"
166+
) && return 0
167+
168+
# C) AndroidManifest.xml
169+
if [ -f "$GRADLE_PROJECT_DIR/app/src/main/AndroidManifest.xml" ]; then
170+
pkg="$(sed -n 's/.*package="\([^"]*\)".*/\1/p' "$GRADLE_PROJECT_DIR/app/src/main/AndroidManifest.xml" | head -n1)"
171+
if [ -n "$pkg" ]; then
172+
printf '%s\n' "$pkg"
173+
return 0
174+
fi
175+
fi
176+
177+
# D) Heuristic from device
178+
adb_target shell cmd package list packages 2>/dev/null | tr -d '\r' \
179+
| awk -F: '/^package:/{print $2}' \
180+
| awk '/^com\.codename1(\.|$)|^com\.codenameone(\.|$)|codename1/ {print; exit}'
181+
}
182+
183+
184+
# Prefer file copied by the AndroidX test runner (TestStorage)
185+
if [ -n "$FOUND" ] && [ -s "$FOUND" ]; then
186+
cp -f "$FOUND" "$SCREENSHOT_OUT"
187+
ra_log "Collected screenshot from TestStorage: $FOUND"
188+
else
189+
# 2) Pull exact device path from logcat marker if present
190+
LOGCAT_FILE="$(find "$GRADLE_PROJECT_DIR/app/build/outputs/androidTest-results/connected" -type f -name 'logcat-*.txt' -print -quit 2>/dev/null || true)"
191+
if [ -n "$LOGCAT_FILE" ]; then
192+
REMOTE_FROM_LOG="$(sed -n 's/^.*WROTE_TO_EXTERNAL_FILES:\(\/sdcard\/[^[:space:]]*\).*$/\1/p; s/^.*WROTE_TO_TEST_STORAGE:\(\/sdcard\/[^[:space:]]*\.png\).*$/\1/p' "$LOGCAT_FILE" | tail -n1)"
193+
if [ -n "$REMOTE_FROM_LOG" ] && [[ "$REMOTE_FROM_LOG" == /sdcard/* ]]; then
194+
ra_log "Path found in logcat: $REMOTE_FROM_LOG"
195+
adb_target pull "$REMOTE_FROM_LOG" "$SCREENSHOT_OUT" >/dev/null 2>&1 || true
196+
fi
197+
fi
198+
199+
# 3) Fallback: constructed external-files path using resolved applicationId
200+
if [ ! -s "$SCREENSHOT_OUT" ]; then
201+
PKG="$(resolve_app_id || true)"
202+
if [ -n "$PKG" ]; then
203+
ra_log "applicationId resolved to: $PKG"
204+
REMOTE="/sdcard/Android/data/$PKG/files/emulator-screenshot.png"
205+
ra_log "Trying fallback pull from device: $REMOTE"
206+
for i in 1 2 3; do
207+
if adb_target shell ls -l "$REMOTE" >/dev/null 2>&1; then
208+
adb_target pull "$REMOTE" "$SCREENSHOT_OUT" >/dev/null 2>&1 && break
209+
fi
210+
sleep 2
211+
done
212+
else
213+
ra_log "Unable to determine applicationId"
214+
fi
215+
fi
216+
fi
217+
132218
# Debug: show what exists
133219
ra_log "Listing connected test outputs under: $RESULTS_ROOT"
134220
find "$RESULTS_ROOT" -maxdepth 4 -printf '%y %p\n' 2>/dev/null | sed 's/^/[run-android-instrumentation-tests] /' || true
@@ -171,13 +257,38 @@ for xml in "${CANDIDATES[@]}"; do
171257
done
172258

173259
if [ "$EXTRACTED" -ne 1 ]; then
174-
ra_log "No markers found in XML. Dumping any marker snippets for debugging:"
260+
# First try XML debug dump (optional)
175261
for xml in "${CANDIDATES[@]}"; do
176262
ra_log "---- ${xml} (BEGIN..END) ----"
177263
sed -n '/<<CN1_SCREENSHOT_BEGIN>>/,/<<CN1_SCREENSHOT_END>>/p' "$xml" || true
178264
done
265+
266+
# Then try the connected-test logcat file
267+
LOGCAT_FILE="$(find "$RESULTS_ROOT" -type f -name 'logcat-*.txt' -print -quit 2>/dev/null || true)"
268+
if [ -n "$LOGCAT_FILE" ]; then
269+
ra_log "Scanning logcat for screenshot markers: $LOGCAT_FILE"
270+
awk '
271+
/<<CN1_SCREENSHOT_BEGIN>>/ {on=1; sub(/^.*<<CN1_SCREENSHOT_BEGIN>>/,""); printf "%s", $0; next}
272+
/<<CN1_SCREENSHOT_END>>/ {sub(/<<CN1_SCREENSHOT_END>>.*$/,""); print; on=0; next}
273+
on { printf "%s", $0 }
274+
' "$LOGCAT_FILE" | tr -d '\r\n' | base64 -d > "$SCREENSHOT_PATH" 2>/dev/null || true
275+
276+
if [ -s "$SCREENSHOT_PATH" ]; then
277+
ra_log "Screenshot recovered from logcat: $(ls -lh "$SCREENSHOT_PATH" | awk "{print \$5, \$9}")"
278+
EXTRACTED=1
279+
else
280+
ra_log "No markers found in logcat or decode failed"
281+
fi
282+
else
283+
ra_log "No logcat file found to scan for markers"
284+
fi
179285
fi
180286

181287
ra_log "Latest connected test report: ${NEWEST_XML:-<none>}"
182288

183289
ra_log "Instrumentation tests completed successfully"
290+
if [ "$EXTRACTED" -ne 1 ] && [ ! -s "$SCREENSHOT_OUT" ]; then
291+
ra_log "Screenshot not found in TestStorage or external files dir"
292+
fi
293+
294+
exit "$status"

0 commit comments

Comments
 (0)