GP-829: Removing processContainer concept from tests.

This commit is contained in:
Dan
2021-04-13 13:29:14 -04:00
parent 3b058380a3
commit 1a3458ef7c
36 changed files with 218 additions and 330 deletions
@@ -15,13 +15,14 @@
*/
package agent.dbgeng.model;
import static org.junit.Assert.*;
import static org.junit.Assert.assertNotNull;
import java.util.List;
import ghidra.dbg.target.*;
import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind;
import ghidra.dbg.target.TargetBreakpointSpecContainer.TargetBreakpointKindSet;
import ghidra.dbg.target.TargetObject;
import ghidra.dbg.target.TargetStackFrame;
import ghidra.dbg.test.*;
import ghidra.dbg.util.PathUtils;
import ghidra.program.model.address.*;
@@ -79,10 +80,4 @@ public abstract class AbstractModelForDbgengBreakpointsTest
throw new AssertionError();
}
}
@Override
protected TargetObject findProcessContainer() throws Throwable {
return m.findContainer(TargetProcess.class, PathUtils.parse("Sessions[0]"));
}
}
@@ -46,7 +46,7 @@ public abstract class AbstractModelForDbgengFrameFocusTest
return retry(() -> {
Map<List<String>, TargetStackFrame> frames =
m.findAll(TargetStackFrame.class, seedPath());
m.findAll(TargetStackFrame.class, seedPath(), true);
assertTrue(frames.size() >= 3);
return Set.copyOf(frames.values());
}, List.of(AssertionError.class));
@@ -17,6 +17,7 @@ package agent.dbgeng.model;
import java.util.List;
import ghidra.dbg.target.TargetProcess;
import ghidra.dbg.test.AbstractDebuggerModelInterpreterTest;
import ghidra.dbg.util.PathUtils;
@@ -48,6 +49,16 @@ public abstract class AbstractModelForDbgengInterpreterTest
return ".attach " + Long.toHexString(dummy.pid);
}
@Override
protected String getDetachCommand(TargetProcess process) {
return ".detach";
}
@Override
protected String getKillCommand(TargetProcess process) {
return ".kill";
}
@Override
public DebuggerTestSpecimen getAttachSpecimen() {
return WindowsSpecimen.NOTEPAD;
@@ -44,7 +44,7 @@ public abstract class AbstractModelForDbgengProcessFocusTest
}
return retry(() -> {
Map<List<String>, TargetProcess> found =
m.findAll(TargetProcess.class, PathUtils.parse("Sessions[0]"));
m.findAll(TargetProcess.class, PathUtils.parse("Sessions[0]"), true);
assertEquals(count, found.size());
return Set.copyOf(found.values());
}, List.of(AssertionError.class));
@@ -20,7 +20,7 @@ import static org.junit.Assert.assertTrue;
import java.util.List;
import ghidra.dbg.target.*;
import ghidra.dbg.target.TargetEnvironment;
import ghidra.dbg.target.TargetMethod.TargetParameterMap;
import ghidra.dbg.test.AbstractDebuggerModelAttacherTest;
import ghidra.dbg.util.PathUtils;
@@ -28,21 +28,11 @@ import ghidra.dbg.util.PathUtils;
public abstract class AbstractModelForDbgengRootAttacherTest
extends AbstractDebuggerModelAttacherTest {
@Override
protected TargetObject findProcessContainer() throws Throwable {
return m.findContainer(TargetProcess.class, PathUtils.parse("Sessions[0]"));
}
@Override
public List<String> getExpectedAttachableContainerPath() {
return List.of("Available");
}
@Override
public List<String> getExpectedProcessesContainerPath() {
return PathUtils.parse("Sessions[0].Processes");
}
@Override
public List<String> getExpectedAttacherPath() {
return PathUtils.parse("");
@@ -21,7 +21,7 @@ import static org.junit.Assert.assertTrue;
import java.util.List;
import java.util.Map;
import ghidra.dbg.target.*;
import ghidra.dbg.target.TargetEnvironment;
import ghidra.dbg.target.TargetMethod.ParameterDescription;
import ghidra.dbg.target.TargetMethod.TargetParameterMap;
import ghidra.dbg.test.AbstractDebuggerModelLauncherTest;
@@ -30,16 +30,6 @@ import ghidra.dbg.util.PathUtils;
public abstract class AbstractModelForDbgengRootLauncherTest
extends AbstractDebuggerModelLauncherTest {
@Override
protected TargetObject findProcessContainer() throws Throwable {
return m.findContainer(TargetProcess.class, PathUtils.parse("Sessions[0]"));
}
@Override
public List<String> getExpectedProcessesContainerPath() {
return PathUtils.parse("Sessions[0].Processes");
}
@Override
public List<String> getExpectedLauncherPath() {
return PathUtils.parse("");
@@ -15,19 +15,11 @@
*/
package agent.dbgeng.model;
import ghidra.dbg.target.TargetObject;
import ghidra.dbg.target.TargetProcess;
import ghidra.dbg.test.AbstractDebuggerModelScenarioCloneExitTest;
import ghidra.dbg.util.PathUtils;
public abstract class AbstractModelForDbgengScenarioCloneExitTest
extends AbstractDebuggerModelScenarioCloneExitTest {
@Override
protected TargetObject findProcessContainer() throws Throwable {
return m.findContainer(TargetProcess.class, PathUtils.parse("Sessions[0]"));
}
@Override
protected DebuggerTestSpecimen getSpecimen() {
return WindowsSpecimen.CREATE_THREAD_EXIT;
@@ -18,18 +18,12 @@ package agent.dbgeng.model;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import ghidra.dbg.target.*;
import ghidra.dbg.target.TargetEnvironment;
import ghidra.dbg.test.AbstractDebuggerModelScenarioForkExitTest;
import ghidra.dbg.util.PathUtils;
public abstract class AbstractModelForDbgengScenarioForkExitTest
extends AbstractDebuggerModelScenarioForkExitTest {
@Override
protected TargetObject findProcessContainer() throws Throwable {
return m.findContainer(TargetProcess.class, PathUtils.parse("Sessions[0]"));
}
@Override
protected DebuggerTestSpecimen getSpecimen() {
return WindowsSpecimen.CREATE_PROCESS;
@@ -29,11 +29,6 @@ import ghidra.program.model.address.Address;
public abstract class AbstractModelForDbgengScenarioMemoryTest
extends AbstractDebuggerModelScenarioMemoryTest {
@Override
protected TargetObject findProcessContainer() throws Throwable {
return m.findContainer(TargetProcess.class, PathUtils.parse("Sessions[0]"));
}
@Override
protected WindowsSpecimen getSpecimen() {
return WindowsSpecimen.PRINT;
@@ -31,11 +31,6 @@ public abstract class AbstractModelForDbgengScenarioStackTest
List.of("break_here", "funcC", "funcB", "funcA");
protected NavigableMap<Address, String> symbolsByAddress = new TreeMap<>();
@Override
protected TargetObject findProcessContainer() throws Throwable {
return m.findContainer(TargetProcess.class, PathUtils.parse("Sessions[0]"));
}
@Override
protected WindowsSpecimen getSpecimen() {
return WindowsSpecimen.STACK;
@@ -20,19 +20,12 @@ import static org.junit.Assert.assertEquals;
import java.util.Map;
import agent.dbgeng.model.impl.DbgModelTargetProcessImpl;
import ghidra.dbg.target.TargetObject;
import ghidra.dbg.target.TargetProcess;
import ghidra.dbg.test.AbstractDebuggerModelScenarioRegistersTest;
import ghidra.dbg.util.PathUtils;
public abstract class AbstractModelForDbgengScenarioX64RegistersTest
extends AbstractDebuggerModelScenarioRegistersTest {
@Override
protected TargetObject findProcessContainer() throws Throwable {
return m.findContainer(TargetProcess.class, PathUtils.parse("Sessions[0]"));
}
@Override
protected DebuggerTestSpecimen getSpecimen() {
return WindowsSpecimen.REGISTERS;
@@ -17,19 +17,11 @@ package agent.dbgeng.model;
import java.util.List;
import ghidra.dbg.target.TargetObject;
import ghidra.dbg.target.TargetProcess;
import ghidra.dbg.test.*;
import ghidra.dbg.util.PathUtils;
public abstract class AbstractModelForDbgengSteppableTest extends AbstractDebuggerModelSteppableTest
implements ProvidesTargetViaLaunchSpecimen {
@Override
protected TargetObject findProcessContainer() throws Throwable {
return m.findContainer(TargetProcess.class, PathUtils.parse("Sessions[0]"));
}
@Override
public AbstractDebuggerModelTest getTest() {
return this;
@@ -44,7 +44,7 @@ public abstract class AbstractModelForDbgengThreadFocusTest
}
return retry(() -> {
Map<List<String>, TargetThread> found =
m.findAll(TargetThread.class, PathUtils.parse("Sessions[0]"));
m.findAll(TargetThread.class, PathUtils.parse("Sessions[0]"), true);
assertEquals(count, found.size());
return Set.copyOf(found.values());
}, List.of(AssertionError.class));
@@ -18,8 +18,6 @@ package agent.dbgeng.model;
import java.util.List;
import java.util.Map;
import ghidra.dbg.target.TargetObject;
import ghidra.dbg.target.TargetProcess;
import ghidra.dbg.test.*;
import ghidra.dbg.util.PathUtils;
@@ -37,11 +35,6 @@ public abstract class AbstractModelForDbgengX64RegistersTest
return this;
}
@Override
protected TargetObject findProcessContainer() throws Throwable {
return m.findContainer(TargetProcess.class, PathUtils.parse("Sessions[0]"));
}
@Override
public List<String> getExpectedRegisterBankPath(List<String> threadPath) {
return PathUtils.extend(threadPath, PathUtils.parse("Registers"));
@@ -100,7 +100,6 @@ public enum WindowsSpecimen implements DebuggerTestSpecimen, DebuggerModelTestUt
public String getBinModuleName() {
return getShortName(getCommandLine().split("\\s+")[0]);
}
@Override
@@ -109,7 +108,7 @@ public enum WindowsSpecimen implements DebuggerTestSpecimen, DebuggerModelTestUt
// NB. ShellUtils.parseArgs removes the \s. Not good.
String expected = getBinModuleName();
Collection<TargetModule> modules =
test.m.findAll(TargetModule.class, process.getPath()).values();
test.m.findAll(TargetModule.class, process.getPath(), true).values();
return modules.stream()
.anyMatch(m -> expected.equalsIgnoreCase(getShortName(m.getModuleName())));
}
@@ -31,11 +31,6 @@ public abstract class AbstractModelForGdbAttacherTest extends AbstractDebuggerMo
return List.of("Available");
}
@Override
public List<String> getExpectedProcessesContainerPath() {
return List.of("Inferiors");
}
@Override
public DebuggerTestSpecimen getAttachSpecimen() {
return GdbLinuxSpecimen.DD;
@@ -17,6 +17,7 @@ package agent.gdb.model;
import java.util.List;
import ghidra.dbg.target.TargetProcess;
import ghidra.dbg.test.AbstractDebuggerModelInterpreterTest;
public abstract class AbstractModelForGdbInterpreterTest
@@ -41,6 +42,16 @@ public abstract class AbstractModelForGdbInterpreterTest
return "attach " + dummy.pid;
}
@Override
protected String getDetachCommand(TargetProcess process) {
return "detach";
}
@Override
protected String getKillCommand(TargetProcess process) {
return "kill";
}
@Override
public DebuggerTestSpecimen getAttachSpecimen() {
return GdbLinuxSpecimen.DD;
@@ -18,7 +18,6 @@ package agent.gdb.model;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.util.List;
import java.util.Map;
import ghidra.dbg.target.TargetEnvironment;
@@ -28,11 +27,6 @@ import ghidra.dbg.test.AbstractDebuggerModelLauncherTest;
public abstract class AbstractModelForGdbLauncherTest extends AbstractDebuggerModelLauncherTest {
@Override
public List<String> getExpectedProcessesContainerPath() {
return List.of("Inferiors");
}
@Override
public DebuggerTestSpecimen getLaunchSpecimen() {
return GdbLinuxSpecimen.ECHO_HW;
@@ -21,8 +21,7 @@ import java.awt.event.MouseEvent;
import java.util.*;
import java.util.concurrent.TimeUnit;
import org.junit.Before;
import org.junit.Test;
import org.junit.*;
import com.google.common.collect.Range;
@@ -489,6 +488,7 @@ public class DebuggerModulesProviderTest extends AbstractGhidraHeadedDebuggerGUI
}
@Test
@Ignore("This action is hidden until supported")
public void testActionCaptureTypes() throws Exception {
assertFalse(modulesProvider.actionCaptureTypes.isEnabled());
createTestModel();
@@ -26,6 +26,7 @@ import ghidra.dbg.target.schema.EnumerableTargetObjectSchema;
import ghidra.dbg.target.schema.TargetObjectSchema;
import ghidra.dbg.util.PathUtils;
import ghidra.lifecycle.Internal;
import ghidra.util.Msg;
import ghidra.util.datastruct.ListenerSet;
/**
@@ -245,8 +246,13 @@ public abstract class AbstractTargetObject<P extends TargetObject> implements Sp
valid = false;
model.objectInvalidated(getProxy());
listeners.fire.invalidated(getProxy(), branch, reason);
listeners.clear();
listeners.clearChained();
CompletableFuture.runAsync(() -> {
listeners.clear();
listeners.clearChained();
}, model.clientExecutor).exceptionally(ex -> {
Msg.error(this, "Error emptying invalidated object's listener set: ", ex);
return null;
});
}
protected void doInvalidateElements(Map<String, ?> elems, String reason) {
@@ -16,7 +16,8 @@
package ghidra.dbg.test;
import static org.junit.Assert.*;
import static org.junit.Assume.*;
import static org.junit.Assume.assumeNotNull;
import static org.junit.Assume.assumeTrue;
import java.lang.invoke.MethodHandles;
import java.util.Collection;
@@ -33,7 +34,6 @@ import ghidra.dbg.error.DebuggerIllegalArgumentException;
import ghidra.dbg.target.*;
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
import ghidra.dbg.target.TargetMethod.TargetParameterMap;
import ghidra.dbg.testutil.ElementTrackingListener;
import ghidra.program.model.address.AddressFactory;
public abstract class AbstractDebuggerModelAttacherTest extends AbstractDebuggerModelTest
@@ -47,10 +47,6 @@ public abstract class AbstractDebuggerModelAttacherTest extends AbstractDebugger
return null;
}
public List<String> getExpectedProcessesContainerPath() {
return null;
}
public abstract TargetParameterMap getExpectedAttachParameters();
public abstract void assertEnvironment(TargetEnvironment environment);
@@ -65,16 +61,6 @@ public abstract class AbstractDebuggerModelAttacherTest extends AbstractDebugger
assertEquals(expectedAttacherPath, attacher.getPath());
}
@Test
public void testProcessContainerIsWhereExpected() throws Throwable {
List<String> expectedProcessContainerPath = getExpectedProcessesContainerPath();
assumeNotNull(expectedProcessContainerPath);
m.build();
TargetObject container = findProcessContainer();
assertEquals(expectedProcessContainerPath, container.getPath());
}
@Test
public void testAttachableContainerIsWhereExpected() throws Throwable {
List<String> expectedAttachableContainerPath = getExpectedAttachableContainerPath();
@@ -195,12 +181,12 @@ public abstract class AbstractDebuggerModelAttacherTest extends AbstractDebugger
runTestAttachByObjBogusThrowsException(attacher);
}
protected void runTestAttachByPidThenDetach(TargetAttacher attacher, TargetObject container)
protected void runTestAttachByPidThenDetach(TargetAttacher attacher)
throws Throwable {
DebuggerTestSpecimen specimen = getAttachSpecimen();
assertNull(getProcessRunning(container, specimen, this));
assertNull(getProcessRunning(specimen, this));
runTestAttachByPid(attacher);
runTestDetach(container, specimen);
runTestDetach(specimen);
assertTrue(dummy.process.isAlive());
}
@@ -212,16 +198,15 @@ public abstract class AbstractDebuggerModelAttacherTest extends AbstractDebugger
dummy = specimen.runDummy();
TargetAttacher attacher = findAttacher();
TargetObject container = findProcessContainer();
runTestAttachByPidThenDetach(attacher, container);
runTestAttachByPidThenDetach(attacher);
}
protected void runTestAttachByPidThenKill(TargetAttacher attacher, TargetObject container)
protected void runTestAttachByPidThenKill(TargetAttacher attacher)
throws Throwable {
DebuggerTestSpecimen specimen = getAttachSpecimen();
assertNull(getProcessRunning(container, specimen, this));
assertNull(getProcessRunning(specimen, this));
runTestAttachByPid(attacher);
runTestKill(container, specimen);
runTestKill(specimen);
retryVoid(() -> assertFalse(dummy.process.isAlive()), List.of(AssertionError.class));
}
@@ -233,16 +218,14 @@ public abstract class AbstractDebuggerModelAttacherTest extends AbstractDebugger
dummy = specimen.runDummy();
TargetAttacher attacher = findAttacher();
TargetObject container = findProcessContainer();
runTestAttachByPidThenKill(attacher, container);
runTestAttachByPidThenKill(attacher);
}
protected void runTestAttachByPidThenResumeInterrupt(TargetAttacher attacher,
TargetObject container) throws Throwable {
protected void runTestAttachByPidThenResumeInterrupt(TargetAttacher attacher) throws Throwable {
DebuggerTestSpecimen specimen = getAttachSpecimen();
assertNull(getProcessRunning(container, specimen, this));
assertNull(getProcessRunning(specimen, this));
runTestAttachByPid(attacher);
runTestResumeInterruptMany(container, specimen, 3);
runTestResumeInterruptMany(specimen, 3);
assertTrue(dummy.process.isAlive());
}
@@ -254,16 +237,14 @@ public abstract class AbstractDebuggerModelAttacherTest extends AbstractDebugger
dummy = specimen.runDummy();
TargetAttacher attacher = findAttacher();
TargetObject container = findProcessContainer();
runTestAttachByPidThenResumeInterrupt(attacher, container);
runTestAttachByPidThenResumeInterrupt(attacher);
}
protected void runTestAttachShowsInProcessContainer(TargetAttacher attacher,
TargetObject container) throws Throwable {
protected void runTestAttachShowsInProcessContainer(TargetAttacher attacher) throws Throwable {
DebuggerTestSpecimen specimen = getAttachSpecimen();
assertNull(getProcessRunning(container, specimen, this));
assertNull(getProcessRunning(specimen, this));
runTestAttachByPid(attacher);
retryForProcessRunning(container, specimen, this);
retryForProcessRunning(specimen, this);
}
@Test
@@ -274,36 +255,6 @@ public abstract class AbstractDebuggerModelAttacherTest extends AbstractDebugger
dummy = specimen.runDummy();
TargetAttacher attacher = findAttacher();
TargetObject container = findProcessContainer();
runTestAttachShowsInProcessContainer(attacher, container);
}
protected void runTestAttachShowsInProcessContainerViaListener(TargetAttacher attacher,
TargetObject container) throws Throwable {
DebuggerTestSpecimen specimen = getAttachSpecimen();
ElementTrackingListener<? extends TargetProcess> procListener =
new ElementTrackingListener<>(TargetProcess.class);
container.addListener(procListener);
// NB. Have to express interest, otherwise model is not obligated to invoke listener
Collection<TargetProcess> procsBefore = fetchProcesses(container);
procListener.putAll(container.getCachedElements());
assertNull(getProcessRunning(procsBefore, specimen, this));
runTestAttachByPid(attacher);
retryVoid(() -> {
// Cannot fetch elements. rely only on listener.
assertNotNull(getProcessRunning(procListener.elements.values(), specimen, this));
}, List.of(AssertionError.class));
}
@Test
public void testAttachShowsInProcessContainerViaListener() throws Throwable {
DebuggerTestSpecimen specimen = getAttachSpecimen();
assumeTrue(m.hasProcessContainer());
m.build();
dummy = specimen.runDummy();
TargetAttacher attacher = findAttacher();
TargetObject container = findProcessContainer();
runTestAttachShowsInProcessContainerViaListener(attacher, container);
runTestAttachShowsInProcessContainer(attacher);
}
}
@@ -16,7 +16,8 @@
package ghidra.dbg.test;
import static org.junit.Assert.*;
import static org.junit.Assume.*;
import static org.junit.Assume.assumeNotNull;
import static org.junit.Assume.assumeTrue;
import java.util.*;
import java.util.Map.Entry;
@@ -38,18 +39,16 @@ import ghidra.util.Msg;
* Tests the functionality of breakpoints
*
* <p>
* Note that this test does not check for nuances regarding specification vs.
* location, as it is meant to generalize across models for interests of the UI
* only. As such, we only test that we can set breakpoints at given addresses
* and that a location manifests there, regardless of the intervening
* mechanisms. We also test some basic operations on the breakpoint (location)
* itself. Models which have separate specifications from locations, or for
* which you want to test non-address specifications will need to add their own
* tests, tailored to the semantics of that model's breakpoint specifications.
* Note that this test does not check for nuances regarding specification vs. location, as it is
* meant to generalize across models for interests of the UI only. As such, we only test that we can
* set breakpoints at given addresses and that a location manifests there, regardless of the
* intervening mechanisms. We also test some basic operations on the breakpoint (location) itself.
* Models which have separate specifications from locations, or for which you want to test
* non-address specifications will need to add their own tests, tailored to the semantics of that
* model's breakpoint specifications.
*
* <p>
* TODO: Enable, disable (if supported), delete (if supported), manipulation via
* CLI is synced
* TODO: Enable, disable (if supported), delete (if supported), manipulation via CLI is synced
*/
public abstract class AbstractDebuggerModelBreakpointsTest extends AbstractDebuggerModelTest
implements RequiresTarget {
@@ -225,7 +224,7 @@ public abstract class AbstractDebuggerModelBreakpointsTest extends AbstractDebug
waitOn(container.placeBreakpoint(range, Set.of(kind)));
retryVoid(() -> {
Collection<? extends TargetBreakpointLocation> found =
m.findAll(TargetBreakpointLocation.class, target.getPath()).values();
m.findAll(TargetBreakpointLocation.class, target.getPath(), true).values();
assertAtLeastOneLocCovers(found, range, kind);
}, List.of(AssertionError.class));
}
@@ -261,7 +260,7 @@ public abstract class AbstractDebuggerModelBreakpointsTest extends AbstractDebug
waitOn(container.placeBreakpoint(range, Set.of(kind)));
locs.add(retry(() -> {
Collection<? extends TargetBreakpointLocation> found =
m.findAll(TargetBreakpointLocation.class, target.getPath()).values();
m.findAll(TargetBreakpointLocation.class, target.getPath(), true).values();
return assertAtLeastOneLocCovers(found, range, kind);
}, List.of(AssertionError.class)));
}
@@ -27,6 +27,10 @@ import ghidra.dbg.target.TargetFocusScope;
import ghidra.dbg.target.TargetObject;
import ghidra.dbg.util.PathUtils;
/**
* TODO: Since activation and focus are separate concepts, we need to fix the terminology here and
* ensure we're testing the right things.
*/
public abstract class AbstractDebuggerModelFocusTest extends AbstractDebuggerModelTest {
/**
@@ -15,9 +15,10 @@
*/
package ghidra.dbg.test;
import static ghidra.lifecycle.Unfinished.*;
import static ghidra.lifecycle.Unfinished.TODO;
import static org.junit.Assert.*;
import static org.junit.Assume.*;
import static org.junit.Assume.assumeNotNull;
import static org.junit.Assume.assumeTrue;
import java.util.List;
import java.util.stream.Collectors;
@@ -27,23 +28,39 @@ import org.junit.Ignore;
import org.junit.Test;
import ghidra.async.AsyncReference;
import ghidra.dbg.DebugModelConventions;
import ghidra.dbg.DebuggerModelListener;
import ghidra.dbg.error.DebuggerModelTerminatingException;
import ghidra.dbg.target.*;
import ghidra.dbg.target.TargetConsole.Channel;
import ghidra.dbg.target.TargetInterpreter;
import ghidra.dbg.target.TargetObject;
import ghidra.dbg.testutil.CatchOffThread;
import ghidra.util.Msg;
public abstract class AbstractDebuggerModelInterpreterTest extends AbstractDebuggerModelTest
implements RequiresAttachSpecimen, RequiresLaunchSpecimen {
/**
* Get the path of the expected result of {@link #findInterpreter()} for this test
*
* @return the expected path
*/
public List<String> getExpectedInterpreterPath() {
return null;
}
/**
* Get the CLI command to echo a string back
*
* @param msg the message to echo
* @return the command
*/
protected abstract String getEchoCommand(String msg);
/**
* If applicable, get the CLI command to terminate the session / model
*
* @return the command
*/
protected abstract String getQuitCommand();
/**
@@ -53,6 +70,30 @@ public abstract class AbstractDebuggerModelInterpreterTest extends AbstractDebug
*/
protected abstract String getAttachCommand();
/**
* Get the CLI command to detach from the given process
*
* <p>
* Note that the given process should already be the current/active process of the interpreter,
* so the parameter may not be needed.
*
* @param process the process to detach from, which should already be active
* @return the command
*/
protected abstract String getDetachCommand(TargetProcess process);
/**
* Get the CLI command to kill the given process
*
* <p>
* Note that the given process should already be the current/active process of the interpreter,
* so the parameter may not be needed.
*
* @param process the process to kill, which should already be active
* @return the command
*/
protected abstract String getKillCommand(TargetProcess process);
@Test
public void testInterpreterIsWhereExpected() throws Throwable {
List<String> expectedInterpreterPath = getExpectedInterpreterPath();
@@ -126,6 +167,11 @@ public abstract class AbstractDebuggerModelInterpreterTest extends AbstractDebug
runTestExecuteCapture(interpreter, cmd);
}
/**
* Test that the user quitting via the CLI properly terminates the model
*
* @throws Throwable expected since the model will terminate
*/
@Test(expected = DebuggerModelTerminatingException.class)
public void testExecuteQuit() throws Throwable {
String cmd = getQuitCommand();
@@ -151,14 +197,22 @@ public abstract class AbstractDebuggerModelInterpreterTest extends AbstractDebug
// TODO: Delete (spec vs. loc?)
}
protected void runTestLaunchViaInterpreterShowsInProcessContainer(TargetInterpreter interpreter,
TargetObject container) throws Throwable {
protected TargetProcess runTestLaunchViaInterpreterShowsInProcessContainer(
TargetInterpreter interpreter) throws Throwable {
DebuggerTestSpecimen specimen = getLaunchSpecimen();
assertNull(getProcessRunning(container, specimen, this));
assertNull(getProcessRunning(specimen, this));
for (String line : specimen.getLaunchScript()) {
waitOn(interpreter.execute(line));
}
retryForProcessRunning(container, specimen, this);
return retryForProcessRunning(specimen, this);
}
protected void runTestKillViaInterpreter(TargetProcess process, TargetInterpreter interpreter)
throws Throwable {
waitOn(interpreter.execute(getKillCommand(process)));
retryVoid(() -> {
assertFalse(DebugModelConventions.isProcessAlive(process));
}, List.of(AssertionError.class));
}
@Test
@@ -167,18 +221,26 @@ public abstract class AbstractDebuggerModelInterpreterTest extends AbstractDebug
m.build();
TargetInterpreter interpreter = findInterpreter();
TargetObject container = findProcessContainer();
assertNotNull("No process container", container);
runTestLaunchViaInterpreterShowsInProcessContainer(interpreter, container);
TargetProcess process = runTestLaunchViaInterpreterShowsInProcessContainer(interpreter);
runTestKillViaInterpreter(process, interpreter);
}
protected void runTestAttachViaInterpreterShowsInProcessContainer(TargetInterpreter interpreter,
TargetObject container) throws Throwable {
protected TargetProcess runTestAttachViaInterpreterShowsInProcessContainer(
TargetInterpreter interpreter) throws Throwable {
DebuggerTestSpecimen specimen = getAttachSpecimen();
assertNull(getProcessRunning(container, specimen, this));
assertNull(getProcessRunning(specimen, this));
String cmd = getAttachCommand();
waitOn(interpreter.execute(cmd));
retryForProcessRunning(container, specimen, this);
return retryForProcessRunning(specimen, this);
}
protected void runTestDetachViaInterpreter(TargetProcess process, TargetInterpreter interpreter)
throws Throwable {
waitOn(interpreter.execute(getDetachCommand(process)));
retryVoid(() -> {
assertFalse(DebugModelConventions.isProcessAlive(process));
}, List.of(AssertionError.class));
}
@Test
@@ -189,8 +251,8 @@ public abstract class AbstractDebuggerModelInterpreterTest extends AbstractDebug
dummy = specimen.runDummy();
TargetInterpreter interpreter = findInterpreter();
TargetObject container = findProcessContainer();
assertNotNull("No process container", container);
runTestAttachViaInterpreterShowsInProcessContainer(interpreter, container);
TargetProcess process = runTestAttachViaInterpreterShowsInProcessContainer(interpreter);
runTestDetachViaInterpreter(process, interpreter);
}
}
@@ -15,12 +15,12 @@
*/
package ghidra.dbg.test;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assume.assumeNotNull;
import static org.junit.Assume.assumeTrue;
import java.lang.invoke.MethodHandles;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@@ -31,7 +31,6 @@ import ghidra.dbg.DebugModelConventions;
import ghidra.dbg.target.*;
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
import ghidra.dbg.target.TargetMethod.TargetParameterMap;
import ghidra.dbg.testutil.ElementTrackingListener;
public abstract class AbstractDebuggerModelLauncherTest extends AbstractDebuggerModelTest
implements RequiresLaunchSpecimen {
@@ -40,10 +39,6 @@ public abstract class AbstractDebuggerModelLauncherTest extends AbstractDebugger
return null;
}
public List<String> getExpectedProcessesContainerPath() {
return null;
}
public abstract TargetParameterMap getExpectedLauncherParameters();
public abstract void assertEnvironment(TargetEnvironment environment);
@@ -58,16 +53,6 @@ public abstract class AbstractDebuggerModelLauncherTest extends AbstractDebugger
assertEquals(expectedLauncherPath, launcher.getPath());
}
@Test
public void testProcessContainerIsWhereExpected() throws Throwable {
List<String> expectedProcessContainerPath = getExpectedProcessesContainerPath();
assumeNotNull(expectedProcessContainerPath);
m.build();
TargetObject container = findProcessContainer();
assertEquals(expectedProcessContainerPath, container.getPath());
}
protected void runTestLaunchParameters(TargetLauncher launcher,
TargetParameterMap expectedParameters) throws Throwable {
waitAcc(launcher);
@@ -123,12 +108,11 @@ public abstract class AbstractDebuggerModelLauncherTest extends AbstractDebugger
waitOn(listener.observedCreated);
}
protected void runTestLaunchThenDetach(TargetLauncher launcher,
TargetObject container) throws Throwable {
protected void runTestLaunchThenDetach(TargetLauncher launcher) throws Throwable {
DebuggerTestSpecimen specimen = getLaunchSpecimen();
assertNull(getProcessRunning(container, specimen, this));
assertNull(getProcessRunning(specimen, this));
runTestLaunch(launcher);
runTestDetach(container, specimen);
runTestDetach(specimen);
}
@Test
@@ -137,16 +121,14 @@ public abstract class AbstractDebuggerModelLauncherTest extends AbstractDebugger
m.build();
TargetLauncher launcher = findLauncher();
TargetObject container = findProcessContainer();
runTestLaunchThenDetach(launcher, container);
runTestLaunchThenDetach(launcher);
}
protected void runTestLaunchThenKill(TargetLauncher launcher,
TargetObject container) throws Throwable {
protected void runTestLaunchThenKill(TargetLauncher launcher) throws Throwable {
DebuggerTestSpecimen specimen = getLaunchSpecimen();
assertNull(getProcessRunning(container, specimen, this));
assertNull(getProcessRunning(specimen, this));
runTestLaunch(launcher);
runTestKill(container, specimen);
runTestKill(specimen);
}
@Test
@@ -155,16 +137,14 @@ public abstract class AbstractDebuggerModelLauncherTest extends AbstractDebugger
m.build();
TargetLauncher launcher = findLauncher();
TargetObject container = findProcessContainer();
runTestLaunchThenKill(launcher, container);
runTestLaunchThenKill(launcher);
}
protected void runTestLaunchThenResume(TargetLauncher launcher,
TargetObject container) throws Throwable {
protected void runTestLaunchThenResume(TargetLauncher launcher) throws Throwable {
DebuggerTestSpecimen specimen = getLaunchSpecimen();
assertNull(getProcessRunning(container, specimen, this));
assertNull(getProcessRunning(specimen, this));
runTestLaunch(launcher);
runTestResumeTerminates(container, specimen);
runTestResumeTerminates(specimen);
}
@Test
@@ -173,16 +153,14 @@ public abstract class AbstractDebuggerModelLauncherTest extends AbstractDebugger
m.build();
TargetLauncher launcher = findLauncher();
TargetObject container = findProcessContainer();
runTestLaunchThenResume(launcher, container);
runTestLaunchThenResume(launcher);
}
protected void runTestLaunchShowsInProcessContainer(TargetLauncher launcher,
TargetObject container) throws Throwable {
protected void runTestLaunchShowsInProcessContainer(TargetLauncher launcher) throws Throwable {
DebuggerTestSpecimen specimen = getLaunchSpecimen();
assertNull(getProcessRunning(container, specimen, this));
assertNull(getProcessRunning(specimen, this));
runTestLaunch(launcher);
retryForProcessRunning(container, specimen, this);
retryForProcessRunning(specimen, this);
}
@Test
@@ -191,34 +169,6 @@ public abstract class AbstractDebuggerModelLauncherTest extends AbstractDebugger
m.build();
TargetLauncher launcher = findLauncher();
TargetObject container = findProcessContainer();
runTestLaunchShowsInProcessContainer(launcher, container);
}
protected void runTestLaunchShowsInProcessContainerViaListener(
TargetLauncher launcher, TargetObject container) throws Throwable {
DebuggerTestSpecimen specimen = getLaunchSpecimen();
ElementTrackingListener<? extends TargetProcess> procListener =
new ElementTrackingListener<>(TargetProcess.class);
container.addListener(procListener);
// NB. Have to express interest, otherwise model is not obligated to invoke listener
Collection<TargetProcess> procsBefore = fetchProcesses(container);
procListener.putAll(container.getCachedElements());
assertNull(getProcessRunning(procsBefore, specimen, this));
runTestLaunch(launcher);
retryVoid(() -> {
// Cannot fetch elements. rely only on listener.
assertNotNull(getProcessRunning(procListener.elements.values(), specimen, this));
}, List.of(AssertionError.class));
}
@Test
public void testLaunchShowsInProcessContainerViaListener() throws Throwable {
assumeTrue(m.hasProcessContainer());
m.build();
TargetLauncher launcher = findLauncher();
TargetObject container = findProcessContainer();
runTestLaunchShowsInProcessContainerViaListener(launcher, container);
runTestLaunchShowsInProcessContainer(launcher);
}
}
@@ -15,7 +15,8 @@
*/
package ghidra.dbg.test;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.lang.invoke.MethodHandles;
import java.util.*;
@@ -131,14 +132,12 @@ public abstract class AbstractDebuggerModelScenarioCloneExitTest extends Abstrac
Msg.debug(this, "Launching " + specimen);
waitOn(launcher.launch(specimen.getLauncherArgs()));
Msg.debug(this, " Done launching");
TargetObject processContainer = findProcessContainer();
assertNotNull("No process container", processContainer);
TargetProcess process = retryForProcessRunning(processContainer, specimen, this);
TargetProcess process = retryForProcessRunning(specimen, this);
postLaunch(process);
TargetBreakpointSpecContainer breakpointContainer =
TargetBreakpointSpecContainer bpContainer =
findBreakpointSpecContainer(process.getPath());
Msg.debug(this, "Placing breakpoint");
waitOn(breakpointContainer.placeBreakpoint(getBreakpointExpression(),
waitOn(bpContainer.placeBreakpoint(getBreakpointExpression(),
Set.of(TargetBreakpointKind.SW_EXECUTE)));
assertTrue(DebugModelConventions.isProcessAlive(process));
@@ -182,8 +182,6 @@ public abstract class AbstractDebuggerModelScenarioForkExitTest extends Abstract
Msg.debug(this, "Launching " + specimen);
waitOn(launcher.launch(specimen.getLauncherArgs()));
Msg.debug(this, " Done launching");
TargetObject processContainer = findProcessContainer();
assertNotNull("No process container", processContainer);
TargetProcess parentProcess = waitOn(stateMonitor.observedParent);
Msg.debug(this, "Parent is " + parentProcess.getJoinedPath("."));
postLaunch(parentProcess);
@@ -201,7 +199,7 @@ public abstract class AbstractDebuggerModelScenarioForkExitTest extends Abstract
Msg.debug(this, " Done " + i);
waitAcc(access(parentProcess));
try {
childProcess = retryForOtherProcessRunning(processContainer, specimen, this,
childProcess = retryForOtherProcessRunning(specimen, this,
p -> p != parentProcess, WAIT_FOR_CHILD_MS);
}
catch (AssertionError e) {
@@ -141,8 +141,7 @@ public abstract class AbstractDebuggerModelScenarioMemoryTest extends AbstractDe
Msg.debug(this, "Launching " + specimen);
waitOn(launcher.launch(specimen.getLauncherArgs()));
Msg.debug(this, " Done launching");
TargetObject processContainer = findProcessContainer();
TargetProcess process = retryForProcessRunning(processContainer, specimen, this);
TargetProcess process = retryForProcessRunning(specimen, this);
postLaunch(process);
Address address = Objects.requireNonNull(getAddressToWrite(process));
@@ -15,7 +15,6 @@
*/
package ghidra.dbg.test;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.lang.invoke.MethodHandles;
@@ -138,9 +137,7 @@ public abstract class AbstractDebuggerModelScenarioRegistersTest extends Abstrac
Msg.debug(this, "Launching " + specimen);
waitOn(launcher.launch(specimen.getLauncherArgs()));
Msg.debug(this, " Done launching");
TargetObject processContainer = findProcessContainer();
assertNotNull(processContainer);
TargetProcess process = retryForProcessRunning(processContainer, specimen, this);
TargetProcess process = retryForProcessRunning(specimen, this);
postLaunch(process);
TargetBreakpointSpecContainer breakpointContainer =
@@ -122,8 +122,7 @@ public abstract class AbstractDebuggerModelScenarioStackTest extends AbstractDeb
TargetLauncher launcher = findLauncher();
waitOn(launcher.launch(specimen.getLauncherArgs()));
Msg.debug(this, " Done launching");
TargetObject processContainer = findProcessContainer();
TargetProcess process = retryForProcessRunning(processContainer, specimen, this);
TargetProcess process = retryForProcessRunning(specimen, this);
postLaunch(process);
TargetBreakpointSpecContainer breakpointContainer =
@@ -157,7 +156,7 @@ public abstract class AbstractDebuggerModelScenarioStackTest extends AbstractDeb
// Sort by path should present them innermost to outermost
List<TargetStackFrame> frames = retry(() -> {
List<TargetStackFrame> result =
List.copyOf(m.findAll(TargetStackFrame.class, stack.getPath()).values());
List.copyOf(m.findAll(TargetStackFrame.class, stack.getPath(), true).values());
assertTrue("Fewer than 4 frames", result.size() > 4);
return result;
}, List.of(AssertionError.class));
@@ -85,19 +85,6 @@ public abstract class AbstractDebuggerModelTest extends AbstractGhidraHeadlessIn
return m.find(TargetLauncher.class, seedPath());
}
/**
* Get the process container under test
*
* <p>
* This can be overridden to force a different object under the test.
*
* @return the process container
* @throws Throwable if anything goes wrong
*/
protected TargetObject findProcessContainer() throws Throwable {
return m.findContainer(TargetProcess.class, seedPath());
}
/**
* Get the breakpoint container of a target under test
*
@@ -285,9 +272,9 @@ public abstract class AbstractDebuggerModelTest extends AbstractGhidraHeadlessIn
return waitOn(listener.trapped);
}
protected void runTestDetach(TargetObject container, DebuggerTestSpecimen specimen)
protected void runTestDetach(DebuggerTestSpecimen specimen)
throws Throwable {
TargetProcess process = retryForProcessRunning(container, specimen, this);
TargetProcess process = retryForProcessRunning(specimen, this);
TargetDetachable detachable = m.suitable(TargetDetachable.class, process.getPath());
waitAcc(detachable);
waitOn(detachable.detach());
@@ -295,9 +282,9 @@ public abstract class AbstractDebuggerModelTest extends AbstractGhidraHeadlessIn
List.of(AssertionError.class));
}
protected void runTestKill(TargetObject container, DebuggerTestSpecimen specimen)
protected void runTestKill(DebuggerTestSpecimen specimen)
throws Throwable {
TargetProcess process = retryForProcessRunning(container, specimen, this);
TargetProcess process = retryForProcessRunning(specimen, this);
TargetKillable killable = m.suitable(TargetKillable.class, process.getPath());
waitAcc(killable);
waitOn(killable.kill());
@@ -305,9 +292,8 @@ public abstract class AbstractDebuggerModelTest extends AbstractGhidraHeadlessIn
List.of(AssertionError.class));
}
protected void runTestResumeTerminates(TargetObject container, DebuggerTestSpecimen specimen)
throws Throwable {
TargetProcess process = retryForProcessRunning(container, specimen, this);
protected void runTestResumeTerminates(DebuggerTestSpecimen specimen) throws Throwable {
TargetProcess process = retryForProcessRunning(specimen, this);
TargetResumable resumable = m.suitable(TargetResumable.class, process.getPath());
AsyncState state =
new AsyncState(m.suitable(TargetExecutionStateful.class, process.getPath()));
@@ -318,9 +304,9 @@ public abstract class AbstractDebuggerModelTest extends AbstractGhidraHeadlessIn
List.of(AssertionError.class));
}
protected void runTestResumeInterruptMany(TargetObject container, DebuggerTestSpecimen specimen,
protected void runTestResumeInterruptMany(DebuggerTestSpecimen specimen,
int repetitions) throws Throwable {
TargetProcess process = retryForProcessRunning(container, specimen, this);
TargetProcess process = retryForProcessRunning(specimen, this);
TargetResumable resumable = m.suitable(TargetResumable.class, process.getPath());
TargetInterruptible interruptible =
m.suitable(TargetInterruptible.class, process.getPath());
@@ -342,6 +328,6 @@ public abstract class AbstractDebuggerModelTest extends AbstractGhidraHeadlessIn
}, List.of(AssertionError.class));
}
}
waitOn(container.getModel().ping("Are you still there?"));
waitOn(m.getModel().ping("Are you still there?"));
}
}
@@ -206,16 +206,20 @@ public abstract class AbstractModelHost implements ModelHost, DebuggerModelTestU
@Override
public <T extends TargetObject> NavigableMap<List<String>, T> findAll(Class<T> cls,
List<String> seedPath) throws Throwable {
List<String> seedPath, boolean atLeastOne) throws Throwable {
PathMatcher matcher =
model.getRootSchema().getSuccessorSchema(seedPath).searchFor(cls, seedPath, false);
if (matcher.isEmpty()) {
return new TreeMap<>();
}
NavigableMap<List<String>, ?> found = atLeastOne
? waitOn(waiter.waitAtLeastOne(matcher))
: matcher.getCachedValues(model.getModelRoot());
// NB. Outside of testing, an "unsafe" cast of the map should be fine.
// During testing, we should expend the energy to verify the heap.
NavigableMap<List<String>, T> result = new TreeMap<>(PathComparator.KEYED);
for (Entry<List<String>, ?> ent : waitOn(waiter.waitAtLeastOne(matcher)).entrySet()) {
for (Entry<List<String>, ?> ent : found.entrySet()) {
result.put(ent.getKey(), cls.cast(ent.getValue()));
}
return result;
@@ -29,12 +29,11 @@ public interface ProvidesTargetViaAttachSpecimen extends RequiresTarget, Require
@Override
default TargetObject obtainTarget() throws Throwable {
TargetAttacher attacher = getTest().findAttacher();
TargetObject container = getTest().findProcessContainer();
DebuggerTestSpecimen specimen = getAttachSpecimen();
waitAcc(attacher);
DummyProc dummy = specimen.runDummy();
setDummy(dummy);
attacher.attach(dummy.pid);
return retryForProcessRunning(container, specimen, getTest());
return retryForProcessRunning(specimen, getTest());
}
}
@@ -15,7 +15,7 @@
*/
package ghidra.dbg.test;
import static org.junit.Assert.*;
import static org.junit.Assert.assertNotNull;
import ghidra.dbg.target.TargetLauncher;
import ghidra.dbg.target.TargetObject;
@@ -34,11 +34,9 @@ public interface ProvidesTargetViaLaunchSpecimen extends RequiresTarget, Require
default TargetObject obtainTarget() throws Throwable {
TargetLauncher launcher = getTest().findLauncher();
assertNotNull("No launcher found", launcher);
TargetObject container = getTest().findProcessContainer();
assertNotNull("No process container found", container);
DebuggerTestSpecimen specimen = getLaunchSpecimen();
waitAcc(launcher);
launcher.launch(specimen.getLauncherArgs());
return retryForProcessRunning(container, specimen, getTest());
return retryForProcessRunning(specimen, getTest());
}
}
@@ -31,6 +31,7 @@ import ghidra.dbg.target.*;
import ghidra.dbg.target.TargetSteppable.TargetStepKind;
import ghidra.dbg.test.AbstractDebuggerModelTest;
import ghidra.dbg.test.AbstractDebuggerModelTest.DebuggerTestSpecimen;
import ghidra.dbg.util.PathUtils;
import ghidra.util.NumericUtilities;
public interface DebuggerModelTestUtils extends AsyncTestUtils {
@@ -164,10 +165,9 @@ public interface DebuggerModelTestUtils extends AsyncTestUtils {
}).findFirst().orElse(null);
}
@SuppressWarnings({ "rawtypes", "unchecked" })
default Collection<TargetProcess> fetchProcesses(TargetObject container)
default Collection<TargetProcess> fetchProcesses(AbstractDebuggerModelTest test)
throws Throwable {
return (Collection) waitOn(container.fetchElements(true)).values();
return test.m.findAll(TargetProcess.class, PathUtils.parse(""), false).values();
}
@SuppressWarnings({ "rawtypes", "unchecked" })
@@ -176,32 +176,30 @@ public interface DebuggerModelTestUtils extends AsyncTestUtils {
return (Collection) waitOn(container.fetchElements(true)).values();
}
default TargetProcess getProcessRunning(TargetObject container,
DebuggerTestSpecimen specimen, AbstractDebuggerModelTest test) throws Throwable {
return getProcessRunning(container, specimen, test, p -> true);
default TargetProcess getProcessRunning(DebuggerTestSpecimen specimen,
AbstractDebuggerModelTest test) throws Throwable {
return getProcessRunning(specimen, test, p -> true);
}
default TargetProcess getProcessRunning(TargetObject container,
DebuggerTestSpecimen specimen, AbstractDebuggerModelTest test,
Predicate<TargetProcess> predicate) throws Throwable {
return getProcessRunning(fetchProcesses(container), specimen, test, predicate);
default TargetProcess getProcessRunning(DebuggerTestSpecimen specimen,
AbstractDebuggerModelTest test, Predicate<TargetProcess> predicate) throws Throwable {
return getProcessRunning(fetchProcesses(test), specimen, test, predicate);
}
default TargetProcess retryForProcessRunning(TargetObject container,
default TargetProcess retryForProcessRunning(
DebuggerTestSpecimen specimen, AbstractDebuggerModelTest test) throws Throwable {
return retry(() -> {
TargetProcess process = getProcessRunning(container, specimen, test);
TargetProcess process = getProcessRunning(specimen, test);
assertNotNull(process);
return process;
}, List.of(AssertionError.class));
}
default TargetProcess retryForOtherProcessRunning(TargetObject container,
DebuggerTestSpecimen specimen, AbstractDebuggerModelTest test,
Predicate<TargetProcess> predicate, long timeoutMs)
default TargetProcess retryForOtherProcessRunning(DebuggerTestSpecimen specimen,
AbstractDebuggerModelTest test, Predicate<TargetProcess> predicate, long timeoutMs)
throws Throwable {
return retry(timeoutMs, () -> {
TargetProcess process = getProcessRunning(container, specimen, test, predicate);
TargetProcess process = getProcessRunning(specimen, test, predicate);
assertNotNull(process);
return process;
}, List.of(AssertionError.class));
@@ -73,8 +73,8 @@ public interface TestDebuggerModelProvider {
<T extends TargetObject> T findAny(Class<T> cls, List<String> seedPath) throws Throwable;
<T extends TargetObject> Map<List<String>, T> findAll(Class<T> cls, List<String> seedPath)
throws Throwable;
<T extends TargetObject> Map<List<String>, T> findAll(Class<T> cls, List<String> seedPath,
boolean atLeastOne) throws Throwable;
TargetObject findContainer(Class<? extends TargetObject> cls, List<String> seedPath)
throws Throwable;