diff --git a/internal/runbits/dependencies/summary.go b/internal/runbits/dependencies/summary.go index 61883d61f2..33fc2f6a49 100644 --- a/internal/runbits/dependencies/summary.go +++ b/internal/runbits/dependencies/summary.go @@ -18,6 +18,7 @@ func OutputSummary(out output.Outputer, directDependencies buildplan.Artifacts) } ingredients := directDependencies.Filter(buildplan.FilterStateArtifacts()).Ingredients() + commonDependencies := ingredients.CommonRuntimeDependencies().ToIDMap() sort.SliceStable(ingredients, func(i, j int) bool { return ingredients[i].Name < ingredients[j].Name @@ -32,12 +33,18 @@ func OutputSummary(out output.Outputer, directDependencies buildplan.Artifacts) prefix = " " + output.TreeEnd } - subdependencies := "" - if numSubs := len(ingredient.RuntimeDependencies(true)); numSubs > 0 { - subdependencies = locale.Tl("summary_subdeps", "([ACTIONABLE]{{.V0}}[/RESET] sub-dependencies)", strconv.Itoa(numSubs)) + subDependencies := ingredient.RuntimeDependencies(true) + if _, isCommon := commonDependencies[ingredient.IngredientID]; !isCommon { + // If the ingredient is itself not a common sub-dependency; filter out any common sub dependencies so we don't + // report counts multiple times. + subDependencies = subDependencies.Filter(buildplan.FilterOutIngredients{commonDependencies}.Filter) + } + subdepLocale := "" + if numSubs := len(subDependencies); numSubs > 0 { + subdepLocale = locale.Tl("summary_subdeps", "([ACTIONABLE]{{.V0}}[/RESET] sub-dependencies)", strconv.Itoa(numSubs)) } - item := fmt.Sprintf("[ACTIONABLE]%s@%s[/RESET] %s", ingredient.Name, ingredient.Version, subdependencies) + item := fmt.Sprintf("[ACTIONABLE]%s@%s[/RESET] %s", ingredient.Name, ingredient.Version, subdepLocale) out.Notice(fmt.Sprintf("[DISABLED]%s[/RESET] %s", prefix, item)) } diff --git a/pkg/buildplan/artifact.go b/pkg/buildplan/artifact.go index 6d718380e0..b793c66f03 100644 --- a/pkg/buildplan/artifact.go +++ b/pkg/buildplan/artifact.go @@ -259,7 +259,7 @@ func (a *Artifact) dependencies(recursive bool, maybeIgnore *map[strfmt.UUID]str for _, ac := range a.children { related := len(relations) == 0 for _, relation := range relations { - if ac.Relation == relation && raw.IsStateToolMimeType(ac.Artifact.MimeType) { + if ac.Relation == relation { related = true } } diff --git a/pkg/buildplan/buildplan.go b/pkg/buildplan/buildplan.go index 0f30cac869..83f0ee353e 100644 --- a/pkg/buildplan/buildplan.go +++ b/pkg/buildplan/buildplan.go @@ -31,7 +31,7 @@ func Unmarshal(data []byte) (*BuildPlan, error) { b.raw = &rawBuild - b.cleanup() + b.sanitize() if err := b.hydrate(); err != nil { return nil, errs.Wrap(err, "error hydrating build plan") @@ -46,12 +46,13 @@ func Unmarshal(data []byte) (*BuildPlan, error) { } func (b *BuildPlan) Marshal() ([]byte, error) { - return json.Marshal(b.raw) + return json.MarshalIndent(b.raw, "", " ") } -// cleanup empty targets -// The type aliasing in the query populates the response with emtpy targets that we should remove -func (b *BuildPlan) cleanup() { +// sanitize will remove empty targets and sort slices to ensure consistent interpretation of the same buildplan +// Empty targets: The type aliasing in the query populates the response with emtpy targets that we should remove +// Sorting: The API does not do any slice ordering, meaning the same buildplan retrieved twice can use different ordering +func (b *BuildPlan) sanitize() { b.raw.Steps = sliceutils.Filter(b.raw.Steps, func(s *raw.Step) bool { return s.StepID != "" }) @@ -63,6 +64,27 @@ func (b *BuildPlan) cleanup() { b.raw.Artifacts = sliceutils.Filter(b.raw.Artifacts, func(a *raw.Artifact) bool { return a.NodeID != "" }) + + sort.Slice(b.raw.Sources, func(i, j int) bool { return b.raw.Sources[i].NodeID < b.raw.Sources[j].NodeID }) + sort.Slice(b.raw.Steps, func(i, j int) bool { return b.raw.Steps[i].StepID < b.raw.Steps[j].StepID }) + sort.Slice(b.raw.Artifacts, func(i, j int) bool { return b.raw.Artifacts[i].NodeID < b.raw.Artifacts[j].NodeID }) + sort.Slice(b.raw.Terminals, func(i, j int) bool { return b.raw.Terminals[i].Tag < b.raw.Terminals[j].Tag }) + sort.Slice(b.raw.ResolvedRequirements, func(i, j int) bool { + return b.raw.ResolvedRequirements[i].Source < b.raw.ResolvedRequirements[j].Source + }) + for _, t := range b.raw.Terminals { + sort.Slice(t.NodeIDs, func(i, j int) bool { return t.NodeIDs[i] < t.NodeIDs[j] }) + } + for _, a := range b.raw.Artifacts { + sort.Slice(a.RuntimeDependencies, func(i, j int) bool { return a.RuntimeDependencies[i] < a.RuntimeDependencies[j] }) + } + for _, step := range b.raw.Steps { + sort.Slice(step.Inputs, func(i, j int) bool { return step.Inputs[i].Tag < step.Inputs[j].Tag }) + sort.Slice(step.Outputs, func(i, j int) bool { return step.Outputs[i] < step.Outputs[j] }) + for _, input := range step.Inputs { + sort.Slice(input.NodeIDs, func(i, j int) bool { return input.NodeIDs[i] < input.NodeIDs[j] }) + } + } } func (b *BuildPlan) Platforms() []strfmt.UUID { diff --git a/pkg/buildplan/filters.go b/pkg/buildplan/filters.go index 242ef56e19..06a7f19b38 100644 --- a/pkg/buildplan/filters.go +++ b/pkg/buildplan/filters.go @@ -76,3 +76,12 @@ func FilterNotBuild() FilterArtifact { return a.Status != types.ArtifactSucceeded } } + +type FilterOutIngredients struct { + Ingredients IngredientIDMap +} + +func (f FilterOutIngredients) Filter(i *Ingredient) bool { + _, blacklist := f.Ingredients[i.IngredientID] + return !blacklist +} diff --git a/pkg/buildplan/hydrate.go b/pkg/buildplan/hydrate.go index e103f0ff56..216bcb7616 100644 --- a/pkg/buildplan/hydrate.go +++ b/pkg/buildplan/hydrate.go @@ -154,8 +154,6 @@ func (b *BuildPlan) hydrateWithIngredients(artifact *Artifact, platformID *strfm err := b.raw.WalkViaSteps([]strfmt.UUID{artifact.ArtifactID}, raw.TagSource, func(node interface{}, parent *raw.Artifact) error { switch v := node.(type) { - case *raw.Artifact: - return nil // We've already got our artifacts case *raw.Source: // logging.Debug("Walking source '%s (%s)'", v.Name, v.NodeID) @@ -193,8 +191,14 @@ func (b *BuildPlan) hydrateWithIngredients(artifact *Artifact, platformID *strfm return nil default: - return errs.New("unexpected node type '%T': %#v", v, v) + if a, ok := v.(*raw.Artifact); ok && a.NodeID == artifact.ArtifactID { + return nil // continue + } + // Source ingredients are only relevant when they link DIRECTLY to the artifact + return raw.WalkInterrupt{} } + + return nil }) if err != nil { return errs.Wrap(err, "error hydrating ingredients") @@ -210,7 +214,7 @@ func (b *BuildPlan) sanityCheck() error { // Ensure all artifacts have an associated ingredient // If this fails either the API is bugged or the hydrate logic is bugged for _, a := range b.Artifacts() { - if len(a.Ingredients) == 0 { + if raw.IsStateToolMimeType(a.MimeType) && len(a.Ingredients) == 0 { return errs.New("artifact '%s (%s)' does not have an ingredient", a.ArtifactID, a.DisplayName) } } diff --git a/pkg/buildplan/hydrate_test.go b/pkg/buildplan/hydrate_test.go new file mode 100644 index 0000000000..da95c9826e --- /dev/null +++ b/pkg/buildplan/hydrate_test.go @@ -0,0 +1,50 @@ +package buildplan + +import ( + "testing" + + "github.com/ActiveState/cli/internal/errs" + "github.com/ActiveState/cli/pkg/buildplan/mock" + "github.com/go-openapi/strfmt" + "github.com/stretchr/testify/require" +) + +func TestBuildPlan_hydrateWithIngredients(t *testing.T) { + tests := []struct { + name string + buildplan *BuildPlan + inputArtifact *Artifact + wantIngredient string + }{ + { + "Ingredient solves for simple artifact > src hop", + &BuildPlan{raw: mock.BuildWithRuntimeDepsViaSrc}, + &Artifact{ArtifactID: "00000000-0000-0000-0000-000000000007"}, + "00000000-0000-0000-0000-000000000009", + }, + { + "Installer should not resolve to an ingredient as it doesn't have a direct source", + &BuildPlan{raw: mock.BuildWithRuntimeDepsViaSrc}, + &Artifact{ArtifactID: "00000000-0000-0000-0000-000000000002"}, + "", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + b := tt.buildplan + if err := b.hydrateWithIngredients(tt.inputArtifact, nil, map[strfmt.UUID]*Ingredient{}); err != nil { + t.Fatalf("hydrateWithIngredients() error = %v", errs.JoinMessage(err)) + } + if tt.wantIngredient == "" { + require.Empty(t, tt.inputArtifact.Ingredients) + return + } + if len(tt.inputArtifact.Ingredients) != 1 { + t.Fatalf("expected 1 ingredient resolution, got %d", len(tt.inputArtifact.Ingredients)) + } + if string(tt.inputArtifact.Ingredients[0].IngredientID) != tt.wantIngredient { + t.Errorf("expected ingredient ID %s, got %s", tt.wantIngredient, tt.inputArtifact.Ingredients[0].IngredientID) + } + }) + } +} diff --git a/pkg/buildplan/ingredient.go b/pkg/buildplan/ingredient.go index 491a68b57f..e08a8e1dc4 100644 --- a/pkg/buildplan/ingredient.go +++ b/pkg/buildplan/ingredient.go @@ -11,7 +11,7 @@ type Ingredient struct { IsBuildtimeDependency bool IsRuntimeDependency bool - Artifacts []*Artifact + Artifacts Artifacts platforms []strfmt.UUID } @@ -61,7 +61,20 @@ func (i Ingredients) ToNameMap() IngredientNameMap { // CommonRuntimeDependencies returns the set of runtime dependencies that are common between all ingredients. // For example, given a set of python ingredients this will return at the very least the python language ingredient. func (i Ingredients) CommonRuntimeDependencies() Ingredients { + var is []ingredientsWithRuntimeDeps + for _, ig := range i { + is = append(is, ig) + } + return commonRuntimeDependencies(is) +} + +type ingredientsWithRuntimeDeps interface { + RuntimeDependencies(recursive bool) Ingredients +} + +func commonRuntimeDependencies(i []ingredientsWithRuntimeDeps) Ingredients { counts := map[strfmt.UUID]int{} + common := Ingredients{} for _, ig := range i { runtimeDeps := ig.RuntimeDependencies(true) @@ -70,13 +83,9 @@ func (i Ingredients) CommonRuntimeDependencies() Ingredients { counts[rd.IngredientID] = 0 } counts[rd.IngredientID]++ - } - } - - common := Ingredients{} - for _, ig := range i { - if counts[ig.IngredientID] == len(i) { - common = append(common, ig) + if counts[rd.IngredientID] == 2 { // only append on 2; we don't want dupes + common = append(common, rd) + } } } @@ -97,9 +106,6 @@ func (i *Ingredient) runtimeDependencies(recursive bool, seen map[strfmt.UUID]st dependencies := Ingredients{} for _, a := range i.Artifacts { - if !raw.IsStateToolMimeType(a.MimeType) { - continue - } for _, ac := range a.children { if ac.Relation != RuntimeRelation { continue diff --git a/pkg/buildplan/ingredient_test.go b/pkg/buildplan/ingredient_test.go index e747e913ce..85ca41de07 100644 --- a/pkg/buildplan/ingredient_test.go +++ b/pkg/buildplan/ingredient_test.go @@ -2,7 +2,10 @@ package buildplan import ( "reflect" + "sort" "testing" + + "github.com/ActiveState/cli/pkg/buildplan/raw" ) func TestIngredient_RuntimeDependencies(t *testing.T) { @@ -55,3 +58,54 @@ func TestIngredient_RuntimeDependencies(t *testing.T) { }) } } + +type mockIngredient struct { + deps Ingredients +} + +func (m mockIngredient) RuntimeDependencies(recursive bool) Ingredients { + return m.deps +} + +func TestIngredients_CommonRuntimeDependencies(t *testing.T) { + tests := []struct { + name string + i []ingredientsWithRuntimeDeps + want []string + }{ + { + "Simple", + []ingredientsWithRuntimeDeps{ + mockIngredient{ + deps: Ingredients{ + { + IngredientSource: &raw.IngredientSource{IngredientID: "sub-ingredient-1"}, + }, + }, + }, + mockIngredient{ + deps: Ingredients{ + { + IngredientSource: &raw.IngredientSource{IngredientID: "sub-ingredient-1"}, + }, + }, + }, + }, + []string{"sub-ingredient-1"}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := commonRuntimeDependencies(tt.i) + gotIDs := []string{} + for _, i := range got { + gotIDs = append(gotIDs, string(i.IngredientID)) + } + sort.Strings(gotIDs) + sort.Strings(tt.want) + if !reflect.DeepEqual(gotIDs, tt.want) { + t.Errorf("Ingredients.CommonRuntimeDependencies() = %v, want %v", gotIDs, tt.want) + } + }) + } +} diff --git a/pkg/buildplan/raw/mock_test.go b/pkg/buildplan/mock/mock.go similarity index 60% rename from pkg/buildplan/raw/mock_test.go rename to pkg/buildplan/mock/mock.go index 43eda806ed..aaf242ec7a 100644 --- a/pkg/buildplan/raw/mock_test.go +++ b/pkg/buildplan/mock/mock.go @@ -1,24 +1,25 @@ -package raw +package mock import ( + "github.com/ActiveState/cli/pkg/buildplan/raw" "github.com/ActiveState/cli/pkg/platform/api/buildplanner/types" "github.com/go-openapi/strfmt" ) -var buildWithSourceFromStep = &Build{ - Terminals: []*NamedTarget{ +var BuildWithSourceFromStep = &raw.Build{ + Terminals: []*raw.NamedTarget{ { Tag: "platform:00000000-0000-0000-0000-000000000001", // Step 1: Traversal starts here, this one points to an artifact NodeIDs: []strfmt.UUID{"00000000-0000-0000-0000-000000000002"}, }, }, - Steps: []*Step{ + Steps: []*raw.Step{ { // Step 4: From here we can find which other nodes are linked to this one StepID: "00000000-0000-0000-0000-000000000003", Outputs: []string{"00000000-0000-0000-0000-000000000002"}, - Inputs: []*NamedTarget{ + Inputs: []*raw.NamedTarget{ // Step 5: Now we know which nodes are responsible for producing the output {Tag: "src", NodeIDs: []strfmt.UUID{"00000000-0000-0000-0000-000000000004"}}, }, @@ -27,13 +28,13 @@ var buildWithSourceFromStep = &Build{ // Step 8: Same as step 4 StepID: "00000000-0000-0000-0000-000000000005", Outputs: []string{"00000000-0000-0000-0000-000000000004"}, - Inputs: []*NamedTarget{ + Inputs: []*raw.NamedTarget{ // Step 9: Same as step 5 {Tag: "src", NodeIDs: []strfmt.UUID{"00000000-0000-0000-0000-000000000006"}}, }, }, }, - Artifacts: []*Artifact{ + Artifacts: []*raw.Artifact{ { // Step 2: We got an artifact, but there may be more hiding behind this one NodeID: "00000000-0000-0000-0000-000000000002", @@ -49,7 +50,7 @@ var buildWithSourceFromStep = &Build{ GeneratedBy: "00000000-0000-0000-0000-000000000005", }, }, - Sources: []*Source{ + Sources: []*raw.Source{ { // Step 10: We have our ingredient NodeID: "00000000-0000-0000-0000-000000000006", @@ -57,14 +58,14 @@ var buildWithSourceFromStep = &Build{ }, } -var buildWithSourceFromGeneratedBy = &Build{ - Terminals: []*NamedTarget{ +var BuildWithSourceFromGeneratedBy = &raw.Build{ + Terminals: []*raw.NamedTarget{ { Tag: "platform:00000000-0000-0000-0000-000000000001", NodeIDs: []strfmt.UUID{"00000000-0000-0000-0000-000000000002", "00000000-0000-0000-0000-000000000004"}, }, }, - Artifacts: []*Artifact{ + Artifacts: []*raw.Artifact{ { NodeID: "00000000-0000-0000-0000-000000000002", DisplayName: "installer", @@ -76,30 +77,30 @@ var buildWithSourceFromGeneratedBy = &Build{ GeneratedBy: "00000000-0000-0000-0000-000000000004", }, }, - Sources: []*Source{ + Sources: []*raw.Source{ { NodeID: "00000000-0000-0000-0000-000000000004", }, }, } -var buildWithBuildDeps = &Build{ - Terminals: []*NamedTarget{ +var BuildWithBuildDeps = &raw.Build{ + Terminals: []*raw.NamedTarget{ { Tag: "platform:00000000-0000-0000-0000-000000000001", NodeIDs: []strfmt.UUID{"00000000-0000-0000-0000-000000000002"}, }, }, - Steps: []*Step{ + Steps: []*raw.Step{ { StepID: "00000000-0000-0000-0000-000000000003", Outputs: []string{"00000000-0000-0000-0000-000000000002"}, - Inputs: []*NamedTarget{ + Inputs: []*raw.NamedTarget{ {Tag: "deps", NodeIDs: []strfmt.UUID{"00000000-0000-0000-0000-000000000004"}}, }, }, }, - Artifacts: []*Artifact{ + Artifacts: []*raw.Artifact{ { NodeID: "00000000-0000-0000-0000-000000000002", DisplayName: "installer", @@ -111,21 +112,21 @@ var buildWithBuildDeps = &Build{ GeneratedBy: "00000000-0000-0000-0000-000000000006", }, }, - Sources: []*Source{ + Sources: []*raw.Source{ { NodeID: "00000000-0000-0000-0000-000000000006", }, }, } -var buildWithRuntimeDeps = &Build{ - Terminals: []*NamedTarget{ +var BuildWithRuntimeDeps = &raw.Build{ + Terminals: []*raw.NamedTarget{ { Tag: "platform:00000000-0000-0000-0000-000000000001", NodeIDs: []strfmt.UUID{"00000000-0000-0000-0000-000000000002"}, }, }, - Artifacts: []*Artifact{ + Artifacts: []*raw.Artifact{ { NodeID: "00000000-0000-0000-0000-000000000002", DisplayName: "installer", @@ -142,7 +143,7 @@ var buildWithRuntimeDeps = &Build{ GeneratedBy: "00000000-0000-0000-0000-000000000008", }, }, - Sources: []*Source{ + Sources: []*raw.Source{ { NodeID: "00000000-0000-0000-0000-000000000006", }, @@ -152,23 +153,30 @@ var buildWithRuntimeDeps = &Build{ }, } -var buildWithRuntimeDepsViaSrc = &Build{ - Terminals: []*NamedTarget{ +var BuildWithRuntimeDepsViaSrc = &raw.Build{ + Terminals: []*raw.NamedTarget{ { Tag: "platform:00000000-0000-0000-0000-000000000001", NodeIDs: []strfmt.UUID{"00000000-0000-0000-0000-000000000002"}, }, }, - Steps: []*Step{ + Steps: []*raw.Step{ { StepID: "00000000-0000-0000-0000-000000000003", Outputs: []string{"00000000-0000-0000-0000-000000000002"}, - Inputs: []*NamedTarget{ + Inputs: []*raw.NamedTarget{ {Tag: "src", NodeIDs: []strfmt.UUID{"00000000-0000-0000-0000-000000000007"}}, }, }, + { + StepID: "00000000-0000-0000-0000-000000000008", + Outputs: []string{"00000000-0000-0000-0000-000000000007"}, + Inputs: []*raw.NamedTarget{ + {Tag: "src", NodeIDs: []strfmt.UUID{"00000000-0000-0000-0000-000000000009"}}, + }, + }, }, - Artifacts: []*Artifact{ + Artifacts: []*raw.Artifact{ { NodeID: "00000000-0000-0000-0000-000000000002", DisplayName: "installer", @@ -183,45 +191,124 @@ var buildWithRuntimeDepsViaSrc = &Build{ GeneratedBy: "00000000-0000-0000-0000-000000000008", }, }, - Sources: []*Source{ + Sources: []*raw.Source{ { - NodeID: "00000000-0000-0000-0000-000000000006", + "00000000-0000-0000-0000-000000000009", + raw.IngredientSource{ + IngredientID: "00000000-0000-0000-0000-000000000009", + }, }, + }, +} + +var BuildWithCommonRuntimeDepsViaSrc = &raw.Build{ + Terminals: []*raw.NamedTarget{ { - NodeID: "00000000-0000-0000-0000-000000000009", + Tag: "platform:00000000-0000-0000-0000-000000000001", + NodeIDs: []strfmt.UUID{"00000000-0000-0000-0000-000000000002"}, + }, + }, + Steps: []*raw.Step{ + { + StepID: "00000000-0000-0000-0000-000000000008", + Outputs: []string{"00000000-0000-0000-0000-000000000007"}, + Inputs: []*raw.NamedTarget{ + {Tag: "src", NodeIDs: []strfmt.UUID{"00000000-0000-0000-0000-000000000009"}}, + }, + }, + { + StepID: "00000000-0000-0000-0000-0000000000011", + Outputs: []string{"00000000-0000-0000-0000-000000000010"}, + Inputs: []*raw.NamedTarget{ + {Tag: "src", NodeIDs: []strfmt.UUID{"00000000-0000-0000-0000-000000000013"}}, + }, + }, + { + StepID: "00000000-0000-0000-0000-0000000000101", + Outputs: []string{"00000000-0000-0000-0000-000000000100"}, + Inputs: []*raw.NamedTarget{ + {Tag: "src", NodeIDs: []strfmt.UUID{"00000000-0000-0000-0000-000000000103"}}, + }, + }, + }, + Artifacts: []*raw.Artifact{ + { + NodeID: "00000000-0000-0000-0000-000000000007", + DisplayName: "pkgOne", + MimeType: types.XActiveStateArtifactMimeType, + GeneratedBy: "00000000-0000-0000-0000-000000000008", + RuntimeDependencies: []strfmt.UUID{ + "00000000-0000-0000-0000-000000000100", + }, + }, + { + NodeID: "00000000-0000-0000-0000-000000000010", + DisplayName: "pkgTwo", + MimeType: types.XActiveStateArtifactMimeType, + GeneratedBy: "00000000-0000-0000-0000-0000000000011", + RuntimeDependencies: []strfmt.UUID{ + "00000000-0000-0000-0000-000000000100", + }, + }, + { + NodeID: "00000000-0000-0000-0000-000000000100", + DisplayName: "pkgThatsCommonDep", + MimeType: types.XActiveStateArtifactMimeType, + GeneratedBy: "00000000-0000-0000-0000-0000000000101", + }, + }, + Sources: []*raw.Source{ + { + "00000000-0000-0000-0000-000000000009", + raw.IngredientSource{ + IngredientID: "00000000-0000-0000-0000-000000000009", + }, + }, + { + "00000000-0000-0000-0000-000000000013", + raw.IngredientSource{ + IngredientID: "00000000-0000-0000-0000-000000000013", + }, + }, + { + "00000000-0000-0000-0000-000000000103", + raw.IngredientSource{ + IngredientID: "00000000-0000-0000-0000-000000000103", + }, }, }, } -// buildWithRuntimeDepsViaSrcCycle is a build with a cycle in the runtime dependencies. +// BuildWithRuntimeDepsViaSrcCycle is a build with a cycle in the runtime dependencies. // The cycle is as follows: // 00000000-0000-0000-0000-000000000002 (Terminal Artifact) -// -> 00000000-0000-0000-0000-000000000003 (Generated by Step) -// -> 00000000-0000-0000-0000-000000000007 (Step Input Artifact) -// -> 00000000-0000-0000-0000-000000000008 (Generated by Step) -// -> 00000000-0000-0000-0000-000000000010 (Step Input Artifact) -// -> 00000000-0000-0000-0000-000000000011 (Generated by Step) -// -> 00000000-0000-0000-0000-000000000013 (Step Input Artifact) -// -> 00000000-0000-0000-0000-000000000002 (Runtime dependency Artifact - Generates Cycle) -var buildWithRuntimeDepsViaSrcCycle = &Build{ - Terminals: []*NamedTarget{ +// +// -> 00000000-0000-0000-0000-000000000003 (Generated by Step) +// -> 00000000-0000-0000-0000-000000000007 (Step Input Artifact) +// -> 00000000-0000-0000-0000-000000000008 (Generated by Step) +// -> 00000000-0000-0000-0000-000000000010 (Step Input Artifact) +// -> 00000000-0000-0000-0000-000000000011 (Generated by Step) +// -> 00000000-0000-0000-0000-000000000013 (Step Input Artifact) +// -> 00000000-0000-0000-0000-000000000002 (Runtime dependency Artifact - Generates Cycle) +var BuildWithRuntimeDepsViaSrcCycle = &raw.Build{ + Terminals: []*raw.NamedTarget{ { Tag: "platform:00000000-0000-0000-0000-000000000001", NodeIDs: []strfmt.UUID{"00000000-0000-0000-0000-000000000002"}, }, }, - Steps: []*Step{ + Steps: []*raw.Step{ { StepID: "00000000-0000-0000-0000-000000000003", Outputs: []string{"00000000-0000-0000-0000-000000000002"}, - Inputs: []*NamedTarget{ + Inputs: []*raw.NamedTarget{ {Tag: "src", NodeIDs: []strfmt.UUID{"00000000-0000-0000-0000-000000000007"}}, }, }, { StepID: "00000000-0000-0000-0000-000000000008", Outputs: []string{"00000000-0000-0000-0000-000000000007"}, - Inputs: []*NamedTarget{ + Inputs: []*raw.NamedTarget{ {Tag: "src", NodeIDs: []strfmt.UUID{"00000000-0000-0000-0000-000000000010"}}, }, }, @@ -230,12 +317,12 @@ var buildWithRuntimeDepsViaSrcCycle = &Build{ Outputs: []string{ "00000000-0000-0000-0000-000000000010", }, - Inputs: []*NamedTarget{ + Inputs: []*raw.NamedTarget{ {Tag: "src", NodeIDs: []strfmt.UUID{"00000000-0000-0000-0000-000000000013"}}, }, }, }, - Artifacts: []*Artifact{ + Artifacts: []*raw.Artifact{ { NodeID: "00000000-0000-0000-0000-000000000002", DisplayName: "installer", @@ -262,7 +349,7 @@ var buildWithRuntimeDepsViaSrcCycle = &Build{ GeneratedBy: "00000000-0000-0000-0000-000000000011", }, }, - Sources: []*Source{ + Sources: []*raw.Source{ { NodeID: "00000000-0000-0000-0000-000000000006", }, diff --git a/pkg/buildplan/mock_test.go b/pkg/buildplan/mock_test.go index 4ffa7ccb12..b4705dfc86 100644 --- a/pkg/buildplan/mock_test.go +++ b/pkg/buildplan/mock_test.go @@ -2,7 +2,6 @@ package buildplan import ( "github.com/ActiveState/cli/pkg/buildplan/raw" - "github.com/ActiveState/cli/pkg/platform/api/buildplanner/types" ) // createMockArtifactWithCycles creates a mock artifact with a cycle. @@ -18,9 +17,9 @@ import ( // -> 00000000-0000-0000-0000-000000000001 (Cycle back to the first artifact) func createMockArtifactWithCycles() *Artifact { // Create the artifacts with placeholders - artifact0001 := &Artifact{ArtifactID: "00000000-0000-0000-0000-000000000001", MimeType: types.XArtifactMimeType} - artifact0002 := &Artifact{ArtifactID: "00000000-0000-0000-0000-000000000002", MimeType: types.XArtifactMimeType} - artifact0003 := &Artifact{ArtifactID: "00000000-0000-0000-0000-000000000003", MimeType: types.XArtifactMimeType} + artifact0001 := &Artifact{ArtifactID: "00000000-0000-0000-0000-000000000001"} + artifact0002 := &Artifact{ArtifactID: "00000000-0000-0000-0000-000000000002"} + artifact0003 := &Artifact{ArtifactID: "00000000-0000-0000-0000-000000000003"} // Create the deepest ingredients and artifacts first artifact0003.children = []ArtifactRelation{ @@ -54,10 +53,10 @@ func createMockArtifactWithCycles() *Artifact { // -> 00000000-0000-0000-0000-000000000002 (child) // -> 00000000-0000-0000-0000-000000000003 (child) func createMockArtifactWithRuntimeDeps() *Artifact { - artifact0001 := &Artifact{ArtifactID: "00000000-0000-0000-0000-000000000001", MimeType: types.XArtifactMimeType} - artifact0002 := &Artifact{ArtifactID: "00000000-0000-0000-0000-000000000002", MimeType: types.XArtifactMimeType} - artifact0003 := &Artifact{ArtifactID: "00000000-0000-0000-0000-000000000003", MimeType: types.XArtifactMimeType} - artifact0004 := &Artifact{ArtifactID: "00000000-0000-0000-0000-000000000004", MimeType: types.XArtifactMimeType} + artifact0001 := &Artifact{ArtifactID: "00000000-0000-0000-0000-000000000001"} + artifact0002 := &Artifact{ArtifactID: "00000000-0000-0000-0000-000000000002"} + artifact0003 := &Artifact{ArtifactID: "00000000-0000-0000-0000-000000000003"} + artifact0004 := &Artifact{ArtifactID: "00000000-0000-0000-0000-000000000004"} artifact0001.children = []ArtifactRelation{ { @@ -90,9 +89,9 @@ func createMockArtifactWithRuntimeDeps() *Artifact { // -> 00000000-0000-0000-0000-000000000002 (child) // -> 00000000-0000-0000-0000-000000000003 (child) func createMockArtifactWithBuildTimeDeps() *Artifact { - artifact0001 := &Artifact{ArtifactID: "00000000-0000-0000-0000-000000000001", MimeType: types.XArtifactMimeType} - artifact0002 := &Artifact{ArtifactID: "00000000-0000-0000-0000-000000000002", MimeType: types.XArtifactMimeType} - artifact0003 := &Artifact{ArtifactID: "00000000-0000-0000-0000-000000000003", MimeType: types.XArtifactMimeType} + artifact0001 := &Artifact{ArtifactID: "00000000-0000-0000-0000-000000000001"} + artifact0002 := &Artifact{ArtifactID: "00000000-0000-0000-0000-000000000002"} + artifact0003 := &Artifact{ArtifactID: "00000000-0000-0000-0000-000000000003"} artifact0001.children = []ArtifactRelation{ { @@ -122,10 +121,10 @@ func createMockArtifactWithBuildTimeDeps() *Artifact { // -> 00000000-0000-0000-0000-000000000004 (Artifact child of Artifact0003) // -> 00000000-0000-0000-0000-000000000030 (Ingredient0030) func createIngredientWithRuntimeDeps() *Ingredient { - artifact0001 := &Artifact{ArtifactID: "00000000-0000-0000-0000-000000000001", MimeType: types.XArtifactMimeType} - artifact0002 := &Artifact{ArtifactID: "00000000-0000-0000-0000-000000000002", MimeType: types.XArtifactMimeType} - artifact0003 := &Artifact{ArtifactID: "00000000-0000-0000-0000-000000000003", MimeType: types.XArtifactMimeType} - artifact0004 := &Artifact{ArtifactID: "00000000-0000-0000-0000-000000000004", MimeType: types.XArtifactMimeType} + artifact0001 := &Artifact{ArtifactID: "00000000-0000-0000-0000-000000000001"} + artifact0002 := &Artifact{ArtifactID: "00000000-0000-0000-0000-000000000002"} + artifact0003 := &Artifact{ArtifactID: "00000000-0000-0000-0000-000000000003"} + artifact0004 := &Artifact{ArtifactID: "00000000-0000-0000-0000-000000000004"} ingredient0010 := &Ingredient{ IngredientSource: &raw.IngredientSource{ @@ -199,12 +198,12 @@ func createIngredientWithRuntimeDeps() *Ingredient { // -> 00000000-0000-0000-0000-000000000006 (Child of Artifact0005) // -> 00000000-0000-0000-0000-000000000010 (Ingredient0010 cycle back to the first ingredient) func createMockIngredientWithCycles() *Ingredient { - artifact0001 := &Artifact{ArtifactID: "00000000-0000-0000-0000-000000000001", MimeType: types.XArtifactMimeType} - artifact0002 := &Artifact{ArtifactID: "00000000-0000-0000-0000-000000000002", MimeType: types.XArtifactMimeType} - artifact0003 := &Artifact{ArtifactID: "00000000-0000-0000-0000-000000000003", MimeType: types.XArtifactMimeType} - artifact0004 := &Artifact{ArtifactID: "00000000-0000-0000-0000-000000000004", MimeType: types.XArtifactMimeType} - artifact0005 := &Artifact{ArtifactID: "00000000-0000-0000-0000-000000000005", MimeType: types.XArtifactMimeType} - artifact0006 := &Artifact{ArtifactID: "00000000-0000-0000-0000-000000000006", MimeType: types.XArtifactMimeType} + artifact0001 := &Artifact{ArtifactID: "00000000-0000-0000-0000-000000000001"} + artifact0002 := &Artifact{ArtifactID: "00000000-0000-0000-0000-000000000002"} + artifact0003 := &Artifact{ArtifactID: "00000000-0000-0000-0000-000000000003"} + artifact0004 := &Artifact{ArtifactID: "00000000-0000-0000-0000-000000000004"} + artifact0005 := &Artifact{ArtifactID: "00000000-0000-0000-0000-000000000005"} + artifact0006 := &Artifact{ArtifactID: "00000000-0000-0000-0000-000000000006"} ingredient0010 := &Ingredient{ IngredientSource: &raw.IngredientSource{ diff --git a/pkg/buildplan/raw/walk.go b/pkg/buildplan/raw/walk.go index 7db44720f8..c9aa7058d6 100644 --- a/pkg/buildplan/raw/walk.go +++ b/pkg/buildplan/raw/walk.go @@ -1,6 +1,8 @@ package raw import ( + "errors" + "github.com/go-openapi/strfmt" "github.com/ActiveState/cli/internal/errs" @@ -16,6 +18,12 @@ type WalkNodeContext struct { lookup map[strfmt.UUID]interface{} } +type WalkInterrupt struct{} + +func (w WalkInterrupt) Error() string { + return "interrupt walk" +} + // WalkViaSteps walks the graph and reports on nodes it encounters // Note that the same node can be encountered multiple times if it is referenced in the graph multiple times. // In this case the context around the node may be different, even if the node itself isn't. @@ -39,6 +47,9 @@ func (b *Build) walkNodeViaSteps(node interface{}, parent *Artifact, tag StepInp lookup := b.LookupMap() if err := walk(node, parent); err != nil { + if errors.As(err, &WalkInterrupt{}) { + return nil + } return errs.Wrap(err, "error walking over node") } diff --git a/pkg/buildplan/raw/walk_test.go b/pkg/buildplan/raw/walk_test.go index 7125f74619..de002aef36 100644 --- a/pkg/buildplan/raw/walk_test.go +++ b/pkg/buildplan/raw/walk_test.go @@ -1,4 +1,4 @@ -package raw +package raw_test import ( "fmt" @@ -7,6 +7,8 @@ import ( "github.com/ActiveState/cli/internal/errs" "github.com/ActiveState/cli/internal/rtutils/ptr" + "github.com/ActiveState/cli/pkg/buildplan/mock" + "github.com/ActiveState/cli/pkg/buildplan/raw" "github.com/go-openapi/strfmt" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -22,16 +24,16 @@ func TestRawBuild_walkNodesViaSteps(t *testing.T) { tests := []struct { name string nodeIDs []strfmt.UUID - tag StepInputTag - build *Build + tag raw.StepInputTag + build *raw.Build wantCalls []walkCall wantErr bool }{ { "Ingredient from step", []strfmt.UUID{"00000000-0000-0000-0000-000000000002"}, - TagSource, - buildWithSourceFromStep, + raw.TagSource, + mock.BuildWithSourceFromStep, []walkCall{ {"00000000-0000-0000-0000-000000000002", "Artifact", ""}, {"00000000-0000-0000-0000-000000000004", "Artifact", strfmt.UUID("00000000-0000-0000-0000-000000000002")}, @@ -42,8 +44,8 @@ func TestRawBuild_walkNodesViaSteps(t *testing.T) { { "Ingredient from generatedBy, multiple artifacts to same ingredient", []strfmt.UUID{"00000000-0000-0000-0000-000000000002", "00000000-0000-0000-0000-000000000003"}, - TagSource, - buildWithSourceFromGeneratedBy, + raw.TagSource, + mock.BuildWithSourceFromGeneratedBy, []walkCall{ {"00000000-0000-0000-0000-000000000002", "Artifact", ""}, {"00000000-0000-0000-0000-000000000004", "Source", strfmt.UUID("00000000-0000-0000-0000-000000000002")}, @@ -55,8 +57,8 @@ func TestRawBuild_walkNodesViaSteps(t *testing.T) { { "Build time deps", []strfmt.UUID{"00000000-0000-0000-0000-000000000002"}, - TagDependency, - buildWithBuildDeps, + raw.TagDependency, + mock.BuildWithBuildDeps, []walkCall{ {"00000000-0000-0000-0000-000000000002", "Artifact", ""}, {"00000000-0000-0000-0000-000000000004", "Artifact", strfmt.UUID("00000000-0000-0000-0000-000000000002")}, @@ -69,16 +71,16 @@ func TestRawBuild_walkNodesViaSteps(t *testing.T) { t.Run(tt.name, func(t *testing.T) { calls := []walkCall{} - walk := func(node interface{}, parent *Artifact) error { + walk := func(node interface{}, parent *raw.Artifact) error { var parentID *strfmt.UUID if parent != nil { parentID = &parent.NodeID } var id strfmt.UUID switch v := node.(type) { - case *Artifact: + case *raw.Artifact: id = v.NodeID - case *Source: + case *raw.Source: id = v.NodeID default: t.Fatalf("unexpected node type %T", v) @@ -122,14 +124,14 @@ func TestRawBuild_walkNodesViaRuntimeDeps(t *testing.T) { tests := []struct { name string nodeIDs []strfmt.UUID - build *Build + build *raw.Build wantCalls []walkCall wantErr bool }{ { "Runtime deps", - buildWithRuntimeDeps.Terminals[0].NodeIDs, - buildWithRuntimeDeps, + mock.BuildWithRuntimeDeps.Terminals[0].NodeIDs, + mock.BuildWithRuntimeDeps, []walkCall{ {"00000000-0000-0000-0000-000000000002", "Artifact", ""}, {"00000000-0000-0000-0000-000000000007", "Artifact", "00000000-0000-0000-0000-000000000002"}, @@ -138,8 +140,8 @@ func TestRawBuild_walkNodesViaRuntimeDeps(t *testing.T) { }, { "Runtime deps via src step", - buildWithRuntimeDepsViaSrc.Terminals[0].NodeIDs, - buildWithRuntimeDepsViaSrc, + mock.BuildWithRuntimeDepsViaSrc.Terminals[0].NodeIDs, + mock.BuildWithRuntimeDepsViaSrc, []walkCall{ {"00000000-0000-0000-0000-000000000007", "Artifact", "00000000-0000-0000-0000-000000000002"}, }, @@ -147,8 +149,8 @@ func TestRawBuild_walkNodesViaRuntimeDeps(t *testing.T) { }, { "Runtime deps with cycle", - buildWithRuntimeDepsViaSrcCycle.Terminals[0].NodeIDs, - buildWithRuntimeDepsViaSrcCycle, + mock.BuildWithRuntimeDepsViaSrcCycle.Terminals[0].NodeIDs, + mock.BuildWithRuntimeDepsViaSrcCycle, []walkCall{ {"00000000-0000-0000-0000-000000000013", "Artifact", "00000000-0000-0000-0000-000000000010"}, }, @@ -159,16 +161,16 @@ func TestRawBuild_walkNodesViaRuntimeDeps(t *testing.T) { t.Run(tt.name, func(t *testing.T) { calls := []walkCall{} - walk := func(node interface{}, parent *Artifact) error { + walk := func(node interface{}, parent *raw.Artifact) error { var parentID *strfmt.UUID if parent != nil { parentID = &parent.NodeID } var id strfmt.UUID switch v := node.(type) { - case *Artifact: + case *raw.Artifact: id = v.NodeID - case *Source: + case *raw.Source: id = v.NodeID default: t.Fatalf("unexpected node type %T", v) diff --git a/test/integration/checkout_int_test.go b/test/integration/checkout_int_test.go index 029e93557e..386786cd68 100644 --- a/test/integration/checkout_int_test.go +++ b/test/integration/checkout_int_test.go @@ -365,6 +365,7 @@ func (suite *CheckoutIntegrationTestSuite) TestCveReport() { } func (suite *CheckoutIntegrationTestSuite) TestCheckoutFromArchive() { + suite.T().Skip("Skipping until https://activestatef.atlassian.net/browse/DX-3057 is fixed") suite.OnlyRunForTags(tagsuite.Checkout) ts := e2e.New(suite.T(), false) defer ts.Close()