/******************************************************************************* * Copyright (c) 2014 Bruno Medeiros and other Contributors. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Bruno Medeiros - initial API and implementation *******************************************************************************/ package dtool.engine; import static melnorme.utilbox.core.Assert.AssertNamespace.assertTrue; import java.io.FileNotFoundException; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.attribute.FileTime; import melnorme.lang.tooling.BundlePath; import melnorme.lang.tooling.context.ModuleSourceException; import melnorme.lang.utils.MiscFileUtils; import melnorme.utilbox.misc.FileUtil; import melnorme.utilbox.misc.Location; import melnorme.utilbox.misc.MiscUtil; import melnorme.utilbox.tests.TestsWorkingDir; import org.junit.BeforeClass; import org.junit.Test; import dtool.dub.CommonDubTest; import dtool.engine.CommonSemanticManagerTest.Tests_DToolServer; import dtool.parser.DeeParserResult.ParsedModule; import dtool.tests.CommonDToolTest; public class ModuleParseCache_Test extends CommonDToolTest { public static final BundlePath XPTO_BUNDLE_PATH = CommonDubTest.XPTO_BUNDLE_PATH; public static final Location WORKING_DIR = TestsWorkingDir.getWorkingDir(); public static final Location TEST_WORKING_DIR = WORKING_DIR.resolve_fromValid(ModuleParseCache_Test.class.getSimpleName()); protected static final Path CU_PATH = TEST_WORKING_DIR.resolve_fromValid("app.d").path; protected static final String SOURCE1 = "module app; /** Source 1 */"; protected static final String WC_SOURCE = "module app; /** WC Source */"; protected static final String SOURCE3 = "module app; /** ModuleParse Test source3 */"; protected ModuleParseCache mpc; @BeforeClass public static void setup() throws IOException { FileUtil.deleteDir(TEST_WORKING_DIR); MiscFileUtils.copyDirContentsIntoDirectory(XPTO_BUNDLE_PATH.resolve(path("src/")), TEST_WORKING_DIR); } @Test public void testCaching() throws Exception { testCaching$(); } public void testCaching$() throws Exception { __initModuleParseCache(); basicSequence(CU_PATH); // repeat basicSequence(CU_PATH); } protected void __initModuleParseCache() { mpc = new ModuleParseCache(new Tests_DToolServer()); } public static void writeToFileAndUpdateMTime(Path file, String contents) throws IOException { if(!file.toFile().exists()) { writeStringToFile(file, contents); return; } FileTime lastModifiedTime = Files.getLastModifiedTime(file); writeStringToFile(file, contents); // Make sure last modified time is different from before Files.setLastModifiedTime(file, FileTime.fromMillis(lastModifiedTime.toMillis() - 1_000)); } public void updateFileContents(Path filePath, String contents) { updateFileContents(filePath, contents, true); } public void updateFileContents(Path filePath, String contents, boolean isEntryStale) { try { writeToFileAndUpdateMTime(filePath, contents); } catch (IOException e) { throw melnorme.utilbox.core.ExceptionAdapter.unchecked(e); } assertTrue(mpc.getEntry(filePath).isStale() == isEntryStale); } protected void basicSequence(Path cuPath) throws ModuleSourceException, IOException, FileNotFoundException { ParsedModule parsedModule = mpc.getParsedModule(CU_PATH); assertTrue(mpc.getParsedModule(cuPath) == parsedModule); updateFileContents(CU_PATH, SOURCE1); assertTrue(mpc.getEntry(CU_PATH).isStale()); parsedModule = mpc.getParsedModule(CU_PATH); assertEquals(parsedModule.source, SOURCE1); // Test caching assertTrue(mpc.getEntry(CU_PATH).isWorkingCopy() == false); assertTrue(mpc.getEntry(CU_PATH).isStale() == false); assertTrue(mpc.getParsedModule(CU_PATH) == parsedModule); // Test caching for WC testUpdateWorkingCopyAndParse(CU_PATH, WC_SOURCE); // Test new source update over previous working copy testUpdateWorkingCopyAndParse(CU_PATH, "blah"); // Test file update over active working copy. Check working copy source still takes precedence testUpdateWorkingCopyAndParse(CU_PATH, WC_SOURCE); updateFileContents(CU_PATH, SOURCE3, false); // do file update assertTrue(mpc.getParsedModule(CU_PATH).source.equals(WC_SOURCE)); // Test discard working copy - last update was on file testUpdateWorkingCopyAndParse(CU_PATH, WC_SOURCE); updateFileContents(CU_PATH, SOURCE3, false); // last update testDiscardWorkingCopy(CU_PATH, WC_SOURCE, SOURCE3); // Test discard working copy - last update was WC and file was parsed previously updateFileContents(CU_PATH, SOURCE3); mpc.getParsedModule(CU_PATH); testUpdateWorkingCopyAndParse(CU_PATH, WC_SOURCE); // last update testDiscardWorkingCopy(CU_PATH, WC_SOURCE, SOURCE3); // Test redundant discardWorkingCopy mpc.discardWorkingCopy(CU_PATH); assertTrue(mpc.getEntry(CU_PATH).isStale() == false); // Special case: first update for path is working copy, then discard __initModuleParseCache(); updateFileContents(CU_PATH, SOURCE1); testUpdateWorkingCopyAndParse(CU_PATH, WC_SOURCE); testDiscardWorkingCopy(CU_PATH, WC_SOURCE, SOURCE1); testOptimizationsWithIdenticalSource(); // Special case: mpc.discardWorkingCopy(CU_PATH); updateFileContents(CU_PATH, SOURCE1); parsedModule = mpc.getParsedModule(CU_PATH); updateFileContents(CU_PATH, SOURCE3, true); assertTrue(getParsedModuleIfNotStale(CU_PATH) == null); assertTrue(mpc.getEntry(CU_PATH).isStale()); assertTrue(mpc.getParsedModule(CU_PATH) != parsedModule); } protected void testUpdateWorkingCopyAndParse(Path modulePath, String source) throws ModuleSourceException { ParsedModule parsedModule = mpc.setSourceAndParseModule(modulePath, source); assertTrue(mpc.getEntry(CU_PATH).isStale() == false); assertTrue(mpc.getParsedModule(modulePath) == parsedModule); assertTrue(mpc.setSourceAndParseModule(modulePath, source) == parsedModule); assertTrue(parsedModule.source == source); } protected void testDiscardWorkingCopy(Path filePath, String previousSource, String expectedNewSource) throws ModuleSourceException { assertTrue(mpc.getExistingParsedModule(CU_PATH).source.equals(previousSource)); mpc.discardWorkingCopy(filePath); assertTrue(mpc.getEntry(filePath).isStale()); assertTrue(mpc.getParsedModule(filePath).source.equals(expectedNewSource)); } protected void testOptimizationsWithIdenticalSource() throws IOException, ModuleSourceException { mpc.discardWorkingCopy(CU_PATH); ParsedModule parsedModule; updateFileContents(CU_PATH, WC_SOURCE); parsedModule = mpc.getParsedModule(CU_PATH); updateFileContents(CU_PATH, WC_SOURCE, true); testNoReparseHappened(parsedModule, CU_PATH); assertTrue(parsedModule == setWorkingCopySourceAndParse(CU_PATH, WC_SOURCE)); testNoReparseHappened(parsedModule, CU_PATH); parsedModule = setWorkingCopySourceAndParse(CU_PATH, SOURCE3); updateFileContents(CU_PATH, SOURCE3, false); mpc.discardWorkingCopy(CU_PATH); testNoReparseHappened(parsedModule, CU_PATH); } protected void testNoReparseHappened(ParsedModule previousModule, Path filePath) throws ModuleSourceException { assertTrue(previousModule == getParsedModuleIfNotStale(filePath)); assertTrue(mpc.getEntry(filePath).isStale() == false); assertTrue(previousModule == mpc.getParsedModule(filePath)); } protected ParsedModule getParsedModuleIfNotStale(Path path) { return mpc.getEntry(path).getParsedModuleIfNotStale(); } protected ParsedModule setWorkingCopySourceAndParse(Path filePath, String source) { ParsedModule parsedModule = mpc.setSourceAndParseModule(filePath, source); assertTrue(mpc.getEntry(filePath).isWorkingCopy()); return parsedModule; } @Test public void testOther() throws Exception { testOther$(); } public void testOther$() throws Exception { __initModuleParseCache(); Path freeformPath = MiscUtil.createValidPath("#freeFormPath"); assertTrue(mpc.setSourceAndParseModule(freeformPath, SOURCE1).source.equals(SOURCE1)); } }