From 37060d602b208865bcba80859512f93867a9974c Mon Sep 17 00:00:00 2001 From: Aleksa Sarai Date: Sat, 23 Jul 2016 13:44:16 +1000 Subject: [PATCH] check: implement "missing" failure type If a file is missing, previously the check would simply fail. This allows you to get information even if files are missing from the tree. This also implements some of the wrappers required to add more failure states. Signed-off-by: Aleksa Sarai --- check.go | 124 ++++++++++++++++++++++++++++++++++++++++---- cmd/gomtree/main.go | 2 +- 2 files changed, 114 insertions(+), 12 deletions(-) diff --git a/check.go b/check.go index 87daf54..17be735 100644 --- a/check.go +++ b/check.go @@ -1,6 +1,7 @@ package mtree import ( + "encoding/json" "fmt" "os" "path/filepath" @@ -13,17 +14,108 @@ type Result struct { Failures []Failure `json:"failures"` } -// Failure of a particular keyword for a path -type Failure struct { - Path string `json:"path"` - Keyword string `json:"keyword"` - Expected string `json:"expected"` - Got string `json:"got"` +// FailureType represents a type of Failure encountered when checking for +// discrepancies between a manifest and a directory state. +type FailureType string + +const ( + // None means no discrepancy (unused). + None FailureType = "none" + + // Missing represents a discrepancy where an object is present in the + // manifest but is not present in the directory state being checked. + Missing FailureType = "missing" + + // Modified represents a discrepancy where one or more of the keywords + // present in the manifest do not match the keywords generated for the same + // object in the directory state being checked. + Modified FailureType = "modified" +) + +// Failure represents a discrepancy between a manifest and the state it is +// being compared against. The discrepancy may take the form of a missing, +// erroneous or otherwise modified object. +type Failure interface { + // String returns a "pretty" formatting for a Failure. It's based on the + // BSD mtree(8) format, so that we are compatible. + String() string + + // MarshalJSON ensures that we fulfil the JSON Marshaler interface. + MarshalJSON() ([]byte, error) + + // Path returns the path (relative to the root of the tree) that this + // discrepancy refers to. + Path() string + + // Type returns the type of failure that occurred. + Type() FailureType } -// String returns a "pretty" formatting for a Failure -func (f Failure) String() string { - return fmt.Sprintf("%q: keyword %q: expected %s; got %s", f.Path, f.Keyword, f.Expected, f.Got) +// An object modified from the manifest state. +type modified struct { + path string + keyword string + expected string + got string +} + +func (m modified) String() string { + return fmt.Sprintf("%q: keyword %q: expected %s; got %s", m.path, m.keyword, m.expected, m.got) +} + +func (m modified) MarshalJSON() ([]byte, error) { + // Because of Go's reflection policies, we have to make an anonymous struct + // with the fields exported. + return json.Marshal(struct { + Type FailureType `json:"type"` + Path string `json:"path"` + Keyword string `json:"keyword"` + Expected string `json:"expected"` + Got string `json:"got"` + }{ + Type: m.Type(), + Path: m.path, + Keyword: m.keyword, + Expected: m.expected, + Got: m.got, + }) +} + +func (m modified) Path() string { + return m.path +} + +func (m modified) Type() FailureType { + return Modified +} + +// An object that is listed in the manifest but is not present in the state. +type missing struct { + path string +} + +func (m missing) String() string { + return fmt.Sprintf("%q: expected object missing", m.path) +} + +func (m missing) MarshalJSON() ([]byte, error) { + // Because of Go's reflection policies, we have to make an anonymous struct + // with the fields exported. + return json.Marshal(struct { + Type FailureType `json:"type"` + Path string `json:"path"` + }{ + Type: m.Type(), + Path: m.path, + }) +} + +func (m missing) Path() string { + return m.path +} + +func (m missing) Type() FailureType { + return Missing } // Check a root directory path against the DirectoryHierarchy, regarding only @@ -53,6 +145,12 @@ func Check(root string, dh *DirectoryHierarchy, keywords []string) (*Result, err case RelativeType, FullType: info, err := os.Lstat(e.Path()) if err != nil { + if os.IsNotExist(err) { + result.Failures = append(result.Failures, missing{ + path: e.Path(), + }) + continue + } return nil, err } @@ -76,8 +174,12 @@ func Check(root string, dh *DirectoryHierarchy, keywords []string) (*Result, err return nil, err } if string(kv) != curKeyVal { - failure := Failure{Path: e.Path(), Keyword: kv.Keyword(), Expected: kv.Value(), Got: KeyVal(curKeyVal).Value()} - result.Failures = append(result.Failures, failure) + result.Failures = append(result.Failures, modified{ + path: e.Path(), + keyword: kv.Keyword(), + expected: kv.Value(), + got: KeyVal(curKeyVal).Value(), + }) } } } diff --git a/cmd/gomtree/main.go b/cmd/gomtree/main.go index a3fa228..46a2f6c 100644 --- a/cmd/gomtree/main.go +++ b/cmd/gomtree/main.go @@ -45,7 +45,7 @@ var formats = map[string]func(*mtree.Result) string{ "text": func(r *mtree.Result) string { var buffer bytes.Buffer for _, fail := range r.Failures { - fmt.Fprintf(&buffer, "modified\t%s\n", fail.Path) + fmt.Fprintf(&buffer, "%s\t%s\n", fail.Type(), fail.Path()) } return buffer.String() },