diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/GhidraSourceBundle.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/GhidraSourceBundle.java index 5325a4abbe..693cce573b 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/GhidraSourceBundle.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/GhidraSourceBundle.java @@ -713,6 +713,7 @@ public class GhidraSourceBundle extends GhidraBundle { } } catch (Throwable e) { + Msg.error(this, "Exception searching ", e); e.printStackTrace(); } } @@ -1090,46 +1091,51 @@ 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 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 f is the class file of an inner class, use the class name - int money = fileName.indexOf('$'); - if (money >= 0) { - return fileName.substring(0, money); - } - // drop ".class" - return fileName.substring(0, fileName.length() - 6); - })); - } - } - else { + if (!Files.exists(directory)) { classToClassFilesMap = Collections.emptyMap(); + return; } + + try (Stream 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) { + return fileName.substring(0, money); + } + // drop ".class" + return fileName.substring(0, fileName.length() - 6); } List findAndRemove(ResourceFile sourceFile) { String className = sourceFile.getName(); - if (className.endsWith(".java")) { - className = className.substring(0, className.length() - 5); - long lastModifiedSource = sourceFile.lastModified(); - List classFiles = classToClassFilesMap.remove(className); - if (classFiles == null) { - classFiles = Collections.emptyList(); - } - long lastModifiedClassFile = classFiles.isEmpty() ? -1 - : classFiles.stream() - .mapToLong(p -> p.toFile().lastModified()) - .min() - .getAsLong(); - // if source is newer than the oldest binary, report - if (lastModifiedSource > lastModifiedClassFile) { - return classFiles; - } + if (!className.endsWith(".java")) { + return null; + } + + className = className.substring(0, className.length() - 5); + long lastModifiedSource = sourceFile.lastModified(); + List classFiles = classToClassFilesMap.remove(className); + if (classFiles == null) { + classFiles = Collections.emptyList(); + } + + long lastModifiedClassFile = classFiles.isEmpty() ? -1 + : classFiles.stream() + .mapToLong(p -> p.toFile().lastModified()) + .min() + .getAsLong(); + // if source is newer than the oldest binary, report + if (lastModifiedSource > lastModifiedClassFile) { + return classFiles; } return null; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScriptUtil.java b/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScriptUtil.java index 43f3270a41..5f2ac752b6 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScriptUtil.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScriptUtil.java @@ -241,6 +241,7 @@ public class GhidraScriptUtil { */ @Deprecated public static List getExplodedCompiledSourceBundlePaths() { + try (Stream pathStream = Files.list(BundleHost.getOsgiDir())) { return pathStream.filter(Files::isDirectory) .map(x -> new ResourceFile(x.toFile())) diff --git a/Ghidra/Features/Base/src/main/java/ghidra/program/database/ProgramBuilder.java b/Ghidra/Features/Base/src/main/java/ghidra/program/database/ProgramBuilder.java index bd1b8fc528..664fb394b4 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/program/database/ProgramBuilder.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/program/database/ProgramBuilder.java @@ -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,8 +373,9 @@ public class ProgramBuilder { startTransaction(); try { - return program.getMemory().createInitializedBlock(name, addr(address), size, (byte) 0, - TaskMonitor.DUMMY, true); + return program.getMemory() + .createInitializedBlock(name, addr(address), size, (byte) 0, + TaskMonitor.DUMMY, true); } catch (Exception e) { throw new RuntimeException("Exception building memory", 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 { diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/merge/listing/RefMergerExtTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/merge/listing/RefMergerExtTest.java index d54e02b61d..b91bb761b6 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/merge/listing/RefMergerExtTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/merge/listing/RefMergerExtTest.java @@ -674,7 +674,7 @@ public class RefMergerExtTest extends AbstractExternalMergerTest { assertEquals("Bar", ((ExternalReference) refs[0]).getExternalLocation().getLabel()); assertTrue(refs[0].getSource() == SourceType.ANALYSIS); } - + @Test public void testExtRefChangeRefTypeConflictPickMy() throws Exception { @@ -688,19 +688,20 @@ 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(); - + Reference[] refs = refMgr.getReferencesFrom(addr(program, "0x1001000"), 0); assertEquals(1, refs.length); Address fromAddress = refs[0].getFromAddress(); int operandIndex = refs[0].getOperandIndex(); refMgr.delete(refs[0]); - + refMgr.addExternalReference(fromAddress, operandIndex, extLoc, - extLoc.getSource(), RefType.DATA); + extLoc.getSource(), RefType.DATA); commit = true; } @@ -721,12 +722,12 @@ public class RefMergerExtTest extends AbstractExternalMergerTest { boolean commit = false; try { ReferenceManager refMgr = program.getReferenceManager(); - + ExternalLocation extLoc = getExternalLocation(program, - new String[] { "Library", "Namespace", "Label1" }); - + new String[] { "Library", "Namespace", "Label1" }); + refMgr.addExternalReference(addr(program, "0x1001000"), 0, extLoc, - extLoc.getSource(), RefType.COMPUTED_CALL); + extLoc.getSource(), RefType.COMPUTED_CALL); commit = true; } @@ -747,12 +748,12 @@ public class RefMergerExtTest extends AbstractExternalMergerTest { boolean commit = false; try { ReferenceManager refMgr = program.getReferenceManager(); - + ExternalLocation extLoc = getExternalLocation(program, - new String[] { "Library", "Namespace", "Label1" }); - + new String[] { "Library", "Namespace", "Label1" }); + refMgr.addExternalReference(addr(program, "0x1001000"), 0, extLoc, - extLoc.getSource(), RefType.COMPUTED_CALL_TERMINATOR); + extLoc.getSource(), RefType.COMPUTED_CALL_TERMINATOR); commit = true; } @@ -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); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/merge/listing/SymbolMergeManagerNamespace1Test.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/merge/listing/SymbolMergeManagerNamespace1Test.java index 49c5fc181e..89f204628c 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/merge/listing/SymbolMergeManagerNamespace1Test.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/merge/listing/SymbolMergeManagerNamespace1Test.java @@ -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,17 +558,21 @@ public class SymbolMergeManagerNamespace1Test extends AbstractListingMergeManage try { Namespace ns; GhidraClass gc; - ns = program.getSymbolTable().createNameSpace(program.getGlobalNamespace(), - "Blue", SourceType.USER_DEFINED); + ns = program.getSymbolTable() + .createNameSpace(program.getGlobalNamespace(), + "Blue", SourceType.USER_DEFINED); assertNotNull(ns); - ns = program.getSymbolTable().createNameSpace(program.getGlobalNamespace(), - "Green", SourceType.USER_DEFINED); + ns = program.getSymbolTable() + .createNameSpace(program.getGlobalNamespace(), + "Green", SourceType.USER_DEFINED); assertNotNull(ns); - gc = program.getSymbolTable().createClass(program.getGlobalNamespace(), "Red", - SourceType.USER_DEFINED); + gc = program.getSymbolTable() + .createClass(program.getGlobalNamespace(), "Red", + SourceType.USER_DEFINED); assertNotNull(gc); - gc = program.getSymbolTable().createClass(program.getGlobalNamespace(), - "Yellow", SourceType.USER_DEFINED); + gc = program.getSymbolTable() + .createClass(program.getGlobalNamespace(), + "Yellow", SourceType.USER_DEFINED); assertNotNull(gc); commit = true; } @@ -598,17 +594,21 @@ public class SymbolMergeManagerNamespace1Test extends AbstractListingMergeManage try { Namespace ns; GhidraClass gc; - gc = program.getSymbolTable().createClass(program.getGlobalNamespace(), "Blue", - SourceType.USER_DEFINED); + gc = program.getSymbolTable() + .createClass(program.getGlobalNamespace(), "Blue", + SourceType.USER_DEFINED); assertNotNull(gc); - gc = program.getSymbolTable().createClass(program.getGlobalNamespace(), "Green", - SourceType.USER_DEFINED); + gc = program.getSymbolTable() + .createClass(program.getGlobalNamespace(), "Green", + SourceType.USER_DEFINED); assertNotNull(gc); - ns = program.getSymbolTable().createNameSpace(program.getGlobalNamespace(), - "Red", SourceType.USER_DEFINED); + ns = program.getSymbolTable() + .createNameSpace(program.getGlobalNamespace(), + "Red", SourceType.USER_DEFINED); assertNotNull(ns); - ns = program.getSymbolTable().createNameSpace(program.getGlobalNamespace(), - "Yellow", SourceType.USER_DEFINED); + ns = program.getSymbolTable() + .createNameSpace(program.getGlobalNamespace(), + "Yellow", SourceType.USER_DEFINED); assertNotNull(ns); commit = true; } @@ -665,17 +665,21 @@ public class SymbolMergeManagerNamespace1Test extends AbstractListingMergeManage "EmptyNamespace", program.getGlobalNamespace()).getObject(); Namespace ns; GhidraClass gc; - ns = program.getSymbolTable().createNameSpace(emptyNamespace, "Blue", - SourceType.USER_DEFINED); + ns = program.getSymbolTable() + .createNameSpace(emptyNamespace, "Blue", + SourceType.USER_DEFINED); assertNotNull(ns); - ns = program.getSymbolTable().createNameSpace(emptyNamespace, "Green", - SourceType.USER_DEFINED); + ns = program.getSymbolTable() + .createNameSpace(emptyNamespace, "Green", + SourceType.USER_DEFINED); assertNotNull(ns); - gc = program.getSymbolTable().createClass(emptyNamespace, "Red", - SourceType.USER_DEFINED); + gc = program.getSymbolTable() + .createClass(emptyNamespace, "Red", + SourceType.USER_DEFINED); assertNotNull(gc); - gc = program.getSymbolTable().createClass(emptyNamespace, "Yellow", - SourceType.USER_DEFINED); + gc = program.getSymbolTable() + .createClass(emptyNamespace, "Yellow", + SourceType.USER_DEFINED); assertNotNull(gc); commit = true; } @@ -699,17 +703,21 @@ public class SymbolMergeManagerNamespace1Test extends AbstractListingMergeManage "EmptyNamespace", program.getGlobalNamespace()).getObject(); Namespace ns; GhidraClass gc; - gc = program.getSymbolTable().createClass(emptyNamespace, "Blue", - SourceType.USER_DEFINED); + gc = program.getSymbolTable() + .createClass(emptyNamespace, "Blue", + SourceType.USER_DEFINED); assertNotNull(gc); - gc = program.getSymbolTable().createClass(emptyNamespace, "Green", - SourceType.USER_DEFINED); + gc = program.getSymbolTable() + .createClass(emptyNamespace, "Green", + SourceType.USER_DEFINED); assertNotNull(gc); - ns = program.getSymbolTable().createNameSpace(emptyNamespace, "Red", - SourceType.USER_DEFINED); + ns = program.getSymbolTable() + .createNameSpace(emptyNamespace, "Red", + SourceType.USER_DEFINED); assertNotNull(ns); - ns = program.getSymbolTable().createNameSpace(emptyNamespace, "Yellow", - SourceType.USER_DEFINED); + ns = program.getSymbolTable() + .createNameSpace(emptyNamespace, "Yellow", + SourceType.USER_DEFINED); assertNotNull(ns); commit = true; } @@ -773,17 +781,21 @@ public class SymbolMergeManagerNamespace1Test extends AbstractListingMergeManage try { Namespace ns; GhidraClass gc; - ns = program.getSymbolTable().createNameSpace(program.getGlobalNamespace(), - "Blue", SourceType.USER_DEFINED); + ns = program.getSymbolTable() + .createNameSpace(program.getGlobalNamespace(), + "Blue", SourceType.USER_DEFINED); assertNotNull(ns); - ns = program.getSymbolTable().createNameSpace(program.getGlobalNamespace(), - "Green", SourceType.USER_DEFINED); + ns = program.getSymbolTable() + .createNameSpace(program.getGlobalNamespace(), + "Green", SourceType.USER_DEFINED); assertNotNull(ns); - gc = program.getSymbolTable().createClass(program.getGlobalNamespace(), "Red", - SourceType.USER_DEFINED); + gc = program.getSymbolTable() + .createClass(program.getGlobalNamespace(), "Red", + SourceType.USER_DEFINED); assertNotNull(gc); - gc = program.getSymbolTable().createClass(program.getGlobalNamespace(), - "Yellow", SourceType.USER_DEFINED); + gc = program.getSymbolTable() + .createClass(program.getGlobalNamespace(), + "Yellow", SourceType.USER_DEFINED); assertNotNull(gc); commit = true; } @@ -805,23 +817,29 @@ public class SymbolMergeManagerNamespace1Test extends AbstractListingMergeManage try { Namespace ns; GhidraClass gc; - gc = program.getSymbolTable().createClass(program.getGlobalNamespace(), "Blue", - SourceType.USER_DEFINED); + gc = program.getSymbolTable() + .createClass(program.getGlobalNamespace(), "Blue", + SourceType.USER_DEFINED); assertNotNull(gc); - gc = program.getSymbolTable().createClass(program.getGlobalNamespace(), "Green", - SourceType.USER_DEFINED); + gc = program.getSymbolTable() + .createClass(program.getGlobalNamespace(), "Green", + SourceType.USER_DEFINED); assertNotNull(gc); - ns = program.getSymbolTable().createNameSpace(program.getGlobalNamespace(), - "Red", SourceType.USER_DEFINED); + ns = program.getSymbolTable() + .createNameSpace(program.getGlobalNamespace(), + "Red", SourceType.USER_DEFINED); assertNotNull(ns); - gc = program.getSymbolTable().createClass(ns, "SubRed", - SourceType.USER_DEFINED); + gc = program.getSymbolTable() + .createClass(ns, "SubRed", + SourceType.USER_DEFINED); assertNotNull(gc); - ns = program.getSymbolTable().createNameSpace(program.getGlobalNamespace(), - "Yellow", SourceType.USER_DEFINED); + ns = program.getSymbolTable() + .createNameSpace(program.getGlobalNamespace(), + "Yellow", SourceType.USER_DEFINED); assertNotNull(ns); - ns = program.getSymbolTable().createNameSpace(ns, "SubYellow", - SourceType.USER_DEFINED); + 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); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/analysis/AnalyzeAllOpenProgramsTaskTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/analysis/AnalyzeAllOpenProgramsTaskTest.java index 62a173769b..8ba6671298 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/analysis/AnalyzeAllOpenProgramsTaskTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/analysis/AnalyzeAllOpenProgramsTaskTest.java @@ -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 { diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/functionwindow/FunctionWindowPluginTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/functionwindow/FunctionWindowPluginTest.java index 33f0b32182..b2075a3b41 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/functionwindow/FunctionWindowPluginTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/functionwindow/FunctionWindowPluginTest.java @@ -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: diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/label/LabelActionTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/label/LabelActionTest.java index e2f9972b66..3c0f8cd153 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/label/LabelActionTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/label/LabelActionTest.java @@ -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 diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/AbstractGhidraScriptMgrPluginTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/AbstractGhidraScriptMgrPluginTest.java index 0f64c9b585..d2f15286a9 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/AbstractGhidraScriptMgrPluginTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/AbstractGhidraScriptMgrPluginTest.java @@ -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 walk = Files.walk(path)) { - for (Path p : (Iterable) 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 pathStream = Files.list(userScriptDir)) { - for (Path p : (Iterable) 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() { diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/BundleHostTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/BundleHostTest.java index c894cfacc6..0e1d4b1883 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/BundleHostTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/BundleHostTest.java @@ -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 tempDirs = new HashSet<>(); - LinkedList bundleStack = new LinkedList<>(); - GhidraBundle currentBundle; + private Set tempDirs = new HashSet<>(); + private LinkedList bundleStack = new LinkedList<>(); + private GhidraBundle currentBundle; - protected static void wipe(Path path) throws IOException { - if (Files.exists(path)) { - try (Stream walk = Files.walk(path)) { - for (Path p : (Iterable) 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); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/BundleStatusManagerTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/BundleStatusManagerTest.java index ec5174a228..5e97d0d14a 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/BundleStatusManagerTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/BundleStatusManagerTest.java @@ -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 selectRows(int... viewRows) { - List 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 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 statuses = selectRows(viewRows); - - List binaryDirs = statuses.stream().map((status) -> { - status.setSummary("no summary"); // we use the summary later to test that the bundle's been cleaned - GhidraSourceBundle bundle = (GhidraSourceBundle) provider.getBundleHost() - .getExistingGhidraBundle(status.getFile()); - assertNotNull(bundle); - File binaryDir = ((Path) getInstanceField("binaryDir", bundle)).toFile(); - assertTrue("Clean of bundle that doesn't exist", binaryDir.exists()); - return binaryDir; - }).collect(Collectors.toList()); - - DockingActionIf cleanBundlesAction = getActionByName(bundleStatusProvider, "CleanBundles"); - performAction(cleanBundlesAction); - waitForSwing(); - - // after cleaning, status is cleared, test for a clear status to know we're done cleaning. - int count = 0; - do { - if (statuses.stream().allMatch(status -> status.getSummary().isEmpty())) { - break; - } - Thread.sleep(250); - } - while (++count < 8); - assertTrue("Failure, clean took too long", count < 8); - for (File binaryDir : binaryDirs) { - assertFalse("Clean of bundle didn't remove directory", binaryDir.exists()); - } - } - - /** - * 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 actionSet = - (Set) 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. - * - *

