CTS测试框架 -- 命令执行

本文深入解析Android测试框架Trade-Federation的执行流程,包括命令执行、组件初始化、测试案例执行及清理过程。通过反射机制调用测试案例的setup、run、teardown方法,实现了灵活高效的自动化测试。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.命令执行

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

public void invoke(
        IInvocationContext context, IConfiguration config, IRescheduler rescheduler,
        ITestInvocationListener... extraListeners)
                throws DeviceNotAvailableException, Throwable {
    // 添加监听器,有device状态,log等
    List<ITestInvocationListener> 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<IMultiTargetPreparer> multiPreparers = config.getMultiTargetPreparers();
    ListIterator<IMultiTargetPreparer> 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<ITargetPreparer> preparers =
                config.getDeviceConfigByName(deviceName).getTargetPreparers();
        ListIterator<ITargetPreparer> 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测试框架

<think>嗯,用户之前问了关于cts-on-gsi-variant配置项的含义,现在他们想知道如何在cts-tradefed中输出cts-on-gsi的测试列表。首先,我需要确认用户的需求是什么。他们可能在进行GSI环境下的CTS测试,需要获取相关的测试用例列表。这可能是因为他们需要验证设备在GSI上的兼容性,或者在进行调试时想要知道哪些测试会被执行。 接下来,我应该回忆一下CTS测试的基本流程。用户提到的cts-tradefed是CTS测试框架工具,通常通过命令行操作。要列出测试,常用的命令是`run cts --collect-tests`,但需要指定模块或测试条件。用户需要的是特定于cts-on-gsi的测试,所以可能需要参数来过滤这些测试。 不过,cts-on-gsi的测试可能并不是一个独立的模块,而是通过条件判断在运行时启用的。这时候可能需要查找相关的测试配置或标记。比如,测试用例中可能有使用`@RequiresDevice`注解标记需要GSI环境的测试。这时候可能需要通过grep命令测试源文件中搜索相关标记。 另外,用户之前的问题中提到过cts-on-gsi-variant这个配置项,这个配置项可能在构建或运行时激活某些测试。因此,在运行cts-tradefed时可能需要设置相应的参数,比如`--enable-parameter`来启用相关测试的收集。 还需要注意权限问题,运行cts-tradefed可能需要adb权限和设备连接。此外,用户可能需要确保设备已经设置了ro.cts_on_gsi属性为true,这样才能正确触发相关测试的收集。 最后,用户可能需要具体的步骤,比如如何运行命令,如何查找测试列表,以及如何验证是否正确获取了测试用例。同时,提醒他们注意XML配置中的引号问题,避免因为格式错误导致命令执行失败。</think>以下是使用CTS测试框架输出`cts-on-gsi`相关测试列表的详细步骤: --- ### 一、准备测试环境 1. **设备要求** - 已刷入GSI镜像的测试设备 - 开启开发者模式/USB调试 ```bash adb devices # 确认设备连接 ``` 2. **CTS工具准备** - 下载对应Android版本的CTS- 解压至工作目录 ```bash unzip android-cts-13_r1-linux.zip ``` --- ### 二、核心操作命令 #### 方法1:通过参数过滤 ```bash ./tools/cts-tradefed run cts --collect-tests-only \ --enable-parameterized-modules \ --module-parameter "cts-on-gsi:true" ``` **输出结果示例**: ``` Generated test list contains 127 tests android.example.cts.GsiFeatureTest#testGsiCompatibility android.example.cts.VintfCheck#testTrebleCompat ... ``` #### 方法2:使用XML配置 1. 创建配置文件`gsi-cts-config.xml` ```xml <configuration description="GSI CTS Filter"> <option name="config-descriptor:flags" value="cts-on-gsi" /> </configuration> ``` 2. 执行命令 ```bash ./tools/cts-tradefed run cts --collect-tests \ --config gsi-cts-config.xml ``` --- ### 三、进阶调试技巧 1. **查看测试元数据** ```bash ./tools/cts-tradefed list modules | grep -i gsi ``` ``` CtsGsiTestCases # 专为GSI设计的测试模块 CtsSecurityTestCases (gsi_related) ``` 2. **源码级验证** ```bash # 在CTS源码中搜索测试标记 grep -r "@RequiresDevice(gsiEnabled=true)" cts/tests/ ``` --- ### 四、验证流程 1. **检查测试过滤器** ```bash adb shell settings get global cts-on-gsi_enabled # 应返回1 ``` 2. **实时监控测试加载** ```bash ./tools/cts-tradefed run cts --log-level VERBOSE \ --enable-parameter "cts-on-gsi" ``` --- ### 五、注意事项 1. **版本对应关系** | Android版本 | CTS包版本要求 | |-------------|---------------| | Android 12 | CTS 12_r5+ | | Android 13 | CTS 13_r1+ | 2. **常见错误处理** - **问题**:`No tests matching criteria` - 解决方案:检查`ro.treble.enabled`属性是否为true ```bash adb shell getprop ro.treble.enabled ``` - **问题**:`Missing GSI Vendor Tests` - 解决方案:确认已安装Vendor Test Suite (VTS) ```bash vts-tradefed run vts --skip-all-system-status-check ``` --- 通过以上步骤,可以精确提取针对GSI环境的CTS测试列表。实际测试中建议结合`--shard-count`参数进行分片测试以提高效率。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值