Exemple #1
0
def test_sip_install(tmpcwd):
    '''
    When SIP dependencies, install them correctly
    '''
    project = project1.copy()
    project.project_py['entry_points'] = {
        'console_scripts': [
            'mycli = operation.mittens.main:main',
        ],
    }
    project.files[Path(
        'requirements.in')] = 'pytest\nsip==4.17\nPyQt5==5.5.1\n'
    project.files[Path('operation/mittens/main.py')] = dedent('''\
        import pytest
        import PyQt5
        
        def main():
            print('meow')
        ''')
    create_project(project)

    pb.local['ct-mkproject']()

    # When mkvenv, all good
    result = mkvenv()
    assert result.exit_code == 0, result.output
    assert Path('venv/bin/python3.5').exists(
    )  # project.py requested python version 3.5

    # When call mycli, all good
    stdout = pb.local['venv/bin/mycli']()
    assert 'meow' in stdout
def create_release_project(project=project1, test_index=True):
    '''
    Create valid project for release
    '''
    project = project.copy()
    test_succeed_path = Path('operation/mittens/tests/test_succeed.py')
    project.files[test_succeed_path] = extra_files[test_succeed_path]
    if test_index:
        project.project_py['index_test'] = 'pypitest'
    create_project(project)

    git_('add', '.')
    mkproject()
    git_['commit', '-m', 'Initial commit'] & pb.FG

    # Create repo and use as remote
    path = Path.cwd() / '.cache'  # some location ignored by .gitignore
    path.mkdir()
    path /= 'other_repo'
    path.mkdir()
    with pb.local.cwd(str(path)):
        git_('init', '--bare')
    git_('remote', 'add', 'origin', path.as_uri())
    git_('push', '--set-upstream', 'origin', 'master')
    git_['status'] & pb.FG
 def test_no_ignore(self, tmpcwd):
     '''
     When well-behaved pre_commit_no_ignore, copy matched files to pre-commit
     tmp dir
     '''
     project = self.project
     project.project_py['pre_commit_no_ignore'] = ['operation/mittens/test/mah_*']
     project.files[Path('operation/mittens/test/mah_file')] = 'content'
     project.files[Path('operation/mittens/test/mah_dir/some_file')] = 'some file content'
     project.files[Path('operation/mittens/test/test_it.py')] = dedent('''\
         from pathlib import Path
         def test_it():
             # file is there
             dir = Path(__file__).parent
             with (dir / 'mah_file').open('r') as f:
                 assert f.read() == 'content'
                 
             # recursively copied directory is there
             with (dir / 'mah_dir/some_file').open('r') as f:
                 assert f.read() == 'some file content'
         ''')
     create_project(project)
     mkproject() # install pre commit hook
     git_('add', '.')
     git_('reset', 'operation/mittens/test/mah_file')
     git_('reset', 'operation/mittens/test/mah_dir')
     git_('commit', '-m', 'message') # run pre-commit
def create_release_project(project=project1, test_index=True):
    '''
    Create valid project for release
    '''
    project = project.copy()
    test_succeed_path = Path('operation/mittens/tests/test_succeed.py')
    project.files[test_succeed_path] = extra_files[test_succeed_path] 
    if test_index:
        project.project_py['index_test'] = 'pypitest'
    create_project(project)
    
    git_('add', '.')
    mkproject()
    git_['commit', '-m', 'Initial commit'] & pb.FG
    
    # Create repo and use as remote
    path = Path.cwd() / '.cache'  # some location ignored by .gitignore
    path.mkdir()
    path /= 'other_repo'
    path.mkdir()
    with pb.local.cwd(str(path)):
        git_('init', '--bare')
    git_('remote', 'add', 'origin', path.as_uri())
    git_('push', '--set-upstream', 'origin', 'master')
    git_['status'] & pb.FG
def test_idempotent(tmpcwd):
    '''
    Running ct-mkproject twice is the same as running it once, in any case
    '''
    create_project()
    mkproject & pb.FG
    with assert_directory_contents(Path('.'), changed=False):
        mkproject()
