content: /*
   * Licensed to the Apache Software Foundation (ASF) under one
   * or more contributor license agreements.  See the NOTICE file
   * distributed with this work for additional information
   * regarding copyright ownership.  The ASF licenses this file
   * to you 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 org.apache.beam.sdk.options;
  
  import org.apache.beam.sdk.testing.CrashingRunner;
  import org.junit.Rule;
  import org.junit.Test;
  import org.junit.rules.ExpectedException;
  import org.junit.runner.RunWith;
  import org.junit.runners.JUnit4;
  
  /** Tests for {@link PipelineOptionsValidator}. */
  @RunWith(JUnit4.class)
  public class PipelineOptionsValidatorTest {
    @Rule public ExpectedException expectedException = ExpectedException.none();
  
    /** A test interface with an {@link Validation.Required} annotation. */
    public interface Required extends PipelineOptions {
      @Validation.Required
      @Description("Fake Description")
      String getObject();
  
      void setObject(String value);
    }
  
    @Test
    public void testWhenRequiredOptionIsSet() {
      Required required = PipelineOptionsFactory.as(Required.class);
      required.setRunner(CrashingRunner.class);
      required.setObject("blah");
      PipelineOptionsValidator.validate(Required.class, required);
    }
  
    @Test
    public void testWhenRequiredOptionIsSetAndCleared() {
      expectedException.expect(IllegalArgumentException.class);
      expectedException.expectMessage(
          "Missing required value for "
              + "[public abstract java.lang.String org.apache.beam."
              + "sdk.options.PipelineOptionsValidatorTest$Required.getObject(), \"Fake Description\"].");
  
      Required required = PipelineOptionsFactory.as(Required.class);
      required.setObject("blah");
      required.setObject(null);
      PipelineOptionsValidator.validate(Required.class, required);
    }
  
    @Test
    public void testWhenRequiredOptionIsSetAndClearedCli() {
      expectedException.expect(IllegalArgumentException.class);
      expectedException.expectMessage(
          "Missing required value for " + "[--object, \"Fake Description\"].");
  
      Required required =
          PipelineOptionsFactory.fromArgs(new String[] {"--object=blah"}).as(Required.class);
      required.setObject(null);
      PipelineOptionsValidator.validateCli(Required.class, required);
    }
  
    @Test
    public void testWhenRequiredOptionIsNeverSet() {
      expectedException.expect(IllegalArgumentException.class);
      expectedException.expectMessage(
          "Missing required value for "
              + "[public abstract java.lang.String org.apache.beam."
              + "sdk.options.PipelineOptionsValidatorTest$Required.getObject(), \"Fake Description\"].");
  
      Required required = PipelineOptionsFactory.as(Required.class);
      PipelineOptionsValidator.validate(Required.class, required);
    }
  
    @Test
    public void testWhenRequiredOptionIsNeverSetCli() {
      expectedException.expect(IllegalArgumentException.class);
      expectedException.expectMessage(
          "Missing required value for " + "[--object, \"Fake Description\"].");
  
      Required required = PipelineOptionsFactory.fromArgs(new String[] {}).as(Required.class);
      PipelineOptionsValidator.validateCli(Required.class, required);
    }
  
    @Test
    public void testWhenRequiredOptionIsNeverSetOnSuperInterface() {
      expectedException.expect(IllegalArgumentException.class);
      expectedException.expectMessage(
          "Missing required value for "
              + "[public abstract java.lang.String org.apache.beam."
              + "sdk.options.PipelineOptionsValidatorTest$Required.getObject(), \"Fake Description\"].");
  
      PipelineOptions options = PipelineOptionsFactory.create();
      PipelineOptionsValidator.validate(Required.class, options);
    }
  
    @Test
    public void testWhenRequiredOptionIsNeverSetOnSuperInterfaceCli() {
      expectedException.expect(IllegalArgumentException.class);
      expectedException.expectMessage(
          "Missing required value for " + "[--object, \"Fake Description\"].");
  
      PipelineOptions options = PipelineOptionsFactory.fromArgs(new String[] {}).create();
      PipelineOptionsValidator.validateCli(Required.class, options);
    }
  
    /** A test interface that overrides the parent's method. */
    public interface SubClassValidation extends Required {
      @Override
      String getObject();
  
      @Override
      void setObject(String value);
    }
  
    @Test
    public void testValidationOnOverriddenMethods() throws Exception {
      expectedException.expect(IllegalArgumentException.class);
      expectedException.expectMessage(
          "Missing required value for "
              + "[public abstract java.lang.String org.apache.beam."
              + "sdk.options.PipelineOptionsValidatorTest$Required.getObject(), \"Fake Description\"].");
  
      SubClassValidation required = PipelineOptionsFactory.as(SubClassValidation.class);
      PipelineOptionsValidator.validate(Required.class, required);
    }
  
    @Test
    public void testValidationOnOverriddenMethodsCli() throws Exception {
      expectedException.expect(IllegalArgumentException.class);
      expectedException.expectMessage(
          "Missing required value for " + "[--object, \"Fake Description\"].");
  
      SubClassValidation required =
          PipelineOptionsFactory.fromArgs(new String[] {}).as(SubClassValidation.class);
      PipelineOptionsValidator.validateCli(Required.class, required);
    }
  
    /** A test interface with a required group. */
    public interface GroupRequired extends PipelineOptions {
      @Validation.Required(groups = {"ham"})
      String getFoo();
  
      void setFoo(String foo);
  
      @Validation.Required(groups = {"ham"})
      String getBar();
  
      void setBar(String bar);
    }
  
    @Test
    public void testWhenOneOfRequiredGroupIsSetIsValid() {
      GroupRequired groupRequired = PipelineOptionsFactory.as(GroupRequired.class);
      groupRequired.setFoo("foo");
      groupRequired.setBar(null);
      groupRequired.setRunner(CrashingRunner.class);
  
      PipelineOptionsValidator.validate(GroupRequired.class, groupRequired);
  
      // Symmetric
      groupRequired.setFoo(null);
      groupRequired.setBar("bar");
      PipelineOptionsValidator.validate(GroupRequired.class, groupRequired);
    }
  
    @Test
    public void testWhenNoneOfRequiredGroupIsSetThrowsException() {
      GroupRequired groupRequired = PipelineOptionsFactory.as(GroupRequired.class);
      groupRequired.setRunner(CrashingRunner.class);
  
      expectedException.expect(IllegalArgumentException.class);
      expectedException.expectMessage("Missing required value for group [ham]");
      expectedException.expectMessage("properties");
      expectedException.expectMessage("getFoo");
      expectedException.expectMessage("getBar");
  
      PipelineOptionsValidator.validate(GroupRequired.class, groupRequired);
    }
  
    /** A test interface with a member in multiple required groups. */
    public interface MultiGroupRequired extends PipelineOptions {
      @Validation.Required(groups = {"spam", "ham"})
      String getFoo();
  
      void setFoo(String foo);
  
      @Validation.Required(groups = {"spam"})
      String getBar();
  
      void setBar(String bar);
  
      @Validation.Required(groups = {"ham"})
      String getBaz();
  
      void setBaz(String baz);
    }
  
    @Test
    public void testWhenOneOfMultipleRequiredGroupsIsSetIsValid() {
      MultiGroupRequired multiGroupRequired = PipelineOptionsFactory.as(MultiGroupRequired.class);
  
      multiGroupRequired.setRunner(CrashingRunner.class);
      multiGroupRequired.setFoo("eggs");
  
      PipelineOptionsValidator.validate(MultiGroupRequired.class, multiGroupRequired);
    }
  
    /** Test interface. */
    public interface LeftOptions extends PipelineOptions {
      @Validation.Required(groups = {"left"})
      String getFoo();
  
      void setFoo(String foo);
  
      @Validation.Required(groups = {"left"})
      String getLeft();
  
      void setLeft(String left);
  
      @Validation.Required(groups = {"both"})
      String getBoth();
  
      void setBoth(String both);
    }
  
    /** Test interface. */
    public interface RightOptions extends PipelineOptions {
      @Validation.Required(groups = {"right"})
      String getFoo();
  
      void setFoo(String foo);
  
      @Validation.Required(groups = {"right"})
      String getRight();
  
      void setRight(String right);
  
      @Validation.Required(groups = {"both"})
      String getBoth();
  
      void setBoth(String both);
    }
  
    /** Test interface. */
    public interface JoinedOptions extends LeftOptions, RightOptions {}
  
    @Test
    public void testWhenOptionIsDefinedInMultipleSuperInterfacesAndIsNotPresentFailsRequirement() {
      RightOptions rightOptions = PipelineOptionsFactory.as(RightOptions.class);
      rightOptions.setBoth("foo");
      rightOptions.setRunner(CrashingRunner.class);
  
      expectedException.expect(IllegalArgumentException.class);
      expectedException.expectMessage("Missing required value for group");
      expectedException.expectMessage("getFoo");
  
      PipelineOptionsValidator.validate(JoinedOptions.class, rightOptions);
    }
  
    @Test
    public void testWhenOptionIsDefinedInMultipleSuperInterfacesMeetsGroupRequirement() {
      RightOptions rightOpts = PipelineOptionsFactory.as(RightOptions.class);
      rightOpts.setFoo("true");
      rightOpts.setBoth("bar");
  
      LeftOptions leftOpts = PipelineOptionsFactory.as(LeftOptions.class);
      leftOpts.setFoo("Untrue");
      leftOpts.setBoth("Raise the");
  
      rightOpts.setRunner(CrashingRunner.class);
      leftOpts.setRunner(CrashingRunner.class);
      PipelineOptionsValidator.validate(JoinedOptions.class, rightOpts);
      PipelineOptionsValidator.validate(JoinedOptions.class, leftOpts);
    }
  
    @Test
    public void testWhenOptionIsDefinedOnOtherOptionsClassMeetsGroupRequirement() {
      RightOptions rightOpts = PipelineOptionsFactory.as(RightOptions.class);
      rightOpts.setFoo("true");
      rightOpts.setBoth("bar");
  
      LeftOptions leftOpts = PipelineOptionsFactory.as(LeftOptions.class);
      leftOpts.setFoo("Untrue");
      leftOpts.setBoth("Raise the");
  
      rightOpts.setRunner(CrashingRunner.class);
      leftOpts.setRunner(CrashingRunner.class);
      PipelineOptionsValidator.validate(RightOptions.class, leftOpts);
      PipelineOptionsValidator.validate(LeftOptions.class, rightOpts);
    }
  
    @Test
    public void testWhenOptionIsDefinedOnMultipleInterfacesOnlyListedOnceWhenNotPresent() {
      JoinedOptions options = PipelineOptionsFactory.as(JoinedOptions.class);
      options.setFoo("Hello");
      options.setRunner(CrashingRunner.class);
  
      expectedException.expect(IllegalArgumentException.class);
      expectedException.expectMessage("required value for group [both]");
      expectedException.expectMessage("properties [getBoth()]");
  
      PipelineOptionsValidator.validate(JoinedOptions.class, options);
    }
  
    /** Test interface. */
    public interface SuperOptions extends PipelineOptions {
      @Validation.Required(groups = {"super"})
      String getFoo();
  
      void setFoo(String foo);
  
      @Validation.Required(groups = {"sub"})
      String getBar();
  
      void setBar(String bar);
  
      @Validation.Required(groups = {"otherSuper"})
      String getSuperclassObj();
  
      void setSuperclassObj(String sup);
    }
  
    /** Test interface. */
    public interface SubOptions extends SuperOptions {
      @Override
      @Validation.Required(groups = {"sub"})
      String getFoo();
  
      @Override
      void setFoo(String foo);
  
      @Override
      String getSuperclassObj();
  
      @Override
      void setSuperclassObj(String sup);
    }
  
    @Test
    public void testSuperInterfaceRequiredOptionsAlsoRequiredInSubInterface() {
      SubOptions subOpts = PipelineOptionsFactory.as(SubOptions.class);
      subOpts.setFoo("Bar");
      subOpts.setRunner(CrashingRunner.class);
  
      expectedException.expect(IllegalArgumentException.class);
      expectedException.expectMessage("otherSuper");
      expectedException.expectMessage("Missing required value");
      expectedException.expectMessage("getSuperclassObj");
  
      PipelineOptionsValidator.validate(SubOptions.class, subOpts);
    }
  
    @Test
    public void
        testSuperInterfaceGroupIsInAdditionToSubInterfaceGroupOnlyWhenValidatingSuperInterface() {
      SubOptions opts = PipelineOptionsFactory.as(SubOptions.class);
      opts.setFoo("Foo");
      opts.setSuperclassObj("Hello world");
      opts.setRunner(CrashingRunner.class);
  
      // Valid SubOptions, but invalid SuperOptions
      PipelineOptionsValidator.validate(SubOptions.class, opts);
  
      expectedException.expectMessage("sub");
      expectedException.expectMessage("Missing required value");
      expectedException.expectMessage("getBar");
      PipelineOptionsValidator.validate(SuperOptions.class, opts);
    }
  
    @Test
    public void testSuperInterfaceRequiredOptionsSatisfiedBySubInterface() {
      SubOptions subOpts = PipelineOptionsFactory.as(SubOptions.class);
      subOpts.setFoo("bar");
      subOpts.setBar("bar");
      subOpts.setSuperclassObj("SuperDuper");
      subOpts.setRunner(CrashingRunner.class);
  
      PipelineOptionsValidator.validate(SubOptions.class, subOpts);
      PipelineOptionsValidator.validate(SuperOptions.class, subOpts);
    }
  }
  