/*
 * Copyright 2009 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.google.gwt.dev;

import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.dev.UnstableNestedAnonymousGenerator.OutputVersion;
import com.google.gwt.dev.cfg.EntryMethodHolderGenerator;
import com.google.gwt.dev.cfg.ModuleDef;
import com.google.gwt.dev.cfg.ModuleDefLoader;
import com.google.gwt.dev.cfg.ResourceLoader;
import com.google.gwt.dev.cfg.ResourceLoaders;
import com.google.gwt.dev.javac.UnitCacheSingleton;
import com.google.gwt.dev.javac.testing.impl.JavaResourceBase;
import com.google.gwt.dev.javac.testing.impl.MockJavaResource;
import com.google.gwt.dev.javac.testing.impl.MockResource;
import com.google.gwt.dev.jjs.JsOutputOption;
import com.google.gwt.dev.jjs.impl.JjsUtils;
import com.google.gwt.dev.util.UnitTestTreeLogger;
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;
import com.google.gwt.thirdparty.guava.common.collect.Sets;
import com.google.gwt.thirdparty.guava.common.io.MoreFiles;
import com.google.gwt.thirdparty.guava.common.io.RecursiveDeleteOption;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
import java.util.regex.Pattern;

/**
 * Test for {@link Compiler}.
 */
public class CompilerTest extends ArgProcessorTestBase {

  public static final String HELLO_MODULE = "com.google.gwt.sample.hello.Hello";
  public static final String HELLO_MODULE_STACKMODE_STRIP =
      "com.google.gwt.sample.hello.Hello_stackMode_strip";

  private MockJavaResource packagePrivateParentResource =
      JavaResourceBase.createMockJavaResource("com.foo.PackagePrivateParent",
          "package com.foo;",
          "public abstract class PackagePrivateParent {",
          "  abstract void someFunction();",
          "}");

  private MockJavaResource packagePrivateChildResource =
      JavaResourceBase.createMockJavaResource("com.foo.PackagePrivateChild",
          "package com.foo;",
          "public class PackagePrivateChild extends PackagePrivateParent {",
          "  @Override",
          "  void someFunction() {}",
          "}");

  private MockJavaResource referencesParentResource =
      JavaResourceBase.createMockJavaResource("com.foo.ReferencesParent",
          "package com.foo;",
          "public class ReferencesParent {",
          "  void run() {",
          "    PackagePrivateParent packagePrivateParent = null;",
          "    packagePrivateParent.someFunction();",
          "  }",
          "}");

  private MockJavaResource packagePrivateDispatchEntryPointResource =
      JavaResourceBase.createMockJavaResource("com.foo.TestEntryPoint",
          "package com.foo;",
          "import com.google.gwt.core.client.EntryPoint;",
          "public class TestEntryPoint implements EntryPoint {",
          "  @Override",
          "  public void onModuleLoad() {",
          "    ReferencesParent referencesParent = new ReferencesParent();",
          "    PackagePrivateChild packagePrivateChild = new PackagePrivateChild();",
          "  }",
          "}");

  private MockJavaResource jsoOne =
      JavaResourceBase.createMockJavaResource("com.foo.JsoOne",
          "package com.foo;",
          "import com.google.gwt.core.client.JavaScriptObject;",
          "public class JsoOne extends JavaScriptObject {",
          "  protected JsoOne() {",
          "  }",
          "}");

  private MockJavaResource jsoTwo_before =
      JavaResourceBase.createMockJavaResource("com.foo.JsoTwo",
          "package com.foo;",
          "public class JsoTwo {",
          "  protected JsoTwo() {",
          "  }",
          "}");

  private MockJavaResource jsoTwo_after =
      JavaResourceBase.createMockJavaResource("com.foo.JsoTwo",
          "package com.foo;",
          "import com.google.gwt.core.client.JavaScriptObject;",
          "public class JsoTwo extends JavaScriptObject {",
          "  protected JsoTwo() {",
          "  }",
          "}");

  private MockJavaResource someClassReferringToJsoOneArrays =
      JavaResourceBase.createMockJavaResource("com.foo.SomeClassReferringToJsoOneArrays",
          "package com.foo;",
          "public class SomeClassReferringToJsoOneArrays {",
          "  public static Object createJsoOneArray() { return new JsoOne[30]; }",
          "}");

  private MockJavaResource someClassReferringToJsoTwoArrays =
      JavaResourceBase.createMockJavaResource("com.foo.SomeClassReferringToJsoTwoArrays",
          "package com.foo;",
          "public class SomeClassReferringToJsoTwoArrays {",
          "  public static Object createJsoTwoArray() { return new JsoTwo[30]; }",
          "}");

  private MockJavaResource jsoArrayTestEntryPointResource =
      JavaResourceBase.createMockJavaResource("com.foo.TestEntryPoint",
          "package com.foo;",
          "import com.google.gwt.core.client.EntryPoint;",
          "public class TestEntryPoint implements EntryPoint {",
          "  @Override",
          "  public void onModuleLoad() {",
          "    Object o1 = SomeClassReferringToJsoOneArrays.createJsoOneArray();",
          "    Object o2 = SomeClassReferringToJsoTwoArrays.createJsoTwoArray();",
          "  }",
          "}");

  private MockJavaResource simpleDialogResourceWithExport =
      JavaResourceBase.createMockJavaResource("com.foo.SimpleDialog",
          "package com.foo;",
          "import jsinterop.annotations.JsProperty;",
          "public class SimpleDialog {",
          "  @JsProperty(namespace = \"ns\")",
          "  public static int show;",
          "}");

  private MockJavaResource complexDialogResourceWithExport =
      JavaResourceBase.createMockJavaResource("com.foo.ComplexDialog",
          "package com.foo;",
          "import jsinterop.annotations.JsProperty;",
          "public class ComplexDialog {",
          "  @JsProperty(namespace = \"ns\")",
          "  public static int show;",
          "}");

  private MockJavaResource complexDialogResourceSansExport =
      JavaResourceBase.createMockJavaResource("com.foo.ComplexDialog",
          "package com.foo;",
          "public class ComplexDialog {",
          "  public static void show() {}",
          "}");

  private MockJavaResource simpleModelEntryPointResource =
      JavaResourceBase.createMockJavaResource("com.foo.TestEntryPoint",
          "package com.foo;",
          "import com.google.gwt.core.client.EntryPoint;",
          "public class TestEntryPoint implements EntryPoint {",
          "  @Override",
          "  public void onModuleLoad() {",
          "    SimpleModel simpleModel1 = new SimpleModel();",
          "    SimpleModel simpleModel2 = new SimpleModel();",
          "    simpleModel2.copyFrom(simpleModel1);",
          "  }",
          "}");

  private MockJavaResource simpleModelResource =
      JavaResourceBase.createMockJavaResource("com.foo.SimpleModel",
          "package com.foo;",
          "public class SimpleModel {",
          "  private int value = Constants.CONSTANT;",
          "  public void copyFrom(Object object) {}",
          "}");

  private MockJavaResource modifiedFunctionSignatureSimpleModelResource =
      JavaResourceBase.createMockJavaResource("com.foo.SimpleModel",
          "package com.foo;",
          "public class SimpleModel {",
          "  private int value = Constants.CONSTANT;",
          "  public void copyFrom(SimpleModel that) {}",
          "}");

  private MockResource simpleModuleResource =
      JavaResourceBase.createMockResource("com/foo/SimpleModule.gwt.xml",
          "<module>",
          "<source path=''/>",
          "<entry-point class='com.foo.TestEntryPoint'/>",
          "</module>");

  private MockJavaResource constantsModelResource =
      JavaResourceBase.createMockJavaResource("com.foo.Constants",
          "package com.foo;",
          "public class Constants {",
          "  public static final int CONSTANT = 0;",
          "}");

  private MockJavaResource modifiedConstantsModelResource =
      JavaResourceBase.createMockJavaResource("com.foo.Constants",
          "package com.foo;",
          "public class Constants {",
          "  public static final int CONSTANT = 2;",
          "}");

  private MockResource unstableGeneratorModuleResource = JavaResourceBase.createMockResource(
      "com/foo/SimpleModule.gwt.xml",
      "<module>",
      "<source path=''/>",
      "<entry-point class='com.foo.TestEntryPoint'/>",
      "<generate-with class='com.google.gwt.dev.UnstableNestedAnonymousGenerator'>",
      "  <when-type-is class='java.lang.Object' />",
      "</generate-with>",
      "</module>");

  private MockResource resourceReadingGeneratorModuleResource =
      JavaResourceBase.createMockResource("com/foo/SimpleModule.gwt.xml",
          "<module>",
          "<source path=''/>",
          "<entry-point class='com.foo.TestEntryPoint'/>",
          "<generate-with class='com.google.gwt.dev.FooResourceGenerator'>",
          "  <when-type-is class='java.lang.Object' />",
          "</generate-with>",
          "</module>");

  private MockResource barReferencesFooGeneratorModuleResource =
      JavaResourceBase.createMockResource("com/foo/SimpleModule.gwt.xml",
          "<module>",
          "<source path=''/>",
          "<entry-point class='com.foo.TestEntryPoint'/>",
          "<generate-with class='com.google.gwt.dev.BarReferencesFooGenerator'>",
          "  <when-type-is class='java.lang.Object' />",
          "</generate-with>",
          "</module>");

  private MockResource cascadingGeneratorModuleResource =
      JavaResourceBase.createMockResource("com/foo/SimpleModule.gwt.xml",
          "<module>",
          "<source path=''/>",
          "<entry-point class='com.foo.TestEntryPoint'/>",
          "<generate-with class='com.google.gwt.dev.CauseStringRebindGenerator'>",
          "  <when-type-is class='java.lang.Object' />",
          "</generate-with>",
          "<generate-with class='com.google.gwt.dev.CauseShortRebindGenerator'>",
          "  <when-type-is class='java.lang.String' />",
          "</generate-with>",
          "<generate-with class='com.google.gwt.dev.FooResourceGenerator'>",
          "  <when-type-is class='java.lang.Short' />",
          "</generate-with>",
          "</module>");

  private MockResource multipleClassGeneratorModuleResource =
      JavaResourceBase.createMockResource("com/foo/SimpleModule.gwt.xml",
          "<module>",
          "<source path=''/>",
          "<entry-point class='com.foo.TestEntryPoint'/>",
          "<generate-with class='com.google.gwt.dev.MultipleClassGenerator'>",
          "  <when-type-is class='java.lang.Object' />",
          "</generate-with>",
          "</module>");

  private MockResource classNameToGenerateResource =
      JavaResourceBase.createMockResource("com/foo/generatedClassName.txt",
          "FooReplacementOne");

  private MockResource modifiedClassNameToGenerateResource =
      JavaResourceBase.createMockResource("com/foo/generatedClassName.txt",
          "FooReplacementTwo");

  private MockJavaResource generatorEntryPointResource =
      JavaResourceBase.createMockJavaResource("com.foo.TestEntryPoint",
          "package com.foo;",
          "import com.google.gwt.core.client.EntryPoint;",
          "import com.google.gwt.core.client.GWT;",
          "public class TestEntryPoint implements EntryPoint {",
          "  @Override",
          "  public void onModuleLoad() {",
          "    GWT.create(Object.class);",
          "  }",
          "}");

  private MockJavaResource referencesBarAndGeneratorEntryPointResource =
      JavaResourceBase.createMockJavaResource("com.foo.TestEntryPoint",
          "package com.foo;",
          "import com.google.gwt.core.client.EntryPoint;",
          "import com.google.gwt.core.client.GWT;",
          "public class TestEntryPoint implements EntryPoint {",
          "  Bar bar = new Bar();",
          "  @Override",
          "  public void onModuleLoad() {",
          "    GWT.create(Object.class);",
          "  }",
          "}");

  private MockJavaResource superClassOrderEntryPointResource =
      JavaResourceBase.createMockJavaResource("com.foo.TestEntryPoint",
          "package com.foo;",
          "import com.google.gwt.core.client.EntryPoint;",
          "public class TestEntryPoint implements EntryPoint {",
          "  @Override",
          "  public void onModuleLoad() {",
          "    SuperClass superClass = new SuperClass();",
          "    ASubClass aSubClass = new ASubClass();",
          "  }",
          "}");

  private MockJavaResource referencedBySuperClassResource =
      JavaResourceBase.createMockJavaResource("com.foo.ReferencedBySuperClass",
          "package com.foo;",
          "public class ReferencedBySuperClass {}");

  private MockJavaResource superClassResource =
      JavaResourceBase.createMockJavaResource("com.foo.SuperClass",
          "package com.foo;",
          "public class SuperClass {",
          "  ReferencedBySuperClass referencedBySuperClass = new ReferencedBySuperClass();",
          "}");

  private MockJavaResource aSubClassResource =
      JavaResourceBase.createMockJavaResource("com.foo.ASubClass",
          "package com.foo;",
          "public class ASubClass extends SuperClass {}");

  private MockJavaResource modifiedSuperEntryPointResource =
      JavaResourceBase.createMockJavaResource("com.foo.TestEntryPoint",
          "package com.foo;",
          "import com.google.gwt.core.client.EntryPoint;",
          "public class TestEntryPoint implements EntryPoint {",
          "  @Override",
          "  public void onModuleLoad() {",
          "    Object d = new ModelD();",
          "    if (d instanceof ModelA) {",
          "      // ModelD extends ModelA;",
          "    } else {",
          "      // ModelD does not ModelA;",
          "    }",
          "  }",
          "}");

  private MockJavaResource modelAResource =
      JavaResourceBase.createMockJavaResource("com.foo.ModelA",
          "package com.foo;",
          "public class ModelA {}");

  private MockJavaResource modelBResource =
      JavaResourceBase.createMockJavaResource("com.foo.ModelB",
          "package com.foo;",
          "public class ModelB extends ModelA {}");

  private MockJavaResource modelCResource =
      JavaResourceBase.createMockJavaResource("com.foo.ModelC",
          "package com.foo;",
          "public class ModelC {}");

  private MockJavaResource modifiedSuperModelCResource =
      JavaResourceBase.createMockJavaResource("com.foo.ModelC",
          "package com.foo;",
          "public class ModelC extends ModelA {}");

  private MockJavaResource modelDResource =
      JavaResourceBase.createMockJavaResource("com.foo.ModelD",
          "package com.foo;",
          "public class ModelD extends ModelC {}");

  private MockJavaResource modifiedJsoIntfDispatchEntryPointResource =
      JavaResourceBase.createMockJavaResource("com.foo.TestEntryPoint",
          "package com.foo;",
          "import com.google.gwt.core.client.EntryPoint;",
          "public class TestEntryPoint implements EntryPoint {",
          "  @Override",
          "  public void onModuleLoad() {",
          "    Caller.call(this);",
          "  }",
          "",
          "  public void runTest(FooInterface fooInterface) {",
          "    fooInterface.run();",
          "  }",
          "}");