All bundles should reside in a common directory. - * - * @param bundleFiles the bundle files - * @throws Exception if waitForUpdateOnChooser fails - */ - void addBundlesViaGUI(File... bundleFiles) throws Exception { - assertTrue("addBundlesViaGUI called with no arguments", bundleFiles.length > 0); - - DockingActionIf addBundlesAction = getActionByName(bundleStatusProvider, "AddBundles"); - performAction(addBundlesAction, false); - waitForSwing(); - - List files = List.of(bundleFiles); - - GhidraFileChooser chooser = waitForDialogComponent(GhidraFileChooser.class); - assertNotNull(chooser); - - runSwing(() -> { - chooser.setCurrentDirectory(bundleFiles[0]); - }, true); - waitForUpdateOnChooser(chooser); - - runSwing(() -> { - // there is no setFiles method of GhidraFileChooser - Object selectedFiles = getInstanceField("selectedFiles", chooser); - invokeInstanceMethod("setFiles", selectedFiles, new Class[] { List.class }, - new Object[] { files }); - Object validatedFiles = getInstanceField("validatedFiles", chooser); - invokeInstanceMethod("setFiles", validatedFiles, new Class[] { List.class }, - new Object[] { files }); - }); - waitForUpdateOnChooser(chooser); - testBundleHostListener.reset(bundleFiles.length); - pressButtonByText(chooser, "OK"); - waitForSwing(); - testBundleHostListener.awaitActivation(); - } - - @Override - protected String runScript(String scriptName) throws Exception { - env.getTool().showComponentProvider(provider, true); - selectScript(scriptName); - String output = super.runScript(scriptName); - env.getTool().showComponentProvider(bundleStatusProvider, true); - return output; - } - @Test public void testRunCleanRun() throws Exception { + int viewRow = getBundleRow(BUNDLE_PATH); + 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 selectRows(int... viewRows) { + List 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 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 statuses = selectRows(viewRows); + + List binaryDirs = statuses.stream().map((status) -> { + status.setSummary("no summary"); // we use the summary later to test that the bundle's been cleaned + GhidraSourceBundle bundle = (GhidraSourceBundle) provider.getBundleHost() + .getExistingGhidraBundle(status.getFile()); + assertNotNull(bundle); + File binaryDir = ((Path) getInstanceField("binaryDir", bundle)).toFile(); + assertTrue("Clean of bundle that doesn't exist", binaryDir.exists()); + return binaryDir; + }).collect(Collectors.toList()); + + DockingActionIf cleanBundlesAction = getActionByName(bundleStatusProvider, "CleanBundles"); + performAction(cleanBundlesAction); + waitForSwing(); + + // after cleaning, status is cleared, test for a clear status to know we're done cleaning. + int count = 0; + do { + if (statuses.stream().allMatch(status -> status.getSummary().isEmpty())) { + break; + } + Thread.sleep(250); + } + while (++count < 8); + assertTrue("Failure, clean took too long", count < 8); + for (File binaryDir : binaryDirs) { + assertFalse("Clean of bundle didn't remove directory", binaryDir.exists()); + } + } + + 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 actionSet = + (Set) 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. + * + *

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 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; diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/searchtext/SearchTextPlugin2Test.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/searchtext/SearchTextPlugin2Test.java index 13cf9ca97d..046b8b77de 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/searchtext/SearchTextPlugin2Test.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/searchtext/SearchTextPlugin2Test.java @@ -631,6 +631,7 @@ public class SearchTextPlugin2Test extends AbstractGhidraHeadedIntegrationTest { waitForSearchTasks(tempDialog); waitForSwing(); assertFalse(tempDialog.isVisible()); + assertFalse(isEnabled(searchAction)); } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/plugintool/dialog/KeyBindingUtilsTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/plugintool/dialog/KeyBindingUtilsTest.java index 1df311e037..b3686bf660 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/plugintool/dialog/KeyBindingUtilsTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/plugintool/dialog/KeyBindingUtilsTest.java @@ -112,6 +112,8 @@ public class KeyBindingUtilsTest extends AbstractGhidraHeadedIntegrationTest { tool.addPlugin(DataPlugin.class.getName()); tool.addPlugin(FunctionPlugin.class.getName()); + env.showTool(); + debug("two"); } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/program/database/map/NormalizedAddressSetTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/program/database/map/NormalizedAddressSetTest.java index 1141aa355f..717cee6491 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/program/database/map/NormalizedAddressSetTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/program/database/map/NormalizedAddressSetTest.java @@ -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; @@ -67,7 +66,7 @@ public class NormalizedAddressSetTest extends AbstractGhidraHeadedIntegrationTes } @Test - public void testAdd() { + public void testAdd() { set.addRange(addr(0), addr(10)); AddressSet addrSet = set.intersect(new AddressSet(addr(5), addr(15))); assertEquals(6, addrSet.getNumAddresses()); @@ -76,14 +75,14 @@ public class NormalizedAddressSetTest extends AbstractGhidraHeadedIntegrationTes } @Test - public void testAddBig() { + public void testAddBig() { set.addRange(addr(0xfffffff0l), addr(0xffffffffl)); assertEquals(addr(0xfffffff0l), set.getMinAddress()); assertEquals(addr(0xffffffffl), set.getMaxAddress()); } @Test - public void testUnionNonOverlap() { + public void testUnionNonOverlap() { set.addRange(addr(0), addr(0)); set.addRange(addr(5), addr(5)); set.addRange(addr(0xffffffffL), addr(0xffffffffL)); @@ -106,7 +105,7 @@ public class NormalizedAddressSetTest extends AbstractGhidraHeadedIntegrationTes } @Test - public void testUnionWithOverlap() { + public void testUnionWithOverlap() { set.addRange(addr(0), addr(22)); set.addRange(addr(42), addr(75)); set.addRange(addr(0xfffffe10L), addr(0xfffffe20L)); @@ -129,7 +128,7 @@ public class NormalizedAddressSetTest extends AbstractGhidraHeadedIntegrationTes } @Test - public void testUnionPosFirst() { + public void testUnionPosFirst() { set.addRange(addr(0), addr(0)); set.addRange(addr(5), addr(5)); set.addRange(addr(40), addr(45)); @@ -152,7 +151,7 @@ public class NormalizedAddressSetTest extends AbstractGhidraHeadedIntegrationTes } @Test - public void testUnionNegFirst() { + public void testUnionNegFirst() { set.addRange(addr(0xffffffffL), addr(0xffffffffL)); set.addRange(addr(0xfffffe0cL), addr(0xfffffe70L)); set.addRange(addr(0xffffffecL), addr(0xfffffffdL)); @@ -185,7 +184,7 @@ public class NormalizedAddressSetTest extends AbstractGhidraHeadedIntegrationTes } @Test - public void testIntersectNonOverlap() { + public void testIntersectNonOverlap() { set.addRange(addr(0), addr(0)); set.addRange(addr(5), addr(5)); set.addRange(addr(0xffffffffL), addr(0xffffffffL)); @@ -204,7 +203,7 @@ public class NormalizedAddressSetTest extends AbstractGhidraHeadedIntegrationTes } @Test - public void testIntersectWithOverlap() { + public void testIntersectWithOverlap() { set.addRange(addr(0), addr(22)); set.addRange(addr(42), addr(75)); set.addRange(addr(0xfffffe10L), addr(0xfffffe20L)); @@ -239,7 +238,7 @@ public class NormalizedAddressSetTest extends AbstractGhidraHeadedIntegrationTes } @Test - public void testDelete() { + public void testDelete() { set.addRange(addr(0), addr(22)); set.addRange(addr(42), addr(75)); set.addRange(addr(0xfffffe10L), addr(0xfffffe20L)); @@ -261,7 +260,7 @@ public class NormalizedAddressSetTest extends AbstractGhidraHeadedIntegrationTes } @Test - public void testSubtract() { + public void testSubtract() { set.addRange(addr(0), addr(22)); set.addRange(addr(42), addr(75)); set.addRange(addr(0xfffffe10L), addr(0xfffffe20L)); @@ -283,7 +282,7 @@ public class NormalizedAddressSetTest extends AbstractGhidraHeadedIntegrationTes } @Test - public void testXor() { + public void testXor() { set.addRange(addr(0), addr(22)); set.addRange(addr(42), addr(75)); set.addRange(addr(0xfffffe10L), addr(0xfffffe20L)); @@ -308,7 +307,7 @@ public class NormalizedAddressSetTest extends AbstractGhidraHeadedIntegrationTes } @Test - public void testContains() { + public void testContains() { set.addRange(addr(0), addr(22)); set.addRange(addr(42), addr(75)); set.addRange(addr(0xfffffe10L), addr(0xfffffe20L)); @@ -348,7 +347,7 @@ public class NormalizedAddressSetTest extends AbstractGhidraHeadedIntegrationTes } @Test - public void testContains2() { + public void testContains2() { set.addRange(addr(0), addr(0xffffffffL)); assertEquals(true, set.contains(addr(0))); @@ -384,7 +383,7 @@ public class NormalizedAddressSetTest extends AbstractGhidraHeadedIntegrationTes } @Test - public void testForwardIterator() { + public void testForwardIterator() { set.addRange(addr(0), addr(22)); set.addRange(addr(42), addr(75)); set.addRange(addr(0xfffffe10L), addr(0xfffffe20L)); @@ -418,7 +417,7 @@ public class NormalizedAddressSetTest extends AbstractGhidraHeadedIntegrationTes } @Test - public void testBackwardIterator() { + public void testBackwardIterator() { set.addRange(addr(0), addr(22)); set.addRange(addr(42), addr(75)); set.addRange(addr(0xfffffe10L), addr(0xfffffe20L)); @@ -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); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/program/database/util/SharedRangeMapDBTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/program/database/util/SharedRangeMapDBTest.java index d1bce9f758..50876c1d95 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/program/database/util/SharedRangeMapDBTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/program/database/util/SharedRangeMapDBTest.java @@ -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 @@ -45,9 +46,9 @@ public class SharedRangeMapDBTest extends AbstractGhidraHeadedIntegrationTest im /* * @see TestCase#setUp() */ - @Before - public void setUp() throws Exception { - + @Before + public void setUp() throws Exception { + dbh = new DBHandle(); transactionID = dbh.startTransaction(); } @@ -55,28 +56,30 @@ public class SharedRangeMapDBTest extends AbstractGhidraHeadedIntegrationTest im /* * @see TestCase#tearDown() */ - @After - public void tearDown() throws Exception { + @After + public void tearDown() throws Exception { dbh.endTransaction(transactionID, false); dbh.close(); - + } /** * @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; } - + /** * Crude inspection - assumes two records will not contain the same * content. @@ -85,76 +88,79 @@ 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); - + iter = map.mapTable.iterator(); cnt = 0; while (iter.hasNext()) { ++cnt; 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()); + rec.getLongValue(SharedRangeMapDB.MAP_VALUE_COL)); + if (indexOf(mapRangeToValue, entry) < 0) { + Assert.fail("Unexpected map entry: rangeKey=" + entry.getStart() + ", value=" + + entry.getEnd()); + } } assertEquals(mapRangeToValue.length, cnt); } - -@Test - public void testAdd() throws IOException { + + @Test + public void testAdd() throws IOException { SharedRangeMapDB map = new SharedRangeMapDB(dbh, "TEST", this, true); - + // Add initial set of ranges map.add(10, 20, 1); map.add(30, 40, 1); map.add(50, 60, 1); 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[] { new IndexRange(10, 1), new IndexRange(30, 1), new IndexRange(50, 1), new IndexRange(70, 1) }; - + inspectRecords(map, ranges, entries); - + // Range already included map.add(52, 58, 1); map.add(52, 60, 1); map.add(50, 60, 1); - + inspectRecords(map, ranges, entries); // Add range 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[] { new IndexRange(10, 1), new IndexRange(50, 1), @@ -162,44 +168,44 @@ System.out.println(" Map entry: rangeKey=" + entry.getStart() + ", value=" + en }; inspectRecords(map, ranges, entries); - + // Add range 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[] { new IndexRange(10, 1), new IndexRange(70, 1) }; inspectRecords(map, ranges, entries); - + // Add range map.add(55, 90, 1); - + ranges = new IndexRange[] { - new IndexRange(10,90) + new IndexRange(10, 90) }; - + entries = new IndexRange[] { new IndexRange(10, 1) }; inspectRecords(map, ranges, entries); - + // Add second overlapped value map.add(20, 30, 2); - + ranges = new IndexRange[] { new IndexRange(10, 19), new IndexRange(20, 30), new IndexRange(31, 90) }; - + entries = new IndexRange[] { new IndexRange(10, 1), new IndexRange(20, 1), @@ -211,7 +217,7 @@ System.out.println(" Map entry: rangeKey=" + entry.getStart() + ", value=" + en // Add third overlapped value map.add(28, 35, 3); - + ranges = new IndexRange[] { new IndexRange(10, 19), new IndexRange(20, 27), @@ -219,7 +225,7 @@ System.out.println(" Map entry: rangeKey=" + entry.getStart() + ", value=" + en new IndexRange(31, 35), new IndexRange(36, 90) }; - + entries = new IndexRange[] { new IndexRange(10, 1), new IndexRange(20, 1), @@ -233,10 +239,10 @@ System.out.println(" Map entry: rangeKey=" + entry.getStart() + ", value=" + en }; inspectRecords(map, ranges, entries); - + // Add fourth overlapped value map.add(28, 35, 4); - + ranges = new IndexRange[] { new IndexRange(10, 19), new IndexRange(20, 27), @@ -244,7 +250,7 @@ System.out.println(" Map entry: rangeKey=" + entry.getStart() + ", value=" + en new IndexRange(31, 35), new IndexRange(36, 90) }; - + entries = new IndexRange[] { new IndexRange(10, 1), new IndexRange(20, 1), @@ -260,10 +266,10 @@ System.out.println(" Map entry: rangeKey=" + entry.getStart() + ", value=" + en }; inspectRecords(map, ranges, entries); - + // Expand fourth overlapped value range map.add(25, 39, 4); - + ranges = new IndexRange[] { new IndexRange(10, 19), new IndexRange(20, 24), @@ -273,7 +279,7 @@ System.out.println(" Map entry: rangeKey=" + entry.getStart() + ", value=" + en new IndexRange(36, 39), new IndexRange(40, 90) }; - + entries = new IndexRange[] { new IndexRange(10, 1), new IndexRange(20, 1), @@ -296,10 +302,10 @@ System.out.println(" Map entry: rangeKey=" + entry.getStart() + ", value=" + en inspectRecords(map, ranges, entries); } -@Test - public void testRemove() throws IOException { + @Test + public void testRemove() throws IOException { SharedRangeMapDB map = new SharedRangeMapDB(dbh, "TEST", this, true); - + // Add same entries as the testAdd used map.add(10, 20, 1); map.add(30, 40, 1); @@ -307,20 +313,20 @@ System.out.println(" Map entry: rangeKey=" + entry.getStart() + ", value=" + en map.add(70, 80, 1); map.add(21, 29, 1); - + map.add(35, 55, 1); - + map.add(55, 90, 1); - + map.add(20, 30, 2); - + map.add(28, 35, 3); - + map.add(28, 35, 4); - + // Remove map.remove(4); - + IndexRange[] ranges = new IndexRange[] { new IndexRange(10, 19), new IndexRange(20, 27), @@ -328,7 +334,7 @@ System.out.println(" Map entry: rangeKey=" + entry.getStart() + ", value=" + en new IndexRange(31, 35), new IndexRange(36, 90) }; - + IndexRange[] entries = new IndexRange[] { new IndexRange(10, 1), new IndexRange(20, 1), @@ -342,16 +348,16 @@ System.out.println(" Map entry: rangeKey=" + entry.getStart() + ", value=" + en }; inspectRecords(map, ranges, entries); - + // Remove map.remove(3); - + ranges = new IndexRange[] { new IndexRange(10, 19), new IndexRange(20, 30), new IndexRange(31, 90) }; - + entries = new IndexRange[] { new IndexRange(10, 1), new IndexRange(20, 1), @@ -360,31 +366,31 @@ System.out.println(" Map entry: rangeKey=" + entry.getStart() + ", value=" + en }; inspectRecords(map, ranges, entries); - + // Remove map.remove(2); - + ranges = new IndexRange[] { - new IndexRange(10,90) + new IndexRange(10, 90) }; - + entries = new IndexRange[] { new IndexRange(10, 1) }; inspectRecords(map, ranges, entries); - + // Remove last one map.remove(1); assertEquals(0, map.rangeTable.getRecordCount()); assertEquals(0, map.mapTable.getRecordCount()); - + } -@Test - public void testGetValueIterator() { + @Test + public void testGetValueIterator() { SharedRangeMapDB map = new SharedRangeMapDB(dbh, "TEST", this, true); - + // Add same entries as the testAdd used map.add(10, 20, 1); map.add(30, 40, 1); @@ -392,17 +398,17 @@ System.out.println(" Map entry: rangeKey=" + entry.getStart() + ", value=" + en map.add(70, 80, 1); map.add(21, 29, 1); - + map.add(35, 55, 1); - + map.add(55, 90, 1); - + map.add(20, 30, 2); - + map.add(28, 35, 3); - + map.add(28, 35, 4); - + // Test 1 Iterator iter = map.getValueIterator(29, 34); LongField[] values = new LongField[] { @@ -414,12 +420,13 @@ 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 iter = map.getValueIterator(0, 20); values = new LongField[] { @@ -429,12 +436,13 @@ 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 iter = map.getValueIterator(89, 100); values = new LongField[] { @@ -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 - public void testGetValueRangeIterator() { + @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); @@ -467,19 +475,19 @@ System.out.println("testGetValueRangeIterator ---"); map.add(70, 80, 1); map.add(21, 29, 1); - + map.add(35, 55, 1); - + map.add(55, 90, 1); - + map.add(20, 30, 2); - + map.add(28, 35, 3); - + map.add(28, 35, 4); - + map.add(25, 39, 4); - + IndexRangeIterator iter = map.getValueRangeIterator(2); IndexRange[] ranges = new IndexRange[] { new IndexRange(20, 24), @@ -490,12 +498,13 @@ 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); - + } } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/util/bean/opteditor/OptionsDialogTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/util/bean/opteditor/OptionsDialogTest.java index 7abe9bcbb3..285aa5ec23 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/util/bean/opteditor/OptionsDialogTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/util/bean/opteditor/OptionsDialogTest.java @@ -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); } diff --git a/Ghidra/Features/Base/src/test/java/ghidra/program/database/AbstractMTFModel.java b/Ghidra/Features/Base/src/test/java/ghidra/program/database/AbstractMTFModel.java index 16812195f4..d5fb7ca90c 100644 --- a/Ghidra/Features/Base/src/test/java/ghidra/program/database/AbstractMTFModel.java +++ b/Ghidra/Features/Base/src/test/java/ghidra/program/database/AbstractMTFModel.java @@ -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; } diff --git a/Ghidra/Features/FunctionGraph/src/test/java/ghidra/app/plugin/core/functiongraph/FunctionGraphPlugin1Test.java b/Ghidra/Features/FunctionGraph/src/test/java/ghidra/app/plugin/core/functiongraph/FunctionGraphPlugin1Test.java index 2e4e237214..326b4516d7 100644 --- a/Ghidra/Features/FunctionGraph/src/test/java/ghidra/app/plugin/core/functiongraph/FunctionGraphPlugin1Test.java +++ b/Ghidra/Features/FunctionGraph/src/test/java/ghidra/app/plugin/core/functiongraph/FunctionGraphPlugin1Test.java @@ -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 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)); } } diff --git a/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/security/loginmodule/ExternalProgramLoginModule.java b/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/security/loginmodule/ExternalProgramLoginModule.java index 90cf3325d5..5605cdf79e 100644 --- a/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/security/loginmodule/ExternalProgramLoginModule.java +++ b/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/security/loginmodule/ExternalProgramLoginModule.java @@ -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 argKeys = options.keySet().stream().filter( - key -> key.startsWith(ARG_OPTION_NAME)).sorted().collect(Collectors.toList()); + List argKeys = options.keySet() + .stream() + .filter( + key -> key.startsWith(ARG_OPTION_NAME)) + .sorted() + .collect(Collectors.toList()); List cmdArrayValues = new ArrayList<>(); cmdArrayValues.add(externalProgram.toString()); for (String argKey : argKeys) { diff --git a/Ghidra/Framework/Docking/src/main/java/docking/DockingWindowManager.java b/Ghidra/Framework/Docking/src/main/java/docking/DockingWindowManager.java index 79432ded3d..cc71c9c4d4 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/DockingWindowManager.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/DockingWindowManager.java @@ -2245,12 +2245,25 @@ 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()) { - Msg.debug(DockingWindowManager.class, - "Unable to find Docking Window Manager for " + - component.getClass().getSimpleName(), - ReflectionUtilities.createJavaFilteredThrowable()); + 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()); } }); diff --git a/Ghidra/Framework/Docking/src/main/java/docking/test/AbstractDockingTest.java b/Ghidra/Framework/Docking/src/main/java/docking/test/AbstractDockingTest.java index 17fecb8a08..775c4f8407 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/test/AbstractDockingTest.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/test/AbstractDockingTest.java @@ -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); } //================================================================================================== diff --git a/Ghidra/Framework/Generic/src/main/java/generic/concurrent/io/IOResult.java b/Ghidra/Framework/Generic/src/main/java/generic/concurrent/io/IOResult.java new file mode 100644 index 0000000000..6070296c66 --- /dev/null +++ b/Ghidra/Framework/Generic/src/main/java/generic/concurrent/io/IOResult.java @@ -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 outputLines = new ArrayList(); + private BufferedReader commandOutput; + private final Throwable inception; + private Consumer 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 consumer) { + this.consumer = consumer; + } + + public String getOutputAsString() { + StringBuilder buffy = new StringBuilder(); + for (String line : outputLines) { + buffy.append(line); + } + return buffy.toString(); + } + + public List 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); + } + } +} diff --git a/Ghidra/Framework/Generic/src/main/java/generic/concurrent/io/ProcessConsumer.java b/Ghidra/Framework/Generic/src/main/java/generic/concurrent/io/ProcessConsumer.java new file mode 100644 index 0000000000..0563259bf6 --- /dev/null +++ b/Ghidra/Framework/Generic/src/main/java/generic/concurrent/io/ProcessConsumer.java @@ -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 asynchronously 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. + * + *

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 consume(InputStream is) { + return consume(is, Dummy.consumer()); + } + + /** + * Read the given input stream line-by-line. + * + *

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 consume(InputStream is, + Consumer 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 future = pool.submit(runnable, runnable); + return future; + } +} diff --git a/Ghidra/Framework/Utility/src/main/java/utilities/util/FileUtilities.java b/Ghidra/Framework/Utility/src/main/java/utilities/util/FileUtilities.java index 43c07957a7..02ad63ab1a 100644 --- a/Ghidra/Framework/Utility/src/main/java/utilities/util/FileUtilities.java +++ b/Ghidra/Framework/Utility/src/main/java/utilities/util/FileUtilities.java @@ -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 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 + * @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 getLines(File file) throws IOException { return getLines(new ResourceFile(file)); @@ -660,7 +684,7 @@ public final class FileUtilities { *

* @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 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 { *

* 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. - *

- * Thread exits when EOF is reached. - * - * @param is {@link InputStream} to read - * @param consumer code that will process each text of the text file. + * 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. + * + *

This method handles closing resources by using the try-with-resources construct on + * {@link Files#list(Path)} + * + * @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(InputStream is, Consumer consumer) { - asyncForEachLine(new BufferedReader(new InputStreamReader(is)), consumer); + public static void forEachFile(Path path, Consumer> consumer) throws IOException { + + if (!Files.isDirectory(path)) { + return; + } + + try (Stream pathStream = Files.list(path)) { + consumer.accept(pathStream); + } } - - /** - * Processes each text line in a text file, in a separate thread. - *

- * Thread exits when EOF is reached. - * - * @param reader {@link BufferedReader} to read - * @param consumer code that will process each text of the text file. - */ - public static void asyncForEachLine(BufferedReader reader, Consumer 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); - } - - }, "Threaded Stream Reader Thread").start(); - } - }