#!/usr/bin/env vpython3
# Copyright 2017 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

"""Tests for process_profiles.py."""

import collections
import unittest

import process_profiles

from test_utils import (
  ProfileFile,
  SimpleTestSymbol,
  TestSymbolOffsetProcessor,
  TestProfileManager,
)


class ProcessProfilesTestCase(unittest.TestCase):
  START_SYMBOL = 'linker_script_start_of_text'

  def setUp(self):
    self.symbol_0 = SimpleTestSymbol(self.START_SYMBOL, 0, 0)
    self.symbol_1 = SimpleTestSymbol('1', 6, 16)
    self.symbol_2 = SimpleTestSymbol('2', 32, 8)
    self.symbol_3 = SimpleTestSymbol('3', 40, 12)
    self.offset_to_symbol_info = (
      [None] * 3
      + [self.symbol_1] * 8
      + [None] * 5
      + [self.symbol_2] * 4
      + [self.symbol_3] * 6
    )
    self.symbol_infos = [
      self.symbol_0,
      self.symbol_1,
      self.symbol_2,
      self.symbol_3,
    ]
    self._file_counter = 0

  def MakeAnnotatedOffset(self, offset, counts):
    ao = process_profiles.ProfileManager.AnnotatedOffset(offset)
    ao._count = counts
    return ao

  def testGetOffsetToSymbolInfo(self):
    processor = TestSymbolOffsetProcessor(self.symbol_infos)
    self.assertListEqual(
      self.offset_to_symbol_info, processor.GetDumpOffsetToSymbolInfo()
    )

  def testOverlappingSymbols(self):
    symbol_1 = SimpleTestSymbol(self.START_SYMBOL, 6, 8)
    symbol_2 = SimpleTestSymbol('2', 10, 10)
    processor = TestSymbolOffsetProcessor([symbol_1, symbol_2])
    self.assertListEqual(
      [symbol_1] * 4 + [symbol_2] * 3, processor.GetDumpOffsetToSymbolInfo()
    )

  def testSymbolsBeforeStart(self):
    self.symbol_infos = [
      SimpleTestSymbol(s.name, s.offset + 8, s.size) for s in self.symbol_infos
    ]
    self.symbol_infos.append(SimpleTestSymbol('early', 0, 4))
    processor = TestSymbolOffsetProcessor(self.symbol_infos)
    self.assertRaises(AssertionError, processor.GetDumpOffsetToSymbolInfo)

  def testGetReachedOffsetsFromDump(self):
    processor = TestSymbolOffsetProcessor(self.symbol_infos)
    # 2 hits for symbol_1, 0 for symbol_2, 1 for symbol_3
    dump = [8, 12, 48]
    reached = processor.GetReachedOffsetsFromDump(dump)
    self.assertListEqual([self.symbol_1.offset, self.symbol_3.offset], reached)
    # Ordering matters, no repetitions
    dump = [48, 12, 8, 12, 8, 16]
    reached = processor.GetReachedOffsetsFromDump(dump)
    self.assertListEqual([self.symbol_3.offset, self.symbol_1.offset], reached)

  def testSymbolNameToPrimary(self):
    symbol_infos = [
      SimpleTestSymbol('1', 8, 16),
      SimpleTestSymbol('AnAlias', 8, 16),
      SimpleTestSymbol('Another', 40, 16),
    ]
    processor = TestSymbolOffsetProcessor(symbol_infos)
    self.assertDictEqual(
      {8: symbol_infos[0], 40: symbol_infos[2]}, processor.OffsetToPrimaryMap()
    )

  def testGetOrderedSymbols(self):
    processor = TestSymbolOffsetProcessor(self.symbol_infos)
    self.assertListEqual(
      ['1', '3', self.START_SYMBOL], processor.GetOrderedSymbols([7, 41, 5, 0])
    )

  def testOffsetToSymbolsMap(self):
    symbol_infos = [
      SimpleTestSymbol('1', 8, 16),
      SimpleTestSymbol('AnAlias', 8, 16),
      SimpleTestSymbol('Another', 40, 16),
    ]
    processor = TestSymbolOffsetProcessor(symbol_infos)
    self.assertDictEqual(
      {8: [symbol_infos[0], symbol_infos[1]], 40: [symbol_infos[2]]},
      processor.OffsetToSymbolsMap(),
    )

  def testPrimarySizeMismatch(self):
    symbol_infos = [
      SimpleTestSymbol('1', 8, 16),
      SimpleTestSymbol('AnAlias', 8, 32),
    ]
    processor = TestSymbolOffsetProcessor(symbol_infos)
    self.assertRaises(AssertionError, processor.OffsetToPrimaryMap)
    symbol_infos = [
      SimpleTestSymbol('1', 8, 0),
      SimpleTestSymbol('2', 8, 32),
      SimpleTestSymbol('3', 8, 32),
      SimpleTestSymbol('4', 8, 0),
    ]
    processor = TestSymbolOffsetProcessor(symbol_infos)
    self.assertDictEqual({8: symbol_infos[1]}, processor.OffsetToPrimaryMap())

  def testMatchSymbols(self):
    symbols = [
      SimpleTestSymbol('W', 30, 10),
      SimpleTestSymbol('Y', 60, 5),
      SimpleTestSymbol('X', 100, 10),
    ]
    processor = TestSymbolOffsetProcessor(symbols)
    self.assertListEqual(
      sorted(symbols[1:3]), processor.MatchSymbolNames(['Y', 'X'])
    )

  def testSymbolsSize(self):
    symbols = [
      SimpleTestSymbol('W', 10, 1),
      SimpleTestSymbol('X', 20, 2),
      SimpleTestSymbol('Y', 30, 4),
      SimpleTestSymbol('Z', 40, 8),
    ]
    processor = TestSymbolOffsetProcessor(symbols)
    self.assertEqual(13, processor.SymbolsSize(['W', 'Y', 'Z']))

  def testMedian(self):
    self.assertEquals(None, process_profiles._Median([]))
    self.assertEquals(5, process_profiles._Median([5]))
    self.assertEquals(5, process_profiles._Median([1, 5, 20]))
    self.assertEquals(5, process_profiles._Median([4, 6]))
    self.assertEquals(5, process_profiles._Median([1, 4, 6, 100]))
    self.assertEquals(5, process_profiles._Median([1, 4, 5, 6, 100]))

  def testRunGroups(self):
    files = [
      ProfileFile(40, 0),
      ProfileFile(100, 0),
      ProfileFile(200, 1),
      ProfileFile(35, 1),
      ProfileFile(42, 0),
      ProfileFile(95, 0),
    ]
    mgr = process_profiles.ProfileManager(files)
    mgr._ComputeRunGroups()
    self.assertEquals(3, len(mgr._run_groups))
    self.assertEquals(3, len(mgr._run_groups[0].Filenames()))
    self.assertEquals(2, len(mgr._run_groups[1].Filenames()))
    self.assertEquals(1, len(mgr._run_groups[2].Filenames()))
    self.assertTrue(files[0] in mgr._run_groups[0].Filenames())
    self.assertTrue(files[3] in mgr._run_groups[0].Filenames())
    self.assertTrue(files[4] in mgr._run_groups[0].Filenames())
    self.assertTrue(files[1] in mgr._run_groups[1].Filenames())
    self.assertTrue(files[5] in mgr._run_groups[1].Filenames())
    self.assertTrue(files[2] in mgr._run_groups[2].Filenames())

  def testRunGroupSanity(self):
    files = []
    # Generate 20 sets of files in groups separated by 60s.
    for ts_base in range(0, 20):
      ts = ts_base * 60
      files.extend(
        [
          ProfileFile(ts, 0, 'browser'),
          ProfileFile(ts + 1, 0, 'renderer'),
          ProfileFile(ts + 2, 1, 'browser'),
          ProfileFile(ts + 3, 0, 'gpu'),
          ProfileFile(ts + 2, 1, 'renderer'),
          ProfileFile(ts + 5, 1, 'gpu'),
        ]
      )
    # The following call should not assert.
    process_profiles.ProfileManager(files)._ComputeRunGroups()

    files.extend(
      [
        ProfileFile(20 * 60, 0, 'browser'),
        ProfileFile(20 * 60 + 2, 1, 'renderer'),
        ProfileFile(21 * 60, 0, 'browser'),
      ]
      + [ProfileFile(22 * 60, 0, 'renderer') for _ in range(0, 10)]
    )

    self.assertRaises(
      AssertionError, process_profiles.ProfileManager(files)._ComputeRunGroups
    )

  def testReadOffsets(self):
    mgr = TestProfileManager(
      {
        ProfileFile(30, 0): [1, 3, 5, 7],
        ProfileFile(40, 1): [8, 10],
        ProfileFile(50, 0): [13, 15],
      }
    )
    self.assertListEqual([1, 3, 5, 7, 8, 10, 13, 15], mgr.GetMergedOffsets())
    self.assertListEqual([8, 10], mgr.GetMergedOffsets(1))
    self.assertListEqual([], mgr.GetMergedOffsets(2))

  def testRunGroupOffsets(self):
    mgr = TestProfileManager(
      {
        ProfileFile(30, 0): [1, 2, 3, 4],
        ProfileFile(150, 0): [9, 11, 13],
        ProfileFile(40, 1): [5, 6, 7],
      }
    )
    offsets_list = mgr.GetRunGroupOffsets()
    self.assertEquals(2, len(offsets_list))
    self.assertListEqual([1, 2, 3, 4, 5, 6, 7], offsets_list[0])
    self.assertListEqual([9, 11, 13], offsets_list[1])
    offsets_list = mgr.GetRunGroupOffsets(0)
    self.assertEquals(2, len(offsets_list))
    self.assertListEqual([1, 2, 3, 4], offsets_list[0])
    self.assertListEqual([9, 11, 13], offsets_list[1])
    offsets_list = mgr.GetRunGroupOffsets(1)
    self.assertEquals(2, len(offsets_list))
    self.assertListEqual([5, 6, 7], offsets_list[0])
    self.assertListEqual([], offsets_list[1])

  def testSorted(self):
    # The fact that the ProfileManager sorts by filename is implicit in the
    # other tests. It is tested explicitly here.
    mgr = TestProfileManager(
      {
        ProfileFile(40, 0): [1, 2, 3, 4],
        ProfileFile(150, 0): [9, 11, 13],
        ProfileFile(30, 1): [5, 6, 7],
      }
    )
    offsets_list = mgr.GetRunGroupOffsets()
    self.assertEquals(2, len(offsets_list))
    self.assertListEqual([5, 6, 7, 1, 2, 3, 4], offsets_list[0])

  def testPhases(self):
    mgr = TestProfileManager(
      {
        ProfileFile(40, 0): [],
        ProfileFile(150, 0): [],
        ProfileFile(30, 1): [],
        ProfileFile(30, 2): [],
        ProfileFile(30, 0): [],
      }
    )
    self.assertEquals(set([0, 1, 2]), mgr.GetPhases())

  def testGetAnnotatedOffsets(self):
    mgr = TestProfileManager(
      {
        ProfileFile(40, 0, ''): [1, 2, 3],
        ProfileFile(50, 1, ''): [3, 4, 5],
        ProfileFile(51, 0, 'renderer'): [2, 3, 6],
        ProfileFile(51, 1, 'gpu-process'): [6, 7],
        ProfileFile(70, 0, ''): [2, 8, 9],
        ProfileFile(70, 1, ''): [9],
      }
    )
    offsets = list(mgr.GetAnnotatedOffsets())
    self.assertListEqual(
      [
        self.MakeAnnotatedOffset(1, {(0, 'browser'): 1}),
        self.MakeAnnotatedOffset(2, {(0, 'browser'): 2, (0, 'renderer'): 1}),
        self.MakeAnnotatedOffset(
          3, {(0, 'browser'): 1, (1, 'browser'): 1, (0, 'renderer'): 1}
        ),
        self.MakeAnnotatedOffset(4, {(1, 'browser'): 1}),
        self.MakeAnnotatedOffset(5, {(1, 'browser'): 1}),
        self.MakeAnnotatedOffset(
          6, {(0, 'renderer'): 1, (1, 'gpu-process'): 1}
        ),
        self.MakeAnnotatedOffset(7, {(1, 'gpu-process'): 1}),
        self.MakeAnnotatedOffset(8, {(0, 'browser'): 1}),
        self.MakeAnnotatedOffset(9, {(0, 'browser'): 1, (1, 'browser'): 1}),
      ],
      offsets,
    )
    self.assertListEqual(
      ['browser', 'renderer'], sorted(offsets[1].Processes())
    )
    self.assertListEqual(['browser'], list(offsets[0].Processes()))
    self.assertListEqual([0], list(offsets[1].Phases()))
    self.assertListEqual([0, 1], sorted(offsets[2].Phases()))
    self.assertListEqual([0, 1], sorted(mgr.GetPhases()))


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