diff --git a/pytest.ini b/pytest.ini index 9fa2d75..b15e082 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,5 +1,6 @@ [pytest] addopts = --doctest-modules -p no:pytest_swh_core norecursedirs = docs .* markers = fs: tests that involve filesystem ios + requires_optional_deps: tests in test_cli.py that should not run if optional dependencies are not installed diff --git a/requirements-test.txt b/requirements-test.txt index f906d8a..984743d 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -1,4 +1,3 @@ Click -dulwich pytest pytz diff --git a/setup.py b/setup.py index 8f9d32f..a44dcbf 100755 --- a/setup.py +++ b/setup.py @@ -1,79 +1,80 @@ #!/usr/bin/env python3 # Copyright (C) 2015-2020 The Software Heritage developers # See the AUTHORS file at the top-level directory of this distribution # License: GNU General Public License version 3, or any later version # See top-level LICENSE file for more information from io import open from os import path from setuptools import find_packages, setup here = path.abspath(path.dirname(__file__)) # Get the long description from the README file with open(path.join(here, "README.md"), encoding="utf-8") as f: long_description = f.read() def parse_requirements(name=None): if name: reqf = "requirements-%s.txt" % name else: reqf = "requirements.txt" requirements = [] if not path.exists(reqf): return requirements with open(reqf) as f: for line in f.readlines(): line = line.strip() if not line or line.startswith("#"): continue requirements.append(line) return requirements blake2_requirements = ['pyblake2;python_version<"3.6"'] setup( name="swh.model", description="Software Heritage data model", long_description=long_description, long_description_content_type="text/markdown", python_requires=">=3.7", author="Software Heritage developers", author_email="swh-devel@inria.fr", url="https://forge.softwareheritage.org/diffusion/DMOD/", packages=find_packages(), setup_requires=["setuptools-scm"], use_scm_version=True, install_requires=( parse_requirements() + parse_requirements("swh") + blake2_requirements ), extras_require={ "cli": parse_requirements("cli"), + "testing-minimal": parse_requirements("test"), "testing": parse_requirements("test") + parse_requirements("cli"), }, include_package_data=True, entry_points=""" [console_scripts] swh-identify=swh.model.cli:identify [swh.cli.subcommands] identify=swh.model.cli """, classifiers=[ "Programming Language :: Python :: 3", "Intended Audience :: Developers", "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", "Operating System :: OS Independent", "Development Status :: 5 - Production/Stable", ], project_urls={ "Bug Reports": "https://forge.softwareheritage.org/maniphest", "Funding": "https://www.softwareheritage.org/donate", "Source": "https://forge.softwareheritage.org/source/swh-model", "Documentation": "https://docs.softwareheritage.org/devel/swh-model/", }, ) diff --git a/swh/model/tests/test_cli.py b/swh/model/tests/test_cli.py index 9a00660..de0de48 100644 --- a/swh/model/tests/test_cli.py +++ b/swh/model/tests/test_cli.py @@ -1,164 +1,179 @@ # Copyright (C) 2018-2019 The Software Heritage developers # See the AUTHORS file at the top-level directory of this distribution # License: GNU General Public License version 3, or any later version # See top-level LICENSE file for more information import os +import sys import tarfile import tempfile import unittest +import unittest.mock from click.testing import CliRunner import pytest from swh.model import cli from swh.model.hashutil import hash_to_hex from swh.model.tests.test_from_disk import DataMixin @pytest.mark.fs class TestIdentify(DataMixin, unittest.TestCase): def setUp(self): super().setUp() self.runner = CliRunner() def assertSWHID(self, result, swhid): self.assertEqual(result.exit_code, 0, result.output) self.assertEqual(result.output.split()[0], swhid) def test_no_args(self): result = self.runner.invoke(cli.identify) self.assertNotEqual(result.exit_code, 0) def test_content_id(self): """identify file content""" self.make_contents(self.tmpdir_name) for filename, content in self.contents.items(): path = os.path.join(self.tmpdir_name, filename) result = self.runner.invoke(cli.identify, ["--type", "content", path]) self.assertSWHID(result, "swh:1:cnt:" + hash_to_hex(content["sha1_git"])) def test_content_id_from_stdin(self): """identify file content""" self.make_contents(self.tmpdir_name) for _, content in self.contents.items(): result = self.runner.invoke(cli.identify, ["-"], input=content["data"]) self.assertSWHID(result, "swh:1:cnt:" + hash_to_hex(content["sha1_git"])) def test_directory_id(self): """identify an entire directory""" self.make_from_tarball(self.tmpdir_name) path = os.path.join(self.tmpdir_name, b"sample-folder") result = self.runner.invoke(cli.identify, ["--type", "directory", path]) self.assertSWHID(result, "swh:1:dir:e8b0f1466af8608c8a3fb9879db172b887e80759") + @pytest.mark.requires_optional_deps def test_snapshot_id(self): """identify a snapshot""" tarball = os.path.join( os.path.dirname(__file__), "data", "repos", "sample-repo.tgz" ) with tempfile.TemporaryDirectory(prefix="swh.model.cli") as d: with tarfile.open(tarball, "r:gz") as t: t.extractall(d) repo_dir = os.path.join(d, "sample-repo") result = self.runner.invoke( cli.identify, ["--type", "snapshot", repo_dir] ) self.assertSWHID( result, "swh:1:snp:abc888898124270905a0ef3c67e872ce08e7e0c1" ) + def test_snapshot_without_dulwich(self): + """checks swh-identify returns a 'nice' message instead of a traceback + when dulwich is not installed""" + with unittest.mock.patch.dict(sys.modules, {"dulwich": None}): + with tempfile.TemporaryDirectory(prefix="swh.model.cli") as d: + result = self.runner.invoke( + cli.identify, ["--type", "snapshot", d], catch_exceptions=False, + ) + + assert result.exit_code == 1 + assert "'swh.model[cli]'" in result.output + def test_origin_id(self): """identify an origin URL""" url = "https://github.com/torvalds/linux" result = self.runner.invoke(cli.identify, ["--type", "origin", url]) self.assertSWHID(result, "swh:1:ori:b63a575fe3faab7692c9f38fb09d4bb45651bb0f") def test_symlink(self): """identify symlink --- both itself and target""" regular = os.path.join(self.tmpdir_name, b"foo.txt") link = os.path.join(self.tmpdir_name, b"bar.txt") open(regular, "w").write("foo\n") os.symlink(os.path.basename(regular), link) result = self.runner.invoke(cli.identify, [link]) self.assertSWHID(result, "swh:1:cnt:257cc5642cb1a054f08cc83f2d943e56fd3ebe99") result = self.runner.invoke(cli.identify, ["--no-dereference", link]) self.assertSWHID(result, "swh:1:cnt:996f1789ff67c0e3f69ef5933a55d54c5d0e9954") def test_show_filename(self): """filename is shown by default""" self.make_contents(self.tmpdir_name) for filename, content in self.contents.items(): path = os.path.join(self.tmpdir_name, filename) result = self.runner.invoke(cli.identify, ["--type", "content", path]) self.assertEqual(result.exit_code, 0) self.assertEqual( result.output.rstrip(), "swh:1:cnt:%s\t%s" % (hash_to_hex(content["sha1_git"]), path.decode()), ) def test_hide_filename(self): """filename is hidden upon request""" self.make_contents(self.tmpdir_name) for filename, content in self.contents.items(): path = os.path.join(self.tmpdir_name, filename) result = self.runner.invoke( cli.identify, ["--type", "content", "--no-filename", path] ) self.assertSWHID(result, "swh:1:cnt:" + hash_to_hex(content["sha1_git"])) def test_auto_content(self): """automatic object type detection: content""" with tempfile.NamedTemporaryFile(prefix="swh.model.cli") as f: result = self.runner.invoke(cli.identify, [f.name]) self.assertEqual(result.exit_code, 0) self.assertRegex(result.output, r"^swh:\d+:cnt:") def test_auto_directory(self): """automatic object type detection: directory""" with tempfile.TemporaryDirectory(prefix="swh.model.cli") as dirname: result = self.runner.invoke(cli.identify, [dirname]) self.assertEqual(result.exit_code, 0) self.assertRegex(result.output, r"^swh:\d+:dir:") def test_auto_origin(self): """automatic object type detection: origin""" result = self.runner.invoke(cli.identify, ["https://github.com/torvalds/linux"]) self.assertEqual(result.exit_code, 0, result.output) self.assertRegex(result.output, r"^swh:\d+:ori:") def test_verify_content(self): """identifier verification""" self.make_contents(self.tmpdir_name) for filename, content in self.contents.items(): expected_id = "swh:1:cnt:" + hash_to_hex(content["sha1_git"]) # match path = os.path.join(self.tmpdir_name, filename) result = self.runner.invoke(cli.identify, ["--verify", expected_id, path]) self.assertEqual(result.exit_code, 0, result.output) # mismatch with open(path, "a") as f: f.write("trailing garbage to make verification fail") result = self.runner.invoke(cli.identify, ["--verify", expected_id, path]) self.assertEqual(result.exit_code, 1) def test_exclude(self): """exclude patterns""" self.make_from_tarball(self.tmpdir_name) path = os.path.join(self.tmpdir_name, b"sample-folder") excluded_dir = os.path.join(path, b"excluded_dir\x96") os.mkdir(excluded_dir) with open(os.path.join(excluded_dir, b"some_file"), "w") as f: f.write("content") result = self.runner.invoke( cli.identify, ["--type", "directory", "--exclude", "excluded_*", path] ) self.assertSWHID(result, "swh:1:dir:e8b0f1466af8608c8a3fb9879db172b887e80759") diff --git a/tox.ini b/tox.ini index 1ea702f..25baa11 100644 --- a/tox.ini +++ b/tox.ini @@ -1,44 +1,44 @@ [tox] -envlist=black,flake8,mypy,py3,identify +envlist=black,flake8,mypy,py3-{minimal,full} [testenv] extras = - testing + full: testing + minimal: testing-minimal deps = pytest-cov commands = pytest --cov={envsitepackagesdir}/swh/model \ --doctest-modules \ - {envsitepackagesdir}/swh/model \ - --cov-branch {posargs} + full: {envsitepackagesdir}/swh/model \ + minimal: {envsitepackagesdir}/swh/model/tests/test_cli.py -m 'not requires_optional_deps' \ + --cov-branch {posargs} -[testenv:identify] -# no 'extras = testing', as it would install swh-core; -# and this test is designed to check 'swh-identify' does not depend on swh-core. -extras = -deps = - -r requirements-test.txt +[testenv:py3] +skip_install = true +deps = tox commands = - pytest {envsitepackagesdir}/swh/model/tests/test_cli.py + tox -e py3-full -- {posargs} + tox -e py3-minimal -- {posargs} [testenv:black] skip_install = true deps = black==19.10b0 commands = {envpython} -m black --check swh [testenv:flake8] skip_install = true deps = flake8 commands = {envpython} -m flake8 [testenv:mypy] extras = testing deps = mypy commands = mypy swh