Skip to content

Commit 58871c4

Browse files
andyclaude
andcommitted
Tests: geometry-trace stress driver in tests/apps (#217)
Builder scene commands gtfocus, gtsweep, gtplay, gtstop, gtupdate (grow|gap|badsize|junk), gtexpect, gtcontext, gtfloat, gtunfocusall, gtremovepanels and gtclose drive the Geometry Trace panel through ECCE_VIEWER_SCENE. tests/apps/geomtrace_stress.py builds three water optimisations from the parser fixtures (Gaussian, ORCA, NWChem, put through the real parsers), steps, plays, delivers running-job messages while playing, floats/docks, switches and closes calculations and removes panels mid-animation. Run by run_tests.py; standalone with --gdb or --valgrind. Without the previous commit it fails with SIGSEGV at "gtupdate badsize" and, with only the timer fix reverted, at "gtremovepanels". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PFPK14wBcLD1s4dYUowpP4
1 parent 9db5a94 commit 58871c4

6 files changed

Lines changed: 576 additions & 0 deletions

File tree

‎src/apps/builder/Builder.C‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5153,6 +5153,8 @@ void Builder::updatePropertyMenus()
51535153
cube->selectGrid(atoi(w[2].c_str()), atof(w[3].c_str()));
51545154
});
51555155
}
5156+
// "gt...": the Geometry Trace stress commands (#217).
5157+
if (w[0].compare(0, 2, "gt") == 0) return traceStressCommand(s, w);
51565158
if ((w[0] != "mopanel" && w[0] != "motable") || w.size() != 2)
51575159
return s.fail("unknown command: " + w[0]);
51585160
MoPanel *mo = 0;

‎src/apps/builder/Builder.H‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ class SFile;
5858
class SGContainer;
5959
class SGContainerManager;
6060
class SGViewer;
61+
class SceneScript;
6162
class StructLib;
6263
class ViewerEvtHandler;
6364
class wxToolBarToolBase;
@@ -449,6 +450,7 @@ class Builder: public BuilderGUI,
449450
void doSaveAs(const bool& imagesOnly=false);
450451
void doSaveThumb();
451452
void doClose(const string& context="", const bool& autoOpen=true);
453+
bool traceStressCommand(SceneScript& s, const vector<string>& w);
452454

