# Copyright 2019 Google LLC
#
# 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
#
#     https://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.

"""Tests for util.error."""

import unittest

from compiler.util import error
from compiler.util import parser_types


class MessageTest(unittest.TestCase):
  """Tests for _Message, as returned by error, warn, and note."""

  def test_error(self):
    error_message = error.error("foo.emb", parser_types.make_location(
        (3, 4), (3, 6)), "Bad thing")
    self.assertEqual("foo.emb", error_message.source_file)
    self.assertEqual(error.ERROR, error_message.severity)
    self.assertEqual(parser_types.make_location((3, 4), (3, 6)),
                     error_message.location)
    self.assertEqual("Bad thing", error_message.message)
    sourceless_format = error_message.format({})
    sourced_format = error_message.format({"foo.emb": "\n\nabcdefghijklm"})
    self.assertEqual("foo.emb:3:4: error: Bad thing",
                     "".join([x[1] for x in sourceless_format]))
    self.assertEqual([(error.BOLD, "foo.emb:3:4: "),  # Location
                      (error.BRIGHT_RED, "error: "),  # Severity
                      (error.BOLD, "Bad thing"),  # Message
                     ], sourceless_format)
    self.assertEqual("foo.emb:3:4: error: Bad thing\n"
                     "abcdefghijklm\n"
                     "   ^^", "".join([x[1] for x in sourced_format]))
    self.assertEqual([(error.BOLD, "foo.emb:3:4: "),  # Location
                      (error.BRIGHT_RED, "error: "),  # Severity
                      (error.BOLD, "Bad thing\n"),  # Message
                      (error.WHITE, "abcdefghijklm\n"),  # Source snippet
                      (error.BRIGHT_GREEN, "   ^^"),  # Error column indicator
                     ], sourced_format)

  def test_synthetic_error(self):
    error_message = error.error("foo.emb", parser_types.make_location(
        (3, 4), (3, 6), True), "Bad thing")
    sourceless_format = error_message.format({})
    sourced_format = error_message.format({"foo.emb": "\n\nabcdefghijklm"})
    self.assertEqual("foo.emb:[compiler bug]: error: Bad thing",
                     "".join([x[1] for x in sourceless_format]))
    self.assertEqual([
        (error.BOLD, "foo.emb:[compiler bug]: "),  # Location
        (error.BRIGHT_RED, "error: "),  # Severity
        (error.BOLD, "Bad thing"),  # Message
    ], sourceless_format)
    self.assertEqual("foo.emb:[compiler bug]: error: Bad thing",
                     "".join([x[1] for x in sourced_format]))
    self.assertEqual([
        (error.BOLD, "foo.emb:[compiler bug]: "),  # Location
        (error.BRIGHT_RED, "error: "),  # Severity
        (error.BOLD, "Bad thing"),  # Message
    ], sourced_format)

  def test_prelude_as_file_name(self):
    error_message = error.error("", parser_types.make_location(
        (3, 4), (3, 6)), "Bad thing")
    self.assertEqual("", error_message.source_file)
    self.assertEqual(error.ERROR, error_message.severity)
    self.assertEqual(parser_types.make_location((3, 4), (3, 6)),
                     error_message.location)
    self.assertEqual("Bad thing", error_message.message)
    sourceless_format = error_message.format({})
    sourced_format = error_message.format({"": "\n\nabcdefghijklm"})
    self.assertEqual("[prelude]:3:4: error: Bad thing",
                     "".join([x[1] for x in sourceless_format]))
    self.assertEqual([(error.BOLD, "[prelude]:3:4: "),  # Location
                      (error.BRIGHT_RED, "error: "),  # Severity
                      (error.BOLD, "Bad thing"),  # Message
                     ], sourceless_format)
    self.assertEqual("[prelude]:3:4: error: Bad thing\n"
                     "abcdefghijklm\n"
                     "   ^^", "".join([x[1] for x in sourced_format]))
    self.assertEqual([(error.BOLD, "[prelude]:3:4: "),  # Location
                      (error.BRIGHT_RED, "error: "),  # Severity
                      (error.BOLD, "Bad thing\n"),  # Message
                      (error.WHITE, "abcdefghijklm\n"),  # Source snippet
                      (error.BRIGHT_GREEN, "   ^^"),  # Error column indicator
                     ], sourced_format)

  def test_multiline_error_source(self):
    error_message = error.error("foo.emb", parser_types.make_location(
        (3, 4), (4, 6)), "Bad thing")
    self.assertEqual("foo.emb", error_message.source_file)
    self.assertEqual(error.ERROR, error_message.severity)
    self.assertEqual(parser_types.make_location((3, 4), (4, 6)),
                     error_message.location)
    self.assertEqual("Bad thing", error_message.message)
    sourceless_format = error_message.format({})
    sourced_format = error_message.format(
        {"foo.emb": "\n\nabcdefghijklm\nnopqrstuv"})
    self.assertEqual("foo.emb:3:4: error: Bad thing",
                     "".join([x[1] for x in sourceless_format]))
    self.assertEqual([(error.BOLD, "foo.emb:3:4: "),  # Location
                      (error.BRIGHT_RED, "error: "),  # Severity
                      (error.BOLD, "Bad thing"),  # Message
                     ], sourceless_format)
    self.assertEqual("foo.emb:3:4: error: Bad thing\n"
                     "abcdefghijklm\n"
                     "   ^", "".join([x[1] for x in sourced_format]))
    self.assertEqual([(error.BOLD, "foo.emb:3:4: "),  # Location
                      (error.BRIGHT_RED, "error: "),  # Severity
                      (error.BOLD, "Bad thing\n"),  # Message
                      (error.WHITE, "abcdefghijklm\n"),  # Source snippet
                      (error.BRIGHT_GREEN, "   ^"),  # Error column indicator
                     ], sourced_format)

  def test_multiline_error(self):
    error_message = error.error("foo.emb", parser_types.make_location(
        (3, 4), (3, 6)), "Bad thing\nSome explanation\nMore explanation")
    self.assertEqual("foo.emb", error_message.source_file)
    self.assertEqual(error.ERROR, error_message.severity)
    self.assertEqual(parser_types.make_location((3, 4), (3, 6)),
                     error_message.location)
    self.assertEqual("Bad thing\nSome explanation\nMore explanation",
                     error_message.message)
    sourceless_format = error_message.format({})
    sourced_format = error_message.format(
        {"foo.emb": "\n\nabcdefghijklm\nnopqrstuv"})
    self.assertEqual("foo.emb:3:4: error: Bad thing\n"
                     "foo.emb:3:4: note: Some explanation\n"
                     "foo.emb:3:4: note: More explanation",
                     "".join([x[1] for x in sourceless_format]))
    self.assertEqual([(error.BOLD, "foo.emb:3:4: "),  # Location
                      (error.BRIGHT_RED, "error: "),  # Severity
                      (error.BOLD, "Bad thing\n"),  # Message
                      (error.BOLD, "foo.emb:3:4: "),  # Location, line 2
                      (error.BRIGHT_BLACK, "note: "),  # "Note" severity, line 2
                      (error.WHITE, "Some explanation\n"),  # Message, line 2
                      (error.BOLD, "foo.emb:3:4: "),  # Location, line 3
                      (error.BRIGHT_BLACK, "note: "),  # "Note" severity, line 3
                      (error.WHITE, "More explanation"),  # Message, line 3
                     ], sourceless_format)
    self.assertEqual("foo.emb:3:4: error: Bad thing\n"
                     "foo.emb:3:4: note: Some explanation\n"
                     "foo.emb:3:4: note: More explanation\n"
                     "abcdefghijklm\n"
                     "   ^^", "".join([x[1] for x in sourced_format]))
    self.assertEqual([(error.BOLD, "foo.emb:3:4: "),  # Location
                      (error.BRIGHT_RED, "error: "),  # Severity
                      (error.BOLD, "Bad thing\n"),  # Message
                      (error.BOLD, "foo.emb:3:4: "),  # Location, line 2
                      (error.BRIGHT_BLACK, "note: "),  # "Note" severity, line 2
                      (error.WHITE, "Some explanation\n"),  # Message, line 2
                      (error.BOLD, "foo.emb:3:4: "),  # Location, line 3
                      (error.BRIGHT_BLACK, "note: "),  # "Note" severity, line 3
                      (error.WHITE, "More explanation\n"),  # Message, line 3
                      (error.WHITE, "abcdefghijklm\n"),  # Source snippet
                      (error.BRIGHT_GREEN, "   ^^"),  # Column indicator
                     ], sourced_format)

  def test_warn(self):
    warning_message = error.warn("foo.emb", parser_types.make_location(
        (3, 4), (3, 6)), "Not good thing")
    self.assertEqual("foo.emb", warning_message.source_file)
    self.assertEqual(error.WARNING, warning_message.severity)
    self.assertEqual(parser_types.make_location((3, 4), (3, 6)),
                     warning_message.location)
    self.assertEqual("Not good thing", warning_message.message)
    sourced_format = warning_message.format({"foo.emb": "\n\nabcdefghijklm"})
    self.assertEqual("foo.emb:3:4: warning: Not good thing\n"
                     "abcdefghijklm\n"
                     "   ^^", "".join([x[1] for x in sourced_format]))
    self.assertEqual([(error.BOLD, "foo.emb:3:4: "),  # Location
                      (error.BRIGHT_MAGENTA, "warning: "),  # Severity
                      (error.BOLD, "Not good thing\n"),  # Message
                      (error.WHITE, "abcdefghijklm\n"),  # Source snippet
                      (error.BRIGHT_GREEN, "   ^^"),  # Column indicator
                     ], sourced_format)

  def test_note(self):
    note_message = error.note("foo.emb", parser_types.make_location(
        (3, 4), (3, 6)), "OK thing")
    self.assertEqual("foo.emb", note_message.source_file)
    self.assertEqual(error.NOTE, note_message.severity)
    self.assertEqual(parser_types.make_location((3, 4), (3, 6)),
                     note_message.location)
    self.assertEqual("OK thing", note_message.message)
    sourced_format = note_message.format({"foo.emb": "\n\nabcdefghijklm"})
    self.assertEqual("foo.emb:3:4: note: OK thing\n"
                     "abcdefghijklm\n"
                     "   ^^", "".join([x[1] for x in sourced_format]))
    self.assertEqual([(error.BOLD, "foo.emb:3:4: "),  # Location
                      (error.BRIGHT_BLACK, "note: "),  # Severity
                      (error.WHITE, "OK thing\n"),  # Message
                      (error.WHITE, "abcdefghijklm\n"),  # Source snippet
                      (error.BRIGHT_GREEN, "   ^^"),  # Column indicator
                     ], sourced_format)

  def test_equality(self):
    note_message = error.note("foo.emb", parser_types.make_location(
        (3, 4), (3, 6)), "thing")
    self.assertEqual(note_message,
                     error.note("foo.emb", parser_types.make_location(
                         (3, 4), (3, 6)), "thing"))
    self.assertNotEqual(note_message,
                        error.warn("foo.emb", parser_types.make_location(
                            (3, 4), (3, 6)), "thing"))
    self.assertNotEqual(note_message,
                        error.note("foo2.emb", parser_types.make_location(
                            (3, 4), (3, 6)), "thing"))
    self.assertNotEqual(note_message,
                        error.note("foo.emb", parser_types.make_location(
                            (2, 4), (3, 6)), "thing"))
    self.assertNotEqual(note_message,
                        error.note("foo.emb", parser_types.make_location(
                            (3, 4), (3, 6)), "thing2"))


