chromium/tools/binary_size/libsupersize/bcanalyzer_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 ast
import itertools
import os
import unittest

import bcanalyzer
import parallel
import test_util


def _MakeBytes(bits, toks):
  """Creates a multi-byte string from ASCII strings and/or ASCII codes.

  Args:
    bits: Number of bits per character, must be in {8, 16, 32}.
    toks: A list of tokens, each of which is a ASCII strings or an integer
      representing a character's ASCII value (e.g., 0 for terminating null).

  Returns: A flattened string of the |bits|-bit string formed by |bit|-extending
    the result of concatanating tokens.
  """
  cfi = itertools.chain.from_iterable
  chars = cfi(map(ord, t) if isinstance(t, str) else (t, ) for t in toks)
  return bytes(cfi((c >> (8 * i) for i in range(bits // 8)) for c in chars))


class BcAnalyzerTest(unittest.TestCase):

  def testParseTag(self):
    # Valid cases.
    self.assertEqual((bcanalyzer.OPENING_TAG, 'FOO', 4),
                     bcanalyzer.ParseTag('<FOO> trailing'))
    self.assertEqual((bcanalyzer.OPENING_TAG, 'BAR', 4),
                     bcanalyzer.ParseTag('<BAR op0=3 op1=4>'))
    self.assertEqual((bcanalyzer.CLOSING_TAG, 'FOO', 5),
                     bcanalyzer.ParseTag('</FOO>'))
    self.assertEqual((bcanalyzer.SELF_CLOSING_TAG, 'FOO', 4),
                     bcanalyzer.ParseTag('<FOO/>'))
    self.assertEqual((bcanalyzer.SELF_CLOSING_TAG, 'TOMATO2', 8),
                     bcanalyzer.ParseTag('<TOMATO2   />'))
    # Not self-closing: For simplicity we requires '/>' with space.
    self.assertEqual((bcanalyzer.OPENING_TAG, 'TOMATO2', 8),
                     bcanalyzer.ParseTag('<TOMATO2  / >'))
    self.assertEqual((bcanalyzer.SELF_CLOSING_TAG, 'BAR', 4),
                     bcanalyzer.ParseTag('<BAR op0=3 op1=4/>'))
    self.assertEqual((bcanalyzer.OPENING_TAG, 'FOO', 4),
                     bcanalyzer.ParseTag('<FOO> / trailing'))
    self.assertEqual((bcanalyzer.SELF_CLOSING_TAG, 'STRUCT_NAME', 12),
                     bcanalyzer.ParseTag('<STRUCT_NAME abbrevid=7 op0=0/>'))
    self.assertEqual((bcanalyzer.SELF_CLOSING_TAG, 'UnkownCode41', 13),
                     bcanalyzer.ParseTag('<UnkownCode41 op0=0 op1=4 op2=5/>'))
    self.assertEqual((bcanalyzer.CLOSING_TAG, 'FOO_BAR', 9),
                     bcanalyzer.ParseTag('</FOO_BAR> \'/>trailing\''))
    self.assertEqual((bcanalyzer.OPENING_TAG, 'A', 2),
                     bcanalyzer.ParseTag('<A>'))
    self.assertEqual((bcanalyzer.OPENING_TAG, 'lower', 6),
                     bcanalyzer.ParseTag('<lower >'))
    # An invalid tag (all numbers), but we allow for simplicity.
    self.assertEqual((bcanalyzer.OPENING_TAG, '123', 4),
                     bcanalyzer.ParseTag('<123>'))
    # Abominations that are allowed for simplicity.
    self.assertEqual((bcanalyzer.SELF_CLOSING_TAG, 'FOO', 5),
                     bcanalyzer.ParseTag('</FOO/>'))
    self.assertEqual((bcanalyzer.SELF_CLOSING_TAG, 'FOO', 4),
                     bcanalyzer.ParseTag('<FOO///////>'))
    self.assertEqual((bcanalyzer.OPENING_TAG, 'FOO', 4),
                     bcanalyzer.ParseTag('<FOO>>>>>'))

    # Invalid cases.
    None3 = (None, None, None)
    self.assertEqual(None3, bcanalyzer.ParseTag(''))
    self.assertEqual(None3, bcanalyzer.ParseTag('     '))
    self.assertEqual(None3, bcanalyzer.ParseTag('<>'))
    self.assertEqual(None3, bcanalyzer.ParseTag('<>      '))
    self.assertEqual(None3, bcanalyzer.ParseTag('< >'))
    self.assertEqual(None3, bcanalyzer.ParseTag('<<FOO>'))
    self.assertEqual(None3, bcanalyzer.ParseTag('< FOO>'))
    self.assertEqual(None3, bcanalyzer.ParseTag('<//FOO>'))
    self.assertEqual(None3, bcanalyzer.ParseTag('</ FOO>'))
    self.assertEqual(None3, bcanalyzer.ParseTag('< FOO />'))
    self.assertEqual(None3, bcanalyzer.ParseTag('<FOO<>'))
    self.assertEqual(None3, bcanalyzer.ParseTag('<NOEND'))
    self.assertEqual(None3, bcanalyzer.ParseTag('some text'))
    self.assertEqual(None3, bcanalyzer.ParseTag('    <UNTRIMMED>'))
    self.assertEqual(None3, bcanalyzer.ParseTag('&lt;AAA&gt;'))

  def testAnalyzer(self):
    # Save global param in bcanalyzer.
    saved_char_width_limit = bcanalyzer._CHAR_WIDTH_LIMIT

    for width_limit, include_4byte_strings in [(2, False), (4, True)]:
      # Tweak global param in bcanalyzer.
      bcanalyzer._CHAR_WIDTH_LIMIT = width_limit

      with test_util.AddMocksToPath():
        encoded_results = bcanalyzer.RunBcAnalyzerOnIntermediates(
            ['test.o'], test_util.TEST_OUTPUT_DIR)

      results = parallel.DecodeDictOfLists(
          encoded_results, value_transform=ast.literal_eval)
      self.assertEqual(['test.o'], list(results.keys()))
      str_list = results['test.o']

      # See mock_bcanalyzer.py for details on the C++ test file.
      expected = []
      expected.append(_MakeBytes(8, ['Test1a', 0]))
      expected.append(_MakeBytes(8, ['Test1b', 0]))
      expected.append(_MakeBytes(8, ['Test2a', 0]))
      expected.append(_MakeBytes(8, ['Test2b', 0]))
      expected.append(_MakeBytes(16, ['Test3a', 0]))
      expected.append(_MakeBytes(16, ['Test3b', 0]))
      if include_4byte_strings:
        expected.append(_MakeBytes(32, ['Test4a', 0]))
        expected.append(_MakeBytes(32, ['Test4b', 0]))
      expected.append(_MakeBytes(8, [1, 0, 0, 1, 1, 0]))
      expected.append(_MakeBytes(8, [1, 0, 0, 1, 1, 1]))
      expected.append(_MakeBytes(8, ['Test5a', 0]))
      expected.append(_MakeBytes(8, ['Test5b', 1]))
      expected.append(_MakeBytes(16, ['Test6a', 0]))
      expected.append(_MakeBytes(16, ['Test6b', 1]))
      if include_4byte_strings:
        expected.append(_MakeBytes(32, ['Test7a', 0]))
        expected.append(_MakeBytes(32, ['Test7b', 1]))
      expected.append(_MakeBytes(8, ['Test8a', 0]))
      expected.append(_MakeBytes(8, ['Test8b', 0]))
      # Exclude |{u8a, u8b, u16a, u16b, u32a, u32b, u64a, u64b}|.
      # Exclude |{s8empty, s16empty, s32empty}|.
      expected.append(_MakeBytes(8, ['1a', 0]))
      # Exclude |zeros|, which should be in .bss section.

      self.assertEqual(expected, str_list)

    # Restore globa param in bcanalyzer.
    bcanalyzer._CHAR_WIDTH_LIMIT = saved_char_width_limit

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