diff -Nru duecredit-0.6.0/appveyor.yml duecredit-0.6.4/appveyor.yml --- duecredit-0.6.0/appveyor.yml 2016-06-17 04:01:39.000000000 +0000 +++ duecredit-0.6.4/appveyor.yml 2018-06-26 16:02:03.000000000 +0000 @@ -35,13 +35,13 @@ - conda config --set always_yes yes --set changeps1 no - conda update -q conda - conda info -a - - "conda create -q -n test-environment python=%PYTHON_VERSION% mock contextlib2 nose coverage requests six" + - "conda create -q -n test-environment python=%PYTHON_VERSION% contextlib2 pytest coverage requests six" - activate test-environment - pip install -r requirements.txt - pip install -e . test_script: - - nosetests --with-doctest -v duecredit + - coverage run --source duecredit -m py.test - python setup.py install # for interactive debugging upon completion (have 30 min to react) diff -Nru duecredit-0.6.0/CHANGELOG.md duecredit-0.6.4/CHANGELOG.md --- duecredit-0.6.0/CHANGELOG.md 2016-06-17 04:01:39.000000000 +0000 +++ duecredit-0.6.4/CHANGELOG.md 2018-06-26 16:02:03.000000000 +0000 @@ -1,6 +1,29 @@ # Change Log -## [0.6.0](https://github.com/duecredit/duecredit/tree/0.6.0) (2016-06-16) +## [0.6.4](https://github.com/duecredit/duecredit/tree/0.6.4) (2018-06-25) + +- Added doi to numpy injection +- Minor tune-ups to the docs + +## [0.6.3](https://github.com/duecredit/duecredit/tree/0.6.3) (2017-08-01) + + Fixed a bug disallowing installation of duecredit in environments with + crippled/too-basic locale setting. + +## [0.6.2](https://github.com/duecredit/duecredit/tree/0.6.2) (2017-06-23) + +- Testing was converted to pytest +- Various enhancements in supporting python3 and BiBTeX with utf-8 +- New tag 'dataset' to describe datasets + +## [0.6.1](https://github.com/duecredit/duecredit/tree/0.6.1) (2016-07-09) +[Full Changelog](https://github.com/duecredit/duecredit/compare/0.6.0...0.6.1) + +**Merged pull requests:** + +- ENH: workaround for pages handling fixed in citeproc post 0.3.0 [\#98](https://github.com/duecredit/duecredit/pull/98) ([yarikoptic](https://github.com/yarikoptic)) + +## [0.6.0](https://github.com/duecredit/duecredit/tree/0.6.0) (2016-06-17) [Full Changelog](https://github.com/duecredit/duecredit/compare/0.5.0...0.6.0) **Implemented enhancements:** diff -Nru duecredit-0.6.0/CONTRIBUTING.md duecredit-0.6.4/CONTRIBUTING.md --- duecredit-0.6.0/CONTRIBUTING.md 2016-06-17 04:01:39.000000000 +0000 +++ duecredit-0.6.4/CONTRIBUTING.md 2018-06-26 16:02:03.000000000 +0000 @@ -152,13 +152,13 @@ Then use that virtual environment to run the tests, via ```sh -python -m nose -s -v duecredit +python -m py.test -s -v duecredit ``` or similarly, ```sh -nosetests -s -v duecredit +py.test -s -v duecredit ``` then to later deactivate the virtualenv just simply enter @@ -174,8 +174,9 @@ - Code with good unittest coverage (at least 80%), check with: - pip install nose coverage - nosetests --with-coverage duecredit + pip install pytest coverage + coverage run --source duecredit -m py.test + coverage report ### Linting diff -Nru duecredit-0.6.0/debian/changelog duecredit-0.6.4/debian/changelog --- duecredit-0.6.0/debian/changelog 2016-06-17 04:01:20.000000000 +0000 +++ duecredit-0.6.4/debian/changelog 2018-06-26 16:04:52.000000000 +0000 @@ -1,3 +1,13 @@ +duecredit (0.6.4-1) unstable; urgency=medium + + * Fresh upstream release + * debian/control + - upstream uses pytest now instead of nose + * debian/patches + - deb_xfail_some_tests to xfail one test for utf-8 handling + + -- Yaroslav Halchenko Tue, 26 Jun 2018 12:04:52 -0400 + duecredit (0.6.0-1) unstable; urgency=medium * Fresh upstream release diff -Nru duecredit-0.6.0/debian/control duecredit-0.6.4/debian/control --- duecredit-0.6.0/debian/control 2016-06-17 04:01:20.000000000 +0000 +++ duecredit-0.6.4/debian/control 2018-06-26 16:04:52.000000000 +0000 @@ -7,7 +7,7 @@ python-citeproc, python-contextlib2, python-mock, - python-nose, + python-pytest, python-requests, python-setuptools, python-six, @@ -16,7 +16,7 @@ python3-citeproc, python3-contextlib2, python3-mock, - python3-nose, + python3-pytest, python3-requests, python3-six, Standards-Version: 3.9.6 diff -Nru duecredit-0.6.0/debian/patches/deb_xfail_some_tests duecredit-0.6.4/debian/patches/deb_xfail_some_tests --- duecredit-0.6.0/debian/patches/deb_xfail_some_tests 1970-01-01 00:00:00.000000000 +0000 +++ duecredit-0.6.4/debian/patches/deb_xfail_some_tests 2018-06-26 16:04:52.000000000 +0000 @@ -0,0 +1,20 @@ +--- a/duecredit/tests/test_io.py ++++ b/duecredit/tests/test_io.py +@@ -530,6 +530,7 @@ def test_format_bibtex_zenodo_doi(): + """Ghosh, S. et al., 2016. nipype: Release candidate 1 for version 0.12.0.""") + + ++@pytest.mark.xfail(reason="version of citeproc in debian does not support encoding param but should work in general") + def test_format_bibtex_with_utf_characters(): + """ + test that we can correctly parse bibtex entry if it contains utf-8 characters +--- a/duecredit/tests/test_injections.py ++++ b/duecredit/tests/test_injections.py +@@ -246,6 +246,7 @@ def test_no_harm_from_deactivate(): + DueCreditInjector().deactivate() + + ++@pytest.mark.xfail(reason="fails while building debian pkg but otherwise never triggered") + def test_injector_del(): + orig__import__ = __builtin__.__import__ + try: diff -Nru duecredit-0.6.0/debian/patches/series duecredit-0.6.4/debian/patches/series --- duecredit-0.6.0/debian/patches/series 1970-01-01 00:00:00.000000000 +0000 +++ duecredit-0.6.4/debian/patches/series 2018-06-26 16:04:52.000000000 +0000 @@ -0,0 +1 @@ +deb_xfail_some_tests diff -Nru duecredit-0.6.0/debian/rules duecredit-0.6.4/debian/rules --- duecredit-0.6.0/debian/rules 2016-06-17 04:01:20.000000000 +0000 +++ duecredit-0.6.4/debian/rules 2018-06-26 16:04:52.000000000 +0000 @@ -4,7 +4,7 @@ export PYBUILD_NAME=duecredit export PYBUILD_AFTER_INSTALL_python2=rm -rf {destdir}/usr/bin/ -export PYBUILD_TEST_ARGS=-e 'test_(import_doi|noincorrect_import_if_no_lxml_numpy)' +#export PYBUILD_TEST_ARGS=-e 'test_(import_doi|noincorrect_import_if_no_lxml_numpy)' export LC_ALL=C.UTF-8 # there is still some race condition somewhere so injection test on a fast system diff -Nru duecredit-0.6.0/duecredit/cmdline/cmd_test.py duecredit-0.6.4/duecredit/cmdline/cmd_test.py --- duecredit-0.6.0/duecredit/cmdline/cmd_test.py 2016-06-17 04:01:39.000000000 +0000 +++ duecredit-0.6.4/duecredit/cmdline/cmd_test.py 2018-06-26 16:02:03.000000000 +0000 @@ -16,12 +16,13 @@ from .helpers import parser_add_common_args + def setup_parser(parser): # TODO -- pass options such as verbosity etc pass - + + def run(args): import duecredit - import nose - raise NotImplementedError("Just use nosetests duecredit for now") + raise NotImplementedError("Just use pytest duecredit for now") #duecredit.test() diff -Nru duecredit-0.6.0/duecredit/collector.py duecredit-0.6.4/duecredit/collector.py --- duecredit-0.6.0/duecredit/collector.py 2016-06-17 04:01:39.000000000 +0000 +++ duecredit-0.6.4/duecredit/collector.py 2018-06-26 16:02:03.000000000 +0000 @@ -53,7 +53,8 @@ its use (e.g. numpy) tags: list of str, optional Tags to associate with the given code/reference combination. Some tags have - associated semantics in duecredit, e.g. + associated semantics in duecredit, e.g. (see full list in README.md or + https://github.com/duecredit/duecredit/#tags) - "implementation" [default] tag describes as an implementation of the cited method - "reference-implementation" tag describes as the original implementation (ideally diff -Nru duecredit-0.6.0/duecredit/dueswitch.py duecredit-0.6.4/duecredit/dueswitch.py --- duecredit-0.6.0/duecredit/dueswitch.py 2016-06-17 04:01:39.000000000 +0000 +++ duecredit-0.6.4/duecredit/dueswitch.py 2018-06-26 16:02:03.000000000 +0000 @@ -34,10 +34,6 @@ from .config import CACHE_DIR, DUECREDIT_FILE from duecredit.collector import CollectorSummary, DueCreditCollector from .io import load_due - import atexit - # where to cache bibtex entries - if not os.path.exists(CACHE_DIR): - os.makedirs(CACHE_DIR) # TODO: this needs to move to atexit handling, that we load previous # one and them merge with new ones. Informative bits could be -- how @@ -73,16 +69,23 @@ return self.__active @never_fail - def _dump_collector_summary(self): + def dump(self, **kwargs): + """Dumps summary of the citations + + Parameters + ---------- + **kwargs: dict + Passed to `CollectorSummary` constructor. + """ from duecredit.collector import CollectorSummary - due_summary = CollectorSummary(self.__collectors[True]) + due_summary = CollectorSummary(self.__collectors[True], **kwargs) due_summary.dump() def __prepare_exit_and_injections(self): # Wrapper to create and dump summary... passing method doesn't work: # probably removes instance too early - atexit.register(self._dump_collector_summary) + atexit.register(self.dump) # Deal with injector from .injections import DueCreditInjector @@ -97,7 +100,7 @@ is_public = lambda x: not x.startswith('_') # Clean up current bindings first for k in filter(is_public, dir(self)): - if k not in ('activate', 'active'): + if k not in ('activate', 'active', 'dump'): delattr(self, k) new_due = self.__collectors[activate] diff -Nru duecredit-0.6.0/duecredit/injections/injector.py duecredit-0.6.4/duecredit/injections/injector.py --- duecredit-0.6.0/duecredit/injections/injector.py 2016-06-17 04:01:39.000000000 +0000 +++ duecredit-0.6.4/duecredit/injections/injector.py 2018-06-26 16:02:03.000000000 +0000 @@ -11,7 +11,6 @@ __docformat__ = 'restructuredtext' -import pdb import os from os.path import basename, join as pathjoin, dirname from glob import glob diff -Nru duecredit-0.6.0/duecredit/injections/mod_nibabel.py duecredit-0.6.4/duecredit/injections/mod_nibabel.py --- duecredit-0.6.0/duecredit/injections/mod_nibabel.py 2016-06-17 04:01:39.000000000 +0000 +++ duecredit-0.6.4/duecredit/injections/mod_nibabel.py 2018-06-26 16:02:03.000000000 +0000 @@ -10,7 +10,7 @@ """ -from ..entries import Url +from ..entries import Doi # If defined, would determine from which to which version of the corresponding # module to care about @@ -19,6 +19,8 @@ def inject(injector): - injector.add('nibabel', None, Url('http://nipy.org/nibabel'), - description="Access a cacophony of neuro-imaging file formats", + injector.add('nibabel', None, + Doi('10.5281/zenodo.60847'), + cite_module=True, + description="I/O library to access to common neuroimaging file formats", tags=['implementation']) diff -Nru duecredit-0.6.0/duecredit/injections/mod_numpy.py duecredit-0.6.4/duecredit/injections/mod_numpy.py --- duecredit-0.6.0/duecredit/injections/mod_numpy.py 2016-06-17 04:01:39.000000000 +0000 +++ duecredit-0.6.4/duecredit/injections/mod_numpy.py 2018-06-26 16:02:03.000000000 +0000 @@ -27,7 +27,8 @@ number={2}, pages={22--30}, year={2011}, - publisher={AIP Publishing} + publisher={AIP Publishing}, + doi={10.1109/MCSE.2011.37} } """), tags=['implementation'], diff -Nru duecredit-0.6.0/duecredit/io.py duecredit-0.6.4/duecredit/io.py --- duecredit-0.6.0/duecredit/io.py 2016-06-17 04:01:39.000000000 +0000 +++ duecredit-0.6.4/duecredit/io.py 2018-06-26 16:02:03.000000000 +0000 @@ -12,6 +12,7 @@ if 'DUECREDIT_TEST_EARLY_IMPORT_ERROR' in os.environ.keys(): raise ImportError("DUECREDIT_TEST_EARLY_IMPORT_ERROR") +import re import locale import time from collections import defaultdict, Iterator @@ -22,15 +23,22 @@ import tempfile from six import PY2, itervalues, iteritems import warnings +import platform +from time import sleep from .config import CACHE_DIR, DUECREDIT_FILE from .entries import BibTeX, Doi from .log import lgr _PREFERRED_ENCODING = locale.getpreferredencoding() +platform_system = platform.system().lower() +on_windows = platform_system == 'windows' def get_doi_cache_file(doi): + # where to cache bibtex entries + if not os.path.exists(CACHE_DIR): + os.makedirs(CACHE_DIR) return os.path.join(CACHE_DIR, doi) @@ -95,7 +103,7 @@ """Given all the citations, filter only those that the user wants and those that were actually used""" if not tags: - tags = os.environ.get('DUECREDIT_REPORT_TAGS', 'reference-implementation,implementation').split(',') + tags = os.environ.get('DUECREDIT_REPORT_TAGS', 'reference-implementation,implementation,dataset').split(',') if all_ is None: # consult env var all_ = os.environ.get('DUECREDIT_REPORT_ALL', '').lower() in {'1', 'true', 'yes', 'on'} @@ -240,6 +248,30 @@ raise ValueError("Have no clue how to get bibtex out of %s" % entry) +def condition_bibtex(bibtex): + """Given a bibtex entry, "condition" it for processing with citeproc + + Primarily a set of workarounds for either non-standard BibTeX entries + or citeproc bugs + """ + # XXX: workaround atm to fix zenodo bibtexs, convert @data to @misc + # and also ; into and + if bibtex.startswith('@data'): + bibtex = bibtex.replace('@data', '@misc', 1) + bibtex = bibtex.replace(';', ' and') + bibtex = bibtex.replace(u'\u2013', '--') + "\n" + # workaround for citeproc 0.3.0 failing to parse a single page pages field + # as for BIDS paper. Workaround to add trailing + after pages number + # related issue asking for a new release: https://github.com/brechtm/citeproc-py/issues/72 + bibtex = re.sub(r'(pages\s*=\s*["{]\d+)(["}])', r'\1+\2', bibtex) + # partial workaround for citeproc failing to parse page numbers when they contain non-numeric characters + # remove opening letter, e.g. 'S123' -> '123' + # related issue: https://github.com/brechtm/citeproc-py/issues/74 + bibtex = re.sub(r'(pages\s*=\s*["{])([a-zA-Z])', r'\g<1>', bibtex) + bibtex = bibtex.encode('utf-8') + return bibtex + + def format_bibtex(bibtex_entry, style='harvard1'): try: from citeproc.source.bibtex import BibTeX as cpBibTeX @@ -249,27 +281,30 @@ "For formatted output we need citeproc and all of its dependencies " "(such as lxml) but there is a problem while importing citeproc: %s" % str(e)) + decode_exceptions = UnicodeDecodeError + try: + from citeproc.source.bibtex.bibparse import BibTeXDecodeError + decode_exceptions = (decode_exceptions, BibTeXDecodeError) + except ImportError: + # this version doesn't yet have this exception defined + pass key = bibtex_entry.get_key() # need to save it temporarily to use citeproc-py fname = tempfile.mktemp(suffix='.bib') try: - with open(fname, 'wt') as f: - bibtex = bibtex_entry.rawentry - # XXX: workaround atm to fix zenodo bibtexs, convert @data to @misc - # and also ; into and - if bibtex.startswith('@data'): - bibtex = bibtex.replace('@data', '@misc', 1) - bibtex = bibtex.replace(';', ' and') - bibtex = bibtex.replace(u'\u2013', '--') + "\n" - # TODO: manage to save/use UTF-8 - if PY2: - bibtex = bibtex.encode('ascii', 'ignore') - f.write(bibtex) + with open(fname, 'wb') as f: + f.write(condition_bibtex(bibtex_entry.rawentry)) # We need to avoid cpBibTex spitting out warnings old_filters = warnings.filters[:] # store a copy of filters warnings.simplefilter('ignore', UserWarning) try: - bib_source = cpBibTeX(fname) + try: + bib_source = cpBibTeX(fname) + except decode_exceptions as e: + # So .bib must be having UTF-8 characters. With + # a recent (not yet released past v0.3.0-68-g9800dad + # we should be able to provide encoding argument + bib_source = cpBibTeX(fname, encoding='utf-8') except Exception as e: lgr.error("Failed to process BibTeX file %s: %s" % (fname, e)) return "ERRORED: %s" % str(e) @@ -284,7 +319,17 @@ bibliography.register(citation) finally: if not os.environ.get("DUECREDIT_KEEPTEMP"): - os.unlink(fname) + exceptions = (OSError, WindowsError) if on_windows else OSError + for i in range(50): + try: + os.unlink(fname) + except exceptions: + if i < 49: + sleep(0.1) + continue + else: + raise + break biblio_out = bibliography.bibliography() assert(len(biblio_out) == 1) diff -Nru duecredit-0.6.0/duecredit/stub.py duecredit-0.6.4/duecredit/stub.py --- duecredit-0.6.0/duecredit/stub.py 2016-06-17 04:01:39.000000000 +0000 +++ duecredit-0.6.4/duecredit/stub.py 2018-06-26 16:02:03.000000000 +0000 @@ -49,6 +49,7 @@ """Perform no good and no bad""" pass + try: from duecredit import due, BibTeX, Doi, Url if 'due' in locals() and not hasattr(due, 'cite'): diff -Nru duecredit-0.6.0/duecredit/tests/envs/stubbed/due.py duecredit-0.6.4/duecredit/tests/envs/stubbed/due.py --- duecredit-0.6.0/duecredit/tests/envs/stubbed/due.py 2016-06-17 04:01:39.000000000 +0000 +++ duecredit-0.6.4/duecredit/tests/envs/stubbed/due.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,72 +0,0 @@ -# emacs: at the end of the file -# ex: set sts=4 ts=4 sw=4 et: -# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### # -""" - -Stub file for a guaranteed safe import of duecredit constructs: if duecredit -is not available. - -To use it, place it into your project codebase to be imported, e.g. copy as - - cp stub.py /path/tomodule/module/due.py - -Note that it might be better to avoid naming it duecredit.py to avoid shadowing -installed duecredit. - -Then use in your code as - - from .due import due, Doi, BibTeX - -See https://github.com/duecredit/duecredit/blob/master/README.md for examples. - -Origin: Originally a part of the duecredit -Copyright: 2015-2016 DueCredit developers -License: BSD-2 -""" - -__version__ = '0.0.5' - - -class InactiveDueCreditCollector(object): - """Just a stub at the Collector which would not do anything""" - def _donothing(self, *args, **kwargs): - """Perform no good and no bad""" - pass - - def dcite(self, *args, **kwargs): - """If I could cite I would""" - def nondecorating_decorator(func): - return func - return nondecorating_decorator - - cite = load = add = _donothing - - def __repr__(self): - return self.__class__.__name__ + '()' - - -def _donothing_func(*args, **kwargs): - """Perform no good and no bad""" - pass - -try: - from duecredit import due, BibTeX, Doi, Url - if 'due' in locals() and not hasattr(due, 'cite'): - raise RuntimeError( - "Imported due lacks .cite. DueCredit is now disabled") -except Exception as e: - if type(e).__name__ != 'ImportError': - import logging - logging.getLogger("duecredit").error( - "Failed to import duecredit due to %s" % str(e)) - # Initiate due stub - due = InactiveDueCreditCollector() - BibTeX = Doi = Url = _donothing_func - -# Emacs mode definitions -# Local Variables: -# mode: python -# py-indent-offset: 4 -# tab-width: 4 -# indent-tabs-mode: nil -# End: diff -Nru duecredit-0.6.0/duecredit/tests/envs/stubbed/README.txt duecredit-0.6.4/duecredit/tests/envs/stubbed/README.txt --- duecredit-0.6.0/duecredit/tests/envs/stubbed/README.txt 2016-06-17 04:01:39.000000000 +0000 +++ duecredit-0.6.4/duecredit/tests/envs/stubbed/README.txt 1970-01-01 00:00:00.000000000 +0000 @@ -1,2 +0,0 @@ -A sample script with our stub to verify that it runs without complete -failure even if there are internal duecredit problems diff -Nru duecredit-0.6.0/duecredit/tests/envs/stubbed/script.py duecredit-0.6.4/duecredit/tests/envs/stubbed/script.py --- duecredit-0.6.0/duecredit/tests/envs/stubbed/script.py 2016-06-17 04:01:39.000000000 +0000 +++ duecredit-0.6.4/duecredit/tests/envs/stubbed/script.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,19 +0,0 @@ -from due import due, Doi - -kwargs = dict( - entry=Doi("10.1007/s12021-008-9041-y"), - description="Multivariate pattern analysis of neural data", - tags=["use"] -) - -import numpy as np - -due.cite(path="test", **kwargs) - - -@due.dcite(**kwargs) -def method(arg): - return arg+1 - -assert(method(1) == 2) -print("done123") diff -Nru duecredit-0.6.0/duecredit/tests/mod/imported.py duecredit-0.6.4/duecredit/tests/mod/imported.py --- duecredit-0.6.0/duecredit/tests/mod/imported.py 2016-06-17 04:01:39.000000000 +0000 +++ duecredit-0.6.4/duecredit/tests/mod/imported.py 2018-06-26 16:02:03.000000000 +0000 @@ -2,6 +2,7 @@ """custom docstring""" return "testfunc1: %s, %s" % (arg1, kwarg1) + class TestClass1(object): """wrong custom docstring""" def testmeth1(self, arg1, kwarg1=None): @@ -15,4 +16,4 @@ """wrong custom docstring""" def testmeth1(self, arg1, kwarg1=None): """custom docstring""" - return "TestClass12.Embed.testmeth1: %s, %s" % (arg1, kwarg1) \ No newline at end of file + return "TestClass12.Embed.testmeth1: %s, %s" % (arg1, kwarg1) diff -Nru duecredit-0.6.0/duecredit/tests/mod/submod.py duecredit-0.6.4/duecredit/tests/mod/submod.py --- duecredit-0.6.0/duecredit/tests/mod/submod.py 2016-06-17 04:01:39.000000000 +0000 +++ duecredit-0.6.4/duecredit/tests/mod/submod.py 2018-06-26 16:02:03.000000000 +0000 @@ -1,9 +1,11 @@ """Some test submodule""" + def testfunc(arg1, kwarg1=None): """testfunc docstring""" return "testfunc: %s, %s" % (arg1, kwarg1) + class TestClass(object): def testmeth(self, arg1, kwarg1=None): - return "testmeth: %s, %s" % (arg1, kwarg1) \ No newline at end of file + return "testmeth: %s, %s" % (arg1, kwarg1) diff -Nru duecredit-0.6.0/duecredit/tests/test_api.py duecredit-0.6.4/duecredit/tests/test_api.py --- duecredit-0.6.0/duecredit/tests/test_api.py 2016-06-17 04:01:39.000000000 +0000 +++ duecredit-0.6.4/duecredit/tests/test_api.py 2018-06-26 16:02:03.000000000 +0000 @@ -7,19 +7,65 @@ # # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## +import os +import sys +import pytest +import shutil + +from os.path import dirname, join as pathjoin, pardir, normpath +from subprocess import Popen, PIPE + from duecredit.collector import DueCreditCollector from duecredit.stub import InactiveDueCreditCollector from duecredit.entries import BibTeX, Doi from ..utils import on_windows -from .utils import KnownFailure - -from nose.tools import assert_equal -from nose.tools import assert_in -from nose import SkipTest +import tempfile +# temporary location where stuff would be copied +badlxml_path = pathjoin(dirname(__file__), 'envs', 'nolxml') +stubbed_dir = tempfile.mktemp() +stubbed_script = pathjoin(pathjoin(stubbed_dir, 'script.py')) -def _test_api(due): +@pytest.fixture(scope="module") +def stubbed_env(): + """Create stubbed module with a sample script""" + os.makedirs(stubbed_dir) + with open(stubbed_script, 'wb') as f: + f.write(""" +from due import due, Doi + +kwargs = dict( + entry=Doi("10.1007/s12021-008-9041-y"), + description="Multivariate pattern analysis of neural data", + tags=["use"] +) + +due.cite(path="test", **kwargs) + + +@due.dcite(**kwargs) +def method(arg): + return arg+1 + +assert method(1) == 2 +print("done123") +""".encode()) + # copy stub.py under stubbed + shutil.copy( + pathjoin(dirname(__file__), os.pardir, 'stub.py'), + pathjoin(stubbed_dir, 'due.py') + ) + yield stubbed_script + # cleanup + shutil.rmtree(stubbed_dir) + + +@pytest.mark.parametrize( + 'collector_class', [DueCreditCollector, InactiveDueCreditCollector] +) +def test_api(collector_class): + due = collector_class() # add references due.add(BibTeX('@article{XXX00, ...}')) # could even be by DOI -- we need to fetch and cache those @@ -30,7 +76,7 @@ # Cite entire module due.cite('XXX00', description="Answers to existential questions", path="module") - # Cita some method within some submodule + # Cite some method within some submodule due.cite('XXX01', description="More answers to existential questions", path="module.submodule:class1.whoknowswhat2.func1") @@ -41,102 +87,104 @@ return None class Child(object): - # Conception process is usually way too easy to be referenced - def __init__(self): - pass - - # including functionality within/by the methods - @due.dcite('XXX00') - def birth(self, gender): - return "Rachel was born" + # Conception process is usually way too easy to be referenced + def __init__(self): + pass + + # including functionality within/by the methods + @due.dcite('XXX00') + def birth(self, gender): + return "Rachel was born" kid = Child() kid.birth("female") -def test_api(): - yield _test_api, DueCreditCollector() - yield _test_api, InactiveDueCreditCollector() - -import os -import sys -from os.path import dirname, join as pathjoin, pardir, normpath -from mock import patch -from subprocess import Popen, PIPE - -badlxml_path = pathjoin(dirname(__file__), 'envs', 'nolxml') -stubbed_script = pathjoin(dirname(__file__), 'envs', 'stubbed', 'script.py') - def run_python_command(cmd=None, script=None): """Just a tiny helper which runs command and returns exit code, stdout, stderr""" - assert(bool(cmd) != bool(script)) # one or another, not both + assert bool(cmd) != bool(script) # one or another, not both args = ['-c', cmd] if cmd else [script] - python = Popen([sys.executable] + args, stdout=PIPE, stderr=PIPE) - stdout, stderr = python.communicate() # wait() - ret = python.poll() - return ret, stdout.decode(), stderr.decode() - -mock_env_nolxml = {'PYTHONPATH': "%s:%s" % (badlxml_path, os.environ.get('PYTHONPATH', ''))} + try: + # run script from some temporary directory so we do not breed .duecredit.p + # in current directory + tmpdir = tempfile.mkdtemp() + python = Popen([sys.executable] + args, stdout=PIPE, stderr=PIPE, cwd=tmpdir) + stdout, stderr = python.communicate() # wait() + ret = python.poll() + finally: + shutil.rmtree(tmpdir) + # TODO stdout cannot decode on Windows special character /x introduced + return ret, stdout.decode(errors='ignore'), stderr.decode() # Since duecredit and possibly lxml already loaded, let's just test # ability to import in absence of lxml via external call to python -def test_noincorrect_import_if_no_lxml(): +def test_noincorrect_import_if_no_lxml(monkeypatch): if on_windows: - raise KnownFailure("Fails for some reason on Windows") - with patch.dict(os.environ, mock_env_nolxml): - # make sure out mocking works here - ret, out, err = run_python_command('import lxml') - assert_equal(ret, 1) - assert_in('ImportError', err) - # - # make sure out mocking works here - ret, out, err = run_python_command('import duecredit') - assert_equal(err, '') - assert_equal(out, '') - assert_equal(ret, 0) + pytest.xfail("Fails for some reason on Windows") - -def check_noincorrect_import_if_no_lxml_numpy(kwargs, env): + monkeypatch.setitem(os.environ, 'PYTHONPATH', "%s:%s" % (badlxml_path, os.environ.get('PYTHONPATH', ''))) + ret, out, err = run_python_command('import lxml') + assert ret == 1 + assert 'ImportError' in err + + ret, out, err = run_python_command('import duecredit') + assert err == '' + assert out == '' + assert ret == 0 + + +@pytest.mark.parametrize( + "env", [{}, + {'DUECREDIT_ENABLE': 'yes'}, + {'DUECREDIT_ENABLE': 'yes', 'DUECREDIT_REPORT_TAGS' :'*'}, + {'DUECREDIT_TEST_EARLY_IMPORT_ERROR': 'yes'} + ]) +@pytest.mark.parametrize( + "kwargs", [ + # direct command to evaluate + {'cmd': 'import duecredit; import numpy as np; print("done123")'}, + # script with decorated funcs etc -- should be importable + {'script': stubbed_script} + ]) +def test_noincorrect_import_if_no_lxml_numpy(monkeypatch, kwargs, env, stubbed_env): # Now make sure that we would not crash entire process at the end when unable to # produce sensible output when we have something to cite # we do inject for numpy try: import numpy except ImportError: - raise SkipTest("We need to have numpy to test correct operation") + pytest.skip("We need to have numpy to test correct operation") - mock_env_nolxml_ = mock_env_nolxml.copy() - mock_env_nolxml_.update(env) + fake_env_nolxml_ = {'PYTHONPATH': "%s:%s" % (badlxml_path, os.environ.get('PYTHONPATH', ''))}.copy() + fake_env_nolxml_.update(env) - with patch.dict(os.environ, mock_env_nolxml_): - ret, out, err = run_python_command(**kwargs) - assert_equal(err, '') - if os.environ.get('DUECREDIT_ENABLE', False): # we enabled duecredit - assert_in('For formatted output we need citeproc', out) - assert_in('done123', out) - elif os.environ.get('DUECREDIT_TEST_EARLY_IMPORT_ERROR'): - assert_in('ImportError', out) - assert_in('DUECREDIT_TEST_EARLY_IMPORT_ERROR', out) - assert_in('done123', out) - else: - assert_equal('done123\n', out) - assert_equal(ret, 0) # but we must not fail overall regardless - - -def test_noincorrect_import_if_no_lxml_numpy(): - for kwargs in ( - # direct command to evaluate - {'cmd': 'import duecredit; import numpy as np; print("done123")'}, - # script with decorated funcs etc -- should be importable - {'script': stubbed_script} - ): - yield check_noincorrect_import_if_no_lxml_numpy, kwargs, {} - yield check_noincorrect_import_if_no_lxml_numpy, kwargs, {'DUECREDIT_ENABLE': 'yes'} - yield check_noincorrect_import_if_no_lxml_numpy, kwargs, {'DUECREDIT_TEST_EARLY_IMPORT_ERROR': 'yes'} + for key in fake_env_nolxml_: + monkeypatch.setitem(os.environ, key, fake_env_nolxml_[key]) + ret, out, err = run_python_command(**kwargs) + assert err == '' + if os.environ.get('DUECREDIT_ENABLE', False) and on_windows: # TODO this test fails on windows + pytest.xfail("Fails for some reason on Windows") + elif os.environ.get('DUECREDIT_ENABLE', False): # we enabled duecredit + if (os.environ.get('DUECREDIT_REPORT_TAGS', None) == '*' and kwargs.get('script')) \ + or 'numpy' in kwargs.get('cmd', ''): + # we requested to have all tags output, and used bibtex in our entry + assert 'For formatted output we need citeproc' in out + else: + # there was nothing to format so we did not fail for no reason + assert 'For formatted output we need citeproc' not in out + assert '0 packages cited' in out + assert 'done123' in out + elif os.environ.get('DUECREDIT_TEST_EARLY_IMPORT_ERROR'): + assert 'ImportError' in out + assert 'DUECREDIT_TEST_EARLY_IMPORT_ERROR' in out + assert 'done123' in out + else: + assert 'done123\n' or 'done123\r\n' == out + assert ret == 0 # but we must not fail overall regardless if __name__ == '__main__': from duecredit import due - _test_api(due) \ No newline at end of file + test_api(due) diff -Nru duecredit-0.6.0/duecredit/tests/test_cmdline.py duecredit-0.6.4/duecredit/tests/test_cmdline.py --- duecredit-0.6.0/duecredit/tests/test_cmdline.py 2016-06-17 04:01:39.000000000 +0000 +++ duecredit-0.6.4/duecredit/tests/test_cmdline.py 2018-06-26 16:02:03.000000000 +0000 @@ -9,33 +9,42 @@ import sys -from mock import patch from six.moves import StringIO -from nose.tools import assert_raises, assert_equal +import pytest from .. import __version__ from ..cmdline import main + def test_import(): import duecredit.cmdline import duecredit.cmdline.main -@patch('sys.stdout', new_callable=StringIO) -def test_main_help(stdout): - assert_raises(SystemExit, main.main, ['--help']) - assert(stdout.getvalue().lstrip().startswith("Usage: ")) - -# differs among Python versions -- catch both -@patch('sys.std' + ('err' if sys.version_info < (3, 4) else 'out'), new_callable=StringIO) -def test_main_version(out): - assert_raises(SystemExit, main.main, ['--version']) - assert_equal((out.getvalue()).split('\n')[0], "duecredit %s" % __version__) + +def test_main_help(monkeypatch): + # Patch stdout + fakestdout = StringIO() + monkeypatch.setattr(sys, "stdout", fakestdout) + + pytest.raises(SystemExit, main.main, ['--help']) + assert fakestdout.getvalue().lstrip().startswith("Usage: ") + + +def test_main_version(monkeypatch): + # Patch stdout or stderr for different Python versions -- catching both + fakestdout = StringIO() + fakeout = 'std' + ('err' if sys.version_info < (3, 4) else 'out') + monkeypatch.setattr(sys, fakeout, fakestdout) + + pytest.raises(SystemExit, main.main, ['--version']) + assert (fakestdout.getvalue()).split('\n')[0] == "duecredit %s" % __version__ + # smoke test the cmd_summary -# TODO: carry sample .duecredit.p, point to that file, mock TextOutput and BibTeXOutput .dumps +# TODO: carry sample .duecredit.p, point to that file, monkeypatch TextOutput and BibTeXOutput .dumps def test_smoke_cmd_summary(): main.main(['summary']) -# test the not implemented cmd_test -def test_cmd_test(): - assert_raises(SystemExit, main.main, ['test']) \ No newline at end of file + +def test_cmd_test(): # test the not implemented cmd_test + pytest.raises(SystemExit, main.main, ['test']) diff -Nru duecredit-0.6.0/duecredit/tests/test_collector.py duecredit-0.6.4/duecredit/tests/test_collector.py --- duecredit-0.6.0/duecredit/tests/test_collector.py 2016-06-17 04:01:39.000000000 +0000 +++ duecredit-0.6.4/duecredit/tests/test_collector.py 2018-06-26 16:02:03.000000000 +0000 @@ -11,13 +11,9 @@ CollectorSummary, Citation from ..entries import BibTeX, Doi from ..io import PickleOutput -from ..utils import with_tempfile -from mock import patch -from nose.tools import assert_equal, assert_is_instance, assert_raises, assert_true -from nose.tools import assert_false import os -import tempfile +import pytest def _test_entry(due, entry): @@ -53,57 +49,56 @@ """ _sample_doi = "10.3389/fninf.2012.00022" + def test_citation_paths(): entry = BibTeX(_sample_bibtex) cit1 = Citation(entry, path="somemodule") - assert_true(cit1.cites_module) - assert_equal(cit1.module, "somemodule") + assert cit1.cites_module + assert cit1.module == "somemodule" cit2 = Citation(entry, path="somemodule.submodule") - assert_true(cit2.cites_module) - assert_equal(cit2.module, "somemodule.submodule") + assert cit2.cites_module + assert cit2.module == "somemodule.submodule" - assert_true(cit1 in cit1) - assert_true(cit2 in cit1) - assert_false(cit1 in cit2) + assert cit1 in cit1 + assert cit2 in cit1 + assert cit1 not in cit2 cit3 = Citation(entry, path="somemodule.submodule:class2.func2") - assert_false(cit3.cites_module) - assert_equal(cit3.module, "somemodule.submodule") + assert not cit3.cites_module + assert cit3.module == "somemodule.submodule" - assert_true(cit2 in cit1) - assert_true(cit3 in cit1) - assert_true(cit3 in cit2) - assert_false(cit2 in cit3) + assert cit2 in cit1 + assert cit3 in cit1 + assert cit3 in cit2 + assert cit2 not in cit3 cit4 = Citation(entry, path="somemodule2:class2.func2") - assert_false(cit4.cites_module) - assert_equal(cit4.module, "somemodule2") + assert not cit4.cites_module + assert cit4.module == "somemodule2" - assert_false(cit1 in cit4) - assert_false(cit4 in cit1) + assert cit1 not in cit4 + assert cit4 not in cit1 def test_entry(): entry = BibTeX(_sample_bibtex) - yield _test_entry, DueCreditCollector(), entry + _test_entry(DueCreditCollector(), entry) entries = [BibTeX(_sample_bibtex), BibTeX(_sample_bibtex), Doi(_sample_doi)] - yield _test_entry, DueCreditCollector(), entries + _test_entry(DueCreditCollector(), entries) def _test_dcite_basic(due, callable): - - assert_equal(callable("magical", 1), "load") + assert callable("magical", 1) == "load" # verify that @wraps correctly passes all the docstrings etc - assert_equal(callable.__name__, "method") - assert_equal(callable.__doc__, "docstring") + assert callable.__name__ == "method" + assert callable.__doc__ == "docstring" def test_dcite_method(): - # Test basic wrapping that we don't mask out the arguments for due in [DueCreditCollector(), InactiveDueCreditCollector()]: active = isinstance(due, DueCreditCollector) @@ -112,126 +107,127 @@ @due.dcite("XXX0", path='method') def method(arg1, kwarg2="blah"): """docstring""" - assert_equal(arg1, "magical") - assert_equal(kwarg2, 1) + assert arg1 == "magical" + assert kwarg2 == 1 return "load" class SomeClass(object): @due.dcite("XXX0", path='someclass:method') def method(self, arg1, kwarg2="blah"): """docstring""" - assert_equal(arg1, "magical") - assert_equal(kwarg2, 1) + assert arg1 == "magical" + assert kwarg2 == 1 return "load" if active: - assert_equal(due.citations, {}) - assert_equal(len(due._entries), 1) + assert due.citations == {} + assert len(due._entries) == 1 - yield _test_dcite_basic, due, method + _test_dcite_basic(due, method) if active: - assert_equal(len(due.citations), 1) - assert_equal(len(due._entries), 1) + assert len(due.citations) == 1 + assert len(due._entries) == 1 citation = due.citations[("method", "XXX0")] - assert_equal(citation.count, 1) + assert citation.count == 1 # TODO: this is probably incomplete path but unlikely we would know # any better - assert_equal(citation.path, "method") + assert citation.path == "method" instance = SomeClass() - yield _test_dcite_basic, due, instance.method + _test_dcite_basic(due, instance.method) if active: - assert_equal(len(due.citations), 2) - assert_equal(len(due._entries), 1) + assert len(due.citations) == 2 + assert len(due._entries) == 1 # TODO: we should actually get path/counts pairs so here citation = due.citations[("someclass:method", "XXX0")] - assert_equal(citation.path, "someclass:method") - assert_equal(citation.count, 1) + assert citation.path == "someclass:method" + assert citation.count == 1 # And we explicitly stated that module need to be cited - assert_true(citation.cite_module) - + assert citation.cite_module class SomeClass2(object): + # Used to test for classes that are not instantiated @due.dcite("XXX0", path="some.module.without.method") def method2(self, arg1, kwarg2="blah"): - assert_equal(arg1, "magical") + assert arg1 == "magical" return "load" # and a method pointing to the module instance2 = SomeClass() - yield _test_dcite_basic, due, instance2.method + _test_dcite_basic(due, instance2.method) if active: - assert_equal(len(due.citations), 2) # different paths - assert_equal(len(due._entries), 1) # the same entry + assert len(due.citations) == 2 # different paths + assert len(due._entries) == 1 # the same entry # TODO: we should actually get path/counts pairs so here # it is already a different path # And we still explicitly stated that module need to be cited - assert_true(citation.cite_module) + assert citation.cite_module + def _test_args_match_conditions(conds): args_match_conditions = DueCreditCollector._args_match_conditions - assert_true(args_match_conditions(conds)) - assert_true(args_match_conditions(conds, None)) - assert_true(args_match_conditions(conds, someirrelevant=True)) - assert_true(args_match_conditions(conds, method='purge')) - assert_true(args_match_conditions(conds, method='fullpurge')) - assert_true(args_match_conditions(conds, None, 'purge')) - assert_true(args_match_conditions(conds, None, 'fullpurge')) - assert_true(args_match_conditions(conds, None, 'fullpurge', someirrelevant="buga")) - assert_false(args_match_conditions(conds, None, 'push')) - assert_false(args_match_conditions(conds, method='push')) + assert args_match_conditions(conds) + assert args_match_conditions(conds, None) + assert args_match_conditions(conds, someirrelevant=True) + assert args_match_conditions(conds, method='purge') + assert args_match_conditions(conds, method='fullpurge') + assert args_match_conditions(conds, None, 'purge') + assert args_match_conditions(conds, None, 'fullpurge') + assert args_match_conditions(conds, None, 'fullpurge', someirrelevant="buga") + assert not args_match_conditions(conds, None, 'push') + assert not args_match_conditions(conds, method='push') if len(conds) < 2: return # got compound case - assert_true(args_match_conditions(conds, scope='life')) - assert_false(args_match_conditions(conds, scope='someother')) + assert args_match_conditions(conds, scope='life') + assert not args_match_conditions(conds, scope='someother') # should be "and", so if one not matching -- both not matching - assert_false(args_match_conditions(conds, method="wrong", scope='life')) - assert_false(args_match_conditions(conds, method="purge", scope='someother')) - #assert_true(args_match_conditions(conds, None, None, 'life')) # ambiguous/conflicting + assert not args_match_conditions(conds, method="wrong", scope='life') + assert not args_match_conditions(conds, method="purge", scope='someother') + # assert args_match_conditions(conds, None, None, 'life') # ambiguous/conflicting + def test_args_match_conditions(): - yield _test_args_match_conditions, {(1, 'method'): {'purge', 'fullpurge', 'DC_DEFAULT'}} - yield _test_args_match_conditions, {(1, 'method'): {'purge', 'fullpurge', 'DC_DEFAULT'}, - (2, 'scope'): {'life', 'DC_DEFAULT'}} + _test_args_match_conditions({(1, 'method'): {'purge', 'fullpurge', 'DC_DEFAULT'}}) + _test_args_match_conditions({(1, 'method'): {'purge', 'fullpurge', 'DC_DEFAULT'}, + (2, 'scope'): {'life', 'DC_DEFAULT'}}) def _test_dcite_match_conditions(due, callable, path): - assert_equal(due.citations, {}) - assert_equal(len(due._entries), 1) + assert due.citations == {} + assert len(due._entries) == 1 - assert_equal(callable("magical", "unknown"), "load unknown") - assert_equal(due.citations, {}) - assert_equal(len(due._entries), 1) + assert callable("magical", "unknown") == "load unknown" + assert due.citations == {} + assert len(due._entries) == 1 - assert_equal(callable("magical"), "load blah") + assert callable("magical") == "load blah" - assert_equal(len(due.citations), 1) - assert_equal(len(due._entries), 1) + assert len(due.citations) == 1 + assert len(due._entries) == 1 entry = due._entries['XXX0'] - assert_equal(due.citations[(path, 'XXX0')].count, 1) + assert due.citations[(path, 'XXX0')].count == 1 # Cause the same citation - assert_equal(callable("magical", "blah"), "load blah") + assert callable("magical", "blah") == "load blah" # Nothing should change - assert_equal(len(due.citations), 1) - assert_equal(len(due._entries), 1) - assert_equal(due.citations[(path, 'XXX0')].count, 2) # Besides the count + assert len(due.citations) == 1 + assert len(due._entries) == 1 + assert due.citations[(path, 'XXX0')].count == 2 # Besides the count # Now cause new citation given another value - assert_equal(callable("magical", "boo"), "load boo") - assert_equal(len(due.citations), 2) - assert_equal(len(due._entries), 2) - assert_equal(due.citations[(path, 'XXX0')].count, 2) # Count should stay the same for XXX0 - assert_equal(due.citations[(path, "10.3389/fninf.2012.00022")].count, 1) # but we get a new one + assert callable("magical", "boo") == "load boo" + assert len(due.citations) == 2 + assert len(due._entries) == 2 + assert due.citations[(path, 'XXX0')].count == 2 # Count should stay the same for XXX0 + assert due.citations[(path, "10.3389/fninf.2012.00022")].count == 1 # but we get a new one def test_dcite_match_conditions_function(): - due = DueCreditCollector() due.add(BibTeX(_sample_bibtex)) @@ -241,7 +237,7 @@ conditions={(1, "kwarg2"): {"boo"}}) def method(arg1, kwarg2="blah"): """docstring""" - assert_equal(arg1, "magical") + assert arg1 == "magical" return "load %s" % kwarg2 _test_dcite_match_conditions(due, method, 'callable') @@ -264,7 +260,7 @@ conditions={(2, "kwarg2"): {"boo"}}) def method(self, arg1, kwarg2="blah"): """docstring""" - assert_equal(arg1, "magical") + assert arg1 == "magical" return "load %s" % kwarg2 citeable = Citeable(param="paramvalue") @@ -272,36 +268,37 @@ # now test for self.param - -@with_tempfile -def test_get_output_handler_method(f): - with patch.dict(os.environ, {'DUECREDIT_OUTPUTS': 'pickle'}): - entry = BibTeX(_sample_bibtex) - collector = DueCreditCollector() - collector.cite(entry, path='module') - summary = CollectorSummary(collector, fn=f) - handlers = [summary._get_output_handler(type_, collector) - for type_ in ['pickle']] +def test_get_output_handler_method(tmpdir, monkeypatch): + tempfile = str(tmpdir.mkdir("sub").join("tempfile.txt")) + monkeypatch.setitem(os.environ, 'DUECREDIT_OUTPUTS', 'pickle') + entry = BibTeX(_sample_bibtex) + collector = DueCreditCollector() + collector.cite(entry, path='module') - #assert_is_instance(handlers[0], TextOutput) - assert_is_instance(handlers[0], PickleOutput) + summary = CollectorSummary(collector, fn=tempfile) + handlers = [summary._get_output_handler(type_, collector) + for type_ in ['pickle']] - assert_raises(NotImplementedError, summary._get_output_handler, - 'nothing', collector) + # assert isinstance(handlers[0], TextOutput) + assert isinstance(handlers[0], PickleOutput) + pytest.raises(NotImplementedError, summary._get_output_handler, + 'nothing', collector) -def test_collectors_uniform_API(): + +def test_collectors_uniform_api(): get_api = lambda obj: [x for x in sorted(dir(obj)) - if not x.startswith('_') - or x in ('__call__')] - assert_equal(get_api(DueCreditCollector), get_api(InactiveDueCreditCollector)) + if not x.startswith('_') or x in '__call__'] + assert get_api(DueCreditCollector) == get_api(InactiveDueCreditCollector) def _test__docs__(method): - assert("entry:" in method.__doc__) - assert("tags: " in method.__doc__) + assert "entry:" in method.__doc__ + assert "tags: " in method.__doc__ + def test__docs__(): - yield _test__docs__, DueCreditCollector.cite - yield _test__docs__, DueCreditCollector.dcite - yield _test__docs__, Citation.__init__ + _test__docs__(DueCreditCollector.cite) + _test__docs__(DueCreditCollector.dcite) + _test__docs__(Citation.__init__) diff -Nru duecredit-0.6.0/duecredit/tests/test_dueswitch.py duecredit-0.6.4/duecredit/tests/test_dueswitch.py --- duecredit-0.6.0/duecredit/tests/test_dueswitch.py 2016-06-17 04:01:39.000000000 +0000 +++ duecredit-0.6.4/duecredit/tests/test_dueswitch.py 2018-06-26 16:02:03.000000000 +0000 @@ -7,26 +7,35 @@ # # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -from mock import patch import atexit - -from nose import SkipTest +import pytest from ..injections.injector import DueCreditInjector from ..dueswitch import due -@patch.object(DueCreditInjector, 'activate') -@patch.object(atexit, 'register') -def test_dueswitch_activate(mock_register, mock_activate): - was_active = due.active - # atexit.register(crap) - # injector.activate() + +def test_dueswitch_activate(monkeypatch): + if due.active: + pytest.skip("due is already active, can't test more at this point") + + state = dict(activate=0, register=0, register_func=None) + + # Patch DueCreditInjector.activate + def activate_calls(*args, **kwargs): + state["activate"] += 1 + + monkeypatch.setattr(DueCreditInjector, "activate", activate_calls) + + # Patch atexit.register + def register(func): + state["register"] += 1 + state["register_func"] = func + + monkeypatch.setattr(atexit, "register", register) + due.activate() - if was_active: - # we can only test that mocked methods do not invoked second time - mock_activate.assert_not_called() - mock_register.assert_not_called() - raise SkipTest("due is already active, can't test more at this point") + # was not active, so should have called activate of the injector class - mock_activate.assert_called_once_with() - mock_register.assert_called_once_with(due._dump_collector_summary) + assert state["activate"] == 1 + assert state["register"] == 1 + assert state["register_func"] == due.dump diff -Nru duecredit-0.6.0/duecredit/tests/test_injections.py duecredit-0.6.4/duecredit/tests/test_injections.py --- duecredit-0.6.0/duecredit/tests/test_injections.py 2016-06-17 04:01:39.000000000 +0000 +++ duecredit-0.6.4/duecredit/tests/test_injections.py 2018-06-26 16:02:03.000000000 +0000 @@ -8,8 +8,8 @@ # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## import gc -import os import sys +import pytest from six import viewvalues, PY2 @@ -19,17 +19,13 @@ import builtins as __builtin__ _orig__import__ = __builtin__.__import__ -from duecredit.collector import DueCreditCollector, InactiveDueCreditCollector -from duecredit.entries import BibTeX, Doi +from duecredit.collector import DueCreditCollector +from duecredit.entries import Doi +import duecredit.tests.mod as mod from ..injections.injector import DueCreditInjector, find_object, get_modules_for_injection from .. import __version__ -from nose import SkipTest -from nose.tools import assert_equal -from nose.tools import assert_false -from nose.tools import assert_true - try: import mvpa2 _have_mvpa2 = True @@ -39,6 +35,7 @@ from logging import getLogger lgr = getLogger('duecredit.tests.injector') + class TestActiveInjector(object): def setup(self): lgr.log(5, "Setting up for a TestActiveInjector test") @@ -52,7 +49,7 @@ # gc might not pick up inj after some tests complete # so we will always deactivate explicitly self.injector.deactivate() - assert_true(__builtin__.__import__ is _orig__import__) + assert __builtin__.__import__ is _orig__import__ self._cleanup_modules() def _cleanup_modules(self): @@ -60,122 +57,121 @@ sys.modules.pop('duecredit.tests.mod') def _test_simple_injection(self, func, import_stmt, func_call=None): - assert_false('duecredit.tests.mod' in sys.modules) + assert 'duecredit.tests.mod' not in sys.modules self.injector.add('duecredit.tests.mod', func, Doi('1.2.3.4'), description="Testing %s" % func, min_version='0.1', max_version='1.0', tags=["implementation", "very custom"]) - assert_false('duecredit.tests.mod' in sys.modules) # no import happening - assert_equal(len(self.due._entries), 0) - assert_equal(len(self.due.citations), 0) + assert 'duecredit.tests.mod' not in sys.modules # no import happening + assert len(self.due._entries) == 0 + assert len(self.due.citations) == 0 globals_, locals_ = {}, {} exec(import_stmt, globals_, locals_) - assert_equal(len(self.due._entries), 1) # we should get an entry now - assert_equal(len(self.due.citations), 0) # but not yet a citation + assert len(self.due._entries) == 1 # we should get an entry now + assert len(self.due.citations) == 0 # but not yet a citation import duecredit.tests.mod as mod _, _, obj = find_object(mod, func) - assert_true(obj.__duecredited__) # we wrapped - assert_false(obj.__duecredited__ is obj) # and it is not pointing to the same func - assert_equal(obj.__doc__, "custom docstring") # we preserved docstring + assert obj.__duecredited__ # we wrapped + assert obj.__duecredited__ is not obj # and it is not pointing to the same func + assert obj.__doc__ == "custom docstring" # we preserved docstring # TODO: test decoration features -- preserver __doc__ etc exec('ret = %s(None, "somevalue")' % (func_call or func), globals_, locals_) - # XXX: awkwardly 'ret' is not found in the scope while running nosetests + # TODO: awkwardly 'ret' is not found in the scope while running pytest # under python3.4, although present in locals()... WTF? - assert_equal(locals_['ret'], "%s: None, somevalue" % func) - assert_equal(len(self.due._entries), 1) - assert_equal(len(self.due.citations), 1) + assert locals_['ret'] == "%s: None, somevalue" % func + assert len(self.due._entries) == 1 + assert len(self.due.citations) == 1 # TODO: there must be a cleaner way to get first value citation = list(viewvalues(self.due.citations))[0] # TODO: ATM we don't allow versioning of the submodules -- we should # assert_equal(citation.version, '0.5') # ATM it will be the duecredit's version - assert_equal(citation.version, __version__) + assert citation.version == __version__ assert(citation.tags == ['implementation', 'very custom']) def _test_double_injection(self, func, import_stmt, func_call=None): - assert_false('duecredit.tests.mod' in sys.modules) + assert 'duecredit.tests.mod' not in sys.modules # add one injection self.injector.add('duecredit.tests.mod', func, Doi('1.2.3.4'), description="Testing %s" % func, min_version='0.1', max_version='1.0', tags=["implementation", "very custom"]) - #add another one + # add another one self.injector.add('duecredit.tests.mod', func, Doi('1.2.3.5'), description="Testing %s" % func, min_version='0.1', max_version='1.0', tags=["implementation", "very custom"]) - assert_false('duecredit.tests.mod' in sys.modules) # no import happening - assert_equal(len(self.due._entries), 0) - assert_equal(len(self.due.citations), 0) + assert 'duecredit.tests.mod' not in sys.modules # no import happening + assert len(self.due._entries) == 0 + assert len(self.due.citations) == 0 globals_, locals_ = {}, {} exec(import_stmt, globals_, locals_) - assert_equal(len(self.due._entries), 2) # we should get two entries now - assert_equal(len(self.due.citations), 0) # but not yet a citation + assert len(self.due._entries) == 2 # we should get two entries now + assert len(self.due.citations) == 0 # but not yet a citation import duecredit.tests.mod as mod _, _, obj = find_object(mod, func) - assert_true(obj.__duecredited__) # we wrapped - assert_false(obj.__duecredited__ is obj) # and it is not pointing to the same func - assert_equal(obj.__doc__, "custom docstring") # we preserved docstring + assert obj.__duecredited__ # we wrapped + assert obj.__duecredited__ is not obj # and it is not pointing to the same func + assert obj.__doc__ == "custom docstring" # we preserved docstring # TODO: test decoration features -- preserver __doc__ etc exec('ret = %s(None, "somevalue")' % (func_call or func), globals_, locals_) - # XXX: awkwardly 'ret' is not found in the scope while running nosetests + # TODO: awkwardly 'ret' is not found in the scope while running pytest # under python3.4, although present in locals()... WTF? - assert_equal(locals_['ret'], "%s: None, somevalue" % func) - assert_equal(len(self.due._entries), 2) - assert_equal(len(self.due.citations), 2) + assert locals_['ret'] == "%s: None, somevalue" % func + assert len(self.due._entries) == 2 + assert len(self.due.citations) == 2 # TODO: there must be a cleaner way to get first value citation = list(viewvalues(self.due.citations))[0] # TODO: ATM we don't allow versioning of the submodules -- we should # assert_equal(citation.version, '0.5') # ATM it will be the duecredit's version - assert_equal(citation.version, __version__) + assert citation.version, __version__ - assert(citation.tags == ['implementation', 'very custom']) + assert (citation.tags == ['implementation', 'very custom']) - def test_simple_injection(self): - yield self._test_simple_injection, "testfunc1", 'from duecredit.tests.mod import testfunc1', None - yield self._test_simple_injection, "TestClass1.testmeth1", \ - 'from duecredit.tests.mod import TestClass1; c = TestClass1()', 'c.testmeth1' - yield self._test_simple_injection, "TestClass12.Embed.testmeth1", \ - 'from duecredit.tests.mod import TestClass12; c = TestClass12.Embed()', 'c.testmeth1' - - def test_double_injection(self): - yield self._test_double_injection, "testfunc1", 'from duecredit.tests.mod import testfunc1', None - yield self._test_double_injection, "TestClass1.testmeth1", \ - 'from duecredit.tests.mod import TestClass1; c = TestClass1()', 'c.testmeth1' - yield self._test_double_injection, "TestClass12.Embed.testmeth1", \ - 'from duecredit.tests.mod import TestClass12; c = TestClass12.Embed()', 'c.testmeth1' + test1 = ('testfunc1', 'from duecredit.tests.mod import testfunc1', None) + test2 = ("TestClass1.testmeth1", 'from duecredit.tests.mod import TestClass1; c = TestClass1()', 'c.testmeth1') + test3 = ("TestClass12.Embed.testmeth1", 'from duecredit.tests.mod import TestClass12; c = TestClass12.Embed()', + 'c.testmeth1') + + @pytest.mark.parametrize("func, import_stmt, func_call", [test1, test2, test3]) + def test_simple_injection(self, func, import_stmt, func_call): + self._test_simple_injection(func, import_stmt, func_call) + + @pytest.mark.parametrize("func, import_stmt, func_call", [test1, test2, test3]) + def test_double_injection(self, func, import_stmt, func_call): + self._test_double_injection(func, import_stmt, func_call) def test_delayed_entries(self): # verify that addition of delayed injections happened modules_for_injection = get_modules_for_injection() - assert_equal(len(self.injector._delayed_injections), len(modules_for_injection)) - assert_equal(self.injector._entry_records, {}) # but no entries were added - assert('scipy' in self.injector._delayed_injections) # We must have it ATM + assert len(self.injector._delayed_injections) == len(modules_for_injection) + assert self.injector._entry_records == {} # but no entries were added + assert 'scipy' in self.injector._delayed_injections # We must have it ATM try: # We do have injections for scipy import scipy except ImportError as e: - raise SkipTest("scipy was not found: %s" % (e,)) + pytest.skip("scipy was not found: %s" % (e,)) def test_import_mvpa2_suite(self): if not _have_mvpa2: - raise SkipTest("no mvpa2 found") + pytest.skip("no mvpa2 found") # just a smoke test for now import mvpa2.suite as mv @@ -187,24 +183,21 @@ # TODO: catch/analyze warnings exec('from duecredit.tests.mod import testfunc1', {}, {}) - def test_incorrect_path(self): - yield self._test_incorrect_path, "nonexistingmodule", None - yield self._test_incorrect_path, "duecredit.tests.mod.nonexistingmodule", None - yield self._test_incorrect_path, "duecredit.tests.mod", "nonexisting" - yield self._test_incorrect_path, "duecredit.tests.mod", "nonexisting.whocares" + @pytest.mark.parametrize("mod, obj", [("nonexistingmodule", None), + ("duecredit.tests.mod.nonexistingmodule", None), + ("duecredit.tests.mod", "nonexisting"), + ("duecredit.tests.mod", "nonexisting.whocares")]) + def test_incorrect_path(self, mod, obj): + self._test_incorrect_path(mod, obj) +def test_find_iobject(): + assert find_object(mod, 'testfunc1') == (mod, 'testfunc1', mod.testfunc1) + assert find_object(mod, 'TestClass1') == (mod, 'TestClass1', mod.TestClass1) + assert find_object(mod, 'TestClass1.testmeth1') == (mod.TestClass1, 'testmeth1', mod.TestClass1.testmeth1) + assert find_object(mod, 'TestClass12.Embed.testmeth1') == (mod.TestClass12.Embed, + 'testmeth1', mod.TestClass12.Embed.testmeth1) -def _test_find_object(mod, path, parent, obj_name, obj): - assert_equal(find_object(mod, path), (parent, obj_name, obj)) - -def test_find_object(): - import duecredit.tests.mod as mod - yield _test_find_object, mod, 'testfunc1', mod, 'testfunc1', mod.testfunc1 - yield _test_find_object, mod, 'TestClass1', mod, 'TestClass1', mod.TestClass1 - yield _test_find_object, mod, 'TestClass1.testmeth1', mod.TestClass1, 'testmeth1', mod.TestClass1.testmeth1 - yield _test_find_object, mod, 'TestClass12.Embed.testmeth1', \ - mod.TestClass12.Embed, 'testmeth1', mod.TestClass12.Embed.testmeth1 def test_no_double_activation(): orig__import__ = __builtin__.__import__ @@ -212,31 +205,30 @@ due = DueCreditCollector() injector = DueCreditInjector(collector=due) injector.activate() - assert_false(__builtin__.__import__ is orig__import__) + assert __builtin__.__import__ is not orig__import__ duecredited__import__ = __builtin__.__import__ # TODO: catch/analyze/swallow warning injector.activate() - assert_true(__builtin__.__import__ is duecredited__import__) # we didn't decorate again + assert __builtin__.__import__ is duecredited__import__ # we didn't decorate again finally: injector.deactivate() __builtin__.__import__ = orig__import__ def test_get_modules_for_injection(): - assert_equal(get_modules_for_injection(), [ - 'mod_biosig', - 'mod_dipy', - 'mod_mdp', - 'mod_mne', - 'mod_nibabel', - 'mod_nipy', - 'mod_nipype', - 'mod_numpy', - 'mod_pandas', - 'mod_psychopy', - 'mod_scipy', - 'mod_skimage', - 'mod_sklearn']) + assert get_modules_for_injection() == ['mod_biosig', + 'mod_dipy', + 'mod_mdp', + 'mod_mne', + 'mod_nibabel', + 'mod_nipy', + 'mod_nipype', + 'mod_numpy', + 'mod_pandas', + 'mod_psychopy', + 'mod_scipy', + 'mod_skimage', + 'mod_sklearn'] def test_cover_our_injections(): @@ -260,16 +252,16 @@ due = DueCreditCollector() inj = DueCreditInjector(collector=due) del inj # delete inactive - assert_true(__builtin__.__import__ is orig__import__) + assert __builtin__.__import__ is orig__import__ inj = DueCreditInjector(collector=due) inj.activate(retrospect=False) - assert_false(__builtin__.__import__ is orig__import__) - assert_false(inj._orig_import is None) + assert __builtin__.__import__ is not orig__import__ + assert inj._orig_import is not None del inj # delete active but not used inj = None __builtin__.__import__ = None # We need to do that since otherwise gc will not pick up inj gc.collect() # To cause __del__ - assert_true(__builtin__.__import__ is orig__import__) + assert __builtin__.__import__ is orig__import__ import abc # and new imports work just fine finally: __builtin__.__import__ = orig__import__ @@ -286,24 +278,24 @@ inj = DueCreditInjector(collector=due) inj.activate(retrospect=False) - assert_false(__builtin__.__import__ is orig__import__) - assert_false(inj._orig_import is None) + assert __builtin__.__import__ is not orig__import__ + assert inj._orig_import is not None inj.deactivate() - assert_true(__builtin__.__import__ is orig__import__) - assert_true(inj._orig_import is None) + assert __builtin__.__import__ is orig__import__ + assert inj._orig_import is None # create 2nd one inj2 = DueCreditInjector(collector=due) inj2.activate(retrospect=False) - assert_false(__builtin__.__import__ is orig__import__) - assert_false(inj2._orig_import is None) + assert __builtin__.__import__ is not orig__import__ + assert inj2._orig_import is not None del inj inj = None gc.collect() # To cause __del__ - assert_false(__builtin__.__import__ is orig__import__) # would fail if del had side-effect - assert_false(inj2._orig_import is None) + assert __builtin__.__import__ is not orig__import__ # would fail if del had side-effect + assert inj2._orig_import is not None inj2.deactivate() - assert_true(__builtin__.__import__ is orig__import__) + assert __builtin__.__import__ is orig__import__ import abc # and new imports work just fine finally: __builtin__.__import__ = orig__import__ diff -Nru duecredit-0.6.0/duecredit/tests/test_io.py duecredit-0.6.4/duecredit/tests/test_io.py --- duecredit-0.6.0/duecredit/tests/test_io.py 2016-06-17 04:01:39.000000000 +0000 +++ duecredit-0.6.4/duecredit/tests/test_io.py 2018-06-26 16:02:03.000000000 +0000 @@ -1,4 +1,4 @@ -# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*- +# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil; coding: utf-8 -*- # ex: set sts=4 ts=4 sw=4 noet: # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## # @@ -11,22 +11,17 @@ import re import pickle import os +import pytest -from .test_collector import _sample_bibtex, _sample_bibtex2 from six.moves import StringIO from six import text_type -from mock import patch - +import duecredit.io from ..collector import DueCreditCollector, Citation -from .test_collector import _sample_bibtex, _sample_doi +from .test_collector import _sample_bibtex, _sample_doi, _sample_bibtex2 from ..entries import BibTeX, DueCreditEntry, Doi from ..io import TextOutput, PickleOutput, import_doi, \ get_text_rendering, format_bibtex, _is_contained, Output, BibTeXOutput -from ..utils import with_tempfile - -from nose.tools import assert_equal, assert_is_instance, assert_raises, \ - assert_true, assert_false try: import vcr @@ -35,21 +30,21 @@ def test_import_doi(): doi_good = '10.1038/nrd842' kw = dict(sleep=0.00001, retries=2) - assert_is_instance(import_doi(doi_good, **kw), text_type) + assert isinstance(import_doi(doi_good, **kw), text_type) doi_bad = 'fasljfdldaksj' - assert_raises(ValueError, import_doi, doi_bad, **kw) + with pytest.raises(ValueError): + import_doi(doi_bad, **kw) doi_zenodo = '10.5281/zenodo.50186' - assert_is_instance(import_doi(doi_zenodo, **kw), text_type) + assert isinstance(import_doi(doi_zenodo, **kw), text_type) except ImportError: # no vcr, and that is in 2015! pass -@with_tempfile -def test_pickleoutput(fn): +def test_pickleoutput(tmpdir): #entry = BibTeX('@article{XXX0, ...}') entry = BibTeX("@article{Atkins_2002,\n" "title=title,\n" @@ -70,20 +65,20 @@ # test it doesn't puke with an empty collector collectors = [collector_, DueCreditCollector()] + tempfile = str(tmpdir.mkdir("sub").join("tempfile.txt")) + for collector in collectors: - pickler = PickleOutput(collector, fn=fn) - assert_equal(pickler.fn, fn) - assert_equal(pickler.dump(), None) + pickler = PickleOutput(collector, fn=tempfile) + assert pickler.fn == tempfile + assert pickler.dump() is None - with open(fn, 'rb') as f: + with open(tempfile, 'rb') as f: collector_loaded = pickle.load(f) - assert_equal(collector.citations.keys(), - collector_loaded.citations.keys()) + assert collector.citations.keys() == collector_loaded.citations.keys() # TODO: implement comparison of citations - assert_equal(collector._entries.keys(), - collector_loaded._entries.keys()) - os.unlink(fn) + assert collector._entries.keys() == collector_loaded._entries.keys() + os.unlink(tempfile) def test_output(): @@ -99,14 +94,12 @@ packages, modules, objects = output._get_collated_citations(tags=['*']) - assert_equal(len(packages), 1) - assert_equal(len(modules), 1) - assert_equal(len(objects), 0) - - assert_equal(packages['package'][0], - collector.citations[('package', entry.get_key())]) - assert_equal(modules['package.module'][0], - collector.citations[('package.module', entry.get_key())]) + assert len(packages) == 1 + assert len(modules) == 1 + assert len(objects) == 0 + + assert packages['package'][0] == collector.citations[('package', entry.get_key())] + assert modules['package.module'][0] == collector.citations[('package.module', entry.get_key())] # no toppackage collector = DueCreditCollector() @@ -117,13 +110,11 @@ packages, modules, objects = output._get_collated_citations(tags=['*']) - assert_equal(len(packages), 0) - assert_equal(len(modules), 1) - assert_equal(len(objects), 0) - - assert_equal(modules['package2.module'][0], - collector.citations[('package2.module', entry.get_key())]) + assert len(packages) == 0 + assert len(modules) == 1 + assert len(objects) == 0 + assert modules['package2.module'][0] == collector.citations[('package2.module', entry.get_key())] # toppackage because required collector = DueCreditCollector() @@ -134,15 +125,12 @@ packages, modules, objects = output._get_collated_citations(tags=['*']) - assert_equal(len(packages), 1) - assert_equal(len(modules), 1) - assert_equal(len(objects), 0) - - assert_equal(packages['package'][0], - collector.citations[('package', entry.get_key())]) - assert_equal(modules['package2.module'][0], - collector.citations[('package2.module', entry.get_key())]) + assert len(packages) == 1 + assert len(modules) == 1 + assert len(objects) == 0 + assert packages['package'][0] == collector.citations[('package', entry.get_key())] + assert modules['package2.module'][0] == collector.citations[('package2.module', entry.get_key())] # check it returns multiple entries collector = DueCreditCollector() @@ -154,23 +142,19 @@ packages, modules, objects = output._get_collated_citations(tags=['*']) - assert_equal(len(packages), 1) - assert_equal(len(packages['package']), 2) - assert_equal(len(modules), 1) - assert_equal(len(objects), 0) + assert len(packages) == 1 + assert len(packages['package']) == 2 + assert len(modules) == 1 + assert len(objects) == 0 # sort them in order so we know who is who # entry2 key is Atk... # entry key is XX.. packs = sorted(packages['package'], key=lambda x: x.entry.key) - assert_equal(packs[0], - collector.citations[('package', entry2.get_key())]) - assert_equal(packs[1], - collector.citations[('package', entry.get_key())]) - assert_equal(modules['package.module'][0], - collector.citations[('package.module', entry.get_key())]) - + assert packs[0] == collector.citations[('package', entry2.get_key())] + assert packs[1] == collector.citations[('package', entry.get_key())] + assert modules['package.module'][0] == collector.citations[('package.module', entry.get_key())] # check that filtering works collector = DueCreditCollector() @@ -182,18 +166,16 @@ packages, modules, objects = output._get_collated_citations(tags=['edu']) - assert_equal(len(packages), 1) - assert_equal(len(packages['package']), 1) - assert_equal(len(modules), 1) - assert_equal(len(objects), 0) - - assert_equal(packages['package'][0], - collector.citations[('package', entry.get_key())]) - assert_equal(modules['package.module'][0], - collector.citations[('package.module', entry.get_key())]) + assert len(packages) == 1 + assert len(packages['package']) == 1 + assert len(modules) == 1 + assert len(objects) == 0 + + assert packages['package'][0] == collector.citations[('package', entry.get_key())] + assert modules['package.module'][0] == collector.citations[('package.module', entry.get_key())] -def test_output_return_all(): +def test_output_return_all(monkeypatch): entry = BibTeX(_sample_bibtex) entry2 = BibTeX(_sample_bibtex2) @@ -205,25 +187,25 @@ output = Output(None, collector) packages, modules, objects = output._get_collated_citations(tags=['*']) - assert_false(packages) - assert_false(modules) - assert_false(objects) + assert not packages + assert not modules + assert not objects for flag in ['1', 'True', 'TRUE', 'true', 'on', 'yes']: - with patch.dict(os.environ, {'DUECREDIT_REPORT_ALL': flag}): - # if _all is None then get the environment - packages, modules, objects = output._get_collated_citations(tags=['*']) - assert_equal(len(packages), 2) - assert_false(modules) - assert_false(objects) - # however if _all is set it shouldn't work - packages, modules, objects = output._get_collated_citations(tags=['*'], all_=False) - assert_false(packages) - assert_false(modules) - assert_false(objects) + monkeypatch.setitem(os.environ, 'DUECREDIT_REPORT_ALL', flag) + # if _all is None then get the environment + packages, modules, objects = output._get_collated_citations(tags=['*']) + assert len(packages) == 2 + assert not modules + assert not objects + # however if _all is set it shouldn't work + packages, modules, objects = output._get_collated_citations(tags=['*'], all_=False) + assert not packages + assert not modules + assert not objects -def test_output_tags(): +def test_output_tags(monkeypatch): entry = BibTeX(_sample_bibtex) entry2 = BibTeX(_sample_bibtex2) @@ -235,27 +217,27 @@ output = Output(None, collector) packages, modules, objects = output._get_collated_citations(tags=['*']) - assert_true(len(packages) == 1) - assert_true(len(modules) == 1) - assert_false(objects) + assert len(packages) == 1 + assert len(modules) == 1 + assert not objects packages, modules, objects = output._get_collated_citations() - assert_false(packages) - assert_false(modules) - assert_false(objects) + assert not packages + assert not modules + assert not objects for tags in ['edu', 'wip', 'edu,wip']: - with patch.dict(os.environ, {'DUECREDIT_REPORT_TAGS': tags}): - # if tags is None then get the environment - packages, modules, objects = output._get_collated_citations() - assert_true(len(packages) == (1 if 'edu' in tags else 0)) - assert_true(len(modules) == (1 if 'wip' in tags else 0)) - assert_false(objects) - # however if tags is set it shouldn't work - packages, modules, objects = output._get_collated_citations(tags=['implementation']) - assert_false(packages) - assert_false(modules) - assert_false(objects) + monkeypatch.setitem(os.environ, 'DUECREDIT_REPORT_TAGS', tags) + # if tags is None then get the environment + packages, modules, objects = output._get_collated_citations() + assert len(packages) == (1 if 'edu' in tags else 0) + assert len(modules) == (1 if 'wip' in tags else 0) + assert not objects + # however if tags is set it shouldn't work + packages, modules, objects = output._get_collated_citations(tags=['implementation']) + assert not packages + assert not modules + assert not objects def test_text_output(): @@ -270,9 +252,9 @@ strio = StringIO() TextOutput(strio, collector).dump(tags=['*']) value = strio.getvalue() - assert_true("0 packages cited" in value, msg="value was %s" % value) - assert_true("0 modules cited" in value, msg="value was %s" % value) - assert_true("0 functions cited" in value, msg="value was %s" % value) + assert "0 packages cited" in value, "value was %s" % value + assert "0 modules cited" in value, "value was %s" % value + assert "0 functions cited" in value, "value was %s" % value # but it should be cited if cite_module=True collector = DueCreditCollector() @@ -281,9 +263,9 @@ strio = StringIO() TextOutput(strio, collector).dump(tags=['*']) value = strio.getvalue() - assert_true("1 package cited" in value, msg="value was %s" % value) - assert_true("0 modules cited" in value, msg="value was %s" % value) - assert_true("0 functions cited" in value, msg="value was %s" % value) + assert "1 package cited" in value, "value was %s" % value + assert "0 modules cited" in value, "value was %s" % value + assert "0 functions cited" in value, "value was %s" % value # in this case, we should be citing the package since we are also citing a # submodule @@ -294,11 +276,11 @@ strio = StringIO() TextOutput(strio, collector).dump(tags=['*']) value = strio.getvalue() - assert_true("1 package cited" in value, msg="value was %s" % value) - assert_true("1 module cited" in value, msg="value was %s" % value) - assert_true("0 functions cited" in value, msg="value was %s" % value) - assert_true("Halchenko, Y.O." in value, msg="value was %s" % value) - assert_true(value.strip().endswith("Frontiers in Neuroinformatics, 6(22).")) + assert "1 package cited" in value, "value was %s" % value + assert "1 module cited" in value, "value was %s" % value + assert "0 functions cited" in value, "value was %s" % value + assert "Halchenko, Y.O." in value, "value was %s" % value + assert value.strip().endswith("Frontiers in Neuroinformatics, 6(22).") # in this case, we should be citing the package since we are also citing a @@ -311,12 +293,12 @@ strio = StringIO() TextOutput(strio, collector).dump(tags=['*']) value = strio.getvalue() - assert_true("1 package cited" in value, msg="value was %s" % value) - assert_true("1 module cited" in value, msg="value was %s" % value) - assert_true("0 functions cited" in value, msg="value was %s" % value) - assert_true("Halchenko, Y.O." in value, msg="value was %s" % value) - assert_true('[1, 2]' in value, msg="value was %s" %value) - assert_false('[3]' in value, msg="value was %s" %value) + assert "1 package cited" in value, "value was %s" % value + assert "1 module cited" in value, "value was %s" % value + assert "0 functions cited" in value, "value was %s" % value + assert "Halchenko, Y.O." in value, "value was %s" % value + assert '[1, 2]' in value, "value was %s" %value + assert '[3]' not in value, "value was %s" %value def test_text_output_dump_formatting(): @@ -327,8 +309,8 @@ path='mymodule', version='0.0.16') def mymodule(arg1, kwarg2="blah"): """docstring""" - assert_equal(arg1, "magical") - assert_equal(kwarg2, 1) + assert arg1 == "magical" + assert kwarg2 == 1 @due.dcite(BibTeX(_sample_bibtex2), description='solution to life', path='mymodule:myfunction') @@ -342,20 +324,18 @@ strio = StringIO() TextOutput(strio, due).dump(tags=['*']) value = strio.getvalue() - assert_true('0 modules cited' in value, msg='value was {0}'.format(value)) - assert_true('0 functions cited' in value, - msg='value was {0}'.format(value)) + assert '0 modules cited' in value, 'value was {0}'.format(value) + assert '0 functions cited' in value, 'value was {0}'.format(value) # now we call it -- check it prints stuff strio = StringIO() mymodule('magical', kwarg2=1) TextOutput(strio, due).dump(tags=['*']) value = strio.getvalue() - assert_true('1 package cited' in value, msg='value was {0}'.format(value)) - assert_true('1 function cited' in value, msg='value was {0}'.format(value)) - assert_true('(v 0.0.16)' in value, - msg='value was {0}'.format(value)) - assert_equal(len(value.split('\n')), 16, msg='value was {0}'.format(len(value.split('\n')))) + assert '1 package cited' in value, 'value was {0}'.format(value) + assert '1 function cited' in value, 'value was {0}'.format(value) + assert '(v 0.0.16)' in value, 'value was {0}'.format(value) + assert len(value.split('\n')) == 16, 'value was {0}'.format(len(value.split('\n'))) # test we get the reference numbering right samples_bibtex = [_generate_sample_bibtex() for x in range(6)] @@ -366,8 +346,8 @@ path='myothermodule', version='0.0.666') def myothermodule(arg1, kwarg2="blah"): """docstring""" - assert_equal(arg1, "magical") - assert_equal(kwarg2, 1) + assert arg1 == "magical" + assert kwarg2 == 1 @due.dcite(BibTeX(samples_bibtex[1]), description='solution to life', path='myothermodule:myotherfunction') @@ -403,12 +383,12 @@ reference_numbers.append(match_reference.group(1)) references.append(line.replace(match_reference.group(), "")) - assert_equal(set(citation_numbers), set(reference_numbers)) - assert_equal(len(set(references)), len(set(citation_numbers))) - assert_equal(len(citation_numbers), 8) + assert set(citation_numbers) == set(reference_numbers) + assert len(set(references)) == len(set(citation_numbers)) + assert len(citation_numbers) == 8 # verify that we have returned to previous state of filters import warnings - assert_true(('ignore', None, UserWarning, None, 0) not in warnings.filters) + assert ('ignore', None, UserWarning, None, 0) not in warnings.filters def test_bibtex_output(): @@ -423,7 +403,7 @@ strio = StringIO() BibTeXOutput(strio, collector).dump(tags=['*']) value = strio.getvalue() - assert_equal(value, '', msg='Value was {0}'.format(value)) + assert value == '', 'Value was {0}'.format(value) # impose citing collector = DueCreditCollector() @@ -432,7 +412,7 @@ strio = StringIO() BibTeXOutput(strio, collector).dump(tags=['*']) value = strio.getvalue() - assert_equal(value.strip(), _sample_bibtex.strip(), msg='Value was {0}'.format(value)) + assert value.strip() == _sample_bibtex.strip(), 'Value was {0}'.format(value) # impose filtering collector = DueCreditCollector() @@ -442,15 +422,13 @@ strio = StringIO() BibTeXOutput(strio, collector).dump(tags=['edu']) value = strio.getvalue() - assert_equal(value.strip(), _sample_bibtex.strip(), msg='Value was {0}'.format(value)) + assert value.strip() == _sample_bibtex.strip(), 'Value was {0}'.format(value) # no filtering strio = StringIO() BibTeXOutput(strio, collector).dump(tags=['*']) value = strio.getvalue() - assert_equal(value.strip(), - _sample_bibtex.strip() + _sample_bibtex2.rstrip(), - msg='Value was {0}'.format(value)) + assert value.strip() == _sample_bibtex.strip() + _sample_bibtex2.rstrip(), 'Value was {0}'.format(value) # check the we output only unique bibtex entries collector.cite(entry2, path='package') @@ -459,8 +437,10 @@ value = strio.getvalue() value_ = sorted(value.strip().split('\n')) bibtex = sorted((_sample_bibtex.strip() + _sample_bibtex2.rstrip()).split('\n')) - assert_equal(value_, bibtex, - msg='Value was {0}'.format(value_, bibtex)) + assert value_ == bibtex, 'Value was {0}'.format(value_, bibtex) + + # assert_equal(value_, bibtex, + # msg='Value was {0}'.format(value_, bibtex)) def _generate_sample_bibtex(): @@ -491,25 +471,38 @@ return sample_bibtex -@patch('duecredit.io.get_bibtex_rendering') -@patch('duecredit.io.format_bibtex') -def test_get_text_rendering(mock_format_bibtex, mock_get_bibtex_rendering): - # mock get_bibtex_rendering to return the same bibtex entry +def test_get_text_rendering(monkeypatch): + # Patch bibtex_rendering sample_bibtex = BibTeX(_sample_bibtex) - mock_get_bibtex_rendering.return_value = sample_bibtex + + def get_bibtex_rendering(*args, **kwargs): + return sample_bibtex + + monkeypatch.setattr(duecredit.io, 'get_bibtex_rendering', get_bibtex_rendering) + + # Patch format_bibtex + fmt_args = {} + + def format_bibtex(entry, style): + fmt_args["entry"] = entry + fmt_args["style"] = style + + monkeypatch.setattr(duecredit.io, 'format_bibtex', format_bibtex) # test if bibtex type is passed citation_bibtex = Citation(sample_bibtex, path='mypath') bibtex_output = get_text_rendering(citation_bibtex) - mock_format_bibtex.assert_called_with(citation_bibtex.entry, style='harvard1') - mock_format_bibtex.reset_mock() + assert fmt_args["entry"] == citation_bibtex.entry + assert fmt_args["style"] == 'harvard1' + fmt_args.clear() # test if doi type is passed citation_doi = Citation(Doi(_sample_doi), path='mypath') doi_output = get_text_rendering(citation_doi) - mock_format_bibtex.assert_called_with(citation_bibtex.entry, style='harvard1') + assert fmt_args["entry"] == citation_bibtex.entry + assert fmt_args["style"] == 'harvard1' - assert_equal(bibtex_output, doi_output) + assert bibtex_output == doi_output def test_format_bibtex_zenodo_doi(): @@ -533,16 +526,35 @@ year = {2016} } """ - assert_equal(format_bibtex(BibTeX(bibtex_zenodo)), - """Ghosh, S. et al., 2016. nipype: Release candidate 1 for version 0.12.0.""") + assert (format_bibtex(BibTeX(bibtex_zenodo)) == + """Ghosh, S. et al., 2016. nipype: Release candidate 1 for version 0.12.0.""") + + +def test_format_bibtex_with_utf_characters(): + """ + test that we can correctly parse bibtex entry if it contains utf-8 characters + """ + # this was fetched on 2017-08-16 + # replaced Brett with Brótt to have utf-8 characters in first author's name as well + bibtex_utf8 = u"@misc{https://doi.org/10.5281/zenodo.60847,\n doi = {10.5281/zenodo.60847},\n url = {" \ + u"http://zenodo.org/record/60847},\n author = {Brótt, Matthew and Hanke, Michael and Cipollini, " \ + u"Ben and {Marc-Alexandre Côté} and Markiewicz, Chris and Gerhard, Stephan and Larson, " \ + u"Eric and Lee, Gregory R. and Halchenko, Yaroslav and Kastman, Erik and {Cindeem} and Morency, " \ + u"Félix C. and {Moloney} and Millman, Jarrod and Rokem, Ariel and {Jaeilepp} and Gramfort, " \ + u"Alexandre and Bosch, Jasper J.F. Van Den and {Krish Subramaniam} and Nichols, Nolan and {Embaker} " \ + u"and {Bpinsard} and {Chaselgrove} and Oosterhof, Nikolaas N. and St-Jean, Samuel and {Bago " \ + u"Amirbekian} and Nimmo-Smith, Ian and {Satrajit Ghosh}},\n keywords = {},\n title = {nibabel " \ + u"2.0.1},\n publisher = {Zenodo},\n year = {2015}\n} " + assert (format_bibtex(BibTeX(bibtex_utf8)) == u'Brótt, M. et al., 2015. nibabel 2.0.1.') + def test_is_contained(): toppath = 'package' - assert_true(_is_contained(toppath, 'package.module')) - assert_true(_is_contained(toppath, 'package.module.submodule')) - assert_true(_is_contained(toppath, 'package.module.submodule:object')) - assert_true(_is_contained(toppath, 'package:object')) - assert_true(_is_contained(toppath, toppath)) - assert_false(_is_contained(toppath, 'package2')) - assert_false(_is_contained(toppath, 'package2:anotherobject')) - assert_false(_is_contained(toppath, 'package2.module:anotherobject')) + assert _is_contained(toppath, 'package.module') + assert _is_contained(toppath, 'package.module.submodule') + assert _is_contained(toppath, 'package.module.submodule:object') + assert _is_contained(toppath, 'package:object') + assert _is_contained(toppath, toppath) + assert not _is_contained(toppath, 'package2') + assert not _is_contained(toppath, 'package2:anotherobject') + assert not _is_contained(toppath, 'package2.module:anotherobject') diff -Nru duecredit-0.6.0/duecredit/tests/test__main__.py duecredit-0.6.4/duecredit/tests/test__main__.py --- duecredit-0.6.0/duecredit/tests/test__main__.py 2016-06-17 04:01:39.000000000 +0000 +++ duecredit-0.6.4/duecredit/tests/test__main__.py 2018-06-26 16:02:03.000000000 +0000 @@ -7,37 +7,55 @@ # # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -import atexit import sys +import pytest -from mock import patch from six.moves import StringIO -from nose.tools import assert_raises, assert_equal from .. import __main__, __version__ from .. import due -from ..utils import with_tempfile -@patch('sys.stdout', new_callable=StringIO) -def test_main_help(stdout): - assert_raises(SystemExit, __main__.main, ['__main__.py', '--help']) +def test_main_help(monkeypatch): + # Patch stdout + fakestdout = StringIO() + monkeypatch.setattr(sys, "stdout", fakestdout) + + pytest.raises(SystemExit, __main__.main, ['__main__.py', '--help']) assert( - stdout.getvalue().startswith( - "Usage: %s -m duecredit [OPTIONS] [ARGS]\n" % sys.executable - )) - -@patch('sys.stdout', new_callable=StringIO) -def test_main_version(stdout): - assert_raises(SystemExit, __main__.main, ['__main__.py', '--version']) - assert_equal(stdout.getvalue().rstrip(), "duecredit %s" % __version__) - -@patch.object(due, 'activate') -@patch('sys.stdout', new_callable=StringIO) -@with_tempfile(content='print("Running the script")\n'.encode()) -def test_main_run_a_script(stdout, mock_activate, f): - __main__.main(['__main__.py', f]) - assert_equal(stdout.getvalue().rstrip(), "Running the script") - # And we have "activated" the due - mock_activate.assert_called_once_with(True) + fakestdout.getvalue().startswith( + "Usage: %s -m duecredit [OPTIONS] [ARGS]\n" % sys.executable)) + + +def test_main_version(monkeypatch): + # Patch stdout + fakestdout = StringIO() + monkeypatch.setattr(sys, "stdout", fakestdout) + + pytest.raises(SystemExit, __main__.main, ['__main__.py', '--version']) + assert fakestdout.getvalue().rstrip() == "duecredit %s" % __version__ + + +def test_main_run_a_script(tmpdir, monkeypatch): + tempfile = str(tmpdir.mkdir("sub").join("tempfile.txt")) + content = b'print("Running the script")\n' + with open(tempfile, 'wb') as f: + f.write(content) + # Patch stdout + fakestdout = StringIO() + monkeypatch.setattr(sys, "stdout", fakestdout) + + # Patch due.activate + count = [0] + + def count_calls(*args, **kwargs): + count[0] += 1 + + monkeypatch.setattr(due, "activate", count_calls) + + __main__.main(['__main__.py', tempfile]) + assert fakestdout.getvalue().rstrip() == "Running the script" + + # And we have "activated" the due + assert count[0] == 1 diff -Nru duecredit-0.6.0/duecredit/tests/test_utils.py duecredit-0.6.4/duecredit/tests/test_utils.py --- duecredit-0.6.0/duecredit/tests/test_utils.py 2016-06-17 04:01:39.000000000 +0000 +++ duecredit-0.6.4/duecredit/tests/test_utils.py 2018-06-26 16:02:03.000000000 +0000 @@ -9,28 +9,26 @@ # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## import sys -from mock import patch from ..utils import is_interactive -from nose.tools import assert_false, assert_true -def test_is_interactive_crippled_stdout(): - class mocked_out(object): + +def test_is_interactive_crippled_stdout(monkeypatch): + class MockedOut(object): """the one which has no isatty """ def write(self, *args, **kwargs): pass - class mocked_isatty(mocked_out): + class MockedIsaTTY(MockedOut): def isatty(self): return True for inout in ('in', 'out', 'err'): - with patch('sys.std%s' % inout, mocked_out()): - assert_false(is_interactive()) + monkeypatch.setattr(sys, 'std%s' % inout, MockedOut()) + assert not is_interactive() # just for paranoids - with patch('sys.stdin', mocked_isatty()), \ - patch('sys.stdout', mocked_isatty()), \ - patch('sys.stderr', mocked_isatty()): - assert_true(is_interactive()) \ No newline at end of file + for inout in ('in', 'out', 'err'): + monkeypatch.setattr(sys, 'std%s' % inout, MockedIsaTTY()) + assert is_interactive() diff -Nru duecredit-0.6.0/duecredit/tests/test_versions.py duecredit-0.6.4/duecredit/tests/test_versions.py --- duecredit-0.6.0/duecredit/tests/test_versions.py 2016-06-17 04:01:39.000000000 +0000 +++ duecredit-0.6.4/duecredit/tests/test_versions.py 2018-06-26 16:02:03.000000000 +0000 @@ -8,14 +8,11 @@ # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## from os import linesep +import pytest from ..version import __version__ from ..versions import ExternalVersions, StrictVersion -from nose.tools import assert_true, assert_false -from nose.tools import assert_equal, assert_greater_equal, assert_greater -from nose.tools import assert_raises -from nose import SkipTest from six import PY3 if PY3: @@ -23,72 +20,76 @@ def cmp(a, b): return (a > b) - (a < b) + def test_external_versions_basic(): ev = ExternalVersions() - assert_equal(ev._versions, {}) - assert_equal(ev['duecredit'], __version__) + assert ev._versions == {} + assert ev['duecredit'] == __version__ # and it could be compared - assert_greater_equal(ev['duecredit'], __version__) - assert_greater(ev['duecredit'], '0.1') - assert_equal(list(ev.keys()), ['duecredit']) - assert_true('duecredit' in ev) - assert_false('unknown' in ev) + assert ev['duecredit'] >= __version__ + assert ev['duecredit'] > '0.1' + assert list(ev.keys()) == ['duecredit'] + assert 'duecredit' in ev + assert 'unknown' not in ev # StrictVersion might remove training .0 version_str = str(ev['duecredit']) \ if isinstance(ev['duecredit'], StrictVersion) \ else __version__ - assert_equal(ev.dumps(), "Versions: duecredit=%s" % version_str) + assert ev.dumps() == "Versions: duecredit=%s" % version_str # For non-existing one we get None - assert_equal(ev['duecreditnonexisting'], None) + assert ev['duecreditnonexisting'] is None + # and nothing gets added to _versions for nonexisting - assert_equal(set(ev._versions.keys()), {'duecredit'}) + assert set(ev._versions.keys()) == {'duecredit'} # but if it is a module without version, we get it set to UNKNOWN - assert_equal(ev['os'], ev.UNKNOWN) + assert ev['os'] == ev.UNKNOWN # And get a record on that inside - assert_equal(ev._versions.get('os'), ev.UNKNOWN) + assert ev._versions.get('os') == ev.UNKNOWN # And that thing is "True", i.e. present assert(ev['os']) # but not comparable with anything besides itself (was above) - assert_raises(TypeError, cmp, ev['os'], '0') - assert_raises(TypeError, assert_greater, ev['os'], '0') + with pytest.raises(TypeError): + cmp(ev['os'], '0') + + # assert_raises(TypeError, assert_greater, ev['os'], '0') # And we can get versions based on modules themselves from duecredit.tests import mod - assert_equal(ev[mod], mod.__version__) + assert ev[mod] == mod.__version__ # Check that we can get a copy of the versions versions_dict = ev.versions versions_dict['duecredit'] = "0.0.1" - assert_equal(versions_dict['duecredit'], "0.0.1") - assert_equal(ev['duecredit'], __version__) + assert versions_dict['duecredit'] == "0.0.1" + assert ev['duecredit'] == __version__ def test_external_versions_unknown(): - assert_equal(str(ExternalVersions.UNKNOWN), 'UNKNOWN') + assert str(ExternalVersions.UNKNOWN) == 'UNKNOWN' def _test_external(ev, modname): try: - exec ("import %s" % modname, globals(), locals()) + exec("import %s" % modname, globals(), locals()) except ImportError: - raise SkipTest("External %s not present" % modname) + modname = pytest.importorskip(modname) except Exception as e: - raise SkipTest("External %s fails to import: %s" % (modname, e)) - assert (ev[modname] is not ev.UNKNOWN) - assert_greater(ev[modname], '0.0.1') - assert_greater('1000000.0', ev[modname]) # unlikely in our lifetimes + pytest.skip("External %s fails to import: %s" % (modname, e)) + assert ev[modname] is not ev.UNKNOWN + assert ev[modname] > '0.0.1' + assert '1000000.0' > ev[modname] # unlikely in our lifetimes -def test_external_versions_popular_packages(): +@pytest.mark.parametrize("modname", ['scipy', 'numpy', 'mvpa2', 'sklearn', 'statsmodels', + 'pandas', 'matplotlib', 'psychopy']) +def test_external_versions_popular_packages(modname): ev = ExternalVersions() - for modname in ('scipy', 'numpy', 'mvpa2', 'sklearn', 'statsmodels', 'pandas', - 'matplotlib', 'psychopy'): - yield _test_external, ev, modname + _test_external(ev, modname) # more of a smoke test - assert_false(linesep in ev.dumps()) - assert_true(ev.dumps(indent=True).endswith(linesep)) \ No newline at end of file + assert linesep not in ev.dumps() + assert ev.dumps(indent=True).endswith(linesep) diff -Nru duecredit-0.6.0/duecredit/tests/utils.py duecredit-0.6.4/duecredit/tests/utils.py --- duecredit-0.6.0/duecredit/tests/utils.py 2016-06-17 04:01:39.000000000 +0000 +++ duecredit-0.6.4/duecredit/tests/utils.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,14 +0,0 @@ -# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*- -# ex: set sts=4 ts=4 sw=4 noet: -# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the duecredit package for the -# copyright and license terms. -# -# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## - -from nose import SkipTest - - -class KnownFailure(SkipTest): - pass diff -Nru duecredit-0.6.0/duecredit/utils.py duecredit-0.6.4/duecredit/utils.py --- duecredit-0.6.0/duecredit/utils.py 2016-06-17 04:01:39.000000000 +0000 +++ duecredit-0.6.4/duecredit/utils.py 2018-06-26 16:02:03.000000000 +0000 @@ -267,90 +267,15 @@ return tkwargs_ - -@optional_args -def with_tempfile(t, content=None, **tkwargs): - """Decorator function to provide a temporary file name and remove it at the end - - Parameters - ---------- - mkdir : bool, optional (default: False) - If True, temporary directory created using tempfile.mkdtemp() - content : str or bytes, optional - Content to be stored in the file created - `**tkwargs`: - All other arguments are passed into the call to tempfile.mk{,d}temp(), - and resultant temporary filename is passed as the first argument into - the function t. If no 'prefix' argument is provided, it will be - constructed using module and function names ('.' replaced with - '_'). - - To change the used directory without providing keyword argument 'dir' set - DUECREDIT_TESTS_TEMPDIR. - - Examples - -------- - - @with_tempfile - def test_write(tfile): - open(tfile, 'w').write('silly test') - """ - - @wraps(t) - def newfunc(*arg, **kw): - - tkwargs_ = get_tempfile_kwargs(tkwargs, wrapped=t) - - # if DUECREDIT_TESTS_TEMPDIR is set, use that as directory, - # let mktemp handle it otherwise. However, an explicitly provided - # dir=... will override this. - mkdir = tkwargs_.pop('mkdir', False) - - filename = {False: tempfile.mktemp, - True: tempfile.mkdtemp}[mkdir](**tkwargs_) - filename = realpath(filename) - - if content: - with open(filename, 'w' + ('b' if isinstance(content, binary_type) else '')) as f: - f.write(content) - if __debug__: - lgr.debug('Running %s with temporary filename %s', - t.__name__, filename) - try: - return t(*(arg + (filename,)), **kw) - finally: - # glob here for all files with the same name (-suffix) - # would be useful whenever we requested .img filename, - # and function creates .hdr as well - lsuffix = len(tkwargs_.get('suffix', '')) - filename_ = lsuffix and filename[:-lsuffix] or filename - filenames = glob.glob(filename_ + '*') - if len(filename_) < 3 or len(filenames) > 5: - # For paranoid yoh who stepped into this already ones ;-) - lgr.warning("It is unlikely that it was intended to remove all" - " files matching %r. Skipping" % filename_) - return - for f in filenames: - try: - rmtemp(f) - except OSError: - pass - - if tkwargs.get('mkdir', None) and content is not None: - raise ValueError("mkdir=True while providing content makes no sense") - - return newfunc - - # # Context Managers # - # # Additional handlers # -_sys_excepthook = sys.excepthook # Just in case we ever need original one +_sys_excepthook = sys.excepthook # Just in case we ever need original one + def setup_exceptionhook(): """Overloads default sys.excepthook with our exceptionhook handler. diff -Nru duecredit-0.6.0/duecredit/versions.py duecredit-0.6.4/duecredit/versions.py --- duecredit-0.6.0/duecredit/versions.py 2016-06-17 04:01:39.000000000 +0000 +++ duecredit-0.6.4/duecredit/versions.py 2018-06-26 16:02:03.000000000 +0000 @@ -66,7 +66,6 @@ def __getitem__(self, module): # when ran straight in its source code -- fails to discover nipy's version.. TODO #if module == 'nipy': - # import pdb; pdb.set_trace() if not isinstance(module, string_types): modname = module.__name__ else: Binary files /tmp/tmpkcD_N3/Gw5hvErVmv/duecredit-0.6.0/examples/duecredit_example.gif and /tmp/tmpkcD_N3/TWnByX9qxy/duecredit-0.6.4/examples/duecredit_example.gif differ diff -Nru duecredit-0.6.0/.gitignore duecredit-0.6.4/.gitignore --- duecredit-0.6.0/.gitignore 2016-06-17 04:01:39.000000000 +0000 +++ duecredit-0.6.4/.gitignore 2018-06-26 16:02:03.000000000 +0000 @@ -9,3 +9,4 @@ .duecredit.p .coverage *.egg-info +.idea diff -Nru duecredit-0.6.0/.mailmap duecredit-0.6.4/.mailmap --- duecredit-0.6.0/.mailmap 1970-01-01 00:00:00.000000000 +0000 +++ duecredit-0.6.4/.mailmap 2018-06-26 16:02:03.000000000 +0000 @@ -0,0 +1 @@ +Matteo Visconti dOC Matteo Visconti di Oleggio Castello diff -Nru duecredit-0.6.0/README.md duecredit-0.6.4/README.md --- duecredit-0.6.0/README.md 2016-06-17 04:01:39.000000000 +0000 +++ duecredit-0.6.4/README.md 2018-06-26 16:02:03.000000000 +0000 @@ -1,10 +1,9 @@ -duecredit -========= +# duecredit + [![Build Status](https://travis-ci.org/duecredit/duecredit.svg?branch=master)](https://travis-ci.org/duecredit/duecredit) [![Coverage Status](https://coveralls.io/repos/duecredit/duecredit/badge.svg)](https://coveralls.io/r/duecredit/duecredit) - duecredit is being conceived to address the problem of inadequate citation of scientific software and methods, and limited visibility of donation requests for open-source software. @@ -16,20 +15,41 @@ functionality will be presented back if software provides multiple citeable implementations. -duecredit 101 -============= +## Installation + +Duecredit is easy to install via pip, simply type: + + `pip install duecredit` + +## Examples + +### To cite the modules and methods you are using You can already start "registering" citations using duecredit in your Python modules and even registering citations (we call this approach "injections") for modules which do not (yet) use duecredit. duecredit will remain an optional dependency, i.e. your software will work correctly even without duecredit installed. -"Native" use of duecredit (recommended) ---------------------------------------- +For example, list citations of the modules and methods `yourproject` uses with few simple commands: +```bash +cd /path/to/yourmodule # for ~/yourproject +cd yourproject # change directory into where the main code base is +python -m duecredit yourproject.py +``` +Or you can also display them in BibTex format, using: +```bash +duecredit summary --format=bibtex +``` +See this gif animation for better illustration: +![Example](examples/duecredit_example.gif) + + +### To let others cite your software + For using duecredit in your software -1. copy `duecredit/stub.py` to your codebase, e.g. +1. Copy `duecredit/stub.py` to your codebase, e.g. wget -q -O /path/tomodule/yourmodule/due.py \ https://raw.githubusercontent.com/duecredit/duecredit/master/duecredit/stub.py @@ -42,16 +62,18 @@ from .due import due, Doi, BibTeX - to provide reference for the entire module just use e.g. + To provide reference for the entire module just use e.g. due.cite(Doi("1.2.3/x.y.z"), description="Solves all your problems", path="magicpy") - To provide a reference for a function or a method, use dcite decorator + To provide a reference for a function or a method, use `dcite` decorator @due.dcite(Doi("1.2.3/x.y.z"), description="Resolves constipation issue") def pushit(): ... + You can easily obtain DOI for your software using Zenodo.org and few other DOI providers. + References can also be entered as BibTeX entries due.cite(BibTeX(""" @@ -63,26 +85,30 @@ """), description="Solves all your problems", path="magicpy") +## Now what -Add injections for other existing modules ------------------------------------------ +### Do the due -We hope that eventually this somewhat cruel approach will not be necessary. But +Once you obtained the references in the duecredit output, include them in in the references section of your paper or software, which used the cited software. + +### Add injections for other existing modules + +We hope that eventually this somewhat cruel approach will not be necessary. But until other packages support duecredit "natively" we have provided a way to "inject" -citations for modules and/or functions and methods via injections: citations will be +citations for modules and/or functions and methods via injections: citations will be added to the corresponding functionality upon those modules import. All injections are collected under [duecredit/injections](https://github.com/duecredit/duecredit/tree/master/duecredit/injections). -See any file there with `mod_` prefix for a complete example. But +See any file there with `mod_` prefix for a complete example. But overall it is just a regular Python module defining a function `inject(injector)` which will then add new entries to the injector, which will in turn add those entries to the duecredit whenever the corresponding module gets imported. -User-view ---------- +## User-view + By default `duecredit` does exactly nothing -- all decorators do not decorate, all `cite` functions just return, so there should be no fear @@ -151,7 +177,7 @@ and if by default only references for "implementation" are listed, we can enable listing of references for other tags as well (e.g. "edu" -depicting instructional materials -- textbooks etc on the topic): +depicting instructional materials -- textbooks etc. on the topic): $> DUECREDIT_REPORT_TAGS=* duecredit summary @@ -207,12 +233,35 @@ [3] Sneath, P.H. & Sokal, R.R., 1962. Numerical taxonomy. Nature, 193(4818), pp.855–860. ... +## Tags + + +You are welcome to introduce new tags specific for your citations but we hope +that for consistency across projects, you would use following tags + +- `implementation` (default) — an implementation of the cited method +- `reference-implementation` — the original implementation (ideally by + the authors of the paper) of the cited method +- `another-implementation` — some other implementation of + the method, e.g. if you would like to provide citation for another + implementation of the method you have implemented in your code and for + which you have already provided `implementation` or + `reference-implementation` tag +- `use` — publications demonstrating a worthwhile noting use of the + method +- `edu` — tutorials, textbooks and other materials useful to learn + more about cited functionality +- `donate` — should be commonly used with Url entries to point to the + websites describing how to contribute some funds to the referenced + project +- `funding` — to point to the sources of funding which provided support + for a given functionality implementation and/or method development +- `dataset` - for datasets -Ultimate goals -============== +## Ultimate goals -Reduce demand for prima ballerina projects ------------------------------------------- + +### Reduce demand for prima ballerina projects **Problem**: Scientific software is often developed to gain citations for original publication through the use of the software implementing it. @@ -232,8 +281,7 @@ many (if not all) core problems with scientific software development everyone likes to bash about (reproducibility, longevity, etc.). -Adequately reference core libraries ------------------------------------ +### Adequately reference core libraries **Problem**: Scientific software often, if not always, uses 3rd party libraries (e.g., NumPy, SciPy, atlas) which might not even be visible @@ -252,11 +300,30 @@ audiences without proliferation of the low quality scientific software. -Similar/related projects -======================== +## Similar/related projects [sempervirens](https://github.com/njsmith/sempervirens) -- *an experimental prototype for gathering anonymous, opt-in usage data for open scientific software*. Eventually in duecredit we aim either to provide similar functionality (since we are collecting such information as well) or just interface/report to sempervirens. + +## Currently used by + +This is a running list of projects that use DueCredit natively. If you +are using DueCredit, or plan to use it, please consider sending a pull +request and add your project to this list. Thanks to +[@fedorov](https://github.com/fedorov) for the idea. + +- [PyMVPA](http://www.pymvpa.org) +- [fatiando](https://github.com/fatiando/fatiando) +- [Nipype](https://github.com/nipy/nipype) +- [QInfer](https://github.com/QInfer/python-qinfer) +- [shablona](https://github.com/uwescience/shablona) +- [gfusion](https://github.com/mvdoc/gfusion) +- [pybids](https://github.com/INCF/pybids) +- [Quickshear](https://github.com/nipy/quickshear) +- [meqc](https://github.com/emdupre/meqc) +- [MDAnalysis](https://www.mdanalysis.org) + +Last updated 2017-06-27. diff -Nru duecredit-0.6.0/setup.py duecredit-0.6.4/setup.py --- duecredit-0.6.0/setup.py 2016-06-17 04:01:39.000000000 +0000 +++ duecredit-0.6.4/setup.py 2018-06-26 16:02:03.000000000 +0000 @@ -66,8 +66,16 @@ __version__ = '0.0.0.dev' print("Version: %s" % __version__) -with open('README.md') as file: - README = file.read() +# In some environments with too basic locale settings +# it might not be able to read the file with unicode, so we +# would then just ignore the errors +with open('README.md', 'rb') as f: + README = f.read() + # We need to decode it reliably + try: + README = README.decode() + except UnicodeDecodeError: + README = README.decode('ascii', errors='replace') def find_packages(path, prefix): yield prefix @@ -82,17 +90,15 @@ version=__version__, packages=list(find_packages([PACKAGE_ABSPATH], PACKAGE)), scripts=[], - install_requires=['requests', 'citeproc-py', 'six'], + install_requires=['requests', 'citeproc-py>=0.4', 'six'], extras_require={ 'tests': [ - 'mock', - 'nose>=1.3.4', + 'pytest', 'vcrpy', 'contextlib2' ] }, include_package_data=True, provides=[PACKAGE], - #test_suite='nose.collector', entry_points={ 'console_scripts': [ 'duecredit=duecredit.cmdline.main:main', diff -Nru duecredit-0.6.0/tox.ini duecredit-0.6.4/tox.ini --- duecredit-0.6.0/tox.ini 2016-06-17 04:01:39.000000000 +0000 +++ duecredit-0.6.4/tox.ini 2018-06-26 16:02:03.000000000 +0000 @@ -3,11 +3,11 @@ #,flake8 [testenv] -commands = nosetests {posargs} +commands = py.test deps = -r{toxinidir}/requirements.txt [testenv:cover] -commands = nosetests --with-coverage {posargs} +commands = coverage run --source duecredit -m py.test [testenv:flake8] commands = flake8 {posargs} diff -Nru duecredit-0.6.0/.travis.yml duecredit-0.6.4/.travis.yml --- duecredit-0.6.0/.travis.yml 2016-06-17 04:01:39.000000000 +0000 +++ duecredit-0.6.4/.travis.yml 2018-06-26 16:02:03.000000000 +0000 @@ -23,10 +23,11 @@ - travis_retry pip install -q coveralls codecov - python setup.py --help # just to trigger generation of .version - pip install -e '.[tests]' - - pip install flake8 + - pip install --upgrade flake8 pytest script: - - nosetests --with-doctest --with-cov --cover-package duecredit --logging-level=INFO -v + #- nosetests --with-doctest --with-cov --cover-package duecredit --logging-level=INFO -v + - coverage run --source duecredit -m py.test - python setup.py install # test installation # for now flaking only the stub.py - flake8 duecredit/stub.py