// Copyright 2016 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.actions;

import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.devtools.build.lib.actions.ActionInputHelper;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.actions.Artifact.SpecialArtifact;
import com.google.devtools.build.lib.actions.Artifact.SpecialArtifactType;
import com.google.devtools.build.lib.actions.Artifact.TreeFileArtifact;
import com.google.devtools.build.lib.actions.ArtifactOwner;
import com.google.devtools.build.lib.actions.ArtifactRoot;
import com.google.devtools.build.lib.actions.util.ActionsTestUtil;
import com.google.devtools.build.lib.analysis.actions.SpawnActionTemplate.OutputPathMapper;
import com.google.devtools.build.lib.testutil.Scratch;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.PathFragment;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

/**
 * Tests {@link SpawnActionTemplate}.
 */
@RunWith(JUnit4.class)
public class SpawnActionTemplateTest {
  private static final OutputPathMapper IDENTITY_MAPPER = new OutputPathMapper() {
    @Override
    public PathFragment parentRelativeOutputPath(TreeFileArtifact inputTreeFileArtifact) {
      return inputTreeFileArtifact.getParentRelativePath();
    }
  };

  private ArtifactRoot root;

  @Before
  public void setRootDir() throws Exception  {
    Scratch scratch = new Scratch();
    Path execRoot = scratch.getFileSystem().getPath("/");
    root = ArtifactRoot.asDerivedRoot(execRoot, scratch.dir("/exec/root"));
  }

  @Test
  public void testInputAndOutputTreeArtifacts() {
    SpawnActionTemplate actionTemplate = createSimpleSpawnActionTemplate();
    assertThat(actionTemplate.getInputs()).containsExactly(createInputTreeArtifact());
    assertThat(actionTemplate.getOutputs()).containsExactly(createOutputTreeArtifact());
  }

  @Test
  public void testCommonToolsAndInputs() {
    SpecialArtifact inputTreeArtifact = createInputTreeArtifact();
    SpecialArtifact outputTreeArtifact = createOutputTreeArtifact();
    Artifact commonInput = createDerivedArtifact("common/input");
    Artifact commonTool = createDerivedArtifact("common/tool");
    Artifact executable = createDerivedArtifact("bin/cp");


    SpawnActionTemplate actionTemplate = builder(inputTreeArtifact, outputTreeArtifact)
        .setExecutionInfo(ImmutableMap.<String, String>of("local", ""))
        .setExecutable(executable)
        .setCommandLineTemplate(
            createSimpleCommandLineTemplate(inputTreeArtifact, outputTreeArtifact))
        .setOutputPathMapper(IDENTITY_MAPPER)
        .setMnemonics("ActionTemplate", "ExpandedAction")
        .addCommonTools(ImmutableList.of(commonTool))
        .addCommonInputs(ImmutableList.of(commonInput))
        .build(ActionsTestUtil.NULL_ACTION_OWNER);

    assertThat(actionTemplate.getTools()).containsAllOf(commonTool, executable);
    assertThat(actionTemplate.getInputs()).containsAllOf(commonInput, commonTool, executable);
  }

  @Test
  public void testBuilder_outputPathMapperRequired() {
    SpecialArtifact inputTreeArtifact = createInputTreeArtifact();
    SpecialArtifact outputTreeArtifact = createOutputTreeArtifact();
    SpawnActionTemplate.Builder builder = builder(inputTreeArtifact, outputTreeArtifact)
        .setExecutionInfo(ImmutableMap.<String, String>of("local", ""))
        .setExecutable(PathFragment.create("/bin/cp"))
        .setCommandLineTemplate(
            createSimpleCommandLineTemplate(inputTreeArtifact, outputTreeArtifact))
        .setMnemonics("ActionTemplate", "ExpandedAction");

    try {
      builder.build(ActionsTestUtil.NULL_ACTION_OWNER);
      fail("Expected exception");
    } catch (NullPointerException expected) {}
  }

  @Test
  public void testBuilder_executableRequired() {
    SpecialArtifact inputTreeArtifact = createInputTreeArtifact();
    SpecialArtifact outputTreeArtifact = createOutputTreeArtifact();
    SpawnActionTemplate.Builder builder = builder(inputTreeArtifact, outputTreeArtifact)
        .setExecutionInfo(ImmutableMap.<String, String>of("local", ""))
        .setOutputPathMapper(IDENTITY_MAPPER)
        .setCommandLineTemplate(
            createSimpleCommandLineTemplate(inputTreeArtifact, outputTreeArtifact))
        .setMnemonics("ActionTemplate", "ExpandedAction");

    try {
      builder.build(ActionsTestUtil.NULL_ACTION_OWNER);
      fail("Expected exception");
    } catch (NullPointerException expected) {}
  }

  @Test
  public void testBuilder_commandlineTemplateRequired() {
    SpecialArtifact inputTreeArtifact = createInputTreeArtifact();
    SpecialArtifact outputTreeArtifact = createOutputTreeArtifact();
    SpawnActionTemplate.Builder builder = builder(inputTreeArtifact, outputTreeArtifact)
        .setExecutionInfo(ImmutableMap.<String, String>of("local", ""))
        .setOutputPathMapper(IDENTITY_MAPPER)
        .setExecutable(PathFragment.create("/bin/cp"))
        .setMnemonics("ActionTemplate", "ExpandedAction");

    try {
      builder.build(ActionsTestUtil.NULL_ACTION_OWNER);
      fail("Expected exception");
    } catch (NullPointerException expected) {}
  }

