forked from danielpaulus/go-ios
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcmd_device_ui_run.go
More file actions
134 lines (123 loc) · 4.4 KB
/
Copy pathcmd_device_ui_run.go
File metadata and controls
134 lines (123 loc) · 4.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
package main
import (
"context"
"fmt"
"io"
"log/slog"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/danielpaulus/go-ios/ios/forward"
"github.com/danielpaulus/go-ios/ios/testmanagerd"
)
type uiRunTarget struct {
name string
defaultBundle string
devicePort uint16
healthPath string
xctestConfig string
}
// runUIRunCommand runs a UI automation runner (WebDriverAgent or DeviceKit) and
// forwards its port to the host, so `ios ui ...` and any HTTP client can reach it
// at http://127.0.0.1:<host-port>. It is the run counterpart to `ios ui download`
// and `ios ui install`, so users don't have to hand-roll `ios runtest`. It blocks
// until interrupted, then stops the runner and tears the forward down.
func runUIRunCommand(ctx commandContext) {
var target uiRunTarget
switch {
case boolArg(ctx.Args, "wda"):
target = uiRunTarget{name: "wda", defaultBundle: defaultWDABundleID, devicePort: 8100, healthPath: "/status", xctestConfig: "WebDriverAgentRunner.xctest"}
case boolArg(ctx.Args, "devicekit"):
target = uiRunTarget{name: "devicekit", defaultBundle: defaultDeviceKitBundleID, devicePort: 12004, healthPath: "/health", xctestConfig: "devicekit-iosUITests.xctest"}
default:
logFatal("unknown ui run target; use 'ios ui run wda' or 'ios ui run devicekit'")
}
bundleID, _ := ctx.Args.String("--bundleid")
if bundleID == "" {
bundleID = target.defaultBundle
}
// The runner bundle is also the test host. Allow overrides for non-default
// builds, but default to the runner bundle id and the target's xctest config.
testRunnerBundleID, _ := ctx.Args.String("--test-runner-bundleid")
if testRunnerBundleID == "" {
testRunnerBundleID = bundleID
}
xctestConfig, _ := ctx.Args.String("--xctest-config")
if xctestConfig == "" {
xctestConfig = target.xctestConfig
}
hostPort := target.devicePort
if hp, err := ctx.Args.Int("--host-port"); err == nil && hp > 0 {
hostPort = uint16(hp)
}
// Run the runner. testmanagerd derives the test-runner bundle id and xctest
// config from the bundle id when the others are left empty, so callers only
// need the runner's bundle id.
runCtx, stopRunner := context.WithCancel(context.Background())
runErr := make(chan error, 1)
go func() {
_, err := testmanagerd.RunTestWithConfig(runCtx, testmanagerd.TestConfig{
BundleId: bundleID,
TestRunnerBundleId: testRunnerBundleID,
XctestConfigName: xctestConfig,
Device: ctx.Device,
Listener: testmanagerd.NewTestListener(uiRunLogWriter(ctx), uiRunLogWriter(ctx), os.TempDir()),
})
if err != nil {
runErr <- err
}
stopRunner()
}()
cl, err := forward.Forward(ctx.Device, hostPort, target.devicePort)
exitIfError("failed to forward "+target.name+" port", err)
defer func() { _ = cl.Close() }()
localURL := fmt.Sprintf("http://127.0.0.1:%d", hostPort)
go reportUIRunReady(localURL+target.healthPath, target.name, localURL, ctx.Device.Properties.SerialNumber)
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
select {
case err := <-runErr:
slog.Error("ui run: runner failed", "target", target.name, "error", err)
stopRunner()
os.Exit(1)
case <-runCtx.Done():
slog.Error("ui run: runner ended unexpectedly", "target", target.name)
os.Exit(1)
case sig := <-c:
slog.Info("signal received, stopping ui run", "signal", sig.String(), "target", target.name)
stopRunner()
}
}
func uiRunLogWriter(ctx commandContext) io.Writer {
rawTestlog, err := ctx.Args.String("--log-output")
if err != nil {
return io.Discard
}
if rawTestlog == "" || rawTestlog == "-" {
return os.Stdout
}
file, ferr := os.Create(rawTestlog)
exitIfError("cannot open "+rawTestlog, ferr)
return file
}
// reportUIRunReady polls the runner's health endpoint over the forward and logs
// once it answers, so users see when `ui run` is usable. Best-effort: the runner
// keeps running regardless.
func reportUIRunReady(healthURL, name, localURL, udid string) {
client := &http.Client{Timeout: 2 * time.Second}
deadline := time.Now().Add(120 * time.Second)
for time.Now().Before(deadline) {
resp, err := client.Get(healthURL)
if err == nil {
_ = resp.Body.Close()
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
slog.Info("ui run ready", "target", name, "url", localURL, "udid", udid)
return
}
}
time.Sleep(time.Second)
}
slog.Warn("ui run: backend not reachable yet; still running", "target", name, "url", localURL)
}