diff --git a/cmd/depviz/main.go b/cmd/depviz/main.go index 3fcf67314..9f9d373e7 100644 --- a/cmd/depviz/main.go +++ b/cmd/depviz/main.go @@ -70,6 +70,8 @@ var ( genHideExternalDeps = genFlags.Bool("hide-external-deps", false, "hide dependencies outside of the specified targets") genHideIsolated = genFlags.Bool("hide-isolated", false, "hide isolated tasks") genShowClosed = genFlags.Bool("show-closed", false, "show closed tasks") + genScope = genFlags.String("scope", "", "target scope") + genScopeSize = genFlags.Int("scope-size", 1, "scope size") fetchFlags = flag.NewFlagSet("fetch", flag.ExitOnError) fetchGitHubToken = fetchFlags.String("github-token", "", "GitHub token") @@ -371,6 +373,8 @@ func execGenGraphviz(ctx context.Context, args []string) error { HideIsolated: *genHideIsolated, HidePRs: *genHidePRs, HideExternalDeps: *genHideExternalDeps, + Scope: *genScope, + ScopeSize: *genScopeSize, } opts := dvcore.GraphvizOpts{ @@ -403,6 +407,8 @@ func execGenJSON(ctx context.Context, args []string) error { HideIsolated: *genHideIsolated, HidePRs: *genHidePRs, HideExternalDeps: *genHideExternalDeps, + Scope: *genScope, + ScopeSize: *genScopeSize, Format: "json", } @@ -429,6 +435,8 @@ func execGenCSV(ctx context.Context, args []string) error { HideIsolated: *genHideIsolated, HidePRs: *genHidePRs, HideExternalDeps: *genHideExternalDeps, + Scope: *genScope, + ScopeSize: *genScopeSize, Format: "csv", } diff --git a/go.mod b/go.mod index 020f2e40d..cf8000648 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module moul.io/depviz/v3 go 1.19 require ( + github.com/Doozers/gl v0.0.0-20230526153138-d7477da16375 github.com/cayleygraph/cayley v0.7.7 github.com/cayleygraph/quad v1.2.4 github.com/go-chi/chi v1.5.4 @@ -19,11 +20,12 @@ require ( github.com/patrickmn/go-cache v2.1.0+incompatible github.com/peterbourgon/ff/v3 v3.3.0 github.com/rs/cors v1.8.3 - github.com/stretchr/testify v1.8.2 + github.com/stretchr/testify v1.8.3 github.com/tailscale/depaware v0.0.0-20210622194025-720c4b409502 github.com/treastech/logger v0.0.0-20180705232552-e381e9ecf2e3 github.com/xhit/go-str2duration/v2 v2.1.0 go.uber.org/zap v1.24.0 + golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 golang.org/x/oauth2 v0.6.0 golang.org/x/tools v0.6.0 google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 diff --git a/go.sum b/go.sum index c333c40f2..8c125f9c0 100644 --- a/go.sum +++ b/go.sum @@ -42,6 +42,8 @@ github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOv github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/Doozers/gl v0.0.0-20230526153138-d7477da16375 h1:eX3dshrL6eaedzgzgvjKQqDPvJWmmUyWu++kfla6/3s= +github.com/Doozers/gl v0.0.0-20230526153138-d7477da16375/go.mod h1:PlImHWfE2dwwNrn6e7+oMeMCJyPpYa7EO+ZI/2Smb6E= github.com/Microsoft/go-winio v0.4.12/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= @@ -473,18 +475,14 @@ github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/y github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= github.com/tailscale/depaware v0.0.0-20201214215404-77d1e9757027/go.mod h1:p9lPsd+cx33L3H9nNoecRRxPssFKUwwI50I3pZ0yT+8= @@ -566,6 +564,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc= +golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.6.0 h1:bR8b5okrPI3g/gyZakLZHeWxAR8Dn5CyxXv1hLH5g/4= diff --git a/pkg/dvcore/gen.go b/pkg/dvcore/gen.go index 4360ab295..2b0dcfc27 100644 --- a/pkg/dvcore/gen.go +++ b/pkg/dvcore/gen.go @@ -35,6 +35,8 @@ type GenOpts struct { HideIsolated bool HidePRs bool HideExternalDeps bool + Scope string + ScopeSize int } func Gen(h *cayley.Handle, args []string, opts GenOpts) error { @@ -50,87 +52,97 @@ func Gen(h *cayley.Handle, args []string, opts GenOpts) error { return fmt.Errorf("parse targets: %w", err) } - if !opts.NoGraph { // nolint:nestif - // load tasks - filters := dvmodel.Filters{ - Targets: targets, - WithClosed: opts.ShowClosed, - WithoutIsolated: opts.HideIsolated, - WithoutPRs: opts.HidePRs, - WithoutExternalDeps: opts.HideExternalDeps, - } - tasks, err := dvstore.LoadTasks(h, opts.Schema, filters, opts.Logger) + var scope multipmuri.Entity + if opts.Scope != "" { + scope, err = dvparser.ParseTarget(opts.Scope) if err != nil { return fmt.Errorf("load tasks: %w", err) } + } - // graph - pertConfig := graphmanPertConfig(tasks, opts) - - switch opts.Format { - case "json": - return genJSON(tasks) - case "csv": - return genCSV(tasks) - case "graphman-pert": - out, err := yaml.Marshal(pertConfig) - if err != nil { - return err - } - fmt.Println(string(out)) - return nil - // TODO: fix many issues with generated dependencies - //case "dot": - // // graph from PERT config - // graph := graphman.FromPertConfig(*pertConfig) - // - // // initialize graph from config - // if !opts.NoPert { - // result := graphman.ComputePert(graph) - // shortestPath, distance := graph.FindShortestPath("Start", "Finish") - // opts.Logger.Debug("pert result", zap.Any("result", result), zap.Int64("distance", distance)) - // - // for _, edge := range shortestPath { - // edge.Dst().SetColor("red") - // edge.SetColor("red") - // } - // } - // - // // graph fine tuning - // graph.GetVertex("Start").SetColor("blue") - // graph.GetVertex("Finish").SetColor("blue") - // if opts.Vertical { - // graph.Attrs["rankdir"] = "TB" - // } - // graph.Attrs["overlap"] = "false" - // graph.Attrs["pack"] = "true" - // graph.Attrs["splines"] = "true" - // graph.Attrs["sep"] = "0.1" - // // graph.Attrs["layout"] = "neato" - // // graph.Attrs["size"] = "\"11,11\"" - // // graph.Attrs["start"] = "random" - // // FIXME: hightlight critical paths - // // FIXME: highlight other infos - // // FIXME: highlight target - // - // // graphviz - // s, err := viz.ToGraphviz(graph, &viz.Opts{ - // CommentsInLabel: true, - // }) - // if err != nil { - // return fmt.Errorf("graphviz: %w", err) - // } - // - // fmt.Println(s) - // return nil - case "quads": - return fmt.Errorf("not implemented") - default: - return fmt.Errorf("unsupported graph format: %q", opts.Format) - } + if opts.NoGraph { // nolint:nestif + return nil } - return nil + // load tasks + filters := dvmodel.Filters{ + Targets: targets, + WithClosed: opts.ShowClosed, + WithoutIsolated: opts.HideIsolated, + WithoutPRs: opts.HidePRs, + WithoutExternalDeps: opts.HideExternalDeps, + Scope: scope, + ScopeSize: opts.ScopeSize, + } + tasks, err := dvstore.LoadTasks(h, opts.Schema, filters, opts.Logger) + if err != nil { + return fmt.Errorf("load tasks: %w", err) + } + + // graph + pertConfig := graphmanPertConfig(tasks, opts) + + switch opts.Format { + case "json": + return genJSON(tasks) + case "csv": + return genCSV(tasks) + case "graphman-pert": + out, err := yaml.Marshal(pertConfig) + if err != nil { + return err + } + fmt.Println(string(out)) + return nil + // TODO: fix many issues with generated dependencies + //case "dot": + // // graph from PERT config + // graph := graphman.FromPertConfig(*pertConfig) + // + // // initialize graph from config + // if !opts.NoPert { + // result := graphman.ComputePert(graph) + // shortestPath, distance := graph.FindShortestPath("Start", "Finish") + // opts.Logger.Debug("pert result", zap.Any("result", result), zap.Int64("distance", distance)) + // + // for _, edge := range shortestPath { + // edge.Dst().SetColor("red") + // edge.SetColor("red") + // } + // } + // + // // graph fine tuning + // graph.GetVertex("Start").SetColor("blue") + // graph.GetVertex("Finish").SetColor("blue") + // if opts.Vertical { + // graph.Attrs["rankdir"] = "TB" + // } + // graph.Attrs["overlap"] = "false" + // graph.Attrs["pack"] = "true" + // graph.Attrs["splines"] = "true" + // graph.Attrs["sep"] = "0.1" + // // graph.Attrs["layout"] = "neato" + // // graph.Attrs["size"] = "\"11,11\"" + // // graph.Attrs["start"] = "random" + // // FIXME: hightlight critical paths + // // FIXME: highlight other infos + // // FIXME: highlight target + // + // // graphviz + // s, err := viz.ToGraphviz(graph, &viz.Opts{ + // CommentsInLabel: true, + // }) + // if err != nil { + // return fmt.Errorf("graphviz: %w", err) + // } + // + // fmt.Println(s) + // return nil + case "quads": + return fmt.Errorf("not implemented") + default: + return fmt.Errorf("unsupported graph format: %q", opts.Format) + } } func pullBatches(targets []multipmuri.Entity, h *cayley.Handle, githubToken string, resync bool, logger *zap.Logger) []dvmodel.Batch { diff --git a/pkg/dvmodel/filters.go b/pkg/dvmodel/filters.go index 89cb08373..faa0b63ce 100644 --- a/pkg/dvmodel/filters.go +++ b/pkg/dvmodel/filters.go @@ -12,4 +12,6 @@ type Filters struct { WithoutPRs bool WithoutExternalDeps bool WithFetch bool + Scope multipmuri.Entity + ScopeSize int } diff --git a/pkg/dvstore/query.go b/pkg/dvstore/query.go index a9643028a..d0a4b5c0f 100644 --- a/pkg/dvstore/query.go +++ b/pkg/dvstore/query.go @@ -60,23 +60,35 @@ func LoadTasks(h *cayley.Handle, schema *schema.Config, filters dvmodel.Filters, ctx := context.TODO() // fetch targets - paths := []*path.Path{} - if filters.TheWorld { - paths = append(paths, path.StartPath(h)) - } else { - for _, target := range filters.Targets { - // FIXME: handle different target types (for now only repo) - p := path.StartPath(h, quad.IRI(target.String())). - Both(). - Has(quad.IRI("rdf:type"), quad.IRI("dv:Task")) - - // FIXME: reverse depends/blocks - paths = append(paths, p) + var p *path.Path + if filters.Scope == nil { + paths := []*path.Path{} + if filters.TheWorld { + paths = append(paths, path.StartPath(h)) + } else { + for _, target := range filters.Targets { + // FIXME: handle different target types (for now only repo) + p := path.StartPath(h, quad.IRI(target.String())). + Both(). + Has(quad.IRI("rdf:type"), quad.IRI("dv:Task")) + + // FIXME: reverse depends/blocks + paths = append(paths, p) + } } - } - p := paths[0] - for _, path := range paths[1:] { - p = p.Or(path) + p = paths[0] + for _, path := range paths[1:] { + p = p.Or(path) + } + } else { + p = path.StartPath(h, quad.IRI(filters.Scope.String())).Is(quad.IRI(filters.Scope.String())) + p = scopeIssue(p, filters.ScopeSize, []quad.IRI{ + "isDependingOn", + "isBlocking", + //"IsRelatedWith", + //"IsPartOf", + //"HasPart", + }) } // filters diff --git a/pkg/dvstore/query_test.go b/pkg/dvstore/query_test.go index eb775779b..5e7a6ab09 100644 --- a/pkg/dvstore/query_test.go +++ b/pkg/dvstore/query_test.go @@ -8,6 +8,7 @@ import ( _ "github.com/cayleygraph/quad/json" "github.com/stretchr/testify/assert" + "moul.io/depviz/v3/pkg/dvmodel" "moul.io/depviz/v3/pkg/dvparser" "moul.io/depviz/v3/pkg/multipmuri" "moul.io/depviz/v3/pkg/testutil" @@ -18,19 +19,19 @@ func TestLoadTasks(t *testing.T) { tests := []struct { golden string name string - filters LoadTasksFilters + filters dvmodel.Filters expectedErr error }{ - {"all-depviz-test", "theworld", LoadTasksFilters{TheWorld: true}, nil}, - {"all-depviz-test", "theworld-light", LoadTasksFilters{TheWorld: true, WithoutPRs: true, WithClosed: false, WithoutExternalDeps: true}, nil}, - {"all-depviz-test", "theworld-with-closed", LoadTasksFilters{TheWorld: true, WithClosed: true}, nil}, - {"all-depviz-test", "theworld-without-prs", LoadTasksFilters{TheWorld: true, WithoutPRs: true}, nil}, - {"all-depviz-test", "theworld-without-isolated", LoadTasksFilters{TheWorld: true, WithoutIsolated: true}, nil}, - {"all-depviz-test", "theworld-without-external-deps", LoadTasksFilters{TheWorld: true, WithoutExternalDeps: true}, nil}, - {"all-depviz-test", "theworld-all-flags", LoadTasksFilters{TheWorld: true, WithClosed: true, WithoutPRs: true, WithoutIsolated: true, WithoutExternalDeps: true}, nil}, - {"all-depviz-test", "moul-depviz-test", LoadTasksFilters{Targets: parseTargets(t, "moul/depviz-test")}, nil}, - {"all-depviz-test", "moulbot-depviz-test", LoadTasksFilters{Targets: parseTargets(t, "moul-bot/depviz-test")}, nil}, - {"all-depviz-test", "moul-and-moulbot-depviz-test", LoadTasksFilters{Targets: parseTargets(t, "moul/depviz-test, moul-bot/depviz-test")}, nil}, + {"all-depviz-test", "theworld", dvmodel.Filters{TheWorld: true}, nil}, + {"all-depviz-test", "theworld-light", dvmodel.Filters{TheWorld: true, WithoutPRs: true, WithClosed: false, WithoutExternalDeps: true}, nil}, + {"all-depviz-test", "theworld-with-closed", dvmodel.Filters{TheWorld: true, WithClosed: true}, nil}, + {"all-depviz-test", "theworld-without-prs", dvmodel.Filters{TheWorld: true, WithoutPRs: true}, nil}, + {"all-depviz-test", "theworld-without-isolated", dvmodel.Filters{TheWorld: true, WithoutIsolated: true}, nil}, + {"all-depviz-test", "theworld-without-external-deps", dvmodel.Filters{TheWorld: true, WithoutExternalDeps: true}, nil}, + {"all-depviz-test", "theworld-all-flags", dvmodel.Filters{TheWorld: true, WithClosed: true, WithoutPRs: true, WithoutIsolated: true, WithoutExternalDeps: true}, nil}, + {"all-depviz-test", "moul-depviz-test", dvmodel.Filters{Targets: parseTargets(t, "moul/depviz-test")}, nil}, + {"all-depviz-test", "moulbot-depviz-test", dvmodel.Filters{Targets: parseTargets(t, "moul-bot/depviz-test")}, nil}, + {"all-depviz-test", "moul-and-moulbot-depviz-test", dvmodel.Filters{Targets: parseTargets(t, "moul/depviz-test, moul-bot/depviz-test")}, nil}, } alreadySeen := map[string]bool{} for _, testptr := range tests { diff --git a/pkg/dvstore/scope.go b/pkg/dvstore/scope.go new file mode 100644 index 000000000..209245e87 --- /dev/null +++ b/pkg/dvstore/scope.go @@ -0,0 +1,39 @@ +package dvstore + +import ( + "github.com/cayleygraph/cayley/graph/path" + "github.com/cayleygraph/quad" +) + +func genCombWithRep[T interface{}](n int, elements []T, currentCombination []T, combinations *[][]T) { + if n == 0 { + *combinations = append(*combinations, append([]T(nil), currentCombination...)) + } else { + for _, element := range elements { + currentCombination = append(currentCombination, element) + genCombWithRep(n-1, elements, currentCombination, combinations) + currentCombination = currentCombination[:len(currentCombination)-1] + } + } +} + +func fold[T interface{}, Y interface{}](initial Y, arr []T, f func(Y, T) Y) Y { + for _, v := range arr { + initial = f(initial, v) + } + return initial +} + +func scopeIssue(p *path.Path, scopeSize int, possibilities []quad.IRI) *path.Path { + if scopeSize == 0 { + return p + } + var perms [][]quad.IRI + var currentPerms []quad.IRI + genCombWithRep(scopeSize, possibilities, currentPerms, &perms) + + for _, perm := range perms { + p = p.Or(fold(p, perm, func(_path *path.Path, _link quad.IRI) *path.Path { return _path.Both(_link) })) + } + return p +} diff --git a/pkg/dvstore/scope_test.go b/pkg/dvstore/scope_test.go new file mode 100644 index 000000000..e71c57f31 --- /dev/null +++ b/pkg/dvstore/scope_test.go @@ -0,0 +1,97 @@ +package dvstore + +import ( + "testing" + + "github.com/Doozers/gl/pkg/funct" + "github.com/cayleygraph/quad" + "golang.org/x/exp/slices" + "moul.io/depviz/v3/pkg/dvmodel" + "moul.io/depviz/v3/pkg/dvparser" + "moul.io/depviz/v3/pkg/testutil" +) + +type recFunc func(task dvmodel.Task, remaining uint8, target string) bool + +func TestScopeIssue(t *testing.T) { + + tests := []struct { + target string + golden string + name string + filters dvmodel.Filters + }{ + { + "https://github.com/moul/depviz-test/issues/6", + "all-depviz-test", + "theworld", + dvmodel.Filters{ + Targets: nil, + TheWorld: true, + WithClosed: true, + WithoutIsolated: false, + WithoutPRs: true, + WithoutExternalDeps: true, + WithFetch: false, + ScopeSize: 1, + }, + }, + } + + logger := testutil.Logger(t) + for _, testptr := range tests { + test := testptr + store, closeFunc := TestingGoldenStore(t, test.golden) + defer closeFunc() + targetEntity, err := dvparser.ParseTarget(test.target) + if err != nil { + t.Fatal(err) + } + + test.filters.Scope = targetEntity + + tasks, err := LoadTasks(store, schemaConfig, test.filters, logger) + if err != nil { + return + } + + mtasks := map[string]dvmodel.Task{} + for _, task := range tasks { + mtasks[task.ID.String()] = task + } + + var rec recFunc + rec = func(_task dvmodel.Task, _remaining uint8, _target string) bool { + if _task.ID.String() == _target { + return true + } + + if _remaining == 0 { + return false + } + + if slices.Contains(funct.Map(_task.IsDependingOn, func(t quad.IRI) string { + return t.String() + }), _target) { + return true + } + for _, dep := range _task.IsDependingOn { + if t, ok := mtasks[dep.String()]; ok { + if rec(t, _remaining-1, _target) { + return true + } + } + } + return false + } + + for _, task := range tasks { + rec1 := rec(task, uint8(test.filters.ScopeSize), "<"+test.target+">") + rec2 := rec(mtasks["<"+test.target+">"], uint8(test.filters.ScopeSize), task.ID.String()) + + if rec1 == false && rec2 == false { + t.Errorf("task %s should be in the scope of %s", task.ID.String(), test.target) + } + } + } +}