  private MockJavaResource callerResource =
      JavaResourceBase.createMockJavaResource("com.foo.Caller",
          "package com.foo;",
          "public class Caller {",
          "  public static void call(TestEntryPoint testEntryPoint) {",
          "    testEntryPoint.runTest(Foo.createFoo());",
          "  }",
          "}");

  private MockJavaResource sameContentDifferentTimeFooResource = JavaResourceBase
      .createMockJavaResource("com.foo.Foo",
          "package com.foo;",
          "public class Foo {}");

  private MockJavaResource fooInterfaceResource =
      JavaResourceBase.createMockJavaResource("com.foo.FooInterface",
          "package com.foo;",
          "public interface FooInterface {",
          "  void run();",
          "}");

  private MockJavaResource jsoFooResource =
      JavaResourceBase.createMockJavaResource("com.foo.Foo",
          "package com.foo;",
          "import com.google.gwt.core.client.JavaScriptObject;",
          "public final class Foo extends JavaScriptObject implements FooInterface {",
          "  public static native Foo createFoo() /*-{",
          "    return {};",
          "  }-*/;",
          "",
          "  protected Foo() {}",
          "",
          "  @Override",
          "  public void run() {}",
          "}");

  private MockJavaResource nonJsoFooResource =
      JavaResourceBase.createMockJavaResource("com.foo.Foo",
          "package com.foo;",
          "public class Foo implements FooInterface {",
          "  public static Foo createFoo() {",
          "    return new Foo();",
          "  }",
          "",
          "  @Override",
          "  public void run() {}",
          "}");

  private MockJavaResource fooResource =
      JavaResourceBase.createMockJavaResource("com.foo.Foo",
          "package com.foo;",
          "public class Foo {}");

  private MockJavaResource barReferencesFooResource =
      JavaResourceBase.createMockJavaResource("com.foo.Bar",
          "package com.foo;",
          "public class Bar {",
          "  Foo foo = new Foo();",
          "}");

  private MockJavaResource jsTypeBarResource =
      JavaResourceBase.createMockJavaResource(
          "com.foo.Bar",
          "package com.foo;",
          "import jsinterop.annotations.JsType;",
          "@JsType public class Bar {",
          "  void doInstanceBar() {}",
          "  public static void doStaticBaz() {}",
          "}");

  private MockJavaResource nonCompilableFooResource =
      JavaResourceBase.createMockJavaResource("com.foo.Foo",
          "package com.foo;",
          "public class Foo {",
          "  public void run() {",
          "    // Not available in GWT.",
          "    String.format(\"asdf\");",
          "  }",
          "}");

  private MockJavaResource emptyEntryPointResource =
      JavaResourceBase.createMockJavaResource("com.foo.TestEntryPoint",
          "package com.foo;",
          "import com.google.gwt.core.client.EntryPoint;",
          "public class TestEntryPoint implements EntryPoint {",
          "  @Override",
          "  public void onModuleLoad() {}",
          "}");

  MockJavaResource entryPointResourceForFoo =
      JavaResourceBase.createMockJavaResource("com.foo.TestEntryPoint",
          "package com.foo;",
          "import com.google.gwt.core.client.EntryPoint;",
          "import com.foo.Foo;",
          "public class TestEntryPoint implements EntryPoint {",
          "  @Override",
          "  public void onModuleLoad() { new Foo(); }",
          "}");

  private MockJavaResource gwtCreateEntryPointResource =
      JavaResourceBase.createMockJavaResource("com.foo.TestEntryPoint",
          "package com.foo;",
          "import com.google.gwt.core.client.EntryPoint;",
          "import com.google.gwt.core.client.GWT;",
          "import jsinterop.annotations.JsType;",
          "public class TestEntryPoint implements EntryPoint {",
          "  @JsType public static class MyJsType {}",
          "  @JsType public interface MyJsTypeInterface {}",
          "  public static class MyTypeImplementsJsType implements MyJsTypeInterface {}",
          "  @Override",
          "  public void onModuleLoad() {",
          "    GWT.create(MyJsType.class);",
          "    GWT.create(MyTypeImplementsJsType.class);",
          "  }",
          "}");

  private MockJavaResource dialogEntryPointResource =
      JavaResourceBase.createMockJavaResource("com.foo.TestEntryPoint",
          "package com.foo;",
          "import com.google.gwt.core.client.EntryPoint;",
          "public class TestEntryPoint implements EntryPoint {",
          "  @Override",
          "  public void onModuleLoad() {",
          "    SimpleDialog simpleDialog = new SimpleDialog();",
          "    ComplexDialog complexDialog = new ComplexDialog();",
          "  }",
          "}");

  private MockJavaResource brokenGwtCreateEntryPointResource =
      JavaResourceBase.createMockJavaResource("com.foo.TestEntryPoint",
          "package com.foo;",
          "import com.google.gwt.core.client.EntryPoint;",
          "import com.google.gwt.core.client.GWT;",
          "import com.google.gwt.core.client.JavaScriptObject;",
          "public class TestEntryPoint implements EntryPoint {",
          "  public static class MyJso extends JavaScriptObject {",
          "    protected MyJso() {}",
          "  }",
          "  @Override",
          "  public void onModuleLoad() {",
          "    GWT.create(MyJso.class);",
          "  }",
          "}");

  private MockJavaResource topResource =
      JavaResourceBase.createMockJavaResource("com.foo.top",
          "package com.foo;",
          "abstract class Top {",
          "  Object run() {",
          "    return \"\";",
          "  }",
          "}");

  private MockJavaResource middleResource =
      JavaResourceBase.createMockJavaResource("com.foo.Middle",
          "package com.foo;",
          "public abstract class Middle extends Top {",
          "  abstract String run();",
          "}");

  private MockJavaResource bottomResource =
      JavaResourceBase.createMockJavaResource("com.foo.Bottom",
          "package com.foo;",
          "public class Bottom extends Middle {",
          "  // ",
          "  private final class Value {}",
          "  String run() {",
          "    Value value = new Value();",
          "    return \"\";",
          "  }",
          "}");

  private MockJavaResource overriddenMethodChainEntryPointResource =
      JavaResourceBase.createMockJavaResource("com.foo.TestEntryPoint",
          "package com.foo;",
          "import com.google.gwt.core.client.EntryPoint;",
          "public class TestEntryPoint implements EntryPoint {",
          "  @Override",
          "  public void onModuleLoad() {",
          "    Bottom bottom = new Bottom();",
          "    ((Top)bottom).run();",
          "  }",
          "}");

  private MockJavaResource bazResource =
      JavaResourceBase.createMockJavaResource("com.foo.Baz",
          "package com.foo;",
          "public class Baz {}");

  private MockJavaResource regularFooImplemetorResource =
      JavaResourceBase.createMockJavaResource("com.foo.FooImplementor",
          "package com.foo;",
          "public class FooImplementor implements FooInterface {",
          "  @Override",
          "  public void run() {}",
          "}");

  private MockJavaResource simpleFactory =
      JavaResourceBase.createMockJavaResource("com.foo.SimpleFactory",
          "package com.foo;",
          "public class SimpleFactory {",
          "  public static SimpleIntf getJso() {",
          "    return getJsoImpl();",
          "  };",
          "  public static native SimpleJso getJsoImpl() /*-{",
          "    return null;",
          "  }-*/;",
          "}");

  private MockJavaResource simpleJso =
      JavaResourceBase.createMockJavaResource("com.foo.SimpleJso",
          "package com.foo;",
          "import com.google.gwt.core.client.JavaScriptObject;",
          "public class SimpleJso extends JavaScriptObject implements SimpleIntf {",
          "  protected SimpleJso() {",
          "  }",
          "  public final void method() {",
          "  }",
          "}");

  private MockJavaResource simpleIntf =
      JavaResourceBase.createMockJavaResource("com.foo.SimpleIntf",
          "package com.foo;",
          "import com.google.gwt.core.client.JavaScriptObject;",
          "public interface SimpleIntf {",
          "  public void method();",
          "}");

  private MockJavaResource jsoTestEntryPointResource =
      JavaResourceBase.createMockJavaResource("com.foo.TestEntryPoint",
          "package com.foo;",
          "import com.google.gwt.core.client.EntryPoint;",
          "public class TestEntryPoint implements EntryPoint {",
          "  @Override",
          "  public void onModuleLoad() {",
          "    SimpleFactory.getJso().method();",
          "  }",
          "}");

  private MockResource jsoTestModuleResource =
      JavaResourceBase.createMockResource("com/foo/SimpleModule.gwt.xml",
          "<module>",
          "<source path=''/>",
          "<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 MockResource myWidgetWithWhiteStyleUiXml =
      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'>",
          "  <ui:style>",
          "  .title {",
          "    background-color: white;",
          "  }",
          "  </ui:style>",
          "  <g:HTMLPanel><g:ListBox ui:field='myListBox' visibleItemCount='1'/></g:HTMLPanel>",
          "</ui:UiBinder>");

  private MockResource myWidgetWithGreyStyleUiXml =
      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'>",
          "  <ui:style>",
          "  .title {",
          "    background-color: grey;",
          "  }",
          "  </ui:style>",
          "  <g:HTMLPanel><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 MockResource devirtualizeStringModuleResource =
      JavaResourceBase.createMockResource("com/foo/DevirtualizeStringModule.gwt.xml",
          "<module>",
          "<source path=''/>",
          "<entry-point class='com.foo.DevirtualizeStringEntryPoint'/>",
          "</module>");

  private MockJavaResource devirtualizeStringEntryPointResource =
      JavaResourceBase.createMockJavaResource("com.foo.DevirtualizeStringEntryPoint",
          "package com.foo;",
          "import com.google.gwt.core.client.EntryPoint;",
          "public class DevirtualizeStringEntryPoint implements EntryPoint {",
          "  @Override",
          "  public void onModuleLoad() {",
          "    CharSequence seq = \"A\";",
          "    seq.subSequence(0,1);",
          "  }",
          "}");

  private MockResource transitivelyFoldableConstantModuleResource =
      JavaResourceBase.createMockResource(
          "com/foo/TransitivelyFoldableConstantModule.gwt.xml",
          "<module>",
          "<source path=''/>",
          "<entry-point class='com.foo.TransitivelyFoldableConstantEntryPoint'/>",
          "</module>");

  private MockJavaResource transitivelyFoldableConstantEntryPointResource =
      JavaResourceBase.createMockJavaResource(
          "com.foo.TransitivelyFoldableConstantEntryPoint",
          "package com.foo;",
          "import com.google.gwt.core.client.EntryPoint;",
          "public class TransitivelyFoldableConstantEntryPoint",
          "    implements EntryPoint {",
          "  static final int CONST = 1 + ClassOne.CONST;",
          "  @Override",
          "  public void onModuleLoad() {",
          "    int c = CONST;",
          "  }",
          "}");

  private MockJavaResource classOneResource =
      JavaResourceBase.createMockJavaResource(
          "com.foo.ClassOne",
          "package com.foo;",
          "import com.google.gwt.core.client.EntryPoint;",
          "public class ClassOne {",
          "  static final int CONST = 1 + ClassTwo.CONST;",
          "}");

  private MockJavaResource classTwoResource =
      JavaResourceBase.createMockJavaResource(
          "com.foo.ClassTwo",
          "package com.foo;",
          "import com.google.gwt.core.client.EntryPoint;",
          "public class ClassTwo {",
          "  static final int CONST = 1 + 3;",
          "}");

  private MockResource superFromStaleInnerModuleResource =
      JavaResourceBase.createMockResource(
          "com/foo/SuperFromStaleInnerModule.gwt.xml",
          "<module>",
          "  <source path=''/>",
          "  <entry-point class='com.foo.SuperFromStaleInnerEntryPoint'/>",
          "</module>");

  private MockJavaResource superFromStaleInnerEntryPointResource =
      JavaResourceBase.createMockJavaResource(
          "com.foo.SuperFromStaleInnerEntryPoint",
          "package com.foo;",
          "import com.google.gwt.core.client.EntryPoint;",
          "public class SuperFromStaleInnerEntryPoint",
          "    implements EntryPoint {",
          "  static class A { int f() { return 1; } }",
          "  static class B extends A {",
          "    int f() { return 2; }",
          "    void g() {",
          "      new InterfaceOne() {",
          "        public int m() { return 2 + B.super.f(); }",
          "      }.m();",
          "    }",
          "  }",
          "  @Override",
          "  public void onModuleLoad() {",
          "    new B().g();",
          "  }",
          "}");

  private MockJavaResource interfaceOneResource =
      JavaResourceBase.createMockJavaResource(
          "com.foo.InterfaceOne",
          "package com.foo;",
          "interface InterfaceOne {",
          "  int m();",
          "}");

  private MockResource helloModuleResource =
      JavaResourceBase.createMockResource(
          "com/foo/Hello.gwt.xml",
          "<module>",
          "  <inherits name='com.google.gwt.user.User'/>",
          "  <source path=''/>",
          "  <set-property name='user.agent' value='safari'/>",
          "  <entry-point class='com.foo.HelloEntryPoint'/>",
          "</module>");

  private MockJavaResource helloEntryPointResource =
      JavaResourceBase.createMockJavaResource(
          "com.foo.HelloEntryPoint",
          "package com.foo;",
          "import com.google.gwt.core.client.EntryPoint;",
          "import com.google.gwt.event.dom.client.ClickEvent;",
          "import com.google.gwt.event.dom.client.ClickHandler;",
          "import com.google.gwt.user.client.Window;",
          "import com.google.gwt.user.client.ui.Button;",
          "import com.google.gwt.user.client.ui.RootPanel;",
          "public class HelloEntryPoint implements EntryPoint {",
          "  public void onModuleLoad() {",
          "    Button b = new Button(\"Click me\", new ClickHandler() {",
          "        public void onClick(ClickEvent event) {",
          "          Window.alert(\"Hello, AJAX\");",
          "        }",
          "      });",
          "    RootPanel.get().add(b);" ,
          "  }",
          "}");

  private Set<String> emptySet = stringSet();

  @Override
  protected void setUp() throws Exception {
    super.setUp();
    FooResourceGenerator.runCount = 0;
    BarReferencesFooGenerator.runCount = 0;
    CauseStringRebindGenerator.runCount = 0;
    CauseShortRebindGenerator.runCount = 0;
  }

