// Copyright 2015 Google Inc. 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.

#include "dyndep_parser.h"

#include <map>
#include <vector>

#include "dyndep.h"
#include "graph.h"
#include "state.h"
#include "test.h"

using namespace std;

struct DyndepParserTest : public testing::Test {
  void AssertParse(const char* input) {
    DyndepParser parser(&state_, &fs_, &dyndep_file_);
    string err;
    EXPECT_TRUE(parser.ParseTest(input, &err));
    ASSERT_EQ("", err);
  }

  virtual void SetUp() {
    ::AssertParse(&state_,
"rule touch\n"
"  command = touch $out\n"
"build out otherout: touch\n");
  }

  State state_;
  VirtualFileSystem fs_;
  DyndepFile dyndep_file_;
};

TEST_F(DyndepParserTest, Empty) {
  const char kInput[] =
"";
  DyndepParser parser(&state_, &fs_, &dyndep_file_);
  string err;
  EXPECT_FALSE(parser.ParseTest(kInput, &err));
  EXPECT_EQ("input:1: expected 'ninja_dyndep_version = ...'\n", err);
}

TEST_F(DyndepParserTest, Version1) {
  ASSERT_NO_FATAL_FAILURE(AssertParse(
"ninja_dyndep_version = 1\n"));
}

TEST_F(DyndepParserTest, Version1Extra) {
  ASSERT_NO_FATAL_FAILURE(AssertParse(
"ninja_dyndep_version = 1-extra\n"));
}

TEST_F(DyndepParserTest, Version1_0) {
  ASSERT_NO_FATAL_FAILURE(AssertParse(
"ninja_dyndep_version = 1.0\n"));
}

TEST_F(DyndepParserTest, Version1_0Extra) {
  ASSERT_NO_FATAL_FAILURE(AssertParse(
"ninja_dyndep_version = 1.0-extra\n"));
}

TEST_F(DyndepParserTest, CommentVersion) {
  ASSERT_NO_FATAL_FAILURE(AssertParse(
"# comment\n"
"ninja_dyndep_version = 1\n"));
}

TEST_F(DyndepParserTest, BlankLineVersion) {
  ASSERT_NO_FATAL_FAILURE(AssertParse(
"\n"
"ninja_dyndep_version = 1\n"));
}

TEST_F(DyndepParserTest, VersionCRLF) {
  ASSERT_NO_FATAL_FAILURE(AssertParse(
"ninja_dyndep_version = 1\r\n"));
}

TEST_F(DyndepParserTest, CommentVersionCRLF) {
  ASSERT_NO_FATAL_FAILURE(AssertParse(
"# comment\r\n"
"ninja_dyndep_version = 1\r\n"));
}

TEST_F(DyndepParserTest, BlankLineVersionCRLF) {
  ASSERT_NO_FATAL_FAILURE(AssertParse(
"\r\n"
"ninja_dyndep_version = 1\r\n"));
}

TEST_F(DyndepParserTest, VersionUnexpectedEOF) {
  const char kInput[] =
"ninja_dyndep_version = 1.0";
  DyndepParser parser(&state_, &fs_, &dyndep_file_);
  string err;
  EXPECT_FALSE(parser.ParseTest(kInput, &err));
  EXPECT_EQ("input:1: unexpected EOF\n"
            "ninja_dyndep_version = 1.0\n"
            "                          ^ near here", err);
}

TEST_F(DyndepParserTest, UnsupportedVersion0) {
  const char kInput[] =
"ninja_dyndep_version = 0\n";
  DyndepParser parser(&state_, &fs_, &dyndep_file_);
  string err;
  EXPECT_FALSE(parser.ParseTest(kInput, &err));
  EXPECT_EQ("input:1: unsupported 'ninja_dyndep_version = 0'\n"
            "ninja_dyndep_version = 0\n"
            "                        ^ near here", err);
}

TEST_F(DyndepParserTest, UnsupportedVersion1_1) {
  const char kInput[] =
"ninja_dyndep_version = 1.1\n";
  DyndepParser parser(&state_, &fs_, &dyndep_file_);
  string err;
  EXPECT_FALSE(parser.ParseTest(kInput, &err));
  EXPECT_EQ("input:1: unsupported 'ninja_dyndep_version = 1.1'\n"
            "ninja_dyndep_version = 1.1\n"
            "                          ^ near here", err);
}

