当前位置

博客

testlink PHP Parse error: syntax error, unexpected T_PAAMAYIM_NEKUDOTAYIM in /var/www/html/testlink/lib/functions/tlIssueTracke

今天在 CentOS 上装一个测试用例管理统计工具 testlink-1.9.5,安装完之后第一次登录在 Firefox 下除了 My Settings 和 Logout 这两个链接能看到,下面全是白的,到 IE 里登录,还多出了 500 的错误提示。

之前在 Windows 上安装同样版本的没有这个问题。

去 /var/log/httpd/error_log 里看,报了一个错:
[error] [client 192.168.XXX.XXX] PHP Parse error: syntax error, unexpected T_PAAMAYIM_NEKUDOTAYIM in /var/www/html/testlink/lib/functions/tlIssueTracker.class.php on line 555, referer: http://192.168.XXX.XXX/testlink/lib/general/mainPage.php

搜了一下,解决方法如下:

vi /var/www/html/testlink/lib/functions/tlIssueTracker.class.php
555 gg
把 $dummy = $impl::checkEnv(); 改成 $dummy = $impl->checkEnv();

具体原因请点击 http://stackoverflow.com/questions/1966010/what-does-this-mean-parse-error-syntax-error-unexpected-t-paamayim-nekudotay

说一句憋了很久的话:stackoverflow 真是一个神奇的网站啊!

Topic: 

一次 5 Whys 应用

为什么提供定期更改密码的功能? 因为竞品有这个功能, IT管理员需要这个, 否则无法签单
为什么IT管理员需要这个功能? 因为用户的密码容易被盗; 而且假如用户的密码已经被盗, 定期更改密码会让小偷停止盗窃
现在变成了两个问题: 为什么用户的密码会被盗? 为什么不能发现小偷现在正在盗窃?
因为密码强度不够; 因为用户还在使用不安全的HTTP/POP/IMAP/SMTP, 我们没有强制SSL; 因为用户的PC上有木马
因为我们没有完善的审计机制发现小偷

至此问题基本清晰了

然后在知乎上搜索到了问题:定期更换密码真的更安全吗?
里面确实提到了一种可能,如果被拖库了,哪怕是hash被拖了, 那定期更改密码是有意义的

但真正的安全可能还是尽早发现小偷,而不是定期更改密码。只关注在后一个方面只是掩盖了真正的问题所在。

Topic: 

说说几个企业网盘的Player

从产品看, 走得最好的是够快. 希望我们能在年底的时候, 在产品传递的理念方面能够达到甚至超越.

从市场看, 近期金山没有什么声音了?? 联想还是一如既往, OATOS 在SEO和微博上做的不错, 微世界也很有意思.

其他的比如同步盘, 坚果云, 115. 暂时没有更多的信息来分析.

Topic: 

随便写写

按照云计算的定义, 按需使用弹性伸缩, 国外 SaaS 的服务费用似乎都是以月结为主. 或者至少, 是有按月计费的标准. 但在中国的企业邮箱行业, 都是按年进行计费的. 这里可能包括两个原因: 其一是留给代理商空间和时间, 其二是中国的企业并不那么快, IT部署和应用的周期长.

qingcloud 据说是按秒计费. 我不觉得这个在商业上有很大的意义

有人问我, 假如360做免费企业网盘将如何应对. 当时我的回答是, 尽快建立起生态体系. 免费的东西, 建立生态体系是困难的, 尤其是在企业服务领域. 免费的安全卫士, 它的生态之一是流量变卖, 挺难想像这个模式能复制到我们现在面对的客户群体领域.

Box 最近的动作不小, 包括在线协作编辑, 云安全, meta信息同步. 产品差距很大, 好东西还要一步步落地.

中国目前的市场上, 很多东西还是空白, 到处都是产品机会, 不好的一面是所有的东西都需要自己做, 否则用户体验是不完整的.

Topic: 

开启真机的View Server引入HierarchyViewer/By写monkeyrunner自动化测试脚本

其实相关文章网上也有不少了,不过在真机上开启View Server的中文文章好像只有一篇,前段时间按照这篇文章的内容,并结合英文源文去hack我的Nexus S(4.1.2)也走了一点弯路。现在总结一下我的步骤(其实有相当一部分拷贝了这篇,衷心感谢原文作者)。并写点在开启View Server之后monkeyrunner的脚本。

先交待一下背景,monkeyrunner作为自动化测试Android系统工具在某些情况下还是比Robotium易用一些,不过monkeryrunner判断测试结果是否正确的方法是把实际测试中的截屏与预先截好的正确的屏跟做比对!这个办法不够灵活。假如返回结果会显示在一个文本框中,我从文本框里取出字符串能直接跟预期的字符串比较,这样就省事多了。

