diff options
Diffstat (limited to 'afdo_tools/bisection/afdo_prof_analysis_test.py')
-rwxr-xr-x | afdo_tools/bisection/afdo_prof_analysis_test.py | 154 |
1 files changed, 154 insertions, 0 deletions
diff --git a/afdo_tools/bisection/afdo_prof_analysis_test.py b/afdo_tools/bisection/afdo_prof_analysis_test.py new file mode 100755 index 00000000..7bd3050c --- /dev/null +++ b/afdo_tools/bisection/afdo_prof_analysis_test.py @@ -0,0 +1,154 @@ +#!/usr/bin/env python2 +# -*- coding: utf-8 -*- +# Copyright 2019 The Chromium OS Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Tests for afdo_prof_analysis.""" + +from __future__ import print_function + +import afdo_prof_analysis as analysis + +import random +import StringIO +import unittest + + +class AfdoProfAnalysisTest(unittest.TestCase): + """Class for testing AFDO Profile Analysis""" + bad_items = {'func_a': '1', 'func_b': '3', 'func_c': '5'} + good_items = {'func_a': '2', 'func_b': '4', 'func_d': '5'} + random.seed(13) # 13 is an arbitrary choice. just for consistency + # add some extra info to make tests more reflective of real scenario + for num in range(128): + func_name = 'func_extra_%d' % num + # 1/3 to both, 1/3 only to good, 1/3 only to bad + rand_val = random.randint(1, 101) + if rand_val < 67: + bad_items[func_name] = 'test_data' + if rand_val < 34 or rand_val >= 67: + good_items[func_name] = 'test_data' + + analysis.random.seed(5) # 5 is an arbitrary choice. For consistent testing + + def test_text_to_json(self): + test_data = StringIO.StringIO('deflate_slow:87460059:3\n' + ' 3: 24\n' + ' 14: 54767\n' + ' 15: 664 fill_window:22\n' + ' 16: 661\n' + ' 19: 637\n' + ' 41: 36692 longest_match:36863\n' + ' 44: 36692\n' + ' 44.2: 5861\n' + ' 46: 13942\n' + ' 46.1: 14003\n') + expected = { + 'deflate_slow': ':87460059:3\n' + ' 3: 24\n' + ' 14: 54767\n' + ' 15: 664 fill_window:22\n' + ' 16: 661\n' + ' 19: 637\n' + ' 41: 36692 longest_match:36863\n' + ' 44: 36692\n' + ' 44.2: 5861\n' + ' 46: 13942\n' + ' 46.1: 14003\n' + } + actual = analysis.text_to_json(test_data) + self.assertEqual(actual, expected) + test_data.close() + + def test_text_to_json_empty_afdo(self): + expected = {} + actual = analysis.text_to_json('') + self.assertEqual(actual, expected) + + def test_json_to_text(self): + example_prof = {'func_a': ':1\ndata\n', 'func_b': ':2\nmore data\n'} + expected_text = 'func_a:1\ndata\nfunc_b:2\nmore data\n' + self.assertEqual(analysis.json_to_text(example_prof), expected_text) + + def test_bisect_profiles(self): + + # mock run of external script with arbitrarily-chosen bad profile vals + # save_run specified and unused b/c afdo_prof_analysis.py + # will call with argument explicitly specified + # pylint: disable=unused-argument + class DeciderClass(object): + """Class for this tests's decider.""" + + def run(self, prof, save_run=False): + if '1' in prof['func_a'] or '3' in prof['func_b']: + return analysis.StatusEnum.BAD_STATUS + return analysis.StatusEnum.GOOD_STATUS + + results = analysis.bisect_profiles_wrapper(DeciderClass(), self.good_items, + self.bad_items) + self.assertEqual(results['individuals'], sorted(['func_a', 'func_b'])) + self.assertEqual(results['ranges'], []) + + def test_range_search(self): + + # arbitrarily chosen functions whose values in the bad profile constitute + # a problematic pair + # pylint: disable=unused-argument + class DeciderClass(object): + """Class for this tests's decider.""" + + def run(self, prof, save_run=False): + if '1' in prof['func_a'] and '3' in prof['func_b']: + return analysis.StatusEnum.BAD_STATUS + return analysis.StatusEnum.GOOD_STATUS + + # put the problematic combination in separate halves of the common funcs + # so that non-bisecting search is invoked for its actual use case + common_funcs = [func for func in self.good_items if func in self.bad_items] + common_funcs.remove('func_a') + common_funcs.insert(0, 'func_a') + common_funcs.remove('func_b') + common_funcs.append('func_b') + + problem_range = analysis.range_search(DeciderClass(), self.good_items, + self.bad_items, common_funcs, 0, + len(common_funcs)) + + self.assertEquals(['func_a', 'func_b'], problem_range) + + def test_check_good_not_bad(self): + func_in_good = 'func_c' + + # pylint: disable=unused-argument + class DeciderClass(object): + """Class for this tests's decider.""" + + def run(self, prof, save_run=False): + if func_in_good in prof: + return analysis.StatusEnum.GOOD_STATUS + return analysis.StatusEnum.BAD_STATUS + + self.assertTrue( + analysis.check_good_not_bad(DeciderClass(), self.good_items, + self.bad_items)) + + def test_check_bad_not_good(self): + func_in_bad = 'func_d' + + # pylint: disable=unused-argument + class DeciderClass(object): + """Class for this tests's decider.""" + + def run(self, prof, save_run=False): + if func_in_bad in prof: + return analysis.StatusEnum.BAD_STATUS + return analysis.StatusEnum.GOOD_STATUS + + self.assertTrue( + analysis.check_bad_not_good(DeciderClass(), self.good_items, + self.bad_items)) + + +if __name__ == '__main__': + unittest.main() |