/****************************************************************************** * Copyright (C) 2011-2013 Fabio Zadrozny and others * * 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: * Fabio Zadrozny <fabiofz@gmail.com> - initial API and implementation ******************************************************************************/ package com.python.pydev.analysis.system_info_builder; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.preference.PreferenceStore; import org.python.pydev.core.ExtensionHelper; import org.python.pydev.core.IInterpreterInfo; import org.python.pydev.core.IInterpreterManager; import org.python.pydev.core.ISystemModulesManager; import org.python.pydev.core.MisconfigurationException; import org.python.pydev.core.TestDependent; import org.python.pydev.editor.codecompletion.revisited.ManagerInfoToUpdate; import org.python.pydev.editor.codecompletion.revisited.ProjectModulesManager; import org.python.pydev.editor.codecompletion.revisited.SyncSystemModulesManagerScheduler; import org.python.pydev.editor.codecompletion.revisited.SyncSystemModulesManagerScheduler.IInfoTrackerListener; import org.python.pydev.editor.codecompletion.revisited.SyncSystemModulesManagerScheduler.InfoTracker; import org.python.pydev.editor.codecompletion.revisited.SynchSystemModulesManager; import org.python.pydev.editor.codecompletion.revisited.SynchSystemModulesManager.PythonpathChange; import org.python.pydev.plugin.PydevPlugin; import org.python.pydev.plugin.PydevTestUtils; import org.python.pydev.shared_core.callbacks.ICallback; import org.python.pydev.shared_core.io.FileUtils; import org.python.pydev.shared_core.string.StringUtils; import org.python.pydev.shared_core.structure.DataAndImageTreeNode; import org.python.pydev.shared_core.structure.TreeNode; import org.python.pydev.shared_core.structure.Tuple; import org.python.pydev.shared_core.testutils.TestUtils; import org.python.pydev.ui.interpreters.PythonInterpreterManager; import org.python.pydev.ui.pythonpathconf.InterpreterInfo; import com.python.pydev.analysis.additionalinfo.AdditionalSystemInterpreterInfo; import com.python.pydev.analysis.additionalinfo.IInfo; import com.python.pydev.analysis.additionalinfo.builders.InterpreterObserver; import junit.framework.TestCase; @SuppressWarnings({ "rawtypes", "unused", "unchecked" }) public class SyncSystemModulesManagerTest extends TestCase { private File baseDir; private File libDir; private File libDir2; private File libDir3; private File libZipFile; @Override protected void setUp() throws Exception { baseDir = new File(FileUtils.getFileAbsolutePath(new File("InterpreterInfoBuilderTest.temporary_dir"))); try { FileUtils.deleteDirectoryTree(baseDir); } catch (Exception e) { //ignore } libDir = new File(baseDir, "Lib"); libDir.mkdirs(); FileUtils.writeStrToFile("class Module1:pass", new File(libDir, "module1.py")); FileUtils.writeStrToFile("class Module2:pass", new File(libDir, "module2.py")); FileUtils.writeStrToFile("class Module3:pass", new File(libDir, "module3.py")); libDir2 = new File(baseDir, "Lib2"); libDir2.mkdirs(); FileUtils.writeStrToFile("class Module4:pass", new File(libDir2, "module4.py")); FileUtils.writeStrToFile("class Module5:pass", new File(libDir2, "module5.py")); libDir3 = new File(baseDir, "Lib3"); libDir3.mkdirs(); libZipFile = new File(baseDir, "entry.egg"); FileOutputStream stream = new FileOutputStream(libZipFile); ZipOutputStream zipOut = new ZipOutputStream(new BufferedOutputStream(stream)); zipOut.putNextEntry(new ZipEntry("zip_mod.py")); zipOut.write("class ZipMod:pass".getBytes()); zipOut.close(); PydevTestUtils.setTestPlatformStateLocation(); ExtensionHelper.testingParticipants = new HashMap<String, List<Object>>(); //Note: needed to restore additional info! List list = Arrays.asList(new InterpreterObserver()); ExtensionHelper.testingParticipants.put(ExtensionHelper.PYDEV_INTERPRETER_OBSERVER, list); FileUtils.IN_TESTS = true; ProjectModulesManager.IN_TESTS = true; PydevPlugin.setPythonInterpreterManager(null); PydevPlugin.setIronpythonInterpreterManager(null); PydevPlugin.setJythonInterpreterManager(null); } @Override protected void tearDown() throws Exception { FileUtils.deleteDirectoryTree(baseDir); ProjectModulesManager.IN_TESTS = false; FileUtils.IN_TESTS = false; ExtensionHelper.testingParticipants = null; } private void setupEnv() throws MisconfigurationException { setupEnv(false); } private void setupEnv(boolean setupInitialInfoProperly) throws MisconfigurationException { Collection<String> pythonpath = new ArrayList<String>(); pythonpath.add(libDir.toString()); final InterpreterInfo info = new InterpreterInfo("2.6", TestDependent.PYTHON_EXE, pythonpath); IPreferenceStore preferences = createPreferenceStore(); final PythonInterpreterManager manager = new PythonInterpreterManager(preferences); PydevPlugin.setPythonInterpreterManager(manager); manager.setInfos(new IInterpreterInfo[] { info }, null, null); AdditionalSystemInterpreterInfo additionalInfo = new AdditionalSystemInterpreterInfo(manager, info.getExecutableOrJar()); AdditionalSystemInterpreterInfo.setAdditionalSystemInfo(manager, info.getExecutableOrJar(), additionalInfo); //Don't load it (otherwise it'll get the 'proper' info). if (setupInitialInfoProperly) { InterpreterInfo infoOnManager = manager.getInterpreterInfo(info.getExecutableOrJar(), null); assertEquals(infoOnManager.getPythonPath(), info.getPythonPath()); NullProgressMonitor monitor = new NullProgressMonitor(); info.restorePythonpath(monitor); AdditionalSystemInterpreterInfo.recreateAllInfo(manager, info.getExecutableOrJar(), monitor); final ISystemModulesManager modulesManager = info.getModulesManager(); assertEquals(3, modulesManager.getSize(false)); assertEquals(3, infoOnManager.getModulesManager().getSize(false)); additionalInfo = (AdditionalSystemInterpreterInfo) AdditionalSystemInterpreterInfo.getAdditionalSystemInfo( manager, info.getExecutableOrJar()); Collection<IInfo> allTokens = additionalInfo.getAllTokens(); assertEquals(3, additionalInfo.getAllTokens().size()); } else { final ISystemModulesManager modulesManager = info.getModulesManager(); assertEquals(0, modulesManager.getSize(false)); assertEquals(0, additionalInfo.getAllTokens().size()); } } private PreferenceStore createPreferenceStore() { return new PreferenceStore(new File(baseDir, "preferenceStore").toString()); } public void testUpdateWhenEggIsAdded() throws Exception { setupEnv(true); SynchSystemModulesManager synchManager = new SynchSystemModulesManager(); final DataAndImageTreeNode root = new DataAndImageTreeNode(null, null, null); Map<IInterpreterManager, Map<String, IInterpreterInfo>> managerToNameToInfoMap = PydevPlugin .getInterpreterManagerToInterpreterNameToInfo(); ManagerInfoToUpdate managerToNameToInfo = new ManagerInfoToUpdate(managerToNameToInfoMap); checkUpdateStructures(synchManager, root, managerToNameToInfo); checkSynchronize(synchManager, root, managerToNameToInfo); root.clear(); managerToNameToInfo = new ManagerInfoToUpdate(PydevPlugin.getInterpreterManagerToInterpreterNameToInfo()); synchManager.updateStructures(null, root, managerToNameToInfo, new SynchSystemModulesManager.CreateInterpreterInfoCallback() { @Override public IInterpreterInfo createInterpreterInfo(IInterpreterManager manager, String executable, IProgressMonitor monitor) { Collection<String> pythonpath = new ArrayList<String>(); pythonpath.add(libDir.toString()); pythonpath.add(libZipFile.toString()); final InterpreterInfo info = new InterpreterInfo("2.6", TestDependent.PYTHON_EXE, pythonpath); return info; } }); assertTrue(root.hasChildren()); List<TreeNode> selectElements = new ArrayList<>(); selectElements.addAll(root.flattenChildren()); synchManager.applySelectedChangesToInterpreterInfosPythonpath(root, selectElements, null); List<IInterpreterInfo> allInterpreterInfos = PydevPlugin.getAllInterpreterInfos(); for (IInterpreterInfo interpreterInfo : allInterpreterInfos) { assertEquals(4, interpreterInfo.getModulesManager().getSize(false)); AdditionalSystemInterpreterInfo additionalInfo = (AdditionalSystemInterpreterInfo) AdditionalSystemInterpreterInfo .getAdditionalSystemInfo( interpreterInfo.getModulesManager().getInterpreterManager(), interpreterInfo.getExecutableOrJar()); Collection<IInfo> allTokens = additionalInfo.getAllTokens(); assertEquals(4, additionalInfo.getAllTokens().size()); } } public void testScheduleCheckForUpdates() throws Exception { setupEnv(); Map<IInterpreterManager, Map<String, IInterpreterInfo>> managerToNameToInfo = PydevPlugin .getInterpreterManagerToInterpreterNameToInfo(); SyncSystemModulesManagerScheduler scheduler = new SyncSystemModulesManagerScheduler(); final Set changes = Collections.synchronizedSet(new HashSet<>()); try { Set<Entry<IInterpreterManager, Map<String, IInterpreterInfo>>> entrySet = managerToNameToInfo.entrySet(); SyncSystemModulesManagerScheduler.IInfoTrackerListener listener = new IInfoTrackerListener() { @Override public void onChangedIInterpreterInfo(InfoTracker infoTracker, File file) { changes.add(file); } }; for (Entry<IInterpreterManager, Map<String, IInterpreterInfo>> entry : entrySet) { Map<String, IInterpreterInfo> value = entry.getValue(); scheduler.afterSetInfos(entry.getKey(), value.values().toArray(new IInterpreterInfo[value.size()]), listener); } final File module4File = new File(libDir, "module4.py"); FileUtils.writeStrToFile("class Module3:pass", module4File); TestUtils.waitUntilCondition(new ICallback<String, Object>() { @Override public String call(Object arg) { if (changes.contains(module4File)) { return null; } return "Changes not found."; } }); changes.clear(); final File myPthFile = new File(libDir, "my.pth"); FileUtils.writeStrToFile("./setuptools-1.1.6-py2.6.egg", myPthFile); TestUtils.waitUntilCondition(new ICallback<String, Object>() { @Override public String call(Object arg) { if (changes.contains(myPthFile)) { return null; } return "Changes not found."; } }); synchronized (this) { this.wait(250); //Wait a bit as we may have 2 notifications (for creation and modification of the pth). } //Now, add an unrelated directory: no notifications are expected then. changes.clear(); final File myUnrelatedDir = new File(libDir, "unrelatedDir"); myUnrelatedDir.mkdir(); synchronized (this) { this.wait(250); } assertEquals(new HashSet<>(), changes); //no changes expected } finally { scheduler.stop(); } changes.clear(); final File myPthFile2 = new File(libDir, "my2.pth"); FileUtils.writeStrToFile("./setuptools-1.1.7-py2.6.egg", myPthFile2); synchronized (this) { this.wait(250); } assertEquals(new HashSet<>(), changes); } public void testUpdateAndApply() throws Exception { setupEnv(); SynchSystemModulesManager synchManager = new SynchSystemModulesManager(); final DataAndImageTreeNode root = new DataAndImageTreeNode(null, null, null); Map<IInterpreterManager, Map<String, IInterpreterInfo>> managerToNameToInfoMap = PydevPlugin .getInterpreterManagerToInterpreterNameToInfo(); ManagerInfoToUpdate managerToNameToInfo = new ManagerInfoToUpdate(managerToNameToInfoMap); checkUpdateStructures(synchManager, root, managerToNameToInfo); checkSynchronize(synchManager, root, managerToNameToInfo); //Ok, the interpreter should be synchronized with the pythonpath which is currently set. //Now, check a different scenario: create a new path and add it to the interpreter pythonpath. //In this situation, the sync manager should ask the user if that path should actually be added //to this interpreter. root.clear(); managerToNameToInfo = new ManagerInfoToUpdate(PydevPlugin.getInterpreterManagerToInterpreterNameToInfo()); synchManager.updateStructures(null, root, managerToNameToInfo, new SynchSystemModulesManager.CreateInterpreterInfoCallback() { @Override public IInterpreterInfo createInterpreterInfo(IInterpreterManager manager, String executable, IProgressMonitor monitor) { Collection<String> pythonpath = new ArrayList<String>(); pythonpath.add(libDir.toString()); pythonpath.add(libDir2.toString()); final InterpreterInfo info = new InterpreterInfo("2.6", TestDependent.PYTHON_EXE, pythonpath); return info; } }); assertTrue(root.hasChildren()); List<TreeNode> selectElements = new ArrayList<>(); selectElements.addAll(root.flattenChildren()); synchManager.applySelectedChangesToInterpreterInfosPythonpath(root, selectElements, null); List<IInterpreterInfo> allInterpreterInfos = PydevPlugin.getAllInterpreterInfos(); for (IInterpreterInfo interpreterInfo : allInterpreterInfos) { assertEquals(5, interpreterInfo.getModulesManager().getSize(false)); } } private void checkUpdateStructures(SynchSystemModulesManager synchManager, final DataAndImageTreeNode root, ManagerInfoToUpdate managerToNameToInfo) { synchManager.updateStructures(null, root, managerToNameToInfo, new SynchSystemModulesManager.CreateInterpreterInfoCallback() { @Override public IInterpreterInfo createInterpreterInfo(IInterpreterManager manager, String executable, IProgressMonitor monitor) { Collection<String> pythonpath = new ArrayList<String>(); pythonpath.add(libDir.toString()); //Still the same! final InterpreterInfo info = new InterpreterInfo("2.6", TestDependent.PYTHON_EXE, pythonpath); return info; } }); Tuple<IInterpreterManager, IInterpreterInfo>[] managerAndInfos = managerToNameToInfo.getManagerAndInfos(); int found = managerAndInfos.length; assertEquals(found, 1); } private void checkSynchronize(SynchSystemModulesManager synchManager, final DataAndImageTreeNode root, ManagerInfoToUpdate managerToNameToInfo) { //Ok, all is Ok in the PYTHONPATH, so, check if something changed inside the interpreter info //and not on the PYTHONPATH. assertFalse(root.hasChildren()); InterpreterInfoBuilder builder = new InterpreterInfoBuilder(); synchManager.synchronizeManagerToNameToInfoPythonpath(null, managerToNameToInfo, builder); Tuple<IInterpreterManager, IInterpreterInfo>[] managerAndInfos = managerToNameToInfo.getManagerAndInfos(); for (Tuple<IInterpreterManager, IInterpreterInfo> tuple : managerAndInfos) { InterpreterInfo interpreterInfo = (InterpreterInfo) tuple.o2; assertEquals(3, interpreterInfo.getModulesManager().getSize(false)); } } public void testSaveUserChoicesAfterSelection() throws Exception { setupEnv(false); IPreferenceStore preferences = createPreferenceStore(); SynchSystemModulesManager synchManager = new SynchSystemModulesManager(); final DataAndImageTreeNode root = new DataAndImageTreeNode(null, null, null); Map<IInterpreterManager, Map<String, IInterpreterInfo>> managerToNameToInfo = PydevPlugin .getInterpreterManagerToInterpreterNameToInfo(); synchManager.updateStructures(null, root, new ManagerInfoToUpdate(managerToNameToInfo), new SynchSystemModulesManager.CreateInterpreterInfoCallback() { @Override public IInterpreterInfo createInterpreterInfo(IInterpreterManager manager, String executable, IProgressMonitor monitor) { Collection<String> pythonpath = new ArrayList<>(); pythonpath.add(libDir.toString()); pythonpath.add(libDir2.toString()); pythonpath.add(libDir3.toString()); pythonpath.add(libZipFile.toString()); final InterpreterInfo info = new InterpreterInfo("2.6", TestDependent.PYTHON_EXE, pythonpath); return info; } }); assertTrue(root.hasChildren()); List<TreeNode> selectedElements = new ArrayList<>(); TreeNode interpreterNode = (TreeNode) root.getChildren().get(0); selectedElements.add(interpreterNode); List<TreeNode> children = interpreterNode.getChildren(); for (TreeNode<PythonpathChange> treeNode : children) { if (treeNode.getData().path.equals(libDir2.toString())) { selectedElements.add(treeNode); } } synchManager.saveUnselected(root, selectedElements, preferences); //Check that we ignored libDir3 and libZipFile String key = SynchSystemModulesManager.createKeyForInfo((IInterpreterInfo) ((TreeNode) root.getChildren() .get(0)).getData()); String entry = preferences.getString(key); List<String> entries = StringUtils.split(entry, "|||"); assertEquals(2, entries.size()); HashSet<String> entriesSet = new HashSet<>(entries); assertEquals(new HashSet(entries), new HashSet(Arrays.asList(libDir3.toString(), libZipFile.toString()))); //Check that only libDir2 is initially selected. List<TreeNode> initialSelection = synchManager.createInitialSelectionForDialogConsideringPreviouslyIgnored( root, preferences); assertEquals(2, initialSelection.size()); TreeNode treeNode = initialSelection.get(0); TreeNode treeNode1 = initialSelection.get(1); TreeNode interpreterInfoNode; TreeNode pythonpathNode; if (treeNode.getData() instanceof IInterpreterInfo) { interpreterNode = treeNode; pythonpathNode = treeNode1; } else { interpreterNode = treeNode1; pythonpathNode = treeNode; } assertEquals(((PythonpathChange) pythonpathNode.getData()).path, libDir2.toString()); } }