diff options
Diffstat (limited to 'tools/purify')
-rw-r--r-- | tools/purify/chrome_tests.py | 24 | ||||
-rw-r--r-- | tools/purify/common.py | 26 | ||||
-rw-r--r-- | tools/purify/purify_analyze.py | 48 | ||||
-rw-r--r-- | tools/purify/purify_coverage.py | 10 | ||||
-rw-r--r-- | tools/purify/purify_inuse.py | 8 | ||||
-rw-r--r-- | tools/purify/purify_message.py | 16 | ||||
-rw-r--r-- | tools/purify/purify_test.py | 10 | ||||
-rw-r--r-- | tools/purify/quantify_test.py | 12 |
8 files changed, 77 insertions, 77 deletions
diff --git a/tools/purify/chrome_tests.py b/tools/purify/chrome_tests.py index 6831fa06..a9ce16c 100644 --- a/tools/purify/chrome_tests.py +++ b/tools/purify/chrome_tests.py @@ -66,7 +66,7 @@ class ChromeTests: # On the buildbot, we archive to a specific location on chrome-web # with a directory based on the test name and the current svn revision. # NOTE: These modules are located in trunk/tools/buildbot, which is not - # in the default config. You'll need to check this out and add + # in the default config. You'll need to check this out and add # scripts/* to your PYTHONPATH to test outside of the buildbot. import slave_utils import chromium_config @@ -79,7 +79,7 @@ class ChromeTests: os.makedirs(self._report_dir) purify_test = os.path.join(script_dir, "purify_test.py") - self._command_preamble = ["python.exe", purify_test, "--echo_to_stdout", + self._command_preamble = ["python.exe", purify_test, "--echo_to_stdout", "--source_dir=%s" % (self._source_dir), "--save_cache"] @@ -131,7 +131,7 @@ class ChromeTests: ''' Runs the test specified by command-line argument --test ''' logging.info("running test %s" % (self._test)) return self._test_list[self._test]() - + def _ReadGtestFilterFile(self, name, cmd): '''Read a file which is a list of tests to filter out with --gtest_filter and append the command-line option to cmd. @@ -170,13 +170,13 @@ class ChromeTests: def ScriptedTest(self, module, exe, name, script, multi=False, cmd_args=None, out_dir_extra=None): - '''Purify a target exe, which will be executed one or more times via a + '''Purify a target exe, which will be executed one or more times via a script or driver program. Args: module - which top level component this test is from (webkit, base, etc.) exe - the name of the exe (it's assumed to exist in build_dir) name - the name of this test (used to name output files) - script - the driver program or script. If it's python.exe, we use + script - the driver program or script. If it's python.exe, we use search-path behavior to execute, otherwise we assume that it is in build_dir. multi - a boolean hint that the exe will be run multiple times, generating @@ -227,7 +227,7 @@ class ChromeTests: def TestIpc(self): return self.SimpleTest("chrome", "ipc_tests.exe") - + def TestNet(self): return self.SimpleTest("net", "net_unittests.exe") @@ -245,8 +245,8 @@ class ChromeTests: # runs a slice of the layout tests of size chunk_size that increments with # each run. Since tests can be added and removed from the layout tests at # any time, this is not going to give exact coverage, but it will allow us - # to continuously run small slices of the layout tests under purify rather - # than having to run all of them in one shot. + # to continuously run small slices of the layout tests under purify rather + # than having to run all of them in one shot. chunk_num = 0 # Tests currently seem to take about 20-30s each. chunk_size = 120 # so about 40-60 minutes per run @@ -265,7 +265,7 @@ class ChromeTests: chunk_num = 0 f.close() except IOError, (errno, strerror): - logging.error("error reading from file %s (%d, %s)" % (chunk_file, + logging.error("error reading from file %s (%d, %s)" % (chunk_file, errno, strerror)) script = os.path.join(self._source_dir, "webkit", "tools", "layout_tests", @@ -282,7 +282,7 @@ class ChromeTests: script_cmd.append("--test-list=%s" % self._args[0]) else: script_cmd.extend(self._args) - + if run_all: ret = self.ScriptedTest("webkit", "test_shell.exe", "layout", script_cmd, multi=True, cmd_args=["--timeout=0"]) @@ -323,7 +323,7 @@ class ChromeTests: instrumentation_error = self.InstrumentDll() if instrumentation_error: return instrumentation_error - return self.ScriptedTest("chrome", "chrome.exe", "ui_tests", + return self.ScriptedTest("chrome", "chrome.exe", "ui_tests", ["ui_tests.exe", "--single-process", "--ui-test-timeout=180000", @@ -351,7 +351,7 @@ def _main(argv): help="Don't force a re-instrumentation for ui_tests") parser.add_option("", "--run-singly", action="store_true", default=False, help="run tests independently of each other so that they " - "don't interfere with each other and so that errors " + "don't interfere with each other and so that errors " "can be accurately attributed to their source"); parser.add_option("", "--report_dir", help="path where report files are saved") diff --git a/tools/purify/common.py b/tools/purify/common.py index 9bd55ae..102c4af6 100644 --- a/tools/purify/common.py +++ b/tools/purify/common.py @@ -10,17 +10,17 @@ running of Rational Purify and Quantify in a consistent manner. """ # Purify and Quantify have a front-end (e.g. quantifyw.exe) which talks to a -# back-end engine (e.g. quantifye.exe). The back-end seems to handle -# instrumentation, while the front-end controls program execution and +# back-end engine (e.g. quantifye.exe). The back-end seems to handle +# instrumentation, while the front-end controls program execution and # measurement. The front-end will dynamically launch the back-end if -# instrumentation is needed (sometimes in the middle of a run if a dll is +# instrumentation is needed (sometimes in the middle of a run if a dll is # loaded dynamically). # In an ideal world, this script would simply execute the front-end and check # the output. However, purify is not the most reliable or well-documented app # on the planet, and my attempts to get it to run this way led to the back-end # engine hanging during instrumentation. The workaround to this was to run two -# passes, first running the engine to do instrumentation rather than letting -# the front-end do it for you, then running the front-end to actually do the +# passes, first running the engine to do instrumentation rather than letting +# the front-end do it for you, then running the front-end to actually do the # run. Each time through we're deleting all of the instrumented files in the # cache to ensure that we're testing that instrumentation works from scratch. # (although this can be changed with an option) @@ -60,7 +60,7 @@ def _print_line(line, flush=True): def RunSubprocess(proc, timeout=0, detach=False): """ Runs a subprocess, until it finishes or |timeout| is exceeded and the process is killed with taskkill. A |timeout| <= 0 means no timeout. - + Args: proc: list of process components (exe + args) timeout: how long to wait before killing, <= 0 means wait forever @@ -156,13 +156,13 @@ class Rational(object): common argument parsing as well as the general program flow of Instrument, Execute, Analyze. ''' - + def __init__(self): google.logging_utils.config_root() self._out_file = None def Run(self): - '''Call this to run through the whole process: + '''Call this to run through the whole process: Setup, Instrument, Execute, Analyze''' start = datetime.datetime.now() retcode = -1 @@ -208,7 +208,7 @@ class Rational(object): parser.add_option("-o", "--out_file", dest="out_file", metavar="OUTFILE", default="", help="output data is written to OUTFILE") - parser.add_option("-s", "--save_cache", + parser.add_option("-s", "--save_cache", dest="save_cache", action="store_true", default=False, help="don't delete instrumentation cache") parser.add_option("-c", "--cache_dir", dest="cache_dir", metavar="CACHEDIR", @@ -231,10 +231,10 @@ class Rational(object): if self.ParseArgv(): logging.info("instrumentation cache in %s" % self._cache_dir) logging.info("output saving to %s" % self._out_file) - # Ensure that Rational's common dir and cache dir are in the front of the + # Ensure that Rational's common dir and cache dir are in the front of the # path. The common dir is required for purify to run in any case, and # the cache_dir is required when using the /Replace=yes option. - os.environ["PATH"] = (COMMON_PATH + ";" + self._cache_dir + ";" + + os.environ["PATH"] = (COMMON_PATH + ";" + self._cache_dir + ";" + os.environ["PATH"]) # clear the cache to make sure we're starting clean self.__ClearInstrumentationCache() @@ -262,7 +262,7 @@ class Rational(object): return False def Execute(self, proc): - ''' Execute the app to be tested after successful instrumentation. + ''' Execute the app to be tested after successful instrumentation. Full execution command-line provided by subclassers via proc.''' logging.info("starting execution...") # note that self._args begins with the exe to be run @@ -330,5 +330,5 @@ class Rational(object): try: os.remove(file) except: - logging.warning("unable to delete file %s: %s" % (file, + logging.warning("unable to delete file %s: %s" % (file, sys.exc_info()[0])) diff --git a/tools/purify/purify_analyze.py b/tools/purify/purify_analyze.py index 70a3610..2c02b0c 100644 --- a/tools/purify/purify_analyze.py +++ b/tools/purify/purify_analyze.py @@ -70,7 +70,7 @@ class MemoryTreeNode(object): return root def __init__(self, function, bytes, blocks): - ''' + ''' Args: function: A string representing a unique method or function. bytes: initial number of bytes allocated in this node @@ -105,15 +105,15 @@ class MemoryTreeNode(object): (other._bytes, other._blocks, other._function)) def __str__(self): - return "(%d bytes, %d blocks, %d allocs) %s" % ( + return "(%d bytes, %d blocks, %d allocs) %s" % ( self._bytes, self._blocks, self._allocs, self._function) def PrintRecursive(self, padding="", byte_filter=0): '''Print the tree and all of its children recursively (depth-first). All nodes at a given level of the tree are sorted in descending order by size. - + Args: - padding: Printed at the front of the line. Each recursive call adds a + padding: Printed at the front of the line. Each recursive call adds a single space character. byte_filter: a number of bytes below which we'll prune the tree ''' @@ -174,7 +174,7 @@ class PurifyAnalyze: # A symbolic name for the run being analyzed, often the name of the # exe which was purified. self._name = name - + # The top of the source code tree of the code we're analyzing. # This prefix is stripped from all filenames in stacks for normalization. if source_dir: @@ -224,7 +224,7 @@ class PurifyAnalyze: return False # check ignore patterns against title and top-most visible stack frames - strings = [msg._title] + strings = [msg._title] err = msg.GetErrorStack() if err: line = err.GetTopVisibleStackLine().get('function', None) @@ -403,7 +403,7 @@ class PurifyAnalyze: # Purify output should never end with a real message if message: logging.error("Unexpected message at end of file %s" % file) - + return fatal_errors == 0 def GetMessageList(self, key): @@ -433,13 +433,13 @@ class PurifyAnalyze: count = 0 for msg in all: count += msg._count - self._PrintAndSave("%s(%s) unique:%d total:%d" % (self._name, + self._PrintAndSave("%s(%s) unique:%d total:%d" % (self._name, purify_message.GetMessageType(key), len(unique), count), file) if key not in ["MIU"]: ignore_file = "%s_%s_ignore.txt" % (self._name, key) ignore_hashes = self._MessageHashesFromFile(ignore_file) ignored = 0 - + groups = list.UniqueMessageGroups() group_keys = groups.keys() group_keys.sort(cmp=lambda x,y: len(groups[y]) - len(groups[x])) @@ -449,11 +449,11 @@ class PurifyAnalyze: ignored += len(groups[group]) - len(kept_msgs) groups[group] = kept_msgs if ignored: - self._PrintAndSave("%s(%s) ignored:%d" % (self._name, + self._PrintAndSave("%s(%s) ignored:%d" % (self._name, purify_message.GetMessageType(key), ignored), file) total = reduce(lambda x, y: x + len(groups[y]), group_keys, 0) if total: - self._PrintAndSave("%s(%s) group summary:" % (self._name, + self._PrintAndSave("%s(%s) group summary:" % (self._name, purify_message.GetMessageType(key)), file) self._PrintAndSave(" TOTAL: %d" % total, file) for group in group_keys: @@ -491,7 +491,7 @@ class PurifyAnalyze: for sublist in sublists: tree = MemoryTreeNode.CreateTree(sublist) trees.append(tree) - + # while the tree is a hierarchical assignment from the root/bottom of the # stack down, the summary is simply adding the total of the top-most # stack item from our code @@ -550,7 +550,7 @@ class PurifyAnalyze: sys.stderr.flush() sys.stdout.flush() logging.info("summary of Purify bugs:") - + # This is a specialized set of counters for unit tests, with some # unfortunate hard-coded knowledge. test_counts = {} @@ -596,7 +596,7 @@ class PurifyAnalyze: prog_args = prog.split(" ") arg_prefix = "--test-name=" test_name = "UNKNOWN" - for arg in prog_args: + for arg in prog_args: index = arg.find(arg_prefix) if index >= 0: test_name = arg[len(arg_prefix):] @@ -636,7 +636,7 @@ class PurifyAnalyze: def SaveStrings(self, string_list, key, fname_extra=""): '''Output a list of strings to a file in the report dir. ''' - out = os.path.join(self._report_dir, + out = os.path.join(self._report_dir, "%s_%s%s.txt" % (self._name, key, fname_extra)) logging.info("saving %s" % (out)) try: @@ -778,7 +778,7 @@ class PurifyAnalyze: # type of message which is used to generate filenames and descriptive # error messages type_name = "%s_%s" % (self._name, type) - + # open the baseline file to compare against baseline_file = "%s.txt" % type_name baseline_hashes = self._MessageHashesFromFile(baseline_file) @@ -795,7 +795,7 @@ class PurifyAnalyze: current_list = self.GetMessageList(type) if current_list: # Since we're looking at the list of unique messages, - # if the number of occurrances of a given unique message + # if the number of occurrances of a given unique message # changes, it won't show up as an error. current_messages = current_list.UniqueMessages() else: @@ -825,10 +825,10 @@ class PurifyAnalyze: len(type_errors), len(type_fixes))) if len(type_errors): - strs = [current_hashes[x].NormalizedStr(verbose=True) + strs = [current_hashes[x].NormalizedStr(verbose=True) for x in type_errors] logging.error("%d new '%s(%s)' errors found\n%s" % (len(type_errors), - purify_message.GetMessageType(type), type, + purify_message.GetMessageType(type), type, '\n'.join(strs))) strs = [current_hashes[x].NormalizedStr() for x in type_errors] self.SaveStrings(strs, type, "_NEW") @@ -838,7 +838,7 @@ class PurifyAnalyze: # we don't have access to the original message, so all we can do is log # the non-verbose normalized text logging.warning("%d new '%s(%s)' unexpected fixes found\n%s" % ( - len(type_fixes), purify_message.GetMessageType(type), + len(type_fixes), purify_message.GetMessageType(type), type, '\n'.join(type_fixes))) self.SaveStrings(type_fixes, type, "_FIXED") fixes += len(type_fixes) @@ -872,10 +872,10 @@ def _main(): parser = optparse.OptionParser("usage: %prog [options] <files to analyze>") parser.add_option("-b", "--baseline", action="store_true", default=False, help="save output to baseline files") - parser.add_option("-m", "--memory_in_use", + parser.add_option("-m", "--memory_in_use", action="store_true", default=False, help="print memory in use summary") - parser.add_option("", "--validate", + parser.add_option("", "--validate", action="store_true", default=False, help="validate results vs. baseline") parser.add_option("-e", "--echo_to_stdout", @@ -908,7 +908,7 @@ def _main(): google.logging_utils.config_root(level=logging.DEBUG) else: google.logging_utils.config_root(level=logging.INFO) - pa = PurifyAnalyze(filenames, options.echo_to_stdout, options.name, + pa = PurifyAnalyze(filenames, options.echo_to_stdout, options.name, options.source_dir, options.data_dir, options.report_dir) execute_crash = not pa.ReadFile() if options.bug_report: @@ -934,6 +934,6 @@ def _main(): sys.exit(retcode) if __name__ == "__main__": - _main() + _main() diff --git a/tools/purify/purify_coverage.py b/tools/purify/purify_coverage.py index e2fef1c..e88af50 100644 --- a/tools/purify/purify_coverage.py +++ b/tools/purify/purify_coverage.py @@ -43,16 +43,16 @@ class PurifyCoverage(common.Rational): self._name = os.path.basename(self._exe) # _out_file can be set in common.Rational.ParseArgv if not self._out_file: - self._out_file = os.path.join(self._latest_dir, + self._out_file = os.path.join(self._latest_dir, "%s_coverage.txt" % (self._name)) self._source_dir = self._options.source_dir return True return False - + def _PurifyCommand(self): - cmd = [common.PURIFYW_PATH, "/CacheDir=" + self._cache_dir, + cmd = [common.PURIFYW_PATH, "/CacheDir=" + self._cache_dir, "/ShowInstrumentationProgress=no", "/ShowLoadLibraryProgress=no", - "/AllocCallStackLength=30", "/Coverage", + "/AllocCallStackLength=30", "/Coverage", "/CoverageDefaultInstrumentationType=line"] return cmd @@ -62,7 +62,7 @@ class PurifyCoverage(common.Rational): cmd.append("/Run=no") cmd.append(os.path.abspath(self._exe)) return common.Rational.Instrument(self, cmd) - + def Execute(self): cmd = self._PurifyCommand() cmd.append("/SaveTextData=" + self._out_file) diff --git a/tools/purify/purify_inuse.py b/tools/purify/purify_inuse.py index ed708a7..12d13f2 100644 --- a/tools/purify/purify_inuse.py +++ b/tools/purify/purify_inuse.py @@ -50,9 +50,9 @@ class PurifyInUse(common.Rational): self._byte_filter = int(self._options.byte_filter) return True return False - + def _PurifyCommand(self): - cmd = [common.PURIFYW_PATH, "/CacheDir=" + self._cache_dir, + cmd = [common.PURIFYW_PATH, "/CacheDir=" + self._cache_dir, "/ShowInstrumentationProgress=no", "/ShowLoadLibraryProgress=no", "/AllocCallStackLength=30", "/ErrorCallStackLength=30", "/LeaksAtExit=no", "/InUseAtExit=yes"] @@ -64,7 +64,7 @@ class PurifyInUse(common.Rational): cmd.append("/Run=no") cmd.append(os.path.abspath(self._exe)) return common.Rational.Instrument(self, cmd) - + def Execute(self): cmd = self._PurifyCommand() cmd.append("/SaveTextData=" + self._out_file) @@ -74,7 +74,7 @@ class PurifyInUse(common.Rational): if not os.path.isfile(self._out_file): logging.info("no output file %s" % self._out_file) return -1 - pa = purify_analyze.PurifyAnalyze(self._out_file, False, + pa = purify_analyze.PurifyAnalyze(self._out_file, False, self._name, self._source_dir) if not pa.ReadFile(): logging.warning("inuse summary suspect due to fatal error during run") diff --git a/tools/purify/purify_message.py b/tools/purify/purify_message.py index 9ff107f..83ed039 100644 --- a/tools/purify/purify_message.py +++ b/tools/purify/purify_message.py @@ -117,7 +117,7 @@ class Stack: # if functions match the following, elide them from the stack pat_func_elide = (re.compile('^std::'), re.compile('^new\(')) # if files match the following, elide them from the stack - pat_file_elide = (re.compile('.*platformsdk_win2008.*'), + pat_file_elide = (re.compile('.*platformsdk_win2008.*'), re.compile('.*.(dll|DLL)$'), # bug 1069902 re.compile('webkit/pending/wtf/fastmalloc\.h'), @@ -162,7 +162,7 @@ class Stack: (e.g. group.subgroup.subsubgroup) ''' return self._group; - + def _ComputeStackLine(self, line): line = line.lstrip() m = Stack.pat_stack_line.match(line) @@ -249,7 +249,7 @@ class Stack: self._stack.append(stack_line) self._eliding = False self._all_external = False - + # when we reach one of the known common stack entry points, truncate # the stack to avoid printing overly redundant information if len(self._stack) > 1: @@ -313,7 +313,7 @@ class Stack: len_self = len(self._stack) len_other = len(other._stack) min_len = min(len_self, len_other) - # sort stacks from the bottom up + # sort stacks from the bottom up for i in range(-1, -(min_len + 1), -1): # compare file, then func, but omit line number ret = cmp((self._stack[i]['file'], self._stack[i]['function']), @@ -530,11 +530,11 @@ class MessageList: self._unique_messages = None self._sublists = None self._bytes = 0 - + def GetType(self): return self._type - def BeginNewSublist(self): + def BeginNewSublist(self): '''Some message types are logically grouped into sets of messages which should not be mixed in the same list. Specifically, Memory In Use (MIU), Memory Leak (MLK) and Potential Memory Leak (MPK) are generated in a set @@ -547,7 +547,7 @@ class MessageList: When the caller determines that one list of messages of a type has ended and a new list has begun, it calls BeginNewSublist() which takes the current set of messages, puts them into a new MessageList and puts that into the - sublists array. Later, when the caller needs to get at these messages, + sublists array. Later, when the caller needs to get at these messages, GetSublists() should be called. ''' if len(self._messages): @@ -584,7 +584,7 @@ class MessageList: return self._messages def UniqueMessages(self): - '''Returns an array of the unique normalized Message objects in this + '''Returns an array of the unique normalized Message objects in this MessageList. ''' # the list is lazily computed since we have to create a sorted list, diff --git a/tools/purify/purify_test.py b/tools/purify/purify_test.py index 1928df5..ae74738 100644 --- a/tools/purify/purify_test.py +++ b/tools/purify/purify_test.py @@ -6,9 +6,9 @@ # purify_test.py '''Runs an exe through Purify and verifies that Purify was -able to successfully instrument and run it. The original purpose was -to be able to identify when a change to our code breaks our ability to Purify -the app. This can happen with seemingly innocuous changes to code due to bugs +able to successfully instrument and run it. The original purpose was +to be able to identify when a change to our code breaks our ability to Purify +the app. This can happen with seemingly innocuous changes to code due to bugs in Purify, and is notoriously difficult to track down when it does happen. Perhaps more importantly in the long run, this can also automate detection of leaks and other memory bugs. It also may be useful to allow people to run @@ -117,7 +117,7 @@ class Purify(common.Rational): # seems to lose its mind, and we have a number of tests that use # much larger than the default of 5. "option -number-of-puts=30", - # With our large pdbs, purify's default timeout (30) isn't always + # With our large pdbs, purify's default timeout (30) isn't always # enough. If this isn't enough, -1 means no timeout. "option -server-comm-timeout=120", # check stack memory loads for UMRs, etc. @@ -224,7 +224,7 @@ class Purify(common.Rational): if self._instrument_only: return cmd = self._PurifyCommand() - # undo the /Replace=yes that was done in Instrument(), which means to + # undo the /Replace=yes that was done in Instrument(), which means to # remove the instrumented exe, and then rename exe.Original back to exe. cmd.append("/UndoReplace") cmd.append(os.path.abspath(self._exe)) diff --git a/tools/purify/quantify_test.py b/tools/purify/quantify_test.py index 29691578..0c8a700 100644 --- a/tools/purify/quantify_test.py +++ b/tools/purify/quantify_test.py @@ -5,9 +5,9 @@ # quantify_test.py -'''Runs an app through Quantify and verifies that Quantify was able to +'''Runs an app through Quantify and verifies that Quantify was able to successfully instrument and run it. The original purpose was to allow people -to run Quantify in a consistent manner without having to worry about broken +to run Quantify in a consistent manner without having to worry about broken PATHs, corrupt instrumentation, or other per-machine flakiness that Quantify is sometimes subject to. Unlike purify_test, the output from quantify_test is a binary file, which is much more useful in manual analysis. As such, this @@ -23,7 +23,7 @@ import common class Quantify(common.Rational): def __init__(self): common.Rational.__init__(self) - + def CreateOptionParser(self): common.Rational.CreateOptionParser(self) self._parser.description = __doc__ @@ -42,11 +42,11 @@ class Quantify(common.Rational): "/CacheDir=" + self._cache_dir, "-first-search-dir=" + self._exe_dir, self._exe] return common.Rational.Instrument(self, proc) - + def Execute(self): # TODO(erikkay): add an option to also do /SaveTextData and add an # Analyze method for automated analysis of that data. - proc = [common.QUANTIFYW_PATH, "/CacheDir=" + self._cache_dir, + proc = [common.QUANTIFYW_PATH, "/CacheDir=" + self._cache_dir, "/ShowInstrumentationProgress=no", "/ShowLoadLibraryProgress=no", "/SaveData=" + self._out_file] return common.Rational.Execute(self, proc) @@ -57,5 +57,5 @@ if __name__ == "__main__": if rational.Run(): retcode = 0 sys.exit(retcode) - + |