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 package com.android.tradefed.command;
17 
18 import com.android.ddmlib.IDevice;
19 import com.android.tradefed.command.CommandFileParser.CommandLine;
20 import com.android.tradefed.command.CommandScheduler.CommandTracker;
21 import com.android.tradefed.command.CommandScheduler.CommandTrackerIdComparator;
22 import com.android.tradefed.command.ICommandScheduler.IScheduledInvocationListener;
23 import com.android.tradefed.config.ConfigurationDescriptor;
24 import com.android.tradefed.config.ConfigurationException;
25 import com.android.tradefed.config.ConfigurationFactory;
26 import com.android.tradefed.config.DeviceConfigurationHolder;
27 import com.android.tradefed.config.IConfiguration;
28 import com.android.tradefed.config.IConfigurationFactory;
29 import com.android.tradefed.config.IDeviceConfiguration;
30 import com.android.tradefed.config.IGlobalConfiguration;
31 import com.android.tradefed.config.OptionSetter;
32 import com.android.tradefed.device.DeviceNotAvailableException;
33 import com.android.tradefed.device.DeviceSelectionOptions;
34 import com.android.tradefed.device.FreeDeviceState;
35 import com.android.tradefed.device.IDeviceManager;
36 import com.android.tradefed.device.ITestDevice;
37 import com.android.tradefed.device.ITestDevice.RecoveryMode;
38 import com.android.tradefed.device.MockDeviceManager;
39 import com.android.tradefed.device.NoDeviceException;
40 import com.android.tradefed.device.StubDevice;
41 import com.android.tradefed.device.TcpDevice;
42 import com.android.tradefed.device.TestDeviceState;
43 import com.android.tradefed.invoker.IInvocationContext;
44 import com.android.tradefed.invoker.IRescheduler;
45 import com.android.tradefed.invoker.ITestInvocation;
46 import com.android.tradefed.invoker.InvocationContext;
47 import com.android.tradefed.log.ILogRegistry.EventType;
48 import com.android.tradefed.log.ITerribleFailureHandler;
49 import com.android.tradefed.log.LogUtil.CLog;
50 import com.android.tradefed.result.ITestInvocationListener;
51 import com.android.tradefed.util.FileUtil;
52 import com.android.tradefed.util.RunUtil;
53 import com.android.tradefed.util.keystore.DryRunKeyStore;
54 import com.android.tradefed.util.keystore.IKeyStoreClient;
55 
56 import junit.framework.TestCase;
57 
58 import org.easymock.EasyMock;
59 import org.easymock.IAnswer;
60 import org.json.JSONArray;
61 import org.json.JSONException;
62 import org.junit.Assert;
63 
64 import java.io.ByteArrayOutputStream;
65 import java.io.File;
66 import java.io.OutputStream;
67 import java.io.PrintWriter;
68 import java.lang.Thread.UncaughtExceptionHandler;
69 import java.util.ArrayList;
70 import java.util.Arrays;
71 import java.util.Collections;
72 import java.util.List;
73 import java.util.Map;
74 
75 
76 /**
77  * Unit tests for {@link CommandScheduler}.
78  */
79 public class CommandSchedulerTest extends TestCase {
80 
81     private static final long SHORT_WAIT_MS = 100L;
82 
83     private CommandScheduler mScheduler;
84     private ITestInvocation mMockInvocation;
85     private MockDeviceManager mMockManager;
86     private IConfigurationFactory mMockConfigFactory;
87     private IConfiguration mMockConfiguration;
88     private CommandOptions mCommandOptions;
89     private DeviceSelectionOptions mDeviceOptions;
90     private CommandFileParser mMockCmdFileParser;
91     private List<IDeviceConfiguration> mMockDeviceConfig;
92     private ConfigurationDescriptor mMockConfigDescriptor;
93     private IInvocationContext mContext;
94 
95     class TestableCommandScheduler extends CommandScheduler {
96 
97         @Override
createRunInstance()98         ITestInvocation createRunInstance() {
99             return mMockInvocation;
100         }
101 
102         @Override
getDeviceManager()103         protected IDeviceManager getDeviceManager() {
104             return mMockManager;
105         }
106 
107         @Override
getConfigFactory()108         protected IConfigurationFactory getConfigFactory() {
109             return mMockConfigFactory;
110         }
111 
112         @Override
createInvocationContext()113         protected IInvocationContext createInvocationContext() {
114             return mContext;
115         }
116 
117         @Override
initLogging()118         protected void initLogging() {
119             // ignore
120         }
121 
122         @Override
cleanUp()123         protected void cleanUp() {
124             // ignore
125         }
126 
127         @Override
logEvent(EventType event, Map<String, String> args)128         void logEvent(EventType event, Map<String, String> args) {
129             // ignore
130         }
131 
132         @Override
checkInvocations()133         void checkInvocations() {
134             // ignore
135         }
136 
137         @Override
createCommandFileParser()138         CommandFileParser createCommandFileParser() {
139             return mMockCmdFileParser;
140         }
141     }
142 
143     /**
144      * {@inheritDoc}
145      */
146     @Override
setUp()147     protected void setUp() throws Exception {
148         super.setUp();
149 
150         mMockInvocation = EasyMock.createMock(ITestInvocation.class);
151         mMockManager = new MockDeviceManager(0);
152         mMockConfigFactory = EasyMock.createMock(IConfigurationFactory.class);
153         mMockConfiguration = EasyMock.createMock(IConfiguration.class);
154         mCommandOptions = new CommandOptions();
155         mDeviceOptions = new DeviceSelectionOptions();
156         mMockDeviceConfig = new ArrayList<IDeviceConfiguration>();
157         mMockConfigDescriptor = new ConfigurationDescriptor();
158         mContext = new InvocationContext();
159 
160         mScheduler = new TestableCommandScheduler();
161         // not starting the CommandScheduler yet because test methods need to setup mocks first
162     }
163 
164     @Override
tearDown()165     protected void tearDown() throws Exception {
166         if (mScheduler != null) {
167             mScheduler.shutdown();
168         }
169         super.tearDown();
170     }
171 
172     /**
173      * Switch all mock objects to replay mode
174      */
replayMocks(Object... additionalMocks)175     private void replayMocks(Object... additionalMocks) {
176         EasyMock.replay(mMockConfigFactory, mMockConfiguration, mMockInvocation);
177         for (Object mock : additionalMocks) {
178             EasyMock.replay(mock);
179         }
180     }
181 
182     /**
183      * Verify all mock objects
184      */
verifyMocks(Object... additionalMocks)185     private void verifyMocks(Object... additionalMocks) {
186         EasyMock.verify(mMockConfigFactory, mMockConfiguration, mMockInvocation);
187         for (Object mock : additionalMocks) {
188             EasyMock.verify(mock);
189         }
190         mMockManager.assertDevicesFreed();
191     }
192 
193     /**
194      * Test {@link CommandScheduler#run()} when no configs have been added
195      */
testRun_empty()196     public void testRun_empty() throws InterruptedException {
197         mMockManager.setNumDevices(1);
198         replayMocks();
199         mScheduler.start();
200         while (!mScheduler.isAlive()) {
201             Thread.sleep(10);
202         }
203         mScheduler.shutdown();
204         // expect run not to block
205         mScheduler.join();
206         verifyMocks();
207     }
208 
209     /**
210      * Test {@link CommandScheduler#addCommand(String[])} when help mode is specified
211      */
testAddConfig_configHelp()212     public void testAddConfig_configHelp() throws ConfigurationException {
213         String[] args = new String[] {};
214         mCommandOptions.setHelpMode(true);
215         setCreateConfigExpectations(args, 1);
216         // expect
217         mMockConfigFactory.printHelpForConfig(EasyMock.aryEq(args), EasyMock.eq(true),
218                 EasyMock.eq(System.out));
219         replayMocks();
220         mScheduler.start();
221         mScheduler.addCommand(args);
222         verifyMocks();
223     }
224 
225     /**
226      * Test {@link CommandScheduler#addCommand(String[])} when json help mode is specified
227      */
testAddConfig_configJsonHelp()228     public void testAddConfig_configJsonHelp() throws ConfigurationException, JSONException {
229         String[] args = new String[] {};
230         mCommandOptions.setJsonHelpMode(true);
231         setCreateConfigExpectations(args, 1);
232         // expect
233         EasyMock.expect(mMockConfiguration.getJsonCommandUsage()).andReturn(new JSONArray());
234         replayMocks();
235         mScheduler.start();
236         mScheduler.addCommand(args);
237         verifyMocks();
238     }
239 
240     /**
241      * Test {@link CommandScheduler#run()} when one config has been added
242      */
testRun_oneConfig()243     public void testRun_oneConfig() throws Throwable {
244         String[] args = new String[] {};
245         mMockManager.setNumDevices(2);
246         setCreateConfigExpectations(args, 1);
247         setExpectedInvokeCalls(1);
248         mMockConfiguration.validateOptions();
249         replayMocks();
250         mScheduler.start();
251         mScheduler.addCommand(args);
252         mScheduler.shutdownOnEmpty();
253         mScheduler.join();
254         verifyMocks();
255     }
256 
257     /**
258      * Test {@link CommandScheduler#removeAllCommands()} for idle case, where command is waiting for
259      * device.
260      */
testRemoveAllCommands()261     public void testRemoveAllCommands() throws Throwable {
262         String[] args = new String[] {};
263         mMockManager.setNumDevices(0);
264         setCreateConfigExpectations(args, 1);
265         mMockConfiguration.validateOptions();
266         replayMocks();
267         mScheduler.start();
268         mScheduler.addCommand(args);
269         assertEquals(1, mScheduler.getAllCommandsSize());
270         mScheduler.removeAllCommands();
271         assertEquals(0, mScheduler.getAllCommandsSize());
272         verifyMocks();
273     }
274 
275     /**
276      * Test {@link CommandScheduler#run()} when one config has been added in dry-run mode
277      */
testRun_dryRun()278     public void testRun_dryRun() throws Throwable {
279         String[] dryRunArgs = new String[] {"--dry-run"};
280         mCommandOptions.setDryRunMode(true);
281         mMockManager.setNumDevices(2);
282         setCreateConfigExpectations(dryRunArgs, 1);
283 
284         // add a second command, to verify the first dry-run command did not get added
285         String[] args2 = new String[] {};
286         setCreateConfigExpectations(args2, 1);
287         setExpectedInvokeCalls(1);
288         mMockConfiguration.validateOptions();
289         EasyMock.expectLastCall().times(2);
290 
291         replayMocks();
292         mScheduler.start();
293         assertFalse(mScheduler.addCommand(dryRunArgs));
294         // the same config object is being used, so clear its state
295         mCommandOptions.setDryRunMode(false);
296         assertTrue(mScheduler.addCommand(args2));
297         mScheduler.shutdownOnEmpty();
298         mScheduler.join();
299         verifyMocks();
300     }
301 
302     /**
303      * Test {@link CommandScheduler#run()} when one config has been added in noisy-dry-run or
304      * dry-run mode the keystore is properly faked by a {@link DryRunKeyStore}.
305      */
testRun_dryRun_keystore()306     public void testRun_dryRun_keystore() throws Throwable {
307         mScheduler =
308                 new TestableCommandScheduler() {
309                     @Override
310                     protected IConfigurationFactory getConfigFactory() {
311                         // Use the real factory for that loading test.
312                         return ConfigurationFactory.getInstance();
313                     }
314                 };
315         String[] dryRunArgs =
316                 new String[] {"empty", "--noisy-dry-run", "--min-loop-time", "USE_KEYSTORE@fake"};
317         mMockManager.setNumDevices(2);
318         //setCreateConfigExpectations(dryRunArgs, 1);
319 
320         replayMocks();
321         mScheduler.start();
322         assertFalse(mScheduler.addCommand(dryRunArgs));
323         mScheduler.shutdownOnEmpty();
324         mScheduler.join();
325         verifyMocks();
326     }
327 
328     /**
329      * Test simple case for
330      * {@link CommandScheduler#execCommand(IScheduledInvocationListener, ITestDevice, String[])}
331      */
332     @SuppressWarnings("unchecked")
testExecCommand()333     public void testExecCommand() throws Throwable {
334         String[] args = new String[] {
335             "foo"
336         };
337         setCreateConfigExpectations(args, 1);
338         setExpectedInvokeCalls(1);
339         mMockConfiguration.validateOptions();
340         IDevice mockIDevice = EasyMock.createMock(IDevice.class);
341         ITestDevice mockDevice = EasyMock.createMock(ITestDevice.class);
342         EasyMock.expect(mockDevice.getSerialNumber()).andStubReturn("serial");
343         EasyMock.expect(mockDevice.getDeviceState()).andStubReturn(TestDeviceState.ONLINE);
344         mockDevice.setRecoveryMode(EasyMock.eq(RecoveryMode.AVAILABLE));
345         EasyMock.expect(mockDevice.getIDevice()).andStubReturn(mockIDevice);
346         IScheduledInvocationListener mockListener = EasyMock
347                 .createMock(IScheduledInvocationListener.class);
348         mockListener.invocationInitiated((IInvocationContext) EasyMock.anyObject());
349         mockListener.invocationComplete((IInvocationContext)EasyMock.anyObject(),
350                 (Map<ITestDevice, FreeDeviceState>)EasyMock.anyObject());
351         EasyMock.expect(mockDevice.waitForDeviceShell(EasyMock.anyLong())).andReturn(true);
352         replayMocks(mockDevice, mockListener);
353         mScheduler.start();
354         mScheduler.execCommand(mockListener, mockDevice, args);
355         mScheduler.shutdownOnEmpty();
356         mScheduler.join(2*1000);
357         verifyMocks(mockListener);
358     }
359 
360     /**
361      * Sets the number of expected
362      * {@link ITestInvocation#invoke(IInvocationContext, IConfiguration, IRescheduler,
363      *      ITestInvocationListener[])} calls
364      *
365      * @param times
366      */
setExpectedInvokeCalls(int times)367     private void setExpectedInvokeCalls(int times) throws Throwable {
368         mMockInvocation.invoke((IInvocationContext)EasyMock.anyObject(),
369                 (IConfiguration)EasyMock.anyObject(), (IRescheduler)EasyMock.anyObject(),
370                 (ITestInvocationListener)EasyMock.anyObject());
371         EasyMock.expectLastCall().times(times);
372     }
373 
374     /**
375      * Sets up a object that will notify when the expected number of
376      * {@link ITestInvocation#invoke(IInvocationContext, IConfiguration, IRescheduler,
377      *      ITestInvocationListener[])} calls occurs
378      *
379      * @param times
380      */
waitForExpectedInvokeCalls(final int times)381     private Object waitForExpectedInvokeCalls(final int times) throws Throwable {
382         IAnswer<Object> blockResult = new IAnswer<Object>() {
383             private int mCalls = 0;
384             @Override
385             public Object answer() throws Throwable {
386                 synchronized(this) {
387                     mCalls++;
388                     if (times == mCalls) {
389                         notifyAll();
390                     }
391                 }
392                 return null;
393             }
394         };
395         mMockInvocation.invoke((IInvocationContext)EasyMock.anyObject(),
396                 (IConfiguration)EasyMock.anyObject(), (IRescheduler)EasyMock.anyObject(),
397                 (ITestInvocationListener)EasyMock.anyObject());
398         EasyMock.expectLastCall().andAnswer(blockResult);
399         EasyMock.expectLastCall().andAnswer(blockResult);
400         return blockResult;
401     }
402 
403     /**
404      * Test {@link CommandScheduler#run()} when one config has been added in a loop
405      */
testRun_oneConfigLoop()406     public void testRun_oneConfigLoop() throws Throwable {
407         String[] args = new String[] {};
408         // track if exception occurs on scheduler thread
409         UncaughtExceptionHandler defaultHandler = Thread.getDefaultUncaughtExceptionHandler();
410         try {
411             ExceptionTracker tracker = new ExceptionTracker();
412             Thread.setDefaultUncaughtExceptionHandler(tracker);
413             mMockManager.setNumDevices(1);
414             // config should only be created three times
415             setCreateConfigExpectations(args, 3);
416             mCommandOptions.setLoopMode(true);
417             mCommandOptions.setMinLoopTime(50);
418             Object notifier = waitForExpectedInvokeCalls(2);
419             mMockConfiguration.validateOptions();
420             replayMocks();
421             mScheduler.start();
422             mScheduler.addCommand(args);
423             synchronized (notifier) {
424                 notifier.wait(1 * 1000);
425             }
426             mScheduler.shutdown();
427             mScheduler.join();
428             // Wait a little for device to be released.
429             RunUtil.getDefault().sleep(SHORT_WAIT_MS);
430             verifyMocks();
431             assertNull("exception occurred on background thread!", tracker.mThrowable);
432         } finally {
433             Thread.setDefaultUncaughtExceptionHandler(defaultHandler);
434         }
435     }
436 
437     class ExceptionTracker implements UncaughtExceptionHandler {
438 
439         private Throwable mThrowable = null;
440 
441         /**
442          * {@inheritDoc}
443          */
444         @Override
uncaughtException(Thread t, Throwable e)445         public void uncaughtException(Thread t, Throwable e) {
446             e.printStackTrace();
447             mThrowable  = e;
448         }
449     }
450 
451     /**
452      * Verify that scheduler goes into shutdown mode when a {@link FatalHostError} is thrown.
453      */
testRun_fatalError()454     public void testRun_fatalError() throws Throwable {
455         mMockInvocation.invoke((IInvocationContext)EasyMock.anyObject(),
456                 (IConfiguration)EasyMock.anyObject(), (IRescheduler)EasyMock.anyObject(),
457                 (ITestInvocationListener)EasyMock.anyObject());
458         EasyMock.expectLastCall().andThrow(new FatalHostError("error"));
459         // set up a mock global config and wtfhandler to handle CLog.wtf when FatalHostError occurs
460         IGlobalConfiguration mockGc = EasyMock.createMock(IGlobalConfiguration.class);
461         CLog.setGlobalConfigInstance(mockGc);
462         try {
463             ITerribleFailureHandler mockWtf = EasyMock.createMock(ITerribleFailureHandler.class);
464             EasyMock.expect(mockGc.getWtfHandler()).andReturn(mockWtf).anyTimes();
465             EasyMock.expect(mockWtf.onTerribleFailure((String)EasyMock.anyObject(),
466                     (Throwable)EasyMock.anyObject())).andReturn(Boolean.TRUE);
467             String[] args = new String[] {};
468             mMockManager.setNumDevices(2);
469             setCreateConfigExpectations(args, 1);
470             mMockConfiguration.validateOptions();
471             replayMocks(mockGc, mockWtf);
472             mScheduler.start();
473             mScheduler.addCommand(args);
474             // no need to call shutdown explicitly - scheduler should shutdown by itself
475             mScheduler.join(2 * 1000);
476             // We don't verify the mockManager for this test since after failure, the device might
477             // not have time to go back to list before shutdown on scheduler.
478             EasyMock.verify(
479                     mMockConfigFactory, mMockConfiguration, mMockInvocation, mockGc, mockWtf);
480         } finally {
481             // reset global config to null, which means 'not overloaded/use default'
482             CLog.setGlobalConfigInstance(null);
483         }
484     }
485 
486     /**
487      * Test{@link CommandScheduler#run()} when config is matched to a specific device serial number
488      * <p/>
489      * Adds two configs to run, and verify they both run on one device
490      */
testRun_configSerial()491     public void testRun_configSerial() throws Throwable {
492         String[] args = new String[] {};
493         mMockManager.setNumDevices(2);
494         setCreateConfigExpectations(args, 2);
495         // allocate and free a device to get its serial
496         ITestDevice dev = mMockManager.allocateDevice();
497         mDeviceOptions.addSerial(dev.getSerialNumber());
498         setExpectedInvokeCalls(1);
499         mMockConfiguration.validateOptions();
500         mMockConfiguration.validateOptions();
501         replayMocks();
502         mScheduler.start();
503         mScheduler.addCommand(args);
504         mScheduler.addCommand(args);
505         mMockManager.freeDevice(dev, FreeDeviceState.AVAILABLE);
506 
507         mScheduler.shutdownOnEmpty();
508         mScheduler.join();
509         verifyMocks();
510     }
511 
512     /**
513      * Test{@link CommandScheduler#run()} when config is matched to a exclude specific device serial
514      * number.
515      * <p/>
516      * Adds two configs to run, and verify they both run on the other device
517      */
testRun_configExcludeSerial()518     public void testRun_configExcludeSerial() throws Throwable {
519         String[] args = new String[] {};
520         mMockManager.setNumDevices(2);
521         setCreateConfigExpectations(args, 2);
522         // allocate and free a device to get its serial
523         ITestDevice dev = mMockManager.allocateDevice();
524         mDeviceOptions.addExcludeSerial(dev.getSerialNumber());
525         ITestDevice expectedDevice = mMockManager.allocateDevice();
526         setExpectedInvokeCalls(1);
527         mMockConfiguration.validateOptions();
528         mMockConfiguration.validateOptions();
529         replayMocks();
530         mScheduler.start();
531         mScheduler.addCommand(args);
532         mScheduler.addCommand(args);
533         mMockManager.freeDevice(dev, FreeDeviceState.AVAILABLE);
534         mMockManager.freeDevice(expectedDevice, FreeDeviceState.AVAILABLE);
535         mScheduler.shutdownOnEmpty();
536         mScheduler.join();
537         verifyMocks();
538     }
539 
540     /**
541      * Test {@link CommandScheduler#run()} when one config has been rescheduled
542      */
testRun_rescheduled()543     public void testRun_rescheduled() throws Throwable {
544         String[] args = new String[] {};
545         mMockManager.setNumDevices(2);
546         setCreateConfigExpectations(args, 1);
547         mMockConfiguration.validateOptions();
548         final IConfiguration rescheduledConfig = EasyMock.createMock(IConfiguration.class);
549         EasyMock.expect(rescheduledConfig.getCommandOptions()).andStubReturn(mCommandOptions);
550         EasyMock.expect(rescheduledConfig.getDeviceRequirements()).andStubReturn(
551                 mDeviceOptions);
552         EasyMock.expect(rescheduledConfig.getDeviceConfig()).andStubReturn(mMockDeviceConfig);
553         EasyMock.expect(rescheduledConfig.getCommandLine()).andStubReturn("");
554         EasyMock.expect(rescheduledConfig.getConfigurationDescription())
555             .andStubReturn(mMockConfigDescriptor);
556 
557         // an ITestInvocationn#invoke response for calling reschedule
558         IAnswer<Object> rescheduleAndThrowAnswer = new IAnswer<Object>() {
559             @Override
560             public Object answer() throws Throwable {
561                 IRescheduler rescheduler =  (IRescheduler) EasyMock.getCurrentArguments()[2];
562                 rescheduler.scheduleConfig(rescheduledConfig);
563                 throw new DeviceNotAvailableException("not avail", "fakeserial");
564             }
565         };
566 
567         mMockInvocation.invoke(EasyMock.<IInvocationContext>anyObject(),
568                 EasyMock.<IConfiguration>anyObject(), EasyMock.<IRescheduler>anyObject(),
569                 EasyMock.<ITestInvocationListener>anyObject());
570         EasyMock.expectLastCall().andAnswer(rescheduleAndThrowAnswer);
571 
572         // expect one more success call
573         setExpectedInvokeCalls(1);
574 
575         replayMocks(rescheduledConfig);
576         mScheduler.start();
577         mScheduler.addCommand(args);
578         mScheduler.shutdownOnEmpty();
579         mScheduler.join();
580 
581         EasyMock.verify(mMockConfigFactory, mMockConfiguration, mMockInvocation);
582     }
583 
584     /**
585      * Simple success case test for {@link CommandScheduler#addCommandFile(String, java.util.List)}
586      * @throws ConfigurationException
587      */
testAddCommandFile()588     public void testAddCommandFile() throws ConfigurationException {
589         // set number of devices to 0 so we can verify command presence
590         mMockManager.setNumDevices(0);
591         List<String> extraArgs = Arrays.asList("--bar");
592         setCreateConfigExpectations(new String[] {"foo", "--bar"}, 1);
593         mMockConfiguration.validateOptions();
594         final List<CommandLine> cmdFileContent = Arrays.asList(new CommandLine(
595                 Arrays.asList("foo"), null, 0));
596         mMockCmdFileParser = new CommandFileParser() {
597             @Override
598             public List<CommandLine> parseFile(File cmdFile) {
599                 return cmdFileContent;
600             }
601         };
602         replayMocks();
603 
604         mScheduler.start();
605         mScheduler.addCommandFile("mycmd.txt", extraArgs);
606         List<CommandTracker> cmds = mScheduler.getCommandTrackers();
607         assertEquals(1, cmds.size());
608         assertEquals("foo", cmds.get(0).getArgs()[0]);
609         assertEquals("--bar", cmds.get(0).getArgs()[1]);
610     }
611 
612     /**
613      * Simple success case test for auto reloading a command file
614      *
615      * @throws ConfigurationException
616      */
testAddCommandFile_reload()617     public void testAddCommandFile_reload() throws ConfigurationException {
618         // set number of devices to 0 so we can verify command presence
619         mMockManager.setNumDevices(0);
620         String[] addCommandArgs = new String[]{"fromcommand"};
621         List<String> extraArgs = Arrays.asList("--bar");
622 
623         setCreateConfigExpectations(addCommandArgs, 1);
624         String[] cmdFile1Args = new String[] {"fromFile1", "--bar"};
625         setCreateConfigExpectations(cmdFile1Args, 1);
626         String[] cmdFile2Args = new String[] {"fromFile2", "--bar"};
627         setCreateConfigExpectations(cmdFile2Args, 1);
628 
629         mMockConfiguration.validateOptions();
630         EasyMock.expectLastCall().times(3);
631 
632         final List<CommandLine> cmdFileContent1 = Arrays.asList(new CommandLine(
633                 Arrays.asList("fromFile1"), null, 0));
634         final List<CommandLine> cmdFileContent2 = Arrays.asList(new CommandLine(
635                 Arrays.asList("fromFile2"), null, 0));
636         mMockCmdFileParser = new CommandFileParser() {
637             boolean firstCall = true;
638             @Override
639             public List<CommandLine> parseFile(File cmdFile) {
640                 if (firstCall) {
641                     firstCall = false;
642                     return cmdFileContent1;
643                 }
644                 return cmdFileContent2;
645             }
646         };
647         replayMocks();
648         mScheduler.start();
649         mScheduler.setCommandFileReload(true);
650         mScheduler.addCommand(addCommandArgs);
651         mScheduler.addCommandFile("mycmd.txt", extraArgs);
652 
653         List<CommandTracker> cmds = mScheduler.getCommandTrackers();
654         assertEquals(2, cmds.size());
655         Collections.sort(cmds, new CommandTrackerIdComparator());
656         Assert.assertArrayEquals(addCommandArgs, cmds.get(0).getArgs());
657         Assert.assertArrayEquals(cmdFile1Args, cmds.get(1).getArgs());
658 
659         // now reload the command file
660         mScheduler.notifyFileChanged(new File("mycmd.txt"), extraArgs);
661 
662         cmds = mScheduler.getCommandTrackers();
663         assertEquals(2, cmds.size());
664         Collections.sort(cmds, new CommandTrackerIdComparator());
665         Assert.assertArrayEquals(addCommandArgs, cmds.get(0).getArgs());
666         Assert.assertArrayEquals(cmdFile2Args, cmds.get(1).getArgs());
667     }
668 
669     /**
670      * Verify attempts to add the same commmand file in reload mode are rejected
671      */
testAddCommandFile_twice()672     public void testAddCommandFile_twice() throws ConfigurationException {
673         // set number of devices to 0 so we can verify command presence
674         mMockManager.setNumDevices(0);
675         String[] cmdFile1Args = new String[] {"fromFile1"};
676         setCreateConfigExpectations(cmdFile1Args, 1);
677         setCreateConfigExpectations(cmdFile1Args, 1);
678         mMockConfiguration.validateOptions();
679         EasyMock.expectLastCall().times(2);
680 
681         final List<CommandLine> cmdFileContent1 = Arrays.asList(new CommandLine(
682                 Arrays.asList("fromFile1"), null, 0));
683         mMockCmdFileParser = new CommandFileParser() {
684             @Override
685             public List<CommandLine> parseFile(File cmdFile) {
686                 return cmdFileContent1;
687             }
688         };
689         replayMocks();
690         mScheduler.start();
691         mScheduler.setCommandFileReload(true);
692         mScheduler.addCommandFile("mycmd.txt", Collections.<String>emptyList());
693 
694         List<CommandTracker> cmds = mScheduler.getCommandTrackers();
695         assertEquals(1, cmds.size());
696         Assert.assertArrayEquals(cmdFile1Args, cmds.get(0).getArgs());
697 
698         // now attempt to add the same command file
699         mScheduler.addCommandFile("mycmd.txt", Collections.<String>emptyList());
700 
701         // expect reload
702         // ensure same state as before
703         cmds = mScheduler.getCommandTrackers();
704         assertEquals(1, cmds.size());
705         Assert.assertArrayEquals(cmdFile1Args, cmds.get(0).getArgs());
706     }
707 
708     /**
709      * Test {@link CommandScheduler#shutdown()} when no devices are available.
710      */
testShutdown()711     public void testShutdown() throws Exception {
712         mMockManager.setNumDevices(0);
713         mScheduler.start();
714         while (!mScheduler.isAlive()) {
715             Thread.sleep(10);
716         }
717         // hack - sleep a bit more to ensure allocateDevices is called
718         Thread.sleep(50);
719         mScheduler.shutdown();
720         mScheduler.join();
721         // test will hang if not successful
722     }
723 
724     /**
725      * Set EasyMock expectations for a create configuration call.
726      */
setCreateConfigExpectations(String[] args, int times)727     private void setCreateConfigExpectations(String[] args, int times)
728             throws ConfigurationException {
729         List<String> nullArg = null;
730         EasyMock.expect(
731                 mMockConfigFactory.createConfigurationFromArgs(EasyMock.aryEq(args),
732                 EasyMock.eq(nullArg), (IKeyStoreClient)EasyMock.anyObject()))
733                 .andReturn(mMockConfiguration)
734                 .times(times);
735         EasyMock.expect(mMockConfiguration.getCommandOptions()).andStubReturn(mCommandOptions);
736         EasyMock.expect(mMockConfiguration.getDeviceRequirements()).andStubReturn(
737                 mDeviceOptions);
738         EasyMock.expect(mMockConfiguration.getDeviceConfig()).andStubReturn(mMockDeviceConfig);
739         EasyMock.expect(mMockConfiguration.getCommandLine()).andStubReturn("");
740         EasyMock.expect(mMockConfiguration.getConfigurationDescription())
741             .andStubReturn(mMockConfigDescriptor);
742 
743         // Assume all legacy test are single device
744         if (mMockDeviceConfig.isEmpty()) {
745             IDeviceConfiguration mockConfig = new DeviceConfigurationHolder("device");
746             mockConfig.addSpecificConfig(mDeviceOptions);
747             mMockDeviceConfig.add(mockConfig);
748         }
749     }
750 
751     /**
752      * Test that Available device at the end of a test are available to be reselected.
753      */
testDeviceReleased()754     public void testDeviceReleased() throws Throwable {
755         String[] args = new String[] {};
756         mMockManager.setNumDevices(1);
757         assertTrue(mMockManager.getQueueOfAvailableDeviceSize() == 1);
758         setCreateConfigExpectations(args, 1);
759         setExpectedInvokeCalls(1);
760         mMockConfiguration.validateOptions();
761         replayMocks();
762         mScheduler.start();
763         mScheduler.addCommand(args);
764         mScheduler.shutdownOnEmpty();
765         mScheduler.join();
766         verifyMocks();
767         assertTrue(mMockManager.getQueueOfAvailableDeviceSize() == 1);
768     }
769 
770     /**
771      * Test that NOT_AVAILABLE devices at the end of a test are not returned to the selectable
772      * devices.
773      */
testDeviceReleased_unavailable()774     public void testDeviceReleased_unavailable() throws Throwable {
775         String[] args = new String[] {};
776         mMockManager.setNumDevicesCustom(1, TestDeviceState.NOT_AVAILABLE, IDevice.class);
777         assert(mMockManager.getQueueOfAvailableDeviceSize() == 1);
778         setCreateConfigExpectations(args, 1);
779         setExpectedInvokeCalls(1);
780         mMockConfiguration.validateOptions();
781         replayMocks();
782         mScheduler.start();
783         mScheduler.addCommand(args);
784         mScheduler.shutdownOnEmpty();
785         mScheduler.join();
786         EasyMock.verify(mMockConfigFactory, mMockConfiguration, mMockInvocation);
787         assertTrue(mMockManager.getQueueOfAvailableDeviceSize() == 0);
788     }
789 
790     /**
791      * Test that only the device NOT_AVAILABLE, selected for invocation is not returned at the end.
792      */
testDeviceReleased_unavailableMulti()793     public void testDeviceReleased_unavailableMulti() throws Throwable {
794         String[] args = new String[] {};
795         mMockManager.setNumDevicesCustom(2, TestDeviceState.NOT_AVAILABLE, IDevice.class);
796         assertTrue(mMockManager.getQueueOfAvailableDeviceSize() == 2);
797         setCreateConfigExpectations(args, 1);
798         setExpectedInvokeCalls(1);
799         mMockConfiguration.validateOptions();
800         replayMocks();
801         mScheduler.start();
802         mScheduler.addCommand(args);
803         mScheduler.shutdownOnEmpty();
804         mScheduler.join();
805         EasyMock.verify(mMockConfigFactory, mMockConfiguration, mMockInvocation);
806         assertTrue(mMockManager.getQueueOfAvailableDeviceSize() == 1);
807     }
808 
809     /**
810      * Test that the TCP device NOT available are NOT released.
811      */
testTcpDevice_NotReleased()812     public void testTcpDevice_NotReleased() throws Throwable {
813         String[] args = new String[] {};
814         mMockManager.setNumDevicesStub(1, TestDeviceState.NOT_AVAILABLE, new TcpDevice("serial"));
815         assertTrue(mMockManager.getQueueOfAvailableDeviceSize() == 1);
816         setCreateConfigExpectations(args, 1);
817         setExpectedInvokeCalls(1);
818         mMockConfiguration.validateOptions();
819         replayMocks();
820         mScheduler.start();
821         mScheduler.addCommand(args);
822         mScheduler.shutdownOnEmpty();
823         mScheduler.join();
824         assertTrue(mMockManager.getQueueOfAvailableDeviceSize() == 1);
825         EasyMock.verify(mMockConfigFactory, mMockConfiguration, mMockInvocation);
826     }
827 
828     /**
829      * Test that the TCP device NOT available selected for a run is NOT released.
830      */
testTcpDevice_NotReleasedMulti()831     public void testTcpDevice_NotReleasedMulti() throws Throwable {
832         String[] args = new String[] {};
833         mMockManager.setNumDevicesStub(2, TestDeviceState.NOT_AVAILABLE, new TcpDevice("serial"));
834         assertTrue(mMockManager.getQueueOfAvailableDeviceSize() == 2);
835         setCreateConfigExpectations(args, 1);
836         setExpectedInvokeCalls(1);
837         mMockConfiguration.validateOptions();
838         replayMocks();
839         mScheduler.start();
840         mScheduler.addCommand(args);
841         mScheduler.shutdownOnEmpty();
842         mScheduler.join();
843         assertTrue(mMockManager.getQueueOfAvailableDeviceSize() == 2);
844         EasyMock.verify(mMockConfigFactory, mMockConfiguration, mMockInvocation);
845     }
846 
847     /**
848      * Test that the Stub device NOT available are NOT released.
849      */
testStubDevice_NotReleased()850     public void testStubDevice_NotReleased() throws Throwable {
851         String[] args = new String[] {};
852         IDevice stub = new StubDevice("emulator-5554", true);
853         mMockManager.setNumDevicesStub(1, TestDeviceState.NOT_AVAILABLE, stub);
854         assertTrue(mMockManager.getQueueOfAvailableDeviceSize() == 1);
855         setCreateConfigExpectations(args, 1);
856         setExpectedInvokeCalls(1);
857         mMockConfiguration.validateOptions();
858         replayMocks();
859         mScheduler.start();
860         mScheduler.addCommand(args);
861         mScheduler.shutdownOnEmpty();
862         mScheduler.join();
863         assertTrue(mMockManager.getQueueOfAvailableDeviceSize() == 1);
864         EasyMock.verify(mMockConfigFactory, mMockConfiguration, mMockInvocation);
865     }
866 
867     /**
868      * Test that a device recovery state is reset when returned to the available queue.
869      */
testDeviceRecoveryState()870     public void testDeviceRecoveryState() throws Throwable {
871         String[] args = new String[] {};
872         mMockManager.setNumDevicesCustomRealNoRecovery(1, IDevice.class);
873         assert(mMockManager.getQueueOfAvailableDeviceSize() == 1);
874         setCreateConfigExpectations(args, 1);
875         setExpectedInvokeCalls(1);
876         mMockConfiguration.validateOptions();
877         replayMocks();
878         mScheduler.start();
879         mScheduler.addCommand(args);
880         mScheduler.shutdownOnEmpty();
881         mScheduler.join();
882         EasyMock.verify(mMockConfigFactory, mMockConfiguration, mMockInvocation);
883         assertEquals(1, mMockManager.getQueueOfAvailableDeviceSize());
884         ITestDevice t = mMockManager.allocateDevice();
885         assertTrue(t.getRecoveryMode().equals(RecoveryMode.AVAILABLE));
886     }
887 
888     /**
889      * Test that a device that is unresponsive at the end of an invocation is made unavailable.
890      */
testDevice_unresponsive()891     public void testDevice_unresponsive() throws Throwable {
892         String[] args = new String[] {};
893         mMockManager.setNumDevicesUnresponsive(1);
894         assert(mMockManager.getQueueOfAvailableDeviceSize() == 1);
895         setCreateConfigExpectations(args, 1);
896         setExpectedInvokeCalls(1);
897         mMockConfiguration.validateOptions();
898         replayMocks();
899         mScheduler.start();
900         mScheduler.addCommand(args);
901         mScheduler.shutdownOnEmpty();
902         mScheduler.join();
903         EasyMock.verify(mMockConfigFactory, mMockConfiguration, mMockInvocation);
904         // Device does not return to the list since it's unavailable.
905         assertEquals(0, mMockManager.getQueueOfAvailableDeviceSize());
906     }
907 
908     /**
909      * Test that {@link CommandScheduler#displayCommandQueue(PrintWriter)} is properly printing
910      * the state of a command.
911      */
testDisplayCommandQueue()912     public void testDisplayCommandQueue() throws Throwable {
913         String[] args = new String[] {"empty"};
914         setCreateConfigExpectations(args, 1);
915         mMockConfiguration.validateOptions();
916         replayMocks();
917         mScheduler.start();
918         mScheduler.addCommand(args);
919         OutputStream res = new ByteArrayOutputStream();
920         PrintWriter pw = new PrintWriter(res);
921         mScheduler.displayCommandQueue(pw);
922         verifyMocks();
923         pw.flush();
924         assertEquals("Id  Config  Created  Exec time  State            Sleep time  Rescheduled  "
925                 + "Loop   \n1   empty   0m:00    0m:00      Wait_for_device  N/A         false  "
926                 + "      false  \n", res.toString());
927         mScheduler.shutdown();
928     }
929 
930     /**
931      * Test that {@link CommandScheduler#dumpCommandsXml(PrintWriter, String)} is properly printing
932      * the xml of a command.
933      */
testDumpCommandXml()934     public void testDumpCommandXml() throws Throwable {
935         String[] args = new String[] {"empty"};
936         OutputStream res = new ByteArrayOutputStream();
937         PrintWriter pw = new PrintWriter(res);
938         setCreateConfigExpectations(args, 1);
939         mMockConfiguration.validateOptions();
940         mMockConfiguration.dumpXml(EasyMock.anyObject());
941         replayMocks();
942         mScheduler.start();
943         mScheduler.addCommand(args);
944         mScheduler.dumpCommandsXml(pw, null);
945         verifyMocks();
946         pw.flush();
947         String filename = res.toString().replace("Saved command dump to ", "").trim();
948         File test = new File(filename);
949         try {
950             assertTrue(test.exists());
951             mScheduler.shutdown();
952         } finally {
953             FileUtil.deleteFile(test);
954         }
955     }
956 
957     /**
958      * Test that {@link CommandScheduler#displayCommandsInfo(PrintWriter, String)} is properly
959      * printing the command.
960      */
testDisplayCommandsInfo()961     public void testDisplayCommandsInfo() throws Throwable {
962         String[] args = new String[] {"empty"};
963         setCreateConfigExpectations(args, 1);
964         mMockConfiguration.validateOptions();
965         replayMocks();
966         mScheduler.start();
967         mScheduler.addCommand(args);
968         OutputStream res = new ByteArrayOutputStream();
969         PrintWriter pw = new PrintWriter(res);
970         mScheduler.displayCommandsInfo(pw, null);
971         verifyMocks();
972         pw.flush();
973         assertEquals("Command 1: [0m:00] empty\n", res.toString());
974         mScheduler.shutdown();
975     }
976 
977     /**
978      * Test that {@link CommandScheduler#getInvocationInfo(int)} is properly returning null if
979      * no invocation matching the id.
980      */
testGetInvocationInfo_null()981     public void testGetInvocationInfo_null() throws Throwable {
982         String[] args = new String[] {"empty", "test"};
983         setCreateConfigExpectations(args, 1);
984         mMockConfiguration.validateOptions();
985         replayMocks();
986         mScheduler.start();
987         mScheduler.addCommand(args);
988         assertNull(mScheduler.getInvocationInfo(999));
989         mScheduler.shutdown();
990     }
991 
testAllocateDevices()992     public void testAllocateDevices() throws Exception {
993         String[] args = new String[] {"foo", "test"};
994         mMockManager.setNumDevices(1);
995         setCreateConfigExpectations(args, 1);
996         mMockConfiguration.validateOptions();
997         replayMocks();
998         mScheduler.start();
999         Map<String, ITestDevice> devices = mScheduler.allocateDevices(
1000                 mMockConfiguration, mMockManager);
1001         assertEquals(1, devices.size());
1002         mScheduler.shutdown();
1003     }
1004 
createDeviceConfig(String serial)1005     private IDeviceConfiguration createDeviceConfig(String serial) throws Exception {
1006         IDeviceConfiguration mockConfig = new DeviceConfigurationHolder(serial);
1007         DeviceSelectionOptions options = new DeviceSelectionOptions();
1008         options.addSerial(serial);
1009         mockConfig.addSpecificConfig(options);
1010         return mockConfig;
1011     }
1012 
testAllocateDevices_multipleDevices()1013     public void testAllocateDevices_multipleDevices() throws Exception {
1014         String[] args = new String[] {"foo", "test"};
1015 
1016         mMockManager.setNumDevices(2);
1017         mMockDeviceConfig.add(createDeviceConfig("serial0"));
1018         mMockDeviceConfig.add(createDeviceConfig("serial1"));
1019 
1020         setCreateConfigExpectations(args, 1);
1021         mMockConfiguration.validateOptions();
1022         replayMocks();
1023         mScheduler.start();
1024         Map<String, ITestDevice> devices = mScheduler.allocateDevices(
1025                 mMockConfiguration, mMockManager);
1026         assertEquals(2, devices.size());
1027         assertEquals(0, mMockManager.getQueueOfAvailableDeviceSize());
1028         mScheduler.shutdown();
1029     }
1030 
testAllocateDevices_multipleDevices_failed()1031     public void testAllocateDevices_multipleDevices_failed() throws Exception {
1032         String[] args = new String[] {"foo", "test"};
1033 
1034         mMockManager.setNumDevices(2);
1035         mMockDeviceConfig.add(createDeviceConfig("serial0"));
1036         mMockDeviceConfig.add(createDeviceConfig("not_exist_serial"));
1037 
1038         setCreateConfigExpectations(args, 1);
1039         mMockConfiguration.validateOptions();
1040         replayMocks();
1041         mScheduler.start();
1042         Map<String, ITestDevice> devices = mScheduler.allocateDevices(
1043                 mMockConfiguration, mMockManager);
1044         assertEquals(0, devices.size());
1045         assertEquals(2, mMockManager.getQueueOfAvailableDeviceSize());
1046         mScheduler.shutdown();
1047     }
1048 
1049     /**
1050      * Test case for execCommand with multiple devices.
1051      * {@link CommandScheduler#execCommand(IScheduledInvocationListener, String[])}
1052      */
1053     @SuppressWarnings("unchecked")
testExecCommand_multipleDevices()1054     public void testExecCommand_multipleDevices() throws Throwable {
1055         String[] args = new String[] {
1056             "foo"
1057         };
1058         mMockManager.setNumDevices(2);
1059         mMockDeviceConfig.add(createDeviceConfig("serial0"));
1060         mMockDeviceConfig.add(createDeviceConfig("serial1"));
1061         setCreateConfigExpectations(args, 1);
1062         mMockConfiguration.validateOptions();
1063         mMockInvocation.invoke((IInvocationContext)EasyMock.anyObject(),
1064                 (IConfiguration)EasyMock.anyObject(), (IRescheduler)EasyMock.anyObject(),
1065                 (ITestInvocationListener)EasyMock.anyObject(),
1066                 // This is FreeDeviceHandler.
1067                 (IScheduledInvocationListener)EasyMock.anyObject());
1068         IScheduledInvocationListener mockListener = EasyMock
1069                 .createMock(IScheduledInvocationListener.class);
1070         mockListener.invocationInitiated((IInvocationContext) EasyMock.anyObject());
1071         mockListener.invocationComplete((IInvocationContext)EasyMock.anyObject(),
1072                 (Map<ITestDevice, FreeDeviceState>)EasyMock.anyObject());
1073         replayMocks(mockListener);
1074 
1075         mScheduler.start();
1076         mScheduler.execCommand(mockListener, args);
1077         mScheduler.shutdownOnEmpty();
1078         mScheduler.join(2 * 1000);
1079         verifyMocks(mockListener);
1080     }
1081 
1082     /**
1083      * Test case for execCommand with multiple devices but fail to allocate some device.
1084      * {@link CommandScheduler#execCommand(IScheduledInvocationListener, String[])}
1085      */
testExecCommand_multipleDevices_noDevice()1086     public void testExecCommand_multipleDevices_noDevice() throws Throwable {
1087         String[] args = new String[] {
1088             "foo"
1089         };
1090         mMockManager.setNumDevices(2);
1091         mMockDeviceConfig.add(createDeviceConfig("serial0"));
1092         mMockDeviceConfig.add(createDeviceConfig("not_exist_serial"));
1093         setCreateConfigExpectations(args, 1);
1094         mMockConfiguration.validateOptions();
1095         IScheduledInvocationListener mockListener = EasyMock
1096                 .createMock(IScheduledInvocationListener.class);
1097         replayMocks(mockListener);
1098 
1099         mScheduler.start();
1100         try {
1101             mScheduler.execCommand(mockListener, args);
1102             fail();
1103         } catch (NoDeviceException e) {
1104             // expect NoDeviceException
1105         }
1106         mScheduler.shutdownOnEmpty();
1107         mScheduler.join(2 * 1000);
1108         verifyMocks(mockListener);
1109     }
1110 
1111     /**
1112      * Test that when a command runs in the versioned subprocess with --invocation-data option we do
1113      * not add the attributes again
1114      */
testExecCommand_versioning()1115     public void testExecCommand_versioning() throws Throwable {
1116         String[] args =
1117                 new String[] {
1118                     "foo", "--invocation-data", "test",
1119                 };
1120         setCreateConfigExpectations(args, 1);
1121         OptionSetter setter = new OptionSetter(mCommandOptions);
1122         // If invocation-data are added and we are in a versioned invocation, the data should not
1123         // be added again.
1124         setter.setOptionValue("invocation-data", "key", "value");
1125         mMockConfigDescriptor.setSandboxed(true);
1126         setExpectedInvokeCalls(1);
1127         mMockConfiguration.validateOptions();
1128         IDevice mockIDevice = EasyMock.createMock(IDevice.class);
1129         ITestDevice mockDevice = EasyMock.createMock(ITestDevice.class);
1130         EasyMock.expect(mockDevice.getSerialNumber()).andStubReturn("serial");
1131         EasyMock.expect(mockDevice.getDeviceState()).andStubReturn(TestDeviceState.ONLINE);
1132         mockDevice.setRecoveryMode(EasyMock.eq(RecoveryMode.AVAILABLE));
1133         EasyMock.expect(mockDevice.getIDevice()).andStubReturn(mockIDevice);
1134         IScheduledInvocationListener mockListener =
1135                 EasyMock.createMock(IScheduledInvocationListener.class);
1136         mockListener.invocationInitiated((InvocationContext) EasyMock.anyObject());
1137         mockListener.invocationComplete(
1138                 (IInvocationContext) EasyMock.anyObject(), EasyMock.anyObject());
1139         EasyMock.expect(mockDevice.waitForDeviceShell(EasyMock.anyLong())).andReturn(true);
1140         replayMocks(mockDevice, mockListener);
1141         mScheduler.start();
1142         mScheduler.execCommand(mockListener, mockDevice, args);
1143         mScheduler.shutdownOnEmpty();
1144         mScheduler.join(2 * 1000);
1145         verifyMocks(mockListener);
1146         assertTrue(mContext.getAttributes().isEmpty());
1147     }
1148 }
1149