// Copyright 2019 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.analysis;

import static com.google.common.truth.Truth.assertThat;
import static com.google.devtools.build.lib.analysis.testing.ResolvedToolchainContextSubject.assertThat;
import static org.junit.Assert.assertThrows;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.devtools.build.lib.analysis.platform.ToolchainTypeInfo;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.rules.platform.ToolchainTestCase;
import com.google.devtools.build.lib.skyframe.ConfiguredTargetAndData;
import com.google.devtools.build.lib.skyframe.ToolchainContextKey;
import com.google.devtools.build.lib.skyframe.ToolchainException;
import com.google.devtools.build.lib.skyframe.UnloadedToolchainContext;
import com.google.devtools.build.lib.skyframe.UnloadedToolchainContextImpl;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

/** Tests for {@link ResolvedToolchainContext}. */
@RunWith(JUnit4.class)
public class ResolvedToolchainContextTest extends ToolchainTestCase {

  @Test
  public void load() throws Exception {
    addToolchain(
        "extra",
        "extra_toolchain_linux",
        ImmutableList.of("//constraints:linux"),
        ImmutableList.of("//constraints:linux"),
        "baz");

    ToolchainContextKey toolchainContextKey =
        ToolchainContextKey.key()
            .configurationKey(targetConfigKey)
            .requiredToolchainTypeLabels(testToolchainTypeLabel)
            .build();

    // Create a static UnloadedToolchainContext.
    UnloadedToolchainContext unloadedToolchainContext =
        UnloadedToolchainContextImpl.builder()
            .setKey(toolchainContextKey)
            .setExecutionPlatform(linuxPlatform)
            .setTargetPlatform(linuxPlatform)
            .setRequiredToolchainTypes(ImmutableSet.of(testToolchainType))
            .setRequestedLabelToToolchainType(
                ImmutableMap.of(testToolchainTypeLabel, testToolchainType))
            .setToolchainTypeToResolved(
                ImmutableSetMultimap.<ToolchainTypeInfo, Label>builder()
                    .put(
                        testToolchainType,
                        Label.parseAbsoluteUnchecked("//extra:extra_toolchain_linux_impl"))
                    .build())
            .build();

    // Create the prerequisites.
    ConfiguredTargetAndData toolchain =
        getConfiguredTargetAndData(
            Label.parseAbsoluteUnchecked("//extra:extra_toolchain_linux_impl"), targetConfig);

    // Resolve toolchains.
    ResolvedToolchainContext toolchainContext =
        ResolvedToolchainContext.load(
            toolchain.getTarget().getPackage().getRepositoryMapping(),
            unloadedToolchainContext,
            "test",
            ImmutableList.of(toolchain));
    assertThat(toolchainContext).isNotNull();
    assertThat(toolchainContext).hasToolchainType(testToolchainTypeLabel);
    assertThat(toolchainContext)
        .forToolchainType(testToolchainTypeLabel)
        .getValue("data")
        .isEqualTo("baz");
  }

  @Test
  public void load_aliasedToolchain() throws Exception {
    scratch.file(
        "alias/BUILD", "alias(name = 'toolchain', actual = '//extra:extra_toolchain_linux_impl')");
    addToolchain(
        "extra",
        "extra_toolchain_linux",
        ImmutableList.of("//constraints:linux"),
        ImmutableList.of("//constraints:linux"),
        "baz");

    ToolchainContextKey toolchainContextKey =
        ToolchainContextKey.key()
            .configurationKey(targetConfigKey)
            .requiredToolchainTypeLabels(testToolchainTypeLabel)
            .build();

    // Create a static UnloadedToolchainContext.
    UnloadedToolchainContext unloadedToolchainContext =
        UnloadedToolchainContextImpl.builder()
            .setKey(toolchainContextKey)
            .setExecutionPlatform(linuxPlatform)
            .setTargetPlatform(linuxPlatform)
            .setRequiredToolchainTypes(ImmutableSet.of(testToolchainType))
            .setRequestedLabelToToolchainType(
                ImmutableMap.of(testToolchainTypeLabel, testToolchainType))
            .setToolchainTypeToResolved(
                ImmutableSetMultimap.<ToolchainTypeInfo, Label>builder()
                    .put(testToolchainType, Label.parseAbsoluteUnchecked("//alias:toolchain"))
                    .build())
            .build();

    // Create the prerequisites.
    ConfiguredTargetAndData toolchain =
        getConfiguredTargetAndData(Label.parseAbsoluteUnchecked("//alias:toolchain"), targetConfig);

    // Resolve toolchains.
    ResolvedToolchainContext toolchainContext =
        ResolvedToolchainContext.load(
            toolchain.getTarget().getPackage().getRepositoryMapping(),
            unloadedToolchainContext,
            "test",
            ImmutableList.of(toolchain));
    assertThat(toolchainContext).isNotNull();
    assertThat(toolchainContext).hasToolchainType(testToolchainTypeLabel);
    assertThat(toolchainContext)
        .forToolchainType(testToolchainTypeLabel)
        .getValue("data")
        .isEqualTo("baz");
  }

