After commit c018f5fd29 Fenrir is being switched over to go. If you do not want to be part of the testing process, please switch to master.
This commit is contained in:
1 parent
c018f5fd29
commit
0e50de4c76
1047 files changed
+58364
-63747
No files matched your search
@@ -0,0 +1,227 @@
|
||||
// Copyright (c) 2026 Storm Dragon
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package clipboard
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestPersistentHistoryRoundTripResizeAndPrivateModes(t *testing.T) {
|
||||
path := filepath.Join(t.TempDir(), "private", "terminal.json")
|
||||
history := New_history(3)
|
||||
for _, text := range []string{"old", "é界\nsecond line", "🎃"} {
|
||||
if err := history.Add(text); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
history.Move(1)
|
||||
store := New_persistence(path, false)
|
||||
store.Enable(history.Snapshot(), 3)
|
||||
if err := store.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for file, mode := range map[string]os.FileMode{path: 0600, filepath.Dir(path): 0700} {
|
||||
info, err := os.Stat(file)
|
||||
if err != nil || info.Mode().Perm() != mode {
|
||||
t.Fatal(info, err)
|
||||
}
|
||||
}
|
||||
store = New_persistence(path, true)
|
||||
if store.LoadErr != nil {
|
||||
t.Fatal(store.LoadErr)
|
||||
}
|
||||
restored := New_history(2)
|
||||
if err := restored.Restore(store.Initial); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got, _ := restored.Current(); got != "é界\nsecond line" || !restored.At_last() {
|
||||
t.Fatal(got)
|
||||
}
|
||||
if err := store.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
restored.Resize(1)
|
||||
if got, _ := restored.Current(); got != "🎃" {
|
||||
t.Fatal(got)
|
||||
}
|
||||
restored.Resize(4)
|
||||
restored.Add("new")
|
||||
if !reflect.DeepEqual(restored.Snapshot().Entries, []string{"new", "🎃"}) {
|
||||
t.Fatal(restored.Snapshot())
|
||||
}
|
||||
}
|
||||
|
||||
func TestPersistenceDisabledDoesNotReadOrCreateAndModesAreSeparate(t *testing.T) {
|
||||
home := t.TempDir()
|
||||
terminalPath := History_path(home, true)
|
||||
if !strings.HasPrefix(terminalPath, home+string(os.PathSeparator)) || History_path(home, false) != "/var/lib/fenrir/clipboard/tty.json" || terminalPath == History_path(home, false) {
|
||||
t.Fatal(terminalPath, History_path(home, false))
|
||||
}
|
||||
store := New_persistence(terminalPath, false)
|
||||
if store.LoadErr != nil || len(store.Initial.Entries) != 0 {
|
||||
t.Fatal(store)
|
||||
}
|
||||
if err := store.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := os.Stat(filepath.Dir(terminalPath)); !errors.Is(err, os.ErrNotExist) {
|
||||
t.Fatal("disabled persistence created state", err)
|
||||
}
|
||||
// A pre-existing malformed file is also ignored while disabled.
|
||||
path := filepath.Join(home, "malformed.json")
|
||||
os.WriteFile(path, []byte("not json"), 0600)
|
||||
store = New_persistence(path, false)
|
||||
if store.LoadErr != nil {
|
||||
t.Fatal("disabled persistence read a file", store.LoadErr)
|
||||
}
|
||||
store.Close()
|
||||
}
|
||||
|
||||
func TestPersistenceClearWinsOverQueuedWritesAndQuitFlushes(t *testing.T) {
|
||||
path := filepath.Join(t.TempDir(), "private", "clipboard.json")
|
||||
state := Snapshot{Version: 1, Entries: []string{"first"}}
|
||||
store := New_persistence(path, false)
|
||||
store.Enable(state, 200)
|
||||
deadline := time.Now().Add(time.Second)
|
||||
for {
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
break
|
||||
}
|
||||
if time.Now().After(deadline) {
|
||||
t.Fatal("initial write did not finish")
|
||||
}
|
||||
time.Sleep(time.Millisecond)
|
||||
}
|
||||
for range 10 {
|
||||
store.Add(strings.Repeat("x", 1<<20), 200)
|
||||
}
|
||||
store.Clear()
|
||||
if err := store.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := os.Stat(path); !errors.Is(err, os.ErrNotExist) {
|
||||
t.Fatal("old write resurrected cleared history", err)
|
||||
}
|
||||
store = New_persistence(path, false)
|
||||
store.Enable(state, 200)
|
||||
if err := store.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got, err := load_history(path)
|
||||
if err != nil || !reflect.DeepEqual(got, state) {
|
||||
t.Fatal("quit did not flush newest state", got, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPersistenceRejectsCorruptUnsafeAndUnboundedFiles(t *testing.T) {
|
||||
root := filepath.Join(t.TempDir(), "private")
|
||||
if err := os.Mkdir(root, 0700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
path := filepath.Join(root, "history.json")
|
||||
for _, data := range [][]byte{
|
||||
[]byte("not JSON"), []byte{0xff},
|
||||
[]byte(`{"version":2,"entries":[],"selected":0}`),
|
||||
[]byte(`{"version":1,"entries":["word"],"selected":2}`),
|
||||
[]byte(`{"version":1,"entries":[],"selected":-1}`),
|
||||
} {
|
||||
if err := os.WriteFile(path, data, 0600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := load_history(path); err == nil {
|
||||
t.Fatal("invalid snapshot accepted")
|
||||
}
|
||||
}
|
||||
state := Snapshot{Version: 1, Entries: []string{"word"}}
|
||||
if err := save_history(path, state); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.Chmod(path, 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := load_history(path); err == nil {
|
||||
t.Fatal("public file accepted")
|
||||
}
|
||||
os.Chmod(path, 0600)
|
||||
file, err := os.OpenFile(path, os.O_WRONLY, 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
file.Truncate(maxHistoryBytes + 1)
|
||||
file.Close()
|
||||
if _, err := load_history(path); err == nil {
|
||||
t.Fatal("oversized file accepted")
|
||||
}
|
||||
if err := save_history(path, state); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
before, _ := os.ReadFile(path)
|
||||
if err := save_history(path, Snapshot{Version: 1, Entries: []string{strings.Repeat("x", MaxBytes+1)}}); err == nil {
|
||||
t.Fatal("oversized entry saved")
|
||||
}
|
||||
after, _ := os.ReadFile(path)
|
||||
if string(before) != string(after) {
|
||||
t.Fatal("rejected save replaced history")
|
||||
}
|
||||
link := filepath.Join(root, "link.json")
|
||||
if err := os.Symlink(path, link); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := load_history(link); err == nil {
|
||||
t.Fatal("symlink read")
|
||||
}
|
||||
if err := save_history(link, state); err == nil {
|
||||
t.Fatal("symlink replaced")
|
||||
}
|
||||
fifo := filepath.Join(root, "fifo")
|
||||
if err := syscall.Mkfifo(fifo, 0600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := load_history(fifo); err == nil {
|
||||
t.Fatal("FIFO accepted")
|
||||
}
|
||||
public := filepath.Join(root, "public")
|
||||
os.Mkdir(public, 0755)
|
||||
if err := save_history(filepath.Join(public, "history.json"), state); err == nil {
|
||||
t.Fatal("public directory accepted")
|
||||
}
|
||||
var decoded Snapshot
|
||||
if err := json.Unmarshal(before, &decoded); err != nil || !reflect.DeepEqual(state, decoded) {
|
||||
t.Fatal(decoded, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClearRemovesTemporaryHistoryLeftByInterruptedWrite(t *testing.T) {
|
||||
root := filepath.Join(t.TempDir(), "private")
|
||||
if err := os.Mkdir(root, 0700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
path := filepath.Join(root, "terminal.json")
|
||||
stale := filepath.Join(root, ".terminal.json-interrupted.tmp")
|
||||
other := filepath.Join(root, ".other.json-interrupted.tmp")
|
||||
for _, name := range []string{stale, other} {
|
||||
if err := os.WriteFile(name, []byte("old clipboard text"), 0600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
store := New_persistence(path, false)
|
||||
store.Clear()
|
||||
if err := store.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := os.Stat(stale); !errors.Is(err, os.ErrNotExist) {
|
||||
t.Fatal("clear retained interrupted write", err)
|
||||
}
|
||||
if _, err := os.Stat(other); err != nil {
|
||||
t.Fatal("clear removed another history's file", err)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user