// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/strings/string_number_conversions.h"
#include "gn/test_with_scope.h"
#include "util/test/test.h"

TEST(Template, Basic) {
  TestWithScope setup;
  TestParseInput input(
      "template(\"foo\") {\n"
      "  print(target_name)\n"
      "  print(invoker.bar)\n"
      "}\n"
      "foo(\"lala\") {\n"
      "  bar = 42\n"
      "}");
  ASSERT_SUCCESS(input);

  Err err;
  input.parsed()->Execute(setup.scope(), &err);
  ASSERT_SUCCESS(err);

  EXPECT_EQ("lala\n42\n", setup.print_output());
}

TEST(Template, UnusedTargetNameShouldThrowError) {
  TestWithScope setup;
  TestParseInput input(
      "template(\"foo\") {\n"
      "  print(invoker.bar)\n"
      "}\n"
      "foo(\"lala\") {\n"
      "  bar = 42\n"
      "}");
  ASSERT_SUCCESS(input);

  Err err;
  input.parsed()->Execute(setup.scope(), &err);
  EXPECT_TRUE(err.has_error());
}

TEST(Template, UnusedInvokerShouldThrowError) {
  TestWithScope setup;
  TestParseInput input(
      "template(\"foo\") {\n"
      "  print(target_name)\n"
      "}\n"
      "foo(\"lala\") {\n"
      "  bar = 42\n"
      "}");
  ASSERT_SUCCESS(input);

  Err err;
  input.parsed()->Execute(setup.scope(), &err);
  EXPECT_TRUE(err.has_error());
}

TEST(Template, UnusedVarInInvokerShouldThrowError) {
  TestWithScope setup;
  TestParseInput input(
      "template(\"foo\") {\n"
      "  print(target_name)\n"
      "  print(invoker.bar)\n"
      "}\n"
      "foo(\"lala\") {\n"
      "  bar = 42\n"
      "  baz = [ \"foo\" ]\n"
      "}");
  ASSERT_SUCCESS(input);

  Err err;
  input.parsed()->Execute(setup.scope(), &err);
  EXPECT_TRUE(err.has_error());
}

// Previous versions of the template implementation would copy templates by
// value when it makes a closure. Doing a sequence of them means that every new
// one copies all previous ones, which gives a significant blow-up in memory.
// If this test doesn't crash with out-of-memory, it passed.
TEST(Template, MemoryBlowUp) {
  TestWithScope setup;
  std::string code;
  for (int i = 0; i < 100; i++)
    code += "template(\"test" + base::IntToString(i) + "\") {}\n";

  TestParseInput input(code);

  Err err;
  input.parsed()->Execute(setup.scope(), &err);
  ASSERT_SUCCESS(input);
}

TEST(Template, ErrorStackTrace) {
  TestWithScope setup;
  TestParseInput input(
      "template(\"my_template\") {\n"
      "  print(invoker.undefined_var)\n"
      "}\n"
      "my_template(\"lala\") {\n"
      "}");
  ASSERT_SUCCESS(input);

  Err err;
  input.parsed()->Execute(setup.scope(), &err);
  EXPECT_TRUE(err.has_error());
  EXPECT_EQ(err.to_string(),
            "ERROR at //test:2:17: No value named \"undefined_var\" in scope "
            "\"invoker\"\n"
            "  print(invoker.undefined_var)\n"
            "                ^------------\n"
            "See //test:4:1: whence my_template was called.\n"
            "my_template(\"lala\") {\n"
            "^--------------------\n");
}
