Files
fenrir/internal/terminal/application_override_native_linux_test.go

114 lines
3.4 KiB
Go

// 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)
}
})
}
}