mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-09-07 09:11:15 -09:00
add bundle status manager tests
This commit is contained in:
@@ -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)");
|
||||
|
||||
@@ -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<BundleStatus> selectRows(int... viewRows) {
|
||||
List<BundleStatus> 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<BundleStatus> 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<BundleStatus> statuses = selectRows(viewRows);
|
||||
|
||||
List<File> 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.
|
||||
*
|
||||
* <p>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<File> 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
|
||||
|
||||
Reference in New Issue
Block a user