package device import ( "os" "path/filepath" "runtime" "strings" "testing" ) func TestUSBDiscoverySelectionAndHotplug(t *testing.T) { if runtime.GOOS == "windows" { t.Skip("fake ADB uses a POSIX shell") } dir := t.TempDir() state := filepath.Join(dir, "devices") calls := filepath.Join(dir, "calls") bin := filepath.Join(dir, "adb") t.Setenv("FAKE_ADB_DEVICES", state) t.Setenv("FAKE_ADB_CALLS", calls) script := `#!/bin/sh printf '%s\n' "$*" >> "$FAKE_ADB_CALLS" if [ "$1" != -P ] || [ "$2" != 17891 ]; then exit 42; fi shift 2 case "$1" in devices) printf 'List of devices attached\n'; cat "$FAKE_ADB_DEVICES" ;; -s) if [ "$4" = wm ]; then printf 'Physical size: 1080x2400\n'; fi ;; esac ` if err := os.WriteFile(bin, []byte(script), 0700); err != nil { t.Fatal(err) } previous := adbPathOverride SetAdbPath(bin) t.Cleanup(func() { SetAdbPath(previous) }) writeDevices := func(devices string) { t.Helper() if err := os.WriteFile(state, []byte(devices), 0600); err != nil { t.Fatal(err) } } const usbA = "USB_A device usb:1 model:Phone_A transport_id:1\n" const usbZ = "USB_Z device usb:2 model:Phone_Z transport_id:2\n" const locked = "USB_LOCKED unauthorized usb:3 transport_id:3\n" const wifiID = "adb-WIFI-random._adb-tls-connect._tcp" const wifi = wifiID + " device model:Wireless_Phone transport_id:4\n" writeDevices(wifi + usbZ + locked + usbA) s := &Service{devices: map[string]DeviceInfo{}} devices, err := s.ListDevices() if err != nil || len(devices) != 4 { t.Fatalf("list: %+v %v", devices, err) } if devices[0].ID != "USB_A" || devices[1].ID != "USB_Z" || devices[2].ID != wifiID || devices[3].ID != "USB_LOCKED" { t.Fatalf("unstable USB/online ordering: %+v", devices) } if devices[3].Online || devices[3].State != "unauthorized" || devices[3].Mode != ConnUSB || devices[3].Width != 0 { t.Fatalf("unauthorized USB not preserved: %+v", devices[3]) } if active := s.GetActiveDevice(); active.ID != "USB_A" || active.Mode != ConnUSB || active.Width != 1080 { t.Fatalf("initial USB selection: %+v", active) } if err := s.SelectDevice(wifiID); err != nil { t.Fatal(err) } if _, err := s.ListDevices(); err != nil || s.GetActiveDevice().ID != wifiID { t.Fatal("refresh changed an explicitly selected online wireless device") } writeDevices(usbZ + usbA + wifiID + " offline\n" + locked) if _, err := s.ListDevices(); err != nil || s.GetActiveDevice().ID != "USB_A" { t.Fatal("offline wireless selection did not fall back to online USB") } if err := s.Tap(TapRequest{DeviceID: "USB_A", X: 10, Y: 20}); err != nil { t.Fatalf("USB input: %v", err) } writeDevices(wifi + locked) if _, err := s.ListDevices(); err != nil || s.GetActiveDevice().ID != wifiID { t.Fatal("unplugged USB selection did not fall back to online wireless") } writeDevices("USB_LOCKED device usb:3 model:Authorized_Phone\n") if _, err := s.ListDevices(); err != nil || s.GetActiveDevice().ID != "USB_LOCKED" || !s.GetActiveDevice().Online { t.Fatal("USB authorization change was not discovered") } if result := s.DisconnectDevice("USB_LOCKED"); !result.OK { t.Fatalf("USB session disconnect: %+v", result) } if devices, err := s.ListDevices(); err != nil || len(devices) != 1 || devices[0].Online || devices[0].State != "disconnected" { t.Fatalf("refresh reconnected USB session: %+v %v", devices, err) } if result := s.StartPreviewStream("USB_LOCKED", false); result.OK { t.Fatal("preview opened a disconnected USB session") } if err := s.SelectDevice("USB_LOCKED"); err != nil || !s.GetActiveDevice().Online { t.Fatalf("USB reconnect failed: %v", err) } history, err := os.ReadFile(calls) if err != nil { t.Fatal(err) } if !strings.Contains(string(history), "-P 17891 -s USB_A shell input tap 10 20\n") { t.Fatalf("input did not target USB serial: %s", history) } if strings.Contains(string(history), "disconnect") || strings.Contains(string(history), "connect ") || strings.Contains(string(history), "kill-server") { t.Fatalf("USB discovery interfered with ADB transports: %s", history) } } func TestInferUSBAndWirelessMode(t *testing.T) { for _, tc := range []struct { serial string want ConnMode }{ {"bdeafa21", ConnUSB}, {"192.0.2.1:5555", ConnLAN}, {"[2001:db8::1]:5555", ConnLAN}, {"adb-serial-random._adb-tls-connect._tcp", ConnLAN}, {"phone._adb._tcp", ConnLAN}, } { if got := inferMode(tc.serial); got != tc.want { t.Errorf("%s: got %s, want %s", tc.serial, got, tc.want) } } }