悲催的调试ASTER与monkey的经验

    刚工做不久,能力有限,经验也不足,志在工做中成长,与你们分享工做经验,有错误的地方请你们指出! 很是感谢! 
html

    ASTER是台湾0xlab开发的一款开源的自动化测试工具,该工具运行在PC机上,与android设备上的monkey进行通信,通信方式使用socket。固然,ASTER能与android设备通信的前提是adb能正常工做。当ASTER与设备链接上后,会用ddms把设备的屏幕截取过来并基本上实现同步显示,而后测试人员就能够在ASTER上(PC端)添加一系列动做去控制设备,最后得出测试结果。java

    我遇到的问题是,ASTER一直链接不上咱们的设备,从logcat看到的信息是在启动monkey的时候,monkey会随机的挂掉,所谓随机的挂掉是指它不是每次都在相同地方退出。问题可能在于ASTER,也可能在于android设备。个人第一反应是问题出如今咱们的设备,由于别人发布出来的工具在正确性方面有必定的保证。惋惜的是,老大认为问题在于ASTER,硬是要求我去调ASTER。因此我就把ASTER的代码翻了一遍,从它如何识别设备,如何跟设备创建链接,如何启动设备的monkey,如何截图等等,一步一步地调试过来。实话实说,这源码我整整看了一个多星期才有点了解。不过它的源码也很多,源码包下载下来有64M!惋惜悲剧的是,用了这么多时间还没找到问题的所在,确实不淡定啊!
linux

    而后我就跑去看monkey的代码,monkey在启动时,要唤醒系统,因为咱们的power管理系统还没完善,问题有可能出如今这里,因此我就把唤醒的代码屏蔽了,但是ASTER在启动monkey时,monkey仍是会中途退出。没办法,只能继续debug!难道这就是传说中的“A bug a day, keep girls away” ?哎,命苦的程序猿啊!Anyway,生命不息,奋斗不止。在monkey中,我看到它启动了一个socket,看成服务端。原来,ASTER经过adb与设备链接后,会启动monkey看成服务端,而后本身也建立一个客户端的socket去与monkey的socket链接。因而,我就怀疑socket通信有问题,ASTER与monkey使用的socket是java层的。等一下!!Java!!?FT!!哥当初为了摆脱java才转去学嵌入式,如今可好了,又栽在这货手上!!只能死马当活马医了!!硬着头皮补一下java的socket基础知识!写了一个socket通信的测试小程序(程序后面给你们贴上),客户端(PC)能正常发送数据到服务端(设备),排除了socket出现问题的可能性。问题又不在于这,情以何堪,时间都快过去两周了!android

    转去看logcat打印的信息,只但愿能在这找到有用的东西了。发现每次monkey退出时,都会打印一行“untracked pid xx exited”,这行信息是从init进程打印出来的。仍是研究研究init吧!init是android系统的主进程,它管理着系统子进程资源的分配和回收。打印刚刚信息的源码在system/core/init/signal_handler.c,看来跟信号有关,因此调查是什么信号令到monkey退出。后来发现是这个信号SIGHUP,没怎么接触linux内核,不晓得这个信号表示什么意思!网上因该有,度娘和谷哥,本身选吧!最后仍是选择了度娘,谷哥半天刷不开网页啊!!有木有!!!百科是这样解释SIGHUP的。SIGHUP会在如下3种状况下被发送给相应的进程:  shell

一、终端关闭时,该信号被发送到session首进程以及做为job提交的进程(即用 & 符号提交的进程)  小程序

二、session首进程退出时,该信号被发送到该session中的前台进程组中的每个进程  bash

三、若父进程退出致使进程组成为孤儿进程组,且该进程组中有进程处于中止状态(收到SIGSTOP或SIGTSTP信号),该信号会被发送到该进程组中的每个进程。 session

