|
3 | 3 | import os |
4 | 4 | import subprocess |
5 | 5 | import socket |
| 6 | +import re |
6 | 7 | from pathlib import Path |
7 | 8 |
|
8 | 9 | from selenium import webdriver |
|
24 | 25 | class MacTest(UnityTest): |
25 | 26 |
|
26 | 27 | altdriver = None |
| 28 | + _altdriver_host = None |
27 | 29 |
|
28 | 30 | @staticmethod |
29 | 31 | def _wait_for_tcp_port(host: str, port: int, *, timeout_seconds: float, poll_seconds: float = 1.0) -> bool: |
@@ -73,28 +75,115 @@ def _dump_player_log_tail(): |
73 | 75 | print(f"Failed to read Player.log at {path}: {e}") |
74 | 76 | print("Player.log not found in known locations.") |
75 | 77 |
|
| 78 | + @staticmethod |
| 79 | + def _dump_alttester_server_log_tail(): |
| 80 | + """ |
| 81 | + Best-effort dump of AltTester server log when connection fails. |
| 82 | + The Player.log usually prints where this file is stored. |
| 83 | + """ |
| 84 | + product = os.getenv("UNITY_APP_NAME", "SampleApp") |
| 85 | + candidates = [ |
| 86 | + os.path.expanduser(f"~/Library/Application Support/Immutable/{product}/AltTester-Server.log"), |
| 87 | + os.path.expanduser("~/Library/Application Support/Immutable/Sample Unity 6 macOS/AltTester-Server.log"), |
| 88 | + os.path.expanduser("~/Library/Application Support/Immutable/SampleApp/AltTester-Server.log"), |
| 89 | + ] |
| 90 | + seen = set() |
| 91 | + for path in candidates: |
| 92 | + if not path or path in seen: |
| 93 | + continue |
| 94 | + seen.add(path) |
| 95 | + try: |
| 96 | + if not os.path.exists(path): |
| 97 | + continue |
| 98 | + print(f"----- AltTester-Server.log tail ({path}) -----") |
| 99 | + with open(path, "r", encoding="utf-8", errors="ignore") as f: |
| 100 | + lines = f.read().splitlines() |
| 101 | + tail = lines[-250:] if len(lines) > 250 else lines |
| 102 | + for line in tail: |
| 103 | + print(line) |
| 104 | + print(f"----- end AltTester-Server.log tail ({path}) -----") |
| 105 | + return |
| 106 | + except Exception as e: |
| 107 | + print(f"Failed to read AltTester-Server.log at {path}: {e}") |
| 108 | + print("AltTester-Server.log not found in known locations.") |
| 109 | + |
76 | 110 | @classmethod |
77 | | - def setUpClass(cls): |
78 | | - open_sample_app() |
79 | | - # Avoid 20-30 minute hangs: first wait for the AltTester server port to open, |
80 | | - # then attempt a small number of AltDriver connections. |
81 | | - if not cls._wait_for_tcp_port("127.0.0.1", 13000, timeout_seconds=90, poll_seconds=1.0): |
82 | | - cls._dump_player_log_tail() |
83 | | - raise SystemExit("AltTester server port 13000 never opened on macOS.") |
| 111 | + def _get_candidate_altdriver_hosts(cls): |
| 112 | + """ |
| 113 | + AltTester on macOS CI sometimes doesn't bind to 127.0.0.1. |
| 114 | + Parse Player.log for host interface IPs (Unity prints them early), |
| 115 | + and try those as connection hosts. |
| 116 | + """ |
| 117 | + hosts = ["127.0.0.1", "localhost"] |
84 | 118 |
|
85 | | - last_err = None |
86 | | - for attempt in range(3): |
| 119 | + candidates = [ |
| 120 | + os.path.expanduser("~/Library/Logs/Unity/Player.log"), |
| 121 | + os.path.expanduser(f"~/Library/Logs/Immutable/{os.getenv('UNITY_APP_NAME', 'SampleApp')}/Player.log"), |
| 122 | + os.path.expanduser("~/Library/Logs/Immutable/Sample Unity 6 macOS/Player.log"), |
| 123 | + ] |
| 124 | + seen = set() |
| 125 | + for path in candidates: |
| 126 | + if not path or path in seen: |
| 127 | + continue |
| 128 | + seen.add(path) |
87 | 129 | try: |
88 | | - cls.altdriver = AltDriver(timeout=120) |
89 | | - last_err = None |
90 | | - break |
91 | | - except Exception as e: |
92 | | - last_err = e |
93 | | - print(f"AltDriver connect attempt {attempt + 1}/3 failed: {e}") |
94 | | - time.sleep(2) |
95 | | - if last_err is not None: |
96 | | - cls._dump_player_log_tail() |
97 | | - raise SystemExit(f"Failed to connect AltDriver on macOS: {last_err}") |
| 130 | + if not os.path.exists(path): |
| 131 | + continue |
| 132 | + with open(path, "r", encoding="utf-8", errors="ignore") as f: |
| 133 | + text = f.read() |
| 134 | + # Example line: |
| 135 | + # "Found 2 interfaces on host : 0) 172.16.200.1 1) 169.254.90.9" |
| 136 | + ips = re.findall(r"\b(?:\d{1,3}\.){3}\d{1,3}\b", text) |
| 137 | + for ip in ips: |
| 138 | + if ip not in hosts: |
| 139 | + hosts.append(ip) |
| 140 | + except Exception: |
| 141 | + pass |
| 142 | + |
| 143 | + # De-dupe while preserving order |
| 144 | + deduped = [] |
| 145 | + for h in hosts: |
| 146 | + if h not in deduped: |
| 147 | + deduped.append(h) |
| 148 | + return deduped |
| 149 | + |
| 150 | + @classmethod |
| 151 | + def _connect_altdriver(cls): |
| 152 | + """ |
| 153 | + Connect to AltTester server using a resilient host discovery strategy. |
| 154 | + """ |
| 155 | + if cls._altdriver_host: |
| 156 | + # Fast path if we already found a working host. |
| 157 | + return AltDriver(cls._altdriver_host, 13000, timeout=120) |
| 158 | + |
| 159 | + hosts = cls._get_candidate_altdriver_hosts() |
| 160 | + print(f"AltDriver candidate hosts: {hosts}") |
| 161 | + |
| 162 | + # Total budget for waiting for the port to appear across all hosts. |
| 163 | + deadline = time.time() + 90 |
| 164 | + last_host_tried = None |
| 165 | + |
| 166 | + while time.time() < deadline: |
| 167 | + for host in hosts: |
| 168 | + last_host_tried = host |
| 169 | + if cls._wait_for_tcp_port(host, 13000, timeout_seconds=3, poll_seconds=1.0): |
| 170 | + try: |
| 171 | + driver = AltDriver(host, 13000, timeout=120) |
| 172 | + cls._altdriver_host = host |
| 173 | + print(f"AltDriver connected via host: {host}") |
| 174 | + return driver |
| 175 | + except Exception as e: |
| 176 | + print(f"AltDriver connect failed on host {host}: {e}") |
| 177 | + time.sleep(1) |
| 178 | + |
| 179 | + cls._dump_player_log_tail() |
| 180 | + cls._dump_alttester_server_log_tail() |
| 181 | + raise SystemExit(f"AltTester server port 13000 never opened on macOS (last host tried: {last_host_tried}).") |
| 182 | + |
| 183 | + @classmethod |
| 184 | + def setUpClass(cls): |
| 185 | + open_sample_app() |
| 186 | + cls.altdriver = cls._connect_altdriver() |
98 | 187 | cls.stop_browser() |
99 | 188 |
|
100 | 189 | @classmethod |
|
0 commit comments