#!/usr/bin/env python
# Copyright 2018 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.

import glob
import os
import sys
import unittest

import linker_map_parser
import test_util

_SCRIPT_DIR = os.path.dirname(__file__)
_TEST_DATA_DIR = os.path.join(_SCRIPT_DIR, 'testdata', 'linker_map_parser')
_TEST_MAP_PATH = os.path.join(_TEST_DATA_DIR, 'test_lld-lto_v1.map')


def _CompareWithGolden(name=None):

  def real_decorator(func):
    basename = name
    if not basename:
      basename = func.__name__.replace('test_', '')
    golden_path = os.path.join(_TEST_DATA_DIR, basename + '.golden')

    def inner(self):
      actual_lines = func(self)
      test_util.Golden.CheckOrUpdate(golden_path, actual_lines)

    return inner

  return real_decorator


def _ReadMapFile(map_file):
  ret = []
  with open(map_file, 'r') as f:
    for line in f:
      # Strip blank lines and comments.
      stripped_line = line.lstrip()
      if not stripped_line or stripped_line.startswith('#'):
        continue
      ret.append(line)
  return ret


class LinkerMapParserTest(unittest.TestCase):

  @_CompareWithGolden()
  def test_Parser(self):
    ret = []
    map_file = _ReadMapFile(_TEST_MAP_PATH)
    linker_name = linker_map_parser.DetectLinkerNameFromMapFile(iter(map_file))
    section_sizes, raw_symbols = (
        linker_map_parser.MapFileParser().Parse(linker_name, iter(map_file)))
    ret.append('******** section_sizes ********')
    for k, v in sorted(section_sizes.iteritems()):
      ret.append('%-24s %d' % (k, v))
    ret.append('')
    ret.append('******** raw_symbols ********')
    for sym in raw_symbols:
      ret.append(repr(sym))
    return ret

  def test_ParseArmAnnotations(self):
    fun = linker_map_parser.MapFileParserLld.ParseArmAnnotations

    # Annotations.
    self.assertEquals((True, False), fun('$a'))
    self.assertEquals((True, False), fun('$a.0'))
    self.assertEquals((True, False), fun('$a.137'))
    self.assertEquals((True, True), fun('$t'))
    self.assertEquals((True, True), fun('$t.42'))
    self.assertEquals((True, None), fun('$d'))
    self.assertEquals((True, None), fun('$d.7'))

    # Annotations that should not appear, but get handled anyway.
    self.assertEquals((True, False), fun('$a.'))
    self.assertEquals((True, True), fun('$t.'))
    self.assertEquals((True, None), fun('$d.'))
    self.assertEquals((True, None), fun('$$.'))

    # Non-annotations.
    self.assertEquals((False, None), fun('$_21::invoke'))
    self.assertEquals((False, None), fun('$aa'))
    self.assertEquals((False, None), fun('$tt.'))
    self.assertEquals((False, None), fun('$'))
    self.assertEquals((False, None), fun(''))
    self.assertEquals((False, None), fun('void foo()'))
    self.assertEquals((False, None), fun('OUTLINED_FUNCTION_'))
    self.assertEquals((False, None), fun('abc'))

  @_CompareWithGolden()
  def test_Tokenize(self):
    ret = []
    map_file = _ReadMapFile(_TEST_MAP_PATH)
    linker_name = linker_map_parser.DetectLinkerNameFromMapFile(iter(map_file))
    parser = linker_map_parser.MapFileParserLld(linker_name)
    tokenizer = parser.Tokenize(iter(map_file))
    for (_, address, size, level, span, tok) in tokenizer:
      ret.append('%8X %8X (%d) %s %s' % (address, size, level, '-' * 8 if
                                         span is None else '%8X' % span, tok))
    return ret


def main():
  argv = sys.argv
  if len(argv) > 1 and argv[1] == '--update':
    argv.pop(0)
    test_util.Golden.EnableUpdate()
    for f in glob.glob(os.path.join(_TEST_DATA_DIR, '*.golden')):
      os.unlink(f)

  unittest.main(argv=argv, verbosity=2)


if __name__ == '__main__':
  main()
