pax_global_header00006660000000000000000000000064145713560610014522gustar00rootroot0000000000000052 comment=373e9afbe129437b583b2257be9206baa4c0fbb7 nbval-0.11.0/000077500000000000000000000000001457135606100127035ustar00rootroot00000000000000nbval-0.11.0/.github/000077500000000000000000000000001457135606100142435ustar00rootroot00000000000000nbval-0.11.0/.github/workflows/000077500000000000000000000000001457135606100163005ustar00rootroot00000000000000nbval-0.11.0/.github/workflows/release.yaml000066400000000000000000000021471457135606100206100ustar00rootroot00000000000000name: Release # always build releases (to make sure wheel-building works) # but only publish to PyPI on tags on: pull_request: paths-ignore: - "docs/**" - ".github/workflows/**" - "!.github/workflows/release.yaml" push: paths-ignore: - "docs/**" - ".github/workflows/**" - "!.github/workflows/release.yaml" branches-ignore: - "dependabot/**" - "pre-commit-ci-update-config" tags: - "**" workflow_dispatch: jobs: build-release: runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: python-version: "3.10" - name: install build package run: | pip install --upgrade pip pip install build pip freeze - name: build release run: | python -m build --sdist --wheel . ls -l dist - name: publish to pypi uses: pypa/gh-action-pypi-publish@v1.4.1 if: startsWith(github.ref, 'refs/tags/') with: user: __token__ password: ${{ secrets.pypi_password }} nbval-0.11.0/.github/workflows/tests.yml000066400000000000000000000013411457135606100201640ustar00rootroot00000000000000name: Tests on: push: pull_request: env: PYTEST_ADDOPTS: "--color=yes" jobs: tests: runs-on: ubuntu-latest strategy: matrix: python-version: [3.7, 3.8, 3.9, '3.10'] steps: - name: Checkout uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | python3 -m pip install --upgrade pip doit doit install_test_deps python3 -m pip install . python3 -m pip freeze # do make debugging easier for failures due to version changes - name: Test with pytest run: | doit test nbval-0.11.0/.gitignore000066400000000000000000000001541457135606100146730ustar00rootroot00000000000000__pycache__ *.pyc .ipynb_checkpoints/ /dist/ **/build/ *.egg-info .cache .pytest_cache .doit.db* .coverage* nbval-0.11.0/.hgignore000066400000000000000000000001021457135606100144770ustar00rootroot00000000000000# use glob syntax. syntax: glob *.swp .ipynb_checkpoints/ utils/ nbval-0.11.0/CONTRIBUTORS000066400000000000000000000001731457135606100145640ustar00rootroot00000000000000David Cortes-Ortuno Oliver Laslett Vidar Tonaas Fauske Thomas Kluyver Min RK Maximilian Albert Ondrej Hovorka Hans Fangohr nbval-0.11.0/INSTALL000066400000000000000000000001311457135606100137270ustar00rootroot00000000000000Install from project directory with: pip install . NOTE: This may require root access. nbval-0.11.0/LICENSE000066400000000000000000000035661457135606100137220ustar00rootroot00000000000000A py.test plugin for validating IPython notebooks License applies to all source code, and binaries generated from the source code, found at https://github.com/computationalmodelling/pytest_validate_nb ------------------------------------------------------------ Copyright (C) 2014 Oliver W. Laslett David Cortes-Ortuno Maximilian Albert Ondrej Hovorka Hans Fangohr (University of Southampton, UK) All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. Neither the names of the contributors nor the associated institutions may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. nbval-0.11.0/MANIFEST.in000066400000000000000000000002571457135606100144450ustar00rootroot00000000000000include LICENSE include documentation.ipynb include README.md include Makefile graft tests graft docs prune docs/build global-exclude .ipynb_checkpoints global-exclude *.pyc nbval-0.11.0/README.md000066400000000000000000000140421457135606100141630ustar00rootroot00000000000000# Py.test plugin for validating Jupyter notebooks [![Tests](https://github.com/computationalmodelling/nbval/actions/workflows/tests.yml/badge.svg)](https://github.com/computationalmodelling/nbval/actions/workflows/tests.yml) [![PyPI Version](https://badge.fury.io/py/nbval.svg)](https://pypi.python.org/pypi/nbval) [![Documentation Status](https://readthedocs.org/projects/nbval/badge/)](https://nbval.readthedocs.io/) The plugin adds functionality to py.test to recognise and collect Jupyter notebooks. The intended purpose of the tests is to determine whether execution of the stored inputs match the stored outputs of the `.ipynb` file. Whilst also ensuring that the notebooks are running without errors. The tests were designed to ensure that Jupyter notebooks (especially those for reference and documentation), are executing consistently. Each cell is taken as a test, a cell that doesn't reproduce the expected output will fail. See [`docs/source/index.ipynb`](http://nbviewer.jupyter.org/github/computationalmodelling/nbval/blob/HEAD/docs/source/index.ipynb) for the full documentation. ## Installation Available on PyPi: pip install nbval or install the latest version from cloning the repository and running: pip install . from the main directory. To uninstall: pip uninstall nbval ## How it works The extension looks through every cell that contains code in an IPython notebook and then the `py.test` system compares the outputs stored in the notebook with the outputs of the cells when they are executed. Thus, the notebook itself is used as a testing function. The output lines when executing the notebook can be sanitized passing an extra option and file, when calling the `py.test` command. This file is a usual configuration file for the `ConfigParser` library. Regarding the execution, roughly, the script initiates an IPython Kernel with a `shell` and an `iopub` sockets. The `shell` is needed to execute the cells in the notebook (it sends requests to the Kernel) and the `iopub` provides an interface to get the messages from the outputs. The contents of the messages obtained from the Kernel are organised in dictionaries with different information, such as time stamps of executions, cell data types, cell types, the status of the Kernel, username, etc. In general, the functionality of the IPython notebook system is quite complex, but a detailed explanation of the messages and how the system works, can be found here https://jupyter-client.readthedocs.io/en/latest/messaging.html#messaging ## Execution To execute this plugin, you need to execute `py.test` with the `nbval` flag to differentiate the testing from the usual python files: py.test --nbval You can also specify `--nbval-lax`, which runs notebooks and checks for errors, but only compares the outputs of cells with a `#NBVAL_CHECK_OUTPUT` marker comment. py.test --nbval-lax The commands above will execute all the `.ipynb` files and 'pytest' tests in the current folder. Specify `-p no:python` if you would like to execute notebooks only. Alternatively, you can execute a specific notebook: py.test --nbval my_notebook.ipynb By default, each `.ipynb` file will be executed using the kernel specified in its metadata. You can override this behavior by passing either `--nbval-kernel-name mykernel` to run all the notebooks using `mykernel`, or `--current-env` to use a kernel in the same environment in which pytest itself was launched. If the output lines are going to be sanitized, an extra flag, `--nbval-sanitize-with` together with the path to a confguration file with regex expressions, must be passed, i.e. py.test --nbval my_notebook.ipynb --nbval-sanitize-with path/to/my_sanitize_file where `my_sanitize_file` has the following structure. ``` [Section1] regex: [a-z]* replace: abcd regex: [1-9]* replace: 0000 [Section2] regex: foo replace: bar ``` The `regex` option contains the expression that is going to be matched in the outputs, and `replace` is the string that will replace the `regex` match. Currently, the section names do not have any meaning or influence in the testing system, it will take all the sections and replace the corresponding options. ### Coverage To use notebooks to generate coverage for imported code, use the pytest-cov plugin. nbval should automatically detect the relevant options and configure itself with it. ### Parallel execution nbval is compatible with the pytest-xdist plugin for parallel running of tests. It does however require the use of the `--dist loadscope` flag to ensure that all cells of one notebook are run on the same kernel. ## Documentation The narrative documentation for nbval can be found at https://nbval.readthedocs.io. ## Help The `py.test` system help can be obtained with `py.test -h`, which will show all the flags that can be passed to the command, such as the verbose `-v` option. Nbval's options can be found under the `Jupyter Notebook validation` section. ## Acknowledgements This plugin was inspired by Andrea Zonca's py.test plugin for collecting unit tests in the IPython notebooks (https://github.com/zonca/pytest-ipynb). The original prototype was based on the template in https://gist.github.com/timo/2621679 and the code of a testing system for notebooks https://gist.github.com/minrk/2620735 which we integrated and mixed with the `py.test` system. We acknowledge financial support from - OpenDreamKit Horizon 2020 European Research Infrastructures project (#676541), http://opendreamkit.org - EPSRC's Centre for Doctoral Training in Next Generation Computational Modelling, http://ngcm.soton.ac.uk (#EP/L015382/1) and EPSRC's Doctoral Training Centre in Complex System Simulation ((EP/G03690X/1), - The Gordon and Betty Moore Foundation through Grant GBMF #4856, by the Alfred P. Sloan Foundation and by the Helmsley Trust. ## Authors 2014 - 2017 David Cortes-Ortuno, Oliver Laslett, T. Kluyver, Vidar Fauske, Maximilian Albert, MinRK, Ondrej Hovorka, Hans Fangohr nbval-0.11.0/appveyor.yml000066400000000000000000000012411457135606100152710ustar00rootroot00000000000000# Do not build feature branch with open Pull Requests skip_branch_with_pr: true image: Visual Studio 2019 # environment variables environment: matrix: - PYTHON: "C:\\Python37-x64" - PYTHON: "C:\\Python38-x64" - PYTHON: "C:\\Python39-x64" # scripts that run after cloning repository install: # Ensure python scripts are from right version: - 'SET "PATH=%PYTHON%\Scripts;%PYTHON%;%PATH%"' # Update pip/setuptools: - python -m pip install --upgrade setuptools pip # Install our package: - pip install . - pip install doit - doit install_test_deps build: off # to run your custom scripts instead of automatic tests test_script: - doit test nbval-0.11.0/docs/000077500000000000000000000000001457135606100136335ustar00rootroot00000000000000nbval-0.11.0/docs/Makefile000066400000000000000000000011431457135606100152720ustar00rootroot00000000000000# Minimal makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = python3 -msphinx SPHINXPROJ = nbval SOURCEDIR = source BUILDDIR = build # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) .PHONY: help Makefile # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) nbval-0.11.0/docs/environment.yml000066400000000000000000000002111457135606100167140ustar00rootroot00000000000000name: nbval_docs channels: - conda-forge dependencies: - python=3.8 - numpy - matplotlib - sphinx - nbsphinx>=0.3.1 - sphinx-rtd-theme nbval-0.11.0/docs/make.bat000066400000000000000000000014031457135606100152360ustar00rootroot00000000000000@ECHO OFF pushd %~dp0 REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=python -msphinx ) set SOURCEDIR=source set BUILDDIR=build set SPHINXPROJ=nbval if "%1" == "" goto help %SPHINXBUILD% >NUL 2>NUL if errorlevel 9009 ( echo. echo.The Sphinx module was not found. Make sure you have Sphinx installed, echo.then set the SPHINXBUILD environment variable to point to the full echo.path of the 'sphinx-build' executable. Alternatively you may add the echo.Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from echo.http://sphinx-doc.org/ exit /b 1 ) %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% goto end :help %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% :end popd nbval-0.11.0/docs/source/000077500000000000000000000000001457135606100151335ustar00rootroot00000000000000nbval-0.11.0/docs/source/conf.py000066400000000000000000000121521457135606100164330ustar00rootroot00000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- # # nbval documentation build configuration file, created by # sphinx-quickstart on Fri Sep 1 14:00:11 2017. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # import os # import sys # sys.path.insert(0, os.path.abspath('.')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.intersphinx', 'sphinx.ext.mathjax', 'IPython.sphinxext.ipython_console_highlighting', 'nbsphinx', ] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix(es) of source filenames. source_suffix = {'.rst': 'restructuredtext'} # The master toctree document. master_doc = 'index' # General information about the project. project = 'nbval' copyright = '2017, Laslett, Cortes, Fauske, Kluyver, Pepper, Fangohr' author = 'Laslett, Cortes, Fauske, Kluyver, Pepper, Fangohr' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # _version_py = '../../nbval/_version.py' version_ns = {} exec(compile(open(_version_py).read(), _version_py, 'exec'), version_ns) # The short X.Y version. version = '%i.%i' % version_ns['version_info'][:2] # The full version, including alpha/beta/rc tags. release = version_ns['__version__'] # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = None # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path exclude_patterns = ['.ipynb_checkpoints'] # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'sphinx_rtd_theme' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # # html_theme_options = {} # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # -- Options for HTMLHelp output ------------------------------------------ # Output file base name for HTML help builder. htmlhelp_basename = 'nbvaldoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # # 'preamble': '', # Latex figure (float) alignment # # 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ (master_doc, 'nbval.tex', 'nbval Documentation', 'Laslett, Cortes, Fauske, Kluyver, Pepper, Fangohr', 'manual'), ] # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ (master_doc, 'nbval', 'nbval Documentation', [author], 1) ] # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ (master_doc, 'nbval', 'nbval Documentation', author, 'nbval', 'One line description of project.', 'Miscellaneous'), ] # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = {'https://docs.python.org/': None} nbval-0.11.0/docs/source/doc_sanitize000066400000000000000000000001621457135606100175300ustar00rootroot00000000000000[regex1] regex: \d{1,2}/\d{1,2}/\d{2,4} replace: DATE-STAMP [regex2] regex: \d{2}:\d{2}:\d{2} replace: TIME-STAMPnbval-0.11.0/docs/source/index.ipynb000066400000000000000000000560421457135606100173140ustar00rootroot00000000000000{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# IPython Notebook Validation for py.test - Documentation" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "One of the powerful uses of the IPython notebook is for documentation purposes, here we use a notebook to demonstrate the behaviour and usage of the IPython Notebook Validation plugin for py.test. The IPython notebook format `.ipynb` stores outputs as well as inputs. Validating the notebook means to rerun the notebook and make sure that it is generating the same output as has been stored.\n", "\n", "Therefore, the **user MUST make the following the distinction**:\n", "\n", "1. Running a notebook manually will likely change the output stored in the associated .ipynb file. These outputs will be used as references for the tests (i.e. the outputs from the last time you ran the notebook)\n", "2. Validating with py.test plugin - these tests run your notebook code seperately without storing the information, the outputs generated will be compared against those in the .ipynb file\n", "\n", "The purpose of the testing module is to ensure that the notebook is behaving as expected and that changes to underlying source code, haven't affected the results of an IPython notebook. For example, for documentation purposes - such as this." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Command line usage" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The py.test program doesn't usually collect notebooks for testing; by passing the `--nbval` flag at the command line, the IPython Notebook Validation plugin will collect and test notebook cells, comparing their outputs with those saved in the file.\n", "\n", "```\n", "$ py.test --nbval my_notebook.ipynb\n", "```\n", "\n", "There is also an option `--nbval-lax`, which collects notebooks and runs them, failing if there is an error. This mode does not check the output of cells unless they are marked with a special `#NBVAL_CHECK_OUTPUT` comment.\n", "\n", "```\n", "$ py.test --nbval-lax my_notebook.ipynb\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### REGEX Output sanitizing" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Since all output is captured by the IPython notebook, some pesky messages and prompts (with time-stamped messages, for example) may fail tests always, which might be expected. The plugin allows the user to specify a sanitizing file at the command prompt using the following flag:\n", "```\n", "$ py.test --nbval my_notebook.ipynb --sanitize-with my_sanitize_file\n", "```\n", "\n", "This sanitize file contains a number of REGEX replacements. It is recommended, when removing output for the tests, that you replace the removed output with some sort of marker, this helps with debugging. The following file is written to the folder of this notebook and can be used to sanitize its outputs:" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Writing doc_sanitize.cfg\n" ] } ], "source": [ "%%writefile doc_sanitize.cfg\n", "[regex1]\n", "regex: \\d{1,2}/\\d{1,2}/\\d{2,4}\n", "replace: DATE-STAMP\n", "\n", "[regex2]\n", "regex: \\d{2}:\\d{2}:\\d{2}\n", "replace: TIME-STAMP" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The first replacement finds dates in the given format replaces them with the label 'DATE-STAMP', likewise for strings that look like time. These will prevent the tests from failing due to time differences." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Validate this notebook" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This documentation is written as a Notebook. You can validate this notebook yourself, as shown below; the outputs that you see here are stored in the ipynb file. If your system produces different outputs, the testing process will fail. Just use the following commands:\n", "```\n", "$ cd /path/to/repo/docs/source\n", "$ py.test --nbval index.ipynb --sanitize-with doc_sanitize.cfg\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Examples of plugin behaviour" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The following examples demonstrate how the plugin behaves during testing. Test this notebook yourself to see the validation in action!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "These two imports produce no output as standard, if any **warnings** are printed out the cell will fail. Under normal operating conditions they will pass." ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import numpy as np\n", "import time" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If python doesn't consistently print 7, then something has gone terribly wrong. **Deterministic cells** are expected to pass everytime" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "7\n" ] } ], "source": [ "print(5+2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Random outputs** will always fail." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[0.36133679016382714, 0.5043774697891126, 0.23281910875007927, 0.2713065513128683]\n", "[0.5512421277985322, 0.02592706358897756, 0.05036036771084684, 0.7515926759190724]\n" ] } ], "source": [ "print([np.random.rand() for i in range(4)])\n", "print([np.random.rand() for i in range(4)])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Inconsistent number of lines** of output will cause an error to be thrown." ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1\n", "1\n", "1\n" ] } ], "source": [ "for i in range(np.random.randint(1, 8)):\n", " print(1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Because the **time and date** will change with each run, we would expect this cell to fail everytime. Using the sanitize file `doc_sanitize.cfg` (created above) you can clean up these outputs." ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "The time is: 15:28:30\n", "Today's date is: 21/12/16\n" ] } ], "source": [ "print('The time is: ' + time.strftime('%H:%M:%S'))\n", "print(\"Today's date is: \" + time.strftime('%d/%m/%y'))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Avoid output comparison for specific cells" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In case we want to avoid the testing process in specific input cells, we can write the comment ** #NBVAL_IGNORE_OUTPUT ** at the\n", "beginning of the them:" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "This is not going to be tested\n", "12544\n" ] } ], "source": [ "# NBVAL_IGNORE_OUTPUT\n", "print('This is not going to be tested')\n", "print(np.random.randint(1, 20000))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "There's also a counterpart, to ensure the output is tested even when using `--nbval-lax` :" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "This will be tested\n", "42\n" ] } ], "source": [ "# NBVAL_CHECK_OUTPUT\n", "print(\"This will be tested\")\n", "print(6 * 7)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note that unexecuted cells will always skip its output check:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print('This is not going to be tested when unrun')\n", "print(np.random.randint(1, 20000))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Skipping specific cells" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If, for some reason, a cell should not be executed during testing, the comment **# NBVAL_SKIP** can be used:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```python\n", "# NBVAL_SKIP\n", "print(\"Entering infinite loop...\")\n", "while True:\n", " pass\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Checking exceptions" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Sometimes, we might want to allow a notebook cell to raise an exception, and check that the traceback is as we expect. By annotating the cell with the comment ** # NBVAL_RAISES_EXCEPTION ** you can indicate that the cell is expected to raise an exception. The full traceback is not compared, but rather just that the raised exception is the same as the stored exception." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "This exception will be tested\n" ] }, { "ename": "RuntimeError", "evalue": "Foo", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mRuntimeError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;32mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"This exception will be tested\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mRuntimeError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"Foo\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mRuntimeError\u001b[0m: Foo" ] } ], "source": [ "# NBVAL_RAISES_EXCEPTION\n", "print(\"This exception will be tested\")\n", "raise RuntimeError(\"Foo\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This composes with the per-cell checking comments, so if you would like to avoid exceptions creating a test failure, but do not want to check the traceback, use `# NBVAL_IGNORE_OUTPUT`" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "If the raised exception doesn't match the stored exception, we get a failure\n" ] }, { "ename": "RuntimeError", "evalue": "Foo", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mRuntimeError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;31m# NBVAL_RAISES_EXCEPTION\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[0;32mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"If the raised exception doesn't match the stored exception, we get a failure\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 3\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mRuntimeError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"Foo\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mRuntimeError\u001b[0m: Foo" ] } ], "source": [ "# NBVAL_RAISES_EXCEPTION\n", "print(\"If the raised exception doesn't match the stored exception, we get a failure\")\n", "raise SyntaxError(\"Foo\")" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "This exception will not be checked, but will not cause a failure.\n" ] }, { "ename": "RuntimeError", "evalue": "Bar", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mRuntimeError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[0;31m# NBVAL_RAISES_EXCEPTION\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3\u001b[0m \u001b[0;32mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"This exception will not be checked, but will not cause a failure.\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 4\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mRuntimeError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"Bar\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mRuntimeError\u001b[0m: Bar" ] } ], "source": [ "# NBVAL_IGNORE_OUTPUT\n", "# NBVAL_RAISES_EXCEPTION\n", "print(\"This exception will not be checked, but will not cause a failure.\")\n", "raise RuntimeError(\"Bar\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Using tags instead of comments" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If you do not want to put nbval comment annotations in your notebook, or your source language is not compatible with such annotations, you can use cell tags instead. Cell tags are strings that are added to the cell metadata under the label \"tags\", and can be added and remove using the \"Tags\" toolbar from Notebook version 5. The tags that Nbval recognizes are the same as the comment names, except lowercase, and with dashes ('-') instead of underscores ('\\_'). For instance, the comment \"`NBVAL_IGNORE_OUTPUT`\" becomes the tag \"`nbval-ignore-output`\". However, for \"`NBVAL_RAISES_EXCEPTION`\", either \"`nbval-raises-exception`\" or the plain \"`raises-exception`\" tag can be used, since as of Notebook 5.1, the latter is a special tag that tells the Notebook cell executor to continue running normally after an exception is raised." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Figures" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "%matplotlib inline" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Currently, only the matplotlib text output of the Figure is compared, but it is possible to modify the plugin to allow comparison of the image whole string." ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAREAAAEACAYAAACUHkKwAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAADtRJREFUeJzt3X2MXmWZx/HvDysmBkXApawUZoiCrFKCkiDqsp0NawSz\nsRujiy+JoJtgjCwm/rEYlixgNqv418qKMUQ0oCFdZROo77iy48uqyEJrW6FQE1qgQEksxEWMW8m1\nf8wBH2ee6UznPjPPU/l+kpO5n3OuOffVM+kv55y5m6aqkKSlOmTUDUg6uBkikpoYIpKaGCKSmhgi\nkpoYIpKaNIVIkiOS3Jrk3iTfTnL4PHVPJ7kryaYkN7fMKWm8pGWdSJKrgF9W1SeTXAIcUVUfHVL3\nq6p6cUOfksZUa4hsB9ZV1Z4kxwDTVXXykLr/raoXNfQpaUy1vhM5uqr2AFTVo8DR89S9IMlPk/wo\nyfrGOSWNkVULFST5DrB6cBdQwGVDyue7rZmoqkeSnADclmRLVd1/wN1KGjsLhkhVvWm+Y0n2JFk9\n8Djz2DzneKT7en+SaeA1wJwQSeI/5JFGqKpyoN+zYIgsYCNwAXAVcD5wy+yCJC8Bnqqq/0vyUuAN\nXf08Lm9saTlMA1Mj7mGWdQV/ecWou/hD/3XF2PV0Vv0F6644a9Rt/IHvXfGDsetpLadyXt61pO9t\nfSdyFfCmJPcCZwOfAEhyepJru5o/A/4nySbgu8DHq2p747ySxkTTnUhV7QX+asj+O4ELu/GPgVNb\n5pE0vlyxuiiTo25grsmpUXcw1xj2NDF1/KhbmGMce2phiCzK5KgbmOuEqVF3MNcY9jQ5NTHqFuYY\nx55aGCKSmhgikpoYIpKaGCKSmhgikpoYIpKaGCKSmhgikpoYIpKaGCKSmhgikpoYIpKaGCKSmhgi\nkpoYIpKaGCKSmhgikpoYIpKaGCKSmhgikpoYIpKaGCKSmhgikpoYIpKaGCKSmhgikpr0EiJJzkmy\nPcl9SS4ZcvzQJBuS7Ejy4yR/XP8ZqfQc1hwiSQ4BPg28GXg18K4kJ88q+ztgb1WdCPwr8MnWeSWN\nhz7uRM4AdlTVrqraB2wA1s+qWQ9c341vAs7uYV5JY6CPEDkWeHDg80PdvqE1VfU08ESSI3uYW9KI\njerFakY0r6SererhHLuBwRela7p9gx4CjgMeTvI84MVVtXf46aYHxpPdJqlvO6d3sWv6AQC2c9+S\nz9NHiNwBvCLJBPAI8E7gXbNqvgqcD9wOvAO4bf7TTfXQkqSFTE5NMDk1AcBaTuWmK/9jSedpDpGq\nejrJRcCtzDweXVdV9yS5Erijqr4GXAd8MckO4JfMBI2kPwJ93IlQVd8CXjlr3+UD498Cf9vHXJLG\niytWJTUxRCQ1MUQkNTFEJDUxRCQ1MUQkNTFEJDUxRCQ1MUQkNTFEJDUxRCQ1MUQkNTFEJDUxRCQ1\nMUQkNTFEJDUxRCQ1MUQkNTFEJDUxRCQ1MUQkNTFEJDUxRCQ1MUQkNTFEJDUxRCQ1MUQkNTFEJDUx\nRCQ16SVEkpyTZHuS+5JcMuT4+UkeS3JXt72/j3kljd6q1hMkOQT4NHA28DBwR5Jbqmr7rNINVXVx\n63ySxksfdyJnADuqaldV7QM2AOuH1KWHuSSNmT5C5FjgwYHPD3X7Zntbks1JvpxkTQ/zShoDzY8z\ni7QRuLGq9iW5ELiemcefudbV78eT62ByagXaO/hc9k//OOoWDgofu+bjo25hbE3fB9M7ZsY/P+qU\nJZ+njxDZDRw/8HlNt+9ZVfX4wMfPAZ+c92xTl/fQkqSFTJ00swFw4lo+9qVtSzpPH48zdwCvSDKR\n5FDgnczceTwryTEDH9cDd/cwr6Qx0HwnUlVPJ7kIuJWZULququ5JciVwR1V9Dbg4yVuBfcBe4ILW\neSWNh17eiVTVt4BXztp3+cD4UuDSPuaSNF5csSqpiSEiqYkhIqmJISKpiSEiqYkhIqmJISKpiSEi\nqYkhIqmJISKpiSEiqYkhIqmJISKpiSEiqYkhIqmJISKpiSEiqYkhIqmJISKpiSEiqYkhIqmJISKp\niSEiqYkhIqmJISKpiSEiqYkhIqmJISKpSS8hkuS6JHuSbNlPzdVJdiTZnOS0PuaVNHp93Yl8AXjz\nfAeTnAu8vKpOBD4AfLaneSWNWC8hUlU/BB7fT8l64Iau9nbg8CSr+5hb0mit1DuRY4EHBz7v7vZJ\nOsitGnUDc0xf+fvx5DqYnBpZK9Ifs+n7YHpH9+GorUs+z0qFyG7guIHPa7p9c01dvhL9SM95UyfN\nbACcuJaPfWnbks7T5+NMum2YjcB7AZKcCTxRVXt6nFvSiPRyJ5LkRmAKOCrJA8DlwKFAVdW1VfWN\nJG9J8gvg18D7+phX0uj1EiJV9e5F1FzUx1ySxosrViU1MUQkNTFEJDUxRCQ1MUQkNTFEJDUxRCQ1\nMUQkNTFEJDUxRCQ1MUQkNTFEJDUxRCQ1MUQkNTFEJDUxRCQ1MUQkNTFEJDUxRCQ1MUQkNTFEJDUx\nRCQ1MUQkNTFEJDUxRCQ1MUQkNTFEJDUxRCQ16SVEklyXZE+SLfMcX5fkiSR3ddtlfcwrafRW9XSe\nLwD/Btywn5rvV9Vbe5pP0pjo5U6kqn4IPL5AWfqYS9J4Wcl3Imcm2ZTk60letYLzSlpGfT3OLORO\nYKKqnkpyLnAzcNKwwrNq3bPjianjmZyaWJkODzJXXvPxUbdwULjy70fdwfja2W0AR5+ydcnnWZEQ\nqaonB8bfTPKZJEdW1d7ZteuuOGslWpKe8ya7DeCUtWv5yrZtSzpPn48zYZ73HklWD4zPADIsQCQd\nfHq5E0lyIzAFHJXkAeBy4FCgqupa4O1JPgjsA34DnNfHvJJGr5cQqap3L3D8GuCaPuaSNF5csSqp\niSEiqYkhIqmJISKpiSEiqYkhIqmJISKpiSEiqYkhIqmJISKpiSEiqYkhIqmJISKpiSEiqYkhIqmJ\nISKpiSEiqYkhIqmJISKpiSEiqYkhIqmJISKpiSEiqYkhIqmJISKpiSEiqYkhIqmJISKpSXOIJFmT\n5LYkP0+yNcnF89RdnWRHks1JTmudV9J4WNXDOX4HfKSqNic5DLgzya1Vtf2ZgiTnAi+vqhOTvA74\nLHBmD3NLGrHmO5GqerSqNnfjJ4F7gGNnla0HbuhqbgcOT7K6dW5Jo9frO5Ekk8BpwO2zDh0LPDjw\neTdzg0bSQai3EOkeZW4CPtzdkUh6DujjnQhJVjETIF+sqluGlOwGjhv4vKbbN8f3rvjBs+OJqeOZ\nnJroo0VJs+zsNoC7t25d8nl6CRHg88DdVfWpeY5vBD4E/HuSM4EnqmrPsMJ1V5zVU0uS9mey2wBO\nWbuWr2zbtqTzNIdIkjcC7wG2JtkEFHApMAFUVV1bVd9I8pYkvwB+DbyvdV5J46E5RKrqv4HnLaLu\nota5JI0fV6xKamKISGpiiEhqYohIamKISGpiiEhqYohIamKISGpiiEhqYohIamKISGpiiEhqYohI\namKISGpiiEhqYohIamKISGpiiEhqYohIamKISGpiiEhqYohIamKISGpiiEhqYohIamKISGpiiEhq\nYohIatIcIknWJLktyc+TbE1y8ZCadUmeSHJXt13WOq+k8dDHncjvgI9U1auB1wMfSnLykLrvV9Vr\nu+2fe5h3xeyc3jXqFuaY3jHqDuYax552jrqBIXaOuoGeNYdIVT1aVZu78ZPAPcCxQ0rTOteo7Jp+\nYNQtzPG9MfwLO4497Rx1A0PsHHUDPev1nUiSSeA04PYhh89MsinJ15O8qs95JY3Oqr5OlOQw4Cbg\nw90dyaA7gYmqeirJucDNwEnDzvMyXtZXS715ES8au74efeEx8Cfj1RMvfHjsejrsmIf505eNWU8P\nj19PR5xwwpK/N1XV3ECSVcDXgG9W1acWUX8/cHpV7Z21v70ZSUtWVQf82qGvO5HPA3fPFyBJVlfV\nnm58BjPhtXd23VL+AJJGqzlEkrwReA+wNckmoIBLgQmgqupa4O1JPgjsA34DnNc6r6Tx0MvjjKTn\nrpGuWE1yRJJbk9yb5NtJDp+n7ulukdqmJDcvUy/nJNme5L4klww5fmiSDUl2JPlxkuOXo48l9HV+\nkscGFvK9f5n7uS7JniRb9lNzdXedNic5bTn7WUxPo1jsuJhFmF3dil2rZVsYWlUj24CrgH/oxpcA\nn5in7lfL3MchwC+YeQR7PrAZOHlWzQeBz3Tj84ANK3B9FtPX+cDVK/gz+3Nmfo2/ZZ7j5wJf78av\nA34yBj2tAzau1DXq5jwGOK0bHwbcO+Rnt6LXapE9HfC1GvW/nVkPXN+Nrwf+Zp665X7hegawo6p2\nVdU+YEPX26DBXm8Czl7mnhbbF6zgQr6q+iHw+H5K1gM3dLW3A4cnWT3inmCFFzvW4hZhrui1WmRP\ncIDXatQhcnR1v7WpqkeBo+epe0GSnyb5UZJhf4laHQs8OPD5IeZe3Gdrqupp4IkkRy5DLwfaF8Db\nutvhLydZs8w9LWR2z7sZ3vNKG9lix/0swhzZtepzYWhvi83mk+Q7wGC6hpnf4Ax71prvLe9EVT2S\n5ATgtiRbqur+nls9UOPy6+iNwI1VtS/JhczcLa3EXdLBZNGLHfu2wCLMkehrYegzlv1OpKreVFWn\nDmxru68bgT3P3L4lOQZ4bJ5zPNJ9vR+YBl7Tc5u7gcEXpWu6fYMeAo7ren0e8OIastZlpfuqqse7\nRx2AzwGnL3NPC9lNd506w67liqqqJ6vqqW78TeD5K3AX+cwizJuAL1bVLUNKVvxaLdTTUq7VqB9n\nNgIXdOPzgTl/qCQvSXJoN34p8Abg7p77uAN4RZKJbq53dr0N+mrXI8A7gNt67mFJfXXh+4z19H9t\nhgnz34ltBN4LkORM4IlnHllH1dPge4b9LXZcBvtdhMlortWCC0MHxou7Viv5xnrI2+Ijgf9k5i3x\nrcBLuv2nA9d249cDW4BNwM+AC5apl3O6PnYAH+32XQn8dTd+AfDl7vhPgMkVukYL9fUvwLbu+nwX\nOGmZ+7kReBj4LfAA8D7gA8CFAzWfZua3Sj8DXrsC12i/PQEfGrhGPwJetwI9vRF4mpnfqG0C7up+\nliO7VovpaSnXysVmkpqM+nFG0kHOEJHUxBCR1MQQkdTEEJHUxBCR1MQQkdTEEJHU5P8BicLjTxAC\nPVsAAAAASUVORK5CYII=\n", "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "plt.imshow(np.array([[i + j for i in range(3)]\n", " for j in range(3)]),\n", " interpolation='None'\n", " )" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "### Skipping certain output types" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In case nbval is comparing some cell outputs you do not care about, like:\n", "\n", "```Traceback:missing key: TESTING dict_keys(['stderr']) != REFERENCE dict_keys(['application/javascript', 'stderr'])```\n", "\n", "There is a workaround. Add the following to your conftest.py:" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": true }, "outputs": [], "source": [ "def pytest_collectstart(collector):\n", " if collector.fspath and collector.fspath.ext == '.ipynb':\n", " collector.skip_compare += 'text/html', 'application/javascript', 'stderr'," ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.10" } }, "nbformat": 4, "nbformat_minor": 1 } nbval-0.11.0/dodo.py000066400000000000000000000024431457135606100142050ustar00rootroot00000000000000import sys PYTHON = sys.executable DOIT_CONFIG = { 'default_tasks': ['test'], 'verbosity': 2, } def _make_cmd(cmd): import os if os.name == 'nt': return ' '.join(cmd) return cmd def _clean_dist_cmd(): import shutil try: shutil.rmtree("dist") except FileNotFoundError: pass def task_test(): return { 'actions': [ _make_cmd(["py.test", "-v", "tests/", "--nbval", "--nbval-current-env", "--nbval-sanitize-with", "tests/sanitize_defaults.cfg", "--ignore", "tests/ipynb-test-samples"]), ], } def task_install_test_deps(): # ipython_genutils is an indirect dependency of nbdime, but can be removed from this list # once https://github.com/jupyter/nbdime/pull/618 ends up in a release test_deps = ['matplotlib', 'sympy', 'pytest-cov', 'pytest-mock', 'nbdime', 'ipython_genutils'] return { 'actions': [_make_cmd(['pip', 'install'] + test_deps)], } def task_build_dists(): return { 'actions': [ (_clean_dist_cmd,), _make_cmd([PYTHON, "setup.py", "sdist"]), _make_cmd([PYTHON, "setup.py", "bdist_wheel"]), ], } def task_release(): return { 'actions': [["twine", "upload", "dist/*"]], 'task_dep': ['build_dists'] } nbval-0.11.0/issues/000077500000000000000000000000001457135606100142165ustar00rootroot00000000000000nbval-0.11.0/issues/27/000077500000000000000000000000001457135606100144465ustar00rootroot00000000000000nbval-0.11.0/issues/27/latex-example.ipynb000066400000000000000000000066401457135606100202650ustar00rootroot00000000000000{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Feature request for NBVAL\n", "For mathematical computation, it would be useful to compare $\\LaTeX$ output. Here is an example:" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [], "source": [ "import sympy\n", "\n", "sympy.init_printing()\n", "\n", "x, y = sympy.symbols([\"x\", \"y\"])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Should pass:" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAABQAAAAUBAMAAAB/pwA+AAAAMFBMVEX///8AAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAv3aB7AAAAD3RSTlMAEHarIkSJZt3NVLsy\nme8Q6PJIAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAAfElEQVQIHWNggIDcvQJQFpsD8wQok8OB5wuM\nGcDyFcpkYOD6CGdyKMCZuXAW+wU4M4whBsrmmbVqI5TJ9///BwYGIWVXNQWwCGMCexMHxFI2AbaP\nXA4QUQbODWAGiOBXgDPPH4AyuQT0GZgEwJz4BfsZ3CHCwkpC1wvATAAvUhbC3/cGUAAAAABJRU5E\nrkJggg==\n", "text/latex": [ "$$x^{2}$$" ], "text/plain": [ " 2\n", "x " ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x**2" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Should fail (in most cases):" ] }, { "cell_type": "code", "execution_count": 20, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import random\n", "n = int(random.uniform(1, 10000000000000))" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAHcAAAAUBAMAAABFd79NAAAAMFBMVEX///8AAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAv3aB7AAAAD3RSTlMAEHarIkSJZt3NVLsy\nme8Q6PJIAAAACXBIWXMAAA7EAAAOxAGVKw4bAAACI0lEQVQ4EZWRT2jTUBzHv0mTtM2y5lnB4xZa\n0Itl05OCh1xkJ20UJoKCoUpvgxy8DsqYZ4sggqfqaW7+yTwrBm87SDsnVfAyr7u4spZCVeLvlyzd\nDjs0D77JL+/7/bz3ey9ANLQXpwTe1AGjCdR2hHFlwQO2AL3rAvVzwNJHwYoMreZB+7kdo0AmDP/I\n83LH2Glg2pv21fAA+HIJKKOCvHMdmp1psiLjqbKLB7g8hhcXN01H6UEn2M3uq68FWWvAAFVRRhE5\n2+iz2JDnJQuraNuHtA7dqQYUJRignR021qD8hensUZlzlAGLjUKLJh6hGmWoBM5gNsAohmcC9WtF\nMCwR3BqV+Hh6j8WG+ZJNvONHPC7QgsYwguUnUJysH7W9h/bDf7hNmZzFYqPaURs0sUKKh+FD9+Vx\n23Tt/QjewNyNEbVO101BltGfbdH5oHUO0bj8fL4ft62MMkIiny7MKLXtAUwPWcqS2KClhsDymEXe\nhQSVtm9gJlCGBZtunmGBO3jGO9/CXRYbeY92Vl01SPCChylrKoC+S39F/qUJ7uot8B4fCH8F4/n6\nJosN1deb2FrfEAmcc6CWvkH5cWAZtfs26l3g2uOLuFmxke1uoxCG+6zIWF4S+B2GxBbLV89ayRop\n35KbXck1U0JJnA7R0+3kK+VbQt5PiRyPm9bxr5R1O0gJHMV1MQd5/MeO5ieq7rU+YWGi5Amh06Xi\nd++E+Umm/gMAdaLbwJw8+AAAAABJRU5ErkJggg==\n", "text/latex": [ "$$x^{7913519622562}$$" ], "text/plain": [ " 7913519622562\n", "x " ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x**n" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.6" } }, "nbformat": 4, "nbformat_minor": 2 } nbval-0.11.0/issues/67/000077500000000000000000000000001457135606100144525ustar00rootroot00000000000000nbval-0.11.0/issues/67/Makefile000066400000000000000000000001141457135606100161060ustar00rootroot00000000000000demo: py.test --nbval *ipynb demo-mixed: py.test --nbval *ipynb test*py nbval-0.11.0/issues/67/info.rst000066400000000000000000000022161457135606100161400ustar00rootroot00000000000000When running:: py.test --nbval notebook1.ipynb notebook2.ipynb the output is:: ======================================= test session starts =============== platform darwin -- Python 3.6.0, pytest-3.0.7, py-1.4.33, pluggy-0.4.0 rootdir: /Users/fangohr/git/nbval, inifile: plugins: nbval-0.6 collected 2 items notebook1.ipynb . notebook2.ipynb F ============================================ FAILURES ===================== _____________________________________________ cell 0 ______________________ Notebook cell execution failed Cell 0: Cell outputs differ Input: import time time.time() Traceback: mismatch 'text/plain' <<<<<<<<<<<< Reference output from ipynb file: 1498639847.528011 ============ disagrees with newly computed (test) output: 1498640219.550588 >>>>>>>>>>>> =============================== 1 failed, 1 passed in 2.07 seconds ======== This is not ideal as we know there is a fail in Cell 0, but we don't know in which file. (Here it is ``notebook1.ipynb``). Suggest that the name of the file in which the reported failures take place is displayed somehow. nbval-0.11.0/issues/67/notebook1.ipynb000066400000000000000000000013701457135606100174170ustar00rootroot00000000000000{ "cells": [ { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "8" ] }, "execution_count": 1, "metadata": {}, "output_type": "execute_result" } ], "source": [ "4 + 4" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.0" } }, "nbformat": 4, "nbformat_minor": 2 } nbval-0.11.0/issues/67/notebook2.ipynb000066400000000000000000000016431457135606100174230ustar00rootroot00000000000000{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Create cell that fails with nbval" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "1498639847.528011" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import time\n", "\n", "time.time()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.0" } }, "nbformat": 4, "nbformat_minor": 2 } nbval-0.11.0/issues/67/test_1.py000066400000000000000000000000271457135606100162220ustar00rootroot00000000000000def test_a(): pass nbval-0.11.0/issues/67/test_2.py000066400000000000000000000001101457135606100162140ustar00rootroot00000000000000def test_b(): raise AssertionError def test_c(): assert 1 == 2 nbval-0.11.0/issues/7/000077500000000000000000000000001457135606100143645ustar00rootroot00000000000000nbval-0.11.0/issues/7/issue.ipynb000066400000000000000000000210071457135606100165570ustar00rootroot00000000000000{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# This notebook describes issue 7" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Use case" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The files in `issues/7` show a typical use case for the feature request of this issue: there is some library of useful code `lib.py` that needs testing. Some of the functionality is covered in the file `test_lib.py`. \n", "\n", "In particular, there are two functions defined in `lib.py` in this simple example: `mysum()` and `myprod()`. The tests in `test_lib.py` only test the `mysum()` function.\n", "\n", "We can measure the test coverage using:" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\u001b[1m============================= test session starts ==============================\u001b[0m\r\n", "platform darwin -- Python 3.4.4, pytest-2.8.5, py-1.4.31, pluggy-0.3.1 -- /Users/fangohr/anaconda/bin/python\r\n", "cachedir: ../../.cache\r\n", "rootdir: /Users/fangohr/git/nbval, inifile: \r\n", "plugins: hypothesis-1.12.0, nbval-0.3, cov-2.1.0\r\n", "\u001b[1m\r", "collecting 0 items\u001b[0m\u001b[1m\r", "collecting 1 items\u001b[0m\u001b[1m\r", "collected 1 items \r\n", "\u001b[0m\r\n", "test_lib.py::test_sum \u001b[32mPASSED\u001b[0m\r\n", "--------------- coverage: platform darwin, python 3.4.4-final-0 ----------------\r\n", "Name Stmts Miss Cover\r\n", "---------------------------\r\n", "lib 4 1 75%\r\n", "\r\n", "\u001b[1m\u001b[32m=========================== 1 passed in 0.01 seconds ===========================\u001b[0m\r\n" ] } ], "source": [ "!py.test -v --cov lib.py test_lib.py \n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The 75% coverage here come from 3 lines of code that are covered, and one that isn't. The 2 functions each have the line containing the `def` keyword, which is always executed, and one line in the body of the function which is covered through tests for `mysum` but not for `myprod`; thus 3/4 = 75%." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Including the notebook in the test coverage\n", "\n", "It is not unusual to have a notebook that documents how to use functions in the library. In this example, the notebook `tutorial_lib.ipynb` calls `mysum` and `myprod` with different values to demonstrate how they work. \n", "\n", "Using nbval, we can treat those calls as tests:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\u001b[1m============================= test session starts ==============================\u001b[0m\n", "platform darwin -- Python 3.4.4, pytest-2.8.5, py-1.4.31, pluggy-0.3.1 -- /Users/fangohr/anaconda/bin/python\n", "cachedir: ../../.cache\n", "rootdir: /Users/fangohr/git/nbval, inifile: \n", "plugins: hypothesis-1.12.0, nbval-0.3, cov-2.1.0\n", "collected 9 items \n", "\u001b[0m\n", "tutorial_lib.ipynb::Cell 1 \u001b[32mPASSED\u001b[0m\n", "tutorial_lib.ipynb::Cell 3 \u001b[32mPASSED\u001b[0m\n", "tutorial_lib.ipynb::Cell 4 \u001b[32mPASSED\u001b[0m\n", "tutorial_lib.ipynb::Cell 5 \u001b[32mPASSED\u001b[0m\n", "tutorial_lib.ipynb::Cell 7 \u001b[32mPASSED\u001b[0m\n", "tutorial_lib.ipynb::Cell 9 \u001b[32mPASSED\u001b[0m\n", "tutorial_lib.ipynb::Cell 10 \u001b[32mPASSED\u001b[0m\n", "tutorial_lib.ipynb::Cell 11 \u001b[32mPASSED\u001b[0m\n", "tutorial_lib.ipynb::Cell 13 \u001b[32mPASSED\u001b[0m\n", "\n", "\u001b[32m\u001b[1m=========================== 9 passed in 2.22 seconds ===========================\u001b[0m\n" ] } ], "source": [ "!py.test -v --nbval tutorial_lib.ipynb " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## The feature request\n", "\n", "The feature request in this issue is to support measuring of the code coverage that takes place when the nbval tests are carried out in the output of the coverage tool. Currently, the function calls from the nbval execution are not recorded:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\u001b[1m============================= test session starts ==============================\u001b[0m\n", "platform darwin -- Python 3.4.4, pytest-2.8.5, py-1.4.31, pluggy-0.3.1 -- /Users/fangohr/anaconda/bin/python\n", "cachedir: ../../.cache\n", "rootdir: /Users/fangohr/git/nbval, inifile: \n", "plugins: hypothesis-1.12.0, nbval-0.3, cov-2.1.0\n", "collected 9 items \n", "\u001b[0m\n", "tutorial_lib.ipynb::Cell 1 \u001b[32mPASSED\u001b[0m\n", "tutorial_lib.ipynb::Cell 3 \u001b[32mPASSED\u001b[0m\n", "tutorial_lib.ipynb::Cell 4 \u001b[32mPASSED\u001b[0m\n", "tutorial_lib.ipynb::Cell 5 \u001b[32mPASSED\u001b[0m\n", "tutorial_lib.ipynb::Cell 7 \u001b[32mPASSED\u001b[0m\n", "tutorial_lib.ipynb::Cell 9 \u001b[32mPASSED\u001b[0m\n", "tutorial_lib.ipynb::Cell 10 \u001b[32mPASSED\u001b[0m\n", "tutorial_lib.ipynb::Cell 11 \u001b[32mPASSED\u001b[0m\n", "tutorial_lib.ipynb::Cell 13 \u001b[32mPASSED\u001b[0mCoverage.py warning: No data was collected.\n", "\n", "--------------- coverage: platform darwin, python 3.4.4-final-0 ----------------\n", "Name Stmts Miss Cover\n", "---------------------------\n", "\n", "\u001b[1m\u001b[32m=========================== 9 passed in 2.29 seconds ===========================\u001b[0m\n" ] } ], "source": [ "!py.test -v --nbval --cov lib.py tutorial_lib.ipynb" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Ideally, we'd like to see 100% code coverage in `lib.py` if we run the above command, or the one below which combines the tests from the notebook (via nbval) and those explicitely coded in `test_lib.py`:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\u001b[1m============================= test session starts ==============================\u001b[0m\n", "platform darwin -- Python 3.4.4, pytest-2.8.5, py-1.4.31, pluggy-0.3.1 -- /Users/fangohr/anaconda/bin/python\n", "cachedir: ../../.cache\n", "rootdir: /Users/fangohr/git/nbval, inifile: \n", "plugins: hypothesis-1.12.0, nbval-0.3, cov-2.1.0\n", "collected 10 items \n", "\u001b[0m\n", "test_lib.py::test_sum \u001b[32mPASSED\u001b[0m\n", "tutorial_lib.ipynb::Cell 1 \u001b[32mPASSED\u001b[0m\n", "tutorial_lib.ipynb::Cell 3 \u001b[32mPASSED\u001b[0m\n", "tutorial_lib.ipynb::Cell 4 \u001b[32mPASSED\u001b[0m\n", "tutorial_lib.ipynb::Cell 5 \u001b[32mPASSED\u001b[0m\n", "tutorial_lib.ipynb::Cell 7 \u001b[32mPASSED\u001b[0m\n", "tutorial_lib.ipynb::Cell 9 \u001b[32mPASSED\u001b[0m\n", "tutorial_lib.ipynb::Cell 10 \u001b[32mPASSED\u001b[0m\n", "tutorial_lib.ipynb::Cell 11 \u001b[32mPASSED\u001b[0m\n", "tutorial_lib.ipynb::Cell 13 \u001b[32mPASSED\u001b[0m\n", "--------------- coverage: platform darwin, python 3.4.4-final-0 ----------------\n", "Name Stmts Miss Cover\n", "---------------------------\n", "lib 4 1 75%\n", "\n", "\u001b[32m\u001b[1m========================== 10 passed in 2.32 seconds ===========================\u001b[0m\n" ] } ], "source": [ "!py.test -v --nbval --cov lib.py test_lib.py tutorial_lib.ipynb" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.4.4" } }, "nbformat": 4, "nbformat_minor": 0 } nbval-0.11.0/issues/7/lib.py000066400000000000000000000001061457135606100155010ustar00rootroot00000000000000def mysum(a, b): return a + b def myprod(a, b): return a * b nbval-0.11.0/issues/7/test_lib.py000066400000000000000000000002241457135606100165410ustar00rootroot00000000000000import lib def test_sum(): assert lib.mysum(1, 3) == 4 assert lib.mysum("cat", "dog") == "catdog" assert lib.mysum(1.5, 2) == 3.5 nbval-0.11.0/issues/7/tutorial_lib.ipynb000066400000000000000000000073411457135606100201250ustar00rootroot00000000000000{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "We start by importing the module" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import lib" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Then we demonstrate what it can do" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "4" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "lib.mysum(1, 3)" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'catdog'" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "lib.mysum(\"cat\", \"dog\")" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "3.5" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "lib.mysum(1.5, 2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The above provides tests that correspond to the content of `test_lib.py`:" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "import lib\r\n", "\r\n", "def test_sum():\r\n", " assert lib.mysum(1, 3) == 4\r\n", " assert lib.mysum(\"cat\", \"dog\") == \"catdog\"\r\n", " assert lib.mysum(1.5, 2) == 3.5\r\n", " \r\n" ] } ], "source": [ "!cat test_lib.py" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we use this notebook to also demonstrate the mysum function:" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "3" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "lib.myprod(1, 3)" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "6.25" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "lib.myprod(2.5, 2.5)" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'catcat'" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "lib.myprod(2, \"cat\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Issue 7 is about counting the execution of the myprod code as part of the 'tested' code in output of the coverage tool." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.6" } }, "nbformat": 4, "nbformat_minor": 1 } nbval-0.11.0/nbval/000077500000000000000000000000001457135606100140055ustar00rootroot00000000000000nbval-0.11.0/nbval/__init__.py000066400000000000000000000001501457135606100161120ustar00rootroot00000000000000""" A pytest plugin for testing and validating ipython notebooks """ from ._version import __version__ nbval-0.11.0/nbval/_cover4.py000066400000000000000000000136061457135606100157260ustar00rootroot00000000000000""" Code to enable coverage of any external code called by the notebook. """ import os import coverage import warnings # Coverage setup/teardown code to run in kernel # Inspired by pytest-cov code. _python_setup = """\ import coverage __cov = coverage.Coverage( data_file=%r, source=%r, config_file=%r, auto_data=True, data_suffix='nbval', ) __cov.load() __cov.start() __cov._warn_no_data = False __cov._warn_unimported_source = False """ _python_teardown = """\ __cov.stop() __cov.save() """ def setup_coverage(config, kernel, floc, output_loc=None): """Start coverage reporting in kernel. Currently supported kernel languages are: - Python """ language = kernel.language if language.startswith('python'): # Get the pytest-cov coverage object cov = get_cov(config) if cov: # If present, copy the data file location used by pytest-cov data_file = os.path.abspath(cov.config.data_file) else: # Fall back on output_loc and current dir if not data_file = os.path.abspath(os.path.join(output_loc or os.getcwd(), '.coverage')) # Get options from pytest-cov's command line arguments: source = config.option.cov_source config_file = config.option.cov_config if isinstance(config_file, str) and os.path.isfile(config_file): config_file = os.path.abspath(config_file) # Build setup command and execute in kernel: cmd = _python_setup % (data_file, source, config_file) msg_id = kernel.kc.execute(cmd, stop_on_error=False) kernel.await_idle(msg_id, 60) # A minute should be plenty to enable coverage else: warnings.warn_explicit( 'Coverage currently not supported for language %r.' % language, category=UserWarning, filename=floc[0] if floc else '', lineno=0 ) return def teardown_coverage(config, kernel, output_loc=None): """Finish coverage reporting in kernel. The coverage should previously have been started with setup_coverage. """ language = kernel.language if language.startswith('python'): # Teardown code does not require any input, simply execute: msg_id = kernel.kc.execute(_python_teardown) kernel.await_idle(msg_id, 60) # A minute should be plenty to write out coverage # Ensure we merge our data into parent data of pytest-cov, if possible cov = get_cov(config) _merge_nbval_coverage_data(cov) else: # Warnings should be given on setup, or there might be no teardown # for a specific language, so do nothing here pass def get_cov(config): """Returns the coverage object of pytest-cov.""" # Check with hasplugin to avoid getplugin exception in older pytest. if config.pluginmanager.hasplugin('_cov'): plugin = config.pluginmanager.getplugin('_cov') if plugin.cov_controller: return plugin.cov_controller.cov return None def _merge_nbval_coverage_data(cov): """Merge nbval coverage data into pytest-cov data.""" if not cov: return if coverage.version_info > (5, 0): data = cov.get_data() nbval_data = coverage.CoverageData(data.data_filename(), suffix='.nbval', debug=cov.debug) nbval_data.read() cov.get_data().update(nbval_data, aliases=aliases) else: # Get the filename of the nbval coverage: filename = cov.data_files.filename + '.nbval' # Read coverage generated by nbval in this run: nbval_data = coverage.CoverageData(debug=cov.debug) try: nbval_data.read_file(os.path.abspath(filename)) except coverage.CoverageException: return # Set up aliases (following internal coverage.py code here) aliases = None if cov.config.paths: aliases = coverage.files.PathAliases() for paths in cov.config.paths.values(): result = paths[0] for pattern in paths[1:]: aliases.add(pattern, result) # Merge nbval data into pytest-cov data: cov.data.update(nbval_data, aliases=aliases) # Delete our nbval coverage data coverage.misc.file_be_gone(filename) """ Note about coverage data/datafiles: When pytest is running, we get the pytest-cov coverage object. This object tracks its own coverage data, which is stored in its data file. For several reasons detailed below, we cannot use the same file in the kernel, so we have to ensure our own, and then ensure that they are all merged correctly at the end. The important factor here is the data_suffix attribute which might be set. Cases: 1. data_suffix is set to None: No suffix is used by pytest-cov. We need to create a new file, so we add a suffix for kernel, and then merge this file into the pytest-cov data at teardown. 2. data_suffix is set to a string: We need to create a new file, so we append a string to the suffix passed to the kernel. We merge this file into the pytest-cov data at teardown. 3. data_suffix is set to True: The suffix will be autogenerated by coverage.py, along the lines of 'hostname.pid.random'. This is typically used for parallel tests. We pass True as suffix to kernel, ensuring a unique auto-suffix later. We cannot merge this data into the pytest-cov one, as we do not know the suffix, but we can just leave the data for automatic collection. However, this might lead to a warning about no coverage data being collected by the pytest-cov collector. Why do we need our own coverage data file? Coverage data can get lost if we try to sync via load/save/load cycles between the two. By having our own file, we can do an in-memory merge of the data afterwards using the official API. Either way, the data will always be merged to one coverage file in the end, so these files are transient. """ nbval-0.11.0/nbval/_cover5.py000066400000000000000000000056151457135606100157300ustar00rootroot00000000000000""" Code to enable coverage of any external code called by the notebook. For coverage.py >= v 5.0.0 """ import os import coverage import warnings # Coverage setup/teardown code to run in kernel # Inspired by pytest-cov code. _python_setup = """\ import coverage __cov = coverage.Coverage( data_file=%r, source=%r, config_file=%r, auto_data=True, data_suffix='.nbval', ) __cov.load() __cov.start() __cov._warn_no_data = False __cov._warn_unimported_source = False """ _python_teardown = """\ __cov.stop() __cov.save() """ def setup_coverage(config, kernel, floc, output_loc=None): """Start coverage reporting in kernel. Currently supported kernel languages are: - Python """ language = kernel.language if language.startswith('python'): # Get the pytest-cov coverage object cov = get_cov(config) if cov: # If present, copy the data file location used by pytest-cov data_file = os.path.abspath(cov.get_data().data_filename()) else: # Fall back on output_loc and current dir if not data_file = os.path.abspath(os.path.join(output_loc or os.getcwd(), '.coverage')) # Get options from pytest-cov's command line arguments: source = config.option.cov_source config_file = config.option.cov_config if isinstance(config_file, str) and os.path.isfile(config_file): config_file = os.path.abspath(config_file) # Build setup command and execute in kernel: cmd = _python_setup % (data_file, source, config_file) msg_id = kernel.kc.execute(cmd, stop_on_error=False) kernel.await_idle(msg_id, 60) # A minute should be plenty to enable coverage else: warnings.warn_explicit( 'Coverage currently not supported for language %r.' % language, category=UserWarning, filename=floc[0] if floc else '', lineno=0 ) return def teardown_coverage(config, kernel, output_loc=None): """Finish coverage reporting in kernel. The coverage should previously have been started with setup_coverage. """ language = kernel.language if language.startswith('python'): # Teardown code does not require any input, simply execute: msg_id = kernel.kc.execute(_python_teardown) kernel.await_idle(msg_id, 60) # A minute should be plenty to write out coverage else: # Warnings should be given on setup, or there might be no teardown # for a specific language, so do nothing here pass def get_cov(config): """Returns the coverage object of pytest-cov.""" # Check with hasplugin to avoid getplugin exception in older pytest. if config.pluginmanager.hasplugin('_cov'): plugin = config.pluginmanager.getplugin('_cov') if plugin.cov_controller: return plugin.cov_controller.cov return None nbval-0.11.0/nbval/_version.py000066400000000000000000000001511457135606100162000ustar00rootroot00000000000000version_info = (0, 11, 0) __version__ = '.'.join(map(str, version_info[:3])) + ''.join(version_info[3:]) nbval-0.11.0/nbval/cover.py000066400000000000000000000002651457135606100155000ustar00rootroot00000000000000 import coverage if coverage.version_info >= (5, 0, 0): from ._cover5 import setup_coverage, teardown_coverage else: from ._cover4 import setup_coverage, teardown_coverage nbval-0.11.0/nbval/kernel.py000066400000000000000000000162221457135606100156420ustar00rootroot00000000000000""" pytest ipython plugin modification Authors: D. Cortes, O. Laslett, T. Kluyver, H. Fangohr, V.T. Fauske """ import os import logging from pprint import pformat try: from Queue import Empty except: from queue import Empty # Kernel for jupyter notebooks from jupyter_client.manager import KernelManager from jupyter_client.kernelspec import KernelSpecManager import ipykernel.kernelspec CURRENT_ENV_KERNEL_NAME = ':nbval-parent-env' logger = logging.getLogger('nbval') # Uncomment to debug kernel communication: # logger.setLevel('DEBUG') # logging.basicConfig(format="[%(asctime)s - %(name)s - %(levelname)s] %(message)s") class NbvalKernelspecManager(KernelSpecManager): """Kernel manager that also allows for python kernel in parent environment """ def get_kernel_spec(self, kernel_name): """Returns a :class:`KernelSpec` instance for the given kernel_name. Raises :exc:`NoSuchKernel` if the given kernel name is not found. """ if kernel_name == CURRENT_ENV_KERNEL_NAME: return self.kernel_spec_class( resource_dir=ipykernel.kernelspec.RESOURCES, **ipykernel.kernelspec.get_kernel_dict()) else: return super(NbvalKernelspecManager, self).get_kernel_spec(kernel_name) def start_new_kernel(startup_timeout=60, kernel_name='python', **kwargs): """Start a new kernel, and return its Manager and Client""" logger.debug('Starting new kernel: "%s"' % kernel_name) km = KernelManager(kernel_name=kernel_name, kernel_spec_manager=NbvalKernelspecManager()) km.start_kernel(**kwargs) kc = km.client() kc.start_channels() try: kc.wait_for_ready(timeout=startup_timeout) except RuntimeError: logger.exception('Failure starting kernel "%s"', kernel_name) kc.stop_channels() km.shutdown_kernel() raise return km, kc class RunningKernel(object): """ Running a Kernel a Jupyter, info can be found at: http://jupyter-client.readthedocs.org/en/latest/messaging.html The purpose of this class is to encapsulate interaction with the jupyter kernel. Thus any changes on the jupyter side to how kernels are started/managed should not require any changes outside this class. """ def __init__(self, kernel_name, cwd=None, startup_timeout=60): """ Initialise a new kernel specify that matplotlib is inline and connect the stderr. Stores the active kernel process and its manager. """ self.km, self.kc = start_new_kernel( startup_timeout=startup_timeout, kernel_name=kernel_name, stderr=open(os.devnull, 'w'), cwd=cwd, ) self._ensure_iopub_up() def _ensure_iopub_up(self): total_timeout = 30 individual_timeout = 1 shell_timeout = 10 for _ in range(total_timeout // individual_timeout): msg_id = self.kc.kernel_info() try: self.await_reply(msg_id, timeout=shell_timeout) except Empty: raise RuntimeError('Kernel info reqest timed out after %d seconds!' % shell_timeout) try: self.await_idle(msg_id, individual_timeout) except Empty: continue else: # got IOPub break else: raise RuntimeError("Wasn't able to establish IOPub after %d seconds." % total_timeout) def get_message(self, stream, timeout=None): """ Function is used to get a message from the iopub channel. Timeout is None by default When timeout is reached """ try: if stream == 'iopub': msg = self.kc.get_iopub_msg(timeout=timeout) elif stream == 'shell': msg = self.kc.get_shell_msg(timeout=timeout) else: raise ValueError('Invalid stream specified: "%s"' % stream) except Empty: logger.debug('Kernel: Timeout waiting for message on %s', stream) raise logger.debug("Kernel message (%s):\n%s", stream, pformat(msg)) return msg def execute_cell_input(self, cell_input, allow_stdin=None): """ Executes a string of python code in cell input. We do not allow the kernel to make requests to the stdin this is the norm for notebooks Function returns a unique message id of the reply from the kernel. """ if cell_input: logger.debug('Executing cell: "%s"...', cell_input.splitlines()[0][:40]) else: logger.debug('Executing empty cell') return self.kc.execute( cell_input, store_history=False, allow_stdin=allow_stdin, stop_on_error=False, ) def await_reply(self, msg_id, timeout=None): """ Continuously poll the kernel 'shell' stream for messages until: - It receives an 'execute_reply' status for the given message id - The timeout is reached awaiting a message, in which case a `Queue.Empty` exception will be raised. """ while True: msg = self.get_message(stream='shell', timeout=timeout) # Is this the message we are waiting for? if msg['parent_header'].get('msg_id') == msg_id: if msg['content']['status'] == 'aborted': # This should not occur! raise RuntimeError('Kernel aborted execution request') return def await_idle(self, parent_id, timeout): """Poll the iopub stream until an idle message is received for the given parent ID""" while True: # Get a message from the kernel iopub channel msg = self.get_message(timeout=timeout, stream='iopub') # raises Empty on timeout! if msg['parent_header'].get('msg_id') != parent_id: continue if msg['msg_type'] == 'status': if msg['content']['execution_state'] == 'idle': break def is_alive(self): if hasattr(self, 'km'): return self.km.is_alive() return False # These options are in case we wanted to restart the nb every time # it is executed a certain task def restart(self): """ Instructs the kernel manager to restart the kernel process now. """ logger.debug('Restarting kernel') self.km.restart_kernel(now=True) def interrupt(self): """ Instructs the kernel to stop whatever it is doing, and await further commands. """ logger.debug('Interrupting kernel') self.km.interrupt_kernel() def stop(self): """ Instructs the kernel process to stop channels and the kernel manager to then shutdown the process. """ logger.debug('Stopping kernel') self.kc.stop_channels() self.km.shutdown_kernel(now=True) del self.km @property def language(self): if self.km.kernel_spec is None: return None return self.km.kernel_spec.language nbval-0.11.0/nbval/nbdime_reporter.py000066400000000000000000000116751457135606100175510ustar00rootroot00000000000000""" pytest ipython plugin modification - Nbdime reporter Authors: V.T. Fauske """ # import the pytest API import pytest try: from _pytest.main import ExitCode EXIT_OK = ExitCode.OK EXIT_TESTSFAILED = ExitCode.TESTS_FAILED EXIT_INTERRUPTED = ExitCode.INTERRUPTED EXIT_USAGEERROR = ExitCode.USAGE_ERROR EXIT_NOTESTSCOLLECTED = ExitCode.NO_TESTS_COLLECTED except ImportError: # pytest < 0.5.0 from _pytest.main import EXIT_OK, EXIT_TESTSFAILED, EXIT_INTERRUPTED, \ EXIT_USAGEERROR, EXIT_NOTESTSCOLLECTED import re import copy import tempfile import os import shutil import io import nbformat import nbdime from nbdime.webapp.nbdiffweb import run_server, browse from .plugin import IPyNbCell, bcolors nbdime.log.set_nbdime_log_level('ERROR') _re_nbval_nodeid = re.compile(r'.*\.ipynb::Cell \d+') class NbdimeReporter: def __init__(self, config, file=None): self.config = config self.verbosity = self.config.option.verbose self._numcollected = 0 self.nbval_items = [] self.nb_ref = nbformat.v4.new_notebook() self.nb_test = nbformat.v4.new_notebook() self.stats = {} # ---- These functions store captured test items and reports ---- def pytest_runtest_logreport(self, report): """Store all test reports for evaluation on finish""" rep = report res = self.config.hook.pytest_report_teststatus(report=rep) cat, letter, word = res self.stats.setdefault(cat, []).append(rep) def pytest_collectreport(self, report): """Store all collected nbval tests for evaluation on finish """ items = [x for x in report.result if isinstance(x, IPyNbCell)] self.nbval_items.extend(items) self._numcollected += len(items) # ---- Code below writes up report ---- @pytest.hookimpl(hookwrapper=True) def pytest_sessionfinish(self, exitstatus): """Called when test session has finished. """ outcome = yield outcome.get_result() summary_exit_codes = ( EXIT_OK, EXIT_TESTSFAILED, EXIT_INTERRUPTED, EXIT_USAGEERROR, EXIT_NOTESTSCOLLECTED) if exitstatus in summary_exit_codes: # We had some failures that might need reporting self.make_report(outcome) def make_report(self, outcome): """Make report in form of two notebooks. Use nbdime diff-web to present the difference between reference cells and test cells. """ failures = self.getreports('failed') if not failures: return for rep in failures: # Check if this is a notebook node msg = self._getfailureheadline(rep) lines = rep.longrepr.splitlines() if len(lines) > 1: self.section(msg, lines[1]) self._outrep_summary(rep) tmpdir = tempfile.mkdtemp() try: ref_file = os.path.join(tmpdir, 'reference.ipynb') test_file = os.path.join(tmpdir, 'test_result.ipynb') with io.open(ref_file, "w", encoding="utf8") as f: nbformat.write(self.nb_ref, f) with io.open(test_file, "w", encoding="utf8") as f: nbformat.write(self.nb_test, f) run_server( port=0, # Run on random port cwd=tmpdir, closable=True, on_port=lambda port: browse( port, ref_file, test_file, None)) finally: shutil.rmtree(tmpdir) # # summaries for sessionfinish # def getreports(self, name): l = [] for x in self.stats.get(name, []): if not hasattr(x, '_pdbshown'): l.append(x) return l def section(self, title, details): # Create markdown cell with title source = "## " + title if details: details = details.replace(bcolors.OKBLUE, '') source += "\n\n**" + details + '**' header = nbformat.v4.new_markdown_cell(source) # Add markdown in both ref and test self.nb_ref.cells.append(header) self.nb_test.cells.append(header) def _outrep_summary(self, rep): # Find corresponding item for item in self.nbval_items: if item.nodeid == rep.nodeid: # item found, output # Sanitize reference cell ref_cell = item.cell ref_cell.outputs = item.sanitize_outputs(ref_cell.outputs) self.nb_ref.cells.append(item.cell) test_cell = copy.copy(item.cell) if item.test_outputs: test_cell.outputs = item.sanitize_outputs(item.test_outputs) self.nb_test.cells.append(test_cell) def _getfailureheadline(self, rep): if hasattr(rep, 'location'): domain = rep.location[2] return domain else: return "test session" # XXX? nbval-0.11.0/nbval/plugin.py000066400000000000000000001062711457135606100156640ustar00rootroot00000000000000""" pytest ipython plugin modification Authors: D. Cortes, O. Laslett, T. Kluyver, H. Fangohr, V.T. Fauske """ from __future__ import print_function # import the pytest API import pytest import sys import os import re import hashlib import warnings from collections import OrderedDict, defaultdict from pathlib import Path from queue import Empty # for reading notebook files import nbformat from nbformat import NotebookNode # Kernel for running notebooks from .kernel import RunningKernel, CURRENT_ENV_KERNEL_NAME from .cover import setup_coverage, teardown_coverage # define colours for pretty outputs class bcolors: HEADER = '\033[95m' OKBLUE = '\033[94m' OKGREEN = '\033[92m' WARNING = '\033[93m' FAIL = '\033[91m' ENDC = '\033[0m' class nocolors: HEADER = '' OKBLUE = '' OKGREEN = '' WARNING = '' FAIL = '' ENDC = '' class NbCellError(Exception): """ custom exception for error reporting. """ def __init__(self, cell_num, msg, source, traceback=None, *args, **kwargs): self.cell_num = cell_num super(NbCellError, self).__init__(msg, *args, **kwargs) self.source = source self.inner_traceback = traceback def pytest_addoption(parser): """ Adds the --nbval option flag for py.test. Adds an optional flag to pass a config file with regex expressions to sanitise the outputs Only will work if the --nbval flag is present This is called by the pytest API """ group = parser.getgroup("nbval", "Jupyter Notebook validation") group.addoption('--nbval', action='store_true', help="Run Jupyter notebooks, validating all output") group.addoption('--nbval-lax', action='store_true', help="Run Jupyter notebooks, only validating output on " "cells marked with # NBVAL_CHECK_OUTPUT") group.addoption('--nbval-sanitize-with', help='File with regex expressions to sanitize ' 'the outputs. This option only works when ' 'the --nbval flag is passed to py.test') group.addoption('--nbval-current-env', action='store_true', help='Force test execution to use a python kernel in ' 'the same environment that py.test was ' 'launched from. Without this flag, the kernel stored ' 'in the notebook is used by default. ' 'See also: --nbval-kernel-name') group.addoption('--nbval-kernel-name', action='store', default=None, help='Force test execution to use the named kernel. ' 'If a kernel is not named, the kernel stored in the ' 'notebook is used by default. ' 'See also: --current-env') group.addoption('--nbval-cell-timeout', action='store', default=2000, type=float, help='Timeout for cell execution, in seconds.') group.addoption('--nbval-kernel-startup-timeout', action='store', default=60, type=float, help='Timeout for kernel startup, in seconds.') group.addoption('--sanitize-with', help='(deprecated) Alias of --nbval-sanitize-with') group.addoption('--current-env', action='store_true', help='(deprecated) Alias of --nbval-current-env') term_group = parser.getgroup("terminal reporting") term_group._addoption( '--nbdime', action='store_true', help="view failed nbval cells with nbdime.") def pytest_configure(config): if config.option.nbdime: from .nbdime_reporter import NbdimeReporter reporter = NbdimeReporter(config, sys.stdout) config.pluginmanager.register(reporter, 'nbdimereporter') if config.option.sanitize_with: warnings.warn("--sanitize-with has been renamed to --nbval-sanitize-with", DeprecationWarning) if config.option.nbval_sanitize_with: raise ValueError("--sanitize-with and --nbval-sanitize-with were both supplied.") config.option.nbval_sanitize_with = config.option.sanitize_with if config.option.current_env: warnings.warn("--current-env has been renamed to --nbval-current-env", DeprecationWarning) if config.option.nbval_current_env: raise ValueError("--current-env and --nbval-current-env were both supplied.") config.option.nbval_current_env = config.option.current_env if config.option.nbval or config.option.nbval_lax: if config.option.nbval_kernel_name and config.option.current_env: raise ValueError("--current-env and --nbval-kernel-name are mutually exclusive.") def pytest_collect_file(file_path, parent): """ Collect IPython notebooks using the specified pytest hook """ opt = parent.config.option if (opt.nbval or opt.nbval_lax) and file_path.suffix == ".ipynb": # https://docs.pytest.org/en/stable/deprecations.html#node-construction-changed-to-node-from-parent return IPyNbFile.from_parent(parent, path=file_path) comment_markers = { 'PYTEST_VALIDATE_IGNORE_OUTPUT': ('check', False), # For backwards compatibility 'NBVAL_IGNORE_OUTPUT': ('check', False), 'NBVAL_CHECK_OUTPUT': 'check', 'NBVAL_RAISES_EXCEPTION': 'check_exception', 'NBVAL_SKIP': 'skip', } metadata_tags = { k.lower().replace('_', '-'): v for (k, v) in comment_markers.items() } metadata_tags['raises-exception'] = 'check_exception' def find_comment_markers(cellsource): """Look through the cell source for comments which affect nbval's behaviour Yield an iterable of ``(MARKER_TYPE, True)``. """ found = {} for line in cellsource.splitlines(): line = line.strip() if line.startswith('#'): # print("Found comment in '{}'".format(line)) comment = line.lstrip('#').strip() if comment in comment_markers: # print("Found marker {}".format(comment)) marker = comment_markers[comment] if not isinstance(marker, tuple): # If not an explicit tuple ('option', True/False), # imply ('option', True) marker = (marker, True) marker_type = marker[0] if marker_type in found: warnings.warn( "Conflicting comment markers found, using the latest: " " %s VS %s" % (found[marker_type], comment)) found[marker_type] = comment yield marker def find_metadata_tags(cell_metadata): tags = cell_metadata.get('tags', None) if tags is None: return elif not isinstance(tags, list): warnings.warn("Cell tags is not a list, ignoring.") return found = {} for tag in tags: if tag in metadata_tags: marker = metadata_tags[tag] if not isinstance(marker, tuple): # If not an explicit tuple ('option', True/False), # imply ('option', True) marker = (marker, True) marker_type = marker[0] if marker_type in found: warnings.warn( "Conflicting metadata tags found, using the latest: " " %s VS %s" % (found[marker_type], tag)) found[marker_type] = tag yield marker class Dummy: """Needed to use xfail for our tests""" def __init__(self): self.__globals__ = {} class IPyNbFile(pytest.File): """ This class represents a pytest collector object. A collector is associated with an ipynb file and collects the cells in the notebook for testing. yields pytest items that are required by pytest. """ def __init__(self, *args, **kwargs): super(IPyNbFile, self).__init__(*args, **kwargs) config = self.parent.config self.sanitize_patterns = OrderedDict() # Filled in setup_sanitize_patterns() self.compare_outputs = not config.option.nbval_lax self.timed_out = False self.skip_compare = ( 'metadata', 'traceback', #'text/latex', 'prompt_number', 'output_type', 'name', 'execution_count', 'application/vnd.jupyter.widget-view+json' # Model IDs are random ) if not config.option.nbdime: self.skip_compare = self.skip_compare + ('image/png', 'image/jpeg') kernel = None def setup(self): """ Called by pytest to setup the collector cells in . Here we start a kernel and setup the sanitize patterns. """ # we've already checked that --nbval-current-env and # --nbval-kernel-name were not both supplied if self.parent.config.option.nbval_current_env: kernel_name = CURRENT_ENV_KERNEL_NAME elif self.parent.config.option.nbval_kernel_name: kernel_name = self.parent.config.option.nbval_kernel_name else: kernel_name = self.nb.metadata.get( 'kernelspec', {}).get('name', 'python') self.kernel = RunningKernel( kernel_name, cwd=str(self.fspath.dirname), startup_timeout=self.config.option.nbval_kernel_startup_timeout, ) self.setup_sanitize_files() if getattr(self.parent.config.option, 'cov_source', None): setup_coverage(self.parent.config, self.kernel, getattr(self, "fspath", None)) def setup_sanitize_files(self): """ For each of the sanitize files that were specified as command line options load the contents of the file into the sanitise patterns dictionary. """ for fname in self.get_sanitize_files(): with open(fname, 'r') as f: self.sanitize_patterns.update(get_sanitize_patterns(f.read())) def get_sanitize_files(self): """ Return list of all sanitize files provided by the user on the command line. N.B.: We only support one sanitize file at the moment, but this is likely to change in the future """ if self.parent.config.option.nbval_sanitize_with is not None: return [self.parent.config.option.nbval_sanitize_with] else: return [] def get_kernel_message(self, timeout=None, stream='iopub'): """ Gets a message from the iopub channel of the notebook kernel. """ return self.kernel.get_message(stream, timeout=timeout) # Read through the specified notebooks and load the data # (which is in json format) def collect(self): """ The collect function is required by pytest and is used to yield pytest Item objects. We specify an Item for each code cell in the notebook. """ self.nb = nbformat.read(str(self.fspath), as_version=4) # Start the cell count cell_num = 0 # Iterate over the cells in the notebook for cell in self.nb.cells: # Skip the cells that have text, headings or related stuff # Only test code cells if cell.cell_type == 'code': # The cell may contain a comment indicating that its output # should be checked or ignored. If it doesn't, use the default # behaviour. The --nbval option checks unmarked cells. with warnings.catch_warnings(record=True) as ws: options = defaultdict(bool, find_metadata_tags(cell.metadata)) comment_opts = dict(find_comment_markers(cell.source)) loc = '%s:Cell %d' % (getattr(self, "fspath", None), cell_num) if set(comment_opts.keys()) & set(options.keys()): warnings.warn_explicit( "Overlapping options from comments and metadata, " "using options from comments: %s" % str(set(comment_opts.keys()) & set(options.keys())), category=UserWarning, filename=loc, lineno=0 ) for w in ws: warnings.warn_explicit( str(w.message), category=UserWarning, filename=loc, lineno=0 ) options.update(comment_opts) options.setdefault('check', self.compare_outputs) name = 'Cell ' + str(cell_num) yield IPyNbCell.from_parent( self, name=name, cell_num=cell_num, cell=cell, options=options ) # Update 'code' cell count cell_num += 1 def teardown(self): if self.kernel is not None and self.kernel.is_alive(): if getattr(self.parent.config.option, 'cov_source', None): teardown_coverage(self.parent.config, self.kernel) self.kernel.stop() class IPyNbCell(pytest.Item): def __init__(self, name, parent, cell_num, cell, options): super(IPyNbCell, self).__init__(name, parent) # Store reference to parent IPynbFile so that we have access # to the running kernel. self.parent = parent self.cell_num = cell_num self.cell = cell self.test_outputs = None self.options = options self.config = parent.parent.config self.output_timeout = 5 # Disable colors if we have been explicitly asked to self.colors = bcolors if self.config.option.color != 'no' else nocolors # _pytest.skipping assumes all pytest.Item have this attribute: self.obj = Dummy() """ ***************************************************** ***************** TESTING FUNCTIONS *************** ***************************************************** """ def repr_failure(self, excinfo): """ called when self.runtest() raises an exception. """ exc = excinfo.value cc = self.colors if isinstance(exc, NbCellError): msg_items = [ cc.FAIL + "Notebook cell execution failed" + cc.ENDC] formatstring = ( cc.OKBLUE + "Cell %d: %s\n\n" + "Input:\n" + cc.ENDC + "%s\n") msg_items.append(formatstring % ( exc.cell_num, str(exc), exc.source )) if exc.inner_traceback: msg_items.append(( cc.OKBLUE + "Traceback:" + cc.ENDC + "\n%s\n") % exc.inner_traceback) return "\n".join(msg_items) else: return "pytest plugin exception: %s" % str(exc) def reportinfo(self): description = "%s::Cell %d" % (self.fspath.relto(self.config.rootdir), self.cell_num) return self.fspath, 0, description def compare_outputs(self, test, ref, skip_compare=None): # Use stored skips unless passed a specific value skip_compare = skip_compare or self.parent.skip_compare test = transform_streams_for_comparison(test) ref = transform_streams_for_comparison(ref) # Color codes to use for reporting cc = self.colors # We reformat outputs into a dictionaries where # key: # - all keys on output except 'data' and those in skip_compare # - all keys on 'data' except those in skip_compare, i.e. data is flattened # value: # - list of all corresponding values for that key, i.e. for all outputs # # This format allows to disregard the relative order of dissimilar # output keys, while still caring about the order of those that share # a key. testing_outs = defaultdict(list) reference_outs = defaultdict(list) for reference in ref: for key in reference.keys(): # We discard the keys from the skip_compare list: if key not in skip_compare: # Flatten out MIME types from data of display_data and execute_result if key == 'data': for data_key in reference[key].keys(): # Filter the keys in the SUB-dictionary again: if data_key not in skip_compare: reference_outs[data_key].append(self.sanitize(reference[key][data_key])) # Otherwise, just create a normal dictionary entry from # one of the keys of the dictionary else: # Create the dictionary entries on the fly, from the # existing ones to be compared reference_outs[key].append(self.sanitize(reference[key])) # the same for the testing outputs (the cells that are being executed) for testing in test: for key in testing.keys(): if key not in skip_compare: if key == 'data': for data_key in testing[key].keys(): if data_key not in skip_compare: testing_outs[data_key].append(self.sanitize(testing[key][data_key])) else: testing_outs[key].append(self.sanitize(testing[key])) # The traceback from the comparison will be stored here. self.comparison_traceback = [] ref_keys = set(reference_outs.keys()) test_keys = set(testing_outs.keys()) if ref_keys - test_keys: self.comparison_traceback.append( cc.FAIL + "Missing output fields from running code: %s" % (ref_keys - test_keys) + cc.ENDC ) return False elif test_keys - ref_keys: self.comparison_traceback.append( cc.FAIL + "Unexpected output fields from running code: %s" % (test_keys - ref_keys) + cc.ENDC ) return False # If we've got to here, the two dicts must have the same set of keys for key in reference_outs.keys(): # Get output values for dictionary entries. # We use str() to be sure that the unicode key strings from the # reference are also read from the testing dictionary: test_values = testing_outs[str(key)] ref_values = reference_outs[key] if len(test_values) != len(ref_values): # The number of outputs for a specific MIME type differs self.comparison_traceback.append( cc.OKBLUE + 'dissimilar number of outputs for key "%s"' % key + cc.FAIL + "<<<<<<<<<<<< Reference outputs from ipynb file:" + cc.ENDC ) for val in ref_values: self.comparison_traceback.append(_trim_base64(val)) self.comparison_traceback.append( cc.FAIL + '============ disagrees with newly computed (test) output:' + cc.ENDC) for val in test_values: self.comparison_traceback.append(_trim_base64(val)) self.comparison_traceback.append( cc.FAIL + '>>>>>>>>>>>>' + cc.ENDC) return False for ref_out, test_out in zip(ref_values, test_values): # Compare the individual values if ref_out != test_out: self.format_output_compare(key, ref_out, test_out) return False return True def format_output_compare(self, key, left, right): """Format an output for printing""" if isinstance(left, str): left = _trim_base64(left) if isinstance(right, str): right = _trim_base64(right) cc = self.colors self.comparison_traceback.append( cc.OKBLUE + " mismatch '%s'" % key + cc.FAIL) # Use comparison repr from pytest: hook_result = self.ihook.pytest_assertrepr_compare( config=self.config, op='==', left=left, right=right) for new_expl in hook_result: if new_expl: new_expl = [' %s' % line.replace("\n", "\\n") for line in new_expl] self.comparison_traceback.append("\n assert reference_output == test_output failed:\n") self.comparison_traceback.extend(new_expl) break else: # Fallback repr: self.comparison_traceback.append( " <<<<<<<<<<<< Reference output from ipynb file:" + cc.ENDC) self.comparison_traceback.append(_indent(left)) self.comparison_traceback.append( cc.FAIL + ' ============ disagrees with newly computed (test) output:' + cc.ENDC) self.comparison_traceback.append(_indent(right)) self.comparison_traceback.append( cc.FAIL + ' >>>>>>>>>>>>') self.comparison_traceback.append(cc.ENDC) """ ***************************************************** ***************************************************** """ def setup(self): if self.parent.timed_out: # xfail(condition, reason=None, run=True, raises=None, strict=False) xfail_mark = pytest.mark.xfail( True, reason='Previous cell timed out, expected cell to fail' ) self.add_marker(xfail_mark) def raise_cell_error(self, message, *args, **kwargs): raise NbCellError(self.cell_num, message, self.cell.source, *args, **kwargs) def runtest(self): """ Run test is called by pytest for each of these nodes that are collected i.e. a notebook cell. Runs all the cell tests in one kernel without restarting. It is very common for ipython notebooks to run through assuming a single kernel. The cells are tested that they execute without errors and that the output matches the output stored in the notebook. """ # Simply skip cell if configured to if self.options['skip']: pytest.skip() kernel = self.parent.kernel if not kernel.is_alive(): raise RuntimeError("Kernel dead on test start") # Execute the code in the current cell in the kernel. Returns the # message id of the corresponding response from iopub. msg_id = kernel.execute_cell_input( self.cell.source, allow_stdin=False) # Timeout for the cell execution # after code is sent for execution, the kernel sends a message on # the shell channel. Timeout if no message received. timeout = self.config.option.nbval_cell_timeout timed_out_this_run = False # Poll the shell channel to get a message try: self.parent.kernel.await_reply(msg_id, timeout=timeout) except Empty: # Timeout reached # Try to interrupt kernel, as this will give us traceback: kernel.interrupt() self.parent.timed_out = True timed_out_this_run = True # This list stores the output information for the entire cell outs = [] # TODO: Only store if comparing with nbdime, to save on memory usage self.test_outputs = outs # Now get the outputs from the iopub channel while True: # The iopub channel broadcasts a range of messages. We keep reading # them until we find the message containing the side-effects of our # code execution. try: # Get a message from the kernel iopub channel msg = self.parent.get_kernel_message(timeout=self.output_timeout) except Empty: # This is not working: ! The code will not be checked # if the time is out (when the cell stops to be executed?) # Halt kernel here! kernel.stop() if timed_out_this_run: self.raise_cell_error( "Timeout of %g seconds exceeded while executing cell." " Failed to interrupt kernel in %d seconds, so " "failing without traceback." % (timeout, self.output_timeout), ) else: self.parent.timed_out = True self.raise_cell_error( "Timeout of %d seconds exceeded waiting for output." % self.output_timeout, ) # now we must handle the message by checking the type and reply # info and we store the output of the cell in a notebook node object msg_type = msg['msg_type'] reply = msg['content'] out = NotebookNode(output_type=msg_type) # Is the iopub message related to this cell execution? if msg['parent_header'].get('msg_id') != msg_id: continue # When the kernel starts to execute code, it will enter the 'busy' # state and when it finishes, it will enter the 'idle' state. # The kernel will publish state 'starting' exactly # once at process startup. if msg_type == 'status': if reply['execution_state'] == 'idle': break else: continue # execute_input: To let all frontends know what code is # being executed at any given time, these messages contain a # re-broadcast of the code portion of an execute_request, # along with the execution_count. elif msg_type == 'execute_input': continue # com? execute reply? elif msg_type.startswith('comm'): continue elif msg_type == 'execute_reply': continue # This message type is used to clear the output that is # visible on the frontend # elif msg_type == 'clear_output': # outs = [] # continue # elif (msg_type == 'clear_output' # and msg_type['execution_state'] == 'idle'): # outs = [] # continue # 'execute_result' is equivalent to a display_data message. # The object being displayed is passed to the display # hook, i.e. the *result* of the execution. # The only difference is that 'execute_result' has an # 'execution_count' number which does not seems useful # (we will filter it in the sanitize function) # # When the reply is display_data or execute_result, # the dictionary contains # a 'data' sub-dictionary with the 'text' AND the 'image/png' # picture (in hexadecimal). There is also a 'metadata' entry # but currently is not of much use, sometimes there is information # as height and width of the image (CHECK the documentation) # Thus we iterate through the keys (mimes) 'data' sub-dictionary # to obtain the 'text' and 'image/png' information elif msg_type in ('display_data', 'execute_result'): out['metadata'] = reply['metadata'] out['data'] = reply['data'] outs.append(out) if msg_type == 'execute_result': out.execution_count = reply['execution_count'] # if the message is a stream then we store the output elif msg_type == 'stream': out.name = reply['name'] out.text = reply['text'] outs.append(out) # if the message type is an error then an error has occurred during # cell execution. Therefore raise a cell error and pass the # traceback information. elif msg_type == 'error': # Store error in output first out['ename'] = reply['ename'] out['evalue'] = reply['evalue'] out['traceback'] = reply['traceback'] outs.append(out) if not self.options['check_exception']: # Ensure we flush iopub before raising error try: self.parent.kernel.await_idle(msg_id, self.output_timeout) except Empty: self.stop() raise RuntimeError('Timed out waiting for idle kernel!') traceback = '\n' + '\n'.join(reply['traceback']) if out['ename'] == 'KeyboardInterrupt' and self.parent.timed_out: msg = "Timeout of %g seconds exceeded executing cell" % timeout else: msg = "Cell execution caused an exception" self.raise_cell_error(msg, traceback) # any other message type is not expected # should this raise an error? else: print("unhandled iopub msg:", msg_type) outs[:] = coalesce_streams(outs) # Cells where the reference is not run, will not check outputs: unrun = self.cell.execution_count is None if unrun and self.cell.outputs: self.raise_cell_error('Unrun reference cell has outputs') # Compare if the outputs have the same number of lines # and throw an error if it fails # if len(outs) != len(self.cell.outputs): # self.diff_number_outputs(outs, self.cell.outputs) # failed = True failed = False if self.options['check'] and not unrun: if not self.compare_outputs(outs, coalesce_streams(self.cell.outputs)): failed = True # If the comparison failed then we raise an exception. if failed: # The traceback containing the difference in the outputs is # stored in the variable comparison_traceback self.raise_cell_error( "Cell outputs differ", # Here we must put the traceback output: '\n'.join(self.comparison_traceback), ) def sanitize_outputs(self, outputs, skip_sanitize=('metadata', 'traceback', 'text/latex', 'prompt_number', 'output_type', 'name', 'execution_count' )): sanitized_outputs = [] for output in outputs: sanitized = {} for key in output.keys(): if key in skip_sanitize: sanitized[key] = output[key] else: if key == 'data': sanitized[key] = {} for data_key in output[key].keys(): # Filter the keys in the SUB-dictionary again if data_key in skip_sanitize: sanitized[key][data_key] = output[key][data_key] else: sanitized[key][data_key] = self.sanitize(output[key][data_key]) # Otherwise, just create a normal dictionary entry from # one of the keys of the dictionary else: # Create the dictionary entries on the fly, from the # existing ones to be compared sanitized[key] = self.sanitize(output[key]) sanitized_outputs.append(nbformat.from_dict(sanitized)) return sanitized_outputs def sanitize(self, s): """sanitize a string for comparison. """ if not isinstance(s, str): return s """ re.sub matches a regex and replaces it with another. The regex replacements are taken from a file if the option is passed when py.test is called. Otherwise, the strings are not processed """ for regex, replace in self.parent.sanitize_patterns.items(): s = re.sub(regex, replace, s) return s carriagereturn_pat = re.compile(r'^.*\r(?=[^\n])', re.MULTILINE) backspace_pat = re.compile(r'[^\n]\b') def coalesce_streams(outputs): """ Merge all stream outputs with shared names into single streams to ensure deterministic outputs. Parameters ---------- outputs : iterable of NotebookNodes Outputs being processed """ if not outputs: return outputs new_outputs = [] streams = {} for output in outputs: if (output.output_type == 'stream'): if output.name in streams: streams[output.name].text += output.text else: new_outputs.append(output) streams[output.name] = output else: new_outputs.append(output) # process \r and \b characters for output in streams.values(): old = output.text while len(output.text) < len(old): old = output.text # Cancel out anything-but-newline followed by backspace output.text = backspace_pat.sub('', output.text) # Replace all carriage returns not followed by newline output.text = carriagereturn_pat.sub('', output.text) return new_outputs def transform_streams_for_comparison(outputs): """Makes failure output for streams better by having key be the stream name""" new_outputs = [] for output in outputs: if (output.output_type == 'stream'): # Transform output new_outputs.append({ 'output_type': 'stream', output.name: output.text, }) else: new_outputs.append(output) return new_outputs def get_sanitize_patterns(string): """ *Arguments* string: str String containing a list of regex-replace pairs as would be read from a sanitize config file. *Returns* A list of (regex, replace) pairs. """ return re.findall('^regex: (.*)$\n^replace: (.*)$', string, flags=re.MULTILINE) def hash_string(s): return hashlib.md5(s.encode("utf8")).hexdigest() _base64 = re.compile(r'^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$', re.MULTILINE | re.UNICODE) def _trim_base64(s): """Trim and hash base64 strings""" if len(s) > 64 and _base64.match(s.replace('\n', '')): h = hash_string(s) s = '%s...' % (s[:8], h[:16]) return s def _indent(s, indent=' '): """Intent each line with indent""" if isinstance(s, str): return '\n'.join(('%s%s' % (indent, line) for line in s.splitlines())) return s nbval-0.11.0/readthedocs.yml000066400000000000000000000002611457135606100157120ustar00rootroot00000000000000version: 2 build: os: "ubuntu-22.04" tools: python: "mambaforge-latest" conda: environment: docs/environment.yml python: install: - method: pip path: . nbval-0.11.0/setup.cfg000066400000000000000000000003561457135606100145300ustar00rootroot00000000000000[bdist_wheel] universal=1 [metadata] license_file = LICENSE [pep8] ignore = E131, E303, W503 max-line-length = 120 [flake8] ignore = E131, E303, W503 max-line-length = 120 [pycodestyle] ignore = E131, E303, W503 max-line-length = 120 nbval-0.11.0/setup.py000066400000000000000000000020111457135606100144070ustar00rootroot00000000000000from setuptools import setup from nbval._version import __version__ with open('README.md') as f: readme = f.read() setup( name="nbval", version=__version__, author="Laslett, Cortes, Fauske, Kluyver, Pepper, Fangohr", description='A py.test plugin to validate Jupyter notebooks', long_description=readme, long_description_content_type="text/markdown", packages = ['nbval'], url='https://github.com/computationalmodelling/nbval', # the following makes a plugin available to pytest entry_points = { 'pytest11': [ 'nbval = nbval.plugin', ] }, install_requires = [ 'pytest >= 7', 'jupyter_client', 'nbformat', 'ipykernel', 'coverage', ], python_requires='>=3.7, <4', classifiers = [ 'Framework :: IPython', 'Framework :: Pytest', 'License :: OSI Approved :: BSD License', 'Programming Language :: Python :: 3', 'Topic :: Software Development :: Testing', ] ) nbval-0.11.0/tests/000077500000000000000000000000001457135606100140455ustar00rootroot00000000000000nbval-0.11.0/tests/Makefile000066400000000000000000000004361457135606100155100ustar00rootroot00000000000000PYTEST ?= py.test TEST_OPTIONS ?= -v --nbval-current-env all: test test: $(PYTEST) $(TEST_OPTIONS) . $(PYTEST) --nbval --nbval-current-env --nbval-sanitize-with sanitize_defaults.cfg sample_notebook.ipynb $(PYTEST) --nbval --nbval-current-env latex-example.ipynb .PHONY: all test nbval-0.11.0/tests/exceptions.ipynb000066400000000000000000000123411457135606100172720ustar00rootroot00000000000000{ "cells": [ { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "ename": "RuntimeError", "evalue": "foo", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mRuntimeError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mRuntimeError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"foo\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mRuntimeError\u001b[0m: foo" ] } ], "source": [ "# NBVAL_RAISES_EXCEPTION\n", "raise RuntimeError(\"foo\")" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import time" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1485539287.9658682\n" ] }, { "ename": "RuntimeError", "evalue": "Bar", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mRuntimeError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;31m# NBVAL_IGNORE_OUTPUT\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mtime\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtime\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 3\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mRuntimeError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"Bar\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mRuntimeError\u001b[0m: Bar" ] } ], "source": [ "# NBVAL_IGNORE_OUTPUT\n", "# NBVAL_RAISES_EXCEPTION\n", "print(time.time())\n", "raise RuntimeError(\"Bar\")" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "tags": [ "nbval-raises-exception" ] }, "outputs": [ { "ename": "RuntimeError", "evalue": "Ignore me", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mRuntimeError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;31m# this cell has a metadata tag of \"nbval-raises-exception\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mRuntimeError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"Ignore me\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mRuntimeError\u001b[0m: Ignore me" ] } ], "source": [ "# this cell has a metadata tag of \"nbval-raises-exception\"\n", "raise RuntimeError(\"Ignore me\")" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "tags": [ "raises-exception" ] }, "outputs": [ { "ename": "RuntimeError", "evalue": "Ignore me also", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mRuntimeError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;31m# this cell has a metadata tag of \"raises-exception\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mRuntimeError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"Ignore me also\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mRuntimeError\u001b[0m: Ignore me also" ] } ], "source": [ "# this cell has a metadata tag of \"raises-exception\"\n", "raise RuntimeError(\"Ignore me also\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "celltoolbar": "Tags", "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.6" } }, "nbformat": 4, "nbformat_minor": 2 } nbval-0.11.0/tests/ipynb-test-samples/000077500000000000000000000000001457135606100176055ustar00rootroot00000000000000nbval-0.11.0/tests/ipynb-test-samples/test-exceptions-fail-storedmismatch.ipynb000066400000000000000000000034761457135606100277550ustar00rootroot00000000000000{ "cells": [ { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "ename": "NameError", "evalue": "name 'a' is not defined", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mfoo\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3\u001b[0m \u001b[0;32mraise\u001b[0m \u001b[0mRuntimeError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"Foo\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 4\u001b[0;31m \u001b[0mfoo\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0ma\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mNameError\u001b[0m: name 'a' is not defined" ] } ], "source": [ "# NBVAL_RAISES_EXCEPTION\n", "def foo():\n", " raise RuntimeError(\"Foo\")\n", "foo()" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.6" } }, "nbformat": 4, "nbformat_minor": 2 } nbval-0.11.0/tests/ipynb-test-samples/test-latex-fail-randomoutput.ipynb000066400000000000000000000045461457135606100264230ustar00rootroot00000000000000{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "This is a notebook for a single test. We like to see a fail." ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "import random\n", "import sympy\n", "sympy.init_printing()\n", "x, y = sympy.symbols([\"x\", \"y\"])" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "n1 = int(random.uniform(1, 10000000000000))" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAHcAAAAUBAMAAABFd79NAAAAMFBMVEX///8AAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAv3aB7AAAAD3RSTlMAEHarIkSJZt3NVLsy\nme8Q6PJIAAAACXBIWXMAAA7EAAAOxAGVKw4bAAACKElEQVQ4EaWRP4gTQRSHf7O3m002m2QuNgHx\nNpyQQhTXwkIRnEausMg2VhYu65FCCKay8iSEE8HGIGq9CDb+uy1s5Rat1CI5T+4EUe8EESw8w12I\nnMX6ZpdsnzjwDbM773sz7w0QD/PMQgtmF/DWOWo14FkDyHxai7kyO0tBeeDaMR6T8VrQvbVEBbRo\nF+Z6G4VWIdBdw1ZspY9FnIo5G0UBsML1kAUS3Fe3cB0HU/kpBwySXf131sGg5KgDLKMnJCcvXORg\nV3leoCtRbFbFC+RS2aEVyUAhsEL8rIcY4ibqjsTHI0CxeOG7aUuKPsV9cV7THA/tPZUTy3NhT+AW\nJdinjRW6D8Fc4K3F2Z2XkJQeUnDlbz9RAdXRg1hW7qLH0Sn65oj2OgkzlMO1OG78EZJ6X2tD31ke\ny4C5N742nfzNCJQhtZuSS94BurB4xn2+LbF8qqlh/KKU8ZjhbJjI6j5deYhXR/eAJdqTNIEyLF4B\nuy0pORixNip+rAJFQd2lmudCdSS7zaAF0FwtlOABvfvnr5vngSOSXAtDNYAyljOcrmdsISuUbaVv\n2PlqPsSbx0+4BD/kEXWeEzgh0QKji3uQnUhGYwPqx92q6V0WaDa5Nv8B2ImiGHozKnr1NNuoCQmW\nmhyHvEX6Wz58rlZNMkw8M1fvZLsTa4lA1Q4MMaXMkAumVKVWqv6H3Aunlg1+HEr6YhOmueSvYmFC\nJw0/MF/ebKVfky3+AYXupr7QCkoOAAAAAElFTkSuQmCC\n", "text/latex": [ "$$x^{5130922748868}$$" ], "text/plain": [ " 5130922748868\n", "x " ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x**n1" ] } ], "metadata": { "anaconda-cloud": {}, "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.6" } }, "nbformat": 4, "nbformat_minor": 2 } nbval-0.11.0/tests/ipynb-test-samples/test-latex-pass-correctouput.ipynb000066400000000000000000000040501457135606100264410ustar00rootroot00000000000000{ "cells": [ { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "import sympy\n", "sympy.init_printing()\n", "x, y = sympy.symbols([\"x\", \"y\"])" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAEYAAAAaCAYAAAAKYioIAAAABHNCSVQICAgIfAhkiAAAAp9JREFUWIXt2EmIVFcUxvGf40INUUFQCSGgUZE4geLGhiABiSIBiQFdyAuRuDCb4EZdZRO7RQQVURwWLbhRQUU3USSgYBRdKBl34rBwxBY1ikOii3PF6rIm6r2iW6g/PKruvdzznXfqnvPOK9q0hHW4iIe4i+P4rE89aozV+F34/RDnsKhIgRP4VgRjGo7gFkYXKdICvsJCfIpJ+BkvML1VgiPwHxa3SqCF3MeqN4PBBRv/AAOTyPvCIHwjftTfWiVyAJeSWH9nGh7jJXpEarWETbiJiU3uz/AKnxfkTz2GCl9noxP31HhwnBTOLSmbH4DutNZVYd9m3MbUHI5mmg/MmrR3TZX1yXiGMzVsnMLeaoszRPH8W+902JyEd1fYs03+oJAvMHPT3kNV1k+KlKn11PkV+2uJdCeRLI3Xp/EBUVhL2SH6gPkYW3KNqCVQhUzzgRmCJ7heYW1psru1ZK4LHfhE1JpO/I8va4l8hKe4ih+S0V9ETpbzqsr1UyN3U0YmX405nfaPL5kbjhviRH9YMt+NayK97og0WtCISKe3N3kWw5p0thpXVQ9qpau7AZsbvFsfN+p9+humWh9zt+T7d+KYFskWjCybmyk60n0icKVcbsDm2fQ5F4cxBT+Kdn9fs46Wskzk200R7Z1FGG2ATL5UGiX8Pp3Gp8SDZFZex4gm5zn+wBj8I94hphRhvA6Z/H3MX/gXy5OtHbm9wjyRMlcwLs19nQSOFiFQh0z+wOxKNh6JcpD7ZXYGHoj0mVC2djGJdeQVqUMmf2BWeFuwV+Z1aKL4q6BH5QboiyR0Pq9QHTL5A9ORbFwQ3XqbxDFRcOf0tSP9iTcFd3tfO9If+BhrsUd0sX8qvhl9L/lenJIeHNT7daBNmzZt+i2vAbLKrASToknyAAAAAElFTkSuQmCC\n", "text/latex": [ "$\\displaystyle x^{2} + y^{3}$" ], "text/plain": [ " 2 3\n", "x + y " ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x**2 + y**3" ] } ], "metadata": { "anaconda-cloud": {}, "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.6" } }, "nbformat": 4, "nbformat_minor": 2 } nbval-0.11.0/tests/ipynb-test-samples/test-latex-pass-failsbutignoreoutput.ipynb000066400000000000000000000046031457135606100302050ustar00rootroot00000000000000{ "cells": [ { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [], "source": [ "import time\n", "import sympy\n", "sympy.init_printing()\n", "x = sympy.Symbol(\"x\")" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [], "source": [ "n = int(time.time()) # changes every second" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The next cell nbval evalution should fail (in most cases), so we exclude this cell from the comparison with the `#NBVAL_IGNORE_OUTPUT` token:" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAFwAAAAUBAMAAADyykdqAAAAMFBMVEX///8AAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAv3aB7AAAAD3RSTlMAEHarIkSJZt3NVLsy\nme8Q6PJIAAAACXBIWXMAAA7EAAAOxAGVKw4bAAABj0lEQVQoFZWQPUsjURRAz8RlkkzG5BlB7CZE\nSCHIZq3Eahqx0CWxSLOwZLQPptJCkEUQYRvzExQbGzX+ADGijcISRTA2sh/sNtsFFQSFeN8Yi6Rx\nvM1593HuBxdewq4IY7AwojTNzT6fZDJwfLJIaM6D07aMfflNnnsqXDOqmj2t1qOfe1aWYivLAlv8\nGHvVsUQ35lXMpaLZUyjsa0byNPlyBKskFNsdeshRvf/sLEILK6/p1PhPQTo9Ed/o0s8cZawfgBAG\nfNZd1viavrDviJS7dE+05QcXTT6hWVesMMGS3SSS6tTDrqNMb/e3JnYVTen+RxauT98R79KTOGoQ\n47sm5jmasvu9DHJmnkjkO7vv3PxsTMGwJlEPTf8yf8mp7sv8kpk5FXX10jlFvOznoXO5+ywlRuTu\n7MqfHx+ub1OED8eNq4yribT1WSrJYiUP8+qCz+ujkByayKTaVW/D8MIrkcrbXtswldm03MC6QbQa\nWNZiIvUuvV57h26pj4RU4ILixiGTgW3608lGObD+DDiqbm3arb05AAAAAElFTkSuQmCC\n", "text/latex": [ "$$x^{1484740501}$$" ], "text/plain": [ " 1484740501\n", "x " ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# NBVAL_IGNORE_OUTPUT\n", "x**n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [] } ], "metadata": { "anaconda-cloud": {}, "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.6" } }, "nbformat": 4, "nbformat_minor": 2 } nbval-0.11.0/tests/ipynb-test-samples/test-stream-fail-missingoutput.ipynb000066400000000000000000000020421457135606100267570ustar00rootroot00000000000000{ "cells": [ { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "test\n" ] } ], "source": [ "print('test')" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "print('test')" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "test\n" ] } ], "source": [ "print('test')" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.3" } }, "nbformat": 4, "nbformat_minor": 2 } nbval-0.11.0/tests/ipynb-test-samples/test-stream-pass-missing-unexecuted-output.ipynb000066400000000000000000000023321457135606100312400ustar00rootroot00000000000000{ "cells": [ { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "test\n" ] } ], "source": [ "print('test')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# An unexecuted cell has an implicit skip of output check\n", "print('test')" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "test\n" ] } ], "source": [ "print('test')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.3" } }, "nbformat": 4, "nbformat_minor": 2 } nbval-0.11.0/tests/latex-example.ipynb000066400000000000000000000103711457135606100176600ustar00rootroot00000000000000{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "For mathematical computation, comparison of LaTeX output is useful. Here is an example:" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "import sympy\n", "sympy.init_printing()\n", "x, y = sympy.symbols([\"x\", \"y\"])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Should pass:" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAABcAAAAWCAYAAAArdgcFAAAABHNCSVQICAgIfAhkiAAAASNJREFUOI3t1LErhVEYx/EPyqBrUYqyMUi4/wCLTMoii42s/gEmG6W7GCiT9U6UBbtSdzAoNtnQLWSgiGt4z63X9b433fsmg1+dOs95nvN9nnPO0+EPaxklPKGMAwxnBT/CQgCOYA+36MoqQVw5vGMaWjOGdwbmfcZcUMQZ2rIGb+AGA2kBx6hgpma9BbvBt56wr4A7DNXLnhc9yIWvRysE8E7Cns2fgKuqVjgf7JVgF31vgC1Rj0+gJzZyafA+vOAaSwF8iPaE2ErKWK1X/Vos8AQd9YLTlNbn5dh8Ec+NwJM0hw9RW1WwnRV4Cq84Rzcu8YbBZsFjouNfoTeszYqq328GnMej6Cr6a3ylkGC8EfCA6Jt8wGiCfzLATxuB/+t39QmeVkMhEWwiCwAAAABJRU5ErkJggg==\n", "text/latex": [ "$\\displaystyle x^{2}$" ], "text/plain": [ " 2\n", "x " ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x**2" ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import random\n", "n = int(random.uniform(1, 10000000000000))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The next cell nbval evalution should fail (in most cases), so we exclude this cell from the comparison with the `#NBVAL_IGNORE_OUTPUT` token:" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAIMAAAAWCAYAAADjNi+WAAAABHNCSVQICAgIfAhkiAAAA/tJREFUaIHt2W2oFkUUwPGfFrdIIwmCklArKRPNIqWIMhErNKKoTIQCoyTIiIg+9AImBF6hhIo0sAiDsKSCoKASsxthGSJlhpW9GVRWhpZYmVlPH848uO7dfV7uc6+Y7h8GdmfOnDm7O3POmVkqKvqJ+7Aeu7Adr2LcAMnUuR81PJGrn4ePk45deB9XNrC9TM+CVJ8tP+ZkthbI1LCkTXsmi2f9IfW/vsDOVmTgdnyDPdiASwpkTsGz4h3/iU24tN44uERxq0zBUlyEqdiH1ThxAGTgQswVLznPd7gX52Mi1uAVnFMg20gPfC5eXL2Mz7VPyrVflupfbNOeIdiIO0rsaFVmFh7DQpyHtXgdIzIyw1L9IDEpx+Iu/NxAb0cMxT+4agBkTsBXYrL06L2ii9iB23J1zfQswCct6M7yKL4UL7pde+o0WvXNZD7AU7m6L9CduV8oJkMpnXqGPMcnnTsGQGYZXhIrrBlHYbaYVO/l2lrRczq+F273eYxqINuFG/GM+Fjt2tMpXcL7rMrVrxKets41WIcVwht8JLxNswncZ1biQ/Hw/SkzV8TBrnTfo9gzjMduEWZ2YkauvRU903Fd0jUNb2Gb3iGrzg1pvOF9sCdLXz3D8FQ/OVc/X4S7OntS6Rah5OZk27wmY/aJh8VLG93PMmeJhGdMRqZH8WToSv0miof+xf5EtB09WYbgJ9xd0v6mSPCKaGRPnk4nQz5hfBCfZe73iiQ2Szc2lw22Kim+Nlc/CMtT26KCfovFCxtbprgDmTlp3H2ZUsO/6fqYBvpW4+l+0PM2niyoHylym6sb9C2zJ09fJ0OXsH9mrn4J3sncf1sw9k34vWywCeLhNjvQjS9Ohiwr6PO45h+5E5lhYjVly3oR+8ZpHPPW4LkO9RwrPNX8grYFqe3oBjaU2ZOn0wQy/222ODCBXIF3czIPaeAZ2O8B5qT7+n58pd4J51Kxh56KkzNl6ADIZOnR270vEq5ylIjV3WLVTy9/1EI9j4i992m4AK8l20bm5AaL1VbkKVu1ZyjOTaWGe9L1iDZlZokwcCvOFrub3TmbJ+FvPCBC10z8pknOcKo4kNgqss0a3rA/6cpSdPBSEyumv2Wy9Oj9EZeLj/OXyJZX44rSpyzX84I44NkrdhQvK/ZolycbzyzR3Yo9UxQ/9/I2ZYhDp61pvA16J5TE+cJGkUhuwZ1a2E10ZwZdi+Oadaj4/1N2zrA9c30L/jgItlQcgswWsW2b8AxFWXTFEcAMESs34SR8KpKOMY06VRx+XCzCwdfi5wuxjamJHywVRwgT8KsIDWfk2tYrPuGqOAwZLf7X71T8u3eamAzrDqZRFRUVFRUVFRUVhyb/Ae8baLugwVuCAAAAAElFTkSuQmCC\n", "text/latex": [ "$\\displaystyle x^{2224345731106}$" ], "text/plain": [ " 2224345731106\n", "x " ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# NBVAL_IGNORE_OUTPUT\n", "x**n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This addresses issue https://github.com/computationalmodelling/nbval/issues/27 ." ] } ], "metadata": { "anaconda-cloud": {}, "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.6" } }, "nbformat": 4, "nbformat_minor": 2 } nbval-0.11.0/tests/minimal_example.ipynb000066400000000000000000000016541457135606100202570ustar00rootroot00000000000000{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Minimal example" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Hello world!\n" ] } ], "source": [ "print(\"Hello world!\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.6" } }, "nbformat": 4, "nbformat_minor": 1 } nbval-0.11.0/tests/sample_notebook.ipynb000066400000000000000000000627741457135606100203110ustar00rootroot00000000000000{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Sample notebook" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This is a sample notebook which can be used to test the `py.test --nbval` plugin." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Hello world\n" ] } ], "source": [ "print(\"Hello world\")" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "from datetime import datetime" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Different ways to ignore output differences\n", "\n", "Some of these cells ignore output by cell metadata, stored in the list \"tags\". The names of the tags are the same as the comment markers, but lowercase and with dashes instead of underscores. Various cell toolbars can be used to edit these, but the barebones version is simply to edit the raw metadata." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "datetime.datetime(2018, 6, 1, 12, 33, 8, 310117)" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# PYTEST_VALIDATE_IGNORE_OUTPUT\n", "datetime.now()" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "datetime.datetime(2018, 6, 1, 12, 33, 9, 629357)" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# NBVAL_IGNORE_OUTPUT\n", "datetime.now()" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "tags": [ "nbval-ignore-output" ] }, "outputs": [ { "data": { "text/plain": [ "datetime.datetime(2018, 6, 1, 12, 33, 11, 435879)" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# This cell uses metadata to ignore output\n", "datetime.now()" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "tags": [ "nbval-check-output" ] }, "outputs": [ { "data": { "text/plain": [ "datetime.datetime(2018, 6, 1, 12, 33, 12, 13278)" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# This cell's metadata says to check output, but comments take precedence\n", "# NBVAL_CHECK_OUTPUT\n", "# NBVAL_IGNORE_OUTPUT\n", "datetime.now()" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "tags": [ "nbval-ignore-output" ] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "5\n" ] } ], "source": [ "# NBVAL_CHECK_OUTPUT\n", "# This cell's metadata says to ignore output, but comments take precedence\n", "# Note to developers: Even if this behavior gets broken, it will not cause this cell to fail!\n", "print(5)" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "tags": [ "nbval-check-output" ] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "5\n" ] } ], "source": [ "# This cell's metadata says to check output\n", "print(5)" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'Last executed: 2018-06-01, 12:33:15'" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "#datetime.today().strftime(\"%a %y %b %Y, %H:%M:%S\")\n", "datetime.today().strftime(\"Last executed: %Y-%m-%d, %H:%M:%S\")" ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "scrolled": true }, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "%matplotlib inline\n", "import matplotlib.pyplot as plt\n", "plt.plot([1, 2, 3, 4, 5], [1, 3, 2, 2.5, 1.5], 'o-', label='graph')\n", "plt.xlabel('x')\n", "plt.ylabel('y')\n", "plt.title('Simple plot')\n", "plt.legend(loc='best')" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "## Skipping cell execution during tests\n", "\n", "If for some reason a cell should not be executed during testing, this can be indicated as demonstrated below." ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "collapsed": true }, "outputs": [], "source": [ "# NBVAL_SKIP\n", "from time import sleep" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "# NBVAL_SKIP\n", "raise Error('This should not run!')" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true, "tags": [ "nbval-skip" ] }, "outputs": [], "source": [ "# This cell's metadata says to skip execution\n", "raise Error('Neither should this!')" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [] } ], "metadata": { "celltoolbar": "Tags", "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.6" } }, "nbformat": 4, "nbformat_minor": 1 } nbval-0.11.0/tests/sanitize_defaults.cfg000066400000000000000000000006061457135606100202450ustar00rootroot00000000000000[Dates and times] regex: \d\d\d\d-\d\d-\d\d replace: DATESTAMP regex: \d\d-\d\d-\d\d\d\d replace: DATESTAMP regex: \d\d:\d\d:\d\d replace: TIMESTAMP regex: \d\d:\d\d replace: TIMESTAMP [Memory addresses] regex: (<[a-zA-Z_][0-9a-zA-Z_.]* at )(0x[0-9a-fA-F]+)(>) replace: \1MEMORY_ADDRESS\3 [Matplotlib figure size] regex: (Figure size )\d+x\d+( with \d+ Axes) replace: \1WIDTHxHEIGHT\2 nbval-0.11.0/tests/test_coalesce.ipynb000066400000000000000000000060601457135606100177270ustar00rootroot00000000000000{ "cells": [ { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": true }, "outputs": [], "source": [ "from __future__ import print_function\n", "import sys" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Foo!\n", "Bar!\n" ] } ], "source": [ "print('Foo!')\n", "print('Bar!')" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "Bar!\n", "Foo!\n" ] } ], "source": [ "print('Bar!', file=sys.stderr)\n", "print('Foo!', file=sys.stderr)" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Combo breaker!\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Bar!\n", "Foo!\n" ] } ], "source": [ "print('Bar!', file=sys.stderr)\n", "print('Combo breaker!', file=sys.stdout)\n", "print('Foo!', file=sys.stderr)" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "Bar!\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Combo breaker!\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Foo!\n" ] } ], "source": [ "print('Bar!', file=sys.stderr)\n", "sys.stderr.flush()\n", "print('Combo breaker!', file=sys.stdout)\n", "sys.stdout.flush()\n", "print('Foo!', file=sys.stderr)" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "Bar!\n", "Foo!\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Combo breaker!\n" ] } ], "source": [ "import time\n", "import numpy as np\n", "print('Bar!', file=sys.stderr)\n", "# Do not run the next line when regenerating test!\n", "sys.stderr.flush()\n", "print('Combo breaker!', file=sys.stdout)\n", "sys.stderr.flush()\n", "print('Foo!', file=sys.stderr)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.6" } }, "nbformat": 4, "nbformat_minor": 2 } nbval-0.11.0/tests/test_collect.py000066400000000000000000000030411457135606100171010ustar00rootroot00000000000000 from __future__ import print_function import os import nbformat pytest_plugins = "pytester" def _build_nb(sources): """Builds a notebook of only code cells, from a list of sources""" nb = nbformat.v4.new_notebook() for src in sources: nb.cells.append(nbformat.v4.new_code_cell(src)) return nb def _write_nb(sources, path): nb = _build_nb(sources) nbformat.write(nb, path) # Sources to try to collect sources = [ # In [1]: "a = 5", # In [2]: "for i in range(10):\n" + " print(i)", # In [3]: "print(a)", # In [4]: "a", # In [5]: "import os\n" + "os.curdir" ] def test_collection_nbval(testdir): # Write notebook to test dir _write_nb(sources, os.path.join(str(testdir.tmpdir), 'test_collection.ipynb')) # Run tests items, recorder = testdir.inline_genitems('--nbval', '--nbval-current-env') # Debug output: for item in items: print('Cell %d:' % item.cell_num) print(" " + "\n ".join(item.cell.source.splitlines()) + "\n") # Checks: assert len(items) == 5 def test_collection_nbval_lax(testdir): # Write notebook to test dir _write_nb(sources, os.path.join(str(testdir.tmpdir), 'test_collection.ipynb')) # Run tests items, recorder = testdir.inline_genitems('--nbval-lax', '--nbval-current-env') # Debug output: for item in items: print('Cell %d:' % item.cell_num) print(" " + "\n ".join(item.cell.source.splitlines()) + "\n") # Checks: assert len(items) == 5 nbval-0.11.0/tests/test_coverage.py000066400000000000000000000030101457135606100172430ustar00rootroot00000000000000 import os import re import nbformat from utils import build_nb, add_expected_plaintext_outputs pytest_plugins = "pytester" _re_coverage_report_line = re.compile(r'^(\w)\s*(\d+)\s*(\d+)\s*(\d+)%$') def test_coverage(testdir): testdir.makepyfile( # Setup file to cover: lib=""" def mysum(a, b): return a + b def myprod(a, b): return a * b """, # Setup python file to cover mysum function test_lib=""" import lib def test_sum(): assert lib.mysum(1, 3) == 4 assert lib.mysum("cat", "dog") == "catdog" assert lib.mysum(1.5, 2) == 3.5 """, ) # Setup notebook to cover myprod function nb = build_nb([ "import lib", "lib.myprod(1, 3)", "lib.myprod(2.5, 2.5)", "lib.myprod(2, 'cat')" ], mark_run=True) add_expected_plaintext_outputs(nb, [ None, "3", "6.25", "'catcat'" ]) # Write notebook to test dir nbformat.write(nb, os.path.join( str(testdir.tmpdir), 'test_coverage.ipynb')) # Run tests result = testdir.runpytest_inprocess('--nbval', '--nbval-current-env', '--cov', '.') # Check tests went off as they should: assert result.ret == 0 # Ensure coverage report was generated: assert os.path.exists(os.path.join(str(testdir.tmpdir), '.coverage')) # Check that all lines were covered: result.stdout.fnmatch_lines([ 'lib.py*4*0*100%' ]) nbval-0.11.0/tests/test_ignore.py000066400000000000000000000026241457135606100167450ustar00rootroot00000000000000 import os import re import nbformat from utils import build_nb pytest_plugins = "pytester" _ignore_stderr_code = """ def pytest_collectstart(collector): if collector.fspath and collector.fspath.ext == '.ipynb': collector.skip_compare += ('stderr',) """ def test_conf_ignore_stderr(testdir): # Setup test config testdir.makeconftest(_ignore_stderr_code) # Setup notebook with stream outputs nb = build_nb([ "import sys", "print('test')", "print('error output', file=sys.stderr)", "print('test')\nprint('error output', file=sys.stderr)", ], mark_run=True) nb.cells[1].outputs.append(nbformat.v4.new_output( 'stream', text=u'test\n', )) nb.cells[2].outputs.append(nbformat.v4.new_output( 'stream', name='stderr', text=u'different error output', )) nb.cells[3].outputs.append(nbformat.v4.new_output( 'stream', text=u'test\n', )) nb.cells[3].outputs.append(nbformat.v4.new_output( 'stream', name='stderr', text=u'different error output', )) # Write notebook to test dir nbformat.write(nb, os.path.join( str(testdir.tmpdir), 'test_ignore.ipynb')) # Run tests result = testdir.runpytest_subprocess('--nbval', '--nbval-current-env', '.') # Check tests went off as they should: assert result.ret == 0 nbval-0.11.0/tests/test_nbdime_reporter.py000066400000000000000000000022561457135606100206430ustar00rootroot00000000000000 from utils import build_nb, add_expected_plaintext_outputs import nbdime import nbformat import os def test_nbdime(testdir, mocker): # Make a test notebook where output doesn't match input nb = build_nb(["1+1", "1+1"], mark_run=True) add_expected_plaintext_outputs(nb, ["2", "3"]) # Write notebook to test dir filename = 'test_nbdime.ipynb' nbformat.write(nb, os.path.join(str(testdir.tmpdir), filename)) # patch the run_server function so that it doesn't actually # spawn a server and display the diff. But the diff is still # calculated. mocker.patch('nbdime.webapp.nbdiffweb.run_server') result = testdir.runpytest_inprocess('--nbval', '--nbval-current-env', '--nbdime', filename) # run_server() is only called if there is a discrepancy in the notebook. # so it should have been called in this case: nbdime.webapp.nbdiffweb.run_server.assert_called_once() # note: this import must be AFTER the mocker.patch from nbval.nbdime_reporter import EXIT_TESTSFAILED assert result.ret == EXIT_TESTSFAILED nbval-0.11.0/tests/test_plugin.py000066400000000000000000000010501457135606100167500ustar00rootroot00000000000000import sys sys.path.append('..') import textwrap from nbval.plugin import * def test_get_sanitize_patterns(): file_contents = textwrap.dedent(""" [Section1] regex: foo replace: bar1 regex: quux replace: 42 [Section2 (overwrites regex 'foo')] regex: foo replace: bar2 """) patterns = get_sanitize_patterns(file_contents) assert patterns == [('foo', 'bar1'), ('quux', '42'), ('foo', 'bar2'), ] nbval-0.11.0/tests/test_timeouts.py000066400000000000000000000045551457135606100173400ustar00rootroot00000000000000import os import nbformat import pytest from utils import build_nb pytest_plugins = "pytester" def test_timeouts(testdir): # This test uses the testdir fixture from pytester, which is useful for # testing pytest plugins. It writes a notebook to a temporary dir # and then runs pytest. # Note: The test and the notebook are defined together in order to # emphasize the close dependence of the test on the structure and # content of the notebook # Setup notebook to test: sources = [ # In [1]: "from time import sleep", # In [2]: "for i in range(100000):\n" + " sleep(1)\n" + "myvar = 5", # In [3]: "a = 5", # In [4]: "print(myvar)", # In [5]: "import signal\n" + "signal.signal(signal.SIGINT, signal.SIG_IGN)\n" + "for i in range(1000):\n" + " sleep(100)", # In [6]: "b = 5", ] nb = build_nb(sources) # Write notebook to test dir nbformat.write(nb, os.path.join( str(testdir.tmpdir), 'test_timeouts.ipynb')) # Run tests result = testdir.inline_run('--nbval', '--nbval-current-env', '--nbval-cell-timeout', '5', '-s') reports = result.getreports('pytest_runtest_logreport') # Setup and teardown of cells should have no issues: setup_teardown = [r for r in reports if r.when != 'call'] for r in setup_teardown: assert r.passed reports = [r for r in reports if r.when == 'call'] assert len(reports) == 6 # Import cell should pass: if not reports[0].passed: pytest.fail("Inner exception: %s" % reports[0].longreprtext) # First timeout cell should fail, unexpectedly assert reports[1].failed and not hasattr(reports[1], 'wasxfail') # Normal cell after timeout should pass, but be expected to fail if not reports[2].passed: pytest.fail("Inner exception: %s" % reports[2].longreprtext) assert hasattr(reports[2], 'wasxfail') # Cell trying to access variable declare after loop in timeout # should fail, expectedly (marked skipped) assert reports[3].skipped and hasattr(reports[3], 'wasxfail') # Second timeout loop should fail, expectedly, and cause all following to fail assert reports[4].skipped and hasattr(reports[4], 'wasxfail') assert reports[5].skipped and hasattr(reports[5], 'wasxfail') nbval-0.11.0/tests/test_unit_tests_in_notebooks.py000066400000000000000000000051071457135606100224330ustar00rootroot00000000000000import os import glob import subprocess import pytest def create_test_cases_from_filenames(): """Idea is to create test cases based on file names. Convention is that the notebook tests start with 'test-' and end in '.ipynb'. Furthermore, we have the following structure: test-NAME-CORRECTOUTCOME-COMMENT.ipynb where the dashes are separators for: - NAME: some more detail on the kind of tests - CORRECTOUTCOME: either 'pass' or 'fail'. This is the correct results for running nbval on the notebook. - COMMENT: can be used to describe the test further. Here is an example: In [8]: glob.glob('test-*.ipynb') Out[8]: ['test-latex-fail-randomoutput.ipynb', 'test-latex-pass-correctouput.ipynb', 'test-latex-pass-failsbutignoreoutput.ipynb'] From the filenames, we can create input data for parametrized tests. """ testdata, testnames = [], [] pattern = os.path.abspath(os.path.join(os.path.dirname(__file__), "ipynb-test-samples", "test-*.ipynb")) ipynb_files = glob.glob(pattern) for filename in ipynb_files: testname = os.path.basename(filename) correct_outcome = testname.split('-')[2] assert correct_outcome in ['pass', 'fail'] testnames.append(filename) testdata.append((filename, correct_outcome)) return testnames, testdata # Create test cases testnames, testdata = create_test_cases_from_filenames() @pytest.mark.parametrize("filename, correctoutcome", testdata, ids=testnames) def test_print(filename, correctoutcome): command = ["py.test", "--nbval", "-v", "--nbval-current-env", filename] print("Starting parametrized test with filename={}, correctoutcome={}" .format(filename, correctoutcome)) print("Command about to execute is '{}'".format(command)) if os.name == 'nt': exitcode = subprocess.call(command, shell=True) else: exitcode = subprocess.call(command) if correctoutcome == 'pass': if exitcode != 0: raise AssertionError("Tests failed on ipynb (expected pass)") assert exitcode == 0 print("The call of py.test has not reported errors - this is good.") elif correctoutcome == 'fail': if exitcode == 0: raise AssertionError("Tests passed on ipynb (expected fail)") print("The call of py.test has reported errors - " + "this is good for this test.") else: raise AssertionError("correctoutcome=%r (not 'pass' or 'fail')" % correctoutcome) nbval-0.11.0/tests/utils.py000066400000000000000000000013751457135606100155650ustar00rootroot00000000000000 import nbformat def build_nb(sources, mark_run=False): """Builds a notebook of only code cells, from a list of sources """ nb = nbformat.v4.new_notebook() execution_count = 1 for src in sources: cell = nbformat.v4.new_code_cell(src) if mark_run: cell.execution_count = execution_count execution_count += 1 nb.cells.append(cell) return nb def add_expected_plaintext_outputs(nb, outputs): for i, (cell, text) in enumerate(zip(nb.cells, outputs)): if text is None: continue output = nbformat.v4.new_output( 'execute_result', data={'text/plain': [text]}, execution_count=i, ) cell.outputs.append(output)