@Test
  public void shouldAddASingleAnnotatedFieldToRuleKey() {
    BuildTarget target = BuildTargetFactory.newInstance("//cheese:peas");
    SourcePathResolver pathResolver = new SourcePathResolver(new BuildRuleResolver());
    BuildRule rule = new EmptyRule(target);

    DefaultRuleKeyBuilderFactory factory =
        new DefaultRuleKeyBuilderFactory(new NullFileHashCache(), pathResolver);
    RuleKeyBuilder builder = factory.newInstance(rule);

    builder.setReflectively("field", "cake-walk");
    RuleKey expected = builder.build();

    class DecoratedFields extends EmptyRule {

      @AddToRuleKey private String field = "cake-walk";

      public DecoratedFields(BuildTarget target) {
        super(target);
      }
    }

    RuleKey seen = factory.build(new DecoratedFields(target));

    assertEquals(expected, seen);
  }
  @Test
  public void fieldsAreAddedInAlphabeticalOrder() {
    BuildTarget target = BuildTargetFactory.newInstance("//cheese:peas");
    SourcePathResolver pathResolver = new SourcePathResolver(new BuildRuleResolver());
    BuildRule rule = new EmptyRule(target);

    DefaultRuleKeyBuilderFactory factory =
        new DefaultRuleKeyBuilderFactory(new NullFileHashCache(), pathResolver);
    RuleKeyBuilder builder = factory.newInstance(rule);

    builder.setReflectively("alpha", "stilton");
    builder.setReflectively("beta", 1);
    builder.setReflectively("gamma", "stinking bishop");
    RuleKey expected = builder.build();

    class UnsortedFields extends EmptyRule {

      @AddToRuleKey private String gamma = "stinking bishop";
      @AddToRuleKey private int beta = 1;
      @AddToRuleKey private String alpha = "stilton";

      public UnsortedFields(BuildTarget target) {
        super(target);
      }
    }

    RuleKey seen = factory.build(new UnsortedFields(target));

    assertEquals(expected, seen);
  }
  @Test
  public void fieldsFromParentClassesAreAlsoAdded() {
    BuildTarget target = BuildTargetFactory.newInstance("//cheese:peas");
    SourcePathResolver pathResolver = new SourcePathResolver(new BuildRuleResolver());
    BuildRule rule = new EmptyRule(target);

    DefaultRuleKeyBuilderFactory factory =
        new DefaultRuleKeyBuilderFactory(new NullFileHashCache(), pathResolver);
    RuleKeyBuilder builder = factory.newInstance(rule);

    builder.setReflectively("key", "child");
    builder.setReflectively("key", "parent");
    RuleKey expected = builder.build();

    class Parent extends EmptyRule {
      @AddToRuleKey private String key = "parent";

      public Parent(BuildTarget target) {
        super(target);
      }
    }

    class Child extends Parent {
      @AddToRuleKey private String key = "child";

      public Child(BuildTarget target) {
        super(target);
      }
    }

    RuleKey seen = factory.build(new Child(target));

    assertEquals(expected, seen);
  }
  @Test
  public void stringifiedRuleKeyAppendablesGetAddedToRuleKeyAsStrings() {
    BuildTarget target = BuildTargetFactory.newInstance("//cheese:peas");
    SourcePathResolver pathResolver = new SourcePathResolver(new BuildRuleResolver());
    BuildRule rule = new EmptyRule(target);

    DefaultRuleKeyBuilderFactory factory =
        new DefaultRuleKeyBuilderFactory(new NullFileHashCache(), pathResolver);
    RuleKeyBuilder builder = factory.newInstance(rule);

    builder.setReflectively("field", "cheddar");
    RuleKey expected = builder.build();

    class AppendingField extends EmptyRule {

      @AddToRuleKey(stringify = true)
      private Appender field = new Appender();

      public AppendingField(BuildTarget target) {
        super(target);
      }
    }

    RuleKey seen = factory.build(new AppendingField(target));

    assertEquals(expected, seen);
  }
  @Test
  public void shouldAllowRuleKeyAppendablesToAppendToRuleKey() {
    BuildTarget target = BuildTargetFactory.newInstance("//cheese:peas");
    SourcePathResolver pathResolver = new SourcePathResolver(new BuildRuleResolver());
    BuildRule rule = new EmptyRule(target);

    FileHashCache fileHashCache = new NullFileHashCache();
    DefaultRuleKeyBuilderFactory factory =
        new DefaultRuleKeyBuilderFactory(fileHashCache, pathResolver);

    RuleKey subKey =
        new RuleKeyBuilder(pathResolver, fileHashCache).setReflectively("cheese", "brie").build();

    RuleKeyBuilder builder = factory.newInstance(rule);
    builder.setReflectively("field.appendableSubKey", subKey);
    RuleKey expected = builder.build();

    class AppendingField extends EmptyRule {

      @AddToRuleKey private Appender field = new Appender();

      public AppendingField(BuildTarget target) {
        super(target);
      }
    }

    RuleKey seen = factory.build(new AppendingField(target));

    assertEquals(expected, seen);
  }
  @Test
  public void shouldAllowAFieldToBeStringified() {
    BuildTarget target = BuildTargetFactory.newInstance("//cheese:peas");
    SourcePathResolver pathResolver = new SourcePathResolver(new BuildRuleResolver());
    BuildRule rule = new EmptyRule(target);

    DefaultRuleKeyBuilderFactory factory =
        new DefaultRuleKeyBuilderFactory(new NullFileHashCache(), pathResolver);
    RuleKeyBuilder builder = factory.newInstance(rule);

    builder.setReflectively("field", "sausages");
    RuleKey expected = builder.build();

    class Stringifiable {
      @Override
      public String toString() {
        return "sausages";
      }
    }

    class StringifiedField extends EmptyRule {

      @AddToRuleKey(stringify = true)
      private Stringifiable field = new Stringifiable();

      public StringifiedField(BuildTarget target) {
        super(target);
      }
    }

    RuleKey seen = factory.build(new StringifiedField(target));

    assertEquals(expected, seen);
  }
  @Test
  public void annotatedAppendableBuildRulesIncludeTheirRuleKey() {
    BuildTarget target = BuildTargetFactory.newInstance("//cheese:peas");
    BuildTarget depTarget = BuildTargetFactory.newInstance("//cheese:more-peas");
    SourcePathResolver pathResolver = new SourcePathResolver(new BuildRuleResolver());
    BuildRule rule = new EmptyRule(target);

    FileHashCache fileHashCache = new NullFileHashCache();
    DefaultRuleKeyBuilderFactory factory =
        new DefaultRuleKeyBuilderFactory(fileHashCache, pathResolver);

    class AppendableRule extends EmptyRule implements RuleKeyAppendable {
      public AppendableRule(BuildTarget target) {
        super(target);
      }

      @Override
      public RuleKeyBuilder appendToRuleKey(RuleKeyBuilder builder) {
        return builder.setReflectively("cheese", "brie");
      }

      @Override
      public RuleKey getRuleKey() {
        return new RuleKey("abcd");
      }
    }

    AppendableRule appendableRule = new AppendableRule(depTarget);

    RuleKey subKey =
        new RuleKeyBuilder(pathResolver, fileHashCache).setReflectively("cheese", "brie").build();

    RuleKeyBuilder builder = factory.newInstance(rule);
    builder.setReflectively("field.appendableSubKey", subKey);
    builder.setReflectively("field", appendableRule.getRuleKey());
    RuleKey expected = builder.build();

    class RuleContainingAppendableRule extends EmptyRule {
      @AddToRuleKey private final AppendableRule field;

      public RuleContainingAppendableRule(BuildTarget target, AppendableRule appendableRule) {
        super(target);
        this.field = appendableRule;
      }
    }

    RuleKey seen = factory.build(new RuleContainingAppendableRule(target, appendableRule));

    assertEquals(expected, seen);
  }
  @Test
  public void fieldsFromParentClassesShouldBeAddedAndFieldsRetainOverallAlphabeticalOrdering() {
    BuildTarget topLevelTarget = BuildTargetFactory.newInstance("//cheese:peas");
    SourcePathResolver pathResolver = new SourcePathResolver(new BuildRuleResolver());
    BuildRule rule = new EmptyRule(topLevelTarget);

    DefaultRuleKeyBuilderFactory factory =
        new DefaultRuleKeyBuilderFactory(new NullFileHashCache(), pathResolver);
    RuleKeyBuilder builder = factory.newInstance(rule);

    builder.setReflectively("exoticCheese", "bavarian smoked");
    builder.setReflectively("target", topLevelTarget.getFullyQualifiedName());
    RuleKey expected = builder.build();

    class Parent extends EmptyRule {

      @AddToRuleKey private BuildTarget target;

      public Parent(BuildTarget target) {
        super(target);
        this.target = target;
      }
    }

    class Child extends Parent {

      @AddToRuleKey private String exoticCheese = "bavarian smoked";

      public Child(BuildTarget target) {
        super(target);
      }
    }

    RuleKey seen = factory.build(new Child(topLevelTarget));

    assertEquals(expected, seen);
  }