1#!/usr/bin/env python3 2# -*- coding: utf-8 -*- 3# Copyright 2019 The ChromiumOS Authors 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 9 10import random 11import io 12import unittest 13 14from afdo_tools.bisection import afdo_prof_analysis as analysis 15 16 17class AfdoProfAnalysisTest(unittest.TestCase): 18 """Class for testing AFDO Profile Analysis""" 19 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( 37 "deflate_slow:87460059:3\n" 38 " 3: 24\n" 39 " 14: 54767\n" 40 " 15: 664 fill_window:22\n" 41 " 16: 661\n" 42 " 19: 637\n" 43 " 41: 36692 longest_match:36863\n" 44 " 44: 36692\n" 45 " 44.2: 5861\n" 46 " 46: 13942\n" 47 " 46.1: 14003\n" 48 ) 49 expected = { 50 "deflate_slow": ":87460059:3\n" 51 " 3: 24\n" 52 " 14: 54767\n" 53 " 15: 664 fill_window:22\n" 54 " 16: 661\n" 55 " 19: 637\n" 56 " 41: 36692 longest_match:36863\n" 57 " 44: 36692\n" 58 " 44.2: 5861\n" 59 " 46: 13942\n" 60 " 46.1: 14003\n" 61 } 62 actual = analysis.text_to_json(test_data) 63 self.assertEqual(actual, expected) 64 test_data.close() 65 66 def test_text_to_json_empty_afdo(self): 67 expected = {} 68 actual = analysis.text_to_json("") 69 self.assertEqual(actual, expected) 70 71 def test_json_to_text(self): 72 example_prof = {"func_a": ":1\ndata\n", "func_b": ":2\nmore data\n"} 73 expected_text = "func_a:1\ndata\nfunc_b:2\nmore data\n" 74 self.assertEqual(analysis.json_to_text(example_prof), expected_text) 75 76 def test_bisect_profiles(self): 77 78 # mock run of external script with arbitrarily-chosen bad profile vals 79 # save_run specified and unused b/c afdo_prof_analysis.py 80 # will call with argument explicitly specified 81 # pylint: disable=unused-argument 82 class DeciderClass(object): 83 """Class for this tests's decider.""" 84 85 def run(self, prof, save_run=False): 86 if "1" in prof["func_a"] or "3" in prof["func_b"]: 87 return analysis.StatusEnum.BAD_STATUS 88 return analysis.StatusEnum.GOOD_STATUS 89 90 results = analysis.bisect_profiles_wrapper( 91 DeciderClass(), self.good_items, self.bad_items 92 ) 93 self.assertEqual(results["individuals"], sorted(["func_a", "func_b"])) 94 self.assertEqual(results["ranges"], []) 95 96 def test_range_search(self): 97 98 # arbitrarily chosen functions whose values in the bad profile constitute 99 # a problematic pair 100 # pylint: disable=unused-argument 101 class DeciderClass(object): 102 """Class for this tests's decider.""" 103 104 def run(self, prof, save_run=False): 105 if "1" in prof["func_a"] and "3" in prof["func_b"]: 106 return analysis.StatusEnum.BAD_STATUS 107 return analysis.StatusEnum.GOOD_STATUS 108 109 # put the problematic combination in separate halves of the common funcs 110 # so that non-bisecting search is invoked for its actual use case 111 common_funcs = [ 112 func for func in self.good_items if func in self.bad_items 113 ] 114 common_funcs.remove("func_a") 115 common_funcs.insert(0, "func_a") 116 common_funcs.remove("func_b") 117 common_funcs.append("func_b") 118 119 problem_range = analysis.range_search( 120 DeciderClass(), 121 self.good_items, 122 self.bad_items, 123 common_funcs, 124 0, 125 len(common_funcs), 126 ) 127 128 self.assertEqual(["func_a", "func_b"], problem_range) 129 130 def test_check_good_not_bad(self): 131 func_in_good = "func_c" 132 133 # pylint: disable=unused-argument 134 class DeciderClass(object): 135 """Class for this tests's decider.""" 136 137 def run(self, prof, save_run=False): 138 if func_in_good in prof: 139 return analysis.StatusEnum.GOOD_STATUS 140 return analysis.StatusEnum.BAD_STATUS 141 142 self.assertTrue( 143 analysis.check_good_not_bad( 144 DeciderClass(), self.good_items, self.bad_items 145 ) 146 ) 147 148 def test_check_bad_not_good(self): 149 func_in_bad = "func_d" 150 151 # pylint: disable=unused-argument 152 class DeciderClass(object): 153 """Class for this tests's decider.""" 154 155 def run(self, prof, save_run=False): 156 if func_in_bad in prof: 157 return analysis.StatusEnum.BAD_STATUS 158 return analysis.StatusEnum.GOOD_STATUS 159 160 self.assertTrue( 161 analysis.check_bad_not_good( 162 DeciderClass(), self.good_items, self.bad_items 163 ) 164 ) 165 166 167if __name__ == "__main__": 168 unittest.main() 169