[Android]CTS Fail项分析方法
常规步骤为:
- 通跑某一模块;
- 查看测试结果;
- 对Fail项进行单跑;
- 辅助信息抓取;
通跑某一模块
以网络模块为例,通过如下测试项为例,在cts-tradefed
交互界面输入list m
可查看与之对应的待测模块(modules):
cts-tf > list m
...
CtsNetTestCases
...
从中可以看到,CtsNetTestCases
明显是网络相关的测试模块,因此执行:
cts-tf > run cts -m CtsNetTestCases
可开始该模块的完整测试;
查看测试结果
测试结果存放目录:${cts_root}/results/
文件夹命名格式:yyyy.MM.dd_HH.mm.ss
(例如2021.12.28_15.18.09
)
如果是最近一次测试结果,可直接在这里找到:${cts_root}/results/latest
查看Fail项可直接使用浏览器打开目录下的test_result_failures.html
文件即可;
对Fail项进行单跑
还是以CtsNetTestCases的测试结果为例,比如在test_result_failures.html
中显示,如下这项测试Fail:
android.net.cts.ConnectivityManagerTest#testConnectivityChanged_manifestRequestOnlyPreN_shouldReceiveIntent
则可在cts-tradefed
交互界面输入时添加-t
参数,接上上面的测试项名称,对单项进行单跑:
cts-tf > run cts -m CtsNetTestCases -t android.net.cts.ConnectivityManagerTest#testConnectivityChanged_manifestRequestOnlyPreN_shouldReceiveIntent
如果是网络不稳定导致的Fail,多单跑几次,一般可以PASS;
但如果遇到逻辑上的Fail项,且从日志中也无法确认原因的,则想办法添加辅助信息,并抓取后分析了;
辅助信息抓取
以上面提到的android.net.cts.ConnectivityManagerTest#testConnectivityChanged_manifestRequestOnlyPreN_shouldReceiveIntent
测试项为例,通过测试项名称,可以快速定位到源码中对应的代码:
类名: android.net.cts.ConnectivityManagerTest
方法名:testConnectivityChanged_manifestRequestOnlyPreN_shouldReceiveIntent
注意:源码中的代码并不一定与当前CTS版本完全一致,只能作为参考,若需要获取完全对应的,可下载AOSP源码,并checkout到对应的分支(tag)后,使用同样方法查看代码;
通过查找,可知ConnectivityManagerTest类在这里:
cts/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java
结合cts-tradefed
交互界面的异常栈信息:
12-29 10:03:34 I/ConsoleReporter: [1/1 arm64-v8a CtsNetTestCases 1234567] android.net.cts.ConnectivityManagerTest#testConnectivityChanged_manifestRequestOnlyPreN_shouldReceiveIntent fail: junit.framework.AssertionFailedError
at junit.framework.Assert.fail(Assert.java:48)
at junit.framework.Assert.assertTrue(Assert.java:20)
at junit.framework.Assert.assertTrue(Assert.java:27)
at android.net.cts.ConnectivityManagerTest.testHttpRequest(ConnectivityManagerTest.java:724)
at android.net.cts.ConnectivityManagerTest.disconnectFromWifi(ConnectivityManagerTest.java:744)
at android.net.cts.ConnectivityManagerTest.toggleWifi(ConnectivityManagerTest.java:663)
at android.net.cts.ConnectivityManagerTest.testConnectivityChanged_manifestRequestOnlyPreN_shouldReceiveIntent(ConnectivityManagerTest.java:636)
at java.lang.reflect.Method.invoke(Native Method)
at junit.framework.TestCase.runTest(TestCase.java:168)
at junit.framework.TestCase.runBare(TestCase.java:134)
at junit.framework.TestResult$1.protect(TestResult.java:115)
at androidx.test.internal.runner.junit3.AndroidTestResult.runProtected(AndroidTestResult.java:73)
at junit.framework.TestResult.run(TestResult.java:118)
at androidx.test.internal.runner.junit3.AndroidTestResult.run(AndroidTestResult.java:51)
at junit.framework.TestCase.run(TestCase.java:124)
at androidx.test.internal.runner.junit3.NonLeakyTestSuite$NonLeakyTest.run(NonLeakyTestSuite.java:62)
at androidx.test.internal.runner.junit3.AndroidTestSuite$2.run(AndroidTestSuite.java:101)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:458)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.lang.Thread.run(Thread.java:764)
可知问题发生在android.net.cts.ConnectivityManagerTest.testHttpRequest
(同理,行数参考意义不大,重点关注方法调用即可)
从而快速定位到Fail的原因:
private void testHttpRequest(Socket s) throws IOException {
OutputStream out = s.getOutputStream();
InputStream in = s.getInputStream();
final byte[] requestBytes = HTTP_REQUEST.getBytes("UTF-8");
byte[] responseBytes = new byte[4096];
out.write(requestBytes);
in.read(responseBytes);
assertTrue(new String(responseBytes, "UTF-8").startsWith("HTTP/1.0 204 No Content\r\n"));
}
可见此处是在打开Socket并尝试进行通信,异常处为方法最后一行,即:判断返回的字符串是否与预期一致。
分析到了这里,我们可以确定与网络环境有较大因素,但是如果要确认assertTrue
为何不满足,即返回值到底是什么的话,那么我们就需要添加一些日志了;
由于上面提到的原因,CTS测试跑的时候,这段代码是通过已经打好包的APK形式提供的,我们并没有办法对其进行修改;但是反过来想,我们是不是可以用自己编译好的APK,替换掉CTS中的APK呢?
那么这就是一个方法:
- 从AOSP下载对应版本的代码(如果行数与当前基线差异不大,也可以先用当前基线尝试);
- 添加必要信息,例如:
private void testHttpRequest(Socket s) throws IOException {
OutputStream out = s.getOutputStream();
InputStream in = s.getInputStream();
final byte[] requestBytes = HTTP_REQUEST.getBytes("UTF-8");
byte[] responseBytes = new byte[4096];
//添加日志输出完整请求URL
Log.i(TAG, "HTTP_REQUEST = " + HTTP_REQUEST);
out.write(requestBytes);
in.read(responseBytes);
//添加日志输出返回结果
String formattedResponse = new String(responseBytes, "UTF-8");
Log.i(TAG, "responseBytes = " + formattedResponse);
assertTrue(formattedResponse.startsWith("HTTP/1.0 204 No Content\r\n"));
}
- 编译模块,模块名查找方式这里不做介绍,此处为
make CtsNetTestCases
:
$ make CtsNetTestCases
编译完成后生成的产物存放在这里:
out/target/product/${project}/testcases/CtsNetTestCases/(arm64|arm)/CtsNetTestCases.apk
备份CTS包中提供的APK:
$ mv ./testcases/CtsNetTestCases.apk ./testcases/CtsNetTestCases_apk
将编译出的APK放入:
$ cp ~/aosp/out/target/product/${project}/testcases/CtsNetTestCases/(arm64|arm)/CtsNetTestCases.apk ./testcases/CtsNetTestCases.apk
- 然后再进行一次单跑,同时抓取logcat日志:
12-29 11:13:40.348 I/ConnectivityManagerTest(26037): Network type: 1 state: CONNECTED
12-29 11:13:40.390 I/ConnectivityManagerTest(26037): HTTP_REQUEST = GET /generate_204 HTTP/1.0
12-29 11:13:40.390 I/ConnectivityManagerTest(26037): Host: connectivitycheck.gstatic.com
12-29 11:13:40.390 I/ConnectivityManagerTest(26037): Connection: keep-alive
12-29 11:13:40.390 I/ConnectivityManagerTest(26037):
12-29 11:13:40.431 I/ConnectivityManagerTest(26037): responseBytes = HTTP/1.0 403 Forbidden
12-29 11:13:40.431 I/ConnectivityManagerTest(26037): Server: bfe
12-29 11:13:40.431 I/ConnectivityManagerTest(26037): Date: Wed, 29 Dec 2021 03:13:40 GMT
12-29 11:13:40.431 I/ConnectivityManagerTest(26037): Content-Length: 0
12-29 11:13:40.431 I/ConnectivityManagerTest(26037): Content-Type: text/plain; charset=utf-8
12-29 11:13:40.431 I/ConnectivityManagerTest(26037):
12-29 11:13:40.431 I/ConnectivityManagerTest(26037): ...
12-29 11:13:40.433 I/TestRunner(26037): failed: testConnectivityChanged_manifestRequestOnlyPreN_shouldReceiveIntent(android.net.cts.ConnectivityManagerTest)
12-29 11:13:40.433 I/TestRunner(26037): at android.net.cts.ConnectivityManagerTest.testHttpRequest(ConnectivityManagerTest.java:725)
12-29 11:13:40.433 I/TestRunner(26037): at android.net.cts.ConnectivityManagerTest.disconnectFromWifi(ConnectivityManagerTest.java:745)
12-29 11:13:40.433 I/TestRunner(26037): at android.net.cts.ConnectivityManagerTest.toggleWifi(ConnectivityManagerTest.java:661)
12-29 11:13:40.433 I/TestRunner(26037): at android.net.cts.ConnectivityManagerTest.testConnectivityChanged_manifestRequestOnlyPreN_shouldReceiveIntent(ConnectivityManagerTest.java:634)
可见此处请求返回码期望为204,返回头期望为"HTTP/1.0 204 No Content"
,但实际返回"HTTP/1.0 403 Forbidden"
导致403的原因很多,这个就是接下来具体问题具体分析的地方了,这里只放出针对此题的结论——路由器端DNS配置不合理,具体过程就不再赘述了。