summaryrefslogtreecommitdiff
path: root/tests/inputs/dummy_file.py
diff options
context:
space:
mode:
Diffstat (limited to 'tests/inputs/dummy_file.py')
-rw-r--r--tests/inputs/dummy_file.py470
1 files changed, 470 insertions, 0 deletions
diff --git a/tests/inputs/dummy_file.py b/tests/inputs/dummy_file.py
new file mode 100644
index 0000000..aec34aa
--- /dev/null
+++ b/tests/inputs/dummy_file.py
@@ -0,0 +1,470 @@
+#!/usr/bin/env python3
+
+__version__ = '0.4.0'
+
+
+import difflib
+import io
+import os
+from pathlib import Path
+import re
+import shutil
+import sys
+
+sys.path.append(str(Path(__file__).resolve().parent.parent))
+from asciidoc import asciidoc # noqa: E402
+
+# Default backends.
+BACKENDS = ('html4', 'xhtml11', 'docbook', 'docbook5', 'html5')
+BACKEND_EXT = {
+ 'html4': '.html',
+ 'xhtml11': '.html',
+ 'docbook': '.xml',
+ 'docbook5': '.xml',
+ 'slidy': '.html',
+ 'html5': '.html'
+}
+
+
+def iif(condition, iftrue, iffalse=None):
+ """
+ Immediate if c.f. ternary ?: operator.
+ False value defaults to '' if the true value is a string.
+ False value defaults to 0 if the true value is a number.
+ """
+ if iffalse is None:
+ if isinstance(iftrue, str):
+ iffalse = ''
+ if type(iftrue) in (int, float):
+ iffalse = 0
+ if condition:
+ return iftrue
+ else:
+ return iffalse
+
+
+def message(msg=''):
+ print(msg, file=sys.stderr)
+
+
+def strip_end(lines):
+ """
+ Strip blank strings from the end of list of strings.
+ """
+ for i in range(len(lines) - 1, -1, -1):
+ if not lines[i]:
+ del lines[i]
+ else:
+ break
+
+
+def normalize_data(lines):
+ """
+ Strip comments and trailing blank strings from lines.
+ """
+ result = [s for s in lines if not s.startswith('#')]
+ strip_end(result)
+ return result
+
+
+class AsciiDocTest(object):
+ def __init__(self):
+ self.number = None # Test number (1..).
+ self.name = '' # Optional test name.
+ self.title = '' # Optional test name.
+ self.description = [] # List of lines followoing title.
+ self.source = None # AsciiDoc test source file name.
+ self.options = []
+ self.attributes = {'asciidoc-version': 'test'}
+ self.backends = BACKENDS
+ self.artifacts = [] # list of generated artifacts to delete
+ self.requires = [] # list of dependencies to check for for the test
+ self.confdir = None
+ self.datadir = None # Where output files are stored.
+ self.disabled = False
+ self.passed = self.skipped = self.failed = 0
+
+ def backend_filename(self, backend):
+ """
+ Return the path name of the backend output file that is generated from
+ the test name and output file type.
+ """
+ return '%s-%s%s' % (
+ os.path.normpath(os.path.join(self.datadir, self.name)),
+ backend,
+ BACKEND_EXT[backend]
+ )
+
+ def parse(self, lines, confdir, datadir):
+ """
+ Parse conf file test section from list of text lines.
+ """
+ self.__init__()
+ self.confdir = confdir
+ self.datadir = datadir
+ lines = Lines(lines)
+ while not lines.eol():
+ text = lines.read_until(r'^%')
+ if text:
+ if not text[0].startswith('%'):
+ if text[0][0] == '!':
+ self.disabled = True
+ self.title = text[0][1:]
+ else:
+ self.title = text[0]
+ self.description = text[1:]
+ continue
+ reo = re.match(r'^%\s*(?P<directive>[\w_-]+)', text[0])
+ if not reo:
+ raise ValueError
+ directive = reo.groupdict()['directive']
+ data = normalize_data(text[1:])
+ if directive == 'source':
+ if data:
+ self.source = os.path.normpath(os.path.join(
+ self.confdir, os.path.normpath(data[0])
+ ))
+ elif directive == 'options':
+ self.options = eval(' '.join(data))
+ for i, v in enumerate(self.options):
+ if isinstance(v, str):
+ self.options[i] = (v, None)
+ elif directive == 'attributes':
+ self.attributes.update(eval(' '.join(data)))
+ elif directive == 'backends':
+ self.backends = eval(' '.join(data))
+ elif directive == 'name':
+ self.name = data[0].strip()
+ elif directive == 'requires':
+ self.requires = eval(' '.join(data))
+ elif directive == 'artifacts':
+ self.artifacts = eval(' '.join(data))
+ else:
+ raise ValueError
+ if not self.title:
+ self.title = self.source
+ if not self.name:
+ self.name = os.path.basename(os.path.splitext(self.source)[0])
+
+ def is_missing(self, backend):
+ """
+ Returns True if there is no output test data file for backend.
+ """
+ return not os.path.isfile(self.backend_filename(backend))
+
+ def is_missing_or_outdated(self, backend):
+ """
+ Returns True if the output test data file is missing or out of date.
+ """
+ return self.is_missing(backend) or (
+ os.path.getmtime(self.source)
+ > os.path.getmtime(self.backend_filename(backend))
+ )
+
+ def clean_artifacts(self):
+ for artifact in self.artifacts:
+ loc = os.path.join(self.confdir, artifact)
+ if os.path.exists(loc):
+ os.unlink(loc)
+
+ def get_expected(self, backend):
+ """
+ Return expected test data output for backend.
+ """
+ with open(
+ self.backend_filename(backend),
+ encoding='utf-8',
+ newline=''
+ ) as open_file:
+ return open_file.readlines()
+
+ def generate_expected(self, backend):
+ """
+ Generate and return test data output for backend.
+ """
+ asciidoc.reset_asciidoc()
+ outfile = io.StringIO()
+ options = self.options[:]
+ options.append(('--out-file', outfile))
+ options.append(('--backend', backend))
+ for k, v in self.attributes.items():
+ if v == '' or k[-1] in '!@':
+ s = str(k)
+ elif v is None:
+ s = k + '!'
+ else:
+ s = '%s=%s' % (k, v)
+ options.append(('--attribute', s))
+ asciidoc.execute('asciidoc', options, [self.source])
+ return outfile.getvalue().splitlines(keepends=True)
+
+ def update_expected(self, backend):
+ """
+ Generate and write backend data.
+ """
+ lines = self.generate_expected(backend)
+ if not os.path.isdir(self.datadir):
+ print('CREATING: %s' % self.datadir)
+ os.mkdir(self.datadir)
+ with open(
+ self.backend_filename(backend),
+ 'w+',
+ encoding='utf-8',
+ newline=''
+ ) as open_file:
+ print('WRITING: %s' % open_file.name)
+ open_file.writelines(lines)
+
+ def update(self, backend=None, force=False):
+ """
+ Regenerate and update expected test data outputs.
+ """
+ if backend is None:
+ backends = self.backends
+ else:
+ backends = [backend]
+
+ print('SOURCE: asciidoc: %s' % self.source)
+ for backend in backends:
+ if force or self.is_missing_or_outdated(backend):
+ self.update_expected(backend)
+ print()
+
+ self.clean_artifacts()
+
+ def run(self, backend=None):
+ """
+ Execute test.
+ Return True if test passes.
+ """
+ if backend is None:
+ backends = self.backends
+ else:
+ backends = [backend]
+ result = True # Assume success.
+ self.passed = self.failed = self.skipped = 0
+ print('%d: %s' % (self.number, self.title))
+ if self.source and os.path.isfile(self.source):
+ print('SOURCE: asciidoc: %s' % self.source)
+ for backend in backends:
+ fromfile = self.backend_filename(backend)
+ skip = False
+ for require in self.requires:
+ if shutil.which(require) is None:
+ skip = True
+ break
+ if not skip and not self.is_missing(backend):
+ expected = self.get_expected(backend)
+ strip_end(expected)
+ got = self.generate_expected(backend)
+ strip_end(got)
+ lines = []
+ for line in difflib.unified_diff(got, expected, n=0):
+ lines.append(line)
+ if lines:
+ result = False
+ self.failed += 1
+ lines = lines[3:]
+ print('FAILED: %s: %s' % (backend, fromfile))
+ message('+++ %s' % fromfile)
+ message('--- got')
+ for line in lines:
+ message(line)
+ message()
+ else:
+ self.passed += 1
+ print('PASSED: %s: %s' % (backend, fromfile))
+ else:
+ self.skipped += 1
+ print('SKIPPED: %s: %s' % (backend, fromfile))
+ self.clean_artifacts()
+ else:
+ self.skipped += len(backends)
+ if self.source:
+ msg = 'MISSING: %s' % self.source
+ else:
+ msg = 'NO ASCIIDOC SOURCE FILE SPECIFIED'
+ print(msg)
+ print('')
+ return result
+
+
+class AsciiDocTests(object):
+ def __init__(self, conffile):
+ """
+ Parse configuration file
+ :param conffile:
+ """
+ self.conffile = conffile
+ self.passed = self.failed = self.skipped = 0
+ # All file names are relative to configuration file directory.
+ self.confdir = os.path.dirname(self.conffile)
+ self.datadir = self.confdir # Default expected files directory.
+ self.tests = [] # List of parsed AsciiDocTest objects.
+ self.globals = {}
+ with open(self.conffile, encoding='utf-8') as open_file:
+ lines = Lines(open_file.readlines())
+ first = True
+ while not lines.eol():
+ s = lines.read_until(r'^%+$')
+ s = [line for line in s if len(line) > 0] # Drop blank lines.
+ # Must be at least one non-blank line in addition to delimiter.
+ if len(s) > 1:
+ # Optional globals precede all tests.
+ if first and re.match(r'^%\s*globals$', s[0]):
+ self.globals = eval(' '.join(normalize_data(s[1:])))
+ if 'datadir' in self.globals:
+ self.datadir = os.path.join(
+ self.confdir,
+ os.path.normpath(self.globals['datadir'])
+ )
+ else:
+ test = AsciiDocTest()
+ test.parse(s[1:], self.confdir, self.datadir)
+ self.tests.append(test)
+ test.number = len(self.tests)
+ first = False
+
+ def run(self, number=None, backend=None):
+ """
+ Run all tests.
+ If number is specified run test number (1..).
+ """
+ self.passed = self.failed = self.skipped = 0
+ for test in self.tests:
+ if (
+ (not test.disabled or number)
+ and (not number or number == test.number)
+ and (not backend or backend in test.backends)
+ ):
+ test.run(backend)
+ self.passed += test.passed
+ self.failed += test.failed
+ self.skipped += test.skipped
+ if self.passed > 0:
+ print('TOTAL PASSED: %s' % self.passed)
+ if self.failed > 0:
+ print('TOTAL FAILED: %s' % self.failed)
+ if self.skipped > 0:
+ print('TOTAL SKIPPED: %s' % self.skipped)
+
+ def update(self, number=None, backend=None, force=False):
+ """
+ Regenerate expected test data and update configuratio file.
+ """
+ for test in self.tests:
+ if (not test.disabled or number) and (not number or number == test.number):
+ test.update(backend, force=force)
+
+ def list(self):
+ """
+ Lists tests to stdout.
+ """
+ for test in self.tests:
+ print('%d: %s%s' % (test.number, iif(test.disabled, '!'), test.title))
+
+
+class Lines(list):
+ """
+ A list of strings.
+ Adds eol() and read_until() to list type.
+ """
+
+ def __init__(self, lines):
+ super(Lines, self).__init__()
+ self.extend([s.rstrip() for s in lines])
+ self.pos = 0
+
+ def eol(self):
+ return self.pos >= len(self)
+
+ def read_until(self, regexp):
+ """
+ Return a list of lines from current position up until the next line
+ matching regexp.
+ Advance position to matching line.
+ """
+ result = []
+ if not self.eol():
+ result.append(self[self.pos])
+ self.pos += 1
+ while not self.eol():
+ if re.match(regexp, self[self.pos]):
+ break
+ result.append(self[self.pos])
+ self.pos += 1
+ return result
+
+
+if __name__ == '__main__':
+ # guarantee a stable timestamp matching the test fixtures
+ os.environ['SOURCE_DATE_EPOCH'] = '1038184662'
+ # Process command line options.
+ from argparse import ArgumentParser
+ parser = ArgumentParser(
+ description='Run AsciiDoc conformance tests specified in configuration'
+ 'FILE.'
+ )
+ msg = 'Use configuration file CONF_FILE (default configuration file is '\
+ 'testasciidoc.conf in testasciidoc.py directory)'
+ parser.add_argument(
+ '-v',
+ '--version',
+ action='version',
+ version='%(prog)s {}'.format(__version__)
+ )
+ parser.add_argument('-f', '--conf-file', help=msg)
+
+ subparsers = parser.add_subparsers(metavar='command', dest='command')
+ subparsers.required = True
+
+ subparsers.add_parser('list', help='List tests')
+
+ options = ArgumentParser(add_help=False)
+ options.add_argument('-n', '--number', type=int, help='Test number to run')
+ options.add_argument('-b', '--backend', type=str, help='Backend to run')
+
+ subparsers.add_parser('run', help='Execute tests', parents=[options])
+
+ subparser = subparsers.add_parser(
+ 'update',
+ help='Regenerate and update test data',
+ parents=[options]
+ )
+ subparser.add_argument(
+ '--force',
+ action='store_true',
+ help='Update all test data overwriting existing data'
+ )
+
+ args = parser.parse_args()
+
+ conffile = os.path.join(os.path.dirname(sys.argv[0]), 'testasciidoc.conf')
+ force = 'force' in args and args.force is True
+ if args.conf_file is not None:
+ conffile = args.conf_file
+ if not os.path.isfile(conffile):
+ message('missing CONF_FILE: %s' % conffile)
+ sys.exit(1)
+ tests = AsciiDocTests(conffile)
+ cmd = args.command
+ number = None
+ backend = None
+ if 'number' in args:
+ number = args.number
+ if 'backend' in args:
+ backend = args.backend
+ if backend and backend not in BACKENDS:
+ message('illegal BACKEND: {:s}'.format(backend))
+ sys.exit(1)
+ if number is not None and (number < 1 or number > len(tests.tests)):
+ message('illegal test NUMBER: {:d}'.format(number))
+ sys.exit(1)
+ if cmd == 'run':
+ tests.run(number, backend)
+ if tests.failed:
+ sys.exit(1)
+ elif cmd == 'update':
+ tests.update(number, backend, force=force)
+ elif cmd == 'list':
+ tests.list()