PageRenderTime 31ms CodeModel.GetById 2ms app.highlight 24ms RepoModel.GetById 1ms app.codeStats 0ms

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

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