-
Notifications
You must be signed in to change notification settings - Fork 232
/
test_examples.py
361 lines (263 loc) · 11.4 KB
/
test_examples.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
import json
from datetime import datetime, timedelta
from unittest.mock import Mock
from pathlib import Path
from click import ClickException
from click.testing import CliRunner
import pytest
from ploomber.cli import examples
from ploomber.cli import cli
def _mock_metadata(**kwargs):
default = dict(timestamp=datetime.now().timestamp(),
branch=examples._DEFAULT_BRANCH)
return {**default, **kwargs}
@pytest.fixture(scope='session')
def clone_examples():
examples.main(name=None, force=True)
@pytest.mark.parametrize('argv, kwargs', [
[
['examples'],
dict(name=None, force=False, branch=None, output=None),
],
[
['examples', '-n', 'name'],
dict(name='name', force=False, branch=None, output=None),
],
[
['examples', '--name', 'name'],
dict(name='name', force=False, branch=None, output=None),
],
[
['examples', '-f'],
dict(name=None, force=True, branch=None, output=None),
],
[
['examples', '--force'],
dict(name=None, force=True, branch=None, output=None),
],
[
['examples', '-b', 'some-branch'],
dict(name=None, force=False, branch='some-branch', output=None),
],
[
['examples', '--branch', 'some-branch'],
dict(name=None, force=False, branch='some-branch', output=None),
],
[
['examples', '--output', 'path/to/dir'],
dict(name=None, force=False, branch=None, output='path/to/dir'),
],
[
['examples', '-o', 'path/to/dir'],
dict(name=None, force=False, branch=None, output='path/to/dir'),
],
])
def test_cli(monkeypatch, argv, kwargs):
mock = Mock()
monkeypatch.setattr(examples, 'main', mock)
CliRunner().invoke(cli.cli, argv, catch_exceptions=False)
mock.assert_called_once_with(**kwargs)
def test_error_if_exception_during_execution(monkeypatch):
monkeypatch.setattr(cli.cli_module.examples, 'main',
Mock(side_effect=ValueError('some error')))
runner = CliRunner()
with pytest.raises(RuntimeError) as excinfo:
runner.invoke(cli.cli, ['examples'], catch_exceptions=False)
assert 'An error happened when executing the examples' in str(
excinfo.value)
def test_click_exception_isnt_shadowed_by_runtime_error(monkeypatch):
monkeypatch.setattr(
cli.cli_module.examples, 'main',
Mock(side_effect=ClickException('some click exception')))
runner = CliRunner()
result = runner.invoke(cli.cli, ['examples'])
assert result.exit_code == 1
assert result.output == 'Error: some click exception\n'
def test_clones_in_home_directory(monkeypatch, tmp_directory):
# patch home directory
monkeypatch.setattr(examples, '_home', str(tmp_directory))
# mock subprocess.run
mock_run = Mock()
monkeypatch.setattr(examples.subprocess, 'run', mock_run)
# mock list, otherwise this will fail since we aren't cloning
monkeypatch.setattr(examples._ExamplesManager, 'list', lambda _: None)
examples.main(name=None, force=False)
# check clones inside home directory
mock_run.assert_called_once_with([
'git', 'clone', '--depth', '1', '--branch', examples._DEFAULT_BRANCH,
'https://github.com/ploomber/projects',
str(Path(tmp_directory, 'projects'))
],
check=True)
def test_change_default_branch(monkeypatch, tmp_directory):
# mock metadata to make it look older
metadata = _mock_metadata(timestamp=(datetime.now() -
timedelta(days=1)).timestamp())
monkeypatch.setattr(examples._ExamplesManager, 'load_metadata',
lambda _: metadata)
# mock subprocess.run
mock_run = Mock()
monkeypatch.setattr(examples.subprocess, 'run', mock_run)
# mock list, otherwise this will fail since we aren't cloning
monkeypatch.setattr(examples._ExamplesManager, 'list', lambda _: None)
examples.main(name=None, force=False, branch='custom-branch')
# check clones inside home directory
mock_run.assert_called_once_with([
'git', 'clone', '--depth', '1', '--branch', 'custom-branch',
'https://github.com/ploomber/projects',
str(Path('~', '.ploomber', 'projects').expanduser())
],
check=True)
def test_does_not_download_again_if_no_explicit_branch_requested(
monkeypatch, tmp_directory):
dir_ = Path(tmp_directory, 'examples')
monkeypatch.setattr(examples, '_home', dir_)
examples.main(name=None, force=False)
# fake metadata to make it believe that we got if from another branch
meta = json.loads((dir_ / '.metadata').read_text())
meta['branch'] = 'some-other-branch'
(dir_ / '.metadata').write_text(json.dumps(meta))
# mock it so we test if we downloaded again
mock_run = Mock()
monkeypatch.setattr(examples.subprocess, 'run', mock_run)
# if called again but no force nor branch arg, it shouldn't download again
examples.main(name=None, force=False, branch=None)
examples.main(name='templates/ml-online', force=False, branch=None)
mock_run.assert_not_called()
def test_home_default_value():
assert examples._home == Path('~', '.ploomber')
def test_list(clone_examples, capsys):
examples.main(name=None, force=False)
captured = capsys.readouterr()
assert examples._DEFAULT_BRANCH in captured.out
assert 'Ploomber examples' in captured.out
assert 'Templates' in captured.out
assert 'Guides' in captured.out
assert 'Cookbook' in captured.out
def test_do_not_clone_if_recent(clone_examples, monkeypatch):
# mock metadata to make it look recent
metadata = _mock_metadata()
monkeypatch.setattr(examples._ExamplesManager, 'load_metadata',
lambda _: metadata)
mock_run = Mock()
monkeypatch.setattr(examples.subprocess, 'run', mock_run)
# prevent actual deletion
monkeypatch.setattr(examples.shutil, 'rmtree', lambda _: None)
examples.main(name=None, force=False)
mock_run.assert_not_called()
def test_clones_if_outdated(clone_examples, monkeypatch, capsys):
# mock metadata to make it look older
metadata = _mock_metadata(timestamp=(datetime.now() -
timedelta(days=1)).timestamp(),
branch='another-branch')
monkeypatch.setattr(examples._ExamplesManager, 'load_metadata',
lambda _: metadata)
mock_run = Mock()
monkeypatch.setattr(examples.subprocess, 'run', mock_run)
# prevent actual deletion
monkeypatch.setattr(examples.shutil, 'rmtree', lambda _: None)
examples.main(name=None, force=False)
mock_run.assert_called_once()
captured = capsys.readouterr()
assert 'Examples copy is more than 1 day old...' in captured.out
def test_clones_if_different_branch(clone_examples, monkeypatch, capsys):
# mock metadata to make it look like it's a copy from another branch
metadata = _mock_metadata(branch='another-branch')
monkeypatch.setattr(examples._ExamplesManager, 'load_metadata',
lambda _: metadata)
mock_run = Mock()
monkeypatch.setattr(examples.subprocess, 'run', mock_run)
# prevent actual deletion
monkeypatch.setattr(examples.shutil, 'rmtree', lambda _: None)
examples.main(name=None, force=False, branch='some-new-branch')
mock_run.assert_called_once()
captured = capsys.readouterr()
assert 'Different branch requested...' in captured.out
def test_clones_if_corrupted_metadata(clone_examples, tmp_directory,
monkeypatch):
# corrupt metadata
not_json = Path(tmp_directory, 'not.json')
not_json.write_text('hello')
monkeypatch.setattr(examples._ExamplesManager, 'path_to_metadata',
not_json)
mock_run = Mock()
monkeypatch.setattr(examples.subprocess, 'run', mock_run)
# prevent actual deletion
monkeypatch.setattr(examples.shutil, 'rmtree', lambda _: None)
examples.main(name=None, force=False)
mock_run.assert_called_once()
def test_force_clone(clone_examples, monkeypatch):
# mock metadata to make it look recent
metadata = _mock_metadata()
monkeypatch.setattr(examples._ExamplesManager, 'load_metadata',
lambda _: metadata)
mock_run = Mock()
monkeypatch.setattr(examples.subprocess, 'run', mock_run)
# prevent actual deletion
monkeypatch.setattr(examples.shutil, 'rmtree', lambda _: None)
# force download
examples.main(name=None, force=True)
mock_run.assert_called_once()
def test_copy_example(clone_examples, tmp_directory):
examples.main(name='templates/ml-online', force=False)
assert Path(tmp_directory, 'templates/ml-online').is_dir()
assert Path(tmp_directory, 'templates/ml-online', 'src',
'ml_online').is_dir()
@pytest.mark.parametrize('target', ['custom-dir', 'custom/dir'])
def test_copy_to_custom_directory(clone_examples, tmp_directory, target):
examples.main('templates/ml-online', output=target)
assert Path(tmp_directory, target).is_dir()
assert Path(tmp_directory, target, 'src', 'ml_online').is_dir()
def test_error_unknown_example(clone_examples, capsys):
examples.main(name='not-an-example', force=False)
captured = capsys.readouterr()
assert "There is no example named 'not-an-example'" in captured.out
def test_error_if_already_exists(clone_examples, tmp_directory):
examples.main(name='templates/ml-online', force=False)
with pytest.raises(ClickException) as excinfo:
examples.main(name='templates/ml-online', force=False)
expected = ("'templates/ml-online' already exists in the current working "
"directory, please rename it or move it to another "
"location and try again.")
assert expected == str(excinfo.value)
def test_error_if_git_clone_fails(monkeypatch):
# mock metadata to make it look recent
metadata = dict(timestamp=datetime.now().timestamp())
monkeypatch.setattr(examples._ExamplesManager, 'load_metadata',
lambda _: metadata)
mock_run = Mock(side_effect=Exception('message'))
monkeypatch.setattr(examples.subprocess, 'run', mock_run)
with pytest.raises(RuntimeError) as excinfo:
examples.main(name=None, force=True)
assert str(excinfo.value) == (
'An error occurred when downloading '
'examples. Verify git is installed and your internet connection. '
"(Error message: 'message')")
@pytest.mark.parametrize('md, expected', [
['', None],
['<!-- start header -->\n', None],
['\n\n<!-- end header -->\n\n', 2],
["""
<!-- start header -->
<!-- end header -->
""", 3],
])
def test_find_header(md, expected):
assert examples._find_header(md) == expected
@pytest.mark.parametrize('md, clean', [
['', ''],
['there is no header', 'there is no header'],
['stuff\n<!-- end header -->\nthings', 'things'],
['more\nstuff\n<!-- end header -->\n\nthings', '\nthings'],
])
def test_skip_header(md, clean):
assert examples._skip_header(md) == clean
@pytest.mark.parametrize('md, clean', [
['', ''],
['there is no header', 'there is no header'],
['stuff\n<!-- end header -->\nthings', 'things'],
['<!-- start description -->\nthings', 'things'],
['<!-- end description -->\nthings', 'things'],
])
def test_cleanup_markdown(md, clean):
assert examples._cleanup_markdown(md) == clean