// Copyright 2015 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.runtime;

import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import com.google.common.collect.ImmutableSet;
import com.google.common.io.BaseEncoding;
import com.google.devtools.build.lib.flags.CommandNameCache;
import com.google.devtools.build.lib.flags.InvocationPolicyEnforcer;
import com.google.devtools.build.lib.runtime.proto.InvocationPolicyOuterClass.InvocationPolicy;
import com.google.devtools.common.options.Option;
import com.google.devtools.common.options.OptionsBase;
import com.google.devtools.common.options.OptionsParser;
import com.google.devtools.common.options.OptionsParsingException;

import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

import java.io.ByteArrayOutputStream;
import java.util.List;

@RunWith(JUnit4.class)
public class InvocationPolicyEnforcerTest {

  public static final String STRING_FLAG_DEFAULT = "test string default";

  public static class TestOptions extends OptionsBase {

    /*
     * Basic types
     */

    @Option(name = "test_string", defaultValue = STRING_FLAG_DEFAULT)
    public String testString;

    /*
     * Repeated flags
     */

    @Option(
        name = "test_multiple_string",
        defaultValue = "", // default value is ignored when allowMultiple = true.
        allowMultiple = true)
    public List<String> testMultipleString;

    /*
     * Expansion flags
     */

    @Option(
        name = "test_expansion",
        defaultValue = "null",
        expansion = {
            "--notest_expansion_a",
            "--test_expansion_b=false",
            "--test_expansion_c", "42",
            "--test_expansion_d", "bar"
        })
    public Void testExpansion;

    @Option(
        name = "test_recursive_expansion_top_level",
        defaultValue = "null",
        expansion = {
            "--test_recursive_expansion_middle1",
            "--test_recursive_expansion_middle2",
        })
    public Void testRecursiveExpansionTopLevel;

    @Option(
        name = "test_recursive_expansion_middle1",
        defaultValue = "null",
        expansion = {
            "--test_expansion_a=false",
            "--test_expansion_c=56",
        })
    public Void testRecursiveExpansionMiddle1;

    @Option(
        name = "test_recursive_expansion_middle2",
        defaultValue = "null",
        expansion = {
            "--test_expansion_b=false",
            "--test_expansion_d=baz",
        })
    public Void testRecursiveExpansionMiddle2;

    @Option(name = "test_expansion_a", defaultValue = "true")
    public boolean testExpansionA;

    @Option(name = "test_expansion_b", defaultValue = "true")
    public boolean testExpansionB;

    @Option(name = "test_expansion_c", defaultValue = "12")
    public int testExpansionC;

    @Option(name = "test_expansion_d", defaultValue = "foo")
    public String testExpansionD;

    /*
     * Implicit requirement flags
     */

    @Option(
        name = "test_implicit_requirement",
        defaultValue = "test implicit requirement default",
        implicitRequirements = {"--an_implicit_requirement=foo"})
    public String testImplicitRequirement;

    @Option(
        name = "an_implicit_requirement",
        defaultValue = "implicit default")
    public String anImplicitRequirement;

    @Option(
        name = "test_recursive_implicit_requirement",
        defaultValue = "test recursive implicit requirement default",
        implicitRequirements = {"--test_implicit_requirement=bar"})
    public String testRecursiveImplicitRequirement;

  }

  private static InvocationPolicyEnforcer createOptionsPolicyEnforcer(
      InvocationPolicy.Builder invocationPolicyBuilder) throws Exception {
    InvocationPolicy policyProto = invocationPolicyBuilder.build();

    // An OptionsPolicyEnforcer could be constructed in the test directly from the InvocationPolicy
    // proto, however Blaze will actually take the policy as another flag with a Base64 encoded
    // binary proto and parse that, so exercise that code path in the test.

    ByteArrayOutputStream out = new ByteArrayOutputStream();
    policyProto.writeTo(out);
    String policyBase64 = BaseEncoding.base64().encode(out.toByteArray());

    OptionsParser startupOptionsParser = OptionsParser.newOptionsParser(
        BlazeServerStartupOptions.class);
    String policyOption = "--invocation_policy=" + policyBase64;
    startupOptionsParser.parse(policyOption);

    return InvocationPolicyEnforcer.create(
        startupOptionsParser.getOptions(BlazeServerStartupOptions.class).invocationPolicy);
  }
  
  private OptionsParser parser;

  @Before
  public final void setParser() throws Exception  {
    parser = OptionsParser.newOptionsParser(TestOptions.class);
  }

  @BeforeClass
  public static void setCommandNameCache() throws Exception {
    CommandNameCache.CommandNameCacheInstance.INSTANCE.setCommandNameCache(
        new CommandNameCache() {
          @Override
          public ImmutableSet<String> get(String commandName) {
            return ImmutableSet.of(commandName);
          }
        });
  }

  private TestOptions getTestOptions() {
    return parser.getOptions(TestOptions.class);
  }

  /*************************************************************************************************
   * Tests for SetValue
   ************************************************************************************************/

  /**
   * Tests that policy overrides a value when that value is from the user.
   */
  @Test
  public void testSetValueOverridesUser() throws Exception {
    InvocationPolicy.Builder invocationPolicyBuilder = InvocationPolicy.newBuilder();
    invocationPolicyBuilder.addFlagPoliciesBuilder()
        .setFlagName("test_string")
        .getSetValueBuilder()
            .addFlagValue("policy value");

    InvocationPolicyEnforcer enforcer = createOptionsPolicyEnforcer(invocationPolicyBuilder);
    parser.parse("--test_string=user value");

    TestOptions testOptions = getTestOptions();
    assertEquals("user value", testOptions.testString);

    enforcer.enforce(parser, "build");

    // Get the options again after policy enforcement.
    testOptions = getTestOptions();
    assertEquals("policy value", testOptions.testString);
  }