TEST_F(DyndepParserTest, DuplicateVersion) {
  const char kInput[] =
"ninja_dyndep_version = 1\n"
"ninja_dyndep_version = 1\n";
  DyndepParser parser(&state_, &fs_, &dyndep_file_);
  string err;
  EXPECT_FALSE(parser.ParseTest(kInput, &err));
  EXPECT_EQ("input:2: unexpected identifier\n", err);
}

TEST_F(DyndepParserTest, MissingVersionOtherVar) {
  const char kInput[] =
"not_ninja_dyndep_version = 1\n";
  DyndepParser parser(&state_, &fs_, &dyndep_file_);
  string err;
  EXPECT_FALSE(parser.ParseTest(kInput, &err));
  EXPECT_EQ("input:1: expected 'ninja_dyndep_version = ...'\n"
            "not_ninja_dyndep_version = 1\n"
            "                            ^ near here", err);
}

TEST_F(DyndepParserTest, MissingVersionBuild) {
  const char kInput[] =
"build out: dyndep\n";
  DyndepParser parser(&state_, &fs_, &dyndep_file_);
  string err;
  EXPECT_FALSE(parser.ParseTest(kInput, &err));
  EXPECT_EQ("input:1: expected 'ninja_dyndep_version = ...'\n", err);
}

TEST_F(DyndepParserTest, UnexpectedEqual) {
  const char kInput[] =
"= 1\n";
  DyndepParser parser(&state_, &fs_, &dyndep_file_);
  string err;
  EXPECT_FALSE(parser.ParseTest(kInput, &err));
  EXPECT_EQ("input:1: unexpected '='\n", err);
}

TEST_F(DyndepParserTest, UnexpectedIndent) {
  const char kInput[] =
" = 1\n";
  DyndepParser parser(&state_, &fs_, &dyndep_file_);
  string err;
  EXPECT_FALSE(parser.ParseTest(kInput, &err));
  EXPECT_EQ("input:1: unexpected indent\n", err);
}

TEST_F(DyndepParserTest, OutDuplicate) {
  const char kInput[] =
"ninja_dyndep_version = 1\n"
"build out: dyndep\n"
"build out: dyndep\n";
  DyndepParser parser(&state_, &fs_, &dyndep_file_);
  string err;
  EXPECT_FALSE(parser.ParseTest(kInput, &err));
  EXPECT_EQ("input:3: multiple statements for 'out'\n"
            "build out: dyndep\n"
            "         ^ near here", err);
}

TEST_F(DyndepParserTest, OutDuplicateThroughOther) {
  const char kInput[] =
"ninja_dyndep_version = 1\n"
"build out: dyndep\n"
"build otherout: dyndep\n";
  DyndepParser parser(&state_, &fs_, &dyndep_file_);
  string err;
  EXPECT_FALSE(parser.ParseTest(kInput, &err));
  EXPECT_EQ("input:3: multiple statements for 'otherout'\n"
            "build otherout: dyndep\n"
            "              ^ near here", err);
}

TEST_F(DyndepParserTest, NoOutEOF) {
  const char kInput[] =
"ninja_dyndep_version = 1\n"
"build";
  DyndepParser parser(&state_, &fs_, &dyndep_file_);
  string err;
  EXPECT_FALSE(parser.ParseTest(kInput, &err));
  EXPECT_EQ("input:2: unexpected EOF\n"
            "build\n"
            "     ^ near here", err);
}

TEST_F(DyndepParserTest, NoOutColon) {
  const char kInput[] =
"ninja_dyndep_version = 1\n"
"build :\n";
  DyndepParser parser(&state_, &fs_, &dyndep_file_);
  string err;
  EXPECT_FALSE(parser.ParseTest(kInput, &err));
  EXPECT_EQ("input:2: expected path\n"
            "build :\n"
            "      ^ near here", err);
}

