Home | History | Annotate | Download | only in command
      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
     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
    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
    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
    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
    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
    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
    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
    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
    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 
    465         public LongInvocation(int iteration) {
    466             mIteration = iteration;
    467         }
    468 
    469         @Override
    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
    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
    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