  /**
   * Tests that policy overrides a value when the user doesn't specify the value (i.e., the value
   * is from the flag's default from its definition).
   */
  @Test
  public void testSetValueOverridesDefault() throws Exception {
    InvocationPolicy.Builder invocationPolicyBuilder = InvocationPolicy.newBuilder();
    invocationPolicyBuilder.addFlagPoliciesBuilder()
        .setFlagName("test_string")
        .getSetValueBuilder()
            .addFlagValue("policy value");

    // No user value.
    InvocationPolicyEnforcer enforcer = createOptionsPolicyEnforcer(invocationPolicyBuilder);

    // All the flags should be their default value.
    TestOptions testOptions = getTestOptions();
    assertEquals(STRING_FLAG_DEFAULT, testOptions.testString);

    enforcer.enforce(parser, "build");

    // Get the options again after policy enforcement.
    testOptions = getTestOptions();
    assertEquals("policy value", testOptions.testString);
  }

  /**
   * Tests that SetValue overrides the user's value when the flag allows multiple values.
   */
  @Test
  public void testSetValueWithMultipleValuesOverridesUser() throws Exception {
    InvocationPolicy.Builder invocationPolicyBuilder = InvocationPolicy.newBuilder();
    invocationPolicyBuilder.addFlagPoliciesBuilder()
        .setFlagName("test_multiple_string")
        .getSetValueBuilder()
            .addFlagValue("policy value 1")
            .addFlagValue("policy value 2");

    InvocationPolicyEnforcer enforcer =  createOptionsPolicyEnforcer(invocationPolicyBuilder);
    parser.parse("--test_multiple_string=user value 1", "--test_multiple_string=user value 2");

    // Options should not be modified by running the parser through OptionsPolicyEnforcer.create().
    TestOptions testOptions = getTestOptions();
    assertThat(testOptions.testMultipleString)
        .containsExactly("user value 1", "user value 2").inOrder();

    enforcer.enforce(parser, "build");

    // Get the options again after policy enforcement.
    testOptions = getTestOptions();
    assertThat(testOptions.testMultipleString)
        .containsExactly("policy value 1", "policy value 2").inOrder();
  }

  /**
   * Tests that policy overrides the default value when the flag allows multiple values and the user
   * doesn't provide a value.
   */
  @Test
  public void testSetValueWithMultipleValuesOverridesDefault() throws Exception {
    InvocationPolicy.Builder invocationPolicyBuilder = InvocationPolicy.newBuilder();
    invocationPolicyBuilder.addFlagPoliciesBuilder()
        .setFlagName("test_multiple_string")
        .getSetValueBuilder()
            .addFlagValue("policy value 1")
            .addFlagValue("policy value 2");

    // No user value.
    InvocationPolicyEnforcer enforcer = createOptionsPolicyEnforcer(invocationPolicyBuilder);

    // Repeatable flags always default to the empty list.
    TestOptions testOptions = getTestOptions();
    assertThat(testOptions.testMultipleString).isEmpty();

    enforcer.enforce(parser, "build");

    // Options should now be the values from the policy.
    testOptions = getTestOptions();
    assertThat(testOptions.testMultipleString)
        .containsExactly("policy value 1", "policy value 2").inOrder();
  }

  @Test
  public void testSetValueHasMultipleValuesButFlagIsNotMultiple() throws Exception {
    InvocationPolicy.Builder invocationPolicyBuilder = InvocationPolicy.newBuilder();
    invocationPolicyBuilder.addFlagPoliciesBuilder()
        .setFlagName("test_string") // Not repeatable flag.
        .getSetValueBuilder()
            .addFlagValue("policy value 1") // Has multiple values.
            .addFlagValue("policy value 2");

    InvocationPolicyEnforcer enforcer = createOptionsPolicyEnforcer(invocationPolicyBuilder);

    try {
      enforcer.enforce(parser, "build");
      fail();
    } catch (OptionsParsingException e) {
      // expected.
    }
  }

  @Test
  public void testSetValueAppendsToMultipleValuedFlag() throws Exception {
    InvocationPolicy.Builder invocationPolicyBuilder = InvocationPolicy.newBuilder();
    invocationPolicyBuilder.addFlagPoliciesBuilder()
        .setFlagName("test_multiple_string")
        .getSetValueBuilder()
            .addFlagValue("policy value 1")
            .addFlagValue("policy value 2")
            .setAppend(true);

    InvocationPolicyEnforcer enforcer =  createOptionsPolicyEnforcer(invocationPolicyBuilder);
    parser.parse("--test_multiple_string=user value 1", "--test_multiple_string=user value 2");

    // Options should not be modified by running the parser through OptionsPolicyEnforcer.create().
    TestOptions testOptions = getTestOptions();
    assertThat(testOptions.testMultipleString)
        .containsExactly("user value 1", "user value 2").inOrder();

    enforcer.enforce(parser, "build");

    // Get the options again after policy enforcement.
    testOptions = getTestOptions();
    assertThat(testOptions.testMultipleString)
        .containsExactly(
            "user value 1",
            "user value 2",
            "policy value 1",
            "policy value 2").inOrder();
  }
  
