File: doctest_rsts.py

package info (click to toggle)
python-cogent 2024.5.7a1%2Bdfsg-3
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 74,600 kB
  • sloc: python: 92,479; makefile: 117; sh: 16
file content (161 lines) | stat: -rwxr-xr-x 5,025 bytes parent folder | download
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
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
#!/usr/bin/env python
"""
This will doctest all files ending with .rst in this directory.
"""
import os
import pathlib
import subprocess
import tempfile

import click
import nbformat

from nbconvert.preprocessors import CellExecutionError, ExecutePreprocessor

from cogent3.app.composable import define_app
from cogent3.util.io import atomic_write


DATA_DIR = pathlib.Path(__file__).parent / "data"


def execute_ipynb(file_paths, exit_on_first, verbose):
    failed = False
    for test in file_paths:
        path = pathlib.Path(test)
        print()
        print("=" * 40)
        print(test)
        with open(test) as f:
            nb = nbformat.read(f, as_version=4)
        ep = ExecutePreprocessor(
            timeout=600, kernel_name="python3", store_widget_state=True
        )
        try:
            ep.preprocess(nb, {"metadata": {"path": path.parent}})
        except CellExecutionError:
            failed = True

        with atomic_write(test, mode="w") as f:
            nbformat.write(nb, f)

        if failed and exit_on_first:
            raise SystemExit(
                f"notebook execution failed in {test}, error saved " "in notebook"
            )


@define_app
def test_file(test: pathlib.Path | str, exit_on_first: bool = True) -> bool:
    test = pathlib.Path(test).absolute()
    orig_wd = pathlib.Path(os.getcwd())
    rst_to_py = orig_wd / "rst2script.py"
    with tempfile.TemporaryDirectory(dir=orig_wd) as working_dir:
        working_dir = pathlib.Path(working_dir)
        # create a symlink to the data dir
        (working_dir / "data").symlink_to(DATA_DIR)

        # copy the rest file
        dest = working_dir / test.name
        dest.write_text(test.read_text())
        test = dest
        cmnd = f"python {str(rst_to_py)} -wd {str(working_dir)} {str(test)}"
        r = subprocess.run(cmnd.split(), capture_output=True, check=False)
        if r.returncode != 0:
            click.secho(f"FAILED: {str(rst_to_py)}", fg="red")
            click.secho(r.stdout.decode("utf8"), fg="red")
            click.secho(r.stderr.decode("utf8"), fg="red")
            exit(1)

        py_path = pathlib.Path(f'{str(test).removesuffix("rst")}py')

        cmnd = f"python {str(py_path)}"
        r = subprocess.run(cmnd.split(), capture_output=True)

        if r.returncode != 0:
            click.secho(f"FAILED: {str(py_path)}", fg="red")
            click.secho(r.stdout.decode("utf8"), fg="red")
            click.secho(r.stderr.decode("utf8"), fg="red")

        if exit_on_first and r.returncode != 0:
            return False

    return True


def execute_rsts(file_paths, exit_on_first, parallel, verbose):
    runfile = test_file(exit_on_first=exit_on_first)
    for result in runfile.as_completed(file_paths, parallel=parallel):
        if result is False and exit_on_first:
            exit(1)


@click.command()
@click.option(
    "-f",
    "--file_paths",
    required=True,
    help="directory or specific"
    " files to test. If directory, glob searches for files matching suffix.",
)
@click.option(
    "-j",
    "--just",
    help="comma separated list of names to be matched to files to be tested",
)
@click.option(
    "-x",
    "--exclude",
    help="comma separated list of names to be matched to files to be excluded",
)
@click.option("-1", "--exit_on_first", is_flag=True, help="exit on first failure")
@click.option(
    "-s", "--suffix", type=click.Choice(["rst", "ipynb"]), help="suffix of docs to test"
)
@click.option("-p", "--parallel", is_flag=True, help="run tests in parallel")
@click.option("-v", "--verbose", is_flag=True, help="verbose output")
def main(file_paths, just, exclude, exit_on_first, suffix, parallel, verbose):
    """runs doctests for the indicated files"""
    cwd = os.getcwd()
    if "*" in file_paths:  # trim to just parent directory
        file_paths = pathlib.Path(file_paths).parent

    if "," in file_paths:
        file_paths = file_paths.split(",")
    elif pathlib.Path(file_paths).is_file():
        p = pathlib.Path(file_paths)
        assert p.suffix.lower() in ".rst.ipynb", f"Unknown format suffix '{p.suffix}'"
        suffix = p.suffix[1:]
        file_paths = [p]
    else:
        file_paths = list(pathlib.Path(file_paths).glob(f"*.{suffix}"))

    if verbose:
        print(file_paths)

    if just:
        just = just.split(",")
        new = []
        for fn in file_paths:
            new.extend(fn for sub_word in just if sub_word in fn.name)
        file_paths = new
    elif exclude:
        exclude = exclude.split(",")
        new = []
        for fn in file_paths:
            keep = all(sub_word not in str(fn) for sub_word in exclude)
            if keep:
                new.append(fn)
        file_paths = new

    if verbose:
        print(f"File paths, after filtering: {str(file_paths)}")

    if suffix == "rst":
        execute_rsts(file_paths, exit_on_first, parallel, verbose)
    else:
        execute_ipynb(file_paths, exit_on_first, verbose)


if __name__ == "__main__":
    main()