; Begin integrating facilities for executing ERT tests on Android
* test/infra/android/README: * test/infra/android/bin/AtsStub.java (AtsStub): * test/infra/android/bin/README: * test/infra/android/test-controller.el (tramp) (ats-adb-executable, ats-adb-host, ats-adb-infile, ats-cache) (ats-adb-disable-stderr, ats-adb-device-regexp, ats-adb) (ats-adb-process-filter, ats-start-adb, ats-enumerate-devices) (ats-online-devices, ats-memoize, ats-ps-device, ats-getprop) (ats-get-sdk-version, ats-package-list-regexp) (ats-is-package-debuggable, ats-list-users, ats-get-package-aid) (ats-aid-user-offset, ats-aid-isolated-start, ats-aid-app-start) (ats-aid-to-uid, ats-uid-to-username, ats-verify-directory) (ats-get-package-data-directory) (ats-get-user-external-storage-directory, ats-transfer-padding) (ats-exec-script, ats-exec-script-checked) (ats-use-private-staging-directory, ats-get-staging-directory) (ats-base64-available, ats-echo-n-e, ats-echo-c, ats-octab, c) (ats-upload-encode-binary, ats-upload, ats-download) (ats-create-empty-temporary, ats-run-jar) (ats-supports-am-force-stop, ats-supports-am-force-stop-user) (ats-kill-process-by-username-and-name) (ats-portforward-local-type-regexp) (ats-portforward-remote-type-regexp, ats-portforward-list-regexp) (ats-portreverse-type-regexp, ats-portreverse-list-regexp) (ats-reverse-list, ats-reverse-tcp, ats-forward-list) (ats-forward-tcp, ats-is-tail-available, ats-java-int-min) (ats-java-int-max, ats-java-long-min, ats-java-long-max) (ats-intent-array-type, ats-fmt-array-element, ats-build-intent) (ats-working-stub-file, ats-file-directory, ats-am-start-intent) (ats-create-commfile, ats-watch-commfile, ats-server) (ats-default-port, ats-accepting-connection) (ats-address-to-hostname, ats-is-localhost-p) (ats-server-sentinel, ats-server-log, ats-server-exists-p) (ats-start-server, ats-await-connection-timeout) (ats-await-connection, ats-forward-server-sentinel) (ats-forward-server-filter, ats-reverse-server) (ats-forward-server, ats-cancel-forward-server, ats-remote-port) (ats-in-connection-context, ats-outstanding-reverse-connection) (ats-terminate-reverse-safely, ats-disconnect-internal) (ats-read-connection, ats-disconnect, ats-establish-connection) (ats-connect, ats-eval, test-controller): * test/infra/android/test-driver.el (ats-process) (ats-connection-established, ats-header, ats-in-eval) (ats-eval-as-printed, ats-eval-serial, ats-process-filter) (ats-display-status-buffer, ats-establish-connection) (ats-driver-log, ats-initiate-connection, test-driver): New files.
This commit is contained in:
parent
ae5674c758
commit
08077788db
5 changed files with 2554 additions and 0 deletions
26
test/infra/android/README
Normal file
26
test/infra/android/README
Normal file
|
@ -0,0 +1,26 @@
|
|||
Copyright (C) 2025 Free Software Foundation, Inc. -*- coding: utf-8 -*-
|
||||
See the end of the file for license conditions.
|
||||
|
||||
In this directory is a collection of scripts which arrange to upload
|
||||
tests (or rather arbitrary Lisp forms) from an Emacs source repository
|
||||
into an Android device, execute them "in vivo", so to speak, and
|
||||
retrieve the results of their execution. While there is an automatic
|
||||
testing system built around another version of these scripts, the
|
||||
versions in the Emacs repository are engineered for interactive
|
||||
execution only.
|
||||
|
||||
|
||||
This file is part of GNU Emacs.
|
||||
|
||||
GNU Emacs is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
GNU Emacs is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
|
332
test/infra/android/bin/AtsStub.java
Normal file
332
test/infra/android/bin/AtsStub.java
Normal file
|
@ -0,0 +1,332 @@
|
|||
/* Launch an intent stated on the command line as an activity. -*- c-file-style: "GNU" -*-
|
||||
|
||||
Copyright (C) 2025 Free Software Foundation, Inc.
|
||||
|
||||
This file is part of GNU Emacs.
|
||||
|
||||
GNU Emacs is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or (at
|
||||
your option) any later version.
|
||||
|
||||
GNU Emacs is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
|
||||
|
||||
package ats;
|
||||
|
||||
import android.app.ActivityManagerNative;
|
||||
import android.app.ActivityThread;
|
||||
import android.app.IActivityManager;
|
||||
import android.app.IApplicationThread;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.RemoteException;
|
||||
|
||||
import android.net.Uri;
|
||||
|
||||
import java.lang.IllegalArgumentException;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
public final class AtsStub
|
||||
{
|
||||
public static final String IDENT = "$Id: AtsStub.java,v 1.4 2024/06/30 04:24:39 jw Exp $";
|
||||
|
||||
private static void
|
||||
neutralizeApplicationThread (ActivityThread thread)
|
||||
{
|
||||
Field field;
|
||||
|
||||
try
|
||||
{
|
||||
field = ActivityThread.class.getDeclaredField ("mAppThread");
|
||||
field.setAccessible (true);
|
||||
field.set (thread, null);
|
||||
}
|
||||
catch (NoSuchFieldException x)
|
||||
{
|
||||
x.printStackTrace ();
|
||||
}
|
||||
catch (IllegalAccessException x)
|
||||
{
|
||||
x.printStackTrace ();
|
||||
}
|
||||
}
|
||||
|
||||
private static int
|
||||
main1 (String[] argv)
|
||||
throws NoSuchMethodException, IllegalAccessException,
|
||||
InvocationTargetException
|
||||
{
|
||||
ActivityThread thread;
|
||||
Context context;
|
||||
|
||||
Looper.prepare ();
|
||||
|
||||
thread = ActivityThread.systemMain ();
|
||||
context = thread.getSystemContext ();
|
||||
if (argv.length < 1 || argv[0].equals ("--help"))
|
||||
{
|
||||
System.out.println ("AtsStub [start] [--user <USER_ID>] <INTENT>");
|
||||
System.out.println (" where INTENT is a series of arguments defining an Intent,");
|
||||
System.out.println (" namely,");
|
||||
System.out.println (" -a <ACTION>");
|
||||
System.out.println (" -d <DATA URI>");
|
||||
System.out.println (" -t <TYPE>");
|
||||
System.out.println (" -c <CATEGORY>");
|
||||
System.out.println (" -n <COMPONENT>");
|
||||
System.out.println (" -e or --es <KEY> <STRING VALUE>");
|
||||
System.out.println (" --esn <KEY>");
|
||||
System.out.println (" --ei <KEY> <INTEGER VALUE>");
|
||||
System.out.println (" --eu <KEY> <URI VALUE>");
|
||||
System.out.println (" --ecn <KEY> <COMPONENT NAME>");
|
||||
System.out.println (" --eia <KEY> <INTEGER>, ...");
|
||||
System.out.println (" --el <KEY> <LONG>");
|
||||
System.out.println (" --ela <KEY> <LONG>, ...");
|
||||
System.out.println (" --ef <KEY> <FLOAT>");
|
||||
System.out.println (" --efa <KEY> <FLOAT ARRAY>");
|
||||
System.out.println (" --esa <KEY> <STRING>, ...");
|
||||
System.out.println (" --ez <KEY> <BOOLEAN>");
|
||||
System.out.println (" -f <KEY> <FLAGS>");
|
||||
return 0;
|
||||
}
|
||||
else if (argv[0].equals ("start"))
|
||||
{
|
||||
Intent intent;
|
||||
int i, userID = 0;
|
||||
String token, type;
|
||||
Uri data;
|
||||
boolean debug;
|
||||
|
||||
intent = new Intent ();
|
||||
debug = false;
|
||||
data = null;
|
||||
type = null;
|
||||
|
||||
for (i = 1; i < argv.length; ++i)
|
||||
{
|
||||
int j;
|
||||
|
||||
token = argv[i];
|
||||
|
||||
if (token.equals ("-a"))
|
||||
intent.setAction (argv[++i]);
|
||||
else if (token.equals ("-d"))
|
||||
data = Uri.parse (argv[++i]);
|
||||
else if (token.equals ("-t"))
|
||||
type = argv[++i];
|
||||
else if (token.equals ("-c"))
|
||||
intent.addCategory (argv[++i]);
|
||||
else if (token.equals ("-e") || token.equals ("--es"))
|
||||
{
|
||||
intent.putExtra (argv[i + 1], argv[i + 2]);
|
||||
i += 2;
|
||||
}
|
||||
else if (token.equals ("--esn"))
|
||||
intent.putExtra (argv[++i], (String) null);
|
||||
else if (token.equals ("--ei"))
|
||||
{
|
||||
int value = Integer.valueOf (argv[i + 2]);
|
||||
intent.putExtra (argv[i + 1], value);
|
||||
i += 2;
|
||||
}
|
||||
else if (token.equals ("--eu"))
|
||||
{
|
||||
Uri value = Uri.parse (argv[i + 2]);
|
||||
intent.putExtra (argv[i + 1], value);
|
||||
i += 2;
|
||||
}
|
||||
else if (token.equals ("--ecn"))
|
||||
{
|
||||
ComponentName value
|
||||
= ComponentName.unflattenFromString (argv[i + 2]);
|
||||
intent.putExtra (argv[i + 1], value);
|
||||
i += 2;
|
||||
}
|
||||
else if (token.equals ("--eia"))
|
||||
{
|
||||
String values[] = argv[i + 2].split (",");
|
||||
int array[] = new int[values.length];
|
||||
|
||||
for (j = 0; j < values.length; ++j)
|
||||
array[j] = Integer.valueOf (values[j]);
|
||||
intent.putExtra (argv[i + 1], array);
|
||||
i += 2;
|
||||
}
|
||||
else if (token.equals ("--el"))
|
||||
{
|
||||
long value = Long.valueOf (argv[i + 2]);
|
||||
intent.putExtra (argv[i + 1], value);
|
||||
i += 2;
|
||||
}
|
||||
else if (token.equals ("--ela"))
|
||||
{
|
||||
String values[] = argv[i + 2].split (",");
|
||||
long array[] = new long[values.length];
|
||||
|
||||
for (j = 0; j < values.length; ++j)
|
||||
array[j] = Long.valueOf (values[j]);
|
||||
intent.putExtra (argv[i + 1], array);
|
||||
i += 2;
|
||||
}
|
||||
else if (token.equals ("--ef"))
|
||||
{
|
||||
float value = Float.valueOf (argv[i + 2]);
|
||||
intent.putExtra (argv[i + 1], value);
|
||||
i += 2;
|
||||
}
|
||||
else if (token.equals ("--efa"))
|
||||
{
|
||||
String values[] = argv[i + 2].split (",");
|
||||
float array[] = new float[values.length];
|
||||
|
||||
for (j = 0; j < values.length; ++j)
|
||||
array[j] = Float.valueOf (values[j]);
|
||||
intent.putExtra (argv[i + 1], array);
|
||||
i += 2;
|
||||
}
|
||||
else if (token.equals ("--esa"))
|
||||
{
|
||||
String[] strings;
|
||||
|
||||
strings = argv[i + 2].split ("(?<!\\\\),");
|
||||
intent.putExtra (argv[i + 1], strings);
|
||||
i += 2;
|
||||
}
|
||||
else if (token.equals ("--ez"))
|
||||
{
|
||||
boolean value = Boolean.valueOf (argv[i + 2]);
|
||||
intent.putExtra (argv[i + 1], value);
|
||||
i += 2;
|
||||
}
|
||||
else if (token.equals ("-n"))
|
||||
{
|
||||
ComponentName value
|
||||
= ComponentName.unflattenFromString (argv[++i]);
|
||||
if (value == null)
|
||||
throw new IllegalArgumentException ("Invalid component name: " + argv[i]);
|
||||
intent.setComponent (value);
|
||||
}
|
||||
else if (token.equals ("-f"))
|
||||
intent.addFlags (Integer.decode (argv[++i]).intValue ());
|
||||
else if (token.equals ("--user"))
|
||||
{
|
||||
int value = Integer.valueOf (argv[++i]);
|
||||
if (value != 0
|
||||
&& (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1))
|
||||
throw new IllegalArgumentException ("Invalid user: " + value);
|
||||
userID = value;
|
||||
}
|
||||
else
|
||||
throw new IllegalArgumentException ("Option not understood: " + argv[i]);
|
||||
}
|
||||
|
||||
intent.addFlags (Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
intent.setDataAndType (data, type);
|
||||
|
||||
if ((Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1)
|
||||
|| userID == 0)
|
||||
{
|
||||
/* mAppThread must be neutralized, or the ActivityManager
|
||||
service will attempt and fail to locate a matching app
|
||||
record when it is passed as the caller argument to the
|
||||
startActivity RPC routine. */
|
||||
neutralizeApplicationThread (thread);
|
||||
context.startActivity (intent);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Otherwise, there are two revisions of startActivityAsUser
|
||||
this utility must support, whose signatures follow:
|
||||
|
||||
(IApplicationThread, Intent, String, IBinder, String,
|
||||
int, int, String, ParcelFileDescriptor, Bundle, int)
|
||||
|
||||
(IApplicationThread, String, Intent, String, IBinder, String,
|
||||
int, int, String, ParcelFileDescriptor, Bundle, int) */
|
||||
|
||||
Method method;
|
||||
IActivityManager am = ActivityManagerNative.getDefault ();
|
||||
int rc;
|
||||
Class klass = IActivityManager.class;
|
||||
|
||||
/* Attempt to resolve the first variant which is mostly
|
||||
observed on Jelly Bean MR1 systems. */
|
||||
try
|
||||
{
|
||||
method = klass.getMethod ("startActivityAsUser",
|
||||
IApplicationThread.class,
|
||||
Intent.class, String.class,
|
||||
IBinder.class, String.class,
|
||||
int.class, int.class,
|
||||
String.class,
|
||||
ParcelFileDescriptor.class,
|
||||
Bundle.class, int.class);
|
||||
}
|
||||
catch (NoSuchMethodException e)
|
||||
{
|
||||
method = null;
|
||||
}
|
||||
|
||||
if (method != null)
|
||||
rc = (Integer) method.invoke (am, null, intent, intent.getType (),
|
||||
null, null, 0, 0, null, null, null,
|
||||
userID);
|
||||
else
|
||||
{
|
||||
/* Now the modern `IActivityManager#startActivityAsUser'. */
|
||||
method = klass.getMethod ("startActivityAsUser",
|
||||
IApplicationThread.class,
|
||||
String.class, Intent.class,
|
||||
String.class, IBinder.class,
|
||||
String.class, int.class,
|
||||
int.class, String.class,
|
||||
ParcelFileDescriptor.class,
|
||||
Bundle.class, int.class);
|
||||
|
||||
rc = (Integer) method.invoke (am, null, null, intent,
|
||||
intent.getType (),
|
||||
null, null, 0, 0, null,
|
||||
null, null, userID);
|
||||
}
|
||||
|
||||
if (rc != 0)
|
||||
{
|
||||
System.err.println ("Failed to start activity as user: " + rc);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
public static void
|
||||
main (String arg[])
|
||||
{
|
||||
try
|
||||
{
|
||||
System.exit (main1 (arg));
|
||||
}
|
||||
catch (Throwable e)
|
||||
{
|
||||
e.printStackTrace ();
|
||||
System.exit (0);
|
||||
}
|
||||
}
|
||||
};
|
50
test/infra/android/bin/README
Normal file
50
test/infra/android/bin/README
Normal file
|
@ -0,0 +1,50 @@
|
|||
Copyright (C) 2025 Free Software Foundation, Inc. -*- coding: utf-8 -*-
|
||||
See the end of the file for license conditions.
|
||||
|
||||
This directory holds a minuscule stub executable that invokes the
|
||||
appropriate Java APIs to execute an activity with string array
|
||||
parameters. It is not necessary on systems that have received AOSP
|
||||
commit:
|
||||
|
||||
https://android.googlesource.com/platform/frameworks/base/+/28600e2%5E%21/
|
||||
|
||||
which is to say, Android 5.0 (API 21) or newer.
|
||||
|
||||
|
||||
------------------------------------------------------------------------
|
||||
In consequence of this stub's liberal usage of private APIs, you must
|
||||
compile this executable yourself, and link it against a copy of the
|
||||
Android framework libraries extracted from a real installation of
|
||||
Android 4.4.x and processed through `dex2jar':
|
||||
|
||||
https://github.com/pxb1988/dex2jar
|
||||
|
||||
The resultant class file, once further compiled into Dex bytecode with
|
||||
`d8' and archived, must be renamed "stub.zip" and placed in this
|
||||
directory; for example:
|
||||
|
||||
$ adb pull /system/framework/framework.jar
|
||||
/system/framework/framework.jar: 1 file pulled, 0 skipped. 1.6 MB/s (3818859 bytes in 2.342s)
|
||||
$ adb pull /system/framework/core.jar
|
||||
/system/framework/core.jar: 1 file pulled, 0 skipped. 1.6 MB/s (1237856 bytes in 0.743s)
|
||||
$ dex2jar {core,framework}.jar
|
||||
dex2jar core.jar -> ./core-dex2jar.jar
|
||||
dex2jar framework.jar -> ./framework-dex2jar.jar
|
||||
$ javac AtsStub.java -source 1.6 -classpath "core-dex2jar.jar:framework-dex2jar.jar"
|
||||
$ d8 AtsStub.class && zip stub.zip classes.dex
|
||||
|
||||
|
||||
This file is part of GNU Emacs.
|
||||
|
||||
GNU Emacs is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
GNU Emacs is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
|
1936
test/infra/android/test-controller.el
Normal file
1936
test/infra/android/test-controller.el
Normal file
File diff suppressed because it is too large
Load diff
210
test/infra/android/test-driver.el
Normal file
210
test/infra/android/test-driver.el
Normal file
|
@ -0,0 +1,210 @@
|
|||
;;; Receive and execute Lisp code submitted by a test controller. -*- lexical-binding: t; -*-
|
||||
;;; $Id: ats-driver.el,v 1.6 2025/02/19 01:56:55 jw Exp $
|
||||
|
||||
;; Copyright (C) 2025 Free Software Foundation, Inc.
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; GNU Emacs is free software; you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
|
||||
;; GNU Emacs is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
;;; Commentary:
|
||||
;;
|
||||
;; This file establishes a connection to a controlling device, executes
|
||||
;; Lisp expressions received from the same, and responds with any
|
||||
;; results available.
|
||||
;;
|
||||
;; There were anciently many more facilities in this file but they are
|
||||
;; in the process of being moved to `test-controller.el' (now in Lisp).
|
||||
|
||||
;;; Code:
|
||||
|
||||
|
||||
|
||||
;; Connection establishment and management.
|
||||
|
||||
(defvar ats-process nil
|
||||
"Connection to the test controller.")
|
||||
|
||||
(defvar ats-connection-established nil
|
||||
"Whether `ats-process' has been initialized.")
|
||||
|
||||
(defface ats-header '((t :height 1.3 :weight bold
|
||||
:inherit variable-pitch))
|
||||
"Face of ATS header elements.")
|
||||
|
||||
(defvar-local ats-in-eval nil
|
||||
"Whether an `-eval' command is being processed and form's size.")
|
||||
|
||||
(defvar-local ats-eval-as-printed nil
|
||||
"Whether to return the values of the submitted form as a string.")
|
||||
|
||||
(defvar-local ats-eval-serial nil
|
||||
"Serial number identifying this result.")
|
||||
|
||||
(defun ats-process-filter (process string)
|
||||
"Filter input from `ats-process'.
|
||||
Insert STRING into the connection buffer, till a full command is
|
||||
read."
|
||||
(when (buffer-live-p (process-buffer process))
|
||||
(with-current-buffer (process-buffer process)
|
||||
(let ((marker (process-mark process)))
|
||||
(unless (marker-position marker)
|
||||
(set-marker marker (point)))
|
||||
(save-excursion
|
||||
(goto-char marker)
|
||||
(insert string)
|
||||
(set-marker marker (point))))
|
||||
(let ((firstchar (char-after (point-min)))
|
||||
(inhibit-quit nil)
|
||||
(in-eval ats-in-eval))
|
||||
(while (or (eq firstchar ?-) in-eval)
|
||||
(unless ats-in-eval
|
||||
(when (eq firstchar ?-)
|
||||
;; A command is being delivered. Search for a newline.
|
||||
(save-excursion
|
||||
(when-let* ((newline (search-forward "\n" nil t))
|
||||
(command (buffer-substring
|
||||
(point-min) (1- newline))))
|
||||
(delete-region (point-min) newline)
|
||||
(cond
|
||||
((equal command "-ok")
|
||||
(setq ats-connection-established t)
|
||||
(ats-display-status-buffer))
|
||||
((equal command "-not-accepting-connections")
|
||||
(error
|
||||
"The server is not accepting connections"))
|
||||
((string-match
|
||||
"^-incorrect-uuid \\([[:alnum:]-]\\) \\([[:alnum:]-]\\)$"
|
||||
command)
|
||||
(error "Connection rejected; wanted ID=%s, received ID=%s"
|
||||
(match-string 2 command) (match-string 1 command)))
|
||||
((string-match
|
||||
"^-eval \\([[:digit:]]+\\) \\([[:digit:]]+\\) \\(t\\|nil\\)$"
|
||||
command)
|
||||
(setq ats-eval-serial (string-to-number
|
||||
(match-string 1 command))
|
||||
ats-in-eval (string-to-number
|
||||
(match-string 2 command))
|
||||
ats-eval-as-printed (equal
|
||||
(match-string 3 command)
|
||||
"t")))
|
||||
(t (error (concat "Unknown command: " command))))))))
|
||||
(when ats-in-eval
|
||||
;; Proceed till `ats-in-eval' characters are read.
|
||||
(when (>= (- (point-max) (point-min)) ats-in-eval)
|
||||
(let ((value
|
||||
(save-restriction
|
||||
(narrow-to-region (point-min) (1+ ats-in-eval))
|
||||
(condition-case err
|
||||
(let* ((str (buffer-string)))
|
||||
(with-current-buffer "*ATS*"
|
||||
(goto-char (point-max))
|
||||
(let ((inhibit-read-only t))
|
||||
(insert "--> " (truncate-string-to-width
|
||||
str 72)
|
||||
"\n")))
|
||||
(let* ((expr (car (read-from-string str)))
|
||||
(value (eval expr)))
|
||||
(cons 'ok value)))
|
||||
(error (cons 'error err))))))
|
||||
(let* ((print-escape-control-characters t)
|
||||
(print-escape-newlines t)
|
||||
(str (prin1-to-string value)))
|
||||
(if ats-eval-as-printed
|
||||
(let* ((quoted (prin1-to-string str)))
|
||||
(process-send-string
|
||||
process (format "\fats-request:%d %d\n"
|
||||
ats-eval-serial
|
||||
(length quoted)))
|
||||
(process-send-string process quoted))
|
||||
(process-send-string
|
||||
process (format "\fats-request:%d %d\n"
|
||||
ats-eval-serial
|
||||
(length str)))
|
||||
(process-send-string process str)))
|
||||
(process-send-string process "\n"))
|
||||
(delete-region (point-min)
|
||||
(+ (point-min) ats-in-eval))
|
||||
(setq ats-in-eval nil)))
|
||||
;; Don't loop if the form data is yet to arrive.
|
||||
(setq firstchar (char-after (point-min))
|
||||
in-eval nil))))))
|
||||
|
||||
(defun ats-display-status-buffer ()
|
||||
"Replace the splash screen with text announcing connection establishment."
|
||||
(with-current-buffer (get-buffer-create "*ATS*")
|
||||
(setq buffer-read-only nil)
|
||||
(erase-buffer)
|
||||
(insert (propertize "ATS ready\n" 'face 'ats-header))
|
||||
(insert (propertize "
|
||||
If you are reading this message, this instance of Emacs has\
|
||||
successfully established a connection with a controlling\
|
||||
machine and is patiently awaiting instructions.
|
||||
"
|
||||
'face 'variable-pitch))
|
||||
(special-mode)
|
||||
(setq-local truncate-lines nil)
|
||||
(visual-line-mode 1))
|
||||
(pop-to-buffer "*ATS*" '(display-buffer-full-frame)))
|
||||
|
||||
(defun ats-establish-connection (host port id)
|
||||
"Connect to the test controller instance at HOST:PORT.
|
||||
ID is the identifier assigned to this client. Establish a
|
||||
connection to a test controller instance through an address or a
|
||||
Unix domain socket provided as aforesaid. Signal an error upon
|
||||
failure."
|
||||
(message "; Connecting to %s:%d..." host port)
|
||||
(setq ats-process (make-network-process
|
||||
:name (format "*ats connection to %s:%d*" host port)
|
||||
:buffer "*ats connection*"
|
||||
:host host
|
||||
:service port
|
||||
:coding 'utf-8-emacs
|
||||
:filter #'ats-process-filter))
|
||||
(process-send-string ats-process (concat id "\n")))
|
||||
|
||||
(defun ats-driver-log (_ connection _)
|
||||
"Log function for ATS driver processes."
|
||||
(if ats-process
|
||||
(delete-process connection)
|
||||
(setq ats-process connection)
|
||||
(set-process-filter connection #'ats-process-filter)
|
||||
(pop-to-buffer (process-buffer connection))))
|
||||
|
||||
(defun ats-initiate-connection (commfile)
|
||||
"Open a network server locally to which the controller may connect.
|
||||
Write its port number to COMMFILE, and await a connection from
|
||||
the controller."
|
||||
(let* ((process (make-network-process :name " *ats driver*"
|
||||
:server t
|
||||
:host 'local
|
||||
:service t
|
||||
:family 'ipv4
|
||||
:coding 'utf-8-emacs
|
||||
:log #'ats-driver-log))
|
||||
(service (process-contact process :service)))
|
||||
(with-temp-buffer
|
||||
(insert (format "%d\n" service))
|
||||
(write-region (point-min) (point-max) commfile t))
|
||||
(message "; Listening for connection from controller at localhost:%d"
|
||||
service)))
|
||||
|
||||
(provide 'test-driver)
|
||||
|
||||
;;; test-driver.el ends here
|
||||
|
||||
;; Local Variables:
|
||||
;; emacs-lisp-docstring-fill-column: 64
|
||||
;; indent-tabs-mode: t
|
||||
;; End:
|
Loading…
Add table
Reference in a new issue