// Copyright (c) 2026 Storm Dragon // SPDX-License-Identifier: GPL-3.0-or-later //go:build linux && cgo package terminal import ( "errors" "os" "os/exec" "slices" "strings" "syscall" "testing" "time" "git.stormux.org/storm/fenrir/internal/applicationoverrides/networkmanager" "git.stormux.org/storm/fenrir/internal/screen" ) // Opt-in read-only application check: open nmtui's main menu, move its // selection, and exit. No connection, hostname or network setting is changed. func TestNetworkManagerOverrideNative(t *testing.T) { if os.Getenv("FENRIR_APPLICATION_OVERRIDE_NATIVE") != "1" { t.Skip("set FENRIR_APPLICATION_OVERRIDE_NATIVE=1 for private nmtui/Screen PTY checks") } nmtui, err := exec.LookPath("nmtui") if err != nil { t.Skip("nmtui unavailable") } for _, multiplexed := range []bool{false, true} { name := "direct" if multiplexed { name = "screen" } t.Run(name, func(t *testing.T) { env := append(os.Environ(), "TERM=xterm-256color", "LANG=C", "LC_ALL=C", "NEWT_COLORS=") cmd := exec.Command(nmtui) if multiplexed { path, err := exec.LookPath("screen") if err != nil { t.Skip("GNU Screen unavailable") } socketDirectory := t.TempDir() if err := os.Chmod(socketDirectory, 0700); err != nil { t.Fatal(err) } env = append(env, "SCREENDIR="+socketDirectory) cmd = exec.Command(path, "-c", "/dev/null", "-m", "-S", "fenrir-override-test", nmtui) defer func() { quit := exec.Command(path, "-S", "fenrir-override-test", "-X", "quit") quit.Env = env _ = quit.Run() }() } master, slave, err := open_pty(24, 100) if err != nil { t.Fatal(err) } defer syscall.Close(master) slaveFile := os.NewFile(uintptr(slave), "private nmtui PTY") defer slaveFile.Close() cmd.Stdin, cmd.Stdout, cmd.Stderr = slaveFile, slaveFile, slaveFile cmd.Env = env cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true, Setctty: true, Ctty: 0} if err := cmd.Start(); err != nil { t.Fatal(err) } defer func() { _ = cmd.Process.Kill(); _ = cmd.Wait() }() if err := syscall.SetNonblock(master, true); err != nil { t.Fatal(err) } emulator, err := new_emulator(24, 100) if err != nil { t.Fatal(err) } defer emulator.close() adapter := &networkmanager.Adapter{} var before []screen.Attribute waitForMenu := func() { t.Helper() deadline := time.Now().Add(4 * time.Second) var buffer [32768]byte for time.Now().Before(deadline) { n, err := syscall.Read(master, buffer[:]) if n > 0 { emulator.feed(buffer[:n]) } if err != nil && !errors.Is(err, syscall.EAGAIN) && !errors.Is(err, syscall.EINTR) { t.Fatalf("nmtui PTY: %v; screen: %s", err, string(emulator.snapshot(true, false).Cells)) } snapshot := emulator.snapshot(true, false) if adapter.Observe(snapshot).Recognized && (before == nil || !slices.Equal(before, snapshot.Attributes)) { return } time.Sleep(10 * time.Millisecond) } t.Fatalf("nmtui menu not recognized: %s", strings.TrimSpace(string(emulator.snapshot(true, false).Cells))) } waitForMenu() before = slices.Clone(emulator.snapshot(true, false).Attributes) // GNU Screen and xterm both forward the normal arrow sequence. if _, err := syscall.Write(master, []byte("\x1bOB")); err != nil { t.Fatal(err) } waitForMenu() if _, err := syscall.Write(master, []byte("\x1b")); err != nil { t.Fatal(err) } }) } }