mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-05-31 10:11:21 +08:00
GP-3551 Added support for internal project link-files with improved link
support within project data tree. Linked-folders are now supported. Addressed link-support issues related to various actions. Revised link-file storage to use smaller non-DB storage. This change does impact the Ghidra Server.
This commit is contained in:
+9
-2
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package help.screenshot;
|
||||
|
||||
import java.awt.Dialog;
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.swing.JComboBox;
|
||||
@@ -27,6 +28,7 @@ import ghidra.app.util.exporter.Exporter;
|
||||
import ghidra.framework.model.*;
|
||||
import ghidra.framework.preferences.Preferences;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.util.Swing;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.exception.VersionException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
@@ -40,10 +42,15 @@ public class ExporterPluginScreenShots extends GhidraScreenShotGenerator {
|
||||
Preferences.setProperty(Preferences.LAST_EXPORT_DIRECTORY, "/path");
|
||||
|
||||
DomainFile df = createDomainFile();
|
||||
ExporterDialog dialog = new ExporterDialog(tool, df);
|
||||
runSwing(() -> tool.showDialog(dialog), false);
|
||||
|
||||
runSwing(() -> ExporterDialog.show(tool, df), false);
|
||||
|
||||
Dialog dialog = waitForJDialog("Export Program_A");
|
||||
|
||||
waitForSwing();
|
||||
captureDialog(dialog);
|
||||
|
||||
Swing.runNow(() -> dialog.dispose());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+48
-16
@@ -37,8 +37,7 @@ import docking.wizard.WizardDialog;
|
||||
import generic.theme.GThemeDefaults.Colors;
|
||||
import ghidra.app.plugin.core.archive.RestoreDialog;
|
||||
import ghidra.framework.Application;
|
||||
import ghidra.framework.data.DefaultProjectData;
|
||||
import ghidra.framework.data.GhidraFileData;
|
||||
import ghidra.framework.data.*;
|
||||
import ghidra.framework.main.*;
|
||||
import ghidra.framework.main.wizard.project.*;
|
||||
import ghidra.framework.model.*;
|
||||
@@ -56,10 +55,14 @@ import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.extensions.ExtensionDetails;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
import resources.MultiIcon;
|
||||
import resources.icons.TranslateIcon;
|
||||
|
||||
public class FrontEndPluginScreenShots extends GhidraScreenShotGenerator {
|
||||
|
||||
private static final String RIGHT_ARROW = "\u2b95";
|
||||
private static final String OTHER_PROJECT = "Other_Project";
|
||||
Icon icon = (Icon) getInstanceField("CONVERT_ICON", ProjectChooseRepositoryWizardModel.class);
|
||||
private Icon icon =
|
||||
(Icon) getInstanceField("CONVERT_ICON", ProjectChooseRepositoryWizardModel.class);
|
||||
|
||||
public FrontEndPluginScreenShots() {
|
||||
super();
|
||||
@@ -139,8 +142,7 @@ public class FrontEndPluginScreenShots extends GhidraScreenShotGenerator {
|
||||
|
||||
TestDummyWizardModel<ProjectWizardData> panelMgr =
|
||||
new TestDummyWizardModel<ProjectWizardData>(panel, false, true, false,
|
||||
"Change Shared Project Information", 600, 375,
|
||||
new ProjectWizardData(), icon);
|
||||
"Change Shared Project Information", 600, 375, new ProjectWizardData(), icon);
|
||||
|
||||
WizardDialog wizard = new WizardDialog(panelMgr, false);
|
||||
wizard.show();
|
||||
@@ -156,9 +158,8 @@ public class FrontEndPluginScreenShots extends GhidraScreenShotGenerator {
|
||||
|
||||
ProjectWizardData data = new ProjectWizardData();
|
||||
data.setServerInfo(new ServerInfo("server1", 13100));
|
||||
TestDummyWizardModel<ProjectWizardData> panelMgr =
|
||||
new TestDummyWizardModel<>(panel, false, true, false,
|
||||
"Change Shared Project Information", 600, 180, data, icon);
|
||||
TestDummyWizardModel<ProjectWizardData> panelMgr = new TestDummyWizardModel<>(panel, false,
|
||||
true, false, "Change Shared Project Information", 600, 180, data, icon);
|
||||
|
||||
WizardDialog wizard = new WizardDialog(panelMgr, false);
|
||||
wizard.show();
|
||||
@@ -326,6 +327,40 @@ public class FrontEndPluginScreenShots extends GhidraScreenShotGenerator {
|
||||
captureIconAndText(multiIcon, "Example");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAbsoluteFileLinkIcon() {
|
||||
Icon programIcon = ProgramContentHandler.PROGRAM_ICON;
|
||||
MultiIcon multiIcon = new MultiIcon(programIcon);
|
||||
multiIcon.addIcon(new TranslateIcon(LinkHandler.LINK_ICON, 0, 1));
|
||||
captureIconAndText(multiIcon, "Example " + RIGHT_ARROW + " /data/Example");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAbsoluteBrokenFileLinkIcon() {
|
||||
Icon programIcon = ProgramContentHandler.PROGRAM_ICON;
|
||||
MultiIcon multiIcon = new MultiIcon(programIcon);
|
||||
multiIcon.addIcon(new TranslateIcon(LinkHandler.LINK_ICON, 0, 1));
|
||||
Icon linkIcon = new BrokenLinkIcon(multiIcon);
|
||||
captureIconAndText(linkIcon, "Example " + RIGHT_ARROW + " /data/Example");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAbsoluteFolderLinkIcon() {
|
||||
Icon folderIcon = DomainFolder.CLOSED_FOLDER_ICON;
|
||||
MultiIcon multiIcon = new MultiIcon(folderIcon);
|
||||
multiIcon.addIcon(new TranslateIcon(LinkHandler.LINK_ICON, 0, 1));
|
||||
captureIconAndText(multiIcon, "Example " + RIGHT_ARROW + " /data/Example");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAbsoluteBrokenFolderLinkIcon() {
|
||||
Icon folderIcon = DomainFolder.CLOSED_FOLDER_ICON;
|
||||
MultiIcon multiIcon = new MultiIcon(folderIcon);
|
||||
multiIcon.addIcon(new TranslateIcon(LinkHandler.LINK_ICON, 0, 1));
|
||||
Icon linkIcon = new BrokenLinkIcon(multiIcon);
|
||||
captureIconAndText(linkIcon, "Example " + RIGHT_ARROW + " /data/Example");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProjectDataTable()
|
||||
throws CancelledException, IOException, InvalidNameException {
|
||||
@@ -337,8 +372,7 @@ public class FrontEndPluginScreenShots extends GhidraScreenShotGenerator {
|
||||
|
||||
FrontEndPlugin plugin = getPlugin(tool, FrontEndPlugin.class);
|
||||
JComponent projectDataPanel = (JComponent) getInstanceField("projectDataPanel", plugin);
|
||||
JTabbedPane tabbedPane =
|
||||
(JTabbedPane) getInstanceField("projectTab", projectDataPanel);
|
||||
JTabbedPane tabbedPane = (JTabbedPane) getInstanceField("projectTab", projectDataPanel);
|
||||
tabbedPane.setSelectedIndex(1);
|
||||
setToolSize(800, 600);
|
||||
captureComponent(projectDataPanel);
|
||||
@@ -407,8 +441,7 @@ public class FrontEndPluginScreenShots extends GhidraScreenShotGenerator {
|
||||
|
||||
TestDummyWizardModel<ProjectWizardData> panelMgr =
|
||||
new TestDummyWizardModel<ProjectWizardData>(panel, false, true, false,
|
||||
"Specify Repository Name on Server1", 600, 375,
|
||||
new ProjectWizardData(), icon);
|
||||
"Specify Repository Name on Server1", 600, 375, new ProjectWizardData(), icon);
|
||||
|
||||
WizardDialog wizard = new WizardDialog(panelMgr, false);
|
||||
|
||||
@@ -694,13 +727,12 @@ public class FrontEndPluginScreenShots extends GhidraScreenShotGenerator {
|
||||
ProjectTestUtils.deleteProject(TEMP_DIR, OTHER_PROJECT);
|
||||
Project otherProject = ProjectTestUtils.getProject(TEMP_DIR, OTHER_PROJECT);
|
||||
Language language = getZ80_LANGUAGE();
|
||||
DomainFile otherFile =
|
||||
ProjectTestUtils.createProgramFile(otherProject, "Program1", language,
|
||||
language.getDefaultCompilerSpec(), null);
|
||||
DomainFile otherFile = ProjectTestUtils.createProgramFile(otherProject, "Program1",
|
||||
language, language.getDefaultCompilerSpec(), null);
|
||||
ProjectTestUtils.createProgramFile(otherProject, "Program2", language,
|
||||
language.getDefaultCompilerSpec(), null);
|
||||
|
||||
otherFile.copyToAsLink(projectData.getRootFolder());
|
||||
otherFile.copyToAsLink(projectData.getRootFolder(), false);
|
||||
|
||||
otherProject.close();
|
||||
|
||||
|
||||
+411
@@ -0,0 +1,411 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* 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 ghidra.framework.main.datatree;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.Window;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.tree.TreePath;
|
||||
|
||||
import org.junit.*;
|
||||
|
||||
import docking.ActionContext;
|
||||
import docking.ComponentProvider;
|
||||
import docking.action.DockingActionIf;
|
||||
import docking.test.AbstractDockingTest;
|
||||
import docking.tool.ToolConstants;
|
||||
import docking.widgets.tree.GTreeNode;
|
||||
import ghidra.framework.main.FrontEndTool;
|
||||
import ghidra.framework.main.datatable.ProjectDataContext;
|
||||
import ghidra.framework.model.*;
|
||||
import ghidra.framework.options.ToolOptions;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.test.*;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* Tests for opening files.
|
||||
*/
|
||||
public class FrontEndPluginOpenProgramActionsTest extends AbstractGhidraHeadedIntegrationTest {
|
||||
|
||||
private FrontEndTool frontEndTool;
|
||||
private TestEnv env;
|
||||
private DataTree tree;
|
||||
private DomainFolder rootFolder;
|
||||
private GTreeNode rootNode;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
env = new TestEnv();
|
||||
env.resetDefaultTools();
|
||||
|
||||
frontEndTool = env.getFrontEndTool();
|
||||
env.showFrontEndTool();
|
||||
setErrorGUIEnabled(false);
|
||||
|
||||
tree = findComponent(frontEndTool.getToolFrame(), DataTree.class);
|
||||
rootFolder = env.getProject().getProjectData().getRootFolder();
|
||||
Program p = ToyProgramBuilder.buildSimpleProgram("sample", this);
|
||||
rootFolder.createFile("sample", p, TaskMonitor.DUMMY);
|
||||
p.release(this);
|
||||
|
||||
p = ToyProgramBuilder.buildSimpleProgram("x", this);
|
||||
rootFolder.createFile("X", p, TaskMonitor.DUMMY);
|
||||
p.release(this);
|
||||
|
||||
rootNode = tree.getViewRoot();
|
||||
|
||||
waitForSwing();
|
||||
tree.expandPath(rootNode.getTreePath());
|
||||
waitForTree();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
waitForTree();
|
||||
env.dispose();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOpenActionsEnabled() throws Exception {
|
||||
setSelectionPath(rootNode.getTreePath());
|
||||
DockingActionIf openAction = getAction("Open File");
|
||||
assertTrue(!openAction.isEnabledForContext(getDomainFileActionContext(rootNode)));
|
||||
|
||||
ToolChest tc = env.getProject().getLocalToolChest();
|
||||
ToolTemplate[] configs = tc.getToolTemplates();
|
||||
for (ToolTemplate config : configs) {
|
||||
DockingActionIf action = getAction("Open" + config.getName());
|
||||
assertTrue(!action.isEnabledForContext(getDomainFileActionContext(rootNode)));
|
||||
assertTrue(!openAction.isEnabledForContext(getDomainFileActionContext(rootNode)));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOpenInDefaultTool() throws Exception {
|
||||
//Open File
|
||||
GTreeNode npNode = rootNode.getChild("sample");
|
||||
setSelectionPath(npNode.getTreePath());
|
||||
waitForTree();
|
||||
DockingActionIf openAction = getAction("Open File");
|
||||
performAction(openAction, getFrontEndContext(), true);
|
||||
verifyToolExistsAndCloseTool();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOpenInDefaultToolMultipleNewTool() throws Exception {
|
||||
|
||||
ToolOptions options = frontEndTool.getOptions(ToolConstants.TOOL_OPTIONS);
|
||||
options.setEnum(FrontEndTool.DEFAULT_TOOL_LAUNCH_MODE, DefaultLaunchMode.NEW_TOOL);
|
||||
|
||||
//Open 1st File
|
||||
DomainFile sampleDf = openInDefaultTool("sample");
|
||||
PluginTool[] runningTools = env.getProject().getToolManager().getRunningTools();
|
||||
assertEquals(1, runningTools.length);
|
||||
assertOpenFiles(runningTools[0], sampleDf);
|
||||
|
||||
//Open 2nd File in new tool
|
||||
DomainFile xDf = openInDefaultTool("X");
|
||||
|
||||
// NOTE: runningTools order may vary
|
||||
runningTools = env.getProject().getToolManager().getRunningTools();
|
||||
assertEquals(2, runningTools.length);
|
||||
DomainFile[] domainFiles0 = runningTools[0].getDomainFiles();
|
||||
assertEquals(1, domainFiles0.length);
|
||||
DomainFile[] domainFiles1 = runningTools[1].getDomainFiles();
|
||||
assertEquals(1, domainFiles1.length);
|
||||
if (sampleDf.equals(domainFiles0[0])) {
|
||||
assertEquals(xDf, domainFiles1[0]);
|
||||
}
|
||||
else if (sampleDf.equals(domainFiles1[0])) {
|
||||
assertEquals(xDf, domainFiles0[0]);
|
||||
}
|
||||
else {
|
||||
fail("Unexpected open domain files");
|
||||
}
|
||||
|
||||
exitTools(runningTools);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOpenInDefaultToolMultipleReuseTool() throws Exception {
|
||||
|
||||
ToolOptions options = frontEndTool.getOptions(ToolConstants.TOOL_OPTIONS);
|
||||
options.setEnum(FrontEndTool.DEFAULT_TOOL_LAUNCH_MODE, DefaultLaunchMode.REUSE_TOOL);
|
||||
|
||||
//Open 1st File
|
||||
DomainFile sampleDf = openInDefaultTool("sample");
|
||||
PluginTool[] runningTools = env.getProject().getToolManager().getRunningTools();
|
||||
assertEquals(1, runningTools.length);
|
||||
assertOpenFiles(runningTools[0], sampleDf);
|
||||
|
||||
//Open 2nd File in same tool
|
||||
DomainFile xDf = openInDefaultTool("X");
|
||||
runningTools = env.getProject().getToolManager().getRunningTools();
|
||||
assertEquals(1, runningTools.length);
|
||||
assertOpenFiles(runningTools[0], sampleDf, xDf);
|
||||
|
||||
exitTools(runningTools);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOpenMultipleNewTool() throws Exception {
|
||||
|
||||
Program p = ToyProgramBuilder.buildSimpleProgram("y", this);
|
||||
rootFolder.createFile("Y", p, TaskMonitor.DUMMY);
|
||||
p.release(this);
|
||||
|
||||
ToolOptions options = frontEndTool.getOptions(ToolConstants.TOOL_OPTIONS);
|
||||
options.setEnum(FrontEndTool.DEFAULT_TOOL_LAUNCH_MODE, DefaultLaunchMode.NEW_TOOL);
|
||||
|
||||
//Open 1st File
|
||||
DomainFile sampleDf = openInDefaultTool("sample");
|
||||
PluginTool[] runningTools = env.getProject().getToolManager().getRunningTools();
|
||||
assertEquals(1, runningTools.length);
|
||||
assertOpenFiles(runningTools[0], sampleDf);
|
||||
|
||||
String toolName = runningTools[0].getName();
|
||||
|
||||
//Open two additional files in new tool
|
||||
openInTool(toolName, "X", "Y");
|
||||
|
||||
// NOTE: runningTools order may vary
|
||||
runningTools = env.getProject().getToolManager().getRunningTools();
|
||||
assertEquals(2, runningTools.length);
|
||||
|
||||
DomainFile[] domainFiles0 = runningTools[0].getDomainFiles();
|
||||
DomainFile[] domainFiles1 = runningTools[1].getDomainFiles();
|
||||
assertEquals(3, domainFiles0.length + domainFiles1.length);
|
||||
|
||||
exitTools(runningTools);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOpenMultipleReuseTool() throws Exception {
|
||||
|
||||
Program p = ToyProgramBuilder.buildSimpleProgram("y", this);
|
||||
rootFolder.createFile("Y", p, TaskMonitor.DUMMY);
|
||||
p.release(this);
|
||||
|
||||
ToolOptions options = frontEndTool.getOptions(ToolConstants.TOOL_OPTIONS);
|
||||
options.setEnum(FrontEndTool.DEFAULT_TOOL_LAUNCH_MODE, DefaultLaunchMode.REUSE_TOOL);
|
||||
|
||||
//Open 1st File
|
||||
DomainFile sampleDf = openInDefaultTool("sample");
|
||||
PluginTool[] runningTools = env.getProject().getToolManager().getRunningTools();
|
||||
assertEquals(1, runningTools.length);
|
||||
assertOpenFiles(runningTools[0], sampleDf);
|
||||
|
||||
String toolName = runningTools[0].getName();
|
||||
|
||||
//Open two additional files in same tool
|
||||
openInTool(toolName, "X", "Y");
|
||||
|
||||
runningTools = env.getProject().getToolManager().getRunningTools();
|
||||
assertEquals(1, runningTools.length);
|
||||
|
||||
DomainFile[] domainFiles0 = runningTools[0].getDomainFiles();
|
||||
assertEquals(3, domainFiles0.length);
|
||||
|
||||
exitTools(runningTools);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOpenWith() throws Exception {
|
||||
|
||||
GTreeNode npNode = rootNode.getChild("sample");
|
||||
setSelectionPath(npNode.getTreePath());
|
||||
waitForTree();
|
||||
|
||||
ToolChest tc = env.getProject().getLocalToolChest();
|
||||
ToolTemplate[] configs = tc.getToolTemplates();
|
||||
|
||||
DockingActionIf action = getAction("Open" + configs[0].getName());
|
||||
performAction(action, getFrontEndContext(), true);
|
||||
verifyToolExistsAndCloseTool();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOpenWithDoubleClick() throws Exception {
|
||||
// make sure that the Code Browser tool is the default
|
||||
ToolChest tc = env.getProject().getLocalToolChest();
|
||||
ToolTemplate[] configs = tc.getToolTemplates();
|
||||
ToolTemplate codeBrowserConfig = null;
|
||||
for (ToolTemplate config : configs) {
|
||||
if ("CodeBrowser".equals(config.getName())) {
|
||||
codeBrowserConfig = config;
|
||||
}
|
||||
}
|
||||
|
||||
if (codeBrowserConfig == null) {
|
||||
Assert.fail("Unable to find the Code Browser config file.");
|
||||
}
|
||||
|
||||
// double click on the program node
|
||||
GTreeNode npNode = rootNode.getChild("sample");
|
||||
JTree jTree = (JTree) invokeInstanceMethod("getJTree", tree);
|
||||
Rectangle rect = jTree.getPathBounds(npNode.getTreePath());
|
||||
setSelectionPath(npNode.getTreePath());
|
||||
waitForTree();
|
||||
|
||||
clickMouse(jTree, MouseEvent.BUTTON1, rect.x, rect.y, 2, 0);
|
||||
|
||||
// make sure that the tool is loaded and processes all of the tasks it launches
|
||||
Window window = waitForToolLaunch();
|
||||
|
||||
// DEBUG:
|
||||
if (window == null) {
|
||||
// see if any tools have been launched
|
||||
PluginTool[] runningTools = frontEndTool.getToolServices().getRunningTools();
|
||||
for (PluginTool tool : runningTools) {
|
||||
System.err.println("\t\"" + tool.getName() + "\"");
|
||||
JFrame toolFrame = tool.getToolFrame();
|
||||
System.err.println("\t\twith window: " + toolFrame.getTitle());
|
||||
}
|
||||
|
||||
System.err.println("Open Windows: ");
|
||||
System.err.println(getOpenWindowsAsString());
|
||||
}
|
||||
|
||||
assertNotNull(window);
|
||||
waitForBusyTool(env.getProject().getToolManager().getRunningTools()[0]);
|
||||
waitForTasks();
|
||||
|
||||
verifyToolExistsAndCloseTool();
|
||||
}
|
||||
|
||||
private Window waitForToolLaunch() {
|
||||
|
||||
waitForSwing();
|
||||
|
||||
long start = System.currentTimeMillis();
|
||||
int tryCount = 0;
|
||||
Window window = null;
|
||||
while (window == null && tryCount < 5) {
|
||||
++tryCount;
|
||||
window = waitForValueWithoutFailing(() -> {
|
||||
return getWindowByTitleContaining(null,
|
||||
"CodeBrowser: " + PROJECT_NAME + ":/sample");
|
||||
});
|
||||
}
|
||||
|
||||
long total = System.currentTimeMillis() - start;
|
||||
assertNotNull("Timed-out waiting for tool - " + total + " ms", window);
|
||||
return window;
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
// Private Methods
|
||||
//==================================================================================================
|
||||
|
||||
private ActionContext getFrontEndContext() {
|
||||
ComponentProvider provider = env.getFrontEndProvider();
|
||||
return runSwing(() -> provider.getActionContext(null));
|
||||
}
|
||||
|
||||
private DomainFile openInDefaultTool(String fileName) throws Exception {
|
||||
GTreeNode node = rootNode.getChild(fileName);
|
||||
assertTrue("Expected domain file node", node instanceof DomainFileNode);
|
||||
DomainFileNode fileNode = (DomainFileNode) node;
|
||||
DomainFile domainFile = fileNode.getDomainFile();
|
||||
setSelectionPath(fileNode.getTreePath());
|
||||
waitForTree();
|
||||
DockingActionIf openAction = getAction("Open File");
|
||||
performAction(openAction, getFrontEndContext(), true);
|
||||
return domainFile;
|
||||
}
|
||||
|
||||
private List<DomainFile> openInTool(String toolName, String... fileNames) throws Exception {
|
||||
|
||||
ToolServices toolServices = env.getProject().getToolServices();
|
||||
|
||||
ArrayList<DomainFile> domainFiles = new ArrayList<>();
|
||||
for (String fileName : fileNames) {
|
||||
DomainFile df = rootFolder.getFile(fileName);
|
||||
assertNotNull(df);
|
||||
domainFiles.add(df);
|
||||
}
|
||||
|
||||
runSwing(() -> toolServices.launchTool(toolName, domainFiles));
|
||||
waitForSwing();
|
||||
return domainFiles;
|
||||
}
|
||||
|
||||
private void assertOpenFiles(PluginTool tool, DomainFile... expectedDomainFiles) {
|
||||
DomainFile[] domainFiles = tool.getDomainFiles();
|
||||
assertArrayEquals(expectedDomainFiles, domainFiles);
|
||||
}
|
||||
|
||||
private void exitTools(PluginTool... tools) {
|
||||
runSwing(() -> {
|
||||
for (PluginTool t : tools) {
|
||||
t.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void verifyToolExistsAndCloseTool() {
|
||||
PluginTool[] runningTools = env.getProject().getToolManager().getRunningTools();
|
||||
assertEquals(1, runningTools.length);
|
||||
exitTools(runningTools[0]);
|
||||
}
|
||||
|
||||
private ActionContext getDomainFileActionContext(GTreeNode... nodes) {
|
||||
List<DomainFile> fileList = new ArrayList<>();
|
||||
List<DomainFolder> folderList = new ArrayList<>();
|
||||
for (GTreeNode node : nodes) {
|
||||
if (node instanceof DomainFileNode fileNode) {
|
||||
fileList.add(fileNode.getDomainFile());
|
||||
}
|
||||
else if (node instanceof DomainFolderNode folderNode) {
|
||||
folderList.add(folderNode.getDomainFolder());
|
||||
}
|
||||
}
|
||||
|
||||
return new ProjectDataContext(null, null, nodes[0], folderList, fileList, tree, true);
|
||||
|
||||
}
|
||||
|
||||
private DockingActionIf getAction(String actionName) {
|
||||
DockingActionIf action =
|
||||
AbstractDockingTest.getAction(frontEndTool, "FrontEndPlugin", actionName);
|
||||
return action;
|
||||
}
|
||||
|
||||
private void setSelectionPath(final TreePath path) throws Exception {
|
||||
SwingUtilities.invokeAndWait(() -> tree.setSelectionPath(path));
|
||||
}
|
||||
|
||||
private void waitForTree() {
|
||||
waitForSwing();
|
||||
while (tree.isBusy()) {
|
||||
try {
|
||||
Thread.sleep(10);
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
// don't care
|
||||
}
|
||||
}
|
||||
waitForSwing();
|
||||
}
|
||||
}
|
||||
+674
File diff suppressed because it is too large
Load Diff
+400
@@ -0,0 +1,400 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* 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 ghidra.framework.main.datatree;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.junit.*;
|
||||
|
||||
import docking.ActionContext;
|
||||
import docking.action.DockingActionIf;
|
||||
import ghidra.framework.client.ClientUtil;
|
||||
import ghidra.framework.data.LinkHandler;
|
||||
import ghidra.framework.data.LinkHandler.LinkStatus;
|
||||
import ghidra.framework.model.*;
|
||||
import ghidra.program.database.ProgramDB;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.server.remote.ServerTestUtil;
|
||||
import ghidra.test.*;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class ProjectCopyPasteTest extends AbstractGhidraHeadedIntegrationTest {
|
||||
|
||||
private FrontEndTestEnv env;
|
||||
|
||||
private DomainFolder abcFolder;
|
||||
private DomainFile programFile;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
|
||||
env = new FrontEndTestEnv();
|
||||
|
||||
/**
|
||||
/abc (folder)
|
||||
foo (program file)
|
||||
/xyz (empty folder)
|
||||
**/
|
||||
|
||||
DomainFolder rootFolder = env.getRootFolder();
|
||||
|
||||
abcFolder = rootFolder.createFolder("abc");
|
||||
rootFolder.createFolder("xyz");
|
||||
|
||||
Program p = ToyProgramBuilder.buildSimpleProgram("foo", this);
|
||||
programFile = abcFolder.createFile("foo", p, TaskMonitor.DUMMY);
|
||||
p.release(this);
|
||||
|
||||
env.waitForTree();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
|
||||
env.dispose();
|
||||
|
||||
ClientUtil.clearRepositoryAdapter("localhost", ServerTestUtil.GHIDRA_TEST_SERVER_PORT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCopyPasteFile() throws Exception {
|
||||
|
||||
// Select /abc/foo file and Copy
|
||||
|
||||
DomainFileNode fooFile = env.waitForFileNode("/abc/foo");
|
||||
|
||||
final ActionContext copyActionContext = env.getDomainFileActionContext(fooFile);
|
||||
|
||||
DockingActionIf copyAction = getAction(env.getFrontEndTool(), "Copy");
|
||||
assertNotNull("Copy action not found", copyAction);
|
||||
|
||||
assertTrue(copyAction.isAddToPopup(copyActionContext));
|
||||
assertTrue(copyAction.isEnabledForContext(copyActionContext));
|
||||
runSwing(() -> copyAction.actionPerformed(copyActionContext));
|
||||
|
||||
// Select /xyz folder and perform Paste
|
||||
|
||||
DomainFolderNode xyzNode = env.waitForFolderNode("/xyz");
|
||||
|
||||
final ActionContext pasteActionContext = env.getDomainFileActionContext(xyzNode);
|
||||
|
||||
DockingActionIf pasteAction = getAction(env.getFrontEndTool(), "Paste");
|
||||
assertNotNull("Paste action not found", pasteAction);
|
||||
|
||||
assertTrue(pasteAction.isAddToPopup(pasteActionContext));
|
||||
assertTrue(pasteAction.isEnabledForContext(pasteActionContext));
|
||||
runSwing(() -> pasteAction.actionPerformed(pasteActionContext));
|
||||
|
||||
DomainFileNode fooCopyNode = env.waitForFileNode("/xyz/foo");
|
||||
DomainFile file = fooCopyNode.getDomainFile();
|
||||
assertTrue(file.exists());
|
||||
assertFalse(file.isLink());
|
||||
|
||||
assertEquals(LinkStatus.NON_LINK, LinkHandler.getLinkFileStatus(file, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCopyPastInternalAbsoluteFileLink() throws Exception {
|
||||
testCopyPastInternalFileLink("Paste Link");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCopyPastInternalRelativeFileLink() throws Exception {
|
||||
testCopyPastInternalFileLink("Paste Relative-Link");
|
||||
}
|
||||
|
||||
private void testCopyPastInternalFileLink(String pastActionName) throws Exception {
|
||||
|
||||
/**
|
||||
/abc
|
||||
foo (copied)
|
||||
/xyz (pasted into)
|
||||
foo -> (direct link)
|
||||
foo.1 -> (link to direct link)
|
||||
**/
|
||||
|
||||
boolean isRelative = pastActionName.contains("Relative");
|
||||
|
||||
DockingActionIf copyAction = getAction(env.getFrontEndTool(), "Copy");
|
||||
assertNotNull("Copy action not found", copyAction);
|
||||
|
||||
// Select /abc/foo file and perform Copy
|
||||
|
||||
DomainFileNode fooNode = env.waitForFileNode("/abc/foo");
|
||||
final ActionContext copyActionContext = env.getDomainFileActionContext(fooNode);
|
||||
|
||||
assertTrue(copyAction.isAddToPopup(copyActionContext));
|
||||
assertTrue(copyAction.isEnabledForContext(copyActionContext));
|
||||
runSwing(() -> copyAction.actionPerformed(copyActionContext));
|
||||
|
||||
DockingActionIf pasteLinkAction = getAction(env.getFrontEndTool(), pastActionName);
|
||||
assertNotNull(pastActionName + " action not found", pasteLinkAction);
|
||||
|
||||
// Select /xyz folder and perform Paste as Link
|
||||
|
||||
DomainFolderNode xyzNode = env.waitForFolderNode("/xyz");
|
||||
final ActionContext pasteLinkActionContext = env.getDomainFileActionContext(xyzNode);
|
||||
|
||||
assertTrue(pasteLinkAction.isAddToPopup(pasteLinkActionContext));
|
||||
assertTrue(pasteLinkAction.isEnabledForContext(pasteLinkActionContext));
|
||||
runSwing(() -> pasteLinkAction.actionPerformed(pasteLinkActionContext));
|
||||
|
||||
DomainFileNode fooLinkNode = env.waitForFileNode("/xyz/foo");
|
||||
DomainFile file = fooLinkNode.getDomainFile();
|
||||
assertTrue(file.exists());
|
||||
assertTrue(file.isLink());
|
||||
LinkFileInfo linkInfo = file.getLinkInfo();
|
||||
assertFalse(linkInfo.isFolderLink());
|
||||
assertFalse(linkInfo.isExternalLink());
|
||||
assertEquals(isRelative ? "../abc/foo" : "/abc/foo", linkInfo.getLinkPath());
|
||||
assertNull(linkInfo.getLinkedFolder());
|
||||
|
||||
ProjectData projectData = env.getFrontEndTool().getProject().getProjectData();
|
||||
|
||||
DomainFile fooLinkFile = projectData.getFile("/xyz/foo");
|
||||
assertNotNull(fooLinkFile);
|
||||
assertTrue(fooLinkFile.exists());
|
||||
|
||||
assertEquals(LinkStatus.INTERNAL, LinkHandler.getLinkFileStatus(fooLinkFile, null));
|
||||
|
||||
DomainObject dobj = fooLinkFile.getDomainObject(this, false, false, TaskMonitor.DUMMY);
|
||||
try {
|
||||
assertTrue(dobj instanceof ProgramDB);
|
||||
assertTrue(dobj.canSave());
|
||||
assertTrue(dobj.isChangeable());
|
||||
assertEquals(programFile, dobj.getDomainFile());
|
||||
}
|
||||
finally {
|
||||
if (dobj != null) {
|
||||
dobj.release(this);
|
||||
}
|
||||
}
|
||||
|
||||
// Select /xyz/foo file and perform Copy
|
||||
|
||||
final ActionContext copy2ActionContext = env.getDomainFileActionContext(fooLinkNode);
|
||||
|
||||
assertTrue(copyAction.isAddToPopup(copy2ActionContext));
|
||||
assertTrue(copyAction.isEnabledForContext(copy2ActionContext));
|
||||
runSwing(() -> copyAction.actionPerformed(copy2ActionContext));
|
||||
|
||||
// Select /xyz folder and perform Paste as Link
|
||||
|
||||
assertTrue(pasteLinkAction.isAddToPopup(pasteLinkActionContext));
|
||||
assertTrue(pasteLinkAction.isEnabledForContext(pasteLinkActionContext));
|
||||
runSwing(() -> pasteLinkAction.actionPerformed(pasteLinkActionContext));
|
||||
|
||||
fooLinkNode = env.waitForFileNode("/xyz/foo.1");
|
||||
file = fooLinkNode.getDomainFile();
|
||||
assertTrue(file.exists());
|
||||
assertTrue(file.isLink());
|
||||
linkInfo = file.getLinkInfo();
|
||||
assertFalse(linkInfo.isFolderLink());
|
||||
assertFalse(linkInfo.isExternalLink());
|
||||
assertEquals(isRelative ? "foo" : "/xyz/foo", linkInfo.getLinkPath());
|
||||
assertNull(linkInfo.getLinkedFolder());
|
||||
|
||||
fooLinkFile = projectData.getFile("/xyz/foo.1");
|
||||
assertNotNull(fooLinkFile);
|
||||
assertTrue(fooLinkFile.exists());
|
||||
|
||||
assertEquals(LinkStatus.INTERNAL, LinkHandler.getLinkFileStatus(fooLinkFile, null));
|
||||
|
||||
dobj = fooLinkFile.getDomainObject(this, false, false, TaskMonitor.DUMMY);
|
||||
try {
|
||||
assertTrue(dobj instanceof ProgramDB);
|
||||
assertTrue(dobj.canSave());
|
||||
assertTrue(dobj.isChangeable());
|
||||
assertEquals(programFile, dobj.getDomainFile());
|
||||
}
|
||||
finally {
|
||||
if (dobj != null) {
|
||||
dobj.release(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCopyPastFolder() throws Exception {
|
||||
|
||||
// Select /abc file from viewed project and Copy
|
||||
|
||||
DomainFolderNode abcFolderNode = env.waitForFolderNode("/abc");
|
||||
|
||||
final ActionContext copyActionContext = env.getDomainFileActionContext(abcFolderNode);
|
||||
|
||||
DockingActionIf copyAction = getAction(env.getFrontEndTool(), "Copy");
|
||||
assertNotNull("Copy action not found", copyAction);
|
||||
|
||||
assertTrue(copyAction.isAddToPopup(copyActionContext));
|
||||
assertTrue(copyAction.isEnabledForContext(copyActionContext));
|
||||
runSwing(() -> copyAction.actionPerformed(copyActionContext));
|
||||
|
||||
// Select /xyz folder and perform Paste
|
||||
|
||||
DomainFolderNode xyzNode = env.waitForFolderNode("/xyz");
|
||||
|
||||
final ActionContext pasteActionContext = env.getDomainFileActionContext(xyzNode);
|
||||
|
||||
DockingActionIf pasteAction = getAction(env.getFrontEndTool(), "Paste");
|
||||
assertNotNull("Paste action not found", pasteAction);
|
||||
|
||||
assertTrue(pasteAction.isAddToPopup(pasteActionContext));
|
||||
assertTrue(pasteAction.isEnabledForContext(pasteActionContext));
|
||||
runSwing(() -> pasteAction.actionPerformed(pasteActionContext));
|
||||
|
||||
DomainFolderNode abcCopyNode = env.waitForFolderNode("/xyz/abc");
|
||||
DomainFolder folder = abcCopyNode.getDomainFolder();
|
||||
assertTrue(!folder.isEmpty());
|
||||
|
||||
DomainFile file = folder.getFile("foo");
|
||||
assertNotNull(file);
|
||||
assertTrue(file.exists());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCopyPastInternalAbsoluteFolderLink() throws Exception {
|
||||
testCopyPastInternalFolderLink("Paste Link");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCopyPastInternalRelativeFolderLink() throws Exception {
|
||||
testCopyPastInternalFolderLink("Paste Relative-Link");
|
||||
}
|
||||
|
||||
private void testCopyPastInternalFolderLink(String pastActionName) throws Exception {
|
||||
|
||||
/**
|
||||
/abc (copied)
|
||||
foo
|
||||
/xyz (pasted into)
|
||||
abc -> (direct link)
|
||||
abc.1 -> (link to direct link)
|
||||
**/
|
||||
|
||||
boolean isRelative = pastActionName.contains("Relative");
|
||||
|
||||
DockingActionIf copyAction = getAction(env.getFrontEndTool(), "Copy");
|
||||
assertNotNull("Copy action not found", copyAction);
|
||||
|
||||
// Select /abc folder and perform Copy
|
||||
|
||||
DomainFolderNode abcNode = env.waitForFolderNode("/abc");
|
||||
final ActionContext copyActionContext = env.getDomainFileActionContext(abcNode);
|
||||
|
||||
assertTrue(copyAction.isAddToPopup(copyActionContext));
|
||||
assertTrue(copyAction.isEnabledForContext(copyActionContext));
|
||||
runSwing(() -> copyAction.actionPerformed(copyActionContext));
|
||||
|
||||
// Select /xyz folder and perform Paste as Link /xyz/abc
|
||||
|
||||
DockingActionIf pasteLinkAction = getAction(env.getFrontEndTool(), pastActionName);
|
||||
assertNotNull(pastActionName + " action not found", pasteLinkAction);
|
||||
|
||||
DomainFolderNode xyzNode = env.waitForFolderNode("/xyz");
|
||||
final ActionContext pasteLinkActionContext = env.getDomainFileActionContext(xyzNode);
|
||||
|
||||
assertTrue(pasteLinkAction.isAddToPopup(pasteLinkActionContext));
|
||||
assertTrue(pasteLinkAction.isEnabledForContext(pasteLinkActionContext));
|
||||
runSwing(() -> pasteLinkAction.actionPerformed(pasteLinkActionContext));
|
||||
|
||||
final DomainFileNode xyzAbcLinkNode = env.waitForFileNode("/xyz/abc");
|
||||
final DomainFile xyzAbcLinkFile = xyzAbcLinkNode.getDomainFile();
|
||||
assertTrue(xyzAbcLinkFile.exists());
|
||||
assertTrue(xyzAbcLinkFile.isLink());
|
||||
LinkFileInfo xyzAbcLinkInfo = xyzAbcLinkFile.getLinkInfo();
|
||||
assertTrue(xyzAbcLinkInfo.isFolderLink());
|
||||
assertFalse(xyzAbcLinkInfo.isExternalLink());
|
||||
assertEquals(isRelative ? "../abc" : "/abc", xyzAbcLinkInfo.getLinkPath());
|
||||
|
||||
assertEquals(LinkStatus.INTERNAL, LinkHandler.getLinkFileStatus(xyzAbcLinkFile, null));
|
||||
|
||||
final LinkedDomainFolder xyzAbcLinkedFolder = xyzAbcLinkInfo.getLinkedFolder();
|
||||
assertNotNull(xyzAbcLinkedFolder);
|
||||
assertTrue(xyzAbcLinkedFolder.isLinked());
|
||||
assertEquals(abcFolder, xyzAbcLinkedFolder.getRealFolder());
|
||||
|
||||
ProjectData projectData = env.getFrontEndTool().getProject().getProjectData();
|
||||
|
||||
DomainFile fooFile = projectData.getFile("/xyz/abc/foo");
|
||||
assertNotNull(fooFile);
|
||||
assertTrue(fooFile.exists());
|
||||
|
||||
DomainObject dobj = fooFile.getDomainObject(this, false, false, TaskMonitor.DUMMY);
|
||||
try {
|
||||
assertTrue(dobj instanceof ProgramDB);
|
||||
assertTrue(dobj.canSave());
|
||||
assertTrue(dobj.isChangeable());
|
||||
assertEquals(programFile, dobj.getDomainFile());
|
||||
}
|
||||
finally {
|
||||
if (dobj != null) {
|
||||
dobj.release(this);
|
||||
}
|
||||
}
|
||||
|
||||
// Select /xyz/abc linked-folder and perform Copy
|
||||
|
||||
final ActionContext copy2ActionContext = env.getDomainFileActionContext(xyzAbcLinkNode);
|
||||
|
||||
assertTrue(copyAction.isAddToPopup(copy2ActionContext));
|
||||
assertTrue(copyAction.isEnabledForContext(copy2ActionContext));
|
||||
runSwing(() -> copyAction.actionPerformed(copy2ActionContext));
|
||||
|
||||
// Select /xyz and perform Paste as Link /xyz/abc.1
|
||||
|
||||
assertTrue(pasteLinkAction.isAddToPopup(pasteLinkActionContext));
|
||||
assertTrue(pasteLinkAction.isEnabledForContext(pasteLinkActionContext));
|
||||
runSwing(() -> pasteLinkAction.actionPerformed(pasteLinkActionContext));
|
||||
|
||||
final DomainFileNode xyzAbc1CopyNode = env.waitForFileNode("/xyz/abc.1");
|
||||
DomainFile xyzAbc1LinkFile = xyzAbc1CopyNode.getDomainFile();
|
||||
assertTrue(xyzAbc1LinkFile.exists());
|
||||
assertTrue(xyzAbc1LinkFile.isLink());
|
||||
LinkFileInfo xyzAbc1LinkInfo = xyzAbc1LinkFile.getLinkInfo();
|
||||
assertTrue(xyzAbc1LinkInfo.isFolderLink());
|
||||
assertFalse(xyzAbc1LinkInfo.isExternalLink());
|
||||
assertEquals(isRelative ? "abc" : "/xyz/abc", xyzAbc1LinkInfo.getLinkPath());
|
||||
|
||||
assertEquals(LinkStatus.INTERNAL, LinkHandler.getLinkFileStatus(xyzAbc1LinkFile, null));
|
||||
|
||||
final LinkedDomainFolder xyzAbc1LinkedFolder = xyzAbc1LinkInfo.getLinkedFolder();
|
||||
assertNotNull(xyzAbc1LinkedFolder);
|
||||
assertTrue(xyzAbc1LinkedFolder.isLinked());
|
||||
|
||||
assertEquals(xyzAbcLinkedFolder.getRealFolder(), xyzAbc1LinkedFolder.getRealFolder());
|
||||
|
||||
fooFile = projectData.getFile("/xyz/abc.1/foo");
|
||||
assertNotNull(fooFile);
|
||||
assertTrue(fooFile.exists());
|
||||
|
||||
dobj = fooFile.getDomainObject(this, false, false, TaskMonitor.DUMMY);
|
||||
try {
|
||||
assertTrue(dobj instanceof ProgramDB);
|
||||
assertTrue(dobj.canSave());
|
||||
assertTrue(dobj.isChangeable());
|
||||
assertEquals(programFile, dobj.getDomainFile());
|
||||
}
|
||||
finally {
|
||||
if (dobj != null) {
|
||||
dobj.release(this);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+439
@@ -0,0 +1,439 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* 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 ghidra.framework.main.datatree;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.function.BooleanSupplier;
|
||||
|
||||
import org.junit.*;
|
||||
|
||||
import ghidra.framework.client.ClientUtil;
|
||||
import ghidra.framework.data.FolderLinkContentHandler;
|
||||
import ghidra.framework.data.LinkHandler;
|
||||
import ghidra.framework.data.LinkHandler.LinkStatus;
|
||||
import ghidra.framework.model.DomainFile;
|
||||
import ghidra.framework.model.DomainFolder;
|
||||
import ghidra.program.database.DataTypeArchiveDB;
|
||||
import ghidra.program.database.ProgramLinkContentHandler;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.server.remote.ServerTestUtil;
|
||||
import ghidra.test.*;
|
||||
import ghidra.util.exception.DuplicateFileException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class ProjectLinkFileStatusTest extends AbstractGhidraHeadedIntegrationTest {
|
||||
|
||||
private FrontEndTestEnv env;
|
||||
|
||||
private DomainFolder abcFolder;
|
||||
private DomainFolder xyzFolder;
|
||||
private DomainFile programFile;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
|
||||
env = new FrontEndTestEnv();
|
||||
|
||||
/**
|
||||
/abc/ (folder)
|
||||
abc -> /xyz/abc (circular)
|
||||
foo (program file)
|
||||
/xyz/
|
||||
abc -> /abc (folder link)
|
||||
abc -> (circular)
|
||||
foo
|
||||
foo -> /abc/foo (program link)
|
||||
**/
|
||||
|
||||
DomainFolder rootFolder = env.getRootFolder();
|
||||
|
||||
abcFolder = rootFolder.createFolder("abc");
|
||||
xyzFolder = rootFolder.createFolder("xyz");
|
||||
DomainFile abcLinkFile = abcFolder.copyToAsLink(xyzFolder, false);
|
||||
abcLinkFile.copyToAsLink(abcFolder, false);
|
||||
|
||||
Program p = ToyProgramBuilder.buildSimpleProgram("foo", this);
|
||||
programFile = abcFolder.createFile("foo", p, TaskMonitor.DUMMY);
|
||||
p.release(this);
|
||||
|
||||
programFile.copyToAsLink(xyzFolder, false);
|
||||
|
||||
env.waitForTree();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
if (env != null) {
|
||||
env.dispose();
|
||||
}
|
||||
ClientUtil.clearRepositoryAdapter("localhost", ServerTestUtil.GHIDRA_TEST_SERVER_PORT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNonFileLink() throws Exception {
|
||||
DomainFileNode fileNode = env.waitForFileNode("/abc/foo");
|
||||
assertEquals(LinkStatus.NON_LINK,
|
||||
LinkHandler.getLinkFileStatus(fileNode.getDomainFile(), null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExternalFileLink() throws Exception {
|
||||
|
||||
//
|
||||
// Create external program file-link /abc/A to remote repository
|
||||
//
|
||||
DomainFile linkFile = abcFolder.createLinkFile("ghidra://localhost/Test/A", "A",
|
||||
ProgramLinkContentHandler.INSTANCE);
|
||||
|
||||
env.waitForTree(); // give time for ChangeManager to update
|
||||
|
||||
//
|
||||
// Verify /abc/A external program file-link exists with correct status and display name
|
||||
//
|
||||
DomainFileNode nodeA = waitForFileNode("/abc/A");
|
||||
assertFalse(nodeA.isFolderLink());
|
||||
assertEquals(linkFile, nodeA.getDomainFile());
|
||||
assertEquals(LinkStatus.EXTERNAL, LinkHandler.getLinkFileStatus(linkFile, null));
|
||||
String displayName = runSwing(() -> nodeA.getDisplayText());
|
||||
assertTrue("Unexpected node display name: " + displayName,
|
||||
displayName.contains("localhost[Test]:/A"));
|
||||
|
||||
//
|
||||
// Create external program file-link /abc/B to local project
|
||||
//
|
||||
linkFile = abcFolder.createLinkFile("ghidra:/x/y/Test?/B", "B",
|
||||
ProgramLinkContentHandler.INSTANCE);
|
||||
|
||||
env.waitForTree(); // give time for ChangeManager to update
|
||||
|
||||
//
|
||||
// Verify /abc/B external program file-link exists with correct status and display name
|
||||
//
|
||||
DomainFileNode nodeB = waitForFileNode("/abc/B");
|
||||
assertFalse(nodeB.isFolderLink());
|
||||
assertEquals(linkFile, nodeB.getDomainFile());
|
||||
assertEquals(LinkStatus.EXTERNAL, LinkHandler.getLinkFileStatus(linkFile, null));
|
||||
displayName = runSwing(() -> nodeB.getDisplayText());
|
||||
assertTrue("Unexpected node display name: " + displayName, displayName.contains("Test:/B"));
|
||||
|
||||
//
|
||||
// Remove /abc/foo file
|
||||
//
|
||||
DomainFile fooFile = abcFolder.getFile("foo");
|
||||
assertNotNull(fooFile);
|
||||
fooFile.delete();
|
||||
|
||||
//
|
||||
// Replace deleted file with external program file-link to local project
|
||||
// which sets-up indirect link path from /xyz/foo -> /abc/foo -> local project file
|
||||
//
|
||||
linkFile = abcFolder.createLinkFile("ghidra:/x/y/Test?/foo", "foo",
|
||||
ProgramLinkContentHandler.INSTANCE);
|
||||
|
||||
waitForSwing(); // give a chance for ChangeManager to be notified
|
||||
|
||||
env.waitForTree(); // give time for ChangeManager to update
|
||||
|
||||
//
|
||||
// Verify /abc/foo external program file-link exists with correct status and display name
|
||||
//
|
||||
DomainFileNode fooNode = waitForFileNode("/abc/foo");
|
||||
assertFalse(fooNode.isFolderLink());
|
||||
assertEquals(linkFile, fooNode.getDomainFile());
|
||||
assertEquals(LinkStatus.EXTERNAL, LinkHandler.getLinkFileStatus(linkFile, null));
|
||||
displayName = runSwing(() -> fooNode.getDisplayText());
|
||||
if (!displayName.contains("Test:/foo")) {
|
||||
int junk = 0;
|
||||
}
|
||||
assertTrue("Unexpected node display name: " + displayName,
|
||||
displayName.contains("Test:/foo"));
|
||||
|
||||
//
|
||||
// Check pre-existing file-link /xyz/foo reflects external status
|
||||
//
|
||||
DomainFileNode fooLinkNode = waitForFileNode("/xyz/foo");
|
||||
assertEquals(LinkStatus.EXTERNAL,
|
||||
LinkHandler.getLinkFileStatus(fooLinkNode.getDomainFile(), null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExternalFolderLink() throws Exception {
|
||||
|
||||
// NOTE: Only refer to root repo folder with remote URL to avoid unwanted connection attempt
|
||||
|
||||
//
|
||||
// Create external folder-link /abc/A to remote repository
|
||||
//
|
||||
DomainFile linkFile = abcFolder.createLinkFile("ghidra://localhost/Test/", "A",
|
||||
FolderLinkContentHandler.INSTANCE);
|
||||
|
||||
env.waitForTree(); // give time for ChangeManager to update
|
||||
|
||||
//
|
||||
// Verify /abc/A external folder-link exists with correct status and display name
|
||||
//
|
||||
DomainFileNode nodeA = waitForFileNode("/abc/A");
|
||||
assertTrue(nodeA.isFolderLink());
|
||||
assertEquals(linkFile, nodeA.getDomainFile());
|
||||
assertEquals(LinkStatus.EXTERNAL, LinkHandler.getLinkFileStatus(linkFile, null));
|
||||
String displayName = runSwing(() -> nodeA.getDisplayText());
|
||||
assertTrue("Unexpected node display name: " + displayName,
|
||||
displayName.contains("localhost[Test]:/"));
|
||||
|
||||
//
|
||||
// Create external folder-link /abc/B to local project
|
||||
//
|
||||
linkFile =
|
||||
abcFolder.createLinkFile("ghidra:/x/y/Test?/B", "B", FolderLinkContentHandler.INSTANCE);
|
||||
|
||||
env.waitForTree(); // give time for ChangeManager to update
|
||||
|
||||
//
|
||||
// Verify /abc/B external folder-link exists with correct status and display name
|
||||
//
|
||||
DomainFileNode nodeB = waitForFileNode("/abc/B");
|
||||
assertTrue(nodeB.isFolderLink());
|
||||
assertEquals(linkFile, nodeB.getDomainFile());
|
||||
assertEquals(LinkStatus.EXTERNAL, LinkHandler.getLinkFileStatus(linkFile, null));
|
||||
displayName = runSwing(() -> nodeB.getDisplayText());
|
||||
assertTrue("Unexpected node display name: " + displayName, displayName.contains("Test:/B"));
|
||||
|
||||
//
|
||||
// Remove /abc folder and its children
|
||||
//
|
||||
DomainFolder rootFolder = abcFolder.getParent();
|
||||
abcFolder.getFile("abc").delete();
|
||||
abcFolder.getFile("foo").delete();
|
||||
abcFolder.getFile("A").delete();
|
||||
abcFolder.getFile("B").delete();
|
||||
abcFolder.delete();
|
||||
|
||||
//
|
||||
// Remove /xyz/foo file to avoid remote access attempt to ghidra://localhost/Test/foo
|
||||
// after /abc is replaced in the next step
|
||||
//
|
||||
DomainFile fooFile = xyzFolder.getFile("foo");
|
||||
assertNotNull(fooFile);
|
||||
fooFile.delete();
|
||||
|
||||
//
|
||||
// Replace deleted folder with external folder-link to local project
|
||||
// which sets-up indirect link path from /xyz/abc -> /abc -> local project root folder
|
||||
//
|
||||
linkFile = rootFolder.createLinkFile("ghidra://localhost/Test/", "abc",
|
||||
FolderLinkContentHandler.INSTANCE);
|
||||
|
||||
env.waitForTree(); // give time for ChangeManager to update
|
||||
|
||||
//
|
||||
// Verify /abc external folder-link exists with correct status and display name
|
||||
//
|
||||
DomainFileNode abcLinkNode = waitForFileNode("/abc");
|
||||
assertTrue(abcLinkNode.isFolderLink());
|
||||
assertEquals(linkFile, abcLinkNode.getDomainFile());
|
||||
assertEquals(LinkStatus.EXTERNAL, LinkHandler.getLinkFileStatus(linkFile, null));
|
||||
displayName = runSwing(() -> abcLinkNode.getDisplayText());
|
||||
assertTrue("Unexpected node display name: " + displayName,
|
||||
displayName.contains("localhost[Test]:/"));
|
||||
|
||||
//
|
||||
// Check pre-existing folder-link /xyz/abc reflects external status
|
||||
//
|
||||
DomainFileNode abcLinkNode2 = waitForFileNode("/xyz/abc");
|
||||
assertEquals(LinkStatus.EXTERNAL,
|
||||
LinkHandler.getLinkFileStatus(abcLinkNode2.getDomainFile(), null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBrokenFolderLink() throws Exception {
|
||||
|
||||
//
|
||||
// Verify broken folder-link status for /abc/abc which has circular reference
|
||||
//
|
||||
DomainFileNode abcAbcLinkNode = waitForFileNode("/abc/abc");
|
||||
assertTrue(abcAbcLinkNode.isFolderLink());
|
||||
String displayName = runSwing(() -> abcAbcLinkNode.getDisplayText());
|
||||
assertTrue("Unexpected node display name: " + displayName,
|
||||
displayName.endsWith(" /xyz/abc"));
|
||||
assertEquals(LinkStatus.BROKEN,
|
||||
LinkHandler.getLinkFileStatus(abcAbcLinkNode.getDomainFile(), null));
|
||||
String tooltip = abcAbcLinkNode.getToolTip().replace(" ", " ");
|
||||
assertTrue(tooltip.contains("circular"));
|
||||
|
||||
//
|
||||
// Verify good folder-link internal status for /xyz/abc which has circular reference
|
||||
//
|
||||
DomainFileNode xyzAbcLinkNode = waitForFileNode("/xyz/abc");
|
||||
assertTrue(xyzAbcLinkNode.isFolderLink());
|
||||
displayName = runSwing(() -> xyzAbcLinkNode.getDisplayText());
|
||||
assertTrue("Unexpected node display name: " + displayName, displayName.endsWith(" /abc"));
|
||||
assertEquals(LinkStatus.INTERNAL,
|
||||
LinkHandler.getLinkFileStatus(xyzAbcLinkNode.getDomainFile(), null));
|
||||
|
||||
//
|
||||
// Verify broken folder-link status for /xyz/abc/abc which has circular reference
|
||||
//
|
||||
DomainFileNode abcLinkedNode = waitForFileNode("/xyz/abc/abc");
|
||||
assertTrue(abcLinkedNode.isFolderLink());
|
||||
displayName = runSwing(() -> abcLinkedNode.getDisplayText());
|
||||
assertTrue("Unexpected node display name: " + displayName,
|
||||
displayName.endsWith(" /xyz/abc"));
|
||||
assertEquals(LinkStatus.BROKEN,
|
||||
LinkHandler.getLinkFileStatus(abcLinkedNode.getDomainFile(), null));
|
||||
tooltip = abcLinkedNode.getToolTip().replace(" ", " ");
|
||||
assertTrue(tooltip.contains("circular"));
|
||||
|
||||
//
|
||||
// Rename folder /abc to /ABC causing folder-link /xyz/abc to become broken
|
||||
//
|
||||
abcFolder = abcFolder.setName("ABC");
|
||||
|
||||
env.waitForTree(); // give time for ChangeManager to update
|
||||
|
||||
// Verify /abc node not found
|
||||
assertNull(env.getRootNode().getChild("abc"));
|
||||
|
||||
//
|
||||
// Verify broken folder-link status for /ABC/abc which has circular reference
|
||||
//
|
||||
DomainFileNode ABCAbcLinkNode = waitForFileNode("/ABC/abc");
|
||||
assertTrue(ABCAbcLinkNode.isFolderLink());
|
||||
displayName = runSwing(() -> ABCAbcLinkNode.getDisplayText());
|
||||
assertTrue("Unexpected node display name: " + displayName,
|
||||
displayName.endsWith(" /xyz/abc"));
|
||||
assertEquals(LinkStatus.BROKEN,
|
||||
LinkHandler.getLinkFileStatus(ABCAbcLinkNode.getDomainFile(), null));
|
||||
tooltip = ABCAbcLinkNode.getToolTip().replace(" ", " ");
|
||||
assertTrue(tooltip.contains("folder not found: /abc"));
|
||||
|
||||
env.waitForTree(); // give time for ChangeManager to update
|
||||
|
||||
//
|
||||
// Verify that folder-link /xyz/abc is broken due to missing /abc
|
||||
//
|
||||
DomainFileNode n = waitForFileNode("/xyz/abc"); // wait for refresh
|
||||
assertTrue(n == xyzAbcLinkNode);
|
||||
assertTrue(xyzAbcLinkNode.isFolderLink());
|
||||
assertEquals(LinkStatus.BROKEN,
|
||||
LinkHandler.getLinkFileStatus(xyzAbcLinkNode.getDomainFile(), null));
|
||||
tooltip = xyzAbcLinkNode.getToolTip().replace(" ", " ");
|
||||
assertTrue("Unexpected tooltip: " + tooltip, tooltip.contains("folder not found: /abc"));
|
||||
|
||||
//
|
||||
// Attempt conflicting folder-link creation
|
||||
//
|
||||
DomainFile linkFile = abcFolder.getParent()
|
||||
.createLinkFile("ghidra://localhost/Test/ABC", "ABC",
|
||||
FolderLinkContentHandler.INSTANCE);
|
||||
assertEquals("ABC.1", linkFile.getName()); // link forced to have unqiue name
|
||||
|
||||
//
|
||||
// Try to force folder vs folder-link name conflict
|
||||
// While it won't be allowed it could occur in-the-wild due to shared project content
|
||||
// (case not tested here)
|
||||
//
|
||||
|
||||
try {
|
||||
linkFile.setName("ABC"); // trigger folder name conflict for folder-link
|
||||
fail("Expected DuplicateFileException");
|
||||
}
|
||||
catch (DuplicateFileException e) {
|
||||
// expected for link file
|
||||
}
|
||||
|
||||
try {
|
||||
abcFolder.setName("ABC.1");
|
||||
fail("Expected DuplicateFileException");
|
||||
}
|
||||
catch (DuplicateFileException e) {
|
||||
// expected for link file
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBrokenFileLink() throws Exception {
|
||||
|
||||
//
|
||||
// Verify good internal file-link status for /xyz/foo -> /abc/foo
|
||||
//
|
||||
DomainFileNode linkNode = waitForFileNode("/xyz/foo");
|
||||
assertEquals(LinkStatus.INTERNAL,
|
||||
LinkHandler.getLinkFileStatus(linkNode.getDomainFile(), null));
|
||||
|
||||
//
|
||||
// Copy program file /abc/foo as relative file-link /abc/foo.1 and verify good internal file-link status
|
||||
//
|
||||
DomainFile relativeProgramLink = programFile.copyToAsLink(abcFolder, true);
|
||||
assertEquals("/abc/foo.1", relativeProgramLink.getPathname());
|
||||
assertEquals(LinkStatus.INTERNAL, LinkHandler.getLinkFileStatus(relativeProgramLink, null));
|
||||
|
||||
//
|
||||
// Delete program file /abc/foo and verify that file-link /abc/foo.1 becomes broken
|
||||
//
|
||||
programFile.delete();
|
||||
assertEquals(LinkStatus.BROKEN, LinkHandler.getLinkFileStatus(relativeProgramLink, null));
|
||||
|
||||
env.waitForTree(); // give time for ChangeManager to update
|
||||
|
||||
//
|
||||
// Verify broken /xyz/foo file link status due to deleted file /abc/foo
|
||||
//
|
||||
linkNode = waitForFileNode("/xyz/foo");
|
||||
assertEquals(LinkStatus.BROKEN,
|
||||
LinkHandler.getLinkFileStatus(linkNode.getDomainFile(), null));
|
||||
String tooltip = linkNode.getToolTip().replace(" ", " ");
|
||||
assertTrue(tooltip.contains("file not found: /abc/foo"));
|
||||
|
||||
//
|
||||
// Create DataTypeArchive project file /abc/foo
|
||||
//
|
||||
DataTypeArchiveDB dtm = new DataTypeArchiveDB(abcFolder, "foo", this);
|
||||
dtm.save(null, TaskMonitor.DUMMY);
|
||||
dtm.release(this);
|
||||
|
||||
env.waitForTree(); // give time for ChangeManager to update
|
||||
|
||||
//
|
||||
// Verify that Program file-link is now broken due to incompatible content for /abc/foo
|
||||
//
|
||||
linkNode = waitForFileNode("/xyz/foo");
|
||||
assertEquals(LinkStatus.BROKEN,
|
||||
LinkHandler.getLinkFileStatus(linkNode.getDomainFile(), null));
|
||||
env.waitForSwing();
|
||||
tooltip = linkNode.getToolTip().replace(" ", " ");
|
||||
assertTrue("Unexpected tooltip: " + tooltip,
|
||||
tooltip.contains("incompatible content-type: /abc/foo"));
|
||||
|
||||
}
|
||||
|
||||
private DomainFileNode waitForFileNode(String path) {
|
||||
DomainFileNode fileNode = env.waitForFileNode(path);
|
||||
waitForRefresh(fileNode);
|
||||
return fileNode;
|
||||
}
|
||||
|
||||
private void waitForRefresh(DomainFileNode fileNode) {
|
||||
waitFor(new BooleanSupplier() {
|
||||
@Override
|
||||
public boolean getAsBoolean() {
|
||||
return !fileNode.hasPendingRefresh();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -4,9 +4,9 @@
|
||||
* 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.
|
||||
@@ -130,11 +130,11 @@ public class DataTreeHelper {
|
||||
for (int i = 0; i < nodes.length; i++) {
|
||||
GTreeNode node = nodes[i];
|
||||
treePaths[i] = node.getTreePath();
|
||||
if (node instanceof DomainFileNode) {
|
||||
fileList.add(((DomainFileNode) node).getDomainFile());
|
||||
if (node instanceof DomainFileNode fileNode) {
|
||||
fileList.add(fileNode.getDomainFile());
|
||||
}
|
||||
else if (node instanceof DomainFolderNode) {
|
||||
folderList.add(((DomainFolderNode) node).getDomainFolder());
|
||||
else if (node instanceof DomainFolderNode folderNode) {
|
||||
folderList.add(folderNode.getDomainFolder());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user