/* Copyright 2023 Google LLC Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package bzlmod import ( "reflect" "strconv" "testing" "github.com/bazelbuild/buildtools/build" ) const proxiesNoModuleHeader = `` const proxiesModuleNameHeader = ` module(name = "name") ` const proxiesModuleRepoNameHeader = ` module(name = "name", repo_name = "repo_name") ` const proxiesBody = ` prox1 = use_extension("@name//bzl:extensions.bzl", "ext") prox1.use(name = "foo") isolated_prox1 = use_extension("@name//bzl:extensions.bzl", "ext", dev_dependency = False, isolate = True) isolated_prox1.use(name = "foo") prox2 = use_extension("@name//bzl:extensions.bzl", "ext", dev_dependency = True) prox2.use(list = ["foo", "bar"]) # some comment prox3 = use_extension("@repo_name//bzl:extensions.bzl", "ext") prox3.use(label = "@dep//:bar") isolated_prox2 = use_extension("@name//bzl:extensions.bzl", "ext", dev_dependency = True, isolate = True) isolated_prox2.use(name = "foo") prox4 = use_extension("@repo_name//bzl:extensions.bzl", "ext", dev_dependency = True) prox4.use(dict = {"foo": "bar"}) isolated_prox3 = use_extension("@name//bzl:extensions.bzl", "ext", dev_dependency = True, isolate = True) isolated_prox3.use(name = "foo") prox5 = use_extension("@//bzl:extensions.bzl", "ext") prox5.use(name = "foo") prox6 = use_extension("@//bzl:extensions.bzl", "ext", dev_dependency = True) prox6.use(list = ["foo", "bar"]) prox7 = use_extension("//bzl:extensions.bzl", "ext", dev_dependency = False) prox7.use(label = "@foo//:bar") isolated_prox4 = use_extension("@name//bzl:extensions.bzl", "ext", isolate = True) isolated_prox4.use(name = "foo") prox8 = use_extension("//bzl:extensions.bzl", "ext", dev_dependency = True) prox8.use(dict = {"foo": "bar"}) prox9 = use_extension( # comment "@dep//:extensions.bzl", "other_ext") prox9.use(label = "@name//:bar") prox10 = use_extension("@dep//:extensions.bzl", "other_ext", dev_dependency = bool(1)) prox10.use(dict = {"foo": "bar"}) ` func TestProxies(t *testing.T) { for i, tc := range []struct { content string extBzlFiles []string extName string dev bool expectedProxies []string }{ { proxiesNoModuleHeader + proxiesBody, []string{"//bzl:extensions.bzl", "@//bzl:extensions.bzl"}, "ext", false, []string{"prox5", "prox7"}, }, { proxiesNoModuleHeader + proxiesBody, []string{"//bzl:extensions.bzl", "@//bzl:extensions.bzl"}, "ext", true, []string{"prox6", "prox8"}, }, { proxiesModuleNameHeader + proxiesBody, []string{"//bzl:extensions.bzl", "@//bzl:extensions.bzl", "@name//bzl:extensions.bzl"}, "ext", false, []string{"prox1", "prox5", "prox7"}, }, { proxiesModuleNameHeader + proxiesBody, []string{"//bzl:extensions.bzl", "@//bzl:extensions.bzl", "@name//bzl:extensions.bzl"}, "ext", true, []string{"prox2", "prox6", "prox8"}, }, { proxiesModuleRepoNameHeader + proxiesBody, []string{"//bzl:extensions.bzl", "@//bzl:extensions.bzl", "@repo_name//bzl:extensions.bzl"}, "ext", false, []string{"prox3", "prox5", "prox7"}, }, { proxiesModuleRepoNameHeader + proxiesBody, []string{"//bzl:extensions.bzl", "@//bzl:extensions.bzl", "@repo_name//bzl:extensions.bzl"}, "ext", true, []string{"prox4", "prox6", "prox8"}, }, { proxiesModuleRepoNameHeader + proxiesBody, []string{"@name//bzl:extensions.bzl"}, "ext", false, []string{"prox1"}, }, { proxiesModuleRepoNameHeader + proxiesBody, []string{"@dep//:extensions.bzl"}, "other_ext", false, []string{"prox9"}, }, { proxiesModuleRepoNameHeader + proxiesBody, []string{"@dep//:extensions.bzl"}, "other_ext", true, []string{"prox10"}, }, } { t.Run(strconv.Itoa(i), func(t *testing.T) { for _, extBzlFile := range tc.extBzlFiles { t.Run("label_"+extBzlFile, func(t *testing.T) { f, err := build.ParseModule("MODULE.bazel", []byte(tc.content)) if err != nil { t.Fatal(err) } actualProxies := Proxies(f, extBzlFile, tc.extName, tc.dev) if !reflect.DeepEqual(actualProxies, tc.expectedProxies) { t.Error("want: ", tc.expectedProxies, ", got: ", actualProxies) } }) } }) } } func TestAllProxies(t *testing.T) { for i, tc := range []struct { proxy string expectedProxies []string }{ { "invalid_proxy", nil, }, { "isolated_prox1", []string{"isolated_prox1"}, }, { "isolated_prox2", []string{"isolated_prox2"}, }, { "isolated_prox3", []string{"isolated_prox3"}, }, { "isolated_prox4", []string{"isolated_prox4"}, }, { "prox1", []string{"prox1", "prox5", "prox7"}, }, { "prox2", []string{"prox2", "prox6", "prox8"}, }, { "prox9", []string{"prox9"}, }, { "prox10", []string{"prox10"}, }, } { t.Run(strconv.Itoa(i), func(t *testing.T) { f, err := build.ParseModule("MODULE.bazel", []byte(proxiesModuleNameHeader+proxiesBody)) if err != nil { t.Fatal(err) } proxies := AllProxies(f, tc.proxy) if !reflect.DeepEqual(proxies, tc.expectedProxies) { t.Error("want: ", tc.expectedProxies, ", got: ", proxies) } }) } } const useReposFile = ` prox1 = use_extension("@mod//bzl:extensions.bzl", "ext") prox1.use() prox1.config() prox2 = use_extension("//bzl:extensions.bzl", "ext") use_repo(prox2) use_repo(prox1, "repo5") prox3 = use_extension("@dep//bzl:extensions.bzl", "ext") prox2_dev = use_extension("//bzl:extensions.bzl", "ext", dev_dependency = True) use_repo(prox1, "repo1") use_repo(prox3, "repo2") use_repo(prox2, "repo3", "repo4") ` func TestUseRepos(t *testing.T) { for i, tc := range []struct { content string proxies []string expectedStmts []int }{ { useReposFile, []string{"prox2"}, []int{4, 10}, }, { useReposFile, []string{"prox1", "prox3"}, []int{5, 8, 9}, }, { useReposFile, []string{"prox2_dev"}, []int{}, }, } { t.Run(strconv.Itoa(i), func(t *testing.T) { f, err := build.ParseModule("MODULE.bazel", []byte(tc.content)) if err != nil { t.Fatal(err) } var expectedUseRepos []*build.CallExpr for _, stmt := range tc.expectedStmts { expectedUseRepos = append(expectedUseRepos, f.Stmt[stmt].(*build.CallExpr)) } actualUseRepos := UseRepos(f, tc.proxies) if !reflect.DeepEqual(actualUseRepos, expectedUseRepos) { t.Error("want: ", expectedUseRepos, ", got: ", actualUseRepos) } }) } } func TestNewUseRepos(t *testing.T) { for i, tc := range []struct { content string proxies []string expectedContent string expectedUseRepo int }{ { ``, []string{"prox1"}, ``, -1, }, { `prox1 = use_extension("@module//:lib.bzl", "ext")`, []string{"prox2"}, `prox1 = use_extension("@module//:lib.bzl", "ext") `, -1, }, { `prox1 = use_extension("@mod//bzl:extensions.bzl", "ext", dev_dependency = True)`, []string{"prox1"}, `prox1 = use_extension("@mod//bzl:extensions.bzl", "ext", dev_dependency = True) use_repo(prox1) `, 1, }, { `prox1 = use_extension("@mod//bzl:extensions.bzl", "ext") prox1.config() prox1.download(name = "foo")`, []string{"prox1"}, `prox1 = use_extension("@mod//bzl:extensions.bzl", "ext") prox1.config() prox1.download(name = "foo") use_repo(prox1) `, 3, }, { `go_deps = use_extension("@gazelle//:extensions.bzl", "go_deps") go_deps.from_file(go_mod = "//:go.mod") pull = use_extension("@rules_oci//oci:pull.bzl", "go_deps") pull.oci_pull(name = "distroless_base") `, []string{"go_deps"}, `go_deps = use_extension("@gazelle//:extensions.bzl", "go_deps") go_deps.from_file(go_mod = "//:go.mod") use_repo(go_deps) pull = use_extension("@rules_oci//oci:pull.bzl", "go_deps") pull.oci_pull(name = "distroless_base") `, 2, }, { `go_deps = use_extension("@gazelle//:extensions.bzl", "go_deps") go_deps.from_file(go_mod = "//:go.mod") pull = use_extension("@rules_oci//oci:pull.bzl", "go_deps") `, []string{"go_deps"}, `go_deps = use_extension("@gazelle//:extensions.bzl", "go_deps") go_deps.from_file(go_mod = "//:go.mod") use_repo(go_deps) pull = use_extension("@rules_oci//oci:pull.bzl", "go_deps") `, 2, }, } { t.Run(strconv.Itoa(i), func(t *testing.T) { f, err := build.ParseModule("MODULE.bazel", []byte(tc.content)) if err != nil { t.Fatal(err) } f, actualNewUseRepo := NewUseRepo(f, tc.proxies) actualContent := string(build.Format(f)) if actualNewUseRepo != nil { if !reflect.DeepEqual(actualNewUseRepo, f.Stmt[tc.expectedUseRepo]) { t.Error("want: ", f.Stmt[tc.expectedUseRepo], ", got: ", actualNewUseRepo) } } else { if tc.expectedUseRepo != -1 { t.Error("wanted a nil new use_repo") } } if !reflect.DeepEqual(actualContent, tc.expectedContent) { t.Errorf("want:\n%q\ngot:\n%q\n", tc.expectedContent, actualContent) } }) } } func TestAddRepoUsages(t *testing.T) { for i, tc := range []struct { content string repos []string expectedContent string }{ { ``, []string{}, ``, }, { `use_repo(prox)`, []string{"repo2", "repo1"}, `use_repo(prox, "repo1", "repo2") `, }, { `use_repo( prox, )`, []string{"repo2", "repo1"}, `use_repo( prox, "repo1", "repo2", ) `, }, { `use_repo(prox, "repo2")`, []string{"repo2", "repo1"}, `use_repo(prox, "repo1", "repo2") `, }, { `use_repo( prox, "repo2", )`, []string{"repo2", "repo1"}, `use_repo( prox, "repo1", "repo2", ) `, }, { `use_repo(prox, "repo1") use_repo( prox2, my_repo = "repo2", "repo5", ) use_repo(prox, "repo3")`, []string{"repo3", "repo2", "repo4"}, `use_repo(prox, "repo1") use_repo( prox2, "repo5", my_repo = "repo2", ) use_repo(prox, "repo3", "repo4") `, }, } { t.Run(strconv.Itoa(i), func(t *testing.T) { f, err := build.ParseModule("MODULE.bazel", []byte(tc.content)) if err != nil { t.Fatal(err) } var useRepos []*build.CallExpr for _, stmt := range f.Stmt { useRepos = append(useRepos, stmt.(*build.CallExpr)) } AddRepoUsages(useRepos, tc.repos...) actualContent := string(build.Format(f)) if !reflect.DeepEqual(actualContent, tc.expectedContent) { t.Errorf("want:\n%q\ngot:\n%q\n", tc.expectedContent, actualContent) } }) } } func TestRemoveRepoUsages(t *testing.T) { for i, tc := range []struct { content string repos []string expectedContent string }{ { ``, []string{"repo1"}, ``, }, { `use_repo(prox)`, []string{"repo2", "repo1"}, `use_repo(prox) `, }, { `use_repo( prox, )`, []string{"repo2", "repo1"}, `use_repo( prox, ) `, }, { `use_repo(prox, "repo2")`, []string{"repo2", "repo1"}, `use_repo(prox) `, }, { `use_repo( prox, "repo2", )`, []string{"repo2", "repo1"}, `use_repo(prox) `, }, { `use_repo(prox, "repo1") use_repo( prox2, my_repo = "repo2", "repo5", ) use_repo(prox, "repo3")`, []string{"repo3", "repo2", "repo4"}, `use_repo(prox, "repo1") use_repo( prox2, "repo5", ) use_repo(prox) `, }, } { t.Run(strconv.Itoa(i), func(t *testing.T) { f, err := build.ParseModule("MODULE.bazel", []byte(tc.content)) if err != nil { t.Fatal(err) } var useRepos []*build.CallExpr for _, stmt := range f.Stmt { useRepos = append(useRepos, stmt.(*build.CallExpr)) } RemoveRepoUsages(useRepos, tc.repos...) actualContent := string(build.Format(f)) if !reflect.DeepEqual(actualContent, tc.expectedContent) { t.Errorf("want:\n%q\ngot:\n%q\n", tc.expectedContent, actualContent) } }) } }