| /* |
| * Copyright 2011 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.javac; |
| |
| import com.google.gwt.core.ext.TreeLogger; |
| import com.google.gwt.core.ext.TreeLogger.Type; |
| import com.google.gwt.core.ext.UnableToCompleteException; |
| import com.google.gwt.dev.util.Util; |
| import com.google.gwt.thirdparty.guava.common.util.concurrent.Futures; |
| |
| import junit.framework.TestCase; |
| |
| import java.io.File; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.ObjectInputStream; |
| import java.io.ObjectOutputStream; |
| import java.io.Serializable; |
| import java.util.Arrays; |
| import java.util.concurrent.ExecutionException; |
| |
| /** |
| * Unit test for {@link PersistentUnitCache}. |
| */ |
| public class PersistentUnitCacheTest extends TestCase { |
| |
| private static class ThrowsClassNotFoundException implements Serializable { |
| @SuppressWarnings("unused") |
| private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { |
| throw new ClassNotFoundException(); |
| } |
| } |
| |
| private static class ThrowsIOException implements Serializable { |
| @SuppressWarnings("unused") |
| private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { |
| throw new IOException(); |
| } |
| } |
| |
| private TreeLogger logger; |
| private File lastParentDir = null; |
| private String hash1 = "HASH1"; |
| private String hash2 = "HASH2"; |
| |
| @Override |
| protected void setUp() throws Exception { |
| super.setUp(); |
| logger = TreeLogger.NULL; |
| // logger = new PrintWriterTreeLogger(); // uncomment for debugging |
| logger.log(Type.INFO, "\n\n*** Running " + getName()); |
| } |
| |
| @Override |
| public void tearDown() { |
| if (lastParentDir != null) { |
| Util.recursiveDelete(lastParentDir, false); |
| } |
| lastParentDir = null; |
| } |
| |
| /** |
| * When a cache file encounters a serialization error, the logic should assume |
| * the cache log is stale and remove it. |
| */ |
| public void testClassNotFoundException() throws IOException, UnableToCompleteException, |
| InterruptedException, ExecutionException { |
| checkInvalidObjectInCache(new ThrowsClassNotFoundException()); |
| } |
| |
| /** |
| * Test if a file already exists with the name we want to put the cache dir |
| * in. |
| */ |
| public void testFileInTheWay() throws IOException { |
| File fileInTheWay = File.createTempFile("PersistentUnitTest-inTheWay", ""); |
| assertNotNull(fileInTheWay); |
| assertTrue(fileInTheWay.exists()); |
| fileInTheWay.deleteOnExit(); |
| try { |
| new PersistentUnitCache(logger, fileInTheWay, hash1); |
| fail("Expected an exception to be thrown"); |
| } catch (UnableToCompleteException expected) { |
| } |
| } |
| |
| /** |
| * If a cache file has some kind of IO exception, (this can happen with a |
| * stale cache file), then the exception should be ignored and the cache file |
| * removed. |
| */ |
| public void testIOException() throws IOException, UnableToCompleteException, |
| InterruptedException, ExecutionException { |
| checkInvalidObjectInCache(new ThrowsIOException()); |
| } |
| |
| /** |
| * The cache should recursively create the directories it needs. |
| */ |
| public void testNewDir() throws IOException, UnableToCompleteException { |
| File baseDir = File.createTempFile("PersistentUnitTest-newDir", ""); |
| assertNotNull(baseDir); |
| assertTrue(baseDir.exists()); |
| assertTrue(baseDir.delete()); |
| File parentDir = lastParentDir = new File(baseDir, "sHoUlDnOtExi57"); |
| new PersistentUnitCache(logger, parentDir, hash1); |
| assertTrue(parentDir.isDirectory()); |
| } |
| |
| public void testPersistentCache() throws IOException, InterruptedException, |
| UnableToCompleteException, ExecutionException { |
| |
| File parentDir = lastParentDir = File.createTempFile("persistentCacheTest", ""); |
| File unitCacheDir = mkCacheDir(parentDir); |
| |
| PersistentUnitCache cache = new PersistentUnitCache(logger, parentDir, hash1); |
| |
| MockCompilationUnit foo1 = new MockCompilationUnit("com.example.Foo", "Foo: source1"); |
| cache.add(foo1); |
| MockCompilationUnit bar1 = new MockCompilationUnit("com.example.Bar", "Bar: source1"); |
| cache.add(bar1); |
| CompilationUnit result; |
| |
| // Find by content Id |
| result = cache.find(foo1.getContentId()); |
| assertNotNull(result); |
| assertEquals("com.example.Foo", result.getTypeName()); |
| result = cache.find(bar1.getContentId()); |
| assertNotNull(result); |
| assertEquals("com.example.Bar", result.getTypeName()); |
| |
| // Find by type name |
| result = cache.find("com/example/Foo.java"); |
| assertNotNull(result); |
| assertEquals("com.example.Foo", result.getTypeName()); |
| result = cache.find("com/example/Bar.java"); |
| assertNotNull(result); |
| assertEquals("com.example.Bar", result.getTypeName()); |
| |
| // Replace Foo with a new version |
| MockCompilationUnit foo2 = new MockCompilationUnit("com.example.Foo", "Foo: source2"); |
| cache.add(foo2); |
| result = cache.find(foo1.getContentId()); |
| assertNull(result); |
| result = cache.find(foo2.getContentId()); |
| assertNotNull(result); |
| assertEquals("com.example.Foo", result.getTypeName()); |
| result = cache.find("com/example/Foo.java"); |
| assertNotNull(result); |
| assertEquals("com.example.Foo", result.getTypeName()); |
| assertEquals(foo2.getContentId(), result.getContentId()); |
| cache.cleanup(logger); |
| cache.waitForCleanup(); |
| |
| // Shutdown the cache and re -load it |
| cache.shutdown(); |
| |
| // There should be a single file in the cache dir. |
| assertNumCacheFiles(unitCacheDir, 1); |
| |
| // Fire up the cache again. It should be pre-populated. |
| // Search by type name |
| cache = new PersistentUnitCache(logger, parentDir, hash1); |
| result = cache.find("com/example/Foo.java"); |
| assertNotNull(result); |
| assertEquals("com.example.Foo", result.getTypeName()); |
| assertEquals(foo2.getContentId(), result.getContentId()); |
| result = cache.find("com/example/Bar.java"); |
| assertNotNull(result); |
| assertEquals("com.example.Bar", result.getTypeName()); |
| assertEquals(bar1.getContentId(), result.getContentId()); |
| |
| // Search by Content ID. |
| // old version of Foo should not be there. |
| result = cache.find(foo1.getContentId()); |
| assertNull(result); |
| result = cache.find(bar1.getContentId()); |
| assertNotNull(result); |
| assertEquals(bar1.getTypeName(), result.getTypeName()); |
| assertEquals(bar1.getContentId(), result.getContentId()); |
| result = cache.find(foo2.getContentId()); |
| assertNotNull(result); |
| assertEquals(foo2.getTypeName(), result.getTypeName()); |
| assertEquals(foo2.getContentId(), result.getContentId()); |
| cache.cleanup(logger); |
| cache.waitForCleanup(); |
| |
| // We didn't write anything, still 1 file. |
| cache.shutdown(); |
| |
| // There should be a single file in the cache dir. |
| assertNumCacheFiles(unitCacheDir, 1); |
| |
| // Fire up the cache again. (Creates a second file in the background.) |
| cache = new PersistentUnitCache(logger, parentDir, hash1); |
| |
| // keep making more files |
| MockCompilationUnit lastUnit = null; |
| assertTrue(PersistentUnitCache.CACHE_FILE_THRESHOLD > 3); |
| for (int i = 2; i <= PersistentUnitCache.CACHE_FILE_THRESHOLD - 2; i++) { |
| lastUnit = new MockCompilationUnit("com.example.Foo", "Foo Source" + i); |
| cache.internalAdd(lastUnit).get(); |
| assertNumCacheFiles(unitCacheDir, i); |
| // force rotation to a new file |
| // (Normally async but we overrode it.) |
| cache.cleanup(logger); |
| cache.waitForCleanup(); |
| assertNumCacheFiles(unitCacheDir, i + 1); |
| } |
| |
| // One last check, we should load the last unit added to the cache. |
| cache = new PersistentUnitCache(logger, parentDir, hash1); |
| result = cache.find(lastUnit.getContentId()); |
| assertNotNull(result); |
| assertEquals("com.example.Foo", result.getTypeName()); |
| assertEquals(lastUnit.getContentId(), result.getContentId()); |
| |
| result = cache.find(bar1.getContentId()); |
| assertNotNull(result); |
| assertEquals("com.example.Bar", result.getTypeName()); |
| assertEquals(bar1.getContentId(), result.getContentId()); |
| |
| result = cache.find("com/example/Foo.java"); |
| assertNotNull(result); |
| assertEquals("com.example.Foo", result.getTypeName()); |
| assertEquals(lastUnit.getContentId(), result.getContentId()); |
| |
| result = cache.find("com/example/Bar.java"); |
| assertNotNull(result); |
| assertEquals("com.example.Bar", result.getTypeName()); |
| assertEquals(bar1.getContentId(), result.getContentId()); |
| |
| lastUnit = new MockCompilationUnit("com.example.Baz", "Baz Source"); |
| cache.add(lastUnit); |
| cache.cleanup(logger); |
| cache.waitForCleanup(); |
| |
| // Add one more to put us over the top. |
| lastUnit = new MockCompilationUnit("com.example.Qux", "Qux Source"); |
| Futures.getUnchecked(cache.internalAdd(lastUnit)); |
| |
| // The currently open file isn't included in this count. |
| assertNumCacheFiles(unitCacheDir, PersistentUnitCache.CACHE_FILE_THRESHOLD + 1); |
| |
| cache.cleanup(logger); |
| cache.waitForCleanup(); |
| cache.shutdown(); |
| |
| // There should be a single file in the cache dir. |
| assertNumCacheFiles(unitCacheDir, 1); |
| |
| // Fire up the cache on this one coalesced file. |
| cache = new PersistentUnitCache(logger, parentDir, hash1); |
| |
| // Verify that we can still find the content that was coalesced. |
| assertNotNull(cache.find("com/example/Foo.java")); |
| assertNotNull(cache.find("com/example/Bar.java")); |
| assertNotNull(cache.find("com/example/Baz.java")); |
| assertNotNull(cache.find("com/example/Qux.java")); |
| |
| cache.shutdown(); |
| |
| // There should be a single file in the cache dir. |
| assertNumCacheFiles(unitCacheDir, 1); |
| |
| cache = new PersistentUnitCache(logger, parentDir, hash2); |
| // A different hash implies a new cache. |
| assertNull(cache.find("com/example/Foo.java")); |
| assertNull(cache.find("com/example/Bar.java")); |
| assertNull(cache.find("com/example/Baz.java")); |
| assertNull(cache.find("com/example/Qux.java")); |
| |
| cache.internalAdd( |
| new MockCompilationUnit("com.example.Hash2", "Foo Source")).get(); |
| |
| assertNotNull(cache.find("com/example/Hash2.java")); |
| |
| cache.shutdown(); |
| |
| // There should be a single file in the cache dir. |
| assertNumCacheFiles(unitCacheDir, 2); |
| |
| cache = new PersistentUnitCache(logger, parentDir, hash1); |
| // Verify that we can still find the content with the original hash. |
| assertNotNull(cache.find("com/example/Foo.java")); |
| assertNotNull(cache.find("com/example/Bar.java")); |
| assertNotNull(cache.find("com/example/Baz.java")); |
| assertNotNull(cache.find("com/example/Qux.java")); |
| assertNull(cache.find("com/example/Hash2.java")); |
| |
| cache.shutdown(); |
| |
| cache = new PersistentUnitCache(logger, parentDir, hash2); |
| // A different hash implies a new cache. |
| assertNull(cache.find("com/example/Foo.java")); |
| assertNull(cache.find("com/example/Bar.java")); |
| assertNull(cache.find("com/example/Baz.java")); |
| assertNull(cache.find("com/example/Qux.java")); |
| assertNotNull(cache.find("com/example/Hash2.java")); |
| |
| cache.shutdown(); |
| } |
| |
| private void assertNumCacheFiles(File unitCacheDir, int expected) { |
| String[] actualFiles = unitCacheDir.list(); |
| if (expected == actualFiles.length) { |
| return; |
| } |
| |
| // list the files |
| Arrays.sort(actualFiles); |
| System.out.println("\nCache file dump (exected " + expected + ")"); |
| for (String file : actualFiles) { |
| System.out.println(file); |
| } |
| System.out.println(); |
| |
| fail("expected " + expected + " cache files but got " + actualFiles.length |
| + " (see stdout for list)"); |
| } |
| |
| private void checkInvalidObjectInCache(Object toSerialize) throws IOException, |
| UnableToCompleteException, InterruptedException, ExecutionException { |
| File parentDir = lastParentDir = File.createTempFile("PersistentUnitTest-CNF", ""); |
| File unitCacheDir = mkCacheDir(parentDir); |
| |
| /* |
| * Create a cache file that has the right filename, but the wrong kind of |
| * object in it. |
| */ |
| File errorFile = new File(unitCacheDir, |
| PersistentUnitCacheDir.CURRENT_VERSION_CACHE_FILE_PREFIX + "-" + hash1 + "-" + "12345"); |
| ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream(errorFile)); |
| os.writeObject(toSerialize); |
| os.close(); |
| |
| assertNumCacheFiles(unitCacheDir, 1); |
| |
| PersistentUnitCache cache = new PersistentUnitCache(logger, parentDir, hash1); |
| cache.cleanup(logger); |
| cache.shutdown(); |
| |
| // The bogus file should have been removed. |
| assertNumCacheFiles(unitCacheDir, 0); |
| } |
| |
| private File mkCacheDir(File parentDir) { |
| assertNotNull(parentDir); |
| assertTrue(parentDir.exists()); |
| parentDir.delete(); |
| File unitCacheDir = PersistentUnitCacheDir.chooseCacheDir(parentDir); |
| unitCacheDir.mkdirs(); |
| return unitCacheDir; |
| } |
| } |