Skip to content

Commit 3730191

Browse files
target: rework modtime comparison logic #323 (#324)
* target: rework modtime comparison logic * fix NewestModTime, add tests for NewestModTime, OldestModTime * added tests for Dir when target is Dir * updates per #324
1 parent 2ded30c commit 3730191

File tree

4 files changed

+264
-131
lines changed

4 files changed

+264
-131
lines changed

target/newer.go

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
package target
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"path/filepath"
7+
"time"
8+
)
9+
10+
var (
11+
// errNewer is an ugly sentinel error to cause filepath.Walk to abort
12+
// as soon as a newer file is encountered
13+
errNewer = fmt.Errorf("newer item encountered")
14+
)
15+
16+
// DirNewer reports whether any item in sources is newer than the target time.
17+
// Sources are searched recursively and searching stops as soon as any entry
18+
// is newer than the target.
19+
func DirNewer(target time.Time, sources ...string) (bool, error) {
20+
walkFn := func(path string, info os.FileInfo, err error) error {
21+
if err != nil {
22+
return err
23+
}
24+
if info.ModTime().After(target) {
25+
return errNewer
26+
}
27+
return nil
28+
}
29+
for _, source := range sources {
30+
source = os.ExpandEnv(source)
31+
err := filepath.Walk(source, walkFn)
32+
if err == nil {
33+
continue
34+
}
35+
if err == errNewer {
36+
return true, nil
37+
}
38+
return false, err
39+
}
40+
return false, nil
41+
}
42+
43+
// GlobNewer performs glob expansion on each source and passes the results to
44+
// PathNewer for inspection. It returns the first time PathNewer encounters a
45+
// newer file
46+
func GlobNewer(target time.Time, sources ...string) (bool, error) {
47+
for _, g := range sources {
48+
files, err := filepath.Glob(g)
49+
if err != nil {
50+
return false, err
51+
}
52+
if len(files) == 0 {
53+
return false, fmt.Errorf("glob didn't match any files: %s", g)
54+
}
55+
newer, err := PathNewer(target, files...)
56+
if err != nil {
57+
return false, err
58+
}
59+
if newer {
60+
return true, nil
61+
}
62+
}
63+
return false, nil
64+
}
65+
66+
// PathNewer checks whether any of the sources are newer than the target time.
67+
// It stops at the first newer file it encounters. Each source path is passed
68+
// through os.ExpandEnv.
69+
func PathNewer(target time.Time, sources ...string) (bool, error) {
70+
for _, source := range sources {
71+
source = os.ExpandEnv(source)
72+
stat, err := os.Stat(source)
73+
if err != nil {
74+
return false, err
75+
}
76+
if stat.ModTime().After(target) {
77+
return true, nil
78+
}
79+
}
80+
return false, nil
81+
}
82+
83+
// OldestModTime recurses a list of target filesystem objects and finds the
84+
// the oldest ModTime among them.
85+
func OldestModTime(targets ...string) (time.Time, error) {
86+
t := time.Now().Add(time.Hour * 100000)
87+
for _, target := range targets {
88+
walkFn := func(_ string, info os.FileInfo, err error) error {
89+
if err != nil {
90+
return err
91+
}
92+
mTime := info.ModTime()
93+
if mTime.Before(t) {
94+
t = mTime
95+
}
96+
return nil
97+
}
98+
if err := filepath.Walk(target, walkFn); err != nil {
99+
return t, err
100+
}
101+
}
102+
return t, nil
103+
}
104+
105+
// NewestModTime recurses a list of target filesystem objects and finds the
106+
// the newest ModTime among them.
107+
func NewestModTime(targets ...string) (time.Time, error) {
108+
t := time.Time{}
109+
for _, target := range targets {
110+
walkFn := func(_ string, info os.FileInfo, err error) error {
111+
if err != nil {
112+
return err
113+
}
114+
mTime := info.ModTime()
115+
if mTime.After(t) {
116+
t = mTime
117+
}
118+
return nil
119+
}
120+
if err := filepath.Walk(target, walkFn); err != nil {
121+
return t, err
122+
}
123+
}
124+
return t, nil
125+
}

