Fix UiBinder non determinism.

- Fixes one source of nondeterminism in UiBinder.
- Adds error reporting for compile failures in CompilerTests.

Change-Id: I44d4caa1aeaa279aa248f60e0deac731bdd1d364
diff --git a/dev/core/test/com/google/gwt/dev/CompilerTest.java b/dev/core/test/com/google/gwt/dev/CompilerTest.java
index c62e7a9..1efda99 100644
--- a/dev/core/test/com/google/gwt/dev/CompilerTest.java
+++ b/dev/core/test/com/google/gwt/dev/CompilerTest.java
@@ -27,6 +27,7 @@
 import com.google.gwt.dev.jjs.JsOutputOption;
 import com.google.gwt.dev.util.Util;
 import com.google.gwt.dev.util.arg.SourceLevel;
+import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
 import com.google.gwt.thirdparty.guava.common.base.Charsets;
 import com.google.gwt.thirdparty.guava.common.collect.ImmutableList;
 import com.google.gwt.thirdparty.guava.common.collect.Lists;
@@ -437,6 +438,65 @@
           "<entry-point class='com.foo.TestEntryPoint'/>",
           "</module>");
 
+  private MockResource myWidgetUiXml =
+      JavaResourceBase.createMockResource("com/foo/MyWidget.ui.xml",
+          "<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'",
+          "    xmlns:g='urn:import:com.google.gwt.user.client.ui'>",
+          "<g:HTMLPanel>",
+          "  Hello, <g:ListBox ui:field='myListBox' visibleItemCount='1'/>.",
+          "</g:HTMLPanel>",
+          "</ui:UiBinder>");
+
+  private MockJavaResource myWidget =
+      JavaResourceBase.createMockJavaResource("com.foo.MyWidget",
+          "package com.foo;",
+          "import com.google.gwt.core.client.GWT;",
+          "import com.google.gwt.core.client.JavaScriptObject;",
+          "import com.google.gwt.uibinder.client.UiBinder;",
+          "import com.google.gwt.uibinder.client.UiField;",
+          "import com.google.gwt.user.client.ui.Composite;",
+          "import com.google.gwt.user.client.ui.ListBox;",
+          "import com.google.gwt.user.client.ui.Widget;",
+          "public class MyWidget extends Composite {",
+          "  interface Binder extends UiBinder<Widget, MyWidget> {",
+          "  }",
+          "  private static final Binder binder = GWT.create(Binder.class);",
+          "  @UiField ListBox myListBox;",
+          "  public MyWidget() {",
+          "    init();",
+          "  }",
+          "  protected void init() {",
+          "    initWidget(binder.createAndBindUi(this));",
+          "    myListBox.addItem(\"One\");",
+          "  }",
+          "}");
+
+  private MockJavaResource uiBinderTestEntryPointResource =
+      JavaResourceBase.createMockJavaResource("com.foo.TestEntryPoint",
+          "package com.foo;",
+          "import com.google.gwt.core.client.EntryPoint;",
+          "import com.google.gwt.dom.client.Node;",
+          "import com.google.gwt.user.client.ui.RootPanel;",
+          "public class TestEntryPoint implements EntryPoint {",
+          "  private Node node;",
+          "  private MyWidget widget;",
+          "  @Override",
+          "  public void onModuleLoad() {",
+          "    node = null; widget = new MyWidget();",
+          "    RootPanel.get().add(widget);",
+          "  }",
+          "}");
+
+  private MockResource uiBinderTestModuleResource =
+      JavaResourceBase.createMockResource("com/foo/UiBinderTestModule.gwt.xml",
+          "<module>",
+          "  <inherits name='com.google.gwt.core.Core'/>",
+          "  <inherits name='com.google.gwt.user.User' />",
+          "  <source path=''/>",
+          "  <set-property name='user.agent' value='safari'/>",
+          "  <entry-point class='com.foo.TestEntryPoint'/>",
+          "</module>");
+
   private Set<String> emptySet = stringSet();
 
   public CompilerTest() {
@@ -578,6 +638,12 @@
     checkPerFileRecompile_dateStampChange(JsOutputOption.DETAILED);
   }
 
