diff options
author | George Kadianakis <desnacked@riseup.net> | 2019-02-27 15:14:19 +0200 |
---|---|---|
committer | George Kadianakis <desnacked@riseup.net> | 2019-02-27 15:22:24 +0200 |
commit | 17dd3167494d69b7afcfdb21395aad135b322722 (patch) | |
tree | 6f06f7670171d1b1847ea45b69ca29d6a1785cb7 | |
parent | 39a10499326fc020a1ad05e9dd3341d7e21e8873 (diff) | |
download | tor-17dd3167494d69b7afcfdb21395aad135b322722.tar.gz tor-17dd3167494d69b7afcfdb21395aad135b322722.zip |
Initial commit of practracker.py .
-rw-r--r-- | scripts/maint/practracker/metrics.py | 31 | ||||
-rwxr-xr-x | scripts/maint/practracker/practracker.py | 119 | ||||
-rw-r--r-- | scripts/maint/practracker/util.py | 23 |
3 files changed, 173 insertions, 0 deletions
diff --git a/scripts/maint/practracker/metrics.py b/scripts/maint/practracker/metrics.py new file mode 100644 index 0000000000..43ec3f809d --- /dev/null +++ b/scripts/maint/practracker/metrics.py @@ -0,0 +1,31 @@ +#!/usr/bin/python + +import re + +def file_len(f): + """Get file length of file""" + for i, l in enumerate(f): + pass + return i + 1 + +def function_lines(f): + """ + Return iterator which iterates over functions and returns (function name, function lines) + """ + + # XXX Buggy! Doesn't work with MOCK_IMPL and ENABLE_GCC_WARNINGS + in_function = False + for lineno, line in enumerate(f): + if not in_function: + # find the start of a function + m = re.match(r'^([a-zA-Z_][a-zA-Z_0-9]*),?\(', line) + if m: + func_name = m.group(1) + func_start = lineno + in_function = True + else: + # Fund the end of a function + if line.startswith("}"): + n_lines = lineno - func_start + in_function = False + yield (func_name, n_lines) diff --git a/scripts/maint/practracker/practracker.py b/scripts/maint/practracker/practracker.py new file mode 100755 index 0000000000..c95c432c39 --- /dev/null +++ b/scripts/maint/practracker/practracker.py @@ -0,0 +1,119 @@ +#!/usr/bin/python + +""" +Tor code best-practices tracker + +Go through the various .c files and collect metrics about them. If the metrics +violate some of our best practices and they are not found in the optional +exceptions file ("./exceptions.txt"), then log a violation about them. + +The exceptions file is meant to be initialized with the current state of the +source code as follows: ./practracker.py > ./exceptions.txt + +We currently do metrics about file size, function size and number of includes. + +TODO: + - How is this tool supposed to be used? How should the exception file work? + How should the UI work? Does it need special exit codes? + - Fix the function_length function so that practracker_tests.py passes. +""" + +import os, sys + +import metrics +import util + +# We don't want to run metrics for unittests, automatically-generated C files, +# external libraries or git leftovers. +EXCLUDE_SOURCE_DIRS = ["/src/test/", "/src/trunnel/", "/src/ext/", "/.git/"] + +# Where the Tor source code is +TOR_TOPDIR = "../../../" +# An optional exceptions_file +EXCEPTIONS_FILE = "./exceptions.txt" + +# Recommended file size +MAX_FILE_SIZE = 3000 # lines +# Recommended function size +MAX_FUNCTION_SIZE = 100 # lines +# Recommended number of #includes +MAX_INCLUDE_COUNT = 50 + +####################################################### + +def print_violation_if_not_exception(violation_str, exceptions_str): + # Check if this violation is already in the optional exceptions file + if exceptions_str and violation_str in exceptions_str: + return + + print violation_str + +####################################################### + +def consider_file_size(fname, f, exceptions_str): + file_size = metrics.file_len(f) + if file_size > MAX_FILE_SIZE: + violation_str = "violation file-size %s %d" % (fname, file_size) + print_violation_if_not_exception(violation_str, exceptions_str) + +def consider_includes(fname, f, exceptions_str): + include_count = 0 + for _, line in enumerate(f): + if line.startswith("#include "): + include_count += 1 + + if include_count > MAX_INCLUDE_COUNT: + violation_str = "violation include-count %s %d" % (fname, include_count) + print_violation_if_not_exception(violation_str, exceptions_str) + +def consider_function_size(fname, f, exceptions_str): + for name, lines in metrics.function_lines(f): + # Don't worry about functions within our limits + if lines <= MAX_FUNCTION_SIZE: + continue + + # That's a big function! Issue a violation! + canonical_function_name = "%s:%s()" % (fname,name) + violation_str = "violation function-size %s %s" % (lines, canonical_function_name) + print_violation_if_not_exception(violation_str, exceptions_str) + +####################################################### + +def consider_all_metrics(files_list, exceptions_str): + """Consider metrics for all files""" + for fname in files_list: + with open(fname, 'r') as f: + consider_metrics_for_file(fname, f, exceptions_str) + +def consider_metrics_for_file(fname, f, exceptions_str): + """ + Get metrics for file with filename 'fname' and file descriptor 'f'. + """ + # Get file length + consider_file_size(fname, f, exceptions_str) + + # Consider number of #includes + f.seek(0) + consider_includes(fname, f, exceptions_str) + + # Get function length + f.seek(0) + consider_function_size(fname, f, exceptions_str) + +def main(): + # 1) Get all the .c files we care about + files_list = util.get_tor_c_files(TOR_TOPDIR, EXCLUDE_SOURCE_DIRS) + + # 2) Read an optional exceptions file so that we don't warn about the past + exceptions_str = None + try: + with open(EXCEPTIONS_FILE, 'r') as exception_f: + exceptions_str = exception_f.read() + except IOError: + print "No exception file provided" + + # 3) Go through all the files and report violations if they are not exceptions + consider_all_metrics(files_list, exceptions_str) + +if __name__ == '__main__': + main() diff --git a/scripts/maint/practracker/util.py b/scripts/maint/practracker/util.py new file mode 100644 index 0000000000..8e9d95ece4 --- /dev/null +++ b/scripts/maint/practracker/util.py @@ -0,0 +1,23 @@ +import os + +def get_tor_c_files(tor_topdir, exclude_dirs): + """ + Return a list with the .c filenames we want to get metrics of. + """ + files_list = [] + + for root, directories, filenames in os.walk(tor_topdir): + for filename in filenames: + # We only care about .c files + if not filename.endswith(".c"): + continue + + # Exclude the excluded paths + full_path = os.path.join(root,filename) + if any(exclude_dir in full_path for exclude_dir in exclude_dirs): + continue + + files_list.append(full_path) + + return files_list + |