/******************************************************************************* * 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 dtool.tests.MockCompilerInstalls.DEFAULT_DMD_INSTALL_BaseLocation; import static dtool.tests.MockCompilerInstalls.GDC_CompilerLocation; import static melnorme.utilbox.core.Assert.AssertNamespace.assertFail; import static melnorme.utilbox.core.Assert.AssertNamespace.assertTrue; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.HashMap; import java.util.concurrent.ExecutionException; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import dtool.dub.CommonDubTest; import dtool.dub.DubDescribeParserTest; import dtool.dub.ResolvedManifest; import dtool.engine.compiler_installs.CompilerInstall; import dtool.parser.DeeParserResult.ParsedModule; import melnorme.lang.tooling.BundlePath; import melnorme.lang.utils.FileCachingEntry; import melnorme.lang.utils.MiscFileUtils; import melnorme.utilbox.core.CommonException; import melnorme.utilbox.misc.FileUtil; import melnorme.utilbox.misc.Location; public class SemanticManager_Test extends CommonSemanticManagerTest { @BeforeClass public static void initDubRepositoriesPath() throws CommonException { CommonDubTest.dubRemovePath(BUNDLEMODEL_TEST_BUNDLES); CommonSemanticsTest.removeSemanticsBundlesDubPath(); CommonDubTest.dubAddPath(SMTEST_WORKING_DIR_BUNDLES); } @AfterClass public static void cleanupDubRepositoriesPath() throws CommonException { CommonDubTest.dubRemovePath(SMTEST_WORKING_DIR_BUNDLES); } @Before public void prepWorkingDir() throws IOException { FileUtil.deleteDirContents(SMTEST_WORKING_DIR_BUNDLES); // Make sure state is reset } @Override public Location getDubRepositoryDir() { return SMTEST_WORKING_DIR_BUNDLES; } /* ----------------- ----------------- */ protected HashMap<BundlePath, BundleResolution> previousSRs; protected BundleResolution storeCurrentInMap(BundlePath bundlePath) throws ExecutionException { BundleResolution bundleSR = sm.getStoredResolution(bundlePath); previousSRs.put(bundlePath, bundleSR); checkChanged(bundlePath, false); return bundleSR; } protected void checkChanged(BundlePath bundlePath, boolean expectedChanged) throws ExecutionException { ResolutionKey resKey = resKey(bundlePath); BundleResolution previousResolution = previousSRs.get(bundlePath); if(previousResolution != null) { assertTrue(previousResolution.resKey.equals(resKey)); } boolean changed = previousResolution != sm.getStoredResolution(resKey); assertTrue(changed == expectedChanged); if(expectedChanged) { checkStaleStatus(BASIC_LIB, StaleState.CURRENT); } } protected void __storeCurrentManifests__() throws ExecutionException { previousSRs = new HashMap<>(); storeCurrentInMap(BASIC_LIB); storeCurrentInMap(BASIC_LIB2); storeCurrentInMap(SMTEST); storeCurrentInMap(COMPLEX_LIB); storeCurrentInMap(COMPLEX_BUNDLE); } /* ----------------- ----------------- */ @Test public void testManifestUpdates() throws Exception { testManifestUpdates$(); } public void testManifestUpdates$() throws Exception { prepSMTestsWorkingDir(); ___initSemanticManager(); checkStaleStatus(BASIC_LIB, StaleState.MANIFEST_STALE); // Test manifest only updates sm.getUpdatedManifest(bundleKey(BASIC_LIB)); checkStaleStatus(BASIC_LIB, StaleState.NO_BUNDLE_RESOLUTION); checkStaleStatus(SMTEST, StaleState.MANIFEST_STALE); sm.getUpdatedManifest(bundleKey(SMTEST)); checkStaleStatus(SMTEST, StaleState.NO_BUNDLE_RESOLUTION); { FileCachingEntry<ResolvedManifest> entry = sm.manifestManager.infos.getEntry(bundleKey(BASIC_LIB)); assertTrue(entry.isStale() == false); // This tests for a case we actually had as a bug undetected for a long time. Location manifestFilePath = BASIC_LIB.getLocation().resolve_fromValid(BundlePath.DUB_MANIFEST_NAME_JSON); assertTrue(entry.getFileLocation().equals(manifestFilePath)); makeManifestFileStale(manifestFilePath); assertTrue(entry.isStale() == true); } // Test update resolution over current manifests getUpdatedResolution(BASIC_LIB); checkStaleStatus(BASIC_LIB, StaleState.CURRENT); checkStaleStatus(SMTEST, StaleState.NO_BUNDLE_RESOLUTION); // Test update resolution over partially current manifests sm = ___initSemanticManager(); sm.getUpdatedManifest(bundleKey(BASIC_LIB)); getUpdatedResolution(SMTEST); checkStaleStatus(SMTEST, StaleState.CURRENT); // -- Test update resolution -- sm = ___initSemanticManager(); __storeCurrentManifests__(); getUpdatedResolution(BASIC_LIB); checkChanged(BASIC_LIB, true); checkChanged(SMTEST, false); assertAreEqual(sm.getStoredResolution(SMTEST), null); __storeCurrentManifests__(); getUpdatedResolution(SMTEST); checkChanged(SMTEST, true); checkChanged(BASIC_LIB, true); checkChanged(BASIC_LIB2, false); __storeCurrentManifests__(); getUpdatedResolution(COMPLEX_BUNDLE); checkChanged(COMPLEX_BUNDLE, true); checkChanged(SMTEST, true); checkChanged(BASIC_LIB, true); checkChanged(COMPLEX_LIB, true); checkChanged(BASIC_LIB2, true); sm.getUpdatedResolution(COMPLEX_BUNDLE); // reset // Test bundle invalidation __storeCurrentManifests__(); invalidateBundleManifest(BASIC_LIB); checkStaleStatus(BASIC_LIB, StaleState.MANIFEST_STALE); checkStaleStatus(BASIC_LIB2, StaleState.CURRENT); checkStaleStatus(SMTEST, StaleState.DEP_STALE); getUpdatedResolution(SMTEST); checkChanged(SMTEST, true); checkChanged(BASIC_LIB, true); checkChanged(BASIC_LIB2, false); checkChanged(COMPLEX_BUNDLE, false); // Test effect of invalidation then update of bundle sub-tree only __storeCurrentManifests__(); invalidateBundleManifest(BASIC_LIB); checkStaleStatus(BASIC_LIB, StaleState.MANIFEST_STALE); checkStaleStatus(SMTEST, StaleState.DEP_STALE); getUpdatedResolution(BASIC_LIB); checkChanged(BASIC_LIB, true); checkStaleStatus(BASIC_LIB, StaleState.CURRENT); checkStaleStatus(SMTEST, StaleState.DEP_STALE); } public void makeManifestFileStale(Location fileLoc) throws IOException { String contents = readStringFromFile(fileLoc); writeToFileAndUpdateMTime(fileLoc, contents + " ", true); } protected void invalidateBundleManifest(BundlePath bundlePath) { FileCachingEntry<ResolvedManifest> manifestEntry = sm.manifestManager.getEntry(bundleKey(bundlePath)); manifestEntry.markStale(); } public static BundlePath NON_EXISTANT = bundlePath(SMTEST_WORKING_DIR_BUNDLES, "__NonExistant"); public static BundlePath ERROR_BUNDLE__MISSING_DEP = bundlePath(SMTEST_WORKING_DIR_BUNDLES, "ErrorBundle_MissingDep"); @Test public void testInvalidInput() throws Exception { testInvalidInput$(); } public void testInvalidInput$() throws Exception { prepSMTestsWorkingDir(); ___initSemanticManager(); try { sm.getUpdatedResolution(NON_EXISTANT); } catch (CommonException e) { assertTrue(e.getCause() instanceof IOException); } ResolvedManifest manifest = null; try { manifest = sm.super_getUpdatedManifest(bundleKey(ERROR_BUNDLE__MISSING_DEP), defaultManifestUpdateOptions()); assertTrue(manifest != null && manifest.bundle.hasErrors()); String errorMsg = manifest.bundle.error.getMessage(); assertTrue(errorMsg.contains("Unknown dependency: NonExistantDep") || errorMsg.contains( "Non-optional dependency NonExistantDep of error_bundle_missing_dep not found in dependency tree!")); sm.getUpdatedResolution(ERROR_BUNDLE__MISSING_DEP); throw assertFail(); } catch (CommonException ce) { assertTrue(ce.getMessage().equals(SemanticManager.ERROR_UNRESOLVED_DUB_MANIFEST)); assertTrue(ce.getCause().getMessage().contains(manifest.bundle.error.getMessage())); } // Test that a DUB error makes manifest remain stale checkStaleStatus(ERROR_BUNDLE__MISSING_DEP, StaleState.MANIFEST_STALE); } @Test public void testSubpackages() throws Exception { testSubpackages$(); } public void testSubpackages$() throws Exception { prepSMTestsWorkingDir(DubDescribeParserTest.DUB_TEST_BUNDLES); ___initSemanticManager(); BundlePath SP_TEST = bundlePath(getDubRepositoryDir(), "SubPackagesTest"); BundlePath SP_FOO = bundlePath(getDubRepositoryDir(), "subpackages_foo"); BundlePath SP_FOO2 = bundlePath(getDubRepositoryDir(), "subpackages_foo2"); sm.getUpdatedResolution(SP_TEST); checkStaleStatus(resKey(SP_TEST, "sub_x"), StaleState.CURRENT); checkStaleStatus(resKey(SP_TEST, "sub_a"), StaleState.CURRENT); checkStaleStatus(resKey(SP_TEST, "sub_b"), StaleState.CURRENT); checkStaleStatus(resKey(SP_TEST, "doesn't exists"), StaleState.MANIFEST_STALE); sm.getUpdatedResolution(SP_FOO); ___initSemanticManager(); checkStaleStatus(resKey(SP_TEST, "sub_a"), StaleState.MANIFEST_STALE); BundleResolution bundleRes; bundleRes = sm.getUpdatedResolution(SP_FOO2); assertTrue(bundleRes.getDirectDependencies().size() == 1); checkStaleStatus(resKey(SP_TEST, "sub_a"), StaleState.CURRENT); } /* ----------------- module updates ----------------- */ protected Location writeToFile(Location newFile, String contents) throws IOException { Files.createDirectories(newFile.getParent().path); writeStringToFile(newFile, contents); return newFile; } public void writeToFileAndUpdateMTime(Location file, String contents) throws IOException { writeToFileAndUpdateMTime(file, contents, true); } public void writeToFileAndUpdateMTime(Location file, String contents, boolean isCacheEntryStale) throws IOException { ModuleParseCache_Test.writeToFileAndUpdateMTime(file.path, contents); assertTrue(sm.parseCache.getEntry(file.path).isStale() == isCacheEntryStale); } protected void deleteFile(Path file) throws IOException { assertTrue(FileUtil.deleteIfExists(file)); assertTrue(file.toFile().exists() == false); } @Test public void testModuleUpdates() throws Exception { testModuleUpdates$(); } public void testModuleUpdates$() throws Exception { prepSMTestsWorkingDir(); ___initSemanticManager(); getUpdatedResolution(COMPLEX_LIB); // Test module-file add Path newModule = writeToFile(loc(BASIC_LIB, "source/newModule.d"), "module newModule;").path; checkStaleStatus(BASIC_LIB, StaleState.MODULE_LIST_STALE); checkStaleStatus(COMPLEX_LIB, StaleState.DEP_STALE); checkGetModule(BASIC_LIB, "newModule", null); getUpdatedResolution(COMPLEX_LIB); checkStaleStatus(BASIC_LIB, StaleState.CURRENT); checkGetModule(BASIC_LIB, "newModule"); // Test module-file delete checkStaleStatus(COMPLEX_LIB, StaleState.CURRENT); deleteFile(newModule); checkStaleStatus(BASIC_LIB, StaleState.MODULES_STALE); checkStaleStatus(COMPLEX_LIB, StaleState.DEP_STALE); getUpdatedResolution(COMPLEX_LIB); checkGetModule(BASIC_LIB, "newModule", null); // Test module-file modification checkStaleStatus(COMPLEX_LIB, StaleState.CURRENT); writeToFileAndUpdateMTime(BASIC_LIB_FOO_MODULE, "module basic_lib_pack.foo; /*A*/"); // The resolution will still be current, because the module is lazy loaded/parsed. // And if it wasn't actually loaded, the SM can still be considered current. checkStaleStatus(BASIC_LIB, StaleState.CURRENT); checkStaleStatus(COMPLEX_LIB, StaleState.CURRENT); // Now actually parse the module. checkGetModule(BASIC_LIB, BASIC_LIB_FOO_MODULE_Name); writeToFileAndUpdateMTime(BASIC_LIB_FOO_MODULE, "module basic_lib_pack.foo; /*B*/"); checkStaleStatus(BASIC_LIB, StaleState.MODULE_CONTENTS_STALE); checkStaleStatus(COMPLEX_LIB, StaleState.DEP_STALE); getUpdatedResolution(COMPLEX_LIB); // Test optimization: module-file modification with same source as before. checkStaleStatus(COMPLEX_LIB, StaleState.CURRENT); checkGetModule(BASIC_LIB, BASIC_LIB_FOO_MODULE_Name, BASIC_LIB_FOO_MODULE_Name); writeToFileAndUpdateMTime(BASIC_LIB_FOO_MODULE, readStringFromFile(BASIC_LIB_FOO_MODULE)); assertTrue(sm.parseCache.getEntry(BASIC_LIB_FOO_MODULE.path).isStale() == true); // This stale check will make the parse cache entry no longer stale sm.getStoredResolution(COMPLEX_LIB).checkIsStale(); assertTrue(sm.parseCache.getEntry(BASIC_LIB_FOO_MODULE.path).isStale() == false); checkStaleStatus(BASIC_LIB, StaleState.CURRENT); checkStaleStatus(COMPLEX_LIB, StaleState.CURRENT); // Test WC testWorkingCopyModifications(); } protected static final String SOURCE1 = "module change1;"; protected static final String SOURCE2 = "module change2; /* */"; protected static final String SOURCE3 = "/* */ module change3;"; protected void testWorkingCopyModifications() throws CommonException, IOException { Location modulePath = loc(COMPLEX_LIB, "source/complex_lib.d"); writeToFileAndUpdateMTime(modulePath, "module change0;"); getUpdatedResolution(COMPLEX_BUNDLE); getUpdatedResolvedModule(modulePath, "change0"); // Test initial working copy modification doUpdateWorkingCopy(modulePath, SOURCE1); checkStaleStatus(COMPLEX_LIB, StaleState.MODULE_CONTENTS_STALE); checkStaleStatus(COMPLEX_BUNDLE, StaleState.DEP_STALE); getUpdatedResolvedModule(modulePath, "change1"); // Do modification with same source: no change in staleness doUpdateWorkingCopy(modulePath, SOURCE1); checkStaleStatus(COMPLEX_LIB, StaleState.CURRENT); getUpdatedResolvedModule(modulePath, "change1"); // Do second modification doUpdateWorkingCopy(modulePath, SOURCE2); checkStaleStatus(COMPLEX_LIB, StaleState.MODULE_CONTENTS_STALE); getUpdatedResolvedModule(modulePath, "change2"); // Test discardWorkingCopy to previous CURRENT state doDiscardWorkingCopy(modulePath); checkStaleStatus(COMPLEX_LIB, StaleState.MODULE_CONTENTS_STALE); getUpdatedResolvedModule(modulePath, "change0"); // Test file update over a working copy doUpdateWorkingCopy(modulePath, SOURCE1); writeToFileAndUpdateMTime(modulePath, SOURCE2, false); // checkStaleStatus(COMPLEX_LIB, StaleState.MODULES_STALE); // getUpdatedResolvedModule(modulePath, SOURCE2); // // Then discard // sm.discardWorkingCopy(modulePath); // checkStaleStatus(COMPLEX_LIB, StaleState.CURRENT); // Test delete over a working copy } protected ParsedModule doUpdateWorkingCopy(Location filePath, String contents) { BundlePath bundlePath = BundlePath.findBundleForPath(filePath); assertTrue(sm.checkIsResolutionStale(bundlePath) == false); return sm.setWorkingCopyAndParse(filePath.path, contents); } protected void doDiscardWorkingCopy(Location filePath) { BundlePath bundlePath = BundlePath.findBundleForPath(filePath); assertTrue(sm.checkIsResolutionStale(bundlePath) == false); sm.discardWorkingCopy(filePath.path); assertTrue(sm.parseCache.getEntry(filePath.path).getParsedModuleIfNotStale() == null); } /* ----------------- ----------------- */ @Test public void testStdLibInteractions() throws Exception { testStdLibInteractions$(); } public void testStdLibInteractions$() throws Exception { prepSMTestsWorkingDir(); ___initSemanticManager(); Location DMD_Install_WC_Base = SMTEST_WORKING_DIR_BUNDLES.resolveOrNull("DMD_Install_WC"); Location DMD_Install_WC = DMD_Install_WC_Base.resolveOrNull("windows/bin/dmd.exe"); MiscFileUtils.copyDirContentsIntoDirectory(DEFAULT_DMD_INSTALL_BaseLocation, DMD_Install_WC_Base); getUpdatedResolution(resKey(COMPLEX_LIB, DMD_Install_WC)); BundleResolution complexLib = sm.getStoredResolution(resKey(COMPLEX_LIB, DMD_Install_WC)); assertAreEqual(complexLib.getCompilerPath(), DMD_Install_WC); BundleResolution complexLib2; complexLib2 = getUpdatedResolution(resKey(COMPLEX_LIB, GDC_CompilerLocation)); assertTrue(complexLib != complexLib2); checkStaleStatus(resKey(COMPLEX_LIB, DMD_Install_WC), StaleState.CURRENT); checkStaleStatus(resKey(COMPLEX_LIB, GDC_CompilerLocation), StaleState.CURRENT); StandardLibraryResolution stdLib = sm.getUpdatedStdLibResolution(compilerInstall(DMD_Install_WC)); assertTrue(stdLib.checkIsModuleContentsStale() == false); Location DMD_INSTALL_ObjectModule = DMD_Install_WC_Base.resolve_fromValid("src/druntime/import/object.di"); stdLib.getBundleResolvedModule("object"); sm.setWorkingCopyAndParse(DMD_INSTALL_ObjectModule.path, "module object.d; /*SM_TEST*/"); assertTrue(stdLib.checkIsModuleContentsStale()); checkStaleStatus(resKey(COMPLEX_LIB, DMD_Install_WC), StaleState.DEP_STALE); checkStaleStatus(resKey(COMPLEX_LIB, GDC_CompilerLocation), StaleState.CURRENT); StandardLibraryResolution stdLib2 = sm.getUpdatedStdLibResolution(compilerInstall(DMD_Install_WC)); assertTrue(stdLib2 != stdLib); assertTrue(stdLib2 == sm.getUpdatedStdLibResolution(compilerInstall(DMD_Install_WC))); checkStaleStatus(resKey(COMPLEX_LIB, DMD_Install_WC), StaleState.DEP_STALE); complexLib = sm.getUpdatedResolution(resKey(COMPLEX_LIB, DMD_Install_WC)); checkStaleStatus(resKey(COMPLEX_LIB, DMD_Install_WC), StaleState.CURRENT); assertTrue(stdLib2 == sm.getUpdatedStdLibResolution(compilerInstall(DMD_Install_WC))); } protected static CompilerInstall compilerInstall(Location compilerPath) { return DToolServer.getCompilerInstallForPath(compilerPath); } }