blob: 5dd3cbd20e77e843489e0a6f627654fce716e416 [file] [log] [blame] [edit]
#!/usr/bin/env python3
#
# Copyright (C) 2024 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import argparse
import sys
from typing import List, Optional
from fetch_kokoro_prebuilts import check_valid_build, check_valid_path, fetch_prebuilts, get_build_number
import context
from llvm_android.utils import check_tools
def parse_args(sys_argv: Optional[List[str]]):
"""Parse the command line arguments."""
parser = argparse.ArgumentParser(description="Fetch prebuilts from kokoro.")
parser.add_argument("good", type=str, nargs=1, help="good SHA")
parser.add_argument("bad", type=str, nargs=1, help="bad SHA")
parser.add_argument(
"target",
type=str,
nargs=1,
help="Target Clang path (e.g. ANDROID_TOP/prebuilts/clang/linux-x86/)",
)
return parser.parse_args(sys_argv)
def get_result(num: str):
"""Testing the build manually and get the result from users."""
print(f"\nTesting build {num}...")
res = input("Is build " + num + " a good build? [y/n]:")
while res != "y" and res != "n":
res = input("Please enter y or n:")
return res
def bisect(start: int, end: int, target: str):
"""Find the build number which might be the root cause by bisection.
Users need to verify manually. If it is a good build, enter 'y'.
Otherwise, enter 'n'. After narrowing the range repeatedly, the
possible root cause will be found.
Args:
start: the build id which is a good build.
bad: the build id which is a bad build.
target: the target path to download the prebuilts.
Returns:
The build number combo that might be the culprit(s).
"""
if start >= end - 1:
return (end, end)
mid = (start + end) // 2
result = fetch_prebuilts(str(mid), target)
if result:
res = get_result(str(mid))
if res == "y":
return bisect(mid, end, target)
else:
return bisect(start, mid, target)
else:
# Mid point build is broken, find the neighbouring good build
left = mid - 1
while start < left:
result = fetch_prebuilts(str(left), target)
if result:
break
left = left - 1
right = mid + 1
while right < end:
result = fetch_prebuilts(str(right), target)
if result:
break
right = right + 1
if start == left:
if end != right:
res = get_result(str(right))
if res == "y":
return bisect(right, end, target)
return (left + 1, right)
else:
res = get_result(str(left))
if res == "y":
if end == right:
return (left + 1, end)
else:
res = get_result(str(right))
if res == "y":
return bisect(right, end, target)
else:
return (left + 1, right)
else:
return bisect(start, left, target)
def main(sys_argv: List[str]):
check_tools(True)
args_output = parse_args(sys_argv)
good = get_build_number(args_output.good[0])
bad = get_build_number(args_output.bad[0])
target = args_output.target[0]
check_valid_path(target)
check_valid_build(good)
check_valid_build(bad)
start = int(good)
end = int(bad)
if start >= end:
err_msg = (
f"{good} is not smaller than {bad}. Please pass a valid combo."
)
raise Exception(err_msg)
result = bisect(start, end, target)
if result[0] == result[1]:
print(f"The culprit is build {result[0]}.")
else:
print(
f"The culprit is in the range from build {result[0]} to"
f" {result[1]}."
)
if __name__ == "__main__":
main(sys.argv[1:])