blob: a79c9a1d5bbc4259af7523f4418f6c6c5f294f2b [file] [log] [blame]
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright 2020 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 bisecting tool."""
__author__ = "shenhan@google.com (Han Shen)"
import os
import random
import sys
import unittest
from cros_utils import command_executer
from binary_search_tool import binary_search_state
from binary_search_tool import run_bisect
from binary_search_tool.test import common
from binary_search_tool.test import gen_obj
def GenObj():
obj_num = random.randint(100, 1000)
bad_obj_num = random.randint(obj_num // 100, obj_num // 20)
if bad_obj_num == 0:
bad_obj_num = 1
gen_obj.Main(["--obj_num", str(obj_num), "--bad_obj_num", str(bad_obj_num)])
def CleanObj():
os.remove(common.OBJECTS_FILE)
os.remove(common.WORKING_SET_FILE)
print(
'Deleted "{0}" and "{1}"'.format(
common.OBJECTS_FILE, common.WORKING_SET_FILE
)
)
class BisectTest(unittest.TestCase):
"""Tests for run_bisect.py"""
def setUp(self):
with open("./is_setup", "w", encoding="utf-8"):
pass
try:
os.remove(binary_search_state.STATE_FILE)
except OSError:
pass
def tearDown(self):
try:
os.remove("./is_setup")
os.remove(os.readlink(binary_search_state.STATE_FILE))
os.remove(binary_search_state.STATE_FILE)
except OSError:
pass
class FullBisector(run_bisect.Bisector):
"""Test bisector to test run_bisect.py with"""
def __init__(self, options, overrides):
super(BisectTest.FullBisector, self).__init__(options, overrides)
def PreRun(self):
GenObj()
return 0
def Run(self):
return binary_search_state.Run(
get_initial_items="./gen_init_list.py",
switch_to_good="./switch_to_good.py",
switch_to_bad="./switch_to_bad.py",
test_script="./is_good.py",
prune=True,
file_args=True,
)
def PostRun(self):
CleanObj()
return 0
def test_full_bisector(self):
ret = run_bisect.Run(self.FullBisector({}, {}))
self.assertEqual(ret, 0)
self.assertFalse(os.path.exists(common.OBJECTS_FILE))
self.assertFalse(os.path.exists(common.WORKING_SET_FILE))
def check_output(self):
_, out, _ = command_executer.GetCommandExecuter().RunCommandWOutput(
(
'grep "Bad items are: " logs/binary_search_tool_test.py.out | '
"tail -n1"
)
)
ls = out.splitlines()
self.assertEqual(len(ls), 1)
line = ls[0]
_, _, bad_ones = line.partition("Bad items are: ")
bad_ones = bad_ones.split()
expected_result = common.ReadObjectsFile()
# Reconstruct objects file from bad_ones and compare
actual_result = [0] * len(expected_result)
for bad_obj in bad_ones:
actual_result[int(bad_obj)] = 1
self.assertEqual(actual_result, expected_result)
class BisectingUtilsTest(unittest.TestCase):
"""Tests for bisecting tool."""
def setUp(self):
"""Generate [100-1000] object files, and 1-5% of which are bad ones."""
GenObj()
with open("./is_setup", "w", encoding="utf-8"):
pass
try:
os.remove(binary_search_state.STATE_FILE)
except OSError:
pass
def tearDown(self):
"""Cleanup temp files."""
CleanObj()
try:
os.remove(os.readlink(binary_search_state.STATE_FILE))
except OSError:
pass
cleanup_list = [
"./is_setup",
binary_search_state.STATE_FILE,
"noinc_prune_bad",
"noinc_prune_good",
"./cmd_script.sh",
]
for f in cleanup_list:
if os.path.exists(f):
os.remove(f)
def runTest(self):
ret = binary_search_state.Run(
get_initial_items="./gen_init_list.py",
switch_to_good="./switch_to_good.py",
switch_to_bad="./switch_to_bad.py",
test_script="./is_good.py",
prune=True,
file_args=True,
)
self.assertEqual(ret, 0)
self.check_output()
def test_arg_parse(self):
args = [
"--get_initial_items",
"./gen_init_list.py",
"--switch_to_good",
"./switch_to_good.py",
"--switch_to_bad",
"./switch_to_bad.py",
"--test_script",
"./is_good.py",
"--prune",
"--file_args",
]
ret = binary_search_state.Main(args)
self.assertEqual(ret, 0)
self.check_output()
def test_test_setup_script(self):
os.remove("./is_setup")
with self.assertRaises(AssertionError):
ret = binary_search_state.Run(
get_initial_items="./gen_init_list.py",
switch_to_good="./switch_to_good.py",
switch_to_bad="./switch_to_bad.py",
test_script="./is_good.py",
prune=True,
file_args=True,
)
ret = binary_search_state.Run(
get_initial_items="./gen_init_list.py",
switch_to_good="./switch_to_good.py",
switch_to_bad="./switch_to_bad.py",
test_script="./is_good.py",
test_setup_script="./test_setup.py",
prune=True,
file_args=True,
)
self.assertEqual(ret, 0)
self.check_output()
def test_bad_test_setup_script(self):
with self.assertRaises(AssertionError):
binary_search_state.Run(
get_initial_items="./gen_init_list.py",
switch_to_good="./switch_to_good.py",
switch_to_bad="./switch_to_bad.py",
test_script="./is_good.py",
test_setup_script="./test_setup_bad.py",
prune=True,
file_args=True,
)
def test_bad_save_state(self):
state_file = binary_search_state.STATE_FILE
hidden_state_file = os.path.basename(
binary_search_state.HIDDEN_STATE_FILE
)
with open(state_file, "w", encoding="utf-8") as f:
f.write("test123")
bss = binary_search_state.MockBinarySearchState()
with self.assertRaises(OSError):
bss.SaveState()
with open(state_file, "r", encoding="utf-8") as f:
self.assertEqual(f.read(), "test123")
os.remove(state_file)
# Cleanup generated save state that has no symlink
files = os.listdir(os.getcwd())
save_states = [x for x in files if x.startswith(hidden_state_file)]
_ = [os.remove(x) for x in save_states]
def test_save_state(self):
state_file = binary_search_state.STATE_FILE
bss = binary_search_state.MockBinarySearchState()
bss.SaveState()
self.assertTrue(os.path.exists(state_file))
first_state = os.readlink(state_file)
bss.SaveState()
second_state = os.readlink(state_file)
self.assertTrue(os.path.exists(state_file))
self.assertTrue(second_state != first_state)
self.assertFalse(os.path.exists(first_state))
bss.RemoveState()
self.assertFalse(os.path.islink(state_file))
self.assertFalse(os.path.exists(second_state))
def test_load_state(self):
test_items = [1, 2, 3, 4, 5]
bss = binary_search_state.MockBinarySearchState()
bss.all_items = test_items
bss.currently_good_items = set([1, 2, 3])
bss.currently_bad_items = set([4, 5])
bss.SaveState()
bss = None
bss2 = binary_search_state.MockBinarySearchState.LoadState()
self.assertEqual(bss2.all_items, test_items)
self.assertEqual(bss2.currently_good_items, set([]))
self.assertEqual(bss2.currently_bad_items, set([]))
def test_tmp_cleanup(self):
bss = binary_search_state.MockBinarySearchState(
get_initial_items='echo "0\n1\n2\n3"',
switch_to_good="./switch_tmp.py",
file_args=True,
)
bss.SwitchToGood(["0", "1", "2", "3"])
tmp_file = None
with open("tmp_file", "r", encoding="utf-8") as f:
tmp_file = f.read()
os.remove("tmp_file")
self.assertFalse(os.path.exists(tmp_file))
ws = common.ReadWorkingSet()
for i in range(3):
self.assertEqual(ws[i], 42)
def test_verify_fail(self):
bss = binary_search_state.MockBinarySearchState(
get_initial_items="./gen_init_list.py",
switch_to_good="./switch_to_bad.py",
switch_to_bad="./switch_to_good.py",
test_script="./is_good.py",
prune=True,
file_args=True,
verify=True,
)
with self.assertRaises(AssertionError):
bss.DoVerify()
def test_early_terminate(self):
bss = binary_search_state.MockBinarySearchState(
get_initial_items="./gen_init_list.py",
switch_to_good="./switch_to_good.py",
switch_to_bad="./switch_to_bad.py",
test_script="./is_good.py",
prune=True,
file_args=True,
iterations=1,
)
bss.DoSearchBadItems()
self.assertFalse(bss.found_items)
def test_no_prune(self):
bss = binary_search_state.MockBinarySearchState(
get_initial_items="./gen_init_list.py",
switch_to_good="./switch_to_good.py",
switch_to_bad="./switch_to_bad.py",
test_script="./is_good.py",
test_setup_script="./test_setup.py",
prune=False,
file_args=True,
)
bss.DoSearchBadItems()
self.assertEqual(len(bss.found_items), 1)
bad_objs = common.ReadObjectsFile()
found_obj = int(bss.found_items.pop())
self.assertEqual(bad_objs[found_obj], 1)
def test_set_file(self):
binary_search_state.Run(
get_initial_items="./gen_init_list.py",
switch_to_good="./switch_to_good_set_file.py",
switch_to_bad="./switch_to_bad_set_file.py",
test_script="./is_good.py",
prune=True,
file_args=True,
verify=True,
)
self.check_output()
def test_noincremental_prune(self):
ret = binary_search_state.Run(
get_initial_items="./gen_init_list.py",
switch_to_good="./switch_to_good_noinc_prune.py",
switch_to_bad="./switch_to_bad_noinc_prune.py",
test_script="./is_good_noinc_prune.py",
test_setup_script="./test_setup.py",
prune=True,
noincremental=True,
file_args=True,
verify=False,
)
self.assertEqual(ret, 0)
self.check_output()
def check_output(self):
_, out, _ = command_executer.GetCommandExecuter().RunCommandWOutput(
(
'grep "Bad items are: " logs/binary_search_tool_test.py.out | '
"tail -n1"
)
)
ls = out.splitlines()
self.assertEqual(len(ls), 1)
line = ls[0]
_, _, bad_ones = line.partition("Bad items are: ")
bad_ones = bad_ones.split()
expected_result = common.ReadObjectsFile()
# Reconstruct objects file from bad_ones and compare
actual_result = [0] * len(expected_result)
for bad_obj in bad_ones:
actual_result[int(bad_obj)] = 1
self.assertEqual(actual_result, expected_result)
class BisectingUtilsPassTest(BisectingUtilsTest):
"""Tests for bisecting tool at pass/transformation level."""
def check_pass_output(self, pass_name, pass_num, trans_num):
_, out, _ = command_executer.GetCommandExecuter().RunCommandWOutput(
(
'grep "Bad pass: " logs/binary_search_tool_test.py.out | '
"tail -n1"
)
)
ls = out.splitlines()
self.assertEqual(len(ls), 1)
line = ls[0]
_, _, bad_info = line.partition("Bad pass: ")
actual_info = pass_name + " at number " + str(pass_num)
self.assertEqual(actual_info, bad_info)
_, out, _ = command_executer.GetCommandExecuter().RunCommandWOutput(
(
'grep "Bad transformation number: '
'" logs/binary_search_tool_test.py.out | '
"tail -n1"
)
)
ls = out.splitlines()
self.assertEqual(len(ls), 1)
line = ls[0]
_, _, bad_info = line.partition("Bad transformation number: ")
actual_info = str(trans_num)
self.assertEqual(actual_info, bad_info)
def test_with_prune(self):
ret = binary_search_state.Run(
get_initial_items="./gen_init_list.py",
switch_to_good="./switch_to_good.py",
switch_to_bad="./switch_to_bad.py",
test_script="./is_good.py",
pass_bisect="./generate_cmd.py",
prune=True,
file_args=True,
)
self.assertEqual(ret, 1)
def test_gen_cmd_script(self):
bss = binary_search_state.MockBinarySearchState(
get_initial_items="./gen_init_list.py",
switch_to_good="./switch_to_good.py",
switch_to_bad="./switch_to_bad.py",
test_script="./is_good.py",
pass_bisect="./generate_cmd.py",
prune=False,
file_args=True,
)
bss.DoSearchBadItems()
cmd_script_path = bss.cmd_script
self.assertTrue(os.path.exists(cmd_script_path))
def test_no_pass_support(self):
bss = binary_search_state.MockBinarySearchState(
get_initial_items="./gen_init_list.py",
switch_to_good="./switch_to_good.py",
switch_to_bad="./switch_to_bad.py",
test_script="./is_good.py",
pass_bisect="./generate_cmd.py",
prune=False,
file_args=True,
)
bss.cmd_script = "./cmd_script_no_support.py"
# No support for -opt-bisect-limit
with self.assertRaises(RuntimeError):
bss.BuildWithPassLimit(-1)
def test_no_transform_support(self):
bss = binary_search_state.MockBinarySearchState(
get_initial_items="./gen_init_list.py",
switch_to_good="./switch_to_good.py",
switch_to_bad="./switch_to_bad.py",
test_script="./is_good.py",
pass_bisect="./generate_cmd.py",
prune=False,
file_args=True,
)
bss.cmd_script = "./cmd_script_no_support.py"
# No support for -print-debug-counter
with self.assertRaises(RuntimeError):
bss.BuildWithTransformLimit(-1, "counter_name")
def test_pass_transform_bisect(self):
bss = binary_search_state.MockBinarySearchState(
get_initial_items="./gen_init_list.py",
switch_to_good="./switch_to_good.py",
switch_to_bad="./switch_to_bad.py",
test_script="./is_good.py",
pass_bisect="./generate_cmd.py",
prune=False,
file_args=True,
)
pass_num = 4
trans_num = 19
bss.cmd_script = "./cmd_script.py %d %d" % (pass_num, trans_num)
bss.DoSearchBadPass()
self.check_pass_output("instcombine-visit", pass_num, trans_num)
def test_result_not_reproduced_pass(self):
bss = binary_search_state.MockBinarySearchState(
get_initial_items="./gen_init_list.py",
switch_to_good="./switch_to_good.py",
switch_to_bad="./switch_to_bad.py",
test_script="./is_good.py",
pass_bisect="./generate_cmd.py",
prune=False,
file_args=True,
)
# Fails reproducing at pass level.
pass_num = 0
trans_num = 19
bss.cmd_script = "./cmd_script.py %d %d" % (pass_num, trans_num)
with self.assertRaises(ValueError):
bss.DoSearchBadPass()
def test_result_not_reproduced_transform(self):
bss = binary_search_state.MockBinarySearchState(
get_initial_items="./gen_init_list.py",
switch_to_good="./switch_to_good.py",
switch_to_bad="./switch_to_bad.py",
test_script="./is_good.py",
pass_bisect="./generate_cmd.py",
prune=False,
file_args=True,
)
# Fails reproducing at transformation level.
pass_num = 4
trans_num = 0
bss.cmd_script = "./cmd_script.py %d %d" % (pass_num, trans_num)
with self.assertRaises(ValueError):
bss.DoSearchBadPass()
class BisectStressTest(unittest.TestCase):
"""Stress tests for bisecting tool."""
def test_every_obj_bad(self):
amt = 25
gen_obj.Main(["--obj_num", str(amt), "--bad_obj_num", str(amt)])
ret = binary_search_state.Run(
get_initial_items="./gen_init_list.py",
switch_to_good="./switch_to_good.py",
switch_to_bad="./switch_to_bad.py",
test_script="./is_good.py",
prune=True,
file_args=True,
verify=False,
)
self.assertEqual(ret, 0)
self.check_output()
def test_every_index_is_bad(self):
amt = 25
for i in range(amt):
obj_list = ["0"] * amt
obj_list[i] = "1"
obj_list = ",".join(obj_list)
gen_obj.Main(["--obj_list", obj_list])
ret = binary_search_state.Run(
get_initial_items="./gen_init_list.py",
switch_to_good="./switch_to_good.py",
switch_to_bad="./switch_to_bad.py",
test_setup_script="./test_setup.py",
test_script="./is_good.py",
prune=True,
file_args=True,
)
self.assertEqual(ret, 0)
self.check_output()
def check_output(self):
_, out, _ = command_executer.GetCommandExecuter().RunCommandWOutput(
(
'grep "Bad items are: " logs/binary_search_tool_test.py.out | '
"tail -n1"
)
)
ls = out.splitlines()
self.assertEqual(len(ls), 1)
line = ls[0]
_, _, bad_ones = line.partition("Bad items are: ")
bad_ones = bad_ones.split()
expected_result = common.ReadObjectsFile()
# Reconstruct objects file from bad_ones and compare
actual_result = [0] * len(expected_result)
for bad_obj in bad_ones:
actual_result[int(bad_obj)] = 1
self.assertEqual(actual_result, expected_result)
def Main(argv):
num_tests = 2
if len(argv) > 1:
num_tests = int(argv[1])
suite = unittest.TestSuite()
for _ in range(0, num_tests):
suite.addTest(BisectingUtilsTest())
suite.addTest(BisectingUtilsTest("test_arg_parse"))
suite.addTest(BisectingUtilsTest("test_test_setup_script"))
suite.addTest(BisectingUtilsTest("test_bad_test_setup_script"))
suite.addTest(BisectingUtilsTest("test_bad_save_state"))
suite.addTest(BisectingUtilsTest("test_save_state"))
suite.addTest(BisectingUtilsTest("test_load_state"))
suite.addTest(BisectingUtilsTest("test_tmp_cleanup"))
suite.addTest(BisectingUtilsTest("test_verify_fail"))
suite.addTest(BisectingUtilsTest("test_early_terminate"))
suite.addTest(BisectingUtilsTest("test_no_prune"))
suite.addTest(BisectingUtilsTest("test_set_file"))
suite.addTest(BisectingUtilsTest("test_noincremental_prune"))
suite.addTest(BisectingUtilsPassTest("test_with_prune"))
suite.addTest(BisectingUtilsPassTest("test_gen_cmd_script"))
suite.addTest(BisectingUtilsPassTest("test_no_pass_support"))
suite.addTest(BisectingUtilsPassTest("test_no_transform_support"))
suite.addTest(BisectingUtilsPassTest("test_pass_transform_bisect"))
suite.addTest(BisectingUtilsPassTest("test_result_not_reproduced_pass"))
suite.addTest(
BisectingUtilsPassTest("test_result_not_reproduced_transform")
)
suite.addTest(BisectTest("test_full_bisector"))
suite.addTest(BisectStressTest("test_every_obj_bad"))
suite.addTest(BisectStressTest("test_every_index_is_bad"))
runner = unittest.TextTestRunner()
runner.run(suite)
if __name__ == "__main__":
Main(sys.argv)