class StringTest(unittest.TestCase):
  """Tests for strings."""

  # These strings are a fixed part of the API.

  def test_color_strings(self):
    self.assertEqual("\033[0;30m", error.BLACK)
    self.assertEqual("\033[0;31m", error.RED)
    self.assertEqual("\033[0;32m", error.GREEN)
    self.assertEqual("\033[0;33m", error.YELLOW)
    self.assertEqual("\033[0;34m", error.BLUE)
    self.assertEqual("\033[0;35m", error.MAGENTA)
    self.assertEqual("\033[0;36m", error.CYAN)
    self.assertEqual("\033[0;37m", error.WHITE)
    self.assertEqual("\033[0;1;30m", error.BRIGHT_BLACK)
    self.assertEqual("\033[0;1;31m", error.BRIGHT_RED)
    self.assertEqual("\033[0;1;32m", error.BRIGHT_GREEN)
    self.assertEqual("\033[0;1;33m", error.BRIGHT_YELLOW)
    self.assertEqual("\033[0;1;34m", error.BRIGHT_BLUE)
    self.assertEqual("\033[0;1;35m", error.BRIGHT_MAGENTA)
    self.assertEqual("\033[0;1;36m", error.BRIGHT_CYAN)
    self.assertEqual("\033[0;1;37m", error.BRIGHT_WHITE)
    self.assertEqual("\033[0;1m", error.BOLD)
    self.assertEqual("\033[0m", error.RESET)

  def test_error_strings(self):
    self.assertEqual("error", error.ERROR)
    self.assertEqual("warning", error.WARNING)
    self.assertEqual("note", error.NOTE)


