mirror of
https://github.com/httprunner/httprunner.git
synced 2026-05-07 05:42:46 +08:00
move ghdc to pkg
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -2,7 +2,6 @@
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
*.apk
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
v5.0.0-beta-2503052133
|
||||
v5.0.0-beta-2503052140
|
||||
|
||||
1
pkg/ghdc
1
pkg/ghdc
Submodule pkg/ghdc deleted from ecb76cf5bd
86
pkg/ghdc/README.md
Normal file
86
pkg/ghdc/README.md
Normal file
@@ -0,0 +1,86 @@
|
||||
# ghdc
|
||||
|
||||
ghdc 是一个用于与鸿蒙设备进行交互的工具,封装了各种 HDC(鸿蒙的 ADB)命令和 UI 自动化能力。
|
||||
|
||||
## 目录结构
|
||||
ghdc \
|
||||
├── client.go 封装 hdc list targets 等非设备命令 \
|
||||
├── device.go 封装 hdc -t connectkey shell 等指定设备的命令 \
|
||||
└── uidevice.go 封装设备所有自动化能力
|
||||
|
||||
## hdc 命令调用
|
||||
目前支持的能力:
|
||||
- 获取设备
|
||||
- 文件传输
|
||||
- shell 命令
|
||||
- 端口挂载
|
||||
- 属性获取(brand, model, 版本等)
|
||||
- 截图
|
||||
|
||||
`hdc`与鸿蒙的关系和`adb`与安卓关系一致,其架构与 adb server 相同,分为 client 和 server。`hdc start` 会启动一个 hdc server,监听本地的 8710 端口。我们也是和该 8710 端口通信执行 HDC 命令。目前支持常用的 hdc 能力,并可扩展至所有 hdc 能力。
|
||||
|
||||
```shell
|
||||
hdc -m -s ::ffff:127.0.0.1:8710
|
||||
```
|
||||
hdc 命令分为两类:
|
||||
- 不指定设备的命令:例如 `hdc list targets`。逻辑封装在 client.go 中。与 gadb 类似,所有的命令通过 RPC 方式与 hdc server 直接通信。
|
||||
- 指定设备的命令:例如 `hdc -t connectkey shell`。逻辑封装在 device.go 中。这些命令会在与 server 建连时指定执行的设备。
|
||||
|
||||
## UI Test 自动化能力
|
||||
目前支持的能力:
|
||||
- 点击\滑动\输入
|
||||
- 按键操作
|
||||
- 手势操作
|
||||
- TouchDown/TouchMove/TouchUp 屏幕操作
|
||||
- 屏幕旋转
|
||||
- 音量设置
|
||||
- 图片流获取
|
||||
- 控件信息监听
|
||||
- 简单控件操作
|
||||
|
||||
Harmony Next 内置了 UI Test 服务,提供了所有常用的自动化能力。并且这个服务也部分开源,支持二次开发。由于协议未开源,我们通过逆向工程绕过 JS API,直接通过 socket 与 UI Test 服务进行通信,操作手机。代码能力封装在 uidevice.go 中。
|
||||
|
||||
UI Test 协议分为两类:
|
||||
- 无 session 单次返回长连接:例如点击、滑动等,发出命令即可,返回一个执行成功或失败。
|
||||
- 有 session 多次返回长连接:例如获取屏幕图片流、监听控件信息变化等。
|
||||
|
||||
## 使用方法
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/httprunner/httprunner/v5/pkg/ghdc"
|
||||
"log"
|
||||
)
|
||||
|
||||
func main() {
|
||||
client, err := ghdc.NewClient()
|
||||
checkErr(err, "fail to connect hdc server")
|
||||
|
||||
devices, err := client.DeviceList()
|
||||
checkErr(err)
|
||||
|
||||
if len(devices) == 0 {
|
||||
log.Fatalln("list of devices is empty")
|
||||
}
|
||||
dev := devices[0]
|
||||
driver, err := ghdc.NewUIDriver(dev)
|
||||
checkErr(err, "fail to init device uiDriver")
|
||||
|
||||
err = driver.Touch(225, 1715)
|
||||
checkErr(err)
|
||||
}
|
||||
|
||||
func checkErr(err error, msg ...string) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
var output string
|
||||
if len(msg) != 0 {
|
||||
output = msg[0] + " "
|
||||
}
|
||||
output += err.Error()
|
||||
log.Fatalln(output)
|
||||
}
|
||||
```
|
||||
BIN
pkg/ghdc/agent.so
Normal file
BIN
pkg/ghdc/agent.so
Normal file
Binary file not shown.
166
pkg/ghdc/client.go
Normal file
166
pkg/ghdc/client.go
Normal file
@@ -0,0 +1,166 @@
|
||||
package ghdc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const HdcServerPort = 8710
|
||||
|
||||
type Client struct {
|
||||
host string
|
||||
port int
|
||||
}
|
||||
|
||||
func NewClient() (Client, error) {
|
||||
return NewClientWith("localhost")
|
||||
}
|
||||
|
||||
func NewClientWith(host string, port ...int) (hdClient Client, err error) {
|
||||
if len(port) == 0 {
|
||||
port = []int{HdcServerPort}
|
||||
}
|
||||
hdClient.host = host
|
||||
hdClient.port = port[0]
|
||||
|
||||
var tp transport
|
||||
if tp, err = hdClient.createTransport(); err != nil {
|
||||
return Client{}, err
|
||||
}
|
||||
defer func() { _ = tp.Close() }()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c Client) ServerVersion() (version string, err error) {
|
||||
return c.executeCommand("version")
|
||||
}
|
||||
|
||||
func (c Client) DeviceSerialList() (serials []string, err error) {
|
||||
var resp string
|
||||
if resp, err = c.executeCommand("list targets"); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
lines := strings.Split(resp, "\n")
|
||||
serials = make([]string, 0, len(lines))
|
||||
|
||||
for i := range lines {
|
||||
if lines[i] == "" {
|
||||
continue
|
||||
}
|
||||
fields := strings.Fields(lines[i])
|
||||
serials = append(serials, fields[0])
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c Client) DeviceList() (devices []Device, err error) {
|
||||
var resp string
|
||||
if resp, err = c.executeCommand("list targets -v"); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
lines := strings.Split(resp, "\n")
|
||||
devices = make([]Device, 0, len(lines))
|
||||
|
||||
for i := range lines {
|
||||
line := strings.TrimSpace(lines[i])
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) < 4 || len(fields[0]) == 0 {
|
||||
debugLog(fmt.Sprintf("can't parse: %s", line))
|
||||
continue
|
||||
}
|
||||
if fields[2] == "Offline" {
|
||||
log.Printf("device [%s] Offline ", fields[0])
|
||||
continue
|
||||
}
|
||||
if fields[1] == "UART" {
|
||||
continue
|
||||
}
|
||||
slickAttrs := make(map[string]string)
|
||||
slickAttrs["usb"] = fields[1]
|
||||
slickAttrs["device_status"] = fields[2]
|
||||
device, err := NewDevice(c, fields[0], slickAttrs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
devices = append(devices, device)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c Client) ForwardList() (deviceForward []DeviceForward, err error) {
|
||||
var resp string
|
||||
if resp, err = c.executeCommand("fport ls"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
lines := strings.Split(resp, "\n")
|
||||
deviceForward = make([]DeviceForward, 0, len(lines))
|
||||
|
||||
for i := range lines {
|
||||
line := strings.TrimSpace(lines[i])
|
||||
line = strings.ReplaceAll(line, "'", "")
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) < 2 {
|
||||
continue
|
||||
}
|
||||
deviceForward = append(deviceForward, DeviceForward{Local: fields[0], Remote: fields[1]})
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c Client) Connect(ip string, port ...int) (err error) {
|
||||
if len(port) == 0 {
|
||||
port = []int{HdcServerPort}
|
||||
}
|
||||
|
||||
var resp string
|
||||
if resp, err = c.executeCommand(fmt.Sprintf("tconn %s:%d", ip, port[0])); err != nil {
|
||||
return err
|
||||
}
|
||||
if !strings.HasPrefix(resp, "connected to") && !strings.HasPrefix(resp, "already connected to") {
|
||||
return fmt.Errorf("hd connect: %s", resp)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c Client) KillServer() (err error) {
|
||||
var tp transport
|
||||
if tp, err = c.createTransport(); err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() { _ = tp.Close() }()
|
||||
|
||||
err = tp.SendCommand("kill")
|
||||
return
|
||||
}
|
||||
|
||||
func (c Client) createTransport() (tp transport, err error) {
|
||||
return newTransport(fmt.Sprintf("%s:%d", c.host, c.port), false, "")
|
||||
}
|
||||
|
||||
func (c Client) executeCommand(command string) (resp string, err error) {
|
||||
var tp transport
|
||||
if tp, err = c.createTransport(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer func() { _ = tp.Close() }()
|
||||
|
||||
if err = tp.SendCommand(command); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return tp.ReadStringAll()
|
||||
}
|
||||
103
pkg/ghdc/client_test.go
Normal file
103
pkg/ghdc/client_test.go
Normal file
@@ -0,0 +1,103 @@
|
||||
package ghdc
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestClient_ServerVersion(t *testing.T) {
|
||||
SetDebug(true)
|
||||
|
||||
hdcClient, err := NewClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
hdServerVersion, err := hdcClient.ServerVersion()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Log(hdServerVersion)
|
||||
}
|
||||
|
||||
func TestClient_DeviceSerialList(t *testing.T) {
|
||||
SetDebug(true)
|
||||
|
||||
hdcClient, err := NewClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
serials, err := hdcClient.DeviceSerialList()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for i := range serials {
|
||||
t.Log(serials[i])
|
||||
}
|
||||
}
|
||||
|
||||
func TestClient_DeviceList(t *testing.T) {
|
||||
SetDebug(true)
|
||||
|
||||
hdcClient, err := NewClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
devices, err := hdcClient.DeviceList()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for i := range devices {
|
||||
t.Log(devices[i].serial, devices[i].DeviceInfo())
|
||||
}
|
||||
}
|
||||
|
||||
func TestClient_ForwardList(t *testing.T) {
|
||||
SetDebug(true)
|
||||
|
||||
hdcClient, err := NewClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
deviceForwardList, err := hdcClient.ForwardList()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for i := range deviceForwardList {
|
||||
t.Log(deviceForwardList[i])
|
||||
}
|
||||
}
|
||||
|
||||
func TestClient_Connect(t *testing.T) {
|
||||
hdcClient, err := NewClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
SetDebug(true)
|
||||
|
||||
err = hdcClient.Connect("192.168.1.28")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClient_KillServer(t *testing.T) {
|
||||
SetDebug(true)
|
||||
|
||||
hdcClient, err := NewClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = hdcClient.KillServer()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
76
pkg/ghdc/connection_pool.go
Normal file
76
pkg/ghdc/connection_pool.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package ghdc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ConnectionPool struct {
|
||||
mu sync.Mutex
|
||||
conns chan net.Conn
|
||||
host string
|
||||
port string
|
||||
maxConns int
|
||||
}
|
||||
|
||||
func newConnectionPool(host string, port string, maxConns int) (*ConnectionPool, error) {
|
||||
pool := &ConnectionPool{
|
||||
conns: make(chan net.Conn, maxConns),
|
||||
host: host,
|
||||
port: port,
|
||||
maxConns: maxConns,
|
||||
}
|
||||
|
||||
for i := 0; i < maxConns; i++ {
|
||||
conn, err := pool.newConnection()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pool.conns <- conn
|
||||
}
|
||||
|
||||
return pool, nil
|
||||
}
|
||||
|
||||
func (p *ConnectionPool) newConnection() (net.Conn, error) {
|
||||
conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%s", p.host, p.port), 5*time.Second)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (p *ConnectionPool) getConnection() (net.Conn, error) {
|
||||
select {
|
||||
case conn := <-p.conns:
|
||||
return conn, nil
|
||||
default:
|
||||
// 如果池中没有可用连接,创建一个新的连接
|
||||
return p.newConnection()
|
||||
}
|
||||
}
|
||||
|
||||
func (p *ConnectionPool) releaseConnection(conn net.Conn) {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
|
||||
select {
|
||||
case p.conns <- conn:
|
||||
// 放回连接池成功
|
||||
default:
|
||||
// 连接池已满,关闭连接
|
||||
conn.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (p *ConnectionPool) close() {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
|
||||
close(p.conns)
|
||||
for conn := range p.conns {
|
||||
conn.Close()
|
||||
}
|
||||
}
|
||||
618
pkg/ghdc/device.go
Normal file
618
pkg/ghdc/device.go
Normal file
@@ -0,0 +1,618 @@
|
||||
package ghdc
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type DeviceFileInfo struct {
|
||||
Name string
|
||||
Mode os.FileMode
|
||||
Size uint32
|
||||
LastModified time.Time
|
||||
}
|
||||
|
||||
func (info DeviceFileInfo) IsDir() bool {
|
||||
return (info.Mode & (1 << 14)) == (1 << 14)
|
||||
}
|
||||
|
||||
type DeviceState string
|
||||
|
||||
const (
|
||||
StateOnline DeviceState = "Online"
|
||||
StateOffline DeviceState = "Offline"
|
||||
)
|
||||
|
||||
var deviceStateStrings = map[string]DeviceState{
|
||||
"Offline": StateOffline,
|
||||
"Connected": StateOnline,
|
||||
}
|
||||
|
||||
type KeyCode int
|
||||
|
||||
const (
|
||||
KEYCODE_FN KeyCode = 0
|
||||
KEYCODE_UNKNOWN KeyCode = -1
|
||||
KEYCODE_HOME KeyCode = 1
|
||||
KEYCODE_BACK KeyCode = 2
|
||||
KEYCODE_MEDIA_PLAY_PAUSE KeyCode = 10
|
||||
KEYCODE_MEDIA_STOP KeyCode = 11
|
||||
KEYCODE_MEDIA_NEXT KeyCode = 12
|
||||
KEYCODE_MEDIA_PREVIOUS KeyCode = 13
|
||||
KEYCODE_MEDIA_REWIND KeyCode = 14
|
||||
KEYCODE_MEDIA_FAST_FORWARD KeyCode = 15
|
||||
KEYCODE_VOLUME_UP KeyCode = 16
|
||||
KEYCODE_VOLUME_DOWN KeyCode = 17
|
||||
KEYCODE_POWER KeyCode = 18
|
||||
KEYCODE_CAMERA KeyCode = 19
|
||||
KEYCODE_VOLUME_MUTE KeyCode = 22
|
||||
KEYCODE_MUTE KeyCode = 23
|
||||
KEYCODE_BRIGHTNESS_UP KeyCode = 40
|
||||
KEYCODE_BRIGHTNESS_DOWN KeyCode = 41
|
||||
KEYCODE_NUM_0 KeyCode = 2000
|
||||
KEYCODE_NUM_1 KeyCode = 2001
|
||||
KEYCODE_NUM_2 KeyCode = 2002
|
||||
KEYCODE_NUM_3 KeyCode = 2003
|
||||
KEYCODE_NUM_4 KeyCode = 2004
|
||||
KEYCODE_NUM_5 KeyCode = 2005
|
||||
KEYCODE_NUM_6 KeyCode = 2006
|
||||
KEYCODE_NUM_7 KeyCode = 2007
|
||||
KEYCODE_NUM_8 KeyCode = 2008
|
||||
KEYCODE_NUM_9 KeyCode = 2009
|
||||
KEYCODE_STAR KeyCode = 2010
|
||||
KEYCODE_POUND KeyCode = 2011
|
||||
KEYCODE_DPAD_UP KeyCode = 2012
|
||||
KEYCODE_DPAD_DOWN KeyCode = 2013
|
||||
KEYCODE_DPAD_LEFT KeyCode = 2014
|
||||
KEYCODE_DPAD_RIGHT KeyCode = 2015
|
||||
KEYCODE_DPAD_CENTER KeyCode = 2016
|
||||
KEYCODE_A KeyCode = 2017
|
||||
KEYCODE_B KeyCode = 2018
|
||||
KEYCODE_C KeyCode = 2019
|
||||
KEYCODE_D KeyCode = 2020
|
||||
KEYCODE_E KeyCode = 2021
|
||||
KEYCODE_F KeyCode = 2022
|
||||
KEYCODE_G KeyCode = 2023
|
||||
KEYCODE_H KeyCode = 2024
|
||||
KEYCODE_I KeyCode = 2025
|
||||
KEYCODE_J KeyCode = 2026
|
||||
KEYCODE_K KeyCode = 2027
|
||||
KEYCODE_L KeyCode = 2028
|
||||
KEYCODE_M KeyCode = 2029
|
||||
KEYCODE_N KeyCode = 2030
|
||||
KEYCODE_O KeyCode = 2031
|
||||
KEYCODE_P KeyCode = 2032
|
||||
KEYCODE_Q KeyCode = 2033
|
||||
KEYCODE_R KeyCode = 2034
|
||||
KEYCODE_S KeyCode = 2035
|
||||
KEYCODE_T KeyCode = 2036
|
||||
KEYCODE_U KeyCode = 2037
|
||||
KEYCODE_V KeyCode = 2038
|
||||
KEYCODE_W KeyCode = 2039
|
||||
KEYCODE_X KeyCode = 2040
|
||||
KEYCODE_Y KeyCode = 2041
|
||||
KEYCODE_Z KeyCode = 2042
|
||||
KEYCODE_COMMA KeyCode = 2043
|
||||
KEYCODE_PERIOD KeyCode = 2044
|
||||
KEYCODE_ALT_LEFT KeyCode = 2045
|
||||
KEYCODE_ALT_RIGHT KeyCode = 2046
|
||||
KEYCODE_SHIFT_LEFT KeyCode = 2047
|
||||
KEYCODE_SHIFT_RIGHT KeyCode = 2048
|
||||
KEYCODE_TAB KeyCode = 2049
|
||||
KEYCODE_SPACE KeyCode = 2050
|
||||
KEYCODE_SYM KeyCode = 2051
|
||||
KEYCODE_EXPLORER KeyCode = 2052
|
||||
KEYCODE_ENVELOPE KeyCode = 2053
|
||||
KEYCODE_ENTER KeyCode = 2054
|
||||
KEYCODE_DEL KeyCode = 2055
|
||||
KEYCODE_GRAVE KeyCode = 2056
|
||||
KEYCODE_MINUS KeyCode = 2057
|
||||
KEYCODE_EQUALS KeyCode = 2058
|
||||
KEYCODE_LEFT_BRACKET KeyCode = 2059
|
||||
KEYCODE_RIGHT_BRACKET KeyCode = 2060
|
||||
KEYCODE_BACKSLASH KeyCode = 2061
|
||||
KEYCODE_SEMICOLON KeyCode = 2062
|
||||
KEYCODE_APOSTROPHE KeyCode = 2063
|
||||
KEYCODE_SLASH KeyCode = 2064
|
||||
KEYCODE_AT KeyCode = 2065
|
||||
KEYCODE_PLUS KeyCode = 2066
|
||||
KEYCODE_MENU KeyCode = 2067
|
||||
KEYCODE_PAGE_UP KeyCode = 2068
|
||||
KEYCODE_PAGE_DOWN KeyCode = 2069
|
||||
KEYCODE_ESCAPE KeyCode = 2070
|
||||
KEYCODE_FORWARD_DEL KeyCode = 2071
|
||||
KEYCODE_CTRL_LEFT KeyCode = 2072
|
||||
KEYCODE_CTRL_RIGHT KeyCode = 2073
|
||||
KEYCODE_CAPS_LOCK KeyCode = 2074
|
||||
KEYCODE_SCROLL_LOCK KeyCode = 2075
|
||||
KEYCODE_META_LEFT KeyCode = 2076
|
||||
KEYCODE_META_RIGHT KeyCode = 2077
|
||||
KEYCODE_FUNCTION KeyCode = 2078
|
||||
KEYCODE_SYSRQ KeyCode = 2079
|
||||
KEYCODE_BREAK KeyCode = 2080
|
||||
KEYCODE_MOVE_HOME KeyCode = 2081
|
||||
KEYCODE_MOVE_END KeyCode = 2082
|
||||
KEYCODE_INSERT KeyCode = 2083
|
||||
KEYCODE_FORWARD KeyCode = 2084
|
||||
KEYCODE_MEDIA_PLAY KeyCode = 2085
|
||||
KEYCODE_MEDIA_PAUSE KeyCode = 2086
|
||||
KEYCODE_MEDIA_CLOSE KeyCode = 2087
|
||||
KEYCODE_MEDIA_EJECT KeyCode = 2088
|
||||
KEYCODE_MEDIA_RECORD KeyCode = 2089
|
||||
KEYCODE_F1 KeyCode = 2090
|
||||
KEYCODE_F2 KeyCode = 2091
|
||||
KEYCODE_F3 KeyCode = 2092
|
||||
KEYCODE_F4 KeyCode = 2093
|
||||
KEYCODE_F5 KeyCode = 2094
|
||||
KEYCODE_F6 KeyCode = 2095
|
||||
KEYCODE_F7 KeyCode = 2096
|
||||
KEYCODE_F8 KeyCode = 2097
|
||||
KEYCODE_F9 KeyCode = 2098
|
||||
KEYCODE_F10 KeyCode = 2099
|
||||
KEYCODE_F11 KeyCode = 2100
|
||||
KEYCODE_F12 KeyCode = 2101
|
||||
KEYCODE_NUM_LOCK KeyCode = 2102
|
||||
KEYCODE_NUMPAD_0 KeyCode = 2103
|
||||
KEYCODE_NUMPAD_1 KeyCode = 2104
|
||||
KEYCODE_NUMPAD_2 KeyCode = 2105
|
||||
KEYCODE_NUMPAD_3 KeyCode = 2106
|
||||
KEYCODE_NUMPAD_4 KeyCode = 2107
|
||||
KEYCODE_NUMPAD_5 KeyCode = 2108
|
||||
KEYCODE_NUMPAD_6 KeyCode = 2109
|
||||
KEYCODE_NUMPAD_7 KeyCode = 2110
|
||||
KEYCODE_NUMPAD_8 KeyCode = 2111
|
||||
KEYCODE_NUMPAD_9 KeyCode = 2112
|
||||
KEYCODE_NUMPAD_DIVIDE KeyCode = 2113
|
||||
KEYCODE_NUMPAD_MULTIPLY KeyCode = 2114
|
||||
KEYCODE_NUMPAD_SUBTRACT KeyCode = 2115
|
||||
KEYCODE_NUMPAD_ADD KeyCode = 2116
|
||||
KEYCODE_NUMPAD_DOT KeyCode = 2117
|
||||
KEYCODE_NUMPAD_COMMA KeyCode = 2118
|
||||
KEYCODE_NUMPAD_ENTER KeyCode = 2119
|
||||
KEYCODE_NUMPAD_EQUALS KeyCode = 2120
|
||||
KEYCODE_NUMPAD_LEFT_PAREN KeyCode = 2121
|
||||
KEYCODE_NUMPAD_RIGHT_PAREN KeyCode = 2122
|
||||
KEYCODE_VIRTUAL_MULTITASK KeyCode = 2210
|
||||
KEYCODE_SLEEP KeyCode = 2600
|
||||
KEYCODE_ZENKAKU_HANKAKU KeyCode = 2601
|
||||
KEYCODE_ND KeyCode = 2602
|
||||
KEYCODE_RO KeyCode = 2603
|
||||
KEYCODE_KATAKANA KeyCode = 2604
|
||||
KEYCODE_HIRAGANA KeyCode = 2605
|
||||
KEYCODE_HENKAN KeyCode = 2606
|
||||
KEYCODE_KATAKANA_HIRAGANA KeyCode = 2607
|
||||
KEYCODE_MUHENKAN KeyCode = 2608
|
||||
KEYCODE_LINEFEED KeyCode = 2609
|
||||
KEYCODE_MACRO KeyCode = 2610
|
||||
KEYCODE_NUMPAD_PLUSMINUS KeyCode = 2611
|
||||
KEYCODE_SCALE KeyCode = 2612
|
||||
KEYCODE_HANGUEL KeyCode = 2613
|
||||
KEYCODE_HANJA KeyCode = 2614
|
||||
KEYCODE_YEN KeyCode = 2615
|
||||
KEYCODE_STOP KeyCode = 2616
|
||||
KEYCODE_AGAIN KeyCode = 2617
|
||||
KEYCODE_PROPS KeyCode = 2618
|
||||
KEYCODE_UNDO KeyCode = 2619
|
||||
KEYCODE_COPY KeyCode = 2620
|
||||
KEYCODE_OPEN KeyCode = 2621
|
||||
KEYCODE_PASTE KeyCode = 2622
|
||||
KEYCODE_FIND KeyCode = 2623
|
||||
KEYCODE_CUT KeyCode = 2624
|
||||
KEYCODE_HELP KeyCode = 2625
|
||||
KEYCODE_CALC KeyCode = 2626
|
||||
KEYCODE_FILE KeyCode = 2627
|
||||
KEYCODE_BOOKMARKS KeyCode = 2628
|
||||
KEYCODE_NEXT KeyCode = 2629
|
||||
KEYCODE_PLAYPAUSE KeyCode = 2630
|
||||
KEYCODE_PREVIOUS KeyCode = 2631
|
||||
KEYCODE_STOPCD KeyCode = 2632
|
||||
KEYCODE_CONFIG KeyCode = 2634
|
||||
KEYCODE_REFRESH KeyCode = 2635
|
||||
KEYCODE_EXIT KeyCode = 2636
|
||||
KEYCODE_EDIT KeyCode = 2637
|
||||
KEYCODE_SCROLLUP KeyCode = 2638
|
||||
KEYCODE_SCROLLDOWN KeyCode = 2639
|
||||
KEYCODE_NEW KeyCode = 2640
|
||||
KEYCODE_REDO KeyCode = 2641
|
||||
KEYCODE_CLOSE KeyCode = 2642
|
||||
KEYCODE_PLAY KeyCode = 2643
|
||||
KEYCODE_BASSBOOST KeyCode = 2644
|
||||
KEYCODE_PRINT KeyCode = 2645
|
||||
KEYCODE_CHAT KeyCode = 2646
|
||||
KEYCODE_FINANCE KeyCode = 2647
|
||||
KEYCODE_CANCEL KeyCode = 2648
|
||||
KEYCODE_KBDILLUM_TOGGLE KeyCode = 2649
|
||||
KEYCODE_KBDILLUM_DOWN KeyCode = 2650
|
||||
KEYCODE_KBDILLUM_UP KeyCode = 2651
|
||||
KEYCODE_SEND KeyCode = 2652
|
||||
KEYCODE_REPLY KeyCode = 2653
|
||||
KEYCODE_FORWARDMAIL KeyCode = 2654
|
||||
KEYCODE_SAVE KeyCode = 2655
|
||||
KEYCODE_DOCUMENTS KeyCode = 2656
|
||||
KEYCODE_VIDEO_NEXT KeyCode = 2657
|
||||
KEYCODE_VIDEO_PREV KeyCode = 2658
|
||||
KEYCODE_BRIGHTNESS_CYCLE KeyCode = 2659
|
||||
KEYCODE_BRIGHTNESS_ZERO KeyCode = 2660
|
||||
KEYCODE_DISPLAY_OFF KeyCode = 2661
|
||||
KEYCODE_BTN_MISC KeyCode = 2662
|
||||
KEYCODE_GOTO KeyCode = 2663
|
||||
KEYCODE_INFO KeyCode = 2664
|
||||
KEYCODE_PROGRAM KeyCode = 2665
|
||||
KEYCODE_PVR KeyCode = 2666
|
||||
KEYCODE_SUBTITLE KeyCode = 2667
|
||||
KEYCODE_FULL_SCREEN KeyCode = 2668
|
||||
KEYCODE_KEYBOARD KeyCode = 2669
|
||||
KEYCODE_ASPECT_RATIO KeyCode = 2670
|
||||
KEYCODE_PC KeyCode = 2671
|
||||
KEYCODE_TV KeyCode = 2672
|
||||
KEYCODE_TV2 KeyCode = 2673
|
||||
KEYCODE_VCR KeyCode = 2674
|
||||
KEYCODE_VCR2 KeyCode = 2675
|
||||
KEYCODE_SAT KeyCode = 2676
|
||||
KEYCODE_CD KeyCode = 2677
|
||||
KEYCODE_TAPE KeyCode = 2678
|
||||
KEYCODE_TUNER KeyCode = 2679
|
||||
KEYCODE_PLAYER KeyCode = 2680
|
||||
KEYCODE_DVD KeyCode = 2681
|
||||
KEYCODE_AUDIO KeyCode = 2682
|
||||
KEYCODE_VIDEO KeyCode = 2683
|
||||
KEYCODE_MEMO KeyCode = 2684
|
||||
KEYCODE_CALENDAR KeyCode = 2685
|
||||
KEYCODE_RED KeyCode = 2686
|
||||
KEYCODE_GREEN KeyCode = 2687
|
||||
KEYCODE_YELLOW KeyCode = 2688
|
||||
KEYCODE_BLUE KeyCode = 2689
|
||||
KEYCODE_CHANNELUP KeyCode = 2690
|
||||
KEYCODE_CHANNELDOWN KeyCode = 2691
|
||||
KEYCODE_LAST KeyCode = 2692
|
||||
KEYCODE_RESTART KeyCode = 2693
|
||||
KEYCODE_SLOW KeyCode = 2694
|
||||
KEYCODE_SHUFFLE KeyCode = 2695
|
||||
KEYCODE_VIDEOPHONE KeyCode = 2696
|
||||
KEYCODE_GAMES KeyCode = 2697
|
||||
KEYCODE_ZOOMIN KeyCode = 2698
|
||||
KEYCODE_ZOOMOUT KeyCode = 2699
|
||||
KEYCODE_ZOOMRESET KeyCode = 2700
|
||||
KEYCODE_WORDPROCESSOR KeyCode = 2701
|
||||
KEYCODE_EDITOR KeyCode = 2702
|
||||
KEYCODE_SPREADSHEET KeyCode = 2703
|
||||
KEYCODE_GRAPHICSEDITOR KeyCode = 2704
|
||||
KEYCODE_PRESENTATION KeyCode = 2705
|
||||
KEYCODE_DATABASE KeyCode = 2706
|
||||
KEYCODE_NEWS KeyCode = 2707
|
||||
KEYCODE_VOICEMAIL KeyCode = 2708
|
||||
KEYCODE_ADDRESSBOOK KeyCode = 2709
|
||||
KEYCODE_MESSENGER KeyCode = 2710
|
||||
KEYCODE_BRIGHTNESS_TOGGLE KeyCode = 2711
|
||||
KEYCODE_SPELLCHECK KeyCode = 2712
|
||||
KEYCODE_COFFEE KeyCode = 2713
|
||||
KEYCODE_MEDIA_REPEAT KeyCode = 2714
|
||||
KEYCODE_IMAGES KeyCode = 2715
|
||||
KEYCODE_BUTTONCONFIG KeyCode = 2716
|
||||
KEYCODE_TASKMANAGER KeyCode = 2717
|
||||
KEYCODE_JOURNAL KeyCode = 2718
|
||||
KEYCODE_CONTROLPANEL KeyCode = 2719
|
||||
KEYCODE_APPSELECT KeyCode = 2720
|
||||
KEYCODE_SCREENSAVER KeyCode = 2721
|
||||
KEYCODE_ASSISTANT KeyCode = 2722
|
||||
KEYCODE_KBD_LAYOUT_NEXT KeyCode = 2723
|
||||
KEYCODE_BRIGHTNESS_MIN KeyCode = 2724
|
||||
KEYCODE_BRIGHTNESS_MAX KeyCode = 2725
|
||||
KEYCODE_KBDINPUTASSIST_PREV KeyCode = 2726
|
||||
KEYCODE_KBDINPUTASSIST_NEXT KeyCode = 2727
|
||||
KEYCODE_KBDINPUTASSIST_PREVGROUP KeyCode = 2728
|
||||
KEYCODE_KBDINPUTASSIST_NEXTGROUP KeyCode = 2729
|
||||
KEYCODE_KBDINPUTASSIST_ACCEPT KeyCode = 2730
|
||||
KEYCODE_KBDINPUTASSIST_CANCEL KeyCode = 2731
|
||||
KEYCODE_FRONT KeyCode = 2800
|
||||
KEYCODE_SETUP KeyCode = 2801
|
||||
KEYCODE_WAKE_UP KeyCode = 2802
|
||||
KEYCODE_SENDFILE KeyCode = 2803
|
||||
KEYCODE_DELETEFILE KeyCode = 2804
|
||||
KEYCODE_XFER KeyCode = 2805
|
||||
KEYCODE_PROG1 KeyCode = 2806
|
||||
KEYCODE_PROG2 KeyCode = 2807
|
||||
KEYCODE_MSDOS KeyCode = 2808
|
||||
KEYCODE_SCREENLOCK KeyCode = 2809
|
||||
KEYCODE_DIRECTION_ROTATE_DISPLAY KeyCode = 2810
|
||||
KEYCODE_CYCLEWINDOWS KeyCode = 2811
|
||||
KEYCODE_COMPUTER KeyCode = 2812
|
||||
KEYCODE_EJECTCLOSECD KeyCode = 2813
|
||||
KEYCODE_ISO KeyCode = 2814
|
||||
KEYCODE_MOVE KeyCode = 2815
|
||||
KEYCODE_F13 KeyCode = 2816
|
||||
KEYCODE_F14 KeyCode = 2817
|
||||
KEYCODE_F15 KeyCode = 2818
|
||||
KEYCODE_F16 KeyCode = 2819
|
||||
KEYCODE_F17 KeyCode = 2820
|
||||
KEYCODE_F18 KeyCode = 2821
|
||||
KEYCODE_F19 KeyCode = 2822
|
||||
KEYCODE_F20 KeyCode = 2823
|
||||
KEYCODE_F21 KeyCode = 2824
|
||||
KEYCODE_F22 KeyCode = 2825
|
||||
KEYCODE_F23 KeyCode = 2826
|
||||
KEYCODE_F24 KeyCode = 2827
|
||||
KEYCODE_PROG3 KeyCode = 2828
|
||||
KEYCODE_PROG4 KeyCode = 2829
|
||||
KEYCODE_DASHBOARD KeyCode = 2830
|
||||
KEYCODE_SUSPEND KeyCode = 2831
|
||||
KEYCODE_HP KeyCode = 2832
|
||||
KEYCODE_SOUND KeyCode = 2833
|
||||
KEYCODE_QUESTION KeyCode = 2834
|
||||
KEYCODE_CONNECT KeyCode = 2836
|
||||
KEYCODE_SPORT KeyCode = 2837
|
||||
KEYCODE_SHOP KeyCode = 2838
|
||||
KEYCODE_ALTERASE KeyCode = 2839
|
||||
KEYCODE_SWITCHVIDEOMODE KeyCode = 2841
|
||||
KEYCODE_BATTERY KeyCode = 2842
|
||||
KEYCODE_BLUETOOTH KeyCode = 2843
|
||||
KEYCODE_WLAN KeyCode = 2844
|
||||
KEYCODE_UWB KeyCode = 2845
|
||||
KEYCODE_WWAN_WIMAX KeyCode = 2846
|
||||
KEYCODE_RFKILL KeyCode = 2847
|
||||
KEYCODE_CHANNEL KeyCode = 3001
|
||||
KEYCODE_BTN_0 KeyCode = 3100
|
||||
KEYCODE_BTN_1 KeyCode = 3101
|
||||
KEYCODE_BTN_2 KeyCode = 3102
|
||||
KEYCODE_BTN_3 KeyCode = 3103
|
||||
KEYCODE_BTN_4 KeyCode = 3104
|
||||
KEYCODE_BTN_5 KeyCode = 3105
|
||||
KEYCODE_BTN_6 KeyCode = 3106
|
||||
KEYCODE_BTN_7 KeyCode = 3107
|
||||
KEYCODE_BTN_8 KeyCode = 3108
|
||||
KEYCODE_BTN_9 KeyCode = 3109
|
||||
)
|
||||
|
||||
type DeviceForward struct {
|
||||
Local string
|
||||
Remote string
|
||||
}
|
||||
|
||||
type Device struct {
|
||||
hdClient Client
|
||||
serial string
|
||||
attrs map[string]string
|
||||
}
|
||||
|
||||
func NewDevice(hdClient Client, serial string, attrs map[string]string) (Device, error) {
|
||||
device := Device{hdClient: hdClient, serial: serial, attrs: attrs}
|
||||
model, err := device.RunShellCommand("param get const.product.model")
|
||||
if err != nil {
|
||||
return device, err
|
||||
}
|
||||
attrs["model"] = model
|
||||
|
||||
brand, err := device.RunShellCommand("param get const.product.brand")
|
||||
if err != nil {
|
||||
return device, err
|
||||
}
|
||||
attrs["brand"] = brand
|
||||
|
||||
sdkVersion, err := device.RunShellCommand("param get const.product.software.version")
|
||||
if err != nil {
|
||||
return device, err
|
||||
}
|
||||
attrs["sdkVersion"] = sdkVersion
|
||||
|
||||
osVersion, err := device.RunShellCommand("param get const.ohos.apiversion")
|
||||
if err != nil {
|
||||
return device, err
|
||||
}
|
||||
attrs["osVersion"] = osVersion
|
||||
|
||||
cpu, err := device.RunShellCommand("param get const.product.cpu.abilist")
|
||||
if err != nil {
|
||||
return device, err
|
||||
}
|
||||
attrs["cpu"] = cpu
|
||||
|
||||
product, err := device.RunShellCommand("param get const.product.name")
|
||||
if err != nil {
|
||||
return device, err
|
||||
}
|
||||
attrs["product"] = product
|
||||
|
||||
_, err = device.RunShellCommand("setenforce 1")
|
||||
if err != nil {
|
||||
return device, err
|
||||
}
|
||||
|
||||
return device, nil
|
||||
}
|
||||
|
||||
func (d Device) HasAttribute(key string) bool {
|
||||
_, ok := d.attrs[key]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (d Device) Product() (string, error) {
|
||||
if d.HasAttribute("product") {
|
||||
return d.attrs["product"], nil
|
||||
}
|
||||
return "", errors.New("does not have attribute: product")
|
||||
}
|
||||
|
||||
func (d Device) Model() (string, error) {
|
||||
if d.HasAttribute("model") {
|
||||
return d.attrs["model"], nil
|
||||
}
|
||||
return "", errors.New("does not have attribute: model")
|
||||
}
|
||||
|
||||
func (d Device) Usb() (string, error) {
|
||||
if d.HasAttribute("usb") {
|
||||
return d.attrs["usb"], nil
|
||||
}
|
||||
return "", errors.New("does not have attribute: usb")
|
||||
}
|
||||
|
||||
func (d Device) DeviceInfo() map[string]string {
|
||||
return d.attrs
|
||||
}
|
||||
|
||||
func (d Device) Serial() string {
|
||||
return d.serial
|
||||
}
|
||||
|
||||
func (d Device) IsUsb() (bool, error) {
|
||||
usb, err := d.Usb()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return usb != "", nil
|
||||
}
|
||||
|
||||
func (d Device) Screenshot(localPath string) error {
|
||||
tmpPath := fmt.Sprintf("/data/local/tmp/hypium_tmp_shot_%d.jpeg", time.Now().Unix())
|
||||
_, err := d.RunShellCommand("snapshot_display", "-f", tmpPath)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to take screencap \n%v", err)
|
||||
return err
|
||||
}
|
||||
err = d.PullFile(tmpPath, localPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, _ = d.RunShellCommand("rm", "-rf", "tmpPath")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d Device) Install(localPath string) error {
|
||||
res, err := d.ExecuteCommand(fmt.Sprintf("install -r %s", localPath))
|
||||
if err != nil || !strings.Contains(res, "success") {
|
||||
err = fmt.Errorf("failed to install %s %v, Msg: %s", localPath, err, res)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d Device) Forward(remotePort int) (localPort int, err error) {
|
||||
remote := fmt.Sprintf("tcp:%d", remotePort)
|
||||
localPort, err = GetFreePort()
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to get free port \n%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
command := ""
|
||||
local := fmt.Sprintf("tcp:%d", localPort)
|
||||
|
||||
command = fmt.Sprintf("fport %s %s", local, remote)
|
||||
_, err = d.ExecuteCommand(command)
|
||||
return
|
||||
}
|
||||
|
||||
func (d Device) ForwardKill(localPort int) (err error) {
|
||||
local := fmt.Sprintf("tcp:%d", localPort)
|
||||
_, err = d.hdClient.executeCommand(fmt.Sprintf("-t %s fport rm %s", d.serial, local))
|
||||
return
|
||||
}
|
||||
|
||||
func (d Device) RunShellCommand(cmd string, args ...string) (string, error) {
|
||||
if len(args) > 0 {
|
||||
cmd = fmt.Sprintf("%s %s", cmd, strings.Join(args, " "))
|
||||
}
|
||||
if strings.TrimSpace(cmd) == "" {
|
||||
return "", errors.New("hd shell: command cannot be empty")
|
||||
}
|
||||
return d.ExecuteCommand(fmt.Sprintf("shell %s", cmd))
|
||||
}
|
||||
|
||||
func (d Device) createDeviceTransport() (tp transport, err error) {
|
||||
return newTransport(fmt.Sprintf("%s:%d", d.hdClient.host, d.hdClient.port), false, d.serial)
|
||||
}
|
||||
|
||||
func (d Device) createDeviceAliveTransport() (tp transport, err error) {
|
||||
return newTransport(fmt.Sprintf("%s:%d", d.hdClient.host, d.hdClient.port), true, d.serial)
|
||||
}
|
||||
|
||||
func (d Device) createUitestTransport() (uTp uitestTransport, err error) {
|
||||
port, err := d.Forward(8012)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to forward uitest port \n%v", err)
|
||||
return
|
||||
}
|
||||
return newUitestTransport(d.hdClient.host, fmt.Sprintf("%d", port))
|
||||
}
|
||||
|
||||
func (d Device) createUitestKitTransport() (uTp uitestKitTransport, err error) {
|
||||
port, err := d.Forward(8012)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to forward uitest port \n%v", err)
|
||||
return
|
||||
}
|
||||
return newUitestKitTransport(d.serial, d.hdClient.host, fmt.Sprintf("%d", port))
|
||||
}
|
||||
|
||||
func (d Device) ExecuteCommand(command string) (resp string, err error) {
|
||||
var tp transport
|
||||
if tp, err = d.createDeviceTransport(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer func() { _ = tp.Close() }()
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
if err = tp.SendCommand(command); err != nil {
|
||||
return "", err
|
||||
}
|
||||
resp, err = tp.ReadStringAll()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if strings.Contains(resp, "[Fail]") {
|
||||
return resp, fmt.Errorf("failed to execute command 「%s」 \nerror: %s", command, resp)
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (d Device) PushFile(localPath string, remotePath string) (err error) {
|
||||
var tp transport
|
||||
if tp, err = d.createDeviceTransport(); err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() { _ = tp.Close() }()
|
||||
if err = tp.SendCommand(fmt.Sprintf("file send %s %s", localPath, remotePath)); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = tp.ReadAll()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d Device) PullFile(remotePath string, localPath string) (err error) {
|
||||
var tp transport
|
||||
if tp, err = d.createDeviceTransport(); err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() { _ = tp.Close() }()
|
||||
|
||||
if err = tp.SendCommand(fmt.Sprintf("file recv %s %s", remotePath, localPath)); err != nil {
|
||||
return err
|
||||
}
|
||||
res, err := tp.ReadStringAll()
|
||||
if err == nil {
|
||||
if strings.Contains(res, "Fail") {
|
||||
return fmt.Errorf("failed to pull: msg: %s", res)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetFreePort() (int, error) {
|
||||
addr, err := net.ResolveTCPAddr("tcp", "localhost:0")
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("resolve tcp addr failed \n%v", err)
|
||||
}
|
||||
|
||||
l, err := net.ListenTCP("tcp", addr)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("listen tcp addr failed \n%v", err)
|
||||
}
|
||||
defer func() {
|
||||
_ = l.Close()
|
||||
}()
|
||||
return l.Addr().(*net.TCPAddr).Port, nil
|
||||
}
|
||||
253
pkg/ghdc/device_test.go
Normal file
253
pkg/ghdc/device_test.go
Normal file
@@ -0,0 +1,253 @@
|
||||
package ghdc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDevice_Product(t *testing.T) {
|
||||
hdClient, err := NewClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
devices, err := hdClient.DeviceList()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for i := range devices {
|
||||
dev := devices[i]
|
||||
product, err := dev.Product()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(dev.Serial(), product)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDevice_Model(t *testing.T) {
|
||||
hdClient, err := NewClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
devices, err := hdClient.DeviceList()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for i := range devices {
|
||||
dev := devices[i]
|
||||
model, err := dev.Model()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(dev.Serial(), model)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDevice_Usb(t *testing.T) {
|
||||
hdClient, err := NewClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
devices, err := hdClient.DeviceList()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for i := range devices {
|
||||
dev := devices[i]
|
||||
usb, err := dev.Usb()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
isUsb, err := dev.IsUsb()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(dev.Serial(), usb, isUsb)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDevice_DeviceInfo(t *testing.T) {
|
||||
hdClient, err := NewClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
devices, err := hdClient.DeviceList()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for i := range devices {
|
||||
dev := devices[i]
|
||||
t.Log(dev.DeviceInfo())
|
||||
}
|
||||
}
|
||||
|
||||
func TestDevice_Forward(t *testing.T) {
|
||||
hdClient, err := NewClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
devices, err := hdClient.DeviceList()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(devices) == 0 {
|
||||
t.Fatal("not found available device")
|
||||
}
|
||||
SetDebug(true)
|
||||
|
||||
localPort := 61000
|
||||
localPort, err = devices[0].Forward(6790)
|
||||
t.Log(fmt.Sprintf("forward local port %d \n", localPort))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = devices[0].ForwardKill(localPort)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDevice_ForwardKill(t *testing.T) {
|
||||
hdClient, err := NewClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
devices, err := hdClient.DeviceList()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(devices) == 0 {
|
||||
t.Fatal("not found available device")
|
||||
}
|
||||
SetDebug(true)
|
||||
|
||||
err = devices[0].ForwardKill(6790)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDevice_RunShellCommand(t *testing.T) {
|
||||
hdClient, err := NewClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
devices, err := hdClient.DeviceList()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(devices) == 0 {
|
||||
t.Fatal("not found available device")
|
||||
}
|
||||
dev := devices[0]
|
||||
|
||||
cmdOutput, err := dev.RunShellCommand("pwd")
|
||||
if err != nil {
|
||||
t.Fatal(dev.serial, err)
|
||||
}
|
||||
t.Log("\n⬇️"+dev.serial+"⬇️\n", cmdOutput)
|
||||
}
|
||||
|
||||
func TestDevice_Push(t *testing.T) {
|
||||
hdClient, err := NewClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
devices, err := hdClient.DeviceList()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(devices) == 0 {
|
||||
t.Fatal("not found available device")
|
||||
}
|
||||
dev := devices[0]
|
||||
|
||||
SetDebug(true)
|
||||
|
||||
err = dev.PushFile("/tmp/test.txt", "/data/local/tmp/push.txt")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDevice_Pull(t *testing.T) {
|
||||
hdClient, err := NewClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
devices, err := hdClient.DeviceList()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(devices) == 0 {
|
||||
t.Fatal("not found available device")
|
||||
}
|
||||
dev := devices[0]
|
||||
|
||||
SetDebug(true)
|
||||
|
||||
err = dev.PullFile("/data/local/tmp/push.txt", "/tmp/test2.txt")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDevice_Screenshot(t *testing.T) {
|
||||
hdClient, err := NewClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
devices, err := hdClient.DeviceList()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(devices) == 0 {
|
||||
t.Fatal("not found available device")
|
||||
}
|
||||
dev := devices[0]
|
||||
|
||||
SetDebug(true)
|
||||
|
||||
err = dev.Screenshot("/tmp/test.jpeg")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDevice_GetSoVersion(t *testing.T) {
|
||||
hdClient, err := NewClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
devices, err := hdClient.DeviceList()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(devices) == 0 {
|
||||
t.Fatal("not found available device")
|
||||
}
|
||||
dev := devices[0]
|
||||
|
||||
SetDebug(true)
|
||||
res, err := dev.RunShellCommand("cat data/local/tmp/agent.so |grep -a UITEST_AGENT_LIBRARY")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(res)
|
||||
}
|
||||
17
pkg/ghdc/ghdc.go
Normal file
17
pkg/ghdc/ghdc.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package ghdc
|
||||
|
||||
import "log"
|
||||
|
||||
var debugFlag = false
|
||||
|
||||
// SetDebug set debug mode
|
||||
func SetDebug(debug bool) {
|
||||
debugFlag = debug
|
||||
}
|
||||
|
||||
func debugLog(msg string) {
|
||||
if !debugFlag {
|
||||
return
|
||||
}
|
||||
log.Println("[DEBUG] [ghd] " + msg)
|
||||
}
|
||||
1
pkg/ghdc/minUiTestVersion.txt
Normal file
1
pkg/ghdc/minUiTestVersion.txt
Normal file
@@ -0,0 +1 @@
|
||||
4.1.4.0
|
||||
236
pkg/ghdc/transport.go
Normal file
236
pkg/ghdc/transport.go
Normal file
@@ -0,0 +1,236 @@
|
||||
package ghdc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
var ErrConnBroken = errors.New("socket connection broken")
|
||||
|
||||
var DefaultReadTimeout time.Duration = 60 * time.Second
|
||||
|
||||
var DATA_UNIT_LENGTH = 4
|
||||
|
||||
type transport struct {
|
||||
sock net.Conn
|
||||
readTimeout time.Duration
|
||||
connectKey string
|
||||
}
|
||||
|
||||
func newTransport(address string, alive bool, connectKey string, readTimeout ...time.Duration) (tp transport, err error) {
|
||||
if len(readTimeout) == 0 {
|
||||
readTimeout = []time.Duration{DefaultReadTimeout}
|
||||
}
|
||||
tp.readTimeout = readTimeout[0]
|
||||
tp.connectKey = connectKey
|
||||
if tp.sock, err = net.Dial("tcp", address); err != nil {
|
||||
err = fmt.Errorf("ghdc transport: %w", err)
|
||||
return
|
||||
}
|
||||
if err = _handleShake(tp.sock, connectKey); err != nil {
|
||||
return
|
||||
}
|
||||
if alive {
|
||||
if err = tp.setAlive(); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func _handleShake(sock net.Conn, connectKey string) (err error) {
|
||||
data, err := _readN(sock, 48)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !(data[4] == 79 && data[5] == 72 && data[6] == 79 && data[7] == 83 && data[9] == 72 && data[10] == 68 && data[11] == 67) {
|
||||
return fmt.Errorf("handle shake error")
|
||||
}
|
||||
bannerStr := []byte("OHOS HDC\x00\x00\x00\x00")
|
||||
connectKeyBytes256 := [256]byte{}
|
||||
copy(connectKeyBytes256[:], connectKey)
|
||||
size := 12 + 256
|
||||
buffer := new(bytes.Buffer)
|
||||
if err = binary.Write(buffer, binary.BigEndian, uint32(size)); err != nil {
|
||||
return fmt.Errorf("transport write: %w", err)
|
||||
}
|
||||
if err = binary.Write(buffer, binary.BigEndian, bannerStr); err != nil {
|
||||
return fmt.Errorf("transport write: %w", err)
|
||||
}
|
||||
if err = binary.Write(buffer, binary.BigEndian, connectKeyBytes256); err != nil {
|
||||
return fmt.Errorf("transport write: %w", err)
|
||||
}
|
||||
return _send(sock, buffer.Bytes())
|
||||
}
|
||||
|
||||
func (t transport) setAlive() (err error) {
|
||||
return _sendCommand(t.sock, "alive")
|
||||
}
|
||||
|
||||
func (t transport) SendCommand(command string) (err error) {
|
||||
return _sendCommand(t.sock, command)
|
||||
}
|
||||
|
||||
func _sendCommand(writer io.Writer, command string) (err error) {
|
||||
command += "\x00"
|
||||
buf := new(bytes.Buffer)
|
||||
if err = binary.Write(buf, binary.BigEndian, uint32(len(command))); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = binary.Write(buf, binary.BigEndian, []byte(command)); err != nil {
|
||||
return err
|
||||
}
|
||||
debugLog(fmt.Sprintf("--> %s", command))
|
||||
return _send(writer, buf.Bytes())
|
||||
}
|
||||
|
||||
func (t transport) SendBytes(data []byte) (err error) {
|
||||
length := uint32(len(data))
|
||||
|
||||
newData := make([]byte, 4+len(data))
|
||||
|
||||
binary.BigEndian.PutUint32(newData[:4], length)
|
||||
|
||||
copy(newData[4:], data)
|
||||
return _send(t.sock, newData)
|
||||
}
|
||||
|
||||
func (t transport) ReadStringAll() (s string, err error) {
|
||||
var raw []byte
|
||||
raw, err = _readAll(t.sock)
|
||||
return string(raw), err
|
||||
}
|
||||
|
||||
func (t transport) ReadAll() (raw []byte, err error) {
|
||||
return _readAll(t.sock)
|
||||
}
|
||||
|
||||
func _readAll(reader io.Reader) (raw []byte, err error) {
|
||||
buffer := new(bytes.Buffer)
|
||||
for true {
|
||||
lengthBuf := make([]byte, 4)
|
||||
_, err := io.ReadFull(reader, lengthBuf)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return buffer.Bytes(), nil
|
||||
} else if errors.Is(err, io.ErrUnexpectedEOF) {
|
||||
err = fmt.Errorf("reached unexpected EOF, read partial data: %s %v", string(buffer.Bytes()), err)
|
||||
return nil, err
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
length := binary.BigEndian.Uint32(lengthBuf)
|
||||
|
||||
data, err := _readN(reader, int(length))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buffer.Write(data)
|
||||
|
||||
}
|
||||
return buffer.Bytes(), nil
|
||||
}
|
||||
|
||||
func (t transport) UnpackString() (s string, err error) {
|
||||
var raw []byte
|
||||
raw, err = t.UnpackBytes()
|
||||
return string(raw), err
|
||||
}
|
||||
|
||||
func (t transport) UnpackBytes() (raw []byte, err error) {
|
||||
var length string
|
||||
if length, err = t.ReadStringN(4); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var size int64
|
||||
if size, err = strconv.ParseInt(length, 16, 64); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
raw, err = t.RehdytesN(int(size))
|
||||
debugLog(fmt.Sprintf("\r%s", raw))
|
||||
return
|
||||
}
|
||||
|
||||
func (t transport) ReadStringN(size int) (s string, err error) {
|
||||
var raw []byte
|
||||
if raw, err = t.RehdytesN(size); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(raw), nil
|
||||
}
|
||||
|
||||
func (t transport) RehdytesN(size int) (raw []byte, err error) {
|
||||
_ = t.sock.SetReadDeadline(time.Now().Add(t.readTimeout))
|
||||
return _readN(t.sock, size)
|
||||
}
|
||||
|
||||
func _readResponse(reader io.Reader) error {
|
||||
raw, err := _readN(reader, DATA_UNIT_LENGTH)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read response length: %w", err)
|
||||
}
|
||||
if !bytes.Equal(raw[0:4], []byte("OKAY")) {
|
||||
fmt.Printf("failed to push file: %s\n", string(raw))
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
raw, err = _readAll(reader)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read response data: %w", err)
|
||||
}
|
||||
return fmt.Errorf("read response error %s", string(raw))
|
||||
}
|
||||
|
||||
func (t transport) Close() (err error) {
|
||||
if t.sock == nil {
|
||||
return nil
|
||||
}
|
||||
return t.sock.Close()
|
||||
}
|
||||
|
||||
func _send(writer io.Writer, msg []byte) (err error) {
|
||||
for totalSent := 0; totalSent < len(msg); {
|
||||
var sent int
|
||||
if sent, err = writer.Write(msg[totalSent:]); err != nil {
|
||||
return err
|
||||
}
|
||||
if sent == 0 {
|
||||
return ErrConnBroken
|
||||
}
|
||||
totalSent += sent
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func _read(reader io.Reader) (data []byte, err error) {
|
||||
buf := make([]byte, 4*1024*1024)
|
||||
n, err := reader.Read(buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf[:n], nil
|
||||
}
|
||||
|
||||
func _readN(reader io.Reader, size int) (raw []byte, err error) {
|
||||
raw = make([]byte, 0, size)
|
||||
for len(raw) < size {
|
||||
buf := make([]byte, size-len(raw))
|
||||
var n int
|
||||
if n, err = io.ReadFull(reader, buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if n == 0 {
|
||||
return nil, ErrConnBroken
|
||||
}
|
||||
raw = append(raw, buf...)
|
||||
}
|
||||
return
|
||||
}
|
||||
927
pkg/ghdc/ui_driver.go
Normal file
927
pkg/ghdc/ui_driver.go
Normal file
@@ -0,0 +1,927 @@
|
||||
package ghdc
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
//go:embed agent.so
|
||||
var agentSO embed.FS
|
||||
|
||||
type UitestRequest struct {
|
||||
Module string `json:"module,omitempty"`
|
||||
Method string `json:"method,omitempty"`
|
||||
Params interface{} `json:"params,omitempty"`
|
||||
RequestId string `json:"request_id,omitempty"`
|
||||
}
|
||||
|
||||
type UitestResponse struct {
|
||||
Result interface{} `json:"result,omitempty"`
|
||||
Exception *UitestException `json:"exception,omitempty"`
|
||||
}
|
||||
|
||||
type UitestKitResponse struct {
|
||||
Result interface{} `json:"result,omitempty"`
|
||||
Exception string `json:"exception,omitempty"`
|
||||
}
|
||||
|
||||
type UitestException struct {
|
||||
Message string `json:"message,omitempty"`
|
||||
Code int `json:"code,omitempty"`
|
||||
}
|
||||
|
||||
type UIDriver struct {
|
||||
Device
|
||||
uTp *uitestTransport
|
||||
uKTp *uitestKitTransport
|
||||
}
|
||||
|
||||
type Dimension struct {
|
||||
Width int `json:"width,omitempty"`
|
||||
Height int `json:"height,omitempty"`
|
||||
}
|
||||
|
||||
func NewUIDriver(device Device) (d *UIDriver, err error) {
|
||||
d = new(UIDriver)
|
||||
d.Device = device
|
||||
err = d.prepareDevice()
|
||||
if err != nil {
|
||||
err = fmt.Errorf("[uitest] failed to prepare device \n%v", err)
|
||||
return
|
||||
}
|
||||
uTp, err := device.createUitestTransport()
|
||||
if err != nil {
|
||||
err = fmt.Errorf("[uitest] failed to create uitest transport \n%v", err)
|
||||
return
|
||||
}
|
||||
uKTp, err := device.createUitestKitTransport()
|
||||
if err != nil {
|
||||
err = fmt.Errorf("[uitest] failed to create uitest kit transport \n%v", err)
|
||||
return
|
||||
}
|
||||
d.uTp = &uTp
|
||||
d.uKTp = &uKTp
|
||||
return
|
||||
}
|
||||
|
||||
func (d *UIDriver) Close() {
|
||||
if d.uTp != nil {
|
||||
d.uTp.Close()
|
||||
}
|
||||
if d.uKTp != nil {
|
||||
d.uKTp.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (d *UIDriver) Reconnect() error {
|
||||
d.Close()
|
||||
uTp, err := d.createUitestTransport()
|
||||
if err != nil {
|
||||
err = fmt.Errorf("[uitest] failed to create uitest transport \n%v", err)
|
||||
return err
|
||||
}
|
||||
uKTp, err := d.createUitestKitTransport()
|
||||
if err != nil {
|
||||
err = fmt.Errorf("[uitest] failed to create uitest kit transport \n%v", err)
|
||||
return err
|
||||
}
|
||||
d.uTp = &uTp
|
||||
d.uKTp = &uKTp
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *UIDriver) prepareDevice() error {
|
||||
uitestPid, err := d.Device.RunShellCommand("pidof uitest")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
uitestPid = strings.TrimSpace(uitestPid)
|
||||
|
||||
isLowerVersion, err := d.needUpdateLib()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if uitestPid != "" && !isLowerVersion {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err = d.Device.RunShellCommand("param set persist.ace.testmode.enabled 1")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if isLowerVersion {
|
||||
if uitestPid != "" {
|
||||
_, err = d.Device.RunShellCommand("kill -9 " + uitestPid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
uitestPid = ""
|
||||
}
|
||||
|
||||
err = d.updateLib()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if uitestPid == "" {
|
||||
_, err = d.Device.RunShellCommand("uitest start-daemon singleness")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *UIDriver) isServerRunning() bool {
|
||||
res, err := d.Device.RunShellCommand("top -H -n 1 -p $(pidof uitest)")
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if strings.Contains(res, "rpc-") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (d *UIDriver) updateLib() error {
|
||||
tmpDir := os.TempDir()
|
||||
soFileName := filepath.Join(tmpDir, "agent.so")
|
||||
soRaw, err := agentSO.ReadFile("agent.so")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.WriteFile(soFileName, soRaw, os.ModePerm)
|
||||
if err != nil {
|
||||
fmt.Println("Error writing file:", err)
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = d.Device.RunShellCommand("rm /data/tmp/local/agent.so")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = d.Device.PushFile(soFileName, "/data/local/tmp/agent.so")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *UIDriver) needUpdateLib() (res bool, err error) {
|
||||
deviceVersionStr, err := d.Device.RunShellCommand("cat data/local/tmp/agent.so |grep -a UITEST_AGENT_LIBRARY")
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
soRaw, err := agentSO.ReadFile("agent.so")
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
// 定义要搜索的字符串
|
||||
searchString := "UITEST_AGENT_LIBRARY"
|
||||
|
||||
// 将二进制内容转换为字符串
|
||||
content := string(soRaw)
|
||||
|
||||
// 按行分割内容
|
||||
lines := strings.Split(content, "\n")
|
||||
|
||||
// 搜索包含特定字符串的行
|
||||
for _, line := range lines {
|
||||
if strings.Contains(line, searchString) {
|
||||
update := false
|
||||
deviceVersion, err := getVersion(deviceVersionStr)
|
||||
if err != nil {
|
||||
update = true
|
||||
}
|
||||
lowestVersion, err := getVersion(line)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if update || lowestVersion[0] > deviceVersion[0] || lowestVersion[1] > deviceVersion[1] || lowestVersion[2] > deviceVersion[2] {
|
||||
return true, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
|
||||
func (d *UIDriver) supportDevice() error {
|
||||
rootDir, err := os.Getwd()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
raw, err := os.ReadFile(filepath.Join(rootDir, "minUiTestVersion.txt"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
lowestVersion := string(raw)
|
||||
uitestVersion, err := d.Device.RunShellCommand("uitest --version")
|
||||
if lowestVersion > uitestVersion {
|
||||
return fmt.Errorf("not supprt uitest lowest version %s, device version %s", lowestVersion, uitestVersion)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *UIDriver) createDriver() (driver string, err error) {
|
||||
params := map[string]interface{}{
|
||||
"api": "Driver.create",
|
||||
"this": nil,
|
||||
"args": []string{},
|
||||
"message_type": "hypium",
|
||||
}
|
||||
res, err := d.sendUitestReq(newHypiumRequest(params, "callHypiumApi"))
|
||||
if err != nil {
|
||||
err = fmt.Errorf("[uitest] failed to create driver")
|
||||
return
|
||||
}
|
||||
if res.Exception != nil {
|
||||
err = fmt.Errorf("[uitest] failed to create driver msg: %s", res.Exception.Message)
|
||||
return
|
||||
}
|
||||
driver = res.Result.(string)
|
||||
return
|
||||
}
|
||||
|
||||
func (d *UIDriver) createFocused() (onName string, err error) {
|
||||
params := map[string]interface{}{
|
||||
"api": "On.focused",
|
||||
"this": "On#seed",
|
||||
"args": []bool{true},
|
||||
"message_type": "hypium",
|
||||
}
|
||||
res, err := d.sendUitestReq(newHypiumRequest(params, "callHypiumApi"))
|
||||
if err != nil {
|
||||
err = fmt.Errorf("[uitest] failed to create focused")
|
||||
return
|
||||
}
|
||||
if res.Exception != nil {
|
||||
err = fmt.Errorf("[uitest] failed to create focused msg: %s", res.Exception.Message)
|
||||
return
|
||||
}
|
||||
onName = res.Result.(string)
|
||||
return
|
||||
}
|
||||
|
||||
func (d *UIDriver) findComponent(driverName, onName string) (componentName string, err error) {
|
||||
params := map[string]interface{}{
|
||||
"api": "Driver.waitForComponent",
|
||||
"this": driverName,
|
||||
"args": []any{onName, 5000},
|
||||
"message_type": "hypium",
|
||||
}
|
||||
res, err := d.sendUitestReq(newHypiumRequest(params, "callHypiumApi"))
|
||||
if err != nil {
|
||||
err = fmt.Errorf("[uitest] failed to create focused")
|
||||
return
|
||||
}
|
||||
if res.Exception != nil {
|
||||
err = fmt.Errorf("[uitest] failed to create focused msg: %s", res.Exception.Message)
|
||||
return
|
||||
}
|
||||
componentName = res.Result.(string)
|
||||
return
|
||||
}
|
||||
|
||||
func (d *UIDriver) createPointMatrix(pointerMatrix *PointerMatrix) (pointMatrixName string, err error) {
|
||||
fingers, steps := pointerMatrix.fingerIndexStats()
|
||||
params := map[string]interface{}{
|
||||
"api": "PointerMatrix.create",
|
||||
"this": nil,
|
||||
"args": []int{fingers, steps},
|
||||
"message_type": "hypium",
|
||||
}
|
||||
res, err := d.sendUitestReq(newHypiumRequest(params, "callHypiumApi"))
|
||||
if err != nil {
|
||||
err = fmt.Errorf("[uitest] failed to create PointerMatrix")
|
||||
return
|
||||
}
|
||||
if res.Exception != nil {
|
||||
err = fmt.Errorf("[uitest] failed to create PointerMatrix msg: %s", res.Exception.Message)
|
||||
return
|
||||
}
|
||||
pointMatrixName = res.Result.(string)
|
||||
return
|
||||
}
|
||||
|
||||
func (d *UIDriver) releaseObj(obj []string) error {
|
||||
params := map[string]interface{}{
|
||||
"api": "BackendObjectsCleaner",
|
||||
"this": nil,
|
||||
"args": obj,
|
||||
}
|
||||
res, err := d.sendUitestReq(newHypiumRequest(params, "callHypiumApi"))
|
||||
if err != nil {
|
||||
err = fmt.Errorf("[uitest] failed to release driver")
|
||||
return err
|
||||
}
|
||||
if res.Exception != nil {
|
||||
err = fmt.Errorf("[uitest] failed to release driver msg: %s", res.Exception.Message)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *UIDriver) Touch(x, y int) error {
|
||||
driverName, err := d.createDriver()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = d.releaseObj([]string{driverName})
|
||||
}()
|
||||
|
||||
params := map[string]interface{}{
|
||||
"api": "Driver.click",
|
||||
"this": driverName,
|
||||
"args": []int{x, y},
|
||||
"message_type": "hypium",
|
||||
}
|
||||
res, err := d.sendUitestReq(newHypiumRequest(params, "callHypiumApi"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if res.Exception != nil {
|
||||
return fmt.Errorf("[uitest] failed to touch (%d, %d): %s", x, y, res.Exception.Message)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *UIDriver) Drag(fromX, fromY, toX, toY int, duration float64) error {
|
||||
driverName, err := d.createDriver()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = d.releaseObj([]string{driverName})
|
||||
}()
|
||||
|
||||
distance := math.Sqrt(math.Pow(float64(fromX-toX), 2) + math.Pow(float64(fromX-toX), 2))
|
||||
speed := int(distance / duration)
|
||||
|
||||
params := map[string]interface{}{
|
||||
"api": "Driver.drag",
|
||||
"this": driverName,
|
||||
"args": []int{fromX, fromY, toX, toY, speed},
|
||||
"message_type": "hypium",
|
||||
}
|
||||
res, err := d.sendUitestReq(newHypiumRequest(params, "callHypiumApi"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if res.Exception != nil {
|
||||
return fmt.Errorf("[uitest] failed to Drag from (%d, %d) to (%d, %d): %s", fromX, fromY, toX, toY, res.Exception.Message)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *UIDriver) PressKey(key KeyCode) error {
|
||||
driverName, err := d.createDriver()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = d.releaseObj([]string{driverName})
|
||||
}()
|
||||
|
||||
params := map[string]interface{}{
|
||||
"api": "Driver.triggerKey",
|
||||
"this": driverName,
|
||||
"args": []KeyCode{key},
|
||||
"message_type": "hypium",
|
||||
}
|
||||
res, err := d.sendUitestReq(newHypiumRequest(params, "callHypiumApi"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if res.Exception != nil {
|
||||
return fmt.Errorf("[uitest] failed to Press Key code:%d: %s", key, res.Exception.Message)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *UIDriver) PressKeys(keys []KeyCode) error {
|
||||
driverName, err := d.createDriver()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = d.releaseObj([]string{driverName})
|
||||
}()
|
||||
|
||||
params := map[string]interface{}{
|
||||
"api": "Driver.triggerCombineKeys",
|
||||
"this": driverName,
|
||||
"args": keys,
|
||||
"message_type": "hypium",
|
||||
}
|
||||
res, err := d.sendUitestReq(newHypiumRequest(params, "callHypiumApi"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if res.Exception != nil {
|
||||
return fmt.Errorf("[uitest] failed to Press Key code:%v: %s", keys, res.Exception.Message)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *UIDriver) InjectGesture(gesture *Gesture, speedArg ...int) error {
|
||||
return d.InjectMultiGesture([]*Gesture{gesture}, speedArg...)
|
||||
}
|
||||
|
||||
func (d *UIDriver) InjectMultiGesture(gestures []*Gesture, speedArg ...int) error {
|
||||
speed := 2000
|
||||
var releaseObj []string
|
||||
defer func() {
|
||||
if len(releaseObj) > 0 {
|
||||
_ = d.releaseObj(releaseObj)
|
||||
}
|
||||
}()
|
||||
if len(speedArg) > 0 && speedArg[0] > 0 {
|
||||
speed = speedArg[0]
|
||||
}
|
||||
driverName, err := d.createDriver()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
releaseObj = append(releaseObj, driverName)
|
||||
|
||||
pointerMatrix := d.gestureToPointMatrix(gestures)
|
||||
|
||||
pointerMatrixName, err := d.createPointMatrix(pointerMatrix)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
releaseObj = append(releaseObj, pointerMatrixName)
|
||||
|
||||
for step, point := range pointerMatrix.points {
|
||||
err = d.setPoint(pointerMatrixName, point.index, step, point.point)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = d.injectMultiPointerAction(driverName, pointerMatrixName, speed)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *UIDriver) InputText(text string) error {
|
||||
params := map[string]interface{}{
|
||||
"api": "Driver.inputText",
|
||||
"this": nil,
|
||||
"args": []any{map[string]interface{}{
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
}, text},
|
||||
"message_type": "hypium",
|
||||
}
|
||||
|
||||
res, err := d.sendUitestReq(newHypiumRequest(params, "callHypiumApi"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if res.Exception != nil {
|
||||
return fmt.Errorf("[uitest] failed to input text %s: %s", text, res.Exception.Message)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *UIDriver) InputTextOnFocused(text string) error {
|
||||
driverName, err := d.createDriver()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = d.releaseObj([]string{driverName})
|
||||
}()
|
||||
|
||||
onName, err := d.createFocused()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = d.releaseObj([]string{onName})
|
||||
}()
|
||||
componentName, err := d.findComponent(driverName, onName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = d.releaseObj([]string{componentName})
|
||||
}()
|
||||
params := map[string]interface{}{
|
||||
"api": "Component.inputText",
|
||||
"this": componentName,
|
||||
"args": []string{text},
|
||||
"message_type": "hypium",
|
||||
}
|
||||
|
||||
res, err := d.sendUitestReq(newHypiumRequest(params, "callHypiumApi"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if res.Exception != nil {
|
||||
return fmt.Errorf("[uitest] failed to input text %s: %s", text, res.Exception.Message)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *UIDriver) TouchDown(x, y int) error {
|
||||
params := map[string]interface{}{
|
||||
"api": "touchDown",
|
||||
"this": nil,
|
||||
"args": map[string]interface{}{
|
||||
"x": x,
|
||||
"y": y,
|
||||
},
|
||||
"message_type": "hypium",
|
||||
}
|
||||
return d.sendUitestKitNoResult(params, "Gestures", DEFAULT, nil)
|
||||
}
|
||||
|
||||
func (d *UIDriver) TouchDownAsync(x, y int) {
|
||||
go func() {
|
||||
err := d.TouchDown(x, y)
|
||||
if err != nil {
|
||||
debugLog(fmt.Sprintf("%v", err))
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (d *UIDriver) TouchMove(x, y int) error {
|
||||
params := map[string]interface{}{
|
||||
"api": "touchMove",
|
||||
"this": nil,
|
||||
"args": map[string]interface{}{
|
||||
"x": x,
|
||||
"y": y,
|
||||
},
|
||||
"message_type": "hypium",
|
||||
}
|
||||
return d.sendUitestKitNoResult(params, "Gestures", DEFAULT, nil)
|
||||
}
|
||||
|
||||
func (d *UIDriver) TouchMoveAsync(x, y int) {
|
||||
go func() {
|
||||
err := d.TouchMove(x, y)
|
||||
if err != nil {
|
||||
debugLog(fmt.Sprintf("%v", err))
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (d *UIDriver) TouchUp(x, y int) error {
|
||||
params := map[string]interface{}{
|
||||
"api": "touchUp",
|
||||
"this": nil,
|
||||
"args": map[string]interface{}{
|
||||
"x": x,
|
||||
"y": y,
|
||||
},
|
||||
"message_type": "hypium",
|
||||
}
|
||||
return d.sendUitestKitNoResult(params, "Gestures", DEFAULT, nil)
|
||||
}
|
||||
|
||||
func (d *UIDriver) TouchUpAsync(x, y int) {
|
||||
go func() {
|
||||
err := d.TouchUp(x, y)
|
||||
if err != nil {
|
||||
debugLog(fmt.Sprintf("%v", err))
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (d *UIDriver) PressRecentApp() error {
|
||||
params := map[string]interface{}{
|
||||
"api": "pressRecentApp",
|
||||
"this": nil,
|
||||
"args": map[string]interface{}{},
|
||||
"message_type": "hypium",
|
||||
}
|
||||
return d.sendUitestKitNoResult(params, "Gestures", DEFAULT, nil)
|
||||
}
|
||||
|
||||
func (d *UIDriver) PressBack() error {
|
||||
params := map[string]interface{}{
|
||||
"api": "pressBack",
|
||||
"this": nil,
|
||||
"args": map[string]interface{}{},
|
||||
"message_type": "hypium",
|
||||
}
|
||||
return d.sendUitestKitNoResult(params, "Gestures", DEFAULT, nil)
|
||||
}
|
||||
|
||||
func (d *UIDriver) PressPowerKey() error {
|
||||
params := map[string]interface{}{
|
||||
"api": "pressPowerKey",
|
||||
"this": nil,
|
||||
"args": map[string]interface{}{},
|
||||
"message_type": "hypium",
|
||||
}
|
||||
return d.sendUitestKitNoResult(params, "CtrlCmd", DEFAULT, nil)
|
||||
}
|
||||
|
||||
func (d *UIDriver) GetDisplayRotation() (direction int, err error) {
|
||||
params := map[string]interface{}{
|
||||
"api": "getDisplayRotation",
|
||||
"this": nil,
|
||||
"args": map[string]interface{}{},
|
||||
"message_type": "hypium",
|
||||
}
|
||||
res, err := d.sendUitestKit(params, "CtrlCmd", DEFAULT, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if res.Result == false {
|
||||
err = fmt.Errorf("[uitest] failed to exec method getDisplayRotation msg: %s", res.Exception)
|
||||
return
|
||||
}
|
||||
direction = (int)(res.Result.(float64))
|
||||
return direction, err
|
||||
}
|
||||
|
||||
func (d *UIDriver) UpVolume() error {
|
||||
params := map[string]interface{}{
|
||||
"api": "upVolume",
|
||||
"this": nil,
|
||||
"args": map[string]interface{}{},
|
||||
"message_type": "hypium",
|
||||
}
|
||||
return d.sendUitestKitNoResult(params, "CtrlCmd", DEFAULT, nil)
|
||||
}
|
||||
|
||||
func (d *UIDriver) DownVolume() error {
|
||||
params := map[string]interface{}{
|
||||
"api": "downVolume",
|
||||
"this": nil,
|
||||
"args": map[string]interface{}{},
|
||||
"message_type": "hypium",
|
||||
}
|
||||
return d.sendUitestKitNoResult(params, "CtrlCmd", DEFAULT, nil)
|
||||
}
|
||||
|
||||
func (d *UIDriver) RotationDisplay(direction int) error {
|
||||
params := map[string]interface{}{
|
||||
"api": "rotationDisplay",
|
||||
"this": nil,
|
||||
"args": map[string]interface{}{
|
||||
"direction": direction,
|
||||
},
|
||||
"message_type": "hypium",
|
||||
}
|
||||
return d.sendUitestKitNoResult(params, "CtrlCmd", DEFAULT, nil)
|
||||
}
|
||||
|
||||
func (d *UIDriver) GetDisplaySize() (display Dimension, err error) {
|
||||
params := map[string]interface{}{
|
||||
"api": "getDisplaySize",
|
||||
"this": nil,
|
||||
"args": map[string]interface{}{},
|
||||
"message_type": "hypium",
|
||||
}
|
||||
res, err := d.sendUitestKit(params, "CtrlCmd", DEFAULT, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if res.Result == false {
|
||||
err = fmt.Errorf("[uitest] failed to exec method getDisplaySize msg: %s", res.Exception)
|
||||
return
|
||||
}
|
||||
raw, err := json.Marshal(res.Result)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = json.Unmarshal(raw, &display)
|
||||
return
|
||||
}
|
||||
|
||||
func (d *UIDriver) StartCaptureScreen(callback UitestKitCallback) error {
|
||||
params := map[string]interface{}{
|
||||
"api": "startCaptureScreen",
|
||||
"this": nil,
|
||||
"args": map[string]interface{}{},
|
||||
"message_type": "hypium",
|
||||
}
|
||||
return d.sendUitestKitNoResult(params, "Captures", SCREEN_CAPTURE, callback)
|
||||
}
|
||||
|
||||
func (d *UIDriver) StopCaptureScreen() error {
|
||||
params := map[string]interface{}{
|
||||
"api": "stopCaptureScreen",
|
||||
"this": nil,
|
||||
"args": map[string]interface{}{},
|
||||
"message_type": "hypium",
|
||||
}
|
||||
return d.sendUitestKitNoResult(params, "Captures", DEFAULT, nil)
|
||||
}
|
||||
|
||||
func (d *UIDriver) StartCaptureUiAction(callback UitestKitCallback) error {
|
||||
params := map[string]interface{}{
|
||||
"api": "startCaptureUiAction",
|
||||
"this": nil,
|
||||
"args": map[string]interface{}{},
|
||||
"message_type": "hypium",
|
||||
}
|
||||
return d.sendUitestKitNoResult(params, "Captures", UI_ACTION_CAPTURE, callback)
|
||||
}
|
||||
|
||||
func (d *UIDriver) StopCaptureUiAction() error {
|
||||
params := map[string]interface{}{
|
||||
"api": "stopCaptureUiAction",
|
||||
"this": nil,
|
||||
"args": map[string]interface{}{},
|
||||
"message_type": "hypium",
|
||||
}
|
||||
return d.sendUitestKitNoResult(params, "Captures", DEFAULT, nil)
|
||||
}
|
||||
|
||||
func (d *UIDriver) CaptureLayout() (layout interface{}, err error) {
|
||||
params := map[string]interface{}{
|
||||
"api": "captureLayout",
|
||||
"this": nil,
|
||||
"args": map[string]interface{}{},
|
||||
"message_type": "hypium",
|
||||
}
|
||||
res, err := d.sendUitestKit(params, "Captures", DEFAULT, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if res.Result == false {
|
||||
err = fmt.Errorf("[uitest] failed to exec method captureLayout msg: %s", res.Exception)
|
||||
return
|
||||
}
|
||||
return res.Result, err
|
||||
}
|
||||
|
||||
func (d *UIDriver) sendUitestKitNoResult(params map[string]interface{}, method string, reqType ReqTypeEnum, callback UitestKitCallback) error {
|
||||
res, err := d.sendUitestKit(params, method, reqType, callback)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if res.Result == false {
|
||||
err = fmt.Errorf("[uitest] failed to exec method %s params %v msg: %s", method, params, res.Exception)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *UIDriver) sendUitestReq(req UitestRequest) (res UitestResponse, err error) {
|
||||
res, err = d.uTp.SendReq(req)
|
||||
if err != nil {
|
||||
fmt.Printf("[uitest] failed to send req first. try reconnect \n%v \n", err)
|
||||
if err = d.Reconnect(); err != nil {
|
||||
return
|
||||
}
|
||||
res, err = d.uTp.SendReq(req)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (d *UIDriver) sendUitestKit(params map[string]interface{}, method string, reqType ReqTypeEnum, callback UitestKitCallback) (response UitestKitResponse, err error) {
|
||||
request := newHypiumRequest(params, method)
|
||||
requestStr, err := request.ToString()
|
||||
if err != nil {
|
||||
err = fmt.Errorf("[uitest] failed to create req while exec method %s %v", method, err)
|
||||
return
|
||||
}
|
||||
sessionId := hashCode(fmt.Sprintf("%s%d", requestStr, time.Now().Unix()))
|
||||
if sessionId <= (1 << 24) {
|
||||
sessionId += 1 << 24
|
||||
}
|
||||
err = d.uKTp.registerCallback(reqType, sessionId, nil)
|
||||
if err != nil {
|
||||
fmt.Printf("[uitest] failed to register callback try reconnect %s %v", method, err)
|
||||
if err = d.Reconnect(); err != nil {
|
||||
return
|
||||
}
|
||||
if err = d.uKTp.registerCallback(reqType, sessionId, nil); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
res, err := d.uKTp.sendMessage(reqType, sessionId, requestStr)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("[uitest] failed to send message while exec method %s sessionId: %d %v", method, sessionId, err)
|
||||
return
|
||||
}
|
||||
err = d.uKTp.registerCallback(reqType, sessionId, callback)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("[uitest] failed to register callback while exec method %s %v", method, err)
|
||||
return
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (d *UIDriver) gestureToPointMatrix(gestures []*Gesture) *PointerMatrix {
|
||||
pointerMatrix := &PointerMatrix{}
|
||||
for fingerIndex, gestures := range gestures {
|
||||
var curPoint Point
|
||||
for _, gestureStep := range gestures.steps {
|
||||
if gestureStep.GestureType == "start" {
|
||||
pointerMatrix.setPoint(gestureStep.Point, fingerIndex, gestureStep.Duration)
|
||||
curPoint = gestureStep.Point
|
||||
}
|
||||
if gestureStep.GestureType == "move" {
|
||||
toPoint := gestureStep.Point
|
||||
offsetX := toPoint.X - curPoint.X
|
||||
offsetY := toPoint.Y - curPoint.Y
|
||||
steps := gestureStep.calculateSteps()
|
||||
for i := 0; i < steps-1; i++ {
|
||||
curPoint = Point{X: curPoint.X + (offsetX / steps), Y: curPoint.Y + (offsetY / steps)}
|
||||
pointerMatrix.setPoint(curPoint, fingerIndex, EVENT_INJECTION_DELAY_MS)
|
||||
}
|
||||
curPoint = toPoint
|
||||
if steps == 1 {
|
||||
pointerMatrix.setPoint(curPoint, fingerIndex, gestureStep.Duration%EVENT_INJECTION_DELAY_MS)
|
||||
} else {
|
||||
pointerMatrix.setPoint(curPoint, fingerIndex, EVENT_INJECTION_DELAY_MS+(gestureStep.Duration%EVENT_INJECTION_DELAY_MS))
|
||||
}
|
||||
}
|
||||
if gestureStep.GestureType == "pause" {
|
||||
steps := gestureStep.calculateSteps()
|
||||
for i := 0; i < steps-1; i++ {
|
||||
pointerMatrix.setPoint(curPoint, fingerIndex, EVENT_INJECTION_DELAY_MS)
|
||||
}
|
||||
pointerMatrix.setPoint(curPoint, fingerIndex, EVENT_INJECTION_DELAY_MS+(gestureStep.Duration%EVENT_INJECTION_DELAY_MS))
|
||||
}
|
||||
}
|
||||
}
|
||||
return pointerMatrix
|
||||
}
|
||||
|
||||
func (d *UIDriver) setPoint(pointerMatrixName string, fingerIndex int, step int, point Point) error {
|
||||
params := map[string]interface{}{
|
||||
"api": "PointerMatrix.setPoint",
|
||||
"this": pointerMatrixName,
|
||||
"args": []any{fingerIndex, step, point},
|
||||
"message_type": "hypium",
|
||||
}
|
||||
res, err := d.sendUitestReq(newHypiumRequest(params, "callHypiumApi"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if res.Exception != nil {
|
||||
return fmt.Errorf("[uitest] failed to setPoint from: %s", res.Exception.Message)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *UIDriver) injectMultiPointerAction(driverName, pointerMatrixName string, speed int) error {
|
||||
params := map[string]interface{}{
|
||||
"api": "Driver.injectMultiPointerAction",
|
||||
"this": driverName,
|
||||
"args": []any{pointerMatrixName, speed},
|
||||
"message_type": "hypium",
|
||||
}
|
||||
res, err := d.sendUitestReq(newHypiumRequest(params, "callHypiumApi"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if res.Exception != nil {
|
||||
return fmt.Errorf("[uitest] failed to injectMultiPointerAction from: %s", res.Exception.Message)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func hashCode(s string) uint32 {
|
||||
h := fnv.New32a()
|
||||
_, _ = h.Write([]byte(s))
|
||||
return h.Sum32()
|
||||
}
|
||||
|
||||
func (r UitestRequest) ToString() (result string, err error) {
|
||||
data, err := json.Marshal(r)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("error: \n%v", err)
|
||||
return
|
||||
}
|
||||
return string(data), nil
|
||||
}
|
||||
|
||||
func getVersion(str string) (version []string, err error) {
|
||||
index := strings.Index(str, "@")
|
||||
if index == -1 {
|
||||
err = fmt.Errorf("invalid version str")
|
||||
return
|
||||
}
|
||||
version = strings.Split(str[index+1:], ".")
|
||||
return
|
||||
}
|
||||
274
pkg/ghdc/ui_driver_test.go
Normal file
274
pkg/ghdc/ui_driver_test.go
Normal file
@@ -0,0 +1,274 @@
|
||||
package ghdc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
client Client
|
||||
device Device
|
||||
driver *UIDriver
|
||||
)
|
||||
|
||||
func setUp(t *testing.T) {
|
||||
var err error
|
||||
client, err = NewClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
devices, err := client.DeviceList()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(devices) == 0 {
|
||||
t.Fatal("not found available device")
|
||||
}
|
||||
device = devices[0]
|
||||
driver, err = NewUIDriver(device)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDevice_Touch(t *testing.T) {
|
||||
setUp(t)
|
||||
err := driver.Touch(1038, 798)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDevice_Drag(t *testing.T) {
|
||||
setUp(t)
|
||||
err := driver.Drag(800, 1000, 200, 1000, 0.2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
type CaptureScreenCallback struct {
|
||||
count int
|
||||
mux sync.Mutex
|
||||
}
|
||||
|
||||
// OnData handles the data received
|
||||
func (cb *CaptureScreenCallback) OnData(data []byte) {
|
||||
cb.mux.Lock()
|
||||
cb.count++
|
||||
cb.mux.Unlock()
|
||||
fmt.Printf("Data received: %s\n", string(data))
|
||||
}
|
||||
|
||||
// onError handles the error received
|
||||
func (cb *CaptureScreenCallback) OnError(err error) {
|
||||
fmt.Printf("Error received: %v\n", err)
|
||||
}
|
||||
|
||||
func (cb *CaptureScreenCallback) startCounter() {
|
||||
ticker := time.NewTicker(1 * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
for range ticker.C {
|
||||
cb.mux.Lock()
|
||||
fmt.Printf("Screen Data received count in the last second: %d\n", cb.count)
|
||||
cb.count = 0
|
||||
cb.mux.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func TestDevice_StartCaptureScreen(t *testing.T) {
|
||||
setUp(t)
|
||||
err := driver.StopCaptureScreen()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
callback := &CaptureScreenCallback{}
|
||||
err = driver.StartCaptureScreen(callback)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
callback.startCounter()
|
||||
time.Sleep(1 * time.Minute)
|
||||
}
|
||||
|
||||
func TestDevice_StartCaptureUIAction(t *testing.T) {
|
||||
setUp(t)
|
||||
err := driver.StopCaptureUiAction()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
callback := &CaptureScreenCallback{}
|
||||
err = driver.StartCaptureUiAction(callback)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
time.Sleep(1 * time.Minute)
|
||||
}
|
||||
|
||||
func TestDevice_TouchDownMoveUp(t *testing.T) {
|
||||
setUp(t)
|
||||
|
||||
for i := 0; i < 10000; i++ {
|
||||
// time.Sleep(1 * time.Second)
|
||||
debugLog(fmt.Sprintf("running... time %d", i))
|
||||
err := driver.TouchDown(225, 1700)
|
||||
if err != nil {
|
||||
debugLog(err.Error())
|
||||
// t.Fatal(err)
|
||||
}
|
||||
time.Sleep(20 * time.Millisecond)
|
||||
err = driver.TouchMove(325, 1700)
|
||||
if err != nil {
|
||||
debugLog(err.Error())
|
||||
// t.Fatal(err)
|
||||
}
|
||||
time.Sleep(20 * time.Millisecond)
|
||||
err = driver.TouchMove(425, 1700)
|
||||
if err != nil {
|
||||
debugLog(err.Error())
|
||||
// t.Fatal(err)
|
||||
}
|
||||
time.Sleep(20 * time.Millisecond)
|
||||
err = driver.TouchMove(525, 1700)
|
||||
if err != nil {
|
||||
debugLog(err.Error())
|
||||
// t.Fatal(err)
|
||||
}
|
||||
err = driver.TouchUp(625, 1700)
|
||||
if err != nil {
|
||||
debugLog(err.Error())
|
||||
// t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDevice_TouchDownUp(t *testing.T) {
|
||||
setUp(t)
|
||||
err := driver.TouchDown(200, 2000)
|
||||
if err != nil {
|
||||
debugLog(err.Error())
|
||||
}
|
||||
err = driver.TouchUp(200, 2000)
|
||||
if err != nil {
|
||||
debugLog(err.Error())
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDevice_TouchDownMoveUpAsync(t *testing.T) {
|
||||
setUp(t)
|
||||
driver.TouchDown(225, 1700)
|
||||
time.Sleep(60 * time.Millisecond)
|
||||
driver.TouchMoveAsync(325, 1700)
|
||||
time.Sleep(60 * time.Millisecond)
|
||||
driver.TouchMoveAsync(425, 1700)
|
||||
time.Sleep(60 * time.Millisecond)
|
||||
driver.TouchMoveAsync(525, 1700)
|
||||
time.Sleep(60 * time.Millisecond)
|
||||
driver.TouchUpAsync(625, 1700)
|
||||
time.Sleep(4 * time.Second)
|
||||
}
|
||||
|
||||
func TestDevice_GetDisplay(t *testing.T) {
|
||||
setUp(t)
|
||||
display, err := driver.GetDisplaySize()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(display)
|
||||
}
|
||||
|
||||
func TestDevice_GetRotation(t *testing.T) {
|
||||
setUp(t)
|
||||
rotation, err := driver.GetDisplayRotation()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(rotation)
|
||||
}
|
||||
|
||||
func TestDevice_PressRecentApp(t *testing.T) {
|
||||
setUp(t)
|
||||
err := driver.PressRecentApp()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDevice_PressBack(t *testing.T) {
|
||||
setUp(t)
|
||||
err := driver.PressBack()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDevice_PressPowerKey(t *testing.T) {
|
||||
setUp(t)
|
||||
err := driver.PressPowerKey()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDevice_UpVolume(t *testing.T) {
|
||||
setUp(t)
|
||||
err := driver.UpVolume()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDevice_DownVolume(t *testing.T) {
|
||||
setUp(t)
|
||||
err := driver.DownVolume()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDevice_CaptureLayout(t *testing.T) {
|
||||
setUp(t)
|
||||
layout, err := driver.CaptureLayout()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(layout)
|
||||
}
|
||||
|
||||
func TestDevice_InputText(t *testing.T) {
|
||||
setUp(t)
|
||||
err := driver.InputText("abcdef")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDevice_InjectPoint(t *testing.T) {
|
||||
setUp(t)
|
||||
err := driver.InjectGesture(NewGesture().Start(Point{800, 2000}).MoveTo(Point{200, 2000}, 2000))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
|
||||
func TestDevice_PressKey(t *testing.T) {
|
||||
setUp(t)
|
||||
err := driver.PressKey(KEYCODE_NUM_1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDevice_PressKeys(t *testing.T) {
|
||||
setUp(t)
|
||||
err := driver.PressKeys([]KeyCode{KEYCODE_SHIFT_LEFT, KEYCODE_NUM_1})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
83
pkg/ghdc/ui_gesture.go
Normal file
83
pkg/ghdc/ui_gesture.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package ghdc
|
||||
|
||||
import (
|
||||
"math"
|
||||
)
|
||||
|
||||
var EVENT_INJECTION_DELAY_MS = 50
|
||||
|
||||
type Point struct {
|
||||
X int `json:"x,omitempty"`
|
||||
Y int `json:"y,omitempty"`
|
||||
}
|
||||
|
||||
func (p Point) distance(p0 Point) float64 {
|
||||
return math.Sqrt(math.Pow(float64(p.X-p0.X), 2) + math.Pow(float64(p.Y-p0.Y), 2))
|
||||
}
|
||||
|
||||
type GestureStep struct {
|
||||
Point
|
||||
GestureType string
|
||||
Duration int
|
||||
}
|
||||
|
||||
type EmptyGesture struct{}
|
||||
|
||||
type Gesture struct {
|
||||
steps []*GestureStep
|
||||
}
|
||||
|
||||
type PointerMatrix struct {
|
||||
points []*FingerPoint
|
||||
}
|
||||
|
||||
type FingerPoint struct {
|
||||
index int
|
||||
point Point
|
||||
}
|
||||
|
||||
func NewGesture() *EmptyGesture {
|
||||
return new(EmptyGesture)
|
||||
}
|
||||
|
||||
func (g *EmptyGesture) Start(point Point) *Gesture {
|
||||
gesture := &Gesture{}
|
||||
gesture.steps = append(gesture.steps, &GestureStep{Point: point, GestureType: "start"})
|
||||
return gesture
|
||||
}
|
||||
|
||||
func (g *Gesture) MoveTo(point Point, duration int) *Gesture {
|
||||
g.steps = append(g.steps, &GestureStep{Point: point, GestureType: "move", Duration: duration})
|
||||
return g
|
||||
}
|
||||
|
||||
func (g *Gesture) Pause(duration int) *Gesture {
|
||||
g.steps = append(g.steps, &GestureStep{GestureType: "pause", Duration: duration})
|
||||
return g
|
||||
}
|
||||
|
||||
func (gs *GestureStep) calculateSteps() int {
|
||||
return (gs.Duration / EVENT_INJECTION_DELAY_MS) + 1
|
||||
}
|
||||
|
||||
func (pm *PointerMatrix) setPoint(point Point, fingerIndex, duration int) {
|
||||
point.X += 65536 * duration
|
||||
pm.points = append(pm.points, &FingerPoint{index: fingerIndex, point: point})
|
||||
}
|
||||
|
||||
func (pm *PointerMatrix) fingerIndexStats() (fingers int, maxSteps int) {
|
||||
indexMap := make(map[int]int)
|
||||
|
||||
for _, fp := range pm.points {
|
||||
indexMap[fp.index]++
|
||||
}
|
||||
|
||||
fingers = len(indexMap)
|
||||
for _, count := range indexMap {
|
||||
if count > maxSteps {
|
||||
maxSteps = count
|
||||
}
|
||||
}
|
||||
|
||||
return fingers, maxSteps
|
||||
}
|
||||
345
pkg/ghdc/uitest_kit_transport.go
Normal file
345
pkg/ghdc/uitest_kit_transport.go
Normal file
@@ -0,0 +1,345 @@
|
||||
package ghdc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"container/list"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type uitestKitTransport struct {
|
||||
connectionPool *ConnectionPool
|
||||
socketMap map[string]*SocketContext
|
||||
serial string
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
type SocketContext struct {
|
||||
conn net.Conn
|
||||
socketId string
|
||||
writeLock sync.Mutex
|
||||
|
||||
callbackMap map[string]UitestKitCallback
|
||||
queue *responseList
|
||||
}
|
||||
|
||||
type response struct {
|
||||
sessionId uint32
|
||||
payload []byte
|
||||
}
|
||||
|
||||
type responseList struct {
|
||||
list *list.List
|
||||
lock sync.Mutex
|
||||
}
|
||||
|
||||
func (r *responseList) Clear() {
|
||||
r.lock.Lock()
|
||||
defer r.lock.Unlock()
|
||||
r.list.Init()
|
||||
}
|
||||
|
||||
func (r *responseList) push(v interface{}) {
|
||||
defer r.lock.Unlock()
|
||||
r.lock.Lock()
|
||||
r.list.PushBack(v)
|
||||
}
|
||||
|
||||
func (r *responseList) traverse(action func(response response)) {
|
||||
r.lock.Lock()
|
||||
defer r.lock.Unlock()
|
||||
for e := r.list.Front(); e != nil; e = e.Next() {
|
||||
action(e.Value.(response))
|
||||
}
|
||||
}
|
||||
|
||||
func (r *responseList) remove(sessionId uint32) *response {
|
||||
r.lock.Lock()
|
||||
defer r.lock.Unlock()
|
||||
for e := r.list.Front(); e != nil; e = e.Next() {
|
||||
response := e.Value.(response)
|
||||
if response.sessionId == sessionId {
|
||||
r.list.Remove(e)
|
||||
return &response
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type UitestKitCallback interface {
|
||||
OnData([]byte)
|
||||
OnError(error)
|
||||
}
|
||||
|
||||
type ReqTypeEnum int
|
||||
|
||||
const (
|
||||
DEFAULT ReqTypeEnum = iota
|
||||
SCREEN_CAPTURE
|
||||
UI_ACTION_CAPTURE
|
||||
)
|
||||
|
||||
const (
|
||||
HEADER_BYTES = "_uitestkit_rpc_message_head_"
|
||||
TAILER_BYTES = "_uitestkit_rpc_message_tail_"
|
||||
)
|
||||
|
||||
func newUitestKitTransport(serial string, host string, port string) (uKtp uitestKitTransport, err error) {
|
||||
pool, err := newConnectionPool(host, port, 3)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("[uitest] failed to init connection pool \n%v", err)
|
||||
return
|
||||
}
|
||||
uKtp.connectionPool = pool
|
||||
uKtp.socketMap = make(map[string]*SocketContext)
|
||||
uKtp.serial = serial
|
||||
return
|
||||
}
|
||||
|
||||
func (uKtp *uitestKitTransport) initializeSocket(reqType ReqTypeEnum) error {
|
||||
socketId := fmt.Sprintf("socket_%d_%s", reqType, uKtp.serial)
|
||||
uKtp.mu.Lock()
|
||||
defer uKtp.mu.Unlock()
|
||||
if uKtp.socketMap[socketId] != nil {
|
||||
return nil
|
||||
}
|
||||
connection, err := uKtp.connectionPool.getConnection()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
uKtp.socketMap[socketId] = &SocketContext{socketId: socketId, conn: connection, callbackMap: make(map[string]UitestKitCallback), queue: newResponseList()}
|
||||
go uKtp.receive(reqType)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (uKtp *uitestKitTransport) disconnect(reqType ReqTypeEnum) {
|
||||
socketId := fmt.Sprintf("socket_%d_%s", reqType, uKtp.serial)
|
||||
uKtp.mu.Lock()
|
||||
defer uKtp.mu.Unlock()
|
||||
socketContext := uKtp.socketMap[socketId]
|
||||
if socketContext == nil {
|
||||
return
|
||||
}
|
||||
socketContext.Close()
|
||||
delete(uKtp.socketMap, socketId)
|
||||
}
|
||||
|
||||
func (uKtp *uitestKitTransport) registerCallback(reqType ReqTypeEnum, sessionId uint32, callback UitestKitCallback) error {
|
||||
socketId := fmt.Sprintf("socket_%d_%s", reqType, uKtp.serial)
|
||||
socketContext := uKtp.socketMap[socketId]
|
||||
if socketContext == nil {
|
||||
if err := uKtp.initializeSocket(reqType); err != nil {
|
||||
return err
|
||||
}
|
||||
socketContext = uKtp.socketMap[socketId]
|
||||
}
|
||||
for {
|
||||
socketContext.writeLock.Lock()
|
||||
res := socketContext.queue.remove(sessionId)
|
||||
socketContext.writeLock.Unlock()
|
||||
if res != nil && callback != nil {
|
||||
callback.OnData(res.payload)
|
||||
}
|
||||
if res == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
socketContext.writeLock.Lock()
|
||||
socketContext.callbackMap[strconv.Itoa(int(sessionId))] = callback
|
||||
socketContext.writeLock.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (uKtp *uitestKitTransport) receive(reqType ReqTypeEnum) {
|
||||
socketId := fmt.Sprintf("socket_%d_%s", reqType, uKtp.serial)
|
||||
defer uKtp.disconnect(reqType)
|
||||
socketContext := uKtp.socketMap[socketId]
|
||||
var receiveError error
|
||||
defer func() {
|
||||
if receiveError != nil {
|
||||
socketContext.onException(receiveError)
|
||||
}
|
||||
}()
|
||||
if socketContext == nil {
|
||||
if receiveError = uKtp.initializeSocket(reqType); receiveError != nil {
|
||||
return
|
||||
}
|
||||
socketContext = uKtp.socketMap[socketId]
|
||||
}
|
||||
|
||||
for {
|
||||
headerSize := len(HEADER_BYTES)
|
||||
raw, err := _readN(socketContext.conn, headerSize+8)
|
||||
if err != nil {
|
||||
receiveError = err
|
||||
break
|
||||
}
|
||||
if len(raw) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
header := raw[:len(HEADER_BYTES)]
|
||||
if !bytes.Equal(header, []byte(HEADER_BYTES)) {
|
||||
receiveError = fmt.Errorf("verify message head failed on channel: %s", socketId)
|
||||
break
|
||||
}
|
||||
var sessionId, length uint32
|
||||
if receiveError = binary.Read(bytes.NewReader(raw[headerSize:headerSize+4]), binary.BigEndian, &sessionId); receiveError != nil {
|
||||
break
|
||||
}
|
||||
if receiveError = binary.Read(bytes.NewReader(raw[headerSize+4:headerSize+8]), binary.BigEndian, &length); receiveError != nil {
|
||||
break
|
||||
}
|
||||
payload, err := _readN(socketContext.conn, int(length))
|
||||
if err != nil {
|
||||
receiveError = err
|
||||
break
|
||||
}
|
||||
tail, err := _readN(socketContext.conn, len(TAILER_BYTES))
|
||||
if err != nil {
|
||||
receiveError = err
|
||||
break
|
||||
}
|
||||
if !bytes.Equal(tail, []byte(TAILER_BYTES)) {
|
||||
receiveError = fmt.Errorf("verify message tail failed on channel: %s", socketId)
|
||||
break
|
||||
}
|
||||
socketContext.writeLock.Lock()
|
||||
callback := socketContext.callbackMap[strconv.Itoa(int(sessionId))]
|
||||
socketContext.writeLock.Unlock()
|
||||
if callback != nil {
|
||||
callback.OnData(payload)
|
||||
} else {
|
||||
socketContext.queue.push(response{sessionId: sessionId, payload: payload})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (uKtp *uitestKitTransport) sendMessage(reqType ReqTypeEnum, sessionId uint32, message string) (response UitestKitResponse, err error) {
|
||||
defer func() {
|
||||
if err != nil {
|
||||
uKtp.disconnect(reqType)
|
||||
}
|
||||
}()
|
||||
if err = uKtp._sendMessage(reqType, sessionId, message); err != nil {
|
||||
return
|
||||
}
|
||||
return uKtp.receiveMessage(reqType, sessionId)
|
||||
}
|
||||
|
||||
func (uKtp *uitestKitTransport) _sendMessage(reqType ReqTypeEnum, sessionId uint32, message string) (err error) {
|
||||
socketId := fmt.Sprintf("socket_%d_%s", reqType, uKtp.serial)
|
||||
socketContext := uKtp.socketMap[socketId]
|
||||
if socketContext == nil {
|
||||
if err = uKtp.initializeSocket(reqType); err != nil {
|
||||
return
|
||||
}
|
||||
socketContext = uKtp.socketMap[socketId]
|
||||
}
|
||||
buffer := new(bytes.Buffer)
|
||||
if err = binary.Write(buffer, binary.BigEndian, []byte(HEADER_BYTES)); err != nil {
|
||||
return
|
||||
}
|
||||
if err = binary.Write(buffer, binary.BigEndian, sessionId); err != nil {
|
||||
return
|
||||
}
|
||||
if err = binary.Write(buffer, binary.BigEndian, uint32(len(message))); err != nil {
|
||||
return
|
||||
}
|
||||
if err = binary.Write(buffer, binary.BigEndian, []byte(message)); err != nil {
|
||||
return
|
||||
}
|
||||
if err = binary.Write(buffer, binary.BigEndian, []byte(TAILER_BYTES)); err != nil {
|
||||
return
|
||||
}
|
||||
socketContext.writeLock.Lock()
|
||||
defer socketContext.writeLock.Unlock()
|
||||
return _send(socketContext.conn, buffer.Bytes())
|
||||
}
|
||||
|
||||
func (sc *SocketContext) Close() {
|
||||
sc.writeLock.Lock()
|
||||
defer sc.writeLock.Unlock()
|
||||
|
||||
if sc.conn != nil {
|
||||
_ = sc.conn.Close()
|
||||
}
|
||||
|
||||
// 清空callbackMap
|
||||
for key := range sc.callbackMap {
|
||||
delete(sc.callbackMap, key)
|
||||
}
|
||||
sc.callbackMap = nil
|
||||
|
||||
// 清理队列
|
||||
if sc.queue != nil {
|
||||
sc.queue.Clear()
|
||||
}
|
||||
}
|
||||
|
||||
func (uKtp *uitestKitTransport) Close() {
|
||||
uKtp.mu.Lock()
|
||||
defer uKtp.mu.Unlock()
|
||||
|
||||
// 关闭所有的SocketContext
|
||||
if uKtp.socketMap != nil {
|
||||
for _, socketContext := range uKtp.socketMap {
|
||||
socketContext.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭连接池
|
||||
if uKtp.connectionPool != nil {
|
||||
uKtp.connectionPool.close()
|
||||
}
|
||||
|
||||
uKtp.socketMap = nil
|
||||
uKtp.connectionPool = nil
|
||||
}
|
||||
|
||||
func (uKtp *uitestKitTransport) receiveMessage(reqType ReqTypeEnum, sessionId uint32) (response UitestKitResponse, err error) {
|
||||
socketId := fmt.Sprintf("socket_%d_%s", reqType, uKtp.serial)
|
||||
socketContext := uKtp.socketMap[socketId]
|
||||
if socketContext == nil {
|
||||
err = fmt.Errorf("failed to read message. not found target connection")
|
||||
return
|
||||
}
|
||||
// 创建一个计时器,设置超时时间为 10 秒
|
||||
timeout := time.After(2 * time.Second)
|
||||
|
||||
// 创建一个 ticker,每秒触发一次
|
||||
ticker := time.NewTicker(10 * time.Millisecond)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-timeout:
|
||||
err = fmt.Errorf("failed to read message in 2 second")
|
||||
return
|
||||
case <-ticker.C:
|
||||
res := socketContext.queue.remove(sessionId)
|
||||
if res != nil {
|
||||
err = json.Unmarshal(res.payload, &response)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (sc *SocketContext) onException(err error) {
|
||||
for _, callback := range sc.callbackMap {
|
||||
if callback != nil {
|
||||
callback.OnError(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func newResponseList() *responseList {
|
||||
return &responseList{list: list.New()}
|
||||
}
|
||||
82
pkg/ghdc/uitest_transport.go
Normal file
82
pkg/ghdc/uitest_transport.go
Normal file
@@ -0,0 +1,82 @@
|
||||
package ghdc
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
type uitestTransport struct {
|
||||
connectionPool *ConnectionPool
|
||||
readTimeout time.Duration
|
||||
}
|
||||
|
||||
func newUitestTransport(host string, port string, readTimeout ...time.Duration) (uTp uitestTransport, err error) {
|
||||
if len(readTimeout) == 0 {
|
||||
readTimeout = []time.Duration{DefaultReadTimeout}
|
||||
}
|
||||
uTp.readTimeout = readTimeout[0]
|
||||
pool, err := newConnectionPool(host, port, 2)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("[uitest] failed to init connection pool \n%v", err)
|
||||
return
|
||||
}
|
||||
uTp.connectionPool = pool
|
||||
return
|
||||
}
|
||||
|
||||
func newHypiumRequest(params interface{}, method string) UitestRequest {
|
||||
return UitestRequest{
|
||||
Module: "com.ohos.devicetest.hypiumApiHelper",
|
||||
Method: method,
|
||||
Params: params,
|
||||
RequestId: MD5(fmt.Sprintf("%d", time.Now().UnixNano())),
|
||||
}
|
||||
}
|
||||
|
||||
func (uTp *uitestTransport) SendReq(req UitestRequest) (res UitestResponse, err error) {
|
||||
requestBytes, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("[uitest] failed to marshal %v request %v", req, err)
|
||||
return
|
||||
}
|
||||
requestBytes = append(requestBytes, '\n')
|
||||
conn, err := uTp.connectionPool.getConnection()
|
||||
if err != nil {
|
||||
err = fmt.Errorf("[uitest] failed to get connection \n%v", err)
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
uTp.connectionPool.releaseConnection(conn)
|
||||
}()
|
||||
_ = conn.SetReadDeadline(time.Now().Add(uTp.readTimeout))
|
||||
debugLog(fmt.Sprintf("[uitest] send Request %v", req))
|
||||
err = _send(conn, requestBytes)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("[uitest] failed to get send %v request %v", req, err)
|
||||
return
|
||||
}
|
||||
raw, err := _read(conn)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("[uitest] failed to read %v response %v", req, err)
|
||||
return
|
||||
}
|
||||
res = UitestResponse{}
|
||||
err = json.Unmarshal(raw, &res)
|
||||
return
|
||||
}
|
||||
|
||||
func (uTp *uitestTransport) Close() {
|
||||
if uTp.connectionPool != nil {
|
||||
uTp.connectionPool.close()
|
||||
}
|
||||
uTp.connectionPool = nil
|
||||
}
|
||||
|
||||
func MD5(str string) string {
|
||||
hash := md5.New()
|
||||
hash.Write([]byte(str))
|
||||
return hex.EncodeToString(hash.Sum(nil))
|
||||
}
|
||||
Reference in New Issue
Block a user