118 lines
3.3 KiB
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")
|
|
}
|
|
}
|