@Test
	public void testRefreshVMs() throws Exception {
		try (JshellAndConnection conn = startAndConnectJshell()) {
			start(conn, "HelloWorld.class");

			RemoteMethod refreshVM = conn.getMethod("refresh_vm");
			RemoteMethod refreshProcess = conn.getMethod("refresh_process");
			try (ManagedDomainObject mdo = openDomainObject("/New Traces/HelloWorld.class")) {
				tb = new ToyDBTraceBuilder((Trace) mdo.get());
				txPut(conn, "VMs");

				TraceObject vm = waitForObject("VMs[]");
				List<TraceObjectValue> children = waitForValues("VMs[].");
				int sz = children.size();
				refreshVM.invoke(Map.of("vm", vm));
				children = waitForValues("VMs[].");
				assertTrue(children.size() >= sz);
				List<TraceObjectValue> eventThread = waitForValues("VMs[]._event_thread");
				assertTrue(eventThread.get(0).getValue().toString().contains("main"));

				TraceObject proc = waitForObject("VMs[].Processes[]");
				refreshProcess.invoke(Map.of("process", proc));
				children = waitForValues("VMs[].Processes[].");
				assertTrue(children.get(0).getEntryKey().contains("Alive"));
				children = waitForValues("VMs[].Processes[].CommandLine");
				assertTrue(((String) children.get(0).getValue()).contains("HelloWorld"));
			}
		}
	}
Because we control the target artifact, we know the steps precisely. NOTE: This must be the
same at least for step_into and step_over, otherwise, we can't know for sure step_over
actually behaves any differently than step_into. By matching with step_into, we can assure
ourselves that step_over actually encountered an invoke* bytecode instruction, because if it
didn't then step_into ought to fail.
Ghidra/Test/DebuggerIntegrationTest/src/test.slow/java/agent/java/rmi/JavaMethodsTest.java