diff --git a/internal/laboratory/traverse-fs.go b/internal/laboratory/traverse-fs.go index 2ad2817..678253d 100644 --- a/internal/laboratory/traverse-fs.go +++ b/internal/laboratory/traverse-fs.go @@ -63,9 +63,13 @@ func (f *TestTraverseFS) MkDirAll(name string, perm os.FileMode) error { func(acc []string, s string, _ int) []string { acc = append(acc, s) path := strings.Join(acc, "/") - f.MapFS[path] = &fstest.MapFile{ - Mode: perm | os.ModeDir, + + if _, found := f.MapFS[path]; !found { + f.MapFS[path] = &fstest.MapFile{ + Mode: perm | os.ModeDir, + } } + return acc }, []string{}, ) diff --git a/internal/opts/json/sampling-options.go b/internal/opts/json/sampling-options.go index ce82a8f..4ffc711 100644 --- a/internal/opts/json/sampling-options.go +++ b/internal/opts/json/sampling-options.go @@ -19,7 +19,7 @@ type ( // InReverse determines the direction of iteration for the sampling // operation - InReverse bool `json:"sample-in-reverse"` + InReverse bool `json:"in-reverse"` // NoOf specifies number of items required in each sample (only applies // when not using Custom iterator options) diff --git a/internal/persist/convert-json_test.go b/internal/persist/convert-json_test.go index ea394f7..7516a1b 100644 --- a/internal/persist/convert-json_test.go +++ b/internal/persist/convert-json_test.go @@ -32,7 +32,7 @@ var _ = Describe("Convert Options via JSON", Ordered, func() { }, } - _ = FS.MkDirAll(to, permDir|os.ModeDir) + _ = FS.MkDirAll(destination, perms.Dir|os.ModeDir) }) Context("ToJSON", func() { diff --git a/internal/persist/data/test-restore.DEFAULT.json b/internal/persist/data/test-restore.DEFAULT.json new file mode 100644 index 0000000..752057b --- /dev/null +++ b/internal/persist/data/test-restore.DEFAULT.json @@ -0,0 +1,47 @@ +{ + "JO": { + "Behaviours": { + "SubPath": { + "KeepTrailingSep": true + }, + "Sort": { + "IsCaseSensitive": false, + "SortFilesFirst": false + }, + "Cascade": { + "Depth": 0, + "NoRecurse": false + } + }, + "Sampling": { + "sample-type": 0, + "in-reverse": false, + "NoOf": { + "no-of-files": 0, + "no-of-folders": 0 + } + }, + "Filter": { + "Node": null, + "Child": null, + "Sample": null + }, + "Hibernate": { + "WakeAt": null, + "SleepAt": null, + "Behaviour": { + "hibernate-inclusive-wake": true, + "hibernate-inclusive-sleep": false + } + }, + "Concurrency": { + "no-of-workers": 8 + } + }, + "Active": { + "Root": "json/marshal", + "Hibernation": 1, + "CurrentPath": "/top/a/b/c", + "Depth": 3 + } +} \ No newline at end of file diff --git a/internal/persist/equals.go b/internal/persist/equals.go index 8b844e7..2da1fb0 100644 --- a/internal/persist/equals.go +++ b/internal/persist/equals.go @@ -43,14 +43,14 @@ func (UnequalPtrError[T, O]) Unwrap() error { } // Equals compare the pref.Options instance to the derived json instance json.Options. -// We can't use DeepEquals because, on the structs, because even though the structs -// may have te same members, DeepEqual will still fail because the host struct is +// We can't use DeepEquals on the structs, because even though the structs +// may have the same members, DeepEqual will still fail because the host struct is // different; eg: pref.NavigationBehaviours and json.NavigationBehaviours contain // the same members, but they are different structs; which means comparison has to be // done manually. -func Equals(o *pref.Options, jo *json.Options) (bool, error) { +func Equals(o *pref.Options, jo *json.Options) error { if o == nil { - return false, fmt.Errorf("pref.Options %w", + return fmt.Errorf("pref.Options %w", UnequalPtrError[pref.Options, json.Options]{ Field: "[nil pref.Options]", Value: o, @@ -60,7 +60,7 @@ func Equals(o *pref.Options, jo *json.Options) (bool, error) { } if jo == nil { - return false, fmt.Errorf("json.Options %w", + return fmt.Errorf("json.Options %w", UnequalPtrError[pref.Options, json.Options]{ Field: "[nil json.Options]", Value: o, @@ -69,28 +69,32 @@ func Equals(o *pref.Options, jo *json.Options) (bool, error) { ) } - if equal, err := equalBehaviours(&o.Behaviours, &jo.Behaviours); !equal { - return false, err + if err := equalBehaviours(&o.Behaviours, &jo.Behaviours); err != nil { + return err } - if equal, err := equalSampling(&o.Sampling, &jo.Sampling); !equal { - return false, err + if err := equalSamplingOptions(&o.Sampling, &jo.Sampling); err != nil { + return err } - if equal, err := equalFilterOptions(&o.Filter, &jo.Filter); !equal { - return false, err + if err := equalFilterOptions(&o.Filter, &jo.Filter); err != nil { + return err } - if equal, err := equalFilterDef("wake-at", o.Hibernate.WakeAt, jo.Hibernate.WakeAt); !equal { - return equal, err + if err := equalFilterDef("wake-at", + o.Hibernate.WakeAt, jo.Hibernate.WakeAt, + ); err != nil { + return err } - if equal, err := equalFilterDef("sleep-at", o.Hibernate.SleepAt, jo.Hibernate.SleepAt); !equal { - return equal, err + if err := equalFilterDef("sleep-at", + o.Hibernate.SleepAt, jo.Hibernate.SleepAt, + ); err != nil { + return err } if o.Hibernate.Behaviour.InclusiveWake != jo.Hibernate.Behaviour.InclusiveWake { - return false, fmt.Errorf("hibernate-behaviour %w", UnequalValueError[bool]{ + return fmt.Errorf("hibernate-behaviour %w", UnequalValueError[bool]{ Field: "InclusiveWake", Value: o.Hibernate.Behaviour.InclusiveWake, Other: jo.Hibernate.Behaviour.InclusiveWake, @@ -98,7 +102,7 @@ func Equals(o *pref.Options, jo *json.Options) (bool, error) { } if o.Hibernate.Behaviour.InclusiveSleep != jo.Hibernate.Behaviour.InclusiveSleep { - return false, fmt.Errorf("hibernate-behaviour %w", UnequalValueError[bool]{ + return fmt.Errorf("hibernate-behaviour %w", UnequalValueError[bool]{ Field: "InclusiveSleep", Value: o.Hibernate.Behaviour.InclusiveSleep, Other: jo.Hibernate.Behaviour.InclusiveSleep, @@ -106,35 +110,43 @@ func Equals(o *pref.Options, jo *json.Options) (bool, error) { } if o.Concurrency.NoW != jo.Concurrency.NoW { - return false, fmt.Errorf("concurrency %w", UnequalValueError[uint]{ + return fmt.Errorf("concurrency %w", UnequalValueError[uint]{ Field: "NoW", Value: o.Concurrency.NoW, Other: jo.Concurrency.NoW, }) } - return true, nil + return nil } -func equalBehaviours(o *pref.NavigationBehaviours, jo *json.NavigationBehaviours) (bool, error) { +func equalBehaviours(o *pref.NavigationBehaviours, jo *json.NavigationBehaviours) error { if o.SubPath.KeepTrailingSep != jo.SubPath.KeepTrailingSep { - return false, fmt.Errorf("subPath %w", UnequalValueError[bool]{ - Field: "SubPath", + return fmt.Errorf("subPath %w", UnequalValueError[bool]{ + Field: "KeepTrailingSep", Value: o.SubPath.KeepTrailingSep, Other: jo.SubPath.KeepTrailingSep, }) } if o.Sort.IsCaseSensitive != jo.Sort.IsCaseSensitive { - return false, fmt.Errorf("sort %w", UnequalValueError[bool]{ + return fmt.Errorf("sort %w", UnequalValueError[bool]{ Field: "IsCaseSensitive", Value: o.Sort.IsCaseSensitive, Other: jo.Sort.IsCaseSensitive, }) } + if o.Sort.SortFilesFirst != jo.Sort.SortFilesFirst { + return fmt.Errorf("sort %w", UnequalValueError[bool]{ + Field: "SortFilesFirst", + Value: o.Sort.SortFilesFirst, + Other: jo.Sort.SortFilesFirst, + }) + } + if o.Cascade.Depth != jo.Cascade.Depth { - return false, fmt.Errorf("cascade %w", UnequalValueError[uint]{ + return fmt.Errorf("cascade %w", UnequalValueError[uint]{ Field: "Depth", Value: o.Cascade.Depth, Other: jo.Cascade.Depth, @@ -142,35 +154,37 @@ func equalBehaviours(o *pref.NavigationBehaviours, jo *json.NavigationBehaviours } if o.Cascade.NoRecurse != jo.Cascade.NoRecurse { - return false, fmt.Errorf("cascade %w", UnequalValueError[bool]{ + return fmt.Errorf("cascade %w", UnequalValueError[bool]{ Field: "NoRecurse", Value: o.Cascade.NoRecurse, Other: jo.Cascade.NoRecurse, }) } - return true, nil + // sort behaviour?? + + return nil } -func equalSampling(o *pref.SamplingOptions, jo *json.SamplingOptions) (bool, error) { +func equalSamplingOptions(o *pref.SamplingOptions, jo *json.SamplingOptions) error { if o.Type != jo.Type { - return false, fmt.Errorf("sampling %w", UnequalValueError[enums.SampleType]{ - Field: "SampleType", + return fmt.Errorf("sampling %w", UnequalValueError[enums.SampleType]{ + Field: "Type", Value: o.Type, Other: jo.Type, }) } if o.InReverse != jo.InReverse { - return false, fmt.Errorf("sampling %w", UnequalValueError[bool]{ - Field: "SampleInReverse", + return fmt.Errorf("sampling %w", UnequalValueError[bool]{ + Field: "InReverse", Value: o.InReverse, Other: jo.InReverse, }) } if o.NoOf.Files != jo.NoOf.Files { - return false, fmt.Errorf("sampling.noOf %w", UnequalValueError[uint]{ + return fmt.Errorf("sampling.noOf %w", UnequalValueError[uint]{ Field: "Files", Value: o.NoOf.Files, Other: jo.NoOf.Files, @@ -178,41 +192,41 @@ func equalSampling(o *pref.SamplingOptions, jo *json.SamplingOptions) (bool, err } if o.NoOf.Folders != jo.NoOf.Folders { - return false, fmt.Errorf("sampling.noOf %w", UnequalValueError[uint]{ + return fmt.Errorf("sampling.noOf %w", UnequalValueError[uint]{ Field: "Folders", Value: o.NoOf.Folders, Other: jo.NoOf.Folders, }) } - return true, nil + return nil } -func equalFilterOptions(o *pref.FilterOptions, jo *json.FilterOptions) (bool, error) { - if equal, err := equalFilterDef("node", o.Node, jo.Node); !equal { - return equal, err +func equalFilterOptions(o *pref.FilterOptions, jo *json.FilterOptions) error { + if err := equalFilterDef("node", o.Node, jo.Node); err != nil { + return err } - if equal, err := equalChildFilterDef("child", o.Child, jo.Child); !equal { - return equal, err + if err := equalChildFilterDef("child", o.Child, jo.Child); err != nil { + return err } - if equal, err := equalSampleFilterDef("sample-filter", o.Sample, jo.Sample); !equal { - return equal, err + if err := equalSampleFilterDef("sample-filter", o.Sample, jo.Sample); err != nil { + return err } - return true, nil + return nil } func equalFilterDef(filterName string, def *core.FilterDef, jdef *json.FilterDef, -) (bool, error) { +) error { if def == nil && jdef == nil { - return true, nil + return nil } if def == nil && jdef != nil { - return false, fmt.Errorf("filter-def %w", + return fmt.Errorf("filter-def %w", UnequalPtrError[core.FilterDef, json.FilterDef]{ Field: "[nil def]", Value: def, @@ -222,7 +236,7 @@ func equalFilterDef(filterName string, } if def != nil && jdef == nil { - return false, fmt.Errorf("json-filter-def %w", + return fmt.Errorf("json-filter-def %w", UnequalPtrError[core.FilterDef, json.FilterDef]{ Field: "[nil jdef]", Value: def, @@ -232,7 +246,7 @@ func equalFilterDef(filterName string, } if def.Type != jdef.Type { - return false, fmt.Errorf("%q filter-def %w", filterName, + return fmt.Errorf("%q filter-def %w", filterName, UnequalValueError[enums.FilterType]{ Field: "Type", Value: def.Type, @@ -242,7 +256,7 @@ func equalFilterDef(filterName string, } if def.Description != jdef.Description { - return false, fmt.Errorf("%q filter-def %w", filterName, + return fmt.Errorf("%q filter-def %w", filterName, UnequalValueError[string]{ Field: "Description", Value: def.Description, @@ -252,7 +266,7 @@ func equalFilterDef(filterName string, } if def.Pattern != jdef.Pattern { - return false, fmt.Errorf("%q filter-def %w", filterName, + return fmt.Errorf("%q filter-def %w", filterName, UnequalValueError[string]{ Field: "Pattern", Value: def.Pattern, @@ -262,7 +276,7 @@ func equalFilterDef(filterName string, } if def.Scope != jdef.Scope { - return false, fmt.Errorf("%q filter-def %w", filterName, + return fmt.Errorf("%q filter-def %w", filterName, UnequalValueError[enums.FilterScope]{ Field: "Scope", Value: def.Scope, @@ -272,7 +286,7 @@ func equalFilterDef(filterName string, } if def.Negate != jdef.Negate { - return false, fmt.Errorf("%q filter-def %w", filterName, + return fmt.Errorf("%q filter-def %w", filterName, UnequalValueError[bool]{ Field: "Negate", Value: def.Negate, @@ -282,7 +296,7 @@ func equalFilterDef(filterName string, } if def.IfNotApplicable != jdef.IfNotApplicable { - return false, fmt.Errorf("%q filter-def %w", filterName, + return fmt.Errorf("%q filter-def %w", filterName, UnequalValueError[enums.TriStateBool]{ Field: "IfNotApplicable", Value: def.IfNotApplicable, @@ -292,27 +306,27 @@ func equalFilterDef(filterName string, } if def.Poly != nil && jdef.Poly != nil { - if equal, err := equalFilterDef("poly", &def.Poly.File, &jdef.Poly.File); !equal { - return equal, err + if err := equalFilterDef("poly", &def.Poly.File, &jdef.Poly.File); err != nil { + return err } - if equal, err := equalFilterDef("poly", &def.Poly.Folder, &jdef.Poly.Folder); !equal { - return equal, err + if err := equalFilterDef("poly", &def.Poly.Folder, &jdef.Poly.Folder); err != nil { + return err } } - return true, nil + return nil } func equalChildFilterDef(filterName string, def *core.ChildFilterDef, jdef *json.ChildFilterDef, -) (bool, error) { +) error { if def == nil && jdef == nil { - return true, nil + return nil } if def == nil && jdef != nil { - return false, fmt.Errorf("filter-def %w", + return fmt.Errorf("filter-def %w", UnequalPtrError[core.ChildFilterDef, json.ChildFilterDef]{ Field: "[nil def]", Value: def, @@ -322,7 +336,7 @@ func equalChildFilterDef(filterName string, } if def != nil && jdef == nil { - return false, fmt.Errorf("filter-def %w", + return fmt.Errorf("filter-def %w", UnequalPtrError[core.ChildFilterDef, json.ChildFilterDef]{ Field: "[nil jdef]", Value: def, @@ -332,7 +346,7 @@ func equalChildFilterDef(filterName string, } if def.Type != jdef.Type { - return false, fmt.Errorf("%q child-filter-def %w", filterName, + return fmt.Errorf("%q child-filter-def %w", filterName, UnequalValueError[enums.FilterType]{ Field: "Type", Value: def.Type, @@ -342,7 +356,7 @@ func equalChildFilterDef(filterName string, } if def.Description != jdef.Description { - return false, fmt.Errorf("%q child-filter-def %w", filterName, + return fmt.Errorf("%q child-filter-def %w", filterName, UnequalValueError[string]{ Field: "Description", Value: def.Description, @@ -352,7 +366,7 @@ func equalChildFilterDef(filterName string, } if def.Pattern != jdef.Pattern { - return false, fmt.Errorf("%q child-filter-def %w", filterName, + return fmt.Errorf("%q child-filter-def %w", filterName, UnequalValueError[string]{ Field: "Pattern", Value: def.Pattern, @@ -362,7 +376,7 @@ func equalChildFilterDef(filterName string, } if def.Negate != jdef.Negate { - return false, fmt.Errorf("%q child-filter-def %w", filterName, + return fmt.Errorf("%q child-filter-def %w", filterName, UnequalValueError[bool]{ Field: "Negate", Value: def.Negate, @@ -371,18 +385,18 @@ func equalChildFilterDef(filterName string, ) } - return true, nil + return nil } func equalSampleFilterDef(filterName string, def *core.SampleFilterDef, jdef *json.SampleFilterDef, -) (bool, error) { +) error { if def == nil && jdef == nil { - return true, nil + return nil } if def == nil && jdef != nil { - return false, fmt.Errorf("filter-def %w", + return fmt.Errorf("filter-def %w", UnequalPtrError[core.SampleFilterDef, json.SampleFilterDef]{ Field: "[nil def]", Value: def, @@ -392,7 +406,7 @@ func equalSampleFilterDef(filterName string, } if def != nil && jdef == nil { - return false, fmt.Errorf("filter-def %w", + return fmt.Errorf("filter-def %w", UnequalPtrError[core.SampleFilterDef, json.SampleFilterDef]{ Field: "[nil jdef]", Value: def, @@ -402,7 +416,7 @@ func equalSampleFilterDef(filterName string, } if def.Type != jdef.Type { - return false, fmt.Errorf("%q sample-filter-def %w", filterName, + return fmt.Errorf("%q sample-filter-def %w", filterName, UnequalValueError[enums.FilterType]{ Field: "Type", Value: def.Type, @@ -412,7 +426,7 @@ func equalSampleFilterDef(filterName string, } if def.Description != jdef.Description { - return false, fmt.Errorf("%q sample-filter-def %w", filterName, + return fmt.Errorf("%q sample-filter-def %w", filterName, UnequalValueError[string]{ Field: "Description", Value: def.Description, @@ -422,7 +436,7 @@ func equalSampleFilterDef(filterName string, } if def.Pattern != jdef.Pattern { - return false, fmt.Errorf("%q sample-filter-def %w", filterName, + return fmt.Errorf("%q sample-filter-def %w", filterName, UnequalValueError[string]{ Field: "Pattern", Value: def.Pattern, @@ -432,7 +446,7 @@ func equalSampleFilterDef(filterName string, } if def.Scope != jdef.Scope { - return false, fmt.Errorf("%q sample-filter-def %w", filterName, + return fmt.Errorf("%q sample-filter-def %w", filterName, UnequalValueError[enums.FilterScope]{ Field: "Scope", Value: def.Scope, @@ -442,7 +456,7 @@ func equalSampleFilterDef(filterName string, } if def.Negate != jdef.Negate { - return false, fmt.Errorf("%q sample-filter-def %w", filterName, + return fmt.Errorf("%q sample-filter-def %w", filterName, UnequalValueError[bool]{ Field: "Negate", Value: def.Negate, @@ -451,5 +465,15 @@ func equalSampleFilterDef(filterName string, ) } - return true, nil + if def.Poly != nil && jdef.Poly != nil { + if err := equalFilterDef("poly", &def.Poly.File, &jdef.Poly.File); err != nil { + return err + } + + if err := equalFilterDef("poly", &def.Poly.Folder, &jdef.Poly.Folder); err != nil { + return err + } + } + + return nil } diff --git a/internal/persist/json-matcher_test.go b/internal/persist/json-matcher_test.go index bc3888c..aa97ddd 100644 --- a/internal/persist/json-matcher_test.go +++ b/internal/persist/json-matcher_test.go @@ -28,9 +28,9 @@ func (m *MarshalJSONMatcher) Match(actual interface{}) (bool, error) { return false, fmt.Errorf("โŒ matcher expected a *json.Options instance (%T)", jo) } - if equal, err := persist.Equals(m.o, jo); !equal { + if err := persist.Equals(m.o, jo); err != nil { m.err = err - return equal, err + return false, err } return true, nil @@ -62,9 +62,9 @@ func (m *UnMarshalJSONMatcher) Match(actual interface{}) (bool, error) { return false, fmt.Errorf("โŒ matcher expected a *pref.Options instance (%T)", o) } - if equal, err := persist.Equals(o, m.jo); !equal { + if err := persist.Equals(o, m.jo); err != nil { m.err = err - return equal, err + return false, err } return true, nil diff --git a/internal/persist/json-options.go b/internal/persist/json-options.go index fbde0a2..52451d1 100644 --- a/internal/persist/json-options.go +++ b/internal/persist/json-options.go @@ -155,7 +155,9 @@ func FromJSON(o *json.Options) *pref.Options { Type: o.Filter.Sample.Type, Description: o.Filter.Sample.Description, Pattern: o.Filter.Sample.Pattern, - Scope: o.Filter.Node.Scope, + Scope: o.Filter.Sample.Scope, + Negate: o.Filter.Sample.Negate, + // Poly: tbd, } }, func() *core.SampleFilterDef { @@ -181,10 +183,13 @@ func NodeFilterDefFromJSON(def *json.FilterDef) *core.FilterDef { return lo.TernaryF(def != nil, func() *core.FilterDef { return &core.FilterDef{ - Type: def.Type, - Description: def.Description, - Pattern: def.Pattern, - Negate: def.Negate, + Type: def.Type, + Description: def.Description, + Pattern: def.Pattern, + Negate: def.Negate, + Scope: def.Scope, + IfNotApplicable: def.IfNotApplicable, + Poly: NodePolyDefFromJSON(def.Poly), } }, func() *core.FilterDef { @@ -192,3 +197,14 @@ func NodeFilterDefFromJSON(def *json.FilterDef) *core.FilterDef { }, ) } + +func NodePolyDefFromJSON(poly *json.PolyFilterDef) *core.PolyFilterDef { + if poly == nil { + return nil + } + + return &core.PolyFilterDef{ + File: *NodeFilterDefFromJSON(&poly.File), + Folder: *NodeFilterDefFromJSON(&poly.Folder), + } +} diff --git a/internal/persist/marshaler-filters_test.go b/internal/persist/marshaler-filters_test.go new file mode 100644 index 0000000..0d601d3 --- /dev/null +++ b/internal/persist/marshaler-filters_test.go @@ -0,0 +1,736 @@ +package persist_test + +import ( + "fmt" + "os" + "testing/fstest" + + . "github.com/onsi/ginkgo/v2" //nolint:revive // ok + . "github.com/onsi/gomega" //nolint:revive // ok + + "github.com/snivilised/li18ngo" + "github.com/snivilised/traverse/core" + "github.com/snivilised/traverse/enums" + lab "github.com/snivilised/traverse/internal/laboratory" + "github.com/snivilised/traverse/internal/opts/json" + "github.com/snivilised/traverse/lfs" + "github.com/snivilised/traverse/pref" +) + +// ๐Ÿ“š NB: these create functions are required because it is vitally important +// that objects are created consistently so as to not accidentally break +// equality checks. They do this by enforcing a single source of truth. + +// ๐Ÿ‘ NODE: +func createJSONFilterFromCore(def *core.FilterDef) *json.FilterDef { + return &json.FilterDef{ + Type: def.Type, + Description: def.Description, + Pattern: def.Pattern, + Scope: def.Scope, + Negate: def.Negate, + IfNotApplicable: def.IfNotApplicable, + } +} + +func createJSONFilterFromCoreWithPoly(def *core.FilterDef, + poly *json.PolyFilterDef, +) *json.FilterDef { + result := createJSONFilterFromCore(def) + result.Poly = poly + + return result +} + +func createCoreFilterDefFromJSON(jdef *json.FilterDef) *core.FilterDef { + return &core.FilterDef{ + Type: jdef.Type, + Description: jdef.Description, + Pattern: jdef.Pattern, + Scope: jdef.Scope, + Negate: jdef.Negate, + IfNotApplicable: jdef.IfNotApplicable, + } +} + +func createCoreFilterDefFromJSONWithPoly(def *json.FilterDef, + poly *core.PolyFilterDef, +) *core.FilterDef { + result := createCoreFilterDefFromJSON(def) + result.Poly = poly + + return result +} + +// ๐Ÿ‘ CHILD: +func createChildFromNode(def *core.FilterDef) *core.ChildFilterDef { + return &core.ChildFilterDef{ + Type: def.Type, + Description: def.Description, + Pattern: def.Pattern, + Negate: def.Negate, + } +} + +func createJSONChildFilterFromCore(def *core.ChildFilterDef) *json.ChildFilterDef { + return &json.ChildFilterDef{ + Type: def.Type, + Description: def.Description, + Pattern: def.Pattern, + Negate: def.Negate, + } +} + +// ๐Ÿ‘ SAMPLE: +func createSampleFromNode(def *core.FilterDef) *core.SampleFilterDef { + return &core.SampleFilterDef{ + Type: def.Type, + Description: def.Description, + Pattern: def.Pattern, + Scope: def.Scope, + Negate: def.Negate, + } +} + +func createJSONSampleFilterFromCore(def *core.SampleFilterDef) *json.SampleFilterDef { + return &json.SampleFilterDef{ + Type: def.Type, + Description: def.Description, + Pattern: def.Pattern, + Scope: def.Scope, + Negate: def.Negate, + } +} + +func createJSONSampleFilterFromCoreWithPoly( + def *core.FilterDef, +) *core.SampleFilterDef { + return &core.SampleFilterDef{ + Poly: &core.PolyFilterDef{ + File: *def, + Folder: *def, + }, + } +} + +func createJSONSampleFilterDefFromCore(def *core.SampleFilterDef) *json.SampleFilterDef { + return &json.SampleFilterDef{ + Type: def.Type, + Description: def.Description, + Pattern: def.Pattern, + Scope: def.Scope, + Negate: def.Negate, + } +} + +func createJSONSampleFilterDefFromCoreWithPoly(def *core.SampleFilterDef, + poly *json.PolyFilterDef, +) *json.SampleFilterDef { + result := createJSONSampleFilterDefFromCore(def) + result.Poly = poly + + return result +} + +var _ = Describe("Marshaler", Ordered, func() { + var ( + FS lfs.TraverseFS + readPath string + + // ๐Ÿ‘ NODE: + // + sourceNodeFilterDef *core.FilterDef + jsonNodeFilterDef json.FilterDef + jsonPolyNodeFilterDef json.FilterDef + polyNodeFilterDef *core.FilterDef + + // ๐Ÿ‘ CHILD: + // + sourceChildFilterDef *core.ChildFilterDef + jsonChildFilterDef *json.ChildFilterDef + + // ๐Ÿ‘ SAMPLE: + // + sourceSampleFilterDef *core.SampleFilterDef + sampleFilterDef *core.SampleFilterDef + jsonSampleFilterDef *json.SampleFilterDef + jsonSamplePolyFilterDef *json.SampleFilterDef + ) + + BeforeAll(func() { + Expect(li18ngo.Use()).To(Succeed()) + readPath = source + "/" + restoreFile + }) + + BeforeEach(func() { + FS = &lab.TestTraverseFS{ + MapFS: fstest.MapFS{ + home: &fstest.MapFile{ + Mode: os.ModeDir, + }, + }, + } + + Expect(FS.MkDirAll(destination, perms.Dir|os.ModeDir)).To(Succeed()) + Expect(FS.MkDirAll(source, perms.Dir|os.ModeDir)).To(Succeed()) + Expect(FS.WriteFile(readPath, content, perms.File)).To(Succeed()) + + // ๐Ÿ‘ NODE: + // + sourceNodeFilterDef = &core.FilterDef{ + Type: enums.FilterTypeGlob, + Description: "items without .flac suffix", + Pattern: flac, + Scope: enums.ScopeAll, + Negate: true, + IfNotApplicable: enums.TriStateBoolTrue, + } + + jsonNodeFilterDef = *createJSONFilterFromCore(sourceNodeFilterDef) + jsonPolyNodeFilterDef = *createJSONFilterFromCoreWithPoly( + sourceNodeFilterDef, &json.PolyFilterDef{ + File: jsonNodeFilterDef, + Folder: jsonNodeFilterDef, + }, + ) + + polyNodeFilterDef = createCoreFilterDefFromJSONWithPoly( + &jsonNodeFilterDef, &core.PolyFilterDef{ + File: *sourceNodeFilterDef, + Folder: *sourceNodeFilterDef, + }, + ) + + // ๐Ÿ‘ CHILD: + // + sourceChildFilterDef = createChildFromNode(sourceNodeFilterDef) + jsonChildFilterDef = createJSONChildFilterFromCore(sourceChildFilterDef) + + // ๐Ÿ‘ SAMPLE: + // + sourceSampleFilterDef = createSampleFromNode(sourceNodeFilterDef) + sampleFilterDef = createJSONSampleFilterFromCoreWithPoly(sourceNodeFilterDef) + jsonSampleFilterDef = createJSONSampleFilterFromCore(sourceSampleFilterDef) + jsonSamplePolyFilterDef = &json.SampleFilterDef{ + Poly: &json.PolyFilterDef{ + File: jsonNodeFilterDef, + Folder: jsonNodeFilterDef, + }, + } + + }) + + Context("map-fs", func() { + DescribeTable("marshal filter defs", + func(entry *marshalTE) { + // This looks a bit odd, but actually helps us to reduce + // the amount of test code required. + // + // marshal tweaks the JSON state to enforce unequal error, but + // the tweak invoked by marshal can be shared by unmarshal, + // without having to invoke unmarshal specific functionality. + // The result of marshal can be passed into unmarshal. + // + unmarshal(entry, FS, readPath, marshal(entry, FS)) + }, + func(entry *marshalTE) string { + return fmt.Sprintf("given: %v, ๐Ÿงช should: marshal successfully", entry.given) + }, + // ๐Ÿ‰ FilterOptions.Node + // + Entry(nil, &marshalTE{ + persistTE: persistTE{ + given: "FilterOptions - nil:pref.Options", + }, + tweak: func(jo *json.Options) { + jo.Filter.Node = &json.FilterDef{ + Type: enums.FilterTypeRegex, + Description: foo, + Pattern: flac, + Scope: enums.ScopeFile, + } + }, + }), + + Entry(nil, &marshalTE{ + persistTE: persistTE{ + given: "FilterOptions - nil:json.Options", + }, + option: func() pref.Option { + return pref.WithFilter(&pref.FilterOptions{ + Node: sourceNodeFilterDef, + }) + }, + tweak: func(jo *json.Options) { + jo.Filter.Node = nil + }, + }), + + Entry(nil, &marshalTE{ + persistTE: persistTE{ + given: "FilterOptions - Node.Type", + }, + checkerTE: &checkerTE{ + field: "Type", + checker: check[enums.FilterType], + }, + option: func() pref.Option { + return pref.WithFilter(&pref.FilterOptions{ + Node: sourceNodeFilterDef, + }) + }, + tweak: func(jo *json.Options) { + jo.Filter.Node = &jsonNodeFilterDef + jo.Filter.Node.Type = enums.FilterTypeExtendedGlob + }, + }), + + Entry(nil, &marshalTE{ + persistTE: persistTE{ + given: "FilterOptions - Node.Description", + }, + checkerTE: &checkerTE{ + field: "Description", + checker: check[string], + }, + option: func() pref.Option { + return pref.WithFilter(&pref.FilterOptions{ + Node: sourceNodeFilterDef, + }) + }, + tweak: func(jo *json.Options) { + jo.Filter.Node = &jsonNodeFilterDef + jo.Filter.Node.Description = foo + }, + }), + + Entry(nil, &marshalTE{ + persistTE: persistTE{ + given: "FilterOptions - Node.Pattern", + }, + checkerTE: &checkerTE{ + field: "Pattern", + checker: check[string], + }, + option: func() pref.Option { + return pref.WithFilter(&pref.FilterOptions{ + Node: sourceNodeFilterDef, + }) + }, + tweak: func(jo *json.Options) { + jo.Filter.Node = &jsonNodeFilterDef + jo.Filter.Node.Pattern = bar + }, + }), + + Entry(nil, &marshalTE{ + persistTE: persistTE{ + given: "FilterOptions - Node.Scope", + }, + checkerTE: &checkerTE{ + field: "Scope", + checker: check[enums.FilterScope], + }, + option: func() pref.Option { + return pref.WithFilter(&pref.FilterOptions{ + Node: sourceNodeFilterDef, + }) + }, + tweak: func(jo *json.Options) { + jo.Filter.Node = &jsonNodeFilterDef + jo.Filter.Node.Scope = enums.ScopeFile + }, + }), + + Entry(nil, &marshalTE{ + persistTE: persistTE{ + given: "FilterOptions - Node.Negate", + }, + checkerTE: &checkerTE{ + field: "Negate", + checker: check[bool], + }, + option: func() pref.Option { + return pref.WithFilter(&pref.FilterOptions{ + Node: sourceNodeFilterDef, + }) + }, + tweak: func(jo *json.Options) { + jo.Filter.Node = &jsonNodeFilterDef + jo.Filter.Node.Negate = false + }, + }), + + Entry(nil, &marshalTE{ + persistTE: persistTE{ + given: "FilterOptions - Node.IfNotApplicable", + }, + checkerTE: &checkerTE{ + field: "IfNotApplicable", + checker: check[enums.TriStateBool], + }, + option: func() pref.Option { + return pref.WithFilter(&pref.FilterOptions{ + Node: sourceNodeFilterDef, + }) + }, + tweak: func(jo *json.Options) { + jo.Filter.Node = &jsonNodeFilterDef + jo.Filter.Node.IfNotApplicable = enums.TriStateBoolFalse + }, + }), + + // ๐Ÿ‰ FilterOptions.Node.Poly + // + Entry(nil, &marshalTE{ + persistTE: persistTE{ + given: "FilterOptions.Node.Poly - nil:pref.Options", + }, + tweak: func(jo *json.Options) { + jo.Filter.Node = &jsonPolyNodeFilterDef + }, + }), + + Entry(nil, &marshalTE{ + persistTE: persistTE{ + given: "FilterOptions.Node.Poly - nil:json.Options", + }, + option: func() pref.Option { + return pref.WithFilter(&pref.FilterOptions{ + Node: polyNodeFilterDef, + }) + }, + tweak: func(jo *json.Options) { + jo.Filter.Node = nil + }, + }), + + Entry(nil, &marshalTE{ + persistTE: persistTE{ + given: "FilterOptions - Node.Poly.File", + }, + checkerTE: &checkerTE{ + field: "Description", + checker: check[string], + }, + option: func() pref.Option { + return pref.WithFilter(&pref.FilterOptions{ + Node: polyNodeFilterDef, + }) + }, + tweak: func(jo *json.Options) { + jo.Filter.Node = &jsonPolyNodeFilterDef + jo.Filter.Node.Poly.File.Description = foo + }, + }), + + Entry(nil, &marshalTE{ + persistTE: persistTE{ + given: "FilterOptions - Node.Poly.Folder", + }, + checkerTE: &checkerTE{ + field: "Description", + checker: check[string], + }, + option: func() pref.Option { + return pref.WithFilter(&pref.FilterOptions{ + Node: polyNodeFilterDef, + }) + }, + tweak: func(jo *json.Options) { + jo.Filter.Node = &jsonPolyNodeFilterDef + jo.Filter.Node.Poly.Folder.Description = foo + }, + }), + + // ๐Ÿ‰ FilterOptions.Child + // + Entry(nil, &marshalTE{ + persistTE: persistTE{ + given: "FilterOptions - nil:pref.Options", + }, + tweak: func(jo *json.Options) { + jo.Filter.Child = &json.ChildFilterDef{ + Type: enums.FilterTypeGlob, + Description: "items without .flac suffix", + Pattern: flac, + Negate: true, + } + }, + }), + + Entry(nil, &marshalTE{ + persistTE: persistTE{ + given: "FilterOptions - nil:json.Options", + }, + option: func() pref.Option { + return pref.WithFilter(&pref.FilterOptions{ + Child: sourceChildFilterDef, + }) + }, + tweak: func(jo *json.Options) { + jo.Filter.Child = nil + }, + }), + + Entry(nil, &marshalTE{ + persistTE: persistTE{ + given: "FilterOptions - Child.Type", + }, + checkerTE: &checkerTE{ + field: "Type", + checker: check[enums.FilterType], + }, + option: func() pref.Option { + return pref.WithFilter(&pref.FilterOptions{ + Child: sourceChildFilterDef, + }) + }, + tweak: func(jo *json.Options) { + jo.Filter.Child = jsonChildFilterDef + jo.Filter.Child.Type = enums.FilterTypeExtendedGlob + }, + }), + + Entry(nil, &marshalTE{ + persistTE: persistTE{ + given: "FilterOptions - Child.Description", + }, + checkerTE: &checkerTE{ + field: "Description", + checker: check[string], + }, + option: func() pref.Option { + return pref.WithFilter(&pref.FilterOptions{ + Child: sourceChildFilterDef, + }) + }, + tweak: func(jo *json.Options) { + jo.Filter.Child = jsonChildFilterDef + jo.Filter.Child.Description = foo + }, + }), + + Entry(nil, &marshalTE{ + persistTE: persistTE{ + given: "FilterOptions - Child.Pattern", + }, + checkerTE: &checkerTE{ + field: "Pattern", + checker: check[string], + }, + option: func() pref.Option { + return pref.WithFilter(&pref.FilterOptions{ + Child: sourceChildFilterDef, + }) + }, + tweak: func(jo *json.Options) { + jo.Filter.Child = jsonChildFilterDef + jo.Filter.Child.Pattern = foo + }, + }), + + Entry(nil, &marshalTE{ + persistTE: persistTE{ + given: "FilterOptions - Child.Negate", + }, + checkerTE: &checkerTE{ + field: "Negate", + checker: check[bool], + }, + option: func() pref.Option { + return pref.WithFilter(&pref.FilterOptions{ + Child: sourceChildFilterDef, + }) + }, + tweak: func(jo *json.Options) { + jo.Filter.Child = jsonChildFilterDef + jo.Filter.Child.Negate = false + }, + }), + + // ๐Ÿ‰ FilterOptions.Sample + // + Entry(nil, &marshalTE{ + persistTE: persistTE{ + given: "FilterOptions - nil:pref.Options", + }, + tweak: func(jo *json.Options) { + jo.Filter.Sample = jsonSampleFilterDef + }, + }), + + Entry(nil, &marshalTE{ + persistTE: persistTE{ + given: "FilterOptions - nil:json.Options", + }, + option: func() pref.Option { + return pref.WithFilter(&pref.FilterOptions{ + Sample: sourceSampleFilterDef, + }) + }, + tweak: func(jo *json.Options) { + jo.Filter.Sample = nil + }, + }), + + Entry(nil, &marshalTE{ + persistTE: persistTE{ + given: "FilterOptions - Sample.Type", + }, + checkerTE: &checkerTE{ + field: "Type", + checker: check[enums.FilterType], + }, + option: func() pref.Option { + return pref.WithFilter(&pref.FilterOptions{ + Sample: sourceSampleFilterDef, + }) + }, + tweak: func(jo *json.Options) { + jo.Filter.Sample = jsonSampleFilterDef + jo.Filter.Sample.Type = enums.FilterTypeExtendedGlob + }, + }), + + Entry(nil, &marshalTE{ + persistTE: persistTE{ + given: "FilterOptions - Sample.Description", + }, + checkerTE: &checkerTE{ + field: "Description", + checker: check[string], + }, + option: func() pref.Option { + return pref.WithFilter(&pref.FilterOptions{ + Sample: sourceSampleFilterDef, + }) + }, + tweak: func(jo *json.Options) { + jo.Filter.Sample = jsonSampleFilterDef + jo.Filter.Sample.Description = foo + }, + }), + + Entry(nil, &marshalTE{ + persistTE: persistTE{ + given: "FilterOptions - Sample.Pattern", + }, + checkerTE: &checkerTE{ + field: "Pattern", + checker: check[string], + }, + option: func() pref.Option { + return pref.WithFilter(&pref.FilterOptions{ + Sample: sourceSampleFilterDef, + }) + }, + tweak: func(jo *json.Options) { + jo.Filter.Sample = jsonSampleFilterDef + jo.Filter.Sample.Pattern = bar + }, + }), + + Entry(nil, &marshalTE{ + persistTE: persistTE{ + given: "FilterOptions - Sample.Scope", + }, + checkerTE: &checkerTE{ + field: "Scope", + checker: check[enums.FilterScope], + }, + option: func() pref.Option { + return pref.WithFilter(&pref.FilterOptions{ + Sample: sourceSampleFilterDef, + }) + }, + tweak: func(jo *json.Options) { + jo.Filter.Sample = jsonSampleFilterDef + jo.Filter.Sample.Scope = enums.ScopeFile + }, + }), + + Entry(nil, &marshalTE{ + persistTE: persistTE{ + given: "FilterOptions - Sample.Negate", + }, + checkerTE: &checkerTE{ + field: "Negate", + checker: check[bool], + }, + option: func() pref.Option { + return pref.WithFilter(&pref.FilterOptions{ + Sample: sourceSampleFilterDef, + }) + }, + tweak: func(jo *json.Options) { + jo.Filter.Sample = jsonSampleFilterDef + jo.Filter.Sample.Negate = false + }, + }), + + // ๐Ÿ‰ FilterOptions.Sample.Poly + // + Entry(nil, &marshalTE{ + persistTE: persistTE{ + given: "FilterOptions.Sample.Poly - nil:pref.Options", + }, + tweak: func(jo *json.Options) { + jo.Filter.Sample = jsonSamplePolyFilterDef + }, + }), + + Entry(nil, &marshalTE{ + persistTE: persistTE{ + given: "FilterOptions.Sample.Poly - nil:json.Options", + }, + option: func() pref.Option { + return pref.WithFilter(&pref.FilterOptions{ + Sample: sampleFilterDef, + }) + }, + tweak: func(jo *json.Options) { + jo.Filter.Sample = nil + }, + }), + + Entry(nil, &marshalTE{ + persistTE: persistTE{ + given: "FilterOptions - Sample.Poly.File", + }, + checkerTE: &checkerTE{ + field: "Description", + checker: check[string], + }, + option: func() pref.Option { + return pref.WithFilter(&pref.FilterOptions{ + Sample: sampleFilterDef, + }) + }, + tweak: func(jo *json.Options) { + jo.Filter.Sample = jsonSamplePolyFilterDef + jo.Filter.Sample.Poly.File.Description = foo + }, + }), + + Entry(nil, &marshalTE{ + persistTE: persistTE{ + given: "FilterOptions - Sample.Poly.Folder", + }, + checkerTE: &checkerTE{ + field: "Description", + checker: check[string], + }, + option: func() pref.Option { + return pref.WithFilter(&pref.FilterOptions{ + Sample: sampleFilterDef, + }) + }, + tweak: func(jo *json.Options) { + jo.Filter.Sample = jsonSamplePolyFilterDef + jo.Filter.Sample.Poly.Folder.Description = foo + }, + }), + ) + }) +}) diff --git a/internal/persist/marshaler-local-fs_test.go b/internal/persist/marshaler-local-fs_test.go index eff33e2..b5b11ff 100644 --- a/internal/persist/marshaler-local-fs_test.go +++ b/internal/persist/marshaler-local-fs_test.go @@ -23,19 +23,19 @@ var _ = Describe("Marshaler", Ordered, func() { Expect(li18ngo.Use()).To(Succeed()) testPath = lab.Repo("test") - testFile := filepath.Join(testPath, to, tempFile) + testFile := filepath.Join(testPath, destination, tempFile) if _, err := os.Stat(testFile); err == nil { _ = os.Remove(testFile) } - toPath := filepath.Join(testPath, to) - if err := os.MkdirAll(toPath, permDir|os.ModeDir); err != nil { + toPath := filepath.Join(testPath, destination) + if err := os.MkdirAll(toPath, perms.Dir|os.ModeDir); err != nil { Fail(err.Error()) } - fromPath := filepath.Join(testPath, from) - if err := os.MkdirAll(fromPath, permDir|os.ModeDir); err != nil { + fromPath := filepath.Join(testPath, source) + if err := os.MkdirAll(fromPath, perms.Dir|os.ModeDir); err != nil { Fail(err.Error()) } }) @@ -50,18 +50,19 @@ var _ = Describe("Marshaler", Ordered, func() { Expect(err).To(Succeed()) writerFS := lfs.NewWriteFileFS(testPath, NoOverwrite) - writePath := to + "/" + tempFile + writePath := destination + "/" + tempFile jo, err := persist.Marshal(&persist.MarshalState{ O: o, Active: &types.ActiveState{ - Root: to, + Root: destination, Hibernation: enums.HibernationPending, - NodePath: "/root/a/b/c", + CurrentPath: "/top/a/b/c", Depth: 3, }, - }, - writePath, permFile, writerFS, - ) + Path: writePath, + Perm: perms.File, + FS: writerFS, + }) Expect(err).To(Succeed()) Expect(jo).NotTo(BeNil()) @@ -78,18 +79,18 @@ var _ = Describe("Marshaler", Ordered, func() { marshaller = persist.NewReader(o, &types.ActiveState{ Root: "some-root-path", Hibernation: enums.HibernationPending, - NodePath: "/root/a/b/c", + CurrentPath: "/top/a/b/c", Depth: 3, }) */ - readerFS := lfs.NewReadFileFS("/some-path") - state, err := persist.Unmarshal(&types.RestoreState{ - Path: "some-restore-path", - Resume: enums.ResumeStrategySpawn, - }, "/some-path", readerFS) - _ = state + // readerFS := lfs.NewReadFileFS("/some-path") + // state, err := persist.Unmarshal(&types.RestoreState{ + // Path: "some-restore-path", + // Resume: enums.ResumeStrategySpawn, + // }, "/some-path", readerFS) + // _ = state - Expect(err).To(Succeed()) + // Expect(err).To(Succeed()) }) }) }) diff --git a/internal/persist/marshaler.go b/internal/persist/marshaler.go index 8b3bb83..6671f64 100644 --- a/internal/persist/marshaler.go +++ b/internal/persist/marshaler.go @@ -11,27 +11,32 @@ import ( ) type ( - stateMarshaler interface { - Marshal(path string) error - Unmarshal(path string) error - } + // TamperFunc provides a way for unit tests to modify the JSON before + // it is un-marshaled. The unit tests marshal a default JSON object + // instance, so a TamperFunc is used to allow modification of that + // default. Typically a single test will focus on a single field, + // so that the TamperFunc is expected to only update 1 of the members at a + // time. + TamperFunc func(jo *json.Options) MarshalState struct { O *pref.Options Active *types.ActiveState + JO *json.Options + Path string + Perm fs.FileMode + FS lfs.WriteFileFS } - jsonState struct { + JSONState struct { JO *json.Options Active *types.ActiveState } ) -func Marshal(ms *MarshalState, path string, perm fs.FileMode, - wfs lfs.WriteFileFS, -) (*json.Options, error) { +func Marshal(ms *MarshalState) (*json.Options, error) { jo := ToJSON(ms.O) - state := &jsonState{ + state := &JSONState{ JO: jo, Active: ms.Active, } @@ -45,18 +50,37 @@ func Marshal(ms *MarshalState, path string, perm fs.FileMode, return nil, err } - if equal, err := Equals(ms.O, jo); !equal { + if err := Equals(ms.O, jo); err != nil { return jo, err } - return jo, wfs.WriteFile(path, data, perm) + return jo, ms.FS.WriteFile(ms.Path, data, ms.Perm) } -func Unmarshal(_ *types.RestoreState, path string, - reader lfs.ReadFileFS, -) (*MarshalState, error) { - _ = path - _ = reader +func Unmarshal(rs *types.RestoreState, tampers ...TamperFunc) (*MarshalState, error) { + bytes, err := rs.FS.ReadFile(rs.Path) + + if err != nil { + return nil, err + } + + var ( + js JSONState + ) + + if err := ejson.Unmarshal(bytes, &js); err != nil { + return nil, err + } + + for _, fn := range tampers { + fn(js.JO) + } + + ms := MarshalState{ + O: FromJSON(js.JO), + Active: js.Active, + JO: js.JO, + } - return &MarshalState{}, nil + return &ms, Equals(ms.O, js.JO) } diff --git a/internal/persist/marshaler_test.go b/internal/persist/marshaler_test.go index 06c0031..2a2352a 100644 --- a/internal/persist/marshaler_test.go +++ b/internal/persist/marshaler_test.go @@ -16,69 +16,132 @@ import ( "github.com/snivilised/traverse/internal/opts" "github.com/snivilised/traverse/internal/opts/json" "github.com/snivilised/traverse/internal/persist" + "github.com/snivilised/traverse/internal/third/lo" "github.com/snivilised/traverse/internal/types" "github.com/snivilised/traverse/lfs" "github.com/snivilised/traverse/locale" "github.com/snivilised/traverse/pref" ) -var _ = Describe("Marshaler", Ordered, func() { - const ( - foo = "foo" - flac = "*.flac" - bar = "*.bar" +func check[T any](entry *checkerTE, err error) error { + if err, ok := errors.Unwrap(err).(persist.UnequalValueError[T]); ok { + return lo.Ternary(err.Field == entry.field, + nil, fmt.Errorf("actual(%q) => expected: %v; value: '%v', other: '%v'", + err.Field, entry.field, err.Value, err.Other, + ), + ) + } + + return &wrongUnequalError{ + field: entry.field, + err: err, + } +} + +func marshal(entry *marshalTE, tfs lfs.TraverseFS) *tampered { + // success: + o, _, err := opts.Get( + pref.IfOptionF(entry.option != nil, func() pref.Option { + return entry.option() + }), ) + Expect(err).To(Succeed(), "MARSHAL") + + writePath := destination + "/" + tempFile + jo, err := persist.Marshal(&persist.MarshalState{ + O: o, + Active: &types.ActiveState{ + Root: destination, + Hibernation: enums.HibernationPending, + CurrentPath: "/top/a/b/c", + Depth: 3, + }, + Path: writePath, + Perm: perms.File, + FS: tfs, + }) + Expect(err).To(Succeed(), "MARSHAL") + Expect(jo).NotTo(BeNil()) + + // unequal error: + if entry.tweak != nil { + entry.tweak(jo) + } + + e := persist.Equals(o, jo) + Expect(e).NotTo(Succeed(), "MARSHAL") + if e != nil && entry.checkerTE != nil && entry.checkerTE.checker != nil { + Expect(entry.checker(entry.checkerTE, e)).To(Succeed(), "MARSHAL") + } + + return &tampered{ + o: o, + jo: jo, + } +} + +func unmarshal(entry *marshalTE, tfs lfs.TraverseFS, restorePath string, t *tampered) { + // success: + state, err := persist.Unmarshal(&types.RestoreState{ + Path: restorePath, + FS: tfs, + Resume: enums.ResumeStrategySpawn, + }, entry.tweak) + Expect(err).To(Succeed(), "UNMARSHAL") + + // unequal error: + e := persist.Equals(t.o, state.JO) + Expect(e).NotTo(Succeed(), "UNMARSHAL") + + if e != nil && entry.checkerTE != nil && entry.checkerTE.checker != nil { + Expect(entry.checker(entry.checkerTE, e)).To(Succeed(), "UNMARSHAL") + } +} + +func createJSONSamplingOptions(so *pref.SamplingOptions) *json.SamplingOptions { + return &json.SamplingOptions{ + Type: so.Type, + InReverse: so.InReverse, + NoOf: json.EntryQuantities{ + Files: so.NoOf.Files, + Folders: so.NoOf.Folders, + }, + } +} + +var _ = Describe("Marshaler", Ordered, func() { var ( - FS lfs.TraverseFS - nodeFilterDef, polyNodeFilterDef *core.FilterDef - childFilterDef *core.ChildFilterDef - samplingOptions *pref.SamplingOptions - sampleFilterDef *core.SampleFilterDef - jsonNodeFilterDef, jsonPolyNodeFilterDef json.FilterDef - polyFilterDef *core.PolyFilterDef + FS lfs.TraverseFS + + sourceNodeFilterDef *core.FilterDef + jsonNodeFilterDef json.FilterDef + samplingOptions *pref.SamplingOptions + jsonSamplingOptions *json.SamplingOptions + + readPath string ) BeforeAll(func() { Expect(li18ngo.Use()).To(Succeed()) - nodeFilterDef = &core.FilterDef{ - Type: enums.FilterTypeGlob, - Description: "items without .flac suffix", - Pattern: flac, - Scope: enums.ScopeAll, - Negate: true, - IfNotApplicable: enums.TriStateBoolTrue, - } - childFilterDef = &core.ChildFilterDef{ - Type: enums.FilterTypeGlob, - Description: "items without .flac suffix", - Pattern: flac, - Negate: true, - } + readPath = source + "/" + restoreFile + }) - samplingOptions = &pref.SamplingOptions{ - Type: enums.SampleTypeFilter, - InReverse: true, - NoOf: pref.EntryQuantities{ - Files: 2, - Folders: 3, + BeforeEach(func() { + FS = &lab.TestTraverseFS{ + MapFS: fstest.MapFS{ + home: &fstest.MapFile{ + Mode: os.ModeDir, + }, }, } - sampleFilterDef = &core.SampleFilterDef{ - Type: enums.FilterTypeGlob, - Description: "items without .flac suffix", - Pattern: flac, - Scope: enums.ScopeAll, - Negate: true, - Poly: &core.PolyFilterDef{ - File: *nodeFilterDef, - Folder: *nodeFilterDef, - }, - } + Expect(FS.MkDirAll(destination, perms.Dir|os.ModeDir)).To(Succeed()) + Expect(FS.MkDirAll(source, perms.Dir|os.ModeDir)).To(Succeed()) + Expect(FS.WriteFile(readPath, content, perms.File)).To(Succeed()) - jsonNodeFilterDef = json.FilterDef{ + sourceNodeFilterDef = &core.FilterDef{ Type: enums.FilterTypeGlob, Description: "items without .flac suffix", Pattern: flac, @@ -87,90 +150,46 @@ var _ = Describe("Marshaler", Ordered, func() { IfNotApplicable: enums.TriStateBoolTrue, } - jsonPolyNodeFilterDef = json.FilterDef{ - Type: enums.FilterTypePoly, - Description: "items without .flac suffix", - Pattern: flac, - Scope: enums.ScopeAll, - Negate: true, - IfNotApplicable: enums.TriStateBoolTrue, - Poly: &json.PolyFilterDef{ - File: jsonNodeFilterDef, - Folder: jsonNodeFilterDef, - }, - } - - polyFilterDef = &core.PolyFilterDef{ - File: *nodeFilterDef, - Folder: *nodeFilterDef, - } - - polyNodeFilterDef = &core.FilterDef{ - Type: enums.FilterTypePoly, - Description: "items without .flac suffix", - Pattern: flac, - Scope: enums.ScopeAll, - Negate: true, - IfNotApplicable: enums.TriStateBoolTrue, - Poly: polyFilterDef, - } - }) + jsonNodeFilterDef = *createJSONFilterFromCore(sourceNodeFilterDef) - BeforeEach(func() { - FS = &lab.TestTraverseFS{ - MapFS: fstest.MapFS{ - home: &fstest.MapFile{ - Mode: os.ModeDir, - }, + samplingOptions = &pref.SamplingOptions{ + Type: enums.SampleTypeFilter, + InReverse: true, + NoOf: pref.EntryQuantities{ + Files: 2, + Folders: 3, }, } - _ = FS.MkDirAll(to, permDir|os.ModeDir) + jsonSamplingOptions = createJSONSamplingOptions(samplingOptions) }) Context("map-fs", func() { DescribeTable("marshal", func(entry *marshalTE) { - // success: - o, _, err := opts.Get( - pref.IfOptionF(entry.option != nil, func() pref.Option { - return entry.option() - }), - ) - Expect(err).To(Succeed()) - - writePath := to + "/" + tempFile - jo, err := persist.Marshal(&persist.MarshalState{ - O: o, - Active: &types.ActiveState{ - Root: to, - Hibernation: enums.HibernationPending, - NodePath: "/root/a/b/c", - Depth: 3, - }, - }, - writePath, permFile, FS, - ) - - Expect(err).To(Succeed()) - Expect(jo).NotTo(BeNil()) - - // unequal error: - if entry.tweak != nil { - entry.tweak(jo) - } - equals, err := persist.Equals(o, jo) - Expect(equals).To(BeFalse(), "should not compare equal") - Expect(err).NotTo(Succeed()) + // This looks a bit odd, but actually helps us to reduce + // the amount of test code required. + // + // marshal tweaks the JSON state to enforce unequal error, but + // the tweak invoked by marshal can be shared by unmarshal, + // without having to invoke unmarshal specific functionality. + // The result of marshal can be passed into unmarshal. + // + unmarshal(entry, FS, readPath, marshal(entry, FS)) }, func(entry *marshalTE) string { return fmt.Sprintf("given: %v, ๐Ÿงช should: marshal successfully", entry.given) }, // ๐Ÿ‰ NavigationBehaviours: + // Entry(nil, &marshalTE{ persistTE: persistTE{ - given: "NavigationBehaviours.SubPathBehaviour", + given: "NavigationBehaviours.SubPathBehaviour.KeepTrailingSep", + }, + checkerTE: &checkerTE{ + field: "KeepTrailingSep", + checker: check[bool], }, option: func() pref.Option { return pref.WithSubPathBehaviour(&pref.SubPathBehaviour{ @@ -184,12 +203,15 @@ var _ = Describe("Marshaler", Ordered, func() { Entry(nil, &marshalTE{ persistTE: persistTE{ - given: "NavigationBehaviours.WithSortBehaviour", + given: "NavigationBehaviours.WithSortBehaviour.IsCaseSensitive", + }, + checkerTE: &checkerTE{ + field: "IsCaseSensitive", + checker: check[bool], }, option: func() pref.Option { return pref.WithSortBehaviour(&pref.SortBehaviour{ IsCaseSensitive: true, - SortFilesFirst: true, }) }, tweak: func(jo *json.Options) { @@ -199,482 +221,83 @@ var _ = Describe("Marshaler", Ordered, func() { Entry(nil, &marshalTE{ persistTE: persistTE{ - given: "NavigationBehaviours.CascadeBehaviour.WithDepth", - }, - option: func() pref.Option { - return pref.WithDepth(4) - }, - tweak: func(jo *json.Options) { - jo.Behaviours.Cascade.Depth = 99 - }, - }), - - Entry(nil, &marshalTE{ - persistTE: persistTE{ - given: "NavigationBehaviours.CascadeBehaviour.NoRecurse", - }, - option: pref.WithNoRecurse, - tweak: func(jo *json.Options) { - jo.Behaviours.Cascade.NoRecurse = false - }, - }), - - // ๐Ÿ‰ SamplingOptions: - Entry(nil, &marshalTE{ - persistTE: persistTE{ - given: "NavigationBehaviours.SamplingOptions.InReverse", - }, - option: func() pref.Option { - return pref.WithSamplingOptions(&pref.SamplingOptions{ - Type: enums.SampleTypeFilter, - InReverse: true, - NoOf: pref.EntryQuantities{ - Files: 3, - Folders: 4, - }, - }) - }, - tweak: func(jo *json.Options) { - jo.Sampling.InReverse = false - }, - }), - - Entry(nil, &marshalTE{ - persistTE: persistTE{ - given: "NavigationBehaviours.SamplingOptions.SampleType", - }, - option: func() pref.Option { - return pref.WithSamplingOptions(&pref.SamplingOptions{ - Type: enums.SampleTypeFilter, - InReverse: true, - NoOf: pref.EntryQuantities{ - Files: 3, - Folders: 4, - }, - }) - }, - tweak: func(jo *json.Options) { - jo.Sampling.Type = enums.SampleTypeSlice - }, - }), - - Entry(nil, &marshalTE{ - persistTE: persistTE{ - given: "NavigationBehaviours.SamplingOptions.NoOf.Files", - }, - option: func() pref.Option { - return pref.WithSamplingOptions(&pref.SamplingOptions{ - Type: enums.SampleTypeFilter, - InReverse: true, - NoOf: pref.EntryQuantities{ - Files: 3, - Folders: 4, - }, - }) - }, - tweak: func(jo *json.Options) { - jo.Sampling.NoOf.Files = 99 - }, - }), - - Entry(nil, &marshalTE{ - persistTE: persistTE{ - given: "NavigationBehaviours.SamplingOptions.NoOf.Folders", - }, - option: func() pref.Option { - return pref.WithSamplingOptions(&pref.SamplingOptions{ - Type: enums.SampleTypeFilter, - InReverse: true, - NoOf: pref.EntryQuantities{ - Files: 3, - Folders: 4, - }, - }) - }, - tweak: func(jo *json.Options) { - jo.Sampling.NoOf.Folders = 99 - }, - }), - - // ๐Ÿ‰ FilterOptions.Node - Entry(nil, &marshalTE{ - persistTE: persistTE{ - given: "FilterOptions - nil:pref.Options", - }, - tweak: func(jo *json.Options) { - jo.Filter.Node = &json.FilterDef{ - Type: enums.FilterTypeRegex, - Description: foo, - Pattern: flac, - Scope: enums.ScopeFile, - } - }, - }), - - Entry(nil, &marshalTE{ - persistTE: persistTE{ - given: "FilterOptions - nil:json.Options", - }, - option: func() pref.Option { - return pref.WithFilter(&pref.FilterOptions{ - Node: nodeFilterDef, - }) - }, - tweak: func(jo *json.Options) { - jo.Filter.Node = nil - }, - }), - - Entry(nil, &marshalTE{ - persistTE: persistTE{ - given: "FilterOptions - Node.Type", - }, - option: func() pref.Option { - return pref.WithFilter(&pref.FilterOptions{ - Node: nodeFilterDef, - }) - }, - tweak: func(jo *json.Options) { - jo.Filter.Node.Type = enums.FilterTypeExtendedGlob - }, - }), - - Entry(nil, &marshalTE{ - persistTE: persistTE{ - given: "FilterOptions - Node.Description", - }, - option: func() pref.Option { - return pref.WithFilter(&pref.FilterOptions{ - Node: nodeFilterDef, - }) - }, - tweak: func(jo *json.Options) { - jo.Filter.Node.Description = foo - }, - }), - - Entry(nil, &marshalTE{ - persistTE: persistTE{ - given: "FilterOptions - Node.Pattern", - }, - option: func() pref.Option { - return pref.WithFilter(&pref.FilterOptions{ - Node: nodeFilterDef, - }) - }, - tweak: func(jo *json.Options) { - jo.Filter.Node.Pattern = bar - }, - }), - - Entry(nil, &marshalTE{ - persistTE: persistTE{ - given: "FilterOptions - Node.Scope", - }, - option: func() pref.Option { - return pref.WithFilter(&pref.FilterOptions{ - Node: nodeFilterDef, - }) - }, - tweak: func(jo *json.Options) { - jo.Filter.Node.Scope = enums.ScopeFile - }, - }), - - Entry(nil, &marshalTE{ - persistTE: persistTE{ - given: "FilterOptions - Node.Negate", - }, - option: func() pref.Option { - return pref.WithFilter(&pref.FilterOptions{ - Node: nodeFilterDef, - }) - }, - tweak: func(jo *json.Options) { - jo.Filter.Node.Negate = false - }, - }), - - Entry(nil, &marshalTE{ - persistTE: persistTE{ - given: "FilterOptions - Node.IfNotApplicable", - }, - option: func() pref.Option { - return pref.WithFilter(&pref.FilterOptions{ - Node: nodeFilterDef, - }) - }, - tweak: func(jo *json.Options) { - jo.Filter.Node.IfNotApplicable = enums.TriStateBoolFalse - }, - }), - - // ๐Ÿ‰ FilterOptions.Node.Poly - Entry(nil, &marshalTE{ - persistTE: persistTE{ - given: "FilterOptions.Node.Poly - nil:pref.Options", - }, - tweak: func(jo *json.Options) { - jo.Filter.Node = &jsonPolyNodeFilterDef - }, - }), - - Entry(nil, &marshalTE{ - persistTE: persistTE{ - given: "FilterOptions.Node.Poly - nil:json.Options", - }, - option: func() pref.Option { - return pref.WithFilter(&pref.FilterOptions{ - Node: polyNodeFilterDef, - }) - }, - tweak: func(jo *json.Options) { - jo.Filter.Node = nil - }, - }), - - Entry(nil, &marshalTE{ - persistTE: persistTE{ - given: "FilterOptions - Node.Poly.File", - }, - option: func() pref.Option { - return pref.WithFilter(&pref.FilterOptions{ - Node: &core.FilterDef{ - Type: enums.FilterTypePoly, - Poly: &core.PolyFilterDef{ - File: *nodeFilterDef, - Folder: *nodeFilterDef, - }, - }, - }) - }, - tweak: func(jo *json.Options) { - jo.Filter.Node.Poly.File.Description = foo - }, - }), - - Entry(nil, &marshalTE{ - persistTE: persistTE{ - given: "FilterOptions - Node.Poly.Folder", - }, - option: func() pref.Option { - return pref.WithFilter(&pref.FilterOptions{ - Node: &core.FilterDef{ - Type: enums.FilterTypePoly, - Poly: &core.PolyFilterDef{ - File: *nodeFilterDef, - Folder: *nodeFilterDef, - }, - }, - }) - }, - tweak: func(jo *json.Options) { - jo.Filter.Node.Poly.Folder.Description = foo - }, - }), - - // ๐Ÿ‰ FilterOptions.Child - Entry(nil, &marshalTE{ - persistTE: persistTE{ - given: "FilterOptions - nil:pref.Options", - }, - tweak: func(jo *json.Options) { - jo.Filter.Child = &json.ChildFilterDef{ - Type: enums.FilterTypeGlob, - Description: "items without .flac suffix", - Pattern: flac, - Negate: true, - } - }, - }), - - Entry(nil, &marshalTE{ - persistTE: persistTE{ - given: "FilterOptions - nil:json.Options", - }, - option: func() pref.Option { - return pref.WithFilter(&pref.FilterOptions{ - Child: childFilterDef, - }) - }, - tweak: func(jo *json.Options) { - jo.Filter.Child = nil - }, - }), - - Entry(nil, &marshalTE{ - persistTE: persistTE{ - given: "FilterOptions - Child.Type", - }, - option: func() pref.Option { - return pref.WithFilter(&pref.FilterOptions{ - Child: childFilterDef, - }) - }, - tweak: func(jo *json.Options) { - jo.Filter.Child.Type = enums.FilterTypeExtendedGlob - }, - }), - - Entry(nil, &marshalTE{ - persistTE: persistTE{ - given: "FilterOptions - Child.Description", - }, - option: func() pref.Option { - return pref.WithFilter(&pref.FilterOptions{ - Child: childFilterDef, - }) - }, - tweak: func(jo *json.Options) { - jo.Filter.Child.Description = foo - }, - }), - - Entry(nil, &marshalTE{ - persistTE: persistTE{ - given: "FilterOptions - Child.Pattern", - }, - option: func() pref.Option { - return pref.WithFilter(&pref.FilterOptions{ - Child: childFilterDef, - }) - }, - tweak: func(jo *json.Options) { - jo.Filter.Child.Pattern = foo - }, - }), - - Entry(nil, &marshalTE{ - persistTE: persistTE{ - given: "FilterOptions - Child.Negate", - }, - option: func() pref.Option { - return pref.WithFilter(&pref.FilterOptions{ - Child: childFilterDef, - }) - }, - tweak: func(jo *json.Options) { - jo.Filter.Child.Negate = false - }, - }), - - // ๐Ÿ‰ FilterOptions.Sample - Entry(nil, &marshalTE{ - persistTE: persistTE{ - given: "FilterOptions - nil:pref.Options", + given: "NavigationBehaviours.WithSortBehaviour.SortFilesFirst", }, - tweak: func(jo *json.Options) { - jo.Filter.Sample = &json.SampleFilterDef{ - Type: enums.FilterTypeRegex, - Description: foo, - Pattern: flac, - Scope: enums.ScopeFile, - } - }, - }), - - Entry(nil, &marshalTE{ - persistTE: persistTE{ - given: "FilterOptions - nil:json.Options", + checkerTE: &checkerTE{ + field: "SortFilesFirst", + checker: check[bool], }, option: func() pref.Option { - return pref.WithFilter(&pref.FilterOptions{ - Sample: sampleFilterDef, + return pref.WithSortBehaviour(&pref.SortBehaviour{ + SortFilesFirst: true, }) }, tweak: func(jo *json.Options) { - jo.Filter.Sample = nil + jo.Behaviours.Sort.SortFilesFirst = false }, }), Entry(nil, &marshalTE{ persistTE: persistTE{ - given: "FilterOptions - Sample.Type", - }, - option: func() pref.Option { - return pref.WithFilter(&pref.FilterOptions{ - Sample: sampleFilterDef, - }) - }, - tweak: func(jo *json.Options) { - jo.Filter.Sample.Type = enums.FilterTypeExtendedGlob - }, - }), - Entry(nil, &marshalTE{ - persistTE: persistTE{ - given: "FilterOptions - Sample.Description", - }, - option: func() pref.Option { - return pref.WithFilter(&pref.FilterOptions{ - Sample: sampleFilterDef, - }) + given: "NavigationBehaviours.CascadeBehaviour.WithDepth", }, - tweak: func(jo *json.Options) { - jo.Filter.Sample.Description = foo - }, - }), - - Entry(nil, &marshalTE{ - persistTE: persistTE{ - given: "FilterOptions - Sample.Pattern", + checkerTE: &checkerTE{ + field: "Depth", + checker: check[uint], }, option: func() pref.Option { - return pref.WithFilter(&pref.FilterOptions{ - Sample: sampleFilterDef, - }) + return pref.WithDepth(4) }, tweak: func(jo *json.Options) { - jo.Filter.Sample.Pattern = bar + jo.Behaviours.Cascade.Depth = 99 }, }), Entry(nil, &marshalTE{ persistTE: persistTE{ - given: "FilterOptions - Sample.Scope", + given: "NavigationBehaviours.CascadeBehaviour.NoRecurse", }, - option: func() pref.Option { - return pref.WithFilter(&pref.FilterOptions{ - Sample: sampleFilterDef, - }) + checkerTE: &checkerTE{ + field: "NoRecurse", + checker: check[bool], }, + option: pref.WithNoRecurse, tweak: func(jo *json.Options) { - jo.Filter.Sample.Scope = enums.ScopeFile + jo.Behaviours.Cascade.NoRecurse = false }, }), + // ๐Ÿ‰ SamplingOptions: + // Entry(nil, &marshalTE{ persistTE: persistTE{ - given: "FilterOptions - Sample.Negate", + given: "NavigationBehaviours.SamplingOptions.Type", }, - option: func() pref.Option { - return pref.WithFilter(&pref.FilterOptions{ - Sample: sampleFilterDef, - }) - }, - tweak: func(jo *json.Options) { - jo.Filter.Sample.Negate = false - }, - }), - - // SamplingOptions: - Entry(nil, &marshalTE{ - persistTE: persistTE{ - given: "Sampling - SampleOptions.Type", + checkerTE: &checkerTE{ + field: "Type", + checker: check[enums.SampleType], }, option: func() pref.Option { return pref.WithSamplingOptions(samplingOptions) }, tweak: func(jo *json.Options) { + jo.Sampling = *jsonSamplingOptions jo.Sampling.Type = enums.SampleTypeSlice }, }), Entry(nil, &marshalTE{ persistTE: persistTE{ - given: "Sampling - SampleOptions.InReverse", + given: "NavigationBehaviours.SamplingOptions.InReverse", + }, + checkerTE: &checkerTE{ + field: "InReverse", + checker: check[bool], }, option: func() pref.Option { - return pref.WithSamplingOptions(samplingOptions) + return pref.WithSamplingOptions(&pref.SamplingOptions{ + InReverse: true, + }) }, tweak: func(jo *json.Options) { jo.Sampling.InReverse = false @@ -683,19 +306,24 @@ var _ = Describe("Marshaler", Ordered, func() { Entry(nil, &marshalTE{ persistTE: persistTE{ - given: "Sampling - SampleOptions.NoOf.Files", + given: "NavigationBehaviours.SamplingOptions.NoOf.Files", + }, + checkerTE: &checkerTE{ + field: "Files", + checker: check[uint], }, option: func() pref.Option { return pref.WithSamplingOptions(samplingOptions) }, tweak: func(jo *json.Options) { + jo.Sampling = *jsonSamplingOptions jo.Sampling.NoOf.Files = 99 }, }), Entry(nil, &marshalTE{ persistTE: persistTE{ - given: "Sampling - SampleOptions.NoOf.Folders", + given: "NavigationBehaviours.SamplingOptions.NoOf.Folders", }, option: func() pref.Option { return pref.WithSamplingOptions(samplingOptions) @@ -706,14 +334,20 @@ var _ = Describe("Marshaler", Ordered, func() { }), // ๐Ÿ‰ HibernateOptions: + // Entry(nil, &marshalTE{ persistTE: persistTE{ given: "HibernateOptions.Behaviour.InclusiveWake", }, + checkerTE: &checkerTE{ + field: "Description", + checker: check[string], + }, option: func() pref.Option { - return pref.WithHibernationFilterWake(nodeFilterDef) + return pref.WithHibernationFilterWake(sourceNodeFilterDef) }, tweak: func(jo *json.Options) { + jo.Hibernate.WakeAt = &jsonNodeFilterDef jo.Hibernate.WakeAt.Description = foo }, }), @@ -722,10 +356,15 @@ var _ = Describe("Marshaler", Ordered, func() { persistTE: persistTE{ given: "HibernateOptions.Behaviour.InclusiveSleep", }, + checkerTE: &checkerTE{ + field: "Description", + checker: check[string], + }, option: func() pref.Option { - return pref.WithHibernationFilterSleep(nodeFilterDef) + return pref.WithHibernationFilterSleep(sourceNodeFilterDef) }, tweak: func(jo *json.Options) { + jo.Hibernate.SleepAt = &jsonNodeFilterDef jo.Hibernate.SleepAt.Description = foo }, }), @@ -734,6 +373,10 @@ var _ = Describe("Marshaler", Ordered, func() { persistTE: persistTE{ given: "HibernateOptions.Behaviour.InclusiveWake", }, + checkerTE: &checkerTE{ + field: "InclusiveWake", + checker: check[bool], + }, option: pref.WithHibernationBehaviourExclusiveWake, tweak: func(jo *json.Options) { jo.Hibernate.Behaviour.InclusiveWake = true @@ -744,6 +387,10 @@ var _ = Describe("Marshaler", Ordered, func() { persistTE: persistTE{ given: "HibernateOptions.Behaviour.InclusiveSleep", }, + checkerTE: &checkerTE{ + field: "InclusiveSleep", + checker: check[bool], + }, option: pref.WithHibernationBehaviourInclusiveSleep, tweak: func(jo *json.Options) { jo.Hibernate.Behaviour.InclusiveSleep = false @@ -751,10 +398,15 @@ var _ = Describe("Marshaler", Ordered, func() { }), // ๐Ÿ‰ ConcurrencyOptions: + // Entry(nil, &marshalTE{ persistTE: persistTE{ given: "ConcurrencyOptions.NoW", }, + checkerTE: &checkerTE{ + field: "NoW", + checker: check[uint], + }, option: func() pref.Option { return pref.WithNoW(5) }, @@ -767,8 +419,7 @@ var _ = Describe("Marshaler", Ordered, func() { Context("UnequalPtrError", func() { When("pref.Options is nil", func() { It("๐Ÿงช should: return UnequalPtrError", func() { - equals, err := persist.Equals(nil, &json.Options{}) - Expect(equals).To(BeFalse(), "should not compare equal") + err := persist.Equals(nil, &json.Options{}) Expect(err).NotTo(Succeed()) Expect(errors.Is(err, locale.ErrUnEqualConversion)).To(BeTrue(), "error should be a locale.ErrUnEqualConversion", @@ -779,8 +430,7 @@ var _ = Describe("Marshaler", Ordered, func() { When("json FilterDef is nil", func() { It("๐Ÿงช should: return UnequalPtrError", func() { o, _, _ := opts.Get() - equals, err := persist.Equals(o, nil) - Expect(equals).To(BeFalse(), "should not compare equal") + err := persist.Equals(o, nil) Expect(err).NotTo(Succeed()) Expect(errors.Is(err, locale.ErrUnEqualConversion)).To(BeTrue(), "error should be a locale.ErrUnEqualConversion", diff --git a/internal/persist/persist-suite_test.go b/internal/persist/persist-suite_test.go index 1f8d7cb..1964846 100644 --- a/internal/persist/persist-suite_test.go +++ b/internal/persist/persist-suite_test.go @@ -1,11 +1,15 @@ package persist_test import ( + _ "embed" + "fmt" + "io/fs" "testing" . "github.com/onsi/ginkgo/v2" //nolint:revive // ok . "github.com/onsi/gomega" //nolint:revive // ok "github.com/snivilised/traverse/internal/opts/json" + "github.com/snivilised/traverse/internal/persist" "github.com/snivilised/traverse/pref" ) @@ -16,29 +20,72 @@ func TestPersist(t *testing.T) { const ( NoOverwrite = false - from = "json/unmarshal" - to = "json/marshal" - permDir = 0o777 - permFile = 0o666 + source = "json/unmarshal" + destination = "json/marshal" tempFile = "test-state-marshal.TEMP.json" + restoreFile = "test-restore.DEFAULT.json" home = "/home" + foo = "foo" + flac = "*.flac" + bar = "*.bar" +) + +var ( + perms = struct { + File fs.FileMode + Dir fs.FileMode + }{ + File: 0o666, + Dir: 0o777, + } + + //go:embed data/test-restore.DEFAULT.json + content []byte ) type ( persistTE struct { given string } + + tampered struct { + o *pref.Options + jo *json.Options + } + + checkerFunc func(entry *checkerTE, err error) error + + checkerTE struct { + field string + // checker ensures the resultant error is reporting the correct field. + // If failure, then an error is returned, indicating name of the actual + // field reported, vs the expected. + checker checkerFunc + } + marshalTE struct { persistTE + *checkerTE // option defines a single option to be defined for the unit test. When // a test case wants to test an optional option in pref.Options (ie it // is a pointer), then that test case will not define this option. Instead // it will define the tweak function to contain the corresponding member // on the json instance, such that the pref.member is nil and json.member - // is noy til, thereby triggering an unequal error. + // is not til, thereby triggering an unequal error. option func() pref.Option // tweak allows a test case to change json.Options to provoke unequal error - tweak func(jo *json.Options) + tweak persist.TamperFunc + } + + wrongUnequalError struct { + field string + err error } ) + +func (e wrongUnequalError) Error() string { + return fmt.Sprintf("wrong unequal error (%v) type for field: %q", + e.err, e.field, + ) +} diff --git a/internal/types/definitions.go b/internal/types/definitions.go index b129a4d..b6c9d05 100644 --- a/internal/types/definitions.go +++ b/internal/types/definitions.go @@ -118,7 +118,7 @@ type ( ActiveState struct { Root string Hibernation enums.Hibernation - NodePath string + CurrentPath string Depth int // metrics } @@ -130,6 +130,7 @@ type ( // RestoreState RestoreState struct { Path string + FS lfs.ReadFileFS Resume enums.ResumeStrategy } ) diff --git a/pref/options.go b/pref/options.go index 4e8610a..e204995 100644 --- a/pref/options.go +++ b/pref/options.go @@ -90,6 +90,8 @@ func IfOptionF(condition bool, option ConditionalOption) Option { // DefaultOptions func DefaultOptions() *Options { + // If a change is made to the default, make sure to make the corresponding + // same change in internal/persist/data/test-restore.DEFAULT.json nopLogger := slog.New(slog.NewTextHandler(io.Discard, nil)) o := &Options{