  @Test
  public void testSetValueWithExpansionFlags() throws Exception {
    InvocationPolicy.Builder invocationPolicyBuilder = InvocationPolicy.newBuilder();
    invocationPolicyBuilder.addFlagPoliciesBuilder()
        .setFlagName("test_expansion")
        .getSetValueBuilder()
            .addFlagValue("true"); // this value is arbitrary, the value for a Void flag is ignored

    InvocationPolicyEnforcer enforcer = createOptionsPolicyEnforcer(invocationPolicyBuilder);
    // Unrelated flag, but --test_expansion is not set
    parser.parse("--test_string=foo");

    // The flags that --test_expansion expands into should still be their default values
    TestOptions testOptions = getTestOptions();
    assertTrue(testOptions.testExpansionA);
    assertTrue(testOptions.testExpansionB);
    assertEquals(12, testOptions.testExpansionC);
    assertEquals("foo", testOptions.testExpansionD);

    enforcer.enforce(parser, "build");

    // After policy enforcement, the flags should be the values from --test_expansion
    testOptions = getTestOptions();
    assertFalse(testOptions.testExpansionA);
    assertFalse(testOptions.testExpansionB);
    assertEquals(42, testOptions.testExpansionC);
    assertEquals("bar", testOptions.testExpansionD);
  }
  
  @Test
  public void testSetValueWithExpandedFlags() throws Exception {
    InvocationPolicy.Builder invocationPolicyBuilder = InvocationPolicy.newBuilder();
    invocationPolicyBuilder.addFlagPoliciesBuilder()
        .setFlagName("test_expansion_c")
        .getSetValueBuilder()
            .addFlagValue("64");

    InvocationPolicyEnforcer enforcer = createOptionsPolicyEnforcer(invocationPolicyBuilder);
    parser.parse("--test_expansion");

    // --test_expansion should set the values from its expansion
    TestOptions testOptions = getTestOptions();
    assertFalse(testOptions.testExpansionA);
    assertFalse(testOptions.testExpansionB);
    assertEquals(42, testOptions.testExpansionC);
    assertEquals("bar", testOptions.testExpansionD);

    enforcer.enforce(parser, "build");

    // After policy enforcement, test_expansion_c should be set to 64 from the policy, but the
    // flags should remain the same from the expansion of --test_expansion.
    testOptions = getTestOptions();
    assertFalse(testOptions.testExpansionA);
    assertFalse(testOptions.testExpansionB);
    assertEquals(64, testOptions.testExpansionC);
    assertEquals("bar", testOptions.testExpansionD);
  }

  @Test
  public void testSetValueWithImplicitlyRequiredFlags() throws Exception {
    InvocationPolicy.Builder invocationPolicyBuilder = InvocationPolicy.newBuilder();
    invocationPolicyBuilder.addFlagPoliciesBuilder()
        .setFlagName("an_implicit_requirement")
        .getSetValueBuilder()
            .addFlagValue("policy value");

    InvocationPolicyEnforcer enforcer = createOptionsPolicyEnforcer(invocationPolicyBuilder);
    parser.parse("--test_implicit_requirement=user value");

    // test_implicit_requirement sets an_implicit_requirement to "foo"
    TestOptions testOptions = getTestOptions();
    assertEquals("user value", testOptions.testImplicitRequirement);
    assertEquals("foo", testOptions.anImplicitRequirement);

    enforcer.enforce(parser, "build");

    testOptions = getTestOptions();
    assertEquals("user value", testOptions.testImplicitRequirement);
    assertEquals("policy value", testOptions.anImplicitRequirement);
  }

  @Test
  public void testSetValueOverridable() throws Exception {
    InvocationPolicy.Builder invocationPolicyBuilder = InvocationPolicy.newBuilder();
    invocationPolicyBuilder.addFlagPoliciesBuilder()
        .setFlagName("test_string")
        .getSetValueBuilder()
            .addFlagValue("policy value")
            .setOverridable(true);

    InvocationPolicyEnforcer enforcer = createOptionsPolicyEnforcer(invocationPolicyBuilder);
    parser.parse("--test_string=user value");

    TestOptions testOptions = getTestOptions();
    assertEquals("user value", testOptions.testString);

    enforcer.enforce(parser, "build");

    // Even though the policy sets the value for test_string, the policy is overridable and the
    // user set the value, so it should be the user's value.
    testOptions = getTestOptions();
    assertEquals("user value", testOptions.testString);
  }

  @Test
  public void testSetValueWithNoValueThrows() throws Exception {
    InvocationPolicy.Builder invocationPolicyBuilder = InvocationPolicy.newBuilder();
    invocationPolicyBuilder.addFlagPoliciesBuilder()
        .setFlagName("test_string")
        .getSetValueBuilder(); // No value.

    InvocationPolicyEnforcer enforcer = createOptionsPolicyEnforcer(invocationPolicyBuilder);
    parser.parse("--test_string=user value");

    TestOptions testOptions = getTestOptions();
    assertEquals("user value", testOptions.testString);

    try {
      enforcer.enforce(parser, "build");
      fail();
    } catch (OptionsParsingException e) {
      // expected.
    }
  }

  /*************************************************************************************************
   * Tests for UseDefault
   ************************************************************************************************/

