diff --git a/android/Android.bp b/android/Android.bp index f3a385025..6450a06ca 100644 --- a/android/Android.bp +++ b/android/Android.bp @@ -110,6 +110,7 @@ bootstrap_go_package { "paths_test.go", "prebuilt_test.go", "rule_builder_test.go", + "sdk_test.go", "singleton_module_test.go", "soong_config_modules_test.go", "util_test.go", diff --git a/android/sdk.go b/android/sdk.go index 1518a8731..1d63d7a94 100644 --- a/android/sdk.go +++ b/android/sdk.go @@ -396,6 +396,25 @@ type sdkRegistry struct { func (r *sdkRegistry) copyAndAppend(registerable sdkRegisterable) *sdkRegistry { oldList := r.list + // Make sure that list does not already contain the property. Uses a simple linear search instead + // of a binary search even though the list is sorted. That is because the number of items in the + // list is small and so not worth the overhead of a binary search. + found := false + newPropertyName := registerable.SdkPropertyName() + for _, r := range oldList { + if r.SdkPropertyName() == newPropertyName { + found = true + break + } + } + if found { + names := []string{} + for _, r := range oldList { + names = append(names, r.SdkPropertyName()) + } + panic(fmt.Errorf("duplicate properties found, %q already exists in %q", newPropertyName, names)) + } + // Copy the slice just in case this is being read while being modified, e.g. when testing. list := make([]sdkRegisterable, 0, len(oldList)+1) list = append(list, oldList...) diff --git a/android/sdk_test.go b/android/sdk_test.go new file mode 100644 index 000000000..51aeb314c --- /dev/null +++ b/android/sdk_test.go @@ -0,0 +1,53 @@ +// Copyright (C) 2021 The Android Open Source Project +// +// 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 android + +import "testing" + +type testSdkRegisterable struct { + name string +} + +func (t *testSdkRegisterable) SdkPropertyName() string { + return t.name +} + +var _ sdkRegisterable = &testSdkRegisterable{} + +func TestSdkRegistry(t *testing.T) { + alpha := &testSdkRegisterable{"alpha"} + beta := &testSdkRegisterable{"beta"} + betaDup := &testSdkRegisterable{"beta"} + + // Make sure that an empty registry is empty. + emptyRegistry := &sdkRegistry{} + AssertDeepEquals(t, "emptyRegistry should be empty", ([]sdkRegisterable)(nil), emptyRegistry.registeredObjects()) + + // Add beta to the empty registry to create another registry, check that it contains beta and make + // sure that it does not affect the creating registry. + registry1 := emptyRegistry.copyAndAppend(beta) + AssertDeepEquals(t, "emptyRegistry should still be empty", ([]sdkRegisterable)(nil), emptyRegistry.registeredObjects()) + AssertDeepEquals(t, "registry1 should contain beta", []sdkRegisterable{beta}, registry1.registeredObjects()) + + // Add alpha to the registry containing beta to create another registry, check that it contains + // alpha,beta (in order) and make sure that it does not affect the creating registry. + registry2 := registry1.copyAndAppend(alpha) + AssertDeepEquals(t, "registry1 should still contain beta", []sdkRegisterable{beta}, registry1.registeredObjects()) + AssertDeepEquals(t, "registry2 should contain alpha,beta", []sdkRegisterable{alpha, beta}, registry2.registeredObjects()) + + AssertPanicMessageContains(t, "duplicate beta should be detected", `"beta" already exists in ["alpha" "beta"]`, func() { + registry2.copyAndAppend(betaDup) + }) +}