mirror of
https://github.com/GothenburgBitFactory/taskwarrior.git
synced 2025-06-26 10:54:26 +02:00
Test: run_all is now in Python and defaults to parallelizing tests
In addition to the conversion to Python, run_all now defaults to running all Python tests in parallel, using the same approach previously available via '--fast'. If desired one can force all tests to run serially by calling run_all with --serial A debugging flag was now also included in run_all. Pass one or more -l (-l, -ll or -lll) for different levels of debugging information.
This commit is contained in:
parent
50fa772ce1
commit
03847ab8ba
5 changed files with 210 additions and 213 deletions
5
test/.gitignore
vendored
5
test/.gitignore
vendored
|
@ -3,9 +3,6 @@
|
||||||
*.data
|
*.data
|
||||||
*.log
|
*.log
|
||||||
*.runlog
|
*.runlog
|
||||||
_run_all_parallel.txt
|
|
||||||
_run_all_serial.txt
|
|
||||||
_run_all_parallel_rc1
|
|
||||||
autocomplete.t
|
autocomplete.t
|
||||||
color.t
|
color.t
|
||||||
config.t
|
config.t
|
||||||
|
@ -56,5 +53,3 @@ variant_xor.t
|
||||||
view.t
|
view.t
|
||||||
|
|
||||||
json_test
|
json_test
|
||||||
|
|
||||||
run_all
|
|
||||||
|
|
|
@ -33,8 +33,6 @@ endif (CYGWIN)
|
||||||
|
|
||||||
endif (${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_BINARY_DIR})
|
endif (${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_BINARY_DIR})
|
||||||
|
|
||||||
configure_file (run_all.in run_all)
|
|
||||||
|
|
||||||
add_custom_target (test ./run_all --verbose
|
add_custom_target (test ./run_all --verbose
|
||||||
DEPENDS ${test_SRCS} task_executable
|
DEPENDS ${test_SRCS} task_executable
|
||||||
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/test)
|
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/test)
|
||||||
|
|
|
@ -22,9 +22,10 @@ failing tests.
|
||||||
|
|
||||||
Any TAP harness may be used.
|
Any TAP harness may be used.
|
||||||
|
|
||||||
Note that adding the '--fast' option to ./run_all, the Python and C++ tests all
|
Note that adding the '--serial' option to ./run_all, all tests are executed serially.
|
||||||
run in parallel, alongside the Perl test that run serially. The result is a much
|
The default runs Python and C++ tests in parallel, alongside the Perl tests
|
||||||
quicker test run.
|
that run serially (due to isolation limitations).
|
||||||
|
Using '--serial' will make for a slower test run.
|
||||||
|
|
||||||
|
|
||||||
Architecture
|
Architecture
|
||||||
|
|
206
test/run_all
Executable file
206
test/run_all
Executable file
|
@ -0,0 +1,206 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import glob
|
||||||
|
import argparse
|
||||||
|
import logging
|
||||||
|
import time
|
||||||
|
from multiprocessing import cpu_count
|
||||||
|
from Queue import Queue, Empty
|
||||||
|
from threading import Thread
|
||||||
|
from subprocess import call, Popen, PIPE
|
||||||
|
|
||||||
|
# Look for taskd in $PATH instead of task/src/
|
||||||
|
os.environ["TASKD_USE_PATH"] = "1"
|
||||||
|
|
||||||
|
TIMEOUT = .2
|
||||||
|
|
||||||
|
|
||||||
|
def run_test(testqueue, outqueue):
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
test = testqueue.get(block=True, timeout=TIMEOUT)
|
||||||
|
except Empty:
|
||||||
|
break
|
||||||
|
|
||||||
|
log.info("Running test %s", test)
|
||||||
|
|
||||||
|
try:
|
||||||
|
p = Popen(os.path.abspath(test), stdout=PIPE, stderr=PIPE,
|
||||||
|
env=os.environ)
|
||||||
|
out, err = p.communicate()
|
||||||
|
except Exception as e:
|
||||||
|
log.exception(e)
|
||||||
|
# Premature end
|
||||||
|
break
|
||||||
|
|
||||||
|
output = ("# {0}\n".format(os.path.basename(test)), out, err)
|
||||||
|
log.debug("Collected output %s", output)
|
||||||
|
outqueue.put(output)
|
||||||
|
|
||||||
|
testqueue.task_done()
|
||||||
|
|
||||||
|
|
||||||
|
class TestRunner(object):
|
||||||
|
def __init__(self):
|
||||||
|
self.threads = []
|
||||||
|
self.tap = open(cmd_args.tapfile, 'w')
|
||||||
|
|
||||||
|
self._parallelq = Queue()
|
||||||
|
self._serialq = Queue()
|
||||||
|
self._outputq = Queue()
|
||||||
|
|
||||||
|
def _find_tests(self):
|
||||||
|
for test in glob.glob("*.t") + glob.glob("*.t.exe"):
|
||||||
|
if os.access(test, os.X_OK):
|
||||||
|
# Executables only
|
||||||
|
if not cmd_args.serial:
|
||||||
|
with open(test) as fh:
|
||||||
|
if "/usr/bin/env python" in fh.readline():
|
||||||
|
log.debug("Treating as parallel: %s", test)
|
||||||
|
self._parallelq.put(test)
|
||||||
|
else:
|
||||||
|
log.debug("Treating as serial: %s", test)
|
||||||
|
self._serialq.put(test)
|
||||||
|
else:
|
||||||
|
log.debug("Treating %s as serial", test)
|
||||||
|
self._serialq.put(test)
|
||||||
|
else:
|
||||||
|
log.debug("Ignored test %s as it is not executable", test)
|
||||||
|
|
||||||
|
log.info("Parallel tests: %s", self._parallelq.qsize())
|
||||||
|
log.info("Serial tests: %s", self._serialq.qsize())
|
||||||
|
|
||||||
|
def _prepare_threads(self):
|
||||||
|
# Serial thread
|
||||||
|
self.threads.append(
|
||||||
|
Thread(target=run_test, args=(self._serialq, self._outputq))
|
||||||
|
)
|
||||||
|
# Parallel threads
|
||||||
|
self.threads.extend([
|
||||||
|
Thread(target=run_test, args=(self._parallelq, self._outputq))
|
||||||
|
for i in range(cpu_count())
|
||||||
|
])
|
||||||
|
log.info("Spawned %s threads to run tests", len(self.threads))
|
||||||
|
|
||||||
|
def _start_threads(self):
|
||||||
|
for thread in self.threads:
|
||||||
|
# Threads die when main thread dies
|
||||||
|
log.debug("Starting thread %s", thread)
|
||||||
|
thread.daemon = True
|
||||||
|
thread.start()
|
||||||
|
|
||||||
|
def _print_timestamp_to_tap(self):
|
||||||
|
now = time.time()
|
||||||
|
timestamp = "# {0} ==> {1}\n".format(
|
||||||
|
now,
|
||||||
|
time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(now)),
|
||||||
|
)
|
||||||
|
|
||||||
|
log.debug("Adding timestamp %s to TAP file", timestamp)
|
||||||
|
self.tap.write(timestamp)
|
||||||
|
|
||||||
|
def _get_remaining_tests(self):
|
||||||
|
return self._parallelq.qsize() + self._serialq.qsize()
|
||||||
|
|
||||||
|
def is_running(self):
|
||||||
|
for thread in self.threads:
|
||||||
|
if thread.is_alive():
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
self._find_tests()
|
||||||
|
self._prepare_threads()
|
||||||
|
|
||||||
|
self._print_timestamp_to_tap()
|
||||||
|
|
||||||
|
finished = 0
|
||||||
|
total = self._get_remaining_tests()
|
||||||
|
|
||||||
|
self._start_threads()
|
||||||
|
|
||||||
|
while self.is_running() or not self._outputq.empty():
|
||||||
|
try:
|
||||||
|
outputs = self._outputq.get(block=True, timeout=TIMEOUT)
|
||||||
|
except Empty:
|
||||||
|
continue
|
||||||
|
|
||||||
|
log.debug("Outputting to TAP: %s", outputs)
|
||||||
|
|
||||||
|
for output in outputs:
|
||||||
|
self.tap.write(output)
|
||||||
|
|
||||||
|
if cmd_args.verbose:
|
||||||
|
sys.stdout.write(output)
|
||||||
|
|
||||||
|
self._outputq.task_done()
|
||||||
|
finished += 1
|
||||||
|
|
||||||
|
log.warning("Finished %s out of %s tests", finished, total)
|
||||||
|
|
||||||
|
self._print_timestamp_to_tap()
|
||||||
|
|
||||||
|
if not self._parallelq.empty() or not self._serialq.empty():
|
||||||
|
raise RuntimeError(
|
||||||
|
"Something went wrong, not all tests were ran. {0} "
|
||||||
|
"remaining.".format(self._get_remaining_tests()))
|
||||||
|
|
||||||
|
def show_report(self):
|
||||||
|
self.tap.flush()
|
||||||
|
|
||||||
|
log.debug("Calling 'problems --summary' for report")
|
||||||
|
call([os.path.abspath("problems"), "--summary", cmd_args.tapfile])
|
||||||
|
|
||||||
|
|
||||||
|
def parse_args():
|
||||||
|
parser = argparse.ArgumentParser(description="Run Taskwarrior tests")
|
||||||
|
parser.add_argument('--verbose', '-v', action="store_true",
|
||||||
|
help="Also send TAP output to stdout")
|
||||||
|
parser.add_argument('--logging', '-l', action="count",
|
||||||
|
help="Logging level. -lll is the highest level")
|
||||||
|
parser.add_argument('--serial', action="store_true",
|
||||||
|
help="Do not run tests in parallel")
|
||||||
|
parser.add_argument('--tapfile', default="all.log",
|
||||||
|
help="File to use for TAP output")
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
runner = TestRunner()
|
||||||
|
runner.start()
|
||||||
|
if not cmd_args.verbose:
|
||||||
|
runner.show_report()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
cmd_args = parse_args()
|
||||||
|
|
||||||
|
if cmd_args.logging == 1:
|
||||||
|
level = logging.WARN
|
||||||
|
elif cmd_args.logging == 2:
|
||||||
|
level = logging.INFO
|
||||||
|
elif cmd_args.logging >= 3:
|
||||||
|
level = logging.DEBUG
|
||||||
|
else:
|
||||||
|
level = logging.ERROR
|
||||||
|
|
||||||
|
logging.basicConfig(
|
||||||
|
format="# %(asctime)s - %(levelname)s - %(message)s",
|
||||||
|
level=level,
|
||||||
|
)
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
log.debug("Parsed commandline arguments: %s", cmd_args)
|
||||||
|
|
||||||
|
try:
|
||||||
|
main()
|
||||||
|
except Exception as e:
|
||||||
|
log.exception(e)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# vim: ai sts=4 et sw=4
|
203
test/run_all.in
203
test/run_all.in
|
@ -1,203 +0,0 @@
|
||||||
#! /bin/sh
|
|
||||||
|
|
||||||
# Look for taskd in $PATH instead of task/src/
|
|
||||||
export TASKD_USE_PATH=1
|
|
||||||
|
|
||||||
|
|
||||||
runlog_cleanup() {
|
|
||||||
if [ -f "_run_all_parallel.txt" ]; then
|
|
||||||
rm _run_all_parallel.txt
|
|
||||||
fi
|
|
||||||
if [ -f "_run_all_serial.txt" ]; then
|
|
||||||
rm _run_all_serial.txt
|
|
||||||
fi
|
|
||||||
if [ -f "_run_all_parallel_rc1" ]; then
|
|
||||||
rm _run_all_parallel_rc1
|
|
||||||
fi
|
|
||||||
for i in *.runlog; do
|
|
||||||
# Ugly hack. :)
|
|
||||||
if [ -f "$i" ]; then
|
|
||||||
rm *.runlog
|
|
||||||
fi
|
|
||||||
break
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
get_numprocs() {
|
|
||||||
numprocs=""
|
|
||||||
# Most Linux systems and OSX have getconf and _NPROCESSORS_ONLN.
|
|
||||||
if command -v getconf >/dev/null 2>&1; then
|
|
||||||
numprocs=$(getconf _NPROCESSORS_ONLN 2>/dev/null)
|
|
||||||
fi
|
|
||||||
|
|
||||||
# OpenBSD doesn't know _NPROCESSORS_ONLN, but it does have hw.ncpu
|
|
||||||
if [ "$numprocs" = "" ] && command -v sysctl >/dev/null 2>&1; then
|
|
||||||
numprocs=$(sysctl -n hw.ncpu 2>/dev/null)
|
|
||||||
fi
|
|
||||||
|
|
||||||
# If we still haven't found the number of CPU cores available, give up.
|
|
||||||
if [ "$numprocs" = "" ] || [ "$numprocs" -lt 1 ]; then
|
|
||||||
echo "Couldn't find number of CPU cores for parallelization. Assuming 2." 1>&2
|
|
||||||
numprocs=2
|
|
||||||
else
|
|
||||||
numprocs=$((numprocs+1))
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo $numprocs
|
|
||||||
}
|
|
||||||
|
|
||||||
run_all_parallel() {
|
|
||||||
numprocs=$(get_numprocs)
|
|
||||||
cat _run_all_parallel.txt | xargs -n 1 -P $numprocs sh -c 'echo "#" $0 > $0.runlog; $0 >> $0.runlog 2>&1'
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
touch _run_all_parallel_rc1
|
|
||||||
fi
|
|
||||||
rm _run_all_parallel.txt
|
|
||||||
}
|
|
||||||
|
|
||||||
if [ ! -z "$1" ] && [ "$1" != "--verbose" ] && [ "$1" != "--fast" ];
|
|
||||||
then
|
|
||||||
echo "Did you mean --fast or --verbose?"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$#" -gt 1 ];
|
|
||||||
then
|
|
||||||
echo "Can only use arguments one at a time."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
rc=0
|
|
||||||
if [ x"$1" = x"--verbose" ];
|
|
||||||
then
|
|
||||||
for i in ${TESTBLOB}
|
|
||||||
do
|
|
||||||
if [ -x "$i" ]; then
|
|
||||||
echo '#' $i
|
|
||||||
$i > test.log 2>&1
|
|
||||||
while read LINE
|
|
||||||
do
|
|
||||||
echo "$LINE"
|
|
||||||
done < test.log
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
rc=1
|
|
||||||
fi
|
|
||||||
rm test.log
|
|
||||||
else
|
|
||||||
echo "# Skipping $(basename $i) execute bit not set"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
exit $rc
|
|
||||||
|
|
||||||
elif [ "$1" = "--fast" ]; then
|
|
||||||
# Useful for faster local testing, might not be portable. Use at own risk.
|
|
||||||
# Results in (almost) the exact same "all.log" as a normal run.
|
|
||||||
# Ordering is off, but could easily be adjusted to be the same.
|
|
||||||
|
|
||||||
date +"# %s ==> %a %b %d %H:%M:%S %Z %Y" > all.log
|
|
||||||
|
|
||||||
# Perl is used here to get the time in seconds
|
|
||||||
# because 'date +%s' isn't supported on Solaris.
|
|
||||||
STARTEPOCH=`perl -e 'print time'`
|
|
||||||
|
|
||||||
# Clean up after aborted runs
|
|
||||||
runlog_cleanup
|
|
||||||
|
|
||||||
for i in ${TESTBLOB}; do
|
|
||||||
if [ -x "$i" ]; then
|
|
||||||
# Only Python tests are guaranteed to run isolated.
|
|
||||||
if head -n 1 "$i" | grep -q '/usr/bin/env python'; then
|
|
||||||
echo $i >> _run_all_parallel.txt
|
|
||||||
else
|
|
||||||
echo $i >> _run_all_serial.txt
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo "# Skipping $(basename $i) execute bit not set" >> all.log 2>&1
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
run_all_parallel&
|
|
||||||
|
|
||||||
while read i; do
|
|
||||||
echo '#' $i >>all.log
|
|
||||||
|
|
||||||
$i >> all.log 2>&1
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
rc=1
|
|
||||||
fi
|
|
||||||
done < _run_all_serial.txt
|
|
||||||
|
|
||||||
while [ -f "_run_all_parallel.txt" ]; do
|
|
||||||
# Wait for the parallelized tests to finish running.
|
|
||||||
sleep 1 # sleep 0.1 is not portable.
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ -f "_run_all_parallel_rc1" ]; then
|
|
||||||
rc=1
|
|
||||||
fi
|
|
||||||
|
|
||||||
cat *.runlog >> all.log
|
|
||||||
|
|
||||||
runlog_cleanup
|
|
||||||
|
|
||||||
date +"# %s ==> %a %b %d %H:%M:%S %Z %Y" >> all.log
|
|
||||||
|
|
||||||
ENDEPOCH=`perl -e 'print time'`
|
|
||||||
RUNTIME=`expr $ENDEPOCH - $STARTEPOCH`
|
|
||||||
|
|
||||||
printf "Pass: %5d\n" `grep -c '^ok' all.log`
|
|
||||||
printf "Fail: %5d\n" `grep -c '^not' all.log`
|
|
||||||
printf "Skipped: %5d\n" `grep -c '^skip' all.log`
|
|
||||||
printf "Runtime: %5d seconds\n" $RUNTIME
|
|
||||||
exit $rc
|
|
||||||
|
|
||||||
else
|
|
||||||
date +"# %s ==> %a %b %d %H:%M:%S %Z %Y" > all.log
|
|
||||||
|
|
||||||
# Perl is used here to get the time in seconds
|
|
||||||
# because 'date +%s' isn't supported on Solaris.
|
|
||||||
STARTEPOCH=`perl -e 'print time'`
|
|
||||||
|
|
||||||
VRAMSTEG=`which vramsteg 2>/dev/null`
|
|
||||||
BAR=0
|
|
||||||
if [ -x "$VRAMSTEG" ]; then
|
|
||||||
BAR=1
|
|
||||||
COUNT=0
|
|
||||||
TOTAL=`ls ${TESTBLOB} | wc -l`
|
|
||||||
START=`$VRAMSTEG --now`
|
|
||||||
fi
|
|
||||||
|
|
||||||
for i in ${TESTBLOB}
|
|
||||||
do
|
|
||||||
if [ -x "$i" ]; then
|
|
||||||
echo '#' $i >>all.log
|
|
||||||
|
|
||||||
$i >> all.log 2>&1
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
rc=1
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo "# Skipping $(basename $i) execute bit not set" >> all.log 2>&1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ $BAR -eq 1 ]; then
|
|
||||||
$VRAMSTEG --label 'All tests' --min 0 --max $TOTAL --current $COUNT --percentage --start $START --estimate
|
|
||||||
COUNT=`expr $COUNT + 1`
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ $BAR -eq 1 ]; then
|
|
||||||
$VRAMSTEG --remove
|
|
||||||
fi
|
|
||||||
|
|
||||||
date +"# %s ==> %a %b %d %H:%M:%S %Z %Y" >> all.log
|
|
||||||
|
|
||||||
ENDEPOCH=`perl -e 'print time'`
|
|
||||||
RUNTIME=`expr $ENDEPOCH - $STARTEPOCH`
|
|
||||||
|
|
||||||
printf "Pass: %5d\n" `grep -c '^ok' all.log`
|
|
||||||
printf "Fail: %5d\n" `grep -c '^not' all.log`
|
|
||||||
printf "Skipped: %5d\n" `grep -c '^skip' all.log`
|
|
||||||
printf "Runtime: %5d seconds\n" $RUNTIME
|
|
||||||
exit $rc
|
|
||||||
fi
|
|
Loading…
Add table
Add a link
Reference in a new issue