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