From c77d209b87cf27a8d9109c96377e1b8fdc988149 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Mon, 6 Apr 2020 16:11:37 -0700 Subject: [PATCH] internal/getproviders: Tests for MultiSource Due to other pressures at the time this was implemented, it was tested only indirectly through integration tests in other packages. This now introduces tests for the two main entry points on the MultiSource, along with its provider-address pattern matching logic. This does not yet include thorough tests for ParseMultiSourceMatchingPatterns, because that function still needs some adjustments to do the same case folding as for normal provider address parsing, which will follow in a latter commit along with suitable tests. With that said, the tests added here do _indirectly_ test the happy path of ParseMultiSourceMatchingPatterns, so we have some incomplete testing of that function in the meantime. --- internal/getproviders/multi_source_test.go | 431 +++++++++++++++++++++ 1 file changed, 431 insertions(+) create mode 100644 internal/getproviders/multi_source_test.go diff --git a/internal/getproviders/multi_source_test.go b/internal/getproviders/multi_source_test.go new file mode 100644 index 000000000000..21c69a14b277 --- /dev/null +++ b/internal/getproviders/multi_source_test.go @@ -0,0 +1,431 @@ +package getproviders + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform/addrs" +) + +func TestMultiSourceAvailableVersions(t *testing.T) { + platform1 := Platform{OS: "amigaos", Arch: "m68k"} + platform2 := Platform{OS: "aros", Arch: "arm"} + + t.Run("unfiltered merging", func(t *testing.T) { + s1 := NewMockSource([]PackageMeta{ + FakePackageMeta( + addrs.NewDefaultProvider("foo"), + MustParseVersion("1.0.0"), + platform1, + ), + FakePackageMeta( + addrs.NewDefaultProvider("foo"), + MustParseVersion("1.0.0"), + platform2, + ), + FakePackageMeta( + addrs.NewDefaultProvider("bar"), + MustParseVersion("1.0.0"), + platform2, + ), + }) + s2 := NewMockSource([]PackageMeta{ + FakePackageMeta( + addrs.NewDefaultProvider("foo"), + MustParseVersion("1.0.0"), + platform1, + ), + FakePackageMeta( + addrs.NewDefaultProvider("foo"), + MustParseVersion("1.2.0"), + platform1, + ), + FakePackageMeta( + addrs.NewDefaultProvider("bar"), + MustParseVersion("1.0.0"), + platform1, + ), + }) + multi := MultiSource{ + {Source: s1}, + {Source: s2}, + } + + // AvailableVersions produces the union of all versions available + // across all of the sources. + got, err := multi.AvailableVersions(addrs.NewDefaultProvider("foo")) + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + want := VersionList{ + MustParseVersion("1.0.0"), + MustParseVersion("1.2.0"), + } + + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("wrong result\n%s", diff) + } + + _, err = multi.AvailableVersions(addrs.NewDefaultProvider("baz")) + if want, ok := err.(ErrProviderNotKnown); !ok { + t.Fatalf("wrong error type:\ngot: %T\nwant: %T", err, want) + } + }) + + t.Run("merging with filters", func(t *testing.T) { + // This is just testing that filters are being honored at all, using a + // specific pair of filters. The different filter combinations + // themselves are tested in TestMultiSourceSelector. + + s1 := NewMockSource([]PackageMeta{ + FakePackageMeta( + addrs.NewDefaultProvider("foo"), + MustParseVersion("1.0.0"), + platform1, + ), + FakePackageMeta( + addrs.NewDefaultProvider("bar"), + MustParseVersion("1.0.0"), + platform1, + ), + }) + s2 := NewMockSource([]PackageMeta{ + FakePackageMeta( + addrs.NewDefaultProvider("foo"), + MustParseVersion("1.2.0"), + platform1, + ), + FakePackageMeta( + addrs.NewDefaultProvider("bar"), + MustParseVersion("1.2.0"), + platform1, + ), + }) + multi := MultiSource{ + { + Source: s1, + Include: mustParseMultiSourceMatchingPatterns("hashicorp/*"), + }, + { + Source: s2, + Include: mustParseMultiSourceMatchingPatterns("hashicorp/bar"), + }, + } + + got, err := multi.AvailableVersions(addrs.NewDefaultProvider("foo")) + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + want := VersionList{ + MustParseVersion("1.0.0"), + // 1.2.0 isn't present because s3 doesn't include "foo" + } + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("wrong result\n%s", diff) + } + + got, err = multi.AvailableVersions(addrs.NewDefaultProvider("bar")) + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + want = VersionList{ + MustParseVersion("1.0.0"), + MustParseVersion("1.2.0"), // included because s2 matches "bar" + } + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("wrong result\n%s", diff) + } + + _, err = multi.AvailableVersions(addrs.NewDefaultProvider("baz")) + if want, ok := err.(ErrProviderNotKnown); !ok { + t.Fatalf("wrong error type:\ngot: %T\nwant: %T", err, want) + } + }) +} + +func TestMultiSourcePackageMeta(t *testing.T) { + platform1 := Platform{OS: "amigaos", Arch: "m68k"} + platform2 := Platform{OS: "aros", Arch: "arm"} + + // We'll use the Filename field of the fake PackageMetas we created above + // to create a difference between the packages in s1 and the ones in s2, + // so we can test where individual packages came from below. + fakeFilename := func(fn string, meta PackageMeta) PackageMeta { + meta.Filename = fn + return meta + } + + onlyInS1 := fakeFilename("s1", FakePackageMeta( + addrs.NewDefaultProvider("foo"), + MustParseVersion("1.0.0"), + platform2, + )) + onlyInS2 := fakeFilename("s2", FakePackageMeta( + addrs.NewDefaultProvider("foo"), + MustParseVersion("1.2.0"), + platform1, + )) + inBothS1 := fakeFilename("s1", FakePackageMeta( + addrs.NewDefaultProvider("foo"), + MustParseVersion("1.0.0"), + platform1, + )) + inBothS2 := fakeFilename("s2", inBothS1) + s1 := NewMockSource([]PackageMeta{ + inBothS1, + onlyInS1, + fakeFilename("s1", FakePackageMeta( + addrs.NewDefaultProvider("bar"), + MustParseVersion("1.0.0"), + platform2, + )), + }) + s2 := NewMockSource([]PackageMeta{ + inBothS2, + onlyInS2, + fakeFilename("s2", FakePackageMeta( + addrs.NewDefaultProvider("bar"), + MustParseVersion("1.0.0"), + platform1, + )), + }) + multi := MultiSource{ + {Source: s1}, + {Source: s2}, + } + + t.Run("only in s1", func(t *testing.T) { + got, err := multi.PackageMeta( + addrs.NewDefaultProvider("foo"), + MustParseVersion("1.0.0"), + platform2, + ) + want := onlyInS1 + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("wrong result\n%s", diff) + } + }) + t.Run("only in s2", func(t *testing.T) { + got, err := multi.PackageMeta( + addrs.NewDefaultProvider("foo"), + MustParseVersion("1.2.0"), + platform1, + ) + want := onlyInS2 + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("wrong result\n%s", diff) + } + }) + t.Run("in both", func(t *testing.T) { + got, err := multi.PackageMeta( + addrs.NewDefaultProvider("foo"), + MustParseVersion("1.0.0"), + platform1, + ) + want := inBothS1 // S1 "wins" because it's earlier in the MultiSource + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("wrong result\n%s", diff) + } + + // Make sure inBothS1 and inBothS2 really are different; if not then + // that's a test bug which we'd rather catch than have this test + // accidentally passing without actually checking anything. + if diff := cmp.Diff(inBothS1, inBothS2); diff == "" { + t.Fatalf("test bug: inBothS1 and inBothS2 are indistinguishable") + } + }) + t.Run("in neither", func(t *testing.T) { + _, err := multi.PackageMeta( + addrs.NewDefaultProvider("nonexist"), + MustParseVersion("1.0.0"), + platform1, + ) + // This case reports "platform not supported" because it assumes that + // a caller would only pass to it package versions that were returned + // by a previousc all to AvailableVersions, and therefore a missing + // object ought to be valid provider/version but an unsupported + // platform. + if want, ok := err.(ErrPlatformNotSupported); !ok { + t.Fatalf("wrong error type:\ngot: %T\nwant: %T", err, want) + } + }) +} + +func TestMultiSourceSelector(t *testing.T) { + emptySource := NewMockSource(nil) + + tests := map[string]struct { + Selector MultiSourceSelector + Provider addrs.Provider + WantMatch bool + }{ + "default provider with no constraints": { + MultiSourceSelector{ + Source: emptySource, + }, + addrs.NewDefaultProvider("foo"), + true, + }, + "built-in provider with no constraints": { + MultiSourceSelector{ + Source: emptySource, + }, + addrs.NewBuiltInProvider("bar"), + true, + }, + + // Include constraints + "default provider with include constraint that matches it exactly": { + MultiSourceSelector{ + Source: emptySource, + Include: mustParseMultiSourceMatchingPatterns("hashicorp/foo"), + }, + addrs.NewDefaultProvider("foo"), + true, + }, + "default provider with include constraint that matches it via type wildcard": { + MultiSourceSelector{ + Source: emptySource, + Include: mustParseMultiSourceMatchingPatterns("hashicorp/*"), + }, + addrs.NewDefaultProvider("foo"), + true, + }, + "default provider with include constraint that matches it via namespace wildcard": { + MultiSourceSelector{ + Source: emptySource, + Include: mustParseMultiSourceMatchingPatterns("*/*"), + }, + addrs.NewDefaultProvider("foo"), + true, + }, + "built-in provider with exact include constraint that does not match it": { + MultiSourceSelector{ + Source: emptySource, + Include: mustParseMultiSourceMatchingPatterns("hashicorp/foo"), + }, + addrs.NewBuiltInProvider("bar"), + false, + }, + "built-in provider with type-wild include constraint that does not match it": { + MultiSourceSelector{ + Source: emptySource, + Include: mustParseMultiSourceMatchingPatterns("hashicorp/*"), + }, + addrs.NewBuiltInProvider("bar"), + false, + }, + "built-in provider with namespace-wild include constraint that does not match it": { + MultiSourceSelector{ + Source: emptySource, + Include: mustParseMultiSourceMatchingPatterns("*/*"), + }, + // Doesn't match because builtin providers are in "terraform.io", + // but a pattern with no hostname is for registry.terraform.io. + addrs.NewBuiltInProvider("bar"), + false, + }, + "built-in provider with include constraint that matches it via type wildcard": { + MultiSourceSelector{ + Source: emptySource, + Include: mustParseMultiSourceMatchingPatterns("terraform.io/builtin/*"), + }, + addrs.NewBuiltInProvider("bar"), + true, + }, + + // Exclude constraints + "default provider with exclude constraint that matches it exactly": { + MultiSourceSelector{ + Source: emptySource, + Exclude: mustParseMultiSourceMatchingPatterns("hashicorp/foo"), + }, + addrs.NewDefaultProvider("foo"), + false, + }, + "default provider with exclude constraint that matches it via type wildcard": { + MultiSourceSelector{ + Source: emptySource, + Exclude: mustParseMultiSourceMatchingPatterns("hashicorp/*"), + }, + addrs.NewDefaultProvider("foo"), + false, + }, + "default provider with exact exclude constraint that doesn't match it": { + MultiSourceSelector{ + Source: emptySource, + Exclude: mustParseMultiSourceMatchingPatterns("hashicorp/bar"), + }, + addrs.NewDefaultProvider("foo"), + true, + }, + + // Both include and exclude in a single selector + "default provider with exclude wildcard overriding include exact": { + MultiSourceSelector{ + Source: emptySource, + Include: mustParseMultiSourceMatchingPatterns("hashicorp/foo"), + Exclude: mustParseMultiSourceMatchingPatterns("hashicorp/*"), + }, + addrs.NewDefaultProvider("foo"), + false, + }, + "default provider with exclude wildcard overriding irrelevant include exact": { + MultiSourceSelector{ + Source: emptySource, + Include: mustParseMultiSourceMatchingPatterns("hashicorp/bar"), + Exclude: mustParseMultiSourceMatchingPatterns("hashicorp/*"), + }, + addrs.NewDefaultProvider("foo"), + false, + }, + "default provider with exclude exact overriding include wildcard": { + MultiSourceSelector{ + Source: emptySource, + Include: mustParseMultiSourceMatchingPatterns("hashicorp/*"), + Exclude: mustParseMultiSourceMatchingPatterns("hashicorp/foo"), + }, + addrs.NewDefaultProvider("foo"), + false, + }, + "default provider with irrelevant exclude exact overriding include wildcard": { + MultiSourceSelector{ + Source: emptySource, + Include: mustParseMultiSourceMatchingPatterns("hashicorp/*"), + Exclude: mustParseMultiSourceMatchingPatterns("hashicorp/bar"), + }, + addrs.NewDefaultProvider("foo"), + true, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + t.Logf("include: %s", test.Selector.Include) + t.Logf("exclude: %s", test.Selector.Exclude) + t.Logf("provider: %s", test.Provider) + got := test.Selector.CanHandleProvider(test.Provider) + want := test.WantMatch + if got != want { + t.Errorf("wrong result %t; want %t", got, want) + } + }) + } +} + +func mustParseMultiSourceMatchingPatterns(strs ...string) MultiSourceMatchingPatterns { + ret, err := ParseMultiSourceMatchingPatterns(strs) + if err != nil { + panic(err) + } + return ret +}