Skip to content
This repository has been archived by the owner on Sep 9, 2020. It is now read-only.

Commit

Permalink
Add tests and put command in own function
Browse files Browse the repository at this point in the history
Added unit test for projUpToDate and harness test for the -old flag.
Put the status -old functionality to it's own function.
  • Loading branch information
zkry committed Feb 9, 2018
1 parent 0db8cb2 commit 1ad042b
Show file tree
Hide file tree
Showing 9 changed files with 382 additions and 33 deletions.
260 changes: 227 additions & 33 deletions cmd/dep/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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())
Expand Down
51 changes: 51 additions & 0 deletions cmd/dep/status_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 1ad042b

Please sign in to comment.