Coverage for src/ensae_teaching_cs/automation/notebook_test_helper.py: 86%

125 statements  

« prev     ^ index     » next       coverage.py v7.1.0, created at 2023-04-28 06:23 +0200

1# -*- coding: utf-8 -*- 

2""" 

3@file 

4@brief Some automation helpers to test notebooks and check they are still working fine. 

5""" 

6import os 

7import shutil 

8from pyquickhelper.loghelper import noLOG 

9from pyquickhelper.ipythonhelper import execute_notebook_list, execute_notebook_list_finalize_ut 

10from pyquickhelper.ipythonhelper import get_additional_paths as pyq_get_additional_paths 

11from pyquickhelper.pycode import get_temp_folder 

12 

13 

14def ls_notebooks(subfolder): 

15 """ 

16 Lists the notebooks in a particular subfolder. 

17 

18 @param subfolder subfolder (related to this module) 

19 @return list of files 

20 """ 

21 this = os.path.abspath(os.path.dirname(__file__)) 

22 docnote = os.path.join( 

23 this, 

24 "..", 

25 "..", 

26 "..", 

27 "_doc", 

28 "notebooks", 

29 subfolder) 

30 notes = [ 

31 os.path.normpath( 

32 os.path.join( 

33 docnote, 

34 _)) for _ in os.listdir(docnote)] 

35 

36 keepnote = [] 

37 for i, note in enumerate(notes): 

38 ext = os.path.splitext(note)[-1] 

39 if ext != ".ipynb": 

40 continue 

41 keepnote.append(note) 

42 return keepnote 

43 

44 

45def get_additional_paths(): 

46 """ 

47 Returns a list of paths to add before running the notebooks, 

48 paths to :epkg:`pyquickhelper`, :epkg:`pyensae`, :epkg:`pymmails`. 

49 

50 @return list of paths 

51 """ 

52 import pyquickhelper 

53 import pyensae 

54 import pymmails 

55 import pymyinstall 

56 import jyquickhelper 

57 addpath = [os.path.dirname(pyquickhelper.__file__), 

58 os.path.dirname(pyensae.__file__), 

59 os.path.dirname(pymmails.__file__), 

60 os.path.dirname(pymyinstall.__file__), 

61 os.path.dirname(jyquickhelper.__file__), 

62 os.path.join(os.path.abspath(os.path.dirname(__file__)), ".."), 

63 ] 

64 try: 

65 import mlstatpy 

66 addpath.append(os.path.dirname(mlstatpy.__file__)) 

67 except ImportError: 

68 pass 

69 addpath = [os.path.normpath(os.path.join(_, "..")) for _ in addpath] 

70 return addpath 

71 

72 

73def clean_function_1a(code): 

74 """ 

75 Function which cleans cells when unittesting notebooks 1A. 

76 

77 @param code cell content 

78 @return modified code 

79 """ 

80 code = code.replace( 

81 'run_cmd("exemple.xlsx"', 

82 'skip_run_cmd("exemple.xlsx"') 

83 

84 skip = ["faire une chose avec la probabilité 0.7", 

85 "# déclenche une exception", 

86 "# pour lancer Excel", 

87 "for k in list_exercice_1 :", 

88 "return ....", 

89 "return [ .... ]", 

90 "def __init__(self, ...) :", 

91 "dictionnaire_depart.items() [0]", 

92 "iterateur(0,10) [0]", 

93 "# ...... à remplir", 

94 'String.Join(",", a.Select(c=>c.ToString()).ToArray())', 

95 "# elle n'existe pas encore", 

96 "print(tab[i] + tab[i+1])", 

97 "if n = 1:", 

98 "clenche une exception", 

99 'y = "a" * 3 + 1', 

100 "i = list_exercice_1.index(k)", 

101 "raise KeyError('Arrêtons-nous...')", 

102 ] 

103 rep = [("# ...", "pass # "), 

104 ("%timeit", "#%timeit"), 

105 ('%system "exemple.xlsx"', '#%system "exemple.xlsx"'), 

106 ('%system "data.xlsx"', '#%system "data.xlsx"'), 

107 ('http://telechargement.insee.fr/fichiersdetail/etatcivil2012/dbase/', 

108 'http://www.xavierdupre.fr/enseignement/complements/'), 

109 ('https://www.insee.fr/fr/statistiques/fichier/2011542/', 

110 'http://www.xavierdupre.fr/enseignement/complements/'), 

111 ] 

112 spl = ["# ......", 

113 "# elle n'existe pas encore", 

114 ] 

115 

116 for s in skip: 

117 if s in code: 

118 return "" 

119 

120 for s in spl: 

121 if s in code: 

122 code = code.split(s)[0] 

123 

124 for s in rep: 

125 code = code.replace(s[0], s[1]) 

126 

127 return code 

128 

129 

130def execute_notebooks(folder, notebooks, filter, clean_function=None, 

131 fLOG=noLOG, deepfLOG=noLOG, replacements=None, dump=None, 

132 additional_path=None): 

