// 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.bazel.rules.genrule;

import static com.google.common.truth.Truth.assertThat;

import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.analysis.ShellConfiguration;
import com.google.devtools.build.lib.analysis.actions.SpawnAction;
import com.google.devtools.build.lib.analysis.util.BuildViewTestCase;
import com.google.devtools.build.lib.util.OS;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

/** Tests of {@link BazelGenRule} on Windows. */
@RunWith(JUnit4.class)
public class GenRuleWindowsConfiguredTargetTest extends BuildViewTestCase {

  private static final Pattern POWERSHELL_COMMAND_PATTERN =
      Pattern.compile(".*'utf8';\\s+(?<command>.*)");

  private static final Pattern BASH_COMMAND_PATTERN =
      Pattern.compile(".*/genrule-setup.sh;\\s+(?<command>.*)");

  private static void assertCommandEquals(String expected, String command, Pattern pattern) {
    // Ensure the command after the genrule setup is correct.
    Matcher m = pattern.matcher(command);
    if (m.matches()) {
      command = m.group("command");
    }
    assertThat(command).isEqualTo(expected);
  }

  private static void assertPowershellCommandEquals(String expected, String command) {
    assertCommandEquals(expected, command, POWERSHELL_COMMAND_PATTERN);
  }

  private static void assertBashCommandEquals(String expected, String command) {
    assertCommandEquals(expected, command, BASH_COMMAND_PATTERN);
  }

  private static String getWindowsPath(Artifact artifact) {
    return artifact.getExecPathString().replace('/', '\\');
  }

  @Test
  public void testCmdBatchIsPreferred() throws Exception {
    scratch.file(
        "genrule1/BUILD",
        "genrule(name = 'hello_world',",
        "outs = ['message.txt'],",
        "cmd  = 'echo \"Hello, default cmd.\" >$(location message.txt)',",
        "cmd_bash  = 'echo \"Hello, Bash cmd.\" >$(location message.txt)',",
        "cmd_bat  = 'echo \"Hello, Batch cmd.\" >$(location message.txt)')");

    Artifact messageArtifact = getFileConfiguredTarget("//genrule1:message.txt").getArtifact();
    SpawnAction shellAction = (SpawnAction) getGeneratingAction(messageArtifact);

    assertThat(shellAction).isNotNull();
    assertThat(shellAction.getOutputs()).containsExactly(messageArtifact);

    String expected = "echo \"Hello, Batch cmd.\" >" + getWindowsPath(messageArtifact);
    assertThat(shellAction.getArguments().get(0)).isEqualTo("cmd.exe");
    int last = shellAction.getArguments().size() - 1;
    assertThat(shellAction.getArguments().get(last - 1)).isEqualTo("/c");
    assertThat(shellAction.getArguments().get(last)).isEqualTo(expected);
  }

  @Test
  public void testCmdPsIsPreferred() throws Exception {
    scratch.file(
        "genrule1/BUILD",
        "genrule(name = 'hello_world',",
        "outs = ['message.txt'],",
        "cmd  = 'echo \"Hello, default cmd.\" >$(location message.txt)',",
        "cmd_bash  = 'echo \"Hello, Bash cmd.\" >$(location message.txt)',",
        "cmd_bat  = 'echo \"Hello, Batch cmd.\" >$(location message.txt)',",
        "cmd_ps  = 'echo \"Hello, Powershell cmd.\" >$(location message.txt)')");

    Artifact messageArtifact = getFileConfiguredTarget("//genrule1:message.txt").getArtifact();
    SpawnAction shellAction = (SpawnAction) getGeneratingAction(messageArtifact);

    assertThat(shellAction).isNotNull();
    assertThat(shellAction.getOutputs()).containsExactly(messageArtifact);

    String expected = "echo \"Hello, Powershell cmd.\" >" + messageArtifact.getExecPathString();
    assertThat(shellAction.getArguments().get(0)).isEqualTo("powershell.exe");
    assertThat(shellAction.getArguments().get(1)).isEqualTo("/c");
    assertPowershellCommandEquals(expected, shellAction.getArguments().get(2));
  }

