Commit 19ff426a authored by Kirill Smelkov's avatar Kirill Smelkov

go/neo/t: nxd/runTestSuite was generalized and factored-out into nxdtest

See:

- https://lab.nexedi.com/nexedi/nxdtest
- https://lab.nexedi.com/nexedi/nxdtest/blob/master/nxdtest/__init__.py
- nexedi/nxdtest@d575236a
- nexedi/slapos!839

-> Leave only .nxdtest file with neotest-specific bits to be run by
nxdtest driver.
parent d14040ac
# setup to run neotest on Nexedi testing infrastructure.
# https://stack.nexedi.com/test_status
# neotest must be on $PATH.
import re
# xint converts number from neo/py test output to integer
def xint(s):
s = s.strip()
if s == '.':
return 0
else:
return int(s)
# extract summary from neo/py test run
def test_py_summary(stdout):
# Test Module | run | unexpected | expected | skipped | time
# ...
# Summary | 366 | . | 9 | . | 353.47s
m = re.search(r'^\s*summary.*$', stdout, re.M | re.I)
assert m is not None, "could not find summary line"
summary = m.group(0)
_, nrun, nfail, nxfail, nskip, _ = summary.split('|')
return {
'test_count': xint(nrun),
'error_count': xint(nfail),
'failure_count': xint(nxfail),
'skip_count': xint(nskip),
}
TestCase('test-go', ['neotest', 'test-go'])
TestCase('test-py', ['neotest', 'test-py'], summaryf=test_py_summary)
TestCase('bench-local', ['neotest', 'bench-local'])
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (C) 2018 Nexedi SA and Contributors.
#
# This program is free software: you can Use, Study, Modify and Redistribute
# it under the terms of the GNU General Public License version 3, or (at your
# option) any later version, as published by the Free Software Foundation.
#
# You can also Link and Combine this program with other software covered by
# the terms of any of the Free Software licenses or any of the Open Source
# Initiative approved licenses and Convey the resulting work. Corresponding
# source of such a combination shall include the source code for all other
# software used.
#
# This program is distributed WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# See COPYING file for full licensing terms.
# See https://www.nexedi.com/licensing for rationale and options.
"""runTestSuite - run neotest under Nexedi testing infrastructure.
neotest must be on $PATH.
"""
from erp5.util.taskdistribution import TaskDistributor
from subprocess import Popen, PIPE
from time import time, strftime, gmtime
import os, sys, threading, argparse, logging, traceback, re
def main():
# testnode executes us giving URL to master results collecting instance and other details
# https://lab.nexedi.com/nexedi/erp5/blob/744f3fde/erp5/util/testnode/UnitTestRunner.py#L137
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--master_url', help='The URL of Master controling many suites')
parser.add_argument('--revision', help='The revision to test', default='dummy_revision')
parser.add_argument('--test_suite', help='The test suite name')
parser.add_argument('--test_suite_title', help='The test suite title')
parser.add_argument('--test_node_title', help='The test node title')
parser.add_argument('--project_title', help='The project title')
parser.add_argument('--verbose', action='store_true', help='increase output verbosity')
args = parser.parse_args()
# if verbose -> log to stderr
logger = None
if args.verbose:
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger()
# connect to master and create 'test result' object with list of tests to run
tool = TaskDistributor(portal_url = args.master_url, logger = logger)
test_result = tool.createTestResult(
revision = args.revision,
test_name_list = ['test-go', 'test-py', 'bench-local'],
node_title = args.test_node_title,
test_title = args.test_suite_title or args.test_suite,
project_title = args.project_title)
if test_result is None:
# a test run for given name and revision has already been completed
return
# make sure we get output from subprocesses without delay.
# go does not buffer stdout/stderr by default, but python does for stdout.
# tell python not to buffer anything.
os.environ['PYTHONUNBUFFERED'] = 'y'
# run the tests
devnull = open(os.devnull)
while 1:
# ask master for next test to run; stop if no more.
test_result_line = test_result.start()
if test_result_line is None:
break
# run `neotest <test-name>`
testname = test_result_line.name
argv = ['neotest', testname]
tstart = time()
try:
# NOTE runs with unchanged cwd. Instance wrapper cares to set cwd before running us.
# bufsize=1 means 'line buffered'
p = Popen(argv, stdin=devnull, stdout=PIPE, stderr=PIPE, bufsize=1)
except:
stdout, stderr = '', traceback.format_exc()
sys.stderr.write(stderr)
ok = False
else:
# tee >stdout,stderr so we can also see in testnode logs
# (explicit teeing instead of p.communicate() to be able to see incremental progress)
buf_out = []
buf_err = []
tout = threading.Thread(target=tee, args=(p.stdout, sys.stdout, buf_out))
terr = threading.Thread(target=tee, args=(p.stderr, sys.stderr, buf_err))
tout.start()
terr.start()
tout.join(); stdout = ''.join(buf_out)
terr.join(); stderr = ''.join(buf_err)
p.wait()
ok = (p.returncode == 0)
# default status dict just by exit code
status = {
'test_count': 1,
'error_count': (0 if ok else 1),
'failure_count': 0,
'skip_count': 0,
#html_test_result
}
# postprocess output, if we can
summaryf = globals().get(testname.replace('-', '_') + '_summary')
if summaryf is not None:
try:
summary = summaryf(stdout)
except:
bad = traceback.format_exc()
sys.stderr.write(bad)
stderr += bad
status['error_count'] += 1
else:
status.update(summary)
tend = time()
# report result of test run back to master
test_result_line.stop(
command = ' '.join(argv),
duration = tend - tstart,
date = strftime("%Y/%m/%d %H:%M:%S", gmtime(tend)),
stdout = stdout,
stderr = stderr,
**status
)
# tee, similar to tee(1) utility, copies data from fin to fout appending them to buf.
def tee(fin, fout, buf):
while 1:
# NOTE use raw os.read because it does not wait for full data to be available.
# ( we could use fin.readline(), but there are cases when e.g. progress
# is reported via printing consequent dots on the same line and
# readline() won't work for that.
#
# besides when a lot of output is available it would be a waste to
# read/flush it line-by-line. )
data = os.read(fin.fileno(), 4096)
if not(data):
return # EOF
fout.write(data)
fout.flush()
buf.append(data)
# xint converts number from neo/py test output to integer
def xint(s):
s = s.strip()
if s == '.':
return 0
else:
return int(s)
# extract summary from neo/py test run
def test_py_summary(stdout):
# Test Module | run | unexpected | expected | skipped | time
# ...
# Summary | 366 | . | 9 | . | 353.47s
m = re.search(r'^\s*summary.*$', stdout, re.M | re.I)
assert m is not None, "could not find summary line"
summary = m.group(0)
_, nrun, nfail, nxfail, nskip, _ = summary.split('|')
return {
'test_count': xint(nrun),
'error_count': xint(nfail),
'failure_count': xint(nxfail),
'skip_count': xint(nskip),
}
if __name__ == '__main__':
main()
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment