1# -*- coding: utf-8 -*-
2
3"""Tests for `generate_files` function and related errors raising.
4
5Use the global clean_system fixture and run additional teardown code to remove
6some special folders.
7
8For a better understanding - order of fixture calls:
9clean_system setup code
10remove_additional_folders setup code
11remove_additional_folders teardown code
12clean_system teardown code
13"""
14
15from __future__ import unicode_literals
16
17import io
18import os
19
20import pytest
21from binaryornot.check import is_binary
22from cookiecutter import exceptions, generate, utils
23
24
25@pytest.mark.parametrize('invalid_dirname', ['', '{foo}', '{{foo', 'bar}}'])
26def test_ensure_dir_is_templated_raises(invalid_dirname):
27    """Verify `ensure_dir_is_templated` raises on wrong directories names input."""
28    with pytest.raises(exceptions.NonTemplatedInputDirException):
29        generate.ensure_dir_is_templated(invalid_dirname)
30
31
32@pytest.fixture(scope='function')
33def remove_additional_folders():
34    """Remove some special folders which are created by the tests."""
35    yield
36    if os.path.exists('inputpizzä'):
37        utils.rmtree('inputpizzä')
38    if os.path.exists('inputgreen'):
39        utils.rmtree('inputgreen')
40    if os.path.exists('inputbinary_files'):
41        utils.rmtree('inputbinary_files')
42    if os.path.exists('tests/custom_output_dir'):
43        utils.rmtree('tests/custom_output_dir')
44    if os.path.exists('inputpermissions'):
45        utils.rmtree('inputpermissions')
46
47
48@pytest.mark.usefixtures('clean_system', 'remove_additional_folders')
49def test_generate_files_nontemplated_exception():
50    """
51    Verify `generate_files` raises when no directories to render exist.
52
53    Note: Check `tests/test-generate-files-nontemplated` location to understand.
54    """
55    with pytest.raises(exceptions.NonTemplatedInputDirException):
56        generate.generate_files(
57            context={'cookiecutter': {'food': 'pizza'}},
58            repo_dir='tests/test-generate-files-nontemplated',
59        )
60
61
62@pytest.mark.usefixtures('clean_system', 'remove_additional_folders')
63def test_generate_files():
64    """Verify directory name correctly rendered with unicode containing context."""
65    generate.generate_files(
66        context={'cookiecutter': {'food': 'pizzä'}},
67        repo_dir='tests/test-generate-files',
68    )
69
70    simple_file = 'inputpizzä/simple.txt'
71    assert os.path.isfile(simple_file)
72
73    simple_text = io.open(simple_file, 'rt', encoding='utf-8').read()
74    assert simple_text == u'I eat pizzä'
75
76
77@pytest.mark.usefixtures('clean_system', 'remove_additional_folders')
78def test_generate_files_with_trailing_newline():
79    """Verify new line not removed by templating engine after folder generation."""
80    generate.generate_files(
81        context={'cookiecutter': {'food': 'pizzä'}},
82        repo_dir='tests/test-generate-files',
83    )
84
85    newline_file = 'inputpizzä/simple-with-newline.txt'
86    assert os.path.isfile(newline_file)
87
88    with io.open(newline_file, 'r', encoding='utf-8') as f:
89        simple_text = f.read()
90    assert simple_text == u'I eat pizzä\n'
91
92
93@pytest.mark.usefixtures('clean_system', 'remove_additional_folders')
94def test_generate_files_binaries():
95    """Verify binary files created during directory generation."""
96    generate.generate_files(
97        context={'cookiecutter': {'binary_test': 'binary_files'}},
98        repo_dir='tests/test-generate-binaries',
99    )
100
101    assert is_binary('inputbinary_files/logo.png')
102    assert is_binary('inputbinary_files/.DS_Store')
103    assert not is_binary('inputbinary_files/readme.txt')
104    assert is_binary('inputbinary_files/some_font.otf')
105    assert is_binary('inputbinary_files/binary_files/logo.png')
106    assert is_binary('inputbinary_files/binary_files/.DS_Store')
107    assert not is_binary('inputbinary_files/binary_files/readme.txt')
108    assert is_binary('inputbinary_files/binary_files/some_font.otf')
109    assert is_binary('inputbinary_files/binary_files/binary_files/logo.png')
110
111
112@pytest.mark.usefixtures('clean_system', 'remove_additional_folders')
113def test_generate_files_absolute_path():
114    """Verify usage of `abspath` does not change files generation behaviour."""
115    generate.generate_files(
116        context={'cookiecutter': {'food': 'pizzä'}},
117        repo_dir=os.path.abspath('tests/test-generate-files'),
118    )
119    assert os.path.isfile('inputpizzä/simple.txt')
120
121
122@pytest.mark.usefixtures('clean_system', 'remove_additional_folders')
123def test_generate_files_output_dir():
124    """Verify `output_dir` option for `generate_files` changing location correctly."""
125    os.mkdir('tests/custom_output_dir')
126    project_dir = generate.generate_files(
127        context={'cookiecutter': {'food': 'pizzä'}},
128        repo_dir=os.path.abspath('tests/test-generate-files'),
129        output_dir='tests/custom_output_dir',
130    )
131    assert os.path.isfile('tests/custom_output_dir/inputpizzä/simple.txt')
132    assert project_dir == os.path.abspath('tests/custom_output_dir/inputpizzä')
133
134
135@pytest.mark.usefixtures('clean_system', 'remove_additional_folders')
136def test_generate_files_permissions():
137    """Verify generates files respect source files permissions.
138
139    simple.txt and script.sh should retain their respective 0o644 and 0o755
140    permissions.
141    """
142    generate.generate_files(
143        context={'cookiecutter': {'permissions': 'permissions'}},
144        repo_dir='tests/test-generate-files-permissions',
145    )
146
147    assert os.path.isfile('inputpermissions/simple.txt')
148
149    # simple.txt should still be 0o644
150    tests_simple_file = os.path.join(
151        'tests',
152        'test-generate-files-permissions',
153        'input{{cookiecutter.permissions}}',
154        'simple.txt',
155    )
156    tests_simple_file_mode = os.stat(tests_simple_file).st_mode & 0o777
157
158    input_simple_file = os.path.join('inputpermissions', 'simple.txt')
159    input_simple_file_mode = os.stat(input_simple_file).st_mode & 0o777
160    assert tests_simple_file_mode == input_simple_file_mode
161
162    assert os.path.isfile('inputpermissions/script.sh')
163
164    # script.sh should still be 0o755
165    tests_script_file = os.path.join(
166        'tests',
167        'test-generate-files-permissions',
168        'input{{cookiecutter.permissions}}',
169        'script.sh',
170    )
171    tests_script_file_mode = os.stat(tests_script_file).st_mode & 0o777
172
173    input_script_file = os.path.join('inputpermissions', 'script.sh')
174    input_script_file_mode = os.stat(input_script_file).st_mode & 0o777
175    assert tests_script_file_mode == input_script_file_mode
176
177
178@pytest.mark.usefixtures('clean_system', 'remove_additional_folders')
179def test_generate_files_with_overwrite_if_exists_with_skip_if_file_exists():
180    """Verify `skip_if_file_exist` has priority over `overwrite_if_exists`."""
181    simple_file = 'inputpizzä/simple.txt'
182    simple_with_new_line_file = 'inputpizzä/simple-with-newline.txt'
183
184    os.makedirs('inputpizzä')
185    with open(simple_file, 'w') as f:
186        f.write('temp')
187
188    generate.generate_files(
189        context={'cookiecutter': {'food': 'pizzä'}},
190        repo_dir='tests/test-generate-files',
191        overwrite_if_exists=True,
192        skip_if_file_exists=True,
193    )
194
195    assert os.path.isfile(simple_file)
196    assert os.path.isfile(simple_with_new_line_file)
197
198    simple_text = io.open(simple_file, 'rt', encoding='utf-8').read()
199    assert simple_text == u'temp'
200
201
202@pytest.mark.usefixtures('clean_system', 'remove_additional_folders')
203def test_generate_files_with_skip_if_file_exists():
204    """Verify existed files not removed if error raised with `skip_if_file_exists`."""
205    simple_file = 'inputpizzä/simple.txt'
206    simple_with_new_line_file = 'inputpizzä/simple-with-newline.txt'
207
208    os.makedirs('inputpizzä')
209    with open(simple_file, 'w') as f:
210        f.write('temp')
211
212    with pytest.raises(exceptions.OutputDirExistsException):
213        generate.generate_files(
214            context={'cookiecutter': {'food': 'pizzä'}},
215            repo_dir='tests/test-generate-files',
216            skip_if_file_exists=True,
217        )
218
219    assert os.path.isfile(simple_file)
220    assert not os.path.exists(simple_with_new_line_file)
221
222    simple_text = io.open(simple_file, 'rt', encoding='utf-8').read()
223    assert simple_text == u'temp'
224
225
226@pytest.mark.usefixtures('clean_system', 'remove_additional_folders')
227def test_generate_files_with_overwrite_if_exists():
228    """Verify overwrite_if_exists overwrites old files."""
229    simple_file = 'inputpizzä/simple.txt'
230    simple_with_new_line_file = 'inputpizzä/simple-with-newline.txt'
231
232    os.makedirs('inputpizzä')
233    with open(simple_file, 'w') as f:
234        f.write('temp')
235
236    generate.generate_files(
237        context={'cookiecutter': {'food': 'pizzä'}},
238        repo_dir='tests/test-generate-files',
239        overwrite_if_exists=True,
240    )
241
242    assert os.path.isfile(simple_file)
243    assert os.path.isfile(simple_with_new_line_file)
244
245    simple_text = io.open(simple_file, 'rt', encoding='utf-8').read()
246    assert simple_text == u'I eat pizzä'
247
248
249@pytest.fixture
250def undefined_context():
251    """Fixture. Populate context variable for future tests."""
252    return {
253        'cookiecutter': {'project_slug': 'testproject', 'github_username': 'hackebrot'}
254    }
255
256
257def test_raise_undefined_variable_file_name(tmpdir, undefined_context):
258    """Verify correct error raised when file name cannot be rendered."""
259    output_dir = tmpdir.mkdir('output')
260
261    with pytest.raises(exceptions.UndefinedVariableInTemplate) as err:
262        generate.generate_files(
263            repo_dir='tests/undefined-variable/file-name/',
264            output_dir=str(output_dir),
265            context=undefined_context,
266        )
267    error = err.value
268    assert "Unable to create file '{{cookiecutter.foobar}}'" == error.message
269    assert error.context == undefined_context
270
271    assert not output_dir.join('testproject').exists()
272
273
274def test_raise_undefined_variable_file_name_existing_project(tmpdir, undefined_context):
275    """Verify correct error raised when file name cannot be rendered."""
276    output_dir = tmpdir.mkdir('output')
277
278    output_dir.join('testproject').mkdir()
279
280    with pytest.raises(exceptions.UndefinedVariableInTemplate) as err:
281        generate.generate_files(
282            repo_dir='tests/undefined-variable/file-name/',
283            output_dir=str(output_dir),
284            context=undefined_context,
285            overwrite_if_exists=True,
286        )
287    error = err.value
288    assert "Unable to create file '{{cookiecutter.foobar}}'" == error.message
289    assert error.context == undefined_context
290
291    assert output_dir.join('testproject').exists()
292
293
294def test_raise_undefined_variable_file_content(tmpdir, undefined_context):
295    """Verify correct error raised when file content cannot be rendered."""
296    output_dir = tmpdir.mkdir('output')
297
298    with pytest.raises(exceptions.UndefinedVariableInTemplate) as err:
299        generate.generate_files(
300            repo_dir='tests/undefined-variable/file-content/',
301            output_dir=str(output_dir),
302            context=undefined_context,
303        )
304    error = err.value
305    assert "Unable to create file 'README.rst'" == error.message
306    assert error.context == undefined_context
307
308    assert not output_dir.join('testproject').exists()
309
310
311def test_raise_undefined_variable_dir_name(tmpdir, undefined_context):
312    """Verify correct error raised when directory name cannot be rendered."""
313    output_dir = tmpdir.mkdir('output')
314
315    with pytest.raises(exceptions.UndefinedVariableInTemplate) as err:
316        generate.generate_files(
317            repo_dir='tests/undefined-variable/dir-name/',
318            output_dir=str(output_dir),
319            context=undefined_context,
320        )
321    error = err.value
322
323    directory = os.path.join('testproject', '{{cookiecutter.foobar}}')
324    msg = "Unable to create directory '{}'".format(directory)
325    assert msg == error.message
326
327    assert error.context == undefined_context
328
329    assert not output_dir.join('testproject').exists()
330
331
332def test_raise_undefined_variable_dir_name_existing_project(tmpdir, undefined_context):
333    """Verify correct error raised when directory name cannot be rendered."""
334    output_dir = tmpdir.mkdir('output')
335
336    output_dir.join('testproject').mkdir()
337
338    with pytest.raises(exceptions.UndefinedVariableInTemplate) as err:
339        generate.generate_files(
340            repo_dir='tests/undefined-variable/dir-name/',
341            output_dir=str(output_dir),
342            context=undefined_context,
343            overwrite_if_exists=True,
344        )
345    error = err.value
346
347    directory = os.path.join('testproject', '{{cookiecutter.foobar}}')
348    msg = "Unable to create directory '{}'".format(directory)
349    assert msg == error.message
350
351    assert error.context == undefined_context
352
353    assert output_dir.join('testproject').exists()
354
355
356def test_raise_undefined_variable_project_dir(tmpdir):
357    """Verify correct error raised when directory name cannot be rendered."""
358    output_dir = tmpdir.mkdir('output')
359
360    with pytest.raises(exceptions.UndefinedVariableInTemplate) as err:
361        generate.generate_files(
362            repo_dir='tests/undefined-variable/dir-name/',
363            output_dir=str(output_dir),
364            context={},
365        )
366    error = err.value
367    msg = "Unable to create project directory '{{cookiecutter.project_slug}}'"
368    assert msg == error.message
369    assert error.context == {}
370
371    assert not output_dir.join('testproject').exists()
372