erc-c/tools/inspect-c

111 lines
3.7 KiB
Python
Executable File

#!/usr/bin/env python2
#
# This script is written to look through source code files, looking for
# missing unit-tests and doc-block comments. It's mostly ugly, but so is
# the task at hand.
import os
import re
# This is sort of a monster function, but I've tried to lift as much out
# of the logic as I could. Basically, this function tries to find
# anything that's "wrong", which is one of:
#
# - missing docblock comment for functions
# - missing test for a function
def parse(fname):
lines = []
with open(fname, 'r') as f:
lines = f.read().split("\n")
length = len(lines)
# We probably got a directory in the path (like './src'); let's chop
# it off.
basefname = os.path.basename(fname)
# The suite is the name of the test suite as Criterion would define
# it.
suite = suite_name_from_file(basefname)
for num in range(0, length):
match = find_func_name(lines[num])
if match is not None:
# The test name is a part of the function
test_name = test_name_from_func(suite, match.group(1))
# We want to find something in a test file for each of the
# functions we define.
if not has_test('tests/' + basefname, suite, test_name):
print "Missing test: %s_%s" % (suite, test_name)
# We also want to have some doc-block comment for every
# function we write.
if not has_doc_block(lines, num):
print "Missing docblock comment: %s:%d: %s" % (fname, num, lines[num])
# Given a source file name, extract the suite name (which is really just
# the name of the file minus the '.c' part, and -- well, you can read
# the source).
def suite_name_from_file(fname):
return fname.replace('.c', '').replace('.', '_')
# Given a line of source code, figure out if there's a function that's
# defined there. We define functions in the form of:
#
# <type>
# <func>(...)
# {
# <body...>
# }
#
# So we can say a function definition will always begin at the first
# character on a line in which the name is immediately followed by an
# open parenthesis.
def find_func_name(line):
return re.search(r'^([a-zA-Z_][a-zA-Z0-9_]*)\(', line)
# Given a suite and a function name, figure out what the test name
# should be.
def test_name_from_func(suite, func):
return func.replace(suite + '_', '')
# Given a line and a line number, determine if we have a doc-block or
# not. (Which we can determine because there should be a closing comment
# token on the line above the type.)
def has_doc_block(lines, linenum):
if lines[linenum].find('// ignore docblock') > -1:
return True
return linenum-2 >= 0 and (lines[linenum-2] == ' */' or lines[linenum-1] == ' */')
# Do we have a test for this function? The fname is the test file, and
# the suite and test are -- well, what they are.
def has_test(fname, suite, test):
# This is a bit of fakery, but inspect-c is not (yet) smart enough
# to know that a macro like `DEFINE_ADDR(xyz)` expands to
# `mos6502_resolve_xyz` and that the test it should associate is
# that. So we skip macro function definitions for now.
if test.isupper():
return True
data = ''
with open(fname, 'r') as f:
data = f.read()
return data.find('Test(%s, %s)' % (suite, test)) > -1
# Walk through a directory, looking for C source files and header files
# to examine.
def walk(dname):
for root, subdirs, subfiles in os.walk(dname):
for fname in subfiles:
if fname == 'main.c':
continue
if fname[-2:] != '.c' and fname[-2:] != '.h':
continue
parse(root + '/' + fname)
# Walk through the src dir and see what we can see.
walk('./src')
# vim:ft=python: