Merge pull request #468 from reaperhulk/backend-flag-pytest

Add backend flag for pytest runs
diff --git a/cryptography/hazmat/backends/openssl/backend.py b/cryptography/hazmat/backends/openssl/backend.py
index 07ee58c..ee82ba7 100644
--- a/cryptography/hazmat/backends/openssl/backend.py
+++ b/cryptography/hazmat/backends/openssl/backend.py
@@ -37,6 +37,7 @@
     """
     OpenSSL API binding interfaces.
     """
+    name = "openssl"
 
     def __init__(self):
         self._binding = Binding()
diff --git a/docs/contributing.rst b/docs/contributing.rst
index 8e32c36..4bb1461 100644
--- a/docs/contributing.rst
+++ b/docs/contributing.rst
@@ -250,6 +250,16 @@
 You may not have all the required Python versions installed, in which case you
 will see one or more ``InterpreterNotFound`` errors.
 
+
+Explicit Backend Selection
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+While testing you may want to run tests against a subset of the backends that
+cryptography supports. Explicit backend selection can be done via the
+``--backend`` flag. This flag should be passed to ``py.test`` with a comma
+delimited list of backend names. To use it with ``tox`` you must pass it as
+``tox -- --backend=openssl``.
+
 Building Documentation
 ~~~~~~~~~~~~~~~~~~~~~~
 
diff --git a/docs/hazmat/backends/openssl.rst b/docs/hazmat/backends/openssl.rst
index 404573a..a1f2d28 100644
--- a/docs/hazmat/backends/openssl.rst
+++ b/docs/hazmat/backends/openssl.rst
@@ -7,7 +7,11 @@
 
 .. data:: cryptography.hazmat.backends.openssl.backend
 
-    This is the exposed API for the OpenSSL backend. It has no public attributes.    
+    This is the exposed API for the OpenSSL backend. It has one public attribute.
+
+    .. attribute:: name
+
+        The string name of this backend: ``"openssl"``
 
 Using your own OpenSSL on Linux
 -------------------------------
diff --git a/tests/conftest.py b/tests/conftest.py
index 1d9f96e..a9acb54 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -5,12 +5,15 @@
     HMACBackend, CipherBackend, HashBackend
 )
 
-from .utils import check_for_iface, check_backend_support
+from .utils import check_for_iface, check_backend_support, select_backends
 
 
-@pytest.fixture(params=_ALL_BACKENDS)
-def backend(request):
-    return request.param
+def pytest_generate_tests(metafunc):
+    names = metafunc.config.getoption("--backend")
+    selected_backends = select_backends(names, _ALL_BACKENDS)
+
+    if "backend" in metafunc.fixturenames:
+        metafunc.parametrize("backend", selected_backends)
 
 
 @pytest.mark.trylast
@@ -19,3 +22,10 @@
     check_for_iface("cipher", CipherBackend, item)
     check_for_iface("hash", HashBackend, item)
     check_backend_support(item)
+
+
+def pytest_addoption(parser):
+    parser.addoption(
+        "--backend", action="store", metavar="NAME",
+        help="Only run tests matching the backend NAME."
+    )
diff --git a/tests/test_utils.py b/tests/test_utils.py
index e3e53d6..f852f3a 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -21,7 +21,7 @@
 from .utils import (
     load_nist_vectors, load_vectors_from_file, load_cryptrec_vectors,
     load_openssl_vectors, load_hash_vectors, check_for_iface,
-    check_backend_support
+    check_backend_support, select_backends
 )
 
 
@@ -29,6 +29,48 @@
     pass
 
 
+def test_select_one_backend():
+    b1 = pretend.stub(name="b1")
+    b2 = pretend.stub(name="b2")
+    b3 = pretend.stub(name="b3")
+    backends = [b1, b2, b3]
+    name = "b2"
+    selected_backends = select_backends(name, backends)
+    assert len(selected_backends) == 1
+    assert selected_backends[0] == b2
+
+
+def test_select_no_backend():
+    b1 = pretend.stub(name="b1")
+    b2 = pretend.stub(name="b2")
+    b3 = pretend.stub(name="b3")
+    backends = [b1, b2, b3]
+    name = "back!"
+    with pytest.raises(ValueError):
+        select_backends(name, backends)
+
+
+def test_select_backends_none():
+    b1 = pretend.stub(name="b1")
+    b2 = pretend.stub(name="b2")
+    b3 = pretend.stub(name="b3")
+    backends = [b1, b2, b3]
+    name = None
+    selected_backends = select_backends(name, backends)
+    assert len(selected_backends) == 3
+
+
+def test_select_two_backends():
+    b1 = pretend.stub(name="b1")
+    b2 = pretend.stub(name="b2")
+    b3 = pretend.stub(name="b3")
+    backends = [b1, b2, b3]
+    name = "b2 ,b1 "
+    selected_backends = select_backends(name, backends)
+    assert len(selected_backends) == 2
+    assert selected_backends == [b1, b2]
+
+
 def test_check_for_iface():
     item = pretend.stub(keywords=["fake_name"], funcargs={"backend": True})
     with pytest.raises(pytest.skip.Exception) as exc_info:
diff --git a/tests/utils.py b/tests/utils.py
index 693a0c8..a243225 100644
--- a/tests/utils.py
+++ b/tests/utils.py
@@ -16,6 +16,25 @@
 import pytest
 
 
+def select_backends(names, backend_list):
+    if names is None:
+        return backend_list
+    split_names = [x.strip() for x in names.split(',')]
+    # this must be duplicated and then removed to preserve the metadata
+    # pytest associates. Appending backends to a new list doesn't seem to work
+    selected_backends = []
+    for backend in backend_list:
+        if backend.name in split_names:
+            selected_backends.append(backend)
+
+    if len(selected_backends) > 0:
+        return selected_backends
+    else:
+        raise ValueError(
+            "No backend selected. Tried to select: {0}".format(split_names)
+        )
+
+
 def check_for_iface(name, iface, item):
     if name in item.keywords and "backend" in item.funcargs:
         if not isinstance(item.funcargs["backend"], iface):
diff --git a/tox.ini b/tox.ini
index ff5df36..5ff0877 100644
--- a/tox.ini
+++ b/tox.ini
@@ -8,7 +8,7 @@
     pretend
     pytest
 commands =
-    coverage run --source=cryptography/,tests/ -m pytest --capture=no --strict
+    coverage run --source=cryptography/,tests/ -m pytest --capture=no --strict {posargs}
     coverage report -m
 
 [testenv:docs]
@@ -28,7 +28,7 @@
 # Temporarily disable coverage on pypy because of performance problems with
 # coverage.py on pypy.
 [testenv:pypy]
-commands = py.test --capture=no --strict
+commands = py.test --capture=no --strict {posargs}
 
 [testenv:pep8]
 deps = flake8