Add calling process id checking for ScriptExecutor.
Bug: 201332144
Bug: 207175350
Test: atest
ScriptExecutorFunctionalTests:ScriptExecutorFunctionalTest
Test: atest ScriptExecutorNonSystemUserTest:ScriptExecutorNoneSystemUserTest
Test: atest ScriptExecutorUnitTest:JniUtilsTest
Change-Id: I72f0c2e8e08e6bcffa9a0d87ada3553ddc0700cd
Merged-In: I72f0c2e8e08e6bcffa9a0d87ada3553ddc0700cd
(cherry picked from commit cb5cf0a7c5b48ccbd0b1de592b5105747f577fee)
diff --git a/packages/ScriptExecutor/Android.bp b/packages/ScriptExecutor/Android.bp
index 952a9b8..7ada5bf 100644
--- a/packages/ScriptExecutor/Android.bp
+++ b/packages/ScriptExecutor/Android.bp
@@ -88,6 +88,6 @@
"src/**/*.java",
],
- sdk_version: "current",
+ sdk_version: "test_current",
}
diff --git a/packages/ScriptExecutor/src/com/android/car/scriptexecutor/ScriptExecutor.java b/packages/ScriptExecutor/src/com/android/car/scriptexecutor/ScriptExecutor.java
index abd4145..362005e 100644
--- a/packages/ScriptExecutor/src/com/android/car/scriptexecutor/ScriptExecutor.java
+++ b/packages/ScriptExecutor/src/com/android/car/scriptexecutor/ScriptExecutor.java
@@ -18,12 +18,15 @@
import android.app.Service;
import android.content.Intent;
+import android.os.Binder;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
+import android.os.Process;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.util.Log;
import com.android.car.telemetry.scriptexecutorinterface.IScriptExecutor;
@@ -37,7 +40,6 @@
* and input arguments.
*/
public final class ScriptExecutor extends Service {
-
static {
System.loadLibrary("scriptexecutorjni");
}
@@ -53,7 +55,8 @@
@Override
public void invokeScript(String scriptBody, String functionName,
PersistableBundle publishedData, PersistableBundle savedState,
- IScriptExecutorListener listener) {
+ IScriptExecutorListener listener) throws SecurityException {
+ ensureCallerIsSystem();
mNativeHandler.post(() ->
nativeInvokeScript(mLuaEnginePtr, scriptBody, functionName, publishedData,
savedState, listener));
@@ -62,7 +65,8 @@
@Override
public void invokeScriptForLargeInput(String scriptBody, String functionName,
ParcelFileDescriptor publishedDataFileDescriptor, PersistableBundle savedState,
- IScriptExecutorListener listener) {
+ IScriptExecutorListener listener) throws SecurityException {
+ ensureCallerIsSystem();
mNativeHandler.post(() -> {
PersistableBundle publishedData;
try (InputStream input = new ParcelFileDescriptor.AutoCloseInputStream(
@@ -117,6 +121,12 @@
return mScriptExecutorBinder;
}
+ private void ensureCallerIsSystem() throws SecurityException {
+ if (UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID) {
+ throw new SecurityException("ScriptExecutor called from non-system user");
+ }
+ }
+
/**
* Initializes Lua Engine.
*
diff --git a/packages/ScriptExecutor/tests/unit/README.md b/packages/ScriptExecutor/tests/README.md
similarity index 69%
rename from packages/ScriptExecutor/tests/unit/README.md
rename to packages/ScriptExecutor/tests/README.md
index 8c5a67a..fb2e52d 100644
--- a/packages/ScriptExecutor/tests/unit/README.md
+++ b/packages/ScriptExecutor/tests/README.md
@@ -14,7 +14,18 @@
limitations under the License
-->
-# How to run unit tests for ScriptExecutor
+# Test categories
+
+**1. Functional Tests
+
+ScriptExecutor functional tests that are run as system user - which is required by
+ScriptExecutor to invoke script processing.
+
+**2. Non-system-user Tests
+
+These are tests that does not need to be run as system user or tests running as non-system user.
+
+# How to run tests for ScriptExecutor
**1. Navigate to the root of the repo and do full build:**
@@ -26,7 +37,7 @@
**3. Run the tests. For example**
-`atest ScriptExecutorUnitTest:ScriptExecutorTest`
+`atest ScriptExecutorFunctionalTests:ScriptExecutorFunctionalTest`
## How to rerun the tests after changes
@@ -34,7 +45,7 @@
device flash.
**1. Navigate to ScriptExecutor unit test location and build its targets:**
-`cd packages/services/Car/packages/ScriptExecutor/tests/unit`
+`cd packages/services/Car/packages/ScriptExecutor/tests/functional`
`mm -j`
@@ -48,5 +59,5 @@
**3. At this point we are ready to run the tests again. For example:**
-`atest ScriptExecutorUnitTest:ScriptExecutorTest`
+`atest ScriptExecutorFunctionalTests:ScriptExecutorFunctionalTest`
diff --git a/packages/ScriptExecutor/tests/functional/Android.bp b/packages/ScriptExecutor/tests/functional/Android.bp
new file mode 100644
index 0000000..1e0ef38
--- /dev/null
+++ b/packages/ScriptExecutor/tests/functional/Android.bp
@@ -0,0 +1,41 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+ name: "ScriptExecutorFunctionalTests",
+
+ srcs: ["src/**/*.java"],
+
+ platform_apis: true,
+
+ certificate: "platform",
+
+ instrumentation_for: "ScriptExecutor",
+
+ static_libs: [
+ "androidx.test.core",
+ "androidx.test.runner",
+ "junit",
+ "scriptexecutor-test-lib",
+ "truth-prebuilt",
+ ],
+
+ test_suites: ["general-tests"],
+}
diff --git a/packages/ScriptExecutor/tests/functional/AndroidManifest.xml b/packages/ScriptExecutor/tests/functional/AndroidManifest.xml
new file mode 100644
index 0000000..79e85d6
--- /dev/null
+++ b/packages/ScriptExecutor/tests/functional/AndroidManifest.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.car.scriptexecutortest.functional"
+ android:sharedUserId="android.uid.system">
+
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
+
+ <queries>
+ <package android:name="com.android.car.scriptexecutor" />
+ </queries>
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.car.scriptexecutortest.functional"
+ android:label="Tests for ScriptExecutor"/>
+</manifest>
diff --git a/packages/ScriptExecutor/tests/functional/src/com/android/car/scriptexecutortest/functional/ScriptExecutorFunctionalTest.java b/packages/ScriptExecutor/tests/functional/src/com/android/car/scriptexecutortest/functional/ScriptExecutorFunctionalTest.java
new file mode 100644
index 0000000..e9c8557
--- /dev/null
+++ b/packages/ScriptExecutor/tests/functional/src/com/android/car/scriptexecutortest/functional/ScriptExecutorFunctionalTest.java
@@ -0,0 +1,915 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.scriptexecutortest.functional;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.os.PersistableBundle;
+import android.os.RemoteException;
+import android.os.UserHandle;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.car.scriptexecutor.ScriptExecutor;
+import com.android.car.telemetry.scriptexecutorinterface.IScriptExecutor;
+import com.android.car.telemetry.scriptexecutorinterface.IScriptExecutorListener;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.OutputStream;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(JUnit4.class)
+public final class ScriptExecutorFunctionalTest {
+
+ private IScriptExecutor mScriptExecutor;
+ private ScriptExecutor mInstance;
+ private Context mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+
+ private static final class ScriptExecutorListener extends IScriptExecutorListener.Stub {
+ private PersistableBundle mInterimResult;
+ private PersistableBundle mFinalResult;
+ private int mErrorType;
+ private String mMessage;
+ private String mStackTrace;
+ private final CountDownLatch mResponseLatch = new CountDownLatch(1);
+
+ @Override
+ public void onScriptFinished(PersistableBundle result) {
+ mFinalResult = result;
+ mResponseLatch.countDown();
+ }
+
+ @Override
+ public void onSuccess(PersistableBundle interimResult) {
+ mInterimResult = interimResult;
+ mResponseLatch.countDown();
+ }
+
+ @Override
+ public void onError(int errorType, String message, String stackTrace) {
+ mErrorType = errorType;
+ mMessage = message;
+ mStackTrace = stackTrace;
+ mResponseLatch.countDown();
+ }
+
+ private boolean awaitResponse(int waitTimeSec) throws InterruptedException {
+ return mResponseLatch.await(waitTimeSec, TimeUnit.SECONDS);
+ }
+ }
+
+ private final ScriptExecutorListener mListener = new ScriptExecutorListener();
+ private final PersistableBundle mEmptyPublishedData = new PersistableBundle();
+ private final PersistableBundle mEmptyIterimResult = new PersistableBundle();
+ private final CountDownLatch mBindLatch = new CountDownLatch(1);
+
+ private static final int BIND_SERVICE_TIMEOUT_SEC = 5;
+ private static final int SCRIPT_PROCESSING_TIMEOUT_SEC = 10;
+
+ private final ServiceConnection mScriptExecutorConnection =
+ new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName className, IBinder service) {
+ mScriptExecutor = IScriptExecutor.Stub.asInterface(service);
+ mBindLatch.countDown();
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName className) {
+ assertWithMessage("Service unexpectedly disconnected").fail();
+ }
+ };
+
+ @Before
+ public void setUp() throws InterruptedException {
+ Intent intent = new Intent();
+ intent.setComponent(
+ new ComponentName(
+ "com.android.car.scriptexecutor",
+ "com.android.car.scriptexecutor.ScriptExecutor"));
+ mContext.bindServiceAsUser(
+ intent, mScriptExecutorConnection, Context.BIND_AUTO_CREATE, UserHandle.SYSTEM);
+ if (!mBindLatch.await(BIND_SERVICE_TIMEOUT_SEC, TimeUnit.SECONDS)) {
+ assertWithMessage("Failed to bind to ScriptExecutor service").fail();
+ }
+ }
+
+ @Test
+ public void invokeScript_returnsResult() throws RemoteException, InterruptedException {
+ String returnResultScript =
+ "function hello(data, state)\n"
+ + " result = {hello=\"world\"}\n"
+ + " on_success(result)\n"
+ + "end\n";
+
+ runScriptAndWaitForResponse(
+ returnResultScript, "hello", mEmptyPublishedData, mEmptyIterimResult);
+
+ // Expect to get back a bundle with a single string key: string value pair:
+ // {"hello": "world"}
+ assertThat(mListener.mInterimResult.size()).isEqualTo(1);
+ assertThat(mListener.mInterimResult.getString("hello")).isEqualTo("world");
+ }
+
+ @Test
+ public void invokeScript_allSupportedPrimitiveTypes()
+ throws RemoteException, InterruptedException {
+ String script =
+ "function knows(data, state)\n"
+ + " result = {string=\"hello\", boolean=true, integer=1, number=1.1}\n"
+ + " on_success(result)\n"
+ + "end\n";
+
+ runScriptAndWaitForResponse(script, "knows", mEmptyPublishedData, mEmptyIterimResult);
+
+ // Expect to get back a bundle with 4 keys, each corresponding to a distinct supported type.
+ assertThat(mListener.mInterimResult.size()).isEqualTo(4);
+ assertThat(mListener.mInterimResult.getString("string")).isEqualTo("hello");
+ assertThat(mListener.mInterimResult.getBoolean("boolean")).isEqualTo(true);
+ assertThat(mListener.mInterimResult.getLong("integer")).isEqualTo(1);
+ assertThat(mListener.mInterimResult.getDouble("number")).isEqualTo(1.1);
+ }
+
+ @Test
+ public void invokeScript_skipsUnsupportedNestedTables()
+ throws RemoteException, InterruptedException {
+ String script =
+ "function nested(data, state)\n"
+ + " result = {string=\"hello\", boolean=true, integer=1, number=1.1}\n"
+ + " result.nested_table = {x=0, y=0}\n"
+ + " on_success(result)\n"
+ + "end\n";
+
+ runScriptAndWaitForResponse(script, "nested", mEmptyPublishedData, mEmptyIterimResult);
+
+ // Verify that expected error is received.
+ assertThat(mListener.mErrorType)
+ .isEqualTo(IScriptExecutorListener.ERROR_TYPE_LUA_SCRIPT_ERROR);
+ assertThat(mListener.mMessage).contains("nested tables are not supported");
+ }
+
+ @Test
+ public void invokeScript_emptyBundle() throws RemoteException, InterruptedException {
+ String script =
+ "function empty(data, state)\n"
+ + " result = {}\n"
+ + " on_success(result)\n"
+ + "end\n";
+
+ runScriptAndWaitForResponse(script, "empty", mEmptyPublishedData, mEmptyIterimResult);
+
+ // If a script returns empty table as the result, we get an empty bundle.
+ assertThat(mListener.mInterimResult).isNotNull();
+ assertThat(mListener.mInterimResult.size()).isEqualTo(0);
+ }
+
+ @Test
+ public void invokeScript_processPreviousStateAndReturnResult()
+ throws RemoteException, InterruptedException {
+ // Here we verify that the script actually processes provided state from a previous run
+ // and makes calculation based on that and returns the result.
+ String script =
+ "function update(data, state)\n"
+ + " result = {y = state.x+1}\n"
+ + " on_success(result)\n"
+ + "end\n";
+ PersistableBundle previousState = new PersistableBundle();
+ previousState.putInt("x", 1);
+
+ runScriptAndWaitForResponse(script, "update", mEmptyPublishedData, previousState);
+
+ // Verify that y = 2, because y = x + 1 and x = 1.
+ assertThat(mListener.mInterimResult.size()).isEqualTo(1);
+ assertThat(mListener.mInterimResult.getLong("y")).isEqualTo(2);
+ }
+
+ @Test
+ public void invokeScript_allSupportedPrimitiveTypesWorkRoundTripWithKeyNamesPreserved()
+ throws RemoteException, InterruptedException {
+ // Here we verify that all supported primitive types in supplied previous state Bundle
+ // are interpreted by the script as expected.
+ String script =
+ "function update_all(data, state)\n"
+ + " result = {}\n"
+ + " result.integer = state.integer + 1\n"
+ + " result.number = state.number + 0.1\n"
+ + " result.boolean = not state.boolean\n"
+ + " result.string = state.string .. \"CADABRA\"\n"
+ + " on_success(result)\n"
+ + "end\n";
+ PersistableBundle previousState = new PersistableBundle();
+ previousState.putInt("integer", 1);
+ previousState.putDouble("number", 0.1);
+ previousState.putBoolean("boolean", false);
+ previousState.putString("string", "ABRA");
+
+ runScriptAndWaitForResponse(script, "update_all", mEmptyPublishedData, previousState);
+
+ // Verify that keys are preserved but the values are modified as expected.
+ assertThat(mListener.mInterimResult.size()).isEqualTo(4);
+ assertThat(mListener.mInterimResult.getLong("integer")).isEqualTo(2);
+ assertThat(mListener.mInterimResult.getDouble("number")).isEqualTo(0.2);
+ assertThat(mListener.mInterimResult.getBoolean("boolean")).isEqualTo(true);
+ assertThat(mListener.mInterimResult.getString("string")).isEqualTo("ABRACADABRA");
+ }
+
+ @Test
+ public void invokeScript_allSupportedArrayTypesWorkRoundTripWithKeyNamesPreserved()
+ throws RemoteException, InterruptedException {
+ // Here we verify that all supported array types in supplied previous state Bundle are
+ // interpreted by the script as expected.
+ String script =
+ "function arrays(data, state)\n"
+ + " result = {}\n"
+ + " result.int_array = state.int_array\n"
+ + " result.long_array = state.long_array\n"
+ + " result.string_array = state.string_array\n"
+ + " on_success(result)\n"
+ + "end\n";
+ PersistableBundle previousState = new PersistableBundle();
+ int[] int_array = new int[] {1, 2};
+ long[] int_array_in_long = new long[] {1, 2};
+ long[] long_array = new long[] {1, 2, 3};
+ String[] string_array = new String[] {"one", "two", "three"};
+ previousState.putIntArray("int_array", int_array);
+ previousState.putLongArray("long_array", long_array);
+ previousState.putStringArray("string_array", string_array);
+
+ runScriptAndWaitForResponse(script, "arrays", mEmptyPublishedData, previousState);
+
+ // Verify that keys are preserved but the values are modified as expected.
+ assertThat(mListener.mInterimResult.size()).isEqualTo(3);
+ // Lua has only one lua_Integer. Here Java long is used to represent it when data is
+ // transferred from Lua to CarTelemetryService.
+ assertThat(mListener.mInterimResult.getLongArray("int_array")).isEqualTo(int_array_in_long);
+ assertThat(mListener.mInterimResult.getLongArray("long_array")).isEqualTo(long_array);
+ assertThat(mListener.mInterimResult.getStringArray("string_array")).isEqualTo(string_array);
+ }
+
+ @Test
+ public void invokeScript_modifiesArray() throws RemoteException, InterruptedException {
+ // Verify that an array modified by a script is properly sent back by the callback.
+ String script =
+ "function modify_array(data, state)\n"
+ + " result = {}\n"
+ + " result.long_array = state.long_array\n"
+ + " result.long_array[2] = 100\n"
+ + " on_success(result)\n"
+ + "end\n";
+ PersistableBundle previousState = new PersistableBundle();
+ long[] long_array = new long[] {1, 2, 3};
+ previousState.putLongArray("long_array", long_array);
+ long[] expected_array = new long[] {1, 100, 3};
+
+ runScriptAndWaitForResponse(script, "modify_array", mEmptyPublishedData, previousState);
+
+ // Verify that keys are preserved but the values are modified as expected.
+ assertThat(mListener.mInterimResult.size()).isEqualTo(1);
+ assertThat(mListener.mInterimResult.getLongArray("long_array")).isEqualTo(expected_array);
+ }
+
+ @Test
+ public void invokeScript_processesStringArray() throws RemoteException, InterruptedException {
+ // Verify that an array modified by a script is properly sent back by the callback.
+ String script =
+ "function process_string_array(data, state)\n"
+ + " result = {}\n"
+ + " result.answer = state.string_array[1] .. state.string_array[2]\n"
+ + " on_success(result)\n"
+ + "end\n";
+ PersistableBundle previousState = new PersistableBundle();
+ String[] string_array = new String[] {"Hello ", "world!"};
+ previousState.putStringArray("string_array", string_array);
+
+ runScriptAndWaitForResponse(
+ script, "process_string_array", mEmptyPublishedData, previousState);
+
+ // Verify that keys are preserved but the values are modified as expected.
+ assertThat(mListener.mInterimResult.size()).isEqualTo(1);
+ assertThat(mListener.mInterimResult.getString("answer")).isEqualTo("Hello world!");
+ }
+
+ @Test
+ public void invokeScript_arraysWithLengthAboveLimitCauseError()
+ throws RemoteException, InterruptedException {
+ // Verifies that arrays pushed by Lua that have their size over the limit cause error.
+ String script =
+ "function size_limit(data, state)\n"
+ + " result = {}\n"
+ + " result.huge_array = {}\n"
+ + " for i=1, 10000 do\n"
+ + " result.huge_array[i]=i\n"
+ + " end\n"
+ + " on_success(result)\n"
+ + "end\n";
+
+ runScriptAndWaitForResponse(
+ script, "size_limit", mEmptyPublishedData, mEmptyIterimResult);
+
+ // Verify that expected error is received.
+ assertThat(mListener.mErrorType)
+ .isEqualTo(IScriptExecutorListener.ERROR_TYPE_LUA_SCRIPT_ERROR);
+ assertThat(mListener.mMessage)
+ .isEqualTo(
+ "Returned table huge_array exceeds maximum allowed size of 1000 elements."
+ + " This key-value cannot be unpacked successfully. This error is"
+ + " unrecoverable.");
+ }
+
+ @Test
+ public void invokeScript_arrayContainingVaryingTypesCausesError()
+ throws RemoteException, InterruptedException {
+ // Verifies that values in returned array must be the same integer type.
+ // For example string values in a Lua array are not allowed.
+ String script =
+ "function table_with_numbers_and_strings(data, state)\n"
+ + " result = {}\n"
+ + " result.mixed_array = state.long_array\n"
+ + " result.mixed_array[2] = 'a'\n"
+ + " on_success(result)\n"
+ + "end\n";
+ PersistableBundle previousState = new PersistableBundle();
+ long[] long_array = new long[] {1, 2, 3};
+ previousState.putLongArray("long_array", long_array);
+
+ runScriptAndWaitForResponse(
+ script, "table_with_numbers_and_strings", mEmptyPublishedData, previousState);
+
+ // Verify that expected error is received.
+ assertThat(mListener.mErrorType)
+ .isEqualTo(IScriptExecutorListener.ERROR_TYPE_LUA_SCRIPT_ERROR);
+ assertThat(mListener.mMessage)
+ .contains("Returned Lua arrays must have elements of the same type.");
+ }
+
+ @Test
+ public void invokeScript_InTablesWithBothKeysAndIndicesCopiesOnlyIndexedData()
+ throws RemoteException, InterruptedException {
+ // Documents the current behavior that copies only indexed values in a Lua table that
+ // contains both keyed and indexed data.
+ String script =
+ "function keys_and_indices(data, state)\n"
+ + " result = {}\n"
+ + " result.mixed_array = state.long_array\n"
+ + " result.mixed_array['a'] = 130\n"
+ + " on_success(result)\n"
+ + "end\n";
+ PersistableBundle previousState = new PersistableBundle();
+ long[] long_array = new long[] {1, 2, 3};
+ previousState.putLongArray("long_array", long_array);
+
+ runScriptAndWaitForResponse(
+ script, "keys_and_indices", mEmptyPublishedData, previousState);
+
+ // Verify that keys are preserved but the values are modified as expected.
+ assertThat(mListener.mInterimResult.size()).isEqualTo(1);
+ assertThat(mListener.mInterimResult.getLongArray("mixed_array")).isEqualTo(long_array);
+ }
+
+ @Test
+ public void invokeScript_noLuaBufferOverflowForLargeInputArrays()
+ throws RemoteException, InterruptedException {
+ // Tests that arrays with length that exceed internal Lua buffer size of 20 elements
+ // do not cause buffer overflow and are handled properly.
+ String script =
+ "function large_input_array(data, state)\n"
+ + " sum = 0\n"
+ + " for _, val in ipairs(state.long_array) do\n"
+ + " sum = sum + val\n"
+ + " end\n"
+ + " result = {total = sum}\n"
+ + " on_success(result)\n"
+ + "end\n";
+
+ PersistableBundle previousState = new PersistableBundle();
+ int n = 10000;
+ long[] longArray = new long[n];
+ for (int i = 0; i < n; i++) {
+ longArray[i] = i;
+ }
+ previousState.putLongArray("long_array", longArray);
+ long expected_sum =
+ (longArray[0] + longArray[n - 1]) * n / 2; // sum of an arithmetic sequence.
+
+ runScriptAndWaitForResponse(
+ script, "large_input_array", mEmptyPublishedData, previousState);
+
+ // Verify that keys are preserved but the values are modified as expected.
+ assertThat(mListener.mInterimResult.size()).isEqualTo(1);
+ assertThat(mListener.mInterimResult.getLong("total")).isEqualTo(expected_sum);
+ }
+
+ @Test
+ public void invokeScript_scriptCallsOnError() throws RemoteException, InterruptedException {
+ String script =
+ "function calls_on_error()\n"
+ + " if 1 ~= 2 then\n"
+ + " on_error(\"one is not equal to two\")\n"
+ + " return\n"
+ + " end\n"
+ + "end\n";
+
+ runScriptAndWaitForError(script, "calls_on_error");
+
+ assertThat(mListener.mErrorType)
+ .isEqualTo(IScriptExecutorListener.ERROR_TYPE_LUA_SCRIPT_ERROR);
+ assertThat(mListener.mMessage).isEqualTo("one is not equal to two");
+ }
+
+ @Test
+ public void invokeScript_tooManyParametersInOnError()
+ throws RemoteException, InterruptedException {
+ String script =
+ "function too_many_params_in_on_error()\n"
+ + " if 1 ~= 2 then\n"
+ + " on_error(\"param1\", \"param2\")\n"
+ + " return\n"
+ + " end\n"
+ + "end\n";
+
+ runScriptAndWaitForError(script, "too_many_params_in_on_error");
+
+ assertThat(mListener.mErrorType)
+ .isEqualTo(IScriptExecutorListener.ERROR_TYPE_LUA_SCRIPT_ERROR);
+ assertThat(mListener.mMessage)
+ .isEqualTo("on_error can push only a single string parameter from Lua");
+ }
+
+ @Test
+ public void invokeScript_onErrorOnlyAcceptsString()
+ throws RemoteException, InterruptedException {
+ String script =
+ "function only_string()\n"
+ + " if 1 ~= 2 then\n"
+ + " on_error(false)\n"
+ + " return\n"
+ + " end\n"
+ + "end\n";
+
+ runScriptAndWaitForError(script, "only_string");
+
+ assertThat(mListener.mErrorType)
+ .isEqualTo(IScriptExecutorListener.ERROR_TYPE_LUA_SCRIPT_ERROR);
+ assertThat(mListener.mMessage)
+ .isEqualTo("on_error can push only a single string parameter from Lua");
+ }
+
+ @Test
+ public void invokeScript_returnsFinalResult() throws RemoteException, InterruptedException {
+ String returnFinalResultScript =
+ "function script_finishes(data, state)\n"
+ + " result = {data = state.input + 1}\n"
+ + " on_script_finished(result)\n"
+ + "end\n";
+ PersistableBundle previousState = new PersistableBundle();
+ previousState.putInt("input", 1);
+
+ runScriptAndWaitForResponse(
+ returnFinalResultScript, "script_finishes", mEmptyPublishedData, previousState);
+
+ // Expect to get back a bundle with a single key-value pair {"data": 2}
+ // because data = state.input + 1 as in the script body above.
+ assertThat(mListener.mFinalResult.size()).isEqualTo(1);
+ assertThat(mListener.mFinalResult.getLong("data")).isEqualTo(2);
+ }
+
+ @Test
+ public void invokeScript_emptyStringValueIsValidValue()
+ throws RemoteException, InterruptedException {
+ // Verify that an empty string value is a valid value to be returned from a script.
+ String returnFinalResultScript =
+ "function empty_string(data, state)\n"
+ + " result = {data = \"\"}\n"
+ + " on_script_finished(result)\n"
+ + "end\n";
+
+ runScriptAndWaitForResponse(
+ returnFinalResultScript,
+ "empty_string",
+ mEmptyPublishedData,
+ new PersistableBundle());
+
+ // Expect to get back a bundle with a single key-value pair {"data": ""}
+ assertThat(mListener.mFinalResult.size()).isEqualTo(1);
+ assertThat(mListener.mFinalResult.getString("data")).isEqualTo("");
+ }
+
+ @Test
+ public void invokeScript_allPrimitiveSupportedTypesForReturningFinalResult()
+ throws RemoteException, InterruptedException {
+ // Here we verify that all supported primitive types are present in the returned final
+ // result bundle are present.
+ String script =
+ "function finalize_all(data, state)\n"
+ + " result = {}\n"
+ + " result.integer = state.integer + 1\n"
+ + " result.number = state.number + 0.1\n"
+ + " result.boolean = not state.boolean\n"
+ + " result.string = state.string .. \"CADABRA\"\n"
+ + " on_script_finished(result)\n"
+ + "end\n";
+ PersistableBundle previousState = new PersistableBundle();
+ previousState.putInt("integer", 1);
+ previousState.putDouble("number", 0.1);
+ previousState.putBoolean("boolean", false);
+ previousState.putString("string", "ABRA");
+
+ runScriptAndWaitForResponse(script, "finalize_all", mEmptyPublishedData, previousState);
+
+ // Verify that keys are preserved but the values are modified as expected.
+ assertThat(mListener.mFinalResult.size()).isEqualTo(4);
+ assertThat(mListener.mFinalResult.getLong("integer")).isEqualTo(2);
+ assertThat(mListener.mFinalResult.getDouble("number")).isEqualTo(0.2);
+ assertThat(mListener.mFinalResult.getBoolean("boolean")).isEqualTo(true);
+ assertThat(mListener.mFinalResult.getString("string")).isEqualTo("ABRACADABRA");
+ }
+
+ @Test
+ public void invokeScript_emptyFinalResultBundle() throws RemoteException, InterruptedException {
+ String script =
+ "function empty_final_result(data, state)\n"
+ + " result = {}\n"
+ + " on_script_finished(result)\n"
+ + "end\n";
+
+ runScriptAndWaitForResponse(
+ script, "empty_final_result", mEmptyPublishedData, mEmptyIterimResult);
+
+ // If a script returns empty table as the final result, we get an empty bundle.
+ assertThat(mListener.mFinalResult).isNotNull();
+ assertThat(mListener.mFinalResult.size()).isEqualTo(0);
+ }
+
+ @Test
+ public void invokeScript_wrongNumberOfCallbackInputsInOnScriptFinished()
+ throws RemoteException, InterruptedException {
+ String script =
+ "function wrong_number_of_outputs_in_on_script_finished(data, state)\n"
+ + " result = {}\n"
+ + " extra = 1\n"
+ + " on_script_finished(result, extra)\n"
+ + "end\n";
+
+ runScriptAndWaitForResponse(
+ script,
+ "wrong_number_of_outputs_in_on_script_finished",
+ mEmptyPublishedData,
+ mEmptyIterimResult);
+
+ // We expect to get an error here because we expect only 1 input parameter in
+ // on_script_finished.
+ assertThat(mListener.mErrorType)
+ .isEqualTo(IScriptExecutorListener.ERROR_TYPE_LUA_SCRIPT_ERROR);
+ assertThat(mListener.mMessage)
+ .isEqualTo(
+ "on_script_finished can push only a single parameter from Lua - a Lua"
+ + " table");
+ }
+
+ @Test
+ public void invokeScript_wrongNumberOfCallbackInputsInOnSuccess()
+ throws RemoteException, InterruptedException {
+ String script =
+ "function wrong_number_of_outputs_in_on_success(data, state)\n"
+ + " result = {}\n"
+ + " extra = 1\n"
+ + " on_success(result, extra)\n"
+ + "end\n";
+
+ runScriptAndWaitForResponse(
+ script,
+ "wrong_number_of_outputs_in_on_success",
+ mEmptyPublishedData,
+ mEmptyIterimResult);
+
+ // We expect to get an error here because we expect only 1 input parameter in on_success.
+ assertThat(mListener.mErrorType)
+ .isEqualTo(IScriptExecutorListener.ERROR_TYPE_LUA_SCRIPT_ERROR);
+ assertThat(mListener.mMessage)
+ .isEqualTo("on_success can push only a single parameter from Lua - a Lua table");
+ }
+
+ @Test
+ public void invokeScript_wrongTypeInOnSuccess() throws RemoteException, InterruptedException {
+ String script =
+ "function wrong_type_in_on_success(data, state)\n"
+ + " result = 1\n"
+ + " on_success(result)\n"
+ + "end\n";
+
+ runScriptAndWaitForResponse(
+ script, "wrong_type_in_on_success", mEmptyPublishedData, mEmptyIterimResult);
+
+ // We expect to get an error here because the type of the input parameter for on_success
+ // must be a Lua table.
+ assertThat(mListener.mErrorType)
+ .isEqualTo(IScriptExecutorListener.ERROR_TYPE_LUA_SCRIPT_ERROR);
+ assertThat(mListener.mMessage)
+ .isEqualTo("on_success can push only a single parameter from Lua - a Lua table");
+ }
+
+ @Test
+ public void invokeScript_wrongTypeInOnScriptFinished()
+ throws RemoteException, InterruptedException {
+ String script =
+ "function wrong_type_in_on_script_finished(data, state)\n"
+ + " result = 1\n"
+ + " on_success(result)\n"
+ + "end\n";
+
+ runScriptAndWaitForResponse(
+ script,
+ "wrong_type_in_on_script_finished",
+ mEmptyPublishedData,
+ mEmptyIterimResult);
+
+ // We expect to get an error here because the type of the input parameter for
+ // on_script_finished must be a Lua table.
+ assertThat(mListener.mErrorType)
+ .isEqualTo(IScriptExecutorListener.ERROR_TYPE_LUA_SCRIPT_ERROR);
+ assertThat(mListener.mMessage)
+ .isEqualTo("on_success can push only a single parameter from Lua - a Lua table");
+ }
+
+ @Test
+ public void invokeScriptLargeInput_largePublishedData() throws Exception {
+ // Verifies that large input does not overwhelm Binder's buffer because pipes are used
+ // instead.
+ String script =
+ "function large_published_data(data, state)\n"
+ + " sum = 0\n"
+ + " for _, val in ipairs(data.array) do\n"
+ + " sum = sum + val\n"
+ + " end\n"
+ + " result = {total = sum}\n"
+ + " on_script_finished(result)\n"
+ + "end\n";
+
+ ParcelFileDescriptor[] fds = ParcelFileDescriptor.createPipe();
+ ParcelFileDescriptor writeFd = fds[1];
+ ParcelFileDescriptor readFd = fds[0];
+
+ PersistableBundle bundle = new PersistableBundle();
+ int n = 1 << 20; // 1024 * 1024 values, roughly 1 Million.
+ long[] array8Mb = new long[n];
+ for (int i = 0; i < n; i++) {
+ array8Mb[i] = i;
+ }
+ bundle.putLongArray("array", array8Mb);
+ long expectedSum =
+ (array8Mb[0] + array8Mb[n - 1]) * n / 2; // sum of an arithmetic sequence.
+
+ mScriptExecutor.invokeScriptForLargeInput(
+ script, "large_published_data", readFd, mEmptyIterimResult, mListener);
+
+ readFd.close();
+ try (OutputStream outputStream = new ParcelFileDescriptor.AutoCloseOutputStream(writeFd)) {
+ bundle.writeToStream(outputStream);
+ }
+
+ boolean gotResponse = mListener.awaitResponse(SCRIPT_PROCESSING_TIMEOUT_SEC);
+
+ assertWithMessage("Failed to get the callback method called by the script on time")
+ .that(gotResponse)
+ .isTrue();
+ assertThat(mListener.mFinalResult.size()).isEqualTo(1);
+ assertThat(mListener.mFinalResult.getLong("total")).isEqualTo(expectedSum);
+ }
+
+ @Test
+ public void invokeScript_bothPublishedDataAndPreviousStateAreProvided()
+ throws RemoteException, InterruptedException {
+ // Verifies that both published data and previous state PersistableBundles
+ // are piped into script.
+ String script =
+ "function data_and_state(data, state)\n"
+ + " result = {answer = data.a .. data.b .. state.c .. state.d}\n"
+ + " on_script_finished(result)\n"
+ + "end\n";
+
+ PersistableBundle publishedData = new PersistableBundle();
+ publishedData.putString("a", "A");
+ publishedData.putString("b", "B");
+
+ PersistableBundle previousState = new PersistableBundle();
+ previousState.putString("c", "C");
+ previousState.putString("d", "D");
+
+ runScriptAndWaitForResponse(script, "data_and_state", publishedData, previousState);
+
+ // Lua script combines both input published data and previous state into a single result.
+ assertThat(mListener.mFinalResult).isNotNull();
+ assertThat(mListener.mFinalResult.size()).isEqualTo(1);
+ assertThat(mListener.mFinalResult.getString("answer")).isEqualTo("ABCD");
+ }
+
+ @Test
+ public void invokeScript_outputIntAndLongAreTreatedAsLong()
+ throws RemoteException, InterruptedException {
+ // Verifies that we treat output both integer and long as long integer type although we
+ // distinguish between int and long in the script input.
+ String script =
+ "function int_and_long_are_output_long(data, state)\n"
+ + " result = {int = data.int, long = state.long}\n"
+ + " on_script_finished(result)\n"
+ + "end\n";
+
+ PersistableBundle publishedData = new PersistableBundle();
+ publishedData.putInt("int", 100);
+
+ PersistableBundle previousState = new PersistableBundle();
+ previousState.putLong("long", 200);
+
+ runScriptAndWaitForResponse(
+ script, "int_and_long_are_output_long", publishedData, previousState);
+
+ // If a script returns empty table as the final result, we get an empty bundle.
+ assertThat(mListener.mFinalResult).isNotNull();
+ assertThat(mListener.mFinalResult.size()).isEqualTo(2);
+ // getInt should always return "empty" value (zero) because all integer types are treated
+ // as Java long.
+ assertThat(mListener.mFinalResult.getInt("int")).isEqualTo(0);
+ assertThat(mListener.mFinalResult.getInt("long")).isEqualTo(0);
+ // Instead all expected integer values are successfully retrieved using getLong method
+ // from the output bundle.
+ assertThat(mListener.mFinalResult.getLong("int")).isEqualTo(100);
+ assertThat(mListener.mFinalResult.getLong("long")).isEqualTo(200);
+ }
+
+ @Test
+ public void invokeScript_nonUTFCharactersDoNotCauseErrors()
+ throws RemoteException, InterruptedException {
+ // Tries to create an output string value that does not conform to Modified UTF-8.
+ // JNI gracefully handles it by parsing on the string as is.
+ String script =
+ "function non_utf_key_string(data, state)\n"
+ + " result = {answer = \"i\0np\200\200ut\"}\n"
+ + " on_script_finished(result)\n"
+ + "end\n";
+
+ runScriptAndWaitForResponse(
+ script, "non_utf_key_string", new PersistableBundle(), new PersistableBundle());
+
+ // The output will still have all characters, including those that do not conform to
+ // Modified UTF-8.
+ assertThat(mListener.mFinalResult).isNotNull();
+ assertThat(mListener.mFinalResult.size()).isEqualTo(1);
+ assertThat(mListener.mFinalResult.getString("answer")).isEqualTo("i\0np\200\200ut");
+ }
+
+ @Test
+ public void invokeScript_wrongFunctionNameProvided()
+ throws RemoteException, InterruptedException {
+ // Verifies that not specifying function name correctly is handled through error callback.
+ String script = "function correct_function(data, state)\n" + "end\n";
+
+ runScriptAndWaitForError(script, "wrong_function");
+
+ // Verify that the expected error is received.
+ assertThat(mListener.mErrorType)
+ .isEqualTo(IScriptExecutorListener.ERROR_TYPE_LUA_RUNTIME_ERROR);
+ assertThat(mListener.mMessage).contains("Wrong function name");
+ }
+
+ @Test
+ public void invokeScript_runtimeErrorDueToSyntax()
+ throws RemoteException, InterruptedException {
+ // Verifies that syntax errors during script loading are handled gracefully.
+ String script = "function wrong_syntax(data, state)\n" + " x == 1\n" + "end\n";
+
+ runScriptAndWaitForError(script, "wrong_syntax");
+
+ // Verify that the expected error is received.
+ assertThat(mListener.mErrorType)
+ .isEqualTo(IScriptExecutorListener.ERROR_TYPE_LUA_RUNTIME_ERROR);
+ assertThat(mListener.mMessage).contains("Error encountered while loading the script");
+ }
+
+ @Test
+ public void invokeScript_runtimeErrorDueToUndefinedMethod()
+ throws RemoteException, InterruptedException {
+ // Verifies that runtime errors encountered during Lua script execution trigger an error
+ // returned via a callback.
+ String script =
+ "function runtime_error(data, state)\n" + " on_problem(data, state)\n" + "end\n";
+
+ runScriptAndWaitForError(script, "runtime_error");
+
+ // Verify that the expected error is received.
+ assertThat(mListener.mErrorType)
+ .isEqualTo(IScriptExecutorListener.ERROR_TYPE_LUA_RUNTIME_ERROR);
+ assertThat(mListener.mMessage).contains("Error encountered while running the script");
+ }
+
+ @Test
+ public void invokeScript_returnedValuesOfUnsupportedTypesReturnError()
+ throws RemoteException, InterruptedException {
+ // Verifies that if we try to return a value of unsupported type, we get an error instead.
+ // In this case, the unsupported type is LUA_TFUNCTION type.
+ String script =
+ "function function_type(data, state)\n"
+ + " result = {fn = function_type}\n"
+ + " on_success(result)\n"
+ + "end\n";
+
+ runScriptAndWaitForResponse(
+ script, "function_type", mEmptyPublishedData, mEmptyIterimResult);
+
+ // Verify that the expected error is received.
+ assertThat(mListener.mErrorType)
+ .isEqualTo(IScriptExecutorListener.ERROR_TYPE_LUA_SCRIPT_ERROR);
+ assertThat(mListener.mMessage)
+ .contains("has a Lua type=function, which is not supported yet");
+ }
+
+ @Test
+ public void invokeScript_returnedFloatingArraysNotSupported()
+ throws RemoteException, InterruptedException {
+ // Verifies that we do not support return values that contain floating number arrays.
+ String script =
+ "function floating_point_arrays(data, state)\n"
+ + " array = {}\n"
+ + " array[0] = 1.1\n"
+ + " array[1] = 1.2\n"
+ + " result = {data = array}\n"
+ + " on_success(result)\n"
+ + "end\n";
+
+ runScriptAndWaitForResponse(
+ script, "floating_point_arrays", mEmptyPublishedData, mEmptyIterimResult);
+
+ // Verify that the expected error is received.
+ assertThat(mListener.mErrorType)
+ .isEqualTo(IScriptExecutorListener.ERROR_TYPE_LUA_SCRIPT_ERROR);
+ assertThat(mListener.mMessage)
+ .contains("a floating number array, which is not supported yet");
+ }
+
+ @Test
+ public void invokeScript_returnedBooleanArraysNotSupported()
+ throws RemoteException, InterruptedException {
+ // Verifies that we do not yet support return values that contain boolean arrays.
+ String script =
+ "function array_of_booleans(data, state)\n"
+ + " array = {}\n"
+ + " array[0] = false\n"
+ + " array[1] = true\n"
+ + " result = {data = array}\n"
+ + " on_success(result)\n"
+ + "end\n";
+
+ runScriptAndWaitForResponse(
+ script, "array_of_booleans", mEmptyPublishedData, mEmptyIterimResult);
+
+ // Verify that the expected error is received.
+ assertThat(mListener.mErrorType)
+ .isEqualTo(IScriptExecutorListener.ERROR_TYPE_LUA_SCRIPT_ERROR);
+ assertThat(mListener.mMessage)
+ .contains("is an array with values of type=boolean, which is not supported yet");
+ }
+
+ // Helper method to invoke the script and wait for it to complete and return a response.
+ private void runScriptAndWaitForResponse(
+ String script,
+ String function,
+ PersistableBundle publishedData,
+ PersistableBundle previousState)
+ throws RemoteException, InterruptedException {
+ mScriptExecutor.invokeScript(script, function, publishedData, previousState, mListener);
+ assertWithMessage("Failed to get the callback method called by the script on time")
+ .that(mListener.awaitResponse(SCRIPT_PROCESSING_TIMEOUT_SEC))
+ .isTrue();
+ }
+
+ private void runScriptAndWaitForError(String script, String function)
+ throws RemoteException, InterruptedException {
+ runScriptAndWaitForResponse(
+ script, function, new PersistableBundle(), new PersistableBundle());
+ }
+}
diff --git a/packages/ScriptExecutor/tests/nonsystemuser/Android.bp b/packages/ScriptExecutor/tests/nonsystemuser/Android.bp
new file mode 100644
index 0000000..cc95fe3
--- /dev/null
+++ b/packages/ScriptExecutor/tests/nonsystemuser/Android.bp
@@ -0,0 +1,41 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+ name: "ScriptExecutorNonSystemUserTests",
+
+ srcs: ["src/**/*.java"],
+
+ platform_apis: true,
+
+ certificate: "platform",
+
+ instrumentation_for: "ScriptExecutor",
+
+ static_libs: [
+ "androidx.test.core",
+ "androidx.test.runner",
+ "junit",
+ "scriptexecutor-test-lib",
+ "truth-prebuilt",
+ ],
+
+ test_suites: ["general-tests"],
+}
diff --git a/packages/ScriptExecutor/tests/nonsystemuser/AndroidManifest.xml b/packages/ScriptExecutor/tests/nonsystemuser/AndroidManifest.xml
new file mode 100644
index 0000000..9e20a43
--- /dev/null
+++ b/packages/ScriptExecutor/tests/nonsystemuser/AndroidManifest.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.car.scriptexecutortest.nonsystemuser">
+
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
+
+ <queries>
+ <package android:name="com.android.car.scriptexecutor" />
+ </queries>
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.car.scriptexecutortest.nonsystemuser"
+ android:label="Tests for ScriptExecutor"/>
+</manifest>
diff --git a/packages/ScriptExecutor/tests/nonsystemuser/src/com/android/car/scriptexecutortest/nonsystemuser/ScriptExecutorNonSystemUserTest.java b/packages/ScriptExecutor/tests/nonsystemuser/src/com/android/car/scriptexecutortest/nonsystemuser/ScriptExecutorNonSystemUserTest.java
new file mode 100644
index 0000000..6949e3e
--- /dev/null
+++ b/packages/ScriptExecutor/tests/nonsystemuser/src/com/android/car/scriptexecutortest/nonsystemuser/ScriptExecutorNonSystemUserTest.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.scriptexecutortest.nonsystemuser;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.os.PersistableBundle;
+import android.os.UserHandle;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.car.scriptexecutor.ScriptExecutor;
+import com.android.car.telemetry.scriptexecutorinterface.IScriptExecutor;
+import com.android.car.telemetry.scriptexecutorinterface.IScriptExecutorListener;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(JUnit4.class)
+public final class ScriptExecutorNonSystemUserTest {
+
+ private IScriptExecutor mScriptExecutor;
+ private ScriptExecutor mInstance;
+ private Context mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+
+ private static final class ScriptExecutorListener extends IScriptExecutorListener.Stub {
+ public PersistableBundle mInterimResult;
+ public PersistableBundle mFinalResult;
+ public int mErrorType;
+ public String mMessage;
+ public String mStackTrace;
+ public final CountDownLatch mResponseLatch = new CountDownLatch(1);
+
+ @Override
+ public void onScriptFinished(PersistableBundle result) {
+ mFinalResult = result;
+ mResponseLatch.countDown();
+ }
+
+ @Override
+ public void onSuccess(PersistableBundle stateToPersist) {
+ mInterimResult = stateToPersist;
+ mResponseLatch.countDown();
+ }
+
+ @Override
+ public void onError(int errorType, String message, String stackTrace) {
+ mErrorType = errorType;
+ mMessage = message;
+ mStackTrace = stackTrace;
+ mResponseLatch.countDown();
+ }
+
+ private boolean awaitResponse(int waitTimeSec) throws InterruptedException {
+ return mResponseLatch.await(waitTimeSec, TimeUnit.SECONDS);
+ }
+ }
+
+ private final ScriptExecutorListener mListener = new ScriptExecutorListener();
+
+ private final PersistableBundle mPublishedData = new PersistableBundle();
+ private final PersistableBundle mSavedState = new PersistableBundle();
+
+ private final CountDownLatch mBindLatch = new CountDownLatch(1);
+
+ private static final int BIND_SERVICE_TIMEOUT_SEC = 5;
+ private static final int SCRIPT_PROCESSING_TIMEOUT_SEC = 10;
+
+ private final ServiceConnection mScriptExecutorConnection =
+ new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName className, IBinder service) {
+ mScriptExecutor = IScriptExecutor.Stub.asInterface(service);
+ mBindLatch.countDown();
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName className) {
+ assertWithMessage("Service unexpectedly disconnected").fail();
+ }
+ };
+
+ @Before
+ public void setUp() throws InterruptedException {
+ Intent intent = new Intent();
+ intent.setComponent(
+ new ComponentName(
+ "com.android.car.scriptexecutor",
+ "com.android.car.scriptexecutor.ScriptExecutor"));
+ mContext.bindServiceAsUser(
+ intent, mScriptExecutorConnection, Context.BIND_AUTO_CREATE, UserHandle.SYSTEM);
+ if (!mBindLatch.await(BIND_SERVICE_TIMEOUT_SEC, TimeUnit.SECONDS)) {
+ assertWithMessage("Failed to bind to ScriptExecutor service").fail();
+ }
+ }
+
+ @Test
+ public void invokeScript_FromNonSystemUser_isNotProcessed() throws Exception {
+ String script =
+ "function hello(data, state)\n"
+ + " result = {hello=\"world\"}\n"
+ + " on_success(result)\n"
+ + "end\n";
+
+ mScriptExecutor.invokeScript(script, "hello", mPublishedData, mSavedState, mListener);
+
+ assertThat(mListener.awaitResponse(SCRIPT_PROCESSING_TIMEOUT_SEC)).isFalse();
+ }
+
+ @Test
+ public void invokeScriptForLargeInput_FromNonSystemUser_isNotProcessed() throws Exception {
+ String script =
+ "function hello(data, state)\n"
+ + " result = {hello=\"world\"}\n"
+ + " on_success(result)\n"
+ + "end\n";
+
+ ParcelFileDescriptor[] fds = ParcelFileDescriptor.createPipe();
+ ParcelFileDescriptor writeFd = fds[1];
+ ParcelFileDescriptor readFd = fds[0];
+
+ mScriptExecutor.invokeScriptForLargeInput(
+ script, "large_published_data", readFd, mSavedState, mListener);
+
+ readFd.close();
+ writeFd.close();
+
+ assertThat(mListener.awaitResponse(SCRIPT_PROCESSING_TIMEOUT_SEC)).isFalse();
+ }
+}
diff --git a/packages/ScriptExecutor/tests/unit/Android.bp b/packages/ScriptExecutor/tests/unit/Android.bp
index 80ba55f..5f6213c 100644
--- a/packages/ScriptExecutor/tests/unit/Android.bp
+++ b/packages/ScriptExecutor/tests/unit/Android.bp
@@ -19,7 +19,7 @@
}
android_test {
- name: "ScriptExecutorUnitTest",
+ name: "ScriptExecutorUnitTests",
srcs: ["src/**/*.java"],
@@ -53,7 +53,7 @@
],
srcs: [
- "src/com/android/car/scriptexecutor/JniUtilsTestHelper.cpp",
+ "src/com/android/car/scriptexecutortest/unit/JniUtilsTestHelper.cpp",
],
stl: "libc++_static",
diff --git a/packages/ScriptExecutor/tests/unit/AndroidManifest.xml b/packages/ScriptExecutor/tests/unit/AndroidManifest.xml
index f393408..9809a8a 100644
--- a/packages/ScriptExecutor/tests/unit/AndroidManifest.xml
+++ b/packages/ScriptExecutor/tests/unit/AndroidManifest.xml
@@ -16,7 +16,7 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.car.scriptexecutor_test">
+ package="com.android.car.scriptexecutortest.unit">
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
@@ -29,6 +29,6 @@
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
- android:targetPackage="com.android.car.scriptexecutor_test"
+ android:targetPackage="com.android.car.scriptexecutortest.unit"
android:label="Tests for ScriptExecutor"/>
</manifest>
diff --git a/packages/ScriptExecutor/tests/unit/src/com/android/car/scriptexecutor/ScriptExecutorTest.java b/packages/ScriptExecutor/tests/unit/src/com/android/car/scriptexecutor/ScriptExecutorTest.java
deleted file mode 100644
index 1a4028a..0000000
--- a/packages/ScriptExecutor/tests/unit/src/com/android/car/scriptexecutor/ScriptExecutorTest.java
+++ /dev/null
@@ -1,907 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.car.scriptexecutor;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import static org.junit.Assert.fail;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.os.IBinder;
-import android.os.ParcelFileDescriptor;
-import android.os.PersistableBundle;
-import android.os.RemoteException;
-import android.os.UserHandle;
-
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import com.android.car.telemetry.scriptexecutorinterface.IScriptExecutor;
-import com.android.car.telemetry.scriptexecutorinterface.IScriptExecutorListener;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.io.OutputStream;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-@RunWith(JUnit4.class)
-public final class ScriptExecutorTest {
-
- private IScriptExecutor mScriptExecutor;
- private ScriptExecutor mInstance;
- private Context mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
-
-
- private static final class ScriptExecutorListener extends IScriptExecutorListener.Stub {
- public PersistableBundle mSavedBundle;
- public PersistableBundle mFinalResult;
- public int mErrorType;
- public String mMessage;
- public String mStackTrace;
- public final CountDownLatch mResponseLatch = new CountDownLatch(1);
-
- @Override
- public void onScriptFinished(PersistableBundle result) {
- mFinalResult = result;
- mResponseLatch.countDown();
- }
-
- @Override
- public void onSuccess(PersistableBundle stateToPersist) {
- mSavedBundle = stateToPersist;
- mResponseLatch.countDown();
- }
-
- @Override
- public void onError(int errorType, String message, String stackTrace) {
- mErrorType = errorType;
- mMessage = message;
- mStackTrace = stackTrace;
- mResponseLatch.countDown();
- }
- }
-
- private final ScriptExecutorListener mFakeScriptExecutorListener =
- new ScriptExecutorListener();
-
- private final PersistableBundle mPublishedData = new PersistableBundle();
- private final PersistableBundle mSavedState = new PersistableBundle();
-
- private final CountDownLatch mBindLatch = new CountDownLatch(1);
-
- private static final int BIND_SERVICE_TIMEOUT_SEC = 5;
- private static final int SCRIPT_PROCESSING_TIMEOUT_SEC = 10;
-
-
- private final ServiceConnection mScriptExecutorConnection =
- new ServiceConnection() {
- @Override
- public void onServiceConnected(ComponentName className, IBinder service) {
- mScriptExecutor = IScriptExecutor.Stub.asInterface(service);
- mBindLatch.countDown();
- }
-
- @Override
- public void onServiceDisconnected(ComponentName className) {
- fail("Service unexpectedly disconnected");
- }
- };
-
- // Helper method to invoke the script and wait for it to complete and return a response.
- private void runScriptAndWaitForResponse(String script, String function,
- PersistableBundle publishedData, PersistableBundle previousState)
- throws RemoteException {
- mScriptExecutor.invokeScript(script, function, publishedData, previousState,
- mFakeScriptExecutorListener);
- try {
- if (!mFakeScriptExecutorListener.mResponseLatch.await(SCRIPT_PROCESSING_TIMEOUT_SEC,
- TimeUnit.SECONDS)) {
- fail("Failed to get the callback method called by the script on time");
- }
- } catch (InterruptedException e) {
- e.printStackTrace();
- fail(e.getMessage());
- }
- }
-
- private void runScriptAndWaitForError(String script, String function) throws RemoteException {
- runScriptAndWaitForResponse(script, function, new PersistableBundle(),
- new PersistableBundle());
- }
-
- @Before
- public void setUp() throws InterruptedException {
- Intent intent = new Intent();
- intent.setComponent(new ComponentName("com.android.car.scriptexecutor",
- "com.android.car.scriptexecutor.ScriptExecutor"));
- mContext.bindServiceAsUser(intent, mScriptExecutorConnection, Context.BIND_AUTO_CREATE,
- UserHandle.SYSTEM);
- if (!mBindLatch.await(BIND_SERVICE_TIMEOUT_SEC, TimeUnit.SECONDS)) {
- fail("Failed to bind to ScriptExecutor service");
- }
- }
-
- @Test
- public void invokeScript_returnsResult() throws RemoteException {
- String returnResultScript =
- "function hello(data, state)\n"
- + " result = {hello=\"world\"}\n"
- + " on_success(result)\n"
- + "end\n";
-
- runScriptAndWaitForResponse(returnResultScript, "hello", mPublishedData, mSavedState);
-
- // Expect to get back a bundle with a single string key: string value pair:
- // {"hello": "world"}
- assertThat(mFakeScriptExecutorListener.mSavedBundle.size()).isEqualTo(1);
- assertThat(mFakeScriptExecutorListener.mSavedBundle.getString("hello")).isEqualTo(
- "world");
- }
-
- @Test
- public void invokeScript_allSupportedPrimitiveTypes() throws RemoteException {
- String script =
- "function knows(data, state)\n"
- + " result = {string=\"hello\", boolean=true, integer=1, number=1.1}\n"
- + " on_success(result)\n"
- + "end\n";
-
- runScriptAndWaitForResponse(script, "knows", mPublishedData, mSavedState);
-
- // Expect to get back a bundle with 4 keys, each corresponding to a distinct supported type.
- assertThat(mFakeScriptExecutorListener.mSavedBundle.size()).isEqualTo(4);
- assertThat(mFakeScriptExecutorListener.mSavedBundle.getString("string")).isEqualTo(
- "hello");
- assertThat(mFakeScriptExecutorListener.mSavedBundle.getBoolean("boolean")).isEqualTo(true);
- assertThat(mFakeScriptExecutorListener.mSavedBundle.getLong("integer")).isEqualTo(1);
- assertThat(mFakeScriptExecutorListener.mSavedBundle.getDouble("number")).isEqualTo(1.1);
- }
-
- @Test
- public void invokeScript_skipsUnsupportedNestedTables() throws RemoteException {
- String script =
- "function nested(data, state)\n"
- + " result = {string=\"hello\", boolean=true, integer=1, number=1.1}\n"
- + " result.nested_table = {x=0, y=0}\n"
- + " on_success(result)\n"
- + "end\n";
-
- runScriptAndWaitForResponse(script, "nested", mPublishedData, mSavedState);
-
- // Verify that expected error is received.
- assertThat(mFakeScriptExecutorListener.mErrorType).isEqualTo(
- IScriptExecutorListener.ERROR_TYPE_LUA_SCRIPT_ERROR);
- assertThat(mFakeScriptExecutorListener.mMessage).contains(
- "nested tables are not supported");
- }
-
- @Test
- public void invokeScript_emptyBundle() throws RemoteException {
- String script =
- "function empty(data, state)\n"
- + " result = {}\n"
- + " on_success(result)\n"
- + "end\n";
-
- runScriptAndWaitForResponse(script, "empty", mPublishedData, mSavedState);
-
- // If a script returns empty table as the result, we get an empty bundle.
- assertThat(mFakeScriptExecutorListener.mSavedBundle).isNotNull();
- assertThat(mFakeScriptExecutorListener.mSavedBundle.size()).isEqualTo(0);
- }
-
- @Test
- public void invokeScript_processPreviousStateAndReturnResult() throws RemoteException {
- // Here we verify that the script actually processes provided state from a previous run
- // and makes calculation based on that and returns the result.
- String script =
- "function update(data, state)\n"
- + " result = {y = state.x+1}\n"
- + " on_success(result)\n"
- + "end\n";
- PersistableBundle previousState = new PersistableBundle();
- previousState.putInt("x", 1);
-
- runScriptAndWaitForResponse(script, "update", mPublishedData, previousState);
-
- // Verify that y = 2, because y = x + 1 and x = 1.
- assertThat(mFakeScriptExecutorListener.mSavedBundle.size()).isEqualTo(1);
- assertThat(mFakeScriptExecutorListener.mSavedBundle.getLong("y")).isEqualTo(2);
- }
-
- @Test
- public void invokeScript_allSupportedPrimitiveTypesWorkRoundTripWithKeyNamesPreserved()
- throws RemoteException {
- // Here we verify that all supported primitive types in supplied previous state Bundle
- // are interpreted by the script as expected.
- String script =
- "function update_all(data, state)\n"
- + " result = {}\n"
- + " result.integer = state.integer + 1\n"
- + " result.number = state.number + 0.1\n"
- + " result.boolean = not state.boolean\n"
- + " result.string = state.string .. \"CADABRA\"\n"
- + " on_success(result)\n"
- + "end\n";
- PersistableBundle previousState = new PersistableBundle();
- previousState.putInt("integer", 1);
- previousState.putDouble("number", 0.1);
- previousState.putBoolean("boolean", false);
- previousState.putString("string", "ABRA");
-
- runScriptAndWaitForResponse(script, "update_all", mPublishedData, previousState);
-
- // Verify that keys are preserved but the values are modified as expected.
- assertThat(mFakeScriptExecutorListener.mSavedBundle.size()).isEqualTo(4);
- assertThat(mFakeScriptExecutorListener.mSavedBundle.getLong("integer")).isEqualTo(2);
- assertThat(mFakeScriptExecutorListener.mSavedBundle.getDouble("number")).isEqualTo(0.2);
- assertThat(mFakeScriptExecutorListener.mSavedBundle.getBoolean("boolean")).isEqualTo(true);
- assertThat(mFakeScriptExecutorListener.mSavedBundle.getString("string")).isEqualTo(
- "ABRACADABRA");
- }
-
- @Test
- public void invokeScript_allSupportedArrayTypesWorkRoundTripWithKeyNamesPreserved()
- throws RemoteException {
- // Here we verify that all supported array types in supplied previous state Bundle are
- // interpreted by the script as expected.
- String script =
- "function arrays(data, state)\n"
- + " result = {}\n"
- + " result.int_array = state.int_array\n"
- + " result.long_array = state.long_array\n"
- + " result.string_array = state.string_array\n"
- + " on_success(result)\n"
- + "end\n";
- PersistableBundle previousState = new PersistableBundle();
- int[] int_array = new int[]{1, 2};
- long[] int_array_in_long = new long[]{1, 2};
- long[] long_array = new long[]{1, 2, 3};
- String[] string_array = new String[]{"one", "two", "three"};
- previousState.putIntArray("int_array", int_array);
- previousState.putLongArray("long_array", long_array);
- previousState.putStringArray("string_array", string_array);
-
- runScriptAndWaitForResponse(script, "arrays", mPublishedData, previousState);
-
- // Verify that keys are preserved but the values are modified as expected.
- assertThat(mFakeScriptExecutorListener.mSavedBundle.size()).isEqualTo(3);
- // Lua has only one lua_Integer. Here Java long is used to represent it when data is
- // transferred from Lua to CarTelemetryService.
- assertThat(mFakeScriptExecutorListener.mSavedBundle.getLongArray("int_array")).isEqualTo(
- int_array_in_long);
- assertThat(mFakeScriptExecutorListener.mSavedBundle.getLongArray("long_array")).isEqualTo(
- long_array);
- assertThat(
- mFakeScriptExecutorListener.mSavedBundle.getStringArray("string_array")).isEqualTo(
- string_array);
- }
-
- @Test
- public void invokeScript_modifiesArray()
- throws RemoteException {
- // Verify that an array modified by a script is properly sent back by the callback.
- String script =
- "function modify_array(data, state)\n"
- + " result = {}\n"
- + " result.long_array = state.long_array\n"
- + " result.long_array[2] = 100\n"
- + " on_success(result)\n"
- + "end\n";
- PersistableBundle previousState = new PersistableBundle();
- long[] long_array = new long[]{1, 2, 3};
- previousState.putLongArray("long_array", long_array);
- long[] expected_array = new long[]{1, 100, 3};
-
- runScriptAndWaitForResponse(script, "modify_array", mPublishedData, previousState);
-
- // Verify that keys are preserved but the values are modified as expected.
- assertThat(mFakeScriptExecutorListener.mSavedBundle.size()).isEqualTo(1);
- assertThat(mFakeScriptExecutorListener.mSavedBundle.getLongArray("long_array")).isEqualTo(
- expected_array);
- }
-
- @Test
- public void invokeScript_processesStringArray()
- throws RemoteException {
- // Verify that an array modified by a script is properly sent back by the callback.
- String script =
- "function process_string_array(data, state)\n"
- + " result = {}\n"
- + " result.answer = state.string_array[1] .. state.string_array[2]\n"
- + " on_success(result)\n"
- + "end\n";
- PersistableBundle previousState = new PersistableBundle();
- String[] string_array = new String[]{"Hello ", "world!"};
- previousState.putStringArray("string_array", string_array);
-
- runScriptAndWaitForResponse(script, "process_string_array", mPublishedData, previousState);
-
- // Verify that keys are preserved but the values are modified as expected.
- assertThat(mFakeScriptExecutorListener.mSavedBundle.size()).isEqualTo(1);
- assertThat(mFakeScriptExecutorListener.mSavedBundle.getString("answer")).isEqualTo(
- "Hello world!");
- }
-
- @Test
- public void invokeScript_arraysWithLengthAboveLimitCauseError()
- throws RemoteException {
- // Verifies that arrays pushed by Lua that have their size over the limit cause error.
- String script =
- "function size_limit(data, state)\n"
- + " result = {}\n"
- + " result.huge_array = {}\n"
- + " for i=1, 10000 do\n"
- + " result.huge_array[i]=i\n"
- + " end\n"
- + " on_success(result)\n"
- + "end\n";
-
- runScriptAndWaitForResponse(script, "size_limit", mPublishedData, mSavedState);
-
- // Verify that expected error is received.
- assertThat(mFakeScriptExecutorListener.mErrorType).isEqualTo(
- IScriptExecutorListener.ERROR_TYPE_LUA_SCRIPT_ERROR);
- assertThat(mFakeScriptExecutorListener.mMessage).isEqualTo(
- "Returned table huge_array exceeds maximum allowed size of 1000 "
- + "elements. This key-value cannot be unpacked successfully. This error "
- + "is unrecoverable.");
- }
-
- @Test
- public void invokeScript_arrayContainingVaryingTypesCausesError()
- throws RemoteException {
- // Verifies that values in returned array must be the same integer type.
- // For example string values in a Lua array are not allowed.
- String script =
- "function table_with_numbers_and_strings(data, state)\n"
- + " result = {}\n"
- + " result.mixed_array = state.long_array\n"
- + " result.mixed_array[2] = 'a'\n"
- + " on_success(result)\n"
- + "end\n";
- PersistableBundle previousState = new PersistableBundle();
- long[] long_array = new long[]{1, 2, 3};
- previousState.putLongArray("long_array", long_array);
-
- runScriptAndWaitForResponse(script, "table_with_numbers_and_strings", mPublishedData,
- previousState);
-
- // Verify that expected error is received.
- assertThat(mFakeScriptExecutorListener.mErrorType).isEqualTo(
- IScriptExecutorListener.ERROR_TYPE_LUA_SCRIPT_ERROR);
- assertThat(mFakeScriptExecutorListener.mMessage).contains(
- "Returned Lua arrays must have elements of the same type.");
- }
-
- @Test
- public void invokeScript_InTablesWithBothKeysAndIndicesCopiesOnlyIndexedData()
- throws RemoteException {
- // Documents the current behavior that copies only indexed values in a Lua table that
- // contains both keyed and indexed data.
- String script =
- "function keys_and_indices(data, state)\n"
- + " result = {}\n"
- + " result.mixed_array = state.long_array\n"
- + " result.mixed_array['a'] = 130\n"
- + " on_success(result)\n"
- + "end\n";
- PersistableBundle previousState = new PersistableBundle();
- long[] long_array = new long[]{1, 2, 3};
- previousState.putLongArray("long_array", long_array);
-
- runScriptAndWaitForResponse(script, "keys_and_indices", mPublishedData, previousState);
-
- // Verify that keys are preserved but the values are modified as expected.
- assertThat(mFakeScriptExecutorListener.mSavedBundle.size()).isEqualTo(1);
- assertThat(mFakeScriptExecutorListener.mSavedBundle.getLongArray("mixed_array")).isEqualTo(
- long_array);
- }
-
- @Test
- public void invokeScript_noLuaBufferOverflowForLargeInputArrays() throws RemoteException {
- // Tests that arrays with length that exceed internal Lua buffer size of 20 elements
- // do not cause buffer overflow and are handled properly.
- String script =
- "function large_input_array(data, state)\n"
- + " sum = 0\n"
- + " for _, val in ipairs(state.long_array) do\n"
- + " sum = sum + val\n"
- + " end\n"
- + " result = {total = sum}\n"
- + " on_success(result)\n"
- + "end\n";
-
- PersistableBundle previousState = new PersistableBundle();
- int n = 10000;
- long[] longArray = new long[n];
- for (int i = 0; i < n; i++) {
- longArray[i] = i;
- }
- previousState.putLongArray("long_array", longArray);
- long expected_sum =
- (longArray[0] + longArray[n - 1]) * n / 2; // sum of an arithmetic sequence.
-
- runScriptAndWaitForResponse(script, "large_input_array", mPublishedData, previousState);
-
- // Verify that keys are preserved but the values are modified as expected.
- assertThat(mFakeScriptExecutorListener.mSavedBundle.size()).isEqualTo(1);
- assertThat(mFakeScriptExecutorListener.mSavedBundle.getLong("total")).isEqualTo(
- expected_sum);
- }
-
- @Test
- public void invokeScript_scriptCallsOnError() throws RemoteException {
- String script =
- "function calls_on_error()\n"
- + " if 1 ~= 2 then\n"
- + " on_error(\"one is not equal to two\")\n"
- + " return\n"
- + " end\n"
- + "end\n";
-
- runScriptAndWaitForError(script, "calls_on_error");
-
- assertThat(mFakeScriptExecutorListener.mErrorType).isEqualTo(
- IScriptExecutorListener.ERROR_TYPE_LUA_SCRIPT_ERROR);
- assertThat(mFakeScriptExecutorListener.mMessage).isEqualTo("one is not equal to two");
- }
-
- @Test
- public void invokeScript_tooManyParametersInOnError() throws RemoteException {
- String script =
- "function too_many_params_in_on_error()\n"
- + " if 1 ~= 2 then\n"
- + " on_error(\"param1\", \"param2\")\n"
- + " return\n"
- + " end\n"
- + "end\n";
-
- runScriptAndWaitForError(script, "too_many_params_in_on_error");
-
- assertThat(mFakeScriptExecutorListener.mErrorType).isEqualTo(
- IScriptExecutorListener.ERROR_TYPE_LUA_SCRIPT_ERROR);
- assertThat(mFakeScriptExecutorListener.mMessage).isEqualTo(
- "on_error can push only a single string parameter from Lua");
- }
-
- @Test
- public void invokeScript_onErrorOnlyAcceptsString() throws RemoteException {
- String script =
- "function only_string()\n"
- + " if 1 ~= 2 then\n"
- + " on_error(false)\n"
- + " return\n"
- + " end\n"
- + "end\n";
-
- runScriptAndWaitForError(script, "only_string");
-
- assertThat(mFakeScriptExecutorListener.mErrorType).isEqualTo(
- IScriptExecutorListener.ERROR_TYPE_LUA_SCRIPT_ERROR);
- assertThat(mFakeScriptExecutorListener.mMessage).isEqualTo(
- "on_error can push only a single string parameter from Lua");
- }
-
- @Test
- public void invokeScript_returnsFinalResult() throws RemoteException {
- String returnFinalResultScript =
- "function script_finishes(data, state)\n"
- + " result = {data = state.input + 1}\n"
- + " on_script_finished(result)\n"
- + "end\n";
- PersistableBundle previousState = new PersistableBundle();
- previousState.putInt("input", 1);
-
- runScriptAndWaitForResponse(returnFinalResultScript, "script_finishes", mPublishedData,
- previousState);
-
- // Expect to get back a bundle with a single key-value pair {"data": 2}
- // because data = state.input + 1 as in the script body above.
- assertThat(mFakeScriptExecutorListener.mFinalResult.size()).isEqualTo(1);
- assertThat(mFakeScriptExecutorListener.mFinalResult.getLong("data")).isEqualTo(2);
- }
-
- @Test
- public void invokeScript_emptyStringValueIsValidValue() throws RemoteException {
- // Verify that an empty string value is a valid value to be returned from a script.
- String returnFinalResultScript =
- "function empty_string(data, state)\n"
- + " result = {data = \"\"}\n"
- + " on_script_finished(result)\n"
- + "end\n";
-
- runScriptAndWaitForResponse(returnFinalResultScript, "empty_string", mPublishedData,
- new PersistableBundle());
-
- // Expect to get back a bundle with a single key-value pair {"data": ""}
- assertThat(mFakeScriptExecutorListener.mFinalResult.size()).isEqualTo(1);
- assertThat(mFakeScriptExecutorListener.mFinalResult.getString("data")).isEqualTo("");
- }
-
- @Test
- public void invokeScript_allPrimitiveSupportedTypesForReturningFinalResult()
- throws RemoteException {
- // Here we verify that all supported primitive types are present in the returned final
- // result bundle are present.
- String script =
- "function finalize_all(data, state)\n"
- + " result = {}\n"
- + " result.integer = state.integer + 1\n"
- + " result.number = state.number + 0.1\n"
- + " result.boolean = not state.boolean\n"
- + " result.string = state.string .. \"CADABRA\"\n"
- + " on_script_finished(result)\n"
- + "end\n";
- PersistableBundle previousState = new PersistableBundle();
- previousState.putInt("integer", 1);
- previousState.putDouble("number", 0.1);
- previousState.putBoolean("boolean", false);
- previousState.putString("string", "ABRA");
-
- runScriptAndWaitForResponse(script, "finalize_all", mPublishedData, previousState);
-
- // Verify that keys are preserved but the values are modified as expected.
- assertThat(mFakeScriptExecutorListener.mFinalResult.size()).isEqualTo(4);
- assertThat(mFakeScriptExecutorListener.mFinalResult.getLong("integer")).isEqualTo(2);
- assertThat(mFakeScriptExecutorListener.mFinalResult.getDouble("number")).isEqualTo(0.2);
- assertThat(mFakeScriptExecutorListener.mFinalResult.getBoolean("boolean")).isEqualTo(true);
- assertThat(mFakeScriptExecutorListener.mFinalResult.getString("string")).isEqualTo(
- "ABRACADABRA");
- }
-
- @Test
- public void invokeScript_emptyFinalResultBundle() throws RemoteException {
- String script =
- "function empty_final_result(data, state)\n"
- + " result = {}\n"
- + " on_script_finished(result)\n"
- + "end\n";
-
- runScriptAndWaitForResponse(script, "empty_final_result", mPublishedData, mSavedState);
-
- // If a script returns empty table as the final result, we get an empty bundle.
- assertThat(mFakeScriptExecutorListener.mFinalResult).isNotNull();
- assertThat(mFakeScriptExecutorListener.mFinalResult.size()).isEqualTo(0);
- }
-
- @Test
- public void invokeScript_wrongNumberOfCallbackInputsInOnScriptFinished()
- throws RemoteException {
- String script =
- "function wrong_number_of_outputs_in_on_script_finished(data, state)\n"
- + " result = {}\n"
- + " extra = 1\n"
- + " on_script_finished(result, extra)\n"
- + "end\n";
-
- runScriptAndWaitForResponse(script, "wrong_number_of_outputs_in_on_script_finished",
- mPublishedData, mSavedState);
-
- // We expect to get an error here because we expect only 1 input parameter in
- // on_script_finished.
- assertThat(mFakeScriptExecutorListener.mErrorType).isEqualTo(
- IScriptExecutorListener.ERROR_TYPE_LUA_SCRIPT_ERROR);
- assertThat(mFakeScriptExecutorListener.mMessage).isEqualTo(
- "on_script_finished can push only a single parameter from Lua - a Lua table");
- }
-
- @Test
- public void invokeScript_wrongNumberOfCallbackInputsInOnSuccess() throws RemoteException {
- String script =
- "function wrong_number_of_outputs_in_on_success(data, state)\n"
- + " result = {}\n"
- + " extra = 1\n"
- + " on_success(result, extra)\n"
- + "end\n";
-
- runScriptAndWaitForResponse(script, "wrong_number_of_outputs_in_on_success",
- mPublishedData, mSavedState);
-
- // We expect to get an error here because we expect only 1 input parameter in on_success.
- assertThat(mFakeScriptExecutorListener.mErrorType).isEqualTo(
- IScriptExecutorListener.ERROR_TYPE_LUA_SCRIPT_ERROR);
- assertThat(mFakeScriptExecutorListener.mMessage).isEqualTo(
- "on_success can push only a single parameter from Lua - a Lua table");
- }
-
- @Test
- public void invokeScript_wrongTypeInOnSuccess() throws RemoteException {
- String script =
- "function wrong_type_in_on_success(data, state)\n"
- + " result = 1\n"
- + " on_success(result)\n"
- + "end\n";
-
- runScriptAndWaitForResponse(script, "wrong_type_in_on_success",
- mPublishedData, mSavedState);
-
- // We expect to get an error here because the type of the input parameter for on_success
- // must be a Lua table.
- assertThat(mFakeScriptExecutorListener.mErrorType).isEqualTo(
- IScriptExecutorListener.ERROR_TYPE_LUA_SCRIPT_ERROR);
- assertThat(mFakeScriptExecutorListener.mMessage).isEqualTo(
- "on_success can push only a single parameter from Lua - a Lua table");
- }
-
- @Test
- public void invokeScript_wrongTypeInOnScriptFinished() throws RemoteException {
- String script =
- "function wrong_type_in_on_script_finished(data, state)\n"
- + " result = 1\n"
- + " on_success(result)\n"
- + "end\n";
-
- runScriptAndWaitForResponse(script, "wrong_type_in_on_script_finished",
- mPublishedData, mSavedState);
-
- // We expect to get an error here because the type of the input parameter for
- // on_script_finished must be a Lua table.
- assertThat(mFakeScriptExecutorListener.mErrorType).isEqualTo(
- IScriptExecutorListener.ERROR_TYPE_LUA_SCRIPT_ERROR);
- assertThat(mFakeScriptExecutorListener.mMessage).isEqualTo(
- "on_success can push only a single parameter from Lua - a Lua table");
- }
-
- @Test
- public void invokeScriptLargeInput_largePublishedData() throws Exception {
- // Verifies that large input does not overwhelm Binder's buffer because pipes are used
- // instead.
- String script =
- "function large_published_data(data, state)\n"
- + " sum = 0\n"
- + " for _, val in ipairs(data.array) do\n"
- + " sum = sum + val\n"
- + " end\n"
- + " result = {total = sum}\n"
- + " on_script_finished(result)\n"
- + "end\n";
-
- ParcelFileDescriptor[] fds = ParcelFileDescriptor.createPipe();
- ParcelFileDescriptor writeFd = fds[1];
- ParcelFileDescriptor readFd = fds[0];
-
- PersistableBundle bundle = new PersistableBundle();
- int n = 1 << 20; // 1024 * 1024 values, roughly 1 Million.
- long[] array8Mb = new long[n];
- for (int i = 0; i < n; i++) {
- array8Mb[i] = i;
- }
- bundle.putLongArray("array", array8Mb);
- long expectedSum =
- (array8Mb[0] + array8Mb[n - 1]) * n / 2; // sum of an arithmetic sequence.
-
- mScriptExecutor.invokeScriptForLargeInput(script, "large_published_data", readFd,
- mSavedState,
- mFakeScriptExecutorListener);
-
- readFd.close();
- try (OutputStream outputStream = new ParcelFileDescriptor.AutoCloseOutputStream(writeFd)) {
- bundle.writeToStream(outputStream);
- }
-
- boolean gotResponse = mFakeScriptExecutorListener.mResponseLatch.await(
- SCRIPT_PROCESSING_TIMEOUT_SEC,
- TimeUnit.SECONDS);
-
- assertWithMessage("Failed to get the callback method called by the script on time")
- .that(gotResponse).isTrue();
- assertThat(mFakeScriptExecutorListener.mFinalResult.size()).isEqualTo(1);
- assertThat(mFakeScriptExecutorListener.mFinalResult.getLong("total"))
- .isEqualTo(expectedSum);
- }
-
- @Test
- public void invokeScript_bothPublishedDataAndPreviousStateAreProvided() throws RemoteException {
- // Verifies that both published data and previous state PersistableBundles
- // are piped into script.
- String script =
- "function data_and_state(data, state)\n"
- + " result = {answer = data.a .. data.b .. state.c .. state.d}\n"
- + " on_script_finished(result)\n"
- + "end\n";
-
- PersistableBundle publishedData = new PersistableBundle();
- publishedData.putString("a", "A");
- publishedData.putString("b", "B");
-
- PersistableBundle previousState = new PersistableBundle();
- previousState.putString("c", "C");
- previousState.putString("d", "D");
-
- runScriptAndWaitForResponse(script, "data_and_state", publishedData, previousState);
-
- // Lua script combines both input published data and previous state into a single result.
- assertThat(mFakeScriptExecutorListener.mFinalResult).isNotNull();
- assertThat(mFakeScriptExecutorListener.mFinalResult.size()).isEqualTo(1);
- assertThat(mFakeScriptExecutorListener.mFinalResult.getString("answer")).isEqualTo(
- "ABCD");
- }
-
- @Test
- public void invokeScript_outputIntAndLongAreTreatedAsLong() throws RemoteException {
- // Verifies that we treat output both integer and long as long integer type although we
- // distinguish between int and long in the script input.
- String script =
- "function int_and_long_are_output_long(data, state)\n"
- + " result = {int = data.int, long = state.long}\n"
- + " on_script_finished(result)\n"
- + "end\n";
-
- PersistableBundle publishedData = new PersistableBundle();
- publishedData.putInt("int", 100);
-
- PersistableBundle previousState = new PersistableBundle();
- previousState.putLong("long", 200);
-
- runScriptAndWaitForResponse(script, "int_and_long_are_output_long",
- publishedData, previousState);
-
- // If a script returns empty table as the final result, we get an empty bundle.
- assertThat(mFakeScriptExecutorListener.mFinalResult).isNotNull();
- assertThat(mFakeScriptExecutorListener.mFinalResult.size()).isEqualTo(2);
- // getInt should always return "empty" value (zero) because all integer types are treated
- // as Java long.
- assertThat(mFakeScriptExecutorListener.mFinalResult.getInt("int")).isEqualTo(0);
- assertThat(mFakeScriptExecutorListener.mFinalResult.getInt("long")).isEqualTo(0);
- // Instead all expected integer values are successfully retrieved using getLong method
- // from the output bundle.
- assertThat(mFakeScriptExecutorListener.mFinalResult.getLong("int")).isEqualTo(100);
- assertThat(mFakeScriptExecutorListener.mFinalResult.getLong("long")).isEqualTo(200);
- }
-
- @Test
- public void invokeScript_nonUTFCharactersDoNotCauseErrors() throws RemoteException {
- // Tries to create an output string value that does not conform to Modified UTF-8.
- // JNI gracefully handles it by parsing on the string as is.
- String script =
- "function non_utf_key_string(data, state)\n"
- + " result = {answer = \"i\0np\200\200ut\"}\n"
- + " on_script_finished(result)\n"
- + "end\n";
-
- runScriptAndWaitForResponse(script, "non_utf_key_string", new PersistableBundle(),
- new PersistableBundle());
-
- // The output will still have all characters, including those that do not conform to
- // Modified UTF-8.
- assertThat(mFakeScriptExecutorListener.mFinalResult).isNotNull();
- assertThat(mFakeScriptExecutorListener.mFinalResult.size()).isEqualTo(1);
- assertThat(mFakeScriptExecutorListener.mFinalResult.getString("answer")).isEqualTo(
- "i\0np\200\200ut");
- }
-
- @Test
- public void invokeScript_wrongFunctionNameProvided() throws RemoteException {
- // Verifies that not specifying function name correctly is handled through error callback.
- String script =
- "function correct_function(data, state)\n"
- + "end\n";
-
- runScriptAndWaitForError(script, "wrong_function");
-
- // Verify that the expected error is received.
- assertThat(mFakeScriptExecutorListener.mErrorType).isEqualTo(
- IScriptExecutorListener.ERROR_TYPE_LUA_RUNTIME_ERROR);
- assertThat(mFakeScriptExecutorListener.mMessage).contains(
- "Wrong function name");
- }
-
- @Test
- public void invokeScript_runtimeErrorDueToSyntax() throws RemoteException {
- // Verifies that syntax errors during script loading are handled gracefully.
- String script =
- "function wrong_syntax(data, state)\n"
- + " x == 1\n"
- + "end\n";
-
- runScriptAndWaitForError(script, "wrong_syntax");
-
- // Verify that the expected error is received.
- assertThat(mFakeScriptExecutorListener.mErrorType).isEqualTo(
- IScriptExecutorListener.ERROR_TYPE_LUA_RUNTIME_ERROR);
- assertThat(mFakeScriptExecutorListener.mMessage).contains(
- "Error encountered while loading the script");
- }
-
- @Test
- public void invokeScript_runtimeErrorDueToUndefinedMethod() throws RemoteException {
- // Verifies that runtime errors encountered during Lua script execution trigger an error
- // returned via a callback.
- String script =
- "function runtime_error(data, state)\n"
- + " on_problem(data, state)\n"
- + "end\n";
-
- runScriptAndWaitForError(script, "runtime_error");
-
- // Verify that the expected error is received.
- assertThat(mFakeScriptExecutorListener.mErrorType).isEqualTo(
- IScriptExecutorListener.ERROR_TYPE_LUA_RUNTIME_ERROR);
- assertThat(mFakeScriptExecutorListener.mMessage).contains(
- "Error encountered while running the script");
- }
-
- @Test
- public void invokeScript_returnedValuesOfUnsupportedTypesReturnError() throws RemoteException {
- // Verifies that if we try to return a value of unsupported type, we get an error instead.
- // In this case, the unsupported type is LUA_TFUNCTION type.
- String script =
- "function function_type(data, state)\n"
- + " result = {fn = function_type}\n"
- + " on_success(result)\n"
- + "end\n";
-
- runScriptAndWaitForResponse(script, "function_type", mPublishedData, mSavedState);
-
- // Verify that the expected error is received.
- assertThat(mFakeScriptExecutorListener.mErrorType).isEqualTo(
- IScriptExecutorListener.ERROR_TYPE_LUA_SCRIPT_ERROR);
- assertThat(mFakeScriptExecutorListener.mMessage).contains(
- "has a Lua type=function, which is not supported yet");
- }
-
- @Test
- public void invokeScript_returnedFloatingArraysNotSupported() throws RemoteException {
- // Verifies that we do not support return values that contain floating number arrays.
- String script =
- "function floating_point_arrays(data, state)\n"
- + " array = {}\n"
- + " array[0] = 1.1\n"
- + " array[1] = 1.2\n"
- + " result = {data = array}\n"
- + " on_success(result)\n"
- + "end\n";
-
- runScriptAndWaitForResponse(script, "floating_point_arrays", mPublishedData, mSavedState);
-
- // Verify that the expected error is received.
- assertThat(mFakeScriptExecutorListener.mErrorType).isEqualTo(
- IScriptExecutorListener.ERROR_TYPE_LUA_SCRIPT_ERROR);
- assertThat(mFakeScriptExecutorListener.mMessage).contains(
- "a floating number array, which is not supported yet");
- }
-
- @Test
- public void invokeScript_returnedBooleanArraysNotSupported() throws RemoteException {
- // Verifies that we do not yet support return values that contain boolean arrays.
- String script =
- "function array_of_booleans(data, state)\n"
- + " array = {}\n"
- + " array[0] = false\n"
- + " array[1] = true\n"
- + " result = {data = array}\n"
- + " on_success(result)\n"
- + "end\n";
-
- runScriptAndWaitForResponse(script, "array_of_booleans", mPublishedData, mSavedState);
-
- // Verify that the expected error is received.
- assertThat(mFakeScriptExecutorListener.mErrorType).isEqualTo(
- IScriptExecutorListener.ERROR_TYPE_LUA_SCRIPT_ERROR);
- assertThat(mFakeScriptExecutorListener.mMessage).contains(
- "is an array with values of type=boolean, which is not supported yet");
- }
-}
-
diff --git a/packages/ScriptExecutor/tests/unit/src/com/android/car/scriptexecutor/JniUtilsTest.java b/packages/ScriptExecutor/tests/unit/src/com/android/car/scriptexecutortest/unit/JniUtilsTest.java
similarity index 98%
rename from packages/ScriptExecutor/tests/unit/src/com/android/car/scriptexecutor/JniUtilsTest.java
rename to packages/ScriptExecutor/tests/unit/src/com/android/car/scriptexecutortest/unit/JniUtilsTest.java
index 3292009..7cf8728 100644
--- a/packages/ScriptExecutor/tests/unit/src/com/android/car/scriptexecutor/JniUtilsTest.java
+++ b/packages/ScriptExecutor/tests/unit/src/com/android/car/scriptexecutortest/unit/JniUtilsTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.car.scriptexecutor;
+package com.android.car.scriptexecutortest.unit;
import static com.google.common.truth.Truth.assertThat;
diff --git a/packages/ScriptExecutor/tests/unit/src/com/android/car/scriptexecutor/JniUtilsTestHelper.cpp b/packages/ScriptExecutor/tests/unit/src/com/android/car/scriptexecutortest/unit/JniUtilsTestHelper.cpp
similarity index 68%
rename from packages/ScriptExecutor/tests/unit/src/com/android/car/scriptexecutor/JniUtilsTestHelper.cpp
rename to packages/ScriptExecutor/tests/unit/src/com/android/car/scriptexecutortest/unit/JniUtilsTestHelper.cpp
index a205b32..57fea30 100644
--- a/packages/ScriptExecutor/tests/unit/src/com/android/car/scriptexecutor/JniUtilsTestHelper.cpp
+++ b/packages/ScriptExecutor/tests/unit/src/com/android/car/scriptexecutortest/unit/JniUtilsTestHelper.cpp
@@ -24,14 +24,16 @@
namespace com {
namespace android {
namespace car {
-namespace scriptexecutor {
+namespace scriptexecutortest {
+namespace unit {
namespace {
template <typename T>
bool hasIntegerArray(JNIEnv* env, jobject object, jlong luaEnginePtr, jstring key, T rawInputArray,
const int arrayLength) {
const char* rawKey = env->GetStringUTFChars(key, nullptr);
- LuaEngine* engine = reinterpret_cast<LuaEngine*>(static_cast<intptr_t>(luaEnginePtr));
+ scriptexecutor::LuaEngine* engine =
+ reinterpret_cast<scriptexecutor::LuaEngine*>(static_cast<intptr_t>(luaEnginePtr));
// Assumes the table is on top of the stack.
auto* luaState = engine->getLuaState();
lua_pushstring(luaState, rawKey);
@@ -69,34 +71,43 @@
#include "lua.h"
-JNIEXPORT jlong JNICALL Java_com_android_car_scriptexecutor_JniUtilsTest_nativeCreateLuaEngine(
- JNIEnv* env, jobject object) {
+JNIEXPORT jlong JNICALL
+Java_com_android_car_scriptexecutortest_unit_JniUtilsTest_nativeCreateLuaEngine(JNIEnv* env,
+ jobject object) {
// Cast first to intptr_t to ensure int can hold the pointer without loss.
- return static_cast<jlong>(reinterpret_cast<intptr_t>(new LuaEngine()));
-}
-
-JNIEXPORT void JNICALL Java_com_android_car_scriptexecutor_JniUtilsTest_nativeDestroyLuaEngine(
- JNIEnv* env, jobject object, jlong luaEnginePtr) {
- delete reinterpret_cast<LuaEngine*>(static_cast<intptr_t>(luaEnginePtr));
+ return static_cast<jlong>(reinterpret_cast<intptr_t>(new scriptexecutor::LuaEngine()));
}
JNIEXPORT void JNICALL
-Java_com_android_car_scriptexecutor_JniUtilsTest_nativePushBundleToLuaTableCaller(
- JNIEnv* env, jobject object, jlong luaEnginePtr, jobject bundle) {
- LuaEngine* engine = reinterpret_cast<LuaEngine*>(static_cast<intptr_t>(luaEnginePtr));
- pushBundleToLuaTable(env, engine->getLuaState(), bundle);
+Java_com_android_car_scriptexecutortest_unit_JniUtilsTest_nativeDestroyLuaEngine(
+ JNIEnv* env, jobject object, jlong luaEnginePtr) {
+ delete reinterpret_cast<scriptexecutor::LuaEngine*>(static_cast<intptr_t>(luaEnginePtr));
}
-JNIEXPORT jint JNICALL Java_com_android_car_scriptexecutor_JniUtilsTest_nativeGetObjectSize(
- JNIEnv* env, jobject object, jlong luaEnginePtr, jint index) {
- LuaEngine* engine = reinterpret_cast<LuaEngine*>(static_cast<intptr_t>(luaEnginePtr));
+JNIEXPORT void JNICALL
+Java_com_android_car_scriptexecutortest_unit_JniUtilsTest_nativePushBundleToLuaTableCaller(
+ JNIEnv* env, jobject object, jlong luaEnginePtr, jobject bundle) {
+ scriptexecutor::LuaEngine* engine =
+ reinterpret_cast<scriptexecutor::LuaEngine*>(static_cast<intptr_t>(luaEnginePtr));
+ scriptexecutor::pushBundleToLuaTable(env, engine->getLuaState(), bundle);
+}
+
+JNIEXPORT jint JNICALL
+Java_com_android_car_scriptexecutortest_unit_JniUtilsTest_nativeGetObjectSize(JNIEnv* env,
+ jobject object,
+ jlong luaEnginePtr,
+ jint index) {
+ scriptexecutor::LuaEngine* engine =
+ reinterpret_cast<scriptexecutor::LuaEngine*>(static_cast<intptr_t>(luaEnginePtr));
return lua_rawlen(engine->getLuaState(), static_cast<int>(index));
}
-JNIEXPORT bool JNICALL Java_com_android_car_scriptexecutor_JniUtilsTest_nativeHasBooleanValue(
+JNIEXPORT bool JNICALL
+Java_com_android_car_scriptexecutortest_unit_JniUtilsTest_nativeHasBooleanValue(
JNIEnv* env, jobject object, jlong luaEnginePtr, jstring key, jboolean value) {
const char* rawKey = env->GetStringUTFChars(key, nullptr);
- LuaEngine* engine = reinterpret_cast<LuaEngine*>(static_cast<intptr_t>(luaEnginePtr));
+ scriptexecutor::LuaEngine* engine =
+ reinterpret_cast<scriptexecutor::LuaEngine*>(static_cast<intptr_t>(luaEnginePtr));
auto* luaState = engine->getLuaState();
lua_pushstring(luaState, rawKey);
env->ReleaseStringUTFChars(key, rawKey);
@@ -110,10 +121,11 @@
return result;
}
-JNIEXPORT bool JNICALL Java_com_android_car_scriptexecutor_JniUtilsTest_nativeHasIntValue(
+JNIEXPORT bool JNICALL Java_com_android_car_scriptexecutortest_unit_JniUtilsTest_nativeHasIntValue(
JNIEnv* env, jobject object, jlong luaEnginePtr, jstring key, jint value) {
const char* rawKey = env->GetStringUTFChars(key, nullptr);
- LuaEngine* engine = reinterpret_cast<LuaEngine*>(static_cast<intptr_t>(luaEnginePtr));
+ scriptexecutor::LuaEngine* engine =
+ reinterpret_cast<scriptexecutor::LuaEngine*>(static_cast<intptr_t>(luaEnginePtr));
// Assumes the table is on top of the stack.
auto* luaState = engine->getLuaState();
lua_pushstring(luaState, rawKey);
@@ -128,10 +140,12 @@
return result;
}
-JNIEXPORT bool JNICALL Java_com_android_car_scriptexecutor_JniUtilsTest_nativeHasDoubleValue(
+JNIEXPORT bool JNICALL
+Java_com_android_car_scriptexecutortest_unit_JniUtilsTest_nativeHasDoubleValue(
JNIEnv* env, jobject object, jlong luaEnginePtr, jstring key, jdouble value) {
const char* rawKey = env->GetStringUTFChars(key, nullptr);
- LuaEngine* engine = reinterpret_cast<LuaEngine*>(static_cast<intptr_t>(luaEnginePtr));
+ scriptexecutor::LuaEngine* engine =
+ reinterpret_cast<scriptexecutor::LuaEngine*>(static_cast<intptr_t>(luaEnginePtr));
// Assumes the table is on top of the stack.
auto* luaState = engine->getLuaState();
lua_pushstring(luaState, rawKey);
@@ -146,10 +160,12 @@
return result;
}
-JNIEXPORT bool JNICALL Java_com_android_car_scriptexecutor_JniUtilsTest_nativeHasStringValue(
+JNIEXPORT bool JNICALL
+Java_com_android_car_scriptexecutortest_unit_JniUtilsTest_nativeHasStringValue(
JNIEnv* env, jobject object, jlong luaEnginePtr, jstring key, jstring value) {
const char* rawKey = env->GetStringUTFChars(key, nullptr);
- LuaEngine* engine = reinterpret_cast<LuaEngine*>(static_cast<intptr_t>(luaEnginePtr));
+ scriptexecutor::LuaEngine* engine =
+ reinterpret_cast<scriptexecutor::LuaEngine*>(static_cast<intptr_t>(luaEnginePtr));
// Assumes the table is on top of the stack.
auto* luaState = engine->getLuaState();
lua_pushstring(luaState, rawKey);
@@ -168,7 +184,8 @@
return result;
}
-JNIEXPORT bool JNICALL Java_com_android_car_scriptexecutor_JniUtilsTest_nativeHasIntArrayValue(
+JNIEXPORT bool JNICALL
+Java_com_android_car_scriptexecutortest_unit_JniUtilsTest_nativeHasIntArrayValue(
JNIEnv* env, jobject object, jlong luaEnginePtr, jstring key, jintArray value) {
jint* rawInputArray = env->GetIntArrayElements(value, nullptr);
const auto kInputLength = env->GetArrayLength(value);
@@ -177,7 +194,8 @@
return result;
}
-JNIEXPORT bool JNICALL Java_com_android_car_scriptexecutor_JniUtilsTest_nativeHasLongArrayValue(
+JNIEXPORT bool JNICALL
+Java_com_android_car_scriptexecutortest_unit_JniUtilsTest_nativeHasLongArrayValue(
JNIEnv* env, jobject object, jlong luaEnginePtr, jstring key, jlongArray value) {
jlong* rawInputArray = env->GetLongArrayElements(value, nullptr);
const auto kInputLength = env->GetArrayLength(value);
@@ -189,7 +207,8 @@
} // extern "C"
} // namespace
-} // namespace scriptexecutor
+} // namespace unit
+} // namespace scriptexecutortest
} // namespace car
} // namespace android
} // namespace com