blob: 3e1708f3ddcd1e7e877153cb055fea295b9e5ea8 [file] [log] [blame]
// 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 com.google.devtools.build.lib.skyframe.serialization.testutils.TestUtils;
import com.google.devtools.build.lib.syntax.SkylarkSemantics;
import com.google.devtools.common.options.Options;
import com.google.devtools.common.options.OptionsParser;
import java.util.Arrays;
import java.util.Random;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/**
* Tests for the flow of flags from {@link SkylarkSemanticsOptions} to {@link SkylarkSemantics}, and
* to and from {@code SkylarkSemantics}' serialized representation.
*
* <p>When adding a new option, it is trivial to make a transposition error or a copy/paste error.
* These tests guard against such errors. The following possible bugs are considered:
* <ul>
* <li>If a new option is added to {@code SkylarkSemantics} but not to {@code
* SkylarkSemanticsOptions}, or vice versa, then the programmer will either be unable to
* implement its behavior, or unable to test it from the command line and add user
* documentation. We hope that the programmer notices this on their own.
*
* <li>If {@link SkylarkSemanticsOptions#toSkylarkSemantics} or {@link
* SkylarkSemanticsCodec#deserialize} is not updated to set all fields of {@code
* SkylarkSemantics}, then it will fail immediately because all fields of {@link
* SkylarkSemantics.Builder} are mandatory.
*
* <li>To catch a copy/paste error where the wrong field's data is threaded through {@code
* toSkylarkSemantics()} or {@code deserialize(...)}, we repeatedly generate matching random
* instances of the input and expected output objects.
*
* <li>The {@link #checkDefaultsMatch} test ensures that there is no divergence between the
* default values of the two classes.
*
* <li>There is no test coverage for failing to update the non-generated webpage documentation.
* So don't forget that!
* </ul>
*/
@RunWith(JUnit4.class)
public class SkylarkSemanticsConsistencyTest {
private static final int NUM_RANDOM_TRIALS = 10;
/**
* Checks that a randomly generated {@link SkylarkSemanticsOptions} object can be converted to a
* {@link SkylarkSemantics} object with the same field values.
*/
@Test
public void optionsToSemantics() throws Exception {
for (int i = 0; i < NUM_RANDOM_TRIALS; i++) {
long seed = i;
SkylarkSemanticsOptions options = buildRandomOptions(new Random(seed));
SkylarkSemantics semantics = buildRandomSemantics(new Random(seed));
SkylarkSemantics semanticsFromOptions = options.toSkylarkSemantics();
assertThat(semanticsFromOptions).isEqualTo(semantics);
}
}
/*
* Checks that a randomly generated {@link SkylarkSemantics} object can be serialized and
* deserialized to an equivalent object.
*/
@Test
public void serializationRoundTrip() throws Exception {
SkylarkSemanticsCodec codec = new SkylarkSemanticsCodec();
for (int i = 0; i < NUM_RANDOM_TRIALS; i++) {
SkylarkSemantics semantics = buildRandomSemantics(new Random(i));
SkylarkSemantics deserialized =
TestUtils.fromBytes(codec, TestUtils.toBytes(codec, semantics));
assertThat(deserialized).isEqualTo(semantics);
}
}
@Test
public void checkDefaultsMatch() {
SkylarkSemanticsOptions defaultOptions = Options.getDefaults(SkylarkSemanticsOptions.class);
SkylarkSemantics defaultSemantics = SkylarkSemantics.DEFAULT_SEMANTICS;
SkylarkSemantics semanticsFromOptions = defaultOptions.toSkylarkSemantics();
assertThat(semanticsFromOptions).isEqualTo(defaultSemantics);
}
/**
* Constructs a {@link SkylarkSemanticsOptions} object with random fields. Must access {@code
* rand} using the same sequence of operations (for the same fields) as {@link
* #buildRandomSemantics}.
*/
private static SkylarkSemanticsOptions buildRandomOptions(Random rand) throws Exception {
return parseOptions(
// <== Add new options here in alphabetic order ==>
"--incompatible_bzl_disallow_load_after_statement=" + rand.nextBoolean(),
"--incompatible_checked_arithmetic=" + rand.nextBoolean(),
"--incompatible_comprehension_variables_do_not_leak=" + rand.nextBoolean(),
"--incompatible_depset_is_not_iterable=" + rand.nextBoolean(),
"--incompatible_dict_literal_has_no_duplicates=" + rand.nextBoolean(),
"--incompatible_disallow_dict_plus=" + rand.nextBoolean(),
"--incompatible_disallow_keyword_only_args=" + rand.nextBoolean(),
"--incompatible_disallow_set_constructor=" + rand.nextBoolean(),
"--incompatible_disallow_toplevel_if_statement=" + rand.nextBoolean(),
"--incompatible_list_plus_equals_inplace=" + rand.nextBoolean(),
"--incompatible_load_argument_is_label=" + rand.nextBoolean(),
"--incompatible_new_actions_api=" + rand.nextBoolean(),
"--incompatible_string_is_not_iterable=" + rand.nextBoolean(),
"--internal_do_not_export_builtins=" + rand.nextBoolean(),
"--internal_skylark_flag_test_canary=" + rand.nextBoolean()
);
}
/**
* Constructs a {@link SkylarkSemantics} object with random fields. Must access {@code rand} using
* the same sequence of operations (for the same fields) as {@link #buildRandomOptions}.
*/
private static SkylarkSemantics buildRandomSemantics(Random rand) {
return SkylarkSemantics.builder()
// <== Add new options here in alphabetic order ==>
.incompatibleBzlDisallowLoadAfterStatement(rand.nextBoolean())
.incompatibleCheckedArithmetic(rand.nextBoolean())
.incompatibleComprehensionVariablesDoNotLeak(rand.nextBoolean())
.incompatibleDepsetIsNotIterable(rand.nextBoolean())
.incompatibleDictLiteralHasNoDuplicates(rand.nextBoolean())
.incompatibleDisallowDictPlus(rand.nextBoolean())
.incompatibleDisallowKeywordOnlyArgs(rand.nextBoolean())
.incompatibleDisallowSetConstructor(rand.nextBoolean())
.incompatibleDisallowToplevelIfStatement(rand.nextBoolean())
.incompatibleListPlusEqualsInplace(rand.nextBoolean())
.incompatibleLoadArgumentIsLabel(rand.nextBoolean())
.incompatibleNewActionsApi(rand.nextBoolean())
.incompatibleStringIsNotIterable(rand.nextBoolean())
.internalDoNotExportBuiltins(rand.nextBoolean())
.internalSkylarkFlagTestCanary(rand.nextBoolean())
.build();
}
private static SkylarkSemanticsOptions parseOptions(String... args) throws Exception {
OptionsParser parser = OptionsParser.newOptionsParser(SkylarkSemanticsOptions.class);
parser.setAllowResidue(false);
parser.parse(Arrays.asList(args));
return parser.getOptions(SkylarkSemanticsOptions.class);
}
}