OLD | NEW |
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # Copyright 2017 The Chromium Authors. All rights reserved. | 2 # Copyright 2017 The Chromium Authors. All rights reserved. |
3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
5 | 5 |
| 6 import contextlib |
6 import copy | 7 import copy |
7 import difflib | 8 import difflib |
8 import glob | 9 import glob |
9 import itertools | 10 import itertools |
10 import logging | 11 import logging |
11 import os | 12 import os |
12 import unittest | 13 import unittest |
| 14 import re |
13 import subprocess | 15 import subprocess |
14 import sys | 16 import sys |
15 import tempfile | 17 import tempfile |
16 | 18 |
17 import archive | 19 import archive |
18 import describe | 20 import describe |
| 21 import diff |
19 import file_format | 22 import file_format |
20 import models | 23 import models |
21 import paths | |
22 | 24 |
23 | 25 |
24 _SCRIPT_DIR = os.path.dirname(__file__) | 26 _SCRIPT_DIR = os.path.dirname(__file__) |
25 _TEST_DATA_DIR = os.path.join(_SCRIPT_DIR, 'testdata') | 27 _TEST_DATA_DIR = os.path.join(_SCRIPT_DIR, 'testdata') |
| 28 _TEST_OUTPUT_DIR = os.path.join(_TEST_DATA_DIR, 'mock_output_directory') |
| 29 _TEST_TOOL_PREFIX = os.path.join( |
| 30 os.path.abspath(_TEST_DATA_DIR), 'mock_toolchain', '') |
26 _TEST_MAP_PATH = os.path.join(_TEST_DATA_DIR, 'test.map') | 31 _TEST_MAP_PATH = os.path.join(_TEST_DATA_DIR, 'test.map') |
| 32 _TEST_ELF_PATH = os.path.join(_TEST_OUTPUT_DIR, 'elf') |
27 | 33 |
28 update_goldens = False | 34 update_goldens = False |
29 | 35 |
30 | 36 |
31 def _AssertGolden(expected_lines, actual_lines): | 37 def _AssertGolden(expected_lines, actual_lines): |
32 expected = list(expected_lines) | 38 expected = list(expected_lines) |
33 actual = list(l + '\n' for l in actual_lines) | 39 actual = list(l + '\n' for l in actual_lines) |
34 assert actual == expected, ('Did not match .golden.\n' + | 40 assert actual == expected, ('Did not match .golden.\n' + |
35 ''.join(difflib.unified_diff(expected, actual, 'expected', 'actual'))) | 41 ''.join(difflib.unified_diff(expected, actual, 'expected', 'actual'))) |
36 | 42 |
37 | 43 |
38 def _CompareWithGolden(func): | 44 def _CompareWithGolden(name=None): |
39 name = func.__name__.replace('test_', '') | 45 def real_decorator(func): |
40 golden_path = os.path.join(_TEST_DATA_DIR, name + '.golden') | 46 basename = name |
| 47 if not basename: |
| 48 basename = func.__name__.replace('test_', '') |
| 49 golden_path = os.path.join(_TEST_DATA_DIR, basename + '.golden') |
41 | 50 |
42 def inner(self): | 51 def inner(self): |
43 actual_lines = func(self) | 52 actual_lines = func(self) |
| 53 actual_lines = (re.sub(r'(elf_mtime=).*', r'\1{redacted}', l) |
| 54 for l in actual_lines) |
44 | 55 |
45 if update_goldens: | 56 if update_goldens: |
46 with open(golden_path, 'w') as file_obj: | 57 with open(golden_path, 'w') as file_obj: |
47 describe.WriteLines(actual_lines, file_obj.write) | 58 describe.WriteLines(actual_lines, file_obj.write) |
48 logging.info('Wrote %s', golden_path) | 59 logging.info('Wrote %s', golden_path) |
49 else: | 60 else: |
50 with open(golden_path) as file_obj: | 61 with open(golden_path) as file_obj: |
51 _AssertGolden(file_obj, actual_lines) | 62 _AssertGolden(file_obj, actual_lines) |
52 return inner | 63 return inner |
| 64 return real_decorator |
53 | 65 |
54 | 66 |
55 def _RunApp(name, *args): | 67 @contextlib.contextmanager |
| 68 def _AddMocksToPath(): |
| 69 prev_path = os.environ['PATH'] |
| 70 os.environ['PATH'] = _TEST_TOOL_PREFIX[:-1] + os.path.pathsep + prev_path |
| 71 yield |
| 72 os.environ['PATH'] = prev_path |
| 73 |
| 74 |
| 75 def _RunApp(name, args, debug_measures=False): |
56 argv = [os.path.join(_SCRIPT_DIR, 'main.py'), name, '--no-pypy'] | 76 argv = [os.path.join(_SCRIPT_DIR, 'main.py'), name, '--no-pypy'] |
57 argv.extend(args) | 77 argv.extend(args) |
58 return subprocess.check_output(argv).splitlines() | 78 with _AddMocksToPath(): |
| 79 env = None |
| 80 if debug_measures: |
| 81 env = os.environ.copy() |
| 82 env['SUPERSIZE_DISABLE_ASYNC'] = '1' |
| 83 env['SUPERSIZE_MEASURE_GZIP'] = '1' |
| 84 |
| 85 return subprocess.check_output(argv, env=env).splitlines() |
59 | 86 |
60 | 87 |
61 class IntegrationTest(unittest.TestCase): | 88 class IntegrationTest(unittest.TestCase): |
62 size_info = None | 89 cached_size_info = [None, None, None] |
63 | 90 |
64 def _CloneSizeInfo(self): | 91 def _CloneSizeInfo(self, use_output_directory=True, use_elf=True): |
65 if not IntegrationTest.size_info: | 92 assert not use_elf or use_output_directory |
66 lazy_paths = paths.LazyPaths(output_directory=_TEST_DATA_DIR) | 93 i = int(use_output_directory) + int(use_elf) |
67 IntegrationTest.size_info = ( | 94 if not IntegrationTest.cached_size_info[i]: |
68 archive.CreateSizeInfo(_TEST_MAP_PATH, lazy_paths)) | 95 elf_path = _TEST_ELF_PATH if use_elf else None |
69 return copy.deepcopy(IntegrationTest.size_info) | 96 output_directory = _TEST_OUTPUT_DIR if use_output_directory else None |
| 97 IntegrationTest.cached_size_info[i] = archive.CreateSizeInfo( |
| 98 _TEST_MAP_PATH, elf_path, _TEST_TOOL_PREFIX, output_directory) |
| 99 if use_elf: |
| 100 with _AddMocksToPath(): |
| 101 IntegrationTest.cached_size_info[i].metadata = archive.CreateMetadata( |
| 102 _TEST_MAP_PATH, elf_path, None, _TEST_TOOL_PREFIX, |
| 103 output_directory) |
| 104 return copy.deepcopy(IntegrationTest.cached_size_info[i]) |
70 | 105 |
71 @_CompareWithGolden | 106 def _DoArchiveTest(self, use_output_directory=True, use_elf=True, |
72 def test_Archive(self): | 107 debug_measures=False): |
73 with tempfile.NamedTemporaryFile(suffix='.size') as temp_file: | 108 with tempfile.NamedTemporaryFile(suffix='.size') as temp_file: |
74 _RunApp('archive', temp_file.name, '--output-directory', _TEST_DATA_DIR, | 109 args = [temp_file.name, '--map-file', _TEST_MAP_PATH] |
75 '--map-file', _TEST_MAP_PATH) | 110 if use_output_directory: |
| 111 # Let autodetection find output_directory when --elf-file is used. |
| 112 if not use_elf: |
| 113 args += ['--output-directory', _TEST_OUTPUT_DIR] |
| 114 else: |
| 115 args += ['--no-source-paths'] |
| 116 if use_elf: |
| 117 args += ['--elf-file', _TEST_ELF_PATH] |
| 118 _RunApp('archive', args, debug_measures=debug_measures) |
76 size_info = archive.LoadAndPostProcessSizeInfo(temp_file.name) | 119 size_info = archive.LoadAndPostProcessSizeInfo(temp_file.name) |
77 # Check that saving & loading is the same as directly parsing the .map. | 120 # Check that saving & loading is the same as directly parsing the .map. |
78 expected_size_info = self._CloneSizeInfo() | 121 expected_size_info = self._CloneSizeInfo( |
| 122 use_output_directory=use_output_directory, use_elf=use_elf) |
79 self.assertEquals(expected_size_info.metadata, size_info.metadata) | 123 self.assertEquals(expected_size_info.metadata, size_info.metadata) |
80 expected = '\n'.join(describe.GenerateLines( | 124 expected = list(describe.GenerateLines( |
81 expected_size_info, verbose=True, recursive=True)), | 125 expected_size_info, verbose=True, recursive=True)) |
82 actual = '\n'.join(describe.GenerateLines( | 126 actual = list(describe.GenerateLines( |
83 size_info, verbose=True, recursive=True)), | 127 size_info, verbose=True, recursive=True)) |
84 self.assertEquals(expected, actual) | 128 self.assertEquals(expected, actual) |
85 | 129 |
86 sym_strs = (repr(sym) for sym in size_info.symbols) | 130 sym_strs = (repr(sym) for sym in size_info.symbols) |
87 stats = describe.DescribeSizeInfoCoverage(size_info) | 131 stats = describe.DescribeSizeInfoCoverage(size_info) |
88 return itertools.chain(stats, sym_strs) | 132 if size_info.metadata: |
| 133 metadata = describe.DescribeMetadata(size_info.metadata) |
| 134 else: |
| 135 metadata = [] |
| 136 return itertools.chain(metadata, stats, sym_strs) |
89 | 137 |
90 def test_Archive_NoSourcePaths(self): | 138 @_CompareWithGolden() |
91 # Just tests that it doesn't crash. | 139 def test_Archive(self): |
92 with tempfile.NamedTemporaryFile(suffix='.size') as temp_file: | 140 return self._DoArchiveTest(use_output_directory=False, use_elf=False) |
93 _RunApp('archive', temp_file.name, '--no-source-paths', | |
94 '--map-file', _TEST_MAP_PATH) | |
95 archive.LoadAndPostProcessSizeInfo(temp_file.name) | |
96 | 141 |
97 @_CompareWithGolden | 142 @_CompareWithGolden() |
| 143 def test_Archive_OutputDirectory(self): |
| 144 return self._DoArchiveTest(use_elf=False) |
| 145 |
| 146 @_CompareWithGolden() |
| 147 def test_Archive_Elf(self): |
| 148 return self._DoArchiveTest() |
| 149 |
| 150 @_CompareWithGolden(name='Archive_Elf') |
| 151 def test_Archive_Elf_DebugMeasures(self): |
| 152 return self._DoArchiveTest(debug_measures=True) |
| 153 |
| 154 @_CompareWithGolden() |
98 def test_Console(self): | 155 def test_Console(self): |
99 with tempfile.NamedTemporaryFile(suffix='.size') as size_file, \ | 156 with tempfile.NamedTemporaryFile(suffix='.size') as size_file, \ |
100 tempfile.NamedTemporaryFile(suffix='.txt') as output_file: | 157 tempfile.NamedTemporaryFile(suffix='.txt') as output_file: |
101 file_format.SaveSizeInfo(self._CloneSizeInfo(), size_file.name) | 158 file_format.SaveSizeInfo(self._CloneSizeInfo(), size_file.name) |
102 query = [ | 159 query = [ |
103 'ShowExamples()', | 160 'ShowExamples()', |
104 'ExpandRegex("_foo_")', | 161 'ExpandRegex("_foo_")', |
105 'Print(size_info, to_file=%r)' % output_file.name, | 162 'Print(size_info, to_file=%r)' % output_file.name, |
106 ] | 163 ] |
107 ret = _RunApp('console', size_file.name, '--query', '; '.join(query)) | 164 ret = _RunApp('console', [size_file.name, '--query', '; '.join(query)]) |
108 with open(output_file.name) as f: | 165 with open(output_file.name) as f: |
109 ret.extend(l.rstrip() for l in f) | 166 ret.extend(l.rstrip() for l in f) |
110 return ret | 167 return ret |
111 | 168 |
112 @_CompareWithGolden | 169 @_CompareWithGolden() |
113 def test_Diff_NullDiff(self): | 170 def test_Diff_NullDiff(self): |
114 with tempfile.NamedTemporaryFile(suffix='.size') as temp_file: | 171 with tempfile.NamedTemporaryFile(suffix='.size') as temp_file: |
115 file_format.SaveSizeInfo(self._CloneSizeInfo(), temp_file.name) | 172 file_format.SaveSizeInfo(self._CloneSizeInfo(), temp_file.name) |
116 return _RunApp('diff', temp_file.name, temp_file.name) | 173 return _RunApp('diff', [temp_file.name, temp_file.name]) |
117 | 174 |
118 @_CompareWithGolden | 175 @_CompareWithGolden() |
119 def test_ActualDiff(self): | 176 def test_Diff_Basic(self): |
120 size_info1 = self._CloneSizeInfo() | 177 size_info1 = self._CloneSizeInfo(use_elf=False) |
121 size_info2 = self._CloneSizeInfo() | 178 size_info2 = self._CloneSizeInfo(use_elf=False) |
122 size_info1.metadata = {"foo": 1, "bar": [1,2,3], "baz": "yes"} | 179 size_info1.metadata = {"foo": 1, "bar": [1,2,3], "baz": "yes"} |
123 size_info2.metadata = {"foo": 1, "bar": [1,3], "baz": "yes"} | 180 size_info2.metadata = {"foo": 1, "bar": [1,3], "baz": "yes"} |
124 size_info1.symbols -= size_info1.symbols[:2] | 181 size_info1.symbols -= size_info1.symbols[:2] |
125 size_info2.symbols -= size_info2.symbols[-3:] | 182 size_info2.symbols -= size_info2.symbols[-3:] |
126 size_info1.symbols[1].size -= 10 | 183 size_info1.symbols[1].size -= 10 |
127 diff = models.Diff(size_info1, size_info2) | 184 d = diff.Diff(size_info1, size_info2) |
128 return describe.GenerateLines(diff, verbose=True) | 185 return describe.GenerateLines(d, verbose=True) |
129 | 186 |
130 @_CompareWithGolden | 187 def test_Diff_Aliases1(self): |
| 188 symbols1 = self._CloneSizeInfo().symbols |
| 189 symbols2 = self._CloneSizeInfo().symbols |
| 190 |
| 191 # Removing 1 alias should not change the size. |
| 192 a1, _, _ = symbols2.Filter(lambda s: s.num_aliases == 3)[0].aliases |
| 193 symbols2 -= [a1] |
| 194 a1.aliases.remove(a1) |
| 195 d = diff.Diff(symbols1, symbols2) |
| 196 self.assertEquals(d.size, 0) |
| 197 self.assertEquals(d.removed_count, 1) |
| 198 |
| 199 # Adding one alias should not change size. |
| 200 d = diff.Diff(symbols2, symbols1) |
| 201 self.assertEquals(d.size, 0) |
| 202 self.assertEquals(d.added_count, 1) |
| 203 |
| 204 def test_Diff_Aliases2(self): |
| 205 symbols1 = self._CloneSizeInfo().symbols |
| 206 symbols2 = self._CloneSizeInfo().symbols |
| 207 |
| 208 # Removing 2 aliases should not change the size. |
| 209 a1, a2, _ = symbols2.Filter(lambda s: s.num_aliases == 3)[0].aliases |
| 210 symbols2 -= [a1, a2] |
| 211 a1.aliases.remove(a1) |
| 212 a1.aliases.remove(a2) |
| 213 d = diff.Diff(symbols1, symbols2) |
| 214 self.assertEquals(d.size, 0) |
| 215 self.assertEquals(d.removed_count, 2) |
| 216 |
| 217 # Adding 2 aliases should not change size. |
| 218 d = diff.Diff(symbols2, symbols1) |
| 219 self.assertEquals(d.size, 0) |
| 220 self.assertEquals(d.added_count, 2) |
| 221 |
| 222 def test_Diff_Aliases3(self): |
| 223 symbols1 = self._CloneSizeInfo().symbols |
| 224 symbols2 = self._CloneSizeInfo().symbols |
| 225 |
| 226 # Removing all 3 aliases should change the size. |
| 227 a1, a2, a3 = symbols2.Filter(lambda s: s.num_aliases == 3)[0].aliases |
| 228 symbols2 -= [a1, a2, a3] |
| 229 d = diff.Diff(symbols1, symbols2) |
| 230 self.assertEquals(d.size, -a1.size) |
| 231 self.assertEquals(d.removed_count, 3) |
| 232 |
| 233 # Adding all 3 aliases should change size. |
| 234 d = diff.Diff(symbols2, symbols1) |
| 235 self.assertEquals(d.size, a1.size) |
| 236 self.assertEquals(d.added_count, 3) |
| 237 |
| 238 @_CompareWithGolden() |
131 def test_FullDescription(self): | 239 def test_FullDescription(self): |
132 return describe.GenerateLines(self._CloneSizeInfo()) | 240 return describe.GenerateLines(self._CloneSizeInfo()) |
133 | 241 |
134 @_CompareWithGolden | 242 @_CompareWithGolden() |
135 def test_SymbolGroupMethods(self): | 243 def test_SymbolGroupMethods(self): |
136 all_syms = self._CloneSizeInfo().symbols | 244 all_syms = self._CloneSizeInfo().symbols |
137 global_syms = all_syms.WhereNameMatches('GLOBAL') | 245 global_syms = all_syms.WhereNameMatches('GLOBAL') |
138 # Tests Filter(), Inverted(), and __sub__(). | 246 # Tests Filter(), Inverted(), and __sub__(). |
139 non_global_syms = global_syms.Inverted() | 247 non_global_syms = global_syms.Inverted() |
140 self.assertEqual(non_global_syms, (all_syms - global_syms)) | 248 self.assertEqual(non_global_syms, (all_syms - global_syms)) |
141 # Tests Sorted() and __add__(). | 249 # Tests Sorted() and __add__(). |
142 self.assertEqual(all_syms.Sorted(), | 250 self.assertEqual(all_syms.Sorted(), |
143 (global_syms + non_global_syms).Sorted()) | 251 (global_syms + non_global_syms).Sorted()) |
144 # Tests GroupByNamespace() and __len__(). | 252 # Tests GroupByNamespace() and __len__(). |
(...skipping 17 matching lines...) Expand all Loading... |
162 global update_goldens | 270 global update_goldens |
163 update_goldens = True | 271 update_goldens = True |
164 for f in glob.glob(os.path.join(_TEST_DATA_DIR, '*.golden')): | 272 for f in glob.glob(os.path.join(_TEST_DATA_DIR, '*.golden')): |
165 os.unlink(f) | 273 os.unlink(f) |
166 | 274 |
167 unittest.main(argv=argv, verbosity=2) | 275 unittest.main(argv=argv, verbosity=2) |
168 | 276 |
169 | 277 |
170 if __name__ == '__main__': | 278 if __name__ == '__main__': |
171 main() | 279 main() |
OLD | NEW |