class SplitErrorsTest(unittest.TestCase):

  def test_split_errors(self):
    user_error = [
        error.error("foo.emb", parser_types.make_location((1, 2), (3, 4)),
                    "Bad thing"),
        error.note("foo.emb", parser_types.make_location((3, 4), (5, 6)),
                   "Note: bad thing referrent")
    ]
    user_error_2 = [
        error.error("foo.emb", parser_types.make_location((8, 9), (10, 11)),
                    "Bad thing"),
        error.note("foo.emb", parser_types.make_location((10, 11), (12, 13)),
                   "Note: bad thing referrent")
    ]
    synthetic_error = [
        error.error("foo.emb", parser_types.make_location((1, 2), (3, 4)),
                    "Bad thing"),
        error.note("foo.emb", parser_types.make_location((3, 4), (5, 6), True),
                   "Note: bad thing referrent")
    ]
    synthetic_error_2 = [
        error.error("foo.emb",
                    parser_types.make_location((8, 9), (10, 11), True),
                    "Bad thing"),
        error.note("foo.emb", parser_types.make_location((10, 11), (12, 13)),
                   "Note: bad thing referrent")
    ]
    user_errors, synthetic_errors = error.split_errors(
        [user_error, synthetic_error])
    self.assertEqual([user_error], user_errors)
    self.assertEqual([synthetic_error], synthetic_errors)
    user_errors, synthetic_errors = error.split_errors(
        [synthetic_error, user_error])
    self.assertEqual([user_error], user_errors)
    self.assertEqual([synthetic_error], synthetic_errors)
    user_errors, synthetic_errors = error.split_errors(
        [synthetic_error, user_error, synthetic_error_2, user_error_2])
    self.assertEqual([user_error, user_error_2], user_errors)
    self.assertEqual([synthetic_error, synthetic_error_2], synthetic_errors)

  def test_filter_errors(self):
    user_error = [
        error.error("foo.emb", parser_types.make_location((1, 2), (3, 4)),
                    "Bad thing"),
        error.note("foo.emb", parser_types.make_location((3, 4), (5, 6)),
                   "Note: bad thing referrent")
    ]
    synthetic_error = [
        error.error("foo.emb", parser_types.make_location((1, 2), (3, 4)),
                    "Bad thing"),
        error.note("foo.emb", parser_types.make_location((3, 4), (5, 6), True),
                   "Note: bad thing referrent")
    ]
    synthetic_error_2 = [
        error.error("foo.emb",
                    parser_types.make_location((8, 9), (10, 11), True),
                    "Bad thing"),
        error.note("foo.emb", parser_types.make_location((10, 11), (12, 13)),
                   "Note: bad thing referrent")
    ]
    self.assertEqual(
        [user_error],
        error.filter_errors([synthetic_error, user_error, synthetic_error_2]))


class FormatErrorsTest(unittest.TestCase):

  def test_format_errors(self):
    errors = [[error.note("foo.emb", parser_types.make_location((3, 4), (3, 6)),
                          "note")]]
    sources = {"foo.emb": "x\ny\nz  bcd\nq\n"}
    self.assertEqual("foo.emb:3:4: note: note\n"
                     "z  bcd\n"
                     "   ^^", error.format_errors(errors, sources))
    bold = error.BOLD
    reset = error.RESET
    white = error.WHITE
    bright_black = error.BRIGHT_BLACK
    bright_green = error.BRIGHT_GREEN
    self.assertEqual(bold + "foo.emb:3:4: " + reset + bright_black + "note: " +
                     reset + white + "note\n" +
                     reset + white + "z  bcd\n" +
                     reset + bright_green + "   ^^" + reset,
                     error.format_errors(errors, sources, use_color=True))

if __name__ == "__main__":
  unittest.main()