Android SDK自带一个工具叫做monitor,它里面的Hierarchy Viewer可以看到app的UI结构、控件属性等等。monkeyrunner有一个类By,通过By可以在代码中根据控件ID定位到该控件从而写更有针对性代码(比如点击按钮、比如获取文本框中的字符串)。

可是出于安全考虑,Hierarchy Viewer只能连接Android开发版手机或是模拟器。只有当设备或模拟器上启动一个叫做View Server的服务,Hierarchy Viewer才能与其进行socket通信,才能看到app的“View”。而绝大多数商业手机是无法开启View Server的,所以Hierarchy Viewer也就无法连接到普通的商业手机。而By又依赖于Hierarchy Viewer,所以如果想在普通的商业手机上通过控件ID去做一些操作,连接模拟器运行通过的脚本连接真机运行是会抛错的。

不过小米手机是个例外,通过执行如下命令可以轻易开启它的View Server:
adb shell service call window 1 i32 4939
然后通过执行如下命令判断是否开启View Server:
adb shell service call window 3
若返回值是:Result: Parcel(00000000 00000001 '........') 说明View Server处于开启状态
若返回值是:Result: Parcel(00000000 00000000 '........') 说明View Server处于关闭状态
如果想关闭View Server执行如下命令:
adb shell service call window 2 i32 4939

除了小米手机之外,别的手机能不能开启View Server?经过一番调查和实践,其实只要是root,并且装有busybox的手机,通过修改手机/system/framework中的某个文件,就能够开启View Server。

下面就是我总结的开启View Server的步骤(提醒:如果照我的步骤导致你的手机变砖,本人概不负责):

1.准备工作

a.解锁手机,刷入第三方Recovery。这一步不是开启View Server必须要做的。但是万一手机通过正常方式启动不了了,可以通过第三方Recovery里的restore功能恢复手机系统,当然前提是在修改系统文件前先通过backup功能做一个备份。

b.root手机。root的作用是获取对手机系统文件的读写权限,这样你就可以修改那个不允许打开View Server的系统文件了。

c.在手机中安装BusyBox应用。我们在给自己生成的odex文件签名时会用到它。

d.用第三方Recovery备份手机系统。这一步不是必须步骤。

e.在D盘下创建hack文件夹,下载baksmali-1.4.2.jarsmali-1.4.2.jarzip.exedexopt-wrapper这些后面要用到的工具并保存在D:\hack下面。

2.开始hack (再次提醒:请确保把下面每个步骤所有文字全部仔细看完后再开始操作)

a.将手机通过USB连接PC,确保adb服务运行正常。

b.备份手机上/system/framework/中的文件至PC。备份的时候请确保PC上保存备份文件的文件夹结构与手机中的/system/framework相同,比如先在D盘上创建hack\system\framework的文件夹结构,然后运行
adb pull /system/framework D:\hack\system\framework

c.进入adb shell,输出BOOTCLASSPATH:
echo $BOOTCLASSPATH
然后将输出的路径先暂时存起来。我的是(每个机器的$BOOTCLASSPATH都不一定一样):
/system/framework/core.jar:/system/framework/core-junit.jar:/system/framework/bouncycastle.jar:/system/framework/ext.jar:/system/framework/framework.jar:/system/framework/android.policy.jar:/system/framework/services.jar:/system/framework/apache-xml.jar

d.在命令行窗口中进入D:\hack,然后运行baksmali反编译\system\framework下的services.odex文件:
java -jar baksmali-1.4.2.jar –x -a <api level> –c <local bootclasspath> system\framework\services.odex
参数解释:https://code.google.com/p/smali/wiki/DeodexInstructions
想特别说明的是“-a”后跟的数字,表示你系统的API Level(与你的系统版本有关)。系统版本和API Level的对照关系如下:

这一步在我的机器(version 4.1.2)上的命令是:
java -jar baksmali-1.4.2.jar -x -a 16 -c system\framework\core.jar:system\framework\core-junit.jar:system\framework\bouncycastle.jar:system\framework\ext.jar:system\framework\framework.jar:system\framework\android.policy.jar:system\framework\services.jar:system\framework\apache-xml.jar system\framework\services.odex
此步成功的话,在D:\hack下,会有个out文件夹生成。
注意,-c后面跟的是本地备份的jar包路径,把上一步暂存的路径中system前面的“/”去掉,把其它的“/”换成“\”。
这里顺便解释一下dex文件、odex文件和smali文件:

  • dex文件:dex是Dalvik VM executes的全称,即Android Dalvik执行程序,并非Java的字节码而是Dalvik字节码,16进制机器指令。
  • odex文件:将dex文件依据具体机型而优化,形成的optimized dex文件,提高软件运行速度,减少软件运行时对RAM的占用。
  • smali文件:将dex文件变为可读易懂的代码形式,反编译出文件的一般格式。