  @Test
  public void testScriptFileIsUsedForBatchCmd() throws Exception {
    scratch.file(
        "genrule1/BUILD",
        "genrule(name = 'hello_world',",
        "outs = ['message.txt'],",
        "cmd_bat  = ' && '.join([\"echo \\\"Hello, Batch cmd, %s.\\\" >$(location message.txt)\" %"
            + " i for i in range(1, 1000)]),)");

    Artifact messageArtifact = getFileConfiguredTarget("//genrule1:message.txt").getArtifact();
    SpawnAction shellAction = (SpawnAction) getGeneratingAction(messageArtifact);

    assertThat(shellAction).isNotNull();
    assertThat(shellAction.getOutputs()).containsExactly(messageArtifact);

    String expected = "bazel-out\\k8-fastbuild\\bin\\genrule1\\hello_world.genrule_script.bat";
    assertThat(shellAction.getArguments().get(0)).isEqualTo("cmd.exe");
    int last = shellAction.getArguments().size() - 1;
    assertThat(shellAction.getArguments().get(last - 1)).isEqualTo("/c");
    assertPowershellCommandEquals(expected, shellAction.getArguments().get(last));
  }

  @Test
  public void testScriptFileIsUsedForPowershellCmd() throws Exception {
    scratch.file(
        "genrule1/BUILD",
        "genrule(name = 'hello_world',",
        "outs = ['message.txt'],",
        "cmd_ps  = '; '.join([\"echo \\\"Hello, Powershell cmd, %s.\\\" >$(location message.txt)\""
            + " % i for i in range(1, 1000)]),)");

    Artifact messageArtifact = getFileConfiguredTarget("//genrule1:message.txt").getArtifact();
    SpawnAction shellAction = (SpawnAction) getGeneratingAction(messageArtifact);

    assertThat(shellAction).isNotNull();
    assertThat(shellAction.getOutputs()).containsExactly(messageArtifact);

    String expected = ".\\bazel-out\\k8-fastbuild\\bin\\genrule1\\hello_world.genrule_script.ps1";
    assertThat(shellAction.getArguments().get(0)).isEqualTo("powershell.exe");
    assertThat(shellAction.getArguments().get(1)).isEqualTo("/c");
    assertPowershellCommandEquals(expected, shellAction.getArguments().get(2));
  }

  @Test
  public void testCmdBashIsPreferred() throws Exception {
    scratch.file(
        "genrule1/BUILD",
        "genrule(name = 'hello_world',",
        "outs = ['message.txt'],",
        "cmd  = 'echo \"Hello, default cmd.\" >$(location message.txt)',",
        "cmd_bash  = 'echo \"Hello, Bash cmd.\" >$(location message.txt)')");

    Artifact messageArtifact = getFileConfiguredTarget("//genrule1:message.txt").getArtifact();
    SpawnAction shellAction = (SpawnAction) getGeneratingAction(messageArtifact);

    assertThat(shellAction).isNotNull();
    assertThat(shellAction.getOutputs()).containsExactly(messageArtifact);

    String expected = "echo \"Hello, Bash cmd.\" >" + messageArtifact.getExecPathString();
    assertThat(shellAction.getArguments().get(0))
        .isEqualTo(
            targetConfig
                .getFragment(ShellConfiguration.class)
                .getShellExecutable()
                .getPathString());
    assertThat(shellAction.getArguments().get(1)).isEqualTo("-c");
    assertBashCommandEquals(expected, shellAction.getArguments().get(2));
  }

  @Test
  public void testMissingCmdAttributeError() throws Exception {
    checkError(
        "foo",
        "bar",
        "missing value for `cmd` attribute, you can also set `cmd_ps` or `cmd_bat` on"
            + " Windows and `cmd_bash` on other platforms.",
        "genrule(name='bar'," + "      srcs = []," + "      outs=['out'])");
  }

  @Test
  public void testMissingCmdAttributeErrorOnNonWindowsPlatform() throws Exception {
    if (OS.getCurrent() == OS.WINDOWS) {
      return;
    }
    checkError(
        "foo",
        "bar",
        "missing value for `cmd` attribute, you can also set `cmd_ps` or `cmd_bat` on"
            + " Windows and `cmd_bash` on other platforms.",
        "genrule(name='bar',"
            + "      srcs = [],"
            + "      outs=['out'],"
            + "      cmd_bat='echo hello > $(@)')");
  }
}
