mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-09-19 16:40:38 -09:00
add bundle status manager tests
This commit is contained in:
@@ -248,7 +248,7 @@ public class BundleStatusComponentProvider extends ComponentProviderAdapter {
|
|||||||
|
|
||||||
private void showAddBundlesFileChooser() {
|
private void showAddBundlesFileChooser() {
|
||||||
if (fileChooser == null) {
|
if (fileChooser == null) {
|
||||||
fileChooser = new GhidraFileChooser(panel);
|
fileChooser = new GhidraFileChooser(getComponent());
|
||||||
fileChooser.setMultiSelectionEnabled(true);
|
fileChooser.setMultiSelectionEnabled(true);
|
||||||
fileChooser.setFileSelectionMode(GhidraFileChooserMode.FILES_AND_DIRECTORIES);
|
fileChooser.setFileSelectionMode(GhidraFileChooserMode.FILES_AND_DIRECTORIES);
|
||||||
fileChooser.setTitle("Select Bundle(s)");
|
fileChooser.setTitle("Select Bundle(s)");
|
||||||
|
|||||||
@@ -18,22 +18,25 @@ package ghidra.app.plugin.core.script;
|
|||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
import java.io.File;
|
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.CountDownLatch;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.junit.*;
|
import org.junit.*;
|
||||||
|
|
||||||
import docking.ComponentProvider;
|
import docking.ComponentProvider;
|
||||||
import docking.action.DockingActionIf;
|
import docking.action.DockingActionIf;
|
||||||
|
import docking.widgets.filechooser.GhidraFileChooser;
|
||||||
import docking.widgets.table.GTable;
|
import docking.widgets.table.GTable;
|
||||||
import generic.jar.ResourceFile;
|
import generic.jar.ResourceFile;
|
||||||
import generic.util.Path;
|
|
||||||
import ghidra.app.plugin.core.osgi.*;
|
import ghidra.app.plugin.core.osgi.*;
|
||||||
|
|
||||||
public class BundleStatusManagerTest extends AbstractGhidraScriptMgrPluginTest {
|
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_NAME = "HelloWorldScript.java";
|
||||||
protected static String SCRIPT_PATH =
|
protected static String SCRIPT_PATH =
|
||||||
translateSeperators("$GHIDRA_HOME/Features/Base/ghidra_scripts/" + SCRIPT_NAME);
|
translateSeperators("$GHIDRA_HOME/Features/Base/ghidra_scripts/" + SCRIPT_NAME);
|
||||||
@@ -88,30 +91,80 @@ public class BundleStatusManagerTest extends AbstractGhidraScriptMgrPluginTest {
|
|||||||
testBundleHostListener.awaitDisablement();
|
testBundleHostListener.awaitDisablement();
|
||||||
}
|
}
|
||||||
|
|
||||||
void cleanViaGUI(int viewRow) throws InterruptedException {
|
List<BundleStatus> selectRows(int... viewRows) {
|
||||||
BundleStatus status = bundleStatusTableModel.getRowObject(viewRow);
|
List<BundleStatus> statuses = Arrays.stream(viewRows)
|
||||||
bundleStatusTable.selectRow(viewRow);
|
.mapToObj(bundleStatusTableModel::getRowObject)
|
||||||
assertNotNull(status);
|
.collect(Collectors.toList());
|
||||||
|
for (BundleStatus status : statuses) {
|
||||||
status.setSummary("not clean");
|
assertNotNull(status);
|
||||||
DockingActionIf cleanBundlesAction = getActionByName(bundleStatusProvider, "CleanBundles");
|
}
|
||||||
|
|
||||||
runSwing(() -> {
|
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();
|
waitForSwing();
|
||||||
|
|
||||||
int count = 0;
|
int count = 0;
|
||||||
|
|
||||||
// after cleaning, status is cleared.
|
|
||||||
do {
|
do {
|
||||||
if (status.getSummary().isEmpty()) {
|
if (statuses.size() <= initialSize - viewRows.length) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
Thread.sleep(250);
|
Thread.sleep(250);
|
||||||
}
|
}
|
||||||
while (++count < 8);
|
while (++count < 8);
|
||||||
|
|
||||||
assertTrue("Failure, clean took too long", 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);
|
BundleStatus status = bundleStatusTableModel.getRowObject(viewRow);
|
||||||
|
|
||||||
// check that it is currently enabled, and our script exists
|
// 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());
|
assertTrue(status.isEnabled());
|
||||||
assertScriptInTable(scriptFile);
|
assertScriptInTable(scriptFile);
|
||||||
|
|
||||||
@@ -180,6 +233,56 @@ public class BundleStatusManagerTest extends AbstractGhidraScriptMgrPluginTest {
|
|||||||
assertScriptInTable(scriptFile);
|
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
|
@Test
|
||||||
public void testRunCleanRun() throws Exception {
|
public void testRunCleanRun() throws Exception {
|
||||||
int viewRow = getBundleRow(BUNDLE_PATH);
|
int viewRow = getBundleRow(BUNDLE_PATH);
|
||||||
@@ -195,40 +298,131 @@ public class BundleStatusManagerTest extends AbstractGhidraScriptMgrPluginTest {
|
|||||||
BundleStatus status = bundleStatusTableModel.getRowObject(viewRow);
|
BundleStatus status = bundleStatusTableModel.getRowObject(viewRow);
|
||||||
|
|
||||||
// check that it is currently enabled, and our script exists
|
// 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());
|
assertTrue(status.isEnabled());
|
||||||
assertScriptInTable(scriptFile);
|
assertScriptInTable(scriptFile);
|
||||||
|
|
||||||
// run
|
// run
|
||||||
env.getTool().showComponentProvider(provider, true);
|
|
||||||
selectScript(SCRIPT_NAME);
|
|
||||||
runScript(SCRIPT_NAME);
|
runScript(SCRIPT_NAME);
|
||||||
env.getTool().showComponentProvider(bundleStatusProvider, true);
|
|
||||||
|
|
||||||
// clean
|
// clean
|
||||||
cleanViaGUI(viewRow);
|
cleanViaGUI(viewRow);
|
||||||
|
|
||||||
// run
|
// run
|
||||||
env.getTool().showComponentProvider(provider, true);
|
|
||||||
selectScript(SCRIPT_NAME);
|
|
||||||
runScript(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.
|
* A {@link BundleHostListener} to help serialize bundle operations.
|
||||||
*/
|
*/
|
||||||
protected class TestBundleHostListener implements BundleHostListener {
|
protected class TestBundleHostListener implements BundleHostListener {
|
||||||
CountDownLatch activationLatch = new CountDownLatch(1);
|
CountDownLatch activationLatch;
|
||||||
CountDownLatch disablementLatch = new CountDownLatch(1);
|
CountDownLatch disablementLatch;
|
||||||
|
|
||||||
TestBundleHostListener() {
|
TestBundleHostListener() {
|
||||||
|
reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
void reset() {
|
void reset() {
|
||||||
activationLatch = new CountDownLatch(1);
|
reset(1);
|
||||||
disablementLatch = new CountDownLatch(1);
|
}
|
||||||
|
|
||||||
|
void reset(int count) {
|
||||||
|
activationLatch = new CountDownLatch(count);
|
||||||
|
disablementLatch = new CountDownLatch(count);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
Reference in New Issue
Block a user