def test_wrong_readme_file_name(tmpcwd, name):
    project = project1.copy()
    project.project_py['readme_file'] = name
    create_project(project)
    Path(name).touch()
    
    with assert_process_fails(stderr_matches='readme_file'):
        mkproject()
 def test_undefined(self, tmpcwd):
     '''
     When project.py does not define `project`, abort
     '''
     create_project()
     write_file('project.py', '')
     with assert_process_fails(stderr_matches='must export a `project` variable'):
         mkproject()
 def test_has_unknown_attr(self, tmpcwd, unknown_attr):
     '''
     When `project` contains an unknown attribute, abort
     '''
     project = project1.copy()
     project.project_py[unknown_attr] = 'value'
     create_project(project)
     with assert_process_fails(stderr_matches=unknown_attr):
         mkproject()
 def test_missing_required_attr(self, tmpcwd, required_attr):
     '''
     When `project` lacks a required attribute, abort
     '''
     project = project1.copy()
     del project.project_py[required_attr]
     create_project(project)
     with assert_process_fails(stderr_matches='Missing.+{}'.format(required_attr)):
         mkproject()
 def test_unpinned_sip_dependency(self, tmpcwd):
     '''
     When sip based dependency is not pinned, error
     '''
     project = project1.copy()
     project.files[Path('requirements.in')] = 'pytest\nPyQt5\n'
     create_project(project)
     
     with assert_process_fails(stderr_matches=r"(?i)'PyQt5' .* pin"):
         mkproject()
 def test_attr_has_invalid_value(self, tmpcwd, attr, value):
     '''
     When '\s*' or None as attr values, abort
     
     When dashes or whitespace in name, abort
     '''
     project = project1.copy()
     project.project_py[attr] = value
     create_project(project)
     with assert_process_fails(stderr_matches=attr):
         mkproject()
 def test_overwrite_file(self, tmpcwd):
     '''
     Test file overwrites
     '''
     create_project(project1) # Note this template is such that each of the files that be overwritten will require an overwrite
     overwritten_files = [path for path, requirements in project_file_requirements.items() if requirements.permission == Permission.overwrite]
     with assert_file_access(*overwritten_files, written=True, contents_changed=True):
         mkproject & pb.FG
     
     for path in overwritten_files:
         requirements = project_file_requirements[path]
         content = read_file(path)
         requirements.verify_default_content(content, project1.format_kwargs)
 def test_new(self, tmpcwd):
     '''
     When project.py missing, ask for a name and generate template
     '''
     # When project.py is missing and git repo exists but has no commits
     create_project()
     remove_file(Path('project.py'))
     (mkproject << '{name}\n{pkg_name}\n{human_friendly_name}\n'.format(**project1.format_kwargs))()
     
     # Then project.py is created and contains the defaults we want
     project_py_path = Path('project.py')
     assert project_py_path.exists()
     assert read_file(project_py_path) == spec.project_py.format(**project1.format_kwargs)
     assert eval_file(project_py_path)['project'] == project_defaults  # double-check
