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

import static com.google.common.truth.Truth.assertThat;
import static com.google.devtools.build.skyframe.EvaluationResultSubjectFactory.assertThatEvaluationResult;

import com.google.common.collect.ImmutableList;
import com.google.common.testing.EqualsTester;
import com.google.common.truth.DefaultSubject;
import com.google.common.truth.Subject;
import com.google.devtools.build.lib.analysis.platform.ConstraintSettingInfo;
import com.google.devtools.build.lib.analysis.platform.ConstraintValueInfo;
import com.google.devtools.build.lib.analysis.platform.DeclaredToolchainInfo;
import com.google.devtools.build.lib.analysis.platform.PlatformInfo;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.rules.platform.ToolchainTestCase;
import com.google.devtools.build.lib.skyframe.util.SkyframeExecutorTestUtils;
import com.google.devtools.build.skyframe.EvaluationResult;
import com.google.devtools.build.skyframe.SkyKey;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

/** Tests for {@link ToolchainResolutionValue} and {@link ToolchainResolutionFunction}. */
@RunWith(JUnit4.class)
public class ToolchainResolutionFunctionTest extends ToolchainTestCase {

  private EvaluationResult<ToolchainResolutionValue> invokeToolchainResolution(SkyKey key)
      throws InterruptedException {

    try {
      getSkyframeExecutor().getSkyframeBuildView().enableAnalysis(true);
      return SkyframeExecutorTestUtils.evaluate(
          getSkyframeExecutor(), key, /*keepGoing=*/ false, reporter);
    } finally {
      getSkyframeExecutor().getSkyframeBuildView().enableAnalysis(false);
    }
  }

  @Test
  public void testResolution() throws Exception {
    SkyKey key =
        ToolchainResolutionValue.key(targetConfig, testToolchainType, linuxPlatform, macPlatform);
    EvaluationResult<ToolchainResolutionValue> result = invokeToolchainResolution(key);

    assertThatEvaluationResult(result).hasNoError();

    ToolchainResolutionValue toolchainResolutionValue = result.get(key);
    assertThat(toolchainResolutionValue.toolchainLabel())
        .isEqualTo(makeLabel("//toolchain:test_toolchain_2"));
  }

  @Test
  public void testResolution_flagOverride() throws Exception {
    // Add extra toolchain.
    scratch.file(
        "extra/BUILD",
        "load('//toolchain:toolchain_def.bzl', 'test_toolchain')",
        "test_toolchain(",
        "  name='extra_toolchain_impl',",
        "  data = 'extra')");

    useConfiguration(
        "--toolchain_resolution_override=" + testToolchainType + "=//extra:extra_toolchain_impl");

    SkyKey key =
        ToolchainResolutionValue.key(targetConfig, testToolchainType, linuxPlatform, macPlatform);
    EvaluationResult<ToolchainResolutionValue> result = invokeToolchainResolution(key);

    assertThatEvaluationResult(result).hasNoError();

    ToolchainResolutionValue toolchainResolutionValue = result.get(key);
    assertThat(toolchainResolutionValue.toolchainLabel())
        .isEqualTo(makeLabel("//extra:extra_toolchain_impl"));
  }

  @Test
  public void testResolution_noneFound() throws Exception {
    // Clear the toolchains.
    rewriteWorkspace();

    SkyKey key =
        ToolchainResolutionValue.key(targetConfig, testToolchainType, linuxPlatform, macPlatform);
    EvaluationResult<ToolchainResolutionValue> result = invokeToolchainResolution(key);

    assertThatEvaluationResult(result)
        .hasErrorEntryForKeyThat(key)
        .hasExceptionThat()
        .hasMessageThat()
        .contains("no matching toolchain found for //toolchain:test_toolchain");
  }

