/interpreter/tags/at_build150307/test/edu/vub/at/actors/natives/DistributionTest.java
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}