  public void testAllValidArgs() {
    CompilerOptionsImpl options = new CompilerOptionsImpl();
    Compiler.ArgProcessor argProcessor = new Compiler.ArgProcessor(options);

    assertProcessSuccess(argProcessor, new String[] {"-logLevel", "DEBUG", "-style",
        "PRETTY", "-ea", "-gen", "myGen",
        "-war", "myWar", "-workDir", "myWork", "-extra", "myExtra", "-incremental",
        "-localWorkers", "2", "-sourceLevel", "1.8", "c.g.g.h.H", "my.Module"});

    assertEquals(new File("myGen").getAbsoluteFile(),
        options.getGenDir().getAbsoluteFile());
    assertEquals(new File("myWar"), options.getWarDir());
    assertEquals(new File("myWork"), options.getWorkDir());
    assertEquals(new File("myExtra"), options.getExtraDir());

    assertEquals(2, options.getLocalWorkers());

    assertEquals(TreeLogger.DEBUG, options.getLogLevel());
    assertEquals(JsOutputOption.PRETTY, options.getOutput());
    assertTrue(options.isEnableAssertions());
    assertTrue(options.shouldClusterSimilarFunctions());
    assertTrue(options.shouldInlineLiteralParameters());
    assertFalse(options.shouldOptimizeDataflow());
    assertTrue(options.shouldOrdinalizeEnums());
    assertTrue(options.shouldRemoveDuplicateFunctions());
    assertTrue(options.isIncrementalCompileEnabled());

    assertEquals(SourceLevel.JAVA8, options.getSourceLevel());

    assertEquals(2, options.getModuleNames().size());
    assertEquals("c.g.g.h.H", options.getModuleNames().get(0));
    assertEquals("my.Module", options.getModuleNames().get(1));
  }

  public void testDefaultArgs() {
    CompilerOptionsImpl options = new CompilerOptionsImpl();
    Compiler.ArgProcessor argProcessor = new Compiler.ArgProcessor(options);

    assertProcessSuccess(argProcessor, new String[]{"c.g.g.h.H"});

    assertEquals(null, options.getGenDir());
    assertEquals(new File("war").getAbsoluteFile(),
        options.getWarDir().getAbsoluteFile());
    assertEquals(null, options.getWorkDir());
    assertEquals(null, options.getExtraDir());

    assertEquals(TreeLogger.INFO, options.getLogLevel());
    assertEquals(JsOutputOption.OBFUSCATED, options.getOutput());
    assertFalse(options.isEnableAssertions());
    assertTrue(options.shouldClusterSimilarFunctions());
    assertTrue(options.shouldInlineLiteralParameters());
    assertFalse(options.shouldOptimizeDataflow());
    assertTrue(options.shouldOrdinalizeEnums());
    assertTrue(options.shouldRemoveDuplicateFunctions());
    assertFalse(options.isIncrementalCompileEnabled());

    assertEquals(1, options.getLocalWorkers());

    assertEquals(1, options.getModuleNames().size());
    assertEquals("c.g.g.h.H", options.getModuleNames().get(0));
  }

  public void testForbiddenArgs() {
    CompilerOptionsImpl options = new CompilerOptionsImpl();
    Compiler.ArgProcessor argProcessor = new Compiler.ArgProcessor(options);

    assertProcessFailure(argProcessor, "Unknown argument", new String[]{"-out", "www"});
    assertProcessFailure(argProcessor, "Source level must be one of",
        new String[]{"-sourceLevel", "ssss"});
    assertProcessFailure(argProcessor, "Source level must be one of",
        new String[]{"-sourceLevel", "1.5"});
  }

  /**
   * Tests ordering for emum {@link SourceLevel}.
   */
  public void testSourceLevelOrdering() {
    SourceLevel[] sourceLevels = SourceLevel.values();
    SourceLevel previousSourceLevel = sourceLevels[0];
    for (int i = 1; i < sourceLevels.length; i++) {
      assertTrue(SourceLevel.versionCompare(previousSourceLevel.getStringValue(),
          sourceLevels[i].getStringValue()) < 0);
      previousSourceLevel = sourceLevels[i];
    }
  }

  public void testSourceLevelSelection() {
    assertEquals(SourceLevel.JAVA8, SourceLevel.getBestMatchingVersion("1.4"));
    assertEquals(SourceLevel.JAVA8, SourceLevel.getBestMatchingVersion("1.5"));
    assertEquals(SourceLevel.JAVA8, SourceLevel.getBestMatchingVersion("1.6"));
    assertEquals(SourceLevel.JAVA8, SourceLevel.getBestMatchingVersion("1.6_26"));

    assertEquals(SourceLevel.JAVA8, SourceLevel.getBestMatchingVersion("1.7"));
    assertEquals(SourceLevel.JAVA8, SourceLevel.getBestMatchingVersion("1.8"));
    assertEquals(SourceLevel.JAVA9, SourceLevel.getBestMatchingVersion("9"));
    assertEquals(SourceLevel.JAVA10, SourceLevel.getBestMatchingVersion("10"));
    assertEquals(SourceLevel.JAVA11, SourceLevel.getBestMatchingVersion("11"));

    // not proper version strings => default to JAVA8.
    assertEquals(SourceLevel.JAVA8, SourceLevel.getBestMatchingVersion("1.6u3"));
    assertEquals(SourceLevel.JAVA8, SourceLevel.getBestMatchingVersion("1.6b3"));
    assertEquals(SourceLevel.JAVA8, SourceLevel.getBestMatchingVersion("1.7b3"));
  }

  public void testSourceLevelHighestVersion() {
    assertEquals(SourceLevel.values()[SourceLevel.values().length - 1], SourceLevel.getHighest());
  }

  /**
   * Verify that a compile with a @JsType at least compiles successfully.
   */
  public void testGwtCreateJsTypeRebindResult() throws Exception {
    CompilerOptions compilerOptions = new CompilerOptionsImpl();
    compileToJs(compilerOptions, createTempDir(), "com.foo.SimpleModule",
        Lists.newArrayList(simpleModuleResource, gwtCreateEntryPointResource),
        new MinimalRebuildCache(), emptySet, JsOutputOption.OBFUSCATED);
  }

   /**
   * Test that some lightly referenced interface through a @JsFunction is included in the output.
   */
  public void testReferenceThroughJsFunction() throws Exception {
    MockJavaResource someJsFunction =
        JavaResourceBase.createMockJavaResource(
            "com.foo.SomeJsFunction",
            "package com.foo;",
            "import jsinterop.annotations.JsFunction;",
            "@JsFunction",
            "public interface SomeJsFunction {",
            "  void m();",
            "}");

    MockJavaResource jsFunctionInterfaceImplementation =
        JavaResourceBase.createMockJavaResource(
            "com.foo.Impl",
            "package com.foo;",
            "public final class Impl implements SomeJsFunction {",
            "  public void m() { SomeInterface.class.getName(); } ",
            "}");

    MockJavaResource someInterface =
        JavaResourceBase.createMockJavaResource(
            "com.foo.SomeInterface",
            "package com.foo;",
            "public interface SomeInterface {",
            "}");

    MockJavaResource testEntryPoint =
        JavaResourceBase.createMockJavaResource(
            "com.foo.TestEntryPoint",
            "package com.foo;",
            "import com.google.gwt.core.client.EntryPoint;",
            "public class TestEntryPoint implements EntryPoint {",
            "  private static native void f(SomeJsFunction f) /*-{}-*/;",
            "  public void onModuleLoad() {",
                // Create Impl and pass it to JS but do not explicitly call m
            "    f(new Impl());",
            "  }",
            "}");

    MockResource moduleResource =
        JavaResourceBase.createMockResource(
            "com/foo/TestEntryPoint.gwt.xml",
            "<module>",
            "  <source path=''/>",
            "  <entry-point class='com.foo.TestEntryPoint'/>",
            "</module>");

    CompilerOptions compilerOptions = new CompilerOptionsImpl();
    String js = compileToJs(compilerOptions, createTempDir(), testEntryPoint.getTypeName(),
        Lists.newArrayList(moduleResource, testEntryPoint, someJsFunction,
            jsFunctionInterfaceImplementation, someInterface),
        new MinimalRebuildCache(), emptySet, JsOutputOption.DETAILED);
    // Make sure the referenced class literals ends up being included in the resulting JS.
    String classliteralHolderVarName =
        JjsUtils.mangleMemberName("com.google.gwt.lang.ClassLiteralHolder",
            JjsUtils.classLiteralFieldNameFromJavahTypeSignatureName(
                JjsUtils.javahSignatureFromName(someInterface.getTypeName())));
    assertTrue(js.contains("var " + classliteralHolderVarName + " = "));
  }

  /**
   * Tests that changing Js namespace name on an exported method comes out accurately.
   *
   * <p>An unrelated and non-updated @JsType is also included in each compile to verify that updated
   * exports do not forget non-edited items in a recompile.
   */
  public void testChangeJsNamespaceOnMethod() throws Exception {
    CompilerOptions compilerOptions = new CompilerOptionsImpl();
    compilerOptions.setUseDetailedTypeIds(true);
    compilerOptions.setGenerateJsInteropExports(true);

    MockJavaResource jsNamespaceFooResource =
        JavaResourceBase.createMockJavaResource(
            "com.foo.Foo",
            "package com.foo;",
            "import jsinterop.annotations.JsMethod;",
            "import jsinterop.annotations.JsType;",
            "@JsType public class Foo {",
            "  @JsMethod(namespace=\"spazz\") public static void doStaticBar() {}",
            "}");

    MockJavaResource regularFooResource =
        JavaResourceBase.createMockJavaResource(
            "com.foo.Foo",
            "package com.foo;",
            "import jsinterop.annotations.JsType;",
            "@JsType public class Foo {",
            "  public static void doStaticBar() {}",
            "}");

    checkRecompiledModifiedApp(
        compilerOptions,
        "com.foo.SimpleModule",
        Lists.newArrayList(simpleModuleResource, emptyEntryPointResource, jsTypeBarResource),
        regularFooResource,
        jsNamespaceFooResource,
        stringSet("com.foo.Bar", "com.foo.Foo"),
        JsOutputOption.DETAILED);
  }

  /**
   * Tests that changing Js namespace on a class comes out accurately.
   *
   * <p>An unrelated and non-updated @JsType is also included in each compile to verify that updated
   * exports do not forget non-edited items in a recompile.
   */
  public void testChangeJsNamespaceOnClass() throws Exception {
    CompilerOptions compilerOptions = new CompilerOptionsImpl();
    compilerOptions.setUseDetailedTypeIds(true);
    compilerOptions.setGenerateJsInteropExports(true);

    MockJavaResource jsNamespaceFooResource =
        JavaResourceBase.createMockJavaResource(
            "com.foo.Foo",
            "package com.foo;",
            "import jsinterop.annotations.JsType;",
            "@JsType(namespace=\"spazz\") public class Foo {",
            "  public static void doStaticBar() {}",
            "}");

    MockJavaResource regularFooResource =
        JavaResourceBase.createMockJavaResource(
            "com.foo.Foo",
            "package com.foo;",
            "import jsinterop.annotations.JsType;",
            "@JsType public class Foo {",
            "  public static void doStaticBar() {}",
            "}");

    checkRecompiledModifiedApp(
        compilerOptions,
        "com.foo.SimpleModule",
        Lists.newArrayList(simpleModuleResource, emptyEntryPointResource, jsTypeBarResource),
        regularFooResource,
        jsNamespaceFooResource,
        stringSet("com.foo.Bar", "com.foo.Foo"),
        JsOutputOption.DETAILED);
  }

  /**
   * Tests that changing @JsFunction name on an interface comes out accurately.
   *
   * <p>An unrelated and non-updated @JsType is also included in each compile to verify that updated
   * exports do not forget non-edited items in a recompile.
   */
  public void testChangeJsFunction() throws Exception {
    CompilerOptions compilerOptions = new CompilerOptionsImpl();
    compilerOptions.setUseDetailedTypeIds(true);
    compilerOptions.setGenerateJsInteropExports(true);

    MockJavaResource jsFunctionIFooResource =
        JavaResourceBase.createMockJavaResource(
            "com.foo.IFoo",
            "package com.foo;",
            "import jsinterop.annotations.JsFunction;",
            "@JsFunction public interface IFoo {",
            "  int foo(int x);",
            "}");

    MockJavaResource regularIFooResource =
        JavaResourceBase.createMockJavaResource(
            "com.foo.IFoo",
            "package com.foo;",
            "public interface IFoo {",
            "  int foo(int x);",
            "}");

    MockJavaResource fooResource =
        JavaResourceBase.createMockJavaResource(
            "com.foo.Foo",
            "package com.foo;",
            "public final class Foo implements IFoo {",
            "  @Override public int foo(int x) { return 0; }",
            "}");

    checkRecompiledModifiedApp(
        compilerOptions,
        "com.foo.SimpleModule",
        Lists.newArrayList(
            simpleModuleResource, entryPointResourceForFoo, fooResource, jsTypeBarResource),
        regularIFooResource,
        jsFunctionIFooResource,
        stringSet("com.foo.Bar", "com.foo.Foo", "com.foo.IFoo", "com.foo.TestEntryPoint"),
        JsOutputOption.DETAILED);
  }

  /**
   * Tests that toggling JsProperty methods in an interface comes out accurately.
   *
   * <p>An unrelated and non-updated @JsType is also included in each compile to verify that updated
   * exports do not forget non-edited items in a recompile.
   */
  public void testChangeJsProperty() throws Exception {
    CompilerOptions compilerOptions = new CompilerOptionsImpl();
    compilerOptions.setUseDetailedTypeIds(true);
    compilerOptions.setGenerateJsInteropExports(true);

    MockJavaResource jsPropertyIFooResource =
        JavaResourceBase.createMockJavaResource(
            "com.foo.IFoo",
            "package com.foo;",
            "import jsinterop.annotations.JsProperty;",
            "import jsinterop.annotations.JsType;",
            "@JsType public interface IFoo {",
            "  @JsProperty int getX();",
            "  @JsProperty int getY();",
            "}");

    MockJavaResource regularIFooResource =
        JavaResourceBase.createMockJavaResource(
            "com.foo.IFoo",
            "package com.foo;",
            "import jsinterop.annotations.JsType;",
            "@JsType public interface IFoo {",
            "  int getX();",
            "  int getY();",
            "}");

    MockJavaResource fooResource =
        JavaResourceBase.createMockJavaResource(
            "com.foo.Foo",
            "package com.foo;",
            "public class Foo implements IFoo {",
            "  @Override public int getX() { return 0; }",
            "  @Override public int getY() { return 0; }",
            "}");

    checkRecompiledModifiedApp(
        compilerOptions,
        "com.foo.SimpleModule",
        Lists.newArrayList(
            simpleModuleResource, entryPointResourceForFoo, fooResource, jsTypeBarResource),
        regularIFooResource,
        jsPropertyIFooResource,
        stringSet("com.foo.Bar", "com.foo.Foo", "com.foo.IFoo", "com.foo.TestEntryPoint"),
        JsOutputOption.DETAILED);
  }

