From 9a2bb7f80444c3a0040b9aa49606c9e91bd6b5cb Mon Sep 17 00:00:00 2001 From: Dmitry Orlov Date: Fri, 17 Sep 2021 19:32:04 +0300 Subject: [PATCH] initial --- .coveragerc | 6 ++ .editorconfig | 25 ++++++ .github/workflows/tests.yml | 89 +++++++++++++++++++ .gitignore | 166 ++++++++++++++++++++++++++++++++++++ MANIFEST.in | 1 + README.rst | 67 +++++++++++++++ pylava.ini | 6 ++ pytest_rst.py | 88 +++++++++++++++++++ setup.py | 39 +++++++++ tox.ini | 42 +++++++++ 10 files changed, 529 insertions(+) create mode 100644 .coveragerc create mode 100644 .editorconfig create mode 100644 .github/workflows/tests.yml create mode 100644 .gitignore create mode 100644 MANIFEST.in create mode 100644 README.rst create mode 100644 pylava.ini create mode 100644 pytest_rst.py create mode 100644 setup.py create mode 100644 tox.ini diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..cc443de --- /dev/null +++ b/.coveragerc @@ -0,0 +1,6 @@ +[run] +branch = True +omit = + */env*/* + */tests/* + */.*/* diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..c2f53d6 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,25 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +charset = utf-8 +trim_trailing_whitespace = true + +[*.{py,yml}] +indent_style = space + +[*.py] +indent_size = 4 + +[docs/**.py] +max_line_length = 80 + +[*.rst] +indent_size = 4 + +[Makefile] +indent_style = tab + +[*.yml] +indent_size = 2 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..b28e455 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,89 @@ +# This workflow will install Python dependencies, run tests and lint with a variety of Python versions +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions + +name: tests + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + + runs-on: ${{ matrix.os }} + + strategy: + matrix: + include: + - python: '3.9' + toxenv: mypy + os: ubuntu-latest + + - python: '3.9' + toxenv: pylava + os: ubuntu-latest + + - python: '3.9' + toxenv: checkdoc + os: ubuntu-latest + + - python: '3.6' + toxenv: py36 + os: ubuntu-latest + - python: '3.7' + toxenv: py37 + os: ubuntu-latest + - python: '3.8' + toxenv: py38 + os: ubuntu-latest + - python: '3.9' + toxenv: py39 + os: ubuntu-latest + + - python: '3.6' + toxenv: py36 + os: macos-latest + - python: '3.7' + toxenv: py37 + os: macos-latest + - python: '3.8' + toxenv: py38 + os: macos-latest + - python: '3.9' + toxenv: py39 + os: macos-latest + + - python: '3.6' + toxenv: py36 + os: windows-latest + - python: '3.7' + toxenv: py37 + os: windows-latest + - python: '3.8' + toxenv: py38 + os: windows-latest + - python: '3.9' + toxenv: py39 + os: windows-latest + + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python }} + uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.python }} + + - name: Install tox + env: + COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_TOKEN }} + TOXENV: ${{ matrix.toxenv }} + run: pip install tox wheel + + - name: Tests + env: + COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_TOKEN }} + FORCE_COLOR: yes + TOXENV: ${{ matrix.toxenv }} + run: tox diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2833afe --- /dev/null +++ b/.gitignore @@ -0,0 +1,166 @@ +# Created by .ignore support plugin (hsz.mobi) +### VirtualEnv template +# Virtualenv +# http://iamzed.com/2009/05/07/a-primer-on-virtualenv/ +.Python +[Bb]in +[Ii]nclude +[Ll]ib +[Ll]ib64 +[Ll]ocal +[Ss]cripts +pyvenv.cfg +.venv +pip-selfcheck.json +### IPythonNotebook template +# Temporary data +.ipynb_checkpoints/ +### Python template +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*,cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ +docs/source/apidoc + +# PyBuilder +target/ + +# IPython Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# dotenv +.env + +# virtualenv +venv/ +ENV/ + +# Spyder project settings +.spyderproject + +# Rope project settings +.ropeproject +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff: +.idea/ + +## File-based project format: +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +/htmlcov +/temp +.DS_Store + +/*/version.py + +.pytest_cache + +# Created by https://www.gitignore.io/api/vim +# Edit at https://www.gitignore.io/?templates=vim + +### Vim ### +# Swap +[._]*.s[a-v][a-z] +[._]*.sw[a-p] +[._]s[a-rt-v][a-z] +[._]ss[a-gi-z] +[._]sw[a-p] + +# Session +Session.vim + +# Temporary +.netrwhist +*~ +# Auto-generated tag files +tags +# Persistent undo +[._]*.un~ + +# End of https://www.gitignore.io/api/vim diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..9561fb1 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1 @@ +include README.rst diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..944a758 --- /dev/null +++ b/README.rst @@ -0,0 +1,67 @@ +.. image:: https://coveralls.io/repos/github/mosquito/pytest-rst/badge.svg?branch=master + :target: https://coveralls.io/github/mosquito/pytest-rst + :alt: Coveralls + +.. image:: https://github.com/aiokitchen/pytest-rst/workflows/tox/badge.svg + :target: https://github.com/aiokitchen/pytest-rst/actions?query=workflow%3Atox + :alt: Actions + +.. image:: https://img.shields.io/pypi/v/pytest-rst.svg + :target: https://pypi.python.org/pypi/pytest-rst/ + :alt: Latest Version + +.. image:: https://img.shields.io/pypi/wheel/pytest-rst.svg + :target: https://pypi.python.org/pypi/pytest-rst/ + +.. image:: https://img.shields.io/pypi/pyversions/pytest-rst.svg + :target: https://pypi.python.org/pypi/pytest-rst/ + +.. image:: https://img.shields.io/pypi/l/pytest-rst.svg + :target: https://pypi.python.org/pypi/pytest-rst/ + + +pytest-rst run python tests in ReStructuredText +=============================================== + +Code block must have ``:name:`` attribute starts with ``test_``. + +Example +------- + +This block will running as a pytest test-case: + +.. code-block:: rst + + .. code-block:: python + :name: test_first + + assert True + +.. code-block:: python + :name: test_first + + assert True + + +This block just not running: + +.. code-block:: rst + + .. code-block:: python + + # not a test + assert False + +.. code-block:: python + + # not a test + assert False + + +Versioning +---------- + +This software follows `Semantic Versioning`_ + + +.. _Semantic Versioning: http://semver.org/ diff --git a/pylava.ini b/pylava.ini new file mode 100644 index 0000000..6470372 --- /dev/null +++ b/pylava.ini @@ -0,0 +1,6 @@ +[pylava] +ignore=C901,E252 +skip = *env*,.tox*,*build*,.* + +[pylava:pycodestyle] +max_line_length = 80 diff --git a/pytest_rst.py b/pytest_rst.py new file mode 100644 index 0000000..1662295 --- /dev/null +++ b/pytest_rst.py @@ -0,0 +1,88 @@ +from types import CodeType +from typing import Iterable, Optional, TextIO + +import docutils.frontend +import docutils.nodes +import docutils.parsers.rst +import docutils.utils +import py +import pytest + + +def parse_rst(fp: TextIO) -> docutils.nodes.document: + parser = docutils.parsers.rst.Parser() + settings = docutils.frontend.OptionParser( + read_config_files=False, + components=(docutils.parsers.rst.Parser,), + ).get_default_values() + + document = docutils.utils.new_document(fp.name, settings=settings) + parser.parse(fp.read(), document) + return document + + +class CodeVisitor(docutils.nodes.NodeVisitor): + def __init__(self, *args, **kwargs): + super(CodeVisitor, self).__init__(*args, **kwargs) + self.code_blocks = [] + + def unknown_visit(self, node: docutils.nodes.Node) -> None: + return + + def visit_literal_block(self, node: docutils.nodes.literal_block) -> None: + if frozenset(node["classes"]) != frozenset(["code", "python"]): + return + + names = node.get("names", []) + if not names: + return + + test_name = names[0] + if not test_name.startswith("test_"): + return + + self.code_blocks.append( + ( + test_name, + compile( + source=node.astext(), + filename=node.source, + mode="exec", + ) + ) + ) + + +class RSTTestItem(pytest.Item): + def __init__(self, name: str, parent: "RSTModule", code: CodeType): + super().__init__(name=name, parent=parent) + self.module = code + + def runtest(self) -> None: + exec(self.module, {"__name__": "__main__"}) + + +class RSTModule(pytest.Module): + obj = None + + def collect(self) -> Iterable["RSTTestItem"]: + with open(self.fspath, "r") as fp: + doc = parse_rst(fp) + visitor = CodeVisitor(doc) + doc.walk(visitor) + + for name, code in visitor.code_blocks: + yield RSTTestItem.from_parent( + name=name, + parent=self, + code=code, + ) + + +def pytest_collect_file( + path: py.path.local, parent: pytest.Collector, +) -> Optional[RSTModule]: + if path.ext != ".rst": + return None + + return RSTModule.from_parent(parent=parent, fspath=path) diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..d7f9330 --- /dev/null +++ b/setup.py @@ -0,0 +1,39 @@ +from setuptools import setup + + +setup( + name='pytest-rst', + version='0.0.3', + author='Dmitry Orlov', + author_email='me@mosquito.su', + license='MIT', + description='Test code from RST documents with pytest', + long_description=open("README.rst").read(), + platforms="all", + classifiers=[ + "Framework :: Pytest", + 'Intended Audience :: Developers', + 'Natural Language :: Russian', + 'Operating System :: MacOS', + 'Operating System :: POSIX', + 'Programming Language :: Python', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: Implementation :: CPython', + ], + packages=".", + install_requires=[ + "pytest", + "py", + "docutils", + "pygments", + ], + entry_points={ + "pytest11": ["pytest-rst = pytest_rst"] + }, + url='https://github.com/mosquito/pytest-rst' +) diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..e6bf51a --- /dev/null +++ b/tox.ini @@ -0,0 +1,42 @@ +[tox] +envlist = pylava,mypy,checkdoc,py3{6,7,8,9} + +[testenv] +passenv = COVERALLS_* TEST_* FORCE_COLOR +usedevelop = true + +deps = + pytest + pytest-cov + coveralls + +commands= + pytest -v --cov=pytest_rst --cov-report=term-missing --doctest-modules README.rst + - coveralls + +[testenv:pylava] +deps = + pylava + +commands= + pylava -o pylava.ini . + + +[testenv:mypy] +basepython=python3.9 +usedevelop = true + +deps = + mypy + +commands = + mypy --color-output --install-types --non-interactive pytest_rst.py + +[testenv:checkdoc] +skip_install=true +deps = + collective.checkdocs + pygments + +commands = + python setup.py checkdocs