PageRenderTime 59ms CodeModel.GetById 17ms app.highlight 36ms RepoModel.GetById 1ms app.codeStats 0ms

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

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