blob: 03abc05fd71b6bfcd9fc54f5bfa2cf36986ad490 [file] [log] [blame]
/*
* Copyright 2014 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.codeserver;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.dev.MinimalRebuildCacheManager;
import com.google.gwt.dev.codeserver.Job.Result;
import com.google.gwt.dev.javac.UnitCache;
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.util.log.PrintWriterTreeLogger;
import com.google.gwt.thirdparty.guava.common.base.Charsets;
import com.google.gwt.thirdparty.guava.common.collect.ImmutableMap;
import com.google.gwt.thirdparty.guava.common.collect.Lists;
import com.google.gwt.thirdparty.guava.common.io.Files;
import junit.framework.TestCase;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Tests for {@link Recompiler}
*/
public class RecompilerTest extends TestCase {
private static File findCompiledJsFile(Result result) {
File outputDir = new File(result.outputDir.getWarDir(), result.outputModuleName);
File[] files = outputDir.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.endsWith(".js") && !name.endsWith("nocache.js");
}
});
Arrays.sort(files, new Comparator<File>() {
@Override
public int compare(File thisFile, File thatFile) {
return -Long.compare(thisFile.length(), thatFile.length());
}
});
return files[0];
}
private static void writeResourcesTo(List<MockResource> resources, File dir) throws IOException {
for (MockResource applicationResource : resources) {
File resourceFile =
new File(dir.getAbsolutePath() + File.separator + applicationResource.getPath());
resourceFile.getParentFile().mkdirs();
Files.write(applicationResource.getContent(), resourceFile, Charsets.UTF_8);
}
}
private MockJavaResource barReferencesBazResource =
JavaResourceBase.createMockJavaResource("com.foo.Bar",
"package com.foo;",
"public class Bar {",
" Baz baz = new Baz();",
"}");
private MockJavaResource bazReferencesFooResource =
JavaResourceBase.createMockJavaResource("com.foo.Baz",
"package com.foo;",
"public class Baz {",
" Foo foo = new Foo();",
"}");
private MockJavaResource fooResource =
JavaResourceBase.createMockJavaResource("com.foo.Foo",
"package com.foo;",
"public class Foo {}");
private MockJavaResource nonCompilableFooResource =
JavaResourceBase.createMockJavaResource("com.foo.Foo",
"package com.foo;",
"import com.google.gwt.core.client.impl.SpecializeMethod;",
"public class Foo {",
" // This will throw an error in UnifyAst.",
" @SpecializeMethod()",
" public void run() {}",
"}");
private MockJavaResource referencesBarEntryPointResource =
JavaResourceBase.createMockJavaResource("com.foo.TestEntryPoint",
"package com.foo;",
"import com.google.gwt.core.client.EntryPoint;",
"public class TestEntryPoint implements EntryPoint {",
" @Override",
" public void onModuleLoad() {",
" Bar bar = new Bar();",
" }",
"}");
private MockResource simpleModuleResource =
JavaResourceBase.createMockResource("com/foo/SimpleModule.gwt.xml",
"<module>",
"<source path=''/>",
"<entry-point class='com.foo.TestEntryPoint'/>",
"</module>");
private MockResource propertyIsFooModuleResource =
JavaResourceBase.createMockResource("com/foo/PropertyModule.gwt.xml",
"<module>",
"<source path=''/>",
"<entry-point class='com.foo.PropertyEntryPoint'/>",
"<define-property name=\"target\" values=\"foo,bar\" />",
"<set-property name=\"target\" value=\"foo\" />",
"<replace-with class=\"com.foo.Foo\">",
" <when-type-is class=\"com.foo.Bar\"/>",
" <when-property-is name=\"target\" value=\"foo\"/>",
"</replace-with>",
"</module>");
private MockResource propertyIsBarModuleResource =
JavaResourceBase.createMockResource("com/foo/PropertyModule.gwt.xml",
"<module>",
"<source path=''/>",
"<entry-point class='com.foo.PropertyEntryPoint'/>",
"<define-property name=\"target\" values=\"foo,bar\" />",
"<set-property name=\"target\" value=\"bar\" />",
"<replace-with class=\"com.foo.Foo\">",
" <when-type-is class=\"com.foo.Bar\"/>",
" <when-property-is name=\"target\" value=\"foo\"/>",
"</replace-with>",
"</module>");
private MockJavaResource performsRebindEntryPointResource =
JavaResourceBase.createMockJavaResource("com.foo.PropertyEntryPoint",
"package com.foo;",
"import com.google.gwt.core.client.EntryPoint;",
"import com.google.gwt.core.client.GWT;",
"public class PropertyEntryPoint implements EntryPoint {",
" @Override",
" public void onModuleLoad() {",
" GWT.create(Bar.class);",
" }",
"}");
public void testIncrementalRecompile_compileErrorDoesntCorruptMinimalRebuildCache()
throws UnableToCompleteException, IOException, InterruptedException {
String moduleName = "com.foo.SimpleModule";
PrintWriterTreeLogger logger = new PrintWriterTreeLogger();
logger.setMaxDetail(TreeLogger.ERROR);
File sourcePath = Files.createTempDir();
// Setup options to perform a per-file compile and compile the given module.
Options options = new Options();
options.parseArgs(new String[] {
"-incremental", "-src", sourcePath.getAbsolutePath(), moduleName});
// Prepare the basic resources in the test application.
List<MockResource> originalResources = Lists.newArrayList(simpleModuleResource,
referencesBarEntryPointResource, barReferencesBazResource, bazReferencesFooResource,
fooResource);
writeResourcesTo(originalResources, sourcePath);
File baseCacheDir = Files.createTempDir();
UnitCache unitCache = UnitCacheSingleton.get(
logger, null, baseCacheDir, new CompilerOptionsImpl(options));
MinimalRebuildCacheManager minimalRebuildCacheManager =
new MinimalRebuildCacheManager(logger, baseCacheDir, ImmutableMap.<String, String>of());
Recompiler recompiler = new Recompiler(OutboxDir.create(Files.createTempDir(), logger), null,
moduleName, options, unitCache, minimalRebuildCacheManager);
Outbox outbox = new Outbox("Transactional Cache", recompiler, options, logger);
OutboxTable outboxTable = new OutboxTable();
outboxTable.addOutbox(outbox);
JobRunner runner = new JobRunner(new JobEventTable(), minimalRebuildCacheManager);
// Perform a first compile. This should pass since all resources are valid.
Result result =
compileWithChanges(logger, runner, outbox, sourcePath, Lists.<MockResource> newArrayList());
assertTrue(result.isOk());
// Recompile should fail since the provided Foo is not compilable.
result = compileWithChanges(logger, runner, outbox, sourcePath,
Lists.<MockResource> newArrayList(nonCompilableFooResource));
assertFalse(result.isOk());
// Recompile with a modified entry point. This should fail again since Foo is still
// bad, but if transactionality protection failed on the minimalRebuildCache this compile will
// succeed because it will think that it has "already processed" Foo.
result = compileWithChanges(logger, runner, outbox, sourcePath,
Lists.<MockResource> newArrayList(referencesBarEntryPointResource));
assertFalse(result.isOk());
}
public void testIncrementalRecompile_modulePropertyEditsWork() throws UnableToCompleteException,
IOException, InterruptedException {
String moduleName = "com.foo.PropertyModule";
PrintWriterTreeLogger logger = new PrintWriterTreeLogger();
logger.setMaxDetail(TreeLogger.ERROR);
File sourcePath = Files.createTempDir();
// Setup options to perform a per-file compile and compile the given module.
Options options = new Options();
options.parseArgs(new String[] {
"-incremental", "-src", sourcePath.getAbsolutePath(), moduleName});
// Prepare the basic resources in the test application.
List<MockResource> originalResources = Lists.newArrayList(propertyIsFooModuleResource,
performsRebindEntryPointResource, barReferencesBazResource, bazReferencesFooResource,
fooResource);
writeResourcesTo(originalResources, sourcePath);
File baseCacheDir = Files.createTempDir();
UnitCache unitCache = UnitCacheSingleton.get(
logger, null, baseCacheDir, new CompilerOptionsImpl(options));
MinimalRebuildCacheManager minimalRebuildCacheManager =
new MinimalRebuildCacheManager(logger, baseCacheDir, ImmutableMap.<String, String>of());
Recompiler recompiler = new Recompiler(OutboxDir.create(Files.createTempDir(), logger), null,
moduleName, options, unitCache, minimalRebuildCacheManager);
Outbox outbox = new Outbox("Transactional Cache", recompiler, options, logger);
OutboxTable outboxTable = new OutboxTable();
outboxTable.addOutbox(outbox);
JobRunner runner = new JobRunner(new JobEventTable(), minimalRebuildCacheManager);
// Perform a first compile with configuration to rebind Bar to Foo.
Result result =
compileWithChanges(logger, runner, outbox, sourcePath, Lists.<MockResource> newArrayList());
assertTrue(result.isOk());
File compiledJsFile1 = findCompiledJsFile(result);
// Perform a second compile with a changed property value that will result in NOT rebinding Bar
// to Foo.
result = compileWithChanges(logger, runner, outbox, sourcePath,
Lists.<MockResource> newArrayList(propertyIsBarModuleResource));
assertTrue(result.isOk());
File compiledJsFile2 = findCompiledJsFile(result);
// The compiled Js files are different files on disk and their contents are not the same as
// evidenced by their names being different (the names are a hash of content).
assertFalse(compiledJsFile1.equals(compiledJsFile2));
assertFalse(compiledJsFile1.getName().equals(compiledJsFile2.getName()));
}
@Override
protected void setUp() throws Exception {
super.setUp();
// Make sure we're using a MemoryUnitCache.
System.setProperty(UnitCacheSingleton.GWT_PERSISTENTUNITCACHE, "false");
}
private Result compileWithChanges(TreeLogger logger, JobRunner runner, Outbox outbox,
File sourcePath, List<MockResource> changedResources) throws InterruptedException,
IOException {
// Wait 1 second so that any new file modification times are actually different.
Thread.sleep(1001);
// Write the Java/XML/etc resources that make up the test application.
writeResourcesTo(changedResources, sourcePath);
// Compile and return success status.
Map<String, String> bindingProperties = new HashMap<String, String>();
Job job = outbox.makeJob(bindingProperties, logger);
runner.submit(job);
return job.waitForResult();
}
}