  @Test
  public void load_notToolchain() throws Exception {
    scratch.file("foo/BUILD", "filegroup(name = 'not_a_toolchain')");

    ToolchainContextKey toolchainContextKey =
        ToolchainContextKey.key()
            .configurationKey(targetConfigKey)
            .requiredToolchainTypeLabels(testToolchainTypeLabel)
            .build();

    // Create a static UnloadedToolchainContext.
    UnloadedToolchainContext unloadedToolchainContext =
        UnloadedToolchainContextImpl.builder()
            .setKey(toolchainContextKey)
            .setExecutionPlatform(linuxPlatform)
            .setTargetPlatform(linuxPlatform)
            .setRequiredToolchainTypes(ImmutableSet.of(testToolchainType))
            .setRequestedLabelToToolchainType(
                ImmutableMap.of(testToolchainTypeLabel, testToolchainType))
            .setToolchainTypeToResolved(
                ImmutableSetMultimap.<ToolchainTypeInfo, Label>builder()
                    .put(testToolchainType, Label.parseAbsoluteUnchecked("//foo:not_a_toolchain"))
                    .build())
            .build();

    // Create the prerequisites, which is not actually a valid toolchain.
    ConfiguredTargetAndData toolchain =
        getConfiguredTargetAndData(
            Label.parseAbsoluteUnchecked("//foo:not_a_toolchain"), targetConfig);
    assertThrows(
        ToolchainException.class,
        () ->
            ResolvedToolchainContext.load(
                toolchain.getTarget().getPackage().getRepositoryMapping(),
                unloadedToolchainContext,
                "test",
                ImmutableList.of(toolchain)));
  }

  @Test
  public void load_withTemplateVariables() throws Exception {
    // Add new toolchain rule that provides template variables.
    Label variableToolchainTypeLabel =
        Label.parseAbsoluteUnchecked("//variable:variable_toolchain_type");
    ToolchainTypeInfo variableToolchainType = ToolchainTypeInfo.create(variableToolchainTypeLabel);
    scratch.file(
        "variable/variable_toolchain_def.bzl",
        "def _impl(ctx):",
        "  value = ctx.attr.value",
        "  toolchain = platform_common.ToolchainInfo()",
        "  template_variables = platform_common.TemplateVariableInfo({'VALUE': value})",
        "  return [toolchain, template_variables]",
        "variable_toolchain = rule(",
        "    implementation = _impl,",
        "    attrs = {'value': attr.string()})");

    // Create instance of new toolchain and register it.
    scratch.file(
        "variable/BUILD",
        "load('//variable:variable_toolchain_def.bzl', 'variable_toolchain')",
        "toolchain_type(name = 'variable_toolchain_type')",
        "variable_toolchain(",
        "  name='variable_toolchain_impl',",
        "  value = 'foo')");

    ToolchainContextKey toolchainContextKey =
        ToolchainContextKey.key()
            .configurationKey(targetConfigKey)
            .requiredToolchainTypeLabels(testToolchainTypeLabel)
            .build();

    // Create a static UnloadedToolchainContext.
    UnloadedToolchainContext unloadedToolchainContext =
        UnloadedToolchainContextImpl.builder()
            .setKey(toolchainContextKey)
            .setExecutionPlatform(linuxPlatform)
            .setTargetPlatform(linuxPlatform)
            .setRequiredToolchainTypes(ImmutableSet.of(variableToolchainType))
            .setRequestedLabelToToolchainType(
                ImmutableMap.of(variableToolchainTypeLabel, variableToolchainType))
            .setToolchainTypeToResolved(
                ImmutableSetMultimap.<ToolchainTypeInfo, Label>builder()
                    .put(
                        variableToolchainType,
                        Label.parseAbsoluteUnchecked("//variable:variable_toolchain_impl"))
                    .build())
            .build();

    // Create the prerequisites.
    ConfiguredTargetAndData toolchain =
        getConfiguredTargetAndData(
            Label.parseAbsoluteUnchecked("//variable:variable_toolchain_impl"), targetConfig);
    ResolvedToolchainContext toolchainContext =
        ResolvedToolchainContext.load(
            toolchain.getTarget().getPackage().getRepositoryMapping(),
            unloadedToolchainContext,
            "test",
            ImmutableList.of(toolchain));
    assertThat(toolchainContext).isNotNull();
    assertThat(toolchainContext).hasToolchainType(variableToolchainTypeLabel);
    assertThat(toolchainContext.templateVariableProviders()).hasSize(1);
    assertThat(toolchainContext.templateVariableProviders().get(0).getVariables())
        .containsExactly("VALUE", "foo");
  }
}
