chromium/tools/binary_size/libsupersize/linker_map_parser_test.py

#!/usr/bin/env python3
# Copyright 2018 The Chromium Authors
# 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')
_TEST_CFI_MAP_PATH = os.path.join(_TEST_DATA_DIR, 'test_lld-lto_v1_cfi.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(path):
  with open(path, '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
      yield line


def _RenderSectionSizesAndRawSymbols(section_sizes, raw_symbols):
  ret = []
  ret.append('******** section_sizes ********')
  for k, (address, size) in sorted(section_sizes.items()):
    address_text = '@%x' % address
    ret.append('%-24s %-9s %d' % (k, address_text, size))
  ret.append('')
  ret.append('******** raw_symbols ********')
  for sym in raw_symbols:
    ret.append(repr(sym))
  return ret


class LinkerMapParserTest(unittest.TestCase):

  @_CompareWithGolden()
  def test_Parser(self):
    lines = _ReadMapFile(_TEST_MAP_PATH)
    section_sizes, raw_symbols, _ = linker_map_parser.ParseLines(lines)
    return _RenderSectionSizesAndRawSymbols(section_sizes, raw_symbols)

  @_CompareWithGolden()
  def test_ParserCfi(self):
    lines = _ReadMapFile(_TEST_CFI_MAP_PATH)
    section_sizes, raw_symbols, _ = linker_map_parser.ParseLines(lines)
    return _RenderSectionSizesAndRawSymbols(section_sizes, raw_symbols)

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

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

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

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

  @_CompareWithGolden()
  def test_Tokenize(self):
    ret = []
    lines = _ReadMapFile(_TEST_MAP_PATH)
    parser = linker_map_parser.MapFileParserLld('lld-lto_v1')
    tokenizer = parser.Tokenize(lines)
    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()