// 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",
			},
			[]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"}, []string{}),
			},
			ProductVariableConfigurationAxis("product_only_with_excludes", NoConfigAxis): labelListSelectValues{
				"a": makeLabelList([]string{}, []string{"not_in_value"}),
			},
		},
	}

	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"),
		},
	}
	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)
			}
		}
	}
}
