Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Incompatibility between execute=False and tb.patch() #146

Open
JMMarchant opened this issue Jun 10, 2022 · 0 comments
Open

Incompatibility between execute=False and tb.patch() #146

JMMarchant opened this issue Jun 10, 2022 · 0 comments
Labels
bug Something isn't working

Comments

@JMMarchant
Copy link

JMMarchant commented Jun 10, 2022

It appears that is impossible to use the execute=False capability of testbook and tb.patch() together due to patched code getting run twice.

Consider this example with a simple two cell notebook; the first cell defines a simple function, the second calls it and prints out some results:

import io

from testbook import testbook

notebook_str = """
{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f92b5b71",
   "metadata": {},
   "outputs": [],
   "source": [
    "def test():\\n",
    "    return int()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ece15770",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(test())\\n",
    "print(test)"
   ]
  }
 ],
 "metadata": {
  "jupytext": {
   "hide_notebook_metadata": true
  },
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "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.13"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
"""

# Execute is False as we want to patch the target method before execution
with testbook(io.StringIO(notebook_str), execute=False) as tb:
    
    # Patch `int()` but could be any imported function
    with tb.patch("__main__.int", return_value="Hello World!") as mock_test:
        tb.execute()

        # Print cell outputs nicely so we can see what was printed
        # Mocked "Hello World!" is output as expected
        for idx, cell in enumerate(tb.cells):
            outputs_texts = [o["text"].strip() for o in cell.outputs]
            outputs_texts = "\n".join(outputs_texts)
            outputs_texts = outputs_texts.split("\n")

            if outputs_texts:
                for o_idx, output in enumerate(outputs_texts):
                    print(f"{idx}.{o_idx}: {output}")
            else:
                print(f"{idx}: No output")

        # I would expect this to work as executed in one cell
        mock_test.assert_called_once()  #  AssertionError: Expected 'int' to have been called once. Called 0 times.

Output:

0.0: 
1.0: Hello World!
1.1: <function test at 0x10b04e430>
2.0: 
AssertionError: Expected 'int' to have been called once. Called 0 times.

The underlying issue seems to be that .patch() works by injecting a new cell at the end, executing it immediately, but leaving it in place (rather than popping it off). This means that the patch-cell gets called twice: once at injection, once at .execute() which results in a new Mock instance being created and assigned to the same variable name.

Ideally there should be a way to specify to pop off the patch-cell (by passing through the appropriate kwarg to .inject()) or even for this to be the default behaviour.

@rohitsanj rohitsanj added the bug Something isn't working label Aug 4, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants