Files
fenrir/internal/console/discovery_linux_test.go

118 lines
3.3 KiB
Go

// Fenrir console screen reader.
// Copyright (c) 2026 Storm Dragon
// SPDX-License-Identifier: GPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free
// Software Foundation, either version 3 of the License, or (at your option)
// any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along with
// this program. If not, see <https://www.gnu.org/licenses/gpl-3.0.html>.
//go:build linux && cgo
package console
import (
"fmt"
"os"
"path/filepath"
"testing"
)
func BenchmarkCachedDeviceScan(b *testing.B) {
root := b.TempDir()
k := &Keyboards{rejected: make(map[string]deviceIdentity)}
var paths []string
for i := range 20 {
path := filepath.Join(root, fmt.Sprintf("event%d", i))
if err := os.WriteFile(path, nil, 0600); err != nil {
b.Fatal(err)
}
identity, err := device_identity(path)
if err != nil {
b.Fatal(err)
}
k.rejected[path] = identity
paths = append(paths, path)
}
b.ReportAllocs()
b.ResetTimer()
for b.Loop() {
if err := k.scan_paths(paths); err != nil {
b.Fatal(err)
}
}
}
func TestScanCachesRejectionsAndRetriesReplacement(t *testing.T) {
path := filepath.Join(t.TempDir(), "event0")
if err := os.WriteFile(path, nil, 0600); err != nil {
t.Fatal(err)
}
identity, err := device_identity(path)
if err != nil {
t.Fatal(err)
}
k := &Keyboards{rejected: map[string]deviceIdentity{path: identity}}
// A cached non-keyboard must not be reopened. This regular file would
// fail libevdev initialization and lose its cache entry if probed.
for range 3 {
if err := k.scan_paths([]string{path}); err != nil {
t.Fatal(err)
}
if _, ok := k.rejected[path]; !ok {
t.Fatal("reopened an unchanged rejected device")
}
}
// Keep the old inode alive so this also exercises a reused event path.
if err := os.Rename(path, path+".old"); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(path, nil, 0600); err != nil {
t.Fatal(err)
}
// Explicit paths expose the open error, proving a new node was probed.
k.paths = []string{path}
if err := k.Scan(); err == nil {
t.Fatal("replacement was skipped")
}
if len(k.rejected) != 0 {
t.Fatal("cached a failed open")
}
if err := k.Scan(); err == nil {
t.Fatal("failed open was not retried")
}
}
func TestScanInvalidatesMissingAndChangedNodes(t *testing.T) {
path := filepath.Join(t.TempDir(), "event0")
if err := os.WriteFile(path, nil, 0600); err != nil {
t.Fatal(err)
}
identity, err := device_identity(path)
if err != nil {
t.Fatal(err)
}
// ctime changes must invalidate even when device and inode stay equal.
stale := identity
stale.changed.Sec--
k := &Keyboards{paths: []string{path}, rejected: map[string]deviceIdentity{path: stale}}
if err := k.Scan(); err == nil {
t.Fatal("changed node was not probed")
}
k.rejected[path] = identity
if err := k.scan_paths(nil); err != nil {
t.Fatal(err)
}
if len(k.rejected) != 0 {
t.Fatal("retained disappeared node")
}
}