• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2016, the R8 project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file.
4 package com.android.tools.r8;
5 
6 import static org.junit.Assert.assertEquals;
7 import static org.junit.Assert.assertTrue;
8 import static org.junit.Assert.fail;
9 
10 import com.android.tools.r8.dex.ApplicationReader;
11 import com.android.tools.r8.dex.Constants;
12 import com.android.tools.r8.graph.DexApplication;
13 import com.android.tools.r8.shaking.ProguardRuleParserException;
14 import com.android.tools.r8.utils.AndroidApp;
15 import com.android.tools.r8.utils.InternalOptions;
16 import com.android.tools.r8.utils.ListUtils;
17 import com.android.tools.r8.utils.Timing;
18 import com.google.common.collect.ImmutableList;
19 import com.google.common.collect.ImmutableMap;
20 import com.google.common.collect.ImmutableSet;
21 import com.google.common.collect.Lists;
22 import com.google.common.io.ByteStreams;
23 import com.google.common.io.CharStreams;
24 import java.io.IOException;
25 import java.io.InputStream;
26 import java.io.InputStreamReader;
27 import java.nio.charset.StandardCharsets;
28 import java.nio.file.Files;
29 import java.nio.file.Path;
30 import java.nio.file.Paths;
31 import java.util.ArrayList;
32 import java.util.Arrays;
33 import java.util.Collection;
34 import java.util.Collections;
35 import java.util.LinkedHashMap;
36 import java.util.List;
37 import java.util.Map;
38 import java.util.Set;
39 import java.util.concurrent.ExecutionException;
40 import java.util.function.Consumer;
41 import java.util.stream.Collectors;
42 import joptsimple.internal.Strings;
43 import org.junit.Assume;
44 import org.junit.rules.TemporaryFolder;
45 
46 public class ToolHelper {
47 
48   public static final String BUILD_DIR = "build/";
49   public static final String EXAMPLES_DIR = "src/test/examples/";
50   public static final String EXAMPLES_ANDROID_O_DIR = "src/test/examplesAndroidO/";
51   public static final String EXAMPLES_BUILD_DIR = BUILD_DIR + "test/examples/";
52   public static final String EXAMPLES_ANDROID_N_BUILD_DIR = BUILD_DIR + "test/examplesAndroidN/";
53   public static final String EXAMPLES_ANDROID_O_BUILD_DIR = BUILD_DIR + "test/examplesAndroidO/";
54   public static final String SMALI_BUILD_DIR = BUILD_DIR + "test/smali/";
55 
56   public static final String LINE_SEPARATOR = System.getProperty("line.separator");
57 
58   private static final String ANDROID_JAR_PATTERN = "third_party/android_jar/lib-v%d/android.jar";
59   private static final int DEFAULT_MIN_SDK = 14;
60 
61   public enum DexVm {
62     ART_4_4_4("4.4.4"),
63     ART_5_1_1("5.1.1"),
64     ART_6_0_1("6.0.1"),
65     ART_7_0_0("7.0.0"),
66     ART_DEFAULT("default");
67 
68     private static final ImmutableMap<String, DexVm> SHORT_NAME_MAP =
69         new ImmutableMap.Builder<String, DexVm>()
70             .putAll(
71                 Arrays.stream(DexVm.values()).collect(
72                     Collectors.toMap(a -> a.toString(), a -> a)))
73             .build();
74 
toString()75     public String toString() {
76       return shortName;
77     }
78 
fromShortName(String shortName)79     public static DexVm fromShortName(String shortName) {
80       return SHORT_NAME_MAP.get(shortName);
81     }
82 
isNewerThan(DexVm other)83     public boolean isNewerThan(DexVm other) {
84       return compareTo(other) > 0;
85     }
86 
DexVm(String shortName)87     private DexVm(String shortName) {
88       this.shortName = shortName;
89     }
90 
91     private final String shortName;
92   }
93 
94 
95   public abstract static class CommandBuilder {
96 
97     private List<String> options = new ArrayList<>();
98     private Map<String, String> systemProperties = new LinkedHashMap<>();
99     private List<String> classpaths = new ArrayList<>();
100     private String mainClass;
101     private List<String> programArguments = new ArrayList<>();
102     private List<String> bootClassPaths = new ArrayList<>();
103 
appendArtOption(String option)104     public CommandBuilder appendArtOption(String option) {
105       options.add(option);
106       return this;
107     }
108 
appendArtSystemProperty(String key, String value)109     public CommandBuilder appendArtSystemProperty(String key, String value) {
110       systemProperties.put(key, value);
111       return this;
112     }
113 
appendClasspath(String classpath)114     public CommandBuilder appendClasspath(String classpath) {
115       classpaths.add(classpath);
116       return this;
117     }
118 
setMainClass(String className)119     public CommandBuilder setMainClass(String className) {
120       this.mainClass = className;
121       return this;
122     }
123 
appendProgramArgument(String option)124     public CommandBuilder appendProgramArgument(String option) {
125       programArguments.add(option);
126       return this;
127     }
128 
appendBootClassPath(String lib)129     public CommandBuilder appendBootClassPath(String lib) {
130       bootClassPaths.add(lib);
131       return this;
132     }
133 
command()134     private List<String> command() {
135       List<String> result = new ArrayList<>();
136       // The art script _must_ be run with bash, bots default to /bin/dash for /bin/sh, so
137       // explicitly set it;
138       if (isLinux()) {
139         result.add("/bin/bash");
140       } else {
141         result.add("tools/docker/run.sh");
142       }
143       result.add(getExecutable());
144       for (String option : options) {
145         result.add(option);
146       }
147       for (Map.Entry<String, String> entry : systemProperties.entrySet()) {
148         StringBuilder builder = new StringBuilder("-D");
149         builder.append(entry.getKey());
150         builder.append("=");
151         builder.append(entry.getValue());
152         result.add(builder.toString());
153       }
154       if (!classpaths.isEmpty()) {
155         result.add("-cp");
156         result.add(Strings.join(classpaths, ":"));
157       }
158       if (!bootClassPaths.isEmpty()) {
159         result.add("-Xbootclasspath:" + String.join(":", bootClassPaths));
160       }
161       if (mainClass != null) {
162         result.add(mainClass);
163       }
164       for (String argument : programArguments) {
165         result.add(argument);
166       }
167       return result;
168     }
169 
asProcessBuilder()170     public ProcessBuilder asProcessBuilder() {
171       return new ProcessBuilder(command());
172     }
173 
build()174     public String build() {
175       return String.join(" ", command());
176     }
177 
getExecutable()178     protected abstract String getExecutable();
179   }
180 
181   public static class ArtCommandBuilder extends CommandBuilder {
182 
183     private DexVm version;
184 
ArtCommandBuilder()185     public ArtCommandBuilder() {
186     }
187 
ArtCommandBuilder(DexVm version)188     public ArtCommandBuilder(DexVm version) {
189       assert ART_BINARY_VERSIONS.containsKey(version);
190       this.version = version;
191     }
192 
193     @Override
getExecutable()194     protected String getExecutable() {
195       return version != null ? getArtBinary(version) : getArtBinary();
196     }
197   }
198 
199   public static class DXCommandBuilder extends CommandBuilder {
200 
DXCommandBuilder()201     public DXCommandBuilder() {
202       appendProgramArgument("--dex");
203     }
204 
205     @Override
getExecutable()206     protected String getExecutable() {
207       return DX;
208     }
209   }
210 
211   private static class StreamReader implements Runnable {
212 
213     private InputStream stream;
214     private String result;
215 
StreamReader(InputStream stream)216     public StreamReader(InputStream stream) {
217       this.stream = stream;
218     }
219 
getResult()220     public String getResult() {
221       return result;
222     }
223 
224     @Override
run()225     public void run() {
226       try {
227         result = CharStreams.toString(new InputStreamReader(stream, StandardCharsets.UTF_8));
228         stream.close();
229       } catch (IOException e) {
230         result = "Failed reading result for stream " + stream;
231       }
232     }
233   }
234 
235   private static final String TOOLS = "tools";
236   private static final Map<DexVm, String> ART_DIRS =
237       ImmutableMap.of(
238           DexVm.ART_DEFAULT, "art",
239           DexVm.ART_7_0_0, "art-7.0.0",
240           DexVm.ART_6_0_1, "art-6.0.1",
241           DexVm.ART_5_1_1, "art-5.1.1",
242           DexVm.ART_4_4_4, "dalvik");
243   private static final Map<DexVm, String> ART_BINARY_VERSIONS =
244       ImmutableMap.of(
245           DexVm.ART_DEFAULT, "bin/art",
246           DexVm.ART_7_0_0, "bin/art",
247           DexVm.ART_6_0_1, "bin/art",
248           DexVm.ART_5_1_1, "bin/art",
249           DexVm.ART_4_4_4, "bin/dalvik");
250 
251   private static final Map<DexVm, String> ART_BINARY_VERSIONS_X64 =
252       ImmutableMap.of(
253           DexVm.ART_DEFAULT, "bin/art",
254           DexVm.ART_7_0_0, "bin/art",
255           DexVm.ART_6_0_1, "bin/art");
256   private static final List<String> ART_BOOT_LIBS =
257       ImmutableList.of(
258           "core-libart-hostdex.jar",
259           "core-oj-hostdex.jar",
260           "apache-xml-hostdex.jar"
261       );
262 
263   private static final String LIB_PATH = TOOLS + "/linux/art/lib";
264   private static final String DX = TOOLS + "/linux/dx/bin/dx";
265   private static final String DEX2OAT = TOOLS + "/linux/art/bin/dex2oat";
266   private static final String ANGLER_DIR = TOOLS + "/linux/art/product/angler";
267   private static final String ANGLER_BOOT_IMAGE = ANGLER_DIR + "/system/framework/boot.art";
268 
getArtDir(DexVm version)269   public static String getArtDir(DexVm version) {
270     String dir = ART_DIRS.get(version);
271     if (dir == null) {
272       throw new IllegalStateException("Does not support dex vm: " + version);
273     }
274     if (isLinux() || isMac()) {
275       // The Linux version is used on Mac, where it is run in a Docker container.
276       return TOOLS + "/linux/" + dir;
277     }
278     fail("Unsupported platform, we currently only support mac and linux: " + getPlatform());
279     return ""; //never here
280   }
281 
getArtBinary(DexVm version)282   public static String getArtBinary(DexVm version) {
283     String binary = ART_BINARY_VERSIONS.get(version);
284     if (binary == null) {
285       throw new IllegalStateException("Does not support running with dex vm: " + version);
286     }
287     return getArtDir(version) + "/" + binary;
288   }
289 
getDefaultAndroidJar()290   public static String getDefaultAndroidJar() {
291     return getAndroidJar(Constants.DEFAULT_ANDROID_API);
292   }
293 
getAndroidJar(int minSdkVersion)294   public static String getAndroidJar(int minSdkVersion) {
295     return String.format(
296         ANDROID_JAR_PATTERN,
297         minSdkVersion == Constants.DEFAULT_ANDROID_API ? DEFAULT_MIN_SDK : minSdkVersion);
298   }
299 
getJdwpTestsJarPath(int minSdk)300   public static Path getJdwpTestsJarPath(int minSdk) {
301     if (minSdk >= Constants.ANDROID_N_API) {
302       return Paths.get("third_party", "jdwp-tests", "apache-harmony-jdwp-tests-host.jar");
303     } else {
304       return Paths.get(ToolHelper.BUILD_DIR, "libs", "jdwp-tests-preN.jar");
305     }
306   }
307 
308   // For non-Linux platforms create the temporary directory in the repository root to simplify
309   // running Art in a docker container
getTemporaryFolderForTest()310   public static TemporaryFolder getTemporaryFolderForTest() {
311     return new TemporaryFolder(ToolHelper.isLinux() ? null : Paths.get("build", "tmp").toFile());
312   }
313 
getArtBinary()314   public static String getArtBinary() {
315     return getArtBinary(getDexVm());
316   }
317 
getArtVersions()318   public static Set<DexVm> getArtVersions() {
319     String artVersion = System.getProperty("dex_vm");
320     if (artVersion != null) {
321       DexVm artVersionEnum = getDexVm();
322       if (!ART_BINARY_VERSIONS.containsKey(artVersionEnum)) {
323         throw new RuntimeException("Unsupported Art version " + artVersion);
324       }
325       return ImmutableSet.of(artVersionEnum);
326     } else {
327       if (isLinux()) {
328         return ART_BINARY_VERSIONS.keySet();
329       } else {
330         return ART_BINARY_VERSIONS_X64.keySet();
331       }
332     }
333   }
334 
getArtBootLibs()335   public static List<String> getArtBootLibs() {
336     String prefix = getArtDir(getDexVm()) + "/";
337     List<String> result = new ArrayList<>();
338     ART_BOOT_LIBS.stream().forEach(x -> result.add(prefix + "framework/" + x));
339     return result;
340   }
341 
342   // Returns if the passed in vm to use is the default.
isDefaultDexVm()343   public static boolean isDefaultDexVm() {
344     return getDexVm() == DexVm.ART_DEFAULT;
345   }
346 
getDexVm()347   public static DexVm getDexVm() {
348     String artVersion = System.getProperty("dex_vm");
349     if (artVersion == null) {
350       return DexVm.ART_DEFAULT;
351     } else {
352       DexVm artVersionEnum = DexVm.fromShortName(artVersion);
353       if (artVersionEnum == null) {
354         throw new RuntimeException("Unsupported Art version " + artVersion);
355       } else {
356         return artVersionEnum;
357       }
358     }
359   }
360 
getMinApiLevelForDexVm(DexVm dexVm)361   public static int getMinApiLevelForDexVm(DexVm dexVm) {
362     switch (dexVm) {
363       case ART_DEFAULT:
364         return Constants.ANDROID_O_API;
365       case ART_7_0_0:
366         return Constants.ANDROID_N_API;
367       default:
368         return Constants.DEFAULT_ANDROID_API;
369     }
370   }
371 
getPlatform()372   private static String getPlatform() {
373     return System.getProperty("os.name");
374   }
375 
isLinux()376   public static boolean isLinux() {
377     return getPlatform().startsWith("Linux");
378   }
379 
isMac()380   public static boolean isMac() {
381     return getPlatform().startsWith("Mac");
382   }
383 
isWindows()384   public static boolean isWindows() {
385     return getPlatform().startsWith("Windows");
386   }
387 
artSupported()388   public static boolean artSupported() {
389     if (!isLinux() && !isMac()) {
390       System.err.println("Testing on your platform is not fully supported. " +
391           "Art does not work on on your platform.");
392       return false;
393     }
394     return true;
395   }
396 
getClassPathForTests()397   public static Path getClassPathForTests() {
398     return Paths.get(BUILD_DIR, "classes", "test");
399   }
400 
getClassFileForTestClass(Class clazz)401   public static Path getClassFileForTestClass(Class clazz) {
402     List<String> parts = Lists.newArrayList(clazz.getCanonicalName().split("\\."));
403     Class enclosing = clazz;
404     while (enclosing.getEnclosingClass() != null) {
405       parts.set(parts.size() - 2, parts.get(parts.size() - 2) + "$" + parts.get(parts.size() - 1));
406       parts.remove(parts.size() - 1);
407       enclosing = clazz.getEnclosingClass();
408     }
409     parts.set(parts.size() - 1, parts.get(parts.size() - 1) + ".class");
410     return getClassPathForTests().resolve(
411         Paths.get("", parts.toArray(new String[parts.size() - 1])));
412   }
413 
buildApplication(List<String> fileNames)414   public static DexApplication buildApplication(List<String> fileNames)
415       throws IOException, ExecutionException {
416     return new ApplicationReader(
417             AndroidApp.fromProgramFiles(ListUtils.map(fileNames, Paths::get)),
418             new InternalOptions(),
419             new Timing("ToolHelper buildApplication"))
420         .read();
421   }
422 
prepareR8CommandBuilder(AndroidApp app)423   public static R8Command.Builder prepareR8CommandBuilder(AndroidApp app) {
424     return R8Command.builder(app);
425   }
426 
runR8(AndroidApp app)427   public static AndroidApp runR8(AndroidApp app)
428       throws ExecutionException, IOException, ProguardRuleParserException, CompilationException {
429     return runR8(R8Command.builder(app).build());
430   }
431 
runR8(AndroidApp app, Path output)432   public static AndroidApp runR8(AndroidApp app, Path output)
433       throws ExecutionException, IOException, ProguardRuleParserException, CompilationException {
434     assert output != null;
435     return runR8(R8Command.builder(app).setOutputPath(output).build());
436   }
437 
runR8(AndroidApp app, Consumer<InternalOptions> optionsConsumer)438   public static AndroidApp runR8(AndroidApp app, Consumer<InternalOptions> optionsConsumer)
439       throws ProguardRuleParserException, ExecutionException, IOException, CompilationException {
440     return runR8(R8Command.builder(app).build(), optionsConsumer);
441   }
442 
runR8(R8Command command)443   public static AndroidApp runR8(R8Command command)
444       throws ProguardRuleParserException, ExecutionException, IOException {
445     return runR8(command, null);
446   }
447 
runR8(R8Command command, Consumer<InternalOptions> optionsConsumer)448   public static AndroidApp runR8(R8Command command, Consumer<InternalOptions> optionsConsumer)
449       throws ProguardRuleParserException, ExecutionException, IOException {
450     return runR8WithFullResult(command, optionsConsumer).androidApp;
451   }
452 
runR8WithFullResult( R8Command command, Consumer<InternalOptions> optionsConsumer)453   public static CompilationResult runR8WithFullResult(
454         R8Command command, Consumer<InternalOptions> optionsConsumer)
455         throws ProguardRuleParserException, ExecutionException, IOException {
456    // TODO(zerny): Should we really be adding the android library in ToolHelper?
457     AndroidApp app = command.getInputApp();
458     if (app.getClassLibraryResources().isEmpty()) {
459       app =
460           AndroidApp.builder(app)
461               .addLibraryFiles(Paths.get(getAndroidJar(command.getMinApiLevel())))
462               .build();
463     }
464     InternalOptions options = command.getInternalOptions();
465     // TODO(zerny): Should we really be setting this in ToolHelper?
466     options.ignoreMissingClasses = true;
467     if (optionsConsumer != null) {
468       optionsConsumer.accept(options);
469     }
470     CompilationResult result = R8.runForTesting(app, options);
471     R8.writeOutputs(command, options, result.androidApp);
472     return result;
473   }
474 
runR8(String fileName, String out)475   public static AndroidApp runR8(String fileName, String out)
476       throws IOException, ProguardRuleParserException, ExecutionException, CompilationException {
477     return runR8(Collections.singletonList(fileName), out);
478   }
479 
runR8(Collection<String> fileNames, String out)480   public static AndroidApp runR8(Collection<String> fileNames, String out)
481       throws IOException, ProguardRuleParserException, ExecutionException, CompilationException {
482     return R8.run(
483         R8Command.builder()
484             .addProgramFiles(ListUtils.map(fileNames, Paths::get))
485             .setOutputPath(Paths.get(out))
486             .setIgnoreMissingClasses(true)
487             .build());
488   }
489 
runD8(AndroidApp app)490   public static AndroidApp runD8(AndroidApp app) throws CompilationException, IOException {
491     return runD8(app, null);
492   }
493 
runD8(AndroidApp app, Consumer<InternalOptions> optionsConsumer)494   public static AndroidApp runD8(AndroidApp app, Consumer<InternalOptions> optionsConsumer)
495       throws CompilationException, IOException {
496     return runD8(D8Command.builder(app).build(), optionsConsumer);
497   }
498 
runD8(D8Command command)499   public static AndroidApp runD8(D8Command command) throws IOException {
500     return runD8(command, null);
501   }
502 
runD8(D8Command command, Consumer<InternalOptions> optionsConsumer)503   public static AndroidApp runD8(D8Command command, Consumer<InternalOptions> optionsConsumer)
504       throws IOException {
505     InternalOptions options = command.getInternalOptions();
506     if (optionsConsumer != null) {
507       optionsConsumer.accept(options);
508     }
509     AndroidApp result = D8.runForTesting(command.getInputApp(), options).androidApp;
510     if (command.getOutputPath() != null) {
511       result.write(command.getOutputPath(), command.getOutputMode());
512     }
513     return result;
514   }
515 
runDexer(String fileName, String outDir, String... extraArgs)516   public static AndroidApp runDexer(String fileName, String outDir, String... extraArgs)
517       throws IOException {
518     List<String> args = new ArrayList<>();
519     Collections.addAll(args, extraArgs);
520     Collections.addAll(args, "--output=" + outDir + "/classes.dex", fileName);
521     int result = runDX(args.toArray(new String[args.size()])).exitCode;
522     return result != 0 ? null : AndroidApp.fromProgramDirectory(Paths.get(outDir));
523   }
524 
runDX(String[] args)525   public static ProcessResult runDX(String[] args) throws IOException {
526     Assume.assumeTrue(ToolHelper.artSupported());
527     DXCommandBuilder builder = new DXCommandBuilder();
528     for (String arg : args) {
529       builder.appendProgramArgument(arg);
530     }
531     return runProcess(builder.asProcessBuilder());
532   }
533 
runJava(Class clazz)534   public static ProcessResult runJava(Class clazz) throws Exception {
535     String main = clazz.getCanonicalName();
536     Path path = getClassPathForTests();
537     return runJava(ImmutableList.of(path.toString()), main);
538   }
539 
runJava(List<String> classpath, String mainClass)540   public static ProcessResult runJava(List<String> classpath, String mainClass) throws IOException {
541     ProcessBuilder builder = new ProcessBuilder(
542         getJavaExecutable(), "-cp", String.join(":", classpath), mainClass);
543     return runProcess(builder);
544   }
545 
forkD8(Path dir, String... args)546   public static ProcessResult forkD8(Path dir, String... args)
547       throws IOException, InterruptedException {
548     return forkJava(dir, D8.class, args);
549   }
550 
forkR8(Path dir, String... args)551   public static ProcessResult forkR8(Path dir, String... args)
552       throws IOException, InterruptedException {
553     return forkJava(dir, R8.class, ImmutableList.builder()
554         .addAll(Arrays.asList(args))
555         .add("--ignore-missing-classes")
556         .build()
557         .toArray(new String[0]));
558   }
559 
forkJava(Path dir, Class clazz, String... args)560   private static ProcessResult forkJava(Path dir, Class clazz, String... args)
561       throws IOException, InterruptedException {
562     List<String> command = new ImmutableList.Builder<String>()
563         .add(getJavaExecutable())
564         .add("-cp").add(System.getProperty("java.class.path"))
565         .add(clazz.getCanonicalName())
566         .addAll(Arrays.asList(args))
567         .build();
568     return runProcess(new ProcessBuilder(command).directory(dir.toFile()));
569   }
570 
getJavaExecutable()571   public static String getJavaExecutable() {
572     return Paths.get(System.getProperty("java.home"), "bin", "java").toString();
573   }
574 
runArtNoVerificationErrors(String file, String mainClass)575   public static String runArtNoVerificationErrors(String file, String mainClass)
576       throws IOException {
577     return runArtNoVerificationErrors(Collections.singletonList(file), mainClass, null);
578   }
579 
runArtNoVerificationErrors(List<String> files, String mainClass, Consumer<ArtCommandBuilder> extras)580   public static String runArtNoVerificationErrors(List<String> files, String mainClass,
581       Consumer<ArtCommandBuilder> extras)
582       throws IOException {
583     return runArtNoVerificationErrors(files, mainClass, extras, null);
584   }
585 
runArtNoVerificationErrors(List<String> files, String mainClass, Consumer<ArtCommandBuilder> extras, DexVm version)586   public static String runArtNoVerificationErrors(List<String> files, String mainClass,
587       Consumer<ArtCommandBuilder> extras,
588       DexVm version)
589       throws IOException {
590     ArtCommandBuilder builder =
591         version != null ? new ArtCommandBuilder(version) : new ArtCommandBuilder();
592     files.forEach(builder::appendClasspath);
593     builder.setMainClass(mainClass);
594     if (extras != null) {
595       extras.accept(builder);
596     }
597     return runArtNoVerificationErrors(builder);
598   }
599 
runArtNoVerificationErrors(ArtCommandBuilder builder)600   public static String runArtNoVerificationErrors(ArtCommandBuilder builder) throws IOException {
601     ProcessResult result = runArtProcess(builder);
602     if (result.stderr.contains("Verification error")) {
603       fail("Verification error: \n" + result.stderr);
604     }
605     return result.stdout;
606   }
607 
runArtProcess(ArtCommandBuilder builder)608   private static ProcessResult runArtProcess(ArtCommandBuilder builder) throws IOException {
609     Assume.assumeTrue(ToolHelper.artSupported());
610     ProcessResult result = runProcess(builder.asProcessBuilder());
611     if (result.exitCode != 0) {
612       fail("Unexpected art failure: '" + result.stderr + "'\n" + result.stdout);
613     }
614     return result;
615   }
616 
runArt(ArtCommandBuilder builder)617   public static String runArt(ArtCommandBuilder builder) throws IOException {
618     ProcessResult result = runArtProcess(builder);
619     return result.stdout;
620   }
621 
checkArtOutputIdentical(String file1, String file2, String mainClass, DexVm version)622   public static String checkArtOutputIdentical(String file1, String file2, String mainClass,
623       DexVm version)
624       throws IOException {
625     return checkArtOutputIdentical(Collections.singletonList(file1),
626         Collections.singletonList(file2), mainClass, null, version);
627   }
628 
checkArtOutputIdentical(List<String> files1, List<String> files2, String mainClass, Consumer<ArtCommandBuilder> extras, DexVm version)629   public static String checkArtOutputIdentical(List<String> files1, List<String> files2,
630       String mainClass,
631       Consumer<ArtCommandBuilder> extras,
632       DexVm version)
633       throws IOException {
634     // Run art on original.
635     for (String file : files1) {
636       assertTrue("file1 " + file + " must exists", Files.exists(Paths.get(file)));
637     }
638     String output1 = ToolHelper.runArtNoVerificationErrors(files1, mainClass, extras, version);
639     // Run art on R8 processed version.
640     for (String file : files2) {
641       assertTrue("file2 " + file + " must exists", Files.exists(Paths.get(file)));
642     }
643     String output2 = ToolHelper.runArtNoVerificationErrors(files2, mainClass, extras, version);
644     assertEquals(output1, output2);
645     return output1;
646   }
647 
runDex2Oat(Path file, Path outFile)648   public static void runDex2Oat(Path file, Path outFile) throws IOException {
649     Assume.assumeTrue(ToolHelper.artSupported());
650     assert Files.exists(file);
651     assert ByteStreams.toByteArray(Files.newInputStream(file)).length > 0;
652     List<String> command = new ArrayList<>();
653     command.add(DEX2OAT);
654     command.add("--android-root=" + ANGLER_DIR);
655     command.add("--runtime-arg");
656     command.add("-Xnorelocate");
657     command.add("--boot-image=" + ANGLER_BOOT_IMAGE);
658     command.add("--dex-file=" + file.toAbsolutePath());
659     command.add("--oat-file=" + outFile.toAbsolutePath());
660     command.add("--instruction-set=arm64");
661     command.add("--compiler-filter=interpret-only");
662     ProcessBuilder builder = new ProcessBuilder(command);
663     builder.environment().put("LD_LIBRARY_PATH", LIB_PATH);
664     ProcessResult result = runProcess(builder);
665     if (result.exitCode != 0) {
666       fail("dex2oat failed, exit code " + result.exitCode + ", stderr:\n" + result.stderr);
667     }
668     if (result.stderr.contains("Verification error")) {
669       fail("Verification error: \n" + result.stderr);
670     }
671   }
672 
673   public static class ProcessResult {
674 
675     public final int exitCode;
676     public final String stdout;
677     public final String stderr;
678 
ProcessResult(int exitCode, String stdout, String stderr)679     ProcessResult(int exitCode, String stdout, String stderr) {
680       this.exitCode = exitCode;
681       this.stdout = stdout;
682       this.stderr = stderr;
683     }
684 
685     @Override
toString()686     public String toString() {
687       StringBuilder builder = new StringBuilder();
688       builder.append("EXIT CODE: ");
689       builder.append(exitCode);
690       builder.append("\n");
691       builder.append("STDOUT: ");
692       builder.append("\n");
693       builder.append(stdout);
694       builder.append("\n");
695       builder.append("STDERR: ");
696       builder.append("\n");
697       builder.append(stderr);
698       builder.append("\n");
699       return builder.toString();
700     }
701   }
702 
runProcess(ProcessBuilder builder)703   public static ProcessResult runProcess(ProcessBuilder builder) throws IOException {
704     System.out.println(String.join(" ", builder.command()));
705     Process p = builder.start();
706     // Drain stdout and stderr so that the process does not block. Read stdout and stderr
707     // in parallel to make sure that neither buffer can get filled up which will cause the
708     // C program to block in a call to write.
709     StreamReader stdoutReader = new StreamReader(p.getInputStream());
710     StreamReader stderrReader = new StreamReader(p.getErrorStream());
711     Thread stdoutThread = new Thread(stdoutReader);
712     Thread stderrThread = new Thread(stderrReader);
713     stdoutThread.start();
714     stderrThread.start();
715     try {
716       p.waitFor();
717       stdoutThread.join();
718       stderrThread.join();
719     } catch (InterruptedException e) {
720       throw new RuntimeException("Execution interrupted", e);
721     }
722     return new ProcessResult(p.exitValue(), stdoutReader.getResult(), stderrReader.getResult());
723   }
724 
getApp(BaseCommand command)725   public static AndroidApp getApp(BaseCommand command) {
726     return command.getInputApp();
727   }
728 }
729