AdGuardHome/internal/aghos/filewalker_test.go

210 lines
4.8 KiB
Go

package aghos
import (
"bufio"
"io"
"io/fs"
"os"
"path/filepath"
"testing"
"github.com/AdguardTeam/golibs/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// testFSDir maps entries' names to entries which should either be a testFSDir
// or byte slice.
type testFSDir map[string]interface{}
// testFSGen is used to generate a temporary filesystem consisting of
// directories and plain text files from itself.
type testFSGen testFSDir
// gen returns the name of top directory of the generated filesystem.
func (g testFSGen) gen(t *testing.T) (dirName string) {
t.Helper()
dirName = t.TempDir()
g.rangeThrough(t, dirName)
return dirName
}
func (g testFSGen) rangeThrough(t *testing.T, dirName string) {
const perm fs.FileMode = 0o777
for k, e := range g {
switch e := e.(type) {
case []byte:
require.NoError(t, os.WriteFile(filepath.Join(dirName, k), e, perm))
case testFSDir:
newDir := filepath.Join(dirName, k)
require.NoError(t, os.Mkdir(newDir, perm))
testFSGen(e).rangeThrough(t, newDir)
default:
t.Fatalf("unexpected entry type %T", e)
}
}
}
func TestFileWalker_Walk(t *testing.T) {
const attribute = `000`
makeFileWalker := func(dirName string) (fw FileWalker) {
return func(r io.Reader) (patterns []string, cont bool, err error) {
s := bufio.NewScanner(r)
for s.Scan() {
line := s.Text()
if line == attribute {
return nil, false, nil
}
if len(line) != 0 {
patterns = append(patterns, filepath.Join(dirName, line))
}
}
return patterns, true, s.Err()
}
}
const nl = "\n"
testCases := []struct {
name string
testFS testFSGen
initPattern string
want bool
}{{
name: "simple",
testFS: testFSGen{
"simple_0001.txt": []byte(attribute + nl),
},
initPattern: "simple_0001.txt",
want: true,
}, {
name: "chain",
testFS: testFSGen{
"chain_0001.txt": []byte(`chain_0002.txt` + nl),
"chain_0002.txt": []byte(`chain_0003.txt` + nl),
"chain_0003.txt": []byte(attribute + nl),
},
initPattern: "chain_0001.txt",
want: true,
}, {
name: "several",
testFS: testFSGen{
"several_0001.txt": []byte(`several_*` + nl),
"several_0002.txt": []byte(`several_0001.txt` + nl),
"several_0003.txt": []byte(attribute + nl),
},
initPattern: "several_0001.txt",
want: true,
}, {
name: "no",
testFS: testFSGen{
"no_0001.txt": []byte(nl),
"no_0002.txt": []byte(nl),
"no_0003.txt": []byte(nl),
},
initPattern: "no_*",
want: false,
}, {
name: "subdirectory",
testFS: testFSGen{
"dir": testFSDir{
"subdir_0002.txt": []byte(attribute + nl),
},
"subdir_0001.txt": []byte(`dir/*`),
},
initPattern: "subdir_0001.txt",
want: true,
}}
for _, tc := range testCases {
testDir := tc.testFS.gen(t)
fw := makeFileWalker(testDir)
t.Run(tc.name, func(t *testing.T) {
ok, err := fw.Walk(filepath.Join(testDir, tc.initPattern))
require.NoError(t, err)
assert.Equal(t, tc.want, ok)
})
}
t.Run("pattern_malformed", func(t *testing.T) {
ok, err := makeFileWalker("").Walk("[]")
require.Error(t, err)
assert.False(t, ok)
assert.ErrorIs(t, err, filepath.ErrBadPattern)
})
t.Run("bad_filename", func(t *testing.T) {
dir := testFSGen{
"bad_filename.txt": []byte("[]"),
}.gen(t)
fw := FileWalker(func(r io.Reader) (patterns []string, cont bool, err error) {
s := bufio.NewScanner(r)
for s.Scan() {
patterns = append(patterns, s.Text())
}
return patterns, true, s.Err()
})
ok, err := fw.Walk(filepath.Join(dir, "bad_filename.txt"))
require.Error(t, err)
assert.False(t, ok)
assert.ErrorIs(t, err, filepath.ErrBadPattern)
})
t.Run("itself_error", func(t *testing.T) {
const rerr errors.Error = "returned error"
dir := testFSGen{
"mockfile.txt": []byte(`mockdata`),
}.gen(t)
ok, err := FileWalker(func(r io.Reader) (patterns []string, ok bool, err error) {
return nil, true, rerr
}).Walk(filepath.Join(dir, "*"))
require.Error(t, err)
require.False(t, ok)
assert.ErrorIs(t, err, rerr)
})
}
func TestWalkerFunc_CheckFile(t *testing.T) {
t.Run("non-existing", func(t *testing.T) {
_, ok, err := checkFile(nil, "lol")
require.NoError(t, err)
assert.True(t, ok)
})
t.Run("invalid_argument", func(t *testing.T) {
const badPath = "\x00"
_, ok, err := checkFile(nil, badPath)
require.Error(t, err)
assert.False(t, ok)
// TODO(e.burkov): Use assert.ErrorsIs within the error from
// less platform-dependent package instead of syscall.EINVAL.
//
// See https://github.com/golang/go/issues/46849 and
// https://github.com/golang/go/issues/30322.
pathErr := &os.PathError{}
require.ErrorAs(t, err, &pathErr)
assert.Equal(t, "open", pathErr.Op)
assert.Equal(t, badPath, pathErr.Path)
})
}