@@ -25,7 +25,7 @@ class TestTutorials(unittest.TestCase):
25
25
26
26
def _execute_notebook (
27
27
self , base_dir : str , path : str
28
- ) -> int :
28
+ ) -> ty . Tuple [ ty . Type [ nbformat . NotebookNode ], ty . List [ str ]] :
29
29
"""Execute a notebook via nbconvert and collect output.
30
30
31
31
Parameters
@@ -37,22 +37,23 @@ def _execute_notebook(
37
37
38
38
Returns
39
39
-------
40
- int
41
- (return code )
40
+ Tuple
41
+ (parsed nbformat.NotebookNode object, list of execution errors )
42
42
"""
43
43
44
44
cwd = os .getcwd ()
45
45
dir_name , notebook = os .path .split (path )
46
46
try :
47
47
env = self ._update_pythonpath (base_dir , dir_name )
48
- result = self ._convert_and_execute_notebook (notebook , env )
49
- errors = self ._collect_errors_from_all_cells (result )
48
+ nb = self ._convert_and_execute_notebook (notebook , env )
49
+ errors = self ._collect_errors_from_all_cells (nb )
50
50
except Exception as e :
51
- errors = - 1
51
+ nb = None
52
+ errors = str (e )
52
53
finally :
53
54
os .chdir (cwd )
54
55
55
- return errors
56
+ return nb , errors
56
57
57
58
def _update_pythonpath (
58
59
self , base_dir : str , dir_name : str
@@ -90,7 +91,7 @@ def _update_pythonpath(
90
91
91
92
def _convert_and_execute_notebook (
92
93
self , notebook : str , env : ty .Dict [str , str ]
93
- ):
94
+ ) -> ty . Type [ nbformat . NotebookNode ] :
94
95
"""Covert notebook and execute it.
95
96
96
97
Parameters
@@ -105,23 +106,25 @@ def _convert_and_execute_notebook(
105
106
nb : nbformat.NotebookNode
106
107
Notebook dict-like node with attribute-access
107
108
"""
108
- with tempfile .NamedTemporaryFile (mode = "w+t" , suffix = ".py " ) as fout :
109
+ with tempfile .NamedTemporaryFile (mode = "w+t" , suffix = ".ipynb " ) as fout :
109
110
args = [
110
111
"jupyter" ,
111
112
"nbconvert" ,
112
113
"--to" ,
113
- "python" ,
114
+ "notebook" ,
115
+ "--execute" ,
116
+ "--ExecutePreprocessor.timeout=-1" ,
114
117
"--output" ,
115
- fout .name [ 0 : - 3 ] ,
118
+ fout .name ,
116
119
notebook ,
117
120
]
118
121
subprocess .check_call (args , env = env ) # noqa: S603
122
+
119
123
fout .seek (0 )
120
- return subprocess .run (["ipython" , "-c" , fout .read ()], # noqa # nosec
121
- env = env ) # noqa # nosec
124
+ return nbformat .read (fout , nbformat .current_nbformat )
122
125
123
126
def _collect_errors_from_all_cells (
124
- self , result
127
+ self , nb : nbformat . NotebookNode
125
128
) -> ty .List [str ]:
126
129
"""Collect errors from executed notebook.
127
130
@@ -135,9 +138,13 @@ def _collect_errors_from_all_cells(
135
138
List
136
139
Collection of errors
137
140
"""
138
- if result .returncode != 0 :
139
- result .check_returncode ()
140
- return result .returncode
141
+ errors = []
142
+ for cell in nb .cells :
143
+ if "outputs" in cell :
144
+ for output in cell ["outputs" ]:
145
+ if output .output_type == "error" :
146
+ errors .append (output )
147
+ return errors
141
148
142
149
def _run_notebook (self , notebook : str , e2e_tutorial : bool = False ):
143
150
"""Run a specific notebook
@@ -176,10 +183,22 @@ def _run_notebook(self, notebook: str, e2e_tutorial: bool = False):
176
183
177
184
# If the notebook is found execute it and store any errors
178
185
for notebook_name in discovered_notebooks :
179
- errors = self ._execute_notebook (
186
+ nb , errors = self ._execute_notebook (
180
187
str (tutorials_directory ), notebook_name
181
188
)
182
- self .assertEqual (errors , 0 )
189
+ errors_joined = (
190
+ "\n " .join (errors ) if isinstance (errors , list ) else errors
191
+ )
192
+ if errors :
193
+ errors_record [notebook_name ] = (errors_joined , nb )
194
+
195
+ self .assertFalse (
196
+ errors_record ,
197
+ "Failed to execute Jupyter Notebooks \
198
+ with errors: \n {}" .format (
199
+ errors_record
200
+ ),
201
+ )
183
202
finally :
184
203
os .chdir (cwd )
185
204
0 commit comments