Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place. Commercial Alternative to JupyterHub.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place. Commercial Alternative to JupyterHub.
Path: blob/master/Tools/autotest/bisect-helper.py
Views: 1798
#!/usr/bin/env python312'''A helper script for bisecting common problems when working with ArduPilot34When running bisections, you should56export SITL_PANIC_EXIT=178Bisect between a commit which builds and one which doesn't,9finding the first commit which broke the build with a10specific failure:1112git bisect reset13git bisect good a7647e77d914git bisect bad 153ad9539866f8d93a99e9998118bb090d2f747f15cp -a Tools/autotest/bisect-helper.py /tmp16git bisect run /tmp/bisect-helper.py --build \17--build-failure-string= \18"reference to 'OpticalFlow' is ambiguous"1920Work out who killed bebop:21cp -a Tools/autotest/bisect-helper.py /tmp22git bisect reset23git bisect good a7647e77d9 &&24git bisect bad 153ad9539866f8d93a99e9998118bb090d2f747f &&25git bisect run /tmp/bisect-helper.py --build \26--waf-configure-arg="--board bebop"2728# Use a failing test to work out which commit broke things:29cp Tools/autotest/bisect-helper.py /tmp30git bisect reset31git bisect start32git bisect bad33git bisect good HEAD~102434time git bisect run /tmp/bisect-helper.py --autotest --autotest-vehicle=Plane --autotest-test=NeedEKFToArm --autotest-branch=wip/bisection-using-named-test # noqa3536Work out who overflowed Omnbusf4pro:37cp -a Tools Tools238GOOD=c4ce6fa3851f93df34393c376fee5b37e0a270d239BAD=f00bf77af75f828334f735580d6b19698b639a7440BFS="overflowed by"41git bisect reset42git bisect start43git bisect good $GOOD &&44git bisect bad $BAD &&45git bisect run Tools2/autotest/bisect-helper.py --build \46--waf-configure-arg="--board OmniBusF4Pro" \47--build-failure-string="$BFS"4849# Use a flapping test to work out which commit broke things. The50# "autotest-branch" is the branch containing the flapping test (which51# may be master)52rm /tmp/bisect-debug/*; git commit -m "stuff" -a ; cp Tools/autotest/bisect-helper.py /tmp; git bisect reset; git bisect start; git bisect bad d24e569b20; git bisect good 3f6fd49507f286ad8f6ccc9e29b110d5e9fc9207^53time git bisect run /tmp/bisect-helper.py --autotest --autotest-vehicle=Copter --autotest-test=Replay --autotest-branch=wip/bisection-using-flapping-test --autotest-test-passes=40 --autotest-failure-require-string="Mismatch in field XKF1.Pitch" --autotest-failure-ignore-string="HALSITL::SITL_State::_check_rc_input"5455AP_FLAKE8_CLEAN5657'''5859import optparse60import os61import subprocess62import shlex63import sys64import time65import traceback666768def get_exception_stacktrace(e):69if sys.version_info[0] >= 3:70ret = "%s\n" % e71ret += ''.join(traceback.format_exception(type(e),72value=e,73tb=e.__traceback__))74return ret75return traceback.format_exc(e)767778class Bisect(object):79def __init__(self, opts):80self.opts = opts8182def exit_skip_code(self):83return 1258485def exit_pass_code(self):86return 08788def exit_fail_code(self):89return 19091def exit_abort_code(self):92return 1299394def exit_skip(self):95self.progress("SKIP")96sys.exit(self.exit_skip_code())9798def exit_pass(self):99self.progress("PASS")100sys.exit(self.exit_pass_code())101102def exit_fail(self):103self.progress("FAIL")104sys.exit(self.exit_fail_code())105106def exit_abort(self):107'''call when this harness has failed (e.g. to reset to required108state)'''109self.progress("ABORT")110sys.exit(self.exit_abort_code())111112def progress(self, string):113'''pretty-print progress'''114print("BH: %s" % string)115116def run_program(self, prefix, cmd_list):117'''copied in from build_binaries.py'''118'''run cmd_list, spewing and setting output in self'''119self.progress("Running (%s)" % " ".join(cmd_list))120p = subprocess.Popen(cmd_list,121stdin=None,122close_fds=True,123stdout=subprocess.PIPE,124stderr=subprocess.STDOUT)125self.program_output = ""126while True:127x = p.stdout.readline()128if len(x) == 0:129waitpid_result = os.waitpid(p.pid, 0)130if waitpid_result:131break132# select not available on Windows... probably...133time.sleep(0.1)134continue135if isinstance(x, bytes):136x = x.decode('utf-8')137self.program_output += x138x = x.rstrip()139print("%s: %s" % (prefix, x))140(pid, status) = waitpid_result141if status != 0:142self.progress("Process failed (%s)" %143str(waitpid_result))144raise subprocess.CalledProcessError(145status, cmd_list)146147def build(self):148'''run ArduCopter build. May exit with skip or fail'''149self.run_program("WAF-clean", ["./waf", "clean"])150cmd_configure = ["./waf", "configure"]151pieces = [shlex.split(x)152for x in self.opts.waf_configure_args]153for piece in pieces:154cmd_configure.extend(piece)155self.run_program("WAF-configure", cmd_configure)156cmd_build = ["./waf", "build"]157pieces = [shlex.split(x)158for x in self.opts.waf_build_args]159for piece in pieces:160cmd_build.extend(piece)161try:162self.run_program("WAF-build", cmd_build)163except subprocess.CalledProcessError:164# well, it definitely failed....165if self.opts.build_failure_string is not None:166if self.opts.build_failure_string in self.program_output:167self.progress("Found relevant build failure")168self.exit_fail()169# it failed, but not for the reason we're looking170# for...171self.exit_skip()172else:173self.exit_fail()174175def update_submodules(self):176try:177self.run_program("Update submodules",178["git", "submodule", "update", "--init", "--recursive"])179except subprocess.CalledProcessError:180self.exit_abort()181182183class BisectBuild(Bisect):184185def __init__(self, opts):186super(BisectBuild, self).__init__(opts)187188def run(self):189if self.opts.build_failure_string is None:190self.progress("--build-failure-string is required when using --build")191self.exit_abort()192193self.update_submodules()194self.build() # may exit with skip or fail195self.exit_pass()196197198class BisectCITest(Bisect):199200def __init__(self, opts):201super(BisectCITest, self).__init__(opts)202203def autotest_script(self):204return os.path.join("Tools", "autotest", "autotest.py")205206def git_reset(self):207try:208self.run_program("Reset autotest directory", ["git", "reset", "--hard"])209except subprocess.CalledProcessError:210self.exit_abort()211212def get_current_hash(self):213self.run_program("Get current hash", ["git", "rev-parse", "HEAD"])214x = self.program_output215return x.strip()216217def run(self):218219current_hash = self.get_current_hash()220221self.debug_dir = os.path.join("/tmp", "bisect-debug")222if not os.path.exists(self.debug_dir):223os.mkdir(self.debug_dir)224225if self.opts.autotest_branch is None:226raise ValueError("expected autotest branch")227228self.update_submodules()229230try:231self.run_program("Check autotest directory out from master",232["git", "checkout", self.opts.autotest_branch, "Tools/autotest"])233except subprocess.CalledProcessError:234self.exit_abort()235236self.progress("Build")237cmd = [self.autotest_script()]238if self.opts.autotest_valgrind:239cmd.append("--debug")240cmd.append("build.%s" % self.opts.autotest_vehicle)241print("build cmd: %s" % str(cmd))242243try:244self.run_program("Run autotest (build)", cmd)245except subprocess.CalledProcessError:246self.git_reset()247self.exit_skip()248249cmd = [self.autotest_script()]250if self.opts.autotest_valgrind:251cmd.append("--valgrind")252cmd.append("test.%s.%s" % (self.opts.autotest_vehicle, self.opts.autotest_test))253254code = self.exit_pass_code()255for i in range(0, self.opts.autotest_test_passes):256ignore = False257try:258self.run_program(259"Run autotest (%u/%u)" % (i+1, self.opts.autotest_test_passes),260cmd)261except subprocess.CalledProcessError:262for ignore_string in self.opts.autotest_failure_ignore_string:263if ignore_string in self.program_output:264self.progress("Found ignore string (%s) in program output" % ignore_string)265ignore = True266if not ignore and self.opts.autotest_failure_require_string is not None:267if self.opts.autotest_failure_require_string not in self.program_output:268# it failed, but not for the reason we're looking269# for...270self.progress("Did not find test failure string (%s); skipping" %271self.opts.autotest_failure_require_string)272code = self.exit_skip_code()273break274if not ignore:275code = self.exit_fail_code()276277with open(os.path.join(self.debug_dir, "run-%s-%u.txt" % (current_hash, i+1)), "w") as f:278f.write(self.program_output)279280if code == self.exit_fail_code():281with open("/tmp/fail-counts", "a") as f:282f.write("Failed on run %u\n" % (i+1,))283if ignore:284self.progress("Ignoring this run")285continue286if code != self.exit_pass_code():287break288289self.git_reset()290291self.progress("Exit code is %u" % code)292293sys.exit(code)294295296if __name__ == '__main__':297298parser = optparse.OptionParser("bisect.py ")299parser.add_option("--build",300action='store_true',301default=False,302help="Help bisect a build failure")303parser.add_option("--build-failure-string",304type='string',305help="Must be present in"306"build output to count as a failure")307308group_autotest = optparse.OptionGroup(parser, "Run-AutoTest Options")309group_autotest.add_option("--autotest",310action='store_true',311default=False,312help="Bisect a failure with an autotest test")313group_autotest.add_option("", "--autotest-vehicle",314dest="autotest_vehicle",315type="string",316default="ArduCopter",317help="Which vehicle to run tests for")318group_autotest.add_option("", "--autotest-test",319dest="autotest_test",320type="string",321default="NavDelayAbsTime",322help="Test to run to find failure")323group_autotest.add_option("", "--autotest-valgrind",324dest="autotest_valgrind",325action='store_true',326default=False,327help="Run autotest under valgrind")328group_autotest.add_option("", "--autotest-test-passes",329dest="autotest_test_passes",330type=int,331default=1,332help="Number of times to run test before declaring it is good")333group_autotest.add_option("", "--autotest-branch",334dest="autotest_branch",335type="string",336help="Branch on which the test exists. The autotest directory will be reset to this branch")337group_autotest.add_option("--autotest-failure-require-string",338type='string',339default=None,340help="If supplied, must be present in"341"test output to count as a failure")342group_autotest.add_option("--autotest-failure-ignore-string",343type='string',344default=[],345action="append",346help="If supplied and present in"347"test output run will be ignored")348349group_build = optparse.OptionGroup(parser, "Build options")350group_build.add_option("", "--waf-configure-arg",351action="append",352dest="waf_configure_args",353type="string",354default=["--board skyviper-v2450"],355help="extra arguments to pass to"356"waf in configure step")357group_build.add_option("", "--waf-build-arg",358action="append",359dest="waf_build_args",360type="string",361default=["--target bin/arducopter"],362help="extra arguments to pass"363"to waf in its build step")364365parser.add_option_group(group_build)366367(opts, args) = parser.parse_args()368369if opts.build:370bisecter = BisectBuild(opts)371elif opts.autotest:372bisecter = BisectCITest(opts)373else:374raise ValueError("Not told how to bisect")375376try:377bisecter.run()378except Exception as e:379print("Caught exception in bisect-helper: %s" % str(e))380print(get_exception_stacktrace(e))381sys.exit(129) # should abort the bisect process382383384