blob: 46a93b42cec92bbf1b691734999e3a43cdd0414b [file] [log] [blame]
/*
* 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.cfg.ModuleDefLoader;
import com.google.gwt.dev.cfg.ResourceLoader;
import com.google.gwt.dev.cfg.ResourceLoaders;
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.util.Util;
import com.google.gwt.dev.util.arg.SourceLevel;
import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
import com.google.gwt.thirdparty.guava.common.base.Charsets;
import com.google.gwt.thirdparty.guava.common.collect.ImmutableList;
import com.google.gwt.thirdparty.guava.common.collect.Lists;
import com.google.gwt.thirdparty.guava.common.collect.Sets;
import com.google.gwt.thirdparty.guava.common.io.Files;
import com.google.gwt.util.tools.Utility;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Set;
/**
* Test for {@link Compiler}.
*/
public class CompilerTest extends ArgProcessorTestBase {
public static final String GWT_PERSISTENTUNITCACHE = "gwt.persistentunitcache";
private final Compiler.ArgProcessor argProcessor;
private final CompilerOptionsImpl options = new CompilerOptionsImpl();
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 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 {",
" public void copyFrom(Object object) {}",
"}");
private MockJavaResource modifiedFunctionSignatureSimpleModelResource =
JavaResourceBase.createMockJavaResource("com.foo.SimpleModel",
"package com.foo;",
"public class SimpleModel {",
" 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 MockResource generatorModuleResource =
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 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 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 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 regularFooImplemetorResource =
JavaResourceBase.createMockJavaResource("com.foo.FooImplementor",
"package com.foo;",
"public class FooImplementor implements FooInterface {",
" @Override",
" public void run() {}",
"}");
public CompilerTest() {
argProcessor = new Compiler.ArgProcessor(options);
}
public void testAllValidArgs() {
assertProcessSuccess(argProcessor, new String[] {"-logLevel", "DEBUG", "-style",
"PRETTY", "-ea", "-XdisableAggressiveOptimization", "-gen", "myGen",
"-war", "myWar", "-workDir", "myWork", "-extra", "myExtra", "-XcompilePerFile",
"-localWorkers", "2", "-sourceLevel", "1.7", "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());
assertFalse(options.shouldClusterSimilarFunctions());
assertFalse(options.shouldInlineLiteralParameters());
assertFalse(options.shouldOptimizeDataflow());
assertFalse(options.shouldOrdinalizeEnums());
assertFalse(options.shouldRemoveDuplicateFunctions());
assertTrue(options.shouldCompilePerFile());
assertEquals(SourceLevel.JAVA7, 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() {
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());
assertTrue(options.shouldOptimizeDataflow());
assertTrue(options.shouldOrdinalizeEnums());
assertTrue(options.shouldRemoveDuplicateFunctions());
assertFalse(options.shouldCompilePerFile());
assertEquals(1, options.getLocalWorkers());
assertEquals(1, options.getModuleNames().size());
assertEquals("c.g.g.h.H", options.getModuleNames().get(0));
}
public void testForbiddenArgs() {
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(Utility.versionCompare(previousSourceLevel.getStringValue(),
sourceLevels[i].getStringValue()) < 0);
previousSourceLevel = sourceLevels[i];
}
}
public void testSourceLevelSelection() {
// We are not able to compile to less that Java 6 so, we might as well do Java7 on
// these cases.
assertEquals(SourceLevel.JAVA7, SourceLevel.getBestMatchingVersion("1.4"));
assertEquals(SourceLevel.JAVA7, SourceLevel.getBestMatchingVersion("1.5"));
assertEquals(SourceLevel.JAVA6, SourceLevel.getBestMatchingVersion("1.6"));
assertEquals(SourceLevel.JAVA6, SourceLevel.getBestMatchingVersion("1.6_26"));
assertEquals(SourceLevel.JAVA7, SourceLevel.getBestMatchingVersion("1.7"));
assertEquals(SourceLevel.JAVA7, SourceLevel.getBestMatchingVersion("1.8"));
// not proper version strings => default to JAVA7.
assertEquals(SourceLevel.JAVA7, SourceLevel.getBestMatchingVersion("1.6u3"));
assertEquals(SourceLevel.JAVA7, SourceLevel.getBestMatchingVersion("1.6b3"));
assertEquals(SourceLevel.JAVA7, SourceLevel.getBestMatchingVersion("1.7b3"));
}
public void testDeterministicBuild_Draft() throws UnableToCompleteException, IOException {
final CompilerOptionsImpl options = new CompilerOptionsImpl();
options.setOptimizationLevel(0);
assertDeterministicBuild(options);
}
public void testDeterministicBuild_Optimized() throws UnableToCompleteException, IOException {
final CompilerOptionsImpl options = new CompilerOptionsImpl();
options.setOptimizationLevel(9);
assertDeterministicBuild(options);
}
// TODO(stalcup): add recompile tests for file deletion, JSO status changes and Generator input
// resource edits.
public void testPerFileRecompile_noop() throws UnableToCompleteException, IOException,
InterruptedException {
checkPerFileRecompile_noop(JsOutputOption.PRETTY);
checkPerFileRecompile_noop(JsOutputOption.DETAILED);
}
public void testPerFileRecompile_dateStampChange() throws UnableToCompleteException, IOException,
InterruptedException {
checkPerFileRecompile_dateStampChange(JsOutputOption.PRETTY);
checkPerFileRecompile_dateStampChange(JsOutputOption.DETAILED);
}
public void testPerFileRecompile_functionSignatureChange() throws UnableToCompleteException,
IOException, InterruptedException {
// Not testing recompile equality with Pretty output since the Pretty namer'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.
checkPerFileRecompile_functionSignatureChange(JsOutputOption.DETAILED);
}
public void testPerFileRecompile_regularClassMadeIntoJsoClass() throws UnableToCompleteException,
IOException, InterruptedException {
// Not testing recompile equality with Pretty output since the Pretty namer'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.
checkPerFileRecompile_regularClassMadeIntoJsoClass(JsOutputOption.DETAILED);
}
public void testPerFileRecompile_typeHierarchyChange() throws UnableToCompleteException,
IOException, InterruptedException {
checkPerFileRecompile_typeHierarchyChange(JsOutputOption.PRETTY);
checkPerFileRecompile_typeHierarchyChange(JsOutputOption.DETAILED);
}
public void testPerFileRecompile_singleJsoIntfDispatchChange() throws UnableToCompleteException,
IOException, InterruptedException {
// Not testing recompile equality with Pretty output since the Pretty namer'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.
checkPerFileRecompile_singleJsoIntfDispatchChange(JsOutputOption.DETAILED);
}
public void testPerFileRecompile_dualJsoIntfDispatchChange() throws UnableToCompleteException,
IOException, InterruptedException {
// Not testing recompile equality with Pretty output since the Pretty namer'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.
checkPerFileRecompile_dualJsoIntfDispatchChange(JsOutputOption.DETAILED);
}
public void testPerFileRecompile_carriesOverGeneratorArtifacts() throws UnableToCompleteException,
IOException, InterruptedException {
// Foo Generator hasn't run yet.
assertEquals(0, FooResourceGenerator.runCount);
CompilerOptions compilerOptions = new CompilerOptionsImpl();
List<MockResource> sharedResources = Lists.newArrayList(generatorModuleResource,
generatorEntryPointResource, fooInterfaceResource);
JsOutputOption output = JsOutputOption.PRETTY;
List<MockResource> originalResources = Lists.newArrayList(sharedResources);
originalResources.add(nonJsoFooResource);
List<MockResource> modifiedResources = Lists.newArrayList(sharedResources);
modifiedResources.add(nonJsoFooResource);
// Compile the app with original files, modify a file and do a per-file recompile.
MinimalRebuildCache relinkMinimalRebuildCache = new MinimalRebuildCache();
File relinkApplicationDir = Files.createTempDir();
compileToJs(compilerOptions, relinkApplicationDir, "com.foo.SimpleModule", originalResources,
relinkMinimalRebuildCache, 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(nonJsoFooResource), relinkMinimalRebuildCache, 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());
}
private void checkPerFileRecompile_noop(JsOutputOption output) throws UnableToCompleteException,
IOException, InterruptedException {
MinimalRebuildCache relinkMinimalRebuildCache = new MinimalRebuildCache();
File relinkApplicationDir = Files.createTempDir();
String originalJs = compileToJs(relinkApplicationDir, "com.foo.SimpleModule", Lists
.newArrayList(simpleModuleResource, simpleModelEntryPointResource, simpleModelResource),
relinkMinimalRebuildCache, output);
// Compile again with absolutely no file changes and reusing the minimalRebuildCache.
String relinkedJs = compileToJs(relinkApplicationDir, "com.foo.SimpleModule",
Lists.<MockResource> newArrayList(), relinkMinimalRebuildCache, output);
assertTrue(originalJs.equals(relinkedJs));
}
private void checkPerFileRecompile_dateStampChange(JsOutputOption output)
throws UnableToCompleteException, IOException, InterruptedException {
MinimalRebuildCache relinkMinimalRebuildCache = new MinimalRebuildCache();
File relinkApplicationDir = Files.createTempDir();
String originalJs = compileToJs(relinkApplicationDir, "com.foo.SimpleModule", Lists
.newArrayList(simpleModuleResource, simpleModelEntryPointResource, simpleModelResource),
relinkMinimalRebuildCache, 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, output);
assertTrue(originalJs.equals(relinkedJs));
}
private void checkPerFileRecompile_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, output);
}
private void checkPerFileRecompile_functionSignatureChange(JsOutputOption output)
throws UnableToCompleteException,
IOException, InterruptedException {
checkRecompiledModifiedApp("com.foo.SimpleModule",
Lists.newArrayList(simpleModuleResource, simpleModelEntryPointResource),
simpleModelResource, modifiedFunctionSignatureSimpleModelResource, output);
}
private void checkPerFileRecompile_typeHierarchyChange(JsOutputOption output)
throws UnableToCompleteException, IOException, InterruptedException {
checkRecompiledModifiedApp("com.foo.SimpleModule", Lists.newArrayList(simpleModuleResource,
modifiedSuperEntryPointResource, modelAResource, modelBResource, modelDResource),
modelCResource, modifiedSuperModelCResource, output);
}
private void checkPerFileRecompile_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, output);
}
private void checkPerFileRecompile_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,
output);
}
private void assertDeterministicBuild(CompilerOptions options)
throws UnableToCompleteException, IOException {
File firstCompileWorkDir = Utility.makeTemporaryDirectory(null, "hellowork");
File secondCompileWorkDir = Utility.makeTemporaryDirectory(null, "hellowork");
String oldPersistentUnitCacheValue = System.setProperty(GWT_PERSISTENTUNITCACHE, "false");
try {
options.addModuleName("com.google.gwt.sample.hello.Hello");
options.setWarDir(new File(firstCompileWorkDir, "war"));
options.setExtraDir(new File(firstCompileWorkDir, "extra"));
PrintWriterTreeLogger logger = new PrintWriterTreeLogger();
logger.setMaxDetail(TreeLogger.ERROR);
// Run the compiler once here.
new Compiler(options).run(logger);
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.
new Compiler(options).run(logger);
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(GWT_PERSISTENTUNITCACHE);
} else {
System.setProperty(GWT_PERSISTENTUNITCACHE, oldPersistentUnitCacheValue);
}
Util.recursiveDelete(firstCompileWorkDir, false);
Util.recursiveDelete(secondCompileWorkDir, false);
}
}
private void checkRecompiledModifiedApp(String moduleName, List<MockResource> sharedResources,
MockJavaResource originalResource, MockJavaResource modifiedResource, JsOutputOption output)
throws IOException,
UnableToCompleteException, InterruptedException {
checkRecompiledModifiedApp(new CompilerOptionsImpl(), moduleName, sharedResources,
originalResource, modifiedResource, output);
}
private void checkRecompiledModifiedApp(CompilerOptions compilerOptions, String moduleName,
List<MockResource> sharedResources, MockJavaResource originalResource,
MockJavaResource modifiedResource, 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 = Files.createTempDir();
String originalAppFromScratchJs =
compileToJs(compilerOptions, relinkApplicationDir, moduleName, originalResources,
relinkMinimalRebuildCache, output);
String modifiedAppRelinkedJs = compileToJs(compilerOptions, relinkApplicationDir, moduleName,
Lists.<MockResource> newArrayList(modifiedResource), relinkMinimalRebuildCache, output);
// Compile the app from scratch with the modified file.
MinimalRebuildCache fromScratchMinimalRebuildCache = new MinimalRebuildCache();
File fromScratchApplicationDir = Files.createTempDir();
String modifiedAppFromScratchJs = compileToJs(compilerOptions, fromScratchApplicationDir,
moduleName, modifiedResources, fromScratchMinimalRebuildCache, output);
// A resource was changed between the original compile and the relink compile. If the compile is
// correct then the output JS will have changed.
assertFalse(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,
JsOutputOption output)
throws IOException, UnableToCompleteException, InterruptedException {
return compileToJs(new CompilerOptionsImpl(), applicationDir, moduleName, applicationResources,
minimalRebuildCache, output);
}
private String compileToJs(CompilerOptions compilerOptions, File applicationDir,
String moduleName, List<MockResource> applicationResources,
MinimalRebuildCache minimalRebuildCache, JsOutputOption output)
throws IOException, UnableToCompleteException, InterruptedException {
// Make sure we're using a MemoryUnitCache.
System.setProperty(GWT_PERSISTENTUNITCACHE, "false");
// Wait 1 second so that any new file modification times are actually different.
Thread.sleep(1001);
TreeLogger logger = TreeLogger.NULL;
// 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()) {
Util.recursiveDelete(outputDir, true);
}
// Fake out the resource loader to read resources both from the normal classpath as well as this
// new application directory.
ResourceLoader resourceLoader = ResourceLoaders.forClassLoader(Thread.currentThread());
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.setCompilePerFile(true);
compilerOptions.setWarDir(applicationDir);
compilerOptions.setModuleNames(ImmutableList.of(moduleName));
compilerOptions.setOutput(output);
CompilerContext compilerContext = new CompilerContext.Builder().options(compilerOptions)
.minimalRebuildCache(minimalRebuildCache).build();
// 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, compilerContext, moduleName, resourceLoader, true);
// Run the compile.
Compiler compiler = new Compiler(compilerOptions, minimalRebuildCache);
compiler.run(logger);
// 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;
}
}
}
assertNotNull(outputJsFile);
return Files.toString(outputJsFile, Charsets.UTF_8);
}
private void writeResourceTo(MockResource mockResource, File applicationDir) throws IOException {
File resourceFile =
new File(applicationDir.getAbsolutePath() + File.separator + mockResource.getPath());
resourceFile.getParentFile().mkdirs();
Files.write(mockResource.getContent(), resourceFile, Charsets.UTF_8);
}
}