github.com/eliastor/durgaform@v0.0.0-20220816172711-d0ab2d17673e/internal/providercache/installer_test.go (about)

     1  package providercache
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"log"
     7  	"net/http"
     8  	"net/http/httptest"
     9  	"path/filepath"
    10  	"strings"
    11  	"testing"
    12  
    13  	"github.com/apparentlymart/go-versions/versions"
    14  	"github.com/apparentlymart/go-versions/versions/constraints"
    15  	"github.com/davecgh/go-spew/spew"
    16  	"github.com/google/go-cmp/cmp"
    17  	svchost "github.com/hashicorp/terraform-svchost"
    18  	"github.com/hashicorp/terraform-svchost/disco"
    19  
    20  	"github.com/eliastor/durgaform/internal/addrs"
    21  	"github.com/eliastor/durgaform/internal/depsfile"
    22  	"github.com/eliastor/durgaform/internal/getproviders"
    23  )
    24  
    25  func TestEnsureProviderVersions(t *testing.T) {
    26  	// This is a sort of hybrid between table-driven and imperative-style
    27  	// testing, because the overall sequence of steps is the same for all
    28  	// of the test cases but the setup and verification have enough different
    29  	// permutations that it ends up being more concise to express them as
    30  	// normal code.
    31  	type Test struct {
    32  		Source     getproviders.Source
    33  		Prepare    func(*testing.T, *Installer, *Dir)
    34  		LockFile   string
    35  		Reqs       getproviders.Requirements
    36  		Mode       InstallMode
    37  		Check      func(*testing.T, *Dir, *depsfile.Locks)
    38  		WantErr    string
    39  		WantEvents func(*Installer, *Dir) map[addrs.Provider][]*testInstallerEventLogItem
    40  	}
    41  
    42  	// noProvider is just the zero value of addrs.Provider, which we're
    43  	// using in this test as the key for installer events that are not
    44  	// specific to a particular provider.
    45  	var noProvider addrs.Provider
    46  	beepProvider := addrs.MustParseProviderSourceString("example.com/foo/beep")
    47  	beepProviderDir := getproviders.PackageLocalDir("testdata/beep-provider")
    48  	fakePlatform := getproviders.Platform{OS: "bleep", Arch: "bloop"}
    49  	wrongPlatform := getproviders.Platform{OS: "wrong", Arch: "wrong"}
    50  	beepProviderHash := getproviders.HashScheme1.New("2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84=")
    51  	durgaformProvider := addrs.MustParseProviderSourceString("terraform.io/builtin/terraform")
    52  
    53  	tests := map[string]Test{
    54  		"no dependencies": {
    55  			Mode: InstallNewProvidersOnly,
    56  			Check: func(t *testing.T, dir *Dir, locks *depsfile.Locks) {
    57  				if allCached := dir.AllAvailablePackages(); len(allCached) != 0 {
    58  					t.Errorf("unexpected cache directory entries\n%s", spew.Sdump(allCached))
    59  				}
    60  				if allLocked := locks.AllProviders(); len(allLocked) != 0 {
    61  					t.Errorf("unexpected provider lock entries\n%s", spew.Sdump(allLocked))
    62  				}
    63  			},
    64  			WantEvents: func(*Installer, *Dir) map[addrs.Provider][]*testInstallerEventLogItem {
    65  				return map[addrs.Provider][]*testInstallerEventLogItem{
    66  					noProvider: {
    67  						{
    68  							Event: "PendingProviders",
    69  							Args:  map[addrs.Provider]getproviders.VersionConstraints(nil),
    70  						},
    71  					},
    72  				}
    73  			},
    74  		},
    75  		"successful initial install of one provider": {
    76  			Source: getproviders.NewMockSource(
    77  				[]getproviders.PackageMeta{
    78  					{
    79  						Provider:       beepProvider,
    80  						Version:        getproviders.MustParseVersion("1.0.0"),
    81  						TargetPlatform: fakePlatform,
    82  						Location:       beepProviderDir,
    83  					},
    84  					{
    85  						Provider:       beepProvider,
    86  						Version:        getproviders.MustParseVersion("2.0.0"),
    87  						TargetPlatform: fakePlatform,
    88  						Location:       beepProviderDir,
    89  					},
    90  					{
    91  						Provider:       beepProvider,
    92  						Version:        getproviders.MustParseVersion("2.1.0"),
    93  						TargetPlatform: fakePlatform,
    94  						Location:       beepProviderDir,
    95  					},
    96  				},
    97  				nil,
    98  			),
    99  			Mode: InstallNewProvidersOnly,
   100  			Reqs: getproviders.Requirements{
   101  				beepProvider: getproviders.MustParseVersionConstraints(">= 2.0.0"),
   102  			},
   103  			Check: func(t *testing.T, dir *Dir, locks *depsfile.Locks) {
   104  				if allCached := dir.AllAvailablePackages(); len(allCached) != 1 {
   105  					t.Errorf("wrong number of cache directory entries; want only one\n%s", spew.Sdump(allCached))
   106  				}
   107  				if allLocked := locks.AllProviders(); len(allLocked) != 1 {
   108  					t.Errorf("wrong number of provider lock entries; want only one\n%s", spew.Sdump(allLocked))
   109  				}
   110  
   111  				gotLock := locks.Provider(beepProvider)
   112  				wantLock := depsfile.NewProviderLock(
   113  					beepProvider,
   114  					getproviders.MustParseVersion("2.1.0"),
   115  					getproviders.MustParseVersionConstraints(">= 2.0.0"),
   116  					[]getproviders.Hash{beepProviderHash},
   117  				)
   118  				if diff := cmp.Diff(wantLock, gotLock, depsfile.ProviderLockComparer); diff != "" {
   119  					t.Errorf("wrong lock entry\n%s", diff)
   120  				}
   121  
   122  				gotEntry := dir.ProviderLatestVersion(beepProvider)
   123  				wantEntry := &CachedProvider{
   124  					Provider:   beepProvider,
   125  					Version:    getproviders.MustParseVersion("2.1.0"),
   126  					PackageDir: filepath.Join(dir.BasePath(), "example.com/foo/beep/2.1.0/bleep_bloop"),
   127  				}
   128  				if diff := cmp.Diff(wantEntry, gotEntry); diff != "" {
   129  					t.Errorf("wrong cache entry\n%s", diff)
   130  				}
   131  			},
   132  			WantEvents: func(inst *Installer, dir *Dir) map[addrs.Provider][]*testInstallerEventLogItem {
   133  				return map[addrs.Provider][]*testInstallerEventLogItem{
   134  					noProvider: {
   135  						{
   136  							Event: "PendingProviders",
   137  							Args: map[addrs.Provider]getproviders.VersionConstraints{
   138  								beepProvider: getproviders.MustParseVersionConstraints(">= 2.0.0"),
   139  							},
   140  						},
   141  						{
   142  							Event: "ProvidersFetched",
   143  							Args: map[addrs.Provider]*getproviders.PackageAuthenticationResult{
   144  								beepProvider: nil,
   145  							},
   146  						},
   147  					},
   148  					beepProvider: {
   149  						{
   150  							Event:    "QueryPackagesBegin",
   151  							Provider: beepProvider,
   152  							Args: struct {
   153  								Constraints string
   154  								Locked      bool
   155  							}{">= 2.0.0", false},
   156  						},
   157  						{
   158  							Event:    "QueryPackagesSuccess",
   159  							Provider: beepProvider,
   160  							Args:     "2.1.0",
   161  						},
   162  						{
   163  							Event:    "FetchPackageMeta",
   164  							Provider: beepProvider,
   165  							Args:     "2.1.0",
   166  						},
   167  						{
   168  							Event:    "FetchPackageBegin",
   169  							Provider: beepProvider,
   170  							Args: struct {
   171  								Version  string
   172  								Location getproviders.PackageLocation
   173  							}{"2.1.0", beepProviderDir},
   174  						},
   175  						{
   176  							Event:    "ProvidersLockUpdated",
   177  							Provider: beepProvider,
   178  							Args: struct {
   179  								Version string
   180  								Local   []getproviders.Hash
   181  								Signed  []getproviders.Hash
   182  								Prior   []getproviders.Hash
   183  							}{
   184  								"2.1.0",
   185  								[]getproviders.Hash{"h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84="},
   186  								nil,
   187  								nil,
   188  							},
   189  						},
   190  						{
   191  							Event:    "FetchPackageSuccess",
   192  							Provider: beepProvider,
   193  							Args: struct {
   194  								Version    string
   195  								LocalDir   string
   196  								AuthResult string
   197  							}{
   198  								"2.1.0",
   199  								filepath.Join(dir.BasePath(), "example.com/foo/beep/2.1.0/bleep_bloop"),
   200  								"unauthenticated",
   201  							},
   202  						},
   203  					},
   204  				}
   205  			},
   206  		},
   207  		"successful initial install of one provider through a cold global cache": {
   208  			Source: getproviders.NewMockSource(
   209  				[]getproviders.PackageMeta{
   210  					{
   211  						Provider:       beepProvider,
   212  						Version:        getproviders.MustParseVersion("2.0.0"),
   213  						TargetPlatform: fakePlatform,
   214  						Location:       beepProviderDir,
   215  					},
   216  					{
   217  						Provider:       beepProvider,
   218  						Version:        getproviders.MustParseVersion("2.1.0"),
   219  						TargetPlatform: fakePlatform,
   220  						Location:       beepProviderDir,
   221  					},
   222  				},
   223  				nil,
   224  			),
   225  			Prepare: func(t *testing.T, inst *Installer, dir *Dir) {
   226  				globalCacheDirPath := tmpDir(t)
   227  				globalCacheDir := NewDirWithPlatform(globalCacheDirPath, fakePlatform)
   228  				inst.SetGlobalCacheDir(globalCacheDir)
   229  			},
   230  			Mode: InstallNewProvidersOnly,
   231  			Reqs: getproviders.Requirements{
   232  				beepProvider: getproviders.MustParseVersionConstraints(">= 2.0.0"),
   233  			},
   234  			Check: func(t *testing.T, dir *Dir, locks *depsfile.Locks) {
   235  				if allCached := dir.AllAvailablePackages(); len(allCached) != 1 {
   236  					t.Errorf("wrong number of cache directory entries; want only one\n%s", spew.Sdump(allCached))
   237  				}
   238  				if allLocked := locks.AllProviders(); len(allLocked) != 1 {
   239  					t.Errorf("wrong number of provider lock entries; want only one\n%s", spew.Sdump(allLocked))
   240  				}
   241  
   242  				gotLock := locks.Provider(beepProvider)
   243  				wantLock := depsfile.NewProviderLock(
   244  					beepProvider,
   245  					getproviders.MustParseVersion("2.1.0"),
   246  					getproviders.MustParseVersionConstraints(">= 2.0.0"),
   247  					[]getproviders.Hash{beepProviderHash},
   248  				)
   249  				if diff := cmp.Diff(wantLock, gotLock, depsfile.ProviderLockComparer); diff != "" {
   250  					t.Errorf("wrong lock entry\n%s", diff)
   251  				}
   252  
   253  				gotEntry := dir.ProviderLatestVersion(beepProvider)
   254  				wantEntry := &CachedProvider{
   255  					Provider:   beepProvider,
   256  					Version:    getproviders.MustParseVersion("2.1.0"),
   257  					PackageDir: filepath.Join(dir.BasePath(), "example.com/foo/beep/2.1.0/bleep_bloop"),
   258  				}
   259  				if diff := cmp.Diff(wantEntry, gotEntry); diff != "" {
   260  					t.Errorf("wrong cache entry\n%s", diff)
   261  				}
   262  			},
   263  			WantEvents: func(inst *Installer, dir *Dir) map[addrs.Provider][]*testInstallerEventLogItem {
   264  				return map[addrs.Provider][]*testInstallerEventLogItem{
   265  					noProvider: {
   266  						{
   267  							Event: "PendingProviders",
   268  							Args: map[addrs.Provider]getproviders.VersionConstraints{
   269  								beepProvider: getproviders.MustParseVersionConstraints(">= 2.0.0"),
   270  							},
   271  						},
   272  						{
   273  							Event: "ProvidersFetched",
   274  							Args: map[addrs.Provider]*getproviders.PackageAuthenticationResult{
   275  								beepProvider: nil,
   276  							},
   277  						},
   278  					},
   279  					beepProvider: {
   280  						{
   281  							Event:    "QueryPackagesBegin",
   282  							Provider: beepProvider,
   283  							Args: struct {
   284  								Constraints string
   285  								Locked      bool
   286  							}{">= 2.0.0", false},
   287  						},
   288  						{
   289  							Event:    "QueryPackagesSuccess",
   290  							Provider: beepProvider,
   291  							Args:     "2.1.0",
   292  						},
   293  						{
   294  							Event:    "FetchPackageMeta",
   295  							Provider: beepProvider,
   296  							Args:     "2.1.0",
   297  						},
   298  						{
   299  							Event:    "FetchPackageBegin",
   300  							Provider: beepProvider,
   301  							Args: struct {
   302  								Version  string
   303  								Location getproviders.PackageLocation
   304  							}{"2.1.0", beepProviderDir},
   305  						},
   306  						{
   307  							Event:    "ProvidersLockUpdated",
   308  							Provider: beepProvider,
   309  							Args: struct {
   310  								Version string
   311  								Local   []getproviders.Hash
   312  								Signed  []getproviders.Hash
   313  								Prior   []getproviders.Hash
   314  							}{
   315  								"2.1.0",
   316  								[]getproviders.Hash{"h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84="},
   317  								nil,
   318  								nil,
   319  							},
   320  						},
   321  						{
   322  							Event:    "FetchPackageSuccess",
   323  							Provider: beepProvider,
   324  							Args: struct {
   325  								Version    string
   326  								LocalDir   string
   327  								AuthResult string
   328  							}{
   329  								"2.1.0",
   330  								// NOTE: With global cache enabled, the fetch
   331  								// goes into the global cache dir and
   332  								// we then to it from the local cache dir.
   333  								filepath.Join(inst.globalCacheDir.BasePath(), "example.com/foo/beep/2.1.0/bleep_bloop"),
   334  								"unauthenticated",
   335  							},
   336  						},
   337  					},
   338  				}
   339  			},
   340  		},
   341  		"successful initial install of one provider through a warm global cache": {
   342  			Source: getproviders.NewMockSource(
   343  				[]getproviders.PackageMeta{
   344  					{
   345  						Provider:       beepProvider,
   346  						Version:        getproviders.MustParseVersion("2.0.0"),
   347  						TargetPlatform: fakePlatform,
   348  						Location:       beepProviderDir,
   349  					},
   350  					{
   351  						Provider:       beepProvider,
   352  						Version:        getproviders.MustParseVersion("2.1.0"),
   353  						TargetPlatform: fakePlatform,
   354  						Location:       beepProviderDir,
   355  					},
   356  				},
   357  				nil,
   358  			),
   359  			Prepare: func(t *testing.T, inst *Installer, dir *Dir) {
   360  				globalCacheDirPath := tmpDir(t)
   361  				globalCacheDir := NewDirWithPlatform(globalCacheDirPath, fakePlatform)
   362  				_, err := globalCacheDir.InstallPackage(
   363  					context.Background(),
   364  					getproviders.PackageMeta{
   365  						Provider:       beepProvider,
   366  						Version:        getproviders.MustParseVersion("2.1.0"),
   367  						TargetPlatform: fakePlatform,
   368  						Location:       beepProviderDir,
   369  					},
   370  					nil,
   371  				)
   372  				if err != nil {
   373  					t.Fatalf("failed to populate global cache: %s", err)
   374  				}
   375  				inst.SetGlobalCacheDir(globalCacheDir)
   376  			},
   377  			Mode: InstallNewProvidersOnly,
   378  			Reqs: getproviders.Requirements{
   379  				beepProvider: getproviders.MustParseVersionConstraints(">= 2.0.0"),
   380  			},
   381  			Check: func(t *testing.T, dir *Dir, locks *depsfile.Locks) {
   382  				if allCached := dir.AllAvailablePackages(); len(allCached) != 1 {
   383  					t.Errorf("wrong number of cache directory entries; want only one\n%s", spew.Sdump(allCached))
   384  				}
   385  				if allLocked := locks.AllProviders(); len(allLocked) != 1 {
   386  					t.Errorf("wrong number of provider lock entries; want only one\n%s", spew.Sdump(allLocked))
   387  				}
   388  
   389  				gotLock := locks.Provider(beepProvider)
   390  				wantLock := depsfile.NewProviderLock(
   391  					beepProvider,
   392  					getproviders.MustParseVersion("2.1.0"),
   393  					getproviders.MustParseVersionConstraints(">= 2.0.0"),
   394  					[]getproviders.Hash{beepProviderHash},
   395  				)
   396  				if diff := cmp.Diff(wantLock, gotLock, depsfile.ProviderLockComparer); diff != "" {
   397  					t.Errorf("wrong lock entry\n%s", diff)
   398  				}
   399  
   400  				gotEntry := dir.ProviderLatestVersion(beepProvider)
   401  				wantEntry := &CachedProvider{
   402  					Provider:   beepProvider,
   403  					Version:    getproviders.MustParseVersion("2.1.0"),
   404  					PackageDir: filepath.Join(dir.BasePath(), "example.com/foo/beep/2.1.0/bleep_bloop"),
   405  				}
   406  				if diff := cmp.Diff(wantEntry, gotEntry); diff != "" {
   407  					t.Errorf("wrong cache entry\n%s", diff)
   408  				}
   409  			},
   410  			WantEvents: func(inst *Installer, dir *Dir) map[addrs.Provider][]*testInstallerEventLogItem {
   411  				return map[addrs.Provider][]*testInstallerEventLogItem{
   412  					noProvider: {
   413  						{
   414  							Event: "PendingProviders",
   415  							Args: map[addrs.Provider]getproviders.VersionConstraints{
   416  								beepProvider: getproviders.MustParseVersionConstraints(">= 2.0.0"),
   417  							},
   418  						},
   419  					},
   420  					beepProvider: {
   421  						{
   422  							Event:    "QueryPackagesBegin",
   423  							Provider: beepProvider,
   424  							Args: struct {
   425  								Constraints string
   426  								Locked      bool
   427  							}{">= 2.0.0", false},
   428  						},
   429  						{
   430  							Event:    "QueryPackagesSuccess",
   431  							Provider: beepProvider,
   432  							Args:     "2.1.0",
   433  						},
   434  						{
   435  							Event:    "LinkFromCacheBegin",
   436  							Provider: beepProvider,
   437  							Args: struct {
   438  								Version   string
   439  								CacheRoot string
   440  							}{
   441  								"2.1.0",
   442  								inst.globalCacheDir.BasePath(),
   443  							},
   444  						},
   445  						{
   446  							Event:    "ProvidersLockUpdated",
   447  							Provider: beepProvider,
   448  							Args: struct {
   449  								Version string
   450  								Local   []getproviders.Hash
   451  								Signed  []getproviders.Hash
   452  								Prior   []getproviders.Hash
   453  							}{
   454  								"2.1.0",
   455  								[]getproviders.Hash{"h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84="},
   456  								nil,
   457  								nil,
   458  							},
   459  						},
   460  						{
   461  							Event:    "LinkFromCacheSuccess",
   462  							Provider: beepProvider,
   463  							Args: struct {
   464  								Version  string
   465  								LocalDir string
   466  							}{
   467  								"2.1.0",
   468  								filepath.Join(dir.BasePath(), "/example.com/foo/beep/2.1.0/bleep_bloop"),
   469  							},
   470  						},
   471  					},
   472  				}
   473  			},
   474  		},
   475  		"successful reinstall of one previously-locked provider": {
   476  			Source: getproviders.NewMockSource(
   477  				[]getproviders.PackageMeta{
   478  					{
   479  						Provider:       beepProvider,
   480  						Version:        getproviders.MustParseVersion("1.0.0"),
   481  						TargetPlatform: fakePlatform,
   482  						Location:       beepProviderDir,
   483  					},
   484  					{
   485  						Provider:       beepProvider,
   486  						Version:        getproviders.MustParseVersion("2.0.0"),
   487  						TargetPlatform: fakePlatform,
   488  						Location:       beepProviderDir,
   489  					},
   490  					{
   491  						Provider:       beepProvider,
   492  						Version:        getproviders.MustParseVersion("2.1.0"),
   493  						TargetPlatform: fakePlatform,
   494  						Location:       beepProviderDir,
   495  					},
   496  				},
   497  				nil,
   498  			),
   499  			LockFile: `
   500  				provider "example.com/foo/beep" {
   501  					version     = "2.0.0"
   502  					constraints = ">= 2.0.0"
   503  					hashes = [
   504  						"h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84=",
   505  					]
   506  				}
   507  			`,
   508  			Mode: InstallNewProvidersOnly,
   509  			Reqs: getproviders.Requirements{
   510  				beepProvider: getproviders.MustParseVersionConstraints(">= 2.0.0"),
   511  			},
   512  			Check: func(t *testing.T, dir *Dir, locks *depsfile.Locks) {
   513  				if allCached := dir.AllAvailablePackages(); len(allCached) != 1 {
   514  					t.Errorf("wrong number of cache directory entries; want only one\n%s", spew.Sdump(allCached))
   515  				}
   516  				if allLocked := locks.AllProviders(); len(allLocked) != 1 {
   517  					t.Errorf("wrong number of provider lock entries; want only one\n%s", spew.Sdump(allLocked))
   518  				}
   519  
   520  				gotLock := locks.Provider(beepProvider)
   521  				wantLock := depsfile.NewProviderLock(
   522  					beepProvider,
   523  					getproviders.MustParseVersion("2.0.0"),
   524  					getproviders.MustParseVersionConstraints(">= 2.0.0"),
   525  					[]getproviders.Hash{"h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84="},
   526  				)
   527  				if diff := cmp.Diff(wantLock, gotLock, depsfile.ProviderLockComparer); diff != "" {
   528  					t.Errorf("wrong lock entry\n%s", diff)
   529  				}
   530  
   531  				gotEntry := dir.ProviderLatestVersion(beepProvider)
   532  				wantEntry := &CachedProvider{
   533  					Provider:   beepProvider,
   534  					Version:    getproviders.MustParseVersion("2.0.0"),
   535  					PackageDir: filepath.Join(dir.BasePath(), "example.com/foo/beep/2.0.0/bleep_bloop"),
   536  				}
   537  				if diff := cmp.Diff(wantEntry, gotEntry); diff != "" {
   538  					t.Errorf("wrong cache entry\n%s", diff)
   539  				}
   540  			},
   541  			WantEvents: func(inst *Installer, dir *Dir) map[addrs.Provider][]*testInstallerEventLogItem {
   542  				return map[addrs.Provider][]*testInstallerEventLogItem{
   543  					noProvider: {
   544  						{
   545  							Event: "PendingProviders",
   546  							Args: map[addrs.Provider]getproviders.VersionConstraints{
   547  								beepProvider: getproviders.MustParseVersionConstraints(">= 2.0.0"),
   548  							},
   549  						},
   550  						{
   551  							Event: "ProvidersFetched",
   552  							Args: map[addrs.Provider]*getproviders.PackageAuthenticationResult{
   553  								beepProvider: nil,
   554  							},
   555  						},
   556  					},
   557  					beepProvider: {
   558  						{
   559  							Event:    "QueryPackagesBegin",
   560  							Provider: beepProvider,
   561  							Args: struct {
   562  								Constraints string
   563  								Locked      bool
   564  							}{">= 2.0.0", true},
   565  						},
   566  						{
   567  							Event:    "QueryPackagesSuccess",
   568  							Provider: beepProvider,
   569  							Args:     "2.0.0",
   570  						},
   571  						{
   572  							Event:    "FetchPackageMeta",
   573  							Provider: beepProvider,
   574  							Args:     "2.0.0",
   575  						},
   576  						{
   577  							Event:    "FetchPackageBegin",
   578  							Provider: beepProvider,
   579  							Args: struct {
   580  								Version  string
   581  								Location getproviders.PackageLocation
   582  							}{"2.0.0", beepProviderDir},
   583  						},
   584  						{
   585  							Event:    "ProvidersLockUpdated",
   586  							Provider: beepProvider,
   587  							Args: struct {
   588  								Version string
   589  								Local   []getproviders.Hash
   590  								Signed  []getproviders.Hash
   591  								Prior   []getproviders.Hash
   592  							}{
   593  								"2.0.0",
   594  								[]getproviders.Hash{"h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84="},
   595  								nil,
   596  								[]getproviders.Hash{"h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84="},
   597  							},
   598  						},
   599  						{
   600  							Event:    "FetchPackageSuccess",
   601  							Provider: beepProvider,
   602  							Args: struct {
   603  								Version    string
   604  								LocalDir   string
   605  								AuthResult string
   606  							}{
   607  								"2.0.0",
   608  								filepath.Join(dir.BasePath(), "example.com/foo/beep/2.0.0/bleep_bloop"),
   609  								"unauthenticated",
   610  							},
   611  						},
   612  					},
   613  				}
   614  			},
   615  		},
   616  		"skipped install of one previously-locked and installed provider": {
   617  			Source: getproviders.NewMockSource(
   618  				[]getproviders.PackageMeta{
   619  					{
   620  						Provider:       beepProvider,
   621  						Version:        getproviders.MustParseVersion("2.0.0"),
   622  						TargetPlatform: fakePlatform,
   623  						Location:       beepProviderDir,
   624  					},
   625  				},
   626  				nil,
   627  			),
   628  			LockFile: `
   629  				provider "example.com/foo/beep" {
   630  					version     = "2.0.0"
   631  					constraints = ">= 2.0.0"
   632  					hashes = [
   633  						"h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84=",
   634  					]
   635  				}
   636  			`,
   637  			Prepare: func(t *testing.T, inst *Installer, dir *Dir) {
   638  				_, err := dir.InstallPackage(
   639  					context.Background(),
   640  					getproviders.PackageMeta{
   641  						Provider:       beepProvider,
   642  						Version:        getproviders.MustParseVersion("2.0.0"),
   643  						TargetPlatform: fakePlatform,
   644  						Location:       beepProviderDir,
   645  					},
   646  					nil,
   647  				)
   648  				if err != nil {
   649  					t.Fatalf("installation to the test dir failed: %s", err)
   650  				}
   651  			},
   652  			Mode: InstallNewProvidersOnly,
   653  			Reqs: getproviders.Requirements{
   654  				beepProvider: getproviders.MustParseVersionConstraints(">= 2.0.0"),
   655  			},
   656  			Check: func(t *testing.T, dir *Dir, locks *depsfile.Locks) {
   657  				if allCached := dir.AllAvailablePackages(); len(allCached) != 1 {
   658  					t.Errorf("wrong number of cache directory entries; want only one\n%s", spew.Sdump(allCached))
   659  				}
   660  				if allLocked := locks.AllProviders(); len(allLocked) != 1 {
   661  					t.Errorf("wrong number of provider lock entries; want only one\n%s", spew.Sdump(allLocked))
   662  				}
   663  
   664  				gotLock := locks.Provider(beepProvider)
   665  				wantLock := depsfile.NewProviderLock(
   666  					beepProvider,
   667  					getproviders.MustParseVersion("2.0.0"),
   668  					getproviders.MustParseVersionConstraints(">= 2.0.0"),
   669  					[]getproviders.Hash{"h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84="},
   670  				)
   671  				if diff := cmp.Diff(wantLock, gotLock, depsfile.ProviderLockComparer); diff != "" {
   672  					t.Errorf("wrong lock entry\n%s", diff)
   673  				}
   674  
   675  				gotEntry := dir.ProviderLatestVersion(beepProvider)
   676  				wantEntry := &CachedProvider{
   677  					Provider:   beepProvider,
   678  					Version:    getproviders.MustParseVersion("2.0.0"),
   679  					PackageDir: filepath.Join(dir.BasePath(), "example.com/foo/beep/2.0.0/bleep_bloop"),
   680  				}
   681  				if diff := cmp.Diff(wantEntry, gotEntry); diff != "" {
   682  					t.Errorf("wrong cache entry\n%s", diff)
   683  				}
   684  			},
   685  			WantEvents: func(inst *Installer, dir *Dir) map[addrs.Provider][]*testInstallerEventLogItem {
   686  				return map[addrs.Provider][]*testInstallerEventLogItem{
   687  					noProvider: {
   688  						{
   689  							Event: "PendingProviders",
   690  							Args: map[addrs.Provider]getproviders.VersionConstraints{
   691  								beepProvider: getproviders.MustParseVersionConstraints(">= 2.0.0"),
   692  							},
   693  						},
   694  					},
   695  					beepProvider: {
   696  						{
   697  							Event:    "QueryPackagesBegin",
   698  							Provider: beepProvider,
   699  							Args: struct {
   700  								Constraints string
   701  								Locked      bool
   702  							}{">= 2.0.0", true},
   703  						},
   704  						{
   705  							Event:    "QueryPackagesSuccess",
   706  							Provider: beepProvider,
   707  							Args:     "2.0.0",
   708  						},
   709  						{
   710  							Event:    "ProviderAlreadyInstalled",
   711  							Provider: beepProvider,
   712  							Args:     versions.Version{Major: 2, Minor: 0, Patch: 0},
   713  						},
   714  					},
   715  				}
   716  			},
   717  		},
   718  		"successful upgrade of one previously-locked provider": {
   719  			Source: getproviders.NewMockSource(
   720  				[]getproviders.PackageMeta{
   721  					{
   722  						Provider:       beepProvider,
   723  						Version:        getproviders.MustParseVersion("1.0.0"),
   724  						TargetPlatform: fakePlatform,
   725  						Location:       beepProviderDir,
   726  					},
   727  					{
   728  						Provider:       beepProvider,
   729  						Version:        getproviders.MustParseVersion("2.0.0"),
   730  						TargetPlatform: fakePlatform,
   731  						Location:       beepProviderDir,
   732  					},
   733  					{
   734  						Provider:       beepProvider,
   735  						Version:        getproviders.MustParseVersion("2.1.0"),
   736  						TargetPlatform: fakePlatform,
   737  						Location:       beepProviderDir,
   738  					},
   739  				},
   740  				nil,
   741  			),
   742  			LockFile: `
   743  				provider "example.com/foo/beep" {
   744  					version     = "2.0.0"
   745  					constraints = ">= 2.0.0"
   746  					hashes = [
   747  						"h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84=",
   748  					]
   749  				}
   750  			`,
   751  			Mode: InstallUpgrades,
   752  			Reqs: getproviders.Requirements{
   753  				beepProvider: getproviders.MustParseVersionConstraints(">= 2.0.0"),
   754  			},
   755  			Check: func(t *testing.T, dir *Dir, locks *depsfile.Locks) {
   756  				if allCached := dir.AllAvailablePackages(); len(allCached) != 1 {
   757  					t.Errorf("wrong number of cache directory entries; want only one\n%s", spew.Sdump(allCached))
   758  				}
   759  				if allLocked := locks.AllProviders(); len(allLocked) != 1 {
   760  					t.Errorf("wrong number of provider lock entries; want only one\n%s", spew.Sdump(allLocked))
   761  				}
   762  
   763  				gotLock := locks.Provider(beepProvider)
   764  				wantLock := depsfile.NewProviderLock(
   765  					beepProvider,
   766  					getproviders.MustParseVersion("2.1.0"),
   767  					getproviders.MustParseVersionConstraints(">= 2.0.0"),
   768  					[]getproviders.Hash{"h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84="},
   769  				)
   770  				if diff := cmp.Diff(wantLock, gotLock, depsfile.ProviderLockComparer); diff != "" {
   771  					t.Errorf("wrong lock entry\n%s", diff)
   772  				}
   773  
   774  				gotEntry := dir.ProviderLatestVersion(beepProvider)
   775  				wantEntry := &CachedProvider{
   776  					Provider:   beepProvider,
   777  					Version:    getproviders.MustParseVersion("2.1.0"),
   778  					PackageDir: filepath.Join(dir.BasePath(), "example.com/foo/beep/2.1.0/bleep_bloop"),
   779  				}
   780  				if diff := cmp.Diff(wantEntry, gotEntry); diff != "" {
   781  					t.Errorf("wrong cache entry\n%s", diff)
   782  				}
   783  			},
   784  			WantEvents: func(inst *Installer, dir *Dir) map[addrs.Provider][]*testInstallerEventLogItem {
   785  				return map[addrs.Provider][]*testInstallerEventLogItem{
   786  					noProvider: {
   787  						{
   788  							Event: "PendingProviders",
   789  							Args: map[addrs.Provider]getproviders.VersionConstraints{
   790  								beepProvider: getproviders.MustParseVersionConstraints(">= 2.0.0"),
   791  							},
   792  						},
   793  						{
   794  							Event: "ProvidersFetched",
   795  							Args: map[addrs.Provider]*getproviders.PackageAuthenticationResult{
   796  								beepProvider: nil,
   797  							},
   798  						},
   799  					},
   800  					beepProvider: {
   801  						{
   802  							Event:    "QueryPackagesBegin",
   803  							Provider: beepProvider,
   804  							Args: struct {
   805  								Constraints string
   806  								Locked      bool
   807  							}{">= 2.0.0", false},
   808  						},
   809  						{
   810  							Event:    "QueryPackagesSuccess",
   811  							Provider: beepProvider,
   812  							Args:     "2.1.0",
   813  						},
   814  						{
   815  							Event:    "FetchPackageMeta",
   816  							Provider: beepProvider,
   817  							Args:     "2.1.0",
   818  						},
   819  						{
   820  							Event:    "FetchPackageBegin",
   821  							Provider: beepProvider,
   822  							Args: struct {
   823  								Version  string
   824  								Location getproviders.PackageLocation
   825  							}{"2.1.0", beepProviderDir},
   826  						},
   827  						{
   828  							Event:    "ProvidersLockUpdated",
   829  							Provider: beepProvider,
   830  							Args: struct {
   831  								Version string
   832  								Local   []getproviders.Hash
   833  								Signed  []getproviders.Hash
   834  								Prior   []getproviders.Hash
   835  							}{
   836  								"2.1.0",
   837  								[]getproviders.Hash{"h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84="},
   838  								nil,
   839  								nil,
   840  							},
   841  						},
   842  						{
   843  							Event:    "FetchPackageSuccess",
   844  							Provider: beepProvider,
   845  							Args: struct {
   846  								Version    string
   847  								LocalDir   string
   848  								AuthResult string
   849  							}{
   850  								"2.1.0",
   851  								filepath.Join(dir.BasePath(), "example.com/foo/beep/2.1.0/bleep_bloop"),
   852  								"unauthenticated",
   853  							},
   854  						},
   855  					},
   856  				}
   857  			},
   858  		},
   859  		"successful install of a built-in provider": {
   860  			Source: getproviders.NewMockSource(
   861  				[]getproviders.PackageMeta{},
   862  				nil,
   863  			),
   864  			Prepare: func(t *testing.T, inst *Installer, dir *Dir) {
   865  				inst.SetBuiltInProviderTypes([]string{"durgaform"})
   866  			},
   867  			Mode: InstallNewProvidersOnly,
   868  			Reqs: getproviders.Requirements{
   869  				durgaformProvider: nil,
   870  			},
   871  			Check: func(t *testing.T, dir *Dir, locks *depsfile.Locks) {
   872  				// Built-in providers are neither included in the cache
   873  				// directory nor mentioned in the lock file, because they
   874  				// are compiled directly into the Durgaform executable.
   875  				if allCached := dir.AllAvailablePackages(); len(allCached) != 0 {
   876  					t.Errorf("wrong number of cache directory entries; want none\n%s", spew.Sdump(allCached))
   877  				}
   878  				if allLocked := locks.AllProviders(); len(allLocked) != 0 {
   879  					t.Errorf("wrong number of provider lock entries; want none\n%s", spew.Sdump(allLocked))
   880  				}
   881  			},
   882  			WantEvents: func(inst *Installer, dir *Dir) map[addrs.Provider][]*testInstallerEventLogItem {
   883  				return map[addrs.Provider][]*testInstallerEventLogItem{
   884  					noProvider: {
   885  						{
   886  							Event: "PendingProviders",
   887  							Args: map[addrs.Provider]getproviders.VersionConstraints{
   888  								durgaformProvider: constraints.IntersectionSpec(nil),
   889  							},
   890  						},
   891  					},
   892  					durgaformProvider: {
   893  						{
   894  							Event:    "BuiltInProviderAvailable",
   895  							Provider: durgaformProvider,
   896  						},
   897  					},
   898  				}
   899  			},
   900  		},
   901  		"remove no-longer-needed provider from lock file": {
   902  			Source: getproviders.NewMockSource(
   903  				[]getproviders.PackageMeta{
   904  					{
   905  						Provider:       beepProvider,
   906  						Version:        getproviders.MustParseVersion("1.0.0"),
   907  						TargetPlatform: fakePlatform,
   908  						Location:       beepProviderDir,
   909  					},
   910  				},
   911  				nil,
   912  			),
   913  			LockFile: `
   914  				provider "example.com/foo/beep" {
   915  					version     = "1.0.0"
   916  					constraints = ">= 1.0.0"
   917  					hashes = [
   918  						"h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84=",
   919  					]
   920  				}
   921  				provider "example.com/foo/obsolete" {
   922  					version     = "2.0.0"
   923  					constraints = ">= 2.0.0"
   924  					hashes = [
   925  						"no:irrelevant",
   926  					]
   927  				}
   928  			`,
   929  			Mode: InstallNewProvidersOnly,
   930  			Reqs: getproviders.Requirements{
   931  				beepProvider: getproviders.MustParseVersionConstraints(">= 1.0.0"),
   932  			},
   933  			Check: func(t *testing.T, dir *Dir, locks *depsfile.Locks) {
   934  				if allCached := dir.AllAvailablePackages(); len(allCached) != 1 {
   935  					t.Errorf("wrong number of cache directory entries; want only one\n%s", spew.Sdump(allCached))
   936  				}
   937  				if allLocked := locks.AllProviders(); len(allLocked) != 1 {
   938  					t.Errorf("wrong number of provider lock entries; want only one\n%s", spew.Sdump(allLocked))
   939  				}
   940  
   941  				gotLock := locks.Provider(beepProvider)
   942  				wantLock := depsfile.NewProviderLock(
   943  					beepProvider,
   944  					getproviders.MustParseVersion("1.0.0"),
   945  					getproviders.MustParseVersionConstraints(">= 1.0.0"),
   946  					[]getproviders.Hash{"h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84="},
   947  				)
   948  				if diff := cmp.Diff(wantLock, gotLock, depsfile.ProviderLockComparer); diff != "" {
   949  					t.Errorf("wrong lock entry\n%s", diff)
   950  				}
   951  
   952  				gotEntry := dir.ProviderLatestVersion(beepProvider)
   953  				wantEntry := &CachedProvider{
   954  					Provider:   beepProvider,
   955  					Version:    getproviders.MustParseVersion("1.0.0"),
   956  					PackageDir: filepath.Join(dir.BasePath(), "example.com/foo/beep/1.0.0/bleep_bloop"),
   957  				}
   958  				if diff := cmp.Diff(wantEntry, gotEntry); diff != "" {
   959  					t.Errorf("wrong cache entry\n%s", diff)
   960  				}
   961  			},
   962  			WantEvents: func(inst *Installer, dir *Dir) map[addrs.Provider][]*testInstallerEventLogItem {
   963  				return map[addrs.Provider][]*testInstallerEventLogItem{
   964  					noProvider: {
   965  						{
   966  							Event: "PendingProviders",
   967  							Args: map[addrs.Provider]getproviders.VersionConstraints{
   968  								beepProvider: getproviders.MustParseVersionConstraints(">= 1.0.0"),
   969  							},
   970  						},
   971  						{
   972  							Event: "ProvidersFetched",
   973  							Args: map[addrs.Provider]*getproviders.PackageAuthenticationResult{
   974  								beepProvider: nil,
   975  							},
   976  						},
   977  					},
   978  					// Note: intentionally no entries for example.com/foo/obsolete
   979  					// here, because it's no longer needed and therefore not
   980  					// installed.
   981  					beepProvider: {
   982  						{
   983  							Event:    "QueryPackagesBegin",
   984  							Provider: beepProvider,
   985  							Args: struct {
   986  								Constraints string
   987  								Locked      bool
   988  							}{">= 1.0.0", true},
   989  						},
   990  						{
   991  							Event:    "QueryPackagesSuccess",
   992  							Provider: beepProvider,
   993  							Args:     "1.0.0",
   994  						},
   995  						{
   996  							Event:    "FetchPackageMeta",
   997  							Provider: beepProvider,
   998  							Args:     "1.0.0",
   999  						},
  1000  						{
  1001  							Event:    "FetchPackageBegin",
  1002  							Provider: beepProvider,
  1003  							Args: struct {
  1004  								Version  string
  1005  								Location getproviders.PackageLocation
  1006  							}{"1.0.0", beepProviderDir},
  1007  						},
  1008  						{
  1009  							Event:    "ProvidersLockUpdated",
  1010  							Provider: beepProvider,
  1011  							Args: struct {
  1012  								Version string
  1013  								Local   []getproviders.Hash
  1014  								Signed  []getproviders.Hash
  1015  								Prior   []getproviders.Hash
  1016  							}{
  1017  								"1.0.0",
  1018  								[]getproviders.Hash{"h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84="},
  1019  								nil,
  1020  								[]getproviders.Hash{"h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84="},
  1021  							},
  1022  						},
  1023  						{
  1024  							Event:    "FetchPackageSuccess",
  1025  							Provider: beepProvider,
  1026  							Args: struct {
  1027  								Version    string
  1028  								LocalDir   string
  1029  								AuthResult string
  1030  							}{
  1031  								"1.0.0",
  1032  								filepath.Join(dir.BasePath(), "example.com/foo/beep/1.0.0/bleep_bloop"),
  1033  								"unauthenticated",
  1034  							},
  1035  						},
  1036  					},
  1037  				}
  1038  			},
  1039  		},
  1040  		"failed install of a non-existing built-in provider": {
  1041  			Source: getproviders.NewMockSource(
  1042  				[]getproviders.PackageMeta{},
  1043  				nil,
  1044  			),
  1045  			Prepare: func(t *testing.T, inst *Installer, dir *Dir) {
  1046  				// NOTE: We're intentionally not calling
  1047  				// inst.SetBuiltInProviderTypes to make the "durgaform"
  1048  				// built-in provider available here, so requests for it
  1049  				// should fail.
  1050  			},
  1051  			Mode: InstallNewProvidersOnly,
  1052  			Reqs: getproviders.Requirements{
  1053  				durgaformProvider: nil,
  1054  			},
  1055  			WantErr: `some providers could not be installed:
  1056  - durgaform.io/builtin/terraform: this Durgaform release has no built-in provider named "terraform"`,
  1057  			WantEvents: func(inst *Installer, dir *Dir) map[addrs.Provider][]*testInstallerEventLogItem {
  1058  				return map[addrs.Provider][]*testInstallerEventLogItem{
  1059  					noProvider: {
  1060  						{
  1061  							Event: "PendingProviders",
  1062  							Args: map[addrs.Provider]getproviders.VersionConstraints{
  1063  								durgaformProvider: constraints.IntersectionSpec(nil),
  1064  							},
  1065  						},
  1066  					},
  1067  					durgaformProvider: {
  1068  						{
  1069  							Event:    "BuiltInProviderFailure",
  1070  							Provider: durgaformProvider,
  1071  							Args:     `this Durgaform release has no built-in provider named "durgaform"`,
  1072  						},
  1073  					},
  1074  				}
  1075  			},
  1076  		},
  1077  		"failed install when a built-in provider has a version constraint": {
  1078  			Source: getproviders.NewMockSource(
  1079  				[]getproviders.PackageMeta{},
  1080  				nil,
  1081  			),
  1082  			Prepare: func(t *testing.T, inst *Installer, dir *Dir) {
  1083  				inst.SetBuiltInProviderTypes([]string{"durgaform"})
  1084  			},
  1085  			Mode: InstallNewProvidersOnly,
  1086  			Reqs: getproviders.Requirements{
  1087  				durgaformProvider: getproviders.MustParseVersionConstraints(">= 1.0.0"),
  1088  			},
  1089  			WantErr: `some providers could not be installed:
  1090  - durgaform.io/builtin/terraform: built-in providers do not support explicit version constraints`,
  1091  			WantEvents: func(inst *Installer, dir *Dir) map[addrs.Provider][]*testInstallerEventLogItem {
  1092  				return map[addrs.Provider][]*testInstallerEventLogItem{
  1093  					noProvider: {
  1094  						{
  1095  							Event: "PendingProviders",
  1096  							Args: map[addrs.Provider]getproviders.VersionConstraints{
  1097  								durgaformProvider: getproviders.MustParseVersionConstraints(">= 1.0.0"),
  1098  							},
  1099  						},
  1100  					},
  1101  					durgaformProvider: {
  1102  						{
  1103  							Event:    "BuiltInProviderFailure",
  1104  							Provider: durgaformProvider,
  1105  							Args:     `built-in providers do not support explicit version constraints`,
  1106  						},
  1107  					},
  1108  				}
  1109  			},
  1110  		},
  1111  		"locked version is excluded by new version constraint": {
  1112  			Source: getproviders.NewMockSource(
  1113  				[]getproviders.PackageMeta{
  1114  					{
  1115  						Provider:       beepProvider,
  1116  						Version:        getproviders.MustParseVersion("1.0.0"),
  1117  						TargetPlatform: fakePlatform,
  1118  						Location:       beepProviderDir,
  1119  					},
  1120  					{
  1121  						Provider:       beepProvider,
  1122  						Version:        getproviders.MustParseVersion("2.0.0"),
  1123  						TargetPlatform: fakePlatform,
  1124  						Location:       beepProviderDir,
  1125  					},
  1126  				},
  1127  				nil,
  1128  			),
  1129  			LockFile: `
  1130  				provider "example.com/foo/beep" {
  1131  					version     = "1.0.0"
  1132  					constraints = ">= 1.0.0"
  1133  					hashes = [
  1134  						"h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84=",
  1135  					]
  1136  				}
  1137  			`,
  1138  			Mode: InstallNewProvidersOnly,
  1139  			Reqs: getproviders.Requirements{
  1140  				beepProvider: getproviders.MustParseVersionConstraints(">= 2.0.0"),
  1141  			},
  1142  			Check: func(t *testing.T, dir *Dir, locks *depsfile.Locks) {
  1143  				if allCached := dir.AllAvailablePackages(); len(allCached) != 0 {
  1144  					t.Errorf("wrong number of cache directory entries; want none\n%s", spew.Sdump(allCached))
  1145  				}
  1146  				if allLocked := locks.AllProviders(); len(allLocked) != 1 {
  1147  					t.Errorf("wrong number of provider lock entries; want only one\n%s", spew.Sdump(allLocked))
  1148  				}
  1149  
  1150  				gotLock := locks.Provider(beepProvider)
  1151  				wantLock := depsfile.NewProviderLock(
  1152  					beepProvider,
  1153  					getproviders.MustParseVersion("1.0.0"),
  1154  					getproviders.MustParseVersionConstraints(">= 1.0.0"),
  1155  					[]getproviders.Hash{"h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84="},
  1156  				)
  1157  				if diff := cmp.Diff(wantLock, gotLock, depsfile.ProviderLockComparer); diff != "" {
  1158  					t.Errorf("wrong lock entry\n%s", diff)
  1159  				}
  1160  			},
  1161  			WantErr: `some providers could not be installed:
  1162  - example.com/foo/beep: locked provider example.com/foo/beep 1.0.0 does not match configured version constraint >= 2.0.0; must use durgaform init -upgrade to allow selection of new versions`,
  1163  			WantEvents: func(inst *Installer, dir *Dir) map[addrs.Provider][]*testInstallerEventLogItem {
  1164  				return map[addrs.Provider][]*testInstallerEventLogItem{
  1165  					noProvider: {
  1166  						{
  1167  							Event: "PendingProviders",
  1168  							Args: map[addrs.Provider]getproviders.VersionConstraints{
  1169  								beepProvider: getproviders.MustParseVersionConstraints(">= 2.0.0"),
  1170  							},
  1171  						},
  1172  					},
  1173  					beepProvider: {
  1174  						{
  1175  							Event:    "QueryPackagesBegin",
  1176  							Provider: beepProvider,
  1177  							Args: struct {
  1178  								Constraints string
  1179  								Locked      bool
  1180  							}{">= 2.0.0", true},
  1181  						},
  1182  						{
  1183  							Event:    "QueryPackagesFailure",
  1184  							Provider: beepProvider,
  1185  							Args:     `locked provider example.com/foo/beep 1.0.0 does not match configured version constraint >= 2.0.0; must use durgaform init -upgrade to allow selection of new versions`,
  1186  						},
  1187  					},
  1188  				}
  1189  			},
  1190  		},
  1191  		"locked version is no longer available": {
  1192  			Source: getproviders.NewMockSource(
  1193  				[]getproviders.PackageMeta{
  1194  					{
  1195  						Provider:       beepProvider,
  1196  						Version:        getproviders.MustParseVersion("1.0.0"),
  1197  						TargetPlatform: fakePlatform,
  1198  						Location:       beepProviderDir,
  1199  					},
  1200  					{
  1201  						Provider:       beepProvider,
  1202  						Version:        getproviders.MustParseVersion("2.0.0"),
  1203  						TargetPlatform: fakePlatform,
  1204  						Location:       beepProviderDir,
  1205  					},
  1206  				},
  1207  				nil,
  1208  			),
  1209  			LockFile: `
  1210  				provider "example.com/foo/beep" {
  1211  					version     = "1.2.0"
  1212  					constraints = ">= 1.0.0"
  1213  					hashes = [
  1214  						"h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84=",
  1215  					]
  1216  				}
  1217  			`,
  1218  			Mode: InstallNewProvidersOnly,
  1219  			Reqs: getproviders.Requirements{
  1220  				beepProvider: getproviders.MustParseVersionConstraints(">= 1.0.0"),
  1221  			},
  1222  			Check: func(t *testing.T, dir *Dir, locks *depsfile.Locks) {
  1223  				if allCached := dir.AllAvailablePackages(); len(allCached) != 0 {
  1224  					t.Errorf("wrong number of cache directory entries; want none\n%s", spew.Sdump(allCached))
  1225  				}
  1226  				if allLocked := locks.AllProviders(); len(allLocked) != 1 {
  1227  					t.Errorf("wrong number of provider lock entries; want only one\n%s", spew.Sdump(allLocked))
  1228  				}
  1229  
  1230  				gotLock := locks.Provider(beepProvider)
  1231  				wantLock := depsfile.NewProviderLock(
  1232  					beepProvider,
  1233  					getproviders.MustParseVersion("1.2.0"),
  1234  					getproviders.MustParseVersionConstraints(">= 1.0.0"),
  1235  					[]getproviders.Hash{"h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84="},
  1236  				)
  1237  				if diff := cmp.Diff(wantLock, gotLock, depsfile.ProviderLockComparer); diff != "" {
  1238  					t.Errorf("wrong lock entry\n%s", diff)
  1239  				}
  1240  			},
  1241  			WantErr: `some providers could not be installed:
  1242  - example.com/foo/beep: the previously-selected version 1.2.0 is no longer available`,
  1243  			WantEvents: func(inst *Installer, dir *Dir) map[addrs.Provider][]*testInstallerEventLogItem {
  1244  				return map[addrs.Provider][]*testInstallerEventLogItem{
  1245  					noProvider: {
  1246  						{
  1247  							Event: "PendingProviders",
  1248  							Args: map[addrs.Provider]getproviders.VersionConstraints{
  1249  								beepProvider: getproviders.MustParseVersionConstraints(">= 1.0.0"),
  1250  							},
  1251  						},
  1252  					},
  1253  					beepProvider: {
  1254  						{
  1255  							Event:    "QueryPackagesBegin",
  1256  							Provider: beepProvider,
  1257  							Args: struct {
  1258  								Constraints string
  1259  								Locked      bool
  1260  							}{">= 1.0.0", true},
  1261  						},
  1262  						{
  1263  							Event:    "QueryPackagesFailure",
  1264  							Provider: beepProvider,
  1265  							Args:     `the previously-selected version 1.2.0 is no longer available`,
  1266  						},
  1267  					},
  1268  				}
  1269  			},
  1270  		},
  1271  		"no versions match the version constraint": {
  1272  			Source: getproviders.NewMockSource(
  1273  				[]getproviders.PackageMeta{
  1274  					{
  1275  						Provider:       beepProvider,
  1276  						Version:        getproviders.MustParseVersion("1.0.0"),
  1277  						TargetPlatform: fakePlatform,
  1278  						Location:       beepProviderDir,
  1279  					},
  1280  				},
  1281  				nil,
  1282  			),
  1283  			Mode: InstallNewProvidersOnly,
  1284  			Reqs: getproviders.Requirements{
  1285  				beepProvider: getproviders.MustParseVersionConstraints(">= 2.0.0"),
  1286  			},
  1287  			WantErr: `some providers could not be installed:
  1288  - example.com/foo/beep: no available releases match the given constraints >= 2.0.0`,
  1289  			WantEvents: func(inst *Installer, dir *Dir) map[addrs.Provider][]*testInstallerEventLogItem {
  1290  				return map[addrs.Provider][]*testInstallerEventLogItem{
  1291  					noProvider: {
  1292  						{
  1293  							Event: "PendingProviders",
  1294  							Args: map[addrs.Provider]getproviders.VersionConstraints{
  1295  								beepProvider: getproviders.MustParseVersionConstraints(">= 2.0.0"),
  1296  							},
  1297  						},
  1298  					},
  1299  					beepProvider: {
  1300  						{
  1301  							Event:    "QueryPackagesBegin",
  1302  							Provider: beepProvider,
  1303  							Args: struct {
  1304  								Constraints string
  1305  								Locked      bool
  1306  							}{">= 2.0.0", false},
  1307  						},
  1308  						{
  1309  							Event:    "QueryPackagesFailure",
  1310  							Provider: beepProvider,
  1311  							Args:     `no available releases match the given constraints >= 2.0.0`,
  1312  						},
  1313  					},
  1314  				}
  1315  			},
  1316  		},
  1317  		"version exists but doesn't support the current platform": {
  1318  			Source: getproviders.NewMockSource(
  1319  				[]getproviders.PackageMeta{
  1320  					{
  1321  						Provider:       beepProvider,
  1322  						Version:        getproviders.MustParseVersion("1.0.0"),
  1323  						TargetPlatform: wrongPlatform,
  1324  						Location:       beepProviderDir,
  1325  					},
  1326  				},
  1327  				nil,
  1328  			),
  1329  			Mode: InstallNewProvidersOnly,
  1330  			Reqs: getproviders.Requirements{
  1331  				beepProvider: getproviders.MustParseVersionConstraints(">= 1.0.0"),
  1332  			},
  1333  			WantErr: `some providers could not be installed:
  1334  - example.com/foo/beep: provider example.com/foo/beep 1.0.0 is not available for bleep_bloop`,
  1335  			WantEvents: func(inst *Installer, dir *Dir) map[addrs.Provider][]*testInstallerEventLogItem {
  1336  				return map[addrs.Provider][]*testInstallerEventLogItem{
  1337  					noProvider: {
  1338  						{
  1339  							Event: "PendingProviders",
  1340  							Args: map[addrs.Provider]getproviders.VersionConstraints{
  1341  								beepProvider: getproviders.MustParseVersionConstraints(">= 1.0.0"),
  1342  							},
  1343  						},
  1344  					},
  1345  					beepProvider: {
  1346  						{
  1347  							Event:    "QueryPackagesBegin",
  1348  							Provider: beepProvider,
  1349  							Args: struct {
  1350  								Constraints string
  1351  								Locked      bool
  1352  							}{">= 1.0.0", false},
  1353  						},
  1354  						{
  1355  							Event:    "QueryPackagesSuccess",
  1356  							Provider: beepProvider,
  1357  							Args:     "1.0.0",
  1358  						},
  1359  						{
  1360  							Event:    "FetchPackageMeta",
  1361  							Provider: beepProvider,
  1362  							Args:     "1.0.0",
  1363  						},
  1364  						{
  1365  							Event:    "FetchPackageFailure",
  1366  							Provider: beepProvider,
  1367  							Args: struct {
  1368  								Version string
  1369  								Error   string
  1370  							}{
  1371  								"1.0.0",
  1372  								"provider example.com/foo/beep 1.0.0 is not available for bleep_bloop",
  1373  							},
  1374  						},
  1375  					},
  1376  				}
  1377  			},
  1378  		},
  1379  		"available package doesn't match locked hash": {
  1380  			Source: getproviders.NewMockSource(
  1381  				[]getproviders.PackageMeta{
  1382  					{
  1383  						Provider:       beepProvider,
  1384  						Version:        getproviders.MustParseVersion("1.0.0"),
  1385  						TargetPlatform: fakePlatform,
  1386  						Location:       beepProviderDir,
  1387  					},
  1388  				},
  1389  				nil,
  1390  			),
  1391  			LockFile: `
  1392  				provider "example.com/foo/beep" {
  1393  					version     = "1.0.0"
  1394  					constraints = ">= 1.0.0"
  1395  					hashes = [
  1396  						"h1:does-not-match",
  1397  					]
  1398  				}
  1399  			`,
  1400  			Mode: InstallNewProvidersOnly,
  1401  			Reqs: getproviders.Requirements{
  1402  				beepProvider: getproviders.MustParseVersionConstraints(">= 1.0.0"),
  1403  			},
  1404  			WantErr: `some providers could not be installed:
  1405  - example.com/foo/beep: the local package for example.com/foo/beep 1.0.0 doesn't match any of the checksums previously recorded in the dependency lock file (this might be because the available checksums are for packages targeting different platforms); for more information: https://www.durgaform.io/language/provider-checksum-verification`,
  1406  			WantEvents: func(inst *Installer, dir *Dir) map[addrs.Provider][]*testInstallerEventLogItem {
  1407  				return map[addrs.Provider][]*testInstallerEventLogItem{
  1408  					noProvider: {
  1409  						{
  1410  							Event: "PendingProviders",
  1411  							Args: map[addrs.Provider]getproviders.VersionConstraints{
  1412  								beepProvider: getproviders.MustParseVersionConstraints(">= 1.0.0"),
  1413  							},
  1414  						},
  1415  					},
  1416  					beepProvider: {
  1417  						{
  1418  							Event:    "QueryPackagesBegin",
  1419  							Provider: beepProvider,
  1420  							Args: struct {
  1421  								Constraints string
  1422  								Locked      bool
  1423  							}{">= 1.0.0", true},
  1424  						},
  1425  						{
  1426  							Event:    "QueryPackagesSuccess",
  1427  							Provider: beepProvider,
  1428  							Args:     "1.0.0",
  1429  						},
  1430  						{
  1431  							Event:    "FetchPackageMeta",
  1432  							Provider: beepProvider,
  1433  							Args:     "1.0.0",
  1434  						},
  1435  						{
  1436  							Event:    "FetchPackageBegin",
  1437  							Provider: beepProvider,
  1438  							Args: struct {
  1439  								Version  string
  1440  								Location getproviders.PackageLocation
  1441  							}{"1.0.0", beepProviderDir},
  1442  						},
  1443  						{
  1444  							Event:    "FetchPackageFailure",
  1445  							Provider: beepProvider,
  1446  							Args: struct {
  1447  								Version string
  1448  								Error   string
  1449  							}{
  1450  								"1.0.0",
  1451  								`the local package for example.com/foo/beep 1.0.0 doesn't match any of the checksums previously recorded in the dependency lock file (this might be because the available checksums are for packages targeting different platforms); for more information: https://www.durgaform.io/language/provider-checksum-verification`,
  1452  							},
  1453  						},
  1454  					},
  1455  				}
  1456  			},
  1457  		},
  1458  		"force mode ignores hashes": {
  1459  			Source: getproviders.NewMockSource(
  1460  				[]getproviders.PackageMeta{
  1461  					{
  1462  						Provider:       beepProvider,
  1463  						Version:        getproviders.MustParseVersion("1.0.0"),
  1464  						TargetPlatform: fakePlatform,
  1465  						Location:       beepProviderDir,
  1466  					},
  1467  				},
  1468  				nil,
  1469  			),
  1470  			LockFile: `
  1471  				provider "example.com/foo/beep" {
  1472  					version     = "1.0.0"
  1473  					constraints = ">= 1.0.0"
  1474  					hashes = [
  1475  						"h1:does-not-match",
  1476  					]
  1477  				}
  1478  			`,
  1479  			Mode: InstallNewProvidersForce,
  1480  			Reqs: getproviders.Requirements{
  1481  				beepProvider: getproviders.MustParseVersionConstraints(">= 1.0.0"),
  1482  			},
  1483  			Check: func(t *testing.T, dir *Dir, locks *depsfile.Locks) {
  1484  				if allCached := dir.AllAvailablePackages(); len(allCached) != 1 {
  1485  					t.Errorf("wrong number of cache directory entries; want only one\n%s", spew.Sdump(allCached))
  1486  				}
  1487  				if allLocked := locks.AllProviders(); len(allLocked) != 1 {
  1488  					t.Errorf("wrong number of provider lock entries; want only one\n%s", spew.Sdump(allLocked))
  1489  				}
  1490  
  1491  				gotLock := locks.Provider(beepProvider)
  1492  				wantLock := depsfile.NewProviderLock(
  1493  					beepProvider,
  1494  					getproviders.MustParseVersion("1.0.0"),
  1495  					getproviders.MustParseVersionConstraints(">= 1.0.0"),
  1496  					[]getproviders.Hash{beepProviderHash, "h1:does-not-match"},
  1497  				)
  1498  				if diff := cmp.Diff(wantLock, gotLock, depsfile.ProviderLockComparer); diff != "" {
  1499  					t.Errorf("wrong lock entry\n%s", diff)
  1500  				}
  1501  
  1502  				gotEntry := dir.ProviderLatestVersion(beepProvider)
  1503  				wantEntry := &CachedProvider{
  1504  					Provider:   beepProvider,
  1505  					Version:    getproviders.MustParseVersion("1.0.0"),
  1506  					PackageDir: filepath.Join(dir.BasePath(), "example.com/foo/beep/1.0.0/bleep_bloop"),
  1507  				}
  1508  				if diff := cmp.Diff(wantEntry, gotEntry); diff != "" {
  1509  					t.Errorf("wrong cache entry\n%s", diff)
  1510  				}
  1511  			},
  1512  			WantEvents: func(inst *Installer, dir *Dir) map[addrs.Provider][]*testInstallerEventLogItem {
  1513  				return map[addrs.Provider][]*testInstallerEventLogItem{
  1514  					noProvider: {
  1515  						{
  1516  							Event: "PendingProviders",
  1517  							Args: map[addrs.Provider]getproviders.VersionConstraints{
  1518  								beepProvider: getproviders.MustParseVersionConstraints(">= 1.0.0"),
  1519  							},
  1520  						},
  1521  						{
  1522  							Event: "ProvidersFetched",
  1523  							Args: map[addrs.Provider]*getproviders.PackageAuthenticationResult{
  1524  								beepProvider: nil,
  1525  							},
  1526  						},
  1527  					},
  1528  					beepProvider: {
  1529  						{
  1530  							Event:    "QueryPackagesBegin",
  1531  							Provider: beepProvider,
  1532  							Args: struct {
  1533  								Constraints string
  1534  								Locked      bool
  1535  							}{">= 1.0.0", true},
  1536  						},
  1537  						{
  1538  							Event:    "QueryPackagesSuccess",
  1539  							Provider: beepProvider,
  1540  							Args:     "1.0.0",
  1541  						},
  1542  						{
  1543  							Event:    "FetchPackageMeta",
  1544  							Provider: beepProvider,
  1545  							Args:     "1.0.0",
  1546  						},
  1547  						{
  1548  							Event:    "FetchPackageBegin",
  1549  							Provider: beepProvider,
  1550  							Args: struct {
  1551  								Version  string
  1552  								Location getproviders.PackageLocation
  1553  							}{"1.0.0", beepProviderDir},
  1554  						},
  1555  						{
  1556  							Event:    "ProvidersLockUpdated",
  1557  							Provider: beepProvider,
  1558  							Args: struct {
  1559  								Version string
  1560  								Local   []getproviders.Hash
  1561  								Signed  []getproviders.Hash
  1562  								Prior   []getproviders.Hash
  1563  							}{
  1564  								"1.0.0",
  1565  								[]getproviders.Hash{"h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84="},
  1566  								nil,
  1567  								[]getproviders.Hash{"h1:does-not-match"},
  1568  							},
  1569  						},
  1570  						{
  1571  							Event:    "FetchPackageSuccess",
  1572  							Provider: beepProvider,
  1573  							Args: struct {
  1574  								Version    string
  1575  								LocalDir   string
  1576  								AuthResult string
  1577  							}{
  1578  								"1.0.0",
  1579  								filepath.Join(dir.BasePath(), "example.com/foo/beep/1.0.0/bleep_bloop"),
  1580  								"unauthenticated",
  1581  							},
  1582  						},
  1583  					},
  1584  				}
  1585  			},
  1586  		},
  1587  	}
  1588  
  1589  	ctx := context.Background()
  1590  
  1591  	for name, test := range tests {
  1592  		t.Run(name, func(t *testing.T) {
  1593  			if test.Check == nil && test.WantEvents == nil && test.WantErr == "" {
  1594  				t.Fatalf("invalid test: must set at least one of Check, WantEvents, or WantErr")
  1595  			}
  1596  
  1597  			outputDir := NewDirWithPlatform(tmpDir(t), fakePlatform)
  1598  			source := test.Source
  1599  			if source == nil {
  1600  				source = getproviders.NewMockSource(nil, nil)
  1601  			}
  1602  			inst := NewInstaller(outputDir, source)
  1603  			if test.Prepare != nil {
  1604  				test.Prepare(t, inst, outputDir)
  1605  			}
  1606  
  1607  			locks, lockDiags := depsfile.LoadLocksFromBytes([]byte(test.LockFile), "test.lock.hcl")
  1608  			if lockDiags.HasErrors() {
  1609  				t.Fatalf("invalid lock file: %s", lockDiags.Err().Error())
  1610  			}
  1611  
  1612  			providerEvents := make(map[addrs.Provider][]*testInstallerEventLogItem)
  1613  			eventsCh := make(chan *testInstallerEventLogItem)
  1614  			var newLocks *depsfile.Locks
  1615  			var instErr error
  1616  			go func(ch chan *testInstallerEventLogItem) {
  1617  				events := installerLogEventsForTests(ch)
  1618  				ctx := events.OnContext(ctx)
  1619  				newLocks, instErr = inst.EnsureProviderVersions(ctx, locks, test.Reqs, test.Mode)
  1620  				close(eventsCh) // exits the event loop below
  1621  			}(eventsCh)
  1622  			for evt := range eventsCh {
  1623  				// We do the event collection in the main goroutine, rather than
  1624  				// running the installer itself in the main goroutine, so that
  1625  				// we can safely t.Log in here without violating the testing.T
  1626  				// usage rules.
  1627  				if evt.Provider == (addrs.Provider{}) {
  1628  					t.Logf("%s(%s)", evt.Event, spew.Sdump(evt.Args))
  1629  				} else {
  1630  					t.Logf("%s: %s(%s)", evt.Provider, evt.Event, spew.Sdump(evt.Args))
  1631  				}
  1632  				providerEvents[evt.Provider] = append(providerEvents[evt.Provider], evt)
  1633  			}
  1634  
  1635  			if test.WantErr != "" {
  1636  				if instErr == nil {
  1637  					t.Errorf("succeeded; want error\nwant: %s", test.WantErr)
  1638  				} else if got, want := instErr.Error(), test.WantErr; got != want {
  1639  					t.Errorf("wrong error\ngot:  %s\nwant: %s", got, want)
  1640  				}
  1641  			} else if instErr != nil {
  1642  				t.Errorf("unexpected error\ngot: %s", instErr.Error())
  1643  			}
  1644  
  1645  			if test.Check != nil {
  1646  				test.Check(t, outputDir, newLocks)
  1647  			}
  1648  
  1649  			if test.WantEvents != nil {
  1650  				wantEvents := test.WantEvents(inst, outputDir)
  1651  				if diff := cmp.Diff(wantEvents, providerEvents); diff != "" {
  1652  					t.Errorf("wrong installer events\n%s", diff)
  1653  				}
  1654  			}
  1655  		})
  1656  	}
  1657  }
  1658  
  1659  func TestEnsureProviderVersions_local_source(t *testing.T) {
  1660  	// create filesystem source using the test provider cache dir
  1661  	source := getproviders.NewFilesystemMirrorSource("testdata/cachedir")
  1662  
  1663  	// create a temporary workdir
  1664  	tmpDirPath := t.TempDir()
  1665  
  1666  	// set up the installer using the temporary directory and filesystem source
  1667  	platform := getproviders.Platform{OS: "linux", Arch: "amd64"}
  1668  	dir := NewDirWithPlatform(tmpDirPath, platform)
  1669  	installer := NewInstaller(dir, source)
  1670  
  1671  	tests := map[string]struct {
  1672  		provider string
  1673  		version  string
  1674  		wantHash getproviders.Hash // getproviders.NilHash if not expected to be installed
  1675  		err      string
  1676  	}{
  1677  		"install-unpacked": {
  1678  			provider: "null",
  1679  			version:  "2.0.0",
  1680  			wantHash: getproviders.HashScheme1.New("qjsREM4DqEWECD43FcPqddZ9oxCG+IaMTxvWPciS05g="),
  1681  		},
  1682  		"invalid-zip-file": {
  1683  			provider: "null",
  1684  			version:  "2.1.0",
  1685  			wantHash: getproviders.NilHash,
  1686  			err:      "zip: not a valid zip file",
  1687  		},
  1688  		"version-constraint-unmet": {
  1689  			provider: "null",
  1690  			version:  "2.2.0",
  1691  			wantHash: getproviders.NilHash,
  1692  			err:      "no available releases match the given constraints 2.2.0",
  1693  		},
  1694  		"missing-executable": {
  1695  			provider: "missing/executable",
  1696  			version:  "2.0.0",
  1697  			wantHash: getproviders.NilHash, // installation fails for a provider with no executable
  1698  			err:      "provider binary not found: could not find executable file starting with durgaform-provider-executable",
  1699  		},
  1700  	}
  1701  
  1702  	for name, test := range tests {
  1703  		t.Run(name, func(t *testing.T) {
  1704  			ctx := context.TODO()
  1705  
  1706  			provider := addrs.MustParseProviderSourceString(test.provider)
  1707  			versionConstraint := getproviders.MustParseVersionConstraints(test.version)
  1708  			version := getproviders.MustParseVersion(test.version)
  1709  			reqs := getproviders.Requirements{
  1710  				provider: versionConstraint,
  1711  			}
  1712  
  1713  			newLocks, err := installer.EnsureProviderVersions(ctx, depsfile.NewLocks(), reqs, InstallNewProvidersOnly)
  1714  			gotProviderlocks := newLocks.AllProviders()
  1715  			wantProviderLocks := map[addrs.Provider]*depsfile.ProviderLock{
  1716  				provider: depsfile.NewProviderLock(
  1717  					provider,
  1718  					version,
  1719  					getproviders.MustParseVersionConstraints("= 2.0.0"),
  1720  					[]getproviders.Hash{
  1721  						test.wantHash,
  1722  					},
  1723  				),
  1724  			}
  1725  			if test.wantHash == getproviders.NilHash {
  1726  				wantProviderLocks = map[addrs.Provider]*depsfile.ProviderLock{}
  1727  			}
  1728  
  1729  			if diff := cmp.Diff(wantProviderLocks, gotProviderlocks, depsfile.ProviderLockComparer); diff != "" {
  1730  				t.Errorf("wrong selected\n%s", diff)
  1731  			}
  1732  
  1733  			if test.err == "" && err == nil {
  1734  				return
  1735  			}
  1736  
  1737  			switch err := err.(type) {
  1738  			case InstallerError:
  1739  				providerError, ok := err.ProviderErrors[provider]
  1740  				if !ok {
  1741  					t.Fatalf("did not get error for provider %s", provider)
  1742  				}
  1743  
  1744  				if got := providerError.Error(); got != test.err {
  1745  					t.Fatalf("wrong result\ngot:  %s\nwant: %s\n", got, test.err)
  1746  				}
  1747  			default:
  1748  				t.Fatalf("wrong error type. Expected InstallerError, got %T", err)
  1749  			}
  1750  		})
  1751  	}
  1752  }
  1753  
  1754  // This test only verifies protocol errors and does not try for successfull
  1755  // installation (at the time of writing, the test files aren't signed so the
  1756  // signature verification fails); that's left to the e2e tests.
  1757  func TestEnsureProviderVersions_protocol_errors(t *testing.T) {
  1758  	source, _, close := testRegistrySource(t)
  1759  	defer close()
  1760  
  1761  	// create a temporary workdir
  1762  	tmpDirPath := t.TempDir()
  1763  
  1764  	version0 := getproviders.MustParseVersionConstraints("0.1.0") // supports protocol version 1.0
  1765  	version1 := getproviders.MustParseVersion("1.2.0")            // this is the expected result in tests with a match
  1766  	version2 := getproviders.MustParseVersionConstraints("2.0")   // supports protocol version 99
  1767  
  1768  	// set up the installer using the temporary directory and mock source
  1769  	platform := getproviders.Platform{OS: "gameboy", Arch: "lr35902"}
  1770  	dir := NewDirWithPlatform(tmpDirPath, platform)
  1771  	installer := NewInstaller(dir, source)
  1772  
  1773  	tests := map[string]struct {
  1774  		provider     addrs.Provider
  1775  		inputVersion getproviders.VersionConstraints
  1776  		wantVersion  getproviders.Version
  1777  	}{
  1778  		"too old": {
  1779  			addrs.MustParseProviderSourceString("example.com/awesomesauce/happycloud"),
  1780  			version0,
  1781  			version1,
  1782  		},
  1783  		"too new": {
  1784  			addrs.MustParseProviderSourceString("example.com/awesomesauce/happycloud"),
  1785  			version2,
  1786  			version1,
  1787  		},
  1788  		"unsupported": {
  1789  			addrs.MustParseProviderSourceString("example.com/weaksauce/unsupported-protocol"),
  1790  			version0,
  1791  			getproviders.UnspecifiedVersion,
  1792  		},
  1793  	}
  1794  
  1795  	for name, test := range tests {
  1796  		t.Run(name, func(t *testing.T) {
  1797  			reqs := getproviders.Requirements{
  1798  				test.provider: test.inputVersion,
  1799  			}
  1800  			ctx := context.TODO()
  1801  			_, err := installer.EnsureProviderVersions(ctx, depsfile.NewLocks(), reqs, InstallNewProvidersOnly)
  1802  
  1803  			switch err := err.(type) {
  1804  			case nil:
  1805  				t.Fatalf("expected error, got success")
  1806  			case InstallerError:
  1807  				providerError, ok := err.ProviderErrors[test.provider]
  1808  				if !ok {
  1809  					t.Fatalf("did not get error for provider %s", test.provider)
  1810  				}
  1811  
  1812  				switch providerError := providerError.(type) {
  1813  				case getproviders.ErrProtocolNotSupported:
  1814  					if !providerError.Suggestion.Same(test.wantVersion) {
  1815  						t.Fatalf("wrong result\ngot:  %s\nwant: %s\n", providerError.Suggestion, test.wantVersion)
  1816  					}
  1817  				default:
  1818  					t.Fatalf("wrong error type. Expected ErrProtocolNotSupported, got %T", err)
  1819  				}
  1820  			default:
  1821  				t.Fatalf("wrong error type. Expected InstallerError, got %T", err)
  1822  			}
  1823  		})
  1824  	}
  1825  }
  1826  
  1827  // testServices starts up a local HTTP server running a fake provider registry
  1828  // service and returns a service discovery object pre-configured to consider
  1829  // the host "example.com" to be served by the fake registry service.
  1830  //
  1831  // The returned discovery object also knows the hostname "not.example.com"
  1832  // which does not have a provider registry at all and "too-new.example.com"
  1833  // which has a "providers.v99" service that is inoperable but could be useful
  1834  // to test the error reporting for detecting an unsupported protocol version.
  1835  // It also knows fails.example.com but it refers to an endpoint that doesn't
  1836  // correctly speak HTTP, to simulate a protocol error.
  1837  //
  1838  // The second return value is a function to call at the end of a test function
  1839  // to shut down the test server. After you call that function, the discovery
  1840  // object becomes useless.
  1841  func testServices(t *testing.T) (services *disco.Disco, baseURL string, cleanup func()) {
  1842  	server := httptest.NewServer(http.HandlerFunc(fakeRegistryHandler))
  1843  
  1844  	services = disco.New()
  1845  	services.ForceHostServices(svchost.Hostname("example.com"), map[string]interface{}{
  1846  		"providers.v1": server.URL + "/providers/v1/",
  1847  	})
  1848  	services.ForceHostServices(svchost.Hostname("not.example.com"), map[string]interface{}{})
  1849  	services.ForceHostServices(svchost.Hostname("too-new.example.com"), map[string]interface{}{
  1850  		// This service doesn't actually work; it's here only to be
  1851  		// detected as "too new" by the discovery logic.
  1852  		"providers.v99": server.URL + "/providers/v99/",
  1853  	})
  1854  	services.ForceHostServices(svchost.Hostname("fails.example.com"), map[string]interface{}{
  1855  		"providers.v1": server.URL + "/fails-immediately/",
  1856  	})
  1857  
  1858  	// We'll also permit registry.durgaform.io here just because it's our
  1859  	// default and has some unique features that are not allowed on any other
  1860  	// hostname. It behaves the same as example.com, which should be preferred
  1861  	// if you're not testing something specific to the default registry in order
  1862  	// to ensure that most things are hostname-agnostic.
  1863  	services.ForceHostServices(svchost.Hostname("registry.durgaform.io"), map[string]interface{}{
  1864  		"providers.v1": server.URL + "/providers/v1/",
  1865  	})
  1866  
  1867  	return services, server.URL, func() {
  1868  		server.Close()
  1869  	}
  1870  }
  1871  
  1872  // testRegistrySource is a wrapper around testServices that uses the created
  1873  // discovery object to produce a Source instance that is ready to use with the
  1874  // fake registry services.
  1875  //
  1876  // As with testServices, the second return value is a function to call at the end
  1877  // of your test in order to shut down the test server.
  1878  func testRegistrySource(t *testing.T) (source *getproviders.RegistrySource, baseURL string, cleanup func()) {
  1879  	services, baseURL, close := testServices(t)
  1880  	source = getproviders.NewRegistrySource(services)
  1881  	return source, baseURL, close
  1882  }
  1883  
  1884  func fakeRegistryHandler(resp http.ResponseWriter, req *http.Request) {
  1885  	path := req.URL.EscapedPath()
  1886  	if strings.HasPrefix(path, "/fails-immediately/") {
  1887  		// Here we take over the socket and just close it immediately, to
  1888  		// simulate one possible way a server might not be an HTTP server.
  1889  		hijacker, ok := resp.(http.Hijacker)
  1890  		if !ok {
  1891  			// Not hijackable, so we'll just fail normally.
  1892  			// If this happens, tests relying on this will fail.
  1893  			resp.WriteHeader(500)
  1894  			resp.Write([]byte(`cannot hijack`))
  1895  			return
  1896  		}
  1897  		conn, _, err := hijacker.Hijack()
  1898  		if err != nil {
  1899  			resp.WriteHeader(500)
  1900  			resp.Write([]byte(`hijack failed`))
  1901  			return
  1902  		}
  1903  		conn.Close()
  1904  		return
  1905  	}
  1906  
  1907  	if strings.HasPrefix(path, "/pkg/") {
  1908  		switch path {
  1909  		case "/pkg/awesomesauce/happycloud_1.2.0.zip":
  1910  			resp.Write([]byte("some zip file"))
  1911  		case "/pkg/awesomesauce/happycloud_1.2.0_SHA256SUMS":
  1912  			resp.Write([]byte("000000000000000000000000000000000000000000000000000000000000f00d happycloud_1.2.0.zip\n"))
  1913  		case "/pkg/awesomesauce/happycloud_1.2.0_SHA256SUMS.sig":
  1914  			resp.Write([]byte("GPG signature"))
  1915  		default:
  1916  			resp.WriteHeader(404)
  1917  			resp.Write([]byte("unknown package file download"))
  1918  		}
  1919  		return
  1920  	}
  1921  
  1922  	if !strings.HasPrefix(path, "/providers/v1/") {
  1923  		resp.WriteHeader(404)
  1924  		resp.Write([]byte(`not a provider registry endpoint`))
  1925  		return
  1926  	}
  1927  
  1928  	pathParts := strings.Split(path, "/")[3:]
  1929  	if len(pathParts) < 2 {
  1930  		resp.WriteHeader(404)
  1931  		resp.Write([]byte(`unexpected number of path parts`))
  1932  		return
  1933  	}
  1934  	log.Printf("[TRACE] fake provider registry request for %#v", pathParts)
  1935  	if len(pathParts) == 2 {
  1936  		switch pathParts[0] + "/" + pathParts[1] {
  1937  
  1938  		case "-/legacy":
  1939  			// NOTE: This legacy lookup endpoint is specific to
  1940  			// registry.durgaform.io and not expected to work on any other
  1941  			// registry host.
  1942  			resp.Header().Set("Content-Type", "application/json")
  1943  			resp.WriteHeader(200)
  1944  			resp.Write([]byte(`{"namespace":"legacycorp"}`))
  1945  
  1946  		default:
  1947  			resp.WriteHeader(404)
  1948  			resp.Write([]byte(`unknown namespace or provider type for direct lookup`))
  1949  		}
  1950  	}
  1951  
  1952  	if len(pathParts) < 3 {
  1953  		resp.WriteHeader(404)
  1954  		resp.Write([]byte(`unexpected number of path parts`))
  1955  		return
  1956  	}
  1957  
  1958  	if pathParts[2] == "versions" {
  1959  		if len(pathParts) != 3 {
  1960  			resp.WriteHeader(404)
  1961  			resp.Write([]byte(`extraneous path parts`))
  1962  			return
  1963  		}
  1964  
  1965  		switch pathParts[0] + "/" + pathParts[1] {
  1966  		case "awesomesauce/happycloud":
  1967  			resp.Header().Set("Content-Type", "application/json")
  1968  			resp.WriteHeader(200)
  1969  			// Note that these version numbers are intentionally misordered
  1970  			// so we can test that the client-side code places them in the
  1971  			// correct order (lowest precedence first).
  1972  			resp.Write([]byte(`{"versions":[{"version":"0.1.0","protocols":["1.0"]},{"version":"2.0.0","protocols":["99.0"]},{"version":"1.2.0","protocols":["5.0"]}, {"version":"1.0.0","protocols":["5.0"]}]}`))
  1973  		case "weaksauce/unsupported-protocol":
  1974  			resp.Header().Set("Content-Type", "application/json")
  1975  			resp.WriteHeader(200)
  1976  			resp.Write([]byte(`{"versions":[{"version":"0.1.0","protocols":["0.1"]}]}`))
  1977  		case "weaksauce/no-versions":
  1978  			resp.Header().Set("Content-Type", "application/json")
  1979  			resp.WriteHeader(200)
  1980  			resp.Write([]byte(`{"versions":[]}`))
  1981  		default:
  1982  			resp.WriteHeader(404)
  1983  			resp.Write([]byte(`unknown namespace or provider type`))
  1984  		}
  1985  		return
  1986  	}
  1987  
  1988  	if len(pathParts) == 6 && pathParts[3] == "download" {
  1989  		switch pathParts[0] + "/" + pathParts[1] {
  1990  		case "awesomesauce/happycloud":
  1991  			if pathParts[4] == "nonexist" {
  1992  				resp.WriteHeader(404)
  1993  				resp.Write([]byte(`unsupported OS`))
  1994  				return
  1995  			}
  1996  			version := pathParts[2]
  1997  			body := map[string]interface{}{
  1998  				"protocols":             []string{"99.0"},
  1999  				"os":                    pathParts[4],
  2000  				"arch":                  pathParts[5],
  2001  				"filename":              "happycloud_" + version + ".zip",
  2002  				"shasum":                "000000000000000000000000000000000000000000000000000000000000f00d",
  2003  				"download_url":          "/pkg/awesomesauce/happycloud_" + version + ".zip",
  2004  				"shasums_url":           "/pkg/awesomesauce/happycloud_" + version + "_SHA256SUMS",
  2005  				"shasums_signature_url": "/pkg/awesomesauce/happycloud_" + version + "_SHA256SUMS.sig",
  2006  				"signing_keys": map[string]interface{}{
  2007  					"gpg_public_keys": []map[string]interface{}{
  2008  						{
  2009  							"ascii_armor": getproviders.HashicorpPublicKey,
  2010  						},
  2011  					},
  2012  				},
  2013  			}
  2014  			enc, err := json.Marshal(body)
  2015  			if err != nil {
  2016  				resp.WriteHeader(500)
  2017  				resp.Write([]byte("failed to encode body"))
  2018  			}
  2019  			resp.Header().Set("Content-Type", "application/json")
  2020  			resp.WriteHeader(200)
  2021  			resp.Write(enc)
  2022  		case "weaksauce/unsupported-protocol":
  2023  			var protocols []string
  2024  			version := pathParts[2]
  2025  			switch version {
  2026  			case "0.1.0":
  2027  				protocols = []string{"1.0"}
  2028  			case "2.0.0":
  2029  				protocols = []string{"99.0"}
  2030  			default:
  2031  				protocols = []string{"5.0"}
  2032  			}
  2033  
  2034  			body := map[string]interface{}{
  2035  				"protocols":             protocols,
  2036  				"os":                    pathParts[4],
  2037  				"arch":                  pathParts[5],
  2038  				"filename":              "happycloud_" + version + ".zip",
  2039  				"shasum":                "000000000000000000000000000000000000000000000000000000000000f00d",
  2040  				"download_url":          "/pkg/awesomesauce/happycloud_" + version + ".zip",
  2041  				"shasums_url":           "/pkg/awesomesauce/happycloud_" + version + "_SHA256SUMS",
  2042  				"shasums_signature_url": "/pkg/awesomesauce/happycloud_" + version + "_SHA256SUMS.sig",
  2043  				"signing_keys": map[string]interface{}{
  2044  					"gpg_public_keys": []map[string]interface{}{
  2045  						{
  2046  							"ascii_armor": getproviders.HashicorpPublicKey,
  2047  						},
  2048  					},
  2049  				},
  2050  			}
  2051  			enc, err := json.Marshal(body)
  2052  			if err != nil {
  2053  				resp.WriteHeader(500)
  2054  				resp.Write([]byte("failed to encode body"))
  2055  			}
  2056  			resp.Header().Set("Content-Type", "application/json")
  2057  			resp.WriteHeader(200)
  2058  			resp.Write(enc)
  2059  		default:
  2060  			resp.WriteHeader(404)
  2061  			resp.Write([]byte(`unknown namespace/provider/version/architecture`))
  2062  		}
  2063  		return
  2064  	}
  2065  
  2066  	resp.WriteHeader(404)
  2067  	resp.Write([]byte(`unrecognized path scheme`))
  2068  }
  2069  
  2070  // In order to be able to compare the recorded temp dir paths, we need to
  2071  // normalize the path to match what the installer would report.
  2072  func tmpDir(t *testing.T) string {
  2073  	unlinked, err := filepath.EvalSymlinks(t.TempDir())
  2074  	if err != nil {
  2075  		t.Fatal(err)
  2076  	}
  2077  	return filepath.Clean(unlinked)
  2078  }