CTS测试框架 -- 命令执行

1.命令执行

经过了前面对于命令的调度,开启真正命令的执行,在TestInvocation中把configuration中的所有组件都取出来执行。
入口:TestInvocation.invoke

public void invoke(
        IInvocationContext context, IConfiguration config, IRescheduler rescheduler,
        ITestInvocationListener... extraListeners)
                throws DeviceNotAvailableException, Throwable {
    // 添加监听器,有device状态,log等
    List allListeners =
            new ArrayList<>(config.getTestInvocationListeners().size() + extraListeners.length);
    allListeners.addAll(config.getTestInvocationListeners());
    allListeners.addAll(Arrays.asList(extraListeners));
    if (config.getProfiler() != null) {
        allListeners.add(new AggregatingProfilerListener(config.getProfiler()));
    }
    // 初始化log监听器
    ITestInvocationListener listener = new LogSaverResultForwarder(config.getLogSaver(),
            allListeners);
    String currentDeviceName = null;
    try {
        mStatus = "fetching build";
        config.getLogOutput().init();
        getLogRegistry().registerLogger(config.getLogOutput());
        // 按照前面的分析,命令的执行是以设备为单位的
        // 这里需要知道所有执行命令的设备,逐个去在设备上执行
        for (String deviceName : context.getDeviceConfigNames()) {
            context.getDevice(deviceName).clearLastConnectedWifiNetwork();
            // 添加命令行参数option
            context.getDevice(deviceName).setOptions(
                    config.getDeviceConfigByName(deviceName).getDeviceOptions());
            if (config.getDeviceConfigByName(deviceName).getDeviceOptions()
                    .isLogcatCaptureEnabled()) {
                if (!(context.getDevice(deviceName).getIDevice() instanceof StubDevice)) {
                    context.getDevice(deviceName).startLogcat();
                }
            }
        }
        String cmdLineArgs = config.getCommandLine();
        if (cmdLineArgs != null) {
            CLog.i("Invocation was started with cmd: %s", cmdLineArgs);
        }
        updateInvocationContext(context, config);
        for (String deviceName : context.getDeviceConfigNames()) {
            currentDeviceName = deviceName;
            // 从configuration中取出需要的组件
            IBuildInfo info = null;
            ITestDevice device = context.getDevice(deviceName);
            IDeviceConfiguration deviceConfig = config.getDeviceConfigByName(deviceName);
            IBuildProvider provider = deviceConfig.getBuildProvider();
            // Set the provider test tag
            if (provider instanceof IInvocationContextReceiver) {
                ((IInvocationContextReceiver)provider).setInvocationContext(context);
            }
            // Get the build
            if (provider instanceof IDeviceBuildProvider) {
                info = ((IDeviceBuildProvider)provider).getBuild(device);
            } else {
                info = provider.getBuild();
            }
            if (info != null) {
                // 执行命令的设备的Serial
                info.setDeviceSerial(device.getSerialNumber());
                context.addDeviceBuildInfo(deviceName, info);
                device.setRecovery(deviceConfig.getDeviceRecovery());
            } else {
                mStatus = "(no build to test)";
                CLog.logAndDisplay(
                        LogLevel.WARN,
                        "No build found to test for device: %s",
                        device.getSerialNumber());
                rescheduleTest(config, rescheduler);
                // save current log contents to global log
                getLogRegistry().dumpToGlobalLog(config.getLogOutput());
                // Set the exit code to error
                setExitCode(ExitCode.NO_BUILD,
                        new BuildRetrievalError("No build found to test."));
                return;
            }
            // TODO: remove build update when reporting is done on context
            updateBuild(info, config);
        }
        if (shardConfig(config, context, rescheduler)) {
            CLog.i("Invocation for %s has been sharded, rescheduling",
                    context.getSerials().toString());
        } else {
            if (config.getTests() == null || config.getTests().isEmpty()) {
                CLog.e("No tests to run");
            } else {
                // 真正的执行
                performInvocation(config, context, rescheduler, listener);
                setExitCode(ExitCode.NO_ERROR, null);
            }
        }
    } catch (BuildRetrievalError e) {
        ...
    } finally {
        ...
        // 停止继续logcat,保存
        for (String deviceName : context.getDeviceConfigNames()) {
            if (!(context.getDevice(deviceName).getIDevice() instanceof StubDevice)) {
                context.getDevice(deviceName).stopLogcat();
            }
        }
        ...
    }
}

performInvocation

