PageRenderTime 40ms CodeModel.GetById 18ms app.highlight 17ms RepoModel.GetById 1ms app.codeStats 1ms

/interpreter/tags/at2dist130208/test/edu/vub/at/actors/natives/DistributionTest.java

http://ambienttalk.googlecode.com/
Java | 485 lines | 290 code | 107 blank | 88 comment | 12 complexity | ee9430085303b9f91b2c1683bd777f0e 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.OBJNil;
 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(OBJNil._INSTANCE_, new SharedActorField[] { }, _TEST_GROUP_NAME_);
 53		virtual2_ = new ELVirtualMachine(OBJNil._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(OBJNil._INSTANCE_) {
 81
 82							public ATObject base_apply(ATTable arguments) throws InterpreterException {
 83								setTestResult(true);
 84								
 85								return OBJNil._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						"deftype 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						"deftype 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		ConnectionListener aConnectionListener = new ConnectionListener() {
135			public void connected() {
136				setTestResult(true);
137			}
138			public void disconnected() { }
139			public void takenOffline() { }
140		};
141		
142		//Since ConnectionListenerManager keeps a map of WeakReferences, aDummyRef is created to prevent aConnectionListener from gc.
143		ConnectionListener aDummyRef = aConnectionListener;
144		
145		virtual1_.connectionManager_.addConnectionListener( virtual2_.getGUID(), aConnectionListener);
146		
147		virtual1_.event_goOnline();
148		virtual2_.event_goOnline();
149		
150		try {
151			this.wait( _TIMEOUT_ );
152		} catch (InterruptedException e) {};
153		
154		if(! getTestResult())
155			fail("DiscoveryBus notification of the VM has failed to arrive within " + _TIMEOUT_ /1000 + " sec.");
156	}
157	
158	/**
159	 * When a virtual machine leaves the AmbientTalk overlay network, all virtual machines are 
160	 * notified of this fact by the underlying distribution layer. Messages are sent to the 
161	 * DiscoveryManager and to all (far reference) listeners connected to that VM.
162	 * 
163	 * This test registers a dedicated listener to test the dispatching of disconnected 
164	 * messages to such listeners when appropriate.
165	 */
166	public synchronized void testVirtualMachineDisconnection() {
167		
168		setTestResult(false);
169		ConnectionListener aConnectionListener = new ConnectionListener() {
170			public void connected() { }
171			public void disconnected() { 
172				setTestResult(true);
173			}
174			public void takenOffline() { }
175		};
176		
177		//Since ConnectionListenerManager keeps a map of WeakReferences, aDummyRef is created to prevent aConnectionListener from gc.
178		ConnectionListener aDummyRef = aConnectionListener;
179		
180		virtual2_.connectionManager_.addConnectionListener( virtual1_.getGUID(), aConnectionListener);
181		
182		virtual1_.event_goOnline();
183		virtual2_.event_goOnline();
184
185		try {
186			this.wait( _TIMEOUT_ );
187		} catch (InterruptedException e) {};
188		
189		
190		virtual1_.event_goOffline();
191		
192		try {
193			this.wait( _TIMEOUT_ );
194		} catch (InterruptedException e) {};
195		
196		if(! getTestResult())
197			fail("Disconnection notification of the VM has failed to arrive within " + _TIMEOUT_ /1000 + " sec.");
198	}
199	
200	/**
201	 * This test registers a dedicated listener to test the dispatching of disconnected 
202	 * messages. When the time lapse betwen connection and disconnection is too small,
203	 * the test may be subject to race conditions, hence we provide a version where
204	 * no wait is performed, to provoke them.
205	 */
206	public synchronized void testVirtualMachineDisconnectionRace() {
207		
208		// If the race occurs and neither the connected, nor the disconnected event listener
209		// are triggered, the test should succeed, unless exceptions were raised.
210		setTestResult(true);
211		
212		ConnectionListener aConnectionListener = new ConnectionListener() {
213			public void connected() { 
214				setTestResult(false);
215			}
216			public void disconnected() { 
217				setTestResult(true);
218			}
219			public void takenOffline() { }
220		};
221		
222		//Since membershipNotifier is a map of WeakReferences, aDummyRef is created to prevent aConnectionListener from gc.
223		ConnectionListener aDummyRef = aConnectionListener;
224		
225		virtual2_.connectionManager_.addConnectionListener( virtual1_.getGUID(), aConnectionListener);
226				/*virtual1_.getGUID(),
227				new ConnectionListener() {
228					public void connected() { 
229						setTestResult(false);
230					}
231					public void disconnected() { 
232						setTestResult(true);
233					}
234					public void takenOffline() { }
235				});*/
236		
237		virtual1_.event_goOnline();
238		virtual2_.event_goOnline();
239		
240		virtual1_.event_goOffline();
241		
242		try {
243			this.wait( _TIMEOUT_ );
244		} catch (InterruptedException e) {};
245		
246		if(! getTestResult())
247			fail("Disconnection notification of the VM has failed to arrive within " + _TIMEOUT_ /1000 + " sec.");
248	}
249	
250	/**
251	 * Uses the when: discovered: and export: as: constructs to make an object on one virtual
252	 * machine accessible to another virtual machine. 
253	 * @throws Exception
254	 */
255	public synchronized void testServiceDiscovery() throws Exception {
256		
257		setTestResult(false);
258		
259		setUpConnectionObservers();
260		
261		virtual1_.event_goOnline();
262		virtual2_.event_goOnline();
263		
264		try {
265			this.wait( _TIMEOUT_ );
266		} catch (InterruptedException e) {};
267		
268		if(! getTestResult())
269			fail("Service DiscoveryBus notification has failed to arrive within " + _TIMEOUT_ /1000 + " sec.");
270	}
271		
272	/**
273	 * This test uses the when: disconnected: to detect when a far reference has become
274	 * disconnected. We distinguish between two tests, depending on the role of the device
275	 * that falls away. If the provider disconnects, the subscriber hosting the far reference
276	 * is notified of this event through by the distribution layer.
277	 * 
278	 * @throws Exception
279	 */
280	public synchronized void testProviderDisconnection() throws Exception {
281		
282		setUpConnectionObservers();
283		
284		virtual1_.event_goOnline();
285		virtual2_.event_goOnline();
286		
287		try {
288			this.wait( _TIMEOUT_ );
289		} catch (InterruptedException e) {};
290		
291		// reset the test condition
292		setTestResult(false);
293		
294		virtual2_.event_goOffline();
295		
296		try {
297			this.wait( _TIMEOUT_ );
298		} catch (InterruptedException e) {};
299		
300		if(! getTestResult())
301			fail("Disconnection observer has failed to trigger within " + _TIMEOUT_ /1000 + " sec.");
302
303		// reset the test condition
304		setTestResult(false);
305
306		virtual2_.event_goOnline();
307		
308		try {
309			this.wait(  );
310		} catch (InterruptedException e) {};
311		
312		if(! getTestResult())
313			fail("Reconnection observer has failed to trigger within " + _TIMEOUT_ /1000 + " sec.");
314	
315	}
316	
317	/**
318	 * This test uses the when: disconnected: to detect when a far reference has become
319	 * disconnected. We distinguish between two tests, depending on the role of the device
320	 * that goes offline.
321	 * 
322	 * @throws Exception
323	 */
324	public synchronized void testSubscriberDisconnection() throws Exception {
325		
326		setUpConnectionObservers();
327		
328		virtual1_.event_goOnline();
329		virtual2_.event_goOnline();
330		
331		try {
332			this.wait( _TIMEOUT_ );
333		} catch (InterruptedException e) {};
334		
335		// reset the test condition
336		setTestResult(false);
337		
338		virtual1_.event_goOffline();
339		
340		try {
341			this.wait( _TIMEOUT_ );
342		} catch (InterruptedException e) {};
343		
344		if(! getTestResult())
345			fail("Disconnection observer has failed to trigger within " + _TIMEOUT_ /1000 + " sec.");
346		
347		// reset the test condition
348		setTestResult(false);
349
350		virtual1_.event_goOnline();
351		
352		try {
353			this.wait(  );
354		} catch (InterruptedException e) {};
355		
356		if(! getTestResult())
357			fail("Reconnection observer has failed to trigger within " + _TIMEOUT_ /1000 + " sec.");
358	
359	}
360
361	public synchronized void testRetract() throws Exception {
362		
363		final ELActor provider = setUpActor(virtual1_);
364		final ELActor subscriber = setUpActor(virtual2_);
365		
366		// We define a closure to inform us the test succeeded
367		setUpSuccessTrigger(subscriber);
368		
369		subscriber.sync_event_eval(
370				NATParser.parse("DistributionTest#testRetract()",
371						"def messages := nil;" +
372						"def far := nil;" +
373						"deftype Service; \n" +
374						"when: Service discovered: { | ref | \n" +
375						"  far := ref; \n" +
376						"  when: ref disconnected: { \n" +
377						"    messages := retract: ref; \n" +
378						"    success(); \n" +
379						"  }; \n" +
380						"  success(); " +
381						"} \n;"));
382		
383		provider.sync_event_eval(
384				NATParser.parse("DistributionTest#testRetract()",
385						"deftype Service; \n" +
386						"export: (object: { def inc(num) { num + 1; }; }) as: Service"));
387		
388		virtual1_.event_goOnline();
389		virtual2_.event_goOnline();
390		
391		try {
392			this.wait( _TIMEOUT_ );
393		} catch (InterruptedException e) {};
394		
395		// reset the test condition
396		setTestResult(false);
397		
398		// Spawn a new thread to allow the disconnection of the provider to be in 
399		// parallel with the sending of messages by the subscriber
400		Thread sender = new Thread() {
401			public void run() {
402				try {
403					subscriber.sync_event_eval(
404							NATParser.parse("DistributionTest#testRetract()",
405									"1.to: 5 do: { | i | \n" +
406									"  far<-inc(i); \n" +
407									"}; \n" +
408									// Stop waiting after five messages were scheduled to ensure
409									// some messages may still be in the outbox
410									"success(); \n"+
411									"6.to: 10 do: { | i | \n" +
412									"  far<-inc(i); \n" +
413									"}; \n"));
414				} catch (InterpreterException e) {
415					e.printStackTrace();
416					fail("exception: " + e);
417				}
418			};
419		};
420
421		sender.start();
422		
423		// wait till some messages were sent
424		try {
425			this.wait( _TIMEOUT_ );
426		} catch (InterruptedException e) {};
427		
428		virtual1_.event_goOffline();
429		
430		// wait till disconnection event was processed
431		try {
432			this.wait( _TIMEOUT_ );
433		} catch (InterruptedException e) {};
434		
435		// ELActor must not be busy while retracting the messages
436		sender.join();
437		
438		ATObject messages = subscriber.sync_event_eval(
439				NATParser.parse("DistributionTest#testRetract()",
440						"messages"));
441		
442		
443	}
444	
445	public void notestSimple() {
446		try {
447			ELActor alice = setUpActor(virtual1_);
448			ELActor bob = setUpActor(virtual2_);
449			
450			alice.sync_event_eval(
451					NATParser.parse("CrossVMCommunicationTest#testSimple()", 
452							"deftype HelloWorld; \n" +
453							"whenever: HelloWorld discovered: { | ref | \n" +
454							"  ref <- hello(\"alice\"); \n" +
455							"}; \n"));
456			
457			bob.sync_event_eval(
458					NATParser.parse("CrossVMCommunicationTest#testSimple()", 
459							"deftype HelloWorld; \n" +
460							"def english := object: { \n" +
461							"  def hello( name ) { \"hello \" + name }; \n" +
462							"}; \n" +
463							"def spanish := object: { \n" +
464							"  def hello( name ) { \"hola \" + name }; \n" +
465							"}; \n" +
466							"export: english as: HelloWorld; \n" +
467							"export: spanish as: HelloWorld; \n"));
468			
469			alice.host_.event_goOnline();
470			bob.host_.event_goOnline();
471			
472			synchronized (this) {
473				try {
474					this.wait(10000);
475				} catch (InterruptedException e) {
476					e.printStackTrace();
477				}
478			}
479			
480		} catch (InterpreterException e) {
481			e.printStackTrace();
482		}	
483	}
484
485}