// Copyright 2021 Google Inc. 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 bazel import ( "reflect" "strings" "testing" "github.com/google/blueprint/proptools" ) func TestUniqueBazelLabels(t *testing.T) { testCases := []struct { originalLabels []Label expectedUniqueLabels []Label }{ { originalLabels: []Label{ {Label: "a"}, {Label: "b"}, {Label: "a"}, {Label: "c"}, }, expectedUniqueLabels: []Label{ {Label: "a"}, {Label: "b"}, {Label: "c"}, }, }, } for _, tc := range testCases { actualUniqueLabels := UniqueSortedBazelLabels(tc.originalLabels) if !reflect.DeepEqual(tc.expectedUniqueLabels, actualUniqueLabels) { t.Fatalf("Expected %v, got %v", tc.expectedUniqueLabels, actualUniqueLabels) } } } func TestSubtractStrings(t *testing.T) { testCases := []struct { haystack []string needle []string expectedResult []string }{ { haystack: []string{ "a", "b", "c", }, needle: []string{ "a", }, expectedResult: []string{ "b", "c", }, }, } for _, tc := range testCases { actualResult := SubtractStrings(tc.haystack, tc.needle) if !reflect.DeepEqual(tc.expectedResult, actualResult) { t.Fatalf("Expected %v, got %v", tc.expectedResult, actualResult) } } } func TestSubtractBazelLabelList(t *testing.T) { testCases := []struct { haystack LabelList needle LabelList expectedResult LabelList }{ { haystack: LabelList{ Includes: []Label{ {Label: "a"}, {Label: "b"}, {Label: "c"}, }, Excludes: []Label{ {Label: "x"}, {Label: "y"}, {Label: "z"}, }, }, needle: LabelList{ Includes: []Label{ {Label: "a"}, }, Excludes: []Label{ {Label: "z"}, }, }, // NOTE: Excludes are intentionally not subtracted expectedResult: LabelList{ Includes: []Label{ {Label: "b"}, {Label: "c"}, }, Excludes: []Label{ {Label: "x"}, {Label: "y"}, {Label: "z"}, }, }, }, } for _, tc := range testCases { actualResult := SubtractBazelLabelList(tc.haystack, tc.needle) if !reflect.DeepEqual(tc.expectedResult, actualResult) { t.Fatalf("Expected %v, got %v", tc.expectedResult, actualResult) } } } func TestFirstUniqueBazelLabelList(t *testing.T) { testCases := []struct { originalLabelList LabelList expectedUniqueLabelList LabelList }{ { originalLabelList: LabelList{ Includes: []Label{ {Label: "a"}, {Label: "b"}, {Label: "a"}, {Label: "c"}, }, Excludes: []Label{ {Label: "x"}, {Label: "x"}, {Label: "y"}, {Label: "z"}, }, }, expectedUniqueLabelList: LabelList{ Includes: []Label{ {Label: "a"}, {Label: "b"}, {Label: "c"}, }, Excludes: []Label{ {Label: "x"}, {Label: "y"}, {Label: "z"}, }, }, }, } for _, tc := range testCases { actualUniqueLabelList := FirstUniqueBazelLabelList(tc.originalLabelList) if !reflect.DeepEqual(tc.expectedUniqueLabelList, actualUniqueLabelList) { t.Fatalf("Expected %v, got %v", tc.expectedUniqueLabelList, actualUniqueLabelList) } } } func TestUniqueSortedBazelLabelList(t *testing.T) { testCases := []struct { originalLabelList LabelList expectedUniqueLabelList LabelList }{ { originalLabelList: LabelList{ Includes: []Label{ {Label: "c"}, {Label: "a"}, {Label: "a"}, {Label: "b"}, }, Excludes: []Label{ {Label: "y"}, {Label: "z"}, {Label: "x"}, {Label: "x"}, }, }, expectedUniqueLabelList: LabelList{ Includes: []Label{ {Label: "a"}, {Label: "b"}, {Label: "c"}, }, Excludes: []Label{ {Label: "x"}, {Label: "y"}, {Label: "z"}, }, }, }, } for _, tc := range testCases { actualUniqueLabelList := UniqueSortedBazelLabelList(tc.originalLabelList) if !reflect.DeepEqual(tc.expectedUniqueLabelList, actualUniqueLabelList) { t.Fatalf("Expected %v, got %v", tc.expectedUniqueLabelList, actualUniqueLabelList) } } } func makeLabels(labels ...string) []Label { var ret []Label for _, l := range labels { ret = append(ret, Label{Label: l}) } return ret } func makeLabelList(includes, excludes []string) LabelList { return LabelList{ Includes: makeLabels(includes...), Excludes: makeLabels(excludes...), } } func TestResolveExcludes(t *testing.T) { attr := LabelListAttribute{ Value: makeLabelList( []string{ "all_include", "arm_exclude", "android_exclude", "product_config_exclude", }, []string{"all_exclude"}, ), ConfigurableValues: configurableLabelLists{ ArchConfigurationAxis: labelListSelectValues{ "arm": makeLabelList([]string{}, []string{"arm_exclude"}), "x86": makeLabelList([]string{"x86_include"}, []string{}), ConditionsDefaultConfigKey: makeLabelList([]string{"default_include"}, []string{}), }, OsConfigurationAxis: labelListSelectValues{ "android": makeLabelList([]string{}, []string{"android_exclude"}), "linux": makeLabelList([]string{"linux_include"}, []string{}), }, OsArchConfigurationAxis: labelListSelectValues{ "linux_x86": makeLabelList([]string{"linux_x86_include"}, []string{}), }, ProductVariableConfigurationAxis("product_with_defaults", NoConfigAxis): labelListSelectValues{ "a": makeLabelList([]string{}, []string{"not_in_value"}), "b": makeLabelList([]string{"b_val"}, []string{}), "c": makeLabelList([]string{"c_val"}, []string{}), ConditionsDefaultConfigKey: makeLabelList([]string{"c_val", "default", "default2", "all_exclude"}, []string{}), }, ProductVariableConfigurationAxis("product_only_with_excludes", NoConfigAxis): labelListSelectValues{ "a": makeLabelList([]string{}, []string{"product_config_exclude"}), }, }, } attr.ResolveExcludes() expectedBaseIncludes := []Label{{Label: "all_include"}} if !reflect.DeepEqual(expectedBaseIncludes, attr.Value.Includes) { t.Errorf("Expected Value includes %q, got %q", attr.Value.Includes, expectedBaseIncludes) } var nilLabels []Label expectedConfiguredIncludes := map[ConfigurationAxis]map[string][]Label{ ArchConfigurationAxis: { "arm": nilLabels, "x86": makeLabels("arm_exclude", "x86_include"), ConditionsDefaultConfigKey: makeLabels("arm_exclude", "default_include"), }, OsConfigurationAxis: { "android": nilLabels, "linux": makeLabels("android_exclude", "linux_include"), ConditionsDefaultConfigKey: makeLabels("android_exclude"), }, OsArchConfigurationAxis: { "linux_x86": makeLabels("linux_x86_include"), ConditionsDefaultConfigKey: nilLabels, }, ProductVariableConfigurationAxis("product_with_defaults", NoConfigAxis): { "a": nilLabels, "b": makeLabels("b_val"), "c": makeLabels("c_val"), ConditionsDefaultConfigKey: makeLabels("c_val", "default", "default2"), }, ProductVariableConfigurationAxis("product_only_with_excludes", NoConfigAxis): { "a": nilLabels, ConditionsDefaultConfigKey: makeLabels("product_config_exclude"), }, } for _, axis := range attr.SortedConfigurationAxes() { if _, ok := expectedConfiguredIncludes[axis]; !ok { t.Errorf("Found unexpected axis %s", axis) continue } expectedForAxis := expectedConfiguredIncludes[axis] gotForAxis := attr.ConfigurableValues[axis] if len(expectedForAxis) != len(gotForAxis) { t.Errorf("Expected %d configs for %s, got %d: %s", len(expectedForAxis), axis, len(gotForAxis), gotForAxis) } for config, value := range gotForAxis { if expected, ok := expectedForAxis[config]; ok { if !reflect.DeepEqual(expected, value.Includes) { t.Errorf("For %s,\nexpected: %#v\ngot %#v", axis, expected, value.Includes) } } else { t.Errorf("Got unexpected config %q for %s", config, axis) } } } } func TestLabelListAttributePartition(t *testing.T) { testCases := []struct { name string input LabelListAttribute predicated LabelListAttribute unpredicated LabelListAttribute predicate func(label Label) bool }{ { name: "move all to predicated partition", input: MakeLabelListAttribute(makeLabelList( []string{"keep1", "throw1", "keep2", "throw2"}, []string{"keep1", "throw1", "keep2", "throw2"}, )), predicated: MakeLabelListAttribute(makeLabelList( []string{"keep1", "throw1", "keep2", "throw2"}, []string{"keep1", "throw1", "keep2", "throw2"}, )), unpredicated: LabelListAttribute{}, predicate: func(label Label) bool { return true }, }, { name: "move all to unpredicated partition", input: MakeLabelListAttribute(makeLabelList( []string{"keep1", "throw1", "keep2", "throw2"}, []string{"keep1", "throw1", "keep2", "throw2"}, )), predicated: LabelListAttribute{}, unpredicated: MakeLabelListAttribute(makeLabelList( []string{"keep1", "throw1", "keep2", "throw2"}, []string{"keep1", "throw1", "keep2", "throw2"}, )), predicate: func(label Label) bool { return false }, }, { name: "partition includes and excludes", input: MakeLabelListAttribute(makeLabelList( []string{"keep1", "throw1", "keep2", "throw2"}, []string{"keep1", "throw1", "keep2", "throw2"}, )), predicated: MakeLabelListAttribute(makeLabelList( []string{"keep1", "keep2"}, []string{"keep1", "keep2"}, )), unpredicated: MakeLabelListAttribute(makeLabelList( []string{"throw1", "throw2"}, []string{"throw1", "throw2"}, )), predicate: func(label Label) bool { return strings.HasPrefix(label.Label, "keep") }, }, { name: "partition excludes only", input: MakeLabelListAttribute(makeLabelList( []string{}, []string{"keep1", "throw1", "keep2", "throw2"}, )), predicated: MakeLabelListAttribute(makeLabelList( []string{}, []string{"keep1", "keep2"}, )), unpredicated: MakeLabelListAttribute(makeLabelList( []string{}, []string{"throw1", "throw2"}, )), predicate: func(label Label) bool { return strings.HasPrefix(label.Label, "keep") }, }, { name: "partition includes only", input: MakeLabelListAttribute(makeLabelList( []string{"keep1", "throw1", "keep2", "throw2"}, []string{}, )), predicated: MakeLabelListAttribute(makeLabelList( []string{"keep1", "keep2"}, []string{}, )), unpredicated: MakeLabelListAttribute(makeLabelList( []string{"throw1", "throw2"}, []string{}, )), predicate: func(label Label) bool { return strings.HasPrefix(label.Label, "keep") }, }, { name: "empty partition", input: MakeLabelListAttribute(makeLabelList([]string{}, []string{})), predicated: LabelListAttribute{}, unpredicated: LabelListAttribute{}, predicate: func(label Label) bool { return true }, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { predicated, unpredicated := tc.input.Partition(tc.predicate) if !predicated.Value.Equals(tc.predicated.Value) { t.Errorf("expected predicated labels to be %v; got %v", tc.predicated, predicated) } for axis, configs := range predicated.ConfigurableValues { tcConfigs, ok := tc.predicated.ConfigurableValues[axis] if !ok || !reflect.DeepEqual(configs, tcConfigs) { t.Errorf("expected predicated labels to be %v; got %v", tc.predicated, predicated) } } if !unpredicated.Value.Equals(tc.unpredicated.Value) { t.Errorf("expected unpredicated labels to be %v; got %v", tc.unpredicated, unpredicated) } for axis, configs := range unpredicated.ConfigurableValues { tcConfigs, ok := tc.unpredicated.ConfigurableValues[axis] if !ok || !reflect.DeepEqual(configs, tcConfigs) { t.Errorf("expected unpredicated labels to be %v; got %v", tc.unpredicated, unpredicated) } } }) } } // labelAddSuffixForTypeMapper returns a LabelMapper that adds suffix to label name for modules of // typ func labelAddSuffixForTypeMapper(suffix, typ string) LabelMapper { return func(omc OtherModuleContext, label Label) (string, bool) { m, ok := omc.ModuleFromName(label.Label) if !ok { return label.Label, false } mTyp := omc.OtherModuleType(m) if typ == mTyp { return label.Label + suffix, true } return label.Label, false } } func TestPartitionLabelListAttribute(t *testing.T) { testCases := []struct { name string ctx *OtherModuleTestContext labelList LabelListAttribute filters LabelPartitions expected PartitionToLabelListAttribute expectedErrMsg *string }{ { name: "no configurable values", ctx: &OtherModuleTestContext{}, labelList: LabelListAttribute{ Value: makeLabelList([]string{"a.a", "b.b", "c.c", "d.d", "e.e"}, []string{}), }, filters: LabelPartitions{ "A": LabelPartition{Extensions: []string{".a"}}, "B": LabelPartition{Extensions: []string{".b"}}, "C": LabelPartition{Extensions: []string{".c"}}, }, expected: PartitionToLabelListAttribute{ "A": LabelListAttribute{Value: makeLabelList([]string{"a.a"}, []string{})}, "B": LabelListAttribute{Value: makeLabelList([]string{"b.b"}, []string{})}, "C": LabelListAttribute{Value: makeLabelList([]string{"c.c"}, []string{})}, }, }, { name: "no configurable values, remainder partition", ctx: &OtherModuleTestContext{}, labelList: LabelListAttribute{ Value: makeLabelList([]string{"a.a", "b.b", "c.c", "d.d", "e.e"}, []string{}), }, filters: LabelPartitions{ "A": LabelPartition{Extensions: []string{".a"}, Keep_remainder: true}, "B": LabelPartition{Extensions: []string{".b"}}, "C": LabelPartition{Extensions: []string{".c"}}, }, expected: PartitionToLabelListAttribute{ "A": LabelListAttribute{Value: makeLabelList([]string{"a.a", "d.d", "e.e"}, []string{})}, "B": LabelListAttribute{Value: makeLabelList([]string{"b.b"}, []string{})}, "C": LabelListAttribute{Value: makeLabelList([]string{"c.c"}, []string{})}, }, }, { name: "no configurable values, empty partition", ctx: &OtherModuleTestContext{}, labelList: LabelListAttribute{ Value: makeLabelList([]string{"a.a", "c.c"}, []string{}), }, filters: LabelPartitions{ "A": LabelPartition{Extensions: []string{".a"}}, "B": LabelPartition{Extensions: []string{".b"}}, "C": LabelPartition{Extensions: []string{".c"}}, }, expected: PartitionToLabelListAttribute{ "A": LabelListAttribute{Value: makeLabelList([]string{"a.a"}, []string{})}, "C": LabelListAttribute{Value: makeLabelList([]string{"c.c"}, []string{})}, }, }, { name: "no configurable values, has map", ctx: &OtherModuleTestContext{ Modules: []TestModuleInfo{{ModuleName: "srcs", Typ: "fg", Dir: "dir"}}, }, labelList: LabelListAttribute{ Value: makeLabelList([]string{"a.a", "srcs", "b.b", "c.c"}, []string{}), }, filters: LabelPartitions{ "A": LabelPartition{Extensions: []string{".a"}, LabelMapper: labelAddSuffixForTypeMapper("_a", "fg")}, "B": LabelPartition{Extensions: []string{".b"}}, "C": LabelPartition{Extensions: []string{".c"}}, }, expected: PartitionToLabelListAttribute{ "A": LabelListAttribute{Value: makeLabelList([]string{"a.a", "srcs_a"}, []string{})}, "B": LabelListAttribute{Value: makeLabelList([]string{"b.b"}, []string{})}, "C": LabelListAttribute{Value: makeLabelList([]string{"c.c"}, []string{})}, }, }, { name: "configurable values, keeps empty if excludes", ctx: &OtherModuleTestContext{}, labelList: LabelListAttribute{ ConfigurableValues: configurableLabelLists{ ArchConfigurationAxis: labelListSelectValues{ "x86": makeLabelList([]string{"a.a", "c.c"}, []string{}), "arm": makeLabelList([]string{"b.b"}, []string{}), "x86_64": makeLabelList([]string{"b.b"}, []string{"d.d"}), }, }, }, filters: LabelPartitions{ "A": LabelPartition{Extensions: []string{".a"}}, "B": LabelPartition{Extensions: []string{".b"}}, "C": LabelPartition{Extensions: []string{".c"}}, }, expected: PartitionToLabelListAttribute{ "A": LabelListAttribute{ ConfigurableValues: configurableLabelLists{ ArchConfigurationAxis: labelListSelectValues{ "x86": makeLabelList([]string{"a.a"}, []string{}), "x86_64": makeLabelList([]string{}, []string{"c.c"}), }, }, }, "B": LabelListAttribute{ ConfigurableValues: configurableLabelLists{ ArchConfigurationAxis: labelListSelectValues{ "arm": makeLabelList([]string{"b.b"}, []string{}), "x86_64": makeLabelList([]string{"b.b"}, []string{"c.c"}), }, }, }, "C": LabelListAttribute{ ConfigurableValues: configurableLabelLists{ ArchConfigurationAxis: labelListSelectValues{ "x86": makeLabelList([]string{"c.c"}, []string{}), "x86_64": makeLabelList([]string{}, []string{"c.c"}), }, }, }, }, }, { name: "error for multiple partitions same value", ctx: &OtherModuleTestContext{}, labelList: LabelListAttribute{ Value: makeLabelList([]string{"a.a", "b.b", "c.c", "d.d", "e.e"}, []string{}), }, filters: LabelPartitions{ "A": LabelPartition{Extensions: []string{".a"}}, "other A": LabelPartition{Extensions: []string{".a"}}, }, expected: PartitionToLabelListAttribute{}, expectedErrMsg: proptools.StringPtr(`"a.a" was found in multiple partitions:`), }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { got := PartitionLabelListAttribute(tc.ctx, &tc.labelList, tc.filters) if hasErrors, expectsErr := len(tc.ctx.errors) > 0, tc.expectedErrMsg != nil; hasErrors != expectsErr { t.Errorf("Unexpected error(s): %q, expected: %q", tc.ctx.errors, *tc.expectedErrMsg) } else if tc.expectedErrMsg != nil { found := false for _, err := range tc.ctx.errors { if strings.Contains(err, *tc.expectedErrMsg) { found = true break } } if !found { t.Errorf("Expected error message: %q, got %q", *tc.expectedErrMsg, tc.ctx.errors) } return } if len(tc.expected) != len(got) { t.Errorf("Expected %d partitions, got %d partitions", len(tc.expected), len(got)) } for partition, expectedLla := range tc.expected { gotLla, ok := got[partition] if !ok { t.Errorf("Expected partition %q, but it was not found %v", partition, got) continue } expectedLabelList := expectedLla.Value gotLabelList := gotLla.Value if !reflect.DeepEqual(expectedLabelList.Includes, gotLabelList.Includes) { t.Errorf("Expected no config includes %v, got %v", expectedLabelList.Includes, gotLabelList.Includes) } expectedAxes := expectedLla.SortedConfigurationAxes() gotAxes := gotLla.SortedConfigurationAxes() if !reflect.DeepEqual(expectedAxes, gotAxes) { t.Errorf("Expected axes %v, got %v (%#v)", expectedAxes, gotAxes, gotLla) } for _, axis := range expectedLla.SortedConfigurationAxes() { if _, exists := gotLla.ConfigurableValues[axis]; !exists { t.Errorf("Expected %s to be a supported axis, but it was not found", axis) } if expected, got := expectedLla.ConfigurableValues[axis], gotLla.ConfigurableValues[axis]; len(expected) != len(got) { t.Errorf("For axis %q: expected configs %v, got %v", axis, expected, got) } for config, expectedLabelList := range expectedLla.ConfigurableValues[axis] { gotLabelList, exists := gotLla.ConfigurableValues[axis][config] if !exists { t.Errorf("Expected %s to be a supported config, but config was not found", config) continue } if !reflect.DeepEqual(expectedLabelList.Includes, gotLabelList.Includes) { t.Errorf("Expected %s %s includes %v, got %v", axis, config, expectedLabelList.Includes, gotLabelList.Includes) } } } } }) } } func TestDeduplicateAxesFromBase(t *testing.T) { attr := StringListAttribute{ Value: []string{ "all_include", "arm_include", "android_include", "linux_x86_include", }, ConfigurableValues: configurableStringLists{ ArchConfigurationAxis: stringListSelectValues{ "arm": []string{"arm_include"}, "x86": []string{"x86_include"}, }, OsConfigurationAxis: stringListSelectValues{ "android": []string{"android_include"}, "linux": []string{"linux_include"}, }, OsArchConfigurationAxis: stringListSelectValues{ "linux_x86": {"linux_x86_include"}, }, ProductVariableConfigurationAxis("a", NoConfigAxis): stringListSelectValues{ "a": []string{"not_in_value"}, }, }, } attr.DeduplicateAxesFromBase() expectedBaseIncludes := []string{ "all_include", "arm_include", "android_include", "linux_x86_include", } if !reflect.DeepEqual(expectedBaseIncludes, attr.Value) { t.Errorf("Expected Value includes %q, got %q", attr.Value, expectedBaseIncludes) } expectedConfiguredIncludes := configurableStringLists{ ArchConfigurationAxis: stringListSelectValues{ "x86": []string{"x86_include"}, }, OsConfigurationAxis: stringListSelectValues{ "linux": []string{"linux_include"}, }, OsArchConfigurationAxis: stringListSelectValues{}, ProductVariableConfigurationAxis("a", NoConfigAxis): stringListSelectValues{ "a": []string{"not_in_value"}, }, } for _, axis := range attr.SortedConfigurationAxes() { if _, ok := expectedConfiguredIncludes[axis]; !ok { t.Errorf("Found unexpected axis %s", axis) continue } expectedForAxis := expectedConfiguredIncludes[axis] gotForAxis := attr.ConfigurableValues[axis] if len(expectedForAxis) != len(gotForAxis) { t.Errorf("Expected %d configs for %s, got %d: %s", len(expectedForAxis), axis, len(gotForAxis), gotForAxis) } for config, value := range gotForAxis { if expected, ok := expectedForAxis[config]; ok { if !reflect.DeepEqual(expected, value) { t.Errorf("For %s, expected: %#v, got %#v", axis, expected, value) } } else { t.Errorf("Got unexpected config %q for %s", config, axis) } } } }