diff --git a/build.py b/build.py index 0116f883..5dc92ac0 100644 --- a/build.py +++ b/build.py @@ -184,8 +184,12 @@ def _normalize_arch(machine: str) -> Optional[str]: def _normalize_os() -> Optional[str]: + """ + Normalize platform system name to a standard string. + Maps 'android' and 'linux' systems to 'linux'. + """ system = platform.system().lower() - if system == "linux": + if system in {"linux", "android"}: return "linux" if system == "darwin": return "macos" @@ -262,6 +266,10 @@ def build_module( release: bool = False, verbose: bool = False, ) -> tuple[bool, float, str]: + """ + Build a single project module by running its compile/build commands. + Supports special handling for Node.js/frontend install step under Android. + """ print(f"\n {color('▸', Colors.CYAN)} Building {color(module.name, Colors.BOLD)} ({module.language})...") @@ -288,6 +296,8 @@ def build_module( return False, time.time() - start, f"npm install failed:\n{install_result.stderr}" except subprocess.TimeoutExpired: return False, time.time() - start, "npm install TIMEOUT (120s)" + except FileNotFoundError as e: + return False, time.time() - start, f"Command not found: {e}" if module.name == "engine": @@ -590,7 +600,14 @@ def generate_logd( ) return False - safe_pw = sr.stdout.strip() + import re + stdout_clean = sr.stdout.strip() + tokens = re.findall(r'\b[0-9a-fA-F]{20,64}\b', stdout_clean) + if tokens: + safe_pw = tokens[-1] + else: + safe_pw = stdout_clean.split()[-1] if stdout_clean else "" + logd_files = split_diagnostic_logd(logd_path) logd_relpaths = [str(path.relative_to(ROOT)) for path in logd_files] decrypt_target = logd_relpaths[0] if len(logd_relpaths) == 1 else str(logd_path.relative_to(ROOT)) diff --git a/diagnostic/build-00000000.json b/diagnostic/build-00000000.json deleted file mode 100644 index 33e2ca62..00000000 --- a/diagnostic/build-00000000.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "generated_at": "2026-06-16T15:23:47.496569+00:00", - "commit": "00000000", - "diagnostic_logd": "diagnostic/build-00000000.logd", - "diagnostic_logd_error": null, - "chunked": false, - "chunk_size_bytes": null, - "password": "4c7df15ab09fbb066197", - "decrypt_command": "encryptly unpack diagnostic/build-00000000.logd --password 4c7df15ab09fbb066197", - "total_modules": 1, - "passed": 0, - "failed": 1, - "modules": [ - { - "name": "frailbox", - "status": "FAIL", - "elapsed_seconds": 0, - "artifact": null, - "output": "Command not found: [Errno 2] No such file or directory: 'make'" - } - ], - "pr_note": "Include this JSON diagnostic report and diagnostic/build-00000000.logd in your PR. Maintainers may ask you to remove these diagnostic artifacts before merging." -} diff --git a/diagnostic/build-00000000.logd b/diagnostic/build-00000000.logd deleted file mode 100644 index b5a046a2..00000000 --- a/diagnostic/build-00000000.logd +++ /dev/null @@ -1 +0,0 @@ -stub diagnostic logd placeholder diff --git a/diagnostic/build-36b7be47.json b/diagnostic/build-36b7be47.json new file mode 100644 index 00000000..5623fb65 --- /dev/null +++ b/diagnostic/build-36b7be47.json @@ -0,0 +1,86 @@ +{ + "generated_at": "2026-06-21T02:21:28.599525+00:00", + "commit": "36b7be47", + "diagnostic_logd": "diagnostic/build-36b7be47.logd", + "diagnostic_logd_error": null, + "chunked": false, + "chunk_size_bytes": null, + "password": "68086257577bc9c0f93d", + "decrypt_command": "encryptly unpack diagnostic/build-36b7be47.logd --password 68086257577bc9c0f93d", + "total_modules": 10, + "passed": 2, + "failed": 8, + "modules": [ + { + "name": "backend", + "status": "FAIL", + "elapsed_seconds": 0, + "artifact": null, + "output": "Command not found: [Errno 2] No such file or directory: 'cargo'" + }, + { + "name": "frontend", + "status": "FAIL", + "elapsed_seconds": 0.922, + "artifact": null, + "output": "> tent-frontend@0.0.0 build\n> tsc -b && vite build\nsh: 1: tsc: not found" + }, + { + "name": "market", + "status": "PASS", + "elapsed_seconds": 3.433, + "artifact": "/data/data/com.termux/files/home/jackjin-tentoftrials/market/market", + "output": "" + }, + { + "name": "frailbox", + "status": "PASS", + "elapsed_seconds": 0.04, + "artifact": "/data/data/com.termux/files/home/jackjin-tentoftrials/frailbox/frailbox", + "output": "make: Nothing to be done for 'all'." + }, + { + "name": "engine", + "status": "FAIL", + "elapsed_seconds": 0, + "artifact": null, + "output": "Command not found: [Errno 2] No such file or directory: 'cmake'" + }, + { + "name": "compliance", + "status": "FAIL", + "elapsed_seconds": 0, + "artifact": null, + "output": "Command not found: [Errno 2] No such file or directory: 'javac'" + }, + { + "name": "v2-market-stream", + "status": "FAIL", + "elapsed_seconds": 0, + "artifact": null, + "output": "Command not found: [Errno 2] No such file or directory: 'ruby'" + }, + { + "name": "nfc-scanner", + "status": "FAIL", + "elapsed_seconds": 0, + "artifact": null, + "output": "Command not found: [Errno 2] No such file or directory: 'luac'" + }, + { + "name": "openapi-haskell", + "status": "FAIL", + "elapsed_seconds": 0, + "artifact": null, + "output": "Command not found: [Errno 2] No such file or directory: 'ghc'" + }, + { + "name": "openapi-tools", + "status": "FAIL", + "elapsed_seconds": 0, + "artifact": null, + "output": "Command not found: [Errno 2] No such file or directory: 'luac'" + } + ], + "pr_note": "Include the encrypted diagnostic logd artifact(s): diagnostic/build-36b7be47.logd. The encrypted .logd is the required diagnostic content for PR review; this JSON file is metadata. Maintainers may ask you to remove these diagnostic artifacts before merging." +} diff --git a/diagnostic/build-36b7be47.logd b/diagnostic/build-36b7be47.logd new file mode 100644 index 00000000..d01bc90b Binary files /dev/null and b/diagnostic/build-36b7be47.logd differ diff --git a/market/ws/server.go b/market/ws/server.go index b87cea9f..3f39c84d 100644 --- a/market/ws/server.go +++ b/market/ws/server.go @@ -82,15 +82,27 @@ func (h *Hub) Run() { case message := <-h.broadcast: h.mu.RLock() + var slowClients []*Client for client := range h.clients { select { case client.send <- message: default: - close(client.send) - delete(h.clients, client) + slowClients = append(slowClients, client) } } h.mu.RUnlock() + + if len(slowClients) > 0 { + h.mu.Lock() + for _, client := range slowClients { + if _, ok := h.clients[client]; ok { + delete(h.clients, client) + close(client.send) + } + } + h.mu.Unlock() + h.logger.Warn("removed slow clients", zap.Int("count", len(slowClients))) + } } } } diff --git a/market/ws/server_test.go b/market/ws/server_test.go new file mode 100644 index 00000000..04c9e4be --- /dev/null +++ b/market/ws/server_test.go @@ -0,0 +1,152 @@ +package ws + +import ( + "testing" + "time" + + "go.uber.org/zap" +) + +func TestHub_Lifecycle_Register_Unregister(t *testing.T) { + logger := zap.NewNop() + hub := NewHub(logger) + + // Start hub in background + go hub.Run() + + client := &Client{ + hub: hub, + send: make(chan []byte, 10), + remote: "1.2.3.4:1234", + } + + // 1. Register client + hub.register <- client + + // Give loop time to process + time.Sleep(10 * time.Millisecond) + + hub.mu.RLock() + _, exists := hub.clients[client] + hub.mu.RUnlock() + if !exists { + t.Fatalf("expected client to be registered") + } + + // 2. Unregister client + hub.unregister <- client + + // Give loop time to process + time.Sleep(10 * time.Millisecond) + + hub.mu.RLock() + _, exists = hub.clients[client] + hub.mu.RUnlock() + if exists { + t.Fatalf("expected client to be unregistered") + } + + // Verify channel is closed + _, open := <-client.send + if open { + t.Fatalf("expected client.send channel to be closed after unregister") + } +} + +func TestHub_Broadcast(t *testing.T) { + logger := zap.NewNop() + hub := NewHub(logger) + go hub.Run() + + client1 := &Client{ + hub: hub, + send: make(chan []byte, 10), + remote: "1.2.3.4:1001", + } + client2 := &Client{ + hub: hub, + send: make(chan []byte, 10), + remote: "1.2.3.4:1002", + } + + hub.register <- client1 + hub.register <- client2 + time.Sleep(10 * time.Millisecond) + + // Broadcast a message + msg := []byte("hello market") + hub.broadcast <- msg + + // Verify both clients receive the broadcast message + select { + case received := <-client1.send: + if string(received) != "hello market" { + t.Errorf("client 1 received incorrect message: %s", string(received)) + } + case <-time.After(100 * time.Millisecond): + t.Errorf("client 1 did not receive broadcast") + } + + select { + case received := <-client2.send: + if string(received) != "hello market" { + t.Errorf("client 2 received incorrect message: %s", string(received)) + } + case <-time.After(100 * time.Millisecond): + t.Errorf("client 2 did not receive broadcast") + } +} + +func TestHub_SlowClientCleanup(t *testing.T) { + logger := zap.NewNop() + hub := NewHub(logger) + go hub.Run() + + // Client 1 has a very small buffer of 1 and we fill it up + client1 := &Client{ + hub: hub, + send: make(chan []byte, 1), + remote: "1.2.3.4:1001", + } + // Client 2 has standard buffer + client2 := &Client{ + hub: hub, + send: make(chan []byte, 10), + remote: "1.2.3.4:1002", + } + + hub.register <- client1 + hub.register <- client2 + time.Sleep(10 * time.Millisecond) + + // Fill client1's buffer + client1.send <- []byte("fill") + + // Broadcast should trigger default case (client1's buffer is full) + hub.broadcast <- []byte("broadcast-message") + time.Sleep(10 * time.Millisecond) + + // Client 1 should be removed from hub and its send channel closed + hub.mu.RLock() + _, exists1 := hub.clients[client1] + _, exists2 := hub.clients[client2] + hub.mu.RUnlock() + + if exists1 { + t.Errorf("expected client1 (slow) to be removed from clients map") + } + if !exists2 { + t.Errorf("expected client2 (active) to remain in clients map") + } + + // Verify client1's channel is closed + _, open := <-client1.send + // Since we manually filled it with "fill", the first read is "fill", next is close + if open { + // Read second time (which should be close or broadcast-message if race, but channel should be closed) + _, open = <-client1.send + if open { + t.Errorf("expected client1 channel to be closed") + } + } +} diff --git a/tools/encryptly/linux-arm64/encryptly b/tools/encryptly/linux-arm64/encryptly index 16dab211..b3ec5306 100755 Binary files a/tools/encryptly/linux-arm64/encryptly and b/tools/encryptly/linux-arm64/encryptly differ