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 5package gps 6 7import ( 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 21func overrideMkBridge(s *solver, sm SourceManager, down bool) sourceBridge { 22 return &depspecBridge{mkBridge(s, sm, down)} 23} 24 25func 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. 45func 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 62func 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. 91func 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 108func 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 133func 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. 217func 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} 263