| /* |
| * 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(); |
| } |
| } |