1*760c253cSXin Li# Copyright 2020 The ChromiumOS Authors 2*760c253cSXin Li# Use of this source code is governed by a BSD-style license that can be 3*760c253cSXin Li# found in the LICENSE file. 4*760c253cSXin Li 5*760c253cSXin LiThis document is for future maintainers of the binary search/bisection tools. 6*760c253cSXin Li 7*760c253cSXin LiAuthors: 8*760c253cSXin Li * Original Tool: asharif@, llozano@, cmtice@ 9*760c253cSXin Li * Updates after May 2016: cburden@ 10*760c253cSXin Li * chromeos-toolchain@ 11*760c253cSXin Li 12*760c253cSXin LiThe following are good reference materials on how the tool works: 13*760c253cSXin Li * Ahmad's original presentation: 14*760c253cSXin Li https://goto.google.com/zxdfyi 15*760c253cSXin Li 16*760c253cSXin Li * Bisection tool update design doc: 17*760c253cSXin Li https://goto.google.com/zcwei 18*760c253cSXin Li 19*760c253cSXin Li * Bisection tool webpage: 20*760c253cSXin Li https://goto.google.com/ruwpyi 21*760c253cSXin Li 22*760c253cSXin Li * Compiler wrapper webpage: 23*760c253cSXin Li https://goto.google.com/xossn 24*760c253cSXin Li 25*760c253cSXin Li 26*760c253cSXin LiTESTING: 27*760c253cSXin LiAll unit tests live under the ./test directory. However, these tests 28*760c253cSXin Lispecifically test binary_search_state.py, binary_search_perforce.py, 29*760c253cSXin Lirun_bisect.py. 30*760c253cSXin Li 31*760c253cSXin LiThese unit tests will not test the specific logic for ChromeOS/Android 32*760c253cSXin Libisection. To test the ChromeOS/Android bisectors, use the common/hash_test.sh 33*760c253cSXin Litest. This is a simple test case that just checks the hashes of files on your 34*760c253cSXin Lifile system. This means you won't have to find a specific compiler error for 35*760c253cSXin Lithe bisector to triage in order to test each bisector. 36*760c253cSXin Li 37*760c253cSXin LiTODO: 38*760c253cSXin LiThe bisection tool (I believe) is in a fairly good state. So these are mostly 39*760c253cSXin Liwishlist items and things that could use some improvement. 40*760c253cSXin Li 41*760c253cSXin Li 1. Get rid of binary_search_perforce.py. This file is mostly legacy code and 42*760c253cSXin Li the majority of it isn't even used to bisect object files. The file was 43*760c253cSXin Li originally intended to bisect CLs, and binary_search_state.py just reused 44*760c253cSXin Li the binary searching logic from it. Maybe just extract the binary searching 45*760c253cSXin Li logic from binary_search_perforce.py and put it in its own module in 46*760c253cSXin Li cros_utils? 47*760c253cSXin Li 48*760c253cSXin Li 2. Cleanup unit tests in ./test. These tests are a little hacked together, 49*760c253cSXin Li and are all under one test suite. Maybe consider organizing them across 50*760c253cSXin Li multiple directories. 51*760c253cSXin Li 52*760c253cSXin Li 3. Create a "checkout setup" system for bisection. Currently if you want to 53*760c253cSXin Li bisect, you have to run scripts/edit sources in this repo. Ideally these 54*760c253cSXin Li scripts would be static, and if you wanted to bisect/make changes you would 55*760c253cSXin Li "checkout" or copy all the scripts to a working directory and have a unique 56*760c253cSXin Li working directory for each bisection. Credits to Luis for this idea =) 57*760c253cSXin Li 58*760c253cSXin Li 4. Make all scripts relative to each other. Currently all scripts enforce the 59*760c253cSXin Li idea that their cwd will be ./binary_search_tool/. But it would be less 60*760c253cSXin Li confusing to have each script relative to each other. There's quite a few 61*760c253cSXin Li stackoverflow topics on how to do this best, but each one has some sort of 62*760c253cSXin Li downside or flaw. 63*760c253cSXin Li 64*760c253cSXin Li 5. Overall modularize code more, especially in binary_search_state.py 65*760c253cSXin Li 66*760c253cSXin LiDESIGN EXPLANATIONS: 67*760c253cSXin LiSome of the design decisions are a bit difficult to understand from just reading 68*760c253cSXin Lithe code unfortunately. I will attempt to clear up the major offenders of this: 69*760c253cSXin Li 70*760c253cSXin Li 1. common.py's argument dictionary: 71*760c253cSXin Li binary_search_state.py and run_bisect.py both have to have near identical 72*760c253cSXin Li arguments in order to support argument overriding in run_bisect.py. However 73*760c253cSXin Li they do have to be slightly different. Mainly, run_bisect.py needs to have 74*760c253cSXin Li no default values for arguments (so it can determine what's being 75*760c253cSXin Li overriden). 76*760c253cSXin Li 77*760c253cSXin Li In order to reduce huge amounts of code duplication for the argument 78*760c253cSXin Li building, we put argument building in common.py. That way both modules 79*760c253cSXin Li can reference the arguments, and they can have different configurations 80*760c253cSXin Li across both. 81*760c253cSXin Li 82*760c253cSXin Li 2. Compiler wrapper: 83*760c253cSXin Li The compiler wrapper is called before all compiler calls. It exists to 84*760c253cSXin Li trick whatever build system (make, emerge, etc.) into thinking our 85*760c253cSXin Li bisection is just a normal build, when really we're doing some tricks. 86*760c253cSXin Li 87*760c253cSXin Li The biggest benefit the compiler wrapper gives is: knowing for sure which 88*760c253cSXin Li files are actually generated by the compiler during bisection setup, and 89*760c253cSXin Li potentially being able to skip compilations while triaging (speeding up the 90*760c253cSXin Li triaging process significantly). 91*760c253cSXin Li 92*760c253cSXin Li 3. The weird options for the --verify, --verbose, --file_args, etc. arguments: 93*760c253cSXin Li Some of the arguments for the bisection tool have a weird set of options 94*760c253cSXin Li for the AddArgument method (nargs, const, default, StrToBool). This is so 95*760c253cSXin Li we can make argument overriding workable. These options allow the following 96*760c253cSXin Li functionality for a boolean argument (using --prune as an example): 97*760c253cSXin Li * --prune (prune set to True) 98*760c253cSXin Li * <not given> (prune set to False) 99*760c253cSXin Li * --prune=True (prune set to True) 100*760c253cSXin Li * --prune=False (prune set to False) 101*760c253cSXin Li 102*760c253cSXin Li The first two are easy to implement (action='store_true'), but the last two 103*760c253cSXin Li are why the extra weird arguments are required. Now, why would we want the 104*760c253cSXin Li last two? Imagine if the Android bisector set --prune=True as a default 105*760c253cSXin Li argument. With just the first two options above it would be impossible for 106*760c253cSXin Li the user to override prune and set it to False. So the user needs the 107*760c253cSXin Li --prune=False option. See the argparse documentation for more details. 108*760c253cSXin Li 109*760c253cSXin Li 4. General binary searching logic/pruning logic: 110*760c253cSXin Li binary_search_state.py will enumerate all items into a list. The binary 111*760c253cSXin Li search will find the *first* bad item (starting with lowest index). 112*760c253cSXin Li Everything to the left of the "current" index is switched to good, 113*760c253cSXin Li everything to right of the "current" index is switched to bad. Once a bad 114*760c253cSXin Li item is found, it's put at the very end of the list. 115*760c253cSXin Li 116*760c253cSXin Li If prune is set, the tool will continuing searching until all bad items are 117*760c253cSXin Li found (instead of stopping after the first one). If the tool finds the same 118*760c253cSXin Li item twice, that means no more bad items exist. This is because the item 119*760c253cSXin Li was found, said item was put at the end of the list, and it was found 120*760c253cSXin Li again. Because the binary search logic finds the bad item with the lowest 121*760c253cSXin Li index, this means nothing in between the start of the list and the end of 122*760c253cSXin Li the list is bad (thus no more bad items remain). 123