diff --git a/go.mod b/go.mod index 29a113b8..cd7c8c6e 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,6 @@ go 1.18 require ( github.com/andybalholm/brotli v1.0.4 github.com/denisbrodbeck/machineid v1.0.1 - github.com/electricbubble/gadb v0.0.7 github.com/electricbubble/gidevice v0.6.2 github.com/electricbubble/opencv-helper v0.0.3 github.com/fatih/color v1.13.0 diff --git a/go.sum b/go.sum index f9df4ccf..5f0cae93 100644 --- a/go.sum +++ b/go.sum @@ -100,8 +100,6 @@ github.com/debugtalk/gidevice v0.6.3-0.20221012071407-9b59e12ecc77 h1:wP/2aKW6YV github.com/debugtalk/gidevice v0.6.3-0.20221012071407-9b59e12ecc77/go.mod h1:bRHL2M9qgeEKju8KRvKMZUVEg7t5zMnTiG3SJ3QDH5o= github.com/denisbrodbeck/machineid v1.0.1 h1:geKr9qtkB876mXguW2X6TU4ZynleN6ezuMSRhl4D7AQ= github.com/denisbrodbeck/machineid v1.0.1/go.mod h1:dJUwb7PTidGDeYyUBmXZ2GphQBbjJCrnectwCyxcUSI= -github.com/electricbubble/gadb v0.0.7 h1:fxvVLVNs3IFKuYAEXDF2tDZUjT9jNCltoTSirjM5dgo= -github.com/electricbubble/gadb v0.0.7/go.mod h1:3293YJ6OWHv/Q6NA5dwSbK43MbmYm8+Vz2d7h5J3IA8= github.com/electricbubble/opencv-helper v0.0.3 h1:p0sHTUPPPm8GqzVUtYH+wQbJoguzotUXVRAS7Ibk7nI= github.com/electricbubble/opencv-helper v0.0.3/go.mod h1:VHB21p5xsIjXUsUleWSaKGJosRsRAO7cuJoZKf7uCcc= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= diff --git a/hrp/cmd/adb/devices.go b/hrp/cmd/adb/devices.go index 7cd53b69..86c93608 100644 --- a/hrp/cmd/adb/devices.go +++ b/hrp/cmd/adb/devices.go @@ -5,10 +5,10 @@ import ( "fmt" "os" - "github.com/electricbubble/gadb" "github.com/pkg/errors" "github.com/spf13/cobra" + "github.com/httprunner/httprunner/v4/hrp/pkg/gadb" "github.com/httprunner/httprunner/v4/hrp/pkg/uixt" ) diff --git a/hrp/pkg/boomer/README.md b/hrp/pkg/boomer/README.md index b6ef5ce2..91065761 100644 --- a/hrp/pkg/boomer/README.md +++ b/hrp/pkg/boomer/README.md @@ -1,5 +1,5 @@ # boomer -This module is initially forked from [myzhan/boomer] and made a lot of changes. +This module is initially forked from [myzhan/boomer@v1.6.0] and made a lot of changes. -[myzhan/boomer]: https://github.com/myzhan/boomer +[myzhan/boomer@v1.6.0]: https://github.com/myzhan/boomer/tree/v1.6.0 diff --git a/hrp/pkg/gadb/README.md b/hrp/pkg/gadb/README.md new file mode 100644 index 00000000..dc67b810 --- /dev/null +++ b/hrp/pkg/gadb/README.md @@ -0,0 +1,5 @@ +# gadb + +This module is initially forked from [electricbubble/gadb@v0.0.7]. + +[electricbubble/gadb@v0.0.7]: https://github.com/electricbubble/gadb/tree/v0.0.7 diff --git a/hrp/pkg/gadb/client.go b/hrp/pkg/gadb/client.go new file mode 100644 index 00000000..951f13b1 --- /dev/null +++ b/hrp/pkg/gadb/client.go @@ -0,0 +1,216 @@ +package gadb + +import ( + "fmt" + "strconv" + "strings" +) + +const AdbServerPort = 5037 +const AdbDaemonPort = 5555 + +type Client struct { + host string + port int +} + +func NewClient() (Client, error) { + return NewClientWith("localhost") +} + +func NewClientWith(host string, port ...int) (adbClient Client, err error) { + if len(port) == 0 { + port = []int{AdbServerPort} + } + adbClient.host = host + adbClient.port = port[0] + + var tp transport + if tp, err = adbClient.createTransport(); err != nil { + return Client{}, err + } + defer func() { _ = tp.Close() }() + + return +} + +func (c Client) ServerVersion() (version int, err error) { + var resp string + if resp, err = c.executeCommand("host:version"); err != nil { + return 0, err + } + + var v int64 + if v, err = strconv.ParseInt(resp, 16, 64); err != nil { + return 0, err + } + + version = int(v) + return +} + +func (c Client) DeviceSerialList() (serials []string, err error) { + var resp string + if resp, err = c.executeCommand("host:devices"); err != nil { + return + } + + lines := strings.Split(resp, "\n") + serials = make([]string, 0, len(lines)) + + for i := range lines { + fields := strings.Fields(lines[i]) + if len(fields) < 2 { + continue + } + serials = append(serials, fields[0]) + } + + return +} + +func (c Client) DeviceList() (devices []Device, err error) { + var resp string + if resp, err = c.executeCommand("host:devices-l"); 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) < 5 || len(fields[0]) == 0 { + debugLog(fmt.Sprintf("can't parse: %s", line)) + continue + } + + sliceAttrs := fields[2:] + mapAttrs := map[string]string{} + for _, field := range sliceAttrs { + split := strings.Split(field, ":") + key, val := split[0], split[1] + mapAttrs[key] = val + } + devices = append(devices, Device{adbClient: c, serial: fields[0], attrs: mapAttrs}) + } + + return +} + +func (c Client) ForwardList() (deviceForward []DeviceForward, err error) { + var resp string + if resp, err = c.executeCommand("host:list-forward"); 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]) + if line == "" { + continue + } + fields := strings.Fields(line) + deviceForward = append(deviceForward, DeviceForward{Serial: fields[0], Local: fields[1], Remote: fields[2]}) + } + + return +} + +func (c Client) ForwardKillAll() (err error) { + _, err = c.executeCommand("host:killforward-all", true) + return +} + +func (c Client) Connect(ip string, port ...int) (err error) { + if len(port) == 0 { + port = []int{AdbDaemonPort} + } + + var resp string + if resp, err = c.executeCommand(fmt.Sprintf("host:connect:%s:%d", ip, port[0])); err != nil { + return err + } + if !strings.HasPrefix(resp, "connected to") && !strings.HasPrefix(resp, "already connected to") { + return fmt.Errorf("adb connect: %s", resp) + } + return +} + +func (c Client) Disconnect(ip string, port ...int) (err error) { + cmd := fmt.Sprintf("host:disconnect:%s", ip) + if len(port) != 0 { + cmd = fmt.Sprintf("host:disconnect:%s:%d", ip, port[0]) + } + + var resp string + if resp, err = c.executeCommand(cmd); err != nil { + return err + } + if !strings.HasPrefix(resp, "disconnected") { + return fmt.Errorf("adb disconnect: %s", resp) + } + return +} + +func (c Client) DisconnectAll() (err error) { + var resp string + if resp, err = c.executeCommand("host:disconnect:"); err != nil { + return err + } + + if !strings.HasPrefix(resp, "disconnected everything") { + return fmt.Errorf("adb disconnect all: %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.Send("host:kill") + return +} + +func (c Client) createTransport() (tp transport, err error) { + return newTransport(fmt.Sprintf("%s:%d", c.host, c.port)) +} + +func (c Client) executeCommand(command string, onlyVerifyResponse ...bool) (resp string, err error) { + if len(onlyVerifyResponse) == 0 { + onlyVerifyResponse = []bool{false} + } + + var tp transport + if tp, err = c.createTransport(); err != nil { + return "", err + } + defer func() { _ = tp.Close() }() + + if err = tp.Send(command); err != nil { + return "", err + } + if err = tp.VerifyResponse(); err != nil { + return "", err + } + + if onlyVerifyResponse[0] { + return + } + + if resp, err = tp.UnpackString(); err != nil { + return "", err + } + return +} diff --git a/hrp/pkg/gadb/client_test.go b/hrp/pkg/gadb/client_test.go new file mode 100644 index 00000000..677e3455 --- /dev/null +++ b/hrp/pkg/gadb/client_test.go @@ -0,0 +1,145 @@ +package gadb + +import ( + "testing" +) + +func TestClient_ServerVersion(t *testing.T) { + SetDebug(true) + + adbClient, err := NewClient() + if err != nil { + t.Fatal(err) + } + + adbServerVersion, err := adbClient.ServerVersion() + if err != nil { + t.Fatal(err) + } + + t.Log(adbServerVersion) +} + +func TestClient_DeviceSerialList(t *testing.T) { + SetDebug(true) + + adbClient, err := NewClient() + if err != nil { + t.Fatal(err) + } + + serials, err := adbClient.DeviceSerialList() + if err != nil { + t.Fatal(err) + } + + for i := range serials { + t.Log(serials[i]) + } +} + +func TestClient_DeviceList(t *testing.T) { + SetDebug(true) + + adbClient, err := NewClient() + if err != nil { + t.Fatal(err) + } + + devices, err := adbClient.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) + + adbClient, err := NewClient() + if err != nil { + t.Fatal(err) + } + + deviceForwardList, err := adbClient.ForwardList() + if err != nil { + t.Fatal(err) + } + + for i := range deviceForwardList { + t.Log(deviceForwardList[i]) + } +} + +func TestClient_ForwardKillAll(t *testing.T) { + SetDebug(true) + + adbClient, err := NewClient() + if err != nil { + t.Fatal(err) + } + + err = adbClient.ForwardKillAll() + if err != nil { + t.Fatal(err) + } +} + +func TestClient_Connect(t *testing.T) { + adbClient, err := NewClient() + if err != nil { + t.Fatal(err) + } + + SetDebug(true) + + err = adbClient.Connect("192.168.1.28") + if err != nil { + t.Fatal(err) + } +} + +func TestClient_Disconnect(t *testing.T) { + adbClient, err := NewClient() + if err != nil { + t.Fatal(err) + } + + SetDebug(true) + + err = adbClient.Disconnect("192.168.1.28") + if err != nil { + t.Fatal(err) + } +} + +func TestClient_DisconnectAll(t *testing.T) { + adbClient, err := NewClient() + if err != nil { + t.Fatal(err) + } + + SetDebug(true) + + err = adbClient.DisconnectAll() + if err != nil { + t.Fatal(err) + } +} + +func TestClient_KillServer(t *testing.T) { + SetDebug(true) + + adbClient, err := NewClient() + if err != nil { + t.Fatal(err) + } + + err = adbClient.KillServer() + if err != nil { + t.Fatal(err) + } +} diff --git a/hrp/pkg/gadb/device.go b/hrp/pkg/gadb/device.go new file mode 100644 index 00000000..9d07bd2e --- /dev/null +++ b/hrp/pkg/gadb/device.go @@ -0,0 +1,299 @@ +package gadb + +import ( + "errors" + "fmt" + "io" + "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) +} + +const DefaultFileMode = os.FileMode(0664) + +type DeviceState string + +const ( + StateUnknown DeviceState = "UNKNOWN" + StateOnline DeviceState = "online" + StateOffline DeviceState = "offline" + StateDisconnected DeviceState = "disconnected" +) + +var deviceStateStrings = map[string]DeviceState{ + "": StateDisconnected, + "offline": StateOffline, + "device": StateOnline, +} + +func deviceStateConv(k string) (deviceState DeviceState) { + var ok bool + if deviceState, ok = deviceStateStrings[k]; !ok { + return StateUnknown + } + return +} + +type DeviceForward struct { + Serial string + Local string + Remote string + // LocalProtocol string + // RemoteProtocol string +} + +type Device struct { + adbClient Client + serial string + attrs map[string]string +} + +func (d Device) Product() string { + return d.attrs["product"] +} + +func (d Device) Model() string { + return d.attrs["model"] +} + +func (d Device) Usb() string { + return d.attrs["usb"] +} + +func (d Device) transportId() string { + return d.attrs["transport_id"] +} + +func (d Device) DeviceInfo() map[string]string { + return d.attrs +} + +func (d Device) Serial() string { + // resp, err := d.adbClient.executeCommand(fmt.Sprintf("host-serial:%s:get-serialno", d.serial)) + return d.serial +} + +func (d Device) IsUsb() bool { + return d.Usb() != "" +} + +func (d Device) State() (DeviceState, error) { + resp, err := d.adbClient.executeCommand(fmt.Sprintf("host-serial:%s:get-state", d.serial)) + return deviceStateConv(resp), err +} + +func (d Device) DevicePath() (string, error) { + resp, err := d.adbClient.executeCommand(fmt.Sprintf("host-serial:%s:get-devpath", d.serial)) + return resp, err +} + +func (d Device) Forward(localPort, remotePort int, noRebind ...bool) (err error) { + command := "" + local := fmt.Sprintf("tcp:%d", localPort) + remote := fmt.Sprintf("tcp:%d", remotePort) + + if len(noRebind) != 0 && noRebind[0] { + command = fmt.Sprintf("host-serial:%s:forward:norebind:%s;%s", d.serial, local, remote) + } else { + command = fmt.Sprintf("host-serial:%s:forward:%s;%s", d.serial, local, remote) + } + + _, err = d.adbClient.executeCommand(command, true) + return +} + +func (d Device) ForwardList() (deviceForwardList []DeviceForward, err error) { + var forwardList []DeviceForward + if forwardList, err = d.adbClient.ForwardList(); err != nil { + return nil, err + } + + deviceForwardList = make([]DeviceForward, 0, len(deviceForwardList)) + for i := range forwardList { + if forwardList[i].Serial == d.serial { + deviceForwardList = append(deviceForwardList, forwardList[i]) + } + } + // resp, err := d.adbClient.executeCommand(fmt.Sprintf("host-serial:%s:list-forward", d.serial)) + return +} + +func (d Device) ForwardKill(localPort int) (err error) { + local := fmt.Sprintf("tcp:%d", localPort) + _, err = d.adbClient.executeCommand(fmt.Sprintf("host-serial:%s:killforward:%s", d.serial, local), true) + return +} + +func (d Device) RunShellCommand(cmd string, args ...string) (string, error) { + raw, err := d.RunShellCommandWithBytes(cmd, args...) + return string(raw), err +} + +func (d Device) RunShellCommandWithBytes(cmd string, args ...string) ([]byte, error) { + if len(args) > 0 { + cmd = fmt.Sprintf("%s %s", cmd, strings.Join(args, " ")) + } + if strings.TrimSpace(cmd) == "" { + return nil, errors.New("adb shell: command cannot be empty") + } + raw, err := d.executeCommand(fmt.Sprintf("shell:%s", cmd)) + return raw, err +} + +func (d Device) EnableAdbOverTCP(port ...int) (err error) { + if len(port) == 0 { + port = []int{AdbDaemonPort} + } + + _, err = d.executeCommand(fmt.Sprintf("tcpip:%d", port[0]), true) + return +} + +func (d Device) createDeviceTransport() (tp transport, err error) { + if tp, err = newTransport(fmt.Sprintf("%s:%d", d.adbClient.host, d.adbClient.port)); err != nil { + return transport{}, err + } + + if err = tp.Send(fmt.Sprintf("host:transport:%s", d.serial)); err != nil { + return transport{}, err + } + err = tp.VerifyResponse() + return +} + +func (d Device) executeCommand(command string, onlyVerifyResponse ...bool) (raw []byte, err error) { + if len(onlyVerifyResponse) == 0 { + onlyVerifyResponse = []bool{false} + } + + var tp transport + if tp, err = d.createDeviceTransport(); err != nil { + return nil, err + } + defer func() { _ = tp.Close() }() + + if err = tp.Send(command); err != nil { + return nil, err + } + + if err = tp.VerifyResponse(); err != nil { + return nil, err + } + + if onlyVerifyResponse[0] { + return + } + + raw, err = tp.ReadBytesAll() + return +} + +func (d Device) List(remotePath string) (devFileInfos []DeviceFileInfo, err error) { + var tp transport + if tp, err = d.createDeviceTransport(); err != nil { + return nil, err + } + defer func() { _ = tp.Close() }() + + var sync syncTransport + if sync, err = tp.CreateSyncTransport(); err != nil { + return nil, err + } + defer func() { _ = sync.Close() }() + + if err = sync.Send("LIST", remotePath); err != nil { + return nil, err + } + + devFileInfos = make([]DeviceFileInfo, 0) + + var entry DeviceFileInfo + for entry, err = sync.ReadDirectoryEntry(); err == nil; entry, err = sync.ReadDirectoryEntry() { + if entry == (DeviceFileInfo{}) { + break + } + devFileInfos = append(devFileInfos, entry) + } + + return +} + +func (d Device) PushFile(local *os.File, remotePath string, modification ...time.Time) (err error) { + if len(modification) == 0 { + var stat os.FileInfo + if stat, err = local.Stat(); err != nil { + return err + } + modification = []time.Time{stat.ModTime()} + } + + return d.Push(local, remotePath, modification[0], DefaultFileMode) +} + +func (d Device) Push(source io.Reader, remotePath string, modification time.Time, mode ...os.FileMode) (err error) { + if len(mode) == 0 { + mode = []os.FileMode{DefaultFileMode} + } + + var tp transport + if tp, err = d.createDeviceTransport(); err != nil { + return err + } + defer func() { _ = tp.Close() }() + + var sync syncTransport + if sync, err = tp.CreateSyncTransport(); err != nil { + return err + } + defer func() { _ = sync.Close() }() + + data := fmt.Sprintf("%s,%d", remotePath, mode[0]) + if err = sync.Send("SEND", data); err != nil { + return err + } + + if err = sync.SendStream(source); err != nil { + return + } + + if err = sync.SendStatus("DONE", uint32(modification.Unix())); err != nil { + return + } + + if err = sync.VerifyStatus(); err != nil { + return + } + return +} + +func (d Device) Pull(remotePath string, dest io.Writer) (err error) { + var tp transport + if tp, err = d.createDeviceTransport(); err != nil { + return err + } + defer func() { _ = tp.Close() }() + + var sync syncTransport + if sync, err = tp.CreateSyncTransport(); err != nil { + return err + } + defer func() { _ = sync.Close() }() + + if err = sync.Send("RECV", remotePath); err != nil { + return err + } + + err = sync.WriteStream(dest) + return +} diff --git a/hrp/pkg/gadb/device_test.go b/hrp/pkg/gadb/device_test.go new file mode 100644 index 00000000..8dca27cf --- /dev/null +++ b/hrp/pkg/gadb/device_test.go @@ -0,0 +1,332 @@ +package gadb + +import ( + "bytes" + "io/ioutil" + "os" + "strings" + "testing" + "time" +) + +func TestDevice_State(t *testing.T) { + adbClient, err := NewClient() + if err != nil { + t.Fatal(err) + } + + devices, err := adbClient.DeviceList() + if err != nil { + t.Fatal(err) + } + + for i := range devices { + dev := devices[i] + state, err := dev.State() + if err != nil { + t.Fatal(err) + } + t.Log(dev.Serial(), state) + } +} + +func TestDevice_DevicePath(t *testing.T) { + adbClient, err := NewClient() + if err != nil { + t.Fatal(err) + } + + devices, err := adbClient.DeviceList() + if err != nil { + t.Fatal(err) + } + + for i := range devices { + dev := devices[i] + devPath, err := dev.DevicePath() + if err != nil { + t.Fatal(err) + } + t.Log(dev.Serial(), devPath) + } +} + +func TestDevice_Product(t *testing.T) { + adbClient, err := NewClient() + if err != nil { + t.Fatal(err) + } + + devices, err := adbClient.DeviceList() + if err != nil { + t.Fatal(err) + } + + for i := range devices { + dev := devices[i] + product := dev.Product() + t.Log(dev.Serial(), product) + } +} + +func TestDevice_Model(t *testing.T) { + adbClient, err := NewClient() + if err != nil { + t.Fatal(err) + } + + devices, err := adbClient.DeviceList() + if err != nil { + t.Fatal(err) + } + + for i := range devices { + dev := devices[i] + t.Log(dev.Serial(), dev.Model()) + } +} + +func TestDevice_Usb(t *testing.T) { + adbClient, err := NewClient() + if err != nil { + t.Fatal(err) + } + + devices, err := adbClient.DeviceList() + if err != nil { + t.Fatal(err) + } + + for i := range devices { + dev := devices[i] + t.Log(dev.Serial(), dev.Usb(), dev.IsUsb()) + } + +} + +func TestDevice_DeviceInfo(t *testing.T) { + adbClient, err := NewClient() + if err != nil { + t.Fatal(err) + } + + devices, err := adbClient.DeviceList() + if err != nil { + t.Fatal(err) + } + + for i := range devices { + dev := devices[i] + t.Log(dev.DeviceInfo()) + } +} + +func TestDevice_Forward(t *testing.T) { + adbClient, err := NewClient() + if err != nil { + t.Fatal(err) + } + + devices, err := adbClient.DeviceList() + if err != nil { + t.Fatal(err) + } + + SetDebug(true) + + localPort := 61000 + err = devices[0].Forward(localPort, 6790) + if err != nil { + t.Fatal(err) + } + + err = devices[0].ForwardKill(localPort) + if err != nil { + t.Fatal(err) + } +} + +func TestDevice_ForwardList(t *testing.T) { + adbClient, err := NewClient() + if err != nil { + t.Fatal(err) + } + + devices, err := adbClient.DeviceList() + if err != nil { + t.Fatal(err) + } + + SetDebug(true) + + for i := range devices { + dev := devices[i] + forwardList, err := dev.ForwardList() + if err != nil { + t.Fatal(err) + } + t.Log(dev.serial, "->", forwardList) + } +} + +func TestDevice_ForwardKill(t *testing.T) { + adbClient, err := NewClient() + if err != nil { + t.Fatal(err) + } + + devices, err := adbClient.DeviceList() + if err != nil { + t.Fatal(err) + } + + SetDebug(true) + + err = devices[0].ForwardKill(6790) + if err != nil { + t.Fatal(err) + } +} + +func TestDevice_RunShellCommand(t *testing.T) { + adbClient, err := NewClient() + if err != nil { + t.Fatal(err) + } + + devices, err := adbClient.DeviceList() + if err != nil { + t.Fatal(err) + } + + // for i := range devices { + // dev := devices[i] + // // cmdOutput, err := dev.RunShellCommand(`pm list packages | grep "bili"`) + // // cmdOutput, err := dev.RunShellCommand(`pm list packages`, `| grep "bili"`) + // // cmdOutput, err := dev.RunShellCommand("dumpsys activity | grep mFocusedActivity") + // cmdOutput, err := dev.RunShellCommand("monkey", "-p", "tv.danmaku.bili", "-c", "android.intent.category.LAUNCHER", "1") + // if err != nil { + // t.Fatal(dev.serial, err) + // } + // t.Log("\n"+dev.serial, cmdOutput) + // } + + // SetDebug(true) + + dev := devices[len(devices)-1] + dev = devices[0] + + // cmdOutput, err := dev.RunShellCommand("monkey", "-p", "tv.danmaku.bili", "-c", "android.intent.category.LAUNCHER", "1") + cmdOutput, err := dev.RunShellCommand("ls /sdcard") + // cmdOutput, err := dev.RunShellCommandWithBytes("screencap -p") + if err != nil { + t.Fatal(dev.serial, err) + } + t.Log("\n⬇️"+dev.serial+"⬇️\n", cmdOutput) + +} + +func TestDevice_EnableAdbOverTCP(t *testing.T) { + adbClient, err := NewClient() + if err != nil { + t.Fatal(err) + } + + devices, err := adbClient.DeviceList() + if err != nil { + t.Fatal(err) + } + + dev := devices[len(devices)-1] + dev = devices[0] + + SetDebug(true) + + err = dev.EnableAdbOverTCP() + if err != nil { + t.Fatal(err) + } +} + +func TestDevice_List(t *testing.T) { + adbClient, err := NewClient() + if err != nil { + t.Fatal(err) + } + + devices, err := adbClient.DeviceList() + if err != nil { + t.Fatal(err) + } + + dev := devices[len(devices)-1] + dev = devices[0] + + SetDebug(true) + + // fileEntries, err := dev.List("/sdcard") + fileEntries, err := dev.List("/sdcard/Download") + if err != nil { + t.Fatal(err) + } + + for i := range fileEntries { + t.Log(fileEntries[i].Name, "\t", fileEntries[i].IsDir()) + } +} + +func TestDevice_Push(t *testing.T) { + adbClient, err := NewClient() + if err != nil { + t.Fatal(err) + } + + devices, err := adbClient.DeviceList() + if err != nil { + t.Fatal(err) + } + + dev := devices[len(devices)-1] + dev = devices[0] + + SetDebug(true) + + file, _ := os.Open("/Users/hero/Documents/temp/MuMu共享文件夹/test.txt") + err = dev.PushFile(file, "/sdcard/Download/push.txt", time.Now()) + if err != nil { + t.Fatal(err) + } + + err = dev.Push(strings.NewReader("world"), "/sdcard/Download/hello.txt", time.Now()) + if err != nil { + t.Fatal(err) + } +} + +func TestDevice_Pull(t *testing.T) { + adbClient, err := NewClient() + if err != nil { + t.Fatal(err) + } + + devices, err := adbClient.DeviceList() + if err != nil { + t.Fatal(err) + } + + dev := devices[len(devices)-1] + dev = devices[0] + + SetDebug(true) + + buffer := bytes.NewBufferString("") + err = dev.Pull("/sdcard/Download/hello.txt", buffer) + if err != nil { + t.Fatal(err) + } + + userHomeDir, _ := os.UserHomeDir() + if err = ioutil.WriteFile(userHomeDir+"/Desktop/hello.txt", buffer.Bytes(), DefaultFileMode); err != nil { + t.Fatal(err) + } +} diff --git a/hrp/pkg/gadb/examples/main.go b/hrp/pkg/gadb/examples/main.go new file mode 100644 index 00000000..e28094ec --- /dev/null +++ b/hrp/pkg/gadb/examples/main.go @@ -0,0 +1,59 @@ +package main + +import ( + "log" + "os" + "strings" + + "github.com/httprunner/httprunner/v4/hrp/pkg/gadb" +) + +func main() { + adbClient, err := gadb.NewClient() + checkErr(err, "fail to connect adb server") + + devices, err := adbClient.DeviceList() + checkErr(err) + + if len(devices) == 0 { + log.Fatalln("list of devices is empty") + } + + dev := devices[0] + + userHomeDir, _ := os.UserHomeDir() + apk, err := os.Open(userHomeDir + "/Desktop/xuexi_android_10002068.apk") + checkErr(err) + + log.Println("starting to push apk") + + remotePath := "/data/local/tmp/xuexi_android_10002068.apk" + err = dev.PushFile(apk, remotePath) + checkErr(err, "adb push") + + log.Println("push completed") + + log.Println("starting to install apk") + + shellOutput, err := dev.RunShellCommand("pm install", remotePath) + checkErr(err, "pm install") + if !strings.Contains(shellOutput, "Success") { + log.Fatalln("fail to install: ", shellOutput) + } + + log.Println("install completed") + +} + +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) +} diff --git a/hrp/pkg/gadb/gadb.go b/hrp/pkg/gadb/gadb.go new file mode 100644 index 00000000..14b224ee --- /dev/null +++ b/hrp/pkg/gadb/gadb.go @@ -0,0 +1,17 @@ +package gadb + +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] [gadb] " + msg) +} diff --git a/hrp/pkg/gadb/sync_transport.go b/hrp/pkg/gadb/sync_transport.go new file mode 100644 index 00000000..5eceee95 --- /dev/null +++ b/hrp/pkg/gadb/sync_transport.go @@ -0,0 +1,252 @@ +package gadb + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + "io" + "net" + "time" +) + +type syncTransport struct { + sock net.Conn + readTimeout time.Duration +} + +func newSyncTransport(sock net.Conn, readTimeout time.Duration) syncTransport { + return syncTransport{sock: sock, readTimeout: readTimeout} +} + +func (sync syncTransport) Send(command, data string) (err error) { + if len(command) != 4 { + return errors.New("sync commands must have length 4") + } + msg := bytes.NewBufferString(command) + if err = binary.Write(msg, binary.LittleEndian, int32(len(data))); err != nil { + return fmt.Errorf("sync transport write: %w", err) + } + msg.WriteString(data) + + debugLog(fmt.Sprintf("--> %s", msg.String())) + return _send(sync.sock, msg.Bytes()) +} + +func (sync syncTransport) SendStream(reader io.Reader) (err error) { + syncMaxChunkSize := 64 * 1024 + for err == nil { + tmp := make([]byte, syncMaxChunkSize) + var n int + n, err = reader.Read(tmp) + if err == io.EOF { + err = nil + break + } + if err == nil { + err = sync.sendChunk(tmp[:n]) + } + } + + return +} + +func (sync syncTransport) SendStatus(statusCode string, n uint32) (err error) { + msg := bytes.NewBufferString(statusCode) + if err = binary.Write(msg, binary.LittleEndian, n); err != nil { + return fmt.Errorf("sync transport write: %w", err) + } + debugLog(fmt.Sprintf("--> %s", msg.String())) + return _send(sync.sock, msg.Bytes()) +} + +func (sync syncTransport) sendChunk(buffer []byte) (err error) { + msg := bytes.NewBufferString("DATA") + if err = binary.Write(msg, binary.LittleEndian, int32(len(buffer))); err != nil { + return fmt.Errorf("sync transport write: %w", err) + } + debugLog(fmt.Sprintf("--> %s ......", msg.String())) + msg.Write(buffer) + return _send(sync.sock, msg.Bytes()) +} + +func (sync syncTransport) VerifyStatus() (err error) { + var status string + if status, err = sync.ReadStringN(4); err != nil { + return err + } + + log := bytes.NewBufferString(fmt.Sprintf("<-- %s", status)) + defer func() { + debugLog(log.String()) + }() + + var tmpUint32 uint32 + if tmpUint32, err = sync.ReadUint32(); err != nil { + return fmt.Errorf("sync transport read (status): %w", err) + } + log.WriteString(fmt.Sprintf(" %d\t", tmpUint32)) + + var msg string + if msg, err = sync.ReadStringN(int(tmpUint32)); err != nil { + return err + } + log.WriteString(msg) + + if status == "FAIL" { + err = fmt.Errorf("sync verify status (fail): %s", msg) + return + } + + if status != "OKAY" { + err = fmt.Errorf("sync verify status: Unknown error: %s", msg) + return + } + + return +} + +var syncReadChunkDone = errors.New("sync read chunk done") + +func (sync syncTransport) WriteStream(dest io.Writer) (err error) { + var chunk []byte + save := func() error { + if chunk, err = sync.readChunk(); err != nil && err != syncReadChunkDone { + return fmt.Errorf("sync read chunk: %w", err) + } + if err == syncReadChunkDone { + return err + } + if err = _send(dest, chunk); err != nil { + return fmt.Errorf("sync write stream: %w", err) + } + return nil + } + + for err == nil { + err = save() + } + + if err == syncReadChunkDone { + err = nil + } + return +} + +func (sync syncTransport) readChunk() (chunk []byte, err error) { + var status string + if status, err = sync.ReadStringN(4); err != nil { + return nil, err + } + + log := bytes.NewBufferString("") + defer func() { debugLog(log.String()) }() + + var tmpUint32 uint32 + if tmpUint32, err = sync.ReadUint32(); err != nil { + return nil, fmt.Errorf("read chunk (length): %w", err) + } + + if status == "FAIL" { + log.WriteString(fmt.Sprintf("<-- %s\t%d\t", status, tmpUint32)) + var sError string + if sError, err = sync.ReadStringN(int(tmpUint32)); err != nil { + return nil, fmt.Errorf("read chunk (error message): %w", err) + } + err = fmt.Errorf("status (fail): %s", sError) + log.WriteString(sError) + return + } + + switch status { + case "DONE": + log.WriteString(fmt.Sprintf("<-- %s", status)) + err = syncReadChunkDone + return + case "DATA": + log.WriteString(fmt.Sprintf("<-- %s\t%d\t", status, tmpUint32)) + if chunk, err = sync.ReadBytesN(int(tmpUint32)); err != nil { + return nil, err + } + default: + log.WriteString(fmt.Sprintf("<-- %s\t%d\t", status, tmpUint32)) + err = errors.New("unknown error") + } + + log.WriteString("......") + + return + +} + +func (sync syncTransport) ReadDirectoryEntry() (entry DeviceFileInfo, err error) { + var status string + if status, err = sync.ReadStringN(4); err != nil { + return DeviceFileInfo{}, err + } + + log := bytes.NewBufferString(fmt.Sprintf("<-- %s", status)) + defer func() { + debugLog(log.String()) + }() + + if status == "DONE" { + return + } + + log = bytes.NewBufferString(fmt.Sprintf("<-- %s\t", status)) + + if err = binary.Read(sync.sock, binary.LittleEndian, &entry.Mode); err != nil { + return DeviceFileInfo{}, fmt.Errorf("sync transport read (mode): %w", err) + } + log.WriteString(entry.Mode.String() + "\t") + + if entry.Size, err = sync.ReadUint32(); err != nil { + return DeviceFileInfo{}, fmt.Errorf("sync transport read (size): %w", err) + } + log.WriteString(fmt.Sprintf("%10d", entry.Size) + "\t") + + var tmpUint32 uint32 + if tmpUint32, err = sync.ReadUint32(); err != nil { + return DeviceFileInfo{}, fmt.Errorf("sync transport read (time): %w", err) + } + entry.LastModified = time.Unix(int64(tmpUint32), 0) + log.WriteString(entry.LastModified.String() + "\t") + + if tmpUint32, err = sync.ReadUint32(); err != nil { + return DeviceFileInfo{}, fmt.Errorf("sync transport read (file name length): %w", err) + } + log.WriteString(fmt.Sprintf("%d\t", tmpUint32)) + + if entry.Name, err = sync.ReadStringN(int(tmpUint32)); err != nil { + return DeviceFileInfo{}, fmt.Errorf("sync transport read (file name): %w", err) + } + log.WriteString(entry.Name + "\t") + + return +} + +func (sync syncTransport) ReadUint32() (n uint32, err error) { + err = binary.Read(sync.sock, binary.LittleEndian, &n) + return +} + +func (sync syncTransport) ReadStringN(size int) (s string, err error) { + var raw []byte + if raw, err = sync.ReadBytesN(size); err != nil { + return "", err + } + return string(raw), nil +} + +func (sync syncTransport) ReadBytesN(size int) (raw []byte, err error) { + _ = sync.sock.SetReadDeadline(time.Now().Add(time.Second * sync.readTimeout)) + return _readN(sync.sock, size) +} + +func (sync syncTransport) Close() (err error) { + if sync.sock == nil { + return nil + } + return sync.sock.Close() +} diff --git a/hrp/pkg/gadb/transport.go b/hrp/pkg/gadb/transport.go new file mode 100644 index 00000000..31726772 --- /dev/null +++ b/hrp/pkg/gadb/transport.go @@ -0,0 +1,150 @@ +package gadb + +import ( + "errors" + "fmt" + "io" + "io/ioutil" + "net" + "strconv" + "time" +) + +var ErrConnBroken = errors.New("socket connection broken") + +var DefaultAdbReadTimeout time.Duration = 60 + +type transport struct { + sock net.Conn + readTimeout time.Duration +} + +func newTransport(address string, readTimeout ...time.Duration) (tp transport, err error) { + if len(readTimeout) == 0 { + readTimeout = []time.Duration{DefaultAdbReadTimeout} + } + tp.readTimeout = readTimeout[0] + if tp.sock, err = net.Dial("tcp", address); err != nil { + err = fmt.Errorf("adb transport: %w", err) + } + return +} + +func (t transport) Send(command string) (err error) { + msg := fmt.Sprintf("%04x%s", len(command), command) + debugLog(fmt.Sprintf("--> %s", command)) + return _send(t.sock, []byte(msg)) +} + +func (t transport) VerifyResponse() (err error) { + var status string + if status, err = t.ReadStringN(4); err != nil { + return err + } + if status == "OKAY" { + debugLog(fmt.Sprintf("<-- %s", status)) + return nil + } + + var sError string + if sError, err = t.UnpackString(); err != nil { + return err + } + err = fmt.Errorf("command failed: %s", sError) + debugLog(fmt.Sprintf("<-- %s %s", status, sError)) + return +} + +func (t transport) ReadStringAll() (s string, err error) { + var raw []byte + raw, err = t.ReadBytesAll() + return string(raw), err +} + +func (t transport) ReadBytesAll() (raw []byte, err error) { + raw, err = ioutil.ReadAll(t.sock) + debugLog(fmt.Sprintf("\r%s", raw)) + return +} + +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.ReadBytesN(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.ReadBytesN(size); err != nil { + return "", err + } + return string(raw), nil +} + +func (t transport) ReadBytesN(size int) (raw []byte, err error) { + _ = t.sock.SetReadDeadline(time.Now().Add(time.Second * t.readTimeout)) + return _readN(t.sock, size) +} + +func (t transport) Close() (err error) { + if t.sock == nil { + return nil + } + return t.sock.Close() +} + +func (t transport) CreateSyncTransport() (sTp syncTransport, err error) { + if err = t.Send("sync:"); err != nil { + return syncTransport{}, err + } + if err = t.VerifyResponse(); err != nil { + return syncTransport{}, err + } + sTp = newSyncTransport(t.sock, t.readTimeout) + return +} + +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 _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 +} diff --git a/hrp/pkg/gadb/transport_test.go b/hrp/pkg/gadb/transport_test.go new file mode 100644 index 00000000..6b3b9b21 --- /dev/null +++ b/hrp/pkg/gadb/transport_test.go @@ -0,0 +1,26 @@ +package gadb + +import ( + "testing" +) + +func Test_transport_VerifyResponse(t *testing.T) { + SetDebug(true) + + transport, err := newTransport("localhost:5037") + if err != nil { + t.Fatal(err) + } + defer transport.Close() + + // err = transport.Send("host:123version") + err = transport.Send("host:version") + if err != nil { + t.Fatal(err) + } + + err = transport.VerifyResponse() + if err != nil { + t.Fatal(err) + } +} diff --git a/hrp/pkg/uixt/android_device.go b/hrp/pkg/uixt/android_device.go index 067b2086..d3545f2a 100644 --- a/hrp/pkg/uixt/android_device.go +++ b/hrp/pkg/uixt/android_device.go @@ -9,13 +9,13 @@ import ( "reflect" "strings" - "github.com/electricbubble/gadb" "github.com/pkg/errors" "github.com/rs/zerolog/log" "github.com/httprunner/httprunner/v4/hrp/internal/code" "github.com/httprunner/httprunner/v4/hrp/internal/json" "github.com/httprunner/httprunner/v4/hrp/internal/myexec" + "github.com/httprunner/httprunner/v4/hrp/pkg/gadb" ) var ( diff --git a/hrp/pkg/uixt/android_driver.go b/hrp/pkg/uixt/android_driver.go index 0d11e996..30138970 100644 --- a/hrp/pkg/uixt/android_driver.go +++ b/hrp/pkg/uixt/android_driver.go @@ -11,11 +11,11 @@ import ( "strings" "time" - "github.com/electricbubble/gadb" "github.com/pkg/errors" "github.com/rs/zerolog/log" "github.com/httprunner/httprunner/v4/hrp/internal/code" + "github.com/httprunner/httprunner/v4/hrp/pkg/gadb" ) var errDriverNotImplemented = errors.New("driver method not implemented")