  @Test
  public void testExpandedAction_inputAndOutputTreeFileArtifacts() throws Exception {
    SpawnActionTemplate actionTemplate = createSimpleSpawnActionTemplate();
    SpecialArtifact inputTreeArtifact = createInputTreeArtifact();
    SpecialArtifact outputTreeArtifact = createOutputTreeArtifact();

    Iterable<TreeFileArtifact> inputTreeFileArtifacts =
        createInputTreeFileArtifacts(inputTreeArtifact);

    List<SpawnAction> expandedActions =
        ImmutableList.copyOf(
            actionTemplate.generateActionForInputArtifacts(
                inputTreeFileArtifacts, ArtifactOwner.NullArtifactOwner.INSTANCE));

    assertThat(expandedActions).hasSize(3);

    for (int i = 0; i < expandedActions.size(); ++i) {
      String baseName = String.format("child%d", i);
      assertThat(expandedActions.get(i).getInputs()).containsExactly(
          ActionInputHelper.treeFileArtifact(
              inputTreeArtifact, PathFragment.create("children/" + baseName)));
      assertThat(expandedActions.get(i).getOutputs()).containsExactly(
          ActionInputHelper.treeFileArtifact(
              outputTreeArtifact, PathFragment.create("children/" + baseName)));
    }
  }

  @Test
  public void testExpandedAction_commonToolsAndInputs() throws Exception {
    SpecialArtifact inputTreeArtifact = createInputTreeArtifact();
    SpecialArtifact outputTreeArtifact = createOutputTreeArtifact();
    Artifact commonInput = createDerivedArtifact("common/input");
    Artifact commonTool = createDerivedArtifact("common/tool");
    Artifact executable = createDerivedArtifact("bin/cp");

    SpawnActionTemplate actionTemplate = builder(inputTreeArtifact, outputTreeArtifact)
        .setExecutionInfo(ImmutableMap.<String, String>of("local", ""))
        .setExecutable(executable)
        .setCommandLineTemplate(
            createSimpleCommandLineTemplate(inputTreeArtifact, outputTreeArtifact))
        .setOutputPathMapper(IDENTITY_MAPPER)
        .setMnemonics("ActionTemplate", "ExpandedAction")
        .addCommonTools(ImmutableList.of(commonTool))
        .addCommonInputs(ImmutableList.of(commonInput))
        .build(ActionsTestUtil.NULL_ACTION_OWNER);

    Iterable<TreeFileArtifact> inputTreeFileArtifacts =
        createInputTreeFileArtifacts(inputTreeArtifact);
    List<SpawnAction> expandedActions =
        ImmutableList.copyOf(
            actionTemplate.generateActionForInputArtifacts(
                inputTreeFileArtifacts, ArtifactOwner.NullArtifactOwner.INSTANCE));

    for (int i = 0; i < expandedActions.size(); ++i) {
      assertThat(expandedActions.get(i).getInputs()).containsAllOf(
          commonInput, commonTool, executable);
      assertThat(expandedActions.get(i).getTools()).containsAllOf(commonTool, executable);
    }
  }

  @Test
  public void testExpandedAction_arguments() throws Exception {
    SpawnActionTemplate actionTemplate = createSimpleSpawnActionTemplate();
    SpecialArtifact inputTreeArtifact = createInputTreeArtifact();
    SpecialArtifact outputTreeArtifact = createOutputTreeArtifact();

    Iterable<TreeFileArtifact> inputTreeFileArtifacts =
        createInputTreeFileArtifacts(inputTreeArtifact);

    List<SpawnAction> expandedActions =
        ImmutableList.copyOf(
            actionTemplate.generateActionForInputArtifacts(
                inputTreeFileArtifacts, ArtifactOwner.NullArtifactOwner.INSTANCE));

    assertThat(expandedActions).hasSize(3);

    for (int i = 0; i < expandedActions.size(); ++i) {
      String baseName = String.format("child%d", i);
      assertThat(expandedActions.get(i).getArguments())
          .containsExactly(
              "/bin/cp",
              inputTreeArtifact.getExecPathString() + "/children/" + baseName,
              outputTreeArtifact.getExecPathString() + "/children/" + baseName)
          .inOrder();
    }
  }

  @Test
  public void testExpandedAction_executionInfoAndEnvironment() throws Exception {
    SpawnActionTemplate actionTemplate = createSimpleSpawnActionTemplate();
    SpecialArtifact inputTreeArtifact = createInputTreeArtifact();
    Iterable<TreeFileArtifact> inputTreeFileArtifacts =
        createInputTreeFileArtifacts(inputTreeArtifact);

    List<SpawnAction> expandedActions =
        ImmutableList.copyOf(
            actionTemplate.generateActionForInputArtifacts(
                inputTreeFileArtifacts, ArtifactOwner.NullArtifactOwner.INSTANCE));

    assertThat(expandedActions).hasSize(3);

    for (int i = 0; i < expandedActions.size(); ++i) {
      assertThat(expandedActions.get(i).getIncompleteEnvironmentForTesting()).containsExactly("env", "value");
      assertThat(expandedActions.get(i).getExecutionInfo()).containsExactly("local", "");
    }
  }

