CTS测试框架 -- V2版本
来源:互联网 发布:淘宝店招素材免费下载 编辑:程序博客网 时间:2024/06/15 18:08
目录
- 概述
- 组织case
- 入口CompatibilityConsole
- ModuleRepo
- 组件CompatibilityTest
- 执行测试
- 总结
1 概述
在Android 6.0以及之前的版本上,CTS测试使用的都是前面介绍的V1框架,上篇文章已经介绍了V1框架的组织case的方式以及不足,主要是当测试case不断的增加之后带来的配置文件的不断变大,各模块之间的接耦合成都还是不够,因此就有了V2版本。
V2版本的组件名称都已经不以CTS作为名称了,变成了以Compatibility作为关键词,从名称上也可以大致看出来这个测试框架应该是变得更加通用了,CTS只是其中一个。其实,事实也确实是如此,因为随着Android O的发布,Google还推出了一个新的测试计划 – VTS
,其实这个VTS框架的变化很小,基本就是在V2版本上稍加配置完成的,在本篇文章的结尾也会介绍下VTS测试框架的组成。
2 组织case
框架再变,主要功能还是不变,就是为了把那么多的case更好的组织起来,顺利跑完所有的测试case,因此重点还是在测试case的组织上。
2.1 入口CompatibilityConsole
代码位置:/cts/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/
CompatibilityConsole同样也是基础框架中Console的子类,启动方式没有变化,其中主要还是添加了自定义Command,setCustomCommands这个方法中添加了V2框架支持自己支持的命令,可以看到在自定义的命令中多了一些关于module的命令,这个module非常重要,是整个V2框架的重心,在V2框架的测试case执行中,淡化了plan的概念,强调了这个module的概念,一个module就代表一组测试case,简单的理解,对于以apk为单位测试的case,一个apk就是一个module。
举个例子:其中的listModules
private void listModules() { File[] files = null; try { // 获取测试目录 // 这个ModuleRepo.ConfigFilter主要作用就是获取所有config结尾的文件 files = getBuildHelper().getTestsDir().listFiles(new ModuleRepo.ConfigFilter()); } catch (FileNotFoundException e) { printLine(e.getMessage()); e.printStackTrace(); } if (files != null && files.length > 0) { List<String> modules = new ArrayList<>(); for (File moduleFile : files) { // 遍历目录下的所有文件 // 把config结尾的文件的文件名列出来 modules.add(FileUtil.getBaseName(moduleFile.getName())); } Collections.sort(modules); for (String module : modules) { printLine(module); } } else { printLine("No modules found"); }}
这个地方ModuleRepo是一个重点,测试case的组织全靠这个repo,下一小节重点介绍。
如果运行过CTS测试case的话,会知道在测试运行的时候控制台上命令提示符前面会有cts-tf
的提示,之前的版本是写死在代码中的,而这里就不一样了:
@Overrideprotected String getConsolePrompt() { return String.format("%s-tf > ", SuiteInfo.NAME.toLowerCase());}
其中的SuiteInfo
并不是某一个固定的java文件,而是动态编译生成的:
代码位置:/cts/build/compatibility_test_suite.mk
这个mk文件定义了SuiteInfo文件的生成:
# Generate the SuiteInfo.javasuite_info_java := $(call intermediates-dir-for,JAVA_LIBRARIES,$(LOCAL_MODULE),true,COMMON)/com/android/compatibility/SuiteInfo.java$(suite_info_java): PRIVATE_SUITE_BUILD_NUMBER := $(LOCAL_SUITE_BUILD_NUMBER)$(suite_info_java): PRIVATE_SUITE_TARGET_ARCH := $(LOCAL_SUITE_TARGET_ARCH)$(suite_info_java): PRIVATE_SUITE_NAME := $(LOCAL_SUITE_NAME)$(suite_info_java): PRIVATE_SUITE_FULLNAME := $(LOCAL_SUITE_FULLNAME)$(suite_info_java): PRIVATE_SUITE_VERSION := $(LOCAL_SUITE_VERSION)$(suite_info_java): cts/build/compatibility_test_suite.mk $(LOCAL_MODULE_MAKEFILE) @echo Generating: $@ $(hide) mkdir -p $(dir $@) $(hide) echo "/* This file is auto generated by Android.mk. Do not modify. */" > $@ $(hide) echo "package com.android.compatibility;" >> $@ $(hide) echo "public class SuiteInfo {" >> $@ $(hide) echo " public static final String BUILD_NUMBER = \"$(PRIVATE_SUITE_BUILD_NUMBER)\";" >> $@ $(hide) echo " public static final String TARGET_ARCH = \"$(PRIVATE_SUITE_TARGET_ARCH)\";" >> $@ $(hide) echo " public static final String NAME = \"$(PRIVATE_SUITE_NAME)\";" >> $@ $(hide) echo " public static final String FULLNAME = \"$(PRIVATE_SUITE_FULLNAME)\";" >> $@ $(hide) echo " public static final String VERSION = \"$(PRIVATE_SUITE_VERSION)\";" >> $@ $(hide) echo "}" >> $@# Include the SuiteInfo.javaLOCAL_GENERATED_SOURCES := $(suite_info_java)
这个文件重点就是生成了一些常量,而这些常量也正是在mk文件中定义的:比如我们上面说的SuiteInfo.NAME
,生成过程:
public static final String NAME = \"$(PRIVATE_SUITE_NAME)\";" >> $@$(suite_info_java): PRIVATE_SUITE_NAME := $(LOCAL_SUITE_NAME)
LOCAL_SUITE_NAME的定义:
/cts/tools/cts-tradefed/Android.mk
其中有一行LOCAL_SUITE_NAME := CTS
上面的文件中还有一些其他常量的定义。
虽然这个只是一个名称的定义,但是意义在于整个V2版本的框架的灵活程度变的更高了,尽可能的把更多的内容放在mk文件中定义,避免hard code。
入口这个文件还是比较简单,就是作为整个框架的启动入口,自定义命令的添加。
2.2 ModuleRepo
在V2版本的框架中淡化的plan的概念,取而代之是module,开始之前我们先看下这个module的config文件究竟长什么样,跟V1版本的有什么区别,依旧是CtsJobSchedulerTestCases:
<configuration description="Config for CTS Job Scheduler test cases"> <option name="config-descriptor:metadata" key="component" value="framework" /> <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> <option name="cleanup-apks" value="true" /> <option name="test-file-name" value="CtsJobSchedulerTestCases.apk" /> <option name="test-file-name" value="CtsJobSchedulerJobPerm.apk" /> </target_preparer> <test class="com.android.tradefed.testtype.AndroidJUnitTest" > <option name="package" value="android.jobscheduler.cts" /> <option name="runtime-hint" value="2m" /> </test></configuration>
可以看到,没有了之前每条测试都配置一个xml中的一个标签,取而代之的是这个module相关的一些配置。
在V1版本中测试case的组织是通过TestPackageRepo,而这里是通过这个ModuleRepo,在初始化的时候扫描测试目录下所有的config文件
private void addModuleDef(String name, IAbi abi, IRemoteTest test, String[] configPaths) throws ConfigurationException { // Invokes parser to process the test module config file IConfiguration config = mConfigFactory.createConfigurationFromArgs(configPaths); addModuleDef(new ModuleDef(name, abi, test, config.getTargetPreparers(), config.getConfigurationDescription()));}
每个config文件代表了一个module,把所有的config文件解析之后放入list。
2.3 组件CompatibilityTest
老套路,还是先看下这个V2框架的组件配置文件:
代码位置
platform/cts/common/host-side/tradefed/res/config/
platform/cts/tools/cts-tradefed/res/config
这次的配置文件比较多,这个地方就不贴代码了,但是核心没有变,组件的配置在/cts/common/host-side/tradefed/res/config/common-compatibility-config.xml
中,有一个test组件的配置<test class="com.android.compatibility.common.tradefed.testtype.CompatibilityTest" />
,可见V2框架的test组件就是这个CompatibilityTest,直奔其run方法:
public void run(ITestInvocationListener listener) throws DeviceNotAvailableException { try { List<ISystemStatusChecker> checkers = new ArrayList<>(); // 系统状态检查 if (mSkipAllSystemStatusCheck) { CLog.d("Skipping system status checkers"); } else { checkSystemStatusBlackAndWhiteList(); for (ISystemStatusChecker checker : mListCheckers) { if(shouldIncludeSystemStatusChecker(checker)) { checkers.add(checker); } } } LinkedList<IModuleDef> modules; synchronized (mModuleRepo) { if (!mModuleRepo.isInitialized()) { // 这步很重要,初始化了filter // 一个代表要删除掉的module,一个代表要添加的额外module setupFilters(); // ModuleRepo的初始化,已经添加了所有的测试case mModuleRepo.initialize(mTotalShards, mShardIndex, mBuildHelper.getTestsDir(), getAbis(), mDeviceTokens, mTestArgs, mModuleArgs, mIncludeFilters, mExcludeFilters, mModuleMetadataIncludeFilter, mModuleMetadataExcludeFilter, mBuildHelper.getBuildInfo()); // Add the entire list of modules to the CompatibilityBuildHelper for reporting mBuildHelper.setModuleIds(mModuleRepo.getModuleIds()); int count = UniqueModuleCountUtil.countUniqueModules( mModuleRepo.getTokenModules()) + UniqueModuleCountUtil.countUniqueModules( mModuleRepo.getNonTokenModules()); CLog.logAndDisplay(LogLevel.INFO, "========================================"); CLog.logAndDisplay(LogLevel.INFO, "Starting a run with %s unique modules.", count); CLog.logAndDisplay(LogLevel.INFO, "========================================"); } else { CLog.d("ModuleRepo already initialized."); } // 获取本次测试要跑的module的集合 modules = mModuleRepo.getModules(getDevice().getSerialNumber(), mShardIndex); } // clearFilter,就是前面提到的一个代表要删除掉的module,一个代表要添加的额外module mExcludeFilters.clear(); mIncludeFilters.clear(); if (mRetrySessionId != null) { loadRetryCommandLineArgs(mRetrySessionId); } listener = new FailureListener(listener, getDevice(), mBugReportOnFailure, mLogcatOnFailure, mScreenshotOnFailure, mRebootOnFailure, mMaxLogcatBytes); int moduleCount = modules.size(); if (moduleCount == 0) { if (sPreparedLatch != null) { sPreparedLatch.countDown(); } return; } else { int uniqueModuleCount = UniqueModuleCountUtil.countUniqueModules(modules); } if (mRebootBeforeTest) { mDevice.reboot(); } if (mSkipConnectivityCheck) { String clazz = NetworkConnectivityChecker.class.getCanonicalName(); mSystemStatusCheckBlacklist.add(clazz); } boolean isPrepared = true; for (int i = 0; i < moduleCount; i++) { IModuleDef module = modules.get(i); module.setBuild(mBuildHelper.getBuildInfo()); module.setDevice(mDevice); module.setPreparerWhitelist(mPreparerWhitelist); // 开始对每个module设置组件以及device if (mCollectTestsOnly != null) { module.setCollectTestsOnly(mCollectTestsOnly); } isPrepared &= (module.prepare(mSkipPreconditions, mPreconditionArgs)); } if (!isPrepared) { throw new RuntimeException(String.format("Failed preconditions on %s", mDevice.getSerialNumber())); } if (mIsLocalSharding) { try { sPreparedLatch.countDown(); int attempt = 1; while(!sPreparedLatch.await(MINUTES_PER_PREP_ATTEMPT, TimeUnit.MINUTES)) { if (attempt > NUM_PREP_ATTEMPTS || InvocationFailureHandler.hasFailed(mBuildHelper)) { CLog.logAndDisplay(LogLevel.ERROR, "Incorrect preparation detected, exiting test run from %s", mDevice.getSerialNumber()); return; } CLog.logAndDisplay(LogLevel.WARN, "waiting on preconditions"); attempt++; } } catch (InterruptedException e) { throw new RuntimeException(e); } } mModuleRepo.tearDown(); mModuleRepo = null; // 开始执行测试 while (!modules.isEmpty()) { IModuleDef module = modules.poll(); long start = System.currentTimeMillis(); if (mRebootPerModule) { if ("user".equals(mDevice.getProperty("ro.build.type"))) { CLog.e("reboot-per-module should only be used during development, " + "this is a\" user\" build device"); } else { mDevice.reboot(); } } // 运行测试检查 if (checkers != null && !checkers.isEmpty()) { runPreModuleCheck(module.getName(), checkers, mDevice, listener); } IInvocationContext moduleContext = new InvocationContext(); moduleContext.setConfigurationDescriptor(module.getConfigurationDescriptor()); moduleContext.addInvocationAttribute(IModuleDef.MODULE_NAME, module.getName()); moduleContext.addInvocationAttribute(IModuleDef.MODULE_ABI, module.getAbi().getName()); mInvocationContext.setModuleInvocationContext(moduleContext); try { // 执行module module.run(listener); } catch (DeviceUnresponsiveException due) { // being able to catch a DeviceUnresponsiveException here implies that recovery // was successful, and test execution should proceed to next module ByteArrayOutputStream stack = new ByteArrayOutputStream(); due.printStackTrace(new PrintWriter(stack, true)); StreamUtil.close(stack); } finally { mInvocationContext.setModuleInvocationContext(null); } long duration = System.currentTimeMillis() - start; long expected = module.getRuntimeHint(); long delta = Math.abs(duration - expected); // Show warning if delta is more than 10% of expected if (expected > 0 && ((float)delta / (float)expected) > 0.1f) { CLog.logAndDisplay(LogLevel.WARN, "Inaccurate runtime hint for %s, expected %s was %s", module.getId(), TimeUtil.formatElapsedTime(expected), TimeUtil.formatElapsedTime(duration)); } if (checkers != null && !checkers.isEmpty()) { runPostModuleCheck(module.getName(), checkers, mDevice, listener); } module = null; } } catch (FileNotFoundException fnfe) { throw new RuntimeException("Failed to initialize modules", fnfe); }}
可以发现这个run方法中并没有想V1版本的框架一样,根据plan文件去组织case,但是运行命令的时候plan参数还是可用的,这是怎么回事呢?
其实是V1和V2的很重要的一点区别就是在这里:
v1版本的plan文件相当于是一个集合,需要执行哪些测试case呢,就把需要执行的测试case添加到plan中,最后把这个集合中的测试case拿出来执行即可。
v2版本则不然,它默认就把所有的测试case给全部拿到并执行,除非配置了不需要执行哪些case,否则的话默认执行全部的case。
这就是执行cts这个plan的时候还是会跑全部的case的原因了,因为其实不管你在执行的时候plan是谁,都是跑全部的case,如果不想跑全部的case的话,就需要去特殊定制配置文件:比如cts-java.xml
其中配置了<option name="compatibility:include-filter" value="CtsLibcoreTestCases" />
,也就是说通过include-filter以及exclude-filter两个filter去特殊定制指定的plan。
2.4 执行测试
前面已经看到,在CompatibilityTest中执行测试的执行了module.run
,这个方法就是去执行测试了,但是好像跟V1版本不一样啊,V1版本因为所有的case都已经在xml文件中注明了,解析完毕就已经知道要执行的测试case了,所以其实直接就进入了执行命令的步骤了,然而这里还是不一样,在ModuleDef的run方法:
public void run(ITestInvocationListener listener) throws DeviceNotAvailableException { CLog.d("Running module %s", toString()); // Run DynamicConfigPusher setup once more, in case cleaner has previously // removed dynamic config file from the target (see b/32877809) for (ITargetPreparer preparer : mDynamicConfigPreparers) { runPreparerSetup(preparer); } // Setup for (ITargetPreparer preparer : mPreparers) { runPreparerSetup(preparer); } CLog.d("Test: %s", mTest.getClass().getSimpleName()); if (mTest instanceof IAbiReceiver) { ((IAbiReceiver) mTest).setAbi(mAbi); } if (mTest instanceof IBuildReceiver) { ((IBuildReceiver) mTest).setBuild(mBuild); } if (mTest instanceof IDeviceTest) { ((IDeviceTest) mTest).setDevice(mDevice); } IModuleListener moduleListener = new ModuleListener(this, listener); // Guarantee events testRunStarted and testRunEnded in case underlying test runner does not ModuleFinisher moduleFinisher = new ModuleFinisher(moduleListener); mTest.run(moduleFinisher); moduleFinisher.finish(); // Tear down for (ITargetCleaner cleaner : mCleaners) { CLog.d("Cleaner: %s", cleaner.getClass().getSimpleName()); cleaner.tearDown(mDevice, mBuild, null); }}
看这个地方好像有些似曾相识,再看前面的config的配置文件,联想到框架,其实从本质上v1和v2的区别就在这里,v1是通过框架把所有的每一条测试case都拿到,逐个去执行,但是V2则是把每个测试module都作为一个configuration,框架做的就是去拿到测试的所有module,但是每个module的执行还是走了框架,因为每个module现在都被认为是一个configuration了,只需要去逐个执行测试module即可。
4 总结
到这里V2框架也说的差不多了,V2框架介绍没有贴太多的代码,一方面是本身V2框架相V1的组织逻辑就简单一些,另外一方面是因为两者的概念不同,v2把每个module作为一个configuration去处理,各个module之间都是独立的,并不需要像V1框架那样再需要一个xml文件去配置,需要plan文件去做一个集合。
另外VTS其实入口也是CompatibilityConsole,运行方式跟这个一样,包括前面SuiteInfo文件的生成,也是跟CTS如出一辙。
下篇文章介绍下添加case与自定义,以及系列总结。
- CTS测试框架 -- V2版本
- CTS测试框架 -- V1版本
- CTS测试框架 -- 开篇
- CTS测试框架 -- RegexTrie
- CTS测试框架 -- 总结
- CTS测试框架 -- 命令解析
- CTS测试框架 -- 命令调度
- CTS测试框架 -- 命令执行
- CTS测试框架 -- 基础框架Trade-Federation
- CTS测试框架 -- 基础框架启动
- Android兼容性测试框架(CTS)手册
- ANDROID兼容性测试框架(CTS)手册
- Android兼容性测试框架(CTS)手册
- Android兼容性测试框架(CTS)手册
- Android兼容性测试框架(CTS)手册
- Android兼容性测试框架(CTS)手册
- Android兼容性测试框架(CTS)手册
- cts 测试
- CES 之后与你见面,中美自动驾驶技术全明星阵容 | 早鸟票倒计时
- 深度学习的教学和课程,与传统 CS 的教学和课程有什么区别?
- 牛人的大学生活
- Python3
- 22号
- CTS测试框架 -- V2版本
- Failed to introspect annotated methods on class org.springframework.boot.web.support.SpringBootServl
- spring技术详解
- vue build打包之后首页白屏的问题
- ASP.NET Core Web API下事件驱动型架构的实现(一):一个简单的实现
- 闲话权限系统的设计
- svn
- Django源码分析3:处理请求wsgi分析与视图View
- JetClean绿色破解版下载及软件破解教程