TEST_F(DyndepParserTest, OutNoStatement) {
  const char kInput[] =
"ninja_dyndep_version = 1\n"
"build missing: dyndep\n";
  DyndepParser parser(&state_, &fs_, &dyndep_file_);
  string err;
  EXPECT_FALSE(parser.ParseTest(kInput, &err));
  EXPECT_EQ("input:2: no build statement exists for 'missing'\n"
            "build missing: dyndep\n"
            "             ^ near here", err);
}

TEST_F(DyndepParserTest, OutEOF) {
  const char kInput[] =
"ninja_dyndep_version = 1\n"
"build out";
  DyndepParser parser(&state_, &fs_, &dyndep_file_);
  string err;
  EXPECT_FALSE(parser.ParseTest(kInput, &err));
  EXPECT_EQ("input:2: unexpected EOF\n"
            "build out\n"
            "         ^ near here", err);
}

TEST_F(DyndepParserTest, OutNoRule) {
  const char kInput[] =
"ninja_dyndep_version = 1\n"
"build out:";
  DyndepParser parser(&state_, &fs_, &dyndep_file_);
  string err;
  EXPECT_FALSE(parser.ParseTest(kInput, &err));
  EXPECT_EQ("input:2: expected build command name 'dyndep'\n"
            "build out:\n"
            "          ^ near here", err);
}

TEST_F(DyndepParserTest, OutBadRule) {
  const char kInput[] =
"ninja_dyndep_version = 1\n"
"build out: touch";
  DyndepParser parser(&state_, &fs_, &dyndep_file_);
  string err;
  EXPECT_FALSE(parser.ParseTest(kInput, &err));
  EXPECT_EQ("input:2: expected build command name 'dyndep'\n"
            "build out: touch\n"
            "           ^ near here", err);
}

TEST_F(DyndepParserTest, BuildEOF) {
  const char kInput[] =
"ninja_dyndep_version = 1\n"
"build out: dyndep";
  DyndepParser parser(&state_, &fs_, &dyndep_file_);
  string err;
  EXPECT_FALSE(parser.ParseTest(kInput, &err));
  EXPECT_EQ("input:2: unexpected EOF\n"
            "build out: dyndep\n"
            "                 ^ near here", err);
}

TEST_F(DyndepParserTest, ExplicitOut) {
  const char kInput[] =
"ninja_dyndep_version = 1\n"
"build out exp: dyndep\n";
  DyndepParser parser(&state_, &fs_, &dyndep_file_);
  string err;
  EXPECT_FALSE(parser.ParseTest(kInput, &err));
  EXPECT_EQ("input:2: explicit outputs not supported\n"
            "build out exp: dyndep\n"
            "             ^ near here", err);
}

TEST_F(DyndepParserTest, ExplicitIn) {
  const char kInput[] =
"ninja_dyndep_version = 1\n"
"build out: dyndep exp\n";
  DyndepParser parser(&state_, &fs_, &dyndep_file_);
  string err;
  EXPECT_FALSE(parser.ParseTest(kInput, &err));
  EXPECT_EQ("input:2: explicit inputs not supported\n"
            "build out: dyndep exp\n"
            "                     ^ near here", err);
}

TEST_F(DyndepParserTest, OrderOnlyIn) {
  const char kInput[] =
"ninja_dyndep_version = 1\n"
"build out: dyndep ||\n";
  DyndepParser parser(&state_, &fs_, &dyndep_file_);
  string err;
  EXPECT_FALSE(parser.ParseTest(kInput, &err));
  EXPECT_EQ("input:2: order-only inputs not supported\n"
            "build out: dyndep ||\n"
            "                  ^ near here", err);
}

TEST_F(DyndepParserTest, BadBinding) {
  const char kInput[] =
"ninja_dyndep_version = 1\n"
"build out: dyndep\n"
"  not_restat = 1\n";
  DyndepParser parser(&state_, &fs_, &dyndep_file_);
  string err;
  EXPECT_FALSE(parser.ParseTest(kInput, &err));
  EXPECT_EQ("input:3: binding is not 'restat'\n"
            "  not_restat = 1\n"
            "                ^ near here", err);
}

