From 1ad042bca81ebc8f0b43c931704bec0f71d094a8 Mon Sep 17 00:00:00 2001 From: Zachary Romero Date: Fri, 9 Feb 2018 09:58:49 +0300 Subject: [PATCH] Add tests and put command in own function Added unit test for projUpToDate and harness test for the -old flag. Put the status -old functionality to it's own function. --- cmd/dep/status.go | 260 +++++++++++++++--- cmd/dep/status_test.go | 51 ++++ .../status/old_constraints/final/Gopkg.lock | 27 ++ .../status/old_constraints/final/Gopkg.toml | 11 + .../status/old_constraints/initial/Gopkg.lock | 27 ++ .../status/old_constraints/initial/Gopkg.toml | 11 + .../status/old_constraints/initial/main.go | 14 + .../status/old_constraints/stdout.txt | 2 + .../status/old_constraints/testcase.json | 12 + 9 files changed, 382 insertions(+), 33 deletions(-) create mode 100644 cmd/dep/testdata/harness_tests/status/old_constraints/final/Gopkg.lock create mode 100644 cmd/dep/testdata/harness_tests/status/old_constraints/final/Gopkg.toml create mode 100644 cmd/dep/testdata/harness_tests/status/old_constraints/initial/Gopkg.lock create mode 100644 cmd/dep/testdata/harness_tests/status/old_constraints/initial/Gopkg.toml create mode 100644 cmd/dep/testdata/harness_tests/status/old_constraints/initial/main.go create mode 100644 cmd/dep/testdata/harness_tests/status/old_constraints/stdout.txt create mode 100644 cmd/dep/testdata/harness_tests/status/old_constraints/testcase.json diff --git a/cmd/dep/status.go b/cmd/dep/status.go index 42830f283d..aecbf3f362 100644 --- a/cmd/dep/status.go +++ b/cmd/dep/status.go @@ -273,7 +273,16 @@ func (cmd *statusCommand) Run(ctx *dep.Ctx, args []string) error { return errors.Errorf("no Gopkg.lock found. Run `dep ensure` to generate lock file") } - hasMissingPkgs, errCount, err := runStatusAll(ctx, out, p, sm, cmd) + // Choose function based on command options + var hasMissingPkgs bool + var errCount int + switch { + case cmd.old: + hasMissingPkgs, errCount, err = runOld(ctx, out, p, sm) + default: + hasMissingPkgs, errCount, err = runStatusAll(ctx, out, p, sm) + } + if err != nil { switch err { case errFailedUpdate: @@ -340,15 +349,6 @@ func (cmd *statusCommand) validateFlags() error { return nil } -// OldStatus contains information about all the out of date packages in a project. -type OldStatus struct { - ProjectRoot string - Constraint gps.Constraint - Version gps.UnpairedVersion - Revision gps.Revision - Latest gps.Version -} - type rawStatus struct { ProjectRoot string Constraint string @@ -432,7 +432,7 @@ type MissingStatus struct { MissingPackages []string } -func runStatusAll(ctx *dep.Ctx, out outputter, p *dep.Project, sm gps.SourceManager, cmd *statusCommand) (hasMissingPkgs bool, errCount int, err error) { +func runStatusAll(ctx *dep.Ctx, out outputter, p *dep.Project, sm gps.SourceManager) (hasMissingPkgs bool, errCount int, err error) { // While the network churns on ListVersions() requests, statically analyze // code from the current project. ptree, err := p.ParseRootPackageTree() @@ -465,17 +465,6 @@ func runStatusAll(ctx *dep.Ctx, out outputter, p *dep.Project, sm gps.SourceMana return false, 0, errors.Wrapf(err, "could not set up solver for input hashing") } - // We only care about solution in the -old flag case - var solution gps.Solution - if cmd.old { - params.ChangeAll = true - var err error - solution, err = s.Solve(context.TODO()) - if err != nil { - return false, 0, err - } - } - // Errors while collecting constraints should not fail the whole status run. // It should count the error and tell the user about incomplete results. cm, ccerrs := collectConstraints(ctx, p, sm) @@ -662,15 +651,6 @@ func runStatusAll(ctx *dep.Ctx, out outputter, p *dep.Project, sm gps.SourceMana for _, proj := range slp { pr := proj.Ident().ProjectRoot - // If -old flag, only display the lines where the solver mismatches - if cmd.old { - if matches, err := projUpToDate(proj, solution); matches { - continue - } else if err != nil { - return false, 0, err - } - } - if err := out.BasicLine(bsMap[string(pr)]); err != nil { return false, 0, err } @@ -752,9 +732,223 @@ outer: return hasMissingPkgs, 0, errInputDigestMismatch } +func runOld(ctx *dep.Ctx, out outputter, p *dep.Project, sm gps.SourceManager) (hasMissingPkgs bool, errCount int, err error) { + ptree, err := p.ParseRootPackageTree() + if err != nil { + return false, 0, err + } + + // Solve the dependencies so we can check if the current dependencies are old + params := gps.SolveParameters{ + ProjectAnalyzer: dep.Analyzer{}, + RootDir: p.AbsRoot, + RootPackageTree: ptree, + Manifest: p.Manifest, + ChangeAll: true, + } + s, err := gps.Prepare(params, sm) + if err != nil { + return false, 0, errors.Wrapf(err, "could not set up solver for input hashing") + } + + if err := out.BasicHeader(); err != nil { + return false, 0, err + } + + // Errors while collecting constraints should not fail the whole status run. + // It should count the error and tell the user about incomplete results. + cm, ccerrs := collectConstraints(ctx, p, sm) + if len(ccerrs) > 0 { + errCount += len(ccerrs) + } + + bsMap := make(map[string]*BasicStatus) + + slp := p.Lock.Projects() + + if !bytes.Equal(s.HashInputs(), p.Lock.SolveMeta.InputsDigest) { + // Hash digest mismatch may indicate that some deps are no longer + // needed, some are missing, or that some constraints or source + // locations have changed. + // + // It's possible for digests to not match, but still have a correct + // lock. + rm, _ := ptree.ToReachMap(true, true, false, p.Manifest.IgnoredPackages()) + + external := rm.FlattenFn(paths.IsStandardImportPath) + roots := make(map[gps.ProjectRoot][]string, len(external)) + + type fail struct { + ex string + err error + } + var errs []fail + for _, e := range external { + root, err := sm.DeduceProjectRoot(e) + if err != nil { + errs = append(errs, fail{ + ex: e, + err: err, + }) + continue + } + + roots[root] = append(roots[root], e) + } + + if len(errs) != 0 { + // TODO this is just a fix quick so staticcheck doesn't complain. + // Visually reconciling failure to deduce project roots with the rest of + // the mismatch output is a larger problem. + ctx.Err.Printf("Failed to deduce project roots for import paths:\n") + for _, fail := range errs { + ctx.Err.Printf("\t%s: %s\n", fail.ex, fail.err.Error()) + } + + return false, 0, errors.New("address issues with undeducible import paths to get more status information") + } + + if err = out.MissingHeader(); err != nil { + return false, 0, err + } + + outer2: + for root, pkgs := range roots { + // TODO also handle the case where the project is present, but there + // are items missing from just the package list + for _, lp := range slp { + if lp.Ident().ProjectRoot == root { + continue outer2 + } + } + + hasMissingPkgs = true + err := out.MissingLine(&MissingStatus{ProjectRoot: string(root), MissingPackages: pkgs}) + if err != nil { + return false, 0, err + } + } + if err = out.MissingFooter(); err != nil { + return false, 0, err + } + + // We are here because of an input-digest mismatch. Return error. + return hasMissingPkgs, 0, errInputDigestMismatch + } + + solution, err := s.Solve(context.TODO()) + if err != nil { + return false, 0, err + } + + for _, proj := range slp { + if matches, err := projUpToDate(proj, solution.Projects()); matches { + continue + } else if err != nil { + return false, 0, err + } + + bs := BasicStatus{ + ProjectRoot: string(proj.Ident().ProjectRoot), + PackageCount: len(proj.Packages()), + } + switch out.(type) { + case *dotOutput: + ptr, err := sm.ListPackages(proj.Ident(), proj.Version()) + + if err != nil { + bs.hasError = true + return false, 0, err + } + + prm, _ := ptr.ToReachMap(true, true, false, p.Manifest.IgnoredPackages()) + bs.Children = prm.FlattenFn(paths.IsStandardImportPath) + } + // Split apart the version from the lock into its constituent parts. + switch tv := proj.Version().(type) { + case gps.UnpairedVersion: + bs.Version = tv + case gps.Revision: + bs.Revision = tv + case gps.PairedVersion: + bs.Version = tv.Unpair() + bs.Revision = tv.Revision() + } + // Check if the manifest has an override for this project. If so, + // set that as the constraint. + if pp, has := p.Manifest.Ovr[proj.Ident().ProjectRoot]; has && pp.Constraint != nil { + bs.hasOverride = true + bs.Constraint = pp.Constraint + } else if pp, has := p.Manifest.Constraints[proj.Ident().ProjectRoot]; has && pp.Constraint != nil { + // If the manifest has a constraint then set that as the constraint. + bs.Constraint = pp.Constraint + } else { + bs.Constraint = gps.Any() + for _, c := range cm[bs.ProjectRoot] { + bs.Constraint = c.Constraint.Intersect(bs.Constraint) + } + } + + // Only if we have a non-rev and non-plain version do/can we display + // anything wrt the version's updateability. + if bs.Version != nil && bs.Version.Type() != gps.IsVersion { + c, has := p.Manifest.Constraints[proj.Ident().ProjectRoot] + if !has { + // Get constraint for locked project + for _, lockedP := range p.Lock.P { + if lockedP.Ident().ProjectRoot == proj.Ident().ProjectRoot { + // Use the unpaired version as the constraint for checking updates. + c.Constraint = bs.Version + } + } + } + // TODO: This constraint is only the constraint imposed by the + // current project, not by any transitive deps. As a result, + // transitive project deps will always show "any" here. + bs.Constraint = c.Constraint + + vl, err := sm.ListVersions(proj.Ident()) + if err == nil { + gps.SortPairedForUpgrade(vl) + + for _, v := range vl { + // Because we've sorted the version list for + // upgrade, the first version we encounter that + // matches our constraint will be what we want. + if c.Constraint.Matches(v) { + // Latest should be of the same type as the Version. + if bs.Version.Type() == gps.IsSemver { + bs.Latest = v + } else { + bs.Latest = v.Revision() + } + break + } + } + } else { + // Failed to fetch version list (could happen due to + // network issue). + bs.hasError = true + return false, 0, err + } + } + + bsMap[bs.ProjectRoot] = &bs + } + + for k := range bsMap { + if err := out.BasicLine(bsMap[k]); err != nil { + return false, 0, err + } + } + if footerErr := out.BasicFooter(); footerErr != nil { + return false, 0, footerErr + } + return false, errCount, err +} + // projUpToDate returns true if the project p, is at the same revision as what the solution indicates -func projUpToDate(p gps.LockedProject, s gps.Solution) (bool, error) { - solutionProjects := s.Projects() +func projUpToDate(p gps.LockedProject, solutionProjects []gps.LockedProject) (bool, error) { for i := range solutionProjects { if solutionProjects[i].Ident().ProjectRoot == p.Ident().ProjectRoot { spr, _, _ := gps.VersionComponentStrings(solutionProjects[i].Version()) diff --git a/cmd/dep/status_test.go b/cmd/dep/status_test.go index d18f9404ea..78b1c5ff3d 100644 --- a/cmd/dep/status_test.go +++ b/cmd/dep/status_test.go @@ -38,6 +38,57 @@ func TestStatusFormatVersion(t *testing.T) { } } +func TestProjUpToDate(t *testing.T) { + deptestProj := gps.NewLockedProject(gps.ProjectIdentifier{ProjectRoot: "github.com/sdboyer/deptest"}, gps.NewVersion("1.0.0").Pair(gps.Revision("ff2948a2ac8f538c4ecd55962e919d1e13e74baf")), []string{}) + deptestDosProj := gps.NewLockedProject(gps.ProjectIdentifier{ProjectRoot: "github.com/sdboyer/deptestdos"}, gps.NewVersion("2.0.0").Pair(gps.Revision("5c607206be5decd28e6263ffffdcee067266015e")), []string{}) + goDepTestProj := gps.NewLockedProject(gps.ProjectIdentifier{ProjectRoot: "github.com/carolynvs/go-dep-test"}, gps.NewVersion("0.1.1").Pair(gps.Revision("40691983e4002d3a3f5879cc0f1fe99bedda148c")), []string{}) + + tests := []struct { + proj gps.LockedProject + solutionProjects []gps.LockedProject + want bool + wantErr bool + }{ + { + proj: gps.NewLockedProject(gps.ProjectIdentifier{ProjectRoot: "github.com/sdboyer/deptest"}, gps.NewVersion("1.0.0").Pair(gps.Revision("ff2948a2ac8f538c4ecd55962e919d1e13e74baf")), []string{}), + solutionProjects: []gps.LockedProject{ + deptestProj, deptestDosProj, goDepTestProj, + }, + want: true, + wantErr: false, + }, + { + proj: gps.NewLockedProject(gps.ProjectIdentifier{ProjectRoot: "github.com/carolynvs/go-dep-test"}, gps.NewVersion("0.1.0").Pair(gps.Revision("b9c5511fa463628e6251554db29a4be161d02aed")), []string{}), + solutionProjects: []gps.LockedProject{ + deptestProj, deptestDosProj, goDepTestProj, + }, + want: false, + wantErr: false, + }, + { + proj: gps.NewLockedProject(gps.ProjectIdentifier{ProjectRoot: "github.com/carolynvs/go-dep-test"}, gps.NewVersion("0.1.0").Pair(gps.Revision("b9c5511fa463628e6251554db29a4be161d02aed")), []string{}), + solutionProjects: []gps.LockedProject{ + deptestProj, deptestDosProj, + }, + want: false, + wantErr: true, + }, + } + + for _, test := range tests { + got, err := projUpToDate(test.proj, test.solutionProjects) + if test.wantErr && err == nil { + t.Errorf("expecting error but didn't receive one") + } + if !test.wantErr && err != nil { + t.Errorf("unexpected error: \n\t(GOT): %v\n\t(WNT): nil", err.Error()) + } + if test.want != got { + t.Errorf("returned incorrect result: \n\t(GOT): %v\n\t(WNT): %v", got, test.want) + } + } +} + func TestBasicLine(t *testing.T) { project := dep.Project{} aSemverConstraint, _ := gps.NewSemverConstraint("1.2.3") diff --git a/cmd/dep/testdata/harness_tests/status/old_constraints/final/Gopkg.lock b/cmd/dep/testdata/harness_tests/status/old_constraints/final/Gopkg.lock new file mode 100644 index 0000000000..7f844d35d6 --- /dev/null +++ b/cmd/dep/testdata/harness_tests/status/old_constraints/final/Gopkg.lock @@ -0,0 +1,27 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + name = "github.com/carolynvs/go-dep-test" + packages = ["."] + revision = "b9c5511fa463628e6251554db29a4be161d02aed" + version = "0.1.0" + +[[projects]] + name = "github.com/sdboyer/deptest" + packages = ["."] + revision = "ff2948a2ac8f538c4ecd55962e919d1e13e74baf" + version = "v1.0.0" + +[[projects]] + name = "github.com/sdboyer/deptestdos" + packages = ["."] + revision = "5c607206be5decd28e6263ffffdcee067266015e" + version = "v2.0.0" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + inputs-digest = "c89811fc98c9a1310c94dc63b84f364d13c46ea3a40bd2cba7d77377ab346543" + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/cmd/dep/testdata/harness_tests/status/old_constraints/final/Gopkg.toml b/cmd/dep/testdata/harness_tests/status/old_constraints/final/Gopkg.toml new file mode 100644 index 0000000000..eeb589024a --- /dev/null +++ b/cmd/dep/testdata/harness_tests/status/old_constraints/final/Gopkg.toml @@ -0,0 +1,11 @@ +[[constraint]] + name = "github.com/sdboyer/deptestdos" + version = "v2.0.0" + +[[constraint]] + name = "github.com/sdboyer/deptest" + version = "v1.0.0" + +[[constraint]] + name = "github.com/carolynvs/go-dep-test" + version = "v0.1.0" diff --git a/cmd/dep/testdata/harness_tests/status/old_constraints/initial/Gopkg.lock b/cmd/dep/testdata/harness_tests/status/old_constraints/initial/Gopkg.lock new file mode 100644 index 0000000000..7f844d35d6 --- /dev/null +++ b/cmd/dep/testdata/harness_tests/status/old_constraints/initial/Gopkg.lock @@ -0,0 +1,27 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + name = "github.com/carolynvs/go-dep-test" + packages = ["."] + revision = "b9c5511fa463628e6251554db29a4be161d02aed" + version = "0.1.0" + +[[projects]] + name = "github.com/sdboyer/deptest" + packages = ["."] + revision = "ff2948a2ac8f538c4ecd55962e919d1e13e74baf" + version = "v1.0.0" + +[[projects]] + name = "github.com/sdboyer/deptestdos" + packages = ["."] + revision = "5c607206be5decd28e6263ffffdcee067266015e" + version = "v2.0.0" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + inputs-digest = "c89811fc98c9a1310c94dc63b84f364d13c46ea3a40bd2cba7d77377ab346543" + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/cmd/dep/testdata/harness_tests/status/old_constraints/initial/Gopkg.toml b/cmd/dep/testdata/harness_tests/status/old_constraints/initial/Gopkg.toml new file mode 100644 index 0000000000..eeb589024a --- /dev/null +++ b/cmd/dep/testdata/harness_tests/status/old_constraints/initial/Gopkg.toml @@ -0,0 +1,11 @@ +[[constraint]] + name = "github.com/sdboyer/deptestdos" + version = "v2.0.0" + +[[constraint]] + name = "github.com/sdboyer/deptest" + version = "v1.0.0" + +[[constraint]] + name = "github.com/carolynvs/go-dep-test" + version = "v0.1.0" diff --git a/cmd/dep/testdata/harness_tests/status/old_constraints/initial/main.go b/cmd/dep/testdata/harness_tests/status/old_constraints/initial/main.go new file mode 100644 index 0000000000..bc0868f975 --- /dev/null +++ b/cmd/dep/testdata/harness_tests/status/old_constraints/initial/main.go @@ -0,0 +1,14 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + _ "github.com/carolynvs/go-dep-test" + _ "github.com/sdboyer/deptest" + _ "github.com/sdboyer/deptestdos" +) + +func main() { +} diff --git a/cmd/dep/testdata/harness_tests/status/old_constraints/stdout.txt b/cmd/dep/testdata/harness_tests/status/old_constraints/stdout.txt new file mode 100644 index 0000000000..cba109f79f --- /dev/null +++ b/cmd/dep/testdata/harness_tests/status/old_constraints/stdout.txt @@ -0,0 +1,2 @@ +PROJECT CONSTRAINT VERSION REVISION LATEST PKGS USED +github.com/carolynvs/go-dep-test ^0.1.0 0.1.0 b9c5511 0.1.1 1 diff --git a/cmd/dep/testdata/harness_tests/status/old_constraints/testcase.json b/cmd/dep/testdata/harness_tests/status/old_constraints/testcase.json new file mode 100644 index 0000000000..a136acd187 --- /dev/null +++ b/cmd/dep/testdata/harness_tests/status/old_constraints/testcase.json @@ -0,0 +1,12 @@ +{ + "commands": [ + ["ensure"], + ["status", "-old"] + ], + "error-expected": "", + "vendor-final": [ + "github.com/carolynvs/go-dep-test", + "github.com/sdboyer/deptest", + "github.com/sdboyer/deptestdos" + ] +}