Skip to content

Commit b8d3686

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

File tree

2 files changed

+186
-78
lines changed

2 files changed

+186
-78
lines changed

scripts/build-android-app.sh

Lines changed: 160 additions & 78 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,193 @@ 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;
385+
import android.content.Context;
329386
import android.os.ParcelFileDescriptor;
330-
import android.util.Base64;
331387
332-
import java.io.FileInputStream;
333-
import java.io.IOException;
388+
import androidx.test.ext.junit.runners.AndroidJUnit4;
389+
import androidx.test.platform.app.InstrumentationRegistry;
390+
import androidx.test.core.app.ApplicationProvider;
391+
392+
import org.junit.Test;
393+
import org.junit.runner.RunWith;
394+
334395
import java.io.InputStream;
396+
import java.io.FileInputStream;
397+
import java.io.OutputStream;
398+
import java.io.File;
399+
import java.io.FileOutputStream;
400+
import java.io.ByteArrayOutputStream;
335401
336-
public class HelloCodenameOneInstrumentedTest extends InstrumentationTestCase {
402+
import static org.junit.Assert.assertEquals;
337403
338-
public void testUseAppContext() {
339-
Context appContext = getInstrumentation().getTargetContext();
340-
assertEquals("$PACKAGE_NAME", appContext.getPackageName());
341-
}
404+
@RunWith(AndroidJUnit4.class)
405+
public class HelloCodenameOneInstrumentedTest {
342406
343-
private static final int CHUNK = 200_000;
407+
@Test
408+
public void testUseAppContext_andSaveScreenshot() throws Exception {
409+
String expectedPkg = "@PACKAGE@";
410+
Context ctx = ApplicationProvider.getApplicationContext();
411+
assertEquals(expectedPkg, ctx.getPackageName());
344412
345-
public static void printPngToStdout(Instrumentation inst) {
346-
try {
413+
// Capture PNG from emulator
414+
Instrumentation inst = InstrumentationRegistry.getInstrumentation();
347415
ParcelFileDescriptor pfd = inst.getUiAutomation().executeShellCommand("screencap -p");
348-
byte[] png;
349-
try (InputStream in = new FileInputStream(pfd.getFileDescriptor())) {
350-
png = readAll(in);
416+
byte[] png = readAll(new FileInputStream(pfd.getFileDescriptor()));
417+
418+
// Try AndroidX TestStorage (preferred)
419+
boolean wrote = tryWriteViaTestStorage(png);
420+
421+
// Fallback: app's external files dir (host can adb pull this)
422+
if (!wrote) {
423+
File f = new File(ctx.getExternalFilesDir(null), "emulator-screenshot.png");
424+
try (FileOutputStream fos = new FileOutputStream(f)) {
425+
fos.write(png);
426+
}
427+
System.out.println("WROTE_TO_EXTERNAL_FILES:" + f.getAbsolutePath());
351428
}
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));
429+
}
430+
431+
private static boolean tryWriteViaTestStorage(byte[] bytes) {
432+
try {
433+
// Avoid direct reference to keep compile working if services lib is missing in some templates
434+
Class<?> cls = Class.forName("androidx.test.services.storage.TestStorage");
435+
java.lang.reflect.Method m = cls.getMethod("openOutputFile", String.class);
436+
try (OutputStream out = (OutputStream) m.invoke(null, "emulator-screenshot.png")) {
437+
out.write(bytes);
438+
}
439+
System.out.println("WROTE_TO_TEST_STORAGE:emulator-screenshot.png");
440+
return true;
441+
} catch (Throwable t) {
442+
System.out.println("TEST_STORAGE_UNAVAILABLE:" + t.getClass().getSimpleName());
443+
return false;
357444
}
358-
System.out.println("<<CN1_SCREENSHOT_END>>");
359-
System.out.flush();
360-
} catch (IOException err) {
361-
err.printStackTrace();
362-
throw new RuntimeException(err);
363-
}
364445
}
365446
366-
private static byte[] readAll(InputStream in) throws IOException {
447+
private static byte[] readAll(InputStream in) throws Exception {
367448
byte[] buf = new byte[64 * 1024];
368449
int n;
369-
java.io.ByteArrayOutputStream out = new java.io.ByteArrayOutputStream();
450+
ByteArrayOutputStream out = new ByteArrayOutputStream();
370451
while ((n = in.read(buf)) != -1) out.write(buf, 0, n);
371452
return out.toByteArray();
372453
}
373454
}
374455
EOF
456+
sed -i "s|@PACKAGE@|$PACKAGE_NAME|g" "$TEST_CLASS"
375457
ba_log "Created instrumentation test at $TEST_CLASS"
376458

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

scripts/run-android-instrumentation-tests.sh

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,32 @@ 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+
if [ -n "$FOUND" ] && [ -s "$FOUND" ]; then
138+
cp -f "$FOUND" "$SCREENSHOT_OUT"
139+
ra_log "Collected screenshot from TestStorage: $FOUND"
140+
else
141+
# Fallback: pull from app external files dir (small file, robust with retries)
142+
PKG="$("$GRADLE_PROJECT_DIR/gradlew" -q :app:properties | awk -F= '/^applicationId=/{print $2;exit}' || echo "$PACKAGE_NAME")"
143+
REMOTE="/sdcard/Android/data/$PKG/files/emulator-screenshot.png"
144+
ra_log "Trying fallback pull from device: $REMOTE"
145+
for i in 1 2 3; do
146+
if adb_target shell ls -l "$REMOTE" >/dev/null 2>&1; then
147+
adb_target pull "$REMOTE" "$SCREENSHOT_OUT" >/dev/null 2>&1 && break
148+
fi
149+
sleep 2
150+
done
151+
if [ -s "$SCREENSHOT_OUT" ]; then
152+
ra_log "Collected screenshot via fallback pull"
153+
else
154+
ra_log "Screenshot not found in TestStorage or external files dir"
155+
fi
156+
fi
157+
132158
# Debug: show what exists
133159
ra_log "Listing connected test outputs under: $RESULTS_ROOT"
134160
find "$RESULTS_ROOT" -maxdepth 4 -printf '%y %p\n' 2>/dev/null | sed 's/^/[run-android-instrumentation-tests] /' || true

0 commit comments

Comments
 (0)