You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
169 lines
5.5 KiB
169 lines
5.5 KiB
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
# Copyright 2019 The ChromiumOS Authors
|
|
# 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."""
|
|
|
|
|
|
import random
|
|
import io
|
|
import unittest
|
|
|
|
from afdo_tools.bisection import afdo_prof_analysis as analysis
|
|
|
|
|
|
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 = io.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.assertEqual(["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()
|