package folderwatcher import ( "context" "os" "path/filepath" "strconv" "sync" "testing" "time" ) func TestNewFileHandling(t *testing.T) { tmpPath, err := os.MkdirTemp("", ".folderwatcher_test") if err != nil { t.Fatal(err) } defer os.RemoveAll(tmpPath) quitChan := make(chan struct{}) filesIn := []string{"A", "B", "C"} for i, f := range filesIn { //remap filesIn to Full Path filesIn[i] = filepath.Join(tmpPath, f) if !filepath.IsAbs(filesIn[i]) { t.Fatalf("file %s is not an absolute path", filesIn[i]) } } var filesOut []string wg := sync.WaitGroup{} conf := Config{ Folder: tmpPath, } watcher, err := NewFolderWatcher(conf, true, quitChan) if err != nil { t.Fatal(err) } go watcher.Watch(func(filePath string) (bool, error) { if !filepath.IsAbs(filePath) { t.Errorf("file %s is not an absolute path", filePath) } filesOut = append(filesOut, filePath) wg.Done() return true, nil }, func(s string, err error) { }) for _, f := range filesIn { wg.Add(1) _ = os.WriteFile(f, []byte{0}, os.ModePerm) } finishedChan := make(chan struct{}) go func() { wg.Wait() finishedChan <- struct{}{} }() ctx, ctxCancel := context.WithTimeout(context.Background(), 10*time.Second) defer ctxCancel() select { case <-finishedChan: case <-ctx.Done(): t.Error(ctx.Err()) } quitChan <- struct{}{} if len(filesOut) != len(filesIn) { t.Errorf("filesOut length %d != %d", len(filesOut), len(filesIn)) } for _, f := range filesIn { if !Contains(filesOut, f) { t.Errorf("File %s not found in %s", f, filesOut) } } } func TestOldFileHandling(t *testing.T) { tmpPath, err := os.MkdirTemp("", ".folderwatcher_test") if err != nil { t.Fatal(err) } defer os.RemoveAll(tmpPath) quitChan := make(chan struct{}) filesIn := []string{"A", "B", "C"} for i, f := range filesIn { //remap filesIn to Full Path filesIn[i] = filepath.Join(tmpPath, f) if !filepath.IsAbs(filesIn[i]) { t.Fatalf("file %s is not an absolute path", filesIn[i]) } } var filesOut []string wg := sync.WaitGroup{} conf := Config{ Folder: tmpPath, } for _, f := range filesIn { wg.Add(1) _ = os.WriteFile(f, []byte{0}, os.ModePerm) } watcher, err := NewFolderWatcher(conf, true, quitChan) if err != nil { t.Fatal(err) } go watcher.Watch(func(filePath string) (bool, error) { if !filepath.IsAbs(filePath) { t.Errorf("file %s is not an absolute path", filePath) } filesOut = append(filesOut, filePath) wg.Done() return true, nil }, func(s string, err error) { }) finishedChan := make(chan struct{}) go func() { wg.Wait() finishedChan <- struct{}{} }() ctx, ctxCancel := context.WithTimeout(context.Background(), 10*time.Second) defer ctxCancel() select { case <-finishedChan: case <-ctx.Done(): t.Error(ctx.Err()) } quitChan <- struct{}{} if len(filesOut) != len(filesIn) { t.Errorf("filesOut length %d != %d", len(filesOut), len(filesIn)) } for _, f := range filesIn { if !Contains(filesOut, f) { t.Errorf("File %s not found in %s", f, filesOut) } } } func TestOldAndNewFileHandling(t *testing.T) { tmpPath, err := os.MkdirTemp("", ".folderwatcher_test") if err != nil { t.Fatal(err) } defer os.RemoveAll(tmpPath) quitChan := make(chan struct{}) filesIn := []string{"A", "B", "C"} for i, f := range filesIn { //remap filesIn to Full Path filesIn[i] = filepath.Join(tmpPath, f) if !filepath.IsAbs(filesIn[i]) { t.Fatalf("file %s is not an absolute path", filesIn[i]) } } filesInRunning := []string{"D", "E", "F"} for i, f := range filesInRunning { //remap filesIn to Full Path filesInRunning[i] = filepath.Join(tmpPath, f) if !filepath.IsAbs(filesInRunning[i]) { t.Fatalf("file %s is not an absolute path", filesInRunning[i]) } } var filesOut []string wg := sync.WaitGroup{} conf := Config{ Folder: tmpPath, } for _, f := range filesIn { wg.Add(1) _ = os.WriteFile(f, []byte{0}, os.ModePerm) } watcher, err := NewFolderWatcher(conf, true, quitChan) if err != nil { t.Fatal(err) } go watcher.Watch(func(filePath string) (bool, error) { if !filepath.IsAbs(filePath) { t.Errorf("file %s is not an absolute path", filePath) } filesOut = append(filesOut, filePath) wg.Done() return true, nil }, func(s string, err error) { }) for _, f := range filesInRunning { wg.Add(1) filesIn = append(filesIn, f) _ = os.WriteFile(f, []byte{0}, os.ModePerm) } finishedChan := make(chan struct{}) go func() { wg.Wait() finishedChan <- struct{}{} }() ctx, ctxCancel := context.WithTimeout(context.Background(), 10*time.Second) defer ctxCancel() select { case <-finishedChan: case <-ctx.Done(): t.Error(ctx.Err()) } quitChan <- struct{}{} if len(filesOut) != len(filesIn) { t.Errorf("filesOut length %d != %d", len(filesOut), len(filesIn)) } for _, f := range filesIn { if !Contains(filesOut, f) { t.Errorf("File %s not found in %s", f, filesOut) } } } func TestOldAndNewFileHandlingWithoutExisting(t *testing.T) { tmpPath, err := os.MkdirTemp("", ".folderwatcher_test") if err != nil { t.Fatal(err) } defer os.RemoveAll(tmpPath) quitChan := make(chan struct{}) filesIn := []string{"A", "B", "C"} for i, f := range filesIn { //remap filesIn to Full Path filesIn[i] = filepath.Join(tmpPath, f) if !filepath.IsAbs(filesIn[i]) { t.Fatalf("file %s is not an absolute path", filesIn[i]) } } filesInRunning := []string{"D", "E", "F"} for i, f := range filesInRunning { //remap filesInRunning to Full Path filesInRunning[i] = filepath.Join(tmpPath, f) if !filepath.IsAbs(filesInRunning[i]) { t.Fatalf("file %s is not an absolute path", filesInRunning[i]) } } var filesOut []string wg := sync.WaitGroup{} conf := Config{ Folder: tmpPath, } for _, f := range filesIn { _ = os.WriteFile(f, []byte{0}, os.ModePerm) } watcher, err := NewFolderWatcher(conf, false, quitChan) if err != nil { t.Fatal(err) } go watcher.Watch(func(filePath string) (bool, error) { if !filepath.IsAbs(filePath) { t.Errorf("file %s is not an absolute path", filePath) } filesOut = append(filesOut, filePath) wg.Done() return true, nil }, func(s string, err error) { }) for _, f := range filesInRunning { wg.Add(1) _ = os.WriteFile(f, []byte{0}, os.ModePerm) } finishedChan := make(chan struct{}) go func() { wg.Wait() finishedChan <- struct{}{} }() ctx, ctxCancel := context.WithTimeout(context.Background(), 10*time.Second) defer ctxCancel() select { case <-finishedChan: case <-ctx.Done(): t.Error(ctx.Err()) } quitChan <- struct{}{} if len(filesOut) != len(filesInRunning) { t.Errorf("filesOut length %d != %d", len(filesOut), len(filesInRunning)) } for _, f := range filesInRunning { if !Contains(filesOut, f) { t.Errorf("File %s not found in %s", f, filesOut) } } for _, f := range filesIn { if Contains(filesOut, f) { t.Errorf("File %s found in %s", f, filesOut) } } } func TestGlobbingN1(t *testing.T) { tmpPath, err := os.MkdirTemp("", ".folderwatcher_test") if err != nil { t.Fatal(err) } defer os.RemoveAll(tmpPath) quitChan := make(chan struct{}) _ = os.MkdirAll(filepath.Join(tmpPath, "g0"), os.ModePerm) _ = os.MkdirAll(filepath.Join(tmpPath, "g1"), os.ModePerm) _ = os.MkdirAll(filepath.Join(tmpPath, "g2"), os.ModePerm) filesIn := []string{"A", "B", "C"} var filesOut []string wg := sync.WaitGroup{} conf := Config{ Folder: filepath.Join(tmpPath, "*"), } watcher, err := NewFolderWatcher(conf, true, quitChan) if err != nil { t.Fatal(err) } go watcher.Watch(func(filePath string) (bool, error) { if !filepath.IsAbs(filePath) { t.Errorf("file %s is not an absolute path", filePath) } rel, err := filepath.Rel(tmpPath, filePath) if err != nil { return true, err } filesOut = append(filesOut, rel) wg.Done() return true, nil }, func(s string, err error) { }) for i, f := range filesIn { wg.Add(1) _ = os.WriteFile(filepath.Join(tmpPath, "g"+strconv.Itoa(i), f), []byte{0}, os.ModePerm) } finishedChan := make(chan struct{}) go func() { wg.Wait() finishedChan <- struct{}{} }() ctx, ctxCancel := context.WithTimeout(context.Background(), 10*time.Second) defer ctxCancel() select { case <-finishedChan: case <-ctx.Done(): t.Error(ctx.Err()) } quitChan <- struct{}{} if len(filesOut) != len(filesIn) { t.Errorf("filesOut length %d != %d", len(filesOut), len(filesIn)) } for i, f := range filesIn { if !Contains(filesOut, filepath.Join("g"+strconv.Itoa(i), f)) { t.Errorf("File %s not found in %s", f, filesOut) } } } func TestGlobbingN2(t *testing.T) { tmpPath, err := os.MkdirTemp("", ".folderwatcher_test") if err != nil { t.Fatal(err) } defer os.RemoveAll(tmpPath) quitChan := make(chan struct{}) _ = os.MkdirAll(filepath.Join(tmpPath, "g0", "s0"), os.ModePerm) _ = os.MkdirAll(filepath.Join(tmpPath, "g0", "s2"), os.ModePerm) _ = os.MkdirAll(filepath.Join(tmpPath, "g1", "s1"), os.ModePerm) _ = os.MkdirAll(filepath.Join(tmpPath, "g1", "s3"), os.ModePerm) _ = os.MkdirAll(filepath.Join(tmpPath, "gX"), os.ModePerm) filesIn := []string{"A", "B", "C", "D"} var filesOut []string wg := sync.WaitGroup{} conf := Config{ Folder: filepath.Join(tmpPath, "*", "*"), } watcher, err := NewFolderWatcher(conf, true, quitChan) if err != nil { t.Fatal(err) } go watcher.Watch(func(filePath string) (bool, error) { if !filepath.IsAbs(filePath) { t.Errorf("file %s is not an absolute path", filePath) } rel, err := filepath.Rel(tmpPath, filePath) if err != nil { return true, err } filesOut = append(filesOut, rel) wg.Done() return true, nil }, func(s string, err error) { }) for i, f := range filesIn { wg.Add(1) _ = os.WriteFile(filepath.Join(tmpPath, "g"+strconv.Itoa(i%2), "s"+strconv.Itoa(i), f), []byte{0}, os.ModePerm) } wg.Add(1) _ = os.WriteFile(filepath.Join(tmpPath, "gX", "foo"), []byte{0}, os.ModePerm) finishedChan := make(chan struct{}) go func() { wg.Wait() finishedChan <- struct{}{} }() ctx, ctxCancel := context.WithTimeout(context.Background(), 5*time.Second) defer ctxCancel() select { case <-finishedChan: case <-ctx.Done(): wg.Done() // ctx Will Timeout, because gX/foo will always be remaining // t.Error(ctx.Err()) } quitChan <- struct{}{} if len(filesOut) != len(filesIn) { t.Errorf("filesOut length %d != %d", len(filesOut), len(filesIn)) } for i, f := range filesIn { if !Contains(filesOut, filepath.Join("g"+strconv.Itoa(i%2), "s"+strconv.Itoa(i), f)) { t.Errorf("File %s not found in %s", f, filesOut) } } if Contains(filesOut, filepath.Join("gX", "foo")) { t.Errorf("File %s found in %s", filepath.Join("gX", "foo"), filesOut) } } func Contains[T comparable](slice []T, item T) bool { for _, v := range slice { if v == item { return true } } return false }