  /**
   * Tests that adding a @JsType annotation on a class comes out accurately and that removing it
   * comes out accurately as well.
   *
   * <p>An unrelated and non-updated @JsType is also included in each compile to verify that updated
   * exports do not forget non-edited items in a recompile.
   */
  public void testChangeJsType() throws Exception {
    CompilerOptions compilerOptions = new CompilerOptionsImpl();
    compilerOptions.setUseDetailedTypeIds(true);
    compilerOptions.setGenerateJsInteropExports(true);

    MockJavaResource jsTypeFooResource =
        JavaResourceBase.createMockJavaResource(
            "com.foo.Foo",
            "package com.foo;",
            "import jsinterop.annotations.JsType;",
            "@JsType public class Foo {",
            "  void doInstanceBar() {}",
            "  public static void doStaticBar() {}",
            "}");

    MockJavaResource regularFooResource =
        JavaResourceBase.createMockJavaResource(
            "com.foo.Foo", "package com.foo;", "public class Foo {}");

    checkRecompiledModifiedApp(
        compilerOptions,
        "com.foo.SimpleModule",
        Lists.newArrayList(simpleModuleResource, emptyEntryPointResource, jsTypeBarResource),
        regularFooResource,
        jsTypeFooResource,
        stringSet("com.foo.Bar", "com.foo.Foo"),
        JsOutputOption.DETAILED);
  }

  /**
   * Tests that changing a prototype on a @JsType annotated class comes out accurately.
   *
   * <p>An unrelated and non-updated @JsType is also included in each compile to verify that updated
   * exports do not forget non-edited items in a recompile.
   */
  public void testChangeJsTypeNative() throws Exception {
    CompilerOptions compilerOptions = new CompilerOptionsImpl();
    compilerOptions.setUseDetailedTypeIds(true);
    compilerOptions.setGenerateJsInteropExports(true);

    MockJavaResource nativeFooResource =
        JavaResourceBase.createMockJavaResource(
            "com.foo.Foo",
            "package com.foo;",
            "import jsinterop.annotations.JsType;",
            "@JsType(isNative=true) public class Foo {",
            "  public static native void doStaticBar();",
            "}");

    MockJavaResource regularFooResource =
        JavaResourceBase.createMockJavaResource(
            "com.foo.Foo",
            "package com.foo;",
            "import jsinterop.annotations.JsType;",
            "@JsType public class Foo {",
            "  public static void doStaticBar() {}",
            "}");

    checkRecompiledModifiedApp(
        compilerOptions,
        "com.foo.SimpleModule",
        Lists.newArrayList(simpleModuleResource, emptyEntryPointResource, jsTypeBarResource),
        regularFooResource,
        nativeFooResource,
        stringSet("com.foo.Bar", "com.foo.Foo"),
        JsOutputOption.DETAILED);
  }

  /**
   * Tests that adding a @JsIgnore annotation on a method comes out accurately and that removing
   * it comes out accurately as well.
   *
   * <p>An unrelated and non-updated @JsType is also included in each compile to verify that updated
   * exports do not forget non-edited items in a recompile.
   */
  public void testChangeJsIgnore() throws Exception {
    CompilerOptions compilerOptions = new CompilerOptionsImpl();
    compilerOptions.setUseDetailedTypeIds(true);
    compilerOptions.setGenerateJsInteropExports(true);

    MockJavaResource jsIgnoreFooResource =
        JavaResourceBase.createMockJavaResource(
            "com.foo.Foo",
            "package com.foo;",
            "import jsinterop.annotations.JsIgnore;",
            "import jsinterop.annotations.JsType;",
            "@JsType public class Foo {",
            "  @JsIgnore public static void doStaticBar() {}",
            "}");

    MockJavaResource regularFooResource =
        JavaResourceBase.createMockJavaResource(
            "com.foo.Foo",
            "package com.foo;",
            "import jsinterop.annotations.JsType;",
            "@JsType public class Foo {",
            "  public static void doStaticBar() {}",
            "}");

    checkRecompiledModifiedApp(
        compilerOptions,
        "com.foo.SimpleModule",
        Lists.newArrayList(simpleModuleResource, emptyEntryPointResource, jsTypeBarResource),
        regularFooResource,
        jsIgnoreFooResource,
        stringSet("com.foo.Bar", "com.foo.Foo"),
        JsOutputOption.DETAILED);
  }

  public void testJsInteropNameCollision() throws Exception {
    MinimalRebuildCache minimalRebuildCache = new MinimalRebuildCache();
    File applicationDir = Files.createTempDirectory("JsInteropNameCollision").toFile();
    CompilerOptions compilerOptions = new CompilerOptionsImpl();
    compilerOptions.setGenerateJsInteropExports(true);

    // Simple compile with one dialog.alert() export succeeds.
    compileToJs(compilerOptions, applicationDir, "com.foo.SimpleModule", Lists.newArrayList(
        simpleModuleResource, dialogEntryPointResource, simpleDialogResourceWithExport,
        complexDialogResourceSansExport), minimalRebuildCache, emptySet, JsOutputOption.OBFUSCATED);

    try {
      // Exporting a second dialog.alert() fails with an exported name collision.
      compileToJs(compilerOptions, applicationDir, "com.foo.SimpleModule",
          Lists.<MockResource> newArrayList(complexDialogResourceWithExport), minimalRebuildCache,
          emptySet, JsOutputOption.OBFUSCATED);
      fail("Compile should have failed");
    } catch (UnableToCompleteException e) {
      // success
    }

    // Reverting to just a single dialog.alert() starts succeeding again.
    compileToJs(compilerOptions, applicationDir, "com.foo.SimpleModule",
        Lists.<MockResource>newArrayList(complexDialogResourceSansExport), minimalRebuildCache,
        stringSet("com.foo.SimpleDialog", "com.foo.ComplexDialog", "com.foo.TestEntryPoint"),
        JsOutputOption.OBFUSCATED);
  }

  public void testGwtCreateJsoRebindResult() throws Exception {
    try {
      compileToJs(createTempDir(), "com.foo.SimpleModule",
          Lists.newArrayList(simpleModuleResource, brokenGwtCreateEntryPointResource),
          new MinimalRebuildCache(), emptySet, JsOutputOption.OBFUSCATED);
      fail("Compile should have failed");
    } catch (UnableToCompleteException e) {
      // success
    }
  }

  public void testNonZeroArgConstructorEntryPoint() throws Exception {
    MockResource moduleResource =
        JavaResourceBase.createMockResource(
            "com/foo/NonZeroArgConstructor.gwt.xml",
            "<module>",
            "  <source path=''/>",
            "  <entry-point class='com.foo.NonZeroArgConstructorEntryPoint'/>",
            "</module>");

    MockJavaResource entryPointResource =
        JavaResourceBase.createMockJavaResource(
            "com.foo.NonZeroArgConstructorEntryPoint",
            "package com.foo;",
            "import com.google.gwt.core.client.EntryPoint;",
            "public class NonZeroArgConstructorEntryPoint implements EntryPoint {",
            "  public NonZeroArgConstructorEntryPoint(String s) {",
            "  }",
            "  public void onModuleLoad() {",
            "  }",
            "}");

    MinimalRebuildCache minimalRebuildCache = new MinimalRebuildCache();
    File applicationDir = createTempDir();
    CompilerOptions compilerOptions = new CompilerOptionsImpl();

    UnitTestTreeLogger.Builder builder = new UnitTestTreeLogger.Builder();
    builder.setLowestLogLevel(TreeLogger.ERROR);
    builder.expectError(Pattern.compile("Errors in .*"), null);
    builder.expectError("Line 3: Rebind result 'com.foo.NonZeroArgConstructorEntryPoint' "
        + "has no default (zero argument) constructors", null);
    UnitTestTreeLogger errorLogger = builder.createLogger();
    try {
      // Simple compile with one dialog.alert() export succeeds.

      compileToJs(errorLogger, compilerOptions, applicationDir, "com.foo.NonZeroArgConstructor",
          Lists.newArrayList(moduleResource, entryPointResource), minimalRebuildCache,
          emptySet, JsOutputOption.OBFUSCATED);
      fail("Compile should have failed");
    } catch (UnableToCompleteException expected) {
      errorLogger.assertCorrectLogEntries();
    }
  }

  public void testDeterministicBuild_Draft_StackModeStrip() throws
      UnableToCompleteException, IOException {
    assertDeterministicBuild(HELLO_MODULE_STACKMODE_STRIP, 0);
  }

  public void testDeterministicBuild_Optimized_StackModeStrip() throws
      UnableToCompleteException, IOException {
    assertDeterministicBuild(HELLO_MODULE_STACKMODE_STRIP, 9);
  }

  public void testDeterministicBuild_Draft() throws UnableToCompleteException, IOException {
    assertDeterministicBuild(HELLO_MODULE, 0);
  }

  public void testDeterministicBuild_Optimized() throws UnableToCompleteException, IOException {
    assertDeterministicBuild(HELLO_MODULE, 9);
  }

  public void testSuccessfulCompile_jsoClassLiteralOrder() throws Exception {
    // Crafted resource to make sure the a native subclass is compiled before the JSO class,
    // In the case of native sublcasses the class hierarchy does not match the class literal
    // hierarchy.
    MockJavaResource nativeClassAndSubclass =
        JavaResourceBase.createMockJavaResource(
            "com.foo.MyNativeSubclass",
            "package com.foo;",
            "import jsinterop.annotations.JsType;",
            "@JsType(isNative=true)",
            "class NativeClass {",
            "}",
            "public class MyNativeSubclass extends NativeClass {",
            "}");

    MockJavaResource testEntryPoint =
        JavaResourceBase.createMockJavaResource(
            "com.foo.MyEntryPoint",
            "package com.foo;",
            "import com.foo.MyNativeSubclass;",
            "public class MyEntryPoint extends MyNativeSubclass {",
            "  public void onModuleLoad() {",
            "    Object o = new Object();",
            "    if (MyNativeSubclass.class.getName() == null) ",
            "      o = new MyNativeSubclass();",
            // Make .clazz reachable so that class literals are emmitted with the respective
            // classses.
            "    o.getClass();",
            "  }",
            "}");

    MockResource moduleResource =
        JavaResourceBase.createMockResource(
            "com/foo/MyEntryPoint.gwt.xml",
            "<module>",
            "  <source path=''/>",
            "  <entry-point class='com.foo.MyEntryPoint'/>",
            "</module>");

    CompilerOptions compilerOptions = new CompilerOptionsImpl();
    // Make sure it compiles successfully with no assertions
    compilerOptions.setEnableAssertions(true);
    compilerOptions.setGenerateJsInteropExports(true);
    compilerOptions.setOutput(JsOutputOption.PRETTY);
    compilerOptions.setOptimizationLevel(9);
    assertCompileSucceeds(compilerOptions, testEntryPoint.getTypeName(),
        Lists.newArrayList(moduleResource, nativeClassAndSubclass, testEntryPoint));
  }

  // TODO(stalcup): add recompile tests for file deletion.

  public void testIncrementalRecompile_noop() throws UnableToCompleteException, IOException,
      InterruptedException {
    checkIncrementalRecompile_noop(JsOutputOption.OBFUSCATED);
    checkIncrementalRecompile_noop(JsOutputOption.DETAILED);
  }

  public void testIncrementalRecompile_dateStampChange() throws UnableToCompleteException,
      IOException, InterruptedException {
    checkIncrementalRecompile_dateStampChange(JsOutputOption.OBFUSCATED);
    checkIncrementalRecompile_dateStampChange(JsOutputOption.DETAILED);
  }

  // Repro for bug #9518
  public void testIncrementalRecompile_jsPropertyConsistencyCheck()
      throws UnableToCompleteException,
      IOException, InterruptedException {
    // Supertype defines the getter.
    MockJavaResource superType =
        JavaResourceBase.createMockJavaResource(
            "com.foo.SuperType",
            "package com.foo;",
            "import jsinterop.annotations.JsProperty;",
            "public class SuperType {",
            "  @JsProperty String getString() { return null; }",
            "}");

    // Subtype defines the setter.
    MockJavaResource subType =
        JavaResourceBase.createMockJavaResource(
            "com.foo.SubType",
            "package com.foo;",
            "import jsinterop.annotations.JsProperty;",
            "public class SubType extends SuperType {",
            "  @JsProperty void setString(String s) {}",
            "}");

    MockJavaResource testEntryPoint =
        JavaResourceBase.createMockJavaResource(
            "com.foo.TestEntryPoint",
            "package com.foo;",
            "import com.google.gwt.core.client.EntryPoint;",
            "public class TestEntryPoint implements EntryPoint {",
            "  public void onModuleLoad() {",
            // Create Impl and pass it to JS but do not explicitly call m
            "    new SubType();",
            "  }",
            "}");

    MockResource moduleResource =
        JavaResourceBase.createMockResource(
            "com/foo/TestModule.gwt.xml",
            "<module>",
            "  <source path=''/>",
            "  <entry-point class='com.foo.TestEntryPoint'/>",
            "</module>");

    MinimalRebuildCache relinkMinimalRebuildCache = new MinimalRebuildCache();
    File relinkApplicationDir = createTempDir();

    // Perform a first compile.
    compileToJs(relinkApplicationDir, "com.foo.TestModule",
        Lists.newArrayList(moduleResource, testEntryPoint, subType, superType),
        relinkMinimalRebuildCache, null, JsOutputOption.OBFUSCATED);

    // Invalidate ONLY the subtype. The types referred by the parameters of supertype methods are
    // going to be reference only which means that the supertype property will have a reference
    // to a JClassType for "java.lang.String" that isExternal() and is different from the
    // (non external) reference in the supertype.
    relinkMinimalRebuildCache.markSourceFileStale("com/foo/SubType.java");
    compileToJs(relinkApplicationDir, "com.foo.TestModule", Lists.<MockResource> newArrayList(),
        relinkMinimalRebuildCache, null, JsOutputOption.OBFUSCATED);
  }

