2013/08/19

Automated GUI testing of Linux Application




前陣子在用 Ubuntu 開機光碟自動化時,查了一下要如何用程式來控制 UI,在 Stackoverflow 上查到了一篇文章,原文節錄如下:


There are two different kind of tools that I know:

Based on the accessibility layer: LDTP, dogtail, strongwind
Based on image recognition: sikuli, xpresser


試用了其中的 LDTP 發覺十分好用,今天便來介紹這個工具

安裝


sudo apt-get install python-ldtp



測試一下吧!


在開始之前,想了解更多一定要參考一下這份 LDTP Tutorial,裡頭的例子是很好的入門。

開始使用 ldtp,由於是它是藉著 a11y 來控制 UI,所要先將 a11y enable

$ gsettings set org.gnome.desktop.interface toolkit-accessibility true


以下例子會用到的 ldtp functions 有

  • getwindowlist
  • getobjectlist
  • getobjectinfo
  • getobjectproperty
  • selectmenuitem

取得目前開啟的視窗

>>> from ldtp import *
>>> getwindowlist()
['frmDesktop', 'frmHome', 'frmmedia', 'dlg0', 'dlg1', 'dlg2', 'dlg3', 'dlg4', 'dlg5', 'dlg6', 'dlg7', 'dlg8', 'dlg9', 'dlg10', 'dlg11', 'dlg12', 'dlg13', 'dlgQuestion', u'frmBlogger\uff1aDoroOneTwoThree-\u4fee\u6539\u6587\u7ae0-MozillaFirefox', 'dlgOracleVMVirtualBoxManager', 'frmSystemSettings', 'frmNotes-Zim', 'frm/bin/bash', 'frmldtp-tutorial.pdf', 'frmutils.py(~/src/gnome/ldtp-2.3.1/ldtpd)-GVIM', 'frmgedit', 'pnl0', 'pnl1', 'pnl2', 'pnl3', 'pnl4', 'pnl5', 'pnl6', 'pnl7']


找一下 gedit 裡有那些 menu 可以按

