github.com/golang/dep@v0.5.4/cmd/dep/status_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 main
     6  
     7  import (
     8  	"bytes"
     9  	"fmt"
    10  	"io"
    11  	"io/ioutil"
    12  	"log"
    13  	"path/filepath"
    14  	"reflect"
    15  	"strings"
    16  	"testing"
    17  	"text/tabwriter"
    18  	"text/template"
    19  
    20  	"github.com/golang/dep"
    21  	"github.com/golang/dep/gps"
    22  	"github.com/golang/dep/internal/test"
    23  	"github.com/pkg/errors"
    24  )
    25  
    26  func TestStatusFormatVersion(t *testing.T) {
    27  	t.Parallel()
    28  
    29  	tests := map[gps.Version]string{
    30  		nil:                            "",
    31  		gps.NewBranch("master"):        "branch master",
    32  		gps.NewVersion("1.0.0"):        "1.0.0",
    33  		gps.Revision("flooboofoobooo"): "flooboo",
    34  	}
    35  	for version, expected := range tests {
    36  		str := formatVersion(version)
    37  		if str != expected {
    38  			t.Fatalf("expected '%v', got '%v'", expected, str)
    39  		}
    40  	}
    41  }
    42  
    43  func TestBasicLine(t *testing.T) {
    44  	project := dep.Project{}
    45  	aSemverConstraint, _ := gps.NewSemverConstraint("1.2.3")
    46  
    47  	templateString := "PR:{{.ProjectRoot}}, Const:{{.Constraint}}, Ver:{{.Version}}, Rev:{{.Revision}}, Lat:{{.Latest}}, PkgCt:{{.PackageCount}}"
    48  	equalityTestTemplate := `{{if eq .Constraint "1.2.3"}}Constraint is 1.2.3{{end}}|{{if eq .Version "flooboo"}}Version is flooboo{{end}}|{{if eq .Latest "unknown"}}Latest is unknown{{end}}`
    49  
    50  	tests := []struct {
    51  		name                 string
    52  		status               BasicStatus
    53  		wantDotStatus        []string
    54  		wantJSONStatus       []string
    55  		wantTableStatus      []string
    56  		wantTemplateStatus   []string
    57  		wantEqTemplateStatus []string
    58  	}{
    59  		{
    60  			name: "BasicStatus with ProjectRoot only",
    61  			status: BasicStatus{
    62  				ProjectRoot: "github.com/foo/bar",
    63  			},
    64  			wantDotStatus:        []string{`[label="github.com/foo/bar"];`},
    65  			wantJSONStatus:       []string{`"Version":""`, `"Revision":""`},
    66  			wantTableStatus:      []string{`github.com/foo/bar                                         0`},
    67  			wantTemplateStatus:   []string{`PR:github.com/foo/bar, Const:, Ver:, Rev:, Lat:, PkgCt:0`},
    68  			wantEqTemplateStatus: []string{`||`},
    69  		},
    70  		{
    71  			name: "BasicStatus with Revision",
    72  			status: BasicStatus{
    73  				ProjectRoot: "github.com/foo/bar",
    74  				Revision:    gps.Revision("flooboofoobooo"),
    75  			},
    76  			wantDotStatus:        []string{`[label="github.com/foo/bar\nflooboo"];`},
    77  			wantJSONStatus:       []string{`"Version":""`, `"Revision":"flooboofoobooo"`, `"Constraint":""`},
    78  			wantTableStatus:      []string{`github.com/foo/bar                       flooboo           0`},
    79  			wantTemplateStatus:   []string{`PR:github.com/foo/bar, Const:, Ver:flooboo, Rev:flooboofoobooo, Lat:, PkgCt:0`},
    80  			wantEqTemplateStatus: []string{`|Version is flooboo|`},
    81  		},
    82  		{
    83  			name: "BasicStatus with Version and Revision",
    84  			status: BasicStatus{
    85  				ProjectRoot: "github.com/foo/bar",
    86  				Version:     gps.NewVersion("1.0.0"),
    87  				Revision:    gps.Revision("flooboofoobooo"),
    88  			},
    89  			wantDotStatus:        []string{`[label="github.com/foo/bar\n1.0.0"];`},
    90  			wantJSONStatus:       []string{`"Version":"1.0.0"`, `"Revision":"flooboofoobooo"`, `"Constraint":""`},
    91  			wantTableStatus:      []string{`github.com/foo/bar              1.0.0    flooboo           0`},
    92  			wantTemplateStatus:   []string{`PR:github.com/foo/bar, Const:, Ver:1.0.0, Rev:flooboofoobooo, Lat:, PkgCt:0`},
    93  			wantEqTemplateStatus: []string{`||`},
    94  		},
    95  		{
    96  			name: "BasicStatus with Constraint, Version and Revision",
    97  			status: BasicStatus{
    98  				ProjectRoot: "github.com/foo/bar",
    99  				Constraint:  aSemverConstraint,
   100  				Version:     gps.NewVersion("1.0.0"),
   101  				Revision:    gps.Revision("revxyz"),
   102  			},
   103  			wantDotStatus:        []string{`[label="github.com/foo/bar\n1.0.0"];`},
   104  			wantJSONStatus:       []string{`"Revision":"revxyz"`, `"Constraint":"1.2.3"`, `"Version":"1.0.0"`},
   105  			wantTableStatus:      []string{`github.com/foo/bar  1.2.3       1.0.0    revxyz            0`},
   106  			wantTemplateStatus:   []string{`PR:github.com/foo/bar, Const:1.2.3, Ver:1.0.0, Rev:revxyz, Lat:, PkgCt:0`},
   107  			wantEqTemplateStatus: []string{`Constraint is 1.2.3||`},
   108  		},
   109  		{
   110  			name: "BasicStatus with update error",
   111  			status: BasicStatus{
   112  				ProjectRoot: "github.com/foo/bar",
   113  				hasError:    true,
   114  			},
   115  			wantDotStatus:        []string{`[label="github.com/foo/bar"];`},
   116  			wantJSONStatus:       []string{`"Version":""`, `"Revision":""`, `"Latest":"unknown"`},
   117  			wantTableStatus:      []string{`github.com/foo/bar                                 unknown  0`},
   118  			wantTemplateStatus:   []string{`PR:github.com/foo/bar, Const:, Ver:, Rev:, Lat:unknown, PkgCt:0`},
   119  			wantEqTemplateStatus: []string{`||Latest is unknown`},
   120  		},
   121  	}
   122  
   123  	for _, test := range tests {
   124  		t.Run(test.name, func(t *testing.T) {
   125  			var buf bytes.Buffer
   126  
   127  			dotout := &dotOutput{
   128  				p: &project,
   129  				w: &buf,
   130  			}
   131  			dotout.BasicHeader()
   132  			dotout.BasicLine(&test.status)
   133  			dotout.BasicFooter()
   134  
   135  			for _, wantStatus := range test.wantDotStatus {
   136  				if ok := strings.Contains(buf.String(), wantStatus); !ok {
   137  					t.Errorf("Did not find expected node status: \n\t(GOT) %v \n\t(WNT) %v", buf.String(), wantStatus)
   138  				}
   139  			}
   140  
   141  			buf.Reset()
   142  
   143  			jsonout := &jsonOutput{w: &buf}
   144  
   145  			jsonout.BasicHeader()
   146  			jsonout.BasicLine(&test.status)
   147  			jsonout.BasicFooter()
   148  
   149  			for _, wantStatus := range test.wantJSONStatus {
   150  				if ok := strings.Contains(buf.String(), wantStatus); !ok {
   151  					t.Errorf("Did not find expected JSON status: \n\t(GOT) %v \n\t(WNT) %v", buf.String(), wantStatus)
   152  				}
   153  			}
   154  
   155  			buf.Reset()
   156  
   157  			tabw := tabwriter.NewWriter(&buf, 0, 4, 2, ' ', 0)
   158  
   159  			tableout := &tableOutput{w: tabw}
   160  
   161  			tableout.BasicHeader()
   162  			tableout.BasicLine(&test.status)
   163  			tableout.BasicFooter()
   164  
   165  			for _, wantStatus := range test.wantTableStatus {
   166  				if ok := strings.Contains(buf.String(), wantStatus); !ok {
   167  					t.Errorf("Did not find expected Table status: \n\t(GOT) %v \n\t(WNT) %v", buf.String(), wantStatus)
   168  				}
   169  			}
   170  
   171  			buf.Reset()
   172  			template, _ := template.New("status").Parse(templateString)
   173  			templateout := &templateOutput{w: &buf, tmpl: template}
   174  			templateout.BasicHeader()
   175  			templateout.BasicLine(&test.status)
   176  			templateout.BasicFooter()
   177  
   178  			for _, wantStatus := range test.wantTemplateStatus {
   179  				if ok := strings.Contains(buf.String(), wantStatus); !ok {
   180  					t.Errorf("Did not find expected template status: \n\t(GOT) %v \n\t(WNT) %v", buf.String(), wantStatus)
   181  				}
   182  			}
   183  
   184  			// The following test is to ensure that certain fields usable with string operations such as .eq
   185  			buf.Reset()
   186  			template, _ = template.New("status").Parse(equalityTestTemplate)
   187  			templateout = &templateOutput{w: &buf, tmpl: template}
   188  			templateout.BasicHeader()
   189  			templateout.BasicLine(&test.status)
   190  			templateout.BasicFooter()
   191  
   192  			for _, wantStatus := range test.wantEqTemplateStatus {
   193  				if ok := strings.Contains(buf.String(), wantStatus); !ok {
   194  					t.Errorf("Did not find expected template status: \n\t(GOT) %v \n\t(WNT) %v", buf.String(), wantStatus)
   195  				}
   196  			}
   197  		})
   198  	}
   199  }
   200  
   201  func TestDetailLine(t *testing.T) {
   202  	project := dep.Project{}
   203  	aSemverConstraint, _ := gps.NewSemverConstraint("1.2.3")
   204  
   205  	templateString := "{{range $p := .Projects}}PR:{{$p.ProjectRoot}}, Src:{{$p.Source}}, Const:{{$p.Constraint}}, Ver:{{$p.Locked.Version}}, Rev:{{$p.Locked.Revision}}, Lat:{{$p.Latest.Revision}}, PkgCt:{{$p.PackageCount}}, Pkgs:{{$p.Packages}}{{end}}"
   206  	equalityTestTemplate := `{{range $p := .Projects}}{{if eq $p.Constraint "1.2.3"}}Constraint is 1.2.3{{end}}|{{if eq $p.Locked.Version "flooboo"}}Version is flooboo{{end}}|{{if eq $p.Locked.Revision "flooboofoobooo"}}Revision is flooboofoobooo{{end}}|{{if eq $p.Latest.Revision "unknown"}}Latest is unknown{{end}}{{end}}`
   207  
   208  	tests := []struct {
   209  		name                 string
   210  		status               DetailStatus
   211  		wantDotStatus        []string
   212  		wantJSONStatus       []string
   213  		wantTableStatus      []string
   214  		wantTemplateStatus   []string
   215  		wantEqTemplateStatus []string
   216  	}{
   217  		{
   218  			name: "DetailStatus with ProjectRoot only",
   219  			status: DetailStatus{
   220  				BasicStatus: BasicStatus{
   221  					ProjectRoot: "github.com/foo/bar",
   222  				},
   223  				Packages: []string{},
   224  			},
   225  			wantDotStatus:        []string{`[label="github.com/foo/bar"];`},
   226  			wantJSONStatus:       []string{`"Locked":{}`},
   227  			wantTableStatus:      []string{`github.com/foo/bar                                                 []`},
   228  			wantTemplateStatus:   []string{`PR:github.com/foo/bar, Src:, Const:, Ver:, Rev:, Lat:, PkgCt:0, Pkgs:[]`},
   229  			wantEqTemplateStatus: []string{`||`},
   230  		},
   231  		{
   232  			name: "DetailStatus with Revision",
   233  			status: DetailStatus{
   234  				BasicStatus: BasicStatus{
   235  					ProjectRoot: "github.com/foo/bar",
   236  					Revision:    gps.Revision("flooboofoobooo"),
   237  				},
   238  				Packages: []string{},
   239  			},
   240  			wantDotStatus:        []string{`[label="github.com/foo/bar\nflooboo"];`},
   241  			wantJSONStatus:       []string{`"Locked":{"Revision":"flooboofoobooo"}`, `"Constraint":""`},
   242  			wantTableStatus:      []string{`github.com/foo/bar                               flooboo           []`},
   243  			wantTemplateStatus:   []string{`PR:github.com/foo/bar, Src:, Const:, Ver:, Rev:flooboofoobooo, Lat:, PkgCt:0, Pkgs:[]`},
   244  			wantEqTemplateStatus: []string{`|Revision is flooboofoobooo|`},
   245  		},
   246  		{
   247  			name: "DetailStatus with Source",
   248  			status: DetailStatus{
   249  				BasicStatus: BasicStatus{
   250  					ProjectRoot: "github.com/foo/bar",
   251  				},
   252  				Packages: []string{},
   253  				Source:   "github.com/baz/bar",
   254  			},
   255  			wantDotStatus:        []string{`[label="github.com/foo/bar"];`},
   256  			wantJSONStatus:       []string{`"Locked":{}`, `"Source":"github.com/baz/bar"`, `"Constraint":""`},
   257  			wantTableStatus:      []string{`github.com/foo/bar  github.com/baz/bar                                         []`},
   258  			wantTemplateStatus:   []string{`PR:github.com/foo/bar, Src:github.com/baz/bar, Const:, Ver:, Rev:, Lat:, PkgCt:0, Pkgs:[]`},
   259  			wantEqTemplateStatus: []string{`||`},
   260  		},
   261  		{
   262  			name: "DetailStatus with Version and Revision",
   263  			status: DetailStatus{
   264  				BasicStatus: BasicStatus{
   265  					ProjectRoot: "github.com/foo/bar",
   266  					Version:     gps.NewVersion("1.0.0"),
   267  					Revision:    gps.Revision("flooboofoobooo"),
   268  				},
   269  				Packages: []string{},
   270  			},
   271  			wantDotStatus:        []string{`[label="github.com/foo/bar\n1.0.0"];`},
   272  			wantJSONStatus:       []string{`"Version":"1.0.0"`, `"Revision":"flooboofoobooo"`, `"Constraint":""`},
   273  			wantTableStatus:      []string{`github.com/foo/bar                      1.0.0    flooboo           []`},
   274  			wantTemplateStatus:   []string{`PR:github.com/foo/bar, Src:, Const:, Ver:1.0.0, Rev:flooboofoobooo, Lat:, PkgCt:0, Pkgs:[]`},
   275  			wantEqTemplateStatus: []string{`||`},
   276  		},
   277  		{
   278  			name: "DetailStatus with Constraint, Version and Revision",
   279  			status: DetailStatus{
   280  				BasicStatus: BasicStatus{
   281  					ProjectRoot: "github.com/foo/bar",
   282  					Constraint:  aSemverConstraint,
   283  					Version:     gps.NewVersion("1.0.0"),
   284  					Revision:    gps.Revision("revxyz"),
   285  				},
   286  				Packages: []string{},
   287  			},
   288  			wantDotStatus:        []string{`[label="github.com/foo/bar\n1.0.0"];`},
   289  			wantJSONStatus:       []string{`"Revision":"revxyz"`, `"Constraint":"1.2.3"`, `"Version":"1.0.0"`},
   290  			wantTableStatus:      []string{`github.com/foo/bar          1.2.3       1.0.0    revxyz            []`},
   291  			wantTemplateStatus:   []string{`PR:github.com/foo/bar, Src:, Const:1.2.3, Ver:1.0.0, Rev:revxyz, Lat:, PkgCt:0, Pkgs:[]`},
   292  			wantEqTemplateStatus: []string{`Constraint is 1.2.3||`},
   293  		},
   294  		{
   295  			name: "DetailStatus with Constraint, Version, Revision, and Package",
   296  			status: DetailStatus{
   297  				BasicStatus: BasicStatus{
   298  					ProjectRoot:  "github.com/foo/bar",
   299  					Constraint:   aSemverConstraint,
   300  					Version:      gps.NewVersion("1.0.0"),
   301  					Revision:     gps.Revision("revxyz"),
   302  					PackageCount: 1,
   303  				},
   304  				Packages: []string{"."},
   305  			},
   306  			wantDotStatus:        []string{`[label="github.com/foo/bar\n1.0.0"];`},
   307  			wantJSONStatus:       []string{`"Revision":"revxyz"`, `"Constraint":"1.2.3"`, `"Version":"1.0.0"`, `"PackageCount":1`, `"Packages":["."]`},
   308  			wantTableStatus:      []string{`github.com/foo/bar          1.2.3       1.0.0    revxyz            [.]`},
   309  			wantTemplateStatus:   []string{`PR:github.com/foo/bar, Src:, Const:1.2.3, Ver:1.0.0, Rev:revxyz, Lat:, PkgCt:1, Pkgs:[.]`},
   310  			wantEqTemplateStatus: []string{`Constraint is 1.2.3||`},
   311  		},
   312  		{
   313  			name: "DetailStatus with Constraint, Version, Revision, and Packages",
   314  			status: DetailStatus{
   315  				BasicStatus: BasicStatus{
   316  					ProjectRoot:  "github.com/foo/bar",
   317  					Constraint:   aSemverConstraint,
   318  					Version:      gps.NewVersion("1.0.0"),
   319  					Revision:     gps.Revision("revxyz"),
   320  					PackageCount: 3,
   321  				},
   322  				Packages: []string{".", "foo", "bar"},
   323  			},
   324  			wantDotStatus:        []string{`[label="github.com/foo/bar\n1.0.0"];`},
   325  			wantJSONStatus:       []string{`"Revision":"revxyz"`, `"Constraint":"1.2.3"`, `"Version":"1.0.0"`, `"PackageCount":3`, `"Packages":[".","foo","bar"]`},
   326  			wantTableStatus:      []string{`github.com/foo/bar          1.2.3       1.0.0    revxyz            [., foo, bar]`},
   327  			wantTemplateStatus:   []string{`PR:github.com/foo/bar, Src:, Const:1.2.3, Ver:1.0.0, Rev:revxyz, Lat:, PkgCt:3, Pkgs:[. foo bar]`},
   328  			wantEqTemplateStatus: []string{`Constraint is 1.2.3||`},
   329  		},
   330  		{
   331  			name: "DetailStatus with update error",
   332  			status: DetailStatus{
   333  				BasicStatus: BasicStatus{
   334  					ProjectRoot: "github.com/foo/bar",
   335  					hasError:    true,
   336  				},
   337  				Packages: []string{},
   338  			},
   339  			wantDotStatus:        []string{`[label="github.com/foo/bar"];`},
   340  			wantJSONStatus:       []string{`"Locked":{}`, `"Latest":{"Revision":"unknown"}`},
   341  			wantTableStatus:      []string{`github.com/foo/bar                                         unknown  []`},
   342  			wantTemplateStatus:   []string{`PR:github.com/foo/bar, Src:, Const:, Ver:, Rev:, Lat:unknown, PkgCt:0, Pkgs:[]`},
   343  			wantEqTemplateStatus: []string{`||Latest is unknown`},
   344  		},
   345  	}
   346  
   347  	for _, test := range tests {
   348  		t.Run(test.name, func(t *testing.T) {
   349  			var buf bytes.Buffer
   350  
   351  			dotout := &dotOutput{
   352  				p: &project,
   353  				w: &buf,
   354  			}
   355  			dotout.DetailHeader(nil)
   356  			dotout.DetailLine(&test.status)
   357  			dotout.DetailFooter(nil)
   358  
   359  			for _, wantStatus := range test.wantDotStatus {
   360  				if ok := strings.Contains(buf.String(), wantStatus); !ok {
   361  					t.Errorf("Did not find expected node status: \n\t(GOT) %v \n\t(WNT) %v", buf.String(), wantStatus)
   362  				}
   363  			}
   364  
   365  			buf.Reset()
   366  
   367  			jsonout := &jsonOutput{w: &buf}
   368  
   369  			jsonout.DetailHeader(nil)
   370  			jsonout.DetailLine(&test.status)
   371  			jsonout.DetailFooter(nil)
   372  
   373  			for _, wantStatus := range test.wantJSONStatus {
   374  				if ok := strings.Contains(buf.String(), wantStatus); !ok {
   375  					t.Errorf("Did not find expected JSON status: \n\t(GOT) %v \n\t(WNT) %v", buf.String(), wantStatus)
   376  				}
   377  			}
   378  
   379  			buf.Reset()
   380  
   381  			tabw := tabwriter.NewWriter(&buf, 0, 4, 2, ' ', 0)
   382  
   383  			tableout := &tableOutput{w: tabw}
   384  
   385  			tableout.DetailHeader(nil)
   386  			tableout.DetailLine(&test.status)
   387  			tableout.DetailFooter(nil)
   388  
   389  			for _, wantStatus := range test.wantTableStatus {
   390  				if ok := strings.Contains(buf.String(), wantStatus); !ok {
   391  					t.Errorf("Did not find expected Table status: \n\t(GOT) %v \n\t(WNT) %v", buf.String(), wantStatus)
   392  				}
   393  			}
   394  
   395  			buf.Reset()
   396  			template, _ := template.New("status").Parse(templateString)
   397  			templateout := &templateOutput{w: &buf, tmpl: template}
   398  			templateout.DetailHeader(nil)
   399  			templateout.DetailLine(&test.status)
   400  			templateout.DetailFooter(nil)
   401  
   402  			for _, wantStatus := range test.wantTemplateStatus {
   403  				if ok := strings.Contains(buf.String(), wantStatus); !ok {
   404  					t.Errorf("Did not find expected template status: \n\t(GOT) %v \n\t(WNT) %v", buf.String(), wantStatus)
   405  				}
   406  			}
   407  
   408  			// The following test is to ensure that certain fields usable with string operations such as .eq
   409  			buf.Reset()
   410  			template, _ = template.New("status").Parse(equalityTestTemplate)
   411  			templateout = &templateOutput{w: &buf, tmpl: template}
   412  			templateout.DetailHeader(nil)
   413  			templateout.DetailLine(&test.status)
   414  			templateout.DetailFooter(nil)
   415  
   416  			for _, wantStatus := range test.wantEqTemplateStatus {
   417  				if ok := strings.Contains(buf.String(), wantStatus); !ok {
   418  					t.Errorf("Did not find expected template status: \n\t(GOT) %v \n\t(WNT) %v", buf.String(), wantStatus)
   419  				}
   420  			}
   421  		})
   422  	}
   423  }
   424  
   425  func TestBasicStatusGetConsolidatedConstraint(t *testing.T) {
   426  	aSemverConstraint, _ := gps.NewSemverConstraint("1.2.1")
   427  
   428  	testCases := []struct {
   429  		name           string
   430  		basicStatus    BasicStatus
   431  		wantConstraint string
   432  	}{
   433  		{
   434  			name:           "empty BasicStatus",
   435  			basicStatus:    BasicStatus{},
   436  			wantConstraint: "",
   437  		},
   438  		{
   439  			name: "BasicStatus with Any Constraint",
   440  			basicStatus: BasicStatus{
   441  				Constraint: gps.Any(),
   442  			},
   443  			wantConstraint: "*",
   444  		},
   445  		{
   446  			name: "BasicStatus with Semver Constraint",
   447  			basicStatus: BasicStatus{
   448  				Constraint: aSemverConstraint,
   449  			},
   450  			wantConstraint: "1.2.1",
   451  		},
   452  		{
   453  			name: "BasicStatus with Override",
   454  			basicStatus: BasicStatus{
   455  				Constraint:  aSemverConstraint,
   456  				hasOverride: true,
   457  			},
   458  			wantConstraint: "1.2.1 (override)",
   459  		},
   460  		{
   461  			name: "BasicStatus with Revision Constraint",
   462  			basicStatus: BasicStatus{
   463  				Constraint: gps.Revision("ddeb6f5d27091ff291b16232e99076a64fb375b8"),
   464  			},
   465  			wantConstraint: "ddeb6f5",
   466  		},
   467  	}
   468  
   469  	for _, tc := range testCases {
   470  		t.Run(tc.name, func(t *testing.T) {
   471  			if tc.basicStatus.getConsolidatedConstraint() != tc.wantConstraint {
   472  				t.Errorf("unexpected consolidated constraint: \n\t(GOT) %v \n\t(WNT) %v", tc.basicStatus.getConsolidatedConstraint(), tc.wantConstraint)
   473  			}
   474  		})
   475  	}
   476  }
   477  
   478  func TestBasicStatusGetConsolidatedVersion(t *testing.T) {
   479  	testCases := []struct {
   480  		name        string
   481  		basicStatus BasicStatus
   482  		wantVersion string
   483  	}{
   484  		{
   485  			name:        "empty BasicStatus",
   486  			basicStatus: BasicStatus{},
   487  			wantVersion: "",
   488  		},
   489  		{
   490  			name: "BasicStatus with Version and Revision",
   491  			basicStatus: BasicStatus{
   492  				Version:  gps.NewVersion("1.0.0"),
   493  				Revision: gps.Revision("revxyz"),
   494  			},
   495  			wantVersion: "1.0.0",
   496  		},
   497  		{
   498  			name: "BasicStatus with only Revision",
   499  			basicStatus: BasicStatus{
   500  				Revision: gps.Revision("revxyz"),
   501  			},
   502  			wantVersion: "revxyz",
   503  		},
   504  	}
   505  
   506  	for _, tc := range testCases {
   507  		t.Run(tc.name, func(t *testing.T) {
   508  			if tc.basicStatus.getConsolidatedVersion() != tc.wantVersion {
   509  				t.Errorf("unexpected consolidated version: \n\t(GOT) %v \n\t(WNT) %v", tc.basicStatus.getConsolidatedVersion(), tc.wantVersion)
   510  			}
   511  		})
   512  	}
   513  }
   514  
   515  func TestBasicStatusGetConsolidatedLatest(t *testing.T) {
   516  	testCases := []struct {
   517  		name        string
   518  		basicStatus BasicStatus
   519  		revSize     uint8
   520  		wantLatest  string
   521  	}{
   522  		{
   523  			name:        "empty BasicStatus",
   524  			basicStatus: BasicStatus{},
   525  			revSize:     shortRev,
   526  			wantLatest:  "",
   527  		},
   528  		{
   529  			name: "nil latest",
   530  			basicStatus: BasicStatus{
   531  				Latest: nil,
   532  			},
   533  			revSize:    shortRev,
   534  			wantLatest: "",
   535  		},
   536  		{
   537  			name: "with error",
   538  			basicStatus: BasicStatus{
   539  				hasError: true,
   540  			},
   541  			revSize:    shortRev,
   542  			wantLatest: "unknown",
   543  		},
   544  		{
   545  			name: "short latest",
   546  			basicStatus: BasicStatus{
   547  				Latest: gps.Revision("adummylonglongrevision"),
   548  			},
   549  			revSize:    shortRev,
   550  			wantLatest: "adummyl",
   551  		},
   552  		{
   553  			name: "long latest",
   554  			basicStatus: BasicStatus{
   555  				Latest: gps.Revision("adummylonglongrevision"),
   556  			},
   557  			revSize:    longRev,
   558  			wantLatest: "adummylonglongrevision",
   559  		},
   560  	}
   561  
   562  	for _, tc := range testCases {
   563  		t.Run(tc.name, func(t *testing.T) {
   564  			gotRev := tc.basicStatus.getConsolidatedLatest(tc.revSize)
   565  			if gotRev != tc.wantLatest {
   566  				t.Errorf("unexpected consolidated latest: \n\t(GOT) %v \n\t(WNT) %v", gotRev, tc.wantLatest)
   567  			}
   568  		})
   569  	}
   570  }
   571  
   572  func TestCollectConstraints(t *testing.T) {
   573  	ver1, _ := gps.NewSemverConstraintIC("v1.0.0")
   574  	ver08, _ := gps.NewSemverConstraintIC("v0.8.0")
   575  	ver2, _ := gps.NewSemverConstraintIC("v2.0.0")
   576  
   577  	cases := []struct {
   578  		name            string
   579  		lock            dep.Lock
   580  		manifest        dep.Manifest
   581  		wantConstraints constraintsCollection
   582  		wantErr         bool
   583  	}{
   584  		{
   585  			name: "without any constraints",
   586  			lock: dep.Lock{
   587  				P: []gps.LockedProject{
   588  					gps.NewLockedProject(
   589  						gps.ProjectIdentifier{ProjectRoot: gps.ProjectRoot("github.com/sdboyer/deptest")},
   590  						gps.NewVersion("v1.0.0"),
   591  						[]string{"."},
   592  					),
   593  				},
   594  			},
   595  			wantConstraints: constraintsCollection{},
   596  		},
   597  		{
   598  			name: "with multiple constraints from dependencies",
   599  			lock: dep.Lock{
   600  				P: []gps.LockedProject{
   601  					gps.NewLockedProject(
   602  						gps.ProjectIdentifier{ProjectRoot: gps.ProjectRoot("github.com/sdboyer/deptest")},
   603  						gps.NewVersion("v1.0.0"),
   604  						[]string{"."},
   605  					),
   606  					gps.NewLockedProject(
   607  						gps.ProjectIdentifier{ProjectRoot: gps.ProjectRoot("github.com/darkowlzz/deptest-project-1")},
   608  						gps.NewVersion("v0.1.0"),
   609  						[]string{"."},
   610  					),
   611  					gps.NewLockedProject(
   612  						gps.ProjectIdentifier{ProjectRoot: gps.ProjectRoot("github.com/darkowlzz/deptest-project-2")},
   613  						gps.NewBranch("master").Pair(gps.Revision("824a8d56a4c6b2f4718824a98cd6d70d3dbd4c3e")),
   614  						[]string{"."},
   615  					),
   616  				},
   617  			},
   618  			wantConstraints: constraintsCollection{
   619  				"github.com/sdboyer/deptestdos": []projectConstraint{
   620  					{"github.com/darkowlzz/deptest-project-2", ver2},
   621  				},
   622  				"github.com/sdboyer/dep-test": []projectConstraint{
   623  					{"github.com/darkowlzz/deptest-project-2", ver1},
   624  				},
   625  				"github.com/sdboyer/deptest": []projectConstraint{
   626  					{"github.com/darkowlzz/deptest-project-1", ver1},
   627  					{"github.com/darkowlzz/deptest-project-2", ver08},
   628  				},
   629  			},
   630  		},
   631  		{
   632  			name: "with multiple constraints from dependencies and root project",
   633  			lock: dep.Lock{
   634  				P: []gps.LockedProject{
   635  					gps.NewLockedProject(
   636  						gps.ProjectIdentifier{ProjectRoot: gps.ProjectRoot("github.com/sdboyer/deptest")},
   637  						gps.NewVersion("v1.0.0"),
   638  						[]string{"."},
   639  					),
   640  					gps.NewLockedProject(
   641  						gps.ProjectIdentifier{ProjectRoot: gps.ProjectRoot("github.com/darkowlzz/deptest-project-1")},
   642  						gps.NewVersion("v0.1.0"),
   643  						[]string{"."},
   644  					),
   645  					gps.NewLockedProject(
   646  						gps.ProjectIdentifier{ProjectRoot: gps.ProjectRoot("github.com/darkowlzz/deptest-project-2")},
   647  						gps.NewBranch("master").Pair(gps.Revision("824a8d56a4c6b2f4718824a98cd6d70d3dbd4c3e")),
   648  						[]string{"."},
   649  					),
   650  				},
   651  			},
   652  			manifest: dep.Manifest{
   653  				Constraints: map[gps.ProjectRoot]gps.ProjectProperties{
   654  					gps.ProjectRoot("github.com/sdboyer/deptest"): {
   655  						Constraint: gps.Revision("3f4c3bea144e112a69bbe5d8d01c1b09a544253f"),
   656  					},
   657  				},
   658  				Ovr: make(gps.ProjectConstraints),
   659  				PruneOptions: gps.CascadingPruneOptions{
   660  					DefaultOptions:    gps.PruneNestedVendorDirs,
   661  					PerProjectOptions: make(map[gps.ProjectRoot]gps.PruneOptionSet),
   662  				},
   663  			},
   664  			wantConstraints: constraintsCollection{
   665  				"github.com/sdboyer/deptestdos": []projectConstraint{
   666  					{"github.com/darkowlzz/deptest-project-2", ver2},
   667  				},
   668  				"github.com/sdboyer/dep-test": []projectConstraint{
   669  					{"github.com/darkowlzz/deptest-project-2", ver1},
   670  				},
   671  				"github.com/sdboyer/deptest": []projectConstraint{
   672  					{"github.com/darkowlzz/deptest-project-1", ver1},
   673  					{"github.com/darkowlzz/deptest-project-2", ver08},
   674  					{"root", gps.Revision("3f4c3bea144e112a69bbe5d8d01c1b09a544253f")},
   675  				},
   676  			},
   677  		},
   678  		{
   679  			name: "skip projects with invalid versions",
   680  			lock: dep.Lock{
   681  				P: []gps.LockedProject{
   682  					gps.NewLockedProject(
   683  						gps.ProjectIdentifier{ProjectRoot: gps.ProjectRoot("github.com/darkowlzz/deptest-project-1")},
   684  						gps.NewVersion("v0.1.0"),
   685  						[]string{"."},
   686  					),
   687  					gps.NewLockedProject(
   688  						gps.ProjectIdentifier{ProjectRoot: gps.ProjectRoot("github.com/darkowlzz/deptest-project-2")},
   689  						gps.NewVersion("v1.0.0"),
   690  						[]string{"."},
   691  					),
   692  				},
   693  			},
   694  			wantConstraints: constraintsCollection{
   695  				"github.com/sdboyer/deptest": []projectConstraint{
   696  					{"github.com/darkowlzz/deptest-project-1", ver1},
   697  				},
   698  			},
   699  			wantErr: true,
   700  		},
   701  		{
   702  			name: "collect only applicable constraints",
   703  			lock: dep.Lock{
   704  				P: []gps.LockedProject{
   705  					gps.NewLockedProject(
   706  						gps.ProjectIdentifier{ProjectRoot: gps.ProjectRoot("github.com/darkowlzz/dep-applicable-constraints")},
   707  						gps.NewVersion("v1.0.0"),
   708  						[]string{"."},
   709  					),
   710  				},
   711  			},
   712  			wantConstraints: constraintsCollection{
   713  				"github.com/boltdb/bolt": []projectConstraint{
   714  					{"github.com/darkowlzz/dep-applicable-constraints", gps.NewBranch("master")},
   715  				},
   716  				"github.com/sdboyer/deptest": []projectConstraint{
   717  					{"github.com/darkowlzz/dep-applicable-constraints", ver08},
   718  				},
   719  			},
   720  		},
   721  		{
   722  			name: "skip ineffective constraint from manifest",
   723  			lock: dep.Lock{
   724  				P: []gps.LockedProject{
   725  					gps.NewLockedProject(
   726  						gps.ProjectIdentifier{ProjectRoot: gps.ProjectRoot("github.com/sdboyer/deptest")},
   727  						gps.NewVersion("v1.0.0"),
   728  						[]string{"."},
   729  					),
   730  				},
   731  			},
   732  			manifest: dep.Manifest{
   733  				Constraints: map[gps.ProjectRoot]gps.ProjectProperties{
   734  					gps.ProjectRoot("github.com/darkowlzz/deptest-project-1"): {
   735  						Constraint: ver1,
   736  					},
   737  				},
   738  				Ovr: make(gps.ProjectConstraints),
   739  				PruneOptions: gps.CascadingPruneOptions{
   740  					DefaultOptions:    gps.PruneNestedVendorDirs,
   741  					PerProjectOptions: make(map[gps.ProjectRoot]gps.PruneOptionSet),
   742  				},
   743  			},
   744  			wantConstraints: constraintsCollection{},
   745  		},
   746  	}
   747  
   748  	h := test.NewHelper(t)
   749  	defer h.Cleanup()
   750  
   751  	testdir := filepath.Join("src", "collect_constraints_test")
   752  	h.TempDir(testdir)
   753  	h.TempCopy(filepath.Join(testdir, "main.go"), filepath.Join("status", "collect_constraints", "main.go"))
   754  	testProjPath := h.Path(testdir)
   755  
   756  	discardLogger := log.New(ioutil.Discard, "", 0)
   757  
   758  	ctx := &dep.Ctx{
   759  		GOPATH: testProjPath,
   760  		Out:    discardLogger,
   761  		Err:    discardLogger,
   762  	}
   763  
   764  	sm, err := ctx.SourceManager()
   765  	h.Must(err)
   766  	defer sm.Release()
   767  
   768  	// Create new project and set root. Setting root is required for PackageList
   769  	// to run properly.
   770  	p := new(dep.Project)
   771  	p.SetRoot(testProjPath)
   772  
   773  	for _, c := range cases {
   774  		t.Run(c.name, func(t *testing.T) {
   775  			p.Lock = &c.lock
   776  			p.Manifest = &c.manifest
   777  			gotConstraints, err := collectConstraints(ctx, p, sm)
   778  			if len(err) > 0 && !c.wantErr {
   779  				t.Fatalf("unexpected errors while collecting constraints: %v", err)
   780  			} else if len(err) == 0 && c.wantErr {
   781  				t.Fatalf("expected errors while collecting constraints, but got none")
   782  			}
   783  
   784  			if !reflect.DeepEqual(gotConstraints, c.wantConstraints) {
   785  				t.Fatalf("unexpected collected constraints: \n\t(GOT): %v\n\t(WNT): %v", gotConstraints, c.wantConstraints)
   786  			}
   787  		})
   788  	}
   789  }
   790  
   791  func TestValidateFlags(t *testing.T) {
   792  	testCases := []struct {
   793  		name    string
   794  		cmd     statusCommand
   795  		wantErr error
   796  	}{
   797  		{
   798  			name:    "no flags",
   799  			cmd:     statusCommand{},
   800  			wantErr: nil,
   801  		},
   802  		{
   803  			name:    "-dot only",
   804  			cmd:     statusCommand{dot: true},
   805  			wantErr: nil,
   806  		},
   807  		{
   808  			name:    "-dot with template",
   809  			cmd:     statusCommand{dot: true, template: "foo"},
   810  			wantErr: errors.New("cannot pass template string with -dot"),
   811  		},
   812  		{
   813  			name:    "-dot with -json",
   814  			cmd:     statusCommand{dot: true, json: true},
   815  			wantErr: errors.New("cannot pass multiple output format flags"),
   816  		},
   817  		{
   818  			name:    "-dot with operating mode",
   819  			cmd:     statusCommand{dot: true, old: true},
   820  			wantErr: errors.New("-dot generates dependency graph; cannot pass other flags"),
   821  		},
   822  		{
   823  			name:    "single operating mode",
   824  			cmd:     statusCommand{old: true},
   825  			wantErr: nil,
   826  		},
   827  		{
   828  			name:    "multiple operating modes",
   829  			cmd:     statusCommand{missing: true, old: true},
   830  			wantErr: errors.Wrapf(errors.New("cannot pass multiple operating mode flags"), "[-old -missing]"),
   831  		},
   832  		{
   833  			name:    "old with -dot",
   834  			cmd:     statusCommand{dot: true, old: true},
   835  			wantErr: errors.New("-dot generates dependency graph; cannot pass other flags"),
   836  		},
   837  		{
   838  			name:    "old with template",
   839  			cmd:     statusCommand{old: true, template: "foo"},
   840  			wantErr: nil,
   841  		},
   842  	}
   843  
   844  	for _, tc := range testCases {
   845  		t.Run(tc.name, func(t *testing.T) {
   846  			err := tc.cmd.validateFlags()
   847  
   848  			if err == nil {
   849  				if tc.wantErr != nil {
   850  					t.Errorf("unexpected error: \n\t(GOT): %v\n\t(WNT): %v", err, tc.wantErr)
   851  				}
   852  			} else if err.Error() != tc.wantErr.Error() {
   853  				t.Errorf("unexpected error: \n\t(GOT): %v\n\t(WNT): %v", err, tc.wantErr)
   854  			}
   855  		})
   856  	}
   857  }
   858  
   859  func execStatusTemplate(w io.Writer, format string, data interface{}) error {
   860  	tpl, err := parseStatusTemplate(format)
   861  	if err != nil {
   862  		return err
   863  	}
   864  	return tpl.Execute(w, data)
   865  }
   866  
   867  const expectedStatusDetail = `# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
   868  
   869  
   870  %s[solve-meta]
   871    analyzer-name = "dep"
   872    analyzer-version = 1
   873    input-imports = %s
   874    solver-name = "gps-cdcl"
   875    solver-version = 1
   876  `
   877  
   878  func TestStatusDetailTemplates(t *testing.T) {
   879  	expectedStatusMetadata := rawDetailMetadata{
   880  		AnalyzerName:    "dep",
   881  		AnalyzerVersion: 1,
   882  		SolverName:      "gps-cdcl",
   883  		SolverVersion:   1,
   884  	}
   885  	expectWithInputs := expectedStatusMetadata
   886  	expectWithInputs.InputImports = []string{"github.com/akutz/one", "github.com/akutz/three/a"}
   887  
   888  	testCases := []struct {
   889  		name string
   890  		tpl  string
   891  		exp  string
   892  		data rawDetail
   893  	}{
   894  		{
   895  			name: "Lock Template No Projects",
   896  			tpl:  statusLockTemplate,
   897  			exp:  fmt.Sprintf(expectedStatusDetail, "", tomlStrSplit(nil)),
   898  			data: rawDetail{
   899  				Metadata: expectedStatusMetadata,
   900  			},
   901  		},
   902  		{
   903  			name: "Lock Template",
   904  			tpl:  statusLockTemplate,
   905  			exp: fmt.Sprintf(expectedStatusDetail, `[[projects]]
   906    branch = "master"
   907    digest = "1:cbcdef1234"
   908    name = "github.com/akutz/one"
   909    packages = ["."]
   910    pruneopts = "UT"
   911    revision = "b78744579491c1ceeaaa3b40205e56b0591b93a3"
   912  
   913  [[projects]]
   914    digest = "1:dbcdef1234"
   915    name = "github.com/akutz/two"
   916    packages = [
   917      ".",
   918      "helloworld",
   919    ]
   920    pruneopts = "NUT"
   921    revision = "12bd96e66386c1960ab0f74ced1362f66f552f7b"
   922    version = "v1.0.0"
   923  
   924  [[projects]]
   925    branch = "feature/morning"
   926    digest = "1:abcdef1234"
   927    name = "github.com/akutz/three"
   928    packages = [
   929      "a",
   930      "b",
   931      "c",
   932    ]
   933    pruneopts = "NUT"
   934    revision = "890a5c3458b43e6104ff5da8dfa139d013d77544"
   935    source = "https://github.com/mandy/three"
   936  
   937  `, tomlStrSplit([]string{"github.com/akutz/one", "github.com/akutz/three/a"})),
   938  			data: rawDetail{
   939  				Projects: []rawDetailProject{
   940  					rawDetailProject{
   941  						Locked: rawDetailVersion{
   942  							Branch:   "master",
   943  							Revision: "b78744579491c1ceeaaa3b40205e56b0591b93a3",
   944  						},
   945  						Packages:    []string{"."},
   946  						ProjectRoot: "github.com/akutz/one",
   947  						PruneOpts:   "UT",
   948  						Digest:      "1:cbcdef1234",
   949  					},
   950  					rawDetailProject{
   951  						Locked: rawDetailVersion{
   952  							Revision: "12bd96e66386c1960ab0f74ced1362f66f552f7b",
   953  							Version:  "v1.0.0",
   954  						},
   955  						ProjectRoot: "github.com/akutz/two",
   956  						Packages: []string{
   957  							".",
   958  							"helloworld",
   959  						},
   960  						PruneOpts: "NUT",
   961  						Digest:    "1:dbcdef1234",
   962  					},
   963  					rawDetailProject{
   964  						Locked: rawDetailVersion{
   965  							Branch:   "feature/morning",
   966  							Revision: "890a5c3458b43e6104ff5da8dfa139d013d77544",
   967  						},
   968  						ProjectRoot: "github.com/akutz/three",
   969  						Packages: []string{
   970  							"a",
   971  							"b",
   972  							"c",
   973  						},
   974  						Source:    "https://github.com/mandy/three",
   975  						PruneOpts: "NUT",
   976  						Digest:    "1:abcdef1234",
   977  					},
   978  				},
   979  				Metadata: expectWithInputs,
   980  			},
   981  		},
   982  	}
   983  
   984  	for _, tc := range testCases {
   985  		t.Run(tc.name, func(t *testing.T) {
   986  			w := &bytes.Buffer{}
   987  			if err := execStatusTemplate(w, tc.tpl, tc.data); err != nil {
   988  				t.Error(err)
   989  			}
   990  			act := w.String()
   991  			if act != tc.exp {
   992  				t.Errorf(
   993  					"unexpected error: \n"+
   994  						"(GOT):\n=== BEGIN ===\n%v\n=== END ===\n"+
   995  						"(WNT):\n=== BEGIN ===\n%v\n=== END ===\n",
   996  					act,
   997  					tc.exp)
   998  			}
   999  		})
  1000  	}
  1001  }