def test_mkdoc(tmpcwd):
    '''When happy days and a file with proper docstring, generate ./doc'''
    # Setup
    project = project1.copy()
    description = 'Meow meow n meow meow meow'
    add_docstring(project, description)
    create_project(project)
    
    # Run
    mkproject()
    pb.local['ct-mkdoc']()
    
    # Assert
    content = read_file('docs/build/html/index.html')
    assert '0.0.0' in content  # correct version
    assert project.project_py['human_friendly_name'] in content  # human friendly project name
 def test_sip_dependency(self, tmpcwd):
     '''
     When sip based dependency, do not put it in setup.py or requirements.txt
     '''
     project = project1.copy()
     project.files[Path('requirements.in')] = 'pytest\nsip==4.17\nPyQt5==5.5.1\n'
     create_project(project)
     
     mkproject & pb.FG
     
     setup_args = get_setup_args()
     assert setup_args['install_requires'] == ['pytest']
     
     requirements_txt = read_file('requirements.txt')
     assert 'sip' not in requirements_txt
     assert not re.match('(?i)pyqt5', requirements_txt)
 def test_happy_days(self, tmpcwd):
     '''
     If all well, commit
     '''
     project = self.project
     create_project(project)
     mkproject & pb.FG  # install pre-commit hook
     git_('add', '.')
     
     project.project_py['entry_points'] = {
         'console_scripts': [
             'mycli = operation.mittens.main:main',
         ],
     }
     project.files[Path('operation/mittens/main.py')] = 'def main(): pass'
     update_project(project)  # without staging it
     mkproject()
     
     git_('commit', '-m', 'message')  # runs the hook
     pb.local['sh']('-c', '. venv/bin/activate; mycli')  # mycli should be available in the venv, even though it wasn't part of the commit (it shouldn't be available in the venv during the pre-commit test run)
 def test_missing_file(self, tmpcwd, missing_path, missing_requirements):
     '''
     Test handling of missing files:
     
     - When files are missing, create them if allowed, error otherwise
     - When files are present and may not be updated, they are left untouched
     '''
     create_project()
     remove_file(missing_path)
     if missing_requirements.permission == Permission.none:
         # When can't create, must raise error
         with assert_process_fails(stderr_matches=missing_path.name):
             mkproject()
     else:
         # When can create, create
         with ExitStack() as contexts:
             for file, requirements in project_file_requirements.items():
                 if missing_path != file and missing_path not in file.parents and requirements.permission <= Permission.create:
                     contexts.enter_context(assert_file_access(file, written=False, contents_changed=False))
             mkproject & pb.FG
         assert missing_path.exists()
         content = read_file(missing_path)
         missing_requirements.verify_default_content(content, project1.format_kwargs)
 def test_update_file(self, tmpcwd):
     '''
     Test updates to files:
     
     - file is updated to match requirements
     - file also still contains original contents
     
     Specifically:
     
     - $project_name/__init__.py: leaves all intact, just inserts/overwrites __version__
     - .gitignore: leaves previous patterns intact, does insert the new patterns
     - setup.cfg: leaves previous intact, except for some lines designated to be overwritten
     - ...
     '''
     create_project(project1) # Note this template is such that each of the files will require an update
     updated_files = [path for path, requirements in project_file_requirements.items() if requirements.permission == Permission.update]
     with assert_file_access(*updated_files, written=True, contents_changed=True):
         mkproject & pb.FG
     
     for path in updated_files:
         requirements = project_file_requirements[path]
         content = read_file(path)
         requirements.verify_updated_content(content, project1.files[path], project1.format_kwargs)
 def test_setup_py(self, tmpcwd):
     '''
     Test generated setup.py and requirements.txt
     
     - install_requires: requirements.in transformed into valid dependency list with version specs maintained
     - long_description present and nonempty
     - classifiers: list of str
     - packages: list of str of packages
     - package_data: dict of package -> list of str of data file paths
     - author, author_email, description, entry_points keywords license name, url: exact same as input
     '''
     project = project1.copy()
     project.project_py['entry_points'] = project_defaults['entry_points']
     add_complex_requirements_in(project)
     project.files[Path('my_extra_requirements.in')] = 'checksumdir\npytest-pep8\n'
     del project.files[Path('test_requirements.in')]
     
     # Create package_data in operation/mittens/tests (it actually may be in non-test as well):
     project.files[Path('operation/mittens/tests/data/subdir/file1')] = ''
     project.files[Path('operation/mittens/tests/data/subdir/file2')] = ''
     project.files[Path('operation/mittens/tests/not_data/file')] = ''
     project.files[Path('operation/mittens/tests/not_data/data/file')] = ''
     project.files[Path('operation/mittens/tests/pkg/__init__.py')] = ''
     project.files[Path('operation/mittens/tests/pkg/data/file')] = ''
     
     #
     create_project(project)
     
     # Run
     mkproject & pb.FG
     
     # Assert setup.py args
     setup_args = get_setup_args()
     
     for attr in ('name', 'author', 'author_email', 'description', 'keywords', 'license', 'url'):
         assert setup_args[attr] == project.project_py[attr].strip()
     assert setup_args['entry_points'] == project.project_py['entry_points']
         
     assert setup_args['long_description'].strip()
     assert set(setup_args['classifiers']) == {'Development Status :: 2 - Pre-Alpha', 'Programming Language :: Python :: Implementation :: Stackless'}
     assert set(setup_args['packages']) == {'operation', 'operation.mittens', 'operation.mittens.tests', 'operation.mittens.tests.pkg'}
     assert {k:set(v) for k,v in setup_args['package_data'].items()} == {
         'operation.mittens.tests' : {'data/subdir/file1', 'data/subdir/file2'},
         'operation.mittens.tests.pkg' : {'data/file'},
     }
     assert set(setup_args['install_requires']) == {'pytest', 'pytest-testmon<5.0.0', 'pytest-env==0.6', 'pkg4', 'pytest-cov'}
     assert set(setup_args['extras_require'].keys()) == {'my_extra', 'test', 'dev'}
     assert set(setup_args['extras_require']['my_extra']) == {'checksumdir', 'pytest-pep8'}
     assert set(setup_args['extras_require']['test']) == set(spec.test_requirements_in)
     assert setup_args['version'] == '0.0.0'
     
     # requirements.txt must contain relevant packages, including optional dependencies
     requirements_txt_content = read_file('requirements.txt')
     for name in {'pytest', 'pytest-testmon', 'pytest-env', 'pkg_magic', 'pytest-cov', 'checksumdir', 'pytest-pep8'} | set(spec.test_requirements_in) | set(spec.dev_requirements_in):
         assert name in requirements_txt_content
          
     #TODO may be able to simplify tests a bit as we no longer care about *.in order. In the future we may get rid of *requirements.in files altogether though
     
     # Requirements.txt must be sorted like pip-compile
     #TODO hard to test, exact order used is: https://github.com/nvie/pip-tools/blob/master/piptools/writer.py#L27
     # maybe 2 local setup.py and 2 trivial pypi packages that have no dependencies and won't have any in the near/distant future 
     deps_txt = [get_dependency_name(line[0], line[1]) for line in parse_requirements_file(Path('requirements.txt')) if line[1]]
     
     # and must contain the dependencies of all *requirements.in files
     for path in map(Path, ('requirements.in', 'my_extra_requirements.in', 'test_requirements.in')):
         deps_in = [get_dependency_name(line[0], line[1]) for line in parse_requirements_file(path) if line[1]]
         assert set(deps_in).issubset(set(deps_txt))
         
     # Multiple runs yield the same setup.py each time
     with open('setup.py') as f:
         expected = f.read()
     for _ in range(5):
         mkproject()
         with open('setup.py') as f:
             actual = f.read()
         assert actual == expected
 def create_project(self):
     create_project(self.project)