  @Test
  public void testExpandedAction_illegalOutputPath() throws Exception {
    SpecialArtifact inputTreeArtifact = createInputTreeArtifact();
    SpecialArtifact outputTreeArtifact = createOutputTreeArtifact();
    Iterable<TreeFileArtifact> inputTreeFileArtifacts =
        createInputTreeFileArtifacts(inputTreeArtifact);

    SpawnActionTemplate.Builder builder = builder(inputTreeArtifact, outputTreeArtifact)
        .setExecutable(PathFragment.create("/bin/cp"))
        .setCommandLineTemplate(
            createSimpleCommandLineTemplate(inputTreeArtifact, outputTreeArtifact));

    OutputPathMapper mapper = new OutputPathMapper() {
      @Override
      public PathFragment parentRelativeOutputPath(TreeFileArtifact inputTreeFileArtifact) {
        return PathFragment.create("//absolute/" + inputTreeFileArtifact.getParentRelativePath());
      }
    };

    SpawnActionTemplate actionTemplate =
        builder.setOutputPathMapper(mapper).build(ActionsTestUtil.NULL_ACTION_OWNER);

    try {
      actionTemplate.generateActionForInputArtifacts(
          inputTreeFileArtifacts, ArtifactOwner.NullArtifactOwner.INSTANCE);
      fail("Absolute output paths not allowed, expected IllegalArgumentException");
    } catch (IllegalArgumentException e) {
      // expected
    }

    mapper = new OutputPathMapper() {
      @Override
      public PathFragment parentRelativeOutputPath(TreeFileArtifact inputTreeFileArtifact) {
        return PathFragment.create("../" + inputTreeFileArtifact.getParentRelativePath());
      }
    };

    actionTemplate = builder.setOutputPathMapper(mapper).build(
        ActionsTestUtil.NULL_ACTION_OWNER);

    try {
      actionTemplate.generateActionForInputArtifacts(
          inputTreeFileArtifacts, ArtifactOwner.NullArtifactOwner.INSTANCE);
      fail("Output paths containing '..' not allowed, expected IllegalArgumentException");
    } catch (IllegalArgumentException e) {
      // expected
    }
  }

  private SpawnActionTemplate.Builder builder(
      SpecialArtifact inputTreeArtifact, SpecialArtifact outputTreeArtifact) {
    return new SpawnActionTemplate.Builder(inputTreeArtifact, outputTreeArtifact);
  }

  private SpawnActionTemplate createSimpleSpawnActionTemplate() {
    SpecialArtifact inputTreeArtifact = createInputTreeArtifact();
    SpecialArtifact outputTreeArtifact = createOutputTreeArtifact();

    return builder(inputTreeArtifact, outputTreeArtifact)
        .setExecutionInfo(ImmutableMap.<String, String>of("local", ""))
        .setEnvironment(ImmutableMap.<String, String>of("env", "value"))
        .setExecutable(PathFragment.create("/bin/cp"))
        .setCommandLineTemplate(
            createSimpleCommandLineTemplate(inputTreeArtifact, outputTreeArtifact))
        .setOutputPathMapper(IDENTITY_MAPPER)
        .setMnemonics("ActionTemplate", "ExpandedAction")
        .build(ActionsTestUtil.NULL_ACTION_OWNER);
  }

  private SpecialArtifact createInputTreeArtifact() {
    return createTreeArtifact("my/inputTree");
  }

  private SpecialArtifact createOutputTreeArtifact() {
    return createTreeArtifact("my/outputTree");
  }

  private SpecialArtifact createTreeArtifact(String rootRelativePath) {
    PathFragment relpath = PathFragment.create(rootRelativePath);
    return new SpecialArtifact(
        root,
        root.getExecPath().getRelative(relpath),
        ArtifactOwner.NullArtifactOwner.INSTANCE,
        SpecialArtifactType.TREE);
  }

  private Artifact createDerivedArtifact(String rootRelativePath) {
    return new Artifact(PathFragment.create(rootRelativePath), root);
  }

  private CustomCommandLine createSimpleCommandLineTemplate(
      Artifact inputTreeArtifact, Artifact outputTreeArtifact) {
    return CustomCommandLine.builder()
        .addPlaceholderTreeArtifactExecPath(inputTreeArtifact)
        .addPlaceholderTreeArtifactExecPath(outputTreeArtifact)
        .build();
  }

  private Iterable<TreeFileArtifact> createInputTreeFileArtifacts(
      SpecialArtifact inputTreeArtifact) {
    return ActionInputHelper.asTreeFileArtifacts(
        inputTreeArtifact,
        ImmutableList.of(
            PathFragment.create("children/child0"),
            PathFragment.create("children/child1"),
            PathFragment.create("children/child2")));
  }
}