  @Test
  public void testUseDefault() throws Exception {
    InvocationPolicy.Builder invocationPolicyBuilder = InvocationPolicy.newBuilder();
    invocationPolicyBuilder.addFlagPoliciesBuilder()
        .setFlagName("test_string")
        .getUseDefaultBuilder();

    InvocationPolicyEnforcer enforcer = createOptionsPolicyEnforcer(invocationPolicyBuilder);
    parser.parse("--test_string=user value");

    // Options should be the user specified value before enforcing policy.
    TestOptions testOptions = getTestOptions();
    assertEquals("user value", testOptions.testString);

    enforcer.enforce(parser, "build");

    // Get the options again after policy enforcement: The flag should now be back to its default
    // value
    testOptions = getTestOptions();
    assertEquals(STRING_FLAG_DEFAULT, testOptions.testString);
  }

  /**
   * Tests UseDefault when the user never actually specified the flag.
   */
  @Test
  public void testUseDefaultWhenFlagWasntSet() throws Exception {
    InvocationPolicy.Builder invocationPolicyBuilder = InvocationPolicy.newBuilder();
    invocationPolicyBuilder.addFlagPoliciesBuilder()
        .setFlagName("test_string")
        .getUseDefaultBuilder();

    InvocationPolicyEnforcer enforcer = createOptionsPolicyEnforcer(invocationPolicyBuilder);

    // Options should be the default since the user never specified it.
    TestOptions testOptions = getTestOptions();
    assertEquals(STRING_FLAG_DEFAULT, testOptions.testString);

    enforcer.enforce(parser, "build");

    // Still the default.
    testOptions = getTestOptions();
    assertEquals(STRING_FLAG_DEFAULT, testOptions.testString);
  }

  @Test
  public void testUseDefaultWithExpansionFlags() throws Exception {
    InvocationPolicy.Builder invocationPolicyBuilder = InvocationPolicy.newBuilder();
    invocationPolicyBuilder.addFlagPoliciesBuilder()
        .setFlagName("test_expansion")
        .getUseDefaultBuilder();

    InvocationPolicyEnforcer enforcer = createOptionsPolicyEnforcer(invocationPolicyBuilder);
    parser.parse("--test_expansion");

    TestOptions testOptions = getTestOptions();
    assertFalse(testOptions.testExpansionA);
    assertFalse(testOptions.testExpansionB);
    assertEquals(42, testOptions.testExpansionC);
    assertEquals("bar", testOptions.testExpansionD);

    enforcer.enforce(parser, "build");

    // After policy enforcement, all the flags that --test_expansion expanded into should be back
    // to their default values.
    testOptions = getTestOptions();
    assertTrue(testOptions.testExpansionA);
    assertTrue(testOptions.testExpansionB);
    assertEquals(12, testOptions.testExpansionC);
    assertEquals("foo", testOptions.testExpansionD);
  }

  @Test
  public void testUseDefaultWithRecursiveExpansionFlags() throws Exception {
    InvocationPolicy.Builder invocationPolicyBuilder = InvocationPolicy.newBuilder();
    invocationPolicyBuilder.addFlagPoliciesBuilder()
        .setFlagName("test_expansion")
        .getUseDefaultBuilder();

    InvocationPolicyEnforcer enforcer = createOptionsPolicyEnforcer(invocationPolicyBuilder);
    parser.parse("--test_recursive_expansion_top_level");

    TestOptions testOptions = getTestOptions();
    assertFalse(testOptions.testExpansionA);
    assertFalse(testOptions.testExpansionB);
    assertEquals(56, testOptions.testExpansionC);
    assertEquals("baz", testOptions.testExpansionD);

    enforcer.enforce(parser, "build");

    // After policy enforcement, all the flags that --test_recursive_expansion_top_level and its
    // recursive expansions set should be back to their default values.
    testOptions = getTestOptions();
    assertTrue(testOptions.testExpansionA);
    assertTrue(testOptions.testExpansionB);
    assertEquals(12, testOptions.testExpansionC);
    assertEquals("foo", testOptions.testExpansionD);
  }

  @Test
  public void testUseDefaultWithExpandedFlags() throws Exception {
    InvocationPolicy.Builder invocationPolicyBuilder = InvocationPolicy.newBuilder();
    invocationPolicyBuilder.addFlagPoliciesBuilder()
        .setFlagName("test_expansion_b")
        .getUseDefaultBuilder();

    InvocationPolicyEnforcer enforcer = createOptionsPolicyEnforcer(invocationPolicyBuilder);
    parser.parse("--test_expansion");

    // --test_expansion should turn set the values from its expansion
    TestOptions testOptions = getTestOptions();
    assertFalse(testOptions.testExpansionA);
    assertFalse(testOptions.testExpansionB);
    assertEquals(42, testOptions.testExpansionC);
    assertEquals("bar", testOptions.testExpansionD);

    enforcer.enforce(parser, "build");

    // After policy enforcement, test_expansion_b should be back to its default (true), but the
    // rest should remain the same.
    testOptions = getTestOptions();
    assertFalse(testOptions.testExpansionA);
    assertTrue(testOptions.testExpansionB);
    assertEquals(42, testOptions.testExpansionC);
    assertEquals("bar", testOptions.testExpansionD);
  }