private void performInvocation(IConfiguration config, IInvocationContext context,
        IRescheduler rescheduler, ITestInvocationListener listener) throws Throwable {

    boolean resumed = false;
    String bugreportName = null;
    long startTime = System.currentTimeMillis();
    long elapsedTime = -1;
    Throwable exception = null;
    Throwable tearDownException = null;
    ITestDevice badDevice = null;

    startInvocation(config, context, listener);
    try {
        logDeviceBatteryLevel(context, "initial");
        // 执行命令
        prepareAndRun(config, context, listener);
    } catch (BuildError e) {
        ...
    } finally {
        ...
        try {
            // 执行doTeardown清理模板
            doTeardown(config, context, exception);
        } catch (Throwable e) {
            tearDownException = e;
            if (exception == null) {
                // only report when the exception is new during tear down
                reportFailure(tearDownException, listener, config, context, rescheduler);
            }
        }
        // 执行clean以及保存log
        ... cleanUp
    if (tearDownException != null) {
        throw tearDownException;
    }
}

prepareAndRun

private void prepareAndRun(
        IConfiguration config, IInvocationContext context, ITestInvocationListener listener)
        throws Throwable {
    getRunUtil().allowInterrupt(true);
    logDeviceBatteryLevel(context, "initial -> setup");
    doSetup(config, context, listener);
    logDeviceBatteryLevel(context, "setup -> test");
    runTests(context, config, listener);
    logDeviceBatteryLevel(context, "after test");
}

doSetup:因为支持的测试种类很多,通过instanceof关键字去判断需要执行的测试到底是哪种接口的子类,就执行该模板的setup方法。

void doSetup(
        IConfiguration config,
        IInvocationContext context,
        final ITestInvocationListener listener)
        throws TargetSetupError, BuildError, DeviceNotAvailableException {
    for (String deviceName : context.getDeviceConfigNames()) {
        ITestDevice device = context.getDevice(deviceName);
        if (device instanceof ITestLoggerReceiver) {
            ((ITestLoggerReceiver) context.getDevice(deviceName))
                    .setTestLogger(listener);
        }
        if (!config.getCommandOptions().shouldSkipPreDeviceSetup()) {
            device.preInvocationSetup(context.getBuildInfo(deviceName));
        }
        for (ITargetPreparer preparer : config.getDeviceConfigByName(deviceName)
                .getTargetPreparers()) {
            if (preparer instanceof ITestLoggerReceiver) {
                ((ITestLoggerReceiver) preparer).setTestLogger(listener);
            }
            preparer.setUp(device, context.getBuildInfo(deviceName));
        }
    }
    for (IMultiTargetPreparer multipreparer : config.getMultiTargetPreparers()) {
        if (multipreparer instanceof ITestLoggerReceiver) {
            ((ITestLoggerReceiver) multipreparer).setTestLogger(listener);
        }
        multipreparer.setUp(context);
    }
    if (config.getProfiler() != null) {
        config.getProfiler().setUp(context);
    }
    for (String deviceName : context.getDeviceConfigNames()) {
        reportLogs(context.getDevice(deviceName), listener, Stage.SETUP);
    }
}

runTests:类似与doSetup,根据不同的情况,看需要执行的测试case到底哪种,进行相应的预处理,最后调用接口的run方法。

private void runTests(IInvocationContext context, IConfiguration config,
        ITestInvocationListener listener) throws DeviceNotAvailableException {
    for (IRemoteTest test : config.getTests()) {
        if (test instanceof IDeviceTest) {
            ((IDeviceTest)test).setDevice(context.getDevices().get(0));
        }
        if (test instanceof IBuildReceiver) {
            ((IBuildReceiver)test).setBuild(context.getBuildInfo(
                    context.getDevices().get(0)));
        }
        if (test instanceof ISystemStatusCheckerReceiver) {
            ((ISystemStatusCheckerReceiver) test).setSystemStatusChecker(
                    config.getSystemStatusCheckers());
        }
        if (test instanceof IMultiDeviceTest) {
            ((IMultiDeviceTest)test).setDeviceInfos(context.getDeviceBuildMap());
        }
        if (test instanceof IInvocationContextReceiver) {
            ((IInvocationContextReceiver)test).setInvocationContext(context);
        }
        test.run(listener);
    }
}

doTearDown:执行各种teardown接口,并进行清理工作

private void doTeardown(IConfiguration config, IInvocationContext context,
        Throwable exception) throws Throwable {
    Throwable throwable = null;

    List multiPreparers = config.getMultiTargetPreparers();
    ListIterator iterator =
            multiPreparers.listIterator(multiPreparers.size());
    while (iterator.hasPrevious()) {
        IMultiTargetPreparer multipreparer = iterator.previous();
        multipreparer.tearDown(context, throwable);
    }
    for (String deviceName : context.getDeviceConfigNames()) {
        ITestDevice device = context.getDevice(deviceName);
        device.clearLastConnectedWifiNetwork();
        List preparers =
                config.getDeviceConfigByName(deviceName).getTargetPreparers();
        ListIterator itr = preparers.listIterator(preparers.size());
        while (itr.hasPrevious()) {
            ITargetPreparer preparer = itr.previous();
            if(preparer instanceof ITargetCleaner) {
                ITargetCleaner cleaner = (ITargetCleaner) preparer;
                if (cleaner != null) {
                    try {
                        device.getSerialNumber());
                        cleaner.tearDown(device, context.getBuildInfo(deviceName), exception);
                    } catch (Throwable e) {
                        throwable = e;
                    }
                }
            }
        }
        device.postInvocationTearDown();
    }
    if (throwable != null) {
        throw throwable;
    }
}

一般测试case都会实现setup,run,teardown,分别在其中做初始化,执行测试以及最后的收尾工作,通过反射的方式都拿到了测试实例,执行模板接口,所以真正执行case的时候只需要复写模板中定义好的方法即可。

总结

到这里,基础框架就介绍的差不多了,可以返回去再看下基础框架启动中最开始的一张大图,整体的流程从main的启动到最后这部分test的执行的逻辑都在里面,再梳理一遍。

现在再看整个基础框架Trade-Federation,虽然这是一个java程序,但是其实就是为Android设备量身定做的,各种命令的运行其实都需要Android设备的配合,整个基础框架的功能已经很强大了,可以说,只要写好了case,很多测试case在这个基础框架的基础上就可以直接运行。另外,其中还有很多关于设备的管理,recorvery,host-log以及device-log的收集等,这些主要是通过前面提到的各种listener中实现的,有兴趣可以自己去详细了解下。

下一篇开始介绍在基础框架上封装了一层的CTS测试框架

你可能感兴趣的:(CTS测试框架)