2017-07-26 22:00:20 +00:00
|
|
|
package tree
|
|
|
|
|
|
|
|
import (
|
|
|
|
"os"
|
|
|
|
"syscall"
|
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Mock file/FileInfo
|
|
|
|
type file struct {
|
|
|
|
name string
|
|
|
|
size int64
|
|
|
|
files []*file
|
|
|
|
lastMod time.Time
|
2017-09-30 14:27:27 +00:00
|
|
|
stat interface{}
|
2017-07-26 22:00:20 +00:00
|
|
|
mode os.FileMode
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f file) Name() string { return f.name }
|
|
|
|
func (f file) Size() int64 { return f.size }
|
|
|
|
func (f file) Mode() (o os.FileMode) {
|
|
|
|
if f.mode != o {
|
|
|
|
return f.mode
|
|
|
|
}
|
2017-09-30 14:27:27 +00:00
|
|
|
if f.stat != nil {
|
|
|
|
stat := (f.stat).(*syscall.Stat_t)
|
2017-07-26 22:00:20 +00:00
|
|
|
o = os.FileMode(stat.Mode)
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
func (f file) ModTime() time.Time { return f.lastMod }
|
|
|
|
func (f file) IsDir() bool { return nil != f.files }
|
|
|
|
func (f file) Sys() interface{} {
|
2017-09-30 14:27:27 +00:00
|
|
|
if f.stat == nil {
|
2017-07-26 22:00:20 +00:00
|
|
|
return new(syscall.Stat_t)
|
|
|
|
}
|
2017-09-30 14:27:27 +00:00
|
|
|
return f.stat
|
2017-07-26 22:00:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Mock filesystem
|
|
|
|
type MockFs struct {
|
|
|
|
files map[string]*file
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewFs() *MockFs {
|
|
|
|
return &MockFs{make(map[string]*file)}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (fs *MockFs) clean() *MockFs {
|
|
|
|
fs.files = make(map[string]*file)
|
|
|
|
return fs
|
|
|
|
}
|
|
|
|
|
|
|
|
func (fs *MockFs) addFile(path string, file *file) *MockFs {
|
|
|
|
fs.files[path] = file
|
|
|
|
if file.IsDir() {
|
|
|
|
for _, f := range file.files {
|
|
|
|
fs.addFile(path+"/"+f.name, f)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return fs
|
|
|
|
}
|
|
|
|
|
|
|
|
func (fs *MockFs) Stat(path string) (os.FileInfo, error) {
|
|
|
|
return fs.files[path], nil
|
|
|
|
}
|
|
|
|
func (fs *MockFs) ReadDir(path string) ([]string, error) {
|
|
|
|
var names []string
|
|
|
|
for _, file := range fs.files[path].files {
|
|
|
|
names = append(names, file.Name())
|
|
|
|
}
|
|
|
|
return names, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Mock output file
|
|
|
|
type Out struct {
|
|
|
|
str string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (o *Out) equal(s string) bool {
|
|
|
|
return o.str == s
|
|
|
|
}
|
|
|
|
|
|
|
|
func (o *Out) Write(p []byte) (int, error) {
|
|
|
|
o.str += string(p)
|
|
|
|
return len(p), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (o *Out) clear() {
|
|
|
|
o.str = ""
|
|
|
|
}
|
|
|
|
|
|
|
|
// FileSystem and Stdout mocks
|
|
|
|
var (
|
|
|
|
fs = NewFs()
|
|
|
|
out = new(Out)
|
|
|
|
)
|
|
|
|
|
|
|
|
type treeTest struct {
|
|
|
|
name string
|
2017-09-30 14:27:27 +00:00
|
|
|
opts *Options // test params.
|
|
|
|
expected string // expected output.
|
|
|
|
dirs int // expected dir count.
|
|
|
|
files int // expected file count.
|
2017-07-26 22:00:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var listTests = []treeTest{
|
|
|
|
{"basic", &Options{Fs: fs, OutFile: out}, `root
|
|
|
|
├── a
|
|
|
|
├── b
|
|
|
|
└── c
|
|
|
|
├── d
|
|
|
|
└── e
|
2017-09-30 14:27:27 +00:00
|
|
|
`, 1, 4},
|
|
|
|
{"all", &Options{Fs: fs, OutFile: out, All: true, NoSort: true}, `root
|
2017-07-26 22:00:20 +00:00
|
|
|
├── a
|
|
|
|
├── b
|
|
|
|
└── c
|
|
|
|
├── d
|
|
|
|
├── e
|
|
|
|
└── .f
|
2017-09-30 14:27:27 +00:00
|
|
|
`, 1, 5},
|
|
|
|
{"dirs", &Options{Fs: fs, OutFile: out, DirsOnly: true}, `root
|
2017-07-26 22:00:20 +00:00
|
|
|
└── c
|
2017-09-30 14:27:27 +00:00
|
|
|
`, 1, 0},
|
|
|
|
{"fullPath", &Options{Fs: fs, OutFile: out, FullPath: true}, `root
|
2017-07-26 22:00:20 +00:00
|
|
|
├── root/a
|
|
|
|
├── root/b
|
|
|
|
└── root/c
|
|
|
|
├── root/c/d
|
|
|
|
└── root/c/e
|
2017-09-30 14:27:27 +00:00
|
|
|
`, 1, 4},
|
|
|
|
{"deepLevel", &Options{Fs: fs, OutFile: out, DeepLevel: 1}, `root
|
2017-07-26 22:00:20 +00:00
|
|
|
├── a
|
|
|
|
├── b
|
|
|
|
└── c
|
2017-09-30 14:27:27 +00:00
|
|
|
`, 1, 2},
|
|
|
|
{"pattern", &Options{Fs: fs, OutFile: out, Pattern: "(a|e)"}, `root
|
2017-07-26 22:00:20 +00:00
|
|
|
├── a
|
|
|
|
└── c
|
|
|
|
└── e
|
2017-09-30 14:27:27 +00:00
|
|
|
`, 1, 2},
|
|
|
|
{"ipattern", &Options{Fs: fs, OutFile: out, IPattern: "(a|e)"}, `root
|
2017-07-26 22:00:20 +00:00
|
|
|
├── b
|
|
|
|
└── c
|
|
|
|
└── d
|
2017-09-30 14:27:27 +00:00
|
|
|
`, 1, 2},
|
|
|
|
{"ignore-case", &Options{Fs: fs, OutFile: out, Pattern: "(A)", IgnoreCase: true}, `root
|
2017-07-26 22:00:20 +00:00
|
|
|
├── a
|
|
|
|
└── c
|
2017-09-30 14:27:27 +00:00
|
|
|
`, 1, 1}}
|
2017-07-26 22:00:20 +00:00
|
|
|
|
|
|
|
func TestSimple(t *testing.T) {
|
|
|
|
root := &file{
|
|
|
|
name: "root",
|
|
|
|
size: 200,
|
|
|
|
files: []*file{
|
2017-09-30 14:27:27 +00:00
|
|
|
{name: "a", size: 50},
|
|
|
|
{name: "b", size: 50},
|
|
|
|
{
|
2017-07-26 22:00:20 +00:00
|
|
|
name: "c",
|
|
|
|
size: 100,
|
|
|
|
files: []*file{
|
2017-09-30 14:27:27 +00:00
|
|
|
{name: "d", size: 50},
|
|
|
|
{name: "e", size: 50},
|
|
|
|
{name: ".f", size: 0},
|
2017-07-26 22:00:20 +00:00
|
|
|
},
|
2017-09-30 14:27:27 +00:00
|
|
|
},
|
|
|
|
},
|
2017-07-26 22:00:20 +00:00
|
|
|
}
|
|
|
|
fs.clean().addFile(root.name, root)
|
|
|
|
for _, test := range listTests {
|
|
|
|
inf := New(root.name)
|
2017-09-30 14:27:27 +00:00
|
|
|
d, f := inf.Visit(test.opts)
|
|
|
|
if d != test.dirs {
|
|
|
|
t.Errorf("wrong dir count for test %q:\ngot:\n%d\nexpected:\n%d", test.name, d, test.dirs)
|
|
|
|
}
|
|
|
|
if f != test.files {
|
|
|
|
t.Errorf("wrong dir count for test %q:\ngot:\n%d\nexpected:\n%d", test.name, d, test.files)
|
|
|
|
}
|
2017-07-26 22:00:20 +00:00
|
|
|
inf.Print(test.opts)
|
|
|
|
if !out.equal(test.expected) {
|
|
|
|
t.Errorf("%s:\ngot:\n%+v\nexpected:\n%+v", test.name, out.str, test.expected)
|
|
|
|
}
|
|
|
|
out.clear()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var sortTests = []treeTest{
|
|
|
|
{"name-sort", &Options{Fs: fs, OutFile: out, NameSort: true}, `root
|
|
|
|
├── a
|
|
|
|
├── b
|
|
|
|
└── c
|
|
|
|
└── d
|
2017-09-30 14:27:27 +00:00
|
|
|
`, 1, 3},
|
|
|
|
{"dirs-first sort", &Options{Fs: fs, OutFile: out, DirSort: true}, `root
|
2017-07-26 22:00:20 +00:00
|
|
|
├── c
|
|
|
|
│ └── d
|
|
|
|
├── b
|
|
|
|
└── a
|
2017-09-30 14:27:27 +00:00
|
|
|
`, 1, 3},
|
|
|
|
{"reverse sort", &Options{Fs: fs, OutFile: out, ReverSort: true, DirSort: true}, `root
|
2017-07-26 22:00:20 +00:00
|
|
|
├── b
|
|
|
|
├── a
|
|
|
|
└── c
|
|
|
|
└── d
|
2017-09-30 14:27:27 +00:00
|
|
|
`, 1, 3},
|
|
|
|
{"no-sort", &Options{Fs: fs, OutFile: out, NoSort: true, DirSort: true}, `root
|
2017-07-26 22:00:20 +00:00
|
|
|
├── b
|
|
|
|
├── c
|
|
|
|
│ └── d
|
|
|
|
└── a
|
2017-09-30 14:27:27 +00:00
|
|
|
`, 1, 3},
|
|
|
|
{"size-sort", &Options{Fs: fs, OutFile: out, SizeSort: true}, `root
|
2017-07-26 22:00:20 +00:00
|
|
|
├── a
|
|
|
|
├── c
|
|
|
|
│ └── d
|
|
|
|
└── b
|
2017-09-30 14:27:27 +00:00
|
|
|
`, 1, 3},
|
|
|
|
{"last-mod-sort", &Options{Fs: fs, OutFile: out, ModSort: true}, `root
|
2017-07-26 22:00:20 +00:00
|
|
|
├── a
|
|
|
|
├── b
|
|
|
|
└── c
|
|
|
|
└── d
|
2017-09-30 14:27:27 +00:00
|
|
|
`, 1, 3},
|
|
|
|
{"c-time-sort", &Options{Fs: fs, OutFile: out, CTimeSort: true}, `root
|
2017-07-26 22:00:20 +00:00
|
|
|
├── b
|
|
|
|
├── c
|
|
|
|
│ └── d
|
|
|
|
└── a
|
2017-09-30 14:27:27 +00:00
|
|
|
`, 1, 3}}
|
2017-07-26 22:00:20 +00:00
|
|
|
|
|
|
|
func TestSort(t *testing.T) {
|
|
|
|
tFmt := "2006-Jan-02"
|
|
|
|
aTime, _ := time.Parse(tFmt, "2015-Aug-01")
|
|
|
|
bTime, _ := time.Parse(tFmt, "2015-Sep-01")
|
|
|
|
cTime, _ := time.Parse(tFmt, "2015-Oct-01")
|
|
|
|
root := &file{
|
|
|
|
name: "root",
|
|
|
|
size: 200,
|
|
|
|
files: []*file{
|
2017-09-30 14:27:27 +00:00
|
|
|
{name: "b", size: 11, lastMod: bTime},
|
|
|
|
{name: "c", size: 10, files: []*file{{name: "d", size: 10, lastMod: cTime}}, lastMod: cTime},
|
|
|
|
{name: "a", size: 9, lastMod: aTime},
|
2017-07-26 22:00:20 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
fs.clean().addFile(root.name, root)
|
|
|
|
for _, test := range sortTests {
|
|
|
|
inf := New(root.name)
|
|
|
|
inf.Visit(test.opts)
|
|
|
|
inf.Print(test.opts)
|
|
|
|
if !out.equal(test.expected) {
|
|
|
|
t.Errorf("%s:\ngot:\n%+v\nexpected:\n%+v", test.name, out.str, test.expected)
|
|
|
|
}
|
|
|
|
out.clear()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var graphicTests = []treeTest{
|
|
|
|
{"no-indent", &Options{Fs: fs, OutFile: out, NoIndent: true}, `root
|
|
|
|
a
|
|
|
|
b
|
|
|
|
c
|
2017-09-30 14:27:27 +00:00
|
|
|
`, 0, 3},
|
|
|
|
{"quotes", &Options{Fs: fs, OutFile: out, Quotes: true}, `"root"
|
2017-07-26 22:00:20 +00:00
|
|
|
├── "a"
|
|
|
|
├── "b"
|
|
|
|
└── "c"
|
2017-09-30 14:27:27 +00:00
|
|
|
`, 0, 3},
|
|
|
|
{"byte-size", &Options{Fs: fs, OutFile: out, ByteSize: true}, `[ 12499] root
|
2017-07-26 22:00:20 +00:00
|
|
|
├── [ 1500] a
|
|
|
|
├── [ 9999] b
|
|
|
|
└── [ 1000] c
|
2017-09-30 14:27:27 +00:00
|
|
|
`, 0, 3},
|
|
|
|
{"unit-size", &Options{Fs: fs, OutFile: out, UnitSize: true}, `[ 12K] root
|
2017-07-26 22:00:20 +00:00
|
|
|
├── [1.5K] a
|
|
|
|
├── [9.8K] b
|
|
|
|
└── [1000] c
|
2017-09-30 14:27:27 +00:00
|
|
|
`, 0, 3},
|
|
|
|
{"show-gid", &Options{Fs: fs, OutFile: out, ShowGid: true}, `root
|
2017-07-26 22:00:20 +00:00
|
|
|
├── [1 ] a
|
|
|
|
├── [2 ] b
|
|
|
|
└── [1 ] c
|
2017-09-30 14:27:27 +00:00
|
|
|
`, 0, 3},
|
|
|
|
{"mode", &Options{Fs: fs, OutFile: out, FileMode: true}, `root
|
2017-07-26 22:00:20 +00:00
|
|
|
├── [-rw-r--r--] a
|
|
|
|
├── [-rwxr-xr-x] b
|
|
|
|
└── [-rw-rw-rw-] c
|
2017-09-30 14:27:27 +00:00
|
|
|
`, 0, 3},
|
|
|
|
{"lastMod", &Options{Fs: fs, OutFile: out, LastMod: true}, `root
|
2017-07-26 22:00:20 +00:00
|
|
|
├── [Feb 11 00:00] a
|
|
|
|
├── [Jan 28 00:00] b
|
|
|
|
└── [Jul 12 00:00] c
|
2017-09-30 14:27:27 +00:00
|
|
|
`, 0, 3}}
|
2017-07-26 22:00:20 +00:00
|
|
|
|
|
|
|
func TestGraphics(t *testing.T) {
|
|
|
|
tFmt := "2006-Jan-02"
|
|
|
|
aTime, _ := time.Parse(tFmt, "2015-Feb-11")
|
|
|
|
bTime, _ := time.Parse(tFmt, "2006-Jan-28")
|
|
|
|
cTime, _ := time.Parse(tFmt, "2015-Jul-12")
|
|
|
|
root := &file{
|
|
|
|
name: "root",
|
|
|
|
size: 11499,
|
|
|
|
files: []*file{
|
2017-09-30 14:27:27 +00:00
|
|
|
{name: "a", size: 1500, lastMod: aTime, stat: &syscall.Stat_t{Gid: 1, Mode: 0644}},
|
|
|
|
{name: "b", size: 9999, lastMod: bTime, stat: &syscall.Stat_t{Gid: 2, Mode: 0755}},
|
|
|
|
{name: "c", size: 1000, lastMod: cTime, stat: &syscall.Stat_t{Gid: 1, Mode: 0666}},
|
2017-07-26 22:00:20 +00:00
|
|
|
},
|
2017-09-30 14:27:27 +00:00
|
|
|
stat: &syscall.Stat_t{Gid: 1},
|
2017-07-26 22:00:20 +00:00
|
|
|
}
|
|
|
|
fs.clean().addFile(root.name, root)
|
|
|
|
for _, test := range graphicTests {
|
|
|
|
inf := New(root.name)
|
|
|
|
inf.Visit(test.opts)
|
|
|
|
inf.Print(test.opts)
|
|
|
|
if !out.equal(test.expected) {
|
|
|
|
t.Errorf("%s:\ngot:\n%+v\nexpected:\n%+v", test.name, out.str, test.expected)
|
|
|
|
}
|
|
|
|
out.clear()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var symlinkTests = []treeTest{
|
|
|
|
{"symlink", &Options{Fs: fs, OutFile: out}, `root
|
|
|
|
└── symlink -> root/symlink
|
2017-09-30 14:27:27 +00:00
|
|
|
`, 0, 1},
|
|
|
|
{"symlink-rec", &Options{Fs: fs, OutFile: out, FollowLink: true}, `root
|
2017-07-26 22:00:20 +00:00
|
|
|
└── symlink -> root/symlink [recursive, not followed]
|
2017-09-30 14:27:27 +00:00
|
|
|
`, 0, 1}}
|
2017-07-26 22:00:20 +00:00
|
|
|
|
|
|
|
func TestSymlink(t *testing.T) {
|
|
|
|
root := &file{
|
|
|
|
name: "root",
|
|
|
|
files: []*file{
|
|
|
|
&file{name: "symlink", mode: os.ModeSymlink, files: make([]*file, 0)},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
fs.clean().addFile(root.name, root)
|
|
|
|
for _, test := range symlinkTests {
|
|
|
|
inf := New(root.name)
|
|
|
|
inf.Visit(test.opts)
|
|
|
|
inf.Print(test.opts)
|
|
|
|
if !out.equal(test.expected) {
|
|
|
|
t.Errorf("%s:\ngot:\n%+v\nexpected:\n%+v", test.name, out.str, test.expected)
|
|
|
|
}
|
|
|
|
out.clear()
|
|
|
|
}
|
|
|
|
}
|
2017-09-30 14:27:27 +00:00
|
|
|
|
|
|
|
func TestCount(t *testing.T) {
|
|
|
|
defer out.clear()
|
|
|
|
root := &file{
|
|
|
|
name: "root",
|
|
|
|
files: []*file{
|
|
|
|
&file{
|
|
|
|
name: "a",
|
|
|
|
files: []*file{
|
|
|
|
{
|
|
|
|
name: "b",
|
|
|
|
files: []*file{{name: "c"}},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "d",
|
|
|
|
files: []*file{
|
|
|
|
{
|
|
|
|
name: "e",
|
|
|
|
files: []*file{{name: "f"}, {name: "g"}},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "h",
|
|
|
|
files: []*file{
|
|
|
|
{
|
|
|
|
name: "i",
|
|
|
|
files: []*file{{name: "j"}},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "k",
|
|
|
|
files: []*file{{name: "l"}, {name: "m"}},
|
|
|
|
},
|
|
|
|
{name: "n"},
|
|
|
|
{name: "o"},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}},
|
|
|
|
}
|
|
|
|
fs.clean().addFile(root.name, root)
|
|
|
|
opt := &Options{Fs: fs, OutFile: out}
|
|
|
|
inf := New(root.name)
|
|
|
|
d, f := inf.Visit(opt)
|
|
|
|
if d != 7 || f != 8 {
|
|
|
|
inf.Print(opt)
|
|
|
|
t.Errorf("TestCount - expect (dir, file) count to be equal to (7, 8)\n%s", out.str)
|
|
|
|
}
|
|
|
|
}
|