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