  @Test
  public void testUseDefaultWithFlagWithImplicitRequirements() throws Exception {
    InvocationPolicy.Builder invocationPolicyBuilder = InvocationPolicy.newBuilder();
    invocationPolicyBuilder.addFlagPoliciesBuilder()
        .setFlagName("test_implicit_requirement")
        .getUseDefaultBuilder();

    InvocationPolicyEnforcer enforcer = createOptionsPolicyEnforcer(invocationPolicyBuilder);
    parser.parse("--test_implicit_requirement=user value");

    // test_implicit_requirement sets an_implicit_requirement to "foo", which ignores the user's
    // value because the parser processes implicit values last.
    TestOptions testOptions = getTestOptions();
    assertEquals("user value", testOptions.testImplicitRequirement);
    assertEquals("foo", testOptions.anImplicitRequirement);

    // Then policy puts test_implicit_requirement and its implicit requirements back to its default.
    enforcer.enforce(parser, "build");

    testOptions = getTestOptions();
    assertEquals("test implicit requirement default", testOptions.testImplicitRequirement);
    assertEquals("implicit default", testOptions.anImplicitRequirement);
  }

  @Test
  public void testUseDefaultWithImplicitlyRequiredFlag() throws Exception {
    InvocationPolicy.Builder invocationPolicyBuilder = InvocationPolicy.newBuilder();
    invocationPolicyBuilder.addFlagPoliciesBuilder()
        .setFlagName("an_implicit_requirement")
        .getUseDefaultBuilder();

    InvocationPolicyEnforcer enforcer = createOptionsPolicyEnforcer(invocationPolicyBuilder);
    parser.parse("--test_implicit_requirement=user value",
        "--an_implicit_requirement=implicit user value");

    // test_implicit_requirement sets an_implicit_requirement to "foo", which ignores the user's
    // value because the parser processes implicit values last.
    TestOptions testOptions = getTestOptions();
    assertEquals("user value", testOptions.testImplicitRequirement);
    assertEquals("foo", testOptions.anImplicitRequirement);

    // Then policy puts an_implicit_requirement back to its default.
    enforcer.enforce(parser, "build");

    testOptions = getTestOptions();
    assertEquals("user value", testOptions.testImplicitRequirement);
    assertEquals("implicit default", testOptions.anImplicitRequirement);
  }

  @Test
  public void testUseDefaultWithFlagWithRecursiveImplicitRequirements() throws Exception {
    InvocationPolicy.Builder invocationPolicyBuilder = InvocationPolicy.newBuilder();
    invocationPolicyBuilder.addFlagPoliciesBuilder()
        .setFlagName("test_recursive_implicit_requirement")
        .getUseDefaultBuilder();

    InvocationPolicyEnforcer enforcer = createOptionsPolicyEnforcer(invocationPolicyBuilder);
    parser.parse("--test_recursive_implicit_requirement=user value");

    // test_recursive_implicit_requirement gets its value from the command line,
    // test_implicit_requirement gets its value from test_recursive_implicit_requirement, and
    // an_implicit_requirement gets its value from test_implicit_requirement.
    TestOptions testOptions = getTestOptions();
    assertEquals("user value", testOptions.testRecursiveImplicitRequirement);
    assertEquals("bar", testOptions.testImplicitRequirement);
    assertEquals("foo", testOptions.anImplicitRequirement);

    enforcer.enforce(parser, "build");

    // Policy enforcement should set everything back to its default value.
    testOptions = getTestOptions();
    assertEquals("test recursive implicit requirement default",
        testOptions.testRecursiveImplicitRequirement);
    assertEquals("test implicit requirement default", testOptions.testImplicitRequirement);
    assertEquals("implicit default", testOptions.anImplicitRequirement);
  }

  /*************************************************************************************************
   * Tests for AllowValues
   ************************************************************************************************/

  /**
   * Tests that AllowValues works in the normal case where the value the user specified is allowed
   * by the policy.
   */
  @Test
  public void testAllowValuesAllowsValue() throws Exception {
    InvocationPolicy.Builder invocationPolicyBuilder = InvocationPolicy.newBuilder();
    invocationPolicyBuilder.addFlagPoliciesBuilder()
        .setFlagName("test_string")
        .getAllowValuesBuilder()
            .addAllowedValues(STRING_FLAG_DEFAULT)
            .addAllowedValues("foo")
            .addAllowedValues("bar");

    InvocationPolicyEnforcer enforcer = createOptionsPolicyEnforcer(invocationPolicyBuilder);
    parser.parse("--test_string=foo");

    // Option should be "foo" as specified by the user.
    TestOptions testOptions = getTestOptions();
    assertEquals("foo", testOptions.testString);

    enforcer.enforce(parser, "build");

    // Still "foo" since "foo" is allowed by the policy.
    testOptions = getTestOptions();
    assertEquals("foo", testOptions.testString);
  }

  @Test
  public void testAllowValuesDisallowsValue() throws Exception {
    InvocationPolicy.Builder invocationPolicyBuilder = InvocationPolicy.newBuilder();
    invocationPolicyBuilder.addFlagPoliciesBuilder()
        .setFlagName("test_string")
        .getAllowValuesBuilder()
            // no foo!
            .addAllowedValues(STRING_FLAG_DEFAULT)
            .addAllowedValues("bar");

    InvocationPolicyEnforcer enforcer = createOptionsPolicyEnforcer(invocationPolicyBuilder);
    parser.parse("--test_string=foo");

    // Option should be "foo" as specified by the user.
    TestOptions testOptions = getTestOptions();
    assertEquals("foo", testOptions.testString);

    try {
      // Should throw because "foo" is not allowed.
      enforcer.enforce(parser, "build");
      fail();
    } catch (OptionsParsingException e) {
      // expected
    }
  }

