blob: 4753b975171b9e6bad2736e2f61b2a407e23a2a2 [file]
/*
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)
unused = 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"})
prox11 = use_extension("extension.bzl", "ext")
prox12 = use_extension(":extension.bzl", "ext")
prox13 = use_extension("//:extension.bzl", "ext")
prox14 = use_extension("@name//:extension.bzl", "ext")
prox15 = use_extension("@repo_name//:extension.bzl", "ext")
`
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"},
},
{
proxiesModuleNameHeader + proxiesBody,
[]string{"//:extension.bzl", "@//:extension.bzl"},
"ext",
false,
[]string{"prox11", "prox12", "prox13", "prox14"},
},
{
proxiesModuleRepoNameHeader + proxiesBody,
[]string{"//:extension.bzl", "@//:extension.bzl"},
"ext",
false,
[]string{"prox11", "prox12", "prox13", "prox15"},
},
} {
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)
}
})
}
}