github.com/golang/dep@v0.5.4/gps/solve_test.go (about)

     1  // Copyright 2017 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package gps
     6  
     7  import (
     8  	"bytes"
     9  	"context"
    10  	"fmt"
    11  	"log"
    12  	"reflect"
    13  	"sort"
    14  	"testing"
    15  
    16  	"github.com/golang/dep/internal/test"
    17  )
    18  
    19  // overrideMkBridge overrides the base bridge with the depspecBridge that skips
    20  // verifyRootDir calls
    21  func overrideMkBridge(s *solver, sm SourceManager, down bool) sourceBridge {
    22  	return &depspecBridge{mkBridge(s, sm, down)}
    23  }
    24  
    25  func fixSolve(params SolveParameters, sm SourceManager, t *testing.T) (Solution, error) {
    26  	// Trace unconditionally; by passing the trace through t.Log(), the testing
    27  	// system will decide whether or not to actually show the output (based on
    28  	// -v, or selectively on test failure).
    29  	params.TraceLogger = log.New(test.Writer{TB: t}, "", 0)
    30  	// always return false, otherwise it would identify pretty much all of
    31  	// our fixtures as being stdlib and skip everything
    32  	params.stdLibFn = func(string) bool { return false }
    33  	params.mkBridgeFn = overrideMkBridge
    34  	s, err := Prepare(params, sm)
    35  	if err != nil {
    36  		return nil, err
    37  	}
    38  
    39  	return s.Solve(context.Background())
    40  }
    41  
    42  // Test all the basic table fixtures.
    43  //
    44  // Or, just the one named in the fix arg.
    45  func TestBasicSolves(t *testing.T) {
    46  	// sort them by their keys so we get stable output
    47  	names := make([]string, 0, len(basicFixtures))
    48  	for n := range basicFixtures {
    49  		names = append(names, n)
    50  	}
    51  
    52  	sort.Strings(names)
    53  	for _, n := range names {
    54  		n := n
    55  		t.Run(n, func(t *testing.T) {
    56  			t.Parallel()
    57  			solveBasicsAndCheck(basicFixtures[n], t)
    58  		})
    59  	}
    60  }
    61  
    62  func solveBasicsAndCheck(fix basicFixture, t *testing.T) (res Solution, err error) {
    63  	sm := newdepspecSM(fix.ds, nil)
    64  	if fix.broken != "" {
    65  		t.Skip(fix.broken)
    66  	}
    67  
    68  	params := SolveParameters{
    69  		RootDir:         string(fix.ds[0].n),
    70  		RootPackageTree: fix.rootTree(),
    71  		Manifest:        fix.rootmanifest(),
    72  		Lock:            dummyLock{},
    73  		Downgrade:       fix.downgrade,
    74  		ChangeAll:       fix.changeall,
    75  		ToChange:        fix.changelist,
    76  		ProjectAnalyzer: naiveAnalyzer{},
    77  	}
    78  
    79  	if fix.l != nil {
    80  		params.Lock = fix.l
    81  	}
    82  
    83  	res, err = fixSolve(params, sm, t)
    84  
    85  	return fixtureSolveSimpleChecks(fix, res, err, t)
    86  }
    87  
    88  // Test all the bimodal table fixtures.
    89  //
    90  // Or, just the one named in the fix arg.
    91  func TestBimodalSolves(t *testing.T) {
    92  	// sort them by their keys so we get stable output
    93  	names := make([]string, 0, len(bimodalFixtures))
    94  	for n := range bimodalFixtures {
    95  		names = append(names, n)
    96  	}
    97  
    98  	sort.Strings(names)
    99  	for _, n := range names {
   100  		n := n
   101  		t.Run(n, func(t *testing.T) {
   102  			t.Parallel()
   103  			solveBimodalAndCheck(bimodalFixtures[n], t)
   104  		})
   105  	}
   106  }
   107  
   108  func solveBimodalAndCheck(fix bimodalFixture, t *testing.T) (res Solution, err error) {
   109  	sm := newbmSM(fix)
   110  	if fix.broken != "" {
   111  		t.Skip(fix.broken)
   112  	}
   113  
   114  	params := SolveParameters{
   115  		RootDir:         string(fix.ds[0].n),
   116  		RootPackageTree: fix.rootTree(),
   117  		Manifest:        fix.rootmanifest(),
   118  		Lock:            dummyLock{},
   119  		Downgrade:       fix.downgrade,
   120  		ChangeAll:       fix.changeall,
   121  		ProjectAnalyzer: naiveAnalyzer{},
   122  	}
   123  
   124  	if fix.l != nil {
   125  		params.Lock = fix.l
   126  	}
   127  
   128  	res, err = fixSolve(params, sm, t)
   129  
   130  	return fixtureSolveSimpleChecks(fix, res, err, t)
   131  }
   132  
   133  func fixtureSolveSimpleChecks(fix specfix, soln Solution, err error, t *testing.T) (Solution, error) {
   134  	ppi := func(id ProjectIdentifier) string {
   135  		// need this so we can clearly tell if there's a Source or not
   136  		if id.Source == "" {
   137  			return string(id.ProjectRoot)
   138  		}
   139  		return fmt.Sprintf("%s (from %s)", id.ProjectRoot, id.Source)
   140  	}
   141  
   142  	pv := func(v Version) string {
   143  		if pv, ok := v.(PairedVersion); ok {
   144  			return fmt.Sprintf("%s (%s)", pv.Unpair(), pv.Revision())
   145  		}
   146  		return v.String()
   147  	}
   148  
   149  	fixfail := fix.failure()
   150  	if err != nil {
   151  		if fixfail == nil {
   152  			t.Errorf("Solve failed unexpectedly:\n%s", err)
   153  		} else if fixfail.Error() != err.Error() {
   154  			// TODO(sdboyer) reflect.DeepEqual works for now, but once we start
   155  			// modeling more complex cases, this should probably become more robust
   156  			t.Errorf("Failure mismatch:\n\t(GOT): %s\n\t(WNT): %s", err, fixfail)
   157  		}
   158  	} else if fixfail != nil {
   159  		var buf bytes.Buffer
   160  		fmt.Fprintf(&buf, "Solver succeeded, but expecting failure:\n%s\nProjects in solution:", fixfail)
   161  		for _, p := range soln.Projects() {
   162  			fmt.Fprintf(&buf, "\n\t- %s at %s", ppi(p.Ident()), p.Version())
   163  		}
   164  		t.Error(buf.String())
   165  	} else {
   166  		r := soln.(solution)
   167  		if fix.maxTries() > 0 && r.Attempts() > fix.maxTries() {
   168  			t.Errorf("Solver completed in %v attempts, but expected %v or fewer", r.att, fix.maxTries())
   169  		}
   170  
   171  		// Dump result projects into a map for easier interrogation
   172  		rp := make(map[ProjectIdentifier]LockedProject)
   173  		for _, lp := range r.p {
   174  			rp[lp.Ident()] = lp
   175  		}
   176  
   177  		fixlen, rlen := len(fix.solution()), len(rp)
   178  		if fixlen != rlen {
   179  			// Different length, so they definitely disagree
   180  			t.Errorf("Solver reported %v package results, result expected %v", rlen, fixlen)
   181  		}
   182  
   183  		// Whether or not len is same, still have to verify that results agree
   184  		// Walk through fixture/expected results first
   185  		for id, flp := range fix.solution() {
   186  			if lp, exists := rp[id]; !exists {
   187  				t.Errorf("Project %q expected but missing from results", ppi(id))
   188  			} else {
   189  				// delete result from map so we skip it on the reverse pass
   190  				delete(rp, id)
   191  				if flp.Version() != lp.Version() {
   192  					t.Errorf("Expected version %q of project %q, but actual version was %q", pv(flp.Version()), ppi(id), pv(lp.Version()))
   193  				}
   194  
   195  				if !reflect.DeepEqual(lp.Packages(), flp.Packages()) {
   196  					t.Errorf("Package list was not not as expected for project %s@%s:\n\t(GOT) %s\n\t(WNT) %s", ppi(id), pv(lp.Version()), lp.Packages(), flp.Packages())
   197  				}
   198  			}
   199  		}
   200  
   201  		// Now walk through remaining actual results
   202  		for id, lp := range rp {
   203  			if _, exists := fix.solution()[id]; !exists {
   204  				t.Errorf("Unexpected project %s@%s present in results, with pkgs:\n\t%s", ppi(id), pv(lp.Version()), lp.Packages())
   205  			}
   206  		}
   207  	}
   208  
   209  	return soln, err
   210  }
   211  
   212  // This tests that, when a root lock is underspecified (has only a version) we
   213  // don't allow a match on that version from a rev in the manifest. We may allow
   214  // this in the future, but disallow it for now because going from an immutable
   215  // requirement to a mutable lock automagically is a bad direction that could
   216  // produce weird side effects.
   217  func TestRootLockNoVersionPairMatching(t *testing.T) {
   218  	fix := basicFixture{
   219  		n: "does not match unpaired lock versions with paired real versions",
   220  		ds: []depspec{
   221  			mkDepspec("root 0.0.0", "foo *"), // foo's constraint rewritten below to foorev
   222  			mkDepspec("foo 1.0.0", "bar 1.0.0"),
   223  			mkDepspec("foo 1.0.1 foorev", "bar 1.0.1"),
   224  			mkDepspec("foo 1.0.2 foorev", "bar 1.0.2"),
   225  			mkDepspec("bar 1.0.0"),
   226  			mkDepspec("bar 1.0.1"),
   227  			mkDepspec("bar 1.0.2"),
   228  		},
   229  		l: mklock(
   230  			"foo 1.0.1",
   231  		),
   232  		r: mksolution(
   233  			"foo 1.0.2 foorev",
   234  			"bar 1.0.2",
   235  		),
   236  	}
   237  
   238  	pd := fix.ds[0].deps[0]
   239  	pd.Constraint = Revision("foorev")
   240  	fix.ds[0].deps[0] = pd
   241  
   242  	sm := newdepspecSM(fix.ds, nil)
   243  
   244  	l2 := make(fixLock, 1)
   245  	copy(l2, fix.l)
   246  
   247  	l2lp := l2[0].(lockedProject)
   248  	l2lp.v = nil
   249  	l2[0] = l2lp
   250  
   251  	params := SolveParameters{
   252  		RootDir:         string(fix.ds[0].n),
   253  		RootPackageTree: fix.rootTree(),
   254  		Manifest:        fix.rootmanifest(),
   255  		Lock:            l2,
   256  		ProjectAnalyzer: naiveAnalyzer{},
   257  	}
   258  
   259  	res, err := fixSolve(params, sm, t)
   260  
   261  	fixtureSolveSimpleChecks(fix, res, err, t)
   262  }