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) }) }