Tests - cleanup of stack traces in test log file

This commit is contained in:
dragonmacher 2020-09-24 18:19:28 -04:00
parent b7c8056c8a
commit e5358323d8
24 changed files with 889 additions and 678 deletions

View file

@ -713,6 +713,7 @@ public class GhidraSourceBundle extends GhidraBundle {
}
}
catch (Throwable e) {
Msg.error(this, "Exception searching ", e);
e.printStackTrace();
}
}
@ -1090,13 +1091,21 @@ public class GhidraSourceBundle extends GhidraBundle {
* @throws IOException if there's a problem listing files
*/
ClassMapper(Path directory) throws IOException {
if (Files.exists(directory)) {
try (Stream<Path> pathStream = Files.list(directory)) {
classToClassFilesMap = pathStream
.filter(f -> Files.isRegularFile(f) &&
f.getFileName().toString().endsWith(".class"))
.collect(groupingBy(f -> {
String fileName = f.getFileName().toString();
if (!Files.exists(directory)) {
classToClassFilesMap = Collections.emptyMap();
return;
}
try (Stream<Path> paths = Files.list(directory)) {
classToClassFilesMap = paths
.filter(p -> Files.isRegularFile(p))
.filter(p -> p.getFileName().toString().endsWith(".class"))
.collect(groupingBy(this::getClassName));
}
}
private String getClassName(Path p) {
String fileName = p.getFileName().toString();
// if f is the class file of an inner class, use the class name
int money = fileName.indexOf('$');
if (money >= 0) {
@ -1104,23 +1113,21 @@ public class GhidraSourceBundle extends GhidraBundle {
}
// drop ".class"
return fileName.substring(0, fileName.length() - 6);
}));
}
}
else {
classToClassFilesMap = Collections.emptyMap();
}
}
List<Path> findAndRemove(ResourceFile sourceFile) {
String className = sourceFile.getName();
if (className.endsWith(".java")) {
if (!className.endsWith(".java")) {
return null;
}
className = className.substring(0, className.length() - 5);
long lastModifiedSource = sourceFile.lastModified();
List<Path> classFiles = classToClassFilesMap.remove(className);
if (classFiles == null) {
classFiles = Collections.emptyList();
}
long lastModifiedClassFile = classFiles.isEmpty() ? -1
: classFiles.stream()
.mapToLong(p -> p.toFile().lastModified())
@ -1130,7 +1137,6 @@ public class GhidraSourceBundle extends GhidraBundle {
if (lastModifiedSource > lastModifiedClassFile) {
return classFiles;
}
}
return null;
}

View file

@ -241,6 +241,7 @@ public class GhidraScriptUtil {
*/
@Deprecated
public static List<ResourceFile> getExplodedCompiledSourceBundlePaths() {
try (Stream<Path> pathStream = Files.list(BundleHost.getOsgiDir())) {
return pathStream.filter(Files::isDirectory)
.map(x -> new ResourceFile(x.toFile()))

View file

@ -50,11 +50,9 @@ import ghidra.program.model.util.*;
import ghidra.program.util.DefaultLanguageService;
import ghidra.program.util.GhidraProgramUtilities;
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
import ghidra.util.NumericUtilities;
import ghidra.util.Saveable;
import ghidra.util.*;
import ghidra.util.exception.*;
import ghidra.util.task.TaskMonitor;
import ghidra.util.task.TaskMonitorAdapter;
// TODO: Move this class into a different package (i.e., ghidra.test.program)
public class ProgramBuilder {
@ -97,7 +95,7 @@ public class ProgramBuilder {
* Construct program builder using the big-endian Toy language and default compiler spec.
* This builder object will be the program consumer and must be disposed to properly
* release the program.
* @throws Exception
* @throws Exception if there is an exception creating the program
*/
public ProgramBuilder() throws Exception {
this("Test Program", _TOY);
@ -109,7 +107,7 @@ public class ProgramBuilder {
* release the program.
* @param name program name
* @param languageName supported language ID (includes all Toy language IDs)
* @throws Exception
* @throws Exception if there is an exception creating the program
*/
public ProgramBuilder(String name, String languageName) throws Exception {
this(name, languageName, null, null);
@ -120,7 +118,7 @@ public class ProgramBuilder {
* @param name program name
* @param languageName supported language ID (includes all Toy language IDs)
* @param consumer program consumer (if null this builder will be used as consumer and must be disposed to release program)
* @throws Exception
* @throws Exception if there is an exception creating the program
*/
public ProgramBuilder(String name, String languageName, Object consumer) throws Exception {
this(name, languageName, null, consumer);
@ -132,7 +130,7 @@ public class ProgramBuilder {
* @param languageName supported language ID (includes all Toy language IDs)
* @param compilerSpecID compiler specification ID (if null default spec will be used)
* @param consumer program consumer (if null this builder will be used as consumer and must be disposed to release program)
* @throws Exception
* @throws Exception if there is an exception creating the program
*/
public ProgramBuilder(String name, String languageName, String compilerSpecID, Object consumer)
throws Exception {
@ -205,10 +203,22 @@ public class ProgramBuilder {
public void dispose() {
if (program.isUsedBy(this)) {
// Make sure any buffered events are processed before we release. This fixes a timing
// issue that can happen when the test thread disposes the program while the Swing
// thread is processing events.
flushEvents();
program.release(this);
}
}
private void flushEvents() {
program.flushEvents();
if (!SystemUtilities.isInHeadlessMode()) {
AbstractGenericTest.waitForSwing();
}
}
public void setName(String name) {
startTransaction();
try {
@ -286,7 +296,8 @@ public class ProgramBuilder {
LanguageService languageService = DefaultLanguageService.getLanguageService(ldefFile);
try {
language = languageService.getLanguage(new LanguageID(languageName));
} catch (LanguageNotFoundException e) {
}
catch (LanguageNotFoundException e) {
throw new LanguageNotFoundException("Unsupported test language: " + languageName);
}
LANGUAGE_CACHE.put(languageName, language);
@ -303,7 +314,10 @@ public class ProgramBuilder {
AbstractGenericTest.setInstanceField("recordChanges", program, Boolean.valueOf(enabled));
}
/** Don't show the 'ask to analyze' dialog by default */
/**
* This prevents the 'ask to analyze' dialog from showing when called with {@code true}
* @param analyzed true to mark the program as analyzed
*/
public void setAnalyzed(boolean analyzed) {
GhidraProgramUtilities.setAnalyzedFlag(program, analyzed);
}
@ -325,7 +339,7 @@ public class ProgramBuilder {
MemoryBlock block = null;
try {
block = memory.createInitializedBlock(name, startAddress, size, initialValue,
TaskMonitorAdapter.DUMMY_MONITOR, false);
TaskMonitor.DUMMY, false);
block.setComment(comment);
}
catch (CancelledException e) {
@ -359,7 +373,8 @@ public class ProgramBuilder {
startTransaction();
try {
return program.getMemory().createInitializedBlock(name, addr(address), size, (byte) 0,
return program.getMemory()
.createInitializedBlock(name, addr(address), size, (byte) 0,
TaskMonitor.DUMMY, true);
}
catch (Exception e) {
@ -378,7 +393,7 @@ public class ProgramBuilder {
* @param address String containing numeric value, preferably hex encoded: "0x1004000"
* @param byteString String containing 2 digit hex values, separated by ' ' space chars
* or by comma ',' chars: "12 05 ff". See {@link NumericUtilities#parseHexLong(String)}.
* @throws Exception
* @throws Exception if there is an exception applying the bytes
*/
public void setBytes(String address, String byteString) throws Exception {
byte[] bytes = NumericUtilities.convertStringToBytes(byteString);
@ -395,7 +410,7 @@ public class ProgramBuilder {
* @param byteString String containing 2 digit hex values, separated by ' ' space chars
* or by comma ',' chars: "12 05 ff". See {@link NumericUtilities#parseHexLong(String)}.
* @param disassemble boolean flag.
* @throws Exception
* @throws Exception if there is an exception applying the bytes
*/
public void setBytes(String address, String byteString, boolean disassemble) throws Exception {
byte[] bytes = NumericUtilities.convertStringToBytes(byteString);
@ -413,7 +428,7 @@ public class ProgramBuilder {
* @param stringAddress String containing numeric value, preferably hex encoded: "0x1004000"
* @param bytes array of bytes to copy into the memory buffer at the addresss.
* @param disassemble boolean flag. See {@link #disassemble(String, int)}
* @throws Exception
* @throws Exception if there is an exception applying the bytes
*/
public void setBytes(String stringAddress, byte[] bytes, boolean disassemble) throws Exception {
Address address = addr(stringAddress);
@ -547,9 +562,6 @@ public class ProgramBuilder {
}
}
/**
* This creates a function as big as you say.
*/
public Function createEmptyFunction(String name, String address, int size, DataType returnType,
Parameter... params) throws Exception, OverlappingFunctionException {

View file

@ -688,7 +688,8 @@ public class RefMergerExtTest extends AbstractExternalMergerTest {
int txId = program.startTransaction("Modify Original Program");
boolean commit = false;
try {
ExternalLocation extLoc = createExternalLabel(program, new String[] { "Library", "Namespace", "Label1" },
ExternalLocation extLoc = createExternalLabel(program,
new String[] { "Library", "Namespace", "Label1" },
addr(program, "77db1020"), SourceType.ANALYSIS);
ReferenceManager refMgr = program.getReferenceManager();
@ -1328,13 +1329,7 @@ public class RefMergerExtTest extends AbstractExternalMergerTest {
finally {
program.endTransaction(txId, commit);
}
ExternalManager externalManager = program.getExternalManager();
ExternalLocationIterator externalLocations =
externalManager.getExternalLocations("ADVAPI32.DLL");
while (externalLocations.hasNext()) {
ExternalLocation next = externalLocations.next();
System.out.println("Location=" + next.getSymbol().getName(true));
}
ExternalLocation externalLocation1 =
getExternalLocation(program, new String[] { "USER32.DLL", "printf" });
assertNotNull(externalLocation1);
@ -1452,13 +1447,7 @@ public class RefMergerExtTest extends AbstractExternalMergerTest {
finally {
program.endTransaction(txId, commit);
}
ExternalManager externalManager = program.getExternalManager();
ExternalLocationIterator externalLocations =
externalManager.getExternalLocations("ADVAPI32.DLL");
while (externalLocations.hasNext()) {
ExternalLocation next = externalLocations.next();
System.out.println("Location=" + next.getSymbol().getName(true));
}
ExternalLocation externalLocation1 =
getExternalLocation(program, new String[] { "USER32.DLL", "printf" });
assertNotNull(externalLocation1);

View file

@ -94,15 +94,7 @@ public class SymbolMergeManagerNamespace1Test extends AbstractListingMergeManage
// 01005c6f FUN_01005c6f body:[1005c6f-1005fbd][1005ff5-10061e2]
// 01006420 entry body:[1006420-1006581][10065a4-10065cd]
/**
*
* @param arg0
*/
public SymbolMergeManagerNamespace1Test() {
super();
}
/**
/*
* Test generic Namespace symbols being removed from either the LATEST or
* CHECKED OUT program when it doesn't result in a conflict.
* @throws Exception
@ -169,7 +161,7 @@ public class SymbolMergeManagerNamespace1Test extends AbstractListingMergeManage
assertNull(funcMgr.getFunctionAt(addr("0x01004bc0")));
}
/**
/*
* Test Class symbols being removed from either the LATEST or
* CHECKED OUT program when it doesn't result in a conflict.
* @throws Exception
@ -566,16 +558,20 @@ public class SymbolMergeManagerNamespace1Test extends AbstractListingMergeManage
try {
Namespace ns;
GhidraClass gc;
ns = program.getSymbolTable().createNameSpace(program.getGlobalNamespace(),
ns = program.getSymbolTable()
.createNameSpace(program.getGlobalNamespace(),
"Blue", SourceType.USER_DEFINED);
assertNotNull(ns);
ns = program.getSymbolTable().createNameSpace(program.getGlobalNamespace(),
ns = program.getSymbolTable()
.createNameSpace(program.getGlobalNamespace(),
"Green", SourceType.USER_DEFINED);
assertNotNull(ns);
gc = program.getSymbolTable().createClass(program.getGlobalNamespace(), "Red",
gc = program.getSymbolTable()
.createClass(program.getGlobalNamespace(), "Red",
SourceType.USER_DEFINED);
assertNotNull(gc);
gc = program.getSymbolTable().createClass(program.getGlobalNamespace(),
gc = program.getSymbolTable()
.createClass(program.getGlobalNamespace(),
"Yellow", SourceType.USER_DEFINED);
assertNotNull(gc);
commit = true;
@ -598,16 +594,20 @@ public class SymbolMergeManagerNamespace1Test extends AbstractListingMergeManage
try {
Namespace ns;
GhidraClass gc;
gc = program.getSymbolTable().createClass(program.getGlobalNamespace(), "Blue",
gc = program.getSymbolTable()
.createClass(program.getGlobalNamespace(), "Blue",
SourceType.USER_DEFINED);
assertNotNull(gc);
gc = program.getSymbolTable().createClass(program.getGlobalNamespace(), "Green",
gc = program.getSymbolTable()
.createClass(program.getGlobalNamespace(), "Green",
SourceType.USER_DEFINED);
assertNotNull(gc);
ns = program.getSymbolTable().createNameSpace(program.getGlobalNamespace(),
ns = program.getSymbolTable()
.createNameSpace(program.getGlobalNamespace(),
"Red", SourceType.USER_DEFINED);
assertNotNull(ns);
ns = program.getSymbolTable().createNameSpace(program.getGlobalNamespace(),
ns = program.getSymbolTable()
.createNameSpace(program.getGlobalNamespace(),
"Yellow", SourceType.USER_DEFINED);
assertNotNull(ns);
commit = true;
@ -665,16 +665,20 @@ public class SymbolMergeManagerNamespace1Test extends AbstractListingMergeManage
"EmptyNamespace", program.getGlobalNamespace()).getObject();
Namespace ns;
GhidraClass gc;
ns = program.getSymbolTable().createNameSpace(emptyNamespace, "Blue",
ns = program.getSymbolTable()
.createNameSpace(emptyNamespace, "Blue",
SourceType.USER_DEFINED);
assertNotNull(ns);
ns = program.getSymbolTable().createNameSpace(emptyNamespace, "Green",
ns = program.getSymbolTable()
.createNameSpace(emptyNamespace, "Green",
SourceType.USER_DEFINED);
assertNotNull(ns);
gc = program.getSymbolTable().createClass(emptyNamespace, "Red",
gc = program.getSymbolTable()
.createClass(emptyNamespace, "Red",
SourceType.USER_DEFINED);
assertNotNull(gc);
gc = program.getSymbolTable().createClass(emptyNamespace, "Yellow",
gc = program.getSymbolTable()
.createClass(emptyNamespace, "Yellow",
SourceType.USER_DEFINED);
assertNotNull(gc);
commit = true;
@ -699,16 +703,20 @@ public class SymbolMergeManagerNamespace1Test extends AbstractListingMergeManage
"EmptyNamespace", program.getGlobalNamespace()).getObject();
Namespace ns;
GhidraClass gc;
gc = program.getSymbolTable().createClass(emptyNamespace, "Blue",
gc = program.getSymbolTable()
.createClass(emptyNamespace, "Blue",
SourceType.USER_DEFINED);
assertNotNull(gc);
gc = program.getSymbolTable().createClass(emptyNamespace, "Green",
gc = program.getSymbolTable()
.createClass(emptyNamespace, "Green",
SourceType.USER_DEFINED);
assertNotNull(gc);
ns = program.getSymbolTable().createNameSpace(emptyNamespace, "Red",
ns = program.getSymbolTable()
.createNameSpace(emptyNamespace, "Red",
SourceType.USER_DEFINED);
assertNotNull(ns);
ns = program.getSymbolTable().createNameSpace(emptyNamespace, "Yellow",
ns = program.getSymbolTable()
.createNameSpace(emptyNamespace, "Yellow",
SourceType.USER_DEFINED);
assertNotNull(ns);
commit = true;
@ -773,16 +781,20 @@ public class SymbolMergeManagerNamespace1Test extends AbstractListingMergeManage
try {
Namespace ns;
GhidraClass gc;
ns = program.getSymbolTable().createNameSpace(program.getGlobalNamespace(),
ns = program.getSymbolTable()
.createNameSpace(program.getGlobalNamespace(),
"Blue", SourceType.USER_DEFINED);
assertNotNull(ns);
ns = program.getSymbolTable().createNameSpace(program.getGlobalNamespace(),
ns = program.getSymbolTable()
.createNameSpace(program.getGlobalNamespace(),
"Green", SourceType.USER_DEFINED);
assertNotNull(ns);
gc = program.getSymbolTable().createClass(program.getGlobalNamespace(), "Red",
gc = program.getSymbolTable()
.createClass(program.getGlobalNamespace(), "Red",
SourceType.USER_DEFINED);
assertNotNull(gc);
gc = program.getSymbolTable().createClass(program.getGlobalNamespace(),
gc = program.getSymbolTable()
.createClass(program.getGlobalNamespace(),
"Yellow", SourceType.USER_DEFINED);
assertNotNull(gc);
commit = true;
@ -805,22 +817,28 @@ public class SymbolMergeManagerNamespace1Test extends AbstractListingMergeManage
try {
Namespace ns;
GhidraClass gc;
gc = program.getSymbolTable().createClass(program.getGlobalNamespace(), "Blue",
gc = program.getSymbolTable()
.createClass(program.getGlobalNamespace(), "Blue",
SourceType.USER_DEFINED);
assertNotNull(gc);
gc = program.getSymbolTable().createClass(program.getGlobalNamespace(), "Green",
gc = program.getSymbolTable()
.createClass(program.getGlobalNamespace(), "Green",
SourceType.USER_DEFINED);
assertNotNull(gc);
ns = program.getSymbolTable().createNameSpace(program.getGlobalNamespace(),
ns = program.getSymbolTable()
.createNameSpace(program.getGlobalNamespace(),
"Red", SourceType.USER_DEFINED);
assertNotNull(ns);
gc = program.getSymbolTable().createClass(ns, "SubRed",
gc = program.getSymbolTable()
.createClass(ns, "SubRed",
SourceType.USER_DEFINED);
assertNotNull(gc);
ns = program.getSymbolTable().createNameSpace(program.getGlobalNamespace(),
ns = program.getSymbolTable()
.createNameSpace(program.getGlobalNamespace(),
"Yellow", SourceType.USER_DEFINED);
assertNotNull(ns);
ns = program.getSymbolTable().createNameSpace(ns, "SubYellow",
ns = program.getSymbolTable()
.createNameSpace(ns, "SubYellow",
SourceType.USER_DEFINED);
assertNotNull(ns);
commit = true;
@ -1271,15 +1289,6 @@ public class SymbolMergeManagerNamespace1Test extends AbstractListingMergeManage
"The following namespaces were not removed", 4000);
waitForMergeCompletion();
SymbolTable symtab = resultProgram.getSymbolTable();
Namespace globalNS = resultProgram.getGlobalNamespace();
SymbolIterator iter = symtab.getSymbols(globalNS);
while (iter.hasNext()) {
Symbol s = iter.next();
if (s.getSymbolType().equals(SymbolType.NAMESPACE)) {
System.out.println("Namespace = " + s.getName(true));
}
}
Symbol firstNsSymbol =
getUniqueSymbol(resultProgram, "FirstNamespace", resultProgram.getGlobalNamespace());
assertNotNull(firstNsSymbol);

View file

@ -15,24 +15,14 @@
*/
package ghidra.app.plugin.core.analysis;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.*;
import javax.swing.JComponent;
import javax.swing.table.TableModel;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.*;
import docking.widgets.OptionDialog;
import ghidra.GhidraOptions;
@ -71,6 +61,7 @@ public class AnalyzeAllOpenProgramsTaskTest extends AbstractGhidraHeadedIntegrat
for (Program program : openPrograms) {
env.release(program);
}
tool.close();
env.dispose();
}
@ -236,7 +227,7 @@ public class AnalyzeAllOpenProgramsTaskTest extends AbstractGhidraHeadedIntegrat
openPrograms.toArray(new Program[openPrograms.size()]), spy);
runTask(task);
OptionDialog warningDialog = waitForDialogComponent(null, OptionDialog.class, 2000);
OptionDialog warningDialog = waitForDialogComponent(OptionDialog.class);
pressButtonByText(warningDialog, "Cancel");
waitForTasks();
@ -307,17 +298,19 @@ public class AnalyzeAllOpenProgramsTaskTest extends AbstractGhidraHeadedIntegrat
findComponent(optionsDialog.getComponent(), AnalysisPanel.class, false);
invokeInstanceMethod("deselectAll", panel);
waitForSwing();
close(optionsDialog);
}
private void enableOption(String optionName, boolean expectWarning) {
if (expectWarning) {
OptionDialog warningDialog = waitForDialogComponent(null, OptionDialog.class, 2000);
OptionDialog warningDialog = waitForDialogComponent(OptionDialog.class);
pressButtonByText(warningDialog, "Continue");
}
AnalysisOptionsDialog optionsDialog =
waitForDialogComponent(null, AnalysisOptionsDialog.class, DEFAULT_WINDOW_TIMEOUT);
waitForDialogComponent(AnalysisOptionsDialog.class);
// select some options
JComponent root = optionsDialog.getComponent();
@ -332,7 +325,7 @@ public class AnalyzeAllOpenProgramsTaskTest extends AbstractGhidraHeadedIntegrat
private void cancelAnalysisDialog() {
AnalysisOptionsDialog optionsDialog =
waitForDialogComponent(null, AnalysisOptionsDialog.class, DEFAULT_WINDOW_TIMEOUT);
waitForDialogComponent(AnalysisOptionsDialog.class);
// press Apply
pressButtonByText(optionsDialog, "Cancel");
@ -437,6 +430,11 @@ public class AnalyzeAllOpenProgramsTaskTest extends AbstractGhidraHeadedIntegrat
}
return super.getOptions(categoryName);
}
@Override
public void close() {
runSwing(super::close);
}
}
private class AnalyzeProgramStrategySpy extends AnalyzeProgramStrategy {

View file

@ -24,6 +24,7 @@ import javax.swing.table.*;
import org.junit.*;
import docking.ActionContext;
import docking.ComponentProvider;
import docking.action.DockingActionIf;
import docking.tool.ToolConstants;
@ -45,6 +46,7 @@ public class FunctionWindowPluginTest extends AbstractGhidraHeadedIntegrationTes
private Program program;
private FunctionWindowPlugin plugin;
private GTable functionTable;
private ComponentProvider provider;
@Before
public void setUp() throws Exception {
@ -56,7 +58,7 @@ public class FunctionWindowPluginTest extends AbstractGhidraHeadedIntegrationTes
plugin.showFunctions();
waitForSwing();
ComponentProvider provider = tool.getComponentProvider("Functions Window");
provider = tool.getComponentProvider("Functions Window");
functionTable = (GTable) findComponentByName(provider.getComponent(), "FunctionTable");
}
@ -74,6 +76,8 @@ public class FunctionWindowPluginTest extends AbstractGhidraHeadedIntegrationTes
private void closeProgram() {
ProgramManager pm = tool.getService(ProgramManager.class);
pm.closeProgram(program, true);
waitForSwing();
waitForNotBusy(functionTable);
}
@Test
@ -96,16 +100,13 @@ public class FunctionWindowPluginTest extends AbstractGhidraHeadedIntegrationTes
waitForNotBusy(functionTable);
assertEquals(numData, functionTable.getRowCount());
}
@Test
public void testProgramClose() throws Exception {
closeProgram();
waitForNotBusy(functionTable);
assertEquals(functionTable.getRowCount(), 0);
loadProgram("notepad");
}
@Test
@ -147,7 +148,8 @@ public class FunctionWindowPluginTest extends AbstractGhidraHeadedIntegrationTes
String signatureText = getRenderedTableCellValue(functionTable, row, column);
DockingActionIf copyAction = getAction(tool, ToolConstants.SHARED_OWNER, "Table Data Copy");
performAction(copyAction);
ActionContext context = new ActionContext(provider, functionTable);
performAction(copyAction, context, true);
//
// Note: we cannot make this call:

View file

@ -15,8 +15,7 @@
*/
package ghidra.app.plugin.core.label;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.*;
import javax.swing.JComponent;
import javax.swing.JTable;
@ -44,7 +43,8 @@ import ghidra.program.model.symbol.*;
import ghidra.program.util.*;
import ghidra.test.*;
public class LabelActionTest extends AbstractGhidraHeadedIntegrationTest implements LocationCallback {
public class LabelActionTest extends AbstractGhidraHeadedIntegrationTest
implements LocationCallback {
private static final String ADD_LABEL = "Add Label";
private static final String EDIT_LABEL = "Edit Label";
private static final String EDIT_EXTERNAL_LOC = "Edit External Location";
@ -80,6 +80,8 @@ public class LabelActionTest extends AbstractGhidraHeadedIntegrationTest impleme
editExternalLocation = getAction(labelMgrPlugin, EDIT_EXTERNAL_LOC);
removeLabel = getAction(labelMgrPlugin, REMOVE_LABEL);
setLabel = getAction(labelMgrPlugin, SET_LABEL);
env.showTool();
}
@After
@ -128,6 +130,7 @@ public class LabelActionTest extends AbstractGhidraHeadedIntegrationTest impleme
Object author = model.getValueAt(0, 2);
assertTrue(author.toString().startsWith(System.getProperty("user.name")));
close(provider);
}
@Test

View file

@ -27,7 +27,6 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.swing.*;
import javax.swing.table.TableModel;
@ -64,6 +63,7 @@ import ghidra.util.exception.CancelledException;
import ghidra.util.table.GhidraTableFilterPanel;
import ghidra.util.task.*;
import util.CollectionUtils;
import utilities.util.FileUtilities;
public abstract class AbstractGhidraScriptMgrPluginTest
extends AbstractGhidraHeadedIntegrationTest {
@ -170,32 +170,19 @@ public abstract class AbstractGhidraScriptMgrPluginTest
deleteFile(testScriptFile);
testScriptFile = null;
}
wipeUserScripts();
deleteUserScripts();
env.dispose();
}
protected static void wipe(ResourceFile path) throws IOException {
wipe(Paths.get(path.getAbsolutePath()));
protected static void delete(Path path) {
FileUtilities.deleteDir(path);
}
protected static void wipe(Path path) throws IOException {
if (Files.exists(path)) {
try (Stream<Path> walk = Files.walk(path)) {
for (Path p : (Iterable<Path>) walk.sorted(Comparator.reverseOrder())::iterator) {
Files.deleteIfExists(p);
}
}
}
}
protected void deleteUserScripts() throws IOException {
protected void wipeUserScripts() throws IOException {
Path userScriptDir = java.nio.file.Paths.get(GhidraScriptUtil.USER_SCRIPTS_DIR);
try (Stream<Path> pathStream = Files.list(userScriptDir)) {
for (Path p : (Iterable<Path>) pathStream::iterator) {
wipe(p);
}
}
Path userScriptDir = Paths.get(GhidraScriptUtil.USER_SCRIPTS_DIR);
FileUtilities.forEachFile(userScriptDir, paths -> paths.forEach(p -> delete(p)));
}
//==================================================================================================
@ -983,9 +970,9 @@ public abstract class AbstractGhidraScriptMgrPluginTest
}
protected void cleanupOldTestFiles() throws IOException {
protected void cleanupOldTestFiles() {
// remove the compiled bundles directory so that any scripts we use will be recompiled
wipe(GhidraSourceBundle.getCompiledBundlesDir());
delete(GhidraSourceBundle.getCompiledBundlesDir());
String myTestName = super.testName.getMethodName();
@ -1559,12 +1546,12 @@ public abstract class AbstractGhidraScriptMgrPluginTest
@Override
public void taskAdded(Task task) {
Msg.debug(this, "taskAdded(): " + task.getTaskTitle());
Msg.trace(this, "taskAdded(): " + task.getTaskTitle());
}
@Override
public void taskRemoved(Task task) {
Msg.debug(this, "taskRemoved(): " + task.getTaskTitle());
Msg.trace(this, "taskRemoved(): " + task.getTaskTitle());
if (taskName.equals(task.getTaskTitle())) {
ended = true;
}
@ -1657,13 +1644,13 @@ public abstract class AbstractGhidraScriptMgrPluginTest
@Override
public void println(String msg) {
apiBuffer.append(msg).append('\n');
Msg.debug(this, "Spy Script Console - println(): " + msg);
Msg.trace(this, "Spy Script Console - println(): " + msg);
}
@Override
public void addMessage(String originator, String msg) {
apiBuffer.append(msg).append('\n');
Msg.debug(this, "Spy Script Console - addMessage(): " + msg);
Msg.trace(this, "Spy Script Console - addMessage(): " + msg);
}
String getApiOutput() {

View file

@ -23,7 +23,6 @@ import java.nio.file.Path;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import org.junit.*;
import org.osgi.framework.Bundle;
@ -31,27 +30,23 @@ import org.osgi.framework.Bundle;
import generic.jar.ResourceFile;
import ghidra.app.plugin.core.osgi.*;
import ghidra.test.AbstractGhidraHeadlessIntegrationTest;
import utilities.util.FileUtilities;
public class BundleHostTest extends AbstractGhidraHeadlessIntegrationTest {
BundleHost bundleHost;
CapturingBundleHostListener capturingBundleHostListener;
private static final String TEMP_NAME_PREFIX = "sourcebundle";
private BundleHost bundleHost;
private CapturingBundleHostListener capturingBundleHostListener;
Set<Path> tempDirs = new HashSet<>();
LinkedList<GhidraBundle> bundleStack = new LinkedList<>();
GhidraBundle currentBundle;
private Set<Path> tempDirs = new HashSet<>();
private LinkedList<GhidraBundle> bundleStack = new LinkedList<>();
private GhidraBundle currentBundle;
protected static void wipe(Path path) throws IOException {
if (Files.exists(path)) {
try (Stream<Path> walk = Files.walk(path)) {
for (Path p : (Iterable<Path>) walk.sorted(Comparator.reverseOrder())::iterator) {
Files.deleteIfExists(p);
}
}
}
private static void wipe(Path path) {
FileUtilities.deleteDir(path);
}
protected GhidraBundle pushNewBundle() throws IOException {
String dir = String.format("sourcebundle%03d", tempDirs.size());
private GhidraBundle pushNewBundle() throws IOException {
String dir = String.format(TEMP_NAME_PREFIX + "%03d", tempDirs.size());
Path tmpDir = new File(getTestDirectoryPath(), dir).toPath();
Files.createDirectories(tmpDir);
tempDirs.add(tmpDir);
@ -62,7 +57,7 @@ public class BundleHostTest extends AbstractGhidraHeadlessIntegrationTest {
return currentBundle;
}
static class CapturingBundleHostListener implements BundleHostListener {
private static class CapturingBundleHostListener implements BundleHostListener {
String lastBuildSummary;
@Override
@ -76,6 +71,7 @@ public class BundleHostTest extends AbstractGhidraHeadlessIntegrationTest {
@Before
public void setup() throws OSGiException, IOException {
wipe(GhidraSourceBundle.getCompiledBundlesDir());
deleteSimilarTempFiles(TEMP_NAME_PREFIX);
bundleHost = new BundleHost();
bundleHost.startFramework();
@ -86,7 +82,7 @@ public class BundleHostTest extends AbstractGhidraHeadlessIntegrationTest {
}
@After
public void tearDown() throws IOException {
public void tearDown() {
bundleHost.dispose();
capturingBundleHostListener = null;
bundleHost = null;
@ -96,7 +92,7 @@ public class BundleHostTest extends AbstractGhidraHeadlessIntegrationTest {
}
}
protected void buildWithExpectations(String expectedCompilerOutput, String expectedSummary)
private void buildWithExpectations(String expectedCompilerOutput, String expectedSummary)
throws Exception {
StringWriter stringWriter = new StringWriter();
@ -110,32 +106,32 @@ public class BundleHostTest extends AbstractGhidraHeadlessIntegrationTest {
capturingBundleHostListener.lastBuildSummary);
}
protected void activate() throws Exception {
private void activate() throws Exception {
Bundle bundle = bundleHost.install(currentBundle);
assertNotNull("failed to install bundle", bundle);
bundle.start();
}
protected void buildAndActivate() throws Exception {
private void buildAndActivate() throws Exception {
buildWithExpectations("", "");
activate();
}
protected Class<?> loadClass(String classname) throws ClassNotFoundException {
private Class<?> loadClass(String classname) throws ClassNotFoundException {
Class<?> clazz = currentBundle.getOSGiBundle().loadClass(classname);
assertNotNull("failed to load class", clazz);
return clazz;
}
protected void addClass(String fullclassname, String body) throws IOException {
private void addClass(String fullclassname, String body) throws IOException {
addClass("", fullclassname, body);
}
protected void addClass(String imports, String fullclassname, String body) throws IOException {
private void addClass(String imports, String fullclassname, String body) throws IOException {
addClass("", imports, fullclassname, body);
}
protected void addClass(String meta, String imports, String fullclassname, String body)
private void addClass(String meta, String imports, String fullclassname, String body)
throws IOException {
String simplename;
Path tmpsource = currentBundle.getFile().getFile(false).toPath();
@ -172,7 +168,7 @@ public class BundleHostTest extends AbstractGhidraHeadlessIntegrationTest {
}
protected Object getInstance(String classname) throws Exception {
private Object getInstance(String classname) throws Exception {
Class<?> clazz = loadClass(classname);
Object object = clazz.getDeclaredConstructor().newInstance();
assertNotNull("failed to create instance", object);

View file

@ -65,155 +65,13 @@ public class BundleStatusManagerTest extends AbstractGhidraScriptMgrPluginTest {
provider.getBundleHost().removeListener(testBundleHostListener);
}
protected static String translateSeperators(String path) {
if (!File.separator.equals("/")) {
return path.replace("/", File.separator);
}
return path;
}
void enableViaGUI(int viewRow) throws InterruptedException {
testBundleHostListener.reset();
runSwing(() -> {
bundleStatusTable.setValueAt(true, viewRow, 0);
});
waitForSwing();
// we wait for the last event, the activation of the bundle.
testBundleHostListener.awaitActivation();
}
void disableViaGUI(int viewRow) throws InterruptedException {
testBundleHostListener.reset();
runSwing(() -> {
bundleStatusTable.setValueAt(false, viewRow, 0);
});
waitForSwing();
testBundleHostListener.awaitDisablement();
}
List<BundleStatus> selectRows(int... viewRows) {
List<BundleStatus> statuses = Arrays.stream(viewRows)
.mapToObj(bundleStatusTableModel::getRowObject)
.collect(Collectors.toList());
for (BundleStatus status : statuses) {
assertNotNull(status);
}
runSwing(() -> {
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;
do {
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());
}
}
/**
* Find the view row index in the BundleStatusTableModel of the status with the given bundle path, or
* -1 if it's not found.
*
* @param bundlePath bundle path to find
* @return view row index or -1 if not found
*/
protected int getBundleRow(String bundlePath) {
AtomicInteger rowref = new AtomicInteger(-1);
runSwing(() -> {
for (int i = 0; i < bundleStatusTableModel.getRowCount(); i++) {
BundleStatus status = bundleStatusTableModel.getRowObject(i);
if (bundlePath.equals(status.getPathAsString())) {
rowref.set(i);
break;
}
}
});
return rowref.get();
}
@SuppressWarnings("unchecked")
DockingActionIf getActionByName(ComponentProvider componentProvider, String actionName) {
Set<DockingActionIf> actionSet =
(Set<DockingActionIf>) getInstanceField("actionSet", bundleStatusProvider);
for (DockingActionIf action : actionSet) {
if (action.getName().equals(actionName)) {
return action;
}
}
return null;
}
@Test
public void testDisableEnableScriptDirectory() throws Exception {
//
// Tests that the user can disable then enable a script directory
//
int viewRow = getBundleRow(BUNDLE_PATH);
assertTrue(viewRow != -1);
runSwing(() -> {
bundleStatusTable.selectRow(viewRow);
bundleStatusTable.scrollToSelectedRow();
});
waitForSwing();
selectRow(viewRow);
BundleStatus status = bundleStatusTableModel.getRowObject(viewRow);
@ -222,78 +80,22 @@ public class BundleStatusManagerTest extends AbstractGhidraScriptMgrPluginTest {
assertTrue(status.isEnabled());
assertScriptInTable(scriptFile);
// disable it
disableViaGUI(viewRow);
assertTrue(!status.isEnabled());
assertScriptNotInTable(scriptFile);
// re-enable it
enableViaGUI(viewRow);
assertTrue(status.isEnabled());
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);
assertNotEquals(viewRow, -1);
assertTrue(viewRow != -1);
runSwing(() -> {
bundleStatusTable.selectRow(viewRow);
bundleStatusTable.scrollToSelectedRow();
});
waitForSwing();
selectRows(viewRow);
BundleStatus status = bundleStatusTableModel.getRowObject(viewRow);
@ -302,18 +104,15 @@ public class BundleStatusManagerTest extends AbstractGhidraScriptMgrPluginTest {
assertTrue(status.isEnabled());
assertScriptInTable(scriptFile);
// run
runScript(SCRIPT_NAME);
// clean
cleanViaGUI(viewRow);
// run
runScript(SCRIPT_NAME);
}
@Test
public void addRunCleanRemoveTwoBundles() throws Exception {
public void testAddRunCleanRemoveTwoBundles() throws Exception {
final String TEST_SCRIPT_NAME = testName.getMethodName();
//@formatter:off
@ -385,30 +184,227 @@ public class BundleStatusManagerTest extends AbstractGhidraScriptMgrPluginTest {
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);
int row1 = getBundleRow(dir1);
int row2 = getBundleRow(dir2);
assertNotEquals(row1, -1);
assertNotEquals(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);
row1 = getBundleRow(dir1);
row2 = getBundleRow(dir2);
assertEquals(row1, -1);
assertEquals(row2, -1);
}
finally {
wipe(dir1.toPath());
wipe(dir2.toPath());
delete(dir1.toPath());
delete(dir2.toPath());
}
}
private static String translateSeperators(String path) {
if (!File.separator.equals("/")) {
return path.replace("/", File.separator);
}
return path;
}
private void enableViaGUI(int viewRow) throws InterruptedException {
testBundleHostListener.reset();
runSwing(() -> {
bundleStatusTable.setValueAt(true, viewRow, 0);
});
waitForSwing();
// we wait for the last event, the activation of the bundle.
testBundleHostListener.awaitActivation();
}
private void disableViaGUI(int viewRow) throws InterruptedException {
testBundleHostListener.reset();
runSwing(() -> {
bundleStatusTable.setValueAt(false, viewRow, 0);
});
waitForSwing();
testBundleHostListener.awaitDisablement();
}
private void selectRow(int viewRow) {
assertNotEquals(viewRow, -1);
runSwing(() -> {
bundleStatusTable.selectRow(viewRow);
bundleStatusTable.scrollToSelectedRow();
});
waitForSwing();
}
private List<BundleStatus> selectRows(int... viewRows) {
List<BundleStatus> statuses = Arrays.stream(viewRows)
.mapToObj(bundleStatusTableModel::getRowObject)
.collect(Collectors.toList());
for (BundleStatus status : statuses) {
assertNotNull(status);
}
runSwing(() -> {
bundleStatusTable.clearSelection();
for (int viewRow : viewRows) {
bundleStatusTable.addRowSelectionInterval(viewRow, viewRow);
}
});
return statuses;
}
private 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;
do {
if (statuses.size() <= initialSize - viewRows.length) {
break;
}
Thread.sleep(250);
}
while (++count < 8);
assertTrue("Failure, clean took too long", count < 8);
}
private 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());
}
}
private int getBundleRow(File dir) {
return getBundleRow(generic.util.Path.toPathString(new ResourceFile(dir)));
}
/**
* Find the view row index in the BundleStatusTableModel of the status with the given bundle path, or
* -1 if it's not found.
*
* @param bundlePath bundle path to find
* @return view row index or -1 if not found
*/
private int getBundleRow(String bundlePath) {
AtomicInteger rowref = new AtomicInteger(-1);
runSwing(() -> {
for (int i = 0; i < bundleStatusTableModel.getRowCount(); i++) {
BundleStatus status = bundleStatusTableModel.getRowObject(i);
if (bundlePath.equals(status.getPathAsString())) {
rowref.set(i);
break;
}
}
});
return rowref.get();
}
@SuppressWarnings("unchecked")
private DockingActionIf getActionByName(ComponentProvider componentProvider,
String actionName) {
Set<DockingActionIf> actionSet =
(Set<DockingActionIf>) getInstanceField("actionSet", bundleStatusProvider);
for (DockingActionIf action : actionSet) {
if (action.getName().equals(actionName)) {
return action;
}
}
return null;
}
/**
* 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
*/
private 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]));
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
public 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;
}
/**
* A {@link BundleHostListener} to help serialize bundle operations.
*/
protected class TestBundleHostListener implements BundleHostListener {
private class TestBundleHostListener implements BundleHostListener {
CountDownLatch activationLatch;
CountDownLatch disablementLatch;

View file

@ -631,6 +631,7 @@ public class SearchTextPlugin2Test extends AbstractGhidraHeadedIntegrationTest {
waitForSearchTasks(tempDialog);
waitForSwing();
assertFalse(tempDialog.isVisible());
assertFalse(isEnabled(searchAction));
}

View file

@ -112,6 +112,8 @@ public class KeyBindingUtilsTest extends AbstractGhidraHeadedIntegrationTest {
tool.addPlugin(DataPlugin.class.getName());
tool.addPlugin(FunctionPlugin.class.getName());
env.showTool();
debug("two");
}

View file

@ -15,8 +15,7 @@
*/
package ghidra.program.database.map;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.*;
import java.util.Iterator;
@ -429,7 +428,7 @@ public class NormalizedAddressSetTest extends AbstractGhidraHeadedIntegrationTes
AddressIterator iter = set.getAddresses(false);
while (iter.hasNext()) {
Address addr = iter.next();
System.out.println(addr.toString(true));
assertEquals(checkAddress, addr);
if (checkAddress.equals(addr(0xffffffccL))) {
checkAddress = addr(0xffffffbbL);

View file

@ -15,8 +15,7 @@
*/
package ghidra.program.database.util;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.*;
import java.io.IOException;
import java.util.Iterator;
@ -30,10 +29,12 @@ import ghidra.util.datastruct.IndexRange;
import ghidra.util.datastruct.IndexRangeIterator;
@SuppressWarnings("deprecation") // the SharedRangeMapDB is deprecated, but we still need to test it
public class SharedRangeMapDBTest extends AbstractGhidraHeadedIntegrationTest implements ErrorHandler {
public class SharedRangeMapDBTest extends AbstractGhidraHeadedIntegrationTest
implements ErrorHandler {
private DBHandle dbh;
private long transactionID;
/**
* Constructor for SharedRangeMapDBTest.
* @param arg0
@ -65,15 +66,17 @@ public class SharedRangeMapDBTest extends AbstractGhidraHeadedIntegrationTest im
/**
* @see db.util.ErrorHandler#dbError(java.io.IOException)
*/
@Override
public void dbError(IOException e) {
throw new RuntimeException(e.getMessage());
}
private int indexOf(Object[] list, Object item) {
for (int i = 0; i < list.length; i++) {
if (list[i].equals(item))
if (list[i].equals(item)) {
return i;
}
}
return -1;
}
@ -85,17 +88,19 @@ public class SharedRangeMapDBTest extends AbstractGhidraHeadedIntegrationTest im
* @param mapRangeToValue (from is rangeKey, to is value)
* @throws IOException
*/
private void inspectRecords(SharedRangeMapDB map, IndexRange[] ranges, IndexRange[] mapRangeToValue) throws IOException {
System.out.println("Inspecting---");
private void inspectRecords(SharedRangeMapDB map, IndexRange[] ranges,
IndexRange[] mapRangeToValue) throws IOException {
RecordIterator iter = map.rangeTable.iterator();
int cnt = 0;
while (iter.hasNext()) {
++cnt;
Record rec = iter.next();
IndexRange range = new IndexRange(rec.getKey(), rec.getLongValue(SharedRangeMapDB.RANGE_TO_COL));
if (indexOf(ranges, range) < 0)
IndexRange range =
new IndexRange(rec.getKey(), rec.getLongValue(SharedRangeMapDB.RANGE_TO_COL));
if (indexOf(ranges, range) < 0) {
Assert.fail("Unexpected range: " + range.getStart() + " - " + range.getEnd());
System.out.println(" Range: " + range.getStart() + " - " + range.getEnd());
}
}
assertEquals(ranges.length, cnt);
@ -106,14 +111,15 @@ System.out.println(" Range: " + range.getStart() + " - " + range.getEnd());
Record rec = iter.next();
IndexRange entry = new IndexRange(rec.getLongValue(SharedRangeMapDB.MAP_RANGE_KEY_COL),
rec.getLongValue(SharedRangeMapDB.MAP_VALUE_COL));
if (indexOf(mapRangeToValue, entry) < 0)
Assert.fail("Unexpected map entry: rangeKey=" + entry.getStart() + ", value=" + entry.getEnd());
System.out.println(" Map entry: rangeKey=" + entry.getStart() + ", value=" + entry.getEnd());
if (indexOf(mapRangeToValue, entry) < 0) {
Assert.fail("Unexpected map entry: rangeKey=" + entry.getStart() + ", value=" +
entry.getEnd());
}
}
assertEquals(mapRangeToValue.length, cnt);
}
@Test
@Test
public void testAdd() throws IOException {
SharedRangeMapDB map = new SharedRangeMapDB(dbh, "TEST", this, true);
@ -124,10 +130,10 @@ System.out.println(" Map entry: rangeKey=" + entry.getStart() + ", value=" + en
map.add(70, 80, 1);
IndexRange[] ranges = new IndexRange[] {
new IndexRange(10,20),
new IndexRange(30,40),
new IndexRange(50,60),
new IndexRange(70,80)
new IndexRange(10, 20),
new IndexRange(30, 40),
new IndexRange(50, 60),
new IndexRange(70, 80)
};
IndexRange[] entries = new IndexRange[] {
@ -150,9 +156,9 @@ System.out.println(" Map entry: rangeKey=" + entry.getStart() + ", value=" + en
map.add(21, 29, 1);
ranges = new IndexRange[] {
new IndexRange(10,40),
new IndexRange(50,60),
new IndexRange(70,80)
new IndexRange(10, 40),
new IndexRange(50, 60),
new IndexRange(70, 80)
};
entries = new IndexRange[] {
@ -167,8 +173,8 @@ System.out.println(" Map entry: rangeKey=" + entry.getStart() + ", value=" + en
map.add(35, 55, 1);
ranges = new IndexRange[] {
new IndexRange(10,60),
new IndexRange(70,80)
new IndexRange(10, 60),
new IndexRange(70, 80)
};
entries = new IndexRange[] {
@ -182,7 +188,7 @@ System.out.println(" Map entry: rangeKey=" + entry.getStart() + ", value=" + en
map.add(55, 90, 1);
ranges = new IndexRange[] {
new IndexRange(10,90)
new IndexRange(10, 90)
};
entries = new IndexRange[] {
@ -296,7 +302,7 @@ System.out.println(" Map entry: rangeKey=" + entry.getStart() + ", value=" + en
inspectRecords(map, ranges, entries);
}
@Test
@Test
public void testRemove() throws IOException {
SharedRangeMapDB map = new SharedRangeMapDB(dbh, "TEST", this, true);
@ -365,7 +371,7 @@ System.out.println(" Map entry: rangeKey=" + entry.getStart() + ", value=" + en
map.remove(2);
ranges = new IndexRange[] {
new IndexRange(10,90)
new IndexRange(10, 90)
};
entries = new IndexRange[] {
@ -381,7 +387,7 @@ System.out.println(" Map entry: rangeKey=" + entry.getStart() + ", value=" + en
}
@Test
@Test
public void testGetValueIterator() {
SharedRangeMapDB map = new SharedRangeMapDB(dbh, "TEST", this, true);
@ -414,10 +420,11 @@ System.out.println(" Map entry: rangeKey=" + entry.getStart() + ", value=" + en
int cnt = 0;
while (iter.hasNext()) {
++cnt;
LongField v = (LongField)iter.next();
if (indexOf(values, v) < 0)
LongField v = (LongField) iter.next();
if (indexOf(values, v) < 0) {
Assert.fail("Unexpected value: " + v.getLongValue());
}
}
assertEquals(values.length, cnt);
// Test 2
@ -429,10 +436,11 @@ System.out.println(" Map entry: rangeKey=" + entry.getStart() + ", value=" + en
cnt = 0;
while (iter.hasNext()) {
++cnt;
LongField v = (LongField)iter.next();
if (indexOf(values, v) < 0)
LongField v = (LongField) iter.next();
if (indexOf(values, v) < 0) {
Assert.fail("Unexpected value: " + v.getLongValue());
}
}
assertEquals(values.length, cnt);
// Test 3
@ -443,23 +451,23 @@ System.out.println(" Map entry: rangeKey=" + entry.getStart() + ", value=" + en
cnt = 0;
while (iter.hasNext()) {
++cnt;
LongField v = (LongField)iter.next();
if (indexOf(values, v) < 0)
LongField v = (LongField) iter.next();
if (indexOf(values, v) < 0) {
Assert.fail("Unexpected value: " + v.getLongValue());
}
}
assertEquals(values.length, cnt);
// Test 4
iter = map.getValueIterator(0, 9);
assertTrue(!iter.hasNext());
}
@Test
@Test
public void testGetValueRangeIterator() {
SharedRangeMapDB map = new SharedRangeMapDB(dbh, "TEST", this, true);
System.out.println("testGetValueRangeIterator ---");
System.out.println("testGetValueRangeIterator ---");
// Add same entries as the testAdd used
map.add(10, 20, 1);
map.add(30, 40, 1);
@ -490,9 +498,10 @@ System.out.println("testGetValueRangeIterator ---");
while (iter.hasNext()) {
++cnt;
IndexRange range = iter.next();
if (indexOf(ranges, range) < 0)
if (indexOf(ranges, range) < 0) {
Assert.fail("Unexpected range: " + range.getStart() + " - " + range.getEnd());
System.out.println(" Range: " + range.getStart() + " - " + range.getEnd());
}
System.out.println(" Range: " + range.getStart() + " - " + range.getEnd());
}
assertEquals(ranges.length, cnt);

View file

@ -60,6 +60,7 @@ import ghidra.test.TestEnv;
*/
public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
private static final String MY_PATH_NAME_OPTION_NAME = "My PathName";
private static final String TOOL_NODE_NAME = "Tool";
private PluginTool tool;
private TestEnv env;
@ -281,7 +282,7 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
ScrollableOptionsEditor editor = showOptions(ToolConstants.TOOL_OPTIONS);
pressBrowseButton(editor, "My PathName");
pressBrowseButton(editor, MY_PATH_NAME_OPTION_NAME);
GhidraFileChooser chooser = waitForDialogComponent(GhidraFileChooser.class);
assertNotNull(chooser);
@ -297,7 +298,7 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
pressButton(openButton);
waitForSwing();
JTextField pathField = getEditorTextField(editor, "My PathName");
JTextField pathField = getEditorTextField(editor, MY_PATH_NAME_OPTION_NAME);
assertEquals(file.getAbsolutePath(), pathField.getText());
}
@ -305,7 +306,7 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
public void testFileChooserEditor_ClearValue() throws Exception {
ScrollableOptionsEditor editor = showOptions(ToolConstants.TOOL_OPTIONS);
JTextField pathField = getEditorTextField(editor, "My PathName");
JTextField pathField = getEditorTextField(editor, MY_PATH_NAME_OPTION_NAME);
setText(pathField, "");
@ -313,7 +314,7 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
showOptionsDialog(tool);
editor = showOptions(ToolConstants.TOOL_OPTIONS);
pathField = getEditorTextField(editor, "My PathName");
pathField = getEditorTextField(editor, MY_PATH_NAME_OPTION_NAME);
assertEquals("", pathField.getText());
}
@ -1059,10 +1060,13 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
// register this options because it is used in a test that saves and restores and
// only registered options are saved.
String myOptionsName = "My Options" + Options.DELIMITER;
options.registerOption(myOptionsName + "my sub group Boolean Value", true, null, null);
options.registerOption(myOptionsName + "my sub group Boolean Value", true, null,
"description");
options.registerOption("My PathName", OptionType.FILE_TYPE, null, null, "");
options.setFile("My PathName", new File(System.getProperty("user.dir")));
File file = new File(System.getProperty("user.dir"));
options.registerOption(MY_PATH_NAME_OPTION_NAME, OptionType.FILE_TYPE, file, null,
"description");
options.setFile(MY_PATH_NAME_OPTION_NAME, file);
// the following "get" methods set a value
options.getInt(myOptionsName + "my sub group" + Options.DELIMITER + "My Test Value", 10);
@ -1071,19 +1075,29 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
String intOptionName = myOptionsName + "my sub group" + Options.DELIMITER + "Group A" +
Options.DELIMITER + "Second Int Value";
options.registerOption(intOptionName, 50, null, "description");
options.setInt(intOptionName, 50);
options.setBoolean(myOptionsName + "my sub group" + Options.DELIMITER + "Group A" +
Options.DELIMITER + "First boolean value", true);
options.setInt(
String name = myOptionsName + "my sub group" + Options.DELIMITER + "Group A" +
Options.DELIMITER + "First boolean value";
options.registerOption(name, true, null, "description");
options.setBoolean(name, true);
name =
"New Options" + Options.DELIMITER + " subgroup A" + Options.DELIMITER + " subgroup B" +
Options.DELIMITER + " subgroup C" + Options.DELIMITER + "Another int value",
300);
Options.DELIMITER + " subgroup C" + Options.DELIMITER + "Another int value";
options.registerOption(name, 300, null, "description");
options.setInt(name, 300);
options.setColor("Favorite Color", Color.RED);
name = "Favorite Color";
options.registerOption(name, Color.RED, null, "description");
options.setColor(name, Color.RED);
// select the middle button
options.setEnum("Mouse Buttons" + Options.DELIMITER + "Mouse Button To Activate",
name = "Mouse Buttons" + Options.DELIMITER + "Mouse Button To Activate";
options.registerOption(name, GhidraOptions.CURSOR_MOUSE_BUTTON_NAMES.MIDDLE, null,
"description");
options.setEnum(name,
GhidraOptions.CURSOR_MOUSE_BUTTON_NAMES.MIDDLE);
}

View file

@ -28,7 +28,7 @@ import ghidra.program.model.listing.ProgramChangeSet;
import ghidra.test.TestEnv;
import ghidra.util.InvalidNameException;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitorAdapter;
import ghidra.util.task.TaskMonitor;
/**
* This model is used by the {@link MergeTestFacilitator} to configure programs needed
@ -62,8 +62,8 @@ public abstract class AbstractMTFModel {
* This represents the original checked-out version.
* Program returned will be released by the MergeTestFacilitator
* when disposed or re-initialized.
* @return the program
*/
public ProgramDB getOriginalProgram() {
return originalProgram;
}
@ -73,8 +73,8 @@ public abstract class AbstractMTFModel {
* This represents the current version.
* Program returned will be released by the MergeTestFacilitator
* when disposed or re-initialized.
* @return the program
*/
public ProgramDB getLatestProgram() {
return latestProgram;
}
@ -84,8 +84,8 @@ public abstract class AbstractMTFModel {
* This represents the local program to be checked-in.
* Program returned will be released by the MergeTestFacilitator
* when disposed or re-initialized.
* @return the program
*/
public ProgramDB getPrivateProgram() {
return privateProgram;
}
@ -95,8 +95,8 @@ public abstract class AbstractMTFModel {
* This represents the checkin program containing the merged data.
* Program returned will be released by the MergeTestFacilitator
* when disposed or re-initialized.
* @return the program
*/
public ProgramDB getResultProgram() {
return resultProgram;
}
@ -129,7 +129,7 @@ public abstract class AbstractMTFModel {
BufferFile bufferFile = item.open();
try {
fileSystem.createDatabase(parent.getPathname(), newName, FileIDFactory.createFileID(),
bufferFile, null, item.getContentType(), false, TaskMonitorAdapter.DUMMY_MONITOR,
bufferFile, null, item.getContentType(), false, TaskMonitor.DUMMY,
null);
}
finally {
@ -141,6 +141,7 @@ public abstract class AbstractMTFModel {
}
protected void cleanup() {
if (originalProgram != null) {
originalProgram.release(this);
originalProgram = null;
@ -154,6 +155,8 @@ public abstract class AbstractMTFModel {
privateProgram = null;
}
if (resultProgram != null) {
resultProgram.flushEvents();
AbstractGenericTest.waitForSwing();
resultProgram.release(this);
resultProgram = null;
}

View file

@ -734,6 +734,8 @@ public class FunctionGraphPlugin1Test extends AbstractFunctionGraphTest {
setNavigationHistoryOption(NavigationHistoryChoices.NAVIGATION_EVENTS);
clearHistory();
FGVertex v1 = vertex("01004178");
pickVertex(v1);
@ -743,17 +745,18 @@ public class FunctionGraphPlugin1Test extends AbstractFunctionGraphTest {
FGVertex v3 = vertex("010041a4");
pickVertex(v3);
// in this navigation mode, merely selecting nodes does *not* put previous nodes in history
assertNotInHistory(v1, v2);
//
// Now leave the function and verify the old function is in the history
// Perform a navigation action (e.g., goTo()) and verify the old function is in the history
//
Address ghidra = getAddress("0x01002cf5");
goTo(ghidra);
assertInHistory(v3.getVertexAddress());
Address foo = getAddress("0x01002339");
goTo(foo);
assertInHistory(v3.getVertexAddress(), ghidra);
}
@ -785,6 +788,14 @@ public class FunctionGraphPlugin1Test extends AbstractFunctionGraphTest {
}
}
private void clearHistory() {
GoToService goTo = tool.getService(GoToService.class);
Navigatable navigatable = goTo.getDefaultNavigatable();
NavigationHistoryService service = tool.getService(NavigationHistoryService.class);
service.clear(navigatable);
}
private List<LocationMemento> getNavigationHistory() {
GoToService goTo = tool.getService(GoToService.class);
@ -823,8 +834,8 @@ public class FunctionGraphPlugin1Test extends AbstractFunctionGraphTest {
for (Address a : expectedAddresses) {
assertTrue("Vertex address should be in the history list: " + a + ".\nHistory: " +
actualAddresses + "\nNavigated vertices: " + expectedAddresses,
assertTrue("Vertex address should be in the history list: " + a + ".\nActual: " +
actualAddresses + "\nExpected: " + expectedAddresses,
actualAddresses.contains(a));
}
}

View file

@ -29,10 +29,10 @@ import javax.security.auth.spi.LoginModule;
import com.sun.security.auth.UserPrincipal;
import generic.concurrent.io.ProcessConsumer;
import ghidra.server.RepositoryManager;
import ghidra.util.DateUtils;
import ghidra.util.timer.Watchdog;
import utilities.util.FileUtilities;
/**
* A JAAS {@link LoginModule} that executes an external program that decides if the username
@ -208,10 +208,11 @@ public class ExternalProgramLoginModule implements LoginModule {
Process p = Runtime.getRuntime().exec(cmdArray);
process.set(p);
FileUtilities.asyncForEachLine(p.getInputStream(), (stdOutStr) -> {
ProcessConsumer.consume(p.getInputStream(), stdOutStr -> {
RepositoryManager.log(null, null, extProgramName + " STDOUT: " + stdOutStr, null);
});
FileUtilities.asyncForEachLine(p.getErrorStream(), (errStr) -> {
ProcessConsumer.consume(p.getErrorStream(), errStr -> {
RepositoryManager.log(null, null, extProgramName + " STDERR: " + errStr, null);
});
@ -266,8 +267,12 @@ public class ExternalProgramLoginModule implements LoginModule {
}
extProgramName = extProFile.getName();
List<String> argKeys = options.keySet().stream().filter(
key -> key.startsWith(ARG_OPTION_NAME)).sorted().collect(Collectors.toList());
List<String> argKeys = options.keySet()
.stream()
.filter(
key -> key.startsWith(ARG_OPTION_NAME))
.sorted()
.collect(Collectors.toList());
List<String> cmdArrayValues = new ArrayList<>();
cmdArrayValues.add(externalProgram.toString());
for (String argKey : argKeys) {

View file

@ -2245,13 +2245,26 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
// Unable to find the manager. This can happen during testing; only report if
// it is unexpected
if (!instances.isEmpty()) {
maybeReportMissingManager();
}
private void maybeReportMissingManager() {
if (instances.isEmpty()) {
return; // not manager means no tool; assume testing
}
if (instances.size() == 1) {
DockingWindowManager dwm = instances.get(0);
if (!dwm.isVisible()) {
return; // not showing; assume testing with tool not shown
}
}
Msg.debug(DockingWindowManager.class,
"Unable to find Docking Window Manager for " +
component.getClass().getSimpleName(),
ReflectionUtilities.createJavaFilteredThrowable());
}
}
});
}

View file

@ -2102,40 +2102,34 @@ public abstract class AbstractDockingTest extends AbstractGenericTest {
}
public static boolean isEnabled(DockingActionIf action) {
AtomicBoolean ref = new AtomicBoolean();
runSwing(() -> ref.set(action.isEnabledForContext(new ActionContext())));
return ref.get();
return runSwing(() -> action.isEnabledForContext(new ActionContext()));
}
public static boolean isEnabled(AbstractButton button) {
AtomicBoolean ref = new AtomicBoolean();
runSwing(() -> ref.set(button.isEnabled()));
return ref.get();
return runSwing(() -> button.isEnabled());
}
public static boolean isSelected(AbstractButton button) {
AtomicBoolean ref = new AtomicBoolean();
runSwing(() -> ref.set(button.isSelected()));
return ref.get();
return runSwing(() -> button.isSelected());
}
/**
* Creates a generic action context with no provider, with the given payload
* @param payload the generic object to put in the context
* Creates a generic action context with no provider, with the given context object
* @param contextObject the generic object to put in the context
* @return the new context
*/
public ActionContext createContext(Object payload) {
return new ActionContext().setContextObject(payload);
public ActionContext createContext(Object contextObject) {
return new ActionContext().setContextObject(contextObject);
}
/**
* Creates a generic action context with the given provider, with the given payload
* Creates a generic action context with the given provider, with the given context object
* @param provider the provider
* @param payload the generic object to put in the context
* @param contextObject the generic object to put in the context
* @return the new context
*/
public ActionContext createContext(ComponentProvider provider, Object payload) {
return new ActionContext(provider).setContextObject(payload);
public ActionContext createContext(ComponentProvider provider, Object contextObject) {
return new ActionContext(provider).setContextObject(contextObject);
}
//==================================================================================================

View file

@ -0,0 +1,81 @@
/* ###
* 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 generic.concurrent.io;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import ghidra.util.Msg;
import utilities.util.reflection.ReflectionUtilities;
import utility.function.Dummy;
/**
* Class to pass to a thread pool that will consume all output from an external process. This is
* a {@link Runnable} that get submitted to a thread pool. This class records the data it reads
*/
public class IOResult implements Runnable {
public static final String THREAD_POOL_NAME = "I/O Thread Pool";
private List<String> outputLines = new ArrayList<String>();
private BufferedReader commandOutput;
private final Throwable inception;
private Consumer<String> consumer = Dummy.consumer();
public IOResult(InputStream input) {
this(ReflectionUtilities.createThrowableWithStackOlderThan(IOResult.class), input);
}
public IOResult(Throwable inception, InputStream input) {
this.inception = inception;
commandOutput = new BufferedReader(new InputStreamReader(input));
}
public void setConsumer(Consumer<String> consumer) {
this.consumer = consumer;
}
public String getOutputAsString() {
StringBuilder buffy = new StringBuilder();
for (String line : outputLines) {
buffy.append(line);
}
return buffy.toString();
}
public List<String> getOutput() {
return outputLines;
}
@Override
public void run() {
String line = null;
try {
while ((line = commandOutput.readLine()) != null) {
consumer.accept(line);
outputLines.add(line);
}
}
catch (Exception e) {
String inceptionString = ReflectionUtilities.stackTraceToString(inception);
Msg.debug(IOResult.class,
"Exception reading output from process. Created from:\n" + inceptionString, e);
}
}
}

View file

@ -0,0 +1,70 @@
/* ###
* 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 generic.concurrent.io;
import java.io.InputStream;
import java.util.concurrent.Future;
import java.util.function.Consumer;
import generic.concurrent.GThreadPool;
import utilities.util.reflection.ReflectionUtilities;
import utility.function.Dummy;
/**
* A class that allows clients to <b>asynchronously</b> consume the output of a {@link Process}s
* input and error streams. The task is asynchronous to avoid deadlocks when both streams need
* to be read in order for the process to proceed.
*/
public class ProcessConsumer {
/**
* Read the given input stream line-by-line.
*
* <p>To get all output after all reading is done you can call the blocking operation
* {@link Future#get()}.
*
* @param is the input stream
* @return the future that will be complete when all lines are read
*/
public static Future<IOResult> consume(InputStream is) {
return consume(is, Dummy.consumer());
}
/**
* Read the given input stream line-by-line.
*
* <p>If you wish to get all output after all reading is done you can call the blocking
* operation {@link Future#get()}.
*
* @param is the input stream
* @param lineConsumer the line consumer; may be null
* @return the future that will be complete when all lines are read
*/
public static Future<IOResult> consume(InputStream is,
Consumer<String> lineConsumer) {
lineConsumer = Dummy.ifNull(lineConsumer);
Throwable inception = ReflectionUtilities.filterJavaThrowable(
ReflectionUtilities.createThrowableWithStackOlderThan(ProcessConsumer.class));
GThreadPool pool = GThreadPool.getSharedThreadPool(IOResult.THREAD_POOL_NAME);
IOResult runnable = new IOResult(inception, is);
runnable.setConsumer(lineConsumer);
Future<IOResult> future = pool.submit(runnable, runnable);
return future;
}
}

View file

@ -26,6 +26,7 @@ import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.*;
import java.util.function.Consumer;
import java.util.stream.Stream;
import generic.jar.ResourceFile;
import ghidra.util.*;
@ -95,6 +96,8 @@ public final class FileUtilities {
/**
* Return an array of bytes read from the given file.
* @param sourceFile the source file
* @return the bytes
* @throws IOException if the file could not be accessed
*/
public final static byte[] getBytesFromFile(File sourceFile) throws IOException {
@ -118,6 +121,8 @@ public final class FileUtilities {
/**
* Return an array of bytes read from the given file.
* @param sourceFile the source file
* @return the bytes
* @throws IOException if the file could not be accessed
*/
public final static byte[] getBytesFromFile(ResourceFile sourceFile) throws IOException {
@ -161,7 +166,6 @@ public final class FileUtilities {
}
byte[] data = new byte[(int) length];
try (InputStream fis = sourceFile.getInputStream()) {
if (fis.skip(offset) != offset) {
throw new IOException("Did not skip to the specified offset!");
@ -383,9 +387,20 @@ public final class FileUtilities {
}
/**
* Delete a directory and all of its contents.
* Delete a file or directory and all of its contents
*
* @param dir
* @param dir the directory to delete
* @return true if delete was successful. If false is returned, a partial
* delete may have occurred.
*/
public static boolean deleteDir(Path dir) {
return deleteDir(dir.toFile());
}
/**
* Delete a file or directory and all of its contents
*
* @param dir the dir to delete
* @return true if delete was successful. If false is returned, a partial
* delete may have occurred.
*/
@ -400,11 +415,13 @@ public final class FileUtilities {
}
/**
* Delete a directory and all of its contents.
* Delete a directory and all of its contents
*
* @param dir
* @param dir the dir to delete
* @param monitor the task monitor
* @return true if delete was successful. If false is returned, a partial
* delete may have occurred.
* @throws CancelledException if the operation is cancelled
*/
public final static boolean deleteDir(File dir, TaskMonitor monitor) throws CancelledException {
File[] files = dir.listFiles();
@ -472,6 +489,12 @@ public final class FileUtilities {
/**
* This is the same as calling {@link #copyDir(File, File, FileFilter, TaskMonitor)} with
* a {@link FileFilter} that accepts all files.
* @param originalDir the source dir
* @param copyDir the destination dir
* @param monitor the task monitor
* @return the number of filed copied
* @throws IOException if there is an issue copying the files
* @throws CancelledException if the operation is cancelled
*/
public final static int copyDir(File originalDir, File copyDir, TaskMonitor monitor)
throws IOException, CancelledException {
@ -487,6 +510,7 @@ public final class FileUtilities {
* @param copyDir The directory in which the extracted contents will be placed
* @param filter a filter to apply against the directory's contents
* @param monitor the task monitor
* @return the number of filed copied
* @throws IOException if there was a problem accessing the files
* @throws CancelledException if the copy is cancelled
*/
@ -572,7 +596,7 @@ public final class FileUtilities {
return;// squash during production mode
}
Msg.debug(SystemUtilities.class, text);
Msg.debug(FileUtilities.class, text);
}
/**
@ -644,10 +668,10 @@ public final class FileUtilities {
}
/**
* Returns all of the lines in the file without any newline characters.
* Returns all of the lines in the file without any newline characters
* @param file The file to read in
* @return a list of file lines
* @throws IOException
* @throws IOException if an error occurs reading the file
*/
public static List<String> getLines(File file) throws IOException {
return getLines(new ResourceFile(file));
@ -660,7 +684,7 @@ public final class FileUtilities {
* <p>
* @param file The text file to read in
* @return a list of file lines
* @throws IOException if an error occurs trying to read the file.
* @throws IOException if an error occurs reading the file
*/
public static List<String> getLines(ResourceFile file) throws IOException {
try (InputStream is = file.getInputStream()) {
@ -857,7 +881,7 @@ public final class FileUtilities {
* @param f1 the parent file
* @param f2 the child file
* @return the portion of the second file that trails the full path of the first file.
* @throws IOException
* @throws IOException if there is an error canonicalizing the path
*/
public static String relativizePath(File f1, File f2) throws IOException {
String parentPath = f1.getCanonicalPath().replace('\\', '/');
@ -1108,7 +1132,7 @@ public final class FileUtilities {
* <p>
* TODO: why is the method using 1000 vs. 1024 for K?
*
* @param length
* @param length the length to format
* @return pretty string - "1.1KB", "5.0MB"
*/
public static String formatLength(long length) {
@ -1123,6 +1147,11 @@ public final class FileUtilities {
return formatter.format((length / 1000000f)) + "MB";
}
/**
* Creates a temporary directory using the given prefix
* @param prefix the prefix
* @return the temp file
*/
public static File createTempDirectory(String prefix) {
try {
File temp = File.createTempFile(prefix, Long.toString(System.currentTimeMillis()));
@ -1175,51 +1204,32 @@ public final class FileUtilities {
public static void openNative(File file) throws IOException {
if (!Desktop.isDesktopSupported()) {
Msg.showError(FileUtilities.class, null, "Native Desktop Unsupported",
"Access to the user's native desktop is not supported in the current environment.");
"Access to the user's native desktop is not supported in the current environment." +
"\nUnable to open file: " + file);
return;
}
Desktop.getDesktop().open(file);
}
/**
* Processes each text line in a text file, in a separate thread.
* <p>
* Thread exits when EOF is reached.
* A convenience method to list the contents of the given directory path and pass each to the
* given consumer. If the given path does not represent a directory, nothing will happen.
*
* @param is {@link InputStream} to read
* @param consumer code that will process each text of the text file.
*/
public static void asyncForEachLine(InputStream is, Consumer<String> consumer) {
asyncForEachLine(new BufferedReader(new InputStreamReader(is)), consumer);
}
/**
* Processes each text line in a text file, in a separate thread.
* <p>
* Thread exits when EOF is reached.
* <p>This method handles closing resources by using the try-with-resources construct on
* {@link Files#list(Path)}
*
* @param reader {@link BufferedReader} to read
* @param consumer code that will process each text of the text file.
* @param path the directory
* @param consumer the consumer of each child in the given directory
* @throws IOException if there is any problem reading the directory contents
*/
public static void asyncForEachLine(BufferedReader reader, Consumer<String> consumer) {
new Thread(() -> {
try {
while (true) {
String line = reader.readLine();
if (line == null) {
break;
}
consumer.accept(line);
}
}
catch (IOException ioe) {
// ignore io errors while reading because thats normal when hitting EOF
}
catch (Exception e) {
Msg.error(FileUtilities.class, "Exception while reading", e);
public static void forEachFile(Path path, Consumer<Stream<Path>> consumer) throws IOException {
if (!Files.isDirectory(path)) {
return;
}
}, "Threaded Stream Reader Thread").start();
try (Stream<Path> pathStream = Files.list(path)) {
consumer.accept(pathStream);
}
}
}