>>> [i for i in getobjectlist('*gedit*') if i.startswith('mnu')]
['mnuFile', 'mnuMakefile', 'mnuObjective-C', 'mnuEiffel', 'mnuMarkdown', 'mnuHighlightMode', 'mnuDPatch', 'mnuEmpty', 'mnuGraphvizDot', 'mnuDTD', 'mnuOpal', 'mnuEmpty6', 'mnuEmpty7', 'mnuEmpty4', 'mnuEmpty5', 'mnuEmpty2', 'mnuEmpty3', 'mnuView', 'mnuEmpty1', 'mnuEmpty8', 'mnuEmpty9', 'mnuGtkRC', 'mnuDiff', 'mnuQuit', 'mnugtk-doc', 'mnuSPARQL', 'mnuCloseAll', 'mnuOctave', 'mnuBoo', 'mnuRPMspec', 'mnuObjective-J', 'mnuBottomPanel', 'mnuSystemVerilog', 'mnuClearHighlight', 'mnuPreviousTabGroup', 'mnuawk', 'mnuDefaults', 'mnuEmpty10', 'mnuEmpty11', 'mnuEmpty12', 'mnuSelectAll', 'mnuTcl', 'mnuPlainText', 'mnudesktop', 'mnuScripts', 'mnuCobol', 'mnuAbout', 'mnuCopy', 'mnuVala', 'mnuTranslateThisApplication', 'mnuToolbar', 'mnuJSON', 'mnuCut', 'mnuPrint', 'mnuPascal', 'mnuDOSBatch', 'mnuLiterateHaskell', 'mnu168', 'mnuAutomake', 'mnuChangeLog', 'mnuHaddock', 'mnuini', 'mnuASP', 'mnuSaveAll', 'mnuGotoLine', 'mnuStatusbar', 'mnuImageJ', 'mnuF#', 'mnuPython', 'mnuRedo', 'mnuDelete', 'mnuForth', 'mnuPrintPreview', 'mnuNewTabGroup', 'mnuPreviousDocument', 'mnuOCL', 'mnuBibTeX', 'mnuOpenCL', 'mnuSQL', 'mnuReplace', 'mnuHighlightMisspelledWords', 'mnu1atxt', 'mnuObjectiveCaml', 'mnuHaskell', 'mnuGDBLog', 'mnuFind', 'mnuC#', 'mnuNextTabGroup', 'mnuOpenGLShadingLanguage', 'mnuRevert', 'mnuYacc', 'mnuFullscreen', 'mnuDocBook', 'mnuTools', 'mnuVerilog', 'mnuCSS', 'mnuFindPrevious', 'mnuPreferences', 'mnuCMake', 'mnuC/C++/ObjCHeader', 'mnuGAP', 'mnuCheckSpelling', 'mnuOthers', 'mnuBennuGD', 'mnuEdit', 'mnuTexinfo', 'mnuXML', 'mnuScientific', 'mnuGo', 'mnuJava', 'mnuAda', 'mnuMovetoNewWindow', 'mnuDocumentStatistics', 'mnuJavaScript', 'mnuPHP', 'mnuHTML', 'mnuContents', 'mnuPerl', 'mnuDocuments', 'mnuVHDL', 'mnuFortran95', 'mnuUndo', 'mnulibtool', 'mnuMarkup', 'mnuPython3', 'mnuPaste', 'mnuSaveAs', 'mnupkg-config', 'mnutxt2tags', 'mnuSetLanguage', 'mnuHelp', 'mnuSidePanel', 'mnuStandardML', 'mnuNemerle', 'mnuErlang', 'mnuMallard', 'mnuSources', 'mnuSearch', 'mnugettexttranslation', 'mnuRuby', 'mnuFindNext', 'mnuNew', 'mnuProtobuf', 'mnuSave', 'mnuFCL', 'mnum4', 'mnuScheme', 'mnu169', 'mnuScilab', 'mnuClose', 'mnuC++', 'mnuOpen', 'mnuProlog', 'mnuNSIS', 'mnuIDL', 'mnuInsertDateandTime', 'mnuXSLT', 'mnuVBNET', 'mnuOOC', 'mnush', 'mnuCUDA', 'mnuGetHelpOnline', 'mnuNextDocument', 'mnuCGShaderLanguage', 'mnuLaTeX', 'mnuMatlab', 'mnuD', 'mnuC', 'mnuLua', 'mnuR']


File 選單下有那些屬性可以用

>>> getobjectinfo('*gedit*', 'mnuFile')
['key_binding', 'label', 'parent', 'key', 'obj_index', 'class', 'children']


看一下 File 選單內有那些 children

>>> getobjectproperty('*gedit*', 'mnuFile', 'children')
'ukn1 mnuNew spr0 spr1 mnuOpen spr2 spr3 spr4 mnuSave mnuSaveAs spr5 spr6 mnuRevert spr7 spr8 spr9 spr10 spr11 mnuPrintPreview mnuPrint spr12 spr13 mnu1atxt spr14 spr15 mnuClose mnuQuit mnuEmpty'


按一下 Open 好了

>>> selectmenuitem('*gedit*', 'mnuFile;mnuOpen')
1



內部實作


呼叫 getwindowlist 時,整個流程由上往下大致上為

  • ldtp
  • pyatspi
  • at-spi2-core

由 ldtp 開始,可以找到它用了 pyatspi 來取得桌面的物件。

import pyatspi
self._desktop = pyatspi.Registry.getDesktop(0)


進入到 pyatspi,它用了 gir 的方法得到 Atspi 物件。至於 Atspi 要怎麼用?這裡附上 API 文件,自行參閱。

@pyatspi/registry.py
from gi.repository import Atspi
return Atspi.get_desktop(i)


另外,這這裡 Atspi 被擴充了一些功能像 __getitem__ 之類的運算,可以參考這個檔案 pyatspi/Accessibility.py 的最後部分。

最後 at-spi2-core 的部分 dbus 的介面使用可以在 at-spi2-core-2.4.2/atspi 裡找到,像是

@atspi/atspi-accessible.c
reply = _atspi_dbus_call_partial (obj, atspi_interface_accessible,
"GetChildAtIndex", error, "i",
child_index);