target/newer_test.go

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
package target
2+
3+
import (
4+
"io/ioutil"
5+
"os"
6+
"path/filepath"
7+
"testing"
8+
"time"
9+
)
10+
11+
func TestNewestModTime(t *testing.T) {
12+
t.Parallel()
13+
dir, err := ioutil.TempDir("", "")
14+
if err != nil {
15+
t.Fatalf("error creating temp dir: %s", err.Error())
16+
}
17+
defer os.RemoveAll(dir)
18+
for _, name := range []string{"a", "b", "c", "d"} {
19+
out := filepath.Join(dir, name)
20+
if err := ioutil.WriteFile(out, []byte("hi!"), 0644); err != nil {
21+
t.Fatalf("error writing file: %s", err.Error())
22+
}
23+
}
24+
time.Sleep(10 * time.Millisecond)
25+
outName := filepath.Join(dir, "c")
26+
outfh, err := os.OpenFile(outName, os.O_APPEND|os.O_WRONLY, 0644)
27+
if err != nil {
28+
t.Fatalf("error opening file to append: %s", err.Error())
29+
}
30+
if _, err := outfh.WriteString("\nbye!\n"); err != nil {
31+
t.Fatalf("error appending to file: %s", err.Error())
32+
}
33+
if err := outfh.Close(); err != nil {
34+
t.Fatalf("error closing file: %s", err.Error())
35+
}
36+
37+
afi, err := os.Stat(filepath.Join(dir, "a"))
38+
if err != nil {
39+
t.Fatalf("error stating unmodified file: %s", err.Error())
40+
}
41+
42+
cfi, err := os.Stat(outName)
43+
if err != nil {
44+
t.Fatalf("error stating modified file: %s", err.Error())
45+
}
46+
if afi.ModTime().Equal(cfi.ModTime()) {
47+
t.Fatal("modified and unmodified file mtimes equal")
48+
}
49+
50+
newest, err := NewestModTime(dir)
51+
if err != nil {
52+
t.Fatalf("error finding newest mod time: %s", err.Error())
53+
}
54+
if !newest.Equal(cfi.ModTime()) {
55+
t.Fatal("expected newest mod time to match c")
56+
}
57+
}
58+
59+
func TestOldestModTime(t *testing.T) {
60+
t.Parallel()
61+
dir, err := ioutil.TempDir("", "")
62+
if err != nil {
63+
t.Fatalf("error creating temp dir: %s", err.Error())
64+
}
65+
defer os.RemoveAll(dir)
66+
for _, name := range []string{"a", "b", "c", "d"} {
67+
out := filepath.Join(dir, name)
68+
if err := ioutil.WriteFile(out, []byte("hi!"), 0644); err != nil {
69+
t.Fatalf("error writing file: %s", err.Error())
70+
}
71+
}
72+
time.Sleep(10 * time.Millisecond)
73+
for _, name := range []string{"a", "b", "d"} {
74+
outName := filepath.Join(dir, name)
75+
outfh, err := os.OpenFile(outName, os.O_APPEND|os.O_WRONLY, 0644)
76+
if err != nil {
77+
t.Fatalf("error opening file to append: %s", err.Error())
78+
}
79+
if _, err := outfh.WriteString("\nbye!\n"); err != nil {
80+
t.Fatalf("error appending to file: %s", err.Error())
81+
}
82+
if err := outfh.Close(); err != nil {
83+
t.Fatalf("error closing file: %s", err.Error())
84+
}
85+
}
86+
87+
afi, err := os.Stat(filepath.Join(dir, "a"))
88+
if err != nil {
89+
t.Fatalf("error stating unmodified file: %s", err.Error())
90+
}
91+
92+
outName := filepath.Join(dir, "c")
93+
cfi, err := os.Stat(outName)
94+
if err != nil {
95+
t.Fatalf("error stating modified file: %s", err.Error())
96+
}
97+
if afi.ModTime().Equal(cfi.ModTime()) {
98+
t.Fatal("modified and unmodified file mtimes equal")
99+
}
100+
101+
newest, err := OldestModTime(dir)
102+
if err != nil {
103+
t.Fatalf("error finding oldest mod time: %s", err.Error())
104+
}
105+
if !newest.Equal(cfi.ModTime()) {
106+
t.Fatal("expected newest mod time to match c")
107+
}
108+
}

0 commit comments

Comments
 (0)