blob: 57317cfc365947ad3698608b74b5cd1e6006b93f [file] [log] [blame]
// Copyright 2018 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.syntax;
import com.google.devtools.build.lib.skylarkinterface.Param;
import com.google.devtools.build.lib.skylarkinterface.SkylarkCallable;
import com.google.devtools.build.lib.skylarkinterface.SkylarkModule;
import com.google.devtools.build.lib.syntax.StarlarkSemantics.FlagIdentifier;
import com.google.devtools.build.lib.syntax.util.EvaluationTestCase;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/**
* Starlark evaluation tests which verify the infrastructure which toggles build API methods and
* parameters with semantic flags.
*/
@RunWith(JUnit4.class)
public final class StarlarkFlagGuardingTest extends EvaluationTestCase {
/** Mock containing exposed methods for flag-guarding tests. */
@SkylarkModule(name = "Mock", doc = "")
public static class Mock implements StarlarkValue {
@SkylarkCallable(
name = "positionals_only_method",
documented = false,
parameters = {
@Param(name = "a", positional = true, named = false, type = Integer.class),
@Param(
name = "b",
positional = true,
named = false,
type = Boolean.class,
enableOnlyWithFlag = FlagIdentifier.EXPERIMENTAL_BUILD_SETTING_API,
valueWhenDisabled = "False"),
@Param(name = "c", positional = true, named = false, type = Integer.class),
},
useStarlarkThread = true)
public String positionalsOnlyMethod(Integer a, boolean b, Integer c, StarlarkThread thread) {
return "positionals_only_method(" + a + ", " + b + ", " + c + ")";
}
@SkylarkCallable(
name = "keywords_only_method",
documented = false,
parameters = {
@Param(name = "a", positional = false, named = true, type = Integer.class),
@Param(
name = "b",
positional = false,
named = true,
type = Boolean.class,
enableOnlyWithFlag = FlagIdentifier.EXPERIMENTAL_BUILD_SETTING_API,
valueWhenDisabled = "False"),
@Param(name = "c", positional = false, named = true, type = Integer.class),
},
useStarlarkThread = true)
public String keywordsOnlyMethod(Integer a, boolean b, Integer c, StarlarkThread thread) {
return "keywords_only_method(" + a + ", " + b + ", " + c + ")";
}
@SkylarkCallable(
name = "mixed_params_method",
documented = false,
parameters = {
@Param(name = "a", positional = true, named = false, type = Integer.class),
@Param(
name = "b",
positional = true,
named = false,
type = Boolean.class,
enableOnlyWithFlag = FlagIdentifier.EXPERIMENTAL_BUILD_SETTING_API,
valueWhenDisabled = "False"),
@Param(
name = "c",
positional = false,
named = true,
type = Integer.class,
enableOnlyWithFlag = FlagIdentifier.EXPERIMENTAL_BUILD_SETTING_API,
valueWhenDisabled = "3"),
@Param(name = "d", positional = false, named = true, type = Boolean.class),
},
useStarlarkThread = true)
public String mixedParamsMethod(
Integer a, boolean b, Integer c, boolean d, StarlarkThread thread) {
return "mixed_params_method(" + a + ", " + b + ", " + c + ", " + d + ")";
}
@SkylarkCallable(
name = "keywords_multiple_flags",
documented = false,
parameters = {
@Param(name = "a", positional = false, named = true, type = Integer.class),
@Param(
name = "b",
positional = false,
named = true,
type = Boolean.class,
disableWithFlag = FlagIdentifier.INCOMPATIBLE_NO_ATTR_LICENSE,
valueWhenDisabled = "False"),
@Param(
name = "c",
positional = false,
named = true,
type = Integer.class,
enableOnlyWithFlag = FlagIdentifier.EXPERIMENTAL_BUILD_SETTING_API,
valueWhenDisabled = "3"),
},
useStarlarkThread = true)
public String keywordsMultipleFlags(Integer a, boolean b, Integer c, StarlarkThread thread) {
return "keywords_multiple_flags(" + a + ", " + b + ", " + c + ")";
}
}
@Test
public void testPositionalsOnlyGuardedMethod() throws Exception {
new Scenario("--experimental_build_setting_api=true")
.update("mock", new Mock())
.testEval(
"mock.positionals_only_method(1, True, 3)", "'positionals_only_method(1, true, 3)'");
new Scenario("--experimental_build_setting_api=true")
.update("mock", new Mock())
.testIfErrorContains(
"in call to positionals_only_method(), parameter 'b' got value of type 'int', want"
+ " 'bool'",
"mock.positionals_only_method(1, 3)");
new Scenario("--experimental_build_setting_api=false")
.update("mock", new Mock())
.testEval("mock.positionals_only_method(1, 3)", "'positionals_only_method(1, false, 3)'");
new Scenario("--experimental_build_setting_api=false")
.update("mock", new Mock())
.testIfErrorContains(
"in call to positionals_only_method(), parameter 'c' got value of type 'bool', want"
+ " 'int'",
"mock.positionals_only_method(1, True, 3)");
}
@Test
public void testKeywordOnlyGuardedMethod() throws Exception {
new Scenario("--experimental_build_setting_api=true")
.update("mock", new Mock())
.testEval(
"mock.keywords_only_method(a=1, b=True, c=3)", "'keywords_only_method(1, true, 3)'");
new Scenario("--experimental_build_setting_api=true")
.update("mock", new Mock())
.testIfErrorContains(
"keywords_only_method() missing 1 required named argument: b",
"mock.keywords_only_method(a=1, c=3)");
new Scenario("--experimental_build_setting_api=false")
.update("mock", new Mock())
.testEval("mock.keywords_only_method(a=1, c=3)", "'keywords_only_method(1, false, 3)'");
new Scenario("--experimental_build_setting_api=false")
.update("mock", new Mock())
.testIfErrorContains(
"parameter 'b' is experimental and thus unavailable with the current "
+ "flags. It may be enabled by setting "
+ "--experimental_build_setting_api",
"mock.keywords_only_method(a=1, b=True, c=3)");
}
@Test
public void testMixedParamsMethod() throws Exception {
// def mixed_params_method(a, b, c = ?, d = ?)
new Scenario("--experimental_build_setting_api=true")
.update("mock", new Mock())
.testEval(
"mock.mixed_params_method(1, True, c=3, d=True)",
"'mixed_params_method(1, true, 3, true)'");
new Scenario("--experimental_build_setting_api=true")
.update("mock", new Mock())
.testIfErrorContains(
// Missing named arguments (d) are not reported
// if there are missing positional arguments.
"mixed_params_method() missing 1 required positional argument: b",
"mock.mixed_params_method(1, c=3)");
// def mixed_params_method(a, b disabled = False, c disabled = 3, d = ?)
new Scenario("--experimental_build_setting_api=false")
.update("mock", new Mock())
.testEval(
"mock.mixed_params_method(1, d=True)", "'mixed_params_method(1, false, 3, true)'");
new Scenario("--experimental_build_setting_api=false")
.update("mock", new Mock())
.testIfErrorContains(
"mixed_params_method() accepts no more than 1 positional argument but got 2",
"mock.mixed_params_method(1, True, d=True)");
new Scenario("--experimental_build_setting_api=false")
.update("mock", new Mock())
.testIfErrorContains(
"mixed_params_method() accepts no more than 1 positional argument but got 2",
"mock.mixed_params_method(1, True, c=3, d=True)");
}
@Test
public void testKeywordsMultipleFlags() throws Exception {
new Scenario("--experimental_build_setting_api=true", "--incompatible_no_attr_license=false")
.update("mock", new Mock())
.testEval(
"mock.keywords_multiple_flags(a=42, b=True, c=0)",
"'keywords_multiple_flags(42, true, 0)'");
new Scenario("--experimental_build_setting_api=true", "--incompatible_no_attr_license=false")
.update("mock", new Mock())
.testIfErrorContains(
"keywords_multiple_flags() missing 2 required named arguments: b, c",
"mock.keywords_multiple_flags(a=42)");
new Scenario("--experimental_build_setting_api=false", "--incompatible_no_attr_license=true")
.update("mock", new Mock())
.testEval("mock.keywords_multiple_flags(a=42)", "'keywords_multiple_flags(42, false, 3)'");
new Scenario("--experimental_build_setting_api=false", "--incompatible_no_attr_license=true")
.update("mock", new Mock())
.testIfErrorContains(
"parameter 'b' is deprecated and will be removed soon. It may be "
+ "temporarily re-enabled by setting --incompatible_no_attr_license=false",
"mock.keywords_multiple_flags(a=42, b=True, c=0)");
}
@Test
public void testExperimentalFlagGuardedValue() throws Exception {
// This test uses an arbitrary experimental flag to verify this functionality. If this
// experimental flag were to go away, this test may be updated to use any experimental flag.
// The flag itself is unimportant to the test.
predeclare(
"GlobalSymbol",
FlagGuardedValue.onlyWhenExperimentalFlagIsTrue(
FlagIdentifier.EXPERIMENTAL_BUILD_SETTING_API, "foo"));
String errorMessage =
"GlobalSymbol is experimental and thus unavailable with the current "
+ "flags. It may be enabled by setting --experimental_build_setting_api";
new Scenario("--experimental_build_setting_api=true")
.setUp("var = GlobalSymbol")
.testLookup("var", "foo");
new Scenario("--experimental_build_setting_api=false")
.testIfErrorContains(errorMessage, "var = GlobalSymbol");
new Scenario("--experimental_build_setting_api=false")
.testIfErrorContains(errorMessage, "def my_function():", " var = GlobalSymbol");
new Scenario("--experimental_build_setting_api=false")
.setUp("GlobalSymbol = 'other'", "var = GlobalSymbol")
.testLookup("var", "other");
}
@Test
public void testIncompatibleFlagGuardedValue() throws Exception {
// This test uses an arbitrary incompatible flag to verify this functionality. If this
// incompatible flag were to go away, this test may be updated to use any incompatible flag.
// The flag itself is unimportant to the test.
predeclare(
"GlobalSymbol",
FlagGuardedValue.onlyWhenIncompatibleFlagIsFalse(
FlagIdentifier.INCOMPATIBLE_NO_TARGET_OUTPUT_GROUP, "foo"));
String errorMessage =
"GlobalSymbol is deprecated and will be removed soon. It may be "
+ "temporarily re-enabled by setting --incompatible_no_target_output_group=false";
new Scenario("--incompatible_no_target_output_group=false")
.setUp("var = GlobalSymbol")
.testLookup("var", "foo");
new Scenario("--incompatible_no_target_output_group=true")
.testIfErrorContains(errorMessage, "var = GlobalSymbol");
new Scenario("--incompatible_no_target_output_group=true")
.testIfErrorContains(errorMessage, "def my_function():", " var = GlobalSymbol");
new Scenario("--incompatible_no_target_output_group=true")
.setUp("GlobalSymbol = 'other'", "var = GlobalSymbol")
.testLookup("var", "other");
}
}