mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-05-22 19:35:42 +08:00
GP-785: Prompting for connection parameters, too.
This commit is contained in:
+11
-22
@@ -20,23 +20,22 @@ import ghidra.app.plugin.core.debug.AbstractDebuggerPlugin;
|
||||
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
||||
import ghidra.app.plugin.core.debug.event.ModelActivatedPluginEvent;
|
||||
import ghidra.app.services.DebuggerModelService;
|
||||
import ghidra.framework.options.SaveState;
|
||||
import ghidra.framework.plugintool.*;
|
||||
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
|
||||
import ghidra.framework.plugintool.util.PluginStatus;
|
||||
|
||||
@PluginInfo( //
|
||||
shortDescription = "Debugger targets manager", //
|
||||
description = "GUI to manage connections to external debuggers and trace recording", //
|
||||
category = PluginCategoryNames.DEBUGGER, //
|
||||
packageName = DebuggerPluginPackage.NAME, //
|
||||
status = PluginStatus.RELEASED, //
|
||||
eventsConsumed = {
|
||||
ModelActivatedPluginEvent.class, //
|
||||
}, //
|
||||
servicesRequired = { //
|
||||
DebuggerModelService.class, //
|
||||
} //
|
||||
shortDescription = "Debugger targets manager", //
|
||||
description = "GUI to manage connections to external debuggers and trace recording", //
|
||||
category = PluginCategoryNames.DEBUGGER, //
|
||||
packageName = DebuggerPluginPackage.NAME, //
|
||||
status = PluginStatus.RELEASED, //
|
||||
eventsConsumed = {
|
||||
ModelActivatedPluginEvent.class, //
|
||||
}, //
|
||||
servicesRequired = { //
|
||||
DebuggerModelService.class, //
|
||||
} //
|
||||
)
|
||||
public class DebuggerTargetsPlugin extends AbstractDebuggerPlugin {
|
||||
@AutoServiceConsumed
|
||||
@@ -68,14 +67,4 @@ public class DebuggerTargetsPlugin extends AbstractDebuggerPlugin {
|
||||
provider.modelActivated(evt.getActiveModel());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeConfigState(SaveState saveState) {
|
||||
provider.writeConfigState(saveState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readConfigState(SaveState saveState) {
|
||||
provider.readConfigState(saveState);
|
||||
}
|
||||
}
|
||||
|
||||
+3
-14
@@ -35,7 +35,6 @@ import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
|
||||
import ghidra.app.services.DebuggerModelService;
|
||||
import ghidra.dbg.DebuggerObjectModel;
|
||||
import ghidra.framework.options.SaveState;
|
||||
import ghidra.framework.plugintool.AutoService;
|
||||
import ghidra.framework.plugintool.ComponentProviderAdapter;
|
||||
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
|
||||
@@ -106,8 +105,9 @@ public class DebuggerTargetsProvider extends ComponentProviderAdapter {
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionContext context) {
|
||||
connectDialog.reset();
|
||||
tool.showDialog(connectDialog);
|
||||
// NB. Drop the future on the floor, because the UI will report issues.
|
||||
// Cancellation should be ignored.
|
||||
modelService.showConnectDialog();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -163,8 +163,6 @@ public class DebuggerTargetsProvider extends ComponentProviderAdapter {
|
||||
protected GTree tree;
|
||||
protected DebuggerConnectionsNode rootNode;
|
||||
|
||||
protected DebuggerConnectDialog connectDialog = new DebuggerConnectDialog();
|
||||
|
||||
ConnectAction actionConnect;
|
||||
DisconnectAction actionDisconnect;
|
||||
DockingAction actionDisconnectAll;
|
||||
@@ -267,7 +265,6 @@ public class DebuggerTargetsProvider extends ComponentProviderAdapter {
|
||||
rootNode = new DebuggerConnectionsNode(modelService, this);
|
||||
tree.setRootNode(rootNode);
|
||||
}
|
||||
connectDialog.setModelService(modelService);
|
||||
}
|
||||
|
||||
protected void updateTree(boolean select, Object obj) {
|
||||
@@ -304,12 +301,4 @@ public class DebuggerTargetsProvider extends ComponentProviderAdapter {
|
||||
model.invalidateAllLocalCaches();
|
||||
}
|
||||
}
|
||||
|
||||
public void writeConfigState(SaveState saveState) {
|
||||
connectDialog.writeConfigState(saveState);
|
||||
}
|
||||
|
||||
public void readConfigState(SaveState saveState) {
|
||||
connectDialog.readConfigState(saveState);
|
||||
}
|
||||
}
|
||||
|
||||
+6
-1
@@ -108,12 +108,17 @@ public class GdbDebuggerProgramLaunchOpinion implements DebuggerProgramLaunchOpi
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMenuTitle() {
|
||||
public String getQuickTitle() {
|
||||
Map<String, Property<?>> opts = factory.getOptions();
|
||||
return String.format("in GDB via ssh:%s@%s",
|
||||
opts.get("SSH username").getValue(),
|
||||
opts.get("SSH hostname").getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMenuTitle() {
|
||||
return "in GDB via ssh";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+35
-5
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.gui.target;
|
||||
package ghidra.app.plugin.core.debug.service.model;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
@@ -86,6 +86,7 @@ public class DebuggerConnectDialog extends DialogComponentProvider
|
||||
|
||||
protected JButton connectButton;
|
||||
protected CompletableFuture<? extends DebuggerObjectModel> futureConnect;
|
||||
protected CompletableFuture<DebuggerObjectModel> result;
|
||||
|
||||
protected static class FactoryEntry {
|
||||
DebuggerModelFactory factory;
|
||||
@@ -236,11 +237,21 @@ public class DebuggerConnectDialog extends DialogComponentProvider
|
||||
synchronized (this) {
|
||||
futureConnect = factory.build();
|
||||
}
|
||||
futureConnect.thenAcceptAsync(model -> {
|
||||
modelService.addModel(model);
|
||||
futureConnect.thenAcceptAsync(m -> {
|
||||
modelService.addModel(m);
|
||||
setStatusText("");
|
||||
close();
|
||||
modelService.activateModel(model);
|
||||
modelService.activateModel(m);
|
||||
synchronized (this) {
|
||||
/**
|
||||
* NB. Errors will typically be reported, the dialog stays up, and the user is given
|
||||
* an opportunity to rectify the failure. Thus, errors should not be used to
|
||||
* complete the result exceptionally. Only catastrophic errors and cancellation
|
||||
* should affect the result.
|
||||
*/
|
||||
result.completeAsync(() -> m);
|
||||
result = null;
|
||||
}
|
||||
}, SwingExecutorService.INSTANCE).exceptionally(e -> {
|
||||
e = AsyncUtils.unwrapThrowable(e);
|
||||
if (!(e instanceof CancellationException)) {
|
||||
@@ -261,12 +272,31 @@ public class DebuggerConnectDialog extends DialogComponentProvider
|
||||
if (futureConnect != null) {
|
||||
futureConnect.cancel(false);
|
||||
}
|
||||
if (result != null) {
|
||||
result.cancel(false);
|
||||
}
|
||||
super.cancelCallback();
|
||||
}
|
||||
|
||||
protected void reset() {
|
||||
protected synchronized CompletableFuture<DebuggerObjectModel> reset(
|
||||
DebuggerModelFactory factory) {
|
||||
if (factory != null) {
|
||||
synchronized (factories) {
|
||||
dropdownModel.setSelectedItem(factories.get(factory));
|
||||
}
|
||||
dropdown.setEnabled(false);
|
||||
}
|
||||
else {
|
||||
dropdown.setEnabled(true);
|
||||
}
|
||||
|
||||
if (result != null) {
|
||||
result.cancel(false);
|
||||
}
|
||||
result = new CompletableFuture<>();
|
||||
setStatusText("");
|
||||
connectButton.setEnabled(true);
|
||||
return result;
|
||||
}
|
||||
|
||||
protected void syncOptionsEnabled() {
|
||||
+19
-1
@@ -201,7 +201,10 @@ public class DebuggerModelServicePlugin extends Plugin
|
||||
protected final ChangeListener classChangeListener = new ChangeListenerForFactoryInstances();
|
||||
protected final ListenerOnRecorders listenerOnRecorders = new ListenerOnRecorders();
|
||||
|
||||
DebuggerSelectMappingOfferDialog offerDialog = new DebuggerSelectMappingOfferDialog();
|
||||
protected final DebuggerSelectMappingOfferDialog offerDialog =
|
||||
new DebuggerSelectMappingOfferDialog();
|
||||
protected final DebuggerConnectDialog connectDialog = new DebuggerConnectDialog();
|
||||
|
||||
DockingAction actionDisconnectAll;
|
||||
|
||||
protected DebuggerObjectModel currentModel;
|
||||
@@ -211,6 +214,7 @@ public class DebuggerModelServicePlugin extends Plugin
|
||||
|
||||
ClassSearcher.addChangeListener(classChangeListener);
|
||||
refreshFactoryInstances();
|
||||
connectDialog.setModelService(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -653,6 +657,7 @@ public class DebuggerModelServicePlugin extends Plugin
|
||||
factory.writeConfigState(factoryState);
|
||||
saveState.putXmlElement(stateName, factoryState.saveToXml());
|
||||
}
|
||||
connectDialog.writeConfigState(saveState);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -665,6 +670,7 @@ public class DebuggerModelServicePlugin extends Plugin
|
||||
factory.readConfigState(factoryState);
|
||||
}
|
||||
}
|
||||
connectDialog.readConfigState(saveState);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -673,4 +679,16 @@ public class DebuggerModelServicePlugin extends Plugin
|
||||
.stream()
|
||||
.flatMap(opinion -> opinion.getOffers(program, tool, this).stream());
|
||||
}
|
||||
|
||||
protected CompletableFuture<DebuggerObjectModel> doShowConnectDialog(PluginTool tool,
|
||||
DebuggerModelFactory factory) {
|
||||
CompletableFuture<DebuggerObjectModel> future = connectDialog.reset(factory);
|
||||
tool.showDialog(connectDialog);
|
||||
return future;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<DebuggerObjectModel> showConnectDialog(DebuggerModelFactory factory) {
|
||||
return doShowConnectDialog(tool, factory);
|
||||
}
|
||||
}
|
||||
|
||||
+25
-3
@@ -18,10 +18,13 @@ package ghidra.app.plugin.core.debug.service.model;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
|
||||
import docking.ActionContext;
|
||||
import docking.action.DockingAction;
|
||||
import docking.action.builder.MultiStateActionBuilder;
|
||||
@@ -57,6 +60,7 @@ import ghidra.util.Msg;
|
||||
import ghidra.util.database.UndoableTransaction;
|
||||
import ghidra.util.datastruct.CollectionChangeListener;
|
||||
import ghidra.util.datastruct.ListenerSet;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
@PluginInfo( //
|
||||
@@ -94,12 +98,17 @@ public class DebuggerModelServiceProxyPlugin extends Plugin
|
||||
|
||||
@Override
|
||||
public String getMenuParentTitle() {
|
||||
return null;
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMenuTitle() {
|
||||
return null;
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getQuickTitle() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -256,6 +265,11 @@ public class DebuggerModelServiceProxyPlugin extends Plugin
|
||||
closeAllModels();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<DebuggerObjectModel> showConnectDialog(DebuggerModelFactory factory) {
|
||||
return delegate.doShowConnectDialog(tool, factory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<DebuggerProgramLaunchOffer> getProgramLaunchOffers(Program program) {
|
||||
return orderOffers(delegate.getProgramLaunchOffers(program), program);
|
||||
@@ -325,7 +339,15 @@ public class DebuggerModelServiceProxyPlugin extends Plugin
|
||||
Msg.error(this, "Trouble writing recent launches to program user data");
|
||||
return null;
|
||||
});
|
||||
return offer.launchProgram(m, prompt);
|
||||
return offer.launchProgram(m, prompt).exceptionally(ex -> {
|
||||
Throwable t = AsyncUtils.unwrapThrowable(ex);
|
||||
if (t instanceof CancellationException || t instanceof CancelledException) {
|
||||
return null;
|
||||
}
|
||||
return ExceptionUtils.rethrow(ex);
|
||||
}).whenCompleteAsync((v, e) -> {
|
||||
updateActionDebugProgram();
|
||||
}, AsyncUtils.SWING_EXECUTOR);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+13
-5
@@ -251,15 +251,23 @@ public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProg
|
||||
}
|
||||
}
|
||||
|
||||
protected CompletableFuture<DebuggerObjectModel> connect(boolean prompt) {
|
||||
DebuggerModelService service = tool.getService(DebuggerModelService.class);
|
||||
DebuggerModelFactory factory = getModelFactory();
|
||||
if (prompt) {
|
||||
return service.showConnectDialog(factory);
|
||||
}
|
||||
return factory.build().thenApplyAsync(m -> {
|
||||
service.addModel(m);
|
||||
return m;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> launchProgram(TaskMonitor monitor, boolean prompt) {
|
||||
monitor.initialize(2);
|
||||
monitor.setMessage("Connecting");
|
||||
return getModelFactory().build().thenApplyAsync(m -> {
|
||||
DebuggerModelService service = tool.getService(DebuggerModelService.class);
|
||||
service.addModel(m);
|
||||
return m;
|
||||
}).thenComposeAsync(m -> {
|
||||
return connect(prompt).thenComposeAsync(m -> {
|
||||
List<String> launcherPath = getLauncherPath();
|
||||
TargetObjectSchema schema = m.getRootSchema().getSuccessorSchema(launcherPath);
|
||||
if (!schema.getInterfaces().contains(TargetLauncher.class)) {
|
||||
|
||||
+14
-1
@@ -85,13 +85,26 @@ public interface DebuggerProgramLaunchOffer {
|
||||
*/
|
||||
String getMenuTitle();
|
||||
|
||||
/**
|
||||
* Get the text displayed if the user will not be prompted
|
||||
*
|
||||
* <p>
|
||||
* Sometimes when "the last options" are being used without prompting, it's a good idea to
|
||||
* remind the user what those options were.
|
||||
*
|
||||
* @return the title
|
||||
*/
|
||||
default String getQuickTitle() {
|
||||
return getMenuTitle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the text displayed on buttons for this offer
|
||||
*
|
||||
* @return the title
|
||||
*/
|
||||
default String getButtonTitle() {
|
||||
return getMenuParentTitle() + " " + getMenuTitle();
|
||||
return getMenuParentTitle() + " " + getQuickTitle();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -266,9 +266,10 @@ public interface DebuggerModelService {
|
||||
* <p>
|
||||
* Assuming the target object is being actively traced, find the last focused object among those
|
||||
* being traced by the same recorder. Essentially, given that the target likely belongs to a
|
||||
* process, find the object within that process that last had focus. This is primarily used when
|
||||
* switching focus between traces. Since the user has not explicitly selected a model object,
|
||||
* the UI should choose the one which had focus when the newly-activated trace was last active.
|
||||
* process, find the object within that process that last had focus. This is primarily used
|
||||
* wh@Override en switching focus between traces. Since the user has not explicitly selected a
|
||||
* model object, the UI should choose the one which had focus when the newly-activated trace was
|
||||
* last active.
|
||||
*
|
||||
* @param target a source model object being actively traced
|
||||
* @return the last focused object being traced by the same recorder
|
||||
@@ -345,4 +346,21 @@ public interface DebuggerModelService {
|
||||
* @return the offers
|
||||
*/
|
||||
Stream<DebuggerProgramLaunchOffer> getProgramLaunchOffers(Program program);
|
||||
|
||||
/**
|
||||
* Prompt the user to create a new connection
|
||||
*
|
||||
* @return a future which completes with the new connection, possibly cancelled
|
||||
*/
|
||||
default CompletableFuture<DebuggerObjectModel> showConnectDialog() {
|
||||
return showConnectDialog(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompt the user to create a new connection, optionally fixing the factory
|
||||
*
|
||||
* @param factory the required factory, or null for user selection
|
||||
* @return a future which completes with the new connection, possible cancelled
|
||||
*/
|
||||
CompletableFuture<DebuggerObjectModel> showConnectDialog(DebuggerModelFactory factory);
|
||||
}
|
||||
|
||||
+1
-2
@@ -21,8 +21,7 @@ import java.util.concurrent.CompletableFuture;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import ghidra.app.plugin.core.debug.service.model.DebuggerModelServiceInternal;
|
||||
import ghidra.app.plugin.core.debug.service.model.DebuggerModelServiceProxyPlugin;
|
||||
import ghidra.app.plugin.core.debug.service.model.*;
|
||||
import ghidra.dbg.DebuggerModelFactory;
|
||||
import ghidra.dbg.DebuggerObjectModel;
|
||||
import ghidra.dbg.agent.AbstractDebuggerObjectModel;
|
||||
|
||||
+2
-62
@@ -18,14 +18,9 @@ package ghidra.app.plugin.core.debug.gui.target;
|
||||
import static ghidra.app.plugin.core.debug.gui.target.DebuggerTargetsProviderFriend.selectNodeForObject;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JTextField;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
@@ -33,11 +28,8 @@ import org.junit.Test;
|
||||
import docking.widgets.tree.GTreeNode;
|
||||
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
|
||||
import ghidra.app.plugin.core.debug.gui.target.DebuggerConnectDialog.FactoryEntry;
|
||||
import ghidra.dbg.DebuggerObjectModel;
|
||||
import ghidra.dbg.model.TestDebuggerModelFactory;
|
||||
import ghidra.app.plugin.core.debug.service.model.DebuggerConnectDialog;
|
||||
import ghidra.dbg.model.TestDebuggerObjectModel;
|
||||
import ghidra.util.datastruct.CollectionChangeListener;
|
||||
|
||||
/**
|
||||
* Tests of the target provider
|
||||
@@ -53,68 +45,16 @@ public class DebuggerTargetsProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConnectDialogPopulates() {
|
||||
public void testConnectActionShowDialog() {
|
||||
modelServiceInternal.setModelFactories(List.of(mb.testFactory));
|
||||
waitForSwing();
|
||||
|
||||
performAction(targetsProvider.actionConnect, false);
|
||||
DebuggerConnectDialog dialog = waitForDialogComponent(DebuggerConnectDialog.class);
|
||||
|
||||
FactoryEntry fe = (FactoryEntry) dialog.dropdownModel.getSelectedItem();
|
||||
assertEquals(mb.testFactory, fe.factory);
|
||||
|
||||
assertEquals(TestDebuggerModelFactory.FAKE_DETAILS_HTML, dialog.description.getText());
|
||||
|
||||
Component[] components = dialog.pairPanel.getComponents();
|
||||
|
||||
assertTrue(components[0] instanceof JLabel);
|
||||
JLabel label = (JLabel) components[0];
|
||||
assertEquals(TestDebuggerModelFactory.FAKE_OPTION_NAME, label.getText());
|
||||
|
||||
assertTrue(components[1] instanceof JTextField);
|
||||
JTextField field = (JTextField) components[1];
|
||||
assertEquals(TestDebuggerModelFactory.FAKE_DEFAULT, field.getText());
|
||||
|
||||
pressButtonByText(dialog, "Cancel", true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConnectDialogConnectsAndRegistersModelWithService() {
|
||||
modelServiceInternal.setModelFactories(List.of(mb.testFactory));
|
||||
|
||||
CompletableFuture<DebuggerObjectModel> futureModel = new CompletableFuture<>();
|
||||
CollectionChangeListener<DebuggerObjectModel> listener =
|
||||
new CollectionChangeListener<DebuggerObjectModel>() {
|
||||
@Override
|
||||
public void elementAdded(DebuggerObjectModel element) {
|
||||
futureModel.complete(element);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void elementModified(DebuggerObjectModel element) {
|
||||
// Don't care
|
||||
}
|
||||
|
||||
@Override
|
||||
public void elementRemoved(DebuggerObjectModel element) {
|
||||
fail();
|
||||
}
|
||||
};
|
||||
modelService.addModelsChangedListener(listener);
|
||||
performAction(targetsProvider.actionConnect, false);
|
||||
|
||||
DebuggerConnectDialog connectDialog = waitForDialogComponent(DebuggerConnectDialog.class);
|
||||
|
||||
FactoryEntry fe = (FactoryEntry) connectDialog.dropdownModel.getSelectedItem();
|
||||
assertEquals(mb.testFactory, fe.factory);
|
||||
|
||||
pressButtonByText(connectDialog, AbstractConnectAction.NAME, true);
|
||||
// NOTE: testModel is null. Don't use #createTestModel(), which adds to service
|
||||
TestDebuggerObjectModel model = new TestDebuggerObjectModel();
|
||||
mb.testFactory.pollBuild().complete(model);
|
||||
assertEquals(model, futureModel.getNow(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRegisteredModelsShowInTree() throws Exception {
|
||||
createTestModel();
|
||||
|
||||
+72
@@ -17,16 +17,23 @@ package ghidra.app.plugin.core.debug.service.model;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JTextField;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import generic.Unique;
|
||||
import ghidra.app.plugin.core.debug.event.ModelObjectFocusedPluginEvent;
|
||||
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.AbstractConnectAction;
|
||||
import ghidra.app.plugin.core.debug.service.model.DebuggerConnectDialog.FactoryEntry;
|
||||
import ghidra.app.plugin.core.debug.service.model.TestDebuggerProgramLaunchOpinion.TestDebuggerProgramLaunchOffer;
|
||||
import ghidra.app.plugin.core.debug.service.model.launch.DebuggerProgramLaunchOffer;
|
||||
import ghidra.app.services.TraceRecorder;
|
||||
@@ -34,9 +41,11 @@ import ghidra.async.AsyncPairingQueue;
|
||||
import ghidra.dbg.DebuggerModelFactory;
|
||||
import ghidra.dbg.DebuggerObjectModel;
|
||||
import ghidra.dbg.model.TestDebuggerModelFactory;
|
||||
import ghidra.dbg.model.TestDebuggerObjectModel;
|
||||
import ghidra.dbg.testutil.DebuggerModelTestUtils;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.util.Swing;
|
||||
import ghidra.util.SystemUtilities;
|
||||
import ghidra.util.datastruct.CollectionChangeListener;
|
||||
import mockit.Mocked;
|
||||
@@ -485,4 +494,67 @@ public class DebuggerModelServiceTest extends AbstractGhidraHeadedDebuggerGUITes
|
||||
waitOn(mb.testModel.close());
|
||||
assertNull(modelService.getCurrentModel());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConnectDialogPopulates() {
|
||||
modelServiceInternal.setModelFactories(List.of(mb.testFactory));
|
||||
waitForSwing();
|
||||
|
||||
Swing.runLater(() -> modelService.showConnectDialog());
|
||||
DebuggerConnectDialog dialog = waitForDialogComponent(DebuggerConnectDialog.class);
|
||||
|
||||
FactoryEntry fe = (FactoryEntry) dialog.dropdownModel.getSelectedItem();
|
||||
assertEquals(mb.testFactory, fe.factory);
|
||||
|
||||
assertEquals(TestDebuggerModelFactory.FAKE_DETAILS_HTML, dialog.description.getText());
|
||||
|
||||
Component[] components = dialog.pairPanel.getComponents();
|
||||
|
||||
assertTrue(components[0] instanceof JLabel);
|
||||
JLabel label = (JLabel) components[0];
|
||||
assertEquals(TestDebuggerModelFactory.FAKE_OPTION_NAME, label.getText());
|
||||
|
||||
assertTrue(components[1] instanceof JTextField);
|
||||
JTextField field = (JTextField) components[1];
|
||||
assertEquals(TestDebuggerModelFactory.FAKE_DEFAULT, field.getText());
|
||||
|
||||
pressButtonByText(dialog, "Cancel", true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConnectDialogConnectsAndRegistersModelWithService() throws Throwable {
|
||||
modelServiceInternal.setModelFactories(List.of(mb.testFactory));
|
||||
|
||||
CompletableFuture<DebuggerObjectModel> futureModel = new CompletableFuture<>();
|
||||
CollectionChangeListener<DebuggerObjectModel> listener =
|
||||
new CollectionChangeListener<DebuggerObjectModel>() {
|
||||
@Override
|
||||
public void elementAdded(DebuggerObjectModel element) {
|
||||
futureModel.complete(element);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void elementModified(DebuggerObjectModel element) {
|
||||
// Don't care
|
||||
}
|
||||
|
||||
@Override
|
||||
public void elementRemoved(DebuggerObjectModel element) {
|
||||
fail();
|
||||
}
|
||||
};
|
||||
modelService.addModelsChangedListener(listener);
|
||||
Swing.runLater(() -> modelService.showConnectDialog());
|
||||
|
||||
DebuggerConnectDialog connectDialog = waitForDialogComponent(DebuggerConnectDialog.class);
|
||||
|
||||
FactoryEntry fe = (FactoryEntry) connectDialog.dropdownModel.getSelectedItem();
|
||||
assertEquals(mb.testFactory, fe.factory);
|
||||
|
||||
pressButtonByText(connectDialog, AbstractConnectAction.NAME, true);
|
||||
// NOTE: testModel is null. Don't use #createTestModel(), which adds to service
|
||||
TestDebuggerObjectModel model = new TestDebuggerObjectModel();
|
||||
mb.testFactory.pollBuild().complete(model);
|
||||
assertEquals(model, waitOn(futureModel));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user