  public void testIncrementalRecompile_invalidatePreamble() throws UnableToCompleteException,
      IOException, InterruptedException {
    MinimalRebuildCache relinkMinimalRebuildCache = new MinimalRebuildCache();
    File relinkApplicationDir = createTempDir();

    // Perform a first compile.
    compileToJs(relinkApplicationDir, "com.foo.SimpleModule",
        Lists.newArrayList(simpleModuleResource, emptyEntryPointResource),
        relinkMinimalRebuildCache, null, JsOutputOption.OBFUSCATED);
    // On first compile nothing is explicitly stale, only implicitly stale.
    assertEquals(0, relinkMinimalRebuildCache.getStaleTypeNames().size());

    // Recompile with a deep change that invalidates the preamble.
    relinkMinimalRebuildCache.markSourceFileStale("java/lang/Object.java");
    compileToJs(relinkApplicationDir, "com.foo.SimpleModule", Lists.<MockResource> newArrayList(),
        relinkMinimalRebuildCache, null, JsOutputOption.OBFUSCATED);
    // Show that preamble invalidation marks everything stale.
    assertTrue(relinkMinimalRebuildCache.getProcessedStaleTypeNames().size() > 100);

    // Recompile again with a tiny change. Prove that it's not stuck repeatedly invalidating the
    // whole world.
    compileToJs(relinkApplicationDir, "com.foo.SimpleModule",
        Lists.<MockResource> newArrayList(emptyEntryPointResource), relinkMinimalRebuildCache, null,
        JsOutputOption.OBFUSCATED);
    // Show that only this little change is stale, not the whole world.
    assertEquals(2, getStaleTypeNames(relinkMinimalRebuildCache).size());
  }

  public void testIncrementalRecompile_bridgeMethodOverrideChain()
      throws UnableToCompleteException, IOException, InterruptedException {
    MinimalRebuildCache relinkMinimalRebuildCache = new MinimalRebuildCache();
    File relinkApplicationDir = createTempDir();

    // Perform a first compile.
    compileToJs(relinkApplicationDir, "com.foo.SimpleModule", Lists.newArrayList(
        simpleModuleResource, overriddenMethodChainEntryPointResource, topResource, middleResource,
        bottomResource), relinkMinimalRebuildCache, null, JsOutputOption.OBFUSCATED);
    // On first compile nothing is explicitly stale, only implicitly stale.
    assertEquals(0, relinkMinimalRebuildCache.getStaleTypeNames().size());

    // Recompile with a change to Bottom.
    relinkMinimalRebuildCache.markSourceFileStale("com/foo/Bottom.java");
    compileToJs(relinkApplicationDir, "com.foo.SimpleModule", Lists.<MockResource> newArrayList(),
        relinkMinimalRebuildCache, null, JsOutputOption.OBFUSCATED);
    // Show that the third level bridge method override of Top.run() is seen to be live and thus
    // makes type com.foo.Bottom$Value live.
    assertTrue(
        relinkMinimalRebuildCache.getProcessedStaleTypeNames().contains("com.foo.Bottom$Value"));
  }

  public void testIncrementalRecompile_classLiteralNewReference()
      throws UnableToCompleteException, IOException, InterruptedException {
    checkIncrementalRecompile_classLiteralNewReference(JsOutputOption.OBFUSCATED);
    checkIncrementalRecompile_classLiteralNewReference(JsOutputOption.DETAILED);
  }

  public void testIncrementalRecompile_primitiveClassLiteralReference()
      throws UnableToCompleteException, IOException, InterruptedException {
    checkIncrementalRecompile_primitiveClassLiteralReference(JsOutputOption.OBFUSCATED);
    checkIncrementalRecompile_primitiveClassLiteralReference(JsOutputOption.DETAILED);
  }

  public void testIncrementalRecompile_superClassOrder()
      throws UnableToCompleteException, IOException, InterruptedException {
    // Linked output is sorted alphabetically except that super-classes come before sub-classes. If
    // on recompile a sub-class -> super-class relationship is lost then a sub-class with an
    // alphabetically earlier name might start linking out before the super-class.
    checkIncrementalRecompile_superClassOrder(JsOutputOption.OBFUSCATED);
    checkIncrementalRecompile_superClassOrder(JsOutputOption.DETAILED);
  }

  public void testIncrementalRecompile_superFromStaleInner()
      throws UnableToCompleteException, IOException, InterruptedException {
    checkIncrementalRecompile_superFromStaleInner(JsOutputOption.OBFUSCATED);
    checkIncrementalRecompile_superFromStaleInner(JsOutputOption.DETAILED);
  }

  public void testIncrementalRecompile_deterministicUiBinder() throws UnableToCompleteException,
      IOException, InterruptedException {
    checkIncrementalRecompile_deterministicUiBinder(JsOutputOption.OBFUSCATED);
    checkIncrementalRecompile_deterministicUiBinder(JsOutputOption.DETAILED);
  }

  public void testIncrementalRecompile_uiBinderCssChange() throws UnableToCompleteException,
      IOException, InterruptedException {
    checkIncrementalRecompile_uiBinderCssChange(JsOutputOption.OBFUSCATED);
    checkIncrementalRecompile_uiBinderCssChange(JsOutputOption.DETAILED);
  }

  public void testIncrementalRecompile_unstableGeneratorReferencesModifiedType()
      throws UnableToCompleteException, IOException, InterruptedException {
    checkIncrementalRecompile_unstableGeneratorReferencesModifiedType(JsOutputOption.OBFUSCATED);
    checkIncrementalRecompile_unstableGeneratorReferencesModifiedType(JsOutputOption.DETAILED);
  }

  public void testIncrementalRecompile_withErrors()
      throws UnableToCompleteException, IOException, InterruptedException {
    MockResource moduleResource =
        JavaResourceBase.createMockResource(
            "com/foo/Errors.gwt.xml",
            "<module>",
            "  <source path=''/>",
            "  <entry-point class='com.foo.ErrorsEntryPoint'/>",
            "</module>");

    MockJavaResource entryPointResource =
        JavaResourceBase.createMockJavaResource(
            "com.foo.ErrorsEntryPoint",
            "package com.foo;",
            "import com.google.gwt.core.client.EntryPoint;",
            "public class ErrorsEntryPoint implements EntryPoint {",
            "  public void onModuleLoad() {",
            "    Foo.foo();",
            "  }",
            "}");

    MockJavaResource fooResource =
        JavaResourceBase.createMockJavaResource(
            "com.foo.Foo",
            "package com.foo;",
            "public class Foo {",
            "  public static void foo() {",
            "  }",
            "}");

    MockJavaResource fooResourceWithErrors =
        JavaResourceBase.createMockJavaResource(
            "com.foo.Foo",
            "package com.foo;",
            "public class Foo {",
            "  public static void foo() {",
            "    // x() method is not defined anywhere, should result in a compile error.",
            "    x();",
            "  }",
            "}");

    MinimalRebuildCache minimalRebuildCache = new MinimalRebuildCache();
    File applicationDir = createTempDir();
    CompilerOptions compilerOptions = new CompilerOptionsImpl();
    compilerOptions.setUseDetailedTypeIds(true);
    compilerOptions.setSourceLevel(SourceLevel.JAVA11);

    // Compile the application with no errors.
    compileToJs(TreeLogger.NULL, compilerOptions, applicationDir, "com.foo.Errors",
        Lists.newArrayList(moduleResource, entryPointResource, fooResource), minimalRebuildCache,
        emptySet, JsOutputOption.OBFUSCATED);

    // Recompile and expect error reporting.
    UnitTestTreeLogger.Builder builder = new UnitTestTreeLogger.Builder();
    builder.setLowestLogLevel(TreeLogger.ERROR);
    builder.expectError("Line 5: The method x() is undefined for the type Foo", null);
    UnitTestTreeLogger errorLogger = builder.createLogger();
    try {
      // Recompile but now the changed file has an error
      compileToJs(errorLogger, compilerOptions, applicationDir, "com.foo.Errors",
          Lists.newArrayList(moduleResource, entryPointResource, fooResourceWithErrors),
          minimalRebuildCache,
          emptySet, JsOutputOption.OBFUSCATED);
      fail("Compile should have failed");
    } catch (UnableToCompleteException expected) {
      errorLogger.assertLogEntriesContainExpected();
    }
  }

  public void testIncrementalRecompile_representedAsNative()
      throws UnableToCompleteException, IOException, InterruptedException {
    MockResource moduleResource =
        JavaResourceBase.createMockResource(
            "com/foo/RepresentedAsNative.gwt.xml",
            "<module>",
            "  <source path=''/>",
            "  <entry-point class='com.foo.RepresentedAsNativeEntryPoint'/>",
            "</module>");

    MockResource entryPointResource =
        JavaResourceBase.createMockJavaResource(
            "com.foo.RepresentedAsNativeEntryPoint",
            "package com.foo;",
            "import com.google.gwt.core.client.EntryPoint;",
            "public class RepresentedAsNativeEntryPoint implements EntryPoint {",
            "  public void onModuleLoad() {",
            "  Double d = new Double(1d);",
            "  }",
            "}");

    MockResource modifiedEntryPointResource =
        JavaResourceBase.createMockJavaResource(
            "com.foo.RepresentedAsNativeEntryPoint",
            "package com.foo;",
            "import com.google.gwt.core.client.EntryPoint;",
            "public class RepresentedAsNativeEntryPoint implements EntryPoint {",
            "  public void onModuleLoad() {",
            "  Double d = new Double(\"1\");",
            "  }",
            "}");

    PrintWriterTreeLogger logger = new PrintWriterTreeLogger();
    logger.setMaxDetail(TreeLogger.ERROR);

    MinimalRebuildCache minimalRebuildCache = new MinimalRebuildCache();
    File applicationDir = createTempDir();
    CompilerOptions compilerOptions = new CompilerOptionsImpl();
    compilerOptions.setUseDetailedTypeIds(true);
    compilerOptions.setSourceLevel(SourceLevel.JAVA11);
    compilerOptions.setGenerateJsInteropExports(false);

    // Compile the application with no errors.
    compileToJs(logger, compilerOptions, applicationDir, "com.foo.RepresentedAsNative",
        Lists.newArrayList(moduleResource, entryPointResource), minimalRebuildCache,
        emptySet, JsOutputOption.OBFUSCATED);

    // Recompile but now the changed file has an error
    compileToJs(logger, compilerOptions, applicationDir, "com.foo.RepresentedAsNative",
        Lists.newArrayList(modifiedEntryPointResource),
        minimalRebuildCache,
        stringSet(
            "com.foo.RepresentedAsNativeEntryPoint",
            getEntryMethodHolderTypeName("com.foo.RepresentedAsNative")),
        JsOutputOption.OBFUSCATED);
  }

  public void testIncrementalRecompile_functionSignatureChange() throws UnableToCompleteException,
      IOException, InterruptedException {
    // Not testing recompile equality with Pretty/Obfuscated output since the JsIncrementalNamer's
    // behavior is order dependent, and while still correct, will come out different in a recompile
    // with this change versus a from scratch compile with this change.
    checkIncrementalRecompile_functionSignatureChange(JsOutputOption.DETAILED);
  }

  public void testIncrementalRecompile_compileTimeConstantChange() throws UnableToCompleteException,
      IOException, InterruptedException {
    checkIncrementalRecompile_compileTimeConstantChange(JsOutputOption.DETAILED);
    checkIncrementalRecompile_compileTimeConstantChange(JsOutputOption.OBFUSCATED);
  }

  public void testIncrementalRecompile_transitivelyFoldableConstant()
      throws UnableToCompleteException,
      IOException, InterruptedException {
    checkIncrementalRecompile_transitivelyFoldableConstant(JsOutputOption.DETAILED);
    checkIncrementalRecompile_transitivelyFoldableConstant(JsOutputOption.OBFUSCATED);
  }

  public void testIncrementalRecompile_packagePrivateDispatch() throws UnableToCompleteException,
      IOException, InterruptedException {
    checkIncrementalRecompile_packagePrivateOverride(JsOutputOption.OBFUSCATED);
    checkIncrementalRecompile_packagePrivateOverride(JsOutputOption.DETAILED);
  }

  public void testIncrementalRecompile_prettyOutput()
      throws UnableToCompleteException, IOException, InterruptedException {
    // Nominal tests for pretty output. Pretty and Obfuscated output share most of the same code
    // paths.
    checkIncrementalRecompile_typeHierarchyChange(JsOutputOption.PRETTY);
    checkIncrementalRecompile_unreachableIncompatibleChange(JsOutputOption.PRETTY);
  }

  public void testIncrementalRecompile_regularClassMadeIntoJsoClass()
      throws UnableToCompleteException, IOException, InterruptedException {
    // Not testing recompile equality with Pretty/Obfuscated output since the JsIncrementalNamer's
    // behavior is order dependent, and while still correct, will come out different in a recompile
    // with this change versus a from scratch compile with this change.
    checkIncrementalRecompile_regularClassMadeIntoJsoClass(JsOutputOption.DETAILED);
  }

  public void testIncrementalRecompile_unreachableIncompatibleChange()
      throws UnableToCompleteException, IOException, InterruptedException {
    // Not testing recompile equality with Pretty/Obfuscated output since the JsIncrementalNamer's
    // behavior is order dependent, and while still correct, will come out different in a recompile
    // with this change versus a from scratch compile with this change.
    checkIncrementalRecompile_unreachableIncompatibleChange(JsOutputOption.OBFUSCATED);
    checkIncrementalRecompile_unreachableIncompatibleChange(JsOutputOption.DETAILED);
  }

  public void testIncrementalRecompile_typeHierarchyChange()
      throws UnableToCompleteException, IOException, InterruptedException {
    checkIncrementalRecompile_typeHierarchyChange(JsOutputOption.OBFUSCATED);
    checkIncrementalRecompile_typeHierarchyChange(JsOutputOption.DETAILED);
  }

  public void testIncrementalRecompile_defaultMethod()
      throws UnableToCompleteException, IOException, InterruptedException {
    // Tests that default method on superclasses are correctly constructed
    checkIncrementalRecompile_defaultMethod(JsOutputOption.OBFUSCATED);
    checkIncrementalRecompile_defaultMethod(JsOutputOption.DETAILED);
  }

  public void testIncrementalRecompile_devirtualizeUnchangedJso()
      throws UnableToCompleteException, IOException, InterruptedException {
    // Tests that a JSO calls through interfaces are correctly devirtualized when compiling per file
    // and the JSOs nor their single impl interfaces are not stale.
    checkIncrementalRecompile_devirtualizeUnchangedJso(JsOutputOption.OBFUSCATED);
    checkIncrementalRecompile_devirtualizeUnchangedJso(JsOutputOption.DETAILED);
  }

  public void testIncrementalRecompile_devirtualizeString()
      throws UnableToCompleteException, IOException, InterruptedException {
    // Tests that String calls through interfaces are correctly devirtualized when compiling per
    // file and neither String nor CharSequence interface are stale.
    checkIncrementalRecompile_devirtualizeString(JsOutputOption.OBFUSCATED);
    checkIncrementalRecompile_devirtualizeString(JsOutputOption.DETAILED);
  }

  public void testIncrementalRecompile_devirtualizeComparable()
      throws UnableToCompleteException, IOException, InterruptedException {
    // Tests that Doublecalls through interfaces are correctly devirtualized when compiling per
    // file and neither String nor CharSequence interface are stale.
    checkIncrementalRecompile_devirtualizeComparable(JsOutputOption.OBFUSCATED);
    checkIncrementalRecompile_devirtualizeComparable(JsOutputOption.DETAILED);
  }

