File: FunctionalTests.py

package info (click to toggle)
plastex 3.1-4
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 6,132 kB
  • sloc: python: 23,341; xml: 18,076; javascript: 7,755; ansic: 46; makefile: 40; sh: 26
file content (145 lines) | stat: -rwxr-xr-x 5,334 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
"""
This file runs end-to-end tests by compiling full TeX files and comparing
with reference output files.
The folder containing this test file and each of its subfolder contains a
"sources" folder for TeX inputs, an optional "extras" folder for auxilliary
input, and a "benchmarks" folder containing the expected outputs.

Each input TeX file can start with the following species of special lines:
%* command to run     which runs a command before compilation
%# filename           which copies extras/filename to the output folder
                      before compilation
%python 3.X           which skips the test if python is older than 3.X


Each time such a test fails, the output is placed into a "new" folder.
This allows both easy comparison and replacement of the benchmark in
case the change is desired.
"""

import sys, os, shutil, difflib, subprocess
from pathlib import Path
import pytest


plastex_dir = str(Path(__file__).absolute().parent.parent)


def which(name, path=None, exts=('',)):
    """
    Search PATH for a binary.

    Args:
    name -- the filename to search for
    path -- the optional path string (default: os.environ['PATH')
    exts -- optional list/tuple of extensions to try (default: ('',))

    Returns:
    The abspath to the binary or None if not found.
    """
    path = os.environ.get('PATH', path)
    for dir in path.split(os.pathsep):
        for ext in exts:
            binpath = os.path.abspath(os.path.join(dir, name) + ext)
            if os.path.isfile(binpath):
                return binpath
    return None

class Process(object):
    """ Simple subprocess wrapper """
    def __init__(self, *args, **kwargs):
        if 'stdin' not in kwargs:
            kwargs['stdin'] = subprocess.PIPE
        if 'stdout' not in kwargs:
            kwargs['stdout'] = subprocess.PIPE
        if 'stderr' not in kwargs:
            kwargs['stderr'] = subprocess.STDOUT
        if 'env' not in kwargs:
            ppath = ':'.join([
                os.environ.get('PYTHONPATH', ''),
                str(plastex_dir),
            ])
            env = os.environ.copy()
            env['PYTHONPATH'] = ppath
            kwargs['env'] = env
        self.process = subprocess.Popen(args, **kwargs)
        self.log = self.process.stdout.read().decode('utf8')
        self.process.wait()
        self.returncode = self.process.returncode
        self.process.stdout.close()
        self.process.stdin.close()

files = [path for path in Path(__file__).parent.glob('**/*.tex')
         if 'sources' in path.parts]

@pytest.mark.parametrize('src', files,
                         ids=lambda p: p.name)
def test_benchmark(src, tmpdir):
    tmpdir = Path(str(tmpdir)) # For old python
    root = src.parent.parent

    # Create temp dir and files
    texfile = tmpdir/src.name
    shutil.copyfile(str(src), str(texfile))
    outdir = str(tmpdir)

    # Run preprocessing commands
    with src.open() as fh:
        for line in fh:
            if line.startswith('%*'):
                command = line[2:].strip()
                p = Process(cwd=outdir, *command.split())
                if p.returncode:
                    raise OSError('Preprocessing command exited abnormally with return code %s: %s' % (command, p.log))
            elif line.startswith('%#'):
                filename = line[2:].strip()
                shutil.copyfile(str(root/'extras'/filename),
                                str(tmpdir/filename))
            elif line.startswith('%python '):
                # skip this test on old python
                version = tuple(int(n) for n in line[8:].strip().split('.'))
                if sys.version_info < version:
                    pytest.skip('This python is too old')
            elif line.startswith('%'):
                continue
            elif not line.strip():
                continue
            else:
                break

    # Run plastex
    outfile = (tmpdir/src.name).with_suffix('.html')
    plastex = which('plastex') or 'plastex'
    python = sys.executable
    p = Process(python, plastex,'--renderer=HTML5',
                '--split-level=0','--no-theme-extras',
                '--dir=%s' % outdir,'--theme=minimal',
                '--filename=%s' % outfile.name, src.name,
                cwd=outdir)
    if p.returncode:
        shutil.rmtree(outdir, ignore_errors=True)
        raise OSError('plastex failed with code %s: %s' % (p.returncode, p.log))

    benchfile = root/'benchmarks'/outfile.name
    if benchfile.exists():
        bench = benchfile.read_text().split('\n')
        output = outfile.read_text().split('\n')
    else:
        (root/'new').mkdir(parents=True, exist_ok=True)
        shutil.copyfile(str(outfile), str(root/'new'/outfile.name))
        shutil.rmtree(outdir, ignore_errors=True)
        raise OSError('No benchmark file: %s' % benchfile)

    # Compare files
    diff = '\n'.join(difflib.unified_diff(bench, output,
        fromfile='benchmark', tofile='output')).strip()

    if diff:
        (root/'new').mkdir(parents=True, exist_ok=True)
        shutil.copyfile(str(outfile), str(root/'new'/outfile.name))
        shutil.rmtree(outdir, ignore_errors=True)
        assert not(diff), 'Differences were found: {}\nplasTeX log:\n{}'.format(diff,  p.log)

    # Clean up
    shutil.rmtree(outdir, ignore_errors=True)