Skip to content

Commit 8383d78

Browse files
Split by whitespace for commands
This is needed because the ShellExecutor uses ProcessBuilder which is known to have encoding issues as it converts Java Strings to C-strings (byte arrays) with no regards to encoding. In order to avoid, the best we can do here is to avoid using characters which can cause encoding issues (eg:whitespaces). Empty-strings also causes the same behavior for which we will raise an IllegalStateException to protect from that as debugging the unusual behavior is time-consuming without any idea what's going wrong so crashing early will make it much easier. PiperOrigin-RevId: 611160643
1 parent 1f6688b commit 8383d78

File tree

3 files changed

+52
-1
lines changed

3 files changed

+52
-1
lines changed

runner/android_test_orchestrator/java/androidx/test/orchestrator/TestRunnable.java

+21-1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import java.io.InputStream;
3434
import java.io.OutputStream;
3535
import java.util.ArrayList;
36+
import java.util.Collections;
3637
import java.util.List;
3738

3839
/** Runnable to run a single am instrument command to execute a single test. */
@@ -188,19 +189,32 @@ private Bundle getTargetInstrumentationArguments() {
188189
/**
189190
* Instrumentation params are delimited by comma, each param is stripped from leading and trailing
190191
* whitespace.
192+
*
193+
* <p>The order of the params are critical to the correctness here as we split up params that have
194+
* whitespace (eg: key value) into two different params `key` and `value` which means that those
195+
* two different params must be next to each other the entire time.
191196
*/
192197
private List<String> getInstrumentationParamsAndRemoveBundleArgs(Bundle arguments) {
193198
List<String> cleanedParams = new ArrayList<>();
194199
String forwardedArgs = arguments.getString(ORCHESTRATOR_FORWARDED_INSTRUMENTATION_ARGS);
195200
if (forwardedArgs != null) {
196201
for (String param : forwardedArgs.split(",")) {
197-
cleanedParams.add(param.strip());
202+
// ShellExecutor exhibits weird behavior when a param has a whitespace in it.
203+
// so we need to split by white-space to remove the spaces.
204+
Collections.addAll(cleanedParams, param.strip().split(" "));
198205
}
199206
arguments.remove(ORCHESTRATOR_FORWARDED_INSTRUMENTATION_ARGS);
200207
}
201208
return cleanedParams;
202209
}
203210

211+
/**
212+
* This method must maintain the order of the params
213+
*
214+
* <p>The order of the params are critical to the correctness here as we split up params that have
215+
* whitespace (eg: key value) into two different params `key` and `value` which means that those
216+
* two different params must be next to each other the entire time.
217+
*/
204218
private List<String> buildShellParams(Bundle arguments) throws IOException, ClientNotConnected {
205219
List<String> params = new ArrayList<>();
206220
params.add("instrument");
@@ -215,6 +229,12 @@ private List<String> buildShellParams(Bundle arguments) throws IOException, Clie
215229
params.add(arguments.getString(key));
216230
}
217231
params.add(getTargetInstrumentation());
232+
for (String param : params) {
233+
if (param.isEmpty() || param.contains(" ")) {
234+
throw new IllegalStateException(
235+
"Params must not contain any white-space to avoid encoding issues.");
236+
}
237+
}
218238
return params;
219239
}
220240

runner/android_test_orchestrator/javatests/androidx/test/orchestrator/BUILD

+1
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ axt_android_local_test(
6161
"//ext/junit",
6262
"//runner/android_test_orchestrator",
6363
"@maven//:com_google_guava_guava",
64+
"@maven//:com_google_truth_truth",
6465
"@maven//:org_hamcrest_hamcrest_core",
6566
"@maven//:org_hamcrest_hamcrest_library",
6667
],

runner/android_test_orchestrator/javatests/androidx/test/orchestrator/TestRunnableTest.java

+30
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,13 @@
2121
import static org.hamcrest.Matchers.endsWith;
2222
import static org.hamcrest.Matchers.is;
2323
import static org.hamcrest.Matchers.startsWith;
24+
import static org.junit.Assert.assertThrows;
2425

2526
import android.content.Context;
2627
import android.os.Bundle;
2728
import androidx.test.ext.junit.runners.AndroidJUnit4;
2829
import com.google.common.base.Joiner;
30+
import com.google.common.truth.Truth;
2931
import java.io.ByteArrayOutputStream;
3032
import java.io.IOException;
3133
import java.io.InputStream;
@@ -212,6 +214,24 @@ public void testRun_buildsParams_givenInstrumentationParamsAreHandledCorrectlyMu
212214
assertContainsRunnerArgs(runnable.params, "--A --B --C --key value");
213215
}
214216

217+
@Test
218+
public void testRun_buildsParams_givenValueWithSpaceParamThrows() {
219+
arguments.putString("someArgument", "A B");
220+
FakeListener listener = new FakeListener();
221+
FakeTestRunnable runnable =
222+
new FakeTestRunnable(context, "secret", arguments, outputStream, listener, null, true);
223+
assertThrows(IllegalStateException.class, runnable::run);
224+
}
225+
226+
@Test
227+
public void testRun_buildsParams_givenEmptyStringParamThrows() {
228+
arguments.putString("someArgument", "");
229+
FakeListener listener = new FakeListener();
230+
FakeTestRunnable runnable =
231+
new FakeTestRunnable(context, "secret", arguments, outputStream, listener, null, true);
232+
assertThrows(IllegalStateException.class, runnable::run);
233+
}
234+
215235
private static void assertContainsRunnerArgs(List<String> params, String... containsArgs) {
216236
String cmdArgs = Joiner.on(" ").join(params);
217237
assertThat(cmdArgs, startsWith("instrument -w -r"));
@@ -220,4 +240,14 @@ private static void assertContainsRunnerArgs(List<String> params, String... cont
220240
}
221241
assertThat(cmdArgs, endsWith("targetInstrumentation/targetRunner"));
222242
}
243+
244+
@Test
245+
public void testRun_buildsParams_givenInstrumentationParamsWithSpaceMaintainsOrder() {
246+
arguments.putString("orchestratorInstrumentationArgs", "--A,--B,--key value");
247+
FakeListener listener = new FakeListener();
248+
FakeTestRunnable runnable =
249+
new FakeTestRunnable(context, "secret", arguments, outputStream, listener, null, true);
250+
runnable.run();
251+
Truth.assertThat(runnable.params).containsAtLeast("--A", "--B", "--key", "value").inOrder();
252+
}
223253
}

0 commit comments

Comments
 (0)