element
author_nodes = root.iterfind('*/author')
authors = [n.text for n in author_nodes]
return set(authors)
except OSError as e:
import errno
# ignore No such file or directory
if e.errno != errno.ENOENT:
raise
return None
class GrassTestFilesMultiReporter(object):
def __init__(self, reporters, forgiving=False):
self.reporters = reporters
self.forgiving = forgiving
def start(self, results_dir):
# TODO: no directory cleaning (self.clean_before)? now cleaned by caller
# TODO: perhaps only those whoe need it should do it (even multiple times)
# and there is also the delet problem
ensure_dir(os.path.abspath(results_dir))
for reporter in self.reporters:
try:
reporter.start(results_dir)
except AttributeError:
if self.forgiving:
pass
else:
raise
def finish(self):
for reporter in self.reporters:
try:
reporter.finish()
except AttributeError:
if self.forgiving:
pass
else:
raise
def start_file_test(self, module):
for reporter in self.reporters:
try:
reporter.start_file_test(module)
except AttributeError:
if self.forgiving:
pass
else:
raise
def end_file_test(self, **kwargs):
for reporter in self.reporters:
try:
reporter.end_file_test(**kwargs)
except AttributeError:
if self.forgiving:
pass
else:
raise
class GrassTestFilesCountingReporter(object):
def __init__(self):
self.test_files = None
self.files_fail = None
self.files_pass = None
self.file_pass_per = None
self.file_fail_per = None
self.main_start_time = None
self.main_end_time = None
self.main_time = None
self.file_start_time = None
self.file_end_time = None
self.file_time = None
self._start_file_test_called = False
def start(self, results_dir):
self.test_files = 0
self.files_fail = 0
self.files_pass = 0
# this might be moved to some report start method
self.main_start_time = datetime.datetime.now()
def finish(self):
self.main_end_time = datetime.datetime.now()
self.main_time = self.main_end_time - self.main_start_time
assert self.test_files == self.files_fail + self.files_pass
self.file_pass_per = 100 * float(self.files_pass) / self.test_files
self.file_fail_per = 100 * float(self.files_fail) / self.test_files
def start_file_test(self, module):
self.file_start_time = datetime.datetime.now()
self._start_file_test_called = True
self.test_files += 1
def end_file_test(self, returncode, **kwargs):
assert self._start_file_test_called
self.file_end_time = datetime.datetime.now()
self.file_time = self.file_end_time - self.file_start_time
if returncode:
self.files_fail += 1
else:
self.files_pass += 1
self._start_file_test_called = False
def percent_to_html(percent):
if percent > 100 or percent < 0:
return "? {:.2f}% ?".format(percent)
elif percent < 40:
color = 'red'
elif percent < 70:
color = 'orange'
else:
color = 'green'
return '{percent:.0f}%'.format(
percent=percent, color=color)
class GrassTestFilesHtmlReporter(GrassTestFilesCountingReporter):
unknown_number = 'unknown'
def __init__(self):
super(GrassTestFilesHtmlReporter, self).__init__()
self.main_index = None
def start(self, results_dir):
super(GrassTestFilesHtmlReporter, self).start(results_dir)
# having all variables public although not really part of API
self.main_index = open(os.path.join(results_dir, 'index.html'), 'w')
# TODO: this can be moved to the counter class
self.failures = 0
self.errors = 0
self.skiped = 0
self.successes = 0
self.expected_failures = 0
self.unexpected_success = 0
self.total = 0
# TODO: skiped and unexpected success
svn_info = get_svn_info()
if not svn_info:
svn_text = (''
'SVN revision cannot be be obtained'
'')
else:
url = get_source_url(path=svn_info['relative-url'],
revision=svn_info['revision'])
svn_text = ('SVN revision'
' '
'{rev}'
).format(url=url, rev=svn_info['revision'])
self.main_index.write(''
'Test results
'
'{time:%Y-%m-%d %H:%M:%S}'
' ({svn})'
''
''
'Tested directory | '
'Test file | '
'Status | '
'Tests | Successful'
' | Failed | Percent successful | '
'
'.format(
time=self.main_start_time,
svn=svn_text))
def finish(self):
super(GrassTestFilesHtmlReporter, self).finish()
if self.total:
pass_per = 100 * (float(self.successes) / self.total)
pass_per = percent_to_html(pass_per)
else:
pass_per = self.unknown_number
tfoot = (''
''
'Summary | '
'{nfiles} test files | '
'{nsper} | '
'{total} | {st} | {ft} | {pt} | '
'
'
''.format(
nfiles=self.test_files,
nsper=percent_to_html(self.file_pass_per),
st=self.successes, ft=self.failures + self.errors,
total=self.total, pt=pass_per
))
summary_sentence = ('Executed {nfiles} test files in {time:}.'
' From them'
' {nsfiles} files ({nsper:.0f}%) were successful'
' and {nffiles} files ({nfper:.0f}%) failed.'
.format(
nfiles=self.test_files,
time=self.main_time,
nsfiles=self.files_pass,
nffiles=self.files_fail,
nsper=self.file_pass_per,
nfper=self.file_fail_per))
self.main_index.write('{tfoot}
'
'{summary}
'
''
.format(
tfoot=tfoot,
summary=summary_sentence))
self.main_index.close()
def start_file_test(self, module):
super(GrassTestFilesHtmlReporter, self).start_file_test(module)
self.main_index.flush() # to get previous lines to the report
def wrap_stdstream_to_html(self, infile, outfile, module, stream):
before = '%s
' % (module.name + ' ' + stream)
after = '
'
html = open(outfile, 'w')
html.write(before)
with open(infile) as text:
for line in text:
html.write(color_error_line(html_escape(line)))
html.write(after)
html.close()
def returncode_to_html_text(self, returncode):
if returncode:
return 'FAILED'
else:
# alternatives: SUCCEEDED, passed, OK
return 'succeeded'
def returncode_to_html_sentence(self, returncode):
if returncode:
return ('❌'
' Test failed (return code %d)' % (returncode))
else:
return ('✓'
' Test succeeded (return code %d)' % (returncode))
def end_file_test(self, module, cwd, returncode, stdout, stderr,
test_summary):
super(GrassTestFilesHtmlReporter, self).end_file_test(
module=module, cwd=cwd, returncode=returncode,
stdout=stdout, stderr=stderr)
# TODO: considering others accoring to total, OK?
total = test_summary.get('total', None)
failures = test_summary.get('failures', 0)
errors = test_summary.get('errors', 0)
# Python unittest TestResult class is reporting success for no
# errors or failures, so skipped, expected failures and unexpected
# success are ignored
# but successful tests are only total - the others
# TODO: add success counter to GrassTestResult base class
skipped = test_summary.get('skipped', 0)
expected_failures = test_summary.get('expected_failures', 0)
unexpected_successes = test_summary.get('unexpected_successes', 0)
successes = test_summary.get('successes', 0)
self.failures += failures
self.errors += errors
self.skiped += skipped
self.expected_failures += expected_failures
self.unexpected_success += unexpected_successes
if total is not None:
# success are only the clear ones
# percentage is influenced by all but putting only failures to table
self.successes += successes
self.total += total
pass_per = 100 * (float(successes) / total)
pass_per = percent_to_html(pass_per)
else:
total = successes = pass_per = self.unknown_number
bad_ones = failures + errors
self.main_index.write(
'{d} | '
'{m} | {sf} | '
'{total} | {st} | {ft} | {pt} | '
'
'.format(
d=module.tested_dir, m=module.name,
sf=self.returncode_to_html_text(returncode),
st=successes, ft=bad_ones, total=total, pt=pass_per))
self.wrap_stdstream_to_html(infile=stdout,
outfile=os.path.join(cwd, 'stdout.html'),
module=module, stream='stdout')
self.wrap_stdstream_to_html(infile=stderr,
outfile=os.path.join(cwd, 'stderr.html'),
module=module, stream='stderr')
file_index_path = os.path.join(cwd, 'index.html')
file_index = open(file_index_path, 'w')
file_index.write(
''
'{m.name}
'
'{m.tested_dir} – {m.name}
'
'{status}'
'
Test duration: {dur}'
'
'
''
.format(
dur=self.file_time, m=module,
status=self.returncode_to_html_sentence(returncode),
))
file_index.close()
if returncode:
pass
# TODO: here we don't have oportunity to write error file
# to stream (stdout/stderr)
# a stream can be added and if not none, we could write
class GrassTestFilesTextReporter(GrassTestFilesCountingReporter):
def __init__(self, stream):
super(GrassTestFilesTextReporter, self).__init__()
self._stream = stream
def start(self, results_dir):
super(GrassTestFilesTextReporter, self).start(results_dir)
def finish(self):
super(GrassTestFilesTextReporter, self).finish()
summary_sentence = ('\nExecuted {nfiles} test files in {time:}.'
'\nFrom them'
' {nsfiles} files ({nsper:.0f}%) were successful'
' and {nffiles} files ({nfper:.0f}%) failed.\n'
.format(
nfiles=self.test_files,
time=self.main_time,
nsfiles=self.files_pass,
nffiles=self.files_fail,
nsper=self.file_pass_per,
nfper=self.file_fail_per))
self._stream.write(summary_sentence)
def start_file_test(self, module):
super(GrassTestFilesTextReporter, self).start_file_test(module)
self._stream.flush() # to get previous lines to the report
def end_file_test(self, module, cwd, returncode, stdout, stderr,
test_summary):
super(GrassTestFilesTextReporter, self).end_file_test(
module=module, cwd=cwd, returncode=returncode,
stdout=stdout, stderr=stderr)
if returncode:
self._stream.write(
'{m} from {d} failed'
.format(
d=module.tested_dir,
m=module.name))
num_failed = test_summary.get('failures', 0)
num_failed += test_summary.get('errors', 0)
if num_failed:
if num_failed > 1:
text = ' ({f} tests failed)'
else:
text = ' ({f} test failed)'
self._stream.write(text.format(f=num_failed))
self._stream.write('\n')
# TODO: here we lost the possibility to include also file name
# of the appropriate report