#### Other Tools

```import math ```
``` ```
```import mozinfo ```
``` ```
``` ```
```class Bisect(object): ```
``` ```
``` "Class for creating, bisecting and summarizing for --bisect-chunk option." ```
``` ```
``` def __init__(self, harness): ```
``` super(Bisect, self).__init__() ```
``` self.summary = [] ```
``` self.contents = {} ```
``` self.repeat = 10 ```
``` self.failcount = 0 ```
``` self.max_failures = 3 ```
``` ```
``` def setup(self, tests): ```
``` """This method is used to initialize various variables that are required ```
``` for test bisection""" ```
``` status = 0 ```
``` self.contents.clear() ```
``` # We need totalTests key in contents for sanity check ```
``` self.contents["totalTests"] = tests ```
``` self.contents["tests"] = tests ```
``` self.contents["loop"] = 0 ```
``` return status ```
``` ```
``` def reset(self, expectedError, result): ```
``` """This method is used to initialize self.expectedError and self.result ```
``` for each loop in runtests.""" ```
``` self.expectedError = expectedError ```
``` self.result = result ```
``` ```
``` def get_tests_for_bisection(self, options, tests): ```
``` """Make a list of tests for bisection from a given list of tests""" ```
``` bisectlist = [] ```
``` for test in tests: ```
``` bisectlist.append(test) ```
``` if test.endswith(options.bisectChunk): ```
``` break ```
``` ```
``` return bisectlist ```
``` ```
``` def pre_test(self, options, tests, status): ```
``` """This method is used to call other methods for setting up variables and ```
``` getting the list of tests for bisection.""" ```
``` if options.bisectChunk == "default": ```
``` return tests ```
``` # The second condition in 'if' is required to verify that the failing ```
``` # test is the last one. ```
``` elif "loop" not in self.contents or not self.contents["tests"][-1].endswith( ```
``` options.bisectChunk ```
``` ): ```
``` tests = self.get_tests_for_bisection(options, tests) ```
``` status = self.setup(tests) ```
``` ```
``` return self.next_chunk_binary(options, status) ```
``` ```
``` def post_test(self, options, expectedError, result): ```
``` """This method is used to call other methods to summarize results and check whether a ```
``` sanity check is done or not.""" ```
``` self.reset(expectedError, result) ```
``` status = self.summarize_chunk(options) ```
``` # Check whether sanity check has to be done. Also it is necessary to check whether ```
``` # options.bisectChunk is present in self.expectedError as we do not want to run ```
``` # if it is "default". ```
``` if status == -1 and options.bisectChunk in self.expectedError: ```
``` # In case we have a debug build, we don't want to run a sanity ```
``` # check, will take too much time. ```
``` if mozinfo.info["debug"]: ```
``` return status ```
``` ```
``` testBleedThrough = self.contents["testsToRun"] ```
``` tests = self.contents["totalTests"] ```
``` tests.remove(testBleedThrough) ```
``` # To make sure that the failing test is dependent on some other ```
``` # test. ```
``` if options.bisectChunk in testBleedThrough: ```
``` return status ```
``` ```
``` status = self.setup(tests) ```
``` self.summary.append("Sanity Check:") ```
``` ```
``` return status ```
``` ```
``` def next_chunk_reverse(self, options, status): ```
``` "This method is used to bisect the tests in a reverse search fashion." ```
``` ```
``` # Base Cases. ```
``` if self.contents["loop"] <= 1: ```
``` self.contents["testsToRun"] = self.contents["tests"] ```
``` if self.contents["loop"] == 1: ```
``` self.contents["testsToRun"] = [self.contents["tests"][-1]] ```
``` self.contents["loop"] += 1 ```
``` return self.contents["testsToRun"] ```
``` ```
``` if "result" in self.contents: ```
``` if self.contents["result"] == "PASS": ```
``` chunkSize = self.contents["end"] - self.contents["start"] ```
``` self.contents["end"] = self.contents["start"] - 1 ```
``` self.contents["start"] = self.contents["end"] - chunkSize ```
``` ```
``` # self.contents['result'] will be expected error only if it fails. ```
``` elif self.contents["result"] == "FAIL": ```
``` self.contents["tests"] = self.contents["testsToRun"] ```
``` status = 1 # for initializing ```
``` ```
``` # initialize ```
``` if status: ```
``` totalTests = len(self.contents["tests"]) ```
``` chunkSize = int(math.ceil(totalTests / 10.0)) ```
``` self.contents["start"] = totalTests - chunkSize - 1 ```
``` self.contents["end"] = totalTests - 2 ```
``` ```
``` start = self.contents["start"] ```
``` end = self.contents["end"] + 1 ```
``` self.contents["testsToRun"] = self.contents["tests"][start:end] ```
``` self.contents["testsToRun"].append(self.contents["tests"][-1]) ```
``` self.contents["loop"] += 1 ```
``` ```
``` return self.contents["testsToRun"] ```
``` ```
``` def next_chunk_binary(self, options, status): ```
``` "This method is used to bisect the tests in a binary search fashion." ```
``` ```
``` # Base cases. ```
``` if self.contents["loop"] <= 1: ```
``` self.contents["testsToRun"] = self.contents["tests"] ```
``` if self.contents["loop"] == 1: ```
``` self.contents["testsToRun"] = [self.contents["tests"][-1]] ```
``` self.contents["loop"] += 1 ```
``` return self.contents["testsToRun"] ```
``` ```
``` # Initialize the contents dict. ```
``` if status: ```
``` totalTests = len(self.contents["tests"]) ```
``` self.contents["start"] = 0 ```
``` self.contents["end"] = totalTests - 2 ```
``` ```
``` # pylint --py3k W1619 ```
``` mid = (self.contents["start"] + self.contents["end"]) / 2 ```
``` if "result" in self.contents: ```
``` if self.contents["result"] == "PASS": ```
``` self.contents["end"] = mid ```
``` ```
``` elif self.contents["result"] == "FAIL": ```
``` self.contents["start"] = mid + 1 ```
``` ```
``` mid = (self.contents["start"] + self.contents["end"]) / 2 ```
``` start = mid + 1 ```
``` end = self.contents["end"] + 1 ```
``` self.contents["testsToRun"] = self.contents["tests"][start:end] ```
``` if not self.contents["testsToRun"]: ```
``` self.contents["testsToRun"].append(self.contents["tests"][mid]) ```
``` self.contents["testsToRun"].append(self.contents["tests"][-1]) ```
``` self.contents["loop"] += 1 ```
``` ```
``` return self.contents["testsToRun"] ```
``` ```
``` def summarize_chunk(self, options): ```
``` "This method is used summarize the results after the list of tests is run." ```
``` if options.bisectChunk == "default": ```
``` # if no expectedError that means all the tests have successfully ```
``` # passed. ```
``` if len(self.expectedError) == 0: ```
``` return -1 ```
``` options.bisectChunk = self.expectedError.keys() ```
``` self.summary.append("\tFound Error in test: %s" % options.bisectChunk) ```
``` return 0 ```
``` ```
``` # If options.bisectChunk is not in self.result then we need to move to ```
``` # the next run. ```
``` if options.bisectChunk not in self.result: ```
``` return -1 ```
``` ```
``` self.summary.append("\tPass %d:" % self.contents["loop"]) ```
``` if len(self.contents["testsToRun"]) > 1: ```
``` self.summary.append( ```
``` "\t\t%d test files(start,end,failing). [%s, %s, %s]" ```
``` % ( ```
``` len(self.contents["testsToRun"]), ```
``` self.contents["testsToRun"], ```
``` self.contents["testsToRun"][-2], ```
``` self.contents["testsToRun"][-1], ```
``` ) ```
``` ) ```
``` else: ```
``` self.summary.append("\t\t1 test file [%s]" % self.contents["testsToRun"]) ```
``` return self.check_for_intermittent(options) ```
``` ```
``` if self.result[options.bisectChunk] == "PASS": ```
``` self.summary.append("\t\tno failures found.") ```
``` if self.contents["loop"] == 1: ```
``` status = -1 ```
``` else: ```
``` self.contents["result"] = "PASS" ```
``` status = 0 ```
``` ```
``` elif self.result[options.bisectChunk] == "FAIL": ```
``` if "expectedError" not in self.contents: ```
``` self.summary.append("\t\t%s failed." % self.contents["testsToRun"][-1]) ```
``` self.contents["expectedError"] = self.expectedError[options.bisectChunk] ```
``` status = 0 ```
``` ```
``` elif ( ```
``` self.expectedError[options.bisectChunk] ```
``` == self.contents["expectedError"] ```
``` ): ```
``` self.summary.append( ```
``` "\t\t%s failed with expected error." ```
``` % self.contents["testsToRun"][-1] ```
``` ) ```
``` self.contents["result"] = "FAIL" ```
``` status = 0 ```
``` ```
``` # This code checks for test-bleedthrough. Should work for any ```
``` # algorithm. ```
``` numberOfTests = len(self.contents["testsToRun"]) ```
``` if numberOfTests < 3: ```
``` # This means that only 2 tests are run. Since the last test ```
``` # is the failing test itself therefore the bleedthrough ```
``` # test is the first test ```
``` self.summary.append( ```
``` "TEST-UNEXPECTED-FAIL | %s | Bleedthrough detected, this test is the " ```
``` "root cause for many of the above failures" ```
``` % self.contents["testsToRun"] ```
``` ) ```
``` status = -1 ```
``` else: ```
``` self.summary.append( ```
``` "\t\t%s failed with different error." ```
``` % self.contents["testsToRun"][-1] ```
``` ) ```
``` status = -1 ```
``` ```
``` return status ```
``` ```
``` def check_for_intermittent(self, options): ```
``` "This method is used to check whether a test is an intermittent." ```
``` if self.result[options.bisectChunk] == "PASS": ```
``` self.summary.append( ```
``` "\t\tThe test %s passed." % self.contents["testsToRun"] ```
``` ) ```
``` if self.repeat > 0: ```
``` # loop is set to 1 to again run the single test. ```
``` self.contents["loop"] = 1 ```
``` self.repeat -= 1 ```
``` return 0 ```
``` else: ```
``` if self.failcount > 0: ```
``` # -1 is being returned as the test is intermittent, so no need to bisect ```
``` # further. ```
``` return -1 ```
``` # If the test does not fail even once, then proceed to next chunk for bisection. ```
``` # loop is set to 2 to proceed on bisection. ```
``` self.contents["loop"] = 2 ```
``` return 1 ```
``` elif self.result[options.bisectChunk] == "FAIL": ```
``` self.summary.append( ```
``` "\t\tThe test %s failed." % self.contents["testsToRun"] ```
``` ) ```
``` self.failcount += 1 ```
``` self.contents["loop"] = 1 ```
``` self.repeat -= 1 ```
``` # self.max_failures is the maximum number of times a test is allowed ```
``` # to fail to be called an intermittent. If a test fails more than ```
``` # limit set, it is a perma-fail. ```
``` if self.failcount < self.max_failures: ```
``` if self.repeat == 0: ```
``` # -1 is being returned as the test is intermittent, so no need to bisect ```
``` # further. ```
``` return -1 ```
``` return 0 ```
``` else: ```
``` self.summary.append( ```
``` "TEST-UNEXPECTED-FAIL | %s | Bleedthrough detected, this test is the " ```
``` "root cause for many of the above failures" ```
``` % self.contents["testsToRun"] ```
``` ) ```
``` return -1 ```
``` ```
``` def print_summary(self): ```
``` "This method is used to print the recorded summary." ```
``` print("Bisection summary:") ```
``` for line in self.summary: ```
``` print(line) ```