+  public void testPerFileRecompile_deterministicUiBinder() throws UnableToCompleteException, IOException,
+      InterruptedException {
+    checkPerFileRecompile_deterministicUiBinder(JsOutputOption.PRETTY);
+    checkPerFileRecompile_deterministicUiBinder(JsOutputOption.DETAILED);
+  }
+
   public void testPerFileRecompile_unstableGeneratorReferencesModifiedType()
       throws UnableToCompleteException, IOException, InterruptedException {
     checkPerFileRecompile_unstableGeneratorReferencesModifiedType(JsOutputOption.PRETTY);
@@ -682,7 +748,7 @@
 
     // Recompile with no changes, which should not trigger any Generator runs.
     compileToJs(compilerOptions, relinkApplicationDir, "com.foo.SimpleModule",
-        Lists.<MockResource> newArrayList(), relinkMinimalRebuildCache, emptySet, output);
+        Lists.<MockResource>newArrayList(), relinkMinimalRebuildCache, emptySet, output);
 
     // Since there were no changes BarReferencesFoo Generator was not run again.
     assertEquals(1, BarReferencesFooGenerator.runCount);
@@ -690,7 +756,7 @@
     // Recompile with a modified Foo class, which should invalidate Bar which was generated by a
     // GWT.create() call in the entry point.
     compileToJs(compilerOptions, relinkApplicationDir, "com.foo.SimpleModule",
-        Lists.<MockResource> newArrayList(fooResource), relinkMinimalRebuildCache,
+        Lists.<MockResource>newArrayList(fooResource), relinkMinimalRebuildCache,
         stringSet("com.foo.TestEntryPoint", "com.foo.Foo", "com.foo.Bar"), output);
 
     // BarReferencesFoo Generator was run again.
@@ -820,6 +886,17 @@
     assertTrue(originalJs.equals(relinkedJs));
   }
 
+  private void checkPerFileRecompile_deterministicUiBinder(JsOutputOption output)
+      throws UnableToCompleteException, IOException, InterruptedException {
+    CompilerOptions compilerOptions = new CompilerOptionsImpl();
+
+    checkRecompiledModifiedApp(compilerOptions, "com.foo.UiBinderTestModule", Lists.newArrayList(
+        uiBinderTestModuleResource, uiBinderTestEntryPointResource, myWidgetUiXml), myWidget,
+        myWidget, stringSet("com.foo.MyWidget", "com.foo.MyWidget_BinderImpl_GenBundle",
+        "com.foo.MyWidget$Binder", "com.foo.MyWidget_BinderImpl$Template", "com.foo.TestEntryPoint",
+        "com.foo.MyWidget_BinderImpl", "com.foo.MyWidget_BinderImpl$Widgets"), output);
+  }
+
   private void checkPerFileRecompile_packagePrivateOverride(JsOutputOption output)
       throws UnableToCompleteException, IOException, InterruptedException {
     CompilerOptions compilerOptions = new CompilerOptionsImpl();
@@ -1047,8 +1124,8 @@
     System.setProperty(GWT_PERSISTENTUNITCACHE, "false");
     // Wait 1 second so that any new file modification times are actually different.
     Thread.sleep(1001);
-    TreeLogger logger = TreeLogger.NULL;
-
+    PrintWriterTreeLogger logger = new PrintWriterTreeLogger();
+    logger.setMaxDetail(TreeLogger.ERROR);
     // We might be reusing the same application dir but we want to make sure that the output dir is
     // clean to avoid confusion when returning the output JS.
     File outputDir = new File(applicationDir.getPath() + File.separator + moduleName);
@@ -1097,6 +1174,7 @@
         }
       }
     }
