diff --git a/cn1-maven-archetypes b/cn1-maven-archetypes new file mode 160000 index 0000000000..639042f98b --- /dev/null +++ b/cn1-maven-archetypes @@ -0,0 +1 @@ +Subproject commit 639042f98b738c73f7ace4bea413d4500c0eae49 diff --git a/scripts/DeviceRunnerTest/pom.xml b/scripts/DeviceRunnerTest/pom.xml new file mode 100644 index 0000000000..d27754d4a7 --- /dev/null +++ b/scripts/DeviceRunnerTest/pom.xml @@ -0,0 +1,56 @@ + + + 4.0.0 + + com.mycompany + device-runner-test + 1.0-SNAPSHOT + + + 1.8 + 1.8 + 7.0.41 + 7.0.41 + + + + + com.codenameone + codenameone-core + ${codenameone.version} + + + + + + + com.codenameone + codenameone-maven-plugin + ${codenameone.plugin.version} + + + + build + + + + + + + + + android + + android + + + + ios + + ios + + + + diff --git a/scripts/DeviceRunnerTest/src/codenameone_settings.properties b/scripts/DeviceRunnerTest/src/codenameone_settings.properties new file mode 100644 index 0000000000..68bd7791cb --- /dev/null +++ b/scripts/DeviceRunnerTest/src/codenameone_settings.properties @@ -0,0 +1,19 @@ +# +#Mon Oct 27 12:12:51 IST 2025 +codename1.vendor=CodenameOne +codename1.displayName=DeviceRunnerTest +codename1.mainName=DeviceRunnerTest +codename1.ios.release.provision= +codename1.languageLevel=5 +codename1.ios.release.certificate= +codename1.ios.version=1.0 +codename1.secondaryTitle=DeviceRunnerTest +codename1.android.keystorePassword= +codename1.android.keystoreAlias= +codename1.android.keyPassword= +codename1.packageName=com.mycompany.app +codename1.ios.debug.certificate= +codename1.version=1.0 +codename1.ios.debug.provision= +codename1.android.keystore= +codename1.icon=icon.png diff --git a/scripts/DeviceRunnerTest/src/com/mycompany/app/DeviceRunnerTest.java b/scripts/DeviceRunnerTest/src/com/mycompany/app/DeviceRunnerTest.java new file mode 100644 index 0000000000..d048be505a --- /dev/null +++ b/scripts/DeviceRunnerTest/src/com/mycompany/app/DeviceRunnerTest.java @@ -0,0 +1,50 @@ +package com.mycompany.app; + +import com.codename1.ui.Display; +import com.codename1.ui.Form; +import com.codename1.ui.Label; +import com.codename1.ui.layouts.BorderLayout; +import com.codename1.ui.util.UITimer; +import com.codename1.io.Log; +import java.io.IOException; +import com.codename1.ui.Image; +import java.io.ByteArrayOutputStream; +import com.codename1.io.Base64; + +public class DeviceRunnerTest { + + private Form current; + + public void init(Object context) { + } + + public void start() { + if (current != null) { + current.show(); + return; + } + Form hi = new Form("Hi World", new BorderLayout()); + hi.add(BorderLayout.CENTER, new Label("Hello, World!")); + hi.show(); + UITimer.timer(1000, false, () -> { + Image screenshot = Image.createImage(hi.getWidth(), hi.getHeight()); + hi.paintComponent(screenshot.getGraphics(), true); + try (ByteArrayOutputStream os = new ByteArrayOutputStream()) { + com.codename1.ui.ImageIO.getImageIO().save(screenshot, os, com.codename1.ui.Image.FORMAT_PNG, 1.0f); + System.out.println("CN1SS_BEGIN"); + System.out.println(Base64.encode(os.toByteArray())); + System.out.println("CN1SS_END"); + } catch (IOException e) { + Log.e(e); + } + Display.getInstance().exitApplication(); + }); + } + + public void stop() { + current = Display.getInstance().getCurrent(); + } + + public void destroy() { + } +} diff --git a/scripts/android/lib/PatchGradleFiles.java b/scripts/android/lib/PatchGradleFiles.java deleted file mode 100644 index 549da1dbf5..0000000000 --- a/scripts/android/lib/PatchGradleFiles.java +++ /dev/null @@ -1,319 +0,0 @@ -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public class PatchGradleFiles { - private static final String REPOSITORIES_BLOCK = """ - repositories { - google() - mavenCentral() - } - """.stripTrailing(); - - private static final Pattern REPOSITORIES_PATTERN = Pattern.compile("(?ms)^\\s*repositories\\s*\\{.*?\\}"); - - private static final Pattern ANDROID_BLOCK_PATTERN = Pattern.compile("(?m)^\\s*android\\s*\\{"); - private static final Pattern DEFAULT_CONFIG_PATTERN = Pattern.compile("(?ms)^\\s*defaultConfig\\s*\\{.*?^\\s*\\}"); - private static final Pattern DEFAULT_CONFIG_HEADER_PATTERN = Pattern.compile("(?ms)^\\s*defaultConfig\\s*\\{"); - private static final Pattern COMPILE_SDK_PATTERN = Pattern.compile("(?m)^\\s*compileSdkVersion\\s+\\d+"); - private static final Pattern TARGET_SDK_PATTERN = Pattern.compile("(?m)^\\s*targetSdkVersion\\s+\\d+"); - private static final Pattern TEST_INSTRUMENTATION_PATTERN = Pattern.compile("(?m)^\\s*testInstrumentationRunner\\s*\".*?\"\\s*$"); - private static final Pattern USE_LIBRARY_PATTERN = Pattern.compile("(?m)^\\s*useLibrary\\s+'android\\.test\\.(?:base|mock|runner)'\\s*$"); - private static final Pattern DEPENDENCY_PATTERN = Pattern.compile("(?m)^\\s*(implementation|api|testImplementation|androidTestImplementation)\\b"); - - public static void main(String[] args) throws Exception { - Arguments arguments = Arguments.parse(args); - if (arguments == null) { - System.exit(2); - return; - } - - boolean modifiedRoot = patchRootBuildGradle(arguments.root); - boolean modifiedApp = patchAppBuildGradle(arguments.app, arguments.compileSdk, arguments.targetSdk); - - if (modifiedRoot) { - System.out.println("Patched " + arguments.root); - } - if (modifiedApp) { - System.out.println("Patched " + arguments.app); - } - if (!modifiedRoot && !modifiedApp) { - System.out.println("Gradle files already normalized"); - } - } - - private static boolean patchRootBuildGradle(Path path) throws IOException { - String content = Files.readString(path, StandardCharsets.UTF_8); - Matcher matcher = REPOSITORIES_PATTERN.matcher(content); - if (!matcher.find()) { - if (!content.endsWith("\n")) { - content += "\n"; - } - content += REPOSITORIES_BLOCK; - Files.writeString(path, ensureTrailingNewline(content), StandardCharsets.UTF_8); - return true; - } - - String block = matcher.group(); - boolean changed = false; - if (!block.contains("google()") || !block.contains("mavenCentral()")) { - String[] lines = block.split("\n"); - java.util.LinkedHashSet body = new java.util.LinkedHashSet<>(); - for (int i = 1; i < lines.length - 1; i++) { - String line = lines[i].trim(); - if (!line.isEmpty()) { - body.add(" " + line.trim()); - } - } - body.add(" google()"); - body.add(" mavenCentral()"); - StringBuilder newBlock = new StringBuilder(); - newBlock.append(lines[0]).append('\n'); - for (String line : body) { - newBlock.append(line).append('\n'); - } - newBlock.append(lines[lines.length - 1]); - content = content.substring(0, matcher.start()) + newBlock + content.substring(matcher.end()); - changed = true; - } - - if (changed) { - Files.writeString(path, ensureTrailingNewline(content), StandardCharsets.UTF_8); - } - return changed; - } - - private static boolean patchAppBuildGradle(Path path, int compileSdk, int targetSdk) throws IOException { - String content = Files.readString(path, StandardCharsets.UTF_8); - boolean changed = false; - - Result r = ensureAndroidBlock(content, compileSdk, targetSdk); - content = r.content(); - changed |= r.changed(); - - r = ensureInstrumentationRunner(content); - content = r.content(); - changed |= r.changed(); - - r = removeLegacyUseLibrary(content); - content = r.content(); - changed |= r.changed(); - - r = ensureTestDependencies(content); - content = r.content(); - changed |= r.changed(); - - if (changed) { - Files.writeString(path, ensureTrailingNewline(content), StandardCharsets.UTF_8); - } - return changed; - } - - private static Result ensureAndroidBlock(String content, int compileSdk, int targetSdk) { - Matcher androidBlockMatcher = ANDROID_BLOCK_PATTERN.matcher(content); - if (!androidBlockMatcher.find()) { - if (!content.endsWith("\n")) { - content += "\n"; - } - String block = "\nandroid {\n" + - " compileSdkVersion " + compileSdk + "\n" + - " defaultConfig {\n" + - " targetSdkVersion " + targetSdk + "\n" + - " }\n}"; - return new Result(content + block, true); - } - - boolean changed = false; - Matcher compileMatcher = COMPILE_SDK_PATTERN.matcher(content); - if (compileMatcher.find()) { - String replacement = " compileSdkVersion " + compileSdk; - String newContent = compileMatcher.replaceFirst(replacement); - if (!newContent.equals(content)) { - content = newContent; - changed = true; - } - } else { - Matcher insertMatcher = ANDROID_BLOCK_PATTERN.matcher(content); - if (insertMatcher.find()) { - int pos = insertMatcher.end(); - content = content.substring(0, pos) + "\n compileSdkVersion " + compileSdk + content.substring(pos); - changed = true; - } - } - - Matcher defaultConfigMatcher = DEFAULT_CONFIG_PATTERN.matcher(content); - if (defaultConfigMatcher.find()) { - String block = defaultConfigMatcher.group(); - Matcher targetMatcher = TARGET_SDK_PATTERN.matcher(block); - String replacement = " targetSdkVersion " + targetSdk; - String updated; - if (targetMatcher.find()) { - updated = targetMatcher.replaceFirst(replacement); - } else { - int brace = block.indexOf('{'); - if (brace >= 0) { - updated = block.substring(0, brace + 1) + "\n" + replacement + block.substring(brace + 1); - } else { - updated = block; - } - } - if (!updated.equals(block)) { - content = content.substring(0, defaultConfigMatcher.start()) + updated + content.substring(defaultConfigMatcher.end()); - changed = true; - } - } else { - Matcher insertMatcher = ANDROID_BLOCK_PATTERN.matcher(content); - if (insertMatcher.find()) { - int pos = insertMatcher.end(); - String snippet = "\n defaultConfig {\n targetSdkVersion " + targetSdk + "\n }"; - content = content.substring(0, pos) + snippet + content.substring(pos); - changed = true; - } - } - - return new Result(content, changed); - } - - private static Result ensureInstrumentationRunner(String content) { - String runner = "androidx.test.runner.AndroidJUnitRunner"; - if (content.contains(runner)) { - return new Result(content, false); - } - Matcher matcher = TEST_INSTRUMENTATION_PATTERN.matcher(content); - if (matcher.find()) { - String replacement = " testInstrumentationRunner \"" + runner + "\""; - String newContent = matcher.replaceAll(replacement); - return new Result(newContent, !newContent.equals(content)); - } - - Matcher defaultConfigHeaderMatcher = DEFAULT_CONFIG_HEADER_PATTERN.matcher(content); - if (defaultConfigHeaderMatcher.find()) { - int pos = defaultConfigHeaderMatcher.end(); - String snippet = "\n testInstrumentationRunner \"" + runner + "\""; - content = content.substring(0, pos) + snippet + content.substring(pos); - return new Result(content, true); - } - - Matcher androidMatcher = ANDROID_BLOCK_PATTERN.matcher(content); - if (androidMatcher.find()) { - int pos = androidMatcher.end(); - String snippet = "\n defaultConfig {\n testInstrumentationRunner \"" + runner + "\"\n }"; - content = content.substring(0, pos) + snippet + content.substring(pos); - return new Result(content, true); - } - return new Result(content, false); - } - - private static Result removeLegacyUseLibrary(String content) { - Matcher matcher = USE_LIBRARY_PATTERN.matcher(content); - String newContent = matcher.replaceAll(""); - return new Result(newContent, !newContent.equals(content)); - } - - private static Result ensureTestDependencies(String content) { - String moduleView = content.replaceAll("(?ms)^\\s*(buildscript|pluginManagement)\\s*\\{.*?^\\s*\\}", ""); - boolean usesModern = DEPENDENCY_PATTERN.matcher(moduleView).find(); - String configuration = usesModern ? "androidTestImplementation" : "androidTestCompile"; - String[] dependencies = { - "androidx.test.ext:junit:1.1.5", - "androidx.test:runner:1.5.2", - "androidx.test:core:1.5.0", - "androidx.test.services:storage:1.4.2" - }; - boolean missing = false; - for (String dep : dependencies) { - if (!moduleView.contains(dep)) { - missing = true; - break; - } - } - if (!missing) { - return new Result(content, false); - } - StringBuilder block = new StringBuilder(); - block.append("\n\ndependencies {\n"); - for (String dep : dependencies) { - if (!moduleView.contains(dep)) { - block.append(" ").append(configuration).append(" \"").append(dep).append("\"\n"); - } - } - block.append("}\n"); - if (!content.endsWith("\n")) { - content += "\n"; - } - return new Result(content + block, true); - } - - private static String ensureTrailingNewline(String content) { - return content.endsWith("\n") ? content : content + "\n"; - } - - private record Result(String content, boolean changed) { - } - - private static class Arguments { - final Path root; - final Path app; - final int compileSdk; - final int targetSdk; - - Arguments(Path root, Path app, int compileSdk, int targetSdk) { - this.root = root; - this.app = app; - this.compileSdk = compileSdk; - this.targetSdk = targetSdk; - } - - static Arguments parse(String[] args) { - Path root = null; - Path app = null; - int compileSdk = 33; - int targetSdk = 33; - for (int i = 0; i < args.length; i++) { - String arg = args[i]; - switch (arg) { - case "--root" -> { - if (i + 1 >= args.length) { - System.err.println("Missing value for --root"); - return null; - } - root = Path.of(args[++i]); - } - case "--app" -> { - if (i + 1 >= args.length) { - System.err.println("Missing value for --app"); - return null; - } - app = Path.of(args[++i]); - } - case "--compile-sdk" -> { - if (i + 1 >= args.length) { - System.err.println("Missing value for --compile-sdk"); - return null; - } - compileSdk = Integer.parseInt(args[++i]); - } - case "--target-sdk" -> { - if (i + 1 >= args.length) { - System.err.println("Missing value for --target-sdk"); - return null; - } - targetSdk = Integer.parseInt(args[++i]); - } - default -> { - System.err.println("Unknown argument: " + arg); - return null; - } - } - } - if (root == null || app == null) { - System.err.println("--root and --app are required"); - return null; - } - return new Arguments(root, app, compileSdk, targetSdk); - } - } -} diff --git a/scripts/ios/create-shared-scheme.py b/scripts/ios/create-shared-scheme.py deleted file mode 100755 index 12b9f63efa..0000000000 --- a/scripts/ios/create-shared-scheme.py +++ /dev/null @@ -1,270 +0,0 @@ -#!/usr/bin/env python3 -"""Ensure an Xcode scheme exists that wires the UI test bundle for Codename One CI. - -The Codename One iOS template historically only shipped a user-specific scheme, -which means fresh CI machines don't see any test actions when invoking -``xcodebuild test``. This helper inspects the generated project, discovers the -primary application target and any associated unit/UI test bundles, and emits a -shared scheme that drives them. - -Usage: - create-shared-scheme.py [scheme_name] - -The script writes the shared scheme into both the .xcodeproj and any sibling -.xcworkspace directories so either entry point exposes the test action. -""" - -from __future__ import annotations - -import argparse -import re -import sys -from dataclasses import dataclass -from pathlib import Path -from typing import Iterable, List, Optional - - -@dataclass -class Target: - identifier: str - name: str - product_type: str - product_name: Optional[str] - - @property - def buildable_name(self) -> str: - if self.product_name: - return self.product_name - if self.product_type.endswith(".application"): - return f"{self.name}.app" - if self.product_type.endswith(".bundle.ui-testing") or self.product_type.endswith(".bundle.unit-test"): - return f"{self.name}.xctest" - return self.name - - -TARGET_BLOCK_RE = re.compile( - r""" - ^\s*(?P[0-9A-F]{24})\s+/\*\s+(?P[^*]+)\s+\*/\s+=\s+\{ - (?P.*?) - ^\s*\}; - """, - re.MULTILINE | re.DOTALL | re.VERBOSE, -) - - -def parse_targets(project_file: Path) -> List[Target]: - content = project_file.read_text(encoding="utf-8") - targets: List[Target] = [] - for match in TARGET_BLOCK_RE.finditer(content): - body = match.group("body") - if "isa = PBXNativeTarget;" not in body: - continue - name = _search_value(body, r"name = (?P[^;]+);") - product_type = _search_value(body, r"productType = \"(?P[^\"]+)\";") - product_name = _search_value(body, r"productReference = [0-9A-F]{24} /\* (?P[^*]+) \*/;") - if not name or not product_type: - continue - targets.append( - Target( - identifier=match.group("identifier"), - name=name.strip().strip('"'), - product_type=product_type.strip(), - product_name=product_name.strip() if product_name else None, - ) - ) - return targets - - -def _search_value(text: str, pattern: str) -> Optional[str]: - m = re.search(pattern, text) - return m.group("value") if m else None - - -def choose_targets(targets: Iterable[Target]) -> tuple[Optional[Target], Optional[Target], Optional[Target]]: - app = None - ui = None - unit = None - for target in targets: - if target.product_type.endswith(".application") and app is None: - app = target - elif target.product_type.endswith(".bundle.ui-testing") and ui is None: - ui = target - elif target.product_type.endswith(".bundle.unit-test") and unit is None: - unit = target - return app, unit, ui - - -def render_testable(target: Target, container: str) -> str: - return f""" - - - """ - - -def render_scheme( - scheme_name: str, - project_container: str, - app: Target, - testables: List[str], -) -> str: - testables_block = "\n".join(testables) if testables else "" - return f""" - - - - - - - - - - - -{testables_block} - - - - - - - - - - - - - - - - - - - - - - - - - -""" - - -def ensure_scheme(destination: Path, scheme_name: str, xml: str) -> None: - destination.mkdir(parents=True, exist_ok=True) - scheme_path = destination / f"{scheme_name}.xcscheme" - scheme_path.write_text(xml, encoding="utf-8") - - -def main(argv: List[str]) -> int: - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("project_dir", type=Path) - parser.add_argument("scheme_name", nargs="?", help="Override the generated scheme name") - args = parser.parse_args(argv[1:]) - - project_dir: Path = args.project_dir.resolve() - if not project_dir.is_dir(): - print(f"error: project directory not found: {project_dir}", file=sys.stderr) - return 1 - - try: - xcodeproj = next(project_dir.glob("*.xcodeproj")) - except StopIteration: - print(f"error: unable to locate an .xcodeproj under {project_dir}", file=sys.stderr) - return 1 - - project_container = xcodeproj.name - project_file = xcodeproj / "project.pbxproj" - if not project_file.is_file(): - print(f"error: missing project file: {project_file}", file=sys.stderr) - return 1 - - targets = parse_targets(project_file) - if not targets: - print(f"error: no build targets discovered in {project_file}", file=sys.stderr) - return 1 - - app, unit, ui = choose_targets(targets) - if not app: - print("error: unable to find application target", file=sys.stderr) - return 1 - - scheme_name = args.scheme_name or app.name - - # Prefer UI tests only. Include unit tests only if there is no UI test target. - testables: List[str] = [] - if ui is not None: - testables.append(render_testable(ui, project_container)) - elif unit is not None: - testables.append(render_testable(unit, project_container)) - - if not testables: - print("warning: no unit or UI test targets discovered; emitting app-only scheme", file=sys.stderr) - - xml = render_scheme(scheme_name, project_container, app, testables) - - destinations = [xcodeproj / "xcshareddata" / "xcschemes"] - destinations.extend(ws / "xcshareddata" / "xcschemes" for ws in project_dir.glob("*.xcworkspace")) - - for dest in destinations: - ensure_scheme(dest, scheme_name, xml) - - return 0 - - -if __name__ == "__main__": - raise SystemExit(main(sys.argv)) diff --git a/scripts/ios/tests/HelloCodenameOneUITests.swift.tmpl b/scripts/ios/tests/HelloCodenameOneUITests.swift.tmpl deleted file mode 100644 index 75a90b9d10..0000000000 --- a/scripts/ios/tests/HelloCodenameOneUITests.swift.tmpl +++ /dev/null @@ -1,159 +0,0 @@ -import XCTest -import UIKit - -final class HelloCodenameOneUITests: XCTestCase { - private var app: XCUIApplication! - private var outputDirectory: URL! - private let chunkSize = 2000 - private let previewChannel = "PREVIEW" - private let previewQualities: [CGFloat] = [0.60, 0.50, 0.40, 0.35, 0.30, 0.25, 0.20, 0.18, 0.16, 0.14, 0.12, 0.10, 0.08, 0.06, 0.05, 0.04, 0.03, 0.02, 0.01] - private let maxPreviewBytes = 20 * 1024 - - override func setUpWithError() throws { - continueAfterFailure = false - app = XCUIApplication() - - // Locale for determinism - app.launchArguments += ["-AppleLocale", "en_US", "-AppleLanguages", "(en)"] - // Tip: force light mode or content size if you need pixel-stable shots - // app.launchArguments += ["-uiuserInterfaceStyle", "Light"] - - // IMPORTANT: write to the app's sandbox, not a host path - let tmp = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true) - if let tag = ProcessInfo.processInfo.environment["CN1SS_OUTPUT_DIR"], !tag.isEmpty { - outputDirectory = tmp.appendingPathComponent(tag, isDirectory: true) - } else { - outputDirectory = tmp.appendingPathComponent("cn1screens", isDirectory: true) - } - try FileManager.default.createDirectory(at: outputDirectory, withIntermediateDirectories: true) - - app.launch() - waitForStableFrame() - } - - override func tearDownWithError() throws { - app?.terminate() - app = nil - } - - private func captureScreenshot(named name: String) throws { - let shot = XCUIScreen.main.screenshot() - - // Save into sandbox tmp (optional – mainly for local debugging) - let pngURL = outputDirectory.appendingPathComponent("\(name).png") - do { try shot.pngRepresentation.write(to: pngURL) } catch { /* ignore */ } - - // ALWAYS attach so we can export from the .xcresult - let att = XCTAttachment(screenshot: shot) - att.name = name - att.lifetime = .keepAlways - add(att) - - emitScreenshotPayloads(for: shot, name: name) - } - - /// Wait for foreground + a short settle time - private func waitForStableFrame(timeout: TimeInterval = 30, settle: TimeInterval = 1.2) { - _ = app.wait(for: .runningForeground, timeout: timeout) - RunLoop.current.run(until: Date(timeIntervalSinceNow: settle)) - } - - /// Tap using normalized coordinates (0...1) - private func tapNormalized(_ dx: CGFloat, _ dy: CGFloat) { - let origin = app.coordinate(withNormalizedOffset: .zero) - let target = origin.withOffset(.init(dx: app.frame.size.width * dx, - dy: app.frame.size.height * dy)) - target.tap() - } - - func testMainScreenScreenshot() throws { - waitForStableFrame() - try captureScreenshot(named: "MainActivity") - } - - func testBrowserComponentScreenshot() throws { - waitForStableFrame() - tapNormalized(0.5, 0.70) - // tiny retry to allow BrowserComponent to render - RunLoop.current.run(until: Date(timeIntervalSinceNow: 2.0)) - try captureScreenshot(named: "BrowserComponent") - } - - private func sanitizeTestName(_ name: String) -> String { - let allowed = CharacterSet(charactersIn: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_.-") - let underscore: UnicodeScalar = "_" - var scalars: [UnicodeScalar] = [] - scalars.reserveCapacity(name.unicodeScalars.count) - for scalar in name.unicodeScalars { - scalars.append(allowed.contains(scalar) ? scalar : underscore) - } - return String(String.UnicodeScalarView(scalars)) - } - - private func emitScreenshotPayloads(for shot: XCUIScreenshot, name: String) { - let safeName = sanitizeTestName(name) - let pngData = shot.pngRepresentation - print("CN1SS:INFO:test=\(safeName) png_bytes=\(pngData.count)") - emitScreenshotChannel(data: pngData, name: safeName, channel: "") - - if let preview = makePreviewJPEG(from: shot, pngData: pngData) { - print("CN1SS:INFO:test=\(safeName) preview_jpeg_bytes=\(preview.data.count) preview_quality=\(preview.quality)") - if preview.data.count > maxPreviewBytes { - print("CN1SS:WARN:test=\(safeName) preview_exceeds_limit_bytes=\(preview.data.count) max_preview_bytes=\(maxPreviewBytes)") - } - emitScreenshotChannel(data: preview.data, name: safeName, channel: previewChannel) - } else { - print("CN1SS:INFO:test=\(safeName) preview_jpeg_bytes=0 preview_quality=0") - } - } - - private func makePreviewJPEG(from shot: XCUIScreenshot, pngData: Data) -> (data: Data, quality: Int)? { - guard let image = UIImage(data: pngData) else { - return nil - } - var chosenData: Data? - var chosenQuality = 0 - var smallest = Int.max - for quality in previewQualities { - guard let jpeg = image.jpegData(compressionQuality: quality) else { continue } - let length = jpeg.count - if length < smallest { - smallest = length - chosenData = jpeg - chosenQuality = Int((quality * 100).rounded()) - } - if length <= maxPreviewBytes { - break - } - } - guard let finalData = chosenData, !finalData.isEmpty else { - return nil - } - return (finalData, chosenQuality) - } - - private func emitScreenshotChannel(data: Data, name: String, channel: String) { - var prefix = "CN1SS" - if !channel.isEmpty { - prefix += channel - } - guard !data.isEmpty else { - print("\(prefix):END:\(name)") - return - } - let base64 = data.base64EncodedString() - var current = base64.startIndex - var position = 0 - var chunkCount = 0 - while current < base64.endIndex { - let next = base64.index(current, offsetBy: chunkSize, limitedBy: base64.endIndex) ?? base64.endIndex - let chunk = base64[current.. { + try { + ProcessBuilder adbLogcat = new ProcessBuilder("adb", "logcat"); + Process logcat = adbLogcat.start(); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(logcat.getInputStream()))) { + String line; + while ((line = reader.readLine()) != null) { + System.out.println(line); + } + } + logcat.destroy(); + } catch (IOException e) { + e.printStackTrace(); + } + }); + + System.out.println("Starting app"); + ProcessBuilder amStart = new ProcessBuilder("adb", "shell", "am", "start", "-n", "com.mycompany.app/com.mycompany.app.DeviceRunnerTestStub"); + amStart.inheritIO(); + amStart.start().waitFor(); + System.out.println("App started"); + + executor.shutdown(); + executor.awaitTermination(1, TimeUnit.MINUTES); + } + + private static void runIOS() throws IOException, InterruptedException { + System.out.println("Running iOS test"); + Path xcodeProject = Paths.get("scripts/DeviceRunnerTest/target/ios/DeviceRunnerTest.xcodeproj"); + System.out.println("Found Xcode project: " + xcodeProject); + new ProcessBuilder("xcrun", "simctl", "boot", "iPhone 15 Pro").inheritIO().start().waitFor(); + + ExecutorService executor = Executors.newSingleThreadExecutor(); + executor.submit(() -> { + try { + ProcessBuilder logStream = new ProcessBuilder("xcrun", "simctl", "spawn", "iPhone 15 Pro", "log", "stream"); + Process log = logStream.start(); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(log.getInputStream()))) { + String line; + while ((line = reader.readLine()) != null) { + System.out.println(line); + } + } + log.destroy(); + } catch (IOException e) { + e.printStackTrace(); + } + }); + + System.out.println("Running xcodebuild"); + new ProcessBuilder("xcodebuild", "-project", xcodeProject.toString(), "-scheme", "DeviceRunnerTest", "-destination", "platform=iOS Simulator,name=iPhone 15 Pro", "test").inheritIO().start().waitFor(); + System.out.println("xcodebuild finished"); + + executor.shutdown(); + executor.awaitTermination(1, TimeUnit.MINUTES); + } + + private static Path findApk(Path dir) throws IOException { + try (Stream stream = Files.walk(dir)) { + return stream.filter(p -> p.toString().endsWith(".apk")).findFirst().orElseThrow(() -> new RuntimeException("APK not found")); + } + } +} diff --git a/scripts/run-android-instrumentation-tests.sh b/scripts/run-android-instrumentation-tests.sh index 56f8de7bc8..9fd05bb9a3 100755 --- a/scripts/run-android-instrumentation-tests.sh +++ b/scripts/run-android-instrumentation-tests.sh @@ -74,12 +74,8 @@ cn1ss_setup "$JAVA17_BIN" "$CN1SS_HELPER_SOURCE_DIR" set -o pipefail ra_log "Running instrumentation tests (stdout -> $TEST_LOG; stderr -> terminal)" ( - cd "$GRADLE_PROJECT_DIR" - ORIG_JAVA_HOME="${JAVA_HOME:-}" - export JAVA_HOME="${JAVA17_HOME:?JAVA17_HOME not set}" - ./gradlew --no-daemon --console=plain connectedDebugAndroidTest | tee "$TEST_LOG" - export JAVA_HOME="$ORIG_JAVA_HOME" -) || { ra_log "STAGE:GRADLE_TEST_FAILED (see $TEST_LOG)"; exit 10; } + "$JAVA17_HOME/bin/java" -cp scripts/java BuildAndRun android | tee "$TEST_LOG" +) || { ra_log "STAGE:TEST_FAILED (see $TEST_LOG)"; exit 10; } echo ra_log "==== Begin connectedAndroidTest.log (tail -n 200) ====" diff --git a/scripts/run-ios-ui-tests.sh b/scripts/run-ios-ui-tests.sh index a9cc289f70..27f0390fff 100755 --- a/scripts/run-ios-ui-tests.sh +++ b/scripts/run-ios-ui-tests.sh @@ -199,19 +199,8 @@ XCODE_TEST_FILTERS=( ) set -o pipefail -if ! xcodebuild \ - -workspace "$WORKSPACE_PATH" \ - -scheme "$SCHEME" \ - -sdk iphonesimulator \ - -configuration Debug \ - -destination "$SIM_DESTINATION" \ - -derivedDataPath "$DERIVED_DATA_DIR" \ - -resultBundlePath "$RESULT_BUNDLE" \ - "${XCODE_TEST_FILTERS[@]}" \ - CODE_SIGNING_ALLOWED=NO CODE_SIGNING_REQUIRED=NO \ - GENERATE_INFOPLIST_FILE=YES \ - test | tee "$TEST_LOG"; then - ri_log "STAGE:XCODE_TEST_FAILED -> See $TEST_LOG" +if ! "$JAVA17_HOME/bin/java" -cp scripts/java BuildAndRun ios | tee "$TEST_LOG"; then + ri_log "STAGE:TEST_FAILED -> See $TEST_LOG" exit 10 fi set +o pipefail