453455
bool isReadOnly() const;
454456
bool isReadOnly(IPropCalculation *calc) const;
Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
// Scene-script commands that drive the Geometry Trace panel the way a user
2+
// and a running job do together (#217): every step, playback started and
3+
// stopped, the trace changed under a running animation, the context
4+
// switched or closed, the panels unfocused as on quit. Used only through
5+
// ECCE_VIEWER_SCENE (tests/apps/geomtrace_stress.py).
6+
7+
#include <cmath>
8+
#include <cstdio>
9+
#include <sstream>
10+
11+
#include <wx/aui/aui.h>
12+
#include <wx/app.h>
13+
#include <wx/utils.h>
14+
15+
#include "util/EventDispatcher.H"
16+
#include "util/Event.H"
17+
using namespace ecce;
18+
19+
#include "tdat/PropTSVecTable.H"
20+
#include "tdat/TAtm.H"
21+
#include "dsm/IPropCalculation.H"
22+
23+
#include "viz/SGContainer.H"
24+
#include "viz/SGFragment.H"
25+
26+
#include "wxgui/ewxPlotCtrl.H"
27+
#include "wxgui/PlaybackControl.H"
28+
#include "wxgui/ThingToggle.H"
29+
30+
#include "wxviz/SceneScript.H"
31+
32+
#include "Builder.H"
33+
#include "GeomTracePropertyPanel.H"
34+
#include "PropertyPanel.H"
35+
36+
37+
namespace {
38+
39+
GeomTracePropertyPanel *tracePanel(IPropCalculation *calc)
40+
{
41+
if (!calc) return 0;
42+
set<PropertyPanel*> panels =
43+
PropertyPanel::getPanels(calc->getURL().toString());
44+
for (PropertyPanel *p : panels) {
45+
GeomTracePropertyPanel *gt = dynamic_cast<GeomTracePropertyPanel*>(p);
46+
if (gt) return gt;
47+
}
48+
return 0;
49+
}
50+
51+
52+
PlaybackControl *playback(wxWindow *w)
53+
{
54+
if (!w) return 0;
55+
PlaybackControl *pb = dynamic_cast<PlaybackControl*>(w);
56+
if (pb) return pb;
57+
for (wxWindow *c : w->GetChildren())
58+
if ((pb = playback(c)) != 0) return pb;
59+
return 0;
60+
}
61+
62+
63+
void spin(int ms)
64+
{
65+
wxLongLong end = wxGetLocalTimeMillis() + ms;
66+
do {
67+
wxTheApp->Yield(true);
68+
wxMilliSleep(2);
69+
} while (wxGetLocalTimeMillis() < end);
70+
}
71+
72+
73+
// One GEOMTRACE step as eccejobstore publishes it.
74+
string stepMessage(int step, int atoms, int values)
75+
{
76+
std::ostringstream os;
77+
os << "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\" ?>\n"
78+
<< "<tsvectable columnLabel=\"Atom\" columnLabels=\"X Y Z\" columns=\"3\""
79+
<< " name=\"GEOMTRACE\" rowLabel=\"Geometry Step\" rows=\"" << atoms
80+
<< "\" units=\"Angstrom\" vectorLabel=\"Coordinate\" vectors=\"1\">"
81+
<< "<step number=\"" << step << "\">";
82+
for (int i = 0; i < values; i++) os << " " << 0.1 * (i % 7) + 0.01 * step;
83+
os << "</step></tsvectable>\n";
84+
return os.str();
85+
}
86+
87+
} // namespace
88+
89+
90+
bool Builder::traceStressCommand(SceneScript& s, const vector<string>& w)
91+
{
92+
const string& c = w[0];
93+
GeomTracePropertyPanel *gt = tracePanel(p_calculation);
94+
PlaybackControl *pb = playback(gt);
95+
96+
// "gtfocus": give the panel the viewer, as a click into it does.
97+
if (c == "gtfocus") {
98+
if (!gt) {
99+
string have;
100+
for (PropertyPanel *p :
101+
PropertyPanel::getPanels(p_calculation->getURL().toString()))
102+
have += " [" + p->getName() + "]";
103+
return s.fail("gtfocus: no Geometry Trace panel in " +
104+
p_calculation->getURL().toString() + "; panels:" + have);
105+
}
106+
gt->setFocus(true);
107+
spin(50);
108+
return true;
109+
}
110+
111+
// "gtsweep": a playback tick for every step and a few past either end.
112+
if (c == "gtsweep") {
113+
if (!gt || !pb) return s.fail("gtsweep: no Geometry Trace panel");
114+
PropTSVecTable *trace =
115+
dynamic_cast<PropTSVecTable*>(p_calculation->getProperty("GEOMTRACE"));
116+
int n = trace ? trace->tables() : 0;
117+
for (int i = -2; i < n + 3; i++) {
118+
wxCommandEvent ev(wxEVT_PLAYBACK_TICK_EVENT, pb->GetId());
119+
ev.SetEventObject(pb);
120+
ev.SetExtraLong(i);
121+
gt->GetEventHandler()->ProcessEvent(ev);
122+
spin(5);
123+
}
124+
return true;
125+
}
126+
127+
// "gtplay <ms> [delay ms]": press Play and let it run.
128+
if (c == "gtplay" && w.size() >= 2) {
129+
if (!pb) return s.fail("gtplay: no playback control");
130+
if (w.size() >= 3) pb->SetDelay(atoi(w[2].c_str()));
131+
wxCustomButton *b =
132+
dynamic_cast<wxCustomButton*>(pb->FindWindow(PlaybackControl::ID_PLAY));
133+
if (!b) return s.fail("gtplay: no Play button");
134+
b->SetValue(true);
135+
wxCommandEvent ev(wxEVT_TOGGLEBUTTON, PlaybackControl::ID_PLAY);
136+
ev.SetEventObject(b);
137+
ev.SetInt(1);
138+
pb->GetEventHandler()->ProcessEvent(ev);
139+
spin(atoi(w[1].c_str()));
140+
return true;
141+
}
142+
143+
// "gtstop": press Stop.
144+
if (c == "gtstop") {
145+
if (!pb) return s.fail("gtstop: no playback control");
146+
wxCommandEvent ev(wxEVT_BUTTON, PlaybackControl::ID_STOP);
147+
ev.SetEventObject(pb->FindWindow(PlaybackControl::ID_STOP));
148+
pb->GetEventHandler()->ProcessEvent(ev);
149+
spin(20);
150+
return true;
151+
}
152+
153+
// "gtupdate grow|gap|badsize|junk": a GEOMTRACE message from a running
154+
// job, delivered as propertyChangeMCB does: the next step, a step that
155+
// skips ahead (forces a reload), a step whose size disagrees with its
156+
// header, and a link to a step the data server cannot deliver.
157+
if (c == "gtupdate" && w.size() == 2) {
158+
PropTSVecTable *trace =
159+
dynamic_cast<PropTSVecTable*>(p_calculation->getProperty("GEOMTRACE"));
160+
if (!trace) return s.fail("gtupdate: no GEOMTRACE");
161+
int n = trace->tables();
162+
int atoms = trace->rows();
163+
string value;
164+
if (w[1] == "grow") value = stepMessage(n + 1, atoms, 3 * atoms);
165+
else if (w[1] == "gap") value = stepMessage(n + 3, atoms, 3 * atoms);
166+
else if (w[1] == "badsize") value = stepMessage(n + 1, atoms, 3 * atoms + 3);
167+
else if (w[1] == "junk")
168+
value = p_calculation->getURL().toString() + "/Props/NOSUCHSTEP";
169+
else return s.fail("gtupdate: grow, gap, badsize or junk");
170+
try {
171+
p_calculation->updateProperty("GEOMTRACE", value);
172+
} catch (...) {
173+
return s.fail("gtupdate " + w[1] + ": updateProperty threw");
174+
}
175+
set<PropertyPanel*> panels = PropertyPanel::getPanelsForPropertyName(
176+
p_calculation->getURL().toString(), "GEOMTRACE");
177+
for (PropertyPanel *p : panels) p->propertyUpdate("GEOMTRACE");
178+
spin(20);
179+
return true;
180+
}
181+
182+
// "gtexpect <steps>": the cached trace still holds <steps> steps and the
183+
// molecule on screen has finite coordinates.
184+
if (c == "gtexpect" && w.size() == 2) {
185+
TProperty *prop = p_calculation->getProperty("GEOMTRACE");
186+
PropTSVecTable *trace = dynamic_cast<PropTSVecTable*>(prop);
187+
int want = atoi(w[1].c_str());
188+
if (!trace || trace->tables() != want) {
189+
char buf[96];
190+
snprintf(buf, sizeof buf, "gtexpect: GEOMTRACE has %d steps, want %d",
191+
trace ? trace->tables() : -1, want);
192+
return s.fail(buf);
193+
}
194+
SGFragment *frag = getSG()->getFragment();
195+
for (size_t i = 0; i < frag->numAtoms(); i++) {
196+
const double *xyz = frag->atomRef(i)->coordinates();
197+
if (!std::isfinite(xyz[0]) || !std::isfinite(xyz[1]) ||
198+
!std::isfinite(xyz[2]))
199+
return s.fail("gtexpect: atom coordinates are not finite");
200+
}
201+
fprintf(stderr, "GTSTRESS: %d steps, %lu atoms\n", want,
202+
(unsigned long)frag->numAtoms());
203+
return true;
204+
}
205+
206+
// "gtcontext <url>": switch calculation, playing or not.
207+
if (c == "gtcontext" && w.size() == 2) {
208+
setContext(w[1]);
209+
spin(200);
210+
return true;
211+
}
212+
213+
// "gtfloat": float the trace pane, then Tools > Dock Floating Panels.
214+
if (c == "gtfloat") {
215+
if (!gt) return s.fail("gtfloat: no Geometry Trace panel");
216+
wxAuiPaneInfo &pane = p_mgr.GetPane(gt);
217+
if (!pane.IsOk()) return s.fail("gtfloat: the panel has no pane");
218+
pane.Show().Float();
219+
p_mgr.Update();
220+
spin(200);
221+
wxCommandEvent none;
222+
OnDockFloatingPanels(none);
223+
spin(200);
224+
return true;
225+
}
226+
227+
// "gtunfocusall": what quit() does to the panels before it exits.
228+
if (c == "gtunfocusall") {
229+
set<VizPropertyPanel*> panels = VizPropertyPanel::getPanels();
230+
for (VizPropertyPanel *p : panels) p->setFocus(false);
231+
spin(50);
232+
return true;
233+
}
234+
235+
// "gtremovepanels": what a state change back to "created" does to the
236+
// panels (urlStateMCB), with the animation still running.
237+
if (c == "gtremovepanels") {
238+
removePropertyPanels(p_calculation->getURL().toString());
239+
spin(500);
240+
return true;
241+
}
242+
243+
// "gtclose": File > Close on the current calculation.
244+
if (c == "gtclose") {
245+
doClose("", true);
246+
spin(300);
247+
return true;
248+
}
249+
250+
return s.fail("unknown command: " + c);
251+
}

‎tests/apps/README.md‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,3 +410,12 @@ it to `cases.HELPERS` (with the reason) or give it a `cases.TIMEOUTS` entry
410410
if it is merely slow. A reproduced crash that is not being fixed yet goes in
411411
`cases.XFAIL` with enough detail to act on. All three lists are checked in
412412
both directions, so a stale entry fails the run.
413+
414+
## The geometry trace under stress
415+
416+
`geomtrace_stress.py` (run by the suite, or alone with `--gdb` or
417+
`--valgrind`) opens three water optimisations built from the parser
418+
fixtures and drives the Geometry Trace panel through the Builder's `gt...`
419+
scene commands: every step, playback, GEOMTRACE messages as a running job
420+
sends them (including ones that fail to parse), floating and docking, and
421+
switching, closing and removing panels while the animation runs (#217).

0 commit comments

Comments
 (0)