1#!/usr/bin/env python
2# Copyright (c) 2012 The Chromium Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6import cStringIO
7import logging
8import os
9import sys
10import textwrap
11import unittest
12
13BASE_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
14sys.path.append(BASE_PATH)
15
16from lib.bucket import Bucket
17from lib.ordered_dict import OrderedDict
18from lib.policy import Policy
19from lib.symbol import SymbolMappingCache
20from lib.symbol import FUNCTION_SYMBOLS, SOURCEFILE_SYMBOLS, TYPEINFO_SYMBOLS
21
22import subcommands
23
24
25class SymbolMappingCacheTest(unittest.TestCase):
26  class MockBucketSet(object):
27    def __init__(self, addresses):
28      self._addresses = addresses
29
30    def iter_addresses(self, symbol_type):  # pylint: disable=W0613
31      for address in self._addresses:
32        yield address
33
34  class MockSymbolFinder(object):
35    def __init__(self, mapping):
36      self._mapping = mapping
37
38    def find(self, address_list):
39      result = OrderedDict()
40      for address in address_list:
41        result[address] = self._mapping[address]
42      return result
43
44  _TEST_FUNCTION_CACHE = textwrap.dedent("""\
45      1 0x0000000000000001
46      7fc33eebcaa4 __gnu_cxx::new_allocator::allocate
47      7fc33ef69242 void DispatchToMethod
48      """)
49
50  _EXPECTED_TEST_FUNCTION_CACHE = textwrap.dedent("""\
51      1 0x0000000000000001
52      7fc33eebcaa4 __gnu_cxx::new_allocator::allocate
53      7fc33ef69242 void DispatchToMethod
54      2 0x0000000000000002
55      7fc33ef7bc3e std::map::operator[]
56      7fc34411f9d5 WTF::RefCounted::operator new
57      """)
58
59  _TEST_FUNCTION_ADDRESS_LIST1 = [
60      0x1, 0x7fc33eebcaa4, 0x7fc33ef69242]
61
62  _TEST_FUNCTION_ADDRESS_LIST2 = [
63      0x1, 0x2, 0x7fc33eebcaa4, 0x7fc33ef69242, 0x7fc33ef7bc3e, 0x7fc34411f9d5]
64
65  _TEST_FUNCTION_DICT = {
66      0x1: '0x0000000000000001',
67      0x2: '0x0000000000000002',
68      0x7fc33eebcaa4: '__gnu_cxx::new_allocator::allocate',
69      0x7fc33ef69242: 'void DispatchToMethod',
70      0x7fc33ef7bc3e: 'std::map::operator[]',
71      0x7fc34411f9d5: 'WTF::RefCounted::operator new',
72  }
73
74  def test_update(self):
75    symbol_mapping_cache = SymbolMappingCache()
76    cache_f = cStringIO.StringIO()
77    cache_f.write(self._TEST_FUNCTION_CACHE)
78
79    # No update from self._TEST_FUNCTION_CACHE
80    symbol_mapping_cache.update(
81        FUNCTION_SYMBOLS,
82        self.MockBucketSet(self._TEST_FUNCTION_ADDRESS_LIST1),
83        self.MockSymbolFinder(self._TEST_FUNCTION_DICT), cache_f)
84    for address in self._TEST_FUNCTION_ADDRESS_LIST1:
85      self.assertEqual(self._TEST_FUNCTION_DICT[address],
86                       symbol_mapping_cache.lookup(FUNCTION_SYMBOLS, address))
87    self.assertEqual(self._TEST_FUNCTION_CACHE, cache_f.getvalue())
88
89    # Update to self._TEST_FUNCTION_ADDRESS_LIST2
90    symbol_mapping_cache.update(
91        FUNCTION_SYMBOLS,
92        self.MockBucketSet(self._TEST_FUNCTION_ADDRESS_LIST2),
93        self.MockSymbolFinder(self._TEST_FUNCTION_DICT), cache_f)
94    for address in self._TEST_FUNCTION_ADDRESS_LIST2:
95      self.assertEqual(self._TEST_FUNCTION_DICT[address],
96                       symbol_mapping_cache.lookup(FUNCTION_SYMBOLS, address))
97    self.assertEqual(self._EXPECTED_TEST_FUNCTION_CACHE, cache_f.getvalue())
98
99
100class PolicyTest(unittest.TestCase):
101  class MockSymbolMappingCache(object):
102    def __init__(self):
103      self._symbol_caches = {
104          FUNCTION_SYMBOLS: {},
105          SOURCEFILE_SYMBOLS: {},
106          TYPEINFO_SYMBOLS: {},
107          }
108
109    def add(self, symbol_type, address, symbol):
110      self._symbol_caches[symbol_type][address] = symbol
111
112    def lookup(self, symbol_type, address):
113      symbol = self._symbol_caches[symbol_type].get(address)
114      return symbol if symbol else '0x%016x' % address
115
116  _TEST_POLICY = textwrap.dedent("""\
117      {
118        "components": [
119          "second",
120          "mmap-v8",
121          "malloc-v8",
122          "malloc-WebKit",
123          "mmap-catch-all",
124          "malloc-catch-all"
125        ],
126        "rules": [
127          {
128            "name": "second",
129            "stacktrace": "optional",
130            "allocator": "optional"
131          },
132          {
133            "name": "mmap-v8",
134            "stacktrace": ".*v8::.*",
135            "allocator": "mmap"
136          },
137          {
138            "name": "malloc-v8",
139            "stacktrace": ".*v8::.*",
140            "allocator": "malloc"
141          },
142          {
143            "name": "malloc-WebKit",
144            "stacktrace": ".*WebKit::.*",
145            "allocator": "malloc"
146          },
147          {
148            "name": "mmap-catch-all",
149            "stacktrace": ".*",
150            "allocator": "mmap"
151          },
152          {
153            "name": "malloc-catch-all",
154            "stacktrace": ".*",
155            "allocator": "malloc"
156          }
157        ],
158        "version": "POLICY_DEEP_3"
159      }
160      """)
161
162  def test_load(self):
163    policy = Policy.parse(cStringIO.StringIO(self._TEST_POLICY), 'json')
164    self.assertTrue(policy)
165    self.assertEqual('POLICY_DEEP_3', policy.version)
166
167  def test_find(self):
168    policy = Policy.parse(cStringIO.StringIO(self._TEST_POLICY), 'json')
169    self.assertTrue(policy)
170
171    symbol_mapping_cache = self.MockSymbolMappingCache()
172    symbol_mapping_cache.add(FUNCTION_SYMBOLS, 0x1212, 'v8::create')
173    symbol_mapping_cache.add(FUNCTION_SYMBOLS, 0x1381, 'WebKit::create')
174
175    bucket1 = Bucket([0x1212, 0x013], 'malloc', 0x29492, '_Z')
176    bucket1.symbolize(symbol_mapping_cache)
177    bucket2 = Bucket([0x18242, 0x1381], 'malloc', 0x9492, '_Z')
178    bucket2.symbolize(symbol_mapping_cache)
179    bucket3 = Bucket([0x18242, 0x181], 'malloc', 0x949, '_Z')
180    bucket3.symbolize(symbol_mapping_cache)
181
182    self.assertEqual('malloc-v8', policy.find_malloc(bucket1))
183    self.assertEqual('malloc-WebKit', policy.find_malloc(bucket2))
184    self.assertEqual('malloc-catch-all', policy.find_malloc(bucket3))
185
186
187class BucketsCommandTest(unittest.TestCase):
188  def test(self):
189    BUCKETS_PATH = os.path.join(BASE_PATH, 'tests', 'output', 'buckets')
190    with open(BUCKETS_PATH) as output_f:
191      expected = output_f.read()
192
193    out = cStringIO.StringIO()
194
195    HEAP_PATH = os.path.join(BASE_PATH, 'tests', 'data', 'heap.01234.0001.heap')
196    subcommand = subcommands.BucketsCommand()
197    returncode = subcommand.do(['buckets', HEAP_PATH], out)
198    self.assertEqual(0, returncode)
199    self.assertEqual(expected, out.getvalue())
200
201
202class UploadCommandTest(unittest.TestCase):
203  def test(self):
204    MOCK_GSUTIL_PATH = os.path.join(BASE_PATH, 'tests', 'mock_gsutil.py')
205    HEAP_PATH = os.path.join(BASE_PATH, 'tests', 'data', 'heap.01234.0001.heap')
206    subcommand = subcommands.UploadCommand()
207    returncode = subcommand.do([
208        'upload',
209         '--gsutil',
210        MOCK_GSUTIL_PATH,
211        HEAP_PATH,
212        'gs://test-storage/'])
213    self.assertEqual(0, returncode)
214
215
216if __name__ == '__main__':
217  logging.basicConfig(
218      level=logging.DEBUG if '-v' in sys.argv else logging.ERROR,
219      format='%(levelname)5s %(filename)15s(%(lineno)3d): %(message)s')
220  unittest.main()
221