| 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) |
| 44 | 53 |
| 45 if update_goldens: | 54 if update_goldens: |
| 46 with open(golden_path, 'w') as file_obj: | 55 with open(golden_path, 'w') as file_obj: |
| 47 describe.WriteLines(actual_lines, file_obj.write) | 56 describe.WriteLines(actual_lines, file_obj.write) |
| 48 logging.info('Wrote %s', golden_path) | 57 logging.info('Wrote %s', golden_path) |
| 49 else: | 58 else: |
| 50 with open(golden_path) as file_obj: | 59 with open(golden_path) as file_obj: |
| 51 _AssertGolden(file_obj, actual_lines) | 60 _AssertGolden(file_obj, actual_lines) |
| 52 return inner | 61 return inner |
| 62 return real_decorator |
| 53 | 63 |
| 54 | 64 |
| 55 def _RunApp(name, *args): | 65 @contextlib.contextmanager |
| 66 def _AddMocksToPath(): |
| 67 prev_path = os.environ['PATH'] |
| 68 os.environ['PATH'] = _TEST_TOOL_PREFIX[:-1] + os.path.pathsep + prev_path |
| 69 yield |
| 70 os.environ['PATH'] = prev_path |
| 71 |
| 72 |
| 73 def _RunApp(name, args, debug_measures=False): |
| 56 argv = [os.path.join(_SCRIPT_DIR, 'main.py'), name, '--no-pypy'] | 74 argv = [os.path.join(_SCRIPT_DIR, 'main.py'), name, '--no-pypy'] |
| 57 argv.extend(args) | 75 argv.extend(args) |
| 58 return subprocess.check_output(argv).splitlines() | 76 with _AddMocksToPath(): |
| 77 env = None |
| 78 if debug_measures: |
| 79 env = os.environ.copy() |
| 80 env['SUPERSIZE_DISABLE_ASYNC'] = '1' |
| 81 env['SUPERSIZE_MEASURE_GZIP'] = '1' |
| 82 |
| 83 return subprocess.check_output(argv, env=env).splitlines() |
| 59 | 84 |
| 60 | 85 |
| 61 class IntegrationTest(unittest.TestCase): | 86 class IntegrationTest(unittest.TestCase): |
| 62 size_info = None | 87 cached_size_info = [None, None, None] |
| 63 | 88 |
| 64 def _CloneSizeInfo(self): | 89 def _CloneSizeInfo(self, use_output_directory=True, use_elf=True): |
| 65 if not IntegrationTest.size_info: | 90 assert not use_elf or use_output_directory |
| 66 lazy_paths = paths.LazyPaths(output_directory=_TEST_DATA_DIR) | 91 i = int(use_output_directory) + int(use_elf) |
| 67 IntegrationTest.size_info = ( | 92 if not IntegrationTest.cached_size_info[i]: |
| 68 archive.CreateSizeInfo(_TEST_MAP_PATH, lazy_paths)) | 93 elf_path = _TEST_ELF_PATH if use_elf else None |
| 69 return copy.deepcopy(IntegrationTest.size_info) | 94 output_directory = _TEST_OUTPUT_DIR if use_output_directory else None |
| 95 IntegrationTest.cached_size_info[i] = archive.CreateSizeInfo( |
| 96 _TEST_MAP_PATH, elf_path, _TEST_TOOL_PREFIX, output_directory) |
| 97 if use_elf: |
| 98 with _AddMocksToPath(): |
| 99 IntegrationTest.cached_size_info[i].metadata = archive.CreateMetadata( |
| 100 _TEST_MAP_PATH, elf_path, None, _TEST_TOOL_PREFIX, |
| 101 output_directory) |
| 102 return copy.deepcopy(IntegrationTest.cached_size_info[i]) |
| 70 | 103 |
| 71 @_CompareWithGolden | 104 def _DoArchiveTest(self, use_output_directory=True, use_elf=True, |
| 72 def test_Archive(self): | 105 debug_measures=False): |
| 73 with tempfile.NamedTemporaryFile(suffix='.size') as temp_file: | 106 with tempfile.NamedTemporaryFile(suffix='.size') as temp_file: |
| 74 _RunApp('archive', temp_file.name, '--output-directory', _TEST_DATA_DIR, | 107 args = [temp_file.name, '--map-file', _TEST_MAP_PATH] |
| 75 '--map-file', _TEST_MAP_PATH) | 108 if use_output_directory: |
| 109 # Let autodetection find output_directory when --elf-file is used. |
| 110 if not use_elf: |
| 111 args += ['--output-directory', _TEST_OUTPUT_DIR] |
| 112 else: |
| 113 args += ['--no-source-paths'] |
| 114 if use_elf: |
| 115 args += ['--elf-file', _TEST_ELF_PATH] |
| 116 _RunApp('archive', args, debug_measures=debug_measures) |
| 76 size_info = archive.LoadAndPostProcessSizeInfo(temp_file.name) | 117 size_info = archive.LoadAndPostProcessSizeInfo(temp_file.name) |
| 77 # Check that saving & loading is the same as directly parsing the .map. | 118 # Check that saving & loading is the same as directly parsing the .map. |
| 78 expected_size_info = self._CloneSizeInfo() | 119 expected_size_info = self._CloneSizeInfo( |
| 120 use_output_directory=use_output_directory, use_elf=use_elf) |
| 79 self.assertEquals(expected_size_info.metadata, size_info.metadata) | 121 self.assertEquals(expected_size_info.metadata, size_info.metadata) |
| 80 expected = '\n'.join(describe.GenerateLines( | 122 expected = list(describe.GenerateLines( |
| 81 expected_size_info, verbose=True, recursive=True)), | 123 expected_size_info, verbose=True, recursive=True)) |
| 82 actual = '\n'.join(describe.GenerateLines( | 124 actual = list(describe.GenerateLines( |
| 83 size_info, verbose=True, recursive=True)), | 125 size_info, verbose=True, recursive=True)) |
| 84 self.assertEquals(expected, actual) | 126 self.assertEquals(expected, actual) |
| 85 | 127 |
| 86 sym_strs = (repr(sym) for sym in size_info.symbols) | 128 sym_strs = (repr(sym) for sym in size_info.symbols) |
| 87 stats = describe.DescribeSizeInfoCoverage(size_info) | 129 stats = describe.DescribeSizeInfoCoverage(size_info) |
| 88 return itertools.chain(stats, sym_strs) | 130 if size_info.metadata: |
| 131 metadata = describe.DescribeMetadata(size_info.metadata) |
| 132 metadata = (re.sub(r'(elf_mtime=).*', r'\1{redacted}', l) |
| 133 for l in 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, a2, a3 = 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, a3 = 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 |