系统对SIGHUP信号的默认处理是终止收到该信号的进程。因此若程序中没有屏蔽该信号,当收到该信号时,进程就会退出。
socket

    总的来讲就是接收到SIGHUP信号的进程就会退出,那么到底是谁给monkey发送了这个信号!在android设备的终端ps一下,可以看见全部进程的pid和父进程的pid,由此发现monkey进程的父进程居然是sh。原来ASTER是经过adb shell启动monkey的,因此就出现了以上情况!好,找到问题所在了,缘由就是monkey还没彻底启动,它的父进程就已经退出了,父进程退出时向它发送了一个可恶的SIGHUP信号,表示说,儿子,来吧,跟老爸走吧!可怜无知的monkey就这样给它老爸陪葬了!如何拯救monkey!?简单,让它忽视老爸的SIGHUP信号就好了,如何忽视?在启动monkey的脚本中加入trap "" HUP。我发现其余的android版本都有这一句话在里面,就咱们的没有,WTF!折腾了两个星期!就是这么一句话引发了血案!或许大多数的android版本加入这一句话就能正常工做了,可是咱们的还不行!贱人就是矫情!!由于咱们的shell版本不支持trap这条指令!更换busybox?kernel团队的人忙的很,哪有时间鸟你!只能本身想办法!原来还有另一个方法能够忽视SIGHUP信号。就是在父进程启动子进程时加入nohup命令!ASTER是在aster/src/com/android/chimpchat/adb/AdbChimpDevice.java里面启动monkey的,找到String command = "monkey --port " + port,把它改为String command = "nohup monkey --port " + port就好了!固然,这样作就有一个弊端,就是必须本身把ASTER的源码下载下来编译!工具

    monkey父进程都已经退出了,为何忽视掉SIGHUP信号后,它就不会退出呢?原来在linux系统中,父进程死去后,该父进程建立的全部未退出的子进程都会变成孤儿进程,init会把它们收为养子!这样,monkey最后的父进程就变成了init!

    通过这么一翻折腾后,ASTER终于跟monkey连上了,看着熟悉的界面,终于能够睡个安稳觉了!



这里贴上文中所提到的测试socket的java小程序:

MonkeyServer.java

import java.io.*; import java.net.*; public class MonkeyServer { private int port=12345; private ServerSocket serverSocket; private Socket socket=null; private BufferedReader input; private PrintWriter output; public MonkeyServer() throws IOException { serverSocket = new ServerSocket(port,0,InetAddress.getLocalHost()); System.out.println("start server..."); } public void service() throws IOException { socket = serverSocket.accept(); System.out.println("New connection accepted " +socket.getInetAddress() + ":" +socket.getPort()); input = new BufferedReader(new InputStreamReader(socket.getInputStream())); output = new PrintWriter(socket.getOutputStream(), true); String read = input.readLine(); if(read!=null){ System.out.println(read); } socket.close(); } public static void main(String args[])throws Exception { MonkeyServer server=new MonkeyServer(); server.service(); } } 

MonkeyClient.java

import java.net.*; import java.io.*; public class MonkeyClient { public static void main(String args[])throws Exception{ String host="127.0.0.1"; int port=12345; Socket socket=null; BufferedWriter output; BufferedReader input; String line; socket = new Socket(InetAddress.getByName(host), port); System.out.println("start client..."); if (socket!=null){ System.out.println("connection successful..."); } output = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); input = new BufferedReader(new InputStreamReader(socket.getInputStream())); output.write("Client has sent a message to server...".trim()+"\n"); Thread.sleep(1000); output.close(); input.close(); } }

MonkeyServer是运行在设备上的,除了javac编译外,还要dx一下,我写了个shell脚原本处理dx这一步:

#!/bin/bash
for j in `ls *.java`
do
        javac $j
done

if [ ! -e classes ] ; then
        mkdir -p classes
fi

cp *.class classes/
dx --dex --output=Main.jar classes

MonkeyClient运行在PC上,按照通常的java文件编译就能执行了!

先在设备上运行MonkeyServer,而后在PC运行MonkeyClient,设备端会打印“Client has sent a message to server...”。至于服务端怎么向client端发送数据,暂时还没去研究!呵呵!