practracker.py 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. #!/usr/bin/python3
  2. """
  3. Best-practices tracker for Tor source code.
  4. Go through the various .c files and collect metrics about them. If the metrics
  5. violate some of our best practices and they are not found in the optional
  6. exceptions file, then log a problem about them.
  7. We currently do metrics about file size, function size and number of includes.
  8. practracker.py should be run with its second argument pointing to the Tor
  9. top-level source directory like this:
  10. $ python3 ./scripts/maint/practracker/practracker.py .
  11. The exceptions file is meant to be initialized once with the current state of
  12. the source code and then get saved in the repository for ever after:
  13. $ python3 ./scripts/maint/practracker/practracker.py . > ./scripts/maint/practracker/exceptions.txt
  14. """
  15. import os, sys
  16. import metrics
  17. import util
  18. import problem
  19. # The filename of the exceptions file (it should be placed in the practracker directory)
  20. EXCEPTIONS_FNAME = "./exceptions.txt"
  21. # Recommended file size
  22. MAX_FILE_SIZE = 3000 # lines
  23. # Recommended function size
  24. MAX_FUNCTION_SIZE = 100 # lines
  25. # Recommended number of #includes
  26. MAX_INCLUDE_COUNT = 50
  27. #######################################################
  28. # ProblemVault singleton
  29. ProblemVault = None
  30. # The Tor source code topdir
  31. TOR_TOPDIR = None
  32. #######################################################
  33. def consider_file_size(fname, f):
  34. """Consider file size issues for 'f' and return True if a new issue was found"""
  35. file_size = metrics.get_file_len(f)
  36. if file_size > MAX_FILE_SIZE:
  37. p = problem.FileSizeProblem(fname, file_size)
  38. return ProblemVault.register_problem(p)
  39. return False
  40. def consider_includes(fname, f):
  41. """Consider #include issues for 'f' and return True if a new issue was found"""
  42. include_count = metrics.get_include_count(f)
  43. if include_count > MAX_INCLUDE_COUNT:
  44. p = problem.IncludeCountProblem(fname, include_count)
  45. return ProblemVault.register_problem(p)
  46. return False
  47. def consider_function_size(fname, f):
  48. """Consider the function sizes for 'f' and return True if a new issue was found"""
  49. found_new_issues = False
  50. for name, lines in metrics.get_function_lines(f):
  51. # Don't worry about functions within our limits
  52. if lines <= MAX_FUNCTION_SIZE:
  53. continue
  54. # That's a big function! Issue a problem!
  55. canonical_function_name = "%s:%s()" % (fname, name)
  56. p = problem.FunctionSizeProblem(canonical_function_name, lines)
  57. found_new_issues |= ProblemVault.register_problem(p)
  58. return found_new_issues
  59. #######################################################
  60. def consider_all_metrics(files_list):
  61. """Consider metrics for all files, and return True if new issues were found"""
  62. found_new_issues = False
  63. for fname in files_list:
  64. with open(fname, 'r') as f:
  65. found_new_issues |= consider_metrics_for_file(fname, f)
  66. return found_new_issues
  67. def consider_metrics_for_file(fname, f):
  68. """
  69. Consider the various metrics for file with filename 'fname' and file descriptor 'f'.
  70. Return True if we found new issues.
  71. """
  72. # Strip the useless part of the path
  73. if fname.startswith(TOR_TOPDIR):
  74. fname = fname[len(TOR_TOPDIR):]
  75. found_new_issues = False
  76. # Get file length
  77. found_new_issues |= consider_file_size(fname, f)
  78. # Consider number of #includes
  79. f.seek(0)
  80. found_new_issues |= consider_includes(fname, f)
  81. # Get function length
  82. f.seek(0)
  83. found_new_issues |= consider_function_size(fname, f)
  84. return found_new_issues
  85. def main():
  86. if (len(sys.argv) != 2):
  87. print("Usage:\n\t$ practracker.py <tor topdir>\n\t(e.g. $ practracker.py ~/tor/)")
  88. return
  89. global TOR_TOPDIR
  90. TOR_TOPDIR = sys.argv[1]
  91. exceptions_file = os.path.join(TOR_TOPDIR, "scripts/maint/practracker", EXCEPTIONS_FNAME)
  92. # 1) Get all the .c files we care about
  93. files_list = util.get_tor_c_files(TOR_TOPDIR)
  94. # 2) Initialize problem vault and load an optional exceptions file so that
  95. # we don't warn about the past
  96. global ProblemVault
  97. ProblemVault = problem.ProblemVault(exceptions_file)
  98. # 3) Go through all the files and report problems if they are not exceptions
  99. found_new_issues = consider_all_metrics(files_list)
  100. # If new issues were found, try to give out some advice to the developer on how to resolve it.
  101. if (found_new_issues):
  102. new_issues_str = "practracker FAILED as indicated by the problem lines above. Please use the exceptions file ({}) to find any previous state of these problems. If you are unable to fix the underlying best-practices issue right now then you need to either update the relevant exception line or add a new one.".format(exceptions_file)
  103. print(new_issues_str)
  104. sys.exit(found_new_issues)
  105. if __name__ == '__main__':
  106. main()