fix(browser_list): parse cmdline colapsado por espacios de Chromium

/proc/<pid>/cmdline normalmente separa argv por NUL, pero Chromium reescribe
su titulo de proceso in-place colapsando la region de argv a una sola cadena
separada por espacios. readProcCmdline asumia solo NUL, asi que para los
masters de Chromium devolvia un unico argv[0] gigante: isChromiumExe y el
prefijo --user-data-dir= fallaban y browser_list devolvia [] aunque hubiera
navegadores vivos.

Extrae parseCmdline (pura, testeable) con fallback a split por espacios cuando
no hay NUL. Test cubre ambos formatos + regresion de deteccion de master.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Egutierrez
2026-06-16 20:02:27 +02:00
parent 1c5b81f711
commit c2470f4f67
2 changed files with 78 additions and 6 deletions
+45
View File
@@ -150,3 +150,48 @@ func TestMatchMaster(t *testing.T) {
t.Errorf("unknown profile should not match")
}
}
// TestParseCmdline cubre el parsing de /proc/<pid>/cmdline en sus dos formatos:
// el canonico separado por NUL y el colapsado por espacios que produce Chromium
// al reescribir su titulo de proceso in-place. El segundo caso es el que rompia
// browser_list (los flags quedaban dentro de un unico argv[0] gigante).
func TestParseCmdline(t *testing.T) {
// Caso canonico: argv separado por NUL (proceso normal).
nul := []byte("/usr/lib/chromium/chromium\x00--user-data-dir=/tmp/x\x00--remote-debugging-port=9333\x00")
got := parseCmdline(nul)
want := []string{"/usr/lib/chromium/chromium", "--user-data-dir=/tmp/x", "--remote-debugging-port=9333"}
if len(got) != len(want) {
t.Fatalf("NUL: got %v, want %v", got, want)
}
for i := range want {
if got[i] != want[i] {
t.Errorf("NUL[%d]: got %q, want %q", i, got[i], want[i])
}
}
// Caso Chromium: cmdline colapsado a una sola cadena separada por espacios.
collapsed := []byte("/usr/lib/chromium/chromium --remote-debugging-port=9333 --user-data-dir=/tmp/browser_mcp_userdata --no-first-run https://www.alsa.es/")
args := parseCmdline(collapsed)
if len(args) == 1 {
t.Fatalf("space-collapsed: parse devolvio un unico elemento gigante: %q", args[0])
}
if args[0] != "/usr/lib/chromium/chromium" {
t.Errorf("space-collapsed argv[0]: got %q, want chromium binary", args[0])
}
// El master debe detectarse a partir del cmdline colapsado (regresion de browser_list).
m, ok := parseChromiumMaster(18148, args)
if !ok {
t.Fatalf("space-collapsed: parseChromiumMaster no detecto el master")
}
if m.UserDataDir != "/tmp/browser_mcp_userdata" {
t.Errorf("space-collapsed udd: got %q, want /tmp/browser_mcp_userdata", m.UserDataDir)
}
if m.CDPPort != "9333" || !m.HasCDP {
t.Errorf("space-collapsed cdp: got port=%q hasCDP=%v, want 9333/true", m.CDPPort, m.HasCDP)
}
if parseCmdline([]byte("")) != nil || parseCmdline([]byte("\x00\x00")) != nil {
t.Errorf("cmdline vacio debe devolver nil")
}
}