  public void testIncrementalRecompile_multipleClassGenerator()
      throws UnableToCompleteException, IOException, InterruptedException {
    // Tests that a Generated type that is not directly referenced from the rebound GWT.create()
    // call is still marked stale, regenerated, retraversed and output as JS.
    checkIncrementalRecompile_multipleClassGenerator(JsOutputOption.OBFUSCATED);
    checkIncrementalRecompile_multipleClassGenerator(JsOutputOption.DETAILED);
  }

  public void testIncrementalRecompile_singleJsoIntfDispatchChange()
      throws UnableToCompleteException,
      IOException, InterruptedException {
    // Not testing recompile equality with Pretty/Obfuscated output since the JsIncrementalNamer's
    // behavior is order dependent, and while still correct, will come out different in a recompile
    // with this change versus a from scratch compile with this change.
    checkIncrementalRecompile_singleJsoIntfDispatchChange(JsOutputOption.DETAILED);
  }

  public void testIncrementalRecompile_dualJsoIntfDispatchChange() throws UnableToCompleteException,
      IOException, InterruptedException {
    // Not testing recompile equality with Pretty/Obfuscated output since the JsIncrementalNamer's
    // behavior is order dependent, and while still correct, will come out different in a recompile
    // with this change versus a from scratch compile with this change.
    checkIncrementalRecompile_dualJsoIntfDispatchChange(JsOutputOption.DETAILED);
  }

  public void testIncrementalRecompile_generatorInputResourceChange() throws IOException,
      UnableToCompleteException, InterruptedException {
    // Not testing recompile equality with Pretty/Obfuscated output since the JsIncrementalNamer's
    // behavior is order dependent, and while still correct, will come out different in a recompile
    // with this change versus a from scratch compile with this change.
    checkIncrementalRecompile_generatorInputResourceChange(JsOutputOption.DETAILED);
  }

  public void testIncrementalRecompile_invalidatedGeneratorOutputRerunsGenerator()
      throws UnableToCompleteException, IOException, InterruptedException {
    // BarReferencesFoo Generator hasn't run yet.
    assertEquals(0, BarReferencesFooGenerator.runCount);

    CompilerOptions compilerOptions = new CompilerOptionsImpl();
    List<MockResource> sharedResources =
        Lists.newArrayList(barReferencesFooGeneratorModuleResource, generatorEntryPointResource);
    JsOutputOption output = JsOutputOption.OBFUSCATED;

    List<MockResource> originalResources = Lists.newArrayList(sharedResources);
    originalResources.add(fooResource);

    // Compile the app with original files, modify a file and do a per-file recompile.
    MinimalRebuildCache relinkMinimalRebuildCache = new MinimalRebuildCache();
    File relinkApplicationDir = createTempDir();
    compileToJs(compilerOptions, relinkApplicationDir, "com.foo.SimpleModule", originalResources,
        relinkMinimalRebuildCache, emptySet, output);

    // BarReferencesFoo Generator has now been run once.
    assertEquals(1, BarReferencesFooGenerator.runCount);

    // Recompile with no changes, which should not trigger any Generator runs.
    compileToJs(compilerOptions, relinkApplicationDir, "com.foo.SimpleModule",
        Lists.<MockResource>newArrayList(), relinkMinimalRebuildCache, emptySet, output);

    // Since there were no changes BarReferencesFoo Generator was not run again.
    assertEquals(1, BarReferencesFooGenerator.runCount);

    // 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,
        stringSet("com.foo.TestEntryPoint", "com.foo.Foo", "com.foo.Bar"), output);

