1#!/usr/bin/env python3 2# -*- coding: utf-8 -*- 3# Copyright 2019 The Chromium OS Authors. All rights reserved. 4# Use of this source code is governed by a BSD-style license that can be 5# found in the LICENSE file. 6 7"""Tests for afdo_prof_analysis.""" 8 9from __future__ import print_function 10 11import random 12import io 13import unittest 14 15from afdo_tools.bisection import afdo_prof_analysis as analysis 16 17 18class AfdoProfAnalysisTest(unittest.TestCase): 19 """Class for testing AFDO Profile Analysis""" 20 bad_items = {'func_a': '1', 'func_b': '3', 'func_c': '5'} 21 good_items = {'func_a': '2', 'func_b': '4', 'func_d': '5'} 22 random.seed(13) # 13 is an arbitrary choice. just for consistency 23 # add some extra info to make tests more reflective of real scenario 24 for num in range(128): 25 func_name = 'func_extra_%d' % num 26 # 1/3 to both, 1/3 only to good, 1/3 only to bad 27 rand_val = random.randint(1, 101) 28 if rand_val < 67: 29 bad_items[func_name] = 'test_data' 30 if rand_val < 34 or rand_val >= 67: 31 good_items[func_name] = 'test_data' 32 33 analysis.random.seed(5) # 5 is an arbitrary choice. For consistent testing 34 35 def test_text_to_json(self): 36 test_data = io.StringIO('deflate_slow:87460059:3\n' 37 ' 3: 24\n' 38 ' 14: 54767\n' 39 ' 15: 664 fill_window:22\n' 40 ' 16: 661\n' 41 ' 19: 637\n' 42 ' 41: 36692 longest_match:36863\n' 43 ' 44: 36692\n' 44 ' 44.2: 5861\n' 45 ' 46: 13942\n' 46 ' 46.1: 14003\n') 47 expected = { 48 'deflate_slow': ':87460059:3\n' 49 ' 3: 24\n' 50 ' 14: 54767\n' 51 ' 15: 664 fill_window:22\n' 52 ' 16: 661\n' 53 ' 19: 637\n' 54 ' 41: 36692 longest_match:36863\n' 55 ' 44: 36692\n' 56 ' 44.2: 5861\n' 57 ' 46: 13942\n' 58 ' 46.1: 14003\n' 59 } 60 actual = analysis.text_to_json(test_data) 61 self.assertEqual(actual, expected) 62 test_data.close() 63 64 def test_text_to_json_empty_afdo(self): 65 expected = {} 66 actual = analysis.text_to_json('') 67 self.assertEqual(actual, expected) 68 69 def test_json_to_text(self): 70 example_prof = {'func_a': ':1\ndata\n', 'func_b': ':2\nmore data\n'} 71 expected_text = 'func_a:1\ndata\nfunc_b:2\nmore data\n' 72 self.assertEqual(analysis.json_to_text(example_prof), expected_text) 73 74 def test_bisect_profiles(self): 75 76 # mock run of external script with arbitrarily-chosen bad profile vals 77 # save_run specified and unused b/c afdo_prof_analysis.py 78 # will call with argument explicitly specified 79 # pylint: disable=unused-argument 80 class DeciderClass(object): 81 """Class for this tests's decider.""" 82 83 def run(self, prof, save_run=False): 84 if '1' in prof['func_a'] or '3' in prof['func_b']: 85 return analysis.StatusEnum.BAD_STATUS 86 return analysis.StatusEnum.GOOD_STATUS 87 88 results = analysis.bisect_profiles_wrapper(DeciderClass(), self.good_items, 89 self.bad_items) 90 self.assertEqual(results['individuals'], sorted(['func_a', 'func_b'])) 91 self.assertEqual(results['ranges'], []) 92 93 def test_range_search(self): 94 95 # arbitrarily chosen functions whose values in the bad profile constitute 96 # a problematic pair 97 # pylint: disable=unused-argument 98 class DeciderClass(object): 99 """Class for this tests's decider.""" 100 101 def run(self, prof, save_run=False): 102 if '1' in prof['func_a'] and '3' in prof['func_b']: 103 return analysis.StatusEnum.BAD_STATUS 104 return analysis.StatusEnum.GOOD_STATUS 105 106 # put the problematic combination in separate halves of the common funcs 107 # so that non-bisecting search is invoked for its actual use case 108 common_funcs = [func for func in self.good_items if func in self.bad_items] 109 common_funcs.remove('func_a') 110 common_funcs.insert(0, 'func_a') 111 common_funcs.remove('func_b') 112 common_funcs.append('func_b') 113 114 problem_range = analysis.range_search(DeciderClass(), self.good_items, 115 self.bad_items, common_funcs, 0, 116 len(common_funcs)) 117 118 self.assertEqual(['func_a', 'func_b'], problem_range) 119 120 def test_check_good_not_bad(self): 121 func_in_good = 'func_c' 122 123 # pylint: disable=unused-argument 124 class DeciderClass(object): 125 """Class for this tests's decider.""" 126 127 def run(self, prof, save_run=False): 128 if func_in_good in prof: 129 return analysis.StatusEnum.GOOD_STATUS 130 return analysis.StatusEnum.BAD_STATUS 131 132 self.assertTrue( 133 analysis.check_good_not_bad(DeciderClass(), self.good_items, 134 self.bad_items)) 135 136 def test_check_bad_not_good(self): 137 func_in_bad = 'func_d' 138 139 # pylint: disable=unused-argument 140 class DeciderClass(object): 141 """Class for this tests's decider.""" 142 143 def run(self, prof, save_run=False): 144 if func_in_bad in prof: 145 return analysis.StatusEnum.BAD_STATUS 146 return analysis.StatusEnum.GOOD_STATUS 147 148 self.assertTrue( 149 analysis.check_bad_not_good(DeciderClass(), self.good_items, 150 self.bad_items)) 151 152 153if __name__ == '__main__': 154 unittest.main() 155