TEST_F(DyndepParserTest, RestatTwice) {
  const char kInput[] =
"ninja_dyndep_version = 1\n"
"build out: dyndep\n"
"  restat = 1\n"
"  restat = 1\n";
  DyndepParser parser(&state_, &fs_, &dyndep_file_);
  string err;
  EXPECT_FALSE(parser.ParseTest(kInput, &err));
  EXPECT_EQ("input:4: unexpected indent\n", err);
}

TEST_F(DyndepParserTest, NoImplicit) {
  ASSERT_NO_FATAL_FAILURE(AssertParse(
"ninja_dyndep_version = 1\n"
"build out: dyndep\n"));

  EXPECT_EQ(1u, dyndep_file_.size());
  DyndepFile::iterator i = dyndep_file_.find(state_.edges_[0]);
  ASSERT_NE(i, dyndep_file_.end());
  EXPECT_EQ(false, i->second.restat_);
  EXPECT_EQ(0u, i->second.implicit_outputs_.size());
  EXPECT_EQ(0u, i->second.implicit_inputs_.size());
}

TEST_F(DyndepParserTest, EmptyImplicit) {
  ASSERT_NO_FATAL_FAILURE(AssertParse(
"ninja_dyndep_version = 1\n"
"build out | : dyndep |\n"));

  EXPECT_EQ(1u, dyndep_file_.size());
  DyndepFile::iterator i = dyndep_file_.find(state_.edges_[0]);
  ASSERT_NE(i, dyndep_file_.end());
  EXPECT_EQ(false, i->second.restat_);
  EXPECT_EQ(0u, i->second.implicit_outputs_.size());
  EXPECT_EQ(0u, i->second.implicit_inputs_.size());
}

TEST_F(DyndepParserTest, ImplicitIn) {
  ASSERT_NO_FATAL_FAILURE(AssertParse(
"ninja_dyndep_version = 1\n"
"build out: dyndep | impin\n"));

  EXPECT_EQ(1u, dyndep_file_.size());
  DyndepFile::iterator i = dyndep_file_.find(state_.edges_[0]);
  ASSERT_NE(i, dyndep_file_.end());
  EXPECT_EQ(false, i->second.restat_);
  EXPECT_EQ(0u, i->second.implicit_outputs_.size());
  ASSERT_EQ(1u, i->second.implicit_inputs_.size());
  EXPECT_EQ("impin", i->second.implicit_inputs_[0]->path());
}

TEST_F(DyndepParserTest, ImplicitIns) {
  ASSERT_NO_FATAL_FAILURE(AssertParse(
"ninja_dyndep_version = 1\n"
"build out: dyndep | impin1 impin2\n"));

  EXPECT_EQ(1u, dyndep_file_.size());
  DyndepFile::iterator i = dyndep_file_.find(state_.edges_[0]);
  ASSERT_NE(i, dyndep_file_.end());
  EXPECT_EQ(false, i->second.restat_);
  EXPECT_EQ(0u, i->second.implicit_outputs_.size());
  ASSERT_EQ(2u, i->second.implicit_inputs_.size());
  EXPECT_EQ("impin1", i->second.implicit_inputs_[0]->path());
  EXPECT_EQ("impin2", i->second.implicit_inputs_[1]->path());
}

TEST_F(DyndepParserTest, ImplicitOut) {
  ASSERT_NO_FATAL_FAILURE(AssertParse(
"ninja_dyndep_version = 1\n"
"build out | impout: dyndep\n"));

  EXPECT_EQ(1u, dyndep_file_.size());
  DyndepFile::iterator i = dyndep_file_.find(state_.edges_[0]);
  ASSERT_NE(i, dyndep_file_.end());
  EXPECT_EQ(false, i->second.restat_);
  ASSERT_EQ(1u, i->second.implicit_outputs_.size());
  EXPECT_EQ("impout", i->second.implicit_outputs_[0]->path());
  EXPECT_EQ(0u, i->second.implicit_inputs_.size());
}

