| // Copyright 2017 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 com.google.devtools.build.lib.packages; | |
| import static com.google.common.truth.Truth.assertThat; | |
| import static com.google.devtools.build.lib.collect.nestedset.Order.STABLE_ORDER; | |
| import static com.google.devtools.build.lib.skyframe.BzlLoadValue.keyForBuild; | |
| import static org.junit.Assert.assertThrows; | |
| import static org.mockito.Mockito.mock; | |
| import static org.mockito.Mockito.verifyNoInteractions; | |
| import com.google.common.collect.ImmutableList; | |
| import com.google.common.collect.ImmutableMap; | |
| import com.google.common.testing.EqualsTester; | |
| import com.google.devtools.build.lib.cmdline.Label; | |
| import com.google.devtools.build.lib.collect.nestedset.Depset; | |
| import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; | |
| import java.util.Optional; | |
| import net.starlark.java.eval.Dict; | |
| import net.starlark.java.eval.EvalException; | |
| import net.starlark.java.eval.Mutability; | |
| import net.starlark.java.eval.Starlark; | |
| import net.starlark.java.eval.StarlarkCallable; | |
| import net.starlark.java.eval.StarlarkInt; | |
| import net.starlark.java.eval.StarlarkList; | |
| import net.starlark.java.eval.StarlarkSemantics; | |
| import net.starlark.java.eval.StarlarkThread; | |
| import net.starlark.java.eval.SymbolGenerator; | |
| import net.starlark.java.eval.Tuple; | |
| import net.starlark.java.syntax.Location; | |
| import org.junit.Test; | |
| import org.junit.runner.RunWith; | |
| import org.junit.runners.JUnit4; | |
| /** Tests for {@link StarlarkProvider}. */ | |
| @RunWith(JUnit4.class) | |
| public final class StarlarkProviderTest { | |
| private final SymbolGenerator<?> generator = SymbolGenerator.create("test"); | |
| @Test | |
| public void unexportedProvider_accessors() { | |
| StarlarkProvider provider = | |
| StarlarkProvider.builder(Location.BUILTIN).buildWithIdentityToken(generator.generate()); | |
| assertThat(provider.isExported()).isFalse(); | |
| assertThat(provider.getName()).isEqualTo("<no name>"); | |
| assertThat(provider.getPrintableName()).isEqualTo("<no name>"); | |
| assertThat(provider.createRawConstructor().getName()).isEqualTo("<raw constructor>"); | |
| assertThat(provider.getErrorMessageForUnknownField("foo")) | |
| .isEqualTo("'struct' value has no field or method 'foo'"); | |
| assertThat(provider.isImmutable()).isFalse(); | |
| assertThat(Starlark.repr(provider)).isEqualTo("<provider>"); | |
| assertThrows(IllegalStateException.class, provider::getKey); | |
| } | |
| @Test | |
| public void exportedProvider_accessors() throws Exception { | |
| StarlarkProvider.Key key = | |
| new StarlarkProvider.Key(keyForBuild(Label.parseCanonical("//foo:bar.bzl")), "prov"); | |
| StarlarkProvider provider = StarlarkProvider.builder(Location.BUILTIN).buildExported(key); | |
| assertThat(provider.isExported()).isTrue(); | |
| assertThat(provider.getName()).isEqualTo("prov"); | |
| assertThat(provider.getPrintableName()).isEqualTo("prov"); | |
| assertThat(provider.createRawConstructor().getName()).isEqualTo("<raw constructor for prov>"); | |
| assertThat(provider.getErrorMessageForUnknownField("foo")) | |
| .isEqualTo("'prov' value has no field or method 'foo'"); | |
| assertThat(provider.isImmutable()).isTrue(); | |
| assertThat(Starlark.repr(provider)).isEqualTo("<provider>"); | |
| assertThat(provider.getKey()).isEqualTo(key); | |
| } | |
| @Test | |
| public void basicInstantiation() throws Exception { | |
| StarlarkProvider provider = | |
| StarlarkProvider.builder(Location.BUILTIN).buildWithIdentityToken(generator.generate()); | |
| StarlarkInfo infoFromNormalConstructor = instantiateWithA1B2C3(provider); | |
| assertHasExactlyValuesA1B2C3(infoFromNormalConstructor); | |
| assertThat(infoFromNormalConstructor.getProvider()).isEqualTo(provider); | |
| StarlarkInfo infoFromRawConstructor = instantiateWithA1B2C3(provider.createRawConstructor()); | |
| assertHasExactlyValuesA1B2C3(infoFromRawConstructor); | |
| assertThat(infoFromRawConstructor.getProvider()).isEqualTo(provider); | |
| assertThat(infoFromNormalConstructor).isEqualTo(infoFromRawConstructor); | |
| } | |
| @Test | |
| public void instantiationWithInit() throws Exception { | |
| StarlarkProvider provider = | |
| StarlarkProvider.builder(Location.BUILTIN) | |
| .setInit(initBC) | |
| .buildWithIdentityToken(generator.generate()); | |
| StarlarkInfo infoFromNormalConstructor = instantiateWithA1(provider); | |
| assertHasExactlyValuesA1B2C3(infoFromNormalConstructor); | |
| assertThat(infoFromNormalConstructor.getProvider()).isEqualTo(provider); | |
| } | |
| @Test | |
| public void instantiationWithInitSignatureMismatch_fails() throws Exception { | |
| StarlarkProvider provider = | |
| StarlarkProvider.builder(Location.BUILTIN) | |
| .setInit(initBC) | |
| .buildWithIdentityToken(generator.generate()); | |
| EvalException e = assertThrows(EvalException.class, () -> instantiateWithA1B2C3(provider)); | |
| assertThat(e).hasMessageThat().contains("expected a single `a` argument"); | |
| } | |
| @Test | |
| public void instantiationWithInitReturnTypeMismatch_fails() throws Exception { | |
| StarlarkCallable initWithInvalidReturnType = | |
| new StarlarkCallable() { | |
| @Override | |
| public Object call(StarlarkThread thread, Tuple args, Dict<String, Object> kwargs) { | |
| return "invalid"; | |
| } | |
| @Override | |
| public String getName() { | |
| return "initWithInvalidReturnType"; | |
| } | |
| @Override | |
| public Location getLocation() { | |
| return Location.BUILTIN; | |
| } | |
| }; | |
| StarlarkProvider provider = | |
| StarlarkProvider.builder(Location.BUILTIN) | |
| .setInit(initWithInvalidReturnType) | |
| .buildWithIdentityToken(generator.generate()); | |
| EvalException e = assertThrows(EvalException.class, () -> instantiateWithA1B2C3(provider)); | |
| assertThat(e) | |
| .hasMessageThat() | |
| .contains("got string for 'return value of provider init()', want dict"); | |
| } | |
| @Test | |
| public void instantiationWithFailingInit_fails() throws Exception { | |
| StarlarkCallable failingInit = | |
| new StarlarkCallable() { | |
| @Override | |
| public Object call(StarlarkThread thread, Tuple args, Dict<String, Object> kwargs) | |
| throws EvalException { | |
| throw Starlark.errorf("failingInit fails"); | |
| } | |
| @Override | |
| public String getName() { | |
| return "failingInit"; | |
| } | |
| @Override | |
| public Location getLocation() { | |
| return Location.BUILTIN; | |
| } | |
| }; | |
| StarlarkProvider provider = | |
| StarlarkProvider.builder(Location.BUILTIN) | |
| .setInit(failingInit) | |
| .buildWithIdentityToken(generator.generate()); | |
| EvalException e = assertThrows(EvalException.class, () -> instantiateWithA1B2C3(provider)); | |
| assertThat(e).hasMessageThat().contains("failingInit fails"); | |
| } | |
| @Test | |
| public void rawConstructorBypassesInit() throws Exception { | |
| StarlarkCallable init = mock(StarlarkCallable.class, "init"); | |
| StarlarkProvider provider = | |
| StarlarkProvider.builder(Location.BUILTIN) | |
| .setInit(init) | |
| .buildWithIdentityToken(generator.generate()); | |
| StarlarkInfo infoFromRawConstructor = instantiateWithA1B2C3(provider.createRawConstructor()); | |
| assertHasExactlyValuesA1B2C3(infoFromRawConstructor); | |
| assertThat(infoFromRawConstructor.getProvider()).isEqualTo(provider); | |
| verifyNoInteractions(init); | |
| } | |
| @Test | |
| public void basicInstantiationWithSchemaWithSomeFieldsUnset() throws Exception { | |
| StarlarkProvider provider = | |
| StarlarkProvider.builder(Location.BUILTIN) | |
| .setSchema(ImmutableList.of("a", "b", "c")) | |
| .buildWithIdentityToken(generator.generate()); | |
| StarlarkInfo infoFromNormalConstructor = instantiateWithA1(provider); | |
| assertHasExactlyValuesA1(infoFromNormalConstructor); | |
| StarlarkInfo infoFromRawConstructor = instantiateWithA1(provider.createRawConstructor()); | |
| assertHasExactlyValuesA1(infoFromRawConstructor); | |
| } | |
| @Test | |
| public void basicInstantiationWithSchemaWithAllFieldsSet() throws Exception { | |
| StarlarkProvider provider = | |
| StarlarkProvider.builder(Location.BUILTIN) | |
| .setSchema(ImmutableList.of("a", "b", "c")) | |
| .buildWithIdentityToken(generator.generate()); | |
| StarlarkInfo infoFromNormalConstructor = instantiateWithA1B2C3(provider); | |
| assertHasExactlyValuesA1B2C3(infoFromNormalConstructor); | |
| StarlarkInfo infoFromRawConstructor = instantiateWithA1B2C3(provider.createRawConstructor()); | |
| assertHasExactlyValuesA1B2C3(infoFromRawConstructor); | |
| } | |
| @Test | |
| public void basicInstantiationWithDocumentedSchema() throws Exception { | |
| StarlarkProvider provider = | |
| StarlarkProvider.builder(Location.BUILTIN) | |
| .setSchema(ImmutableMap.of("a", "Parameter a", "b", "Parameter b", "c", "Parameter c")) | |
| .buildWithIdentityToken(generator.generate()); | |
| StarlarkInfo infoFromNormalConstructor = instantiateWithA1(provider); | |
| assertHasExactlyValuesA1(infoFromNormalConstructor); | |
| StarlarkInfo infoFromRawConstructor = instantiateWithA1B2C3(provider.createRawConstructor()); | |
| assertHasExactlyValuesA1B2C3(infoFromRawConstructor); | |
| } | |
| @Test | |
| public void schemaDisallowsUnexpectedFields() throws Exception { | |
| StarlarkProvider provider = | |
| StarlarkProvider.builder(Location.BUILTIN) | |
| .setSchema(ImmutableList.of("a", "b")) | |
| .buildWithIdentityToken(generator.generate()); | |
| EvalException e = assertThrows(EvalException.class, () -> instantiateWithA1B2C3(provider)); | |
| assertThat(e) | |
| .hasMessageThat() | |
| .contains("got unexpected field 'c' in call to instantiate provider"); | |
| } | |
| @Test | |
| public void documentedSchemaDisallowsUnexpectedFields() throws Exception { | |
| StarlarkProvider provider = | |
| StarlarkProvider.builder(Location.BUILTIN) | |
| .setSchema(ImmutableMap.of("a", "Parameter a", "b", "Parameter b")) | |
| .buildWithIdentityToken(generator.generate()); | |
| EvalException e = assertThrows(EvalException.class, () -> instantiateWithA1B2C3(provider)); | |
| assertThat(e) | |
| .hasMessageThat() | |
| .contains("got unexpected field 'c' in call to instantiate provider"); | |
| } | |
| @Test | |
| public void schemaEnforcedOnRawConstructor() throws Exception { | |
| StarlarkProvider provider = | |
| StarlarkProvider.builder(Location.BUILTIN) | |
| .setSchema(ImmutableList.of("a", "b")) | |
| .buildWithIdentityToken(generator.generate()); | |
| EvalException e = | |
| assertThrows( | |
| EvalException.class, () -> instantiateWithA1B2C3(provider.createRawConstructor())); | |
| assertThat(e) | |
| .hasMessageThat() | |
| .contains("got unexpected field 'c' in call to instantiate provider"); | |
| } | |
| @Test | |
| public void schemaEnforcedOnInit() throws Exception { | |
| StarlarkProvider provider = | |
| StarlarkProvider.builder(Location.BUILTIN) | |
| .setSchema(ImmutableList.of("a", "b")) | |
| .setInit(initBC) | |
| .buildWithIdentityToken(generator.generate()); | |
| EvalException e = assertThrows(EvalException.class, () -> instantiateWithA1(provider)); | |
| assertThat(e) | |
| .hasMessageThat() | |
| .contains("got unexpected field 'c' in call to instantiate provider"); | |
| } | |
| @Test | |
| public void documentedProvider_getDocumentation() throws Exception { | |
| StarlarkProvider provider = | |
| StarlarkProvider.builder(Location.BUILTIN) | |
| .setDocumentation("My doc string") | |
| .buildWithIdentityToken(generator.generate()); | |
| assertThat(provider.getDocumentation()).hasValue("My doc string"); | |
| } | |
| @Test | |
| public void undocumentedProvider_getDocumentation() throws Exception { | |
| StarlarkProvider provider = | |
| StarlarkProvider.builder(Location.BUILTIN).buildWithIdentityToken(generator.generate()); | |
| assertThat(provider.getDocumentation()).isEmpty(); | |
| } | |
| @Test | |
| public void schemalessProvider_getFields() throws Exception { | |
| StarlarkProvider provider = | |
| StarlarkProvider.builder(Location.BUILTIN).buildWithIdentityToken(generator.generate()); | |
| assertThat(provider.getFields()).isNull(); | |
| } | |
| @Test | |
| public void schemalessProvider_getSchema() throws Exception { | |
| StarlarkProvider provider = | |
| StarlarkProvider.builder(Location.BUILTIN).buildWithIdentityToken(generator.generate()); | |
| assertThat(provider.getSchema()).isNull(); | |
| } | |
| @Test | |
| public void providerWithUndocumentedSchema_getFields() throws Exception { | |
| StarlarkProvider provider = | |
| StarlarkProvider.builder(Location.BUILTIN) | |
| // Note fields in setSchema() call below are not alphabetized to simulate | |
| // non-alphabetized field order in a provider declaration in Starlark code. | |
| .setSchema(ImmutableList.of("c", "a", "b")) | |
| .buildWithIdentityToken(generator.generate()); | |
| assertThat(provider.getFields()).containsExactly("a", "b", "c").inOrder(); | |
| } | |
| @Test | |
| public void providerWithUndocumentedSchema_getSchema() throws Exception { | |
| StarlarkProvider provider = | |
| StarlarkProvider.builder(Location.BUILTIN) | |
| // Note fields in setSchema() call below are not alphabetized to simulate | |
| // non-alphabetized field order in a provider declaration in Starlark code. | |
| .setSchema(ImmutableList.of("c", "a", "b")) | |
| .buildWithIdentityToken(generator.generate()); | |
| assertThat(provider.getSchema()) | |
| .containsExactly("c", Optional.empty(), "a", Optional.empty(), "b", Optional.empty()) | |
| .inOrder(); | |
| } | |
| @Test | |
| public void providerWithDocumentedSchema_getFields() throws Exception { | |
| StarlarkProvider provider = | |
| StarlarkProvider.builder(Location.BUILTIN) | |
| // Note fields in setSchema() call below are not alphabetized to simulate | |
| // non-alphabetized field order in a provider declaration in Starlark code. | |
| .setSchema(ImmutableMap.of("c", "Parameter c", "a", "Parameter a", "b", "Parameter b")) | |
| .buildWithIdentityToken(generator.generate()); | |
| assertThat(provider.getFields()).containsExactly("a", "b", "c").inOrder(); | |
| } | |
| @Test | |
| public void providerWithDocumentedSchema_getSchema() throws Exception { | |
| StarlarkProvider provider = | |
| StarlarkProvider.builder(Location.BUILTIN) | |
| // Note fields in setSchema() call below are not alphabetized to simulate | |
| // non-alphabetized field order in a provider declaration in Starlark code. | |
| .setSchema(ImmutableMap.of("c", "Parameter c", "a", "Parameter a", "b", "Parameter b")) | |
| .buildWithIdentityToken(generator.generate()); | |
| assertThat(provider.getSchema()) | |
| .containsExactly( | |
| "c", | |
| Optional.of("Parameter c"), | |
| "a", | |
| Optional.of("Parameter a"), | |
| "b", | |
| Optional.of("Parameter b")) | |
| .inOrder(); | |
| } | |
| /** | |
| * Tests the safe storage and retrieval of depsets, which may be optimized to nested sets in the | |
| * internal representation. | |
| */ | |
| @Test | |
| public void schemafulProvider_withDepset() throws Exception { | |
| StarlarkProvider provider = | |
| StarlarkProvider.builder(Location.BUILTIN) | |
| .setSchema(ImmutableList.of("field")) | |
| .buildWithIdentityToken(generator.generate()); | |
| StarlarkInfo instance1; | |
| StarlarkInfo instance2; | |
| StarlarkInfo instance3; | |
| StarlarkInfo instance4; | |
| StarlarkInfo instance5; | |
| StarlarkInfo instance6; | |
| try (Mutability mu = Mutability.create()) { | |
| StarlarkThread thread = StarlarkThread.createTransient(mu, StarlarkSemantics.DEFAULT); | |
| // Instantiates provider with values of different types all in the same field. | |
| // Instance with an empty depset of string | |
| instance1 = | |
| (StarlarkInfo) | |
| Starlark.call( | |
| thread, | |
| provider, | |
| /* args= */ ImmutableList.of(), | |
| /* kwargs= */ ImmutableMap.of( | |
| "field", Depset.of(String.class, NestedSetBuilder.emptySet(STABLE_ORDER)))); | |
| // Instance with a non-empty depset of string | |
| instance2 = | |
| (StarlarkInfo) | |
| Starlark.call( | |
| thread, | |
| provider, | |
| /* args= */ ImmutableList.of(), | |
| /* kwargs= */ ImmutableMap.of( | |
| "field", | |
| Depset.of(String.class, NestedSetBuilder.create(STABLE_ORDER, "foo")))); | |
| // Instance with a non-empty depset of int | |
| instance3 = | |
| (StarlarkInfo) | |
| Starlark.call( | |
| thread, | |
| provider, | |
| /* args= */ ImmutableList.of(), | |
| /* kwargs= */ ImmutableMap.of( | |
| "field", | |
| Depset.of( | |
| StarlarkInt.class, | |
| NestedSetBuilder.create(STABLE_ORDER, StarlarkInt.of(1))))); | |
| // Instance with a string (not a depset) | |
| instance4 = | |
| (StarlarkInfo) | |
| Starlark.call( | |
| thread, | |
| provider, | |
| /* args= */ ImmutableList.of(), | |
| /* kwargs= */ ImmutableMap.of("field", "foo")); | |
| // Instance with a None | |
| instance5 = | |
| (StarlarkInfo) | |
| Starlark.call( | |
| thread, | |
| provider, | |
| /* args= */ ImmutableList.of(), | |
| /* kwargs= */ ImmutableMap.of("field", Starlark.NONE)); | |
| // Instance with the field not set | |
| instance6 = | |
| (StarlarkInfo) | |
| Starlark.call( | |
| thread, | |
| provider, | |
| /* args= */ ImmutableList.of(), | |
| /* kwargs= */ ImmutableMap.of()); | |
| } | |
| assertThat(instance1.getValue("field")).isInstanceOf(Depset.class); | |
| assertThat(((Depset) instance1.getValue("field")).isEmpty()).isTrue(); | |
| assertThat(instance2.getValue("field")).isInstanceOf(Depset.class); | |
| assertThat(((Depset) instance2.getValue("field")).getElementClass()).isEqualTo(String.class); | |
| assertThat(((Depset) instance2.getValue("field")).toList()).containsExactly("foo"); | |
| assertThat(instance3.getValue("field")).isInstanceOf(Depset.class); | |
| assertThat(((Depset) instance3.getValue("field")).getElementClass()) | |
| .isEqualTo(StarlarkInt.class); | |
| assertThat(((Depset) instance3.getValue("field")).toList()).containsExactly(StarlarkInt.of(1)); | |
| assertThat(instance4.getValue("field")).isEqualTo("foo"); | |
| assertThat(instance5.getValue("field")).isEqualTo(Starlark.NONE); | |
| assertThat(instance6.getValue("field")).isNull(); | |
| } | |
| @Test | |
| public void schemafulProvider_optimizeField() { | |
| StarlarkProvider provider = | |
| StarlarkProvider.builder(Location.BUILTIN) | |
| .setSchema(ImmutableList.of("field")) | |
| .buildWithIdentityToken(generator.generate()); | |
| // The first set is unwrapped and the type String is registered in the predictor. | |
| Depset depset1 = Depset.of(String.class, NestedSetBuilder.create(STABLE_ORDER, "a", "b", "c")); | |
| assertThat(provider.optimizeField(0, depset1)).isSameInstanceAs(depset1.getSet()); | |
| // A set with Integer type does not match and cannot be optimized. | |
| Depset depset2 = | |
| Depset.of( | |
| StarlarkInt.class, | |
| NestedSetBuilder.create(STABLE_ORDER, StarlarkInt.of(1), StarlarkInt.of(2))); | |
| assertThat(provider.optimizeField(0, depset2)).isSameInstanceAs(depset2); | |
| // A third, matching Depset is unwrapped. | |
| Depset depset3 = Depset.of(String.class, NestedSetBuilder.create(STABLE_ORDER, "d", "e")); | |
| assertThat(provider.optimizeField(0, depset3)).isSameInstanceAs(depset3.getSet()); | |
| } | |
| @Test | |
| public void schemafulProvider_mutable() throws Exception { | |
| StarlarkProvider.Key key = | |
| new StarlarkProvider.Key(keyForBuild(Label.parseCanonical("//foo:bar.bzl")), "prov"); | |
| StarlarkProvider provider = | |
| StarlarkProvider.builder(Location.BUILTIN) | |
| .setSchema(ImmutableList.of("a")) | |
| .buildExported(key); | |
| StarlarkInfo instance; | |
| try (Mutability mu = Mutability.create()) { | |
| StarlarkThread thread = StarlarkThread.createTransient(mu, StarlarkSemantics.DEFAULT); | |
| instance = | |
| (StarlarkInfo) | |
| Starlark.call( | |
| thread, | |
| provider, | |
| /* args= */ ImmutableList.of(), | |
| /* kwargs= */ ImmutableMap.of("a", StarlarkList.of(mu, "x"))); | |
| @SuppressWarnings("unchecked") | |
| StarlarkList<String> list = (StarlarkList<String>) instance.getValue("a"); | |
| list.addElement("y"); // verifies the fields of the provider instance are mutable | |
| assertThat(instance.isImmutable()).isFalse(); | |
| } | |
| @SuppressWarnings("unchecked") | |
| StarlarkList<String> list = (StarlarkList<String>) instance.getValue("a"); | |
| assertThat((Iterable<?>) list).containsExactly("x", "y"); | |
| } | |
| @Test | |
| public void schemafulProvider_immutable() throws Exception { | |
| StarlarkProvider.Key key = | |
| new StarlarkProvider.Key(keyForBuild(Label.parseCanonical("//foo:bar.bzl")), "prov"); | |
| StarlarkProvider provider = | |
| StarlarkProvider.builder(Location.BUILTIN) | |
| .setSchema(ImmutableList.of("a")) | |
| .buildExported(key); | |
| StarlarkInfo instance; | |
| try (Mutability mu = Mutability.create()) { | |
| StarlarkThread thread = StarlarkThread.createTransient(mu, StarlarkSemantics.DEFAULT); | |
| instance = | |
| (StarlarkInfo) | |
| Starlark.call( | |
| thread, | |
| provider, | |
| /* args= */ ImmutableList.of(), | |
| /* kwargs= */ ImmutableMap.of("a", StarlarkList.of(mu, "x"))); | |
| } | |
| assertThat(instance.isImmutable()).isTrue(); | |
| @SuppressWarnings("unchecked") | |
| StarlarkList<String> list = (StarlarkList<String>) instance.getValue("a"); | |
| assertThat((Iterable<?>) list).containsExactly("x"); | |
| // verifies the fields of the frozen provider instance are immutable | |
| EvalException e = assertThrows(EvalException.class, () -> list.addElement("y")); | |
| assertThat(e).hasMessageThat().contains("trying to mutate a frozen list value"); | |
| } | |
| @Test | |
| public void schemafulProviderWithDepset_isImmutable() throws Exception { | |
| StarlarkProvider.Key key = | |
| new StarlarkProvider.Key(keyForBuild(Label.parseCanonical("//foo:bar.bzl")), "prov"); | |
| StarlarkProvider provider = | |
| StarlarkProvider.builder(Location.BUILTIN) | |
| .setSchema(ImmutableList.of("a")) | |
| .buildExported(key); | |
| StarlarkInfo instance; | |
| try (Mutability mu = Mutability.create()) { | |
| StarlarkThread thread = StarlarkThread.createTransient(mu, StarlarkSemantics.DEFAULT); | |
| instance = | |
| (StarlarkInfo) | |
| Starlark.call( | |
| thread, | |
| provider, | |
| /* args= */ ImmutableList.of(), | |
| /* kwargs= */ ImmutableMap.of( | |
| "a", Depset.of(String.class, NestedSetBuilder.create(STABLE_ORDER, "foo")))); | |
| assertThat(instance.isImmutable()).isTrue(); | |
| } | |
| } | |
| @Test | |
| public void schemafulProviderWithDepset_becomesImmutable() throws Exception { | |
| StarlarkProvider.Key key = | |
| new StarlarkProvider.Key(keyForBuild(Label.parseCanonical("//foo:bar.bzl")), "prov"); | |
| StarlarkProvider provider = | |
| StarlarkProvider.builder(Location.BUILTIN) | |
| .setSchema(ImmutableList.of("a", "b")) | |
| .buildExported(key); | |
| StarlarkInfo instance; | |
| try (Mutability mu = Mutability.create()) { | |
| StarlarkThread thread = StarlarkThread.createTransient(mu, StarlarkSemantics.DEFAULT); | |
| instance = | |
| (StarlarkInfo) | |
| Starlark.call( | |
| thread, | |
| provider, | |
| /* args= */ ImmutableList.of(), | |
| /* kwargs= */ ImmutableMap.of( | |
| "a", | |
| Depset.of(String.class, NestedSetBuilder.create(STABLE_ORDER, "foo")), | |
| "b", | |
| StarlarkList.of(mu, "x"))); | |
| assertThat(instance.isImmutable()).isFalse(); | |
| } | |
| assertThat(instance.isImmutable()).isTrue(); | |
| } | |
| @Test | |
| public void schemafulProvider_optimisedImmutable() throws Exception { | |
| StarlarkProvider.Key key = | |
| new StarlarkProvider.Key(keyForBuild(Label.parseCanonical("//foo:bar.bzl")), "prov"); | |
| StarlarkProvider provider = | |
| StarlarkProvider.builder(Location.BUILTIN) | |
| .setSchema(ImmutableList.of("a")) | |
| .buildExported(key); | |
| StarlarkInfo instance; | |
| try (Mutability mu = Mutability.create()) { | |
| StarlarkThread thread = StarlarkThread.createTransient(mu, StarlarkSemantics.DEFAULT); | |
| instance = | |
| (StarlarkInfo) | |
| Starlark.call( | |
| thread, | |
| provider, | |
| /* args= */ ImmutableList.of(), | |
| /* kwargs= */ ImmutableMap.of("a", StarlarkList.of(mu, "x"))); | |
| } | |
| instance = instance.unsafeOptimizeMemoryLayout(); | |
| assertThat(instance.isImmutable()).isTrue(); | |
| @SuppressWarnings("unchecked") | |
| StarlarkList<String> list = (StarlarkList<String>) instance.getValue("a"); | |
| assertThat((Iterable<?>) list).containsExactly("x"); | |
| // verifies the fields of the frozen and optimised provider instance are immutable | |
| EvalException e = assertThrows(EvalException.class, () -> list.addElement("y")); | |
| assertThat(e).hasMessageThat().contains("trying to mutate a frozen list value"); | |
| } | |
| @Test | |
| public void providerEquals() throws Exception { | |
| // All permutations of differing label and differing name. | |
| StarlarkProvider.Key keyFooA = | |
| new StarlarkProvider.Key(keyForBuild(Label.parseCanonical("//foo.bzl")), "provA"); | |
| StarlarkProvider.Key keyFooB = | |
| new StarlarkProvider.Key(keyForBuild(Label.parseCanonical("//foo.bzl")), "provB"); | |
| StarlarkProvider.Key keyBarA = | |
| new StarlarkProvider.Key(keyForBuild(Label.parseCanonical("//bar.bzl")), "provA"); | |
| StarlarkProvider.Key keyBarB = | |
| new StarlarkProvider.Key(keyForBuild(Label.parseCanonical("//bar.bzl")), "provB"); | |
| // 1 for each key, plus a duplicate for one of the keys, plus 2 that have no key. | |
| StarlarkProvider provFooA1 = StarlarkProvider.builder(Location.BUILTIN).buildExported(keyFooA); | |
| StarlarkProvider provFooA2 = StarlarkProvider.builder(Location.BUILTIN).buildExported(keyFooA); | |
| StarlarkProvider provFooB = StarlarkProvider.builder(Location.BUILTIN).buildExported(keyFooB); | |
| StarlarkProvider provBarA = StarlarkProvider.builder(Location.BUILTIN).buildExported(keyBarA); | |
| StarlarkProvider provBarB = StarlarkProvider.builder(Location.BUILTIN).buildExported(keyBarB); | |
| SymbolGenerator<?> generator = SymbolGenerator.create("test"); | |
| StarlarkProvider provUnexported1 = | |
| StarlarkProvider.builder(Location.BUILTIN).buildWithIdentityToken(generator.generate()); | |
| StarlarkProvider provUnexported2 = | |
| StarlarkProvider.builder(Location.BUILTIN).buildWithIdentityToken(generator.generate()); | |
| // For exported providers, different keys -> unequal, same key -> equal. For unexported | |
| // providers it comes down to object identity. | |
| new EqualsTester() | |
| .addEqualityGroup(provFooA1, provFooA2) | |
| .addEqualityGroup(provFooB) | |
| .addEqualityGroup(provBarA, provBarA) // reflexive equality (exported) | |
| .addEqualityGroup(provBarB) | |
| .addEqualityGroup(provUnexported1, provUnexported1) // reflexive equality (unexported) | |
| .addEqualityGroup(provUnexported2) | |
| .testEquals(); | |
| } | |
| /** Custom init equivalent to `def initBC(a): return {a:a, b:a*2, c:a*3}` */ | |
| private static final StarlarkCallable initBC = | |
| new StarlarkCallable() { | |
| @Override | |
| public Object call(StarlarkThread thread, Tuple args, Dict<String, Object> kwargs) | |
| throws EvalException { | |
| if (!args.isEmpty()) { | |
| throw Starlark.errorf("unexpected positional arguments"); | |
| } | |
| if (kwargs.size() != 1 || !kwargs.containsKey("a")) { | |
| throw Starlark.errorf("expected a single `a` argument"); | |
| } | |
| StarlarkInt a = (StarlarkInt) kwargs.get("a"); | |
| return Dict.builder() | |
| .put("a", a) | |
| .put("b", StarlarkInt.of(a.toIntUnchecked() * 2)) | |
| .put("c", StarlarkInt.of(a.toIntUnchecked() * 3)) | |
| .build(Mutability.IMMUTABLE); | |
| } | |
| @Override | |
| public String getName() { | |
| return "initBC"; | |
| } | |
| @Override | |
| public Location getLocation() { | |
| return Location.BUILTIN; | |
| } | |
| }; | |
| /** Instantiates a {@link StarlarkInfo} with fields a=1 (and nothing else). */ | |
| private static StarlarkInfo instantiateWithA1(StarlarkCallable provider) throws Exception { | |
| try (Mutability mu = Mutability.create()) { | |
| StarlarkThread thread = StarlarkThread.createTransient(mu, StarlarkSemantics.DEFAULT); | |
| Object result = | |
| Starlark.call( | |
| thread, | |
| provider, | |
| /*args=*/ ImmutableList.of(), | |
| /*kwargs=*/ ImmutableMap.of("a", StarlarkInt.of(1))); | |
| assertThat(result).isInstanceOf(StarlarkInfo.class); | |
| return (StarlarkInfo) result; | |
| } | |
| } | |
| /** Instantiates a {@link StarlarkInfo} with fields a=1, b=2, c=3 (and nothing else). */ | |
| private static StarlarkInfo instantiateWithA1B2C3(StarlarkCallable provider) throws Exception { | |
| try (Mutability mu = Mutability.create()) { | |
| StarlarkThread thread = StarlarkThread.createTransient(mu, StarlarkSemantics.DEFAULT); | |
| Object result = | |
| Starlark.call( | |
| thread, | |
| provider, | |
| /*args=*/ ImmutableList.of(), | |
| /*kwargs=*/ ImmutableMap.of( | |
| "a", StarlarkInt.of(1), "b", StarlarkInt.of(2), "c", StarlarkInt.of(3))); | |
| assertThat(result).isInstanceOf(StarlarkInfo.class); | |
| return (StarlarkInfo) result; | |
| } | |
| } | |
| /** Asserts that a {@link StarlarkInfo} has field a=1 (and nothing else). */ | |
| private static void assertHasExactlyValuesA1(StarlarkInfo info) throws Exception { | |
| assertThat(info.getFieldNames()).containsExactly("a"); | |
| assertThat(info.getValue("a")).isEqualTo(StarlarkInt.of(1)); | |
| } | |
| /** Asserts that a {@link StarlarkInfo} has fields a=1, b=2, c=3 (and nothing else). */ | |
| private static void assertHasExactlyValuesA1B2C3(StarlarkInfo info) throws Exception { | |
| assertThat(info.getFieldNames()).containsExactly("a", "b", "c"); | |
| assertThat(info.getValue("a")).isEqualTo(StarlarkInt.of(1)); | |
| assertThat(info.getValue("b")).isEqualTo(StarlarkInt.of(2)); | |
| assertThat(info.getValue("c")).isEqualTo(StarlarkInt.of(3)); | |
| } | |
| } |