PageRenderTime 25ms CodeModel.GetById 6ms app.highlight 15ms RepoModel.GetById 1ms app.codeStats 1ms

/interpreter/tags/at2-build060407/test/edu/vub/at/actors/natives/DistributionTest.java

http://ambienttalk.googlecode.com/
Java | 464 lines | 287 code | 102 blank | 75 comment | 12 complexity | e0e68d2f8cab5d418cb689f3f7c5d2d8 MD5 | raw file
  1package edu.vub.at.actors.natives;
  2
  3import edu.vub.at.actors.eventloops.Callable;
  4import edu.vub.at.actors.net.ConnectionListener;
  5import edu.vub.at.eval.Evaluator;
  6import edu.vub.at.exceptions.InterpreterException;
  7import edu.vub.at.objects.ATObject;
  8import edu.vub.at.objects.ATTable;
  9import edu.vub.at.objects.mirrors.NativeClosure;
 10import edu.vub.at.objects.natives.NATNil;
 11import edu.vub.at.objects.natives.NATObjectClosureTest;
 12import edu.vub.at.objects.natives.grammar.AGSymbol;
 13import edu.vub.at.parser.NATParser;
 14
 15import junit.framework.TestCase;
 16
 17public class DistributionTest extends TestCase {
 18
 19	private ELVirtualMachine virtual1_;
 20	private ELVirtualMachine virtual2_;
 21	
 22	/**
 23	 * the VMs from this unit test join a dedicated test group (not the default group)
 24	 * to avoid as much interference as possible with other running AmbientTalk interpreters.
 25	 */
 26	private static final String _TEST_GROUP_NAME_ = "AmbientTalkTest";
 27	
 28	private static final int _TIMEOUT_ = 10000;
 29	
 30	private boolean testResult_ = false;
 31	
 32	// used to avoid 'final' restriction for nested classes
 33	protected synchronized void setTestResult(boolean value) {
 34		testResult_ = value;
 35		
 36		// typically, we wait for a given timeout but can resume earlier when this event takes place
 37		this.notify();
 38	}
 39	
 40	// used to avoid 'final' restriction for nested classes
 41	protected boolean getTestResult() {
 42		return testResult_;
 43	}
 44	
 45	public static void main(String[] args) {
 46		junit.swingui.TestRunner.run(NATObjectClosureTest.class);
 47	}
 48
 49	protected void setUp() throws Exception {
 50		super.setUp();
 51
 52		virtual1_ = new ELVirtualMachine(NATNil._INSTANCE_, new SharedActorField[] { }, _TEST_GROUP_NAME_);
 53		virtual2_ = new ELVirtualMachine(NATNil._INSTANCE_, new SharedActorField[] { }, _TEST_GROUP_NAME_);
 54	}
 55	
 56	protected void tearDown() throws Exception {
 57		if(virtual1_ != null) {
 58			virtual1_.event_goOffline();
 59			virtual1_.stopProcessing();
 60		}
 61		
 62		if(virtual2_ != null) {
 63			virtual2_.event_goOffline();
 64			virtual2_.stopProcessing();
 65		}
 66	}
 67	
 68	// Creates an ELActor, hosted on the provided VM.
 69	private ELActor setUpActor(ELVirtualMachine host) throws InterpreterException {
 70		return NATActorMirror.createEmptyActor(host, new NATActorMirror(host)).getFarHost();
 71	}
 72	
 73	// installs a closure in a particular actor's scope which allows signalling a return value
 74	// with the added bonus of waking up the test thread from waiting.
 75	private void setUpSuccessTrigger(ELActor processor) throws Exception {
 76		processor.sync_event_performTest(new Callable() {
 77			public Object call(Object argument)throws InterpreterException {
 78				return Evaluator.getGlobalLexicalScope().meta_defineField(
 79						AGSymbol.jAlloc("success"),
 80						new NativeClosure(NATNil._INSTANCE_) {
 81
 82							public ATObject base_apply(ATTable arguments) throws InterpreterException {
 83								setTestResult(true);
 84								
 85								return NATNil._INSTANCE_;
 86							}
 87						});
 88			}
 89		});
 90	}
 91	
 92	// Joint code for the various test suites to test the behaviour of the AT connection observers 
 93	private void setUpConnectionObservers() throws Exception {
 94		ELActor subscriber = setUpActor(virtual1_);
 95		ELActor provider   = setUpActor(virtual2_);
 96		
 97		// We define a closure to inform us the test succeeded
 98		setUpSuccessTrigger(subscriber);
 99		
100		subscriber.sync_event_eval(
101				NATParser.parse("DistributionTest#setUpConnectionObservers()",
102						"defstripe Service; \n" +
103						"when: Service discovered: { | ref |" +
104						"  when: ref disconnected: { success(); }; \n" +
105						"  when: ref reconnected:  { success(); }; \n" +
106						// explicitly triggering success, although we are not testing service discovery
107						// allows to minimize the waiting time until we can go offline
108						"  success(); " +
109						"} \n;"));
110		
111		provider.sync_event_eval(
112				NATParser.parse("DistributionTest#setUpConnectionObservers()",
113						"defstripe Service; \n" +
114						"export: (object: { nil }) as: Service"));
115	}
116
117	
118	/**
119	 * When a virtual machine joins the AmbientTalk overlay network, all virtual machines are 
120	 * notified of this fact by the underlying distribution layer. Messages are sent to the 
121	 * DiscoveryManager to notify it of a new vm which may host required services and to all
122	 * (far reference) listeners waiting for the appearance of that VM.
123	 * 
124	 * This test registers a dedicated listener to test the dispatching of connected and 
125	 * disconnected messages to such listeners when appropriate.
126	 */
127	public synchronized void testVirtualMachineDiscovery() {
128		
129		// We prevent any race conditions between the going online and offline, forcing both
130		// handlers to be called. Therefore the test fails unless the disconnected handler
131		// determines it was successful
132		setTestResult(false);
133		
134		virtual1_.connectionManager_.addConnectionListener(
135				virtual2_.getGUID(),
136				new ConnectionListener() {
137					public void connected() {
138						setTestResult(true);
139					}
140					public void disconnected() { }
141				});
142		
143		virtual1_.event_goOnline();
144		virtual2_.event_goOnline();
145		
146		try {
147			this.wait( _TIMEOUT_ );
148		} catch (InterruptedException e) {};
149		
150		if(! getTestResult())
151			fail("DiscoveryBus notification of the VM has failed to arrive within " + _TIMEOUT_ /1000 + " sec.");
152	}
153	
154	/**
155	 * When a virtual machine leaves the AmbientTalk overlay network, all virtual machines are 
156	 * notified of this fact by the underlying distribution layer. Messages are sent to the 
157	 * DiscoveryManager and to all (far reference) listeners connected to that VM.
158	 * 
159	 * This test registers a dedicated listener to test the dispatching of disconnected 
160	 * messages to such listeners when appropriate.
161	 */
162	public synchronized void testVirtualMachineDisconnection() {
163		
164		setTestResult(false);
165		
166		virtual2_.connectionManager_.addConnectionListener(
167				virtual1_.getGUID(),
168				new ConnectionListener() {
169					public void connected() { }
170					public void disconnected() { 
171						setTestResult(true);
172					}
173				});
174		
175		virtual1_.event_goOnline();
176		virtual2_.event_goOnline();
177
178		try {
179			this.wait( _TIMEOUT_ );
180		} catch (InterruptedException e) {};
181		
182		
183		virtual1_.event_goOffline();
184		
185		try {
186			this.wait( _TIMEOUT_ );
187		} catch (InterruptedException e) {};
188		
189		if(! getTestResult())
190			fail("Disconnection notification of the VM has failed to arrive within " + _TIMEOUT_ /1000 + " sec.");
191	}
192	
193	/**
194	 * This test registers a dedicated listener to test the dispatching of disconnected 
195	 * messages. When the time lapse betwen connection and disconnection is too small,
196	 * the test may be subject to race conditions, hence we provide a version where
197	 * no wait is performed, to provoke them.
198	 */
199	public synchronized void testVirtualMachineDisconnectionRace() {
200		
201		// If the race occurs and neither the connected, nor the disconnected event listener
202		// are triggered, the test should succeed, unless exceptions were raised.
203		setTestResult(true);
204		
205		virtual2_.connectionManager_.addConnectionListener(
206				virtual1_.getGUID(),
207				new ConnectionListener() {
208					public void connected() { 
209						setTestResult(false);
210					}
211					public void disconnected() { 
212						setTestResult(true);
213					}
214				});
215		
216		virtual1_.event_goOnline();
217		virtual2_.event_goOnline();
218		
219		virtual1_.event_goOffline();
220		
221		try {
222			this.wait( _TIMEOUT_ );
223		} catch (InterruptedException e) {};
224		
225		if(! getTestResult())
226			fail("Disconnection notification of the VM has failed to arrive within " + _TIMEOUT_ /1000 + " sec.");
227	}
228	
229	/**
230	 * Uses the when: discovered: and export: as: constructs to make an object on one virtual
231	 * machine accessible to another virtual machine. 
232	 * @throws Exception
233	 */
234	public synchronized void testServiceDiscovery() throws Exception {
235		
236		setTestResult(false);
237		
238		setUpConnectionObservers();
239		
240		virtual1_.event_goOnline();
241		virtual2_.event_goOnline();
242		
243		try {
244			this.wait( _TIMEOUT_ );
245		} catch (InterruptedException e) {};
246		
247		if(! getTestResult())
248			fail("Service DiscoveryBus notification has failed to arrive within " + _TIMEOUT_ /1000 + " sec.");
249	}
250		
251	/**
252	 * This test uses the when: disconnected: to detect when a far reference has become
253	 * disconnected. We distinguish between two tests, depending on the role of the device
254	 * that falls away. If the provider disconnects, the subscriber hosting the far reference
255	 * is notified of this event through by the distribution layer.
256	 * 
257	 * @throws Exception
258	 */
259	public synchronized void testProviderDisconnection() throws Exception {
260		
261		setUpConnectionObservers();
262		
263		virtual1_.event_goOnline();
264		virtual2_.event_goOnline();
265		
266		try {
267			this.wait( _TIMEOUT_ );
268		} catch (InterruptedException e) {};
269		
270		// reset the test condition
271		setTestResult(false);
272		
273		virtual2_.event_goOffline();
274		
275		try {
276			this.wait( _TIMEOUT_ );
277		} catch (InterruptedException e) {};
278		
279		if(! getTestResult())
280			fail("Disconnection observer has failed to trigger within " + _TIMEOUT_ /1000 + " sec.");
281
282		// reset the test condition
283		setTestResult(false);
284
285		virtual2_.event_goOnline();
286		
287		try {
288			this.wait(  );
289		} catch (InterruptedException e) {};
290		
291		if(! getTestResult())
292			fail("Reconnection observer has failed to trigger within " + _TIMEOUT_ /1000 + " sec.");
293	
294	}
295	
296	/**
297	 * This test uses the when: disconnected: to detect when a far reference has become
298	 * disconnected. We distinguish between two tests, depending on the role of the device
299	 * that goes offline.
300	 * 
301	 * @throws Exception
302	 */
303	public synchronized void testSubscriberDisconnection() throws Exception {
304		
305		setUpConnectionObservers();
306		
307		virtual1_.event_goOnline();
308		virtual2_.event_goOnline();
309		
310		try {
311			this.wait( _TIMEOUT_ );
312		} catch (InterruptedException e) {};
313		
314		// reset the test condition
315		setTestResult(false);
316		
317		virtual1_.event_goOffline();
318		
319		try {
320			this.wait( _TIMEOUT_ );
321		} catch (InterruptedException e) {};
322		
323		if(! getTestResult())
324			fail("Disconnection observer has failed to trigger within " + _TIMEOUT_ /1000 + " sec.");
325		
326		// reset the test condition
327		setTestResult(false);
328
329		virtual1_.event_goOnline();
330		
331		try {
332			this.wait(  );
333		} catch (InterruptedException e) {};
334		
335		if(! getTestResult())
336			fail("Reconnection observer has failed to trigger within " + _TIMEOUT_ /1000 + " sec.");
337	
338	}
339
340	public synchronized void testRetract() throws Exception {
341		
342		final ELActor provider = setUpActor(virtual1_);
343		final ELActor subscriber = setUpActor(virtual2_);
344		
345		// We define a closure to inform us the test succeeded
346		setUpSuccessTrigger(subscriber);
347		
348		subscriber.sync_event_eval(
349				NATParser.parse("DistributionTest#testRetract()",
350						"def messages := nil;" +
351						"def far := nil;" +
352						"defstripe Service; \n" +
353						"when: Service discovered: { | ref | \n" +
354						"  far := ref; \n" +
355						"  when: ref disconnected: { \n" +
356						"    messages := retract: ref; \n" +
357						"    success(); \n" +
358						"  }; \n" +
359						"  success(); " +
360						"} \n;"));
361		
362		provider.sync_event_eval(
363				NATParser.parse("DistributionTest#testRetract()",
364						"defstripe Service; \n" +
365						"export: (object: { def inc(num) { num + 1; }; }) as: Service"));
366		
367		virtual1_.event_goOnline();
368		virtual2_.event_goOnline();
369		
370		try {
371			this.wait( _TIMEOUT_ );
372		} catch (InterruptedException e) {};
373		
374		// reset the test condition
375		setTestResult(false);
376		
377		// Spawn a new thread to allow the disconnection of the provider to be in 
378		// parallel with the sending of messages by the subscriber
379		Thread sender = new Thread() {
380			public void run() {
381				try {
382					subscriber.sync_event_eval(
383							NATParser.parse("DistributionTest#testRetract()",
384									"1.to: 5 do: { | i | \n" +
385									"  far<-inc(i); \n" +
386									"}; \n" +
387									// Stop waiting after five messages were scheduled to ensure
388									// some messages may still be in the outbox
389									"success(); \n"+
390									"6.to: 10 do: { | i | \n" +
391									"  far<-inc(i); \n" +
392									"}; \n"));
393				} catch (InterpreterException e) {
394					e.printStackTrace();
395					fail("exception: " + e);
396				}
397			};
398		};
399
400		sender.start();
401		
402		// wait till some messages were sent
403		try {
404			this.wait( _TIMEOUT_ );
405		} catch (InterruptedException e) {};
406		
407		virtual1_.event_goOffline();
408		
409		// wait till disconnection event was processed
410		try {
411			this.wait( _TIMEOUT_ );
412		} catch (InterruptedException e) {};
413		
414		// ELActor must not be busy while retracting the messages
415		sender.join();
416		
417		ATObject messages = subscriber.sync_event_eval(
418				NATParser.parse("DistributionTest#testRetract()",
419						"messages"));
420		
421		
422	}
423	
424	public void notestSimple() {
425		try {
426			ELActor alice = setUpActor(virtual1_);
427			ELActor bob = setUpActor(virtual2_);
428			
429			alice.sync_event_eval(
430					NATParser.parse("CrossVMCommunicationTest#testSimple()", 
431							"defstripe HelloWorld; \n" +
432							"whenever: HelloWorld discovered: { | ref | \n" +
433							"  ref <- hello(\"alice\"); \n" +
434							"}; \n"));
435			
436			bob.sync_event_eval(
437					NATParser.parse("CrossVMCommunicationTest#testSimple()", 
438							"defstripe HelloWorld; \n" +
439							"def english := object: { \n" +
440							"  def hello( name ) { \"hello \" + name }; \n" +
441							"}; \n" +
442							"def spanish := object: { \n" +
443							"  def hello( name ) { \"hola \" + name }; \n" +
444							"}; \n" +
445							"export: english as: HelloWorld; \n" +
446							"export: spanish as: HelloWorld; \n"));
447			
448			alice.host_.event_goOnline();
449			bob.host_.event_goOnline();
450			
451			synchronized (this) {
452				try {
453					this.wait(10000);
454				} catch (InterruptedException e) {
455					e.printStackTrace();
456				}
457			}
458			
459		} catch (InterpreterException e) {
460			e.printStackTrace();
461		}	
462	}
463
464}