// 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.packages;

import static com.google.common.truth.Truth.assertThat;
import static com.google.devtools.build.lib.packages.Attribute.attr;
import static com.google.devtools.build.lib.syntax.Type.BOOLEAN;
import static com.google.devtools.build.lib.syntax.Type.INTEGER;
import static com.google.devtools.build.lib.syntax.Type.STRING;
import static com.google.devtools.build.lib.syntax.Type.STRING_LIST;
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.devtools.build.lib.packages.RuleClass.Builder.RuleClassType;
import com.google.devtools.build.lib.packages.util.PackageLoadingTestCase;

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

/**
 * Tests for the {@link RuleClass.Builder}.
 */
@RunWith(JUnit4.class)
public class RuleClassBuilderTest extends PackageLoadingTestCase {
  private static final RuleClass.ConfiguredTargetFactory<Object, Object>
      DUMMY_CONFIGURED_TARGET_FACTORY =
          new RuleClass.ConfiguredTargetFactory<Object, Object>() {
            @Override
            public Object create(Object ruleContext) throws InterruptedException {
              throw new IllegalStateException();
            }
          };

  @Test
  public void testRuleClassBuilderBasics() throws Exception {
    RuleClass ruleClassA =
        new RuleClass.Builder("ruleA", RuleClassType.NORMAL, false)
            .factory(DUMMY_CONFIGURED_TARGET_FACTORY)
            .add(attr("srcs", BuildType.LABEL_LIST).legacyAllowAnyFileType())
            .add(attr("tags", STRING_LIST))
            .add(attr("X", com.google.devtools.build.lib.syntax.Type.INTEGER).mandatory())
            .build();

    assertEquals("ruleA", ruleClassA.getName());
    assertEquals(3, ruleClassA.getAttributeCount());
    assertTrue(ruleClassA.hasBinaryOutput());

    assertEquals(0, (int) ruleClassA.getAttributeIndex("srcs"));
    assertEquals(ruleClassA.getAttribute(0), ruleClassA.getAttributeByName("srcs"));

    assertEquals(1, (int) ruleClassA.getAttributeIndex("tags"));
    assertEquals(ruleClassA.getAttribute(1), ruleClassA.getAttributeByName("tags"));

    assertEquals(2, (int) ruleClassA.getAttributeIndex("X"));
    assertEquals(ruleClassA.getAttribute(2), ruleClassA.getAttributeByName("X"));
  }

  @Test
  public void testRuleClassBuilderTestIsBinary() throws Exception {
    RuleClass ruleClassA =
        new RuleClass.Builder("rule_test", RuleClassType.TEST, false)
            .factory(DUMMY_CONFIGURED_TARGET_FACTORY)
            .add(attr("tags", STRING_LIST))
            .add(attr("size", STRING).value("medium"))
            .add(attr("timeout", STRING))
            .add(attr("flaky", BOOLEAN).value(false))
            .add(attr("shard_count", INTEGER).value(-1))
            .add(attr("local", BOOLEAN))
            .build();
    assertTrue(ruleClassA.hasBinaryOutput());
  }

  @Test
  public void testRuleClassBuilderGenruleIsNotBinary() throws Exception {
    RuleClass ruleClassA =
        new RuleClass.Builder("ruleA", RuleClassType.NORMAL, false)
            .factory(DUMMY_CONFIGURED_TARGET_FACTORY)
            .setOutputToGenfiles()
            .add(attr("tags", STRING_LIST))
            .build();
    assertFalse(ruleClassA.hasBinaryOutput());
  }

  @Test
  public void testRuleClassTestNameValidity() throws Exception {
    try {
      new RuleClass.Builder("ruleA", RuleClassType.TEST, false).build();
      fail();
    } catch (IllegalArgumentException e) {
      // Expected exception.
    }
  }

  @Test
  public void testRuleClassNormalNameValidity() throws Exception {
    try {
      new RuleClass.Builder("ruleA_test", RuleClassType.NORMAL, false).build();
      fail();
    } catch (IllegalArgumentException e) {
      // Expected exception.
    }
  }

  @Test
  public void testDuplicateAttribute() throws Exception {
    RuleClass.Builder builder =
        new RuleClass.Builder("ruleA", RuleClassType.NORMAL, false).add(attr("a", STRING));
    try {
      builder.add(attr("a", STRING));
      fail();
    } catch (IllegalStateException e) {
      // Expected exception.
    }
  }

  @Test
  public void testPropertiesOfAbstractRuleClass() throws Exception {
    try {
      new RuleClass.Builder("$ruleA", RuleClassType.ABSTRACT, false).setOutputToGenfiles();
      fail();
    } catch (IllegalStateException e) {
      // Expected exception.
    }

    try {
      new RuleClass.Builder("$ruleB", RuleClassType.ABSTRACT, false)
          .setImplicitOutputsFunction(null);
      fail();
    } catch (IllegalStateException e) {
      // Expected exception.
    }
  }

  @Test
  public void testDuplicateInheritedAttribute() throws Exception {
    RuleClass a =
        new RuleClass.Builder("ruleA", RuleClassType.NORMAL, false)
            .factory(DUMMY_CONFIGURED_TARGET_FACTORY)
            .add(attr("a", STRING).value("A"))
            .add(attr("tags", STRING_LIST))
            .build();
    RuleClass b =
        new RuleClass.Builder("ruleB", RuleClassType.NORMAL, false)
            .factory(DUMMY_CONFIGURED_TARGET_FACTORY)
            .add(attr("a", STRING).value("B"))
            .add(attr("tags", STRING_LIST))
            .build();
    try {
      // In case of multiple attribute inheritance the attributes must equal
      new RuleClass.Builder("ruleC", RuleClassType.NORMAL, false, a, b).build();
      fail();
    } catch (IllegalArgumentException e) {
      assertThat(e).hasMessage("Attribute a is inherited multiple times in ruleC ruleclass");
    }
  }

  @Test
  public void testRemoveAttribute() throws Exception {
    RuleClass a =
        new RuleClass.Builder("rule", RuleClassType.NORMAL, false)
            .factory(DUMMY_CONFIGURED_TARGET_FACTORY)
            .add(attr("a", STRING))
            .add(attr("b", STRING))
            .add(attr("tags", STRING_LIST))
            .build();
    RuleClass.Builder builder =
        new RuleClass.Builder("c", RuleClassType.NORMAL, false, a)
            .factory(DUMMY_CONFIGURED_TARGET_FACTORY);
    RuleClass c = builder.removeAttribute("a").add(attr("a", INTEGER)).removeAttribute("b").build();
    assertFalse(c.hasAttr("a", STRING));
    assertTrue(c.hasAttr("a", INTEGER));
    assertFalse(c.hasAttr("b", STRING));

    try {
      builder.removeAttribute("c");
      fail();
    } catch (IllegalStateException e) {
      // Expected exception.
    }
  }
}
