279 lines
23 KiB
Python
279 lines
23 KiB
Python
"""Manual JVM simulation of TouchGestures callbacks; no Android device is used.
|
|
|
|
Run explicitly from the repository root:
|
|
uv run python tests/native_actions_fixture/touch_jvm_harness.py
|
|
|
|
Requires a JDK (javac and java) through JAVA_HOME or PATH. Uses only the Python
|
|
standard library, generates fake Android classes in a temporary directory, and
|
|
compiles the repository's actual TouchGestures.java and GestureController.java.
|
|
It checks callback ownership,
|
|
cancellation, release, and error handling. It cannot prove real Android input,
|
|
MotionEvent delivery, visual results, or physical pointer continuity.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
from pathlib import Path
|
|
import re
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
from tempfile import TemporaryDirectory
|
|
from time import perf_counter
|
|
|
|
|
|
# These minimal fakes model callback scheduling, not Android's implementation.
|
|
STUBS: dict[str, str] = {
|
|
"android/accessibilityservice/AccessibilityService.java": r"""
|
|
package android.accessibilityservice;
|
|
import android.os.Handler;import android.content.Context;import java.util.*;
|
|
public class AccessibilityService extends Context {
|
|
public static final int GLOBAL_ACTION_BACK=1,GLOBAL_ACTION_HOME=2,GLOBAL_ACTION_RECENTS=3,GLOBAL_ACTION_NOTIFICATIONS=4,GLOBAL_ACTION_QUICK_SETTINGS=5,GLOBAL_ACTION_POWER_DIALOG=6,GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN=7,GLOBAL_ACTION_LOCK_SCREEN=8,GLOBAL_ACTION_TAKE_SCREENSHOT=9;
|
|
public volatile int calls; public volatile int rejectOn,throwOn,cancelOn,dropOn,throwAndCompleteOn;public volatile boolean duplicate,earlyStationary;
|
|
public volatile List<GestureDescription.StrokeDescription> held;
|
|
public volatile GestureDescription lastGesture;private volatile GestureResultCallback lastCallback;private volatile Handler lastHandler;
|
|
public boolean performGlobalAction(int action){return false;}
|
|
private void emit(int n,GestureDescription gesture,GestureResultCallback callback){if(n==cancelOn){held=null;callback.onCancelled(gesture);}else{held=gesture.strokes.get(0).continuing?gesture.strokes:null;callback.onCompleted(gesture);if(duplicate)callback.onCompleted(gesture);}}
|
|
public void completeLast(){final GestureDescription gesture=lastGesture;final GestureResultCallback callback=lastCallback;lastHandler.post(()->emit(calls,gesture,callback));}
|
|
public boolean dispatchGesture(GestureDescription gesture,GestureResultCallback callback,Handler handler){
|
|
int n=++calls;
|
|
if(n==rejectOn)return false;
|
|
if(n==throwOn)throw new RuntimeException("mock dispatch uncertainty");
|
|
for(int i=0;i<gesture.strokes.size();i++){GestureDescription.StrokeDescription s=gesture.strokes.get(i);if(s.previous!=null&&(held==null||held.get(i)!=s.previous))throw new IllegalStateException("lost continuing contact");}
|
|
lastGesture=gesture;lastCallback=callback;lastHandler=handler;
|
|
long duration=0;for(GestureDescription.StrokeDescription stroke:gesture.strokes)duration=Math.max(duration,stroke.start+stroke.duration);
|
|
GestureDescription.StrokeDescription first=gesture.strokes.get(0);if(earlyStationary&&first.continuing&&first.path.x1==first.path.x2&&first.path.y1==first.path.y2)duration=0;
|
|
if(n!=dropOn)handler.postDelayed(()->emit(n,gesture,callback),duration+3);
|
|
if(n==throwAndCompleteOn)throw new RuntimeException("mock callback after throw");
|
|
return true;
|
|
}
|
|
public static class GestureResultCallback {public void onCompleted(GestureDescription d){}public void onCancelled(GestureDescription d){}}
|
|
}
|
|
""",
|
|
"android/accessibilityservice/GestureDescription.java": r"""
|
|
package android.accessibilityservice;
|
|
import android.graphics.Path; import java.util.*;
|
|
public class GestureDescription {
|
|
public final List<StrokeDescription> strokes; private GestureDescription(List<StrokeDescription> v){strokes=v;}
|
|
public static class Builder {final List<StrokeDescription> s=new ArrayList<>(); public Builder addStroke(StrokeDescription v){s.add(v);return this;}public GestureDescription build(){return new GestureDescription(new ArrayList<>(s));}}
|
|
public static class StrokeDescription { public final Path path;public final long start,duration; public final boolean continuing;public final StrokeDescription previous;
|
|
public StrokeDescription(Path p,long start,long d){this(p,start,d,false);}
|
|
public StrokeDescription(Path p,long start,long d,boolean c){this(p,start,d,c,null);if(start<0||d<1)throw new IllegalArgumentException();}
|
|
private StrokeDescription(Path p,long start,long d,boolean c,StrokeDescription prev){path=p;this.start=start;duration=d;continuing=c;previous=prev;}
|
|
public StrokeDescription continueStroke(Path p,long start,long d,boolean c){if(!continuing||start!=0||d<1||p.x1!=path.x2||p.y1!=path.y2)throw new IllegalArgumentException("invalid continuation");return new StrokeDescription(p,start,d,c,this);}
|
|
}
|
|
}
|
|
""",
|
|
"android/graphics/Path.java": r"""
|
|
package android.graphics; public class Path { public float x1,y1,x2,y2; public void moveTo(float x,float y){x1=x2=x;y1=y2=y;}public void lineTo(float x,float y){x2=x;y2=y;} }
|
|
""",
|
|
"android/os/Build.java": r"""
|
|
package android.os; public final class Build { public static final class VERSION { public static int SDK_INT=36; } public static final class VERSION_CODES {public static final int O=26,P=28;} }
|
|
""",
|
|
"android/os/Handler.java": r"""
|
|
package android.os;
|
|
import java.util.*; import java.util.concurrent.*;
|
|
public class Handler {
|
|
public static final ScheduledThreadPoolExecutor EXEC=new ScheduledThreadPoolExecutor(1,r->{Thread t=new Thread(r,"mock-main");t.setDaemon(true);return t;});
|
|
private static final Map<Runnable,List<Future<?>>> TASKS=new ConcurrentHashMap<>();
|
|
public Handler(Looper l){} public boolean post(Runnable r){return postDelayed(r,0);}
|
|
public boolean postDelayed(Runnable r,long delay){Future<?> f=EXEC.schedule(r,Math.max(0,delay),TimeUnit.MILLISECONDS);TASKS.computeIfAbsent(r,k->new CopyOnWriteArrayList<>()).add(f);return true;}
|
|
public void removeCallbacks(Runnable r){List<Future<?>> fs=TASKS.remove(r);if(fs!=null)for(Future<?> f:fs)f.cancel(false);}
|
|
}
|
|
""",
|
|
"android/os/Looper.java": r"""
|
|
package android.os; public final class Looper { private static final Looper MAIN=new Looper(); public static Looper getMainLooper(){return MAIN;} public static Looper myLooper(){return Thread.currentThread().getName().equals("mock-main")?MAIN:null;} }
|
|
""",
|
|
"android/os/SystemClock.java": r"""
|
|
package android.os; public final class SystemClock { private static volatile long offset;public static long uptimeMillis(){return System.nanoTime()/1000000L+offset;}public static void advance(long millis){offset+=millis;} }
|
|
""",
|
|
"android/os/Bundle.java": r"""
|
|
package android.os;public class Bundle {public void putCharSequence(String key,CharSequence value){}}
|
|
""",
|
|
"android/content/Context.java": r"""
|
|
package android.content;public class Context {public static final String CLIPBOARD_SERVICE="clipboard";public Object getSystemService(String name){return null;}}
|
|
""",
|
|
"android/content/ClipData.java": r"""
|
|
package android.content;public class ClipData {public static ClipData newPlainText(CharSequence label,CharSequence text){return new ClipData();}}
|
|
""",
|
|
"android/content/ClipboardManager.java": r"""
|
|
package android.content;public class ClipboardManager {public void setPrimaryClip(ClipData clip){}}
|
|
""",
|
|
"android/util/Log.java": r"""
|
|
package android.util;public final class Log {public static int w(String tag,String message){return 0;}public static int w(String tag,String message,Throwable error){return 0;}}
|
|
""",
|
|
"android/view/accessibility/AccessibilityNodeInfo.java": r"""
|
|
package android.view.accessibility;import android.os.Bundle;public class AccessibilityNodeInfo {public static final int ACTION_SET_TEXT=2097152;public static final String ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE="text";public CharSequence getText(){return null;}public boolean isShowingHintText(){return false;}public boolean performAction(int action,Bundle args){return false;}}
|
|
""",
|
|
"com/artemis/helper/HierarchyDumper.java": r"""
|
|
package com.artemis.helper;import android.accessibilityservice.AccessibilityService;import android.view.accessibility.AccessibilityNodeInfo;public final class HierarchyDumper {public static AccessibilityNodeInfo findInputNode(AccessibilityService service){return null;}public static void safeRecycle(AccessibilityNodeInfo node){}}
|
|
""",
|
|
"com/artemis/helper/DisplayUtils.java": r"""
|
|
package com.artemis.helper;import android.accessibilityservice.AccessibilityService;public final class DisplayUtils {public static volatile int width=1080,height=2400;public static class DisplayInfo {public int width,height;DisplayInfo(int w,int h){width=w;height=h;}}public static DisplayInfo getDisplayInfo(AccessibilityService s){return new DisplayInfo(width,height);}}
|
|
""",
|
|
"com/artemis/helper/NativeControls.java": r"""
|
|
package com.artemis.helper;import android.accessibilityservice.AccessibilityService;import org.json.JSONObject;public final class NativeControls {public static volatile JSONObject result;public static JSONObject inspect(AccessibilityService s,JSONObject args){return result;}}
|
|
""",
|
|
"com/artemis/helper/TestMain.java": r"""
|
|
package com.artemis.helper;
|
|
import android.accessibilityservice.AccessibilityService;import android.os.*;import org.json.*;import java.lang.reflect.*;import java.util.concurrent.*;import java.util.concurrent.atomic.*;
|
|
public final class TestMain {
|
|
static int checks;static String scenario="initialization";static ExecutorService workers=Executors.newCachedThreadPool();
|
|
static void check(boolean v,String message){checks++;if(!v)throw new AssertionError(scenario+": "+message);}
|
|
static void eq(Object actual,Object expected,String message){check(expected.equals(actual),message+": "+actual+" != "+expected);}
|
|
static JSONObject spec(String id,int interval,int frames,int fingers)throws Exception {JSONArray fs=new JSONArray();for(int f=0;f<frames;f++){JSONArray p=new JSONArray();for(int n=0;n<fingers;n++)p.put(new JSONArray().put(100+n*100+f*10).put(200+n*100+f*20));fs.put(new JSONObject().put("at_ms",f*interval).put("points",p));}return new JSONObject().put("operation_id",id).put("screen_size",new JSONArray().put(1080).put(2400)).put("frames",fs);}
|
|
static void code(JSONObject r,String c,String execution)throws Exception{eq(r.get("code"),c,"code");eq(r.get("execution"),execution,"execution");}
|
|
static JSONObject cancel(String id)throws Exception{return TouchGestures.cancel(new JSONObject().put("operation_id",id));}
|
|
static void waitCalls(AccessibilityService service,int calls)throws Exception {long end=System.currentTimeMillis()+2000;while(service.calls<calls&&System.currentTimeMillis()<end)Thread.sleep(1);check(service.calls>=calls,"dispatch started");}
|
|
static Object active()throws Exception{Field f=TouchGestures.class.getDeclaredField("ACTIVE");f.setAccessible(true);return ((AtomicReference<?>)f.get(null)).get();}
|
|
static void waitQuiet()throws Exception{long end=System.currentTimeMillis()+2000;while(active()!=null&&System.currentTimeMillis()<end)Thread.sleep(1);check(active()==null,"active ownership released");}
|
|
static void resetUnknown()throws Exception{Field f=TouchGestures.class.getDeclaredField("ACTIVE");f.setAccessible(true);((AtomicReference<?>)f.get(null)).set(null);}
|
|
static JSONObject perform(AccessibilityService s,JSONObject a){return TouchGestures.perform(s,a);}
|
|
public static void main(String[] args)throws Exception {
|
|
try{
|
|
scenario="normal synchronized continuation";AccessibilityService s=new AccessibilityService();JSONObject r=perform(s,spec("normal",30,4,2));code(r,"OK","completed");eq(r.get("completed_segments"),3,"completed");eq(r.get("dispatched_segments"),3,"dispatched");eq(r.get("fingers"),2,"fingers");check(s.held==null,"normal final release");waitQuiet();
|
|
scenario="duplicate callbacks";s=new AccessibilityService();s.duplicate=true;r=perform(s,spec("duplicate",20,3,1));code(r,"OK","completed");eq(s.calls,2,"duplicate callback no extra dispatch");eq(r.get("completed_segments"),2,"duplicate callback no extra completion");waitQuiet();
|
|
scenario="Android early stationary callback preserves hold";s=new AccessibilityService();s.earlyStationary=true;JSONObject hold=spec("hold-timing",250,4,1);hold.optJSONArray("frames").optJSONObject(1).put("points",new JSONArray().put(new JSONArray().put(100).put(200)));long holdStarted=SystemClock.uptimeMillis();r=perform(s,hold);code(r,"OK","completed");check(SystemClock.uptimeMillis()-holdStarted>=750,"hold is not compressed by callback");eq(s.calls,3,"no extra gesture during hold");check(s.held==null,"hold final release");waitQuiet();
|
|
scenario="invalid frame validation";s=new AccessibilityService();JSONObject invalid=spec("invalid",20,2,1);invalid.put("frames",new JSONArray().put(new JSONObject().put("at_ms",0).put("points",new JSONArray().put(new JSONArray().put(0).put(0)))).put(new JSONObject().put("at_ms",0).put("points",new JSONArray().put(new JSONArray().put(1).put(1)))));r=perform(s,invalid);code(r,"INVALID_ARGUMENT","not_started");eq(s.calls,0,"invalid untouched");
|
|
scenario="display-size mismatch";DisplayUtils.width=800;r=perform(s,spec("size",20,2,1));code(r,"DISPLAY_CHANGED","not_started");eq(s.calls,0,"size untouched");DisplayUtils.width=1080;
|
|
scenario="unsupported Android API";Build.VERSION.SDK_INT=24;r=perform(s,spec("api",20,2,1));code(r,"UNSUPPORTED","not_started");Build.VERSION.SDK_INT=36;
|
|
scenario="ambiguous visible guard";NativeControls.result=new JSONObject().put("success",false).put("code","AMBIGUOUS");JSONObject guarded=spec("guard",20,2,1).put("require_visible","fixture:id/touch");r=perform(s,guarded);code(r,"AMBIGUOUS","not_started");eq(s.calls,0,"guard untouched");waitQuiet();
|
|
scenario="visible guard acceptance";NativeControls.result=new JSONObject().put("success",true).put("node",new JSONObject().put("visible",true).put("enabled",true));r=perform(s,spec("guarded-ok",20,2,1).put("require_visible","fixture:id/touch"));code(r,"OK","completed");waitQuiet();
|
|
scenario="busy ownership and mid-segment cancellation";final AccessibilityService moving=new AccessibilityService();Future<JSONObject> pending=workers.submit(()->perform(moving,spec("cancel-mid",150,4,2)));waitCalls(moving,1);AccessibilityService other=new AccessibilityService();code(perform(other,spec("busy",20,2,1)),"BUSY","not_started");eq(other.calls,0,"busy untouched");code(cancel("wrong-id"),"CANCEL_RECORDED","observed");code(cancel("cancel-mid"),"CANCEL_REQUESTED","unknown");r=pending.get(2,TimeUnit.SECONDS);code(r,"CANCELLED","cancelled");eq(r.get("completed_segments"),1,"current interval completed");eq(r.get("dispatched_segments"),1,"later movement skipped");eq(moving.calls,2,"one endpoint release");check(moving.held==null,"cancel final release");waitQuiet();
|
|
scenario="queued cancellation before input";CountDownLatch block=new CountDownLatch(1),entered=new CountDownLatch(1);new Handler(Looper.getMainLooper()).post(()->{entered.countDown();try{block.await();}catch(Exception e){throw new RuntimeException(e);}});entered.await();final AccessibilityService queued=new AccessibilityService();pending=workers.submit(()->perform(queued,spec("queued",30,2,1)));long e=System.currentTimeMillis()+1000;while(active()==null&&System.currentTimeMillis()<e)Thread.sleep(1);cancel("queued");block.countDown();r=pending.get(2,TimeUnit.SECONDS);code(r,"CANCELLED","not_started");eq(queued.calls,0,"queued cancellation no input");waitQuiet();
|
|
scenario="first dispatch rejection";s=new AccessibilityService();s.rejectOn=1;r=perform(s,spec("rejected",20,2,1));code(r,"DISPATCH_REJECTED","not_started");eq(r.get("dispatched_segments"),0,"rejected receipt");waitQuiet();
|
|
scenario="Android cancellation callback";s=new AccessibilityService();s.cancelOn=1;r=perform(s,spec("os-cancel",20,3,1));code(r,"GESTURE_CANCELLED","cancelled");eq(r.get("completed_segments"),0,"OS cancelled incomplete");waitQuiet();
|
|
scenario="continuation rejection and endpoint release";s=new AccessibilityService();s.rejectOn=2;r=perform(s,spec("reject-continuation",20,3,2));code(r,"DISPATCH_REJECTED","cancelled");eq(s.calls,3,"rejection release once");eq(r.get("completed_segments"),1,"rejected no fake completion");check(s.held==null,"rejection release confirmed");waitQuiet();
|
|
scenario="continuation exception and endpoint release";s=new AccessibilityService();s.throwOn=2;r=perform(s,spec("throw-continuation",20,3,2));code(r,"DISPATCH_ERROR","unknown");eq(s.calls,3,"throw release once");check(s.held==null,"throw release confirmed");waitQuiet();
|
|
scenario="late callback after dispatch exception";s=new AccessibilityService();s.throwAndCompleteOn=1;r=perform(s,spec("late-callback",60,3,1));code(r,"DISPATCH_ERROR","unknown");check(active()!=null,"unknown keeps active while in flight");code(perform(new AccessibilityService(),spec("still-busy",20,2,1)),"BUSY","not_started");waitQuiet();check(s.held==null,"late callback release confirmed");eq(s.calls,2,"no movement retry after throw");
|
|
scenario="failed release retains ownership";final AccessibilityService releaseReject=new AccessibilityService();releaseReject.rejectOn=2;pending=workers.submit(()->perform(releaseReject,spec("release-reject",80,3,1)));waitCalls(releaseReject,1);cancel("release-reject");r=pending.get(2,TimeUnit.SECONDS);code(r,"RELEASE_REJECTED","unknown");check(active()!=null,"release failure retains active");code(perform(new AccessibilityService(),spec("blocked",20,2,1)),"BUSY","not_started");eq(releaseReject.calls,2,"release no retry");resetUnknown();
|
|
scenario="missing callback bounded timeout";s=new AccessibilityService();s.dropOn=1;long before=SystemClock.uptimeMillis();r=perform(s,spec("timeout",15,2,1));code(r,"TIMEOUT","unknown");long elapsed=SystemClock.uptimeMillis()-before;check(elapsed<5200,"bounded timeout "+elapsed);check(active()!=null,"callback timeout retains active");eq(s.calls,1,"callback timeout no retry");resetUnknown();
|
|
int baseline=checks;if(baseline<85)throw new AssertionError("Existing baseline checks were lost: "+baseline);
|
|
OwnershipCases.run();
|
|
System.out.println("BASELINE "+baseline+" existing checks; "+(checks-baseline)+" additional ownership checks");
|
|
System.out.println("PASS "+checks+" deterministic checks");
|
|
}finally{workers.shutdownNow();Handler.EXEC.shutdownNow();}
|
|
}
|
|
}
|
|
""",
|
|
"org/json/JSONArray.java": r"""
|
|
package org.json;import java.util.*;public class JSONArray {private final List<Object> v=new ArrayList<>();public JSONArray put(Object x){v.add(x);return this;}public int length(){return v.size();}public Object get(int i)throws Exception{return v.get(i);}public JSONObject optJSONObject(int i){Object x=v.get(i);return x instanceof JSONObject?(JSONObject)x:null;}public JSONArray optJSONArray(int i){Object x=v.get(i);return x instanceof JSONArray?(JSONArray)x:null;}public String toString(){return v.toString();}}
|
|
""",
|
|
"org/json/JSONObject.java": r"""
|
|
package org.json;import java.util.*;
|
|
public class JSONObject {
|
|
public static final Object NULL=new Object(){public String toString(){return "null";}};private final Map<String,Object> m=new LinkedHashMap<>();
|
|
public JSONObject put(String k,Object v)throws Exception{m.put(k,v==null?NULL:v);return this;}public boolean has(String k){return m.containsKey(k);}public Object opt(String k){return m.get(k);}public Object get(String k)throws Exception{if(!m.containsKey(k))throw new Exception(k);return m.get(k);}
|
|
public JSONObject optJSONObject(String k){Object v=m.get(k);return v instanceof JSONObject?(JSONObject)v:null;}public JSONArray optJSONArray(String k){Object v=m.get(k);return v instanceof JSONArray?(JSONArray)v:null;}
|
|
public boolean optBoolean(String k){return optBoolean(k,false);}public boolean optBoolean(String k,boolean d){Object v=m.get(k);return v instanceof Boolean?(Boolean)v:d;}
|
|
public String optString(String k,String d){Object v=m.get(k);return v instanceof String?(String)v:d;}public String toString(){return m.toString();}
|
|
}
|
|
""",
|
|
}
|
|
|
|
|
|
def find_tool(name: str) -> str:
|
|
java_home = os.environ.get("JAVA_HOME")
|
|
if java_home:
|
|
for suffix in (".exe", ""):
|
|
candidate = Path(java_home) / "bin" / f"{name}{suffix}"
|
|
if candidate.is_file():
|
|
return str(candidate)
|
|
resolved = shutil.which(name)
|
|
if resolved:
|
|
return resolved
|
|
raise RuntimeError(f"{name} was not found; set JAVA_HOME to a JDK or add it to PATH")
|
|
|
|
|
|
def run_command(arguments: list[str], timeout: int) -> subprocess.CompletedProcess[str]:
|
|
result = subprocess.run(
|
|
arguments,
|
|
capture_output=True,
|
|
text=True,
|
|
encoding="utf-8",
|
|
errors="replace",
|
|
timeout=timeout,
|
|
check=False,
|
|
)
|
|
if result.stdout:
|
|
print(result.stdout, end="" if result.stdout.endswith("\n") else "\n")
|
|
if result.stderr:
|
|
print(result.stderr, end="" if result.stderr.endswith("\n") else "\n", file=sys.stderr)
|
|
if result.returncode:
|
|
raise RuntimeError(f"{Path(arguments[0]).name} exited with status {result.returncode}")
|
|
return result
|
|
|
|
|
|
def main() -> int:
|
|
started = perf_counter()
|
|
try:
|
|
javac = find_tool("javac")
|
|
java = find_tool("java")
|
|
repository = Path(__file__).resolve().parents[2]
|
|
actual_source = (
|
|
repository
|
|
/ "packages"
|
|
/ "artemis-accessibility-helper"
|
|
/ "app/src/main/java/com/artemis/helper/TouchGestures.java"
|
|
)
|
|
if not actual_source.is_file():
|
|
raise RuntimeError("The repository TouchGestures.java source is missing")
|
|
legacy_source = actual_source.with_name("GestureController.java")
|
|
ownership_cases = Path(__file__).with_name("OwnershipCases.java")
|
|
if not legacy_source.is_file() or not ownership_cases.is_file():
|
|
raise RuntimeError(
|
|
"The real GestureController.java or ownership regression cases are missing"
|
|
)
|
|
print(
|
|
"JVM simulation with fake Android callbacks; no device input is exercised.", flush=True
|
|
)
|
|
with TemporaryDirectory(prefix="artemis-touch-jvm-") as temporary:
|
|
workspace = Path(temporary)
|
|
sources = workspace / "src"
|
|
classes = workspace / "classes"
|
|
classes.mkdir()
|
|
generated: list[str] = []
|
|
for name, source in STUBS.items():
|
|
destination = sources / name
|
|
destination.parent.mkdir(parents=True, exist_ok=True)
|
|
destination.write_text(source.lstrip("\n"), encoding="utf-8")
|
|
generated.append(str(destination))
|
|
run_command(
|
|
[
|
|
javac,
|
|
"-J-Duser.language=en",
|
|
"-encoding",
|
|
"UTF-8",
|
|
"-source",
|
|
"8",
|
|
"-target",
|
|
"8",
|
|
"-Xlint:-options",
|
|
"-d",
|
|
str(classes),
|
|
*generated,
|
|
str(actual_source),
|
|
str(legacy_source),
|
|
str(ownership_cases),
|
|
],
|
|
timeout=45,
|
|
)
|
|
result = run_command(
|
|
[java, "-Duser.language=en", "-cp", str(classes), "com.artemis.helper.TestMain"],
|
|
timeout=45,
|
|
)
|
|
if not re.search(
|
|
r"^PASS [1-9]\d* deterministic checks$", result.stdout, flags=re.MULTILINE
|
|
):
|
|
raise RuntimeError("The JVM process returned without a deterministic check count")
|
|
print(f"Elapsed {perf_counter() - started:.2f}s (compile + JVM simulation)")
|
|
return 0
|
|
except (OSError, RuntimeError, subprocess.TimeoutExpired) as error:
|
|
print(f"FAIL: {error}", file=sys.stderr)
|
|
return 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|