@tapflowio/ios-agent 0.27.1 → 0.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,13 +7,15 @@ import XCTest
7
7
  // (no Simulator.app window required — the tapflow headless path).
8
8
  //
9
9
  // Routes:
10
- // GET /health → "ok" (readiness probe)
10
+ // GET /health → the token the host passed, or "ok" without one (readiness probe)
11
11
  // GET /tree?bundleId=<id> → the app's XCUIApplication.debugDescription (text)
12
12
  final class TreeServer {
13
13
  private let listener: NWListener
14
14
  private let queue = DispatchQueue(label: "dev.tapflow.treeserver")
15
+ private let token: String
15
16
 
16
- init(port: UInt16) throws {
17
+ init(port: UInt16, token: String) throws {
18
+ self.token = token
17
19
  let params = NWParameters.tcp
18
20
  params.allowLocalEndpointReuse = true
19
21
  guard let nwPort = NWEndpoint.Port(rawValue: port) else {
@@ -33,13 +35,23 @@ final class TreeServer {
33
35
  conn.start(queue: self.queue)
34
36
  self.receive(conn, buffer: Data())
35
37
  }
36
- // Surface a bind failure (e.g. port held by a stale runner) immediately by
38
+ // Surface a bind failure immediately by
37
39
  // exiting — otherwise the process stays alive looking "ready" and the host
38
40
  // only fails after the full 90s /health poll.
39
- listener.stateUpdateHandler = { state in
40
- if case .failed(let error) = state {
41
+ listener.stateUpdateHandler = { [weak self] state in
42
+ switch state {
43
+ case .ready:
44
+ // Announced only once bound, with the port the kernel gave (the host passes 0), because the host
45
+ // talks to nothing but the port this line names. Printed before the bind, as it once was, a runner
46
+ // that lost its port still announced it, and the host then read another simulator's tree from
47
+ // whoever held it.
48
+ print("TAPFLOW_TREE_READY port=\(self?.listener.port?.rawValue ?? 0)")
49
+ fflush(stdout)
50
+ case .failed(let error):
41
51
  FileHandle.standardError.write(Data("tree server failed to bind: \(error)\n".utf8))
42
52
  exit(1)
53
+ default:
54
+ break
43
55
  }
44
56
  }
45
57
  listener.start(queue: queue)
@@ -66,14 +78,15 @@ final class TreeServer {
66
78
  let path = parts.count >= 2 ? String(parts[1]) : "/"
67
79
  // XCUITest snapshot access must run on the main thread.
68
80
  DispatchQueue.main.async {
69
- let (status, body) = Self.route(path)
81
+ let (status, body) = Self.route(path, token: self.token)
70
82
  self.respond(conn, status: status, body: body)
71
83
  }
72
84
  }
73
85
 
74
- private static func route(_ path: String) -> (String, String) {
86
+ private static func route(_ path: String, token: String) -> (String, String) {
75
87
  if path.hasPrefix("/health") {
76
- return ("200 OK", "ok")
88
+ // The token tells the host this is the runner it started, not one another agent left on the port.
89
+ return ("200 OK", token.isEmpty ? "ok" : token)
77
90
  }
78
91
  if path.hasPrefix("/tree") {
79
92
  guard let bundleId = query(path, "bundleId"), !bundleId.isEmpty else {
@@ -2,18 +2,21 @@ import XCTest
2
2
 
3
3
  // Resident runner: starts the HTTP tree server, then blocks so the process stays
4
4
  // alive serving queries until the host terminates it (session end / shutdown).
5
- // The host detects readiness by polling GET /health on the chosen port.
5
+ // The host learns the port from the TAPFLOW_TREE_READY line and confirms it with GET /health.
6
6
  final class TreeServerTest: XCTestCase {
7
7
  func testServeTree() throws {
8
- let portStr = ProcessInfo.processInfo.environment["TAPFLOW_TREE_PORT"] ?? "22087"
8
+ // The host passes these as TEST_RUNNER_TAPFLOW_TREE_*: xcodebuild strips the prefix on the way in. Port 0
9
+ // lets the kernel pick, and the server announces what it got.
10
+ let env = ProcessInfo.processInfo.environment
11
+ let portStr = env["TAPFLOW_TREE_PORT"] ?? "22087"
9
12
  guard let port = UInt16(portStr) else {
10
13
  XCTFail("invalid TAPFLOW_TREE_PORT: \(portStr)")
11
14
  return
12
15
  }
13
- let server = try TreeServer(port: port)
16
+ let server = try TreeServer(port: port, token: env["TAPFLOW_TREE_TOKEN"] ?? "")
14
17
  server.start()
15
- print("TAPFLOW_TREE_READY port=\(port)")
16
- // Block forever — the host kills the process to stop the runner.
17
- RunLoop.current.run()
18
+ // Block forever — the host kills the process to stop the runner. The server's handlers hold it weakly, so
19
+ // keep it alive here explicitly rather than trusting the local to outlive its last use.
20
+ withExtendedLifetime(server) { RunLoop.current.run() }
18
21
  }
19
22
  }