e.用Eclipse打开out\com\android\server\wm\WindowManagerService.smali文件查找.method private isSystemSecure()Z这个函数,在这段代码的倒数7,8行“:goto_21”和“return v0”之间加入“const/4 v0, 0x0”一行。
.method private isSystemSecure()Z函数最后几行变为:
if-eqz v0, :cond_22

const/4 v0, 0x1

:goto_21
const/4 v0, 0x0
return v0

:cond_22
const/4 v0, 0x0

goto :goto_21
.end method

f.现在运行smali,重新编译:
java -jar smali-1.4.2.jar -o classes.dex out
这时候,应该在D:\hack文件夹中出现了classes.dex文件

g.用zip工具把生成的classes.dex打成jar包
zip.exe services_hacked.jar classes.dex

h.进入adb shell,输入su然后回车,获得ROOT权限

i.接着输入mount | grep /system查看哪个分区挂载了/system,例如我的是:
/dev/block/platform/s3c-sdhci.0/by-name/system /system ext4 ro,relatime,barrier=1,data=ordered 0 0

j.接着输入以下命令重新挂载/system,并更改/system权限(请将“/dev/block/platform/s3c-sdhci.0/by-name/system”替换成你的/system挂载分区):
mount -o remount /dev/block/platform/s3c-sdhci.0/by-name/system /system
这一步的作用是为了后面的p步能够将/system/framework里的services.odex替换掉。

k.再次输入mount | grep /system 确认/system已经改成可写的了(以前是“ro”,现在是“rw”)

l.将services_hacked.jar和dexopt-wrapper复制到手机的/data/local/tmp文件夹中
adb push D:\hack\services_hacked.jar /data/local/tmp
adb push D:\hack\dexopt-wrapper /data/local/tmp

m.进入adb shell,输入su后,将dexopt-wrapper的权限改为777
chmod 777 /data/local/tmp/dexopt-wrapper

n.cd到/data/local/tmp文件夹下,运行:
./dexopt-wrapper ./services_hacked.jar ./services_hacked.odex <c步暂存的bootclasspath,但要排除掉“:/system/framework/services.jar”>
这一步在我的机器上的命令是:
./dexopt-wrapper ./services_hacked.jar ./services_hacked.odex /system/framework/core.jar:/system/framework/core-junit.jar:/system/framework/bouncycastle.jar:/system/framework/ext.jar:/system/framework/framework.jar:/system/framework/android.policy.jar:/system/framework/apache-xml.jar
这样,便在/data/local/tmp文件夹中生成了services_hacked.odex这个文件

o.给我们自己生成的services_hacked.odex签名:
busybox dd if=/system/framework/services.odex of=/data/local/tmp/services_hacked.odex bs=1 count=20 skip=52 seek=52 conv=notrunc
参数解释:

  • if - input file
  • of - output file
  • bs - block size (1 byte)
  • count - number of blocks
  • skip - input file offset
  • seek - output file offset
  • conv=notrunc - don’t truncate the output file.

p.将/system/framework里的services.odex替换成我们自己制作的services_hacked.odex
dd if=/data/local/tmp/services_hacked.odex of=/system/framework/services.odex
稍过一会,手机就会自动重启

q.成功重启后,用以下命令开启View Server:
adb shell service call window 1 i32 4939

r.用以下命令查看View Server是否开启:
adb shell service call window 3
返回的值若是Result: Parcel(00000000 00000001 '........'),那么你就成功开启View Server了!

3.灾难恢复

如果你不幸在上一节p步手机重启后进不了HOME,一直处在bootloop状态,不要用拔电池的方式重启手机。这个时候你已经可以使用adb了,在命令行窗口里执行:
adb push D:\hack\system\framework\services.odex /system/framework/services.odex
就可以把之前备份的services.odex再拷回去,这样手机就能进入HOME了。

如果你十分不小心重启了手机,这时候你会发现既进不了HOME也使用不了adb,那就只能进入第三方的Recovery,用之前的备份去恢复手机系统了。

下面的是如何利用HierarchyViewer和By这两个类去灵活完成monkeyrunner的脚本(monkeyrunner的其它基本代码在这里不赘述)。

