1418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
2418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager// for details. All rights reserved. Use of this source code is governed by a
3418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager// BSD-style license that can be found in the LICENSE file.
4418d1ca139ea11316113beafbb3b3dd3fd5587aMads Agerpackage com.android.tools.r8.compatdx;
5418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager
6418d1ca139ea11316113beafbb3b3dd3fd5587aMads Agerimport static org.junit.Assert.assertEquals;
7418d1ca139ea11316113beafbb3b3dd3fd5587aMads Agerimport static org.junit.Assert.assertNotEquals;
8418d1ca139ea11316113beafbb3b3dd3fd5587aMads Agerimport static org.junit.Assert.assertTrue;
9418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager
10418d1ca139ea11316113beafbb3b3dd3fd5587aMads Agerimport com.android.tools.r8.ToolHelper;
11854a7d4617a50c95bdbd7efc266c7ba6335b8d0cSøren Gjesseimport com.android.tools.r8.dex.Constants;
12418d1ca139ea11316113beafbb3b3dd3fd5587aMads Agerimport com.android.tools.r8.errors.CompilationError;
13418d1ca139ea11316113beafbb3b3dd3fd5587aMads Agerimport com.android.tools.r8.maindexlist.MainDexListTests;
14854a7d4617a50c95bdbd7efc266c7ba6335b8d0cSøren Gjesseimport com.android.tools.r8.utils.AndroidApp;
15418d1ca139ea11316113beafbb3b3dd3fd5587aMads Agerimport com.android.tools.r8.utils.FileUtils;
16854a7d4617a50c95bdbd7efc266c7ba6335b8d0cSøren Gjesseimport com.android.tools.r8.utils.OutputMode;
17418d1ca139ea11316113beafbb3b3dd3fd5587aMads Agerimport com.android.tools.r8.utils.StringUtils;
18418d1ca139ea11316113beafbb3b3dd3fd5587aMads Agerimport com.android.tools.r8.utils.StringUtils.BraceType;
19854a7d4617a50c95bdbd7efc266c7ba6335b8d0cSøren Gjesseimport com.google.common.collect.ImmutableList;
20418d1ca139ea11316113beafbb3b3dd3fd5587aMads Agerimport java.io.IOException;
21418d1ca139ea11316113beafbb3b3dd3fd5587aMads Agerimport java.nio.file.Files;
22418d1ca139ea11316113beafbb3b3dd3fd5587aMads Agerimport java.nio.file.Path;
23418d1ca139ea11316113beafbb3b3dd3fd5587aMads Agerimport java.nio.file.Paths;
24418d1ca139ea11316113beafbb3b3dd3fd5587aMads Agerimport java.util.ArrayList;
25418d1ca139ea11316113beafbb3b3dd3fd5587aMads Agerimport java.util.Collections;
26418d1ca139ea11316113beafbb3b3dd3fd5587aMads Agerimport java.util.List;
27418d1ca139ea11316113beafbb3b3dd3fd5587aMads Agerimport java.util.Set;
28854a7d4617a50c95bdbd7efc266c7ba6335b8d0cSøren Gjesseimport java.util.concurrent.ExecutionException;
29418d1ca139ea11316113beafbb3b3dd3fd5587aMads Agerimport java.util.stream.Collectors;
30418d1ca139ea11316113beafbb3b3dd3fd5587aMads Agerimport java.util.zip.ZipEntry;
31418d1ca139ea11316113beafbb3b3dd3fd5587aMads Agerimport java.util.zip.ZipFile;
32418d1ca139ea11316113beafbb3b3dd3fd5587aMads Agerimport org.junit.Rule;
33418d1ca139ea11316113beafbb3b3dd3fd5587aMads Agerimport org.junit.Test;
34418d1ca139ea11316113beafbb3b3dd3fd5587aMads Agerimport org.junit.rules.ExpectedException;
35418d1ca139ea11316113beafbb3b3dd3fd5587aMads Agerimport org.junit.rules.TemporaryFolder;
36418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager
37418d1ca139ea11316113beafbb3b3dd3fd5587aMads Agerpublic class CompatDxTests {
38418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager  private static final String EXAMPLE_JAR_FILE1 = "build/test/examples/arithmetic.jar";
39418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager  private static final String EXAMPLE_JAR_FILE2 = "build/test/examples/barray.jar";
40418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager
41418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager  private static final String NO_LOCALS = "--no-locals";
42418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager  private static final String NO_POSITIONS = "--positions=none";
43418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager  private static final String MULTIDEX = "--multi-dex";
44418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager  private static final String NUM_THREADS_5 = "--num-threads=5";
45418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager
46418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager  @Rule
47418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager  public ExpectedException thrown = ExpectedException.none();
48418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager
49418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager  @Rule
50418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager  public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
51418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager
52418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager  @Test
53418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager  public void noFilesTest() throws IOException {
54418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager    runDexer("--no-files");
55418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager  }
56418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager
57418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager  @Test
58418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager  public void noOutputTest() throws IOException {
59418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager    runDexerWithoutOutput(NO_POSITIONS, NO_LOCALS, MULTIDEX, EXAMPLE_JAR_FILE1);
60418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager  }
61418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager
62418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager  @Test
63418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager  public void singleJarInputFile() throws IOException {
64418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager    runDexer(NO_POSITIONS, NO_LOCALS, MULTIDEX, EXAMPLE_JAR_FILE1);
65418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager  }
66418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager
67418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager  @Test
68418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager  public void multipleJarInputFiles() throws IOException {
69418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager    runDexer(NO_POSITIONS, NO_LOCALS, MULTIDEX, EXAMPLE_JAR_FILE1, EXAMPLE_JAR_FILE2);
70418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager  }
71418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager
72418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager  @Test
73418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager  public void outputZipFile() throws IOException {
74418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager    runDexerWithOutput("foo.dex.zip", NO_POSITIONS, NO_LOCALS, MULTIDEX, EXAMPLE_JAR_FILE1);
75418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager  }
76418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager
77418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager  @Test
78418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager  public void useMultipleThreads() throws IOException {
79418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager    runDexer(NUM_THREADS_5, NO_POSITIONS, NO_LOCALS, EXAMPLE_JAR_FILE1);
80418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager  }
81418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager
82418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager  @Test
83418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager  public void withPositions() throws IOException {
84418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager    runDexer(NO_LOCALS, MULTIDEX, EXAMPLE_JAR_FILE1);
85418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager  }
86418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager
87418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager  @Test
88418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager  public void withLocals() throws IOException {
89418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager    runDexer(NO_POSITIONS, MULTIDEX, EXAMPLE_JAR_FILE1);
90418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager  }
91418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager
92418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager  @Test
93418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager  public void withoutMultidex() throws IOException {
94418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager    runDexer(NO_POSITIONS, NO_LOCALS, EXAMPLE_JAR_FILE1);
95418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager  }
96418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager
97418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager  @Test
98418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager  public void writeToNamedDexFile() throws IOException {
99418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager    runDexerWithOutput("named-output.dex", EXAMPLE_JAR_FILE1);
100418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager  }
101418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager
102418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager  @Test
10302cc4ebce7deb01a5509624f70e7d667d5f4e2c8Ivan Gavrilovic  public void singleDexProgramFull() throws IOException, ExecutionException {
10402cc4ebce7deb01a5509624f70e7d667d5f4e2c8Ivan Gavrilovic    // Generate an application that fills the whole dex file.
10502cc4ebce7deb01a5509624f70e7d667d5f4e2c8Ivan Gavrilovic    AndroidApp generated =
10602cc4ebce7deb01a5509624f70e7d667d5f4e2c8Ivan Gavrilovic        MainDexListTests.generateApplication(
10702cc4ebce7deb01a5509624f70e7d667d5f4e2c8Ivan Gavrilovic            ImmutableList.of("A"), Constants.ANDROID_L_API, Constants.U16BIT_MAX + 1);
10802cc4ebce7deb01a5509624f70e7d667d5f4e2c8Ivan Gavrilovic    Path applicationJar = temp.newFile("application.jar").toPath();
10902cc4ebce7deb01a5509624f70e7d667d5f4e2c8Ivan Gavrilovic    generated.write(applicationJar, OutputMode.Indexed);
11002cc4ebce7deb01a5509624f70e7d667d5f4e2c8Ivan Gavrilovic    runDexer(applicationJar.toString());
11102cc4ebce7deb01a5509624f70e7d667d5f4e2c8Ivan Gavrilovic  }
11202cc4ebce7deb01a5509624f70e7d667d5f4e2c8Ivan Gavrilovic
11302cc4ebce7deb01a5509624f70e7d667d5f4e2c8Ivan Gavrilovic  @Test
114854a7d4617a50c95bdbd7efc266c7ba6335b8d0cSøren Gjesse  public void singleDexProgramIsTooLarge() throws IOException, ExecutionException {
115854a7d4617a50c95bdbd7efc266c7ba6335b8d0cSøren Gjesse    // Generate an application that will not fit into a single dex file.
116854a7d4617a50c95bdbd7efc266c7ba6335b8d0cSøren Gjesse    AndroidApp generated = MainDexListTests.generateApplication(
11702cc4ebce7deb01a5509624f70e7d667d5f4e2c8Ivan Gavrilovic        ImmutableList.of("A", "B"), Constants.ANDROID_L_API, Constants.U16BIT_MAX / 2 + 2);
118854a7d4617a50c95bdbd7efc266c7ba6335b8d0cSøren Gjesse    Path applicationJar = temp.newFile("application.jar").toPath();
119341bbdae4c7b6fe0f5dd95c7158488af6768e7baIan Zerny    generated.write(applicationJar, OutputMode.Indexed);
120418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager    thrown.expect(CompilationError.class);
121854a7d4617a50c95bdbd7efc266c7ba6335b8d0cSøren Gjesse    runDexer(applicationJar.toString());
122418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager  }
123418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager
12454af84519a2116bb70618c65631fcf06b47d729bIan Zerny  @Test
12554af84519a2116bb70618c65631fcf06b47d729bIan Zerny  public void keepClassesTest() throws IOException {
12654af84519a2116bb70618c65631fcf06b47d729bIan Zerny    runDexerWithOutput("out.zip", "--keep-classes", EXAMPLE_JAR_FILE1);
12754af84519a2116bb70618c65631fcf06b47d729bIan Zerny  }
12854af84519a2116bb70618c65631fcf06b47d729bIan Zerny
129418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager  private void runDexer(String... args) throws IOException {
130418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager    runDexerWithOutput("", args);
131418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager  }
132418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager
133418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager  private void runDexerWithoutOutput(String... args) throws IOException {
134418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager    runDexerWithOutput(null, args);
135418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager  }
136418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager
13754af84519a2116bb70618c65631fcf06b47d729bIan Zerny  private Path getOutputD8() {
13854af84519a2116bb70618c65631fcf06b47d729bIan Zerny    return temp.getRoot().toPath().resolve("d8-out");
13954af84519a2116bb70618c65631fcf06b47d729bIan Zerny  }
14054af84519a2116bb70618c65631fcf06b47d729bIan Zerny
14154af84519a2116bb70618c65631fcf06b47d729bIan Zerny  private Path getOutputDX() {
14254af84519a2116bb70618c65631fcf06b47d729bIan Zerny    return temp.getRoot().toPath().resolve("dx-out");
14354af84519a2116bb70618c65631fcf06b47d729bIan Zerny  }
14454af84519a2116bb70618c65631fcf06b47d729bIan Zerny
145418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager  private void runDexerWithOutput(String out, String... args) throws IOException {
146418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager    Path d8Out = null;
147418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager    Path dxOut = null;
148418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager    if (out != null) {
14954af84519a2116bb70618c65631fcf06b47d729bIan Zerny      Path baseD8 = getOutputD8();
15054af84519a2116bb70618c65631fcf06b47d729bIan Zerny      Path baseDX = getOutputDX();
15154af84519a2116bb70618c65631fcf06b47d729bIan Zerny      Files.createDirectory(baseD8);
15254af84519a2116bb70618c65631fcf06b47d729bIan Zerny      Files.createDirectory(baseDX);
15354af84519a2116bb70618c65631fcf06b47d729bIan Zerny      d8Out = baseD8.resolve(out);
15454af84519a2116bb70618c65631fcf06b47d729bIan Zerny      dxOut = baseDX.resolve(out);
155418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager      assertNotEquals(d8Out, dxOut);
156418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager    }
157418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager
158418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager    List<String> d8Args = new ArrayList<>(args.length + 2);
159418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager    d8Args.add("--dex");
160418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager    if (d8Out != null) {
161418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager      d8Args.add("--output=" + d8Out);
162418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager    }
163418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager    Collections.addAll(d8Args, args);
164418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager    System.out.println("running: d8 " + StringUtils.join(d8Args, " "));
165418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager    CompatDx.main(d8Args.toArray(new String[d8Args.size()]));
166418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager
167418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager    List<String> dxArgs = new ArrayList<>(args.length + 2);
168418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager    if (dxOut != null) {
169418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager      dxArgs.add("--output=" + dxOut);
170418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager    }
171418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager    Collections.addAll(dxArgs, args);
172418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager    System.out.println("running: dx " + StringUtils.join(dxArgs, " "));
173418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager    ToolHelper.runDX(dxArgs.toArray(new String[dxArgs.size()]));
174418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager
175418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager    if (out == null) {
176418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager      // Can't check output if explicitly not writing any.
177418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager      return;
178418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager    }
179418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager
180418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager    List<Path> d8Files = Files.list(Files.isDirectory(d8Out) ? d8Out : d8Out.getParent())
181418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager        .sorted().collect(Collectors.toList());
182418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager    List<Path> dxFiles = Files.list(Files.isDirectory(dxOut) ? dxOut : dxOut.getParent())
183418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager        .sorted().collect(Collectors.toList());
184418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager    assertEquals("Out file names differ",
185418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager        StringUtils.join(dxFiles, "\n", BraceType.NONE, (file) ->
186418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager            file.getFileName().toString()),
187418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager        StringUtils.join(d8Files, "\n", BraceType.NONE, (file) ->
188418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager            file.getFileName().toString()));
189418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager
190418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager    for (int i = 0; i < d8Files.size(); i++) {
191418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager      if (FileUtils.isArchive(d8Files.get(i))) {
192418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager        compareArchiveFiles(d8Files.get(i), dxFiles.get(i));
193418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager      }
194418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager    }
195418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager  }
196418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager
197418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager  private void compareArchiveFiles(Path d8File, Path dxFile) throws IOException {
198418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager    ZipFile d8Zip = new ZipFile(d8File.toFile());
199418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager    ZipFile dxZip = new ZipFile(dxFile.toFile());
200418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager    // TODO(zerny): This should test resource containment too once supported.
201418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager    Set<String> d8Content = d8Zip.stream().map(ZipEntry::getName).collect(Collectors.toSet());
202418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager    Set<String> dxContent = dxZip.stream().map(ZipEntry::getName).collect(Collectors.toSet());
203418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager    for (String entry : d8Content) {
204418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager      assertTrue("Expected dx output to contain " + entry, dxContent.contains(entry));
205418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager    }
206418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager    for (String entry : dxContent) {
20754af84519a2116bb70618c65631fcf06b47d729bIan Zerny      Path path = Paths.get(entry);
20854af84519a2116bb70618c65631fcf06b47d729bIan Zerny      if (FileUtils.isDexFile(path) || FileUtils.isClassFile(path)) {
209418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager        assertTrue("Expected d8 output to contain " + entry, d8Content.contains(entry));
210418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager      }
211418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager    }
212418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager  }
213418d1ca139ea11316113beafbb3b3dd3fd5587aMads Ager}
214