3 Commits

Author SHA1 Message Date
1cb8e59d0d Merge pull request #3 from taigrr/cd/cleanup-filter-err-and-deps
fix(errors): implement filterErr with common adb stderr patterns
2026-04-12 10:26:49 +02:00
747c4abb7f fix(errors): implement filterErr with common adb stderr patterns
- Implement filterErr to detect device-not-found, offline, unauthorized,
  connection-refused, and multiple-device errors from stderr output
- Add ErrDeviceNotFound, ErrDeviceOffline, ErrDeviceUnauthorized,
  ErrConnectionRefused, ErrMoreThanOneDevice sentinel errors
- Remove log.Printf in CaptureSequence (was marked TODO for removal)
- Fix ConnString to use local variable instead of mutating value receiver
- Update go directive to 1.26.2
- Update README: check implemented features, fix intro typo, add
  root/keyevent/getevent to supported functions list
- Expand filterErr tests for all new error patterns
2026-04-08 10:38:56 +00:00
80d01ae3e9 Merge pull request #2 from taigrr/cd/tests-quality-fixes
fix: replace panic with error, fix device targeting, add tests
2026-03-14 22:49:58 -04:00
7 changed files with 60 additions and 27 deletions

View File

@@ -4,8 +4,8 @@
This library aims at providing idiomatic `adb` bindings for go developers, in order to make it easier to write system tooling using golang. This library aims at providing idiomatic `adb` bindings for go developers, in order to make it easier to write system tooling using golang.
This tool tries to take guesswork out of arbitrarily shelling out to `adb` by providing a structured, thoroughly-tested wrapper for the `adb` functions most-likely to be used in a system program. This tool tries to take guesswork out of arbitrarily shelling out to `adb` by providing a structured, thoroughly-tested wrapper for the `adb` functions most-likely to be used in a system program.
If `adb` must be installed in your path `PATH`. At this time, while this library may work on Windows or MacOS, only Linux is supported. `adb` must be installed and available in your `PATH`. At this time, while this library may work on Windows or macOS, only Linux is supported.
If you would like to add support for Windows, MacOS, *BSD..., please [Submit a Pull Request](https://github.com/taigrr/adb/pulls). If you would like to add support for Windows, macOS, *BSD, etc., please [Submit a Pull Request](https://github.com/taigrr/adb/pulls).
## What is adb ## What is adb
@@ -14,17 +14,21 @@ If you would like to add support for Windows, MacOS, *BSD..., please [Submit a P
## Supported adb functions ## Supported adb functions
- [ ] `adb connect` - [x] `adb connect`
- [ ] `adb disconnect` - [x] `adb disconnect`
- [ ] `adb shell <input>s` - [x] `adb shell <command>`
- [ ] `adb kill-server` - [x] `adb kill-server`
- [ ] `adb device` - [x] `adb devices`
- [ ] `adb pull` - [x] `adb pull`
- [ ] `adb install` - [ ] `adb install`
- [ ] `adb push` - [x] `adb push`
- [ ] `adb reboot` - [x] `adb reboot`
- [ ] `adb shell input tap X Y` - [x] `adb root`
- [ ] `adb shell input swipe X1 Y1 X2 Y2 duration` - [x] `adb shell input tap X Y`
- [x] `adb shell input swipe X1 Y1 X2 Y2 duration`
- [x] `adb shell input keyevent` (home, back, app switch)
- [x] `adb shell wm size` (screen resolution)
- [x] `adb shell getevent` (capture and replay tap sequences)
Please note that as this is a purpose-driven project library, not all functionality of ADB is currently supported, but if you need functionality that's not currently supported, Please note that as this is a purpose-driven project library, not all functionality of ADB is currently supported, but if you need functionality that's not currently supported,
Feel free to [Open an Issue](https://github.com/taigrr/adb/issues) or [Submit a Pull Request](https://github.com/taigrr/adb/pulls) Feel free to [Open an Issue](https://github.com/taigrr/adb/issues) or [Submit a Pull Request](https://github.com/taigrr/adb/pulls)

7
adb.go
View File

@@ -51,10 +51,11 @@ func Connect(ctx context.Context, opts ConnOptions) (Device, error) {
} }
func (d Device) ConnString() string { func (d Device) ConnString() string {
if d.Port == 0 { port := d.Port
d.Port = 5555 if port == 0 {
port = 5555
} }
return d.IP.String() + ":" + strconv.Itoa(int(d.Port)) return d.IP.String() + ":" + strconv.Itoa(int(port))
} }
// Connect to a previously discovered device. // Connect to a previously discovered device.

View File

@@ -4,7 +4,6 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"errors" "errors"
"log"
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
@@ -236,10 +235,7 @@ func (d Device) CaptureSequence(ctx context.Context) (t TapSequence, err error)
if errors.Is(err, ErrUnspecified) { if errors.Is(err, ErrUnspecified) {
err = nil err = nil
} }
if errCode != 130 && errCode != -1 && errCode != 1 { _ = errCode
// TODO remove log output here
log.Printf("Expected error code 130 or -1, but got %d\n", errCode)
}
if stdout == "" { if stdout == "" {
return TapSequence{}, ErrStdoutEmpty return TapSequence{}, ErrStdoutEmpty

View File

@@ -17,6 +17,16 @@ var (
ErrResolutionParseFail = errors.New("failed to parse screen size from input text") ErrResolutionParseFail = errors.New("failed to parse screen size from input text")
// ErrDestExists is returned when a pull destination file already exists. // ErrDestExists is returned when a pull destination file already exists.
ErrDestExists = errors.New("destination file already exists") ErrDestExists = errors.New("destination file already exists")
// ErrDeviceNotFound is returned when no device is connected or the target device cannot be found.
ErrDeviceNotFound = errors.New("device not found")
// ErrDeviceOffline is returned when the target device is offline.
ErrDeviceOffline = errors.New("device offline")
// ErrDeviceUnauthorized is returned when the device has not authorized USB debugging.
ErrDeviceUnauthorized = errors.New("device unauthorized; check the confirmation dialog on the device")
// ErrConnectionRefused is returned when the connection to a device is refused.
ErrConnectionRefused = errors.New("connection refused")
// ErrMoreThanOneDevice is returned when multiple devices are connected and no serial is specified.
ErrMoreThanOneDevice = errors.New("more than one device/emulator; use -s to specify a device")
// ErrUnspecified is returned when the exact error cannot be determined. // ErrUnspecified is returned when the exact error cannot be determined.
ErrUnspecified = errors.New("an unknown error has occurred, please open an issue on GitHub") ErrUnspecified = errors.New("an unknown error has occurred, please open an issue on GitHub")
) )

2
go.mod
View File

@@ -1,5 +1,5 @@
module github.com/taigrr/adb module github.com/taigrr/adb
go 1.26.1 go 1.26.2
require github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 require github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510

20
util.go
View File

@@ -5,6 +5,7 @@ import (
"context" "context"
"fmt" "fmt"
"os/exec" "os/exec"
"strings"
"sync" "sync"
) )
@@ -56,7 +57,22 @@ func execute(ctx context.Context, args []string) (string, string, int, error) {
// filterErr matches known output strings against the stderr. // filterErr matches known output strings against the stderr.
// //
// The inferred error type is then returned. // The inferred error type is then returned.
// TODO: implement
func filterErr(stderr string) error { func filterErr(stderr string) error {
return nil if stderr == "" {
return nil
}
switch {
case strings.Contains(stderr, "device not found"):
return ErrDeviceNotFound
case strings.Contains(stderr, "device offline"):
return ErrDeviceOffline
case strings.Contains(stderr, "device unauthorized"):
return ErrDeviceUnauthorized
case strings.Contains(stderr, "Connection refused"):
return ErrConnectionRefused
case strings.Contains(stderr, "more than one device"):
return ErrMoreThanOneDevice
default:
return nil
}
} }

View File

@@ -1,6 +1,7 @@
package adb package adb
import ( import (
"errors"
"testing" "testing"
) )
@@ -8,15 +9,20 @@ func Test_filterErr(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
stderr string stderr string
wantErr bool wantErr error
}{ }{
{name: "empty stderr", stderr: "", wantErr: false}, {name: "empty stderr", stderr: "", wantErr: nil},
{name: "random output", stderr: "some warning text", wantErr: false}, {name: "random output", stderr: "some warning text", wantErr: nil},
{name: "device not found", stderr: "error: device not found", wantErr: ErrDeviceNotFound},
{name: "device offline", stderr: "error: device offline", wantErr: ErrDeviceOffline},
{name: "device unauthorized", stderr: "error: device unauthorized.\nThis adb server's $ADB_VENDOR_KEYS is not set", wantErr: ErrDeviceUnauthorized},
{name: "connection refused", stderr: "cannot connect to daemon at tcp:5037: Connection refused", wantErr: ErrConnectionRefused},
{name: "more than one device", stderr: "error: more than one device/emulator", wantErr: ErrMoreThanOneDevice},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
err := filterErr(tt.stderr) err := filterErr(tt.stderr)
if (err != nil) != tt.wantErr { if !errors.Is(err, tt.wantErr) {
t.Errorf("filterErr() error = %v, wantErr %v", err, tt.wantErr) t.Errorf("filterErr() error = %v, wantErr %v", err, tt.wantErr)
} }
}) })