實際上的實作可以在 registryd 裡找到,如下:

@registryd/registry.c
impl_GetChildAtIndex

2013/08/12

使用 live-build 客製化無人值守 Ubuntu 安裝光碟




要客製化 Ubuntu 安裝光碟,在 Ubutnu wiki 有兩篇教學。1) LiveCDCustomization。2) InstallCDCustomization 。這兩篇文章雖然都很有參考價值,但需要先下載 Ubuntu 的光碟,並以此加工。

小弟早有所聞用 live-build 也可自製開機光碟,google 一下便又找到 Ubuntu 12.04 Cloud LiveLive-build使用指南由於不需先下載光碟,個人偏好使用這種方式,本文也將以此繼續發揮。

Build ISO


不囉嗦,請照下面步驟做:

$ sudo apt-get install live-build livecd-rootfs syslinux-themes-ubuntu-oneiric gfxboot-theme-ubuntu
$ mkdir live-build && cd live-build
$ git clone https://github.com/fcwu/custom-ubuntu-config.git config
$ sudo PROJECT=ubuntu SUITE=precise ARCH=amd64 lb build


約過半小時或好幾個小時,便會生成 binary.iso,此檔便是 Ubuntu 12.04 的安裝光碟。若要改成 13.04 的話,只需將 SUITE=precise 改成 SUITE=raring 即可。

大方向來說,指令可分 3 種, config, clean and build,這些指令若在 auto 資料匣內有相對應的檔案,舉例來說,以我們的平台 lb build 會去執行 auto/build。以下是各指令簡單的介紹:

  • lb config:在當前目錄下建立 config 目錄和相關配置文件
  • lb clean:清空所有做 ISO 時的遺物,其後還可接像 --stage --chroot --bootstrap 這類的參數來指令要清除那部分
  • lb build:按照 config 目錄下的各種配置腳本建構 ISO

無人值守安裝


好加在,Ubiquity (Ubuntu 的安裝程式) 本來就有為了無人值守留後門,Ubiquity 會用 debconf 從 /var/cache/debconf/config.dat 內讀取安裝的設定。要修改這部分可以參考 config/binary_local-includes/preseed/ubuntu.seed 或官方 preseed 文件說明

此外,要讓 Ubiquity 直接跳過那些選項,需在 boot command 加上 automatic-ubiquity。如同 config/binary_local-includes/isolinux/txt.cfg 內的這段

label live-install-auto
menu label ^Install Ubuntu Automatically
kernel /casper/vmlinuz
append file=/cdrom/preseed/ubuntu.seed boot=casper automatic-ubiquity initrd=/casper/initrd.lz quiet splash --


安裝更多軟體


安裝程式可以選擇安裝到 live 系統,或是用 Ubiquity 安裝到硬碟的 install 系統。以下提供 2 種方法。

用 .deb 安裝


這種方法會同時安裝到 live and install 系統。只需將要安裝的 .deb 丟到 config/chroot_packages 即可。但要注意 .deb 相依性的問題。

用 apt-get install 安裝


用 apt-get 的安裝方式,可任選要安裝到 live or install 系統。我們要做的事只需將要安裝的程式列表放到 package-lists 裡即可,如同 config/package-lists/example.chroot.list 所示。

terminator
vim
vim-gtk


若想選擇擇只裝到 live 或 install 系統的話,在命名 *.list 有這些規則

  • *_install.list: 只安裝到 install 系統
  • *_live.list: 只安裝到 live 系統

live-build 內部


live-build 在製作 iso 時,共分 4 大步驟:

  1. bootstrap
  2. chroot
  3. binary
  4. source

更進一步在查看 lb 源碼後,可以發現在執行 lb build 時,會分別做這 4 大步的指令 lb bootstrap, lb chroot, lb binary, lb source。相關源碼在 /usr/share/live/build/scripts/build 裡,如 lb_bootstrap, lb_chroot 這些 scripts。

bootsrap 最主要工作是用 lb bootstrap_debootstrap 建立起最基本的 root filesystem。