133 """ 

134 Executes a list of notebooks. 

135 

136 @param folder folder 

137 @param notebooks list of notebooks 

138 @param filter function which validates the notebooks to test (True means will be tested) 

139 @param clean_function cleaning function to apply to the code before running it 

140 @param fLOG logging function 

141 @param deepfLOG logging function used to run the notebook 

142 @param replacements replacements 

143 @param dump see function `execute_notebook_list_finalize_ut 

144 <http://www.xavierdupre.fr/app/pyquickhelper/helpsphinx/pyquickhelper/ipythonhelper/run_notebook.html# 

145 pyquickhelper.ipythonhelper.run_notebook.execute_notebook_list_finalize_ut>`_ 

146 @param additional_path additional path to add 

147 @return dictionary { notebook_file: (isSuccess, outout) } 

148 

149 The signature of function ``filter`` is:: 

150 

151 def filter(i, filename): 

152 return True or False 

153 """ 

154 

155 def valid_cell(cell): 

156 if "%system" in cell: 

157 return False 

158 if "df.plot(...)" in cell: 

159 return False 

160 if 'df["difference"] = ...' in cell: 

161 return False 

162 if 'remote_open' in cell: 

163 return None 

164 if 'blobpassword' in cell: 

165 return None 

166 if 'String.Join(",", a.Select(c=>c.ToString()).ToArray())' in cell: 

167 return False 

168 if 'Speech.VocalSynthesis("ENSAE", "fr-FR","","")' in cell: 

169 return False 

170 if 'Speech.VocalSynthesis(text, lang, voice, filename)' in cell: 

171 return False 

172 if "%%SPEAK fr-FR" in cell: 

173 return False 

174 if " noeud tri n'est pas encore défini" in cell: 

175 return False 

176 if "nuplet[1] = 5" in cell: 

177 return False 

178 if cell == "dico[0]": 

179 return False 

180 if cell == "dico[ [4,6] ] = 6": 

181 return False 

182 return True 

183 

184 addpaths = get_additional_paths() 

185 if additional_path is not None: 

186 addpaths += additional_path 

187 if filter: 

188 notebooks = [_ for i, _ in enumerate(notebooks) if filter(i, _)] 

189 if len(notebooks) == 0: 

190 raise ValueError("Empty list of notebooks.") 

191 res = execute_notebook_list(folder, notebooks, fLOG=fLOG, clean_function=clean_function, 

192 valid=valid_cell, additional_path=addpaths, 

193 replacements=replacements) 

194 execute_notebook_list_finalize_ut( 

195 res, fLOG=fLOG, dump=dump) 

196 return res 

197 

198 

199def copy_data_file(notebook_folder, filename, dest, fLOG=noLOG): 

200 """ 

201 Copies a data file from a notebook folder to the current folder. 

202 

203 @param notebook_folder notebook_folder 

204 @param filename filename or list of file names 

205 @param dest destination folder 

206 @parm fLOG logging function 

207 @return copied files 

208 """ 

209 if isinstance(filename, list): 

210 return [copy_data_file(notebook_folder, f, dest) for f in filename] 

211 else: 

212 src = os.path.abspath(os.path.join(os.path.dirname( 

213 __file__), "..", "..", "..", "_doc", "notebooks", notebook_folder, filename)) 

214 if not os.path.exists(src): 

215 raise FileNotFoundError(src) 

216 if not os.path.exists(dest): 

217 raise FileNotFoundError(dest) 

218 shutil.copy(src, dest) 

219 res = os.path.join(dest, os.path.split(src)[-1]) 

220 fLOG("copy", res) 

221 return res 

222 

223 

224def a_test_notebook_runner(filename, name, folder, valid=None, copy_files=None, modules=None, fLOG=noLOG): 

225 """ 

226 Runs and tests a specific list of notebooks. 

227 The function raises an exception if the execution fails. 

228 

229 @param filename test filename 

230 @param name substring to look into notebook filenames 

231 @param folder where to look for notebooks 

232 @param valid skip cells if valid is False, None for all valid 

233 @param copy_files files to copy before running the notebooks. 

234 @param modules list of extra dependencies 

235 @param fLOG logging function 

236 """ 

237 filename = os.path.abspath(filename) 

238 temp = get_temp_folder(filename, f"temp_notebook_123_{name}") 

239 doc = os.path.normpath(os.path.join( 

240 temp, "..", "..", "..", "_doc", "notebooks", folder)) 

241 if not os.path.exists(doc): 

242 raise FileNotFoundError(doc) 

243 keepnote = [os.path.join(doc, _) for _ in os.listdir( 

244 doc) if name in _ and ".ipynb" in _ and ".ipynb_checkpoints" not in _] 

245 if len(keepnote) == 0: 

246 raise AssertionError("No found notebook in '{0}'\n{1}".format( 

247 doc, "\n".join(os.listdir(doc)))) 

248 

249 if copy_files is not None: 

250 for name_ in copy_files: 

251 dest = os.path.join(temp, name_) 

252 dest_dir = os.path.dirname(dest) 

253 if not os.path.exists(dest_dir): 

254 os.mkdir(dest_dir) 

255 src_file = os.path.join(doc, name_) 

256 fLOG( 

257 f"[a_test_notebook_runner] copy '{src_file}' to '{dest_dir}'.") 

258 shutil.copy(src_file, dest_dir) 

259 

260 import pyquickhelper 

261 import jyquickhelper 

262 import pyensae 

263 import ensae_teaching_cs 

264 base = [jyquickhelper, pyquickhelper, pyensae, ensae_teaching_cs] 

265 if modules: 

266 base.extend(modules) 

267 add_path = pyq_get_additional_paths(base) 

268 res = execute_notebook_list( 

269 temp, keepnote, additional_path=add_path, valid=valid, fLOG=fLOG) 

270 execute_notebook_list_finalize_ut(res, fLOG=fLOG, dump=ensae_teaching_cs)