rs2322tcp/internal/client/virtual_port_linux_test.go

348 lines
6 KiB
Go

//go:build linux
/*
* ============================================================================
* Projekt.....: rs2322tcp
* Datei.......: internal/client/virtual_port_linux_test.go
* Copyright (C) 2026 Dieter Lang
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Beschreibung:
* Tests für die Linux-spezifische Verwaltung der internen Symlinks
* virtueller rs2322tcp-Seriellschnittstellen.
* ============================================================================
*/
package client
import (
"os"
"path/filepath"
"reflect"
"testing"
"git.lang-dieter.de/rs2322tcp/internal/config"
)
func TestVirtualPortLinkPath(t *testing.T) {
path, err := virtualPortLinkPath("/dev/ttyUSB100")
if err != nil {
t.Fatalf("virtualPortLinkPath() failed: %v", err)
}
wantSuffix := filepath.Join(
".rs2322tcp",
"virtual",
"ttyUSB100",
)
if !filepath.IsAbs(path) {
t.Fatalf("path is not absolute: %q", path)
}
if !hasPathSuffix(path, wantSuffix) {
t.Errorf(
"virtualPortLinkPath() = %q, want suffix %q",
path,
wantSuffix,
)
}
}
func TestSetAndRemoveVirtualPortLink(t *testing.T) {
tempHome := t.TempDir()
t.Setenv("HOME", tempHome)
portPath := "/dev/ttyUSB100"
target := "/dev/pts/2"
if err := setVirtualPortLink(portPath, target); err != nil {
t.Fatalf("setVirtualPortLink() failed: %v", err)
}
linkPath, err := virtualPortLinkPath(portPath)
if err != nil {
t.Fatalf("virtualPortLinkPath() failed: %v", err)
}
got, err := os.Readlink(linkPath)
if err != nil {
t.Fatalf("Readlink() failed: %v", err)
}
if got != target {
t.Errorf(
"link target = %q, want %q",
got,
target,
)
}
if err := removeVirtualPortLink(portPath); err != nil {
t.Fatalf("removeVirtualPortLink() failed: %v", err)
}
if _, err := os.Lstat(linkPath); !os.IsNotExist(err) {
t.Fatalf(
"link still exists after removal: %q",
linkPath,
)
}
}
func TestSetVirtualPortLinkReplacesExistingLink(t *testing.T) {
tempHome := t.TempDir()
t.Setenv("HOME", tempHome)
portPath := "/dev/ttyUSB100"
if err := setVirtualPortLink(
portPath,
"/dev/pts/2",
); err != nil {
t.Fatalf(
"first setVirtualPortLink() failed: %v",
err,
)
}
if err := setVirtualPortLink(
portPath,
"/dev/pts/7",
); err != nil {
t.Fatalf(
"second setVirtualPortLink() failed: %v",
err,
)
}
linkPath, err := virtualPortLinkPath(portPath)
if err != nil {
t.Fatalf("virtualPortLinkPath() failed: %v", err)
}
got, err := os.Readlink(linkPath)
if err != nil {
t.Fatalf("Readlink() failed: %v", err)
}
if got != "/dev/pts/7" {
t.Errorf(
"link target = %q, want %q",
got,
"/dev/pts/7",
)
}
}
func TestRemoveVirtualPortLinkMissing(t *testing.T) {
tempHome := t.TempDir()
t.Setenv("HOME", tempHome)
if err := removeVirtualPortLink("/dev/ttyUSB100"); err != nil {
t.Fatalf(
"removeVirtualPortLink() failed for missing link: %v",
err,
)
}
}
func TestVirtualPortLinkPathUsesBaseName(t *testing.T) {
path, err := virtualPortLinkPath("/dev/ttyUSB123")
if err != nil {
t.Fatalf("virtualPortLinkPath() failed: %v", err)
}
if filepath.Base(path) != "ttyUSB123" {
t.Errorf(
"filepath.Base() = %q, want %q",
filepath.Base(path),
"ttyUSB123",
)
}
}
func TestExistingVirtualPortLinksNone(t *testing.T) {
tempDir := t.TempDir()
cfg := config.VirtualPortRangeConfig{
First: 100,
Last: 102,
}
got, err := existingVirtualPortLinksInDirectory(
cfg,
tempDir,
)
if err != nil {
t.Fatalf(
"existingVirtualPortLinksInDirectory() failed: %v",
err,
)
}
if len(got) != 0 {
t.Fatalf(
"existingVirtualPortLinksInDirectory() = %v, want empty list",
got,
)
}
}
func TestExistingVirtualPortLinksFindsSymlinks(t *testing.T) {
tempDir := t.TempDir()
cfg := config.VirtualPortRangeConfig{
First: 100,
Last: 102,
}
for _, number := range []int{100, 102} {
path := filepath.Join(
tempDir,
"ttyUSB"+itoa(number),
)
if err := os.Symlink(
"/dev/pts/"+itoa(number),
path,
); err != nil {
t.Fatalf(
"create test symlink %q: %v",
path,
err,
)
}
}
got, err := existingVirtualPortLinksInDirectory(
cfg,
tempDir,
)
if err != nil {
t.Fatalf(
"existingVirtualPortLinksInDirectory() failed: %v",
err,
)
}
want := []string{
filepath.Join(tempDir, "ttyUSB100"),
filepath.Join(tempDir, "ttyUSB102"),
}
if !reflect.DeepEqual(got, want) {
t.Fatalf(
"existingVirtualPortLinksInDirectory() = %v, want %v",
got,
want,
)
}
}
func TestExistingVirtualPortLinksIgnoresRegularFile(t *testing.T) {
tempDir := t.TempDir()
cfg := config.VirtualPortRangeConfig{
First: 100,
Last: 102,
}
path := filepath.Join(tempDir, "ttyUSB100")
if err := os.WriteFile(
path,
[]byte("not a symlink"),
0644,
); err != nil {
t.Fatalf(
"create regular test file %q: %v",
path,
err,
)
}
got, err := existingVirtualPortLinksInDirectory(
cfg,
tempDir,
)
if err != nil {
t.Fatalf(
"existingVirtualPortLinksInDirectory() failed: %v",
err,
)
}
if len(got) != 0 {
t.Fatalf(
"regular file was detected as virtual port: %v",
got,
)
}
}
func TestExistingVirtualPortLinksIgnoresOutsideRange(t *testing.T) {
tempDir := t.TempDir()
cfg := config.VirtualPortRangeConfig{
First: 100,
Last: 102,
}
path := filepath.Join(tempDir, "ttyUSB103")
if err := os.Symlink(
"/dev/pts/103",
path,
); err != nil {
t.Fatalf(
"create outside-range symlink %q: %v",
path,
err,
)
}
got, err := existingVirtualPortLinksInDirectory(
cfg,
tempDir,
)
if err != nil {
t.Fatalf(
"existingVirtualPortLinksInDirectory() failed: %v",
err,
)
}
if len(got) != 0 {
t.Fatalf(
"outside-range symlink was detected: %v",
got,
)
}
}
func itoa(number int) string {
const digits = "0123456789"
if number == 0 {
return "0"
}
result := ""
for number > 0 {
result = string(digits[number%10]) + result
number /= 10
}
return result
}
func hasPathSuffix(path string, suffix string) bool {
return path == suffix ||
len(path) > len(suffix) &&
path[len(path)-len(suffix):] == suffix
}