    // BarReferencesFoo Generator was run again.
    assertEquals(2, BarReferencesFooGenerator.runCount);
  }

  public void testIncrementalRecompile_invalidatedGeneratorOutputRerunsCascadedGenerators()
      throws UnableToCompleteException, IOException, InterruptedException {
    // Generators haven't run yet.
    assertEquals(0, CauseStringRebindGenerator.runCount);
    assertEquals(0, CauseShortRebindGenerator.runCount);
    assertEquals(0, FooResourceGenerator.runCount);

    CompilerOptions compilerOptions = new CompilerOptionsImpl();
    List<MockResource> sharedResources = Lists.newArrayList(cascadingGeneratorModuleResource,
        generatorEntryPointResource, classNameToGenerateResource);
    JsOutputOption output = JsOutputOption.OBFUSCATED;

    List<MockResource> originalResources = Lists.newArrayList(sharedResources);

    // Compile the app with original files.
    MinimalRebuildCache relinkMinimalRebuildCache = new MinimalRebuildCache();
    File relinkApplicationDir = createTempDir();
    compileToJs(compilerOptions, relinkApplicationDir, "com.foo.SimpleModule", originalResources,
        relinkMinimalRebuildCache, emptySet, output);

    // Generators have now been run once.
    assertEquals(1, CauseStringRebindGenerator.runCount);
    assertEquals(1, CauseShortRebindGenerator.runCount);
    assertEquals(1, FooResourceGenerator.runCount);

    // Recompile with no changes, which should not trigger any Generator runs.
    compileToJs(compilerOptions, relinkApplicationDir, "com.foo.SimpleModule",
        Lists.<MockResource>newArrayList(), relinkMinimalRebuildCache, emptySet, output);

    // Since there were no changes Generators were not run again.
    assertEquals(1, CauseStringRebindGenerator.runCount);
    assertEquals(1, CauseShortRebindGenerator.runCount);
    assertEquals(1, FooResourceGenerator.runCount);

    // Recompile with a modified resource, which should invalidate the output of the
    // FooResourceGenerator and cascade the invalidate the Generators that triggered
    // FooResourceGenerator.
    compileToJs(compilerOptions, relinkApplicationDir, "com.foo.SimpleModule",
        Lists.<MockResource>newArrayList(modifiedClassNameToGenerateResource),
        relinkMinimalRebuildCache, stringSet("com.foo.TestEntryPoint", "com.foo.Baz$InnerBaz",
            "com.foo.Bar", "com.foo.HasCustomContent", "com.foo.FooReplacementTwo"), output);

    // Generators were run again.
    assertEquals(2, CauseStringRebindGenerator.runCount);
    assertEquals(2, CauseShortRebindGenerator.runCount);
    assertEquals(2, FooResourceGenerator.runCount);
  }

  public void testIncrementalRecompile_carriesOverGeneratorArtifacts()
      throws UnableToCompleteException,
      IOException, InterruptedException {
    // Foo Generator hasn't run yet.
    assertEquals(0, FooResourceGenerator.runCount);

    CompilerOptions compilerOptions = new CompilerOptionsImpl();
    List<MockResource> sharedResources = Lists.newArrayList(resourceReadingGeneratorModuleResource,
        referencesBarAndGeneratorEntryPointResource, classNameToGenerateResource,
        barReferencesFooResource);
    JsOutputOption output = JsOutputOption.OBFUSCATED;

    List<MockResource> originalResources = Lists.newArrayList(sharedResources);
    originalResources.add(fooResource);

    // Compile the app with original files.
    MinimalRebuildCache relinkMinimalRebuildCache = new MinimalRebuildCache();
    File relinkApplicationDir = createTempDir();
    compileToJs(compilerOptions, relinkApplicationDir, "com.foo.SimpleModule", originalResources,
        relinkMinimalRebuildCache, emptySet, output);

    // Foo Generator has now been run once.
    assertEquals(1, FooResourceGenerator.runCount);
    // The bar.txt artifact was output.
    File barFile = new File(relinkApplicationDir.getPath() + File.separator + "com.foo.SimpleModule"
        + File.separator + "bar.txt");
    assertTrue(barFile.exists());

    // Recompile with just 1 file change, which should not trigger any Generator runs.
    compileToJs(compilerOptions, relinkApplicationDir, "com.foo.SimpleModule",
        Lists.<MockResource> newArrayList(fooResource), relinkMinimalRebuildCache,
        stringSet("com.foo.Foo", "com.foo.Bar"), output);

    // Foo Generator was not run again.
    assertEquals(1, FooResourceGenerator.runCount);
    // But the bar.txt artifact was still output.
    barFile = new File(relinkApplicationDir.getPath() + File.separator + "com.foo.SimpleModule"
        + File.separator + "bar.txt");
    assertTrue(barFile.exists());
  }

  /**
   * Regression test for UnifyAST assertion failure problem in incremental SDM.
   */
  public void testIncrementalRecompile_unifyASTAssertionRegression()
      throws UnableToCompleteException, IOException, InterruptedException {

    CompilerOptions compilerOptions = new CompilerOptionsImpl();
    List<MockResource> originalResources = Lists.newArrayList(helloEntryPointResource,
        helloModuleResource);
    JsOutputOption output = JsOutputOption.OBFUSCATED;

    // Compile the app with original files.
    MinimalRebuildCache relinkMinimalRebuildCache = new MinimalRebuildCache();
    File relinkApplicationDir = createTempDir();
    compileToJs(compilerOptions, relinkApplicationDir, "com.foo.Hello",
        originalResources, relinkMinimalRebuildCache, emptySet, output);
  }

  private void checkIncrementalRecompile_noop(JsOutputOption output) throws UnableToCompleteException,
      IOException, InterruptedException {
    MinimalRebuildCache relinkMinimalRebuildCache = new MinimalRebuildCache();
    File relinkApplicationDir = createTempDir();

    String originalJs = compileToJs(relinkApplicationDir, "com.foo.SimpleModule", Lists
        .newArrayList(simpleModuleResource, simpleModelEntryPointResource, simpleModelResource,
            constantsModelResource),
        relinkMinimalRebuildCache, emptySet, output);

    // Compile again with absolutely no file changes and reusing the minimalRebuildCache.
    String relinkedJs = compileToJs(relinkApplicationDir, "com.foo.SimpleModule",
        Lists.<MockResource>newArrayList(), relinkMinimalRebuildCache, emptySet, output);

    assertTrue(originalJs.equals(relinkedJs));
  }

  private void checkIncrementalRecompile_defaultMethod(JsOutputOption output)
      throws UnableToCompleteException, IOException, InterruptedException {
    // Tests that when a superclass has a "inherits" a default method,
    MockJavaResource interfaceWithDefaultMethod =
        JavaResourceBase.createMockJavaResource(
            "com.foo.InterfaceWithDefaultMethod",
            "package com.foo;",
            "interface InterfaceWithDefaultMethod {",
            "  default String m() {return null; }",
            "}");

    MockJavaResource classImplementingInterfaceWithDefaultMethod =
        JavaResourceBase.createMockJavaResource(
            "com.foo.classImplementingInterfaceWithDefaultMethod",
            "package com.foo;",
            "public class classImplementingInterfaceWithDefaultMethod",
            "    implements InterfaceWithDefaultMethod {",
            "}");

    MockJavaResource aSubclass =
        JavaResourceBase.createMockJavaResource(
            "com.foo.ASubclass",
            "package com.foo;",
            "public class ASubclass extends classImplementingInterfaceWithDefaultMethod {",
            "  public String someMethod() {return super.m(); }",
            "}");

    MockResource moduleResource =
        JavaResourceBase.createMockResource(
            "com/foo/DefaultMethod.gwt.xml",
            "<module>",
            "  <source path=''/>",
            "  <entry-point class='com.foo.DefaultMethodEntryPoint'/>",
            "</module>");

    MockJavaResource entryPointResource =
        JavaResourceBase.createMockJavaResource(
            "com.foo.DefaultMethodEntryPoint",
            "package com.foo;",
            "import com.google.gwt.core.client.EntryPoint;",
            "public class DefaultMethodEntryPoint implements EntryPoint {",
            "  public void onModuleLoad() {",
            "    new ASubclass().someMethod();",
            "  }",
            "}");

    CompilerOptions compilerOptions = new CompilerOptionsImpl();
    compilerOptions.setUseDetailedTypeIds(true);
    compilerOptions.setSourceLevel(SourceLevel.JAVA11);

    checkRecompiledModifiedApp(compilerOptions, "com.foo.DefaultMethod",
        Lists.newArrayList(moduleResource, entryPointResource, aSubclass,
            classImplementingInterfaceWithDefaultMethod, interfaceWithDefaultMethod),
        aSubclass, aSubclass, stringSet("com.foo.ASubclass", "com.foo.DefaultMethodEntryPoint"),
        output);
  }

  public void checkIncrementalRecompile_classLiteralNewReference(JsOutputOption output)
      throws UnableToCompleteException, IOException, InterruptedException {
    MockJavaResource interfaceA =
        JavaResourceBase.createMockJavaResource(
            "com.foo.A",
            "package com.foo;",
            "interface A {",
            " static String b = \"\";",
            "}");

    MockJavaResource classBSansLiteralReference =
        JavaResourceBase.createMockJavaResource(
            "com.foo.B",
            "package com.foo;",
            "public class B {",
            "}");

    MockJavaResource classBWithLiteralReference =
        JavaResourceBase.createMockJavaResource(
            "com.foo.B",
            "package com.foo;",
            "public class B {",
            "  public B() { Class c = A.class; }",
            "}");

    MockJavaResource classC =
        JavaResourceBase.createMockJavaResource(
            "com.foo.C",
            "package com.foo;",
            "import com.google.gwt.core.client.EntryPoint;",
            "public class C implements EntryPoint {",
            "  public void onModuleLoad() {",
            "    if (A.b == null) ",
            "      new B();",
            "  }",
            "}");

    MockResource moduleResource =
        JavaResourceBase.createMockResource(
            "com/foo/ClassLiteralReference.gwt.xml",
            "<module>",
            "  <source path=''/>",
            "  <entry-point class='com.foo.C'/>",
            "</module>");

    checkRecompiledModifiedApp("com.foo.ClassLiteralReference", Lists.newArrayList(
            moduleResource, interfaceA, classC),
        classBSansLiteralReference, classBWithLiteralReference,
        stringSet("com.foo.B", "com.foo.C"), output);
  }

  public void checkIncrementalRecompile_primitiveClassLiteralReference(JsOutputOption output)
      throws UnableToCompleteException, IOException, InterruptedException {
    MockJavaResource classA =
        JavaResourceBase.createMockJavaResource(
            "com.foo.A",
            "package com.foo;",
            "public class A {",
            "  public A() { ",
            "    Class c = void.class; ",
            "    c = int.class; ",
            "    c = boolean.class;",
            "    c = short.class;",
            "    c = byte.class;",
            "    c = long.class;",
            "    c = float.class;",
            "    c = double.class;",
            "  }",
            "}");

    MockJavaResource classB =
        JavaResourceBase.createMockJavaResource(
            "com.foo.B",
            "package com.foo;",
            "public class B {",
            "  public B() {  }",
            "  public void m () { new A(); }",
            "}");

    MockJavaResource classC =
        JavaResourceBase.createMockJavaResource(
            "com.foo.C",
            "package com.foo;",
            "import com.google.gwt.core.client.EntryPoint;",
            "public class C implements EntryPoint {",
            "  public void onModuleLoad() {",
            "      new B().m();",
            "  }",
            "}");

    MockResource moduleResource =
        JavaResourceBase.createMockResource(
            "com/foo/PrimitiveClassLiteralReference.gwt.xml",
            "<module>",
            "  <source path=''/>",
            "  <entry-point class='com.foo.C'/>",
            "</module>");

    checkRecompiledModifiedApp("com.foo.PrimitiveClassLiteralReference", Lists.newArrayList(
            moduleResource, classA, classB),
        classC, classC,
        stringSet(getEntryMethodHolderTypeName("com.foo.PrimitiveClassLiteralReference"),
            "com.foo.C"), output);
  }

  private void checkIncrementalRecompile_dateStampChange(JsOutputOption output)
      throws UnableToCompleteException, IOException, InterruptedException {
    MinimalRebuildCache relinkMinimalRebuildCache = new MinimalRebuildCache();
    File relinkApplicationDir = createTempDir();

    String originalJs = compileToJs(relinkApplicationDir, "com.foo.SimpleModule",
        Lists.newArrayList(simpleModuleResource, simpleModelEntryPointResource, simpleModelResource,
            constantsModelResource),
        relinkMinimalRebuildCache, emptySet, output);

    // Compile again with the same source but a new date stamp on SimpleModel and reusing the
    // minimalRebuildCache.
    String relinkedJs = compileToJs(relinkApplicationDir, "com.foo.SimpleModule",
        Lists.<MockResource>newArrayList(simpleModelResource), relinkMinimalRebuildCache,
        stringSet("com.foo.TestEntryPoint", "com.foo.SimpleModel"), output);

    assertTrue(originalJs.equals(relinkedJs));
  }

  private void checkIncrementalRecompile_superClassOrder(JsOutputOption output)
      throws UnableToCompleteException, IOException, InterruptedException {
    // Mark ReferencedBySuperClassResource modified, so that SuperClass becomes stale. This will
    // result in SuperClass's indexing being rebuilt but NOT ASubClasses's. If there is a bug in the
    // index updates then the link output ordering will change.
    checkRecompiledModifiedApp("com.foo.SimpleModule", Lists.newArrayList(simpleModuleResource,
            superClassOrderEntryPointResource, referencedBySuperClassResource, superClassResource,
            aSubClassResource), referencedBySuperClassResource, referencedBySuperClassResource,
        stringSet("com.foo.SuperClass", "com.foo.ReferencedBySuperClass"), output);
  }

  private void checkIncrementalRecompile_superFromStaleInner(JsOutputOption output)
      throws UnableToCompleteException, IOException, InterruptedException {
    // Makes sure that type ids for (effectively static) super dispatches, i.e. of the form
    // super.m() or X.this.m(), are computed also for classes only referenced by stale classes.
    checkRecompiledModifiedApp("com.foo.SuperFromStaleInnerModule",
        Lists.newArrayList(superFromStaleInnerModuleResource,
            superFromStaleInnerEntryPointResource), interfaceOneResource, interfaceOneResource,
        stringSet(interfaceOneResource.getTypeName(), "com.foo.SuperFromStaleInnerEntryPoint$B",
            "com.foo.SuperFromStaleInnerEntryPoint$B$1"),
        output);
  }

  private void checkIncrementalRecompile_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$Binder", "com.foo.TestEntryPoint",
            "com.foo.MyWidget_BinderImpl", "com.foo.MyWidget_BinderImpl$Widgets"), output);
  }

  private void checkIncrementalRecompile_uiBinderCssChange(JsOutputOption output)
      throws UnableToCompleteException, IOException, InterruptedException {
    // Switches from a white styled widget to a grey styled widget in the CSS in the style tag
    // nested in the .ui.xml template file.
    String binderImpl = "com.foo.MyWidget_BinderImpl";
    checkRecompiledModifiedApp("com.foo.UiBinderTestModule",
        Lists.newArrayList(uiBinderTestModuleResource, uiBinderTestEntryPointResource, myWidget),
        myWidgetWithWhiteStyleUiXml, myWidgetWithGreyStyleUiXml,
        stringSet("com.foo.MyWidget",
            binderImpl, binderImpl + "_GenBundle_default_InlineClientBundleGenerator",
            binderImpl + "_GenBundle_default_InlineClientBundleGenerator$1",
            binderImpl + "_GenBundle_default_InlineClientBundleGenerator$styleInitializer",
            binderImpl + "_TemplateImpl",
            binderImpl + "$Widgets", binderImpl + "$Template"), output);
  }

  private void checkIncrementalRecompile_packagePrivateOverride(JsOutputOption output)
      throws UnableToCompleteException, IOException, InterruptedException {
    CompilerOptions compilerOptions = new CompilerOptionsImpl();
    compilerOptions.setUseDetailedTypeIds(true);

    checkRecompiledModifiedApp(compilerOptions, "com.foo.SimpleModule", Lists.newArrayList(
            simpleModuleResource, packagePrivateDispatchEntryPointResource,
            packagePrivateParentResource, referencesParentResource),
        packagePrivateChildResource, packagePrivateChildResource,
        stringSet("com.foo.PackagePrivateChild", "com.foo.TestEntryPoint"), output);
  }

  private void checkIncrementalRecompile_regularClassMadeIntoJsoClass(JsOutputOption output)
      throws UnableToCompleteException, IOException, InterruptedException {
    CompilerOptions compilerOptions = new CompilerOptionsImpl();
    compilerOptions.setUseDetailedTypeIds(true);

    checkRecompiledModifiedApp(compilerOptions, "com.foo.SimpleModule", Lists.newArrayList(
        simpleModuleResource, jsoArrayTestEntryPointResource, someClassReferringToJsoOneArrays,
        someClassReferringToJsoTwoArrays, jsoOne), jsoTwo_before, jsoTwo_after,
        stringSet("com.foo.JsoTwo", "com.foo.SomeClassReferringToJsoTwoArrays"), output);
  }

  private void checkIncrementalRecompile_unstableGeneratorReferencesModifiedType(
      JsOutputOption output)
      throws UnableToCompleteException, IOException, InterruptedException {
    // Make sure that the recompile with changes and the full compile with changes have the same
    // output from the unstable generator, so that it is safe to make byte for byte output
    // comparisons.
    UnstableNestedAnonymousGenerator.outputVersionOrder.addAll(Lists.newArrayList(
        // first compile
        OutputVersion.A,
        // recompile with changes
        OutputVersion.B,
        // full compile with changes
        OutputVersion.B));
    checkRecompiledModifiedApp("com.foo.SimpleModule",
        Lists.newArrayList(generatorEntryPointResource, unstableGeneratorModuleResource),
        fooResource, sameContentDifferentTimeFooResource, stringSet(
            "com.foo.NestedAnonymousClasses$1", "com.foo.NestedAnonymousClasses",
            "com.foo.NestedAnonymousClasses$ClassTwo", "com.foo.NestedAnonymousClasses$ClassOne",
            "com.foo.TestEntryPoint", "com.foo.NestedAnonymousClasses$1$1", "com.foo.Foo"), output);
  }

  private void checkIncrementalRecompile_functionSignatureChange(JsOutputOption output)
      throws UnableToCompleteException, IOException, InterruptedException {
    checkRecompiledModifiedApp("com.foo.SimpleModule", Lists.newArrayList(
            simpleModuleResource, simpleModelEntryPointResource, constantsModelResource),
        simpleModelResource, modifiedFunctionSignatureSimpleModelResource,
        stringSet("com.foo.TestEntryPoint", "com.foo.SimpleModel"), output);
  }

  private void checkIncrementalRecompile_compileTimeConstantChange(JsOutputOption output)
      throws UnableToCompleteException, IOException, InterruptedException {
    checkRecompiledModifiedApp("com.foo.SimpleModule", Lists.newArrayList(
            simpleModuleResource, simpleModelEntryPointResource, simpleModelResource),
        constantsModelResource, modifiedConstantsModelResource,
        stringSet("com.foo.SimpleModel", "com.foo.Constants"), output);
  }

  private void checkIncrementalRecompile_transitivelyFoldableConstant(JsOutputOption output)
      throws UnableToCompleteException, IOException, InterruptedException {
    // Tests that constants that are provided by types are only referenced by reference only types
    // (hence not traversed) are still available for constant propagation.
    checkRecompiledModifiedApp("com.foo.TransitivelyFoldableConstantModule", Lists.newArrayList(
            transitivelyFoldableConstantModuleResource, classOneResource, classTwoResource),
        transitivelyFoldableConstantEntryPointResource,
        transitivelyFoldableConstantEntryPointResource,
        stringSet("com.foo.TransitivelyFoldableConstantEntryPoint",
            getEntryMethodHolderTypeName("com.foo.TransitivelyFoldableConstantModule")),
        output);
  }

  private void checkIncrementalRecompile_unreachableIncompatibleChange(JsOutputOption output)
      throws UnableToCompleteException, IOException, InterruptedException {
    checkRecompiledModifiedApp("com.foo.SimpleModule",
        Lists.newArrayList(simpleModuleResource, emptyEntryPointResource), nonCompilableFooResource,
        nonCompilableFooResource, stringSet(), output);
  }

  private void checkIncrementalRecompile_typeHierarchyChange(JsOutputOption output)
      throws UnableToCompleteException, IOException, InterruptedException {
    checkRecompiledModifiedApp("com.foo.SimpleModule", Lists.newArrayList(simpleModuleResource,
        modifiedSuperEntryPointResource, modelAResource, modelBResource, modelDResource),
        modelCResource, modifiedSuperModelCResource,
        stringSet("com.foo.TestEntryPoint", "com.foo.ModelC", "com.foo.ModelD"), output);
  }

  private void checkIncrementalRecompile_singleJsoIntfDispatchChange(JsOutputOption output)
      throws UnableToCompleteException, IOException, InterruptedException {
    CompilerOptions compilerOptions = new CompilerOptionsImpl();
    compilerOptions.setUseDetailedTypeIds(true);

    checkRecompiledModifiedApp(compilerOptions, "com.foo.SimpleModule", Lists.newArrayList(
        simpleModuleResource, modifiedJsoIntfDispatchEntryPointResource, callerResource,
        fooInterfaceResource), nonJsoFooResource, jsoFooResource,
        stringSet("com.foo.TestEntryPoint", "com.foo.Foo", "com.foo.Caller"), output);
  }

  private void checkIncrementalRecompile_devirtualizeUnchangedJso(JsOutputOption output)
      throws UnableToCompleteException, IOException, InterruptedException {
    CompilerOptions compilerOptions = new CompilerOptionsImpl();
    compilerOptions.setUseDetailedTypeIds(true);

    checkRecompiledModifiedApp(compilerOptions, "com.foo.SimpleModule",
        Lists.newArrayList(jsoTestModuleResource, simpleFactory, simpleIntf, simpleJso),
        jsoTestEntryPointResource, jsoTestEntryPointResource, stringSet("com.foo.TestEntryPoint",
        getEntryMethodHolderTypeName("com.foo.SimpleModule")), output);
  }

  private void checkIncrementalRecompile_devirtualizeString(JsOutputOption output)
      throws UnableToCompleteException, IOException, InterruptedException {
    CompilerOptions compilerOptions = new CompilerOptionsImpl();
    compilerOptions.setUseDetailedTypeIds(true);

    checkRecompiledModifiedApp(compilerOptions, "com.foo.DevirtualizeStringModule",
        Lists.newArrayList(devirtualizeStringModuleResource),
        devirtualizeStringEntryPointResource, devirtualizeStringEntryPointResource,
        stringSet("com.foo.DevirtualizeStringEntryPoint",
            getEntryMethodHolderTypeName("com.foo.DevirtualizeStringModule")),
        output);
  }

  private void checkIncrementalRecompile_devirtualizeComparable(JsOutputOption output)
      throws UnableToCompleteException, IOException, InterruptedException {

    final MockResource devirtualizeComparableModuleResource =
        JavaResourceBase.createMockResource("com/foo/DevirtualizeComparableModule.gwt.xml",
            "<module>",
            "<source path=''/>",
            "<entry-point class='com.foo.DevirtualizeComparableEntryPoint'/>",
            "</module>");

    final MockJavaResource devirtualizeComparableEntryPointResource =
        JavaResourceBase.createMockJavaResource("com.foo.DevirtualizeComparableEntryPoint",
            "package com.foo;",
            "import com.google.gwt.core.client.EntryPoint;",
            "public class DevirtualizeComparableEntryPoint implements EntryPoint {",
            "  @Override",
            "  public void onModuleLoad() {",
            "    Comparable c = (Double) 0.1;",
            "    c.compareTo(c);",
            "  }",
            "}");

    CompilerOptions compilerOptions = new CompilerOptionsImpl();
    compilerOptions.setUseDetailedTypeIds(true);

    checkRecompiledModifiedApp(compilerOptions, "com.foo.DevirtualizeComparableModule",
        Lists.newArrayList(devirtualizeComparableModuleResource),
        devirtualizeComparableEntryPointResource, devirtualizeComparableEntryPointResource,
        stringSet("com.foo.DevirtualizeComparableEntryPoint",
            getEntryMethodHolderTypeName("com.foo.DevirtualizeComparableModule")),
        output);
  }

  private void checkIncrementalRecompile_multipleClassGenerator(JsOutputOption output)
      throws UnableToCompleteException, IOException, InterruptedException {
    CompilerOptions compilerOptions = new CompilerOptionsImpl();
    compilerOptions.setUseDetailedTypeIds(true);

    checkRecompiledModifiedApp(compilerOptions, "com.foo.SimpleModule",
        Lists.newArrayList(multipleClassGeneratorModuleResource, generatorEntryPointResource),
        bazResource, bazResource,
        stringSet("com.foo.Baz", "com.foo.TestEntryPoint", "com.foo.Bar"), output);
  }

  private void checkIncrementalRecompile_dualJsoIntfDispatchChange(JsOutputOption output)
      throws UnableToCompleteException, IOException, InterruptedException {
    CompilerOptions compilerOptions = new CompilerOptionsImpl();
    compilerOptions.setUseDetailedTypeIds(true);

    checkRecompiledModifiedApp(compilerOptions, "com.foo.SimpleModule", Lists.newArrayList(
        simpleModuleResource, modifiedJsoIntfDispatchEntryPointResource, callerResource,
        fooInterfaceResource, regularFooImplemetorResource), nonJsoFooResource, jsoFooResource,
        stringSet("com.foo.TestEntryPoint", "com.foo.Foo", "com.foo.Caller"), output);
  }

  private void checkIncrementalRecompile_generatorInputResourceChange(JsOutputOption outputOption)
      throws IOException, UnableToCompleteException, InterruptedException {
    CompilerOptions compilerOptions = new CompilerOptionsImpl();
    compilerOptions.setUseDetailedTypeIds(true);

    checkRecompiledModifiedApp(compilerOptions, "com.foo.SimpleModule", Lists.newArrayList(
        resourceReadingGeneratorModuleResource, generatorEntryPointResource, fooInterfaceResource,
        nonJsoFooResource), classNameToGenerateResource, modifiedClassNameToGenerateResource,
        stringSet("com.foo.TestEntryPoint", "com.foo.HasCustomContent",
            "com.foo.FooReplacementTwo"), outputOption);
  }

  private void assertCompileSucceeds(CompilerOptions options, String moduleName,
      List<MockResource> applicationResources) throws Exception {
    File compileWorkDir = Files.createTempDirectory(moduleName).toFile();
    final CompilerOptionsImpl compilerOptions = new CompilerOptionsImpl(options);
    PrintWriterTreeLogger logger = new PrintWriterTreeLogger();
    logger.setMaxDetail(TreeLogger.ERROR);

    String oldPersistentUnitCacheValue =
        System.setProperty(UnitCacheSingleton.GWT_PERSISTENTUNITCACHE, "false");
    try {

      File applicationDir = createTempDir();
      Thread.sleep(1001);
      // 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);
      if (outputDir.exists()) {
        MoreFiles.deleteDirectoryContents(outputDir.toPath(), RecursiveDeleteOption.ALLOW_INSECURE);
      }

      // Fake out the resource loader to read resources both from the normal classpath as well as
      // this new application directory.
      ResourceLoader resourceLoader = ResourceLoaders.fromContextClassLoader();
      resourceLoader =
          ResourceLoaders.forPathAndFallback(ImmutableList.of(applicationDir), resourceLoader);

      // Setup options to perform a per-file compile, output to this new application directory and
      // compile the given module.
      compilerOptions.setGenerateJsInteropExports(true);
      compilerOptions.setWarDir(applicationDir);
      compilerOptions.setModuleNames(ImmutableList.of(moduleName));

      // Write the Java/XML/etc resources that make up the test application.
      for (MockResource applicationResource : applicationResources) {
        writeResourceTo(applicationResource, applicationDir);
      }

      // Cause the module to be cached with a reference to the prefixed resource loader so that the
      // compile process will see those resources.
      ModuleDefLoader.clearModuleCache();
      ModuleDefLoader.loadFromResources(logger, moduleName, resourceLoader, true);

      options.addModuleName(moduleName);
      options.setWarDir(new File(compileWorkDir, "war"));
      options.setExtraDir(new File(compileWorkDir, "extra"));

      // Run the compiler once here.
      Compiler.compile(logger, options);
    } finally {
      MoreFiles.deleteRecursively(compileWorkDir.toPath(), RecursiveDeleteOption.ALLOW_INSECURE);
      if (oldPersistentUnitCacheValue == null) {
        System.clearProperty(UnitCacheSingleton.GWT_PERSISTENTUNITCACHE);
      } else {
        System.setProperty(UnitCacheSingleton.GWT_PERSISTENTUNITCACHE, oldPersistentUnitCacheValue);
      }
    }
  }

  private void assertDeterministicBuild(String topLevelModule, int optimizationLevel)
      throws UnableToCompleteException, IOException {

    final CompilerOptionsImpl options = new CompilerOptionsImpl();
    options.setOptimizationLevel(optimizationLevel);

    File firstCompileWorkDir = Files.createTempDirectory("hellowork").toFile();
    File secondCompileWorkDir = Files.createTempDirectory("hellowork").toFile();
    String oldPersistentUnitCacheValue =
        System.setProperty(UnitCacheSingleton.GWT_PERSISTENTUNITCACHE, "false");
    try {
      options.addModuleName(topLevelModule);
      options.setWarDir(new File(firstCompileWorkDir, "war"));
      options.setExtraDir(new File(firstCompileWorkDir, "extra"));
      TreeLogger logger = TreeLogger.NULL;

      // Run the compiler once here.
      Compiler.compile(logger, options);
      Set<String> firstTimeOutput =
          Sets.newHashSet(new File(options.getWarDir() + "/hello").list());

      options.setWarDir(new File(secondCompileWorkDir, "war"));
      options.setExtraDir(new File(secondCompileWorkDir, "extra"));
      // Run the compiler for a second time here.
      Compiler.compile(logger, options);
      Set<String> secondTimeOutput =
          Sets.newHashSet(new File(options.getWarDir() + "/hello").list());

      // It is only necessary to check that the filenames in the output directory are the same
      // because the names of the files for the JavaScript outputs are the hash of its contents.
      assertEquals("First and second compile produced different outputs", firstTimeOutput,
          secondTimeOutput);
    } finally {
      if (oldPersistentUnitCacheValue == null) {
        System.clearProperty(UnitCacheSingleton.GWT_PERSISTENTUNITCACHE);
      } else {
        System.setProperty(UnitCacheSingleton.GWT_PERSISTENTUNITCACHE, oldPersistentUnitCacheValue);
      }
      MoreFiles.deleteRecursively(firstCompileWorkDir.toPath(), RecursiveDeleteOption.ALLOW_INSECURE);
      MoreFiles.deleteRecursively(secondCompileWorkDir.toPath(), RecursiveDeleteOption.ALLOW_INSECURE);
    }
  }

  /**
   * Compiles an initial application with version 1 of file Foo, then recompiles using version 2 of
   * file Foo. Lastly it performs a final from scratch compile using version 2 of file Foo and
   * verifies that the recompile and the full compile (both of which used version 2 of file Foo)
   * come out the same.
   */
  private void checkRecompiledModifiedApp(
      String moduleName,
      List<MockResource> sharedResources,
      MockResource originalResource,
      MockResource modifiedResource,
      Set<String> expectedStaleTypeNamesOnModify,
      JsOutputOption output)
      throws IOException, UnableToCompleteException, InterruptedException {
    checkRecompiledModifiedApp(new CompilerOptionsImpl(), moduleName, sharedResources,
        originalResource, modifiedResource, expectedStaleTypeNamesOnModify, output);
  }

  /**
   * Compiles an initial application with version 1 of file Foo, then recompiles using version 2 of
   * file Foo. Lastly it performs a final from scratch compile using version 2 of file Foo and
   * verifies that the recompile and the full compile (both of which used version 2 of file Foo)
   * come out the same.
   */
  private void checkRecompiledModifiedApp(
      CompilerOptions compilerOptions,
      String moduleName,
      List<MockResource> sharedResources,
      MockResource originalResource,
      MockResource modifiedResource,
      Set<String> expectedStaleTypeNamesOnModify,
      JsOutputOption output)
      throws IOException, UnableToCompleteException, InterruptedException {
    List<MockResource> originalResources = Lists.newArrayList(sharedResources);
    originalResources.add(originalResource);

    List<MockResource> modifiedResources = Lists.newArrayList(sharedResources);
    modifiedResources.add(modifiedResource);

    // Compile the app with original files, modify a file and do a per-file recompile.
    MinimalRebuildCache relinkMinimalRebuildCache = new MinimalRebuildCache();
    File relinkApplicationDir = createTempDir();
    String originalAppFromScratchJs = compileToJs(compilerOptions, relinkApplicationDir, moduleName,
        originalResources, relinkMinimalRebuildCache, emptySet, output);
    String modifiedAppRelinkedJs = compileToJs(compilerOptions, relinkApplicationDir, moduleName,
        Lists.<MockResource> newArrayList(modifiedResource), relinkMinimalRebuildCache,
        expectedStaleTypeNamesOnModify, output);

    // Compile the app from scratch with the modified file.
    MinimalRebuildCache fromScratchMinimalRebuildCache = new MinimalRebuildCache();
    File fromScratchApplicationDir = createTempDir();
    String modifiedAppFromScratchJs = compileToJs(compilerOptions, fromScratchApplicationDir,
        moduleName, modifiedResources, fromScratchMinimalRebuildCache, emptySet, output);

    // If a resource contents were changed between the original compile and the relink compile
    // check that the output JS has also changed. If all resources have the same content (their
    // timestamps might have changed) then outputs should be the same.
    boolean wasNotModified = modifiedResource == originalResource;
    assertTrue("Resource " + modifiedResource.getPath()  + " was " +
        (wasNotModified ? "NOT " : "") + "modified but the output was " +
        (wasNotModified ? "" : "NOT ") + "different.",
        wasNotModified  == originalAppFromScratchJs.equals(modifiedAppRelinkedJs));

    // If per-file compiles properly avoids global-knowledge dependencies and correctly invalidates
    // referencing types when a type changes, then the relinked and from scratch JS will be
    // identical.
    assertTrue(modifiedAppRelinkedJs.equals(modifiedAppFromScratchJs));
  }

  private String compileToJs(File applicationDir, String moduleName,
      List<MockResource> applicationResources, MinimalRebuildCache minimalRebuildCache,
      Set<String> expectedStaleTypeNames, JsOutputOption output) throws IOException,
      UnableToCompleteException, InterruptedException {
    return compileToJs(new CompilerOptionsImpl(), applicationDir, moduleName, applicationResources,
        minimalRebuildCache, expectedStaleTypeNames, output);
  }

  private String compileToJs(CompilerOptions compilerOptions, File applicationDir,
      String moduleName, List<MockResource> applicationResources,
      MinimalRebuildCache minimalRebuildCache, Set<String> expectedProcessedStaleTypeNames,
      JsOutputOption output) throws IOException, UnableToCompleteException, InterruptedException {

    PrintWriterTreeLogger logger = new PrintWriterTreeLogger();
    logger.setMaxDetail(TreeLogger.ERROR);
    return compileToJs(logger, compilerOptions, applicationDir, moduleName, applicationResources,
        minimalRebuildCache, expectedProcessedStaleTypeNames, output);
  }

  private String compileToJs(TreeLogger logger, CompilerOptions compilerOptions, File applicationDir,
      String moduleName, List<MockResource> applicationResources,
      MinimalRebuildCache minimalRebuildCache, Set<String> expectedProcessedStaleTypeNames,
      JsOutputOption output) throws IOException, UnableToCompleteException, InterruptedException {
    // Make sure we're using a MemoryUnitCache.
    System.setProperty(UnitCacheSingleton.GWT_PERSISTENTUNITCACHE, "false");
    // Wait 1 second so that any new file modification times are actually different.
    Thread.sleep(1001);
    // 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);
    if (outputDir.exists()) {
      MoreFiles.deleteDirectoryContents(outputDir.toPath(), RecursiveDeleteOption.ALLOW_INSECURE);
    }

    // Fake out the resource loader to read resources both from the normal classpath as well as this
    // new application directory.
    ResourceLoader resourceLoader = ResourceLoaders.fromContextClassLoader();
    resourceLoader =
        ResourceLoaders.forPathAndFallback(ImmutableList.of(applicationDir), resourceLoader);

    // Setup options to perform a per-file compile, output to this new application directory and
    // compile the given module.
    compilerOptions.setIncrementalCompileEnabled(true);
    compilerOptions.setWarDir(applicationDir);
    compilerOptions.setModuleNames(ImmutableList.of(moduleName));
    compilerOptions.setOutput(output);

    // Write the Java/XML/etc resources that make up the test application.
    for (MockResource applicationResource : applicationResources) {
      writeResourceTo(applicationResource, applicationDir);
    }

    // Cause the module to be cached with a reference to the prefixed resource loader so that the
    // compile process will see those resources.
    ModuleDefLoader.clearModuleCache();
    ModuleDef moduleDef = ModuleDefLoader.loadFromResources(logger, moduleName, resourceLoader, true);

    // Run the compile.
    Compiler.compile(logger, compilerOptions, minimalRebuildCache, moduleDef);

    // Find, read and return the created JS.
    File outputJsFile = null;
    outputDir = new File(applicationDir.getPath() + File.separator + moduleName);
    if (outputDir.exists()) {
      for (File outputFile : outputDir.listFiles()) {
        if (outputFile.getPath().endsWith(".cache.js")) {
          outputJsFile = outputFile;
          break;
        }
      }
    }

    if (outputJsFile == null) {
      throw new UnableToCompleteException();
    }

    if (expectedProcessedStaleTypeNames != null) {
      assertEquals(expectedProcessedStaleTypeNames, getStaleTypeNames(minimalRebuildCache));
    }
    return Files.readString(outputJsFile.toPath(), Charsets.UTF_8);
  }

  private static File createTempDir() throws IOException {
    return Files.createTempDirectory("CompilerTest").toFile();
  }

  private Set<String> getStaleTypeNames(MinimalRebuildCache relinkMinimalRebuildCache) {
    Set<String> staleTypeNames =
        new HashSet<>(relinkMinimalRebuildCache.getProcessedStaleTypeNames());
    // List of JRE types that provide JsInterop entry points and jre native JsTypes. These are
    // always traversed fully and polute the tests, so they will be removed from stale type
    // comparisons.
    staleTypeNames.removeAll(
        Arrays.asList(
            "java.io.HasSerializableTypeMarker",
            "java.io.Serializable",
            "java.lang.Boolean",
            "java.lang.CharSequence",
            "java.lang.Cloneable",
            "java.lang.Comparable",
            "java.lang.Double",
            "java.lang.HasCharSequenceTypeMarker",
            "java.lang.HasCloneableTypeMarker",
            "java.lang.HasComparableTypeMarker",
            "java.lang.Integer$NativeNumber",
            "java.lang.Number",
            "java.lang.Number$JavaLangNumber",
            "java.lang.String",
            "java.lang.String$NativeFunction",
            "java.lang.String$NativeString",
            "java.lang.Throwable",
            "java.lang.Throwable$HasJavaThrowable",
            "java.lang.Throwable$NativeError",
            "java.lang.Throwable$NativeTypeError"));

    staleTypeNames.removeIf(
        new Predicate<String>() {
          @Override
          public boolean test(String name) {
            return name.startsWith("javaemul.internal");
          }
        });

    return staleTypeNames;
  }

  private String getEntryMethodHolderTypeName(String typeName) {
    return "com.google.gwt.lang." +
        EntryMethodHolderGenerator.getEntryMethodHolderTypeName(typeName);
  }

  private Set<String> stringSet(String... strings) {
    return Sets.newHashSet(strings);
  }

  private void writeResourceTo(MockResource mockResource, File applicationDir) throws IOException {
    File resourceFile =
        new File(applicationDir.getAbsolutePath() + File.separator + mockResource.getPath());
    resourceFile.getParentFile().mkdirs();
    Files.write(resourceFile.toPath(), List.of(mockResource.getContent()), Charsets.UTF_8);
  }
}