  @Test
  public void testAllowValuesDisallowsMultipleValues() throws Exception {
    InvocationPolicy.Builder invocationPolicyBuilder = InvocationPolicy.newBuilder();
    invocationPolicyBuilder.addFlagPoliciesBuilder()
        .setFlagName("test_multiple_string")
        .getAllowValuesBuilder()
            .addAllowedValues("foo")
            .addAllowedValues("bar");

    InvocationPolicyEnforcer enforcer = createOptionsPolicyEnforcer(invocationPolicyBuilder);
    parser.parse("--test_multiple_string=baz", "--test_multiple_string=bar");

    // Option should be "baz" and "bar" as specified by the user.
    TestOptions testOptions = getTestOptions();
    assertThat(testOptions.testMultipleString).containsExactly("baz", "bar").inOrder();

    try {
      enforcer.enforce(parser, "build");
      fail();
    } catch (OptionsParsingException e) {
      // expected, since baz is not allowed.
    }
  }

  @Test
  public void testAllowValuesSetsNewValue() throws Exception {
    InvocationPolicy.Builder invocationPolicyBuilder = InvocationPolicy.newBuilder();
    invocationPolicyBuilder.addFlagPoliciesBuilder()
        .setFlagName("test_string")
        .getAllowValuesBuilder()
            .addAllowedValues("foo")
            .addAllowedValues("bar")
            .setNewValue("foo");

    InvocationPolicyEnforcer enforcer = createOptionsPolicyEnforcer(invocationPolicyBuilder);
    parser.parse("--test_string=baz");

    TestOptions testOptions = getTestOptions();
    assertEquals("baz", testOptions.testString);

    enforcer.enforce(parser, "build");

    testOptions = getTestOptions();
    assertEquals("foo", testOptions.testString);
  }

  @Test
  public void testAllowValuesSetsDefaultValue() throws Exception {
    InvocationPolicy.Builder invocationPolicyBuilder = InvocationPolicy.newBuilder();
    invocationPolicyBuilder.addFlagPoliciesBuilder()
        .setFlagName("test_string")
        .getAllowValuesBuilder()
            .addAllowedValues("foo")
            .addAllowedValues(STRING_FLAG_DEFAULT)
            .getUseDefaultBuilder();

    InvocationPolicyEnforcer enforcer = createOptionsPolicyEnforcer(invocationPolicyBuilder);
    parser.parse("--test_string=bar");

    TestOptions testOptions = getTestOptions();
    assertEquals("bar", testOptions.testString);

    enforcer.enforce(parser, "build");

    testOptions = getTestOptions();
    assertEquals(STRING_FLAG_DEFAULT, testOptions.testString);
  }

  @Test
  public void testAllowValuesSetsDefaultValueForRepeatableFlag() throws Exception {
    InvocationPolicy.Builder invocationPolicyBuilder = InvocationPolicy.newBuilder();
    invocationPolicyBuilder.addFlagPoliciesBuilder()
        .setFlagName("test_multiple_string")
        .getAllowValuesBuilder()
            .addAllowedValues("foo")
            .addAllowedValues("bar")
            .getUseDefaultBuilder();

    InvocationPolicyEnforcer enforcer = createOptionsPolicyEnforcer(invocationPolicyBuilder);
    parser.parse("--test_multiple_string=foo", "--test_multiple_string=baz");

    TestOptions testOptions = getTestOptions();
    assertThat(testOptions.testMultipleString).containsExactly("foo", "baz").inOrder();

    enforcer.enforce(parser, "build");

    testOptions = getTestOptions();
    // Default value for repeatable flags is always empty.
    assertThat(testOptions.testMultipleString).isEmpty();
  }
  
  /**
   * Tests that AllowValues sets its default value when the user doesn't provide a value and the
   * flag's default value is disallowed.
   */
  @Test
  public void testAllowValuesSetsNewDefaultWhenFlagDefaultIsDisallowed() throws Exception {
    InvocationPolicy.Builder invocationPolicyBuilder = InvocationPolicy.newBuilder();
    invocationPolicyBuilder.addFlagPoliciesBuilder()
        .setFlagName("test_string")
        .getAllowValuesBuilder()
            // default value from flag's definition is not allowed
            .addAllowedValues("foo")
            .addAllowedValues("bar")
            .setNewValue("new default");

    InvocationPolicyEnforcer enforcer = createOptionsPolicyEnforcer(invocationPolicyBuilder);

    // Option should be its default
    TestOptions testOptions = getTestOptions();
    assertEquals(STRING_FLAG_DEFAULT, testOptions.testString);

    enforcer.enforce(parser, "build");

    // Flag's value should be the default value from the policy.
    testOptions = getTestOptions();
    assertEquals("new default", testOptions.testString);
  }

  @Test
  public void testAllowValuesDisallowsFlagDefaultButNoPolicyDefault() throws Exception {
    InvocationPolicy.Builder invocationPolicyBuilder = InvocationPolicy.newBuilder();
    invocationPolicyBuilder.addFlagPoliciesBuilder()
        .setFlagName("test_string")
        .getAllowValuesBuilder()
            // default value from flag's definition is not allowed, and no alternate default
            // is given.
            .addAllowedValues("foo")
            .addAllowedValues("bar");

    InvocationPolicyEnforcer enforcer = createOptionsPolicyEnforcer(invocationPolicyBuilder);

    // Option should be its default
    TestOptions testOptions = getTestOptions();
    assertEquals(STRING_FLAG_DEFAULT, testOptions.testString);

    try {
      enforcer.enforce(parser, "build");
      fail();
    } catch (OptionsParsingException e) {
      // expected.
    }
  }