  @Test
  public void testResolveConstraints() throws Exception {
    ConstraintSettingInfo setting1 =
        ConstraintSettingInfo.create(makeLabel("//constraint:setting1"));
    ConstraintSettingInfo setting2 =
        ConstraintSettingInfo.create(makeLabel("//constraint:setting2"));
    ConstraintValueInfo constraint1a =
        ConstraintValueInfo.create(setting1, makeLabel("//constraint:value1a"));
    ConstraintValueInfo constraint1b =
        ConstraintValueInfo.create(setting1, makeLabel("//constraint:value1b"));
    ConstraintValueInfo constraint2a =
        ConstraintValueInfo.create(setting2, makeLabel("//constraint:value2a"));
    ConstraintValueInfo constraint2b =
        ConstraintValueInfo.create(setting2, makeLabel("//constraint:value2b"));

    Label toolchainType1 = makeLabel("//toolchain:type1");
    Label toolchainType2 = makeLabel("//toolchain:type2");

    DeclaredToolchainInfo toolchain1a =
        DeclaredToolchainInfo.create(
            toolchainType1,
            ImmutableList.of(constraint1a, constraint2a),
            ImmutableList.of(constraint1a, constraint2a),
            makeLabel("//toolchain:toolchain1a"));
    DeclaredToolchainInfo toolchain1b =
        DeclaredToolchainInfo.create(
            toolchainType1,
            ImmutableList.of(constraint1a, constraint2b),
            ImmutableList.of(constraint1a, constraint2b),
            makeLabel("//toolchain:toolchain1b"));
    DeclaredToolchainInfo toolchain2a =
        DeclaredToolchainInfo.create(
            toolchainType2,
            ImmutableList.of(constraint1b, constraint2a),
            ImmutableList.of(constraint1b, constraint2a),
            makeLabel("//toolchain:toolchain2a"));
    DeclaredToolchainInfo toolchain2b =
        DeclaredToolchainInfo.create(
            toolchainType2,
            ImmutableList.of(constraint1b, constraint2b),
            ImmutableList.of(constraint1b, constraint2b),
            makeLabel("//toolchain:toolchain2b"));

    ImmutableList<DeclaredToolchainInfo> allToolchains =
        ImmutableList.of(toolchain1a, toolchain1b, toolchain2a, toolchain2b);

    assertToolchainResolution(
            toolchainType1,
            ImmutableList.of(constraint1a, constraint2a),
            ImmutableList.of(constraint1a, constraint2a),
            allToolchains)
        .isEqualTo(toolchain1a);
    assertToolchainResolution(
            toolchainType1,
            ImmutableList.of(constraint1a, constraint2b),
            ImmutableList.of(constraint1a, constraint2b),
            allToolchains)
        .isEqualTo(toolchain1b);
    assertToolchainResolution(
            toolchainType2,
            ImmutableList.of(constraint1b, constraint2a),
            ImmutableList.of(constraint1b, constraint2a),
            allToolchains)
        .isEqualTo(toolchain2a);
    assertToolchainResolution(
            toolchainType2,
            ImmutableList.of(constraint1b, constraint2b),
            ImmutableList.of(constraint1b, constraint2b),
            allToolchains)
        .isEqualTo(toolchain2b);

    // No toolchains of type.
    assertToolchainResolution(
            makeLabel("//toolchain:type3"),
            ImmutableList.of(constraint1a, constraint2a),
            ImmutableList.of(constraint1a, constraint2a),
            allToolchains)
        .isNull();
  }

  private Subject<DefaultSubject, Object> assertToolchainResolution(
      Label toolchainType,
      Iterable<ConstraintValueInfo> targetConstraints,
      Iterable<ConstraintValueInfo> execConstraints,
      ImmutableList<DeclaredToolchainInfo> toolchains)
      throws Exception {

    PlatformInfo execPlatform =
        PlatformInfo.builder()
            .setLabel(makeLabel("//platform:exec"))
            .addConstraints(execConstraints)
            .build();
    PlatformInfo targetPlatform =
        PlatformInfo.builder()
            .setLabel(makeLabel("//platform:target"))
            .addConstraints(targetConstraints)
            .build();

    DeclaredToolchainInfo resolvedToolchain =
        ToolchainResolutionFunction.resolveConstraints(
            toolchainType, execPlatform, targetPlatform, toolchains, null);
    return assertThat(resolvedToolchain);
  }

  @Test
  public void testToolchainResolutionValue_equalsAndHashCode() {
    new EqualsTester()
        .addEqualityGroup(
            ToolchainResolutionValue.create(makeLabel("//test:toolchain_impl_1")),
            ToolchainResolutionValue.create(makeLabel("//test:toolchain_impl_1")))
        .addEqualityGroup(
            ToolchainResolutionValue.create(makeLabel("//test:toolchain_impl_2")),
            ToolchainResolutionValue.create(makeLabel("//test:toolchain_impl_2")));
  }
}
