001: package abbot.editor.recorder;
002:
003: import java.awt.*;
004: import java.util.*;
005:
006: import abbot.Log;
007: import abbot.script.*;
008: import abbot.tester.Robot;
009:
010: /**
011: * All SemanticRecorder tests should derive from this class.
012: * Provides a framework similar to EventRecorder which creates appropriate
013: * semantic recorders based on incoming events.<p>
014: * Will continue recording past the first semantic recorder used, preserving
015: * the first one until an invocation of assert[No]Step, which resets the
016: * preserved recorder, replacing it with the current one when appropriate.
017: * This allows a more or less continuous stream of actions alternating with
018: * assert[No]Step calls.<p>
019: * NOTE: it would probably be simpler to not do this and require explicit
020: * startRecording steps, although there is some benefit to checking recorders
021: * back to back (it more closely simulates the EventRecorder behavior).
022: */
023: // FIXME still needs some refactoring
024: // Needs clearer design on when to stop recording and examination of trailing
025: // events that are consumed/not consumed
026: public abstract class AbstractSemanticRecorderFixture extends
027: AbstractRecorderFixture {
028:
029: private ArrayList steps;
030: private Map stepEvents;
031: private SemanticRecorder currentRecorder;
032: private AWTEvent trailingEvent;
033:
034: public AbstractSemanticRecorderFixture() {
035: }
036:
037: /** Create a new test case with the given name. */
038: public AbstractSemanticRecorderFixture(String name) {
039: super (name);
040: }
041:
042: protected void fixtureSetUp() throws Throwable {
043: super .fixtureSetUp();
044: steps = new ArrayList();
045: stepEvents = new WeakHashMap();
046: }
047:
048: protected void fixtureTearDown() throws Throwable {
049: currentRecorder = null;
050: trailingEvent = null;
051: steps.clear();
052: stepEvents.clear();
053: super .fixtureTearDown();
054: }
055:
056: /** Each Recorder subclass test should implement this method to return the
057: SemanticRecorder to be tested. */
058: protected abstract SemanticRecorder createSemanticRecorder(
059: Resolver r);
060:
061: protected void startRecording() {
062: startRecording(true);
063: }
064:
065: protected void startRecording(boolean waitForIdle) {
066: // Don't pick up any lingering events
067: if (waitForIdle)
068: getRobot().waitForIdle();
069: trailingEvent = null;
070: super .startRecording();
071: }
072:
073: private class DefaultRecorder extends Recorder {
074: public DefaultRecorder(Resolver r) {
075: super (r);
076: }
077:
078: protected void recordEvent(AWTEvent event) {
079: AbstractSemanticRecorderFixture.this .recordEvent(event);
080: }
081:
082: protected Step createStep() {
083: return AbstractSemanticRecorderFixture.this .getStep();
084: }
085:
086: public long getEventMask() {
087: return EventRecorder.RECORDING_EVENT_MASK;
088: }
089:
090: public void terminate() {
091: }
092: }
093:
094: /** Clear the current state of all steps and active recorder. */
095: protected synchronized void clear() {
096: currentRecorder = null;
097: super .clear();
098: steps.clear();
099: }
100:
101: /** Provide a recorder which acts sort of like EventRecorder, but will
102: always pass events on to the same SemanticRecorder obtained with
103: createRecorder, rather than dynamically determining which recorder to
104: create. */
105: protected synchronized Recorder getRecorder() {
106: clear();
107: return new DefaultRecorder(getResolver());
108: }
109:
110: // FIXME is this the best way to handle multiple recorders?
111: private synchronized void saveCurrentStep() {
112: if (currentRecorder != null) {
113: Step step = currentRecorder.getStep();
114: if (step == null)
115: Log.debug("null step!");
116: if (steps.size() > 0)
117: Log.debug("Adding step " + step + " to list of "
118: + steps.size());
119: steps.add(step);
120: stepEvents.put(step, listEvents());
121: currentRecorder = null;
122: }
123: }
124:
125: // This dispatching is the canonical usage for a semantic recorder; cf
126: // EventRecorder's lookup and dispatch
127: // FIXME maybe make a semantic controller object that both this test and
128: // event recorder use?
129: // FIXME use a different synchronization lock
130: protected synchronized void recordEvent(AWTEvent event) {
131: if (Boolean.getBoolean("abbot.recorder.log_events"))
132: Log.log("SREC: " + Robot.toString(event));
133:
134: if (currentRecorder == null) {
135: // ordinarily, you'd get an appropriate recorder based on the
136: // event type
137: SemanticRecorder cr = createSemanticRecorder(getResolver());
138: if (cr.accept(event)) {
139: currentRecorder = cr;
140: Log.log("New recorder, event accepted");
141: }
142: }
143:
144: // Don't bother passing on events if the recorder has captured its
145: // target event
146: if (currentRecorder != null) {
147: SemanticRecorder cr = currentRecorder;
148: if (cr.isFinished()) {
149: if (trailingEvent == null) {
150: trailingEvent = event;
151: Log.log("Unconsumed trailing event");
152: }
153: return;
154: }
155: boolean consumed;
156: boolean finished;
157: Log.debug("Recording with " + cr);
158: consumed = cr.record(event);
159: finished = cr.isFinished();
160: if (finished) {
161: Log.log("Recorder is finished");
162: saveCurrentStep();
163: clearEvents();
164: }
165: if (!consumed) {
166: Log.log("Re-sending event");
167: recordEvent(event);
168: }
169: }
170: }
171:
172: /** Verify that no unconsumed events were seen since the last recorded
173: step.
174: */
175: protected void assertNoTrailingEvents() {
176: getRobot().waitForIdle();
177: synchronized (this ) {
178: AWTEvent e = trailingEvent;
179: trailingEvent = null;
180: if (e != null) {
181: String bugInfo = bugInfo(listEvents());
182: fail("Unconsumed trailing events starting at "
183: + Robot.toString(e) + " " + bugInfo);
184: }
185: }
186: }
187:
188: protected void assertNoStep() {
189: getRobot().waitForIdle();
190: synchronized (this ) {
191: Step step = getStep();
192: String bugInfo = step != null ? bugInfo((String) stepEvents
193: .get(step)) : "";
194: assertTrue("Recorder should not have recorded the step "
195: + step + " " + bugInfo, step == null);
196: }
197: }
198:
199: /** Make sure the step created matches the given pattern. The recorder
200: * may not nececessarily have detected the end of the pattern.
201: */
202: protected void assertStep(String pattern) {
203: assertStep(pattern, false);
204: }
205:
206: public void assertStep(String pattern, Step step) {
207: assertStep(pattern, step, (String) stepEvents.get(step));
208: }
209:
210: /** Make sure the recorder created step matches the given pattern,
211: * optionally requiring the recorder to have detected the end of the
212: * semantic event.
213: */
214: // FIXME this depends on the description that is generated; it should
215: // check the actual step structure instead
216: protected void assertStep(String pattern, boolean mustBeFinished) {
217: // Make sure all events have been processed
218: getRobot().waitForIdle();
219: synchronized (this ) {
220: String bugInfo = bugInfo(listEvents());
221: Log.log("Verifying recorder state (" + pattern + ")");
222: try {
223: assertTrue("No recorder instantiated " + bugInfo,
224: currentRecorder != null || steps.size() > 0);
225: if (mustBeFinished) {
226: assertTrue(
227: "Semantic recorder should have detected the "
228: + "end of this event sequence "
229: + bugInfo,
230: (currentRecorder != null && currentRecorder
231: .isFinished())
232: || steps.size() > 0);
233: }
234: Step step = getStep();
235: assertStep(pattern, step, (String) stepEvents.get(step));
236: } catch (RuntimeException thr) {
237: Log.log("** FAILED ***");
238: throw thr;
239: }
240: }
241: }
242:
243: // FIXME this methods purpose is unclear
244: protected synchronized boolean hasRecorder() {
245: return currentRecorder != null || steps.size() > 0;
246: }
247:
248: protected synchronized Step getStep() {
249: Step step = null;
250: if (steps.size() > 0) {
251: Log.log("retrieving saved step");
252: step = (Step) steps.get(0);
253: steps.remove(0);
254: } else if (currentRecorder != null) {
255: Log.log("asking recorder for step");
256: step = currentRecorder.getStep();
257: currentRecorder = null;
258: if (step != null) {
259: stepEvents.put(step, listEvents());
260: super .clear();
261: }
262: } else {
263: Log.log("No steps and no recorder");
264: }
265: return step;
266: }
267: }
|