1 | // jademx - JADE management using JMX |
2 | // Copyright 2005-2006 Caboodle Networks, Inc. |
3 | // |
4 | // This library is free software; you can redistribute it and/or |
5 | // modify it under the terms of the GNU Lesser General Public |
6 | // License as published by the Free Software Foundation; either |
7 | // version 2.1 of the License, or (at your option) any later version. |
8 | // |
9 | // This library is distributed in the hope that it will be useful, |
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
12 | // Lesser General Public License for more details. |
13 | // |
14 | // You should have received a copy of the GNU Lesser General Public |
15 | // License along with this library; if not, write to the Free Software |
16 | // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA |
17 | |
18 | package jade.jademx.unit; |
19 | |
20 | import jade.content.AgentAction; |
21 | import jade.content.ContentElement; |
22 | import jade.content.onto.basic.Action; |
23 | import jade.core.AID; |
24 | import jade.core.behaviours.Behaviour; |
25 | import jade.domain.FIPAException; |
26 | import jade.domain.FIPANames; |
27 | import jade.domain.FIPAService; |
28 | import jade.domain.JADEAgentManagement.JADEManagementOntology; |
29 | import jade.domain.JADEAgentManagement.JADEManagementVocabulary; |
30 | import jade.domain.JADEAgentManagement.SniffOff; |
31 | import jade.domain.JADEAgentManagement.SniffOn; |
32 | import jade.domain.introspection.AMSSubscriber; |
33 | import jade.domain.introspection.Event; |
34 | import jade.domain.introspection.EventRecord; |
35 | import jade.domain.introspection.IntrospectionOntology; |
36 | import jade.domain.introspection.Occurred; |
37 | import jade.domain.introspection.PostedMessage; |
38 | import jade.domain.introspection.ReceivedMessage; |
39 | import jade.domain.introspection.SentMessage; |
40 | import jade.jademx.agent.JademxAgent; |
41 | import jade.jademx.util.AclMsgCmp; |
42 | import jade.jademx.util.ThrowableUtil; |
43 | import jade.jademx.util.iso8601.Duration; |
44 | import jade.lang.acl.ACLMessage; |
45 | import jade.lang.acl.MessageTemplate; |
46 | import jade.util.Logger; |
47 | |
48 | import java.util.Arrays; |
49 | import java.util.Iterator; |
50 | import java.util.LinkedList; |
51 | import java.util.List; |
52 | import java.util.Properties; |
53 | |
54 | /** |
55 | * behaviour to perform unit test on agent: inject msg and see msg out. |
56 | * @author David Bernstein, <a href="http://www.caboodlenetworks.com" |
57 | * >Caboodle Networks, Inc.</a> |
58 | */ |
59 | public class UnitTestBehaviour extends Behaviour { |
60 | |
61 | // logger |
62 | |
63 | /** logger for this class */ |
64 | private final Logger logger = |
65 | Logger.getMyLogger(UnitTestBehaviour.class.getName()); |
66 | |
67 | // AMS timeout |
68 | |
69 | /** default value for AMS timeout */ |
70 | private String DFLT_AMS_TIMEOUT_STR = "PT1M"; |
71 | /** how long to wait for AMS response */ |
72 | private long amsTimeoutMS = 0; |
73 | /** AMS timeout expressed as ISO 8601 duration */ |
74 | private String amsTimeoutStr = null; |
75 | |
76 | // AMS messaging |
77 | |
78 | /** message instantiated once to request subscription to AMS */ |
79 | private ACLMessage amsSubscribeMsg = new ACLMessage( ACLMessage.SUBSCRIBE ); |
80 | /** message instantiated once to request cancellation to AMS */ |
81 | private ACLMessage amsCancelMsg = new ACLMessage( ACLMessage.CANCEL ); |
82 | /** message instantiated once to request specific action from AMS */ |
83 | private ACLMessage amsRequestMsg = new ACLMessage( ACLMessage.REQUEST ); |
84 | /** prefix for conversation-id talking to AMS */ |
85 | private String AMS_CONV_ID_PREFIX = "ams-"; |
86 | |
87 | // sniffing |
88 | |
89 | /** list of agents to sniff - DO NOT include self */ |
90 | private List sniffedAgents = new LinkedList(); |
91 | /** count how many times sniffing turned on */ |
92 | private int sniffOnCount = 0; |
93 | /** count how many times sniffing turned off */ |
94 | private int sniffOffCount = 0; |
95 | /** message that was sniffed */ |
96 | private ACLMessage sniffedMessage = null; |
97 | /** are we currently sniffing */ |
98 | private boolean sniffing = false; |
99 | |
100 | // timeout |
101 | |
102 | /** once msg injected, can compute absolute testcase timeout in ms */ |
103 | private long testcaseStartMsAbs = 0; |
104 | /** once msg injected, can compute absolute testcase timeout in ms */ |
105 | private long testcaseTimeoutMsAbs = 0; |
106 | |
107 | // testcase parameters |
108 | |
109 | /** timeout for testcase */ |
110 | private Duration testcaseTimeout = null; |
111 | /** message to inject */ |
112 | private ACLMessage messageToInject = null; |
113 | /** variable strings: each key is string that may change, value is group */ |
114 | private Properties varProps = null; |
115 | /** expected message to be sniffed */ |
116 | private ACLMessage expectedMessage = null; |
117 | /** agent-under-test */ |
118 | private JademxAgent jademxAgent = null; |
119 | |
120 | // message templates |
121 | |
122 | /** template for messages being sniffed */ |
123 | private MessageTemplate matchSenderReceiverTemplate = null; |
124 | /** pattern for messages that might hold sniffed messages */ |
125 | private MessageTemplate eventPattern = null; |
126 | |
127 | // state |
128 | |
129 | /** start off in this state */ |
130 | private final static int STATE_INIT = 0; |
131 | /** inject the test message */ |
132 | private final static int STATE_INJECT = 1; |
133 | /** wait for matching sniffed message or timeout */ |
134 | private final static int STATE_WAIT = 2; |
135 | /** compare expected message with sniffed message, if any */ |
136 | private final static int STATE_COMPARE = 3; |
137 | /** send unit testing success or failure JMX notification */ |
138 | private final static int STATE_NOTIFY = 4; |
139 | /** behaviour is done */ |
140 | private final static int STATE_COMPLETE = 5; |
141 | /** behaviour state variable */ |
142 | private int state = STATE_INIT; |
143 | |
144 | /** is this behaviour finished */ |
145 | private boolean finished = false; |
146 | |
147 | /** text describing why test failed */ |
148 | private String failMsg = null; |
149 | |
150 | |
151 | /** |
152 | * make behaviour to run a unit test on a JademxAgent. |
153 | * this is done by injecting a message and waiting for an expected message. |
154 | * @param jademxAgent agent to test |
155 | * @param expectedMessage message to expect |
156 | * @param messageToInject message to inject |
157 | * @param testcaseTimeout timeout for the testcase |
158 | * @param varProps variable strings |
159 | */ |
160 | public UnitTestBehaviour( |
161 | JademxAgent jademxAgent, |
162 | ACLMessage expectedMessage, |
163 | ACLMessage messageToInject, |
164 | Duration testcaseTimeout, |
165 | Properties varProps ) { |
166 | |
167 | super( jademxAgent ); |
168 | |
169 | if ( null == jademxAgent ) { |
170 | throw new IllegalArgumentException("null agent"); |
171 | } |
172 | if ( null == expectedMessage ) { |
173 | throw new IllegalArgumentException("null expected message"); |
174 | } |
175 | if ( null == messageToInject ) { |
176 | throw new IllegalArgumentException("null message to inject"); |
177 | } |
178 | if ( null == testcaseTimeout ) { |
179 | throw new IllegalArgumentException("null testcase timeout"); |
180 | } |
181 | if ( null == varProps ) { |
182 | throw new IllegalArgumentException("null variable properties"); |
183 | } |
184 | |
185 | this.jademxAgent = jademxAgent; |
186 | this.expectedMessage = expectedMessage; |
187 | this.messageToInject = messageToInject; |
188 | this.testcaseTimeout = testcaseTimeout; |
189 | this.varProps = varProps; |
190 | |
191 | String conversationId = AMS_CONV_ID_PREFIX + myAgent.getLocalName(); |
192 | |
193 | amsSubscribeMsg.setSender( myAgent.getAID() ); |
194 | amsSubscribeMsg.clearAllReceiver( ); |
195 | amsSubscribeMsg.addReceiver( myAgent.getAMS() ); |
196 | amsSubscribeMsg.setLanguage( FIPANames.ContentLanguage.FIPA_SL0 ); |
197 | amsSubscribeMsg.setOntology( IntrospectionOntology.NAME ); |
198 | amsSubscribeMsg.setReplyWith( AMSSubscriber.AMS_SUBSCRIPTION ); |
199 | amsSubscribeMsg.setConversationId( conversationId ); |
200 | amsSubscribeMsg.setContent( AMSSubscriber.PLATFORM_EVENTS ); |
201 | |
202 | amsCancelMsg.setSender( myAgent.getAID() ); |
203 | amsCancelMsg.clearAllReceiver( ); |
204 | amsCancelMsg.addReceiver( myAgent.getAMS() ); |
205 | amsCancelMsg.setLanguage( FIPANames.ContentLanguage.FIPA_SL0 ); |
206 | amsCancelMsg.setOntology( IntrospectionOntology.NAME ); |
207 | amsCancelMsg.setReplyWith( AMSSubscriber.AMS_CANCELLATION ); |
208 | amsCancelMsg.setConversationId( conversationId ); |
209 | |
210 | amsRequestMsg.setSender( myAgent.getAID() ); |
211 | amsRequestMsg.clearAllReceiver( ); |
212 | amsRequestMsg.addReceiver( myAgent.getAMS() ); |
213 | amsRequestMsg.setProtocol( FIPANames.InteractionProtocol.FIPA_REQUEST ); |
214 | amsRequestMsg.setLanguage( FIPANames.ContentLanguage.FIPA_SL0 ); |
215 | amsRequestMsg.setConversationId( conversationId ); |
216 | |
217 | matchSenderReceiverTemplate = |
218 | new MessageTemplate( |
219 | new SenderReceiverMatchExpression( |
220 | expectedMessage ) ); |
221 | |
222 | // set agents to sniff |
223 | logger.log( Logger.FINEST,"expected message "+expectedMessage); |
224 | logger.log( Logger.FINEST,"expected message sender "+expectedMessage.getSender()); |
225 | addSniffedAgent( expectedMessage.getSender() ); |
226 | jade.util.leap.Iterator rcvrI = expectedMessage.getAllReceiver(); |
227 | while ( rcvrI.hasNext() ) { |
228 | AID rcvr = (AID)rcvrI.next(); |
229 | logger.log( Logger.FINEST,"expected message receiver "+rcvr); |
230 | addSniffedAgent( rcvr ); |
231 | } |
232 | |
233 | // only msgs matching eventPattern might hold sniffed msgs |
234 | eventPattern = |
235 | MessageTemplate.MatchConversationId( |
236 | myAgent.getAID().getName()+"-event" ); |
237 | |
238 | // set default timeout for AMS communication |
239 | setAmsTimeout( DFLT_AMS_TIMEOUT_STR ); |
240 | |
241 | } |
242 | |
243 | |
244 | /** |
245 | * set the AMS timeout |
246 | * @param amsTimeoutStr ISO 8601 duration specification of timeout |
247 | */ |
248 | public void setAmsTimeout( String amsTimeoutStr ) { |
249 | this.amsTimeoutStr = amsTimeoutStr; |
250 | amsTimeoutMS = new Duration( amsTimeoutStr ).toMilliseconds(); |
251 | } |
252 | |
253 | /** |
254 | * get the current value of AMS timeout |
255 | * @return ISO 8601 duration specification of timeout |
256 | */ |
257 | public String getAmsTimeout() { |
258 | return amsTimeoutStr; |
259 | } |
260 | |
261 | |
262 | /** |
263 | * start or stop the agent sniffing |
264 | * @param sniffing true => on, false => off |
265 | * @throws FIPAException on problem |
266 | */ |
267 | private void setSniffing( boolean sniffing ) throws FIPAException { |
268 | ACLMessage msg = getSniffRequestMessage( sniffing ); |
269 | logger.log( Logger.FINEST, "sniff "+sniffing+" msg:"+msg); |
270 | ACLMessage successMsg = |
271 | FIPAService.doFipaRequestClient( myAgent, msg, amsTimeoutMS ); |
272 | if ( null == successMsg ) { |
273 | throw new FIPAException("AMS timeout on SNIFF"+ |
274 | (sniffing?"ON":"OFF")+" request"); |
275 | } |
276 | this.sniffing = sniffing; |
277 | } |
278 | |
279 | /** |
280 | * create and return message to turn sniffing on or off for agents to watch. |
281 | * assumes that <code>sniffedAgents</code> set. |
282 | * @param on whether to turn sniffing on or off |
283 | * @return message to turn sniffing on or off (null if none in list) |
284 | */ |
285 | private ACLMessage getSniffRequestMessage( boolean on ) { |
286 | |
287 | ACLMessage sniffMessage = null; // the message to return |
288 | |
289 | // create an agent action for turning sniffing on or off. |
290 | |
291 | AgentAction sniffAction; |
292 | String replyId; |
293 | |
294 | if ( on ) { |
295 | SniffOn sniffOn = new SniffOn(); |
296 | replyId = JADEManagementVocabulary.SNIFFON + sniffOnCount++; |
297 | sniffOn.setSniffer( myAgent.getAID() ); |
298 | Iterator sniffedAgentsI = sniffedAgents.iterator(); |
299 | while ( sniffedAgentsI.hasNext() ) { |
300 | sniffOn.addSniffedAgents( (AID)sniffedAgentsI.next() ); |
301 | } |
302 | sniffAction = sniffOn; |
303 | } |
304 | else { |
305 | SniffOff sniffOff = new SniffOff(); |
306 | replyId = JADEManagementVocabulary.SNIFFOFF + sniffOffCount++; |
307 | sniffOff.setSniffer( myAgent.getAID() ); |
308 | Iterator sniffedAgentsI = sniffedAgents.iterator(); |
309 | while ( sniffedAgentsI.hasNext() ) { |
310 | sniffOff.addSniffedAgents( (AID)sniffedAgentsI.next() ); |
311 | } |
312 | sniffAction = sniffOff; |
313 | } |
314 | |
315 | // fill in message |
316 | |
317 | try { |
318 | Action action = new Action(); |
319 | action.setActor( myAgent.getAMS() ); |
320 | action.setAction( sniffAction ); |
321 | amsRequestMsg.setOntology( JADEManagementOntology.NAME ); |
322 | amsRequestMsg.setReplyWith( replyId ); |
323 | logger.log( Logger.FINEST,"action:"+action); |
324 | myAgent.getContentManager().fillContent( amsRequestMsg, action ); |
325 | } |
326 | catch ( Exception e ) { |
327 | e.printStackTrace(); |
328 | throw new RuntimeException( e ); |
329 | } |
330 | sniffMessage = amsRequestMsg; |
331 | |
332 | // return constructed message |
333 | return sniffMessage; |
334 | } |
335 | |
336 | |
337 | /** |
338 | * convert local sender/receiver/replyTo to ISGUID format |
339 | * @param m message for which to globalize each agent id |
340 | */ |
341 | private void globalizeAgentIds( ACLMessage m ) { |
342 | |
343 | // perhaps should clone AIDs before they're globalized? |
344 | |
345 | // sender |
346 | AID sender = m.getSender(); |
347 | globalizeAID( sender ); |
348 | m.setSender( sender ); |
349 | |
350 | // receiver |
351 | jade.util.leap.Iterator receivers = m.getAllReceiver(); |
352 | jade.util.leap.List globalizedReceivers = |
353 | new jade.util.leap.LinkedList(); |
354 | while ( receivers.hasNext() ) { |
355 | AID receiver = (AID)receivers.next(); |
356 | globalizeAID( receiver ); |
357 | globalizedReceivers.add( receiver ); |
358 | } |
359 | m.clearAllReceiver(); |
360 | receivers = globalizedReceivers.iterator(); |
361 | while ( receivers.hasNext() ) { |
362 | m.addReceiver( (AID)receivers.next() ); |
363 | } |
364 | |
365 | // reply-to |
366 | jade.util.leap.Iterator replyTos = m.getAllReplyTo(); |
367 | jade.util.leap.List globalizedReplyTos = |
368 | new jade.util.leap.LinkedList(); |
369 | while ( replyTos.hasNext() ) { |
370 | AID replyTo = (AID)replyTos.next(); |
371 | globalizeAID( replyTo ); |
372 | globalizedReplyTos.add( replyTo ); |
373 | } |
374 | m.clearAllReplyTo(); |
375 | replyTos = globalizedReplyTos.iterator(); |
376 | while ( replyTos.hasNext() ) { |
377 | m.addReplyTo( (AID)replyTos.next() ); |
378 | } |
379 | |
380 | // content |
381 | if ( !m.hasByteSequenceContent() ) { |
382 | // wanted globalize method to be static, but was bit messy |
383 | // doubt object creation will be huge performance hit. |
384 | AclMsgCmp amc = new AclMsgCmp(); |
385 | AID fooAID = new AID( "foo", false ); |
386 | globalizeAID( fooAID ); |
387 | String hap = fooAID.getHap(); |
388 | String content = |
389 | amc.globalizeContentAgentIds( m.getContent(), hap ); |
390 | m.setContent( content ); |
391 | } |
392 | |
393 | } |
394 | |
395 | /** |
396 | * run a testcase |
397 | */ |
398 | private void injectMessage() throws Throwable { |
399 | // make sure local names turned into global |
400 | globalizeAgentIds( messageToInject ); |
401 | logger.log( Logger.FINER, |
402 | "globalized ACLMessage to inject:"+messageToInject ); |
403 | // send message |
404 | myAgent.send( messageToInject ); |
405 | logger.log( Logger.FINER, |
406 | myAgent.getAID() + " sent " + messageToInject ); |
407 | } |
408 | |
409 | // string, aidlist, and message comparator classes used for |
410 | // comparing and imposing an order on multiple messages, which |
411 | // is functionality not currently used. |
412 | |
413 | // /** |
414 | // * helper class for string sorting |
415 | // */ |
416 | // class StringComparator implements Comparator { |
417 | // /** collator to use for string comparison */ |
418 | // Collator collator = Collator.getInstance(); |
419 | // /** |
420 | // * compare two strings |
421 | // * @param source left object to compare |
422 | // * @param target right object to compare |
423 | // * @return <0 if 1st >, 0 if equal, >0 if 2nd > |
424 | // * @throws ClassCastException if args aren't strings |
425 | // */ |
426 | // public int compare( Object source, Object target ) { |
427 | // return collator.compare( (String)source,(String)target ); |
428 | // } |
429 | // } |
430 | // |
431 | // /** |
432 | // * this helper class used to compare lists of AIDs. |
433 | // * Iterator is passed in for comparison rather than the list |
434 | // * itself. criterion is local part of each AID. |
435 | // */ |
436 | // class AIDListComparator implements Comparator { |
437 | // /** comparator for strings */ |
438 | // StringComparator stringComparator = new StringComparator(); |
439 | // /** |
440 | // * compare two lists of AIDs |
441 | // * @param o1 the first object to be compared. |
442 | // * @param o2 the second object to be compared. |
443 | // * @return <0 if 1st >, 0 if equal, >0 if 2nd > |
444 | // * @throws ClassCastException if args aren't messages |
445 | // */ |
446 | // public int compare( Object o1, Object o2 ) { |
447 | // int result = 0; |
448 | // // check for null arguments |
449 | // if ( ( null == o1 ) && ( null == o2 ) ) { |
450 | // result = 0; |
451 | // } |
452 | // else if ( ( null == o1 ) && ( null != o2 ) ) { |
453 | // result = -1; |
454 | // } |
455 | // else if ( ( null != o1 ) && ( null == o2 ) ) { |
456 | // result = 1; |
457 | // } |
458 | // else { |
459 | // // non-null arguments |
460 | // // translate list of AID to list of strings |
461 | // // of local names |
462 | // Iterator iter1 = (Iterator)o1; |
463 | // Iterator iter2 = (Iterator)o2; |
464 | // List list1 = new LinkedList(); |
465 | // List list2 = new LinkedList(); |
466 | // while ( iter1.hasNext() ) { |
467 | // AID aid1 = (AID)iter1.next(); |
468 | // list1.add( aid1.getLocalName() ); |
469 | // } |
470 | // while ( iter2.hasNext() ) { |
471 | // AID aid2 = (AID)iter2.next(); |
472 | // list2.add( aid2.getLocalName() ); |
473 | // } |
474 | // Collections.sort( list1 ); |
475 | // Collections.sort( list2 ); |
476 | // iter1 = list1.iterator(); |
477 | // iter2 = list2.iterator(); |
478 | // boolean done = false; |
479 | // while ( !done ) { |
480 | // boolean hasNext1 = iter1.hasNext(); |
481 | // boolean hasNext2 = iter2.hasNext(); |
482 | // if ( !hasNext1 && !hasNext2 ) { |
483 | // // ran off end of both lists |
484 | // result = 0; |
485 | // done = true; |
486 | // } |
487 | // else if ( !hasNext1 && hasNext2 ) { |
488 | // // length mismatch |
489 | // result = -1; |
490 | // done = true; |
491 | // } |
492 | // else if ( hasNext1 && !hasNext2 ) { |
493 | // // length mismatch |
494 | // result = 1; |
495 | // done = true; |
496 | // } |
497 | // else { |
498 | // // compare strings at this point |
499 | // result = stringComparator.compare( |
500 | // iter1.next(), |
501 | // iter2.next() ); |
502 | // if ( 0 != result ) { |
503 | // // found a difference |
504 | // done = true; |
505 | // } |
506 | // } |
507 | // } |
508 | // } |
509 | // |
510 | // return result; |
511 | // } |
512 | // } |
513 | // |
514 | // /** |
515 | // * this helper class used to compare messages. |
516 | // * in descending order, criteria are: |
517 | // * <ol> |
518 | // * <li>local part of sender AID</li> |
519 | // * <li>sorted list of local part of receiver AIDs</li> |
520 | // * <li>message envelope date</li> |
521 | // * Assuming that no agent sends more than one message to the |
522 | // * same group of agents within a millisecond, this |
523 | // * is a total ordering. |
524 | // * </ol> |
525 | // */ |
526 | // class MessageComparator implements Comparator { |
527 | // /** comparator for strings */ |
528 | // StringComparator stringComparator = |
529 | // new StringComparator(); |
530 | // /** comparator for list of AIDs */ |
531 | // AIDListComparator aidListComparator = |
532 | // new AIDListComparator(); |
533 | // /** |
534 | // * compare two messages |
535 | // * @param o1 the first object to be compared. |
536 | // * @param o2 the second object to be compared. |
537 | // * @return <0 if 1st <, 0 if =, >0 if 1st > |
538 | // * @throws ClassCastException if args aren't messages |
539 | // */ |
540 | // public int compare( Object o1, Object o2 ) { |
541 | // int result; |
542 | // // check for null arguments |
543 | // if ( ( null == o1 ) && ( null == o2 ) ) { |
544 | // result = 0; |
545 | // } |
546 | // else if ( ( null == o1 ) && ( null != o2 ) ) { |
547 | // result = -1; |
548 | // } |
549 | // else if ( ( null != o1 ) && ( null == o2 ) ) { |
550 | // result = 1; |
551 | // } |
552 | // else { |
553 | // // non-null arguments |
554 | // ACLMessage m1 = (ACLMessage)o1; |
555 | // ACLMessage m2 = (ACLMessage)o2; |
556 | // AID sender1 = m1.getSender(); |
557 | // AID sender2 = m2.getSender(); |
558 | // // check for null senders |
559 | // if ( ( null == sender1 ) && ( null != sender2 ) ) { |
560 | // result = -1; |
561 | // } |
562 | // else if ( ( null != sender1 ) && |
563 | // ( null == sender2 ) ) { |
564 | // result = 1; |
565 | // } |
566 | // else { |
567 | // // compare senders |
568 | // if ( ( null == sender1 ) && |
569 | // ( null == sender2 ) ) { |
570 | // result = 0; |
571 | // } |
572 | // else { |
573 | // String localSender1 = sender1.getLocalName(); |
574 | // String localSender2 = sender2.getLocalName(); |
575 | // result = |
576 | // stringComparator.compare( localSender1, |
577 | // localSender2 ); |
578 | // } |
579 | // if ( 0 == result ) { |
580 | // // equal senders, now check receivers |
581 | // result = |
582 | // aidListComparator.compare( |
583 | // m1.getAllReceiver(), |
584 | // m2.getAllReceiver() ); |
585 | // if ( 0 == result ) { |
586 | // // equal receivers, now check time |
587 | // Envelope e1 = m1.getEnvelope(); |
588 | // Envelope e2 = m2.getEnvelope(); |
589 | // if ( ( null == e1 ) && ( null == e2 ) ) { |
590 | // result = 0; |
591 | // } |
592 | // else if ( ( null == e1 ) && |
593 | // ( null != e2 ) ) { |
594 | // result = -1; |
595 | // } |
596 | // else if ( ( null != e1 ) && |
597 | // ( null == e2 ) ) { |
598 | // result = 1; |
599 | // } |
600 | // else { |
601 | // Date d1 = e1.getDate(); |
602 | // Date d2 = e2.getDate(); |
603 | // if ( ( null == d1 ) && |
604 | // ( null == d2 ) ) { |
605 | // result = 0; |
606 | // } |
607 | // else if ( ( null == d1 ) && |
608 | // ( null != d2 ) ) { |
609 | // result = -1; |
610 | // } |
611 | // else if ( ( null != d1 ) && |
612 | // ( null == d2 ) ) { |
613 | // result = 1; |
614 | // } |
615 | // else { |
616 | // long longResult = |
617 | // d1.getTime() - d2.getTime(); |
618 | // if ( 0 == longResult ) { |
619 | // result = 0; |
620 | // } |
621 | // else if ( longResult < 0 ) { |
622 | // result = -1; |
623 | // } |
624 | // else if ( longResult > 0 ) { |
625 | // result = 1; |
626 | // } |
627 | // else { |
628 | // throw new RuntimeException( |
629 | // "unreachable"); |
630 | // } |
631 | // } |
632 | // } |
633 | // } |
634 | // } |
635 | // } |
636 | // } |
637 | // |
638 | // return result; |
639 | // } |
640 | // } |
641 | |
642 | /** |
643 | * A match expression on local part of sender and receivers. |
644 | * @author David Bernstein, <a href="http://www.caboodlenetworks.com" |
645 | * >Caboodle Networks, Inc.</a> |
646 | */ |
647 | private class SenderReceiverMatchExpression |
648 | implements MessageTemplate.MatchExpression { |
649 | |
650 | /** local part of expected sender AID */ |
651 | private String templateSender = null; |
652 | /** sorted list of local part of expected receiver AIDs */ |
653 | private Object templateReceivers[] = null; |
654 | |
655 | /** |
656 | * construct match expression on local parts of sender and receiver |
657 | * @param templateMsg message to use as template |
658 | */ |
659 | public SenderReceiverMatchExpression( ACLMessage templateMsg ) { |
660 | AID templateSenderAID = templateMsg.getSender(); |
661 | if ( null != templateSenderAID ) { |
662 | templateSender = templateSenderAID.getLocalName(); |
663 | } |
664 | jade.util.leap.Iterator rcvrI = templateMsg.getAllReceiver(); |
665 | List templateReceiverList = new LinkedList(); |
666 | while ( rcvrI.hasNext() ) { |
667 | templateReceiverList.add( ((AID)rcvrI.next()).getLocalName() ); |
668 | } |
669 | templateReceivers = templateReceiverList.toArray(); |
670 | Arrays.sort( templateReceivers ); |
671 | } |
672 | |
673 | /* (non-Javadoc) |
674 | * @see jade.lang.acl.MessageTemplate$MatchExpression#match( |
675 | * jade.lang.acl.ACLMessage) |
676 | */ |
677 | public boolean match(ACLMessage msg) { |
678 | boolean isMatch = true; |
679 | |
680 | // sender part |
681 | AID senderAID = msg.getSender(); |
682 | String sender = null; |
683 | if ( null != senderAID ) { |
684 | sender = senderAID.getLocalName(); |
685 | } |
686 | if ( ( ( null == templateSender ) && ( null != sender ) ) || |
687 | ( ( null != templateSender ) && ( null == sender ) ) ) { |
688 | isMatch = false; |
689 | } |
690 | else if ( ( null != templateSender ) && ( null != sender ) ) { |
691 | isMatch = sender.equals(templateSender); |
692 | } |
693 | |
694 | // receivers part |
695 | if ( isMatch ) { |
696 | List receiverList = new LinkedList(); |
697 | jade.util.leap.Iterator rcvrI = msg.getAllReceiver(); |
698 | while ( rcvrI.hasNext() ) { |
699 | receiverList.add( ((AID)rcvrI.next()).getLocalName() ); |
700 | } |
701 | if ( receiverList.size() != templateReceivers.length ) { |
702 | isMatch = false; |
703 | } |
704 | else { |
705 | Object receivers[] = receiverList.toArray(); |
706 | Arrays.sort( receivers ); |
707 | int receiverCount = receivers.length; |
708 | for ( int i = 0; i < receiverCount; i++ ) { |
709 | if ( !receivers[i].equals( templateReceivers[i])) { |
710 | isMatch = false; |
711 | break; |
712 | } |
713 | } |
714 | } |
715 | } |
716 | |
717 | return isMatch; |
718 | } |
719 | |
720 | } |
721 | |
722 | /** |
723 | * add AID for agent whose messages should be sniffed. |
724 | * take care not to add self or will drown in see of self-referential |
725 | * messages. |
726 | * @param aid AID of agent to be sniffed |
727 | */ |
728 | private void addSniffedAgent( AID aid ) { |
729 | String localName = aid.getLocalName(); |
730 | if ( !myAgent.getAID().getLocalName().equalsIgnoreCase( localName ) ) { |
731 | AID sniffedAgent = new AID( localName, AID.ISLOCALNAME ); |
732 | sniffedAgents.add( sniffedAgent ); |
733 | logger.log( Logger.FINER,"adding "+sniffedAgent.getName()+" to sniff"); |
734 | } |
735 | } |
736 | |
737 | |
738 | /** |
739 | * convert local AID to ISGUID format |
740 | * @param aid AID to globalize |
741 | */ |
742 | private void globalizeAID( AID aid ) { |
743 | if ( aid.getName().equals( aid.getLocalName() ) ) { |
744 | aid.setLocalName( aid.getName() ); |
745 | } |
746 | } |
747 | |
748 | /** |
749 | * this behaviour's action: inject message and see if get expected back |
750 | */ |
751 | public void action() { |
752 | |
753 | switch ( state ) { |
754 | case STATE_INIT: |
755 | jademxAgent.setUnitTestActualMessage( null ); |
756 | startSniffing(); |
757 | state = ( ( null == failMsg ) ? STATE_INJECT : STATE_NOTIFY ); |
758 | break; |
759 | case STATE_INJECT: |
760 | inject(); |
761 | state = ( ( null == failMsg ) ? STATE_WAIT : STATE_NOTIFY ); |
762 | break; |
763 | case STATE_WAIT: |
764 | doSniff(); |
765 | if ( ( null == failMsg ) && ( null == sniffedMessage ) ) { |
766 | // check for timeout |
767 | long nowMS = System.currentTimeMillis(); |
768 | if ( nowMS >= testcaseTimeoutMsAbs ) { |
769 | failMsg = "test timeout after "+ |
770 | ( (nowMS-testcaseStartMsAbs) / 1000.0 ) + " seconds"; |
771 | } |
772 | } |
773 | if ( null != failMsg ) { |
774 | state = STATE_NOTIFY; |
775 | } |
776 | else if ( null != sniffedMessage ) { |
777 | jademxAgent.setUnitTestActualMessage( sniffedMessage ); |
778 | state = STATE_COMPARE; |
779 | } |
780 | else { |
781 | // stay in STATE_WAIT |
782 | block(); |
783 | logger.log( Logger.FINE, |
784 | "blocking: matching msg not yet sniffed and "+ |
785 | "timeout not yet reached"); |
786 | } |
787 | |
788 | break; |
789 | case STATE_COMPARE: |
790 | compare(); |
791 | state = STATE_NOTIFY; |
792 | break; |
793 | case STATE_NOTIFY: |
794 | sendJmxNotification(); |
795 | finished = true; |
796 | state = STATE_COMPLETE; |
797 | break; |
798 | case STATE_COMPLETE: |
799 | logger.warning("behaviour running even though complete"); |
800 | break; |
801 | default: |
802 | throw new RuntimeException( |
803 | getClass().getName()+": unknown state "+state); |
804 | } |
805 | |
806 | } |
807 | |
808 | /** |
809 | * start sniffing |
810 | */ |
811 | private void startSniffing() { |
812 | try { |
813 | setSniffing( true ); |
814 | } |
815 | catch ( Throwable t ) { |
816 | failMsg = |
817 | ThrowableUtil.errMsg( "problem starting sniffing", t ); |
818 | } |
819 | } |
820 | |
821 | /** |
822 | * inject test message and set absolute testcase timeout |
823 | */ |
824 | private void inject() { |
825 | // inject test message |
826 | try { |
827 | injectMessage(); |
828 | } |
829 | catch ( Throwable t ) { |
830 | failMsg = |
831 | ThrowableUtil.errMsg( "problem injecting test message", t ); |
832 | } |
833 | // set testcase start and timeout |
834 | testcaseStartMsAbs = System.currentTimeMillis(); |
835 | testcaseTimeoutMsAbs = |
836 | testcaseStartMsAbs + testcaseTimeout.toMilliseconds(); |
837 | } |
838 | |
839 | /** |
840 | * do the sniffing until decide it's time to stop. |
841 | * criteria to stop listening for sniffed messages: |
842 | * <ol> |
843 | * <li>timeout for running this testcase</li> |
844 | * <li>seen message that matches pattern</li> |
845 | * </ol> |
846 | * @throws Exception on problem |
847 | */ |
848 | private void doSniff() { |
849 | |
850 | // sniff messages look like: |
851 | // |
852 | // |
853 | // (INFORM |
854 | // :sender ( agent-identifier |
855 | // :name tester-on-Main-Container@picturebook:1099/JADE |
856 | // :addresses (sequence http://picturebook:7778/acc )) |
857 | // :receiver ( set |
858 | // ( agent-identifier |
859 | // :name tester@picturebook:1099/JADE |
860 | // :addresses (sequence http://picturebook:7778/acc )) ) |
861 | // :content "( |
862 | // (occurred |
863 | // (event-record |
864 | // :what |
865 | // (posted-message |
866 | // :receiver |
867 | // (agent-identifier |
868 | // :name testee1@picturebook:1099/JADE |
869 | // :addresses (sequence http://picturebook:7778/acc)) |
870 | // :message (acl-message :payload \" |
871 | // (REQUEST |
872 | // :sender ( agent-identifier :name testee2@picturebook:1099/JADE |
873 | // :addresses (sequence http://picturebook:7778/acc )) |
874 | // :receiver (set ( agent-identifier :name testee1@picturebook:1099/JADE ) ) |
875 | // :content \\"?How are you testee1@picturebook:1099/JADE\\" |
876 | // :reply-with msg1 |
877 | // :in-reply-to msg1 |
878 | // :language my-language |
879 | // :conversation-id |
880 | // scripted-conversation1 )\")) |
881 | // :when 20040802T023026649Z |
882 | // :where (container-ID :name Main-Container |
883 | // :address picturebook))))" |
884 | // :language fipa-sl0 |
885 | // :ontology JADE-Introspection |
886 | // :conversation-id tester@picturebook:1099/JADE-event ) |
887 | // |
888 | |
889 | // the occurred->event-record->:what can be one of: |
890 | // 1. sent-message |
891 | // 2. posted-message |
892 | // 3. received-message |
893 | // the injected message just causes the last two. |
894 | // the real messages cause all three, so just look for |
895 | // the "sent-message" notifications to filter out |
896 | // the injected message. |
897 | |
898 | try { |
899 | ACLMessage matchedMessage = null; |
900 | // get next message matching event pattern |
901 | ACLMessage m = myAgent.receive( eventPattern ); |
902 | if ( null == m ) { |
903 | // nothing currently available |
904 | logger.log( Logger.FINEST, "no message yet matches" ); |
905 | } |
906 | else { |
907 | // got an event pattern message |
908 | logger.log( Logger.FINEST,"received message: "+m); |
909 | // decode the event pattern message |
910 | ContentElement ce = null; |
911 | ce = myAgent.getContentManager().extractContent( m ); |
912 | if ( ce instanceof Occurred ) { |
913 | // it's an occurred notification |
914 | Occurred occurred = (Occurred)ce; |
915 | EventRecord eventRecord = occurred.getWhat(); |
916 | Event event = eventRecord.getWhat(); |
917 | jade.domain.introspection.ACLMessage |
918 | sniffedMessageAsContent = null; |
919 | // take whichever shows up first |
920 | // sent/posted/received should share a superclass... |
921 | if ( event instanceof SentMessage ) { |
922 | // it's a sent-message notification |
923 | SentMessage sentMessage = (SentMessage)event; |
924 | // now have a message that was sniffed, |
925 | // as content element |
926 | sniffedMessageAsContent = |
927 | sentMessage.getMessage(); |
928 | } |
929 | else if ( event instanceof PostedMessage ) { |
930 | // it's a posted-message notification |
931 | PostedMessage postedMessage = (PostedMessage)event; |
932 | // now have a message that was sniffed, |
933 | // as content element |
934 | sniffedMessageAsContent = |
935 | postedMessage.getMessage(); |
936 | } |
937 | else if ( event instanceof ReceivedMessage ) { |
938 | // it's a received-message notification |
939 | ReceivedMessage receivedMessage = (ReceivedMessage)event; |
940 | // now have a message that was sniffed, |
941 | // as content element |
942 | sniffedMessageAsContent = |
943 | receivedMessage.getMessage(); |
944 | } |
945 | if ( null != sniffedMessageAsContent ) { |
946 | logger.log( Logger.FINEST, |
947 | "sniffed message as content:"+ |
948 | sniffedMessageAsContent); |
949 | // cvt jade.domain.introspection.ACLMessage |
950 | // to jade.lang.acl.ACLMessage |
951 | String msgPayload = |
952 | sniffedMessageAsContent.getPayload(); |
953 | ACLMessage sniffedMessage = |
954 | AclMsgCmp.stringToMessage( msgPayload ); |
955 | // see if user pattern filters out this msg |
956 | if ( matchSenderReceiverTemplate.match( sniffedMessage ) ) { |
957 | // now sniffed msg that want to compare! |
958 | logger.log( Logger.FINE, |
959 | "matching message:"+sniffedMessage); |
960 | matchedMessage = sniffedMessage; |
961 | } |
962 | } |
963 | } |
964 | } |
965 | // set sniffed message to matched message, if any |
966 | sniffedMessage = matchedMessage; |
967 | } |
968 | catch ( Exception e ) { |
969 | failMsg = |
970 | ThrowableUtil.errMsg( |
971 | "exception caught looking for matching sniffed message", e ); |
972 | } |
973 | |
974 | } |
975 | |
976 | /** |
977 | * compare sniffed message (if any) with expected message. |
978 | * use agent mbean attributes to decide slots are significant. |
979 | */ |
980 | private void compare() { |
981 | if ( null == sniffedMessage ) { |
982 | failMsg = |
983 | "no message matching expected sender and receiver(s) seen"; |
984 | } |
985 | else { |
986 | logger.log( Logger.FINER,"sniffed message:"+sniffedMessage); |
987 | AclMsgCmp aclMsgCmp = new AclMsgCmp(); |
988 | aclMsgCmp.setCmpContent( jademxAgent.isUnitTestCmpContent() ); |
989 | aclMsgCmp.setCmpConversationId( jademxAgent.isUnitTestCmpConversationId() ); |
990 | aclMsgCmp.setCmpEncoding( jademxAgent.isUnitTestCmpEncoding() ); |
991 | aclMsgCmp.setCmpEnvelope( jademxAgent.isUnitTestCmpEnvelope() ); |
992 | aclMsgCmp.setCmpInReplyTo( jademxAgent.isUnitTestCmpInReplyTo() ); |
993 | aclMsgCmp.setCmpLanguage( jademxAgent.isUnitTestCmpLanguage() ); |
994 | aclMsgCmp.setCmpOntology( jademxAgent.isUnitTestCmpOntology() ); |
995 | aclMsgCmp.setCmpPerformative( jademxAgent.isUnitTestCmpPerformative() ); |
996 | aclMsgCmp.setCmpProtocol( jademxAgent.isUnitTestCmpProtocol() ); |
997 | aclMsgCmp.setCmpReceiver( jademxAgent.isUnitTestCmpReceiver() ); |
998 | aclMsgCmp.setCmpReplyBy( jademxAgent.isUnitTestCmpReplyBy() ); |
999 | aclMsgCmp.setCmpReplyTo( jademxAgent.isUnitTestCmpReplyTo() ); |
1000 | aclMsgCmp.setCmpReplyWith( jademxAgent.isUnitTestCmpReplyWith() ); |
1001 | aclMsgCmp.setCmpSender( jademxAgent.isUnitTestCmpSender() ); |
1002 | aclMsgCmp.setCmpUserProperties( jademxAgent.isUnitTestCmpUserProperties() ); |
1003 | aclMsgCmp.setIgnoreContentAIDAddresses( jademxAgent.isUnitTestIgnoreContentAIDAddresses() ); |
1004 | aclMsgCmp.setNewlinesNormalized( jademxAgent.isUnitTestNewlinesNormalized() ); |
1005 | failMsg = aclMsgCmp.compare( |
1006 | expectedMessage, sniffedMessage, |
1007 | varProps ); |
1008 | } |
1009 | } |
1010 | |
1011 | /** |
1012 | * stop sniffing if currently are |
1013 | */ |
1014 | private void stopSniffing() { |
1015 | if ( sniffing ) { |
1016 | try { |
1017 | setSniffing( false ); |
1018 | } |
1019 | catch ( Exception e ) { |
1020 | String err = "problem stopping sniffing"; |
1021 | logger.log( Logger.WARNING, err, e ); |
1022 | if ( null == failMsg ) { |
1023 | failMsg = ThrowableUtil.errMsg( err, e ); |
1024 | } |
1025 | } |
1026 | } |
1027 | } |
1028 | |
1029 | /** |
1030 | * send a unit testing success or failure JMX notification |
1031 | */ |
1032 | private void sendJmxNotification() { |
1033 | // we always want to exit through here, so make sure sniffing off |
1034 | stopSniffing(); |
1035 | // now generate and send notification |
1036 | String notifType; |
1037 | String notifMsg = null; |
1038 | logger.log( Logger.FINEST,"failure message:"+failMsg); |
1039 | if ( null == failMsg ) { |
1040 | notifType = JademxAgent.NOTIF_UNIT_TEST_SUCCESS_NAME; |
1041 | } |
1042 | else { |
1043 | notifType = JademxAgent.NOTIF_UNIT_TEST_FAILURE_NAME; |
1044 | notifMsg = failMsg; |
1045 | } |
1046 | jademxAgent.notifyListeners( notifType, notifMsg, null ); |
1047 | } |
1048 | |
1049 | |
1050 | /* (non-Javadoc) |
1051 | * @see jade.core.behaviours.Behaviour#done() |
1052 | */ |
1053 | public boolean done() { |
1054 | return finished; |
1055 | } |
1056 | |
1057 | |
1058 | } |