1 /* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.tradefed.command; 18 19 import static org.junit.Assert.*; 20 21 import com.android.ddmlib.IDevice; 22 import com.android.ddmlib.Log; 23 import com.android.tradefed.config.ConfigurationDescriptor; 24 import com.android.tradefed.config.DeviceConfigurationHolder; 25 import com.android.tradefed.config.IConfiguration; 26 import com.android.tradefed.config.IConfigurationFactory; 27 import com.android.tradefed.config.IDeviceConfiguration; 28 import com.android.tradefed.device.DeviceNotAvailableException; 29 import com.android.tradefed.device.DeviceSelectionOptions; 30 import com.android.tradefed.device.IDeviceManager; 31 import com.android.tradefed.device.ITestDevice; 32 import com.android.tradefed.device.MockDeviceManager; 33 import com.android.tradefed.device.StubDevice; 34 import com.android.tradefed.device.TestDeviceOptions; 35 import com.android.tradefed.device.TestDeviceState; 36 import com.android.tradefed.invoker.IInvocationContext; 37 import com.android.tradefed.invoker.IRescheduler; 38 import com.android.tradefed.invoker.ITestInvocation; 39 import com.android.tradefed.log.LogUtil.CLog; 40 import com.android.tradefed.result.ITestInvocationListener; 41 import com.android.tradefed.util.RunInterruptedException; 42 import com.android.tradefed.util.RunUtil; 43 import com.android.tradefed.util.keystore.IKeyStoreClient; 44 45 import com.google.common.util.concurrent.SettableFuture; 46 47 import org.easymock.EasyMock; 48 import org.junit.After; 49 import org.junit.Before; 50 import org.junit.Test; 51 import org.junit.runner.RunWith; 52 import org.junit.runners.JUnit4; 53 54 import java.util.ArrayList; 55 import java.util.List; 56 import java.util.concurrent.Future; 57 58 /** Longer running test for {@link CommandScheduler} */ 59 @RunWith(JUnit4.class) 60 public class CommandSchedulerFuncTest { 61 62 private static final String LOG_TAG = "CommandSchedulerFuncTest"; 63 private static final long WAIT_TIMEOUT_MS = 30 * 1000; 64 /** the {@link CommandScheduler} under test, with all dependencies mocked out */ 65 private CommandScheduler mCommandScheduler; 66 private MeasuredInvocation mMockTestInvoker; 67 private MockDeviceManager mMockDeviceManager; 68 private List<IDeviceConfiguration> mMockDeviceConfig; 69 private IConfiguration mSlowConfig; 70 private IConfiguration mFastConfig; 71 private IConfigurationFactory mMockConfigFactory; 72 private CommandOptions mCommandOptions; 73 private DeviceSelectionOptions mDeviceOptions; 74 private boolean mInterruptible = false; 75 private IDeviceConfiguration mMockConfig; 76 77 @Before setUp()78 public void setUp() throws Exception { 79 mDeviceOptions = new DeviceSelectionOptions(); 80 mMockDeviceConfig = new ArrayList<IDeviceConfiguration>(); 81 mMockConfig = new DeviceConfigurationHolder("device"); 82 mMockConfig.addSpecificConfig(mDeviceOptions); 83 mMockConfig.addSpecificConfig(new TestDeviceOptions()); 84 mMockDeviceConfig.add(mMockConfig); 85 86 mInterruptible = false; 87 mSlowConfig = EasyMock.createNiceMock(IConfiguration.class); 88 mFastConfig = EasyMock.createNiceMock(IConfiguration.class); 89 mMockDeviceManager = new MockDeviceManager(1); 90 mMockTestInvoker = new MeasuredInvocation(); 91 mMockConfigFactory = EasyMock.createMock(IConfigurationFactory.class); 92 mCommandOptions = new CommandOptions(); 93 mCommandOptions.setLoopMode(true); 94 mCommandOptions.setMinLoopTime(0); 95 EasyMock.expect(mSlowConfig.getCommandOptions()).andStubReturn(mCommandOptions); 96 EasyMock.expect(mSlowConfig.getTestInvocationListeners()) 97 .andStubReturn(new ArrayList<ITestInvocationListener>()); 98 EasyMock.expect(mFastConfig.getCommandOptions()).andStubReturn(mCommandOptions); 99 EasyMock.expect(mFastConfig.getTestInvocationListeners()) 100 .andStubReturn(new ArrayList<ITestInvocationListener>()); 101 EasyMock.expect(mSlowConfig.getDeviceRequirements()).andStubReturn( 102 new DeviceSelectionOptions()); 103 EasyMock.expect(mFastConfig.getDeviceRequirements()).andStubReturn( 104 new DeviceSelectionOptions()); 105 EasyMock.expect(mSlowConfig.getDeviceConfig()).andStubReturn(mMockDeviceConfig); 106 EasyMock.expect(mSlowConfig.getDeviceConfigByName(EasyMock.eq("device"))) 107 .andStubReturn(mMockConfig); 108 EasyMock.expect(mSlowConfig.getCommandLine()).andStubReturn(""); 109 EasyMock.expect(mFastConfig.getDeviceConfigByName(EasyMock.eq("device"))) 110 .andStubReturn(mMockConfig); 111 EasyMock.expect(mFastConfig.getDeviceConfig()).andStubReturn(mMockDeviceConfig); 112 EasyMock.expect(mFastConfig.getCommandLine()).andStubReturn(""); 113 EasyMock.expect(mSlowConfig.getConfigurationDescription()) 114 .andStubReturn(new ConfigurationDescriptor()); 115 EasyMock.expect(mFastConfig.getConfigurationDescription()) 116 .andStubReturn(new ConfigurationDescriptor()); 117 118 mCommandScheduler = 119 new CommandScheduler() { 120 @Override 121 ITestInvocation createRunInstance() { 122 return mMockTestInvoker; 123 } 124 125 @Override 126 protected IDeviceManager getDeviceManager() { 127 return mMockDeviceManager; 128 } 129 130 @Override 131 protected IConfigurationFactory getConfigFactory() { 132 if (mInterruptible) { 133 // simulate the invocation becoming interruptible 134 RunUtil.getDefault().allowInterrupt(true); 135 } 136 return mMockConfigFactory; 137 } 138 139 @Override 140 protected void initLogging() { 141 // ignore 142 } 143 144 @Override 145 protected void cleanUp() { 146 // ignore 147 } 148 }; 149 } 150 151 @After tearDown()152 public void tearDown() throws Exception { 153 if (mCommandScheduler != null) { 154 mCommandScheduler.shutdownOnEmpty(); 155 } 156 } 157 158 /** 159 * Test config priority scheduling. Verifies that configs are prioritized according to their 160 * total run time. 161 * 162 * <p>This test continually executes two configs in loop mode. One config executes quickly (ie 163 * "fast config"). The other config (ie "slow config") takes ~ 2 * fast config time to execute. 164 * 165 * <p>The run is stopped after the slow config is executed 20 times. At the end of the test, it 166 * is expected that "fast config" has executed roughly twice as much as the "slow config". 167 */ 168 @Test testRun_scheduling()169 public void testRun_scheduling() throws Exception { 170 String[] fastConfigArgs = new String[] {"fastConfig"}; 171 String[] slowConfigArgs = new String[] {"slowConfig"}; 172 List<String> nullArg = null; 173 EasyMock.expect( 174 mMockConfigFactory.createConfigurationFromArgs(EasyMock.aryEq(fastConfigArgs), 175 EasyMock.eq(nullArg), (IKeyStoreClient)EasyMock.anyObject())) 176 .andReturn(mFastConfig).anyTimes(); 177 EasyMock.expect( 178 mMockConfigFactory.createConfigurationFromArgs(EasyMock.aryEq(slowConfigArgs), 179 EasyMock.eq(nullArg), (IKeyStoreClient)EasyMock.anyObject())) 180 .andReturn(mSlowConfig).anyTimes(); 181 182 EasyMock.replay(mFastConfig, mSlowConfig, mMockConfigFactory); 183 mCommandScheduler.start(); 184 mCommandScheduler.addCommand(fastConfigArgs); 185 mCommandScheduler.addCommand(slowConfigArgs); 186 187 synchronized (mMockTestInvoker) { 188 mMockTestInvoker.wait(WAIT_TIMEOUT_MS); 189 } 190 mCommandScheduler.shutdown(); 191 mCommandScheduler.join(WAIT_TIMEOUT_MS); 192 193 Log.i(LOG_TAG, String.format("fast times %d slow times %d", 194 mMockTestInvoker.mFastCount, mMockTestInvoker.mSlowCount)); 195 // assert that fast config has executed roughly twice as much as slow config. Allow for 196 // some variance since the execution time of each config (governed via Thread.sleep) will 197 // not be 100% accurate 198 assertEquals(mMockTestInvoker.mSlowCount * 2, mMockTestInvoker.mFastCount, 5); 199 assertFalse(mMockTestInvoker.runInterrupted); 200 } 201 202 private class MeasuredInvocation implements ITestInvocation { 203 Integer mSlowCount = 0; 204 Integer mFastCount = 0; 205 Integer mSlowCountLimit = 40; 206 public boolean runInterrupted = false; 207 public boolean printedStop = false; 208 209 @Override invoke( IInvocationContext metadata, IConfiguration config, IRescheduler rescheduler, ITestInvocationListener... listeners)210 public void invoke( 211 IInvocationContext metadata, 212 IConfiguration config, 213 IRescheduler rescheduler, 214 ITestInvocationListener... listeners) 215 throws DeviceNotAvailableException { 216 try { 217 if (mInterruptible) { 218 // simulate the invocation becoming interruptible 219 RunUtil.getDefault().allowInterrupt(true); 220 } 221 if (config.equals(mSlowConfig)) { 222 // sleep for 2 * fast config time 223 RunUtil.getDefault().sleep(200); 224 synchronized (mSlowCount) { 225 mSlowCount++; 226 } 227 if (mSlowCount >= mSlowCountLimit) { 228 synchronized (this) { 229 notify(); 230 } 231 } 232 } else if (config.equals(mFastConfig)) { 233 RunUtil.getDefault().sleep(100); 234 synchronized (mFastCount) { 235 mFastCount++; 236 } 237 } else { 238 throw new IllegalArgumentException("unknown config"); 239 } 240 } catch (RunInterruptedException e) { 241 CLog.e(e); 242 // Yield right away if an exception occur due to an interrupt. 243 runInterrupted = true; 244 synchronized (this) { 245 notify(); 246 } 247 } 248 } 249 250 @Override notifyInvocationStopped()251 public void notifyInvocationStopped() { 252 printedStop = true; 253 } 254 } 255 256 /** Test that the Invocation is not interruptible even when Battery is low. */ 257 @Test testBatteryLowLevel()258 public void testBatteryLowLevel() throws Throwable { 259 ITestDevice mockDevice = EasyMock.createNiceMock(ITestDevice.class); 260 EasyMock.expect(mockDevice.getSerialNumber()).andReturn("serial").anyTimes(); 261 IDevice mockIDevice = new StubDevice("serial"); 262 EasyMock.expect(mockDevice.getIDevice()).andReturn(mockIDevice).anyTimes(); 263 EasyMock.expect(mockDevice.getDeviceState()).andReturn( 264 TestDeviceState.ONLINE).anyTimes(); 265 266 TestDeviceOptions testDeviceOptions= new TestDeviceOptions(); 267 testDeviceOptions.setCutoffBattery(20); 268 mMockConfig.addSpecificConfig(testDeviceOptions); 269 assertTrue(testDeviceOptions.getCutoffBattery() == 20); 270 EasyMock.expect(mSlowConfig.getDeviceOptions()).andReturn(testDeviceOptions).anyTimes(); 271 272 EasyMock.replay(mockDevice); 273 mMockDeviceManager.clearAllDevices(); 274 mMockDeviceManager.addDevice(mockDevice); 275 276 String[] slowConfigArgs = new String[] {"slowConfig"}; 277 List<String> nullArg = null; 278 EasyMock.expect( 279 mMockConfigFactory.createConfigurationFromArgs(EasyMock.aryEq(slowConfigArgs), 280 EasyMock.eq(nullArg), (IKeyStoreClient)EasyMock.anyObject())) 281 .andReturn(mSlowConfig).anyTimes(); 282 283 EasyMock.replay(mFastConfig, mSlowConfig, mMockConfigFactory); 284 mCommandScheduler.start(); 285 mCommandScheduler.addCommand(slowConfigArgs); 286 287 synchronized (mMockTestInvoker) { 288 mMockTestInvoker.wait(WAIT_TIMEOUT_MS); 289 } 290 291 mCommandScheduler.shutdown(); 292 mCommandScheduler.join(WAIT_TIMEOUT_MS); 293 assertFalse(mMockTestInvoker.runInterrupted); 294 // Notify was not sent to the invocation because it was not forced shutdown. 295 assertFalse(mMockTestInvoker.printedStop); 296 } 297 298 /** Test that the Invocation is interruptible when Battery is low. */ 299 @Test testBatteryLowLevel_interruptible()300 public void testBatteryLowLevel_interruptible() throws Throwable { 301 ITestDevice mockDevice = EasyMock.createNiceMock(ITestDevice.class); 302 EasyMock.expect(mockDevice.getSerialNumber()).andReturn("serial").anyTimes(); 303 IDevice mockIDevice = new StubDevice("serial") { 304 @Override 305 public Future<Integer> getBattery() { 306 SettableFuture<Integer> f = SettableFuture.create(); 307 f.set(10); 308 return f; 309 } 310 }; 311 312 EasyMock.expect(mockDevice.getIDevice()).andReturn(mockIDevice).anyTimes(); 313 EasyMock.expect(mockDevice.getDeviceState()).andReturn( 314 TestDeviceState.ONLINE).anyTimes(); 315 316 TestDeviceOptions testDeviceOptions= new TestDeviceOptions(); 317 testDeviceOptions.setCutoffBattery(20); 318 mMockConfig.addSpecificConfig(testDeviceOptions); 319 EasyMock.expect(mSlowConfig.getDeviceOptions()).andReturn(testDeviceOptions).anyTimes(); 320 321 EasyMock.replay(mockDevice); 322 mMockDeviceManager.clearAllDevices(); 323 mMockDeviceManager.addDevice(mockDevice); 324 325 String[] slowConfigArgs = new String[] {"slowConfig"}; 326 List<String> nullArg = null; 327 EasyMock.expect( 328 mMockConfigFactory.createConfigurationFromArgs(EasyMock.aryEq(slowConfigArgs), 329 EasyMock.eq(nullArg), (IKeyStoreClient)EasyMock.anyObject())) 330 .andReturn(mSlowConfig).anyTimes(); 331 332 EasyMock.replay(mFastConfig, mSlowConfig, mMockConfigFactory); 333 mCommandScheduler.start(); 334 mInterruptible = true; 335 mCommandScheduler.addCommand(slowConfigArgs); 336 337 synchronized (mMockTestInvoker) { 338 mMockTestInvoker.wait(WAIT_TIMEOUT_MS); 339 } 340 341 mCommandScheduler.shutdown(); 342 mCommandScheduler.join(WAIT_TIMEOUT_MS); 343 assertTrue(mMockTestInvoker.runInterrupted); 344 } 345 346 /** 347 * Test that the Invocation is interrupted by the shutdownHard and finishes with an 348 * interruption. {@link CommandScheduler#shutdownHard()} 349 */ 350 @Test testShutdown_interruptible()351 public void testShutdown_interruptible() throws Throwable { 352 String[] slowConfigArgs = new String[] {"slowConfig"}; 353 List<String> nullArg = null; 354 EasyMock.expect( 355 mMockConfigFactory.createConfigurationFromArgs(EasyMock.aryEq(slowConfigArgs), 356 EasyMock.eq(nullArg), (IKeyStoreClient)EasyMock.anyObject())) 357 .andReturn(mSlowConfig).anyTimes(); 358 359 EasyMock.replay(mFastConfig, mSlowConfig, mMockConfigFactory); 360 mCommandScheduler.start(); 361 mInterruptible = true; 362 mCommandScheduler.addCommand(slowConfigArgs); 363 364 Thread test = new Thread(new Runnable() { 365 @Override 366 public void run() { 367 RunUtil.getDefault().sleep(500); 368 mCommandScheduler.shutdownHard(); 369 } 370 }); 371 test.setName("CommandSchedulerFuncTest#testShutdown_interruptible"); 372 test.start(); 373 synchronized (mMockTestInvoker) { 374 mMockTestInvoker.wait(WAIT_TIMEOUT_MS); 375 } 376 test.join(); 377 mCommandScheduler.join(WAIT_TIMEOUT_MS); 378 // Was interrupted during execution. 379 assertTrue(mMockTestInvoker.runInterrupted); 380 // Notify was sent to the invocation 381 assertTrue(mMockTestInvoker.printedStop); 382 } 383 384 /** 385 * Test that the Invocation is not interrupted by shutdownHard. Invocation terminate then 386 * scheduler finishes. {@link CommandScheduler#shutdownHard()} 387 */ 388 @Test testShutdown_notInterruptible()389 public void testShutdown_notInterruptible() throws Throwable { 390 final LongInvocation li = new LongInvocation(5); 391 mCommandOptions.setLoopMode(false); 392 mCommandScheduler = 393 new CommandScheduler() { 394 @Override 395 ITestInvocation createRunInstance() { 396 return li; 397 } 398 399 @Override 400 protected IDeviceManager getDeviceManager() { 401 return mMockDeviceManager; 402 } 403 404 @Override 405 protected IConfigurationFactory getConfigFactory() { 406 if (mInterruptible) { 407 // simulate the invocation becoming interruptible 408 RunUtil.getDefault().allowInterrupt(true); 409 } 410 return mMockConfigFactory; 411 } 412 413 @Override 414 protected void initLogging() { 415 // ignore 416 } 417 418 @Override 419 protected void cleanUp() { 420 // ignore 421 } 422 423 @Override 424 public long getShutdownTimeout() { 425 return 30000; 426 } 427 }; 428 String[] slowConfigArgs = new String[] {"slowConfig"}; 429 List<String> nullArg = null; 430 EasyMock.expect( 431 mMockConfigFactory.createConfigurationFromArgs(EasyMock.aryEq(slowConfigArgs), 432 EasyMock.eq(nullArg), (IKeyStoreClient)EasyMock.anyObject())) 433 .andReturn(mSlowConfig).anyTimes(); 434 435 EasyMock.replay(mFastConfig, mSlowConfig, mMockConfigFactory); 436 mCommandScheduler.start(); 437 mInterruptible = false; 438 mCommandScheduler.addCommand(slowConfigArgs); 439 440 Thread shutdownThread = new Thread(new Runnable() { 441 @Override 442 public void run() { 443 RunUtil.getDefault().sleep(1000); 444 mCommandScheduler.shutdownHard(); 445 } 446 }); 447 shutdownThread.setName("CommandSchedulerFuncTest#testShutdown_notInterruptible"); 448 shutdownThread.start(); 449 synchronized (li) { 450 // Invocation will finish first because shorter than shutdownHard final timeout 451 li.wait(WAIT_TIMEOUT_MS); 452 } 453 shutdownThread.join(); 454 mCommandScheduler.join(WAIT_TIMEOUT_MS); 455 // Stop but was not interrupted 456 assertFalse(mMockTestInvoker.runInterrupted); 457 // Notify was not sent to the invocation because it was not interrupted. 458 assertFalse(mMockTestInvoker.printedStop); 459 } 460 461 private class LongInvocation implements ITestInvocation { 462 public boolean runInterrupted = false; 463 private int mIteration = 15; 464 LongInvocation(int iteration)465 public LongInvocation(int iteration) { 466 mIteration = iteration; 467 } 468 469 @Override invoke( IInvocationContext metadata, IConfiguration config, IRescheduler rescheduler, ITestInvocationListener... listeners)470 public void invoke( 471 IInvocationContext metadata, 472 IConfiguration config, 473 IRescheduler rescheduler, 474 ITestInvocationListener... listeners) 475 throws DeviceNotAvailableException { 476 try { 477 if (mInterruptible) { 478 // simulate the invocation becoming interruptible 479 RunUtil.getDefault().allowInterrupt(true); 480 } 481 for (int i = 0; i < mIteration; i++) { 482 RunUtil.getDefault().sleep(2000); 483 } 484 synchronized (this) { 485 notify(); 486 } 487 } catch (RunInterruptedException e) { 488 CLog.e(e); 489 // Yield right away if an exception occur due to an interrupt. 490 runInterrupted = true; 491 synchronized (this) { 492 notify(); 493 } 494 } 495 } 496 } 497 498 /** 499 * Test that the Invocation is interrupted by {@link CommandScheduler#shutdownHard()} but only 500 * after the shutdown timeout is expired because the invocation was uninterruptible so we only 501 * allow for so much time before shutting down. 502 */ 503 @Test testShutdown_notInterruptible_timeout()504 public void testShutdown_notInterruptible_timeout() throws Throwable { 505 final LongInvocation li = new LongInvocation(15); 506 mCommandOptions.setLoopMode(false); 507 mCommandScheduler = 508 new CommandScheduler() { 509 @Override 510 ITestInvocation createRunInstance() { 511 return li; 512 } 513 514 @Override 515 protected IDeviceManager getDeviceManager() { 516 return mMockDeviceManager; 517 } 518 519 @Override 520 protected IConfigurationFactory getConfigFactory() { 521 if (mInterruptible) { 522 // simulate the invocation becoming interruptible 523 RunUtil.getDefault().allowInterrupt(true); 524 } 525 return mMockConfigFactory; 526 } 527 528 @Override 529 protected void initLogging() { 530 // ignore 531 } 532 533 @Override 534 protected void cleanUp() { 535 // ignore 536 } 537 538 @Override 539 public long getShutdownTimeout() { 540 return 5000; 541 } 542 }; 543 String[] slowConfigArgs = new String[] {"slowConfig"}; 544 List<String> nullArg = null; 545 EasyMock.expect( 546 mMockConfigFactory.createConfigurationFromArgs(EasyMock.aryEq(slowConfigArgs), 547 EasyMock.eq(nullArg), (IKeyStoreClient)EasyMock.anyObject())) 548 .andReturn(mSlowConfig).anyTimes(); 549 550 EasyMock.replay(mFastConfig, mSlowConfig, mMockConfigFactory); 551 mCommandScheduler.start(); 552 mInterruptible = false; 553 mCommandScheduler.addCommand(slowConfigArgs); 554 555 Thread shutdownThread = new Thread(new Runnable() { 556 @Override 557 public void run() { 558 RunUtil.getDefault().sleep(1000); 559 mCommandScheduler.shutdownHard(); 560 } 561 }); 562 shutdownThread.setName("CommandSchedulerFuncTest#testShutdown_notInterruptible_timeout"); 563 shutdownThread.start(); 564 synchronized (li) { 565 // Setting a timeout longer than the shutdown timeout. 566 li.wait(WAIT_TIMEOUT_MS); 567 } 568 shutdownThread.join(); 569 mCommandScheduler.join(WAIT_TIMEOUT_MS); 570 // Stop and was interrupted by timeout of shutdownHard() 571 assertTrue(li.runInterrupted); 572 } 573 574 /** Test that if the invocation run time goes over the timeout, it will be forced stopped. */ 575 @Test testShutdown_invocation_timeout()576 public void testShutdown_invocation_timeout() throws Throwable { 577 final LongInvocation li = new LongInvocation(2); 578 mCommandOptions.setLoopMode(false); 579 mCommandOptions.setInvocationTimeout(500l); 580 mCommandScheduler = 581 new CommandScheduler() { 582 @Override 583 ITestInvocation createRunInstance() { 584 return li; 585 } 586 587 @Override 588 protected IDeviceManager getDeviceManager() { 589 return mMockDeviceManager; 590 } 591 592 @Override 593 protected IConfigurationFactory getConfigFactory() { 594 return mMockConfigFactory; 595 } 596 597 @Override 598 protected void initLogging() { 599 // ignore 600 } 601 602 @Override 603 protected void cleanUp() { 604 // ignore 605 } 606 }; 607 String[] slowConfigArgs = new String[] {"slowConfig"}; 608 List<String> nullArg = null; 609 EasyMock.expect( 610 mMockConfigFactory.createConfigurationFromArgs(EasyMock.aryEq(slowConfigArgs), 611 EasyMock.eq(nullArg), (IKeyStoreClient)EasyMock.anyObject())) 612 .andReturn(mSlowConfig).anyTimes(); 613 614 EasyMock.replay(mFastConfig, mSlowConfig, mMockConfigFactory); 615 mCommandScheduler.start(); 616 mInterruptible = true; 617 mCommandScheduler.addCommand(slowConfigArgs); 618 mCommandScheduler.join(mCommandOptions.getInvocationTimeout() * 2); 619 // Stop and was interrupted by timeout 620 assertTrue(li.runInterrupted); 621 } 622 } 623