Merge "Add a test for writing files with shared classes."
diff --git a/src/main/java/com/android/tools/r8/dex/VirtualFile.java b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
index 746c62e..b80dbd4 100644
--- a/src/main/java/com/android/tools/r8/dex/VirtualFile.java
+++ b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.dex;
 
-import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.DexOverflowException;
 import com.android.tools.r8.errors.InternalCompilerError;
 import com.android.tools.r8.graph.DexApplication;
@@ -24,7 +23,6 @@
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.FileUtils;
-
 import com.google.common.collect.Iterators;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
diff --git a/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java b/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
new file mode 100644
index 0000000..508ed3f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
@@ -0,0 +1,134 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.dex;
+
+import com.android.tools.r8.Resource;
+import com.android.tools.r8.Resource.Kind;
+import com.android.tools.r8.code.ConstString;
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.code.ReturnVoid;
+import com.android.tools.r8.errors.DexOverflowException;
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.DexAccessFlags;
+import com.android.tools.r8.graph.DexAnnotationSet;
+import com.android.tools.r8.graph.DexAnnotationSetRefList;
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.graph.DexCode.Try;
+import com.android.tools.r8.graph.DexCode.TryHandler;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexTypeList;
+import com.android.tools.r8.graph.DirectMappedDexApplication;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.OutputMode;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.Timing;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class SharedClassWritingTest {
+
+  private final static String PREFIX = "A";
+
+  DexItemFactory dexItemFactory = new DexItemFactory();
+
+  private DexString[] strings;
+
+  @Before
+  public void generateStringArray() {
+    strings = new DexString[Constants.MAX_NON_JUMBO_INDEX + 100];
+    for (int i = 0; i < strings.length; i++) {
+      // Format i as string with common prefix and leading 0's so that they are in the array
+      // in lexicographic order.
+      String string = PREFIX + StringUtils.zeroPrefix(i, 8);
+      strings[i] = dexItemFactory.createString(string);
+    }
+  }
+
+  private DexEncodedMethod makeMethod(DexType holder, int stringCount, int startOffset) {
+    assert stringCount + startOffset < strings.length;
+    Instruction[] instructions = new Instruction[stringCount + 1];
+    for (int i = 0; i < stringCount; i++) {
+      instructions[i] = new ConstString(0, strings[startOffset + i]);
+    }
+    instructions[stringCount] = new ReturnVoid();
+    DexCode code = new DexCode(1, 0, 0, instructions, new Try[0], new TryHandler[0], null,
+        strings[startOffset + stringCount - 1]);
+    return new DexEncodedMethod(dexItemFactory
+        .createMethod(holder, dexItemFactory.createProto(dexItemFactory.voidType), "theMethod"),
+        new DexAccessFlags(Constants.ACC_PUBLIC), DexAnnotationSet.empty(),
+        DexAnnotationSetRefList.empty(), code);
+  }
+
+  private DexProgramClass makeClass(String name, int stringCount, int startOffset,
+      Collection<DexProgramClass> synthesizedFrom) {
+    String desc = DescriptorUtils.javaTypeToDescriptor(name);
+    DexType type = dexItemFactory.createType(desc);
+    return new DexProgramClass(type, Kind.DEX, new DexAccessFlags(Constants.ACC_PUBLIC),
+        dexItemFactory.objectType, DexTypeList.empty(), null, DexAnnotationSet.empty(),
+        DexEncodedField.EMPTY_ARRAY, DexEncodedField.EMPTY_ARRAY, DexEncodedMethod.EMPTY_ARRAY,
+        new DexEncodedMethod[]{makeMethod(type, stringCount, startOffset)},
+        synthesizedFrom);
+  }
+
+  @Test
+  public void manyFilesWithSharedSynthesizedClass()
+      throws ExecutionException, IOException, DexOverflowException {
+
+    // Create classes that all reference enough strings to overflow the index, but are all
+    // at different offsets in the strings array. This ensures we trigger multiple rounds of
+    // rewrites.
+    List<DexProgramClass> classes = new ArrayList<>();
+    for (int i = 0; i < 1000; i++) {
+      classes.add(makeClass("Class" + i, Constants.MAX_NON_JUMBO_INDEX - 1, i / 10,
+          Collections.emptyList()));
+    }
+
+    // Create a shared class that references strings above the maximum.
+    DexProgramClass sharedSynthesizedClass = makeClass("SharedSynthesized", 100,
+        Constants.MAX_NON_JUMBO_INDEX - 1,
+        classes);
+
+    DexApplication.Builder builder = DirectMappedDexApplication
+        .builder(dexItemFactory, new Timing("SharedClassWritingTest"));
+    builder.addSynthesizedClass(sharedSynthesizedClass, false);
+    classes.forEach(builder::addProgramClass);
+    DexApplication application = builder.build();
+
+    InternalOptions options = new InternalOptions(dexItemFactory);
+    options.outputMode = OutputMode.FilePerInputClass;
+    ApplicationWriter writer = new ApplicationWriter(application, new AppInfo(application),
+        options, null, null, NamingLens.getIdentityLens(), null);
+    ExecutorService executorService = ThreadUtils.getExecutorService(options);
+    AndroidApp output = writer.write(executorService);
+    List<Resource> resourcesForOutput = output.getDexProgramResourcesForOutput();
+    // Check all files present.
+    Assert.assertEquals(1000, resourcesForOutput.size());
+    // And each file contains two classes of which one is the shared one.
+    for (Resource res : resourcesForOutput) {
+      Set<String> classDescriptors = res.getClassDescriptors();
+      Assert.assertEquals(2, classDescriptors.size());
+      Assert
+          .assertTrue(classDescriptors.contains(sharedSynthesizedClass.type.toDescriptorString()));
+    }
+  }
+}