chroot 的工作是 chroot 到剛才建立起的 root filesystem,接著按照 config/chroot_*, package-lists and task-lists 的內容將所需修改的部分裝進去。舉例來說 chroot_local-hooks 可以放一些 hook script,chroot_local-includes 拿來放要覆蓋的檔案。

最後,binary 的工作是將 ISO 的 rootfs 和 ISO 產生出來。

source 沒在用,不用提了。

最後的最後,值得一提的是在做完任何一項工作時,.stage/ 資料匣內會產生表示做過的 flag 檔案,若有時只需重做部分工作,可手動將之刪除即可。要不然每次都等個半小時重做 ISO 就太...笨...了



2013/08/01

Debug Xorg with GDB on Ubuntu



在 Ubuntu 上要用 GDB 除錯那些用 apt-get install 安裝的程式十分的容易:1) 安裝 symbols, 2) 打開 GDB。這篇文章以除錯 Xorg 的 evdev 為例,evdev 是 Xorg 的 input driver 之一,它的主要功能是向 Xorg 送出一些鍵盤的按鍵,這篇主要會說明如何用 GDB 在送出 key 之前增加斷點。

實驗環境如下:

  • OS: Ubuntu 12.04 LTS
  • Kernel: 3.2.0-34-generic #53-Ubuntu
  • xserver-xorg-core: 2:1.11.4-0ubuntu10.13
  • xserver-xorg-input-evdev: 1:2.7.0-0ubuntu1.2


前情提要


本來只是除錯 trackpad (觸碰板)的一些行為,這類的工具很多像是

  • xinput
  • xev
  • lsinput
  • input-event
  • evtest

但是準備要用 input-event and evtest,卻發生 Operation not permit,也沒想到要怎麼解只好開始用 GDB。

設定 debug symbol 來源


完全從這裡抄來的, DebuggingProgramCrash,

$ echo "deb http://ddebs.ubuntu.com 1(lsb_release -cs) main restricted universe multiverse"  \
sudo tee -a /etc/apt/sources.list.d/ddebs.list

Stable releases (not alphas and betas) require three more lines adding to the same file, which is done by the following terminal command:

$ echo "deb http://ddebs.ubuntu.com 1(lsb_release -cs)-updates main restricted universe multiverse
deb http://ddebs.ubuntu.com 1(lsb_release -cs)-security main restricted universe multiverse
deb http://ddebs.ubuntu.com 1(lsb_release -cs)-proposed main restricted universe multiverse"  \
sudo tee -a /etc/apt/sources.list.d/ddebs.list
$ sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 428D7C01
$ sudo apt-get update

尋找要安裝的 symbol packages


先用 lsof 找到要除錯的 evdev_drv.so 是那支程式在用:

sudo lsof /usr/lib/xorg/modules/input/evdev_drv.so
lsof: WARNING: can't stat() fuse.gvfs-fuse-daemon file system /home/u/.gvfs
      Output information may be incomplete.
COMMAND  PID USER  FD   TYPE DEVICE SIZE/OFF    NODE NAME
Xorg    1603 root mem    REG    8,3    57000 1572945 /usr/lib/xorg/modules/input/evdev_drv.so

接著再看看是那個 packages 提供:

$ dpkg -S /usr/bin/Xorg
xserver-xorg-core: /usr/bin/Xorg

最後就安裝他們的 dbgsym,順便也把 source code 抓下來:

$ sudo apt-get install xserver-xorg-core-dbgsym xserver-xorg-input-evdev-dbgsym
$ apt-get source xserver-xorg-core xserver-xorg-input-evdev

除錯


由於使用 GDB 除錯 Xorg 會造成整個畫面都暫停,請用 ssh 連到 target 再開啟 GDB。

用 cgdb 指定除錯程式及其 PID。正常載入 symbols 應該會有 reading symbols ... done 那行。

$ sudo cgdb -- /usr/bin/Xorg `pidof X`
Reading symbols from /usr/lib/xorg/modules/input/evdev_drv.so...Reading symbols from /usr/lib/debug/usr/lib/xorg/modules/input/evdev_drv.so...done.

設定源碼路徑

(gdb) directory xserver-xorg-input-evdev-2.7.0/src
Source directories searched: /home/u/xorg/xserver-xorg-input-evdev-2.7.0/src:1cdir:1cwd

