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