#!/usr/bin/env python # -*-mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*- """ Runs doctests locallly doctest files are in the tests/ directory. Note that when writing new test files, it will be convenient to use the command-line flags to avoid time-consuming reprovisioning or to target particular boxes or tests. """ from __future__ import print_function from sys import stderr import argparse import doctest import glob import re import subprocess import sys import os OPTIONS = doctest.ELLIPSIS | doctest.NORMALIZE_WHITESPACE # Convenience items for testing. # We'll pass these as globals to the doctests. if os.path.exists('/dev/null'): DEV_NULL = open('/dev/null', 'w') EXE='vagrant' else: DEV_NULL = open('NUL:', 'w') EXE='sh /i/bin/vagrant.msys' # find all our available boxes #with open('Vagrantfile', 'r') as f: # avail_boxes = re.findall(r'^\s+config.vm.define "(.+?)"', f.read(), re.MULTILINE) # unused because it could be a Ruby variable parser = argparse.ArgumentParser(description='Run playbook tests.') parser.add_argument( '-f', '--force', action='store_true', help="Force tests to proceed if box already exists. Do not destroy box at end of tests." ) parser.add_argument( '-n', '--no-provision', action='store_true', help="Skip provisioning." ) parser.add_argument( '-F', '--fail-fast', action='store_true', help="REPORT_ONLY_FIRST_FAILURE." ) parser.add_argument( '-o', '--options', help="" ) parser.add_argument( '--haltonfail', action='store_true', help="Stop multibox tests after a fail; leave box running." ) parser.add_argument( '--file', help="Specify a single doctest file (default tests/*.txt).", ) parser.add_argument( '--box', help="Specify a particular target box", action="append", ) args = parser.parse_args() if args.box: lBoxes = args.box else: # find all our available running boxes # sed -e 's/ .*//' try: s = os.system("vagrant global-status 2>&1| grep running | cut -f 1 -d ' ' ") except StandardError as e: print("ERROR: Unable to find any running boxes. Rerun with the --box argument.", file=sys.stderr) raise assert s, "ERROR: Unable to find a running box. Rerun with the --box argument." lBoxes = s.split(' ') # mplatform = None # def get_mplatform(): # global mplatform # # Linux-4.14.80-gentoo-x86_64-Intel-R-_Pentium-R-_CPU_N3700_@_1.60GHz-with-gentoo-2.2.1 # if mplatform is None: # mplatform = subprocess.check_output( # """vagrant ssh %s -c 'python -mplatform'""" % box, # shell=True, # stderr=DEV_NULL # ) # return mplatform print (repr(args)) def ssh_run(cmd): """ Run a command line in a vagrant box via vagrant ssh. Return the output. """ return subprocess.check_output( """%s ssh %s -c '%s'""" % (EXE, box, cmd), shell=True, stderr=DEV_NULL ).replace('^@', '') def run(cmd): """ Run a command in the host. Stop the tests with a useful message if it fails. """ if sys.platform.startswith('win'): p = subprocess.Popen( cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) else: p = subprocess.Popen( cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True ) stdout, stderr = p.communicate() if p.returncode != 0: print(stdout, file=sys.stderr) # Stop the doctest raise KeyboardInterrupt(stderr) return stdout def cut(y, column_nums, sort=False): """ returns a list of lines reduced to the chosen column_nums """ assert y and len(y) > 0, "Empty string passed to cut" # if hasattr(y,'encode'): s = y.encode('utf-8') else: s = y lines = s.splitlines() line_lists = [l.split() for l in lines if l] rez = ["\t".join([col[col_num] for col_num in column_nums if col_num < len(col)]) for col in line_lists] if sort: return sorted(rez) else: return rez def joined_cut(s, column_nums, sort=False): return "\n".join(cut(s, column_nums, sort)) for box in lBoxes: globs = { 'ssh_run': ssh_run, 'run': run, 'cut': cut, 'joined_cut': joined_cut, 'skip_provisioning': args.no_provision, 'no_provisioning': args.no_provision, 'forcing': args.force, 'box': box, } if args.fail_fast: OPTIONS = doctest.REPORT_ONLY_FIRST_FAILURE | OPTIONS if box and not args.force: output = subprocess.check_output("%s status %s" % (EXE, box,), shell=True) if re.search(r"%s\s+not created" % box, output) is None: print( "Vagrant box already exists. Destroy it or use '-f' to skip this test.", file=sys.stderr) print ("Use '-f' in combination with '-n' to skip provisioning.", file=sys.stderr) exit(1) if args.file is None: files = glob.glob('tests/*.txt') else: files = [args.file] for fn in files: print ( "%s / %s" % (box, fn) , file=sys.stderr) print( '*' * 50 ) print (box) print( '*' * 50 ) print (fn) print( '*' * 50 ) try: failure_count, test_count = doctest.testfile(fn, module_relative=False, optionflags=OPTIONS, globs=globs) except Exception as e: sys.stderr.write('\n'.join(sys.path) +'\n') raise if args.haltonfail and failure_count > 0: print ("Test failures occurred. Stopping tests and leaving vagrant box %s running." % box , file=sys.stderr) exit(1) # Clean up our vagrant box. if box and not args.force: print ( "Destroying %s" % box , file=sys.stderr) run("%s destroy %s -f" % (EXE, box,)) elif box: print ( "Vagrant box %s left running." % box, file=sys.stderr)