TEST_F(DyndepParserTest, ImplicitOuts) {
  ASSERT_NO_FATAL_FAILURE(AssertParse(
"ninja_dyndep_version = 1\n"
"build out | impout1 impout2 : dyndep\n"));

  EXPECT_EQ(1u, dyndep_file_.size());
  DyndepFile::iterator i = dyndep_file_.find(state_.edges_[0]);
  ASSERT_NE(i, dyndep_file_.end());
  EXPECT_EQ(false, i->second.restat_);
  ASSERT_EQ(2u, i->second.implicit_outputs_.size());
  EXPECT_EQ("impout1", i->second.implicit_outputs_[0]->path());
  EXPECT_EQ("impout2", i->second.implicit_outputs_[1]->path());
  EXPECT_EQ(0u, i->second.implicit_inputs_.size());
}

TEST_F(DyndepParserTest, ImplicitInsAndOuts) {
  ASSERT_NO_FATAL_FAILURE(AssertParse(
"ninja_dyndep_version = 1\n"
"build out | impout1 impout2: dyndep | impin1 impin2\n"));

  EXPECT_EQ(1u, dyndep_file_.size());
  DyndepFile::iterator i = dyndep_file_.find(state_.edges_[0]);
  ASSERT_NE(i, dyndep_file_.end());
  EXPECT_EQ(false, i->second.restat_);
  ASSERT_EQ(2u, i->second.implicit_outputs_.size());
  EXPECT_EQ("impout1", i->second.implicit_outputs_[0]->path());
  EXPECT_EQ("impout2", i->second.implicit_outputs_[1]->path());
  ASSERT_EQ(2u, i->second.implicit_inputs_.size());
  EXPECT_EQ("impin1", i->second.implicit_inputs_[0]->path());
  EXPECT_EQ("impin2", i->second.implicit_inputs_[1]->path());
}

TEST_F(DyndepParserTest, Restat) {
  ASSERT_NO_FATAL_FAILURE(AssertParse(
"ninja_dyndep_version = 1\n"
"build out: dyndep\n"
"  restat = 1\n"));

  EXPECT_EQ(1u, dyndep_file_.size());
  DyndepFile::iterator i = dyndep_file_.find(state_.edges_[0]);
  ASSERT_NE(i, dyndep_file_.end());
  EXPECT_EQ(true, i->second.restat_);
  EXPECT_EQ(0u, i->second.implicit_outputs_.size());
  EXPECT_EQ(0u, i->second.implicit_inputs_.size());
}

TEST_F(DyndepParserTest, OtherOutput) {
  ASSERT_NO_FATAL_FAILURE(AssertParse(
"ninja_dyndep_version = 1\n"
"build otherout: dyndep\n"));

  EXPECT_EQ(1u, dyndep_file_.size());
  DyndepFile::iterator i = dyndep_file_.find(state_.edges_[0]);
  ASSERT_NE(i, dyndep_file_.end());
  EXPECT_EQ(false, i->second.restat_);
  EXPECT_EQ(0u, i->second.implicit_outputs_.size());
  EXPECT_EQ(0u, i->second.implicit_inputs_.size());
}

TEST_F(DyndepParserTest, MultipleEdges) {
    ::AssertParse(&state_,
"build out2: touch\n");
  ASSERT_EQ(2u, state_.edges_.size());
  ASSERT_EQ(1u, state_.edges_[1]->outputs_.size());
  EXPECT_EQ("out2", state_.edges_[1]->outputs_[0]->path());
  EXPECT_EQ(0u, state_.edges_[0]->inputs_.size());

  ASSERT_NO_FATAL_FAILURE(AssertParse(
"ninja_dyndep_version = 1\n"
"build out: dyndep\n"
"build out2: dyndep\n"
"  restat = 1\n"));

  EXPECT_EQ(2u, dyndep_file_.size());
  {
    DyndepFile::iterator i = dyndep_file_.find(state_.edges_[0]);
    ASSERT_NE(i, dyndep_file_.end());
    EXPECT_EQ(false, i->second.restat_);
    EXPECT_EQ(0u, i->second.implicit_outputs_.size());
    EXPECT_EQ(0u, i->second.implicit_inputs_.size());
  }
  {
    DyndepFile::iterator i = dyndep_file_.find(state_.edges_[1]);
    ASSERT_NE(i, dyndep_file_.end());
    EXPECT_EQ(true, i->second.restat_);
    EXPECT_EQ(0u, i->second.implicit_outputs_.size());
    EXPECT_EQ(0u, i->second.implicit_inputs_.size());
  }
}
