Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

target: rework modtime comparison logic #323 #324

Merged
merged 4 commits into from
Dec 22, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 125 additions & 0 deletions target/newer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package target

import (
"fmt"
"os"
"path/filepath"
"time"
)

var (
// errNewer is an ugly sentinel error to cause filepath.Walk to abort
// as soon as a newer file is encountered
errNewer = fmt.Errorf("newer item encountered")
)

// DirNewer reports whether any item in sources is newer than the target time.
// Sources are searched recursively and searching stops as soon as any entry
// is newer than the target.
func DirNewer(target time.Time, sources ...string) (bool, error) {
walkFn := func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.ModTime().After(target) {
return errNewer
}
return nil
}
for _, source := range sources {
source = os.ExpandEnv(source)
err := filepath.Walk(source, walkFn)
if err == nil {
continue
}
if err == errNewer {
return true, nil
}
return false, err
}
return false, nil
}

// GlobNewer performs glob expansion on each source and passes the results to
// PathNewer for inspection. It returns the first time PathNewer encounters a
// newer file
func GlobNewer(target time.Time, sources ...string) (bool, error) {
for _, g := range sources {
files, err := filepath.Glob(g)
if err != nil {
return false, err
}
if len(files) == 0 {
return false, fmt.Errorf("glob didn't match any files: %s", g)
}
newer, err := PathNewer(target, files...)
if err != nil {
return false, err
}
if newer {
return true, nil
}
}
return false, nil
}

// PathNewer checks whether any of the sources are newer than the target time.
// It stops at the first newer file it encounters. Each source path is passed
// through os.ExpandEnv.
func PathNewer(target time.Time, sources ...string) (bool, error) {
for _, source := range sources {
source = os.ExpandEnv(source)
stat, err := os.Stat(source)
if err != nil {
return false, err
}
if stat.ModTime().After(target) {
return true, nil
}
}
return false, nil
}

// OldestModTime recurses a list of target filesystem objects and finds the
// the oldest ModTime among them.
func OldestModTime(targets ...string) (time.Time, error) {
t := time.Now().Add(time.Hour * 100000)
for _, target := range targets {
walkFn := func(_ string, info os.FileInfo, err error) error {
if err != nil {
return err
}
mTime := info.ModTime()
if mTime.Before(t) {
t = mTime
}
return nil
}
if err := filepath.Walk(target, walkFn); err != nil {
return t, err
}
}
return t, nil
}

// NewestModTime recurses a list of target filesystem objects and finds the
// the newest ModTime among them.
func NewestModTime(targets ...string) (time.Time, error) {
t := time.Time{}
for _, target := range targets {
walkFn := func(_ string, info os.FileInfo, err error) error {
if err != nil {
return err
}
mTime := info.ModTime()
if mTime.After(t) {
t = mTime
}
return nil
}
if err := filepath.Walk(target, walkFn); err != nil {
return t, err
}
}
return t, nil
}
108 changes: 108 additions & 0 deletions target/newer_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package target

import (
"io/ioutil"
"os"
"path/filepath"
"testing"
"time"
)

func TestNewestModTime(t *testing.T) {
t.Parallel()
dir, err := ioutil.TempDir("", "")
if err != nil {
t.Fatalf("error creating temp dir: %s", err.Error())
}
defer os.RemoveAll(dir)
for _, name := range []string{"a", "b", "c", "d"} {
out := filepath.Join(dir, name)
if err := ioutil.WriteFile(out, []byte("hi!"), 0644); err != nil {
t.Fatalf("error writing file: %s", err.Error())
}
}
time.Sleep(10 * time.Millisecond)
outName := filepath.Join(dir, "c")
outfh, err := os.OpenFile(outName, os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
t.Fatalf("error opening file to append: %s", err.Error())
}
if _, err := outfh.WriteString("\nbye!\n"); err != nil {
t.Fatalf("error appending to file: %s", err.Error())
}
if err := outfh.Close(); err != nil {
t.Fatalf("error closing file: %s", err.Error())
}

afi, err := os.Stat(filepath.Join(dir, "a"))
if err != nil {
t.Fatalf("error stating unmodified file: %s", err.Error())
}

cfi, err := os.Stat(outName)
if err != nil {
t.Fatalf("error stating modified file: %s", err.Error())
}
if afi.ModTime().Equal(cfi.ModTime()) {
t.Fatal("modified and unmodified file mtimes equal")
}

newest, err := NewestModTime(dir)
if err != nil {
t.Fatalf("error finding newest mod time: %s", err.Error())
}
if !newest.Equal(cfi.ModTime()) {
t.Fatal("expected newest mod time to match c")
}
}

func TestOldestModTime(t *testing.T) {
t.Parallel()
dir, err := ioutil.TempDir("", "")
if err != nil {
t.Fatalf("error creating temp dir: %s", err.Error())
}
defer os.RemoveAll(dir)
for _, name := range []string{"a", "b", "c", "d"} {
out := filepath.Join(dir, name)
if err := ioutil.WriteFile(out, []byte("hi!"), 0644); err != nil {
t.Fatalf("error writing file: %s", err.Error())
}
}
time.Sleep(10 * time.Millisecond)
for _, name := range []string{"a", "b", "d"} {
outName := filepath.Join(dir, name)
outfh, err := os.OpenFile(outName, os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
t.Fatalf("error opening file to append: %s", err.Error())
}
if _, err := outfh.WriteString("\nbye!\n"); err != nil {
t.Fatalf("error appending to file: %s", err.Error())
}
if err := outfh.Close(); err != nil {
t.Fatalf("error closing file: %s", err.Error())
}
}

afi, err := os.Stat(filepath.Join(dir, "a"))
if err != nil {
t.Fatalf("error stating unmodified file: %s", err.Error())
}

outName := filepath.Join(dir, "c")
cfi, err := os.Stat(outName)
if err != nil {
t.Fatalf("error stating modified file: %s", err.Error())
}
if afi.ModTime().Equal(cfi.ModTime()) {
t.Fatal("modified and unmodified file mtimes equal")
}

newest, err := OldestModTime(dir)
if err != nil {
t.Fatalf("error finding oldest mod time: %s", err.Error())
}
if !newest.Equal(cfi.ModTime()) {
t.Fatal("expected newest mod time to match c")
}
}
Loading