1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
|
// SPDX-License-Identifier: GPL-3.0-or-later
package main
import (
"os"
"path/filepath"
"strings"
"testing"
)
// home gives each test its own HOME with no XDG overrides, and points the
// package's stdin seam at something guaranteed non-terminal: every test
// that reaches cmdSort's terminal check or the interactive review must not
// depend on what the ambient test binary's stdin happens to be - if that
// were ever a real terminal, such a test would silently fall through to
// the interactive prompt and block on a keypress instead of failing.
func home(t *testing.T) string {
t.Helper()
h := t.TempDir()
t.Setenv("HOME", h)
for _, v := range []string{"XDG_CONFIG_HOME", "XDG_STATE_HOME", "XDG_DATA_HOME", "XDG_CACHE_HOME"} {
t.Setenv(v, "")
}
setNonTerminalStdin(t)
return h
}
// setNonTerminalStdin points the stdin seam (sort.go) at os.DevNull for the
// duration of the calling test, restoring it afterward.
func setNonTerminalStdin(t *testing.T) {
t.Helper()
f, err := os.Open(os.DevNull)
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { f.Close() })
old := stdin
stdin = f
t.Cleanup(func() { stdin = old })
}
func TestInitNewCheck(t *testing.T) {
h := home(t)
if err := os.Mkdir(filepath.Join(h, "dl"), 0o755); err != nil {
t.Fatal(err)
}
code, out, errOut := runCLI(t, "init")
if code != 0 || !strings.Contains(out, "created ~/.config/krino/krino.conf") {
t.Fatalf("init: %d %q %q", code, out, errOut)
}
code, out, _ = runCLI(t, "check")
if code != 0 || !strings.Contains(out, "no directories included") {
t.Fatalf("check, empty: %d %q", code, out)
}
code, out, errOut = runCLI(t, "new", "dl", "~/dl")
if code != 0 || !strings.Contains(out, "created ~/.config/krino/dirs/dl.conf") {
t.Fatalf("new: %d %q %q", code, out, errOut)
}
conf := filepath.Join(h, ".config", "krino", "dirs", "dl.conf")
rules := "(path \"~/dl\")\n(rule \"pdfs\" (when (type pdf)) (move \"PDF\") (stop))\n"
if err := os.WriteFile(conf, []byte(rules), 0o644); err != nil {
t.Fatal(err)
}
code, out, errOut = runCLI(t, "check")
if code != 0 || !strings.Contains(out, "dl ~/dl") || !strings.Contains(out, "pdfs") ||
!strings.Contains(out, "move PDF, stop") {
t.Fatalf("check: %d %q %q", code, out, errOut)
}
if err := os.WriteFile(conf, []byte(`(path "~/dl") (rule "x" (move PDF))`), 0o644); err != nil {
t.Fatal(err)
}
code, _, errOut = runCLI(t, "check")
want := `~/.config/krino/dirs/dl.conf:1:31: rule "x": move takes a string: write (move "PDF")`
if code != 2 || !strings.Contains(errOut, want) || !strings.Contains(errOut, "1 problem found") {
t.Fatalf("check, broken: %d %q", code, errOut)
}
}
// TestConfigFlagExpandsTilde: -c=~/... is not expanded by the shell (the ~
// comes after =), so krino must expand it itself.
func TestConfigFlagExpandsTilde(t *testing.T) {
h := home(t)
cwd := t.TempDir()
t.Chdir(cwd)
code, out, errOut := runCLI(t, "-c=~/k/krino.conf", "init")
if code != 0 {
t.Fatalf("init: %d %q %q", code, out, errOut)
}
want := filepath.Join(h, "k", "krino.conf")
if _, err := os.Stat(want); err != nil {
t.Fatalf("%s not created: %v", want, err)
}
if _, err := os.Lstat(filepath.Join(cwd, "~")); !os.IsNotExist(err) {
t.Fatalf(`"~" created in the current directory: %v`, err)
}
}
func TestConfigFlag(t *testing.T) {
h := home(t)
conf := filepath.Join(h, "elsewhere", "krino.conf")
if code, _, errOut := runCLI(t, "init", "-c", conf); code != 0 {
t.Fatalf("init -c: %q", errOut)
}
for _, args := range [][]string{{"-c", conf, "check"}, {"check", "-c", conf}} {
if code, out, errOut := runCLI(t, args...); code != 0 || !strings.Contains(out, "config: ~/elsewhere/krino.conf") {
t.Errorf("%v: %d %q %q", args, code, out, errOut)
}
}
}
func TestCommandErrors(t *testing.T) {
home(t)
tests := []struct {
args []string
want string
}{
{[]string{"check"}, "not found; create it with: krino init"},
{[]string{"new", "onlyname"}, "usage: krino new NAME PATH"},
{[]string{"init", "extra"}, "init takes no arguments"},
}
for _, tt := range tests {
if code, _, errOut := runCLI(t, tt.args...); code != 2 || !strings.Contains(errOut, tt.want) {
t.Errorf("%v: %d %q, want %q", tt.args, code, errOut, tt.want)
}
}
}
|