diff --git a/quickunit/filechecker.py b/quickunit/filechecker.py new file mode 100644 index 0000000..b1e54d6 --- /dev/null +++ b/quickunit/filechecker.py @@ -0,0 +1,69 @@ +import os.path +import re + +from quickunit.utils import clean_bytecode_extension + + +class FileChecker(dict): + def __init__(self, rules, root=None): + self.rules = rules + self.changed_files = set() + self.root = root + self.compiled = False + dict.__init__(self) + + def __missing__(self, filepath): + self[filepath] = self.check(filepath) + return self[filepath] + + def compile(self): + self.compiled = True + + if self.root is None: + try: + filepath = self.changed_files.pop() + except IndexError: + self.root = '' + else: + self.changed_files.add(filepath) + self.root = os.path.abspath(filepath)[:-len(filepath)] + + rules = [] + for filepath in self.changed_files: + try: + path, filename = filepath.rsplit('/', 1) + except ValueError: + path, filename = '', filepath + + basename = filename.rsplit('.', 1)[0] + + params = { + 'path': path, + 'filename': filename, + 'basename': basename, + } + for rule in self.rules: + rules.append(re.compile(rule.format(**params))) + self.rules = rules + + def add(self, filepath): + if self.compiled: + raise Exception('The rules for this FileChecker are already compiled and files can no longer be added') + self.changed_files.add(filepath) + + def check(self, filepath): + filepath = clean_bytecode_extension(filepath) + + # check if this test was modified (e.g. added/changed) + if self.root and filepath.startswith(self.root): + filepath = filepath[len(self.root):] + + # if the filepath is actually the changed file + if filepath in self.changed_files: + return None + + for rule in self.rules: + if rule.search(filepath): + return None + + return False diff --git a/quickunit/plugin.py b/quickunit/plugin.py index 85710f7..8560a79 100644 --- a/quickunit/plugin.py +++ b/quickunit/plugin.py @@ -9,84 +9,13 @@ from __future__ import absolute_import import inspect -import logging -import os -import re import sys -from collections import namedtuple from nose.plugins.base import Plugin -from subprocess import Popen, PIPE, STDOUT -from quickunit.diff import DiffParser -from quickunit.utils import is_py_script, clean_bytecode_extension - - -ChangedFile = namedtuple('ChangedFile', ['filename', 'is_new']) - -class FileChecker(dict): - def __init__(self, rules, root=None): - self.rules = rules - self.changed_files = set() - self.root = root - self.compiled = False - dict.__init__(self) - - def __missing__(self, filepath): - self[filepath] = self.check(filepath) - return self[filepath] - - def compile(self): - self.compiled = True - - if self.root is None: - try: - filepath = self.changed_files.pop() - except IndexError: - self.root = '' - else: - self.changed_files.add(filepath) - self.root = os.path.abspath(filepath)[:-len(filepath)] - - rules = [] - for filepath in self.changed_files: - try: - path, filename = filepath.rsplit('/', 1) - except ValueError: - path, filename = '', filepath - - basename = filename.rsplit('.', 1)[0] - - params = { - 'path': path, - 'filename': filename, - 'basename': basename, - } - for rule in self.rules: - rules.append(re.compile(rule.format(**params))) - self.rules = rules - - def add(self, filepath): - if self.compiled: - raise Exception('The rules for this FileChecker are already compiled and files can no longer be added') - self.changed_files.add(filepath) - - def check(self, filepath): - filepath = clean_bytecode_extension(filepath) - - # check if this test was modified (e.g. added/changed) - if self.root and filepath.startswith(self.root): - filepath = filepath[len(self.root):] - - # if the filepath is actually the changed file - if filepath in self.changed_files: - return None - - for rule in self.rules: - if rule.search(filepath): - return None - - return False +from quickunit.filechecker import FileChecker +from quickunit.utils import is_py_script +from quickunit.vcs import git class QuickUnitPlugin(Plugin): @@ -112,8 +41,8 @@ def _get_name_from_test(self, test): # proper coverage test_ = getattr(sys.modules[test.__module__], test.__class__.__name__) - test_name = '%s:%s.%s' % (test_.__module__, test_.__name__, - test_method_name) + test_name = '%s:%s.%s' % ( + test_.__module__, test_.__name__, test_method_name) return test_name @@ -137,61 +66,23 @@ def configure(self, options, config): root = options.quickunit_root - self.logger = logging.getLogger(__name__) - self.verbosity = options.verbosity self.parent_branch = options.quickunit_parent_branch # files which were changed as part of the diff self.changed_files = set() # store a list of filenames that should be accepted self.file_checker = FileChecker(rules, root) - def parse_git_commit(self): - parent_branch = self.parent_branch or 'master' - proc = Popen(['git', 'merge-base', 'HEAD', parent_branch], stdout=PIPE, stderr=STDOUT) - - parent_revision = proc.stdout.read().strip() - - if self.verbosity > 1: - self.logger.info("Parent commit identified as %s", parent_revision) - - # pull in our diff - # git diff `git merge-base HEAD master` - proc = Popen(['git', 'diff', parent_revision], stdout=PIPE, stderr=STDOUT) - diff = proc.stdout.read().strip() - - parser = DiffParser(diff) - - files = [] - for file in parser.parse(): - if file['is_header']: - continue - - # file was removed - if file['new_filename'] == '/dev/null': - continue - - filename = file['new_filename'][2:] + def begin(self): + file_list = git.parse_commit(parent=self.parent_branch) + for c_file in file_list: # Ignore non python files - if not is_py_script(filename): + if not is_py_script(c_file.filename): continue - is_new = (file['old_filename'] == '/dev/null') - - files.append(ChangedFile(filename, is_new)) - - return files - - def begin(self): - file_list = self.parse_git_commit() - - for c_file in file_list: self.file_checker.add(c_file.filename) self.file_checker.compile() - if self.verbosity > 1: - self.logger.info("Found %d changed file(s)", file_list) - def wantMethod(self, method): # only works with unittest compatible functions currently method = getattr(sys.modules[method.im_class.__module__], method.im_class.__name__) diff --git a/quickunit/vcs/__init__.py b/quickunit/vcs/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/quickunit/vcs/base.py b/quickunit/vcs/base.py new file mode 100644 index 0000000..c71a4d2 --- /dev/null +++ b/quickunit/vcs/base.py @@ -0,0 +1,4 @@ +from collections import namedtuple + +ChangedFile = namedtuple('ChangedFile', ['filename', 'is_new']) + diff --git a/quickunit/vcs/git.py b/quickunit/vcs/git.py new file mode 100644 index 0000000..e2d0143 --- /dev/null +++ b/quickunit/vcs/git.py @@ -0,0 +1,37 @@ +from subprocess import Popen, PIPE, STDOUT + +from quickunit.diff import DiffParser +from quickunit.vcs.base import ChangedFile + + +def parse_commit(parent=None): + if parent is None: + parent = 'master' + + proc = Popen(['git', 'merge-base', 'HEAD', parent], stdout=PIPE, stderr=STDOUT) + + parent_revision = proc.stdout.read().strip() + + # pull in our diff + # git diff `git merge-base HEAD master` + proc = Popen(['git', 'diff', parent_revision], stdout=PIPE, stderr=STDOUT) + diff = proc.stdout.read().strip() + + parser = DiffParser(diff) + + files = [] + for file in parser.parse(): + if file['is_header']: + continue + + # file was removed + if file['new_filename'] == '/dev/null': + continue + + filename = file['new_filename'][2:] + + is_new = (file['old_filename'] == '/dev/null') + + files.append(ChangedFile(filename, is_new)) + + return files diff --git a/setup.py b/setup.py index 9c42bba..7688322 100644 --- a/setup.py +++ b/setup.py @@ -26,7 +26,9 @@ }, license='Apache License 2.0', tests_require=tests_require, - extras_require={'tests': tests_require}, + extras_require={ + 'tests': tests_require, + }, include_package_data=True, classifiers=[ 'Intended Audience :: Developers',