  /*************************************************************************************************
   * Tests for DisallowValues
   ************************************************************************************************/

  @Test
  public void testDisallowValuesAllowsValue() throws Exception {
    InvocationPolicy.Builder invocationPolicyBuilder = InvocationPolicy.newBuilder();
    invocationPolicyBuilder.addFlagPoliciesBuilder()
        .setFlagName("test_string")
        .getDisallowValuesBuilder()
            .addDisallowedValues("foo")
            .addDisallowedValues("bar");

    InvocationPolicyEnforcer enforcer = createOptionsPolicyEnforcer(invocationPolicyBuilder);
    parser.parse("--test_string=baz");

    // Option should be "baz" as specified by the user.
    TestOptions testOptions = getTestOptions();
    assertEquals("baz", testOptions.testString);

    enforcer.enforce(parser, "build");

    // Still "baz" since "baz" is allowed by the policy.
    testOptions = getTestOptions();
    assertEquals("baz", testOptions.testString);
  }

  @Test
  public void testDisallowValuesDisallowsValue() throws Exception {
    InvocationPolicy.Builder invocationPolicyBuilder = InvocationPolicy.newBuilder();
    invocationPolicyBuilder.addFlagPoliciesBuilder()
        .setFlagName("test_string")
        .getDisallowValuesBuilder()
            .addDisallowedValues("foo")
            .addDisallowedValues("bar");

    InvocationPolicyEnforcer enforcer = createOptionsPolicyEnforcer(invocationPolicyBuilder);
    parser.parse("--test_string=foo");

    // Option should be "foo" as specified by the user.
    TestOptions testOptions = getTestOptions();
    assertEquals("foo", testOptions.testString);

    try {
      enforcer.enforce(parser, "build");
      fail();
    } catch (OptionsParsingException e) {
      // expected, since foo is disallowed.
    }
  }

  @Test
  public void testDisallowValuesDisallowsMultipleValues() throws Exception {
    InvocationPolicy.Builder invocationPolicyBuilder = InvocationPolicy.newBuilder();
    invocationPolicyBuilder.addFlagPoliciesBuilder()
        .setFlagName("test_multiple_string")
        .getDisallowValuesBuilder()
            .addDisallowedValues("foo")
            .addDisallowedValues("bar");

    InvocationPolicyEnforcer enforcer = createOptionsPolicyEnforcer(invocationPolicyBuilder);
    parser.parse("--test_multiple_string=baz", "--test_multiple_string=bar");

    // Option should be "baz" and "bar" as specified by the user.
    TestOptions testOptions = getTestOptions();
    assertThat(testOptions.testMultipleString).containsExactly("baz", "bar").inOrder();

    try {
      enforcer.enforce(parser, "build");
      fail();
    } catch (OptionsParsingException e) {
      // expected, since bar is disallowed.
    }
  }

  @Test
  public void testDisallowValuesSetsNewValue() throws Exception {
    InvocationPolicy.Builder invocationPolicyBuilder = InvocationPolicy.newBuilder();
    invocationPolicyBuilder.addFlagPoliciesBuilder()
        .setFlagName("test_string")
        .getDisallowValuesBuilder()
            .addDisallowedValues("user value")
            .setNewValue("baz");

    InvocationPolicyEnforcer enforcer = createOptionsPolicyEnforcer(invocationPolicyBuilder);
    parser.parse("--test_string=user value");

    TestOptions testOptions = getTestOptions();
    assertEquals("user value", testOptions.testString);

    enforcer.enforce(parser, "build");

    // Should now be "baz" because the policy forces disallowed values to "baz"
    testOptions = getTestOptions();
    assertEquals("baz", testOptions.testString);
  }

  @Test
  public void testDisallowValuesSetsDefaultValue() throws Exception {
    InvocationPolicy.Builder invocationPolicyBuilder = InvocationPolicy.newBuilder();
    invocationPolicyBuilder.addFlagPoliciesBuilder()
        .setFlagName("test_string")
        .getDisallowValuesBuilder()
            .addDisallowedValues("user value")
            .getUseDefaultBuilder();

    InvocationPolicyEnforcer enforcer = createOptionsPolicyEnforcer(invocationPolicyBuilder);
    parser.parse("--test_string=user value");

    TestOptions testOptions = getTestOptions();
    assertEquals("user value", testOptions.testString);

    enforcer.enforce(parser, "build");

    testOptions = getTestOptions();
    assertEquals(STRING_FLAG_DEFAULT, testOptions.testString);
  }

  @Test
  public void testDisallowValuesSetsDefaultValueForRepeatableFlag() throws Exception {
    InvocationPolicy.Builder invocationPolicyBuilder = InvocationPolicy.newBuilder();
    invocationPolicyBuilder.addFlagPoliciesBuilder()
        .setFlagName("test_multiple_string")
        .getDisallowValuesBuilder()
            .addDisallowedValues("user value")
            .getUseDefaultBuilder();

    InvocationPolicyEnforcer enforcer = createOptionsPolicyEnforcer(invocationPolicyBuilder);
    parser.parse("--test_multiple_string=user value");

    TestOptions testOptions = getTestOptions();
    assertThat(testOptions.testMultipleString).containsExactly("user value");

    enforcer.enforce(parser, "build");

    testOptions = getTestOptions();
    // Default for repeatable flags is always empty.
    assertThat(testOptions.testMultipleString).isEmpty();
  }

