diff --git a/setup.py b/setup.py index b9143245..ea560959 100644 --- a/setup.py +++ b/setup.py @@ -115,6 +115,7 @@ setup( 'deb': helpers.DebCommand, 'test': helpers.TestCommand, 'debug': helpers.DebugCommand, + 'mergetest': helpers.MergeTestCommand, 'style': helpers.StyleCommand}, packages=allPackages, package_dir={'pyqtgraph.examples': 'examples'}, ## install examples along with the rest of the source diff --git a/tools/setupHelpers.py b/tools/setupHelpers.py index 16defeaa..3cf0122a 100644 --- a/tools/setupHelpers.py +++ b/tools/setupHelpers.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- import os, sys, re try: - from subprocess import check_output + from subprocess import check_output, check_call except ImportError: import subprocess as sp def check_output(*args, **kwds): @@ -16,6 +16,11 @@ except ImportError: raise ex return output +# Maximum allowed repository size difference (in kB) following merge. +# This is used to prevent large files from being inappropriately added to +# the repository history. +MERGE_SIZE_LIMIT = 100 + # Paths that are checked for style by flake and flake_diff FLAKE_CHECK_PATHS = ['pyqtgraph', 'examples', 'tools'] @@ -150,6 +155,26 @@ def checkStyle(): ret = proc.wait() printFlakeOutput(output) + # Check for DOS newlines + print('check line endings in all files...') + count = 0 + allowedEndings = set([None, '\n']) + for path, dirs, files in os.walk('.'): + for f in files: + if os.path.splitext(f)[1] not in ('.py', '.rst'): + continue + filename = os.path.join(path, f) + fh = open(filename, 'U') + x = fh.readlines() + endings = set(fh.newlines if isinstance(fh.newlines, tuple) else (fh.newlines,)) + endings -= allowedEndings + if len(endings) > 0: + print("\033[0;31m" + "File has invalid line endings: %s" % filename + "\033[0m") + ret = ret | 2 + count += 1 + print('checked line endings in %d files' % count) + + # Next check new code with optional error codes print('flake8: check new code against recommended error set...') diff = subprocess.check_output(['git', 'diff']) @@ -163,11 +188,10 @@ def checkStyle(): ret |= printFlakeOutput(output) if ret == 0: - print('flake8 test passed.') + print('style test passed.') else: - print('flake8 test failed: %d' % ret) - sys.exit(ret) - + print('style test failed: %d' % ret) + return ret def printFlakeOutput(text): """ Print flake output, colored by error category. @@ -203,6 +227,10 @@ def printFlakeOutput(text): def unitTests(): + """ + Run all unit tests (using py.test) + Return the exit code. + """ try: if sys.version[0] == '3': out = check_output('PYTHONPATH=. py.test-3', shell=True) @@ -215,6 +243,82 @@ def unitTests(): print(out.decode('utf-8')) return ret + +def checkMergeSize(sourceBranch=None, targetBranch='develop', sourceRepo=None, targetRepo=None): + """ + Check that a git merge would not increase the repository size by MERGE_SIZE_LIMIT. + """ + if sourceBranch is None: + sourceBranch = getGitBranch() + if sourceRepo is None: + sourceRepo = '..' + if targetRepo is None: + targetRepo = '..' + + workingDir = '__merge-test-clone' + env = dict(TARGET_BRANCH=targetBranch, + SOURCE_BRANCH=sourceBranch, + TARGET_REPO=targetRepo, + SOURCE_REPO=sourceRepo, + WORKING_DIR=workingDir, + ) + + print("Testing merge size difference:\n" + " SOURCE: {SOURCE_REPO} {SOURCE_BRANCH}\n" + " TARGET: {TARGET_BRANCH} {TARGET_REPO}".format(**env)) + + setup = """ + mkdir {WORKING_DIR} && cd {WORKING_DIR} && + git init && git remote add -t {TARGET_BRANCH} target {TARGET_REPO} && + git fetch target {TARGET_BRANCH} && + git checkout -qf target/{TARGET_BRANCH} && + git gc -q --aggressive + """.format(**env) + + checkSize = """ + cd {WORKING_DIR} && + du -s . | sed -e "s/\t.*//" + """.format(**env) + + merge = """ + cd {WORKING_DIR} && + git pull -q {SOURCE_REPO} {SOURCE_BRANCH} && + git gc -q --aggressive + """.format(**env) + + try: + print("Check out target branch:\n" + setup) + check_call(setup, shell=True) + targetSize = int(check_output(checkSize, shell=True)) + print("TARGET SIZE: %d kB" % targetSize) + print("Merge source branch:\n" + merge) + check_call(merge, shell=True) + mergeSize = int(check_output(checkSize, shell=True)) + print("MERGE SIZE: %d kB" % mergeSize) + + diff = mergeSize - targetSize + if diff <= MERGE_SIZE_LIMIT: + print("DIFFERENCE: %d kB [OK]" % diff) + return 0 + else: + print("\033[0;31m" + "DIFFERENCE: %d kB [exceeds %d kB]" % (diff, MERGE_SIZE_LIMIT) + "\033[0m") + return 2 + finally: + if os.path.isdir(workingDir): + shutil.rmtree(workingDir) + + +def mergeTests(): + ret = checkMergeSize() + ret |= unitTests() + ret |= checkStyle() + if ret == 0: + print("\033[0;32m" + "\nAll merge tests passed." + "\033[0m") + else: + print("\033[0;31m" + "\nMerge tests failed." + "\033[0m") + return ret + + def listAllPackages(pkgroot): path = os.getcwd() n = len(path.split(os.path.sep)) @@ -415,7 +519,6 @@ class TestCommand(Command): def run(self): sys.exit(unitTests()) - def initialize_options(self): pass @@ -435,4 +538,20 @@ class StyleCommand(Command): def finalize_options(self): pass + +class MergeTestCommand(Command): + description = "Run all tests needed to determine whether the current code is suitable for merge." + user_options = [] + + def run(self): + sys.exit(mergeTests()) + + def initialize_options(self): + pass + + def finalize_options(self): + pass + + + \ No newline at end of file