先假设一个场景,有一个app,打开后有一个按钮,点击这个按钮后,正常情况下会在下面的文本框里返回“ok”。我们需要用代码实现点击这个按钮,然后取得文本框中的返回值与预期结果“ok”做比对。

我们通过前面介绍的Hierarchy Viewer看到app里按钮的ID是“id/button”,文本框的ID是“id/output”。

为了通过控件ID操作手机,我们需要在代码开头import这两个类:
from com.android.monkeyrunner.easy import By
from com.android.chimpchat.hierarchyviewer import HierarchyViewer

然后用下面的代码获得按钮对象:
hierarchyViewer = device.getHierarchyViewer()
viewNodeButton = hierarchyViewer.findViewById("id/button")

用下面的代码获得按钮的中心坐标:
pointButton = HierarchyViewer.getAbsoluteCenterOfView(viewNodeButton)

这个时候pointButton.x是按钮的中心点横坐标,pointButton.y是按钮的中心点纵坐标,可是有了这两个坐标,我们还不能直接用device.touch(x, y, "DOWN_AND_UP")的方式去点这个按钮,因为这个坐标是以开发设计app时手机的屏幕分辨率为基准的,所以我们还需要换算一下才知道在目前的测试手机上按钮的中心坐标是什么。

先通过Hierarchy Viewer查到设计时的屏幕分辨率(比方说是320和533),并在代码中定义:
originalResolutionWidth = 320
originalResolutionHeight = 533

再通过MonkeyDevice的API获得目前的测试手机的屏幕分辨率:
actualResolutionWidth = int(device.getProperty("display.width"))
actualResolutionHeight = int(device.getProperty("display.height"))

然后用下面代码得到目的手机分辨率与开发设计时的分辨率的比值:
xRatio = float(actualResolutionWidth) / originalResolutionWidth
yRatio = float(actualResolutionHeight) / originalResolutionHeight

有了xRatio和yRatio,我们用下面的代码轻而易举就能点到正确的坐标上了:
device.touch(int(pointRegister.x * xRatio), int(pointRegister.y * yRatio), "DOWN_AND_UP")

按钮点下后,我们需要用下面代码获取文本框里的返回值:
viewNodeOutput = hierarchyViewer.findViewById("id/output")
output = viewNodeOutput.namedProperties.get("text:mText").value

这样我们就能用output与预期的“ok”做比对了:
if output == "ok":
    print "success"
else:
    print "fail"

最后加一句关于unittest的,如果想按照python的unittest框架写测试用例,会用到
self.assertEquals(expectedString, actualString)
这样的语句,如果是中文操作系统,跑的时候有可能会出现LookupError: unknown encoding gbk这样的错误,请参考Android 自动化测试学习笔记里面提供的方法解决。

更新20130912:
如果要点击Menu里的Label,会发现所有的id名都一样。这个时候怎么办?也许可以用device.press('KEYCODE_DPAD_UP/DOWN/LEFT/RIGHT')的方法来导航到你需要点击的Label,不过我没有试过。
第三方的包AndroidViewClient,可以通过Label上的Text定位到你想点击的Label。
1.把二进制的jar下载下来并放到sdk\tools\lib下
2.在py文件里from com.dtmilano.android.viewclient import ViewClient
3.然后device, serialno = ViewClient.connectToDeviceOrExit(),启动一个activity,用viewclient = ViewClient(device, serialno)和viewclient.dump()可以拿到所有的控件,然后通过Text就能找到需要的控件了。具体请参考http://blog.csdn.net/jiguanghoverli/article/details/10189401https://github.com/dtmilano/AndroidViewClient/issues/22
如果在运行过程中看到Exception: adb="adb.exe" is not executable. Did you forget to set ANDROID_HOME in the environment?这种错误,把adb.exe放到C:\Windows\system32\下面。
另外,引入这个第三方包还有一个好处是,在测试某些app时不用考虑分辨率的问题了(目前我碰到的是如果点击某个app的menu里的label时不需要考虑分辨率,没有调查到底是因为menu的原因,还是不同的app的开发机制原因)。

更新20130913:
在Windows中文系统下,即使按正文中链接里的办法解决了LookupError: unknown encoding gbk这样的错误,但碰到真正的中文(如果不“解决”,就算assert的是英文,也会报上面的错误)还是会报错,如AssertionError: '\xe5\x9f\x8e\xe5\xb8\x82' != u'\u57ce\u5e02',这时需要把被比较的字符串encode("UTF-8")一下,具体请参考http://1.vb.blog.163.com/blog/static/104546220071113105047729/