以 cgdb 為例,設完中斷點如下,如上圖:

(gdb)
Breakpoint 1 at 0x7fb2407a9959: file ../../src/evdev.c, line 360.

針對這個中斷點設定一些 messages,再使其繼續執行。

(gdb) commands 1
Type commands for breakpoint(s) 1, one per line.
End with a line saying just "end".
>printf "0x%X, 0x%X", code, value
>c
>end
(gdb) c

接著在測試主機上按 asd,就會有如下訊息丟出來。它輸出 2 個數字,第一個是 kernel 回報的 event code,第二個數字應該是 key press/release。

Continuing.

Breakpoint 1, EvdevQueueKbdEvent (pInfo=0x7fb2477a0010, ev=0x7fffc5cd0548, value=1) at ../../src/evdev.c:360
0x26, 0x1
Breakpoint 1, EvdevQueueKbdEvent (pInfo=0x7fb2477a0010, ev=0x7fffc5cd0548, value=0) at ../../src/evdev.c:360
0x26, 0x0
Breakpoint 1, EvdevQueueKbdEvent (pInfo=0x7fb2477a0010, ev=0x7fffc5cd0548, value=1) at ../../src/evdev.c:360
0x27, 0x1
Breakpoint 1, EvdevQueueKbdEvent (pInfo=0x7fb2477a0010, ev=0x7fffc5cd0548, value=0) at ../../src/evdev.c:360
0x27, 0x0
Breakpoint 1, EvdevQueueKbdEvent (pInfo=0x7fb2477a0010, ev=0x7fffc5cd0548, value=1) at ../../src/evdev.c:360
0x28, 0x1
Breakpoint 1, EvdevQueueKbdEvent (pInfo=0x7fb2477a0010, ev=0x7fffc5cd0548, value=0) at ../../src/evdev.c:360
0x28, 0x0

其中,回報的 event code 可以用 xmodmap 看到列表:

$ xmodmap -pke  grep -A 3 ' 38 ='
No protocol specified
keycode  38 = a A a A
keycode  39 = s S s S
keycode  40 = d D d D
keycode  41 = f F f F

evtest 也可以,但是要 +8

$ sudo evtest /dev/input/event2
Input driver version is 1.0.1
Input device ID: bus 0x3 vendor 0x4ca product 0x4b version 0x110
Input device name: "Lite-On Technology Corp. USB Keyboard"
Supported events:
  Event type 0 (EV_SYN)
  Event type 1 (EV_KEY)
    Event code 1 (KEY_ESC)
    Event code 2 (KEY_1)
    Event code 3 (KEY_2)
    Event code 4 (KEY_3)
    Event code 5 (KEY_4)
    Event code 6 (KEY_5)
    Event code 7 (KEY_6)
    Event code 8 (KEY_7)
    Event code 9 (KEY_8)
    Event code 10 (KEY_9)
    Event code 11 (KEY_0)
    Event code 12 (KEY_MINUS)
    Event code 13 (KEY_EQUAL)
    Event code 14 (KEY_BACKSPACE)
    Event code 15 (KEY_TAB)
    Event code 16 (KEY_Q)
    Event code 17 (KEY_W)
    Event code 18 (KEY_E)
    Event code 19 (KEY_R)
    Event code 20 (KEY_T)
    Event code 21 (KEY_Y)
    Event code 22 (KEY_U)
    Event code 23 (KEY_I)
    Event code 24 (KEY_O)
    Event code 25 (KEY_P)
    Event code 26 (KEY_LEFTBRACE)
    Event code 27 (KEY_RIGHTBRACE)
    Event code 28 (KEY_ENTER)
    Event code 29 (KEY_LEFTCTRL)
    Event code 30 (KEY_A)

vim gdb plugin - pyclewn and dbgsym


(8/5 update)

習慣用 debug 可以用 pyclewn 當作介面,參考我之前介紹的文章

若是需要自己編譯的 deb 也有 dbgysm 的話由這篇文章提到,可以 1) 安裝 pkg-create-dbgsym 或 2)修改 debian/rules。