mirror of
https://github.com/httprunner/httprunner.git
synced 2026-05-13 17:29:56 +08:00
Merge pull request #1584 from httprunner/gadb
replace gadb with optimized version authoried by @appl3s
This commit is contained in:
@@ -26,7 +26,7 @@ var listAndroidDevicesCmd = &cobra.Command{
|
||||
return errors.Wrap(err, "list android devices failed")
|
||||
}
|
||||
|
||||
var deviceList []gadb.Device
|
||||
var deviceList []*gadb.Device
|
||||
// filter by serial
|
||||
for _, d := range devices {
|
||||
if serial != "" && serial != d.Serial() {
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# gadb
|
||||
|
||||
This module is initially forked from [electricbubble/gadb@v0.0.7].
|
||||
This module is initially forked from [electricbubble/gadb@v0.0.7] and optimized by [@appl3s].
|
||||
|
||||
- feat: add reverse forward command
|
||||
- feat: add `RunShellCommandV2` which supports running nohup
|
||||
- feat: add `InstallAPK` with feature judgment
|
||||
- feat: add `Uninstall` for specified package name
|
||||
|
||||
[electricbubble/gadb@v0.0.7]: https://github.com/electricbubble/gadb/tree/v0.0.7
|
||||
[@appl3s]: https://github.com/appl3s
|
||||
|
||||
@@ -38,6 +38,16 @@ func NewClientWith(host string, port ...int) (adbClient Client, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func NewClientWithoutTransport(host string, port ...int) (adbClient Client, err error) {
|
||||
if len(port) == 0 {
|
||||
port = []int{AdbServerPort}
|
||||
}
|
||||
adbClient.host = host
|
||||
adbClient.port = port[0]
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c Client) ServerVersion() (version int, err error) {
|
||||
var resp string
|
||||
if resp, err = c.executeCommand("host:version"); err != nil {
|
||||
@@ -73,14 +83,14 @@ func (c Client) DeviceSerialList() (serials []string, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (c Client) DeviceList() (devices []Device, err error) {
|
||||
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))
|
||||
devices = make([]*Device, 0, len(lines))
|
||||
|
||||
for i := range lines {
|
||||
line := strings.TrimSpace(lines[i])
|
||||
@@ -101,7 +111,7 @@ func (c Client) DeviceList() (devices []Device, err error) {
|
||||
key, val := split[0], split[1]
|
||||
mapAttrs[key] = val
|
||||
}
|
||||
devices = append(devices, Device{adbClient: c, serial: fields[0], attrs: mapAttrs})
|
||||
devices = append(devices, &Device{adbClient: c, serial: fields[0], attrs: mapAttrs})
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
package gadb
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -127,3 +128,22 @@ func TestClient_KillServer(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestScreenCap(t *testing.T) {
|
||||
adbClient, err := NewClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
dl, err := adbClient.DeviceList()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
d := dl[0]
|
||||
res, err := d.ScreenCap()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
t.Log(len(res))
|
||||
ioutil.WriteFile("/tmp/1.png", res, 0o644)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package gadb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -48,9 +50,10 @@ func deviceStateConv(k string) (deviceState DeviceState) {
|
||||
}
|
||||
|
||||
type DeviceForward struct {
|
||||
Serial string
|
||||
Local string
|
||||
Remote string
|
||||
Serial string
|
||||
Local string
|
||||
Remote string
|
||||
Reverse bool
|
||||
// LocalProtocol string
|
||||
// RemoteProtocol string
|
||||
}
|
||||
@@ -59,51 +62,92 @@ type Device struct {
|
||||
adbClient Client
|
||||
serial string
|
||||
attrs map[string]string
|
||||
feat Features
|
||||
}
|
||||
|
||||
func (d Device) Product() string {
|
||||
func (d *Device) HasFeature(name Feature) bool {
|
||||
feats, err := d.GetFeatures()
|
||||
if err != nil || len(feats) == 0 {
|
||||
return false
|
||||
}
|
||||
return feats.HasFeature(name)
|
||||
}
|
||||
|
||||
func (d *Device) GetFeatures() (features Features, err error) {
|
||||
if len(d.feat) > 0 {
|
||||
return d.feat, nil
|
||||
}
|
||||
return d.features()
|
||||
}
|
||||
|
||||
func (d *Device) features() (features Features, err error) {
|
||||
res, err := d.executeCommand("host:features")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(res) > 4 {
|
||||
// stip hash
|
||||
res = res[4:]
|
||||
}
|
||||
fs := strings.Split(string(res), ",")
|
||||
features = make(Features, len(fs))
|
||||
for _, f := range fs {
|
||||
features[Feature(f)] = struct{}{}
|
||||
}
|
||||
d.feat = features
|
||||
return features, nil
|
||||
}
|
||||
|
||||
func (d *Device) Product() string {
|
||||
return d.attrs["product"]
|
||||
}
|
||||
|
||||
func (d Device) Model() string {
|
||||
func (d *Device) Model() string {
|
||||
return d.attrs["model"]
|
||||
}
|
||||
|
||||
func (d Device) Usb() string {
|
||||
func (d *Device) Usb() string {
|
||||
return d.attrs["usb"]
|
||||
}
|
||||
|
||||
func (d Device) transportId() string {
|
||||
func (d *Device) transportId() string {
|
||||
return d.attrs["transport_id"]
|
||||
}
|
||||
|
||||
func (d Device) DeviceInfo() map[string]string {
|
||||
func (d *Device) DeviceInfo() map[string]string {
|
||||
return d.attrs
|
||||
}
|
||||
|
||||
func (d Device) Serial() string {
|
||||
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 {
|
||||
func (d *Device) IsUsb() bool {
|
||||
return d.Usb() != ""
|
||||
}
|
||||
|
||||
func (d Device) State() (DeviceState, error) {
|
||||
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) {
|
||||
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) {
|
||||
func (d *Device) Forward(localPort int, remoteInterface interface{}, noRebind ...bool) (err error) {
|
||||
command := ""
|
||||
var remote string
|
||||
local := fmt.Sprintf("tcp:%d", localPort)
|
||||
remote := fmt.Sprintf("tcp:%d", remotePort)
|
||||
switch r := remoteInterface.(type) {
|
||||
// for unix sockets
|
||||
case string:
|
||||
remote = r
|
||||
case int:
|
||||
remote = fmt.Sprintf("tcp:%d", r)
|
||||
}
|
||||
|
||||
if len(noRebind) != 0 && noRebind[0] {
|
||||
command = fmt.Sprintf("host-serial:%s:forward:norebind:%s;%s", d.serial, local, remote)
|
||||
@@ -115,7 +159,7 @@ func (d Device) Forward(localPort, remotePort int, noRebind ...bool) (err error)
|
||||
return
|
||||
}
|
||||
|
||||
func (d Device) ForwardList() (deviceForwardList []DeviceForward, err error) {
|
||||
func (d *Device) ForwardList() (deviceForwardList []DeviceForward, err error) {
|
||||
var forwardList []DeviceForward
|
||||
if forwardList, err = d.adbClient.ForwardList(); err != nil {
|
||||
return nil, err
|
||||
@@ -131,18 +175,80 @@ func (d Device) ForwardList() (deviceForwardList []DeviceForward, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (d Device) ForwardKill(localPort int) (err error) {
|
||||
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) {
|
||||
func (d *Device) ReverseForward(localPort int, remoteInterface interface{}, noRebind ...bool) (err error) {
|
||||
var command string
|
||||
var remote string
|
||||
local := fmt.Sprintf("tcp:%d", localPort)
|
||||
switch r := remoteInterface.(type) {
|
||||
// for unix sockets
|
||||
case string:
|
||||
remote = r
|
||||
case int:
|
||||
remote = fmt.Sprintf("tcp:%d", r)
|
||||
}
|
||||
|
||||
if len(noRebind) != 0 && noRebind[0] {
|
||||
command = fmt.Sprintf("reverse:forward:norebind:%s;%s", remote, local)
|
||||
} else {
|
||||
command = fmt.Sprintf("reverse:forward:%s;%s", remote, local)
|
||||
}
|
||||
_, err = d.executeCommand(command, true)
|
||||
return
|
||||
}
|
||||
|
||||
func (d *Device) ReverseForwardList() (deviceForwardList []DeviceForward, err error) {
|
||||
res, err := d.executeCommand("reverse:list-forward")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resStr := string(res)
|
||||
lines := strings.Split(resStr, "\n")
|
||||
for _, line := range lines {
|
||||
groups := strings.Split(line, " ")
|
||||
if len(groups) == 3 {
|
||||
deviceForwardList = append(deviceForwardList, DeviceForward{
|
||||
Reverse: true,
|
||||
Serial: d.serial,
|
||||
Remote: groups[1],
|
||||
Local: groups[2],
|
||||
})
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (d *Device) ReverseForwardKill(remoteInterface interface{}) error {
|
||||
remote := ""
|
||||
switch r := remoteInterface.(type) {
|
||||
case string:
|
||||
remote = r
|
||||
case int:
|
||||
remote = fmt.Sprintf("tcp:%d", r)
|
||||
}
|
||||
_, err := d.executeCommand(fmt.Sprintf("reverse:killforward:%s", remote), true)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *Device) ReverseForwardKillAll() error {
|
||||
_, err := d.executeCommand("reverse:killforward-all")
|
||||
return err
|
||||
}
|
||||
|
||||
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) {
|
||||
func (d *Device) RunShellCommandWithBytes(cmd string, args ...string) ([]byte, error) {
|
||||
if d.HasFeature(FeatShellV2) {
|
||||
return d.RunShellCommandV2WithBytes(cmd, args...)
|
||||
}
|
||||
if len(args) > 0 {
|
||||
cmd = fmt.Sprintf("%s %s", cmd, strings.Join(args, " "))
|
||||
}
|
||||
@@ -156,7 +262,86 @@ func (d Device) RunShellCommandWithBytes(cmd string, args ...string) ([]byte, er
|
||||
return raw, err
|
||||
}
|
||||
|
||||
func (d Device) EnableAdbOverTCP(port ...int) (err error) {
|
||||
func (d *Device) RunShellCommandV2(cmd string, args ...string) (string, error) {
|
||||
raw, err := d.RunShellCommandV2WithBytes(cmd, args...)
|
||||
return string(raw), err
|
||||
}
|
||||
|
||||
// RunShellCommandV2WithBytes shell v2, 支持后台运行而不会阻断
|
||||
func (d *Device) RunShellCommandV2WithBytes(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")
|
||||
}
|
||||
log.Debug().Str("cmd",
|
||||
fmt.Sprintf("adb -s %s shell %s", d.serial, cmd)).
|
||||
Msg("run adb command in v2")
|
||||
raw, err := d.executeCommand(fmt.Sprintf("shell,v2,raw:%s", cmd))
|
||||
if err != nil {
|
||||
return raw, err
|
||||
}
|
||||
return d.parseV2CommandWithBytes(raw)
|
||||
}
|
||||
|
||||
func (d *Device) parseV2CommandWithBytes(input []byte) (res []byte, err error) {
|
||||
if len(input) == 0 {
|
||||
return input, nil
|
||||
}
|
||||
reader := bytes.NewReader(input)
|
||||
sizeBuf := make([]byte, 4)
|
||||
var (
|
||||
resBuf []byte
|
||||
exitCode int
|
||||
)
|
||||
loop:
|
||||
for {
|
||||
msgCode, err := reader.ReadByte()
|
||||
if err != nil {
|
||||
return input, err
|
||||
}
|
||||
switch msgCode {
|
||||
case 0x01, 0x02: // STDOUT, STDERR
|
||||
_, err = io.ReadFull(reader, sizeBuf)
|
||||
if err != nil {
|
||||
return input, err
|
||||
}
|
||||
size := binary.LittleEndian.Uint32(sizeBuf)
|
||||
if cap(resBuf) < int(size) {
|
||||
resBuf = make([]byte, int(size))
|
||||
}
|
||||
_, err = io.ReadFull(reader, resBuf[:size])
|
||||
if err != nil {
|
||||
return input, err
|
||||
}
|
||||
res = append(res, resBuf[:size]...)
|
||||
case 0x03: // EXIT
|
||||
_, err = io.ReadFull(reader, sizeBuf)
|
||||
if err != nil {
|
||||
return input, err
|
||||
}
|
||||
size := binary.LittleEndian.Uint32(sizeBuf)
|
||||
if cap(resBuf) < int(size) {
|
||||
resBuf = make([]byte, int(size))
|
||||
}
|
||||
ec, err := reader.ReadByte()
|
||||
if err != nil {
|
||||
return input, err
|
||||
}
|
||||
exitCode = int(ec)
|
||||
break loop
|
||||
default:
|
||||
return input, nil
|
||||
}
|
||||
}
|
||||
if exitCode != 0 {
|
||||
return nil, errors.New(string(res))
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (d *Device) EnableAdbOverTCP(port ...int) (err error) {
|
||||
if len(port) == 0 {
|
||||
port = []int{AdbDaemonPort}
|
||||
}
|
||||
@@ -168,7 +353,7 @@ func (d Device) EnableAdbOverTCP(port ...int) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (d Device) createDeviceTransport() (tp transport, err error) {
|
||||
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
|
||||
}
|
||||
@@ -180,7 +365,7 @@ func (d Device) createDeviceTransport() (tp transport, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (d Device) executeCommand(command string, onlyVerifyResponse ...bool) (raw []byte, err error) {
|
||||
func (d *Device) executeCommand(command string, onlyVerifyResponse ...bool) (raw []byte, err error) {
|
||||
if len(onlyVerifyResponse) == 0 {
|
||||
onlyVerifyResponse = []bool{false}
|
||||
}
|
||||
@@ -207,7 +392,7 @@ func (d Device) executeCommand(command string, onlyVerifyResponse ...bool) (raw
|
||||
return
|
||||
}
|
||||
|
||||
func (d Device) List(remotePath string) (devFileInfos []DeviceFileInfo, err error) {
|
||||
func (d *Device) List(remotePath string) (devFileInfos []DeviceFileInfo, err error) {
|
||||
var tp transport
|
||||
if tp, err = d.createDeviceTransport(); err != nil {
|
||||
return nil, err
|
||||
@@ -237,7 +422,7 @@ func (d Device) List(remotePath string) (devFileInfos []DeviceFileInfo, err erro
|
||||
return
|
||||
}
|
||||
|
||||
func (d Device) PushFile(local *os.File, remotePath string, modification ...time.Time) (err error) {
|
||||
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 {
|
||||
@@ -249,7 +434,7 @@ func (d Device) PushFile(local *os.File, remotePath string, modification ...time
|
||||
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) {
|
||||
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}
|
||||
}
|
||||
@@ -285,7 +470,7 @@ func (d Device) Push(source io.Reader, remotePath string, modification time.Time
|
||||
return
|
||||
}
|
||||
|
||||
func (d Device) Pull(remotePath string, dest io.Writer) (err error) {
|
||||
func (d *Device) Pull(remotePath string, dest io.Writer) (err error) {
|
||||
var tp transport
|
||||
if tp, err = d.createDeviceTransport(); err != nil {
|
||||
return err
|
||||
@@ -305,3 +490,87 @@ func (d Device) Pull(remotePath string, dest io.Writer) (err error) {
|
||||
err = sync.WriteStream(dest)
|
||||
return
|
||||
}
|
||||
|
||||
func (d *Device) installViaABBExec(apk io.ReadSeeker) (raw []byte, err error) {
|
||||
var (
|
||||
tp transport
|
||||
filesize int64
|
||||
)
|
||||
filesize, err = apk.Seek(0, io.SeekEnd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if tp, err = d.createDeviceTransport(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() { _ = tp.Close() }()
|
||||
if err = tp.Send(fmt.Sprintf("abb_exec:package\x00install\x00-t\x00-S\x00%d", filesize)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = tp.VerifyResponse(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = apk.Seek(0, io.SeekStart)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = io.Copy(tp.Conn(), apk)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
raw, err = tp.ReadBytesAll()
|
||||
return
|
||||
}
|
||||
|
||||
func (d *Device) InstallAPK(apk io.ReadSeeker) (string, error) {
|
||||
haserr := func(ret string) bool {
|
||||
return strings.Contains(ret, "Failure")
|
||||
}
|
||||
if d.HasFeature(FeatAbbExec) {
|
||||
raw, err := d.installViaABBExec(apk)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error installing: %v", err)
|
||||
}
|
||||
if haserr(string(raw)) {
|
||||
return "", errors.New(string(raw))
|
||||
}
|
||||
return string(raw), err
|
||||
}
|
||||
|
||||
remote := fmt.Sprintf("/data/local/tmp/gadb_remote_%d.apk", time.Now().Unix())
|
||||
err := d.Push(apk, remote, time.Now())
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error pushing: %v", err)
|
||||
}
|
||||
|
||||
res, err := d.RunShellCommand("pm", "install", "-f", remote)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error installing: %v", err)
|
||||
}
|
||||
if haserr(res) {
|
||||
return "", errors.New(res)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (d *Device) Uninstall(packageName string, keepData ...bool) (string, error) {
|
||||
if len(keepData) == 0 {
|
||||
keepData = []bool{false}
|
||||
}
|
||||
packageName = strings.ReplaceAll(packageName, " ", "")
|
||||
if len(packageName) == 0 {
|
||||
return "", fmt.Errorf("invalid package name")
|
||||
}
|
||||
args := []string{"uninstall"}
|
||||
if keepData[0] {
|
||||
args = append(args, "-k")
|
||||
}
|
||||
args = append(args, packageName)
|
||||
return d.RunShellCommandV2("pm", args...)
|
||||
}
|
||||
|
||||
func (d *Device) ScreenCap() ([]byte, error) {
|
||||
return d.RunShellCommandV2WithBytes("screencap", "-p")
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -145,6 +146,42 @@ func TestDevice_Forward(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDevice_ReverseForward(t *testing.T) {
|
||||
adbClient, err := NewClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
devices, err := adbClient.DeviceList()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
localPort := 5005
|
||||
err = devices[0].ReverseForward(localPort, "localabstract:scrcpy")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = devices[0].ReverseForward(localPort, "localabstract:scrcpy1")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = devices[0].ReverseForwardList()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = devices[0].ReverseForwardKill("localabstract:scrcpy1")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = devices[0].ReverseForwardKillAll()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDevice_ForwardList(t *testing.T) {
|
||||
adbClient, err := NewClient()
|
||||
if err != nil {
|
||||
@@ -314,3 +351,94 @@ func TestDevice_Pull(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDevice_RunShellCommandBackgroundWithBytes(t *testing.T) {
|
||||
type fields struct {
|
||||
adbClient Client
|
||||
serial string
|
||||
attrs map[string]string
|
||||
}
|
||||
type args struct {
|
||||
cmd string
|
||||
args []string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
want []byte
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "runShellCommandBackground",
|
||||
fields: fields{
|
||||
adbClient: func() Client {
|
||||
c, _ := NewClient()
|
||||
return c
|
||||
}(),
|
||||
serial: "63c1ee94",
|
||||
},
|
||||
args: args{
|
||||
cmd: "nohup sleep 10 2>/dev/null 1>/dev/null &",
|
||||
// cmd: "sleep 10",
|
||||
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
d := Device{
|
||||
adbClient: tt.fields.adbClient,
|
||||
serial: tt.fields.serial,
|
||||
attrs: tt.fields.attrs,
|
||||
}
|
||||
got, err := d.RunShellCommandV2WithBytes(tt.args.cmd, tt.args.args...)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("Device.RunShellCommandBackgroundWithBytes() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("Device.RunShellCommandBackgroundWithBytes() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDevice_InstallAPK(t *testing.T) {
|
||||
apk, _ := os.Open("test.apk")
|
||||
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]
|
||||
|
||||
res, err := dev.InstallAPK(apk)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(res)
|
||||
}
|
||||
|
||||
func TestDevice_HasFeature(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]
|
||||
|
||||
t.Log(dev.GetFeatures())
|
||||
}
|
||||
|
||||
26
hrp/pkg/gadb/features.go
Normal file
26
hrp/pkg/gadb/features.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package gadb
|
||||
|
||||
type (
|
||||
Feature string
|
||||
Features map[Feature]struct{}
|
||||
)
|
||||
|
||||
var (
|
||||
FeatSendrecvV2Brotli = Feature("sendrecv_v2_brotli")
|
||||
FeatRemountShell = Feature("remount_shell")
|
||||
FeatSendrecvV2 = Feature("sendrecv_v2")
|
||||
FeatAbbExec = Feature("abb_exec")
|
||||
FeatFixedPushMkdir = Feature("fixed_push_mkdir")
|
||||
FeatFixedPushSymlinkTimestamp = Feature("fixed_push_symlink_timestamp")
|
||||
FeatAbb = Feature("abb")
|
||||
FeatShellV2 = Feature("shell_v2")
|
||||
FeatCmd = Feature("cmd")
|
||||
FeatLsV2 = Feature("ls_v2")
|
||||
FeatApex = Feature("apex")
|
||||
FeatStatV2 = Feature("stat_v2")
|
||||
)
|
||||
|
||||
func (fs Features) HasFeature(name Feature) bool {
|
||||
_, has := fs[name]
|
||||
return has
|
||||
}
|
||||
@@ -250,5 +250,6 @@ func (sync syncTransport) Close() (err error) {
|
||||
if sync.sock == nil {
|
||||
return nil
|
||||
}
|
||||
_ = DisableTimeWait(sync.sock.(*net.TCPConn))
|
||||
return sync.sock.Close()
|
||||
}
|
||||
|
||||
@@ -37,6 +37,14 @@ func (t transport) Send(command string) (err error) {
|
||||
return _send(t.sock, []byte(msg))
|
||||
}
|
||||
|
||||
func (t transport) SendBytes(b []byte) (err error) {
|
||||
return _send(t.sock, b)
|
||||
}
|
||||
|
||||
func (t transport) Conn() net.Conn {
|
||||
return t.sock
|
||||
}
|
||||
|
||||
func (t transport) VerifyResponse() (err error) {
|
||||
var status string
|
||||
if status, err = t.ReadStringN(4); err != nil {
|
||||
@@ -103,6 +111,7 @@ func (t transport) Close() (err error) {
|
||||
if t.sock == nil {
|
||||
return nil
|
||||
}
|
||||
_ = DisableTimeWait(t.sock.(*net.TCPConn))
|
||||
return t.sock.Close()
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ func Test_transport_VerifyResponse(t *testing.T) {
|
||||
}
|
||||
defer transport.Close()
|
||||
|
||||
// err = transport.Send("host:123version")
|
||||
err = transport.Send("host:version")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
9
hrp/pkg/gadb/utils.go
Normal file
9
hrp/pkg/gadb/utils.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package gadb
|
||||
|
||||
import (
|
||||
"net"
|
||||
)
|
||||
|
||||
func DisableTimeWait(conn *net.TCPConn) error {
|
||||
return conn.SetLinger(0)
|
||||
}
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
type adbDriver struct {
|
||||
Driver
|
||||
|
||||
adbClient gadb.Device
|
||||
adbClient *gadb.Device
|
||||
logcat *AdbLogcat
|
||||
}
|
||||
|
||||
@@ -280,9 +280,7 @@ func (ad *adbDriver) SetRotation(rotation Rotation) (err error) {
|
||||
|
||||
func (ad *adbDriver) Screenshot() (raw *bytes.Buffer, err error) {
|
||||
// adb shell screencap -p
|
||||
resp, err := ad.adbClient.RunShellCommandWithBytes(
|
||||
"screencap", "-p",
|
||||
)
|
||||
resp, err := ad.adbClient.ScreenCap()
|
||||
if err == nil {
|
||||
return bytes.NewBuffer(resp), nil
|
||||
}
|
||||
|
||||
@@ -115,7 +115,7 @@ func NewAndroidDevice(options ...AndroidDeviceOption) (device *AndroidDevice, er
|
||||
fmt.Sprintf("device %s not found", device.SerialNumber))
|
||||
}
|
||||
|
||||
func DeviceList() (devices []gadb.Device, err error) {
|
||||
func DeviceList() (devices []*gadb.Device, err error) {
|
||||
var adbClient gadb.Client
|
||||
if adbClient, err = gadb.NewClientWith(AdbServerHost, AdbServerPort); err != nil {
|
||||
return nil, errors.Wrap(code.AndroidDeviceConnectionError, err.Error())
|
||||
@@ -125,7 +125,7 @@ func DeviceList() (devices []gadb.Device, err error) {
|
||||
}
|
||||
|
||||
type AndroidDevice struct {
|
||||
d gadb.Device
|
||||
d *gadb.Device
|
||||
logcat *AdbLogcat
|
||||
SerialNumber string `json:"serial,omitempty" yaml:"serial,omitempty"`
|
||||
UIA2 bool `json:"uia2,omitempty" yaml:"uia2,omitempty"` // use uiautomator2
|
||||
|
||||
Reference in New Issue
Block a user