Topic: 

企业云存储和企业网盘

今天看到, 印象笔记面对中国, 推出了学校版. 我思维比较发散, 立刻就想到了曾经的 ChinaRen 幼儿园. 现在看来, 这个可能是搜狐最接地气的互动产品之一, 可惜夭折.

潜意识里还是比较艳羡 Evernote 的产品形态, 相比较于网盘, 它似乎离用户的业务更近一些, 更容易找到卖点. 网盘现在面对客户, 真是满腹衷言, 却不知从何说起

想了又想, 还是归结到产品不成熟. 不是说产品不好, 或者功能不全; 而是我们自己的定位还很摇摆, 我们设想的, 和客户目前心理上能接受的, 差别有点大.

以后会多思考多写关于企业网盘的 blog

Topic: 

关于消费者邮箱使用的想法

从注册的角度来讲, 使用 QQ/Weibo 等第三方认证服务是符合逻辑的.

但是从客服的角度来讲, 让用户留下一个邮件地址, 或者留下一个电话号码也是符合逻辑的.

邮箱理应成为一个很好的客服平台(当然, 某种程度上客服也是最好的营销)

我们 SendCloud 在强调触发邮之余, 也应该宣传Email对于客服的意义. 触发邮不是这件事的目的, 共同为用户提供好的服务才是我们的目的.

以前可能因为社会习惯, 因为企业的意识, 因为缺乏好用的工具, 等等...我们对邮箱的客服作用发掘得不够. 更多的把邮箱当成了一个营销平台. 希望透过我们的努力, 可以改变这一点, 或者至少可以说我们为改变这一点提供基础服务.

目前 SendCloud 的用户里面, 应该有不少是把 Email 作为服务的一部分或者延伸来使用. 好的案例我们必须进行收集和推广.

换个角度, 为什么中国的在线 CRM 没有做起来? 再说说更近的例子, 微信的服务号最终会是个什么模式?

我觉得在从帐号服务, 到 EDM 之间, 应该还是有一个 CRM 的产品空间. 跨越 CRM 去做邮件营销, 尤其是意识上的跨越, 一定会出问题. 我们做触发邮也不会真正成功

西藏第九天

本来是波澜不惊的一天因为北京的雷雨天气毁了。返程在成都中转时被迫滞留。我打趣老妈说:您这趟旅游赚了,我做了这么多次飞机,这不过是第二回安排住宿,您头一趟坐飞机旅游,就有这样的体验。

这天就是安排了上午布达拉宫,下午回北京,结果。。。

Topic: 

西藏第八天

一大早取到了布宫的购票证。每人限登记4张身份证,还必须包括排队者自己的身份证。所以最后持购票证去买票还很麻烦,因为我爸的身份证登记在另外一拨委托人的购票证上。。。

然后出发去西藏博物馆,强烈推荐。如果旅游前没有做过西藏地理历史宗教文化风俗功课的,最好一到拉萨就来这里补课。

中午吃了五味轩火锅,是我在拉萨见到的第一家接受刷卡的餐馆,就凭这个也值得推荐。

下午去了色拉寺——总不能来西藏九天,三大寺一个也不去吧。可惜下午寺院大部分参观点都关闭,非常遗憾,计划下次来拉萨就花三天时间一个个参观过去。

幸运的是赶上了色拉寺的辩经时间。据说这里全盛时期有8000名僧人,而哲蚌寺有12000名僧人。。。我想这可能算是最大规模的神学院了吧。亲身走一遭,才体会三大寺之“大”

Topic: 

西藏第七天

按照司机的吩咐,我们六点半就在酒店门口等候,但小面包直到7点才赶到。因为它也是在各个等候点接满一车人才出发。小小一辆面包总共11个乘客,除去我们剩下六人里就是三拨人。

最后有乘客还提出要去吃早饭,反正磨磨蹭蹭8点才算正式上路。经过羊八井时俺还看到了启孜峰,然后是念青唐古拉峰的观景处,大家下来一通拍照。

经过那根拉山口,很欣慰邱可心没有表现出任何不适。远眺纳木错,又是拍照。

到了纳木错湖边,我是有些小小的失望。和四年前相比,游客太多了!而且湖边招待游客的藏式大帐篷变成了一排排低矮的简易房。整个景区感觉变得俗气。

回拉萨的路上,碰到两次查身份证。不知道是常年如此,还是雪顿节的原因

找了个代排队的,把身份证交给她彻夜排队去。我和老爸一起喝了约5两青稞酒 庆祝高原之行成功

Topic: 
订阅 RSS - 博客