aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorwbond <will@wbond.net>2019-09-09 01:53:00 -0400
committerwbond <will@wbond.net>2019-09-13 06:41:24 -0400
commitbba5d621c393b86e6fb0838e631ccea252852189 (patch)
tree1e700c6b17079456926febf5f646911828d685c7
parent35d686750d96fddcdef6c44d7514dde71dfb0ca0 (diff)
downloadasn1crypto-bba5d621c393b86e6fb0838e631ccea252852189.tar.gz
Create asn1crypto_tests package, along with supporting tooling
Adds the following tasks: - python run.py build - python run.py version {pep440_version} Tests may now be executed a number of different ways and will automatically ensure the local copy of asn1crypto is used, if run from a Git working copy, or archive of a working copy. Versioning scheme switched from SemVer to PEP 440 since that is what the Python ecosystem tooling supports.
-rw-r--r--MANIFEST.in3
-rw-r--r--asn1crypto/version.py4
-rw-r--r--dev/__init__.py3
-rw-r--r--dev/_import.py93
-rw-r--r--dev/build.py89
-rw-r--r--dev/ci.py23
-rw-r--r--dev/deps.py84
-rw-r--r--dev/release.py19
-rw-r--r--dev/tests.py58
-rw-r--r--dev/version.py80
-rw-r--r--readme.md38
-rw-r--r--requires/release4
-rw-r--r--run.py16
-rw-r--r--setup.cfg5
-rw-r--r--setup.py97
-rw-r--r--tests/LICENSE19
-rw-r--r--tests/__init__.py61
-rw-r--r--tests/__main__.py14
-rw-r--r--tests/readme.md9
-rw-r--r--tests/setup.py153
20 files changed, 764 insertions, 108 deletions
diff --git a/MANIFEST.in b/MANIFEST.in
deleted file mode 100644
index 40e672e..0000000
--- a/MANIFEST.in
+++ /dev/null
@@ -1,3 +0,0 @@
-include LICENSE
-include readme.md changelog.md
-recursive-include docs *.md
diff --git a/asn1crypto/version.py b/asn1crypto/version.py
index 1874cfe..aa36a93 100644
--- a/asn1crypto/version.py
+++ b/asn1crypto/version.py
@@ -2,5 +2,5 @@
from __future__ import unicode_literals, division, absolute_import, print_function
-__version__ = '0.25.0-alpha'
-__version_info__ = (0, 25, 0, 'alpha')
+__version__ = '0.25.0.dev1'
+__version_info__ = (0, 25, 0, 'dev1')
diff --git a/dev/__init__.py b/dev/__init__.py
index 403922e..02e9c6c 100644
--- a/dev/__init__.py
+++ b/dev/__init__.py
@@ -15,6 +15,9 @@ other_packages = [
"ocspbuilder"
]
+requires_oscrypto = False
+has_tests_package = True
+
package_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
build_root = os.path.abspath(os.path.join(package_root, '..'))
diff --git a/dev/_import.py b/dev/_import.py
new file mode 100644
index 0000000..2599588
--- /dev/null
+++ b/dev/_import.py
@@ -0,0 +1,93 @@
+# coding: utf-8
+from __future__ import unicode_literals, division, absolute_import, print_function
+
+import imp
+import sys
+import os
+
+from . import build_root
+
+
+def _import_from(mod, path, mod_dir=None):
+ """
+ Imports a module from a specific path
+
+ :param mod:
+ A unicode string of the module name
+
+ :param path:
+ A unicode string to the directory containing the module
+
+ :param mod_dir:
+ If the sub directory of "path" is different than the "mod" name,
+ pass the sub directory as a unicode string
+
+ :return:
+ None if not loaded, otherwise the module
+ """
+
+ if mod_dir is None:
+ mod_dir = mod
+
+ if not os.path.exists(path):
+ return None
+
+ if not os.path.exists(os.path.join(path, mod_dir)):
+ return None
+
+ try:
+ mod_info = imp.find_module(mod_dir, [path])
+ return imp.load_module(mod, *mod_info)
+ except ImportError:
+ return None
+
+
+def _preload(require_oscrypto, print_info):
+ """
+ Preloads asn1crypto and optionally oscrypto from a local source checkout,
+ or from a normal install
+
+ :param require_oscrypto:
+ A bool if oscrypto needs to be preloaded
+
+ :param print_info:
+ A bool if info about asn1crypto and oscrypto should be printed
+ """
+
+ if print_info:
+ print('Python ' + sys.version.replace('\n', ''))
+
+ asn1crypto = None
+ oscrypto = None
+
+ if require_oscrypto:
+ oscrypto_dir = os.path.join(build_root, 'oscrypto')
+ oscrypto_tests = None
+ if os.path.exists(oscrypto_dir):
+ oscrypto_tests = _import_from('oscrypto_tests', oscrypto_dir, 'tests')
+ if oscrypto_tests is None:
+ import oscrypto_tests
+ asn1crypto, oscrypto = oscrypto_tests.local_oscrypto()
+
+ else:
+ asn1crypto_dir = os.path.join(build_root, 'asn1crypto')
+ if os.path.exists(asn1crypto_dir):
+ asn1crypto = _import_from('asn1crypto', asn1crypto_dir)
+ if asn1crypto is None:
+ import asn1crypto
+
+ if print_info:
+ print(
+ '\nasn1crypto: %s, %s' % (
+ asn1crypto.__version__,
+ os.path.dirname(asn1crypto.__file__)
+ )
+ )
+ if require_oscrypto:
+ print(
+ 'oscrypto: %s backend, %s, %s' % (
+ oscrypto.backend(),
+ oscrypto.__version__,
+ os.path.dirname(oscrypto.__file__)
+ )
+ )
diff --git a/dev/build.py b/dev/build.py
new file mode 100644
index 0000000..4899594
--- /dev/null
+++ b/dev/build.py
@@ -0,0 +1,89 @@
+# coding: utf-8
+from __future__ import unicode_literals, division, absolute_import, print_function
+
+import imp
+import os
+import tarfile
+import zipfile
+
+import setuptools.sandbox
+
+from . import package_root, package_name, has_tests_package
+
+
+def _list_zip(filename):
+ """
+ Prints all of the files in a .zip file
+ """
+
+ zf = zipfile.ZipFile(filename, 'r')
+ for name in zf.namelist():
+ print(' %s' % name)
+
+
+def _list_tgz(filename):
+ """
+ Prints all of the files in a .tar.gz file
+ """
+
+ tf = tarfile.open(filename, 'r:gz')
+ for name in tf.getnames():
+ print(' %s' % name)
+
+
+def run():
+ """
+ Creates a sdist .tar.gz and a bdist_wheel --univeral .whl
+
+ :return:
+ A bool - if the packaging process was successful
+ """
+
+ setup = os.path.join(package_root, 'setup.py')
+ tests_root = os.path.join(package_root, 'tests')
+ tests_setup = os.path.join(tests_root, 'setup.py')
+
+ # Trying to call setuptools.sandbox.run_setup(setup, ['--version'])
+ # resulted in a segfault, so we do this instead
+ module_info = imp.find_module('version', [os.path.join(package_root, package_name)])
+ version_mod = imp.load_module('%s.version' % package_name, *module_info)
+
+ pkg_name_info = (package_name, version_mod.__version__)
+ print('Building %s-%s' % pkg_name_info)
+
+ sdist = '%s-%s.tar.gz' % pkg_name_info
+ whl = '%s-%s-py2.py3-none-any.whl' % pkg_name_info
+ setuptools.sandbox.run_setup(setup, ['-q', 'sdist'])
+ print(' - created %s' % sdist)
+ _list_tgz(os.path.join(package_root, 'dist', sdist))
+ setuptools.sandbox.run_setup(setup, ['-q', 'bdist_wheel', '--universal'])
+ print(' - created %s' % whl)
+ _list_zip(os.path.join(package_root, 'dist', whl))
+ setuptools.sandbox.run_setup(setup, ['-q', 'clean'])
+
+ if has_tests_package:
+ print('Building %s_tests-%s' % (package_name, version_mod.__version__))
+
+ tests_sdist = '%s_tests-%s.tar.gz' % pkg_name_info
+ tests_whl = '%s_tests-%s-py2.py3-none-any.whl' % pkg_name_info
+ setuptools.sandbox.run_setup(tests_setup, ['-q', 'sdist'])
+ print(' - created %s' % tests_sdist)
+ _list_tgz(os.path.join(tests_root, 'dist', tests_sdist))
+ setuptools.sandbox.run_setup(tests_setup, ['-q', 'bdist_wheel', '--universal'])
+ print(' - created %s' % tests_whl)
+ _list_zip(os.path.join(tests_root, 'dist', tests_whl))
+ setuptools.sandbox.run_setup(tests_setup, ['-q', 'clean'])
+
+ dist_dir = os.path.join(package_root, 'dist')
+ tests_dist_dir = os.path.join(tests_root, 'dist')
+ os.rename(
+ os.path.join(tests_dist_dir, tests_sdist),
+ os.path.join(dist_dir, tests_sdist)
+ )
+ os.rename(
+ os.path.join(tests_dist_dir, tests_whl),
+ os.path.join(dist_dir, tests_whl)
+ )
+ os.rmdir(tests_dist_dir)
+
+ return True
diff --git a/dev/ci.py b/dev/ci.py
index c819696..a3c91e9 100644
--- a/dev/ci.py
+++ b/dev/ci.py
@@ -3,9 +3,9 @@ from __future__ import unicode_literals, division, absolute_import, print_functi
import sys
import os
-import imp
-from . import build_root
+from . import build_root, requires_oscrypto
+from ._import import _preload
deps_dir = os.path.join(build_root, 'modularcrypto-deps')
@@ -34,24 +34,7 @@ def run():
A bool - if the linter and tests ran successfully
"""
- print('Python ' + sys.version.replace('\n', ''))
-
- oscrypto_tests_module_info = imp.find_module('tests', [os.path.join(build_root, 'oscrypto')])
- oscrypto_tests = imp.load_module('oscrypto.tests', *oscrypto_tests_module_info)
- asn1crypto, oscrypto = oscrypto_tests.local_oscrypto()
- print(
- '\nasn1crypto: %s, %s' % (
- asn1crypto.__version__,
- os.path.dirname(asn1crypto.__file__)
- )
- )
- print(
- 'oscrypto: %s backend, %s, %s' % (
- oscrypto.backend(),
- oscrypto.__version__,
- os.path.dirname(oscrypto.__file__)
- )
- )
+ _preload(requires_oscrypto, True)
if run_lint:
print('')
diff --git a/dev/deps.py b/dev/deps.py
index d995c55..d4865cf 100644
--- a/dev/deps.py
+++ b/dev/deps.py
@@ -205,7 +205,7 @@ def _extract_info(archive, info):
return None
-def _extract_package(deps_dir, pkg_path):
+def _extract_package(deps_dir, pkg_path, pkg_dir):
"""
Extract a .whl, .zip, .tar.gz or .tar.bz2 into a package path to
use when running CI tasks
@@ -215,6 +215,9 @@ def _extract_package(deps_dir, pkg_path):
:param pkg_path:
A unicode string of the path to the archive
+
+ :param pkg_dir:
+ If running setup.py, change to this dir first - a unicode string
"""
if pkg_path.endswith('.exe'):
@@ -249,51 +252,61 @@ def _extract_package(deps_dir, pkg_path):
zf.close()
return
- # Source archives may contain a bunch of other things.
- # The following code works for the packages coverage and
- # configparser, which are the two we currently require that
- # do not provide wheels
+ # Source archives may contain a bunch of other things, including mutliple
+ # packages, so we must use setup.py/setuptool to install/extract it
+ ar = None
+ staging_dir = os.path.join(deps_dir, '_staging')
try:
- ar = None
ar = _open_archive(pkg_path)
- pkg_name = None
- base_path = _archive_single_dir(ar) or ''
- if len(base_path):
- if '-' in base_path:
- pkg_name, _ = base_path.split('-', 1)
- base_path += '/'
-
- base_pkg_path = None
- if pkg_name is not None:
- base_pkg_path = base_path + pkg_name + '/'
- src_path = base_path + 'src/'
+ common_root = _archive_single_dir(ar)
members = []
for info in _list_archive_members(ar):
- fn = _info_name(info)
- if base_pkg_path is not None and fn.startswith(base_pkg_path):
- dst_path = fn[len(base_pkg_path) - len(pkg_name) - 1:]
- members.append((info, dst_path))
- continue
- if fn.startswith(src_path):
- members.append((info, fn[len(src_path):]))
- continue
+ dst_rel_path = _info_name(info)
+ if common_root is not None:
+ dst_rel_path = dst_rel_path[len(common_root) + 1:]
+ members.append((info, dst_rel_path))
+
+ if not os.path.exists(staging_dir):
+ os.makedirs(staging_dir)
- for info, path in members:
+ for info, rel_path in members:
info_data = _extract_info(ar, info)
# Dirs won't return a file
if info_data is not None:
- dst_path = os.path.join(deps_dir, path)
+ dst_path = os.path.join(staging_dir, rel_path)
dst_dir = os.path.dirname(dst_path)
if not os.path.exists(dst_dir):
os.makedirs(dst_dir)
with open(dst_path, 'wb') as f:
f.write(info_data)
+
+ setup_dir = staging_dir
+ if pkg_dir:
+ setup_dir = os.path.join(staging_dir, pkg_dir)
+
+ root = os.path.abspath(os.path.join(deps_dir, '..'))
+ install_lib = os.path.basename(deps_dir)
+
+ _execute(
+ [
+ 'python',
+ 'setup.py',
+ 'install',
+ '--root=%s' % root,
+ '--install-lib=%s' % install_lib,
+ '--no-compile'
+ ],
+ setup_dir
+ )
+
finally:
if ar:
ar.close()
+ if staging_dir:
+ shutil.rmtree(staging_dir)
def _stage_requirements(deps_dir, path):
@@ -306,7 +319,7 @@ def _stage_requirements(deps_dir, path):
A unicode path to a temporary diretory to use for downloads
:param path:
- A unicoe filesystem path to a requirements file
+ A unicode filesystem path to a requirements file
"""
valid_tags = _pep425tags()
@@ -320,7 +333,20 @@ def _stage_requirements(deps_dir, path):
packages = _parse_requires(path)
for p in packages:
pkg = p['pkg']
+ pkg_sub_dir = None
if p['type'] == 'url':
+ anchor = None
+ if '#' in pkg:
+ pkg, anchor = pkg.split('#', 1)
+ if '&' in anchor:
+ parts = anchor.split('&')
+ else:
+ parts = [anchor]
+ for part in parts:
+ param, value = part.split('=')
+ if param == 'subdirectory':
+ pkg_sub_dir = value
+
if pkg.endswith('.zip') or pkg.endswith('.tar.gz') or pkg.endswith('.tar.bz2') or pkg.endswith('.whl'):
url = pkg
else:
@@ -383,7 +409,7 @@ def _stage_requirements(deps_dir, path):
local_path = _download(url, deps_dir)
- _extract_package(deps_dir, local_path)
+ _extract_package(deps_dir, local_path, pkg_sub_dir)
os.remove(local_path)
diff --git a/dev/release.py b/dev/release.py
index d5a3c7e..a854196 100644
--- a/dev/release.py
+++ b/dev/release.py
@@ -1,14 +1,13 @@
# coding: utf-8
from __future__ import unicode_literals, division, absolute_import, print_function
-import os
import subprocess
import sys
-import setuptools.sandbox
import twine.cli
-from . import package_name, package_root
+from . import package_name, package_root, has_tests_package
+from .build import run as build
def run():
@@ -20,8 +19,6 @@ def run():
A bool - if the packaging and upload process was successful
"""
- setup_file = os.path.join(package_root, 'setup.py')
-
git_wc_proc = subprocess.Popen(
['git', 'status', '--porcelain', '-uno'],
stdout=subprocess.PIPE,
@@ -54,14 +51,10 @@ def run():
tag = tag.decode('ascii').strip()
- setuptools.sandbox.run_setup(
- setup_file,
- ['sdist', 'bdist_wheel', '--universal']
- )
+ build()
twine.cli.dispatch(['upload', 'dist/%s-%s*' % (package_name, tag)])
+ if has_tests_package:
+ twine.cli.dispatch(['upload', 'dist/%s_tests-%s*' % (package_name, tag)])
- setuptools.sandbox.run_setup(
- setup_file,
- ['clean']
- )
+ return True
diff --git a/dev/tests.py b/dev/tests.py
index e05d4a2..a065c38 100644
--- a/dev/tests.py
+++ b/dev/tests.py
@@ -1,16 +1,23 @@
# coding: utf-8
from __future__ import unicode_literals, division, absolute_import, print_function
-import os
import unittest
import re
import sys
+from . import requires_oscrypto
+from ._import import _preload
+
from tests import test_classes
-import asn1crypto
+
+if sys.version_info < (3,):
+ range = xrange # noqa
+ from cStringIO import StringIO
+else:
+ from io import StringIO
-def run(matcher=None, ci=False):
+def run(matcher=None, repeat=1, ci=False):
"""
Runs the tests
@@ -18,24 +25,51 @@ def run(matcher=None, ci=False):
A unicode string containing a regular expression to use to filter test
names by. A value of None will cause no filtering.
+ :param repeat:
+ An integer - the number of times to run the tests
+
+ :param ci:
+ A bool, indicating if the tests are being run as part of CI
+
:return:
A bool - if the tests succeeded
"""
- if not ci:
- print('Python ' + sys.version.replace('\n', ''))
- print('\nasn1crypto: %s, %s\n' % (asn1crypto.__version__, os.path.dirname(asn1crypto.__file__)))
+ _preload(requires_oscrypto, not ci)
- suite = unittest.TestSuite()
loader = unittest.TestLoader()
+ # We have to manually track the list of applicable tests because for
+ # some reason with Python 3.4 on Windows, the tests in a suite are replaced
+ # with None after being executed. This breaks the repeat functionality.
+ test_list = []
for test_class in test_classes():
if matcher:
names = loader.getTestCaseNames(test_class)
for name in names:
if re.search(matcher, name):
- suite.addTest(test_class(name))
+ test_list.append(test_class(name))
else:
- suite.addTest(loader.loadTestsFromTestCase(test_class))
- verbosity = 2 if matcher else 1
- result = unittest.TextTestRunner(stream=sys.stdout, verbosity=verbosity).run(suite)
- return result.wasSuccessful()
+ test_list.append(loader.loadTestsFromTestCase(test_class))
+
+ stream = sys.stdout
+ verbosity = 1
+ if matcher and repeat == 1:
+ verbosity = 2
+ elif repeat > 1:
+ stream = StringIO()
+
+ for _ in range(0, repeat):
+ suite = unittest.TestSuite()
+ for test in test_list:
+ suite.addTest(test)
+ result = unittest.TextTestRunner(stream=stream, verbosity=verbosity).run(suite)
+
+ if len(result.errors) > 0 or len(result.failures) > 0:
+ if repeat > 1:
+ print(stream.getvalue())
+ return False
+
+ if repeat > 1:
+ stream.truncate(0)
+
+ return True
diff --git a/dev/version.py b/dev/version.py
new file mode 100644
index 0000000..3027431
--- /dev/null
+++ b/dev/version.py
@@ -0,0 +1,80 @@
+# coding: utf-8
+from __future__ import unicode_literals, division, absolute_import, print_function
+
+import codecs
+import os
+import re
+
+from . import package_root, package_name, has_tests_package
+
+
+def run(new_version):
+ """
+ Updates the package version in the various locations
+
+ :param new_version:
+ A unicode string of the new library version as a PEP 440 version
+
+ :return:
+ A bool - if the version number was successfully bumped
+ """
+
+ # We use a restricted form of PEP 440 versions
+ version_match = re.match(
+ r'(\d+)\.(\d+)\.(\d)+(?:\.((?:dev|a|b|rc)\d+))?$',
+ new_version
+ )
+ if not version_match:
+ raise ValueError('Invalid PEP 440 version: %s' % new_version)
+
+ new_version_info = (
+ int(version_match.group(1)),
+ int(version_match.group(2)),
+ int(version_match.group(3)),
+ )
+ if version_match.group(4):
+ new_version_info += (version_match.group(4),)
+
+ version_path = os.path.join(package_root, package_name, 'version.py')
+ setup_path = os.path.join(package_root, 'setup.py')
+ setup_tests_path = os.path.join(package_root, 'tests', 'setup.py')
+ tests_path = os.path.join(package_root, 'tests', '__init__.py')
+
+ file_paths = [version_path, setup_path]
+ if has_tests_package:
+ file_paths.extend([setup_tests_path, tests_path])
+
+ for file_path in file_paths:
+ orig_source = ''
+ with codecs.open(file_path, 'r', encoding='utf-8') as f:
+ orig_source = f.read()
+
+ found = 0
+ new_source = ''
+ for line in orig_source.splitlines(True):
+ if line.startswith('__version__ = '):
+ found += 1
+ new_source += '__version__ = %r\n' % new_version
+ elif line.startswith('__version_info__ = '):
+ found += 1
+ new_source += '__version_info__ = %r\n' % (new_version_info,)
+ elif line.startswith('PACKAGE_VERSION = '):
+ found += 1
+ new_source += 'PACKAGE_VERSION = %r\n' % new_version
+ else:
+ new_source += line
+
+ if found == 0:
+ raise ValueError('Did not find any versions in %s' % file_path)
+
+ s = 's' if found > 1 else ''
+ rel_path = file_path[len(package_root) + 1:]
+ was_were = 'was' if found == 1 else 'were'
+ if new_source != orig_source:
+ print('Updated %d version%s in %s' % (found, s, rel_path))
+ with codecs.open(file_path, 'w', encoding='utf-8') as f:
+ f.write(new_source)
+ else:
+ print('%d version%s in %s %s up-to-date' % (found, s, rel_path, was_were))
+
+ return True
diff --git a/readme.md b/readme.md
index 4732dfc..ae47397 100644
--- a/readme.md
+++ b/readme.md
@@ -13,6 +13,7 @@ A fast, pure Python library for parsing and serializing ASN.1 structures.
- [Continuous Integration](#continuous-integration)
- [Testing](#testing)
- [Development](#development)
+ - [CI Tasks](#ci-tasks)
[![Travis CI](https://api.travis-ci.org/wbond/asn1crypto.svg?branch=master)](https://travis-ci.org/wbond/asn1crypto)
[![AppVeyor](https://ci.appveyor.com/api/projects/status/github/wbond/asn1crypto?branch=master&svg=true)](https://ci.appveyor.com/project/wbond/asn1crypto)
@@ -161,7 +162,15 @@ links to the source for the various pre-defined type classes.
## Testing
-Tests are written using `unittest` and require no third-party packages:
+Tests are written using `unittest` and require no third-party packages.
+
+Depending on what type of source is available for the package, the following
+commands can be used to run the test suite.
+
+### Git Repository
+
+When working within a Git working copy, or an archive of the Git repository,
+the full test suite is run via:
```bash
python run.py tests
@@ -173,6 +182,25 @@ To run only some tests, pass a regular expression as a parameter to `tests`.
python run.py tests ocsp
```
+### PyPi Source Distribution
+
+When working within an extracted source distribution (aka `.tar.gz`) from
+PyPi, the full test suite is run via:
+
+```bash
+python setup.py test
+```
+
+### Package
+
+When the package has been installed via pip (or another method), the package
+`asn1crypto_tests` may be installed and invoked to run the full test suite:
+
+```bash
+pip install asn1crypto_tests
+python -m asn1crypto_tests
+```
+
## Development
To install the package used for linting, execute:
@@ -199,6 +227,12 @@ Coverage is measured by running:
python run.py coverage
```
+To change the version number of the package, run:
+
+```bash
+python run.py version {pep440_version}
+```
+
To install the necessary packages for releasing a new version on PyPI, run:
```bash
@@ -207,7 +241,7 @@ pip install --user -r requires/release
Releases are created by:
- - Making a git tag in [semver](http://semver.org/) format
+ - Making a git tag in [PEP 440](https://www.python.org/dev/peps/pep-0440/#examples-of-compliant-version-schemes) format
- Running the command:
```bash
diff --git a/requires/release b/requires/release
index af996cf..91cff65 100644
--- a/requires/release
+++ b/requires/release
@@ -1 +1,3 @@
-twine
+wheel>=0.31.0
+twine>=1.11.0
+setuptools>=38.6.0
diff --git a/run.py b/run.py
index aa86fe5..64666d9 100644
--- a/run.py
+++ b/run.py
@@ -11,7 +11,7 @@ else:
def show_usage():
- print('Usage: run.py (lint | tests [regex] | coverage | deps | ci | release)', file=sys.stderr)
+ print('Usage: run.py (lint | tests [regex] | coverage | deps | ci | version {pep440_version} | build | release)', file=sys.stderr)
sys.exit(1)
@@ -29,10 +29,10 @@ if len(sys.argv) < 2 or len(sys.argv) > 3:
task, next_arg = get_arg(1)
-if task not in set(['lint', 'tests', 'coverage', 'deps', 'ci', 'release']):
+if task not in set(['lint', 'tests', 'coverage', 'deps', 'ci', 'version', 'build', 'release']):
show_usage()
-if task != 'tests' and len(sys.argv) == 3:
+if task != 'tests' and task != 'version' and len(sys.argv) == 3:
show_usage()
params = []
@@ -54,6 +54,16 @@ elif task == 'deps':
elif task == 'ci':
from dev.ci import run
+elif task == 'version':
+ from dev.version import run
+ if len(sys.argv) != 3:
+ show_usage()
+ pep440_version, next_arg = get_arg(next_arg)
+ params.append(pep440_version)
+
+elif task == 'build':
+ from dev.build import run
+
elif task == 'release':
from dev.release import run
diff --git a/setup.cfg b/setup.cfg
deleted file mode 100644
index ed8a958..0000000
--- a/setup.cfg
+++ /dev/null
@@ -1,5 +0,0 @@
-[bdist_wheel]
-universal = 1
-
-[metadata]
-license_file = LICENSE
diff --git a/setup.py b/setup.py
index 6a6d8c1..f4b6aba 100644
--- a/setup.py
+++ b/setup.py
@@ -1,9 +1,69 @@
+import codecs
import os
import shutil
+import sys
+import warnings
+
+import setuptools
+from setuptools import setup, Command
+from setuptools.command.egg_info import egg_info
+
+
+PACKAGE_NAME = 'asn1crypto'
+PACKAGE_VERSION = '0.25.0.dev1'
+PACKAGE_ROOT = os.path.dirname(os.path.abspath(__file__))
+
+
+# setuptools 38.6.0 and newer know about long_description_content_type, but
+# distutils still complains about it, so silence the warning
+sv = setuptools.__version__
+svi = tuple(int(o) if o.isdigit() else o for o in sv.split('.'))
+if svi >= (38, 6):
+ warnings.filterwarnings(
+ 'ignore',
+ "Unknown distribution option: 'long_description_content_type'",
+ module='distutils.dist'
+ )
+
+
+# Try to load the tests first from the source repository layout. If that
+# doesn't work, we assume this file is in the release package, and the tests
+# are part of the package {PACKAGE_NAME}_tests.
+if os.path.exists(os.path.join(PACKAGE_ROOT, 'tests')):
+ tests_require = []
+ test_suite = 'tests.make_suite'
+else:
+ tests_require = ['%s_tests' % PACKAGE_NAME]
+ test_suite = '%s_tests.make_suite' % PACKAGE_NAME
+
+
+# This allows us to send the LICENSE and docs when creating a sdist. Wheels
+# automatically include the LICENSE, and don't need the docs. For these
+# to be included, the command must be "python setup.py sdist".
+package_data = {}
+if sys.argv[1:] == ['sdist'] or sorted(sys.argv[1:]) == ['-q', 'sdist']:
+ package_data[PACKAGE_NAME] = [
+ '../LICENSE',
+ '../*.md',
+ '../docs/*.md',
+ ]
-from setuptools import setup, find_packages, Command
-from asn1crypto import version
+# Ensures a copy of the LICENSE is included with the egg-info for
+# install and bdist_egg commands
+class EggInfoCommand(egg_info):
+ def run(self):
+ egg_info_path = os.path.join(
+ PACKAGE_ROOT,
+ '%s.egg-info' % PACKAGE_NAME
+ )
+ if not os.path.exists(egg_info_path):
+ os.mkdir(egg_info_path)
+ shutil.copy2(
+ os.path.join(PACKAGE_ROOT, 'LICENSE'),
+ os.path.join(egg_info_path, 'LICENSE')
+ )
+ egg_info.run(self)
class CleanCommand(Command):
@@ -18,30 +78,38 @@ class CleanCommand(Command):
pass
def run(self):
- folder = os.path.dirname(os.path.abspath(__file__))
- for sub_folder in ['build', 'dist', 'asn1crypto.egg-info']:
- full_path = os.path.join(folder, sub_folder)
+ sub_folders = ['build', 'temp', '%s.egg-info' % PACKAGE_NAME]
+ if self.all:
+ sub_folders.append('dist')
+ for sub_folder in sub_folders:
+ full_path = os.path.join(PACKAGE_ROOT, sub_folder)
if os.path.exists(full_path):
shutil.rmtree(full_path)
- for root, dirnames, filenames in os.walk(os.path.join(folder, 'asn1crypto')):
- for filename in filenames:
+ for root, dirs, files in os.walk(os.path.join(PACKAGE_ROOT, PACKAGE_NAME)):
+ for filename in files:
if filename[-4:] == '.pyc':
os.unlink(os.path.join(root, filename))
- for dirname in list(dirnames):
+ for dirname in list(dirs):
if dirname == '__pycache__':
shutil.rmtree(os.path.join(root, dirname))
+readme = ''
+with codecs.open(os.path.join(PACKAGE_ROOT, 'readme.md'), 'r', 'utf-8') as f:
+ readme = f.read()
+
+
setup(
- name='asn1crypto',
- version=version.__version__,
+ name=PACKAGE_NAME,
+ version=PACKAGE_VERSION,
description=(
'Fast ASN.1 parser and serializer with definitions for private keys, '
'public keys, certificates, CRL, OCSP, CMS, PKCS#3, PKCS#7, PKCS#8, '
'PKCS#12, PKCS#5, X.509 and TSP'
),
- long_description='Docs for this project are maintained at https://github.com/wbond/asn1crypto#readme.',
+ long_description=readme,
+ long_description_content_type='text/markdown',
url='https://github.com/wbond/asn1crypto',
@@ -76,11 +144,14 @@ setup(
keywords='asn1 crypto pki x509 certificate rsa dsa ec dh',
- packages=find_packages(exclude=['tests*', 'dev*']),
+ packages=[PACKAGE_NAME],
+ package_data=package_data,
- test_suite='tests.make_suite',
+ tests_require=tests_require,
+ test_suite=test_suite,
cmdclass={
'clean': CleanCommand,
+ 'egg_info': EggInfoCommand,
}
)
diff --git a/tests/LICENSE b/tests/LICENSE
new file mode 100644
index 0000000..8038d9a
--- /dev/null
+++ b/tests/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2015-2019 Will Bond <will@wbond.net>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/tests/__init__.py b/tests/__init__.py
index 783a20f..1af7e94 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -6,6 +6,44 @@ import os
import unittest
+__version__ = '0.25.0.dev1'
+__version_info__ = (0, 25, 0, 'dev1')
+
+
+def _import_from(mod, path, mod_dir=None):
+ """
+ Imports a module from a specific path
+
+ :param mod:
+ A unicode string of the module name
+
+ :param path:
+ A unicode string to the directory containing the module
+
+ :param mod_dir:
+ If the sub directory of "path" is different than the "mod" name,
+ pass the sub directory as a unicode string
+
+ :return:
+ None if not loaded, otherwise the module
+ """
+
+ if mod_dir is None:
+ mod_dir = mod
+
+ if not os.path.exists(path):
+ return None
+
+ if not os.path.exists(os.path.join(path, mod_dir)):
+ return None
+
+ try:
+ mod_info = imp.find_module(mod_dir, [path])
+ return imp.load_module(mod, *mod_info)
+ except ImportError:
+ return None
+
+
def make_suite():
"""
Constructs a unittest.TestSuite() of all tests for the package. For use
@@ -31,11 +69,24 @@ def test_classes():
A list of unittest.TestCase classes
"""
- # Make sure the module is loaded from this source folder
- module_name = 'asn1crypto'
- src_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..')
- module_info = imp.find_module(module_name, [src_dir])
- imp.load_module(module_name, *module_info)
+ # If we are in a source folder and these tests aren't installed as a
+ # package, we want to load asn1crypto from this source folder
+ tests_dir = os.path.dirname(os.path.abspath(__file__))
+
+ asn1crypto = None
+ if os.path.basename(tests_dir) == 'tests':
+ asn1crypto = _import_from(
+ 'asn1crypto',
+ os.path.join(tests_dir, '..')
+ )
+ if asn1crypto is None:
+ import asn1crypto
+
+ if asn1crypto.__version__ != __version__:
+ raise AssertionError(
+ ('asn1crypto_tests version %s can not be run with ' % __version__) +
+ ('asn1crypto version %s' % asn1crypto.__version__)
+ )
from .test_algos import AlgoTests
from .test_cms import CMSTests
diff --git a/tests/__main__.py b/tests/__main__.py
new file mode 100644
index 0000000..644391e
--- /dev/null
+++ b/tests/__main__.py
@@ -0,0 +1,14 @@
+# coding: utf-8
+from __future__ import unicode_literals, division, absolute_import, print_function
+
+import sys
+import unittest
+
+from . import test_classes
+
+
+suite = unittest.TestSuite()
+loader = unittest.TestLoader()
+for test_class in test_classes():
+ suite.addTest(loader.loadTestsFromTestCase(test_class))
+unittest.TextTestRunner(stream=sys.stdout, verbosity=2).run(suite)
diff --git a/tests/readme.md b/tests/readme.md
new file mode 100644
index 0000000..930f7cb
--- /dev/null
+++ b/tests/readme.md
@@ -0,0 +1,9 @@
+# asn1crypto_tests
+
+Run the test suite via:
+
+```bash
+python -m asn1crypto_tests
+```
+
+Full documentation a <https://github.com/wbond/asn1crypto#readme>.
diff --git a/tests/setup.py b/tests/setup.py
new file mode 100644
index 0000000..078c7e4
--- /dev/null
+++ b/tests/setup.py
@@ -0,0 +1,153 @@
+import codecs
+import os
+import shutil
+import sys
+import warnings
+
+import setuptools
+from setuptools import setup, Command
+from setuptools.command.egg_info import egg_info
+
+
+PACKAGE_NAME = 'asn1crypto'
+PACKAGE_VERSION = '0.25.0.dev1'
+TEST_PACKAGE_NAME = '%s_tests' % PACKAGE_NAME
+
+
+# setuptools 38.6.0 and newer know about long_description_content_type, but
+# distutils still complains about it, so silence the warning
+sv = setuptools.__version__
+svi = tuple(int(o) if o.isdigit() else o for o in sv.split('.'))
+if svi >= (38, 6):
+ warnings.filterwarnings(
+ 'ignore',
+ "Unknown distribution option: 'long_description_content_type'",
+ module='distutils.dist'
+ )
+
+
+package_data = {
+ TEST_PACKAGE_NAME: [
+ 'fixtures/*',
+ 'fixtures/*/*',
+ ]
+}
+# This allows us to send the LICENSE when creating a sdist. Wheels
+# automatically include the license, and don't need the docs. For these
+# to be included, the command must be "python setup.py sdist".
+if sys.argv[1:] == ['sdist'] or sorted(sys.argv[1:]) == ['-q', 'sdist']:
+ package_data[TEST_PACKAGE_NAME].extend([
+ 'LICENSE',
+ 'readme.md',
+ ])
+
+
+tests_root = os.path.dirname(os.path.abspath(__file__))
+package_root = os.path.abspath(os.path.join(tests_root, '..'))
+
+
+# Ensures a copy of the LICENSE is included with the egg-info for
+# install and bdist_egg commands
+class EggInfoCommand(egg_info):
+ def run(self):
+ egg_info_path = os.path.join(
+ tests_root,
+ '%s.egg-info' % TEST_PACKAGE_NAME
+ )
+ if not os.path.exists(egg_info_path):
+ os.mkdir(egg_info_path)
+ shutil.copy2(
+ os.path.join(package_root, 'LICENSE'),
+ os.path.join(egg_info_path, 'LICENSE')
+ )
+ egg_info.run(self)
+
+
+class CleanCommand(Command):
+ user_options = [
+ ('all', 'a', '(Compatibility with original clean command)'),
+ ]
+
+ def initialize_options(self):
+ self.all = False
+
+ def finalize_options(self):
+ pass
+
+ def run(self):
+ sub_folders = ['build', 'temp', '%s.egg-info' % TEST_PACKAGE_NAME]
+ if self.all:
+ sub_folders.append('dist')
+ for sub_folder in sub_folders:
+ full_path = os.path.join(tests_root, sub_folder)
+ if os.path.exists(full_path):
+ shutil.rmtree(full_path)
+ for root, dirs, files in os.walk(tests_root):
+ for filename in files:
+ if filename[-4:] == '.pyc':
+ os.unlink(os.path.join(root, filename))
+ for dirname in list(dirs):
+ if dirname == '__pycache__':
+ shutil.rmtree(os.path.join(root, dirname))
+
+
+readme = ''
+with codecs.open(os.path.join(tests_root, 'readme.md'), 'r', 'utf-8') as f:
+ readme = f.read()
+
+
+setup(
+ name=TEST_PACKAGE_NAME,
+ version=PACKAGE_VERSION,
+
+ description=(
+ 'Test suite for asn1crypto, separated due to file size'
+ ),
+ long_description=readme,
+ long_description_content_type='text/markdown',
+
+ url='https://github.com/wbond/asn1crypto',
+
+ author='wbond',
+ author_email='will@wbond.net',
+
+ license='MIT',
+
+ classifiers=[
+ 'Development Status :: 4 - Beta',
+
+ 'Intended Audience :: Developers',
+
+ 'License :: OSI Approved :: MIT License',
+
+ 'Programming Language :: Python',
+ 'Programming Language :: Python :: 2',
+ 'Programming Language :: Python :: 2.6',
+ 'Programming Language :: Python :: 2.7',
+ 'Programming Language :: Python :: 3',
+ 'Programming Language :: Python :: 3.2',
+ 'Programming Language :: Python :: 3.3',
+ 'Programming Language :: Python :: 3.4',
+ 'Programming Language :: Python :: 3.5',
+ 'Programming Language :: Python :: 3.6',
+ 'Programming Language :: Python :: 3.7',
+ 'Programming Language :: Python :: Implementation :: CPython',
+ 'Programming Language :: Python :: Implementation :: PyPy',
+
+ 'Topic :: Security :: Cryptography',
+ ],
+
+ keywords='asn1 crypto pki x509 certificate rsa dsa ec dh',
+ packages=[TEST_PACKAGE_NAME],
+ package_dir={TEST_PACKAGE_NAME: '.'},
+ package_data=package_data,
+
+ install_requires=[
+ '%s==%s' % (PACKAGE_NAME, PACKAGE_VERSION),
+ ],
+
+ cmdclass={
+ 'clean': CleanCommand,
+ 'egg_info': EggInfoCommand,
+ }
+)