/* Copyright 2016 The Bazel Authors. All rights reserved. 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 http://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 merger_test import ( "path/filepath" "testing" "github.com/bazelbuild/bazel-gazelle/language" golang "github.com/bazelbuild/bazel-gazelle/language/go" "github.com/bazelbuild/bazel-gazelle/language/proto" "github.com/bazelbuild/bazel-gazelle/merger" "github.com/bazelbuild/bazel-gazelle/rule" ) // should fix // * updated srcs from new // * data and size preserved from old // * load stmt fixed to those in use and sorted type testCase struct { desc, previous, current, empty, expected string } var testCases = []testCase{ { desc: "basic functionality", previous: ` load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library", "go_prefix", "go_test") go_prefix("github.com/jr_hacker/tools") go_library( name = "go_default_library", srcs = [ "lex.go", "print.go", "debug.go", ], ) go_test( name = "go_default_test", size = "small", srcs = [ "gen_test.go", # keep "parse_test.go", ], data = glob(["testdata/*"]), embed = [":go_default_library"], ) `, current: ` load("@io_bazel_rules_go//go:def.bzl", "go_test", "go_library") go_prefix("") go_library( name = "go_default_library", srcs = [ "lex.go", "print.go", ], ) go_test( name = "go_default_test", srcs = [ "parse_test.go", "print_test.go", ], embed = [":go_default_library"], ) `, expected: ` load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_prefix", "go_test") go_prefix("github.com/jr_hacker/tools") go_library( name = "go_default_library", srcs = [ "lex.go", "print.go", ], ) go_test( name = "go_default_test", size = "small", srcs = [ "gen_test.go", # keep "parse_test.go", "print_test.go", ], data = glob(["testdata/*"]), embed = [":go_default_library"], ) `, }, { desc: "merge dicts", previous: ` load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "go_default_library", srcs = select({ "darwin_amd64": [ "foo_darwin_amd64.go", # keep "bar_darwin_amd64.go", ], "linux_arm": [ "foo_linux_arm.go", # keep "bar_linux_arm.go", ], }), ) `, current: ` load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "go_default_library", srcs = select({ "linux_arm": ["baz_linux_arm.go"], "darwin_amd64": ["baz_darwin_amd64.go"], "//conditions:default": [], }), ) `, expected: ` load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "go_default_library", srcs = select({ "darwin_amd64": [ "baz_darwin_amd64.go", "foo_darwin_amd64.go", # keep ], "linux_arm": [ "baz_linux_arm.go", "foo_linux_arm.go", # keep ], "//conditions:default": [], }), ) `, }, { desc: "merge old dict with gen list", previous: ` load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "go_default_library", srcs = select({ "linux_arm": [ "foo_linux_arm.go", # keep "bar_linux_arm.go", # keep ], "darwin_amd64": [ "bar_darwin_amd64.go", ], }), ) `, current: ` load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "go_default_library", srcs = ["baz.go"], ) `, expected: ` load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "go_default_library", srcs = [ "baz.go", ] + select({ "linux_arm": [ "bar_linux_arm.go", # keep "foo_linux_arm.go", # keep ], }), ) `, }, { desc: "merge old list with gen dict", previous: ` load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "go_default_library", srcs = [ "foo.go", # keep "bar.go", # keep ], ) `, current: ` load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "go_default_library", srcs = select({ "linux_arm": [ "foo_linux_arm.go", "bar_linux_arm.go", ], "darwin_amd64": [ "bar_darwin_amd64.go", ], "//conditions:default": [], }), ) `, expected: ` load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "go_default_library", srcs = [ "bar.go", # keep "foo.go", # keep ] + select({ "linux_arm": [ "bar_linux_arm.go", "foo_linux_arm.go", ], "darwin_amd64": [ "bar_darwin_amd64.go", ], "//conditions:default": [], }), ) `, }, { desc: "merge old list and dict with gen list and dict", previous: ` load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "go_default_library", srcs = [ "foo.go", # keep "bar.go", ] + select({ "linux_arm": [ "foo_linux_arm.go", # keep ], "//conditions:default": [], }), ) `, current: ` load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "go_default_library", srcs = ["baz.go"] + select({ "linux_arm": ["bar_linux_arm.go"], "darwin_amd64": ["foo_darwin_amd64.go"], "//conditions:default": [], }), ) `, expected: ` load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "go_default_library", srcs = [ "baz.go", "foo.go", # keep ] + select({ "darwin_amd64": ["foo_darwin_amd64.go"], "linux_arm": [ "bar_linux_arm.go", "foo_linux_arm.go", # keep ], "//conditions:default": [], }), ) `, }, { desc: "os and arch", previous: ` load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "go_default_library", srcs = [ "generic_1.go", ] + select({ "@io_bazel_rules_go//go/platform:linux": [ "os_linux.go", # keep ], "//conditions:default": [], }), ) `, current: ` load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "go_default_library", srcs = [ "generic_2.go", ] + select({ "@io_bazel_rules_go//go/platform:amd64": ["arch_amd64.go"], "//conditions:default": [], }), ) `, expected: ` load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "go_default_library", srcs = [ "generic_2.go", ] + select({ "@io_bazel_rules_go//go/platform:linux": [ "os_linux.go", # keep ], "//conditions:default": [], }) + select({ "@io_bazel_rules_go//go/platform:amd64": ["arch_amd64.go"], "//conditions:default": [], }), ) `, }, { desc: "merge error keeps old", previous: ` load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "go_default_library", srcs = glob(["*.go"]), ) `, current: ` load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "go_default_library", srcs = ["foo.go"], ) `, expected: ` load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "go_default_library", srcs = glob(["*.go"]), ) `, }, { desc: "delete empty list", previous: ` load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "go_default_library", srcs = ["deleted.go"], ) `, current: ` load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "go_default_library", srcs = select({ "linux_arm": ["foo_linux_arm.go"], }), ) `, expected: ` load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "go_default_library", srcs = select({ "linux_arm": ["foo_linux_arm.go"], }), ) `, }, { desc: "delete empty dict", previous: ` load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "go_default_library", srcs = select({ "linux_arm": ["foo_linux_arm.go"], "//conditions:default": [], }), ) `, current: ` load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "go_default_library", srcs = ["foo.go"], ) `, expected: ` load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "go_default_library", srcs = ["foo.go"], ) `, }, { desc: "delete empty attr", previous: ` load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "go_default_library", srcs = ["foo.go"], embed = ["deleted"], ) `, current: ` load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "go_default_library", srcs = ["foo.go"], ) `, expected: ` load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "go_default_library", srcs = ["foo.go"], ) `, }, { desc: "delete empty lists", previous: ` load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "go_default_library", srcs = ["foo.go"], embed = ["deleted"], ) `, current: ` load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "go_default_library", srcs = ["foo.go"], embed = [], ) `, expected: ` load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "go_default_library", srcs = ["foo.go"], ) `, }, { desc: "merge comments", previous: ` # load load("@io_bazel_rules_go//go:def.bzl", "go_library") # rule go_library( # unmerged attr name = "go_default_library", # merged attr srcs = ["foo.go"], ) `, current: ` load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "go_default_library", srcs = ["foo.go"], ) `, expected: ` # load load("@io_bazel_rules_go//go:def.bzl", "go_library") # rule go_library( # unmerged attr name = "go_default_library", # merged attr srcs = ["foo.go"], ) `, }, { desc: "preserve comments", previous: ` load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "go_default_library", srcs = [ "a.go", # preserve "b.go", # comments ], ) `, current: ` load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "go_default_library", srcs = ["a.go", "b.go"], ) `, expected: ` load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "go_default_library", srcs = [ "a.go", # preserve "b.go", # comments ], ) `, }, { desc: "merge copts and clinkopts", previous: ` load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "cgo_default_library", copts = [ "-O0", "-g", # keep ], clinkopts = [ "-lX11", ], ) `, current: ` load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "cgo_default_library", cgo = True, copts = [ "-O2", ], clinkopts = [ "-lpng", ], ) `, expected: ` load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "cgo_default_library", cgo = True, clinkopts = [ "-lpng", ], copts = [ "-g", # keep "-O2", ], ) `, }, { desc: "keep scalar attr", previous: ` load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "go_default_library", embed = [":lib"], # keep ) `, current: ` load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "go_default_library", ) `, expected: ` load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "go_default_library", embed = [":lib"], # keep ) `, }, { desc: "don't delete list with keep", previous: ` load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "go_default_library", srcs = [ "one.go", # keep ], ) `, current: ` load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "go_default_library", ) `, expected: ` load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "go_default_library", srcs = [ "one.go", # keep ], ) `, }, { desc: "keep list multiline", previous: ` load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "go_default_library", srcs = [ "one.go", # keep "two.go", ], ) `, current: ` load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "go_default_library", ) `, expected: ` load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "go_default_library", srcs = [ "one.go", # keep ], ) `, }, { desc: "keep dict list multiline", previous: ` load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "go_default_library", srcs = select({ "darwin_amd64": [ "one_darwin.go", # keep ], "linux_arm": [ "one_linux.go", # keep "two_linux.go", ], }), ) `, current: ` load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "go_default_library", ) `, expected: ` load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "go_default_library", srcs = select({ "darwin_amd64": [ "one_darwin.go", # keep ], "linux_arm": [ "one_linux.go", # keep ], }), ) `, }, { desc: "keep prevents delete", previous: ` load("@io_bazel_rules_go//go:def.bzl", "go_library") # keep go_library( name = "go_default_library", srcs = ["lib.go"], ) `, empty: ` go_library(name = "go_default_library") `, expected: ` load("@io_bazel_rules_go//go:def.bzl", "go_library") # keep go_library( name = "go_default_library", srcs = ["lib.go"], ) `, }, { desc: "keep prevents merge", previous: ` load("@io_bazel_rules_go//go:def.bzl", "go_library") # keep go_library( name = "go_default_library", srcs = ["old.go"], ) `, current: ` go_library( name = "go_default_library", srcs = ["new.go"], ) `, expected: ` load("@io_bazel_rules_go//go:def.bzl", "go_library") # keep go_library( name = "go_default_library", srcs = ["old.go"], ) `, }, { desc: "delete empty rule", previous: ` load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") go_library( name = "go_default_library", srcs = ["lib.go"], ) go_binary( name = "old", srcs = ["bin.go"], embed = [":go_default_library"], importpath = "foo", ) `, current: ` load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "go_default_library", srcs = ["lib.go"], ) `, empty: ` go_binary(name = "old") `, expected: ` load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "go_default_library", srcs = ["lib.go"], ) `, }, { desc: "don't delete kept rule", previous: ` load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "go_default_library", srcs = [ "lib.go", # keep ], ) `, empty: `go_library(name = "go_default_library")`, expected: ` load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "go_default_library", srcs = [ "lib.go", # keep ], ) `, }, { desc: "match and rename", previous: ` load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library") go_binary( name = "custom_bin", embed = [":custom_library"], ) go_library( name = "custom_library", embed = [":custom_proto_library"], importpath = "example.com/repo/foo", ) go_proto_library( name = "custom_proto_library", importpath = "example.com/repo/foo", proto = ":foo_proto", ) `, current: ` go_binary( name = "bin", srcs = ["bin.go"], embed = [":go_default_library"], ) go_library( name = "go_default_library", srcs = ["lib.go"], embed = [":foo_proto_library"], importpath = "example.com/repo/foo", ) go_proto_library( name = "foo_proto_library", importpath = "example.com/repo/foo", proto = ":foo_proto", ) `, expected: ` load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library") go_binary( name = "custom_bin", srcs = ["bin.go"], embed = [":custom_library"], ) go_library( name = "custom_library", srcs = ["lib.go"], embed = [":custom_proto_library"], importpath = "example.com/repo/foo", ) go_proto_library( name = "custom_proto_library", importpath = "example.com/repo/foo", proto = ":foo_proto", ) `, }, { desc: "struct macro", previous: `load("@bazel_skylib//lib:selects.bzl", "selects") selects.config_setting_group( name = "a", match_any = [ "//:config_a", "//:config_b", ], ) `, current: `load("@bazel_skylib//lib:selects.bzl", "selects") selects.config_setting_group( name = "a", match_any = [ "//:config_c", "//:config_d", ], ) `, expected: `load("@bazel_skylib//lib:selects.bzl", "selects") selects.config_setting_group( name = "a", match_any = [ "//:config_c", "//:config_d", ], ) `, }, } func TestMergeFile(t *testing.T) { for _, tc := range testCases { t.Run(tc.desc, func(t *testing.T) { genFile, err := rule.LoadData(filepath.Join("current", "BUILD.bazel"), "", []byte(tc.current)) if err != nil { t.Fatalf("%s: %v", tc.desc, err) } f, err := rule.LoadData(filepath.Join("previous", "BUILD.bazel"), "", []byte(tc.previous)) if err != nil { t.Fatalf("%s: %v", tc.desc, err) } emptyFile, err := rule.LoadData(filepath.Join("empty", "BUILD.bazel"), "", []byte(tc.empty)) if err != nil { t.Fatalf("%s: %v", tc.desc, err) } merger.MergeFile(f, emptyFile.Rules, genFile.Rules, merger.PreResolve, testKinds) merger.FixLoads(f, testLoads) want := tc.expected if len(want) > 0 && want[0] == '\n' { want = want[1:] } if got := string(f.Format()); got != want { t.Fatalf("%s: got %s; want %s", tc.desc, got, want) } }) } } var ( testKinds map[string]rule.KindInfo testLoads []rule.LoadInfo ) func init() { testKinds = make(map[string]rule.KindInfo) langs := []language.Language{proto.NewLanguage(), golang.NewLanguage()} for _, lang := range langs { for kind, info := range lang.Kinds() { testKinds[kind] = info } loads := lang.(language.ModuleAwareLanguage).ApparentLoads(func(s string) string { return "" }) testLoads = append(testLoads, loads...) } // add skylib defs testLoads = append(testLoads, rule.LoadInfo{ Name: "@bazel_skylib//lib:selects.bzl", Symbols: []string{ "selects", }, }) testKinds["selects.config_setting_group"] = rule.KindInfo{ MergeableAttrs: map[string]bool{ "match_all": true, "match_any": true, }, } } func TestMatch(t *testing.T) { for _, tc := range []struct { desc, gen, old string wantIndex int wantError bool }{ { desc: "no_match", gen: `go_library(name = "lib")`, wantIndex: -1, }, { desc: "name_match", gen: `go_library(name = "lib")`, old: `go_library(name = "lib", srcs = ["lib.go"])`, wantIndex: 0, }, { desc: "name_match_kind_different", gen: `go_library(name = "lib")`, old: `cc_library(name = "lib")`, wantError: true, }, { desc: "attr_match", gen: `go_library(name = "x", importpath = "foo")`, old: `go_library(name = "y", importpath = "foo")`, wantIndex: 0, }, { desc: "multiple_attr_match", gen: `go_library(name = "x", importpath = "foo")`, old: ` go_library(name = "y", importpath = "foo") go_library(name = "z", importpath = "foo") `, wantError: true, }, { desc: "any_match", gen: `go_binary(name = "x")`, old: `go_binary(name = "y")`, wantIndex: 0, }, { desc: "multiple_any_match", gen: `go_binary(name = "x")`, old: ` go_binary(name = "y") go_binary(name = "z") `, wantError: true, }, { desc: "srcs match", gen: `proto_library(name = "proto1", srcs = ["foo.proto", "bar.proto"])`, old: `proto_library(name = "proto2", srcs = ["bar.proto", "foo.proto"])`, wantIndex: 0, }, { desc: "importpath match", gen: `go_proto_library(name = "go_proto1", importpath="example.com/foo")`, old: `go_proto_library(name = "go_proto2", importpath="example.com/foo")`, }, { desc: "struct macro match", gen: `selects.config_setting_group(name = "conf_group_1", match_any = ["//:config_a", "//:config_b"])`, old: `selects.config_setting_group(name = "conf_group_1", match_any = ["//:config_c", "//:config_d"])`, wantIndex: 0, }, } { t.Run(tc.desc, func(t *testing.T) { genFile, err := rule.LoadData(filepath.Join("gen", "BUILD.bazel"), "", []byte(tc.gen)) if err != nil { t.Fatal(err) } oldFile, err := rule.LoadData(filepath.Join("old", "BUILD.bazel"), "", []byte(tc.old)) if err != nil { t.Fatal(err) } r := genFile.Rules[0] info := testKinds[r.Kind()] if got, gotErr := merger.Match(oldFile.Rules, r, info); gotErr != nil { if !tc.wantError { t.Errorf("unexpected error: %v", gotErr) } } else if tc.wantError { t.Error("unexpected success") } else if got == nil && tc.wantIndex >= 0 { t.Errorf("got nil; want index %d", tc.wantIndex) } else if got != nil && got.Index() != tc.wantIndex { t.Fatalf("got index %d ; want %d", got.Index(), tc.wantIndex) } }) } }