  @Test
  public void testDisallowValuesRaisesErrorIfDefaultIsDisallowedAndSetsUseDefault()
      throws Exception {
    InvocationPolicy.Builder invocationPolicyBuilder = InvocationPolicy.newBuilder();
    invocationPolicyBuilder.addFlagPoliciesBuilder()
        .setFlagName("test_string")
        .getDisallowValuesBuilder()
            .addDisallowedValues(STRING_FLAG_DEFAULT)
            .getUseDefaultBuilder();

    InvocationPolicyEnforcer enforcer = createOptionsPolicyEnforcer(invocationPolicyBuilder);

    try {
      enforcer.enforce(parser, "build");
      fail();
    } catch (OptionsParsingException e) {
      assertThat(e.getMessage()).contains("but also specifies to use the default value");
    }
  }
  
  @Test
  public void testDisallowValuesSetsNewValueWhenDefaultIsDisallowed() throws Exception {
    InvocationPolicy.Builder invocationPolicyBuilder = InvocationPolicy.newBuilder();
    invocationPolicyBuilder.addFlagPoliciesBuilder()
        .setFlagName("test_string")
        .getDisallowValuesBuilder()
            .addDisallowedValues(STRING_FLAG_DEFAULT)
            .setNewValue("baz");

    InvocationPolicyEnforcer enforcer = createOptionsPolicyEnforcer(invocationPolicyBuilder);

    // Option should be the default since the use didn't specify a value.
    TestOptions testOptions = getTestOptions();
    assertEquals(STRING_FLAG_DEFAULT, testOptions.testString);

    enforcer.enforce(parser, "build");

    // Should now be "baz" because the policy set the new default to "baz"
    testOptions = getTestOptions();
    assertEquals("baz", testOptions.testString);
  }

  @Test
  public void testDisallowValuesDisallowsFlagDefaultButNoPolicyDefault() throws Exception {
    InvocationPolicy.Builder invocationPolicyBuilder = InvocationPolicy.newBuilder();
    invocationPolicyBuilder.addFlagPoliciesBuilder()
        .setFlagName("test_string")
        .getDisallowValuesBuilder()
            // No new default is set
            .addDisallowedValues(STRING_FLAG_DEFAULT);

    InvocationPolicyEnforcer enforcer = createOptionsPolicyEnforcer(invocationPolicyBuilder);

    // Option should be the default since the use didn't specify a value.
    TestOptions testOptions = getTestOptions();
    assertEquals(STRING_FLAG_DEFAULT, testOptions.testString);

    try {
      enforcer.enforce(parser, "build");
      fail();
    } catch (OptionsParsingException e) {
      // expected.
    }
  }
  
  /*************************************************************************************************
   * Other tests
   ************************************************************************************************/

  @Test
  public void testFlagPolicyDoesNotApply() throws Exception {
    InvocationPolicy.Builder invocationPolicyBuilder = InvocationPolicy.newBuilder();
    invocationPolicyBuilder.addFlagPoliciesBuilder()
        .setFlagName("test_string")
        .addCommands("build")
        .getSetValueBuilder()
            .addFlagValue("policy value");

    InvocationPolicyEnforcer enforcer = createOptionsPolicyEnforcer(invocationPolicyBuilder);
    parser.parse("--test_string=user value");

    TestOptions testOptions = getTestOptions();
    assertEquals("user value", testOptions.testString);

    enforcer.enforce(parser, "test");

    // Still user value.
    testOptions = getTestOptions();
    assertEquals("user value", testOptions.testString);
  }

  @Test
  public void testNonExistantFlagFromPolicy() throws Exception {
    InvocationPolicy.Builder invocationPolicyBuilder = InvocationPolicy.newBuilder();
    invocationPolicyBuilder.addFlagPoliciesBuilder()
        .setFlagName("i_do_not_exist")
        .getSetValueBuilder()
            .addFlagValue("policy value 1");
    invocationPolicyBuilder.addFlagPoliciesBuilder()
        .setFlagName("test_string")
        .getSetValueBuilder()
            .addFlagValue("policy value 2");
    
    InvocationPolicyEnforcer enforcer = createOptionsPolicyEnforcer(invocationPolicyBuilder);
    parser.parse("--test_string=user value");

    TestOptions testOptions = getTestOptions();
    assertEquals("user value", testOptions.testString);

    enforcer.enforce(parser, "test");

    // Still user value.
    testOptions = getTestOptions();
    assertEquals("policy value 2", testOptions.testString);
  }

  @Test
  public void testOperationNotSet() throws Exception {
    InvocationPolicy.Builder invocationPolicyBuilder = InvocationPolicy.newBuilder();
    invocationPolicyBuilder.addFlagPoliciesBuilder();
    // No operations added to the flag policy
    
    InvocationPolicyEnforcer enforcer = createOptionsPolicyEnforcer(invocationPolicyBuilder);
    parser.parse("--test_string=user value");
    
    TestOptions testOptions = getTestOptions();
    assertEquals("user value", testOptions.testString);

    // Shouldn't throw.
    enforcer.enforce(parser, "test");

    // Still user value.
    testOptions = getTestOptions();
    assertEquals("user value", testOptions.testString);
  }
}
