From 2bd0f3077286ecacd0e001b32774a3ba59057b2c Mon Sep 17 00:00:00 2001 From: plastikfan Date: Sat, 28 Oct 2023 12:10:54 +0100 Subject: [PATCH] feat(nav): navigate with pre-constructed options (#348) chore: lint - clean up unused items feat(nav): setup failing tests for provided options (#348) feat(nav): added from methods for options (#348) feat(nav): call o.afterUserOptions for pre-constructed options (#348) --- xfs/nav/helpers_test.go | 2 +- xfs/nav/navigation-async_test.go | 6 - xfs/nav/navigation-listener_test.go | 137 +++++++------ xfs/nav/navigation-runner.go | 10 +- xfs/nav/navigation-session.go | 19 +- xfs/nav/navigation-sync.go | 2 - xfs/nav/navigation-with-runner_test.go | 258 ++++++++++++++++--------- xfs/nav/navigator-folders.go | 2 +- xfs/nav/new-navigator.go | 26 ++- xfs/nav/traverse-defs.go | 5 +- 10 files changed, 289 insertions(+), 178 deletions(-) diff --git a/xfs/nav/helpers_test.go b/xfs/nav/helpers_test.go index c149676..6abfc61 100644 --- a/xfs/nav/helpers_test.go +++ b/xfs/nav/helpers_test.go @@ -174,7 +174,7 @@ func universalCallback(name string, extended bool) *nav.LabelledTraverseCallback } } -func universalCallbackNoAssert(name string, extended bool) *nav.LabelledTraverseCallback { +func universalCallbackNoAssert(name string, extended bool) *nav.LabelledTraverseCallback { //nolint:unparam // for future use ex := lo.Ternary(extended, "-EX", "") return &nav.LabelledTraverseCallback{ diff --git a/xfs/nav/navigation-async_test.go b/xfs/nav/navigation-async_test.go index a474d53..aba9c7d 100644 --- a/xfs/nav/navigation-async_test.go +++ b/xfs/nav/navigation-async_test.go @@ -40,12 +40,6 @@ type ( asyncOkTE struct { asyncTE } - - asyncErrorTE struct { - asyncTE - fragment string - timeout time.Duration - } ) const ( diff --git a/xfs/nav/navigation-listener_test.go b/xfs/nav/navigation-listener_test.go index 0868ed7..62dae4c 100644 --- a/xfs/nav/navigation-listener_test.go +++ b/xfs/nav/navigation-listener_test.go @@ -272,68 +272,73 @@ var _ = Describe("Listener", Ordered, func() { }) Context("folders", func() { - Context("given: filter and listen both active", func() { - It("๐Ÿงช should: apply filter within the listen range", func() { - path := helpers.Path(root, "edm/ELECTRONICA") - optionFn := func(o *nav.TraverseOptions) { - o.Notify.OnBegin = begin("๐Ÿ›ก๏ธ") - o.Store.Subscription = nav.SubscribeFolders - o.Store.FilterDefs = &nav.FilterDefinitions{ - Node: nav.FilterDef{ - Type: nav.FilterTypeRegexEn, - Description: "Contains 'o'", - Scope: nav.ScopeAllEn, - Pattern: "(i?)o", - }, - } + var setOptions func(o *nav.TraverseOptions) + + BeforeAll(func() { + setOptions = func(o *nav.TraverseOptions) { + o.Notify.OnBegin = begin("๐Ÿ›ก๏ธ") + o.Store.Subscription = nav.SubscribeFolders + o.Store.FilterDefs = &nav.FilterDefinitions{ + Node: nav.FilterDef{ + Type: nav.FilterTypeRegexEn, + Description: "Contains 'o'", + Scope: nav.ScopeAllEn, + Pattern: "(i?)o", + }, + } - o.Store.ListenDefs = nav.ListenDefinitions{ - StartAt: &nav.FilterDef{ - Type: nav.FilterTypeCustomEn, - Custom: &helpers.CustomFilter{ - Value: "Orbital", - Name: "Start Listening At: Orbital", - }, + o.Store.ListenDefs = nav.ListenDefinitions{ + StartAt: &nav.FilterDef{ + Type: nav.FilterTypeCustomEn, + Custom: &helpers.CustomFilter{ + Value: "Orbital", + Name: "Start Listening At: Orbital", }, - StopAt: &nav.FilterDef{ - Type: nav.FilterTypeCustomEn, - Custom: &helpers.CustomFilter{ - Value: "Underworld", - Name: "Stop Listening At: Underworld", - }, + }, + StopAt: &nav.FilterDef{ + Type: nav.FilterTypeCustomEn, + Custom: &helpers.CustomFilter{ + Value: "Underworld", + Name: "Stop Listening At: Underworld", }, - } + }, + } - o.Notify.OnStart = func(description string) { - GinkgoWriter.Printf("===> ๐ŸŽถ Start Listening: '%v'\n", description) - } - o.Notify.OnStop = func(description string) { - GinkgoWriter.Printf("===> โ›” Stop Listening: '%v'\n", description) - } - o.Store.DoExtend = true - o.Callback = &nav.LabelledTraverseCallback{ - Label: "Listener Test Callback", - Fn: func(item *nav.TraverseItem) error { - GinkgoWriter.Printf("---> ๐Ÿ”Š LISTENING-CALLBACK: name: '%v'\n", - item.Extension.Name, - ) - GinkgoWriter.Printf( - "===> โš—๏ธ Regex Filter(%v) source: '%v', item-name: '%v', item-scope(fs): '%v(%v)'\n", - o.Store.FilterDefs.Node.Description, - o.Store.FilterDefs.Node.Pattern, - item.Extension.Name, - item.Extension.NodeScope, - o.Store.FilterDefs.Node.Scope, - ) - Expect(item.Extension.Name).To(MatchRegexp(o.Store.FilterDefs.Node.Pattern), - helpers.Reason(item.Extension.Name), - ) - return nil - }, - } - o.Store.Logging = logo() + o.Notify.OnStart = func(description string) { + GinkgoWriter.Printf("===> ๐ŸŽถ Start Listening: '%v'\n", description) } + o.Notify.OnStop = func(description string) { + GinkgoWriter.Printf("===> โ›” Stop Listening: '%v'\n", description) + } + o.Store.DoExtend = true + o.Callback = &nav.LabelledTraverseCallback{ + Label: "Listener Test Callback", + Fn: func(item *nav.TraverseItem) error { + GinkgoWriter.Printf("---> ๐Ÿ”Š LISTENING-CALLBACK: name: '%v'\n", + item.Extension.Name, + ) + GinkgoWriter.Printf( + "===> โš—๏ธ Regex Filter(%v) source: '%v', item-name: '%v', item-scope(fs): '%v(%v)'\n", + o.Store.FilterDefs.Node.Description, + o.Store.FilterDefs.Node.Pattern, + item.Extension.Name, + item.Extension.NodeScope, + o.Store.FilterDefs.Node.Scope, + ) + Expect(item.Extension.Name).To(MatchRegexp(o.Store.FilterDefs.Node.Pattern), + helpers.Reason(item.Extension.Name), + ) + return nil + }, + } + o.Store.Logging = logo() + } + }) + Context("given: filter and listen both active", func() { + It("๐Ÿงช should: apply filter within the listen range", func() { + path := helpers.Path(root, "edm/ELECTRONICA") + optionFn := setOptions result, _ := nav.New().Primary(&nav.Prime{ Path: path, OptionsFn: optionFn, @@ -349,6 +354,26 @@ var _ = Describe("Listener", Ordered, func() { _ = result.Session.StartedAt() _ = result.Session.Elapsed() }) + + When("using ProvidedOptions", func() { + It("๐Ÿงช should: apply filter within the listen range", func() { + providedOptions := nav.GetDefaultOptions() + setOptions(providedOptions) // the sort option is not being set properly + // + path := helpers.Path(root, "edm/ELECTRONICA") + result, _ := nav.New().Primary(&nav.Prime{ + Path: path, + ProvidedOptions: providedOptions, + }).Run() + + files := result.Metrics.Count(nav.MetricNoFilesInvokedEn) + folders := result.Metrics.Count(nav.MetricNoFoldersInvokedEn) + + GinkgoWriter.Printf("---> ๐Ÿ•๐Ÿ• Metrics, files:'%v', folders:'%v'\n", + files, folders, + ) + }) + }) }) }) }) diff --git a/xfs/nav/navigation-runner.go b/xfs/nav/navigation-runner.go index 0c73f73..7ae7943 100644 --- a/xfs/nav/navigation-runner.go +++ b/xfs/nav/navigation-runner.go @@ -83,8 +83,9 @@ func (r *runner) With(with CreateNewRunnerWith, info *RunnerInfo) NavigationRunn lo.TernaryF(with&RunnerWithResume == 0, func() NavigationRunner { return r.Primary(&Prime{ - Path: info.PrimeInfo.Path, - OptionsFn: info.PrimeInfo.OptionsFn, + Path: info.PrimeInfo.Path, + OptionsFn: info.PrimeInfo.OptionsFn, + ProvidedOptions: info.PrimeInfo.ProvidedOptions, }) }, func() NavigationRunner { @@ -146,8 +147,9 @@ func IfWithPoolUseContext(with CreateNewRunnerWith, args ...any) []any { func (r *runner) Primary(info *Prime) NavigationRunner { r.session = &Primary{ - Path: info.Path, - OptionFn: info.OptionsFn, + Path: info.Path, + OptionFn: info.OptionsFn, + ProvidedOptions: info.ProvidedOptions, } return r diff --git a/xfs/nav/navigation-session.go b/xfs/nav/navigation-session.go index 22b69d2..8073674 100644 --- a/xfs/nav/navigation-session.go +++ b/xfs/nav/navigation-session.go @@ -1,6 +1,7 @@ package nav import ( + "errors" "time" xi18n "github.com/snivilised/extendio/i18n" @@ -47,9 +48,10 @@ func (s *session) finish(result *TraverseResult, _ error) { // Primary type Primary struct { session - Path string - OptionFn TraverseOptionFn - navigator TraverseNavigator + Path string + OptionFn TraverseOptionFn + ProvidedOptions *TraverseOptions + navigator TraverseNavigator } // Save persists the current state for a primary session, that allows @@ -59,7 +61,16 @@ func (s *Primary) Save(path string) error { } func (s *Primary) init() { - s.navigator = navigatorFactory{}.new(s.OptionFn) + switch { + case s.OptionFn != nil: + s.navigator = navigatorFactory{}.fromOptionsFn(s.OptionFn) + + case s.ProvidedOptions != nil: + s.navigator = navigatorFactory{}.fromProvidedOptions(s.ProvidedOptions) + + default: + panic(errors.New("missing traverse options")) + } } func (s *Primary) run(sync NavigationSync, args ...any) (*TraverseResult, error) { diff --git a/xfs/nav/navigation-sync.go b/xfs/nav/navigation-sync.go index 84d5b26..dcc0e63 100644 --- a/xfs/nav/navigation-sync.go +++ b/xfs/nav/navigation-sync.go @@ -14,7 +14,6 @@ type NavigationSync interface { } type baseSync struct { - session TraverseSession } func (s *baseSync) extract(args ...any) (bool, context.Context, context.CancelFunc) { @@ -51,7 +50,6 @@ func (s *baseSync) extract(args ...any) (bool, context.Context, context.CancelFu } type inlineSync struct { - baseSync } func (s *inlineSync) Run(callback sessionCallback, _ syncable, _ ...any) (*TraverseResult, error) { diff --git a/xfs/nav/navigation-with-runner_test.go b/xfs/nav/navigation-with-runner_test.go index 72b4494..f0740e5 100644 --- a/xfs/nav/navigation-with-runner_test.go +++ b/xfs/nav/navigation-with-runner_test.go @@ -4,6 +4,7 @@ import ( "context" "time" + "github.com/fortytw2/leaktest" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -16,15 +17,31 @@ import ( var _ = Describe("NavigationWithRunner", Ordered, func() { var ( - root string - jroot string - fromJSONPath string + root string + jroot string + fromJSONPath string + path string + now int + jobsChOut boost.JobStream[nav.TraverseItemInput] + jobsOutputChOut boost.JobOutputStream[nav.TraverseOutput] + setOptions func(o *nav.TraverseOptions) ) BeforeAll(func() { root = musico() jroot = helpers.JoinCwd("Test", "json") fromJSONPath = helpers.Path(jroot, "resume-state.json") + path = helpers.Path(root, "RETRO-WAVE") + now = 3 + + setOptions = func(o *nav.TraverseOptions) { + o.Notify.OnBegin = begin("๐Ÿ›ก๏ธ") + o.Store.Subscription = nav.SubscribeAny + o.Callback = universalCallbackNoAssert( + "universal: Path contains folders(Prime-WithRunner)", + NotExtended, + ) + } }) BeforeEach(func() { @@ -33,120 +50,171 @@ var _ = Describe("NavigationWithRunner", Ordered, func() { }); err != nil { Fail(err.Error()) } + + jobsChOut = make(boost.JobStream[nav.TraverseItemInput], DefaultJobsChSize) + jobsOutputChOut = make(boost.JobOutputStream[nav.TraverseOutput], DefaultJobsChSize) }) Context("resume and worker pool acceleration", func() { + var ( + restorer func(o *nav.TraverseOptions, active *nav.ActiveState) + ) + + BeforeAll(func() { + restorer = func(o *nav.TraverseOptions, active *nav.ActiveState) { + // synthetic assignments + // + active.Root = helpers.Path(root, "RETRO-WAVE") + active.NodePath = helpers.Path(root, ResumeAtTeenageColor) + active.Listen = nav.ListenPending + o.Store.Subscription = nav.SubscribeAny + // + // end of synthetic assignments + o.Callback = universalCallbackNoAssert( + "universal: listen pending(Resume-WithRunner)", + NotExtended, + ) + } + }) + When("universal: listen pending(logged)", func() { - It("๐Ÿงช should: ...", SpecTimeout(time.Second*5), func(ctxSpec SpecContext) { - ctx, cancel := context.WithCancel(ctxSpec) - path := helpers.Path(root, "RETRO-WAVE") - restorer := func(o *nav.TraverseOptions, active *nav.ActiveState) { - // synthetic assignments - // - active.Root = helpers.Path(root, "RETRO-WAVE") - active.NodePath = helpers.Path(root, ResumeAtTeenageColor) - active.Listen = nav.ListenPending - o.Store.Subscription = nav.SubscribeAny - // - // end of synthetic assignments - o.Callback = universalCallbackNoAssert( - "universal: listen pending(Resume-WithRunner)", - NotExtended, + It("๐Ÿงช should: resume without error", + SpecTimeout(time.Second*5), + func(ctxSpec SpecContext) { + defer leaktest.Check(GinkgoT())() + + ctx, cancel := context.WithCancel(ctxSpec) + wgan := boost.NewAnnotatedWaitGroup("๐Ÿ‚ traversal") + wgan.Add(1, navigatorRoutineName) + createWith := nav.RunnerWithResume | nav.RunnerWithPool + + result, err := nav.New().With(createWith, &nav.RunnerInfo{ + PrimeInfo: &nav.Prime{ + Path: path, + OptionsFn: setOptions, + }, + ResumeInfo: &nav.Resumption{ + RestorePath: fromJSONPath, + Restorer: restorer, + Strategy: nav.ResumeStrategySpawnEn, + }, + AccelerationInfo: &nav.Acceleration{ + WgAn: wgan, + RoutineName: navigatorRoutineName, + NoW: now, + JobsChOut: jobsChOut, + JobResultsCh: jobsOutputChOut, + OutputChTimeout: outputChTimeout, + }, + }).Run( + nav.IfWithPoolUseContext(createWith, ctx, cancel)..., ) - } + + if createWith&nav.RunnerWithPool > 0 { + wgan.Wait("๐Ÿ‘พ test-main") + } + + Expect(err).Error().To(BeNil()) + _ = result.Session.StartedAt() + _ = result.Session.Elapsed() + }, + ) + }) + }) + + When("Filter Applied", func() { + It("๐Ÿงช should: only invoke sync callback for filtered items", + SpecTimeout(time.Second*5), + func(ctxSpec SpecContext) { + defer leaktest.Check(GinkgoT())() + + ctx, cancel := context.WithCancel(ctxSpec) + defer cancel() wgan := boost.NewAnnotatedWaitGroup("๐Ÿ‚ traversal") wgan.Add(1, navigatorRoutineName) - createWith := nav.RunnerWithResume | nav.RunnerWithPool - now := 3 - JobsChOut := make(boost.JobStream[nav.TraverseItemInput], DefaultJobsChSize) - jobsOutputChOut := make(boost.JobOutputStream[nav.TraverseOutput], DefaultJobsChSize) - - result, err := nav.New().With(createWith, &nav.RunnerInfo{ - PrimeInfo: &nav.Prime{ - Path: path, - OptionsFn: func(o *nav.TraverseOptions) { - o.Notify.OnBegin = begin("๐Ÿ›ก๏ธ") - o.Store.Subscription = nav.SubscribeAny - o.Callback = universalCallbackNoAssert( - "universal: Path contains folders(Prime-WithRunner)", - NotExtended, - ) - }, + + filterDefs := &nav.FilterDefinitions{ + Node: nav.FilterDef{ + Type: nav.FilterTypeGlobEn, + Description: "flac files", + Pattern: "*.flac", + Scope: nav.ScopeLeafEn, }, - ResumeInfo: &nav.Resumption{ - RestorePath: fromJSONPath, - Restorer: restorer, - Strategy: nav.ResumeStrategySpawnEn, + } + + result, err := nav.New().Primary(&nav.Prime{ + Path: path, + OptionsFn: func(o *nav.TraverseOptions) { + o.Notify.OnBegin = begin("๐Ÿ›ก๏ธ") + o.Store.Subscription = nav.SubscribeFiles + o.Callback = universalCallbackNoAssert( + "filtered *.flac files: WithPool", + NotExtended, + ) + o.Store.FilterDefs = filterDefs }, - AccelerationInfo: &nav.Acceleration{ - WgAn: wgan, - RoutineName: navigatorRoutineName, - NoW: now, - JobsChOut: JobsChOut, - JobResultsCh: jobsOutputChOut, - OutputChTimeout: outputChTimeout, + }).WithPool( + &nav.AsyncInfo{ + NavigatorRoutineName: navigatorRoutineName, + WaitAQ: wgan, + JobsChanOut: jobsChOut, }, - }).Run( - nav.IfWithPoolUseContext(createWith, ctx, cancel)..., - ) + ).NoW(now).Run(ctx, cancel) - if createWith&nav.RunnerWithPool > 0 { - wgan.Wait("๐Ÿ‘พ test-main") - } + wgan.Wait("๐Ÿ‘พ test-main") Expect(err).Error().To(BeNil()) _ = result.Session.StartedAt() _ = result.Session.Elapsed() - }) - }) - }) - - When("Filter Applied", func() { - It("๐Ÿงช should: only invoke sync callback for filtered items", func(ctxSpec SpecContext) { - ctx, cancel := context.WithCancel(ctxSpec) - defer cancel() - - path := helpers.Path(root, "RETRO-WAVE") - - wgan := boost.NewAnnotatedWaitGroup("๐Ÿ‚ traversal") - wgan.Add(1, navigatorRoutineName) - now := 3 - jobsChOut := make(boost.JobStream[nav.TraverseItemInput], DefaultJobsChSize) - - filterDefs := &nav.FilterDefinitions{ - Node: nav.FilterDef{ - Type: nav.FilterTypeGlobEn, - Description: "flac files", - Pattern: "*.flac", - Scope: nav.ScopeLeafEn, - }, - } + }, + ) + When("using ProvidedOptions", func() { + It("๐Ÿงช should: ProvideOptions - only invoke sync callback for filtered items", + SpecTimeout(time.Second*5), + func(ctxSpec SpecContext) { + defer leaktest.Check(GinkgoT())() + + ctx, cancel := context.WithCancel(ctxSpec) + defer cancel() + + wgan := boost.NewAnnotatedWaitGroup("๐Ÿ‚ traversal") + wgan.Add(1, navigatorRoutineName) + + filterDefs := &nav.FilterDefinitions{ + Node: nav.FilterDef{ + Type: nav.FilterTypeGlobEn, + Description: "flac files", + Pattern: "*.flac", + Scope: nav.ScopeLeafEn, + }, + } - result, err := nav.New().Primary(&nav.Prime{ - Path: path, - OptionsFn: func(o *nav.TraverseOptions) { - o.Notify.OnBegin = begin("๐Ÿ›ก๏ธ") - o.Store.Subscription = nav.SubscribeFiles - o.Callback = universalCallbackNoAssert( + providedOptions := nav.GetDefaultOptions() + providedOptions.Notify.OnBegin = begin("๐Ÿ›ก๏ธ") + providedOptions.Store.Subscription = nav.SubscribeFiles + providedOptions.Callback = universalCallbackNoAssert( "filtered *.flac files: WithPool", NotExtended, ) - o.Store.FilterDefs = filterDefs - }, - }).WithPool( - &nav.AsyncInfo{ - NavigatorRoutineName: navigatorRoutineName, - WaitAQ: wgan, - JobsChanOut: jobsChOut, - }, - ).NoW(now).Run(ctx, cancel) + providedOptions.Store.FilterDefs = filterDefs + + _, err := nav.New().Primary(&nav.Prime{ + Path: path, + ProvidedOptions: providedOptions, // ๐Ÿ˜Ž + }).WithPool( + &nav.AsyncInfo{ + NavigatorRoutineName: navigatorRoutineName, + WaitAQ: wgan, + JobsChanOut: jobsChOut, + }, + ).NoW(now).Run(ctx, cancel) - wgan.Wait("๐Ÿ‘พ test-main") + wgan.Wait("๐Ÿ‘พ test-main") - Expect(err).Error().To(BeNil()) - _ = result.Session.StartedAt() - _ = result.Session.Elapsed() + Expect(err).Error().To(BeNil()) + }, + ) }) }) }) diff --git a/xfs/nav/navigator-folders.go b/xfs/nav/navigator-folders.go index bdb674c..8051033 100644 --- a/xfs/nav/navigator-folders.go +++ b/xfs/nav/navigator-folders.go @@ -44,7 +44,7 @@ func (n *foldersNavigator) traverse(params *traverseParams) error { DirectoryEntryOrderFoldersFirstEn, ) folders := entries.Folders - entries.sort(&folders) + entries.sort(&folders) // !!!! if n.o.Store.Subscription == SubscribeFoldersWithFiles { var files []fs.DirEntry diff --git a/xfs/nav/new-navigator.go b/xfs/nav/new-navigator.go index 4e8e767..e350b16 100644 --- a/xfs/nav/new-navigator.go +++ b/xfs/nav/new-navigator.go @@ -9,13 +9,7 @@ import ( type navigatorFactory struct{} -func (f navigatorFactory) new(fn ...TraverseOptionFn) TraverseNavigator { - o := composeTraverseOptions(fn...) - - if o.Callback.Fn == nil { - panic(xi18n.NewMissingCallbackError()) - } - +func (f navigatorFactory) new(o *TraverseOptions) TraverseNavigator { impl := navigatorImplFactory{}.new(o) nc := &navigationController{ impl: impl, @@ -30,6 +24,24 @@ func (f navigatorFactory) new(fn ...TraverseOptionFn) TraverseNavigator { return nc } +func (f navigatorFactory) fromOptionsFn(fn ...TraverseOptionFn) TraverseNavigator { + o := composeTraverseOptions(fn...) + + if o.Callback.Fn == nil { + panic(xi18n.NewMissingCallbackError()) + } + + return f.new(o) +} + +func (f navigatorFactory) fromProvidedOptions(o *TraverseOptions) TraverseNavigator { + nav := f.new(o) + + o.afterUserOptions() + + return nav +} + type navigatorImplFactory struct{} func (f navigatorImplFactory) new(o *TraverseOptions) navigatorImpl { diff --git a/xfs/nav/traverse-defs.go b/xfs/nav/traverse-defs.go index 7ee3b1d..ddfaa88 100644 --- a/xfs/nav/traverse-defs.go +++ b/xfs/nav/traverse-defs.go @@ -112,8 +112,9 @@ func (r *TraverseResult) merge(other *TraverseResult) (*TraverseResult, error) { } type Prime struct { - Path string - OptionsFn TraverseOptionFn + Path string + OptionsFn TraverseOptionFn + ProvidedOptions *TraverseOptions } // Resumption