diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleStatusComponentProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleStatusComponentProvider.java index 9dc775fadd..df46dca2b2 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleStatusComponentProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleStatusComponentProvider.java @@ -248,7 +248,7 @@ public class BundleStatusComponentProvider extends ComponentProviderAdapter { private void showAddBundlesFileChooser() { if (fileChooser == null) { - fileChooser = new GhidraFileChooser(panel); + fileChooser = new GhidraFileChooser(getComponent()); fileChooser.setMultiSelectionEnabled(true); fileChooser.setFileSelectionMode(GhidraFileChooserMode.FILES_AND_DIRECTORIES); fileChooser.setTitle("Select Bundle(s)"); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/BundleStatusManagerTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/BundleStatusManagerTest.java index c4d884ca05..ec5174a228 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/BundleStatusManagerTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/BundleStatusManagerTest.java @@ -18,22 +18,25 @@ package ghidra.app.plugin.core.script; import static org.junit.Assert.*; import java.io.File; -import java.util.Set; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; import org.junit.*; import docking.ComponentProvider; import docking.action.DockingActionIf; +import docking.widgets.filechooser.GhidraFileChooser; import docking.widgets.table.GTable; import generic.jar.ResourceFile; -import generic.util.Path; import ghidra.app.plugin.core.osgi.*; public class BundleStatusManagerTest extends AbstractGhidraScriptMgrPluginTest { - protected static String BUNDLE_PATH ="$GHIDRA_HOME/Features/Base/ghidra_scripts"; + protected static String BUNDLE_PATH = "$GHIDRA_HOME/Features/Base/ghidra_scripts"; protected static String SCRIPT_NAME = "HelloWorldScript.java"; protected static String SCRIPT_PATH = translateSeperators("$GHIDRA_HOME/Features/Base/ghidra_scripts/" + SCRIPT_NAME); @@ -88,30 +91,80 @@ public class BundleStatusManagerTest extends AbstractGhidraScriptMgrPluginTest { testBundleHostListener.awaitDisablement(); } - void cleanViaGUI(int viewRow) throws InterruptedException { - BundleStatus status = bundleStatusTableModel.getRowObject(viewRow); - bundleStatusTable.selectRow(viewRow); - assertNotNull(status); - - status.setSummary("not clean"); - DockingActionIf cleanBundlesAction = getActionByName(bundleStatusProvider, "CleanBundles"); + List selectRows(int... viewRows) { + List statuses = Arrays.stream(viewRows) + .mapToObj(bundleStatusTableModel::getRowObject) + .collect(Collectors.toList()); + for (BundleStatus status : statuses) { + assertNotNull(status); + } + runSwing(() -> { - cleanBundlesAction.actionPerformed(null); + bundleStatusTable.clearSelection(); + for (int viewRow : viewRows) { + bundleStatusTable.addRowSelectionInterval(viewRow, viewRow); + } }); + + return statuses; + } + + void removeViaGUI(int... viewRows) throws InterruptedException { + assertTrue("removeViaGUI called with no arguments", viewRows.length > 0); + selectRows(viewRows); + + List statuses = bundleStatusTableModel.getModelData(); + int initialSize = statuses.size(); + + DockingActionIf removeBundlesAction = + getActionByName(bundleStatusProvider, "RemoveBundles"); + performAction(removeBundlesAction); waitForSwing(); - + int count = 0; - - // after cleaning, status is cleared. do { - if (status.getSummary().isEmpty()) { + if (statuses.size() <= initialSize - viewRows.length) { break; } Thread.sleep(250); } while (++count < 8); - assertTrue("Failure, clean took too long", count < 8); + + } + + void cleanViaGUI(int... viewRows) throws InterruptedException { + assertTrue("cleanViaGUI called with no arguments", viewRows.length > 0); + + List statuses = selectRows(viewRows); + + List binaryDirs = statuses.stream().map((status) -> { + status.setSummary("no summary"); // we use the summary later to test that the bundle's been cleaned + GhidraSourceBundle bundle = (GhidraSourceBundle) provider.getBundleHost() + .getExistingGhidraBundle(status.getFile()); + assertNotNull(bundle); + File binaryDir = ((Path) getInstanceField("binaryDir", bundle)).toFile(); + assertTrue("Clean of bundle that doesn't exist", binaryDir.exists()); + return binaryDir; + }).collect(Collectors.toList()); + + DockingActionIf cleanBundlesAction = getActionByName(bundleStatusProvider, "CleanBundles"); + performAction(cleanBundlesAction); + waitForSwing(); + + // after cleaning, status is cleared, test for a clear status to know we're done cleaning. + int count = 0; + do { + if (statuses.stream().allMatch(status -> status.getSummary().isEmpty())) { + break; + } + Thread.sleep(250); + } + while (++count < 8); + assertTrue("Failure, clean took too long", count < 8); + for (File binaryDir : binaryDirs) { + assertFalse("Clean of bundle didn't remove directory", binaryDir.exists()); + } } /** @@ -165,7 +218,7 @@ public class BundleStatusManagerTest extends AbstractGhidraScriptMgrPluginTest { BundleStatus status = bundleStatusTableModel.getRowObject(viewRow); // check that it is currently enabled, and our script exists - ResourceFile scriptFile = Path.fromPathString(SCRIPT_PATH); + ResourceFile scriptFile = generic.util.Path.fromPathString(SCRIPT_PATH); assertTrue(status.isEnabled()); assertScriptInTable(scriptFile); @@ -180,6 +233,56 @@ public class BundleStatusManagerTest extends AbstractGhidraScriptMgrPluginTest { assertScriptInTable(scriptFile); } + /** + * Add a list of bundles with the addBundles dialogue. + * + *

All bundles should reside in a common directory. + * + * @param bundleFiles the bundle files + * @throws Exception if waitForUpdateOnChooser fails + */ + void addBundlesViaGUI(File... bundleFiles) throws Exception { + assertTrue("addBundlesViaGUI called with no arguments", bundleFiles.length > 0); + + DockingActionIf addBundlesAction = getActionByName(bundleStatusProvider, "AddBundles"); + performAction(addBundlesAction, false); + waitForSwing(); + + List files = List.of(bundleFiles); + + GhidraFileChooser chooser = waitForDialogComponent(GhidraFileChooser.class); + assertNotNull(chooser); + + runSwing(() -> { + chooser.setCurrentDirectory(bundleFiles[0]); + }, true); + waitForUpdateOnChooser(chooser); + + runSwing(() -> { + // there is no setFiles method of GhidraFileChooser + Object selectedFiles = getInstanceField("selectedFiles", chooser); + invokeInstanceMethod("setFiles", selectedFiles, new Class[] { List.class }, + new Object[] { files }); + Object validatedFiles = getInstanceField("validatedFiles", chooser); + invokeInstanceMethod("setFiles", validatedFiles, new Class[] { List.class }, + new Object[] { files }); + }); + waitForUpdateOnChooser(chooser); + testBundleHostListener.reset(bundleFiles.length); + pressButtonByText(chooser, "OK"); + waitForSwing(); + testBundleHostListener.awaitActivation(); + } + + @Override + protected String runScript(String scriptName) throws Exception { + env.getTool().showComponentProvider(provider, true); + selectScript(scriptName); + String output = super.runScript(scriptName); + env.getTool().showComponentProvider(bundleStatusProvider, true); + return output; + } + @Test public void testRunCleanRun() throws Exception { int viewRow = getBundleRow(BUNDLE_PATH); @@ -195,40 +298,131 @@ public class BundleStatusManagerTest extends AbstractGhidraScriptMgrPluginTest { BundleStatus status = bundleStatusTableModel.getRowObject(viewRow); // check that it is currently enabled, and our script exists - ResourceFile scriptFile = Path.fromPathString(SCRIPT_PATH); + ResourceFile scriptFile = generic.util.Path.fromPathString(SCRIPT_PATH); assertTrue(status.isEnabled()); assertScriptInTable(scriptFile); // run - env.getTool().showComponentProvider(provider, true); - selectScript(SCRIPT_NAME); runScript(SCRIPT_NAME); - env.getTool().showComponentProvider(bundleStatusProvider, true); // clean cleanViaGUI(viewRow); // run - env.getTool().showComponentProvider(provider, true); - selectScript(SCRIPT_NAME); runScript(SCRIPT_NAME); - env.getTool().showComponentProvider(bundleStatusProvider, true); + } + @Test + public void addRunCleanRemoveTwoBundles() throws Exception { + final String TEST_SCRIPT_NAME = testName.getMethodName(); + + //@formatter:off + final String EXPECTED_OUTPUT = + TEST_SCRIPT_NAME+".java> Running...\n" + + TEST_SCRIPT_NAME+".java> Hello from pack2.Klass2\n" + + TEST_SCRIPT_NAME+".java> Finished!\n"; + //@formatter:on + final File dir1 = new File(getTestDirectoryPath() + "/test_scripts1"); + final File dir2 = new File(getTestDirectoryPath() + "/test_scripts2"); + try { + dir1.mkdirs(); + //@formatter:off + Files.writeString(new File(dir1, TEST_SCRIPT_NAME+".java").toPath(), + "//@importpackage pack2\n" + + "\n" + + "import pack1.Klass1;\n" + + "import ghidra.app.script.GhidraScript;\n" + + "public class "+TEST_SCRIPT_NAME+" extends GhidraScript {\n" + + " @Override\n" + + " protected void run() throws Exception {\n" + + " new Klass1(this).hello();\n" + + " }\n" + + "}\n" + ); + File pack1 = new File(dir1,"pack1"); + pack1.mkdirs(); + Files.writeString(new File(pack1, "Klass1.java").toPath(), + "package pack1;\n" + + "\n" + + "import ghidra.app.script.GhidraScript;\n" + + "import pack2.Klass2;\n" + + "\n" + + "public class Klass1 {\n" + + " GhidraScript script;\n" + + " public Klass1(GhidraScript script) {\n" + + " this.script = script;\n" + + " }\n" + + "\n" + + " public void hello() {\n" + + " new Klass2(script).hello();\n" + + " }\n" + + "}\n" + ); + dir2.mkdirs(); + File pack2 = new File(dir2,"pack2"); + pack2.mkdirs(); + Files.writeString(new File(pack2, "Klass2.java").toPath(), + "package pack2;\n" + + "\n" + + "import ghidra.app.script.GhidraScript;\n" + + "import pack2.Klass2;\n" + + "\n" + + "public class Klass2 {\n" + + " GhidraScript script;\n" + + " public Klass2(GhidraScript script) {\n" + + " this.script = script;\n" + + " }\n" + + "\n" + + " public void hello() {\n" + + " script.println(\"Hello from pack2.Klass2\");\n" + + " }\n" + + "}\n" + ); + //@formatter:on + + addBundlesViaGUI(dir1, dir2); + + String output = runScript(TEST_SCRIPT_NAME + ".java"); + assertEquals(EXPECTED_OUTPUT, output); + + int row1 = getBundleRow(generic.util.Path.toPathString(new ResourceFile(dir1))); + int row2 = getBundleRow(generic.util.Path.toPathString(new ResourceFile(dir2))); + assertFalse(row1 == -1); + assertFalse(row2 == -1); + + cleanViaGUI(row1, row2); + + removeViaGUI(row1, row2); + + row1 = getBundleRow(generic.util.Path.toPathString(new ResourceFile(dir1))); + row2 = getBundleRow(generic.util.Path.toPathString(new ResourceFile(dir2))); + assertTrue(row1 == -1); + assertTrue(row2 == -1); + } + finally { + wipe(dir1.toPath()); + wipe(dir2.toPath()); + } } /** * A {@link BundleHostListener} to help serialize bundle operations. */ protected class TestBundleHostListener implements BundleHostListener { - CountDownLatch activationLatch = new CountDownLatch(1); - CountDownLatch disablementLatch = new CountDownLatch(1); + CountDownLatch activationLatch; + CountDownLatch disablementLatch; TestBundleHostListener() { + reset(); } void reset() { - activationLatch = new CountDownLatch(1); - disablementLatch = new CountDownLatch(1); + reset(1); + } + + void reset(int count) { + activationLatch = new CountDownLatch(count); + disablementLatch = new CountDownLatch(count); } @Override