+
     assertNotNull(outputJsFile);
     assertEquals(expectedStaleTypeNames, minimalRebuildCache.getStaleTypeNames());
     return Files.toString(outputJsFile, Charsets.UTF_8);
diff --git a/user/src/com/google/gwt/uibinder/rebind/AbstractFieldWriter.java b/user/src/com/google/gwt/uibinder/rebind/AbstractFieldWriter.java
index f6f08ae..37f8fdc 100644
--- a/user/src/com/google/gwt/uibinder/rebind/AbstractFieldWriter.java
+++ b/user/src/com/google/gwt/uibinder/rebind/AbstractFieldWriter.java
@@ -45,12 +45,6 @@
       + " a @UiFactory method on the UiBinder's owner, or annotate a constructor of %2$s with"
       + " @UiConstructor.";
 
-  private static int nextAttachVar;
-
-  public static String getNextAttachVar() {
-    return "attachRecord" + nextAttachVar++;
-  }
-
   private final FieldManager manager;
   private final Set<FieldWriter> needs = new LinkedHashSet<FieldWriter>();
   private final List<String> statements = new ArrayList<String>();
@@ -260,16 +254,12 @@
     }
 
     w.write("// Setup section.");
-    for (String s : statements) {
-      w.write(s);
-    }
-
-    String attachedVar = null;
+    writeStatements(w, statements);
 
     if (attachStatements.size() > 0) {
       w.newline();
-      w.write("// Attach section.");
       if (outputAttachDetachCallbacks) {
+        w.write("// Attach section.");
         // TODO(rdcastro): This is too coupled with RenderablePanel.
         // Make this nicer.
         w.write("%s.wrapInitializationCallback = ", getName());
@@ -280,36 +270,39 @@
         w.outdent();
         w.write("@Override public void execute() {");
         w.indent();
-      } else {
-        attachedVar = getNextAttachVar();
 
+        writeStatements(w, attachStatements);
+
+        w.outdent();
+        w.write("}");
+        w.outdent();
+        w.write("};");
+      } else {
+        String attachedVar = "__attachRecord__";
+
+        w.write("{");
+        w.indent();
+        w.write("// Attach section.");
         String elementToAttach = getInstantiableType().isAssignableTo(getDomElement(typeOracle))
             ? name : name + ".getElement()";
 
         w.write("UiBinderUtil.TempAttachment %s = UiBinderUtil.attachToDom(%s);",
                 attachedVar, elementToAttach);
-      }
 
-      for (String s : attachStatements) {
-        w.write(s);
-      }
+        w.newline();
 
-      if (outputAttachDetachCallbacks) {
+        writeStatements(w, attachStatements);
+
+        w.newline();
+        // If we forced an attach, we should always detach, regardless of whether
+        // there are any detach statements.
+        w.write("// Detach section.");
+        w.write("%s.detach();", attachedVar);
         w.outdent();
         w.write("}");
-        w.outdent();
-        w.write("};");
       }
     }
 
-    w.newline();
-    // If we forced an attach, we should always detach, regardless of whether
-    // there are any detach statements.
-    if (attachedVar != null) {
-      w.write("// Detach section.");
-      w.write("%s.detach();", attachedVar);
-    }
-
     if (detachStatements.size() > 0) {
       if (outputAttachDetachCallbacks) {
         w.write("%s.detachedInitializationCallback = ", getName());
@@ -321,9 +314,7 @@
         w.indent();
       }
 
-      for (String s : detachStatements) {
-        w.write(s);
-      }
+      writeStatements(w, detachStatements);
 
       if (outputAttachDetachCallbacks) {
         w.outdent();
@@ -406,4 +397,10 @@
     }
     return type;
   }
+
+  private static void writeStatements(IndentedWriter w, Iterable<String> statements) {
+    for (String s : statements) {
+      w.write(s);
+    }
+  }
 }