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