Split test_python_types.cpp into builtin_casters, stl and pytypes
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index be223f5..a57896d 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -28,10 +28,11 @@
 set(PYBIND11_TEST_FILES
   test_alias_initialization.cpp
   test_buffers.cpp
+  test_builtin_casters.cpp
   test_call_policies.cpp
   test_callbacks.cpp
   test_chrono.cpp
-  test_class_args.cpp
+  test_class.cpp
   test_constants_and_functions.cpp
   test_copy_move.cpp
   test_docstring_options.cpp
@@ -50,9 +51,10 @@
   test_opaque_types.cpp
   test_operator_overloading.cpp
   test_pickling.cpp
-  test_python_types.cpp
+  test_pytypes.cpp
   test_sequences_and_iterators.cpp
   test_smart_ptr.cpp
+  test_stl.cpp
   test_stl_binders.cpp
   test_virtual_functions.cpp
 )
diff --git a/tests/pybind11_tests.cpp b/tests/pybind11_tests.cpp
index 1e24350..81fe8bf 100644
--- a/tests/pybind11_tests.cpp
+++ b/tests/pybind11_tests.cpp
@@ -10,6 +10,9 @@
 #include "pybind11_tests.h"
 #include "constructor_stats.h"
 
+#include <functional>
+#include <list>
+
 /*
 For testing purposes, we define a static global variable here in a function that each individual
 test .cpp calls with its initialization lambda.  It's convenient here because we can just not
@@ -28,8 +31,15 @@
     return inits;
 }
 
-test_initializer::test_initializer(std::function<void(py::module &)> initializer) {
-    initializers().push_back(std::move(initializer));
+test_initializer::test_initializer(Initializer init) {
+    initializers().push_back(init);
+}
+
+test_initializer::test_initializer(const char *submodule_name, Initializer init) {
+    initializers().push_back([=](py::module &parent) {
+        auto m = parent.def_submodule(submodule_name);
+        init(m);
+    });
 }
 
 void bind_ConstructorStats(py::module &m) {
@@ -57,6 +67,24 @@
 
     bind_ConstructorStats(m);
 
+#if !defined(NDEBUG)
+    m.attr("debug_enabled") = true;
+#else
+    m.attr("debug_enabled") = false;
+#endif
+
+    py::class_<UserType>(m, "UserType", "A `py::class_` type for testing")
+        .def(py::init<>())
+        .def(py::init<int>())
+        .def("get_value", &UserType::value, "Get value using a method")
+        .def_property_readonly("value", &UserType::value, "Get value using a property")
+        .def("__repr__", [](const UserType& u) { return "UserType({})"_s.format(u.value()); });
+
+    py::class_<IncType, UserType>(m, "IncType")
+        .def(py::init<>())
+        .def(py::init<int>())
+        .def("__repr__", [](const IncType& u) { return "IncType({})"_s.format(u.value()); });
+
     for (const auto &initializer : initializers())
         initializer(m);
 
diff --git a/tests/pybind11_tests.h b/tests/pybind11_tests.h
index c11b687..dd8d159 100644
--- a/tests/pybind11_tests.h
+++ b/tests/pybind11_tests.h
@@ -1,12 +1,45 @@
 #pragma once
 #include <pybind11/pybind11.h>
-#include <functional>
-#include <list>
 
 namespace py = pybind11;
 using namespace pybind11::literals;
 
 class test_initializer {
+    using Initializer = void (*)(py::module &);
+
 public:
-    test_initializer(std::function<void(py::module &)> initializer);
+    test_initializer(Initializer init);
+    test_initializer(const char *submodule_name, Initializer init);
+};
+
+#define TEST_SUBMODULE(name, variable)                   \
+    void test_submodule_##name(py::module &);            \
+    test_initializer name(#name, test_submodule_##name); \
+    void test_submodule_##name(py::module &variable)
+
+
+/// Dummy type which is not exported anywhere -- something to trigger a conversion error
+struct UnregisteredType { };
+
+/// A user-defined type which is exported and can be used by any test
+class UserType {
+public:
+    UserType() = default;
+    UserType(int i) : i(i) { }
+
+    int value() const { return i; }
+
+private:
+    int i = -1;
+};
+
+/// Like UserType, but increments `value` on copy for quick reference vs. copy tests
+class IncType : public UserType {
+public:
+    using UserType::UserType;
+    IncType() = default;
+    IncType(const IncType &other) : IncType(other.value() + 1) { }
+    IncType(IncType &&) = delete;
+    IncType &operator=(const IncType &) = delete;
+    IncType &operator=(IncType &&) = delete;
 };
diff --git a/tests/test_builtin_casters.cpp b/tests/test_builtin_casters.cpp
new file mode 100644
index 0000000..33fe689
--- /dev/null
+++ b/tests/test_builtin_casters.cpp
@@ -0,0 +1,136 @@
+/*
+    tests/test_builtin_casters.cpp -- Casters available without any additional headers
+
+    Copyright (c) 2017 Wenzel Jakob <wenzel.jakob@epfl.ch>
+
+    All rights reserved. Use of this source code is governed by a
+    BSD-style license that can be found in the LICENSE file.
+*/
+
+#include "pybind11_tests.h"
+#include <pybind11/complex.h>
+
+#if defined(_MSC_VER)
+#  pragma warning(push)
+#  pragma warning(disable: 4127) // warning C4127: Conditional expression is constant
+#endif
+
+TEST_SUBMODULE(builtin_casters, m) {
+    // test_simple_string
+    m.def("string_roundtrip", [](const char *s) { return s; });
+
+    // test_unicode_conversion
+    // Some test characters in utf16 and utf32 encodings.  The last one (the 𝐀) contains a null byte
+    char32_t a32 = 0x61 /*a*/, z32 = 0x7a /*z*/, ib32 = 0x203d /*β€½*/, cake32 = 0x1f382 /*πŸŽ‚*/,              mathbfA32 = 0x1d400 /*𝐀*/;
+    char16_t b16 = 0x62 /*b*/, z16 = 0x7a,       ib16 = 0x203d,       cake16_1 = 0xd83c, cake16_2 = 0xdf82, mathbfA16_1 = 0xd835, mathbfA16_2 = 0xdc00;
+    std::wstring wstr;
+    wstr.push_back(0x61); // a
+    wstr.push_back(0x2e18); // ⸘
+    if (sizeof(wchar_t) == 2) { wstr.push_back(mathbfA16_1); wstr.push_back(mathbfA16_2); } // 𝐀, utf16
+    else { wstr.push_back((wchar_t) mathbfA32); } // 𝐀, utf32
+    wstr.push_back(0x7a); // z
+
+    m.def("good_utf8_string", []() { return std::string(u8"Say utf8\u203d \U0001f382 \U0001d400"); }); // Say utf8β€½ πŸŽ‚ 𝐀
+    m.def("good_utf16_string", [=]() { return std::u16string({ b16, ib16, cake16_1, cake16_2, mathbfA16_1, mathbfA16_2, z16 }); }); // bβ€½πŸŽ‚π€z
+    m.def("good_utf32_string", [=]() { return std::u32string({ a32, mathbfA32, cake32, ib32, z32 }); }); // aπ€πŸŽ‚β€½z
+    m.def("good_wchar_string", [=]() { return wstr; }); // a‽𝐀z
+    m.def("bad_utf8_string", []()  { return std::string("abc\xd0" "def"); });
+    m.def("bad_utf16_string", [=]() { return std::u16string({ b16, char16_t(0xd800), z16 }); });
+    // Under Python 2.7, invalid unicode UTF-32 characters don't appear to trigger UnicodeDecodeError
+    if (PY_MAJOR_VERSION >= 3)
+        m.def("bad_utf32_string", [=]() { return std::u32string({ a32, char32_t(0xd800), z32 }); });
+    if (PY_MAJOR_VERSION >= 3 || sizeof(wchar_t) == 2)
+        m.def("bad_wchar_string", [=]() { return std::wstring({ wchar_t(0x61), wchar_t(0xd800) }); });
+    m.def("u8_Z", []() -> char { return 'Z'; });
+    m.def("u8_eacute", []() -> char { return '\xe9'; });
+    m.def("u16_ibang", [=]() -> char16_t { return ib16; });
+    m.def("u32_mathbfA", [=]() -> char32_t { return mathbfA32; });
+    m.def("wchar_heart", []() -> wchar_t { return 0x2665; });
+
+    // test_single_char_arguments
+    m.attr("wchar_size") = py::cast(sizeof(wchar_t));
+    m.def("ord_char", [](char c) -> int { return static_cast<unsigned char>(c); });
+    m.def("ord_char16", [](char16_t c) -> uint16_t { return c; });
+    m.def("ord_char32", [](char32_t c) -> uint32_t { return c; });
+    m.def("ord_wchar", [](wchar_t c) -> int { return c; });
+
+    // test_bytes_to_string
+    m.def("strlen", [](char *s) { return strlen(s); });
+    m.def("string_length", [](std::string s) { return s.length(); });
+
+    // test_string_view
+#ifdef PYBIND11_HAS_STRING_VIEW
+    m.attr("has_string_view") = true;
+    m.def("string_view_print",   [](std::string_view s)    { py::print(s, s.size()); });
+    m.def("string_view16_print", [](std::u16string_view s) { py::print(s, s.size()); });
+    m.def("string_view32_print", [](std::u32string_view s) { py::print(s, s.size()); });
+    m.def("string_view_chars",   [](std::string_view s)    { py::list l; for (auto c : s) l.append((std::uint8_t) c); return l; });
+    m.def("string_view16_chars", [](std::u16string_view s) { py::list l; for (auto c : s) l.append((int) c); return l; });
+    m.def("string_view32_chars", [](std::u32string_view s) { py::list l; for (auto c : s) l.append((int) c); return l; });
+    m.def("string_view_return",   []() { return std::string_view(u8"utf8 secret \U0001f382"); });
+    m.def("string_view16_return", []() { return std::u16string_view(u"utf16 secret \U0001f382"); });
+    m.def("string_view32_return", []() { return std::u32string_view(U"utf32 secret \U0001f382"); });
+#endif
+
+    // test_tuple
+    m.def("pair_passthrough", [](std::pair<bool, std::string> input) {
+        return std::make_pair(input.second, input.first);
+    }, "Return a pair in reversed order");
+    m.def("tuple_passthrough", [](std::tuple<bool, std::string, int> input) {
+        return std::make_tuple(std::get<2>(input), std::get<1>(input), std::get<0>(input));
+    }, "Return a triple in reversed order");
+
+
+    // test_builtins_cast_return_none
+    m.def("return_none_string", []() -> std::string * { return nullptr; });
+    m.def("return_none_char",   []() -> const char *  { return nullptr; });
+    m.def("return_none_bool",   []() -> bool *        { return nullptr; });
+    m.def("return_none_int",    []() -> int *         { return nullptr; });
+    m.def("return_none_float",  []() -> float *       { return nullptr; });
+
+    // test_none_deferred
+    m.def("defer_none_cstring", [](char *) { return false; });
+    m.def("defer_none_cstring", [](py::none) { return true; });
+    m.def("defer_none_custom", [](UserType *) { return false; });
+    m.def("defer_none_custom", [](py::none) { return true; });
+    m.def("nodefer_none_void", [](void *) { return true; });
+    m.def("nodefer_none_void", [](py::none) { return false; });
+
+    // test_void_caster
+    m.def("load_nullptr_t", [](std::nullptr_t) {}); // not useful, but it should still compile
+    m.def("cast_nullptr_t", []() { return std::nullptr_t{}; });
+
+    // test_reference_wrapper
+    m.def("refwrap_builtin", [](std::reference_wrapper<int> p) { return 10 * p.get(); });
+    m.def("refwrap_usertype", [](std::reference_wrapper<UserType> p) { return p.get().value(); });
+    // Not currently supported (std::pair caster has return-by-value cast operator);
+    // triggers static_assert failure.
+    //m.def("refwrap_pair", [](std::reference_wrapper<std::pair<int, int>>) { });
+
+    m.def("refwrap_list", [](bool copy) {
+        static IncType x1(1), x2(2);
+        py::list l;
+        for (auto &f : {std::ref(x1), std::ref(x2)}) {
+            l.append(py::cast(f, copy ? py::return_value_policy::copy
+                                      : py::return_value_policy::reference));
+        }
+        return l;
+    }, "copy"_a);
+
+    m.def("refwrap_iiw", [](const IncType &w) { return w.value(); });
+    m.def("refwrap_call_iiw", [](IncType &w, py::function f) {
+        py::list l;
+        l.append(f(std::ref(w)));
+        l.append(f(std::cref(w)));
+        IncType x(w.value());
+        l.append(f(std::ref(x)));
+        IncType y(w.value());
+        auto r3 = std::ref(y);
+        l.append(f(r3));
+        return l;
+    });
+
+    // test_complex
+    m.def("complex_cast", [](float x) { return "{}"_s.format(x); });
+    m.def("complex_cast", [](std::complex<float> x) { return "({}, {})"_s.format(x.real(), x.imag()); });
+}
diff --git a/tests/test_builtin_casters.py b/tests/test_builtin_casters.py
new file mode 100644
index 0000000..59af0ee
--- /dev/null
+++ b/tests/test_builtin_casters.py
@@ -0,0 +1,221 @@
+# Python < 3 needs this: coding=utf-8
+import pytest
+
+from pybind11_tests import builtin_casters as m
+from pybind11_tests import UserType, IncType
+
+
+def test_simple_string():
+    assert m.string_roundtrip("const char *") == "const char *"
+
+
+def test_unicode_conversion():
+    """Tests unicode conversion and error reporting."""
+    assert m.good_utf8_string() == u"Say utf8β€½ πŸŽ‚ 𝐀"
+    assert m.good_utf16_string() == u"bβ€½πŸŽ‚π€z"
+    assert m.good_utf32_string() == u"aπ€πŸŽ‚β€½z"
+    assert m.good_wchar_string() == u"aβΈ˜π€z"
+
+    with pytest.raises(UnicodeDecodeError):
+        m.bad_utf8_string()
+
+    with pytest.raises(UnicodeDecodeError):
+        m.bad_utf16_string()
+
+    # These are provided only if they actually fail (they don't when 32-bit and under Python 2.7)
+    if hasattr(m, "bad_utf32_string"):
+        with pytest.raises(UnicodeDecodeError):
+            m.bad_utf32_string()
+    if hasattr(m, "bad_wchar_string"):
+        with pytest.raises(UnicodeDecodeError):
+            m.bad_wchar_string()
+
+    assert m.u8_Z() == 'Z'
+    assert m.u8_eacute() == u'é'
+    assert m.u16_ibang() == u'β€½'
+    assert m.u32_mathbfA() == u'𝐀'
+    assert m.wchar_heart() == u'♥'
+
+
+def test_single_char_arguments():
+    """Tests failures for passing invalid inputs to char-accepting functions"""
+    def toobig_message(r):
+        return "Character code point not in range({0:#x})".format(r)
+    toolong_message = "Expected a character, but multi-character string found"
+
+    assert m.ord_char(u'a') == 0x61  # simple ASCII
+    assert m.ord_char(u'é') == 0xE9  # requires 2 bytes in utf-8, but can be stuffed in a char
+    with pytest.raises(ValueError) as excinfo:
+        assert m.ord_char(u'Δ€') == 0x100  # requires 2 bytes, doesn't fit in a char
+    assert str(excinfo.value) == toobig_message(0x100)
+    with pytest.raises(ValueError) as excinfo:
+        assert m.ord_char(u'ab')
+    assert str(excinfo.value) == toolong_message
+
+    assert m.ord_char16(u'a') == 0x61
+    assert m.ord_char16(u'é') == 0xE9
+    assert m.ord_char16(u'Δ€') == 0x100
+    assert m.ord_char16(u'β€½') == 0x203d
+    assert m.ord_char16(u'♥') == 0x2665
+    with pytest.raises(ValueError) as excinfo:
+        assert m.ord_char16(u'πŸŽ‚') == 0x1F382  # requires surrogate pair
+    assert str(excinfo.value) == toobig_message(0x10000)
+    with pytest.raises(ValueError) as excinfo:
+        assert m.ord_char16(u'aa')
+    assert str(excinfo.value) == toolong_message
+
+    assert m.ord_char32(u'a') == 0x61
+    assert m.ord_char32(u'é') == 0xE9
+    assert m.ord_char32(u'Δ€') == 0x100
+    assert m.ord_char32(u'β€½') == 0x203d
+    assert m.ord_char32(u'♥') == 0x2665
+    assert m.ord_char32(u'πŸŽ‚') == 0x1F382
+    with pytest.raises(ValueError) as excinfo:
+        assert m.ord_char32(u'aa')
+    assert str(excinfo.value) == toolong_message
+
+    assert m.ord_wchar(u'a') == 0x61
+    assert m.ord_wchar(u'é') == 0xE9
+    assert m.ord_wchar(u'Δ€') == 0x100
+    assert m.ord_wchar(u'β€½') == 0x203d
+    assert m.ord_wchar(u'♥') == 0x2665
+    if m.wchar_size == 2:
+        with pytest.raises(ValueError) as excinfo:
+            assert m.ord_wchar(u'πŸŽ‚') == 0x1F382  # requires surrogate pair
+        assert str(excinfo.value) == toobig_message(0x10000)
+    else:
+        assert m.ord_wchar(u'πŸŽ‚') == 0x1F382
+    with pytest.raises(ValueError) as excinfo:
+        assert m.ord_wchar(u'aa')
+    assert str(excinfo.value) == toolong_message
+
+
+def test_bytes_to_string():
+    """Tests the ability to pass bytes to C++ string-accepting functions.  Note that this is
+    one-way: the only way to return bytes to Python is via the pybind11::bytes class."""
+    # Issue #816
+    import sys
+    byte = bytes if sys.version_info[0] < 3 else str
+
+    assert m.strlen(byte("hi")) == 2
+    assert m.string_length(byte("world")) == 5
+    assert m.string_length(byte("a\x00b")) == 3
+    assert m.strlen(byte("a\x00b")) == 1  # C-string limitation
+
+    # passing in a utf8 encoded string should work
+    assert m.string_length(u'πŸ’©'.encode("utf8")) == 4
+
+
+@pytest.mark.skipif(not hasattr(m, "has_string_view"), reason="no <string_view>")
+def test_string_view(capture):
+    """Tests support for C++17 string_view arguments and return values"""
+    assert m.string_view_chars("Hi") == [72, 105]
+    assert m.string_view_chars("Hi πŸŽ‚") == [72, 105, 32, 0xf0, 0x9f, 0x8e, 0x82]
+    assert m.string_view16_chars("Hi πŸŽ‚") == [72, 105, 32, 0xd83c, 0xdf82]
+    assert m.string_view32_chars("Hi πŸŽ‚") == [72, 105, 32, 127874]
+
+    assert m.string_view_return() == "utf8 secret πŸŽ‚"
+    assert m.string_view16_return() == "utf16 secret πŸŽ‚"
+    assert m.string_view32_return() == "utf32 secret πŸŽ‚"
+
+    with capture:
+        m.string_view_print("Hi")
+        m.string_view_print("utf8 πŸŽ‚")
+        m.string_view16_print("utf16 πŸŽ‚")
+        m.string_view32_print("utf32 πŸŽ‚")
+    assert capture == """
+        Hi 2
+        utf8 πŸŽ‚ 9
+        utf16 πŸŽ‚ 8
+        utf32 πŸŽ‚ 7
+    """
+
+    with capture:
+        m.string_view_print("Hi, ascii")
+        m.string_view_print("Hi, utf8 πŸŽ‚")
+        m.string_view16_print("Hi, utf16 πŸŽ‚")
+        m.string_view32_print("Hi, utf32 πŸŽ‚")
+    assert capture == """
+        Hi, ascii 9
+        Hi, utf8 πŸŽ‚ 13
+        Hi, utf16 πŸŽ‚ 12
+        Hi, utf32 πŸŽ‚ 11
+    """
+
+
+def test_tuple(doc):
+    """std::pair <-> tuple & std::tuple <-> tuple"""
+    assert m.pair_passthrough((True, "test")) == ("test", True)
+    assert m.tuple_passthrough((True, "test", 5)) == (5, "test", True)
+    # Any sequence can be cast to a std::pair or std::tuple
+    assert m.pair_passthrough([True, "test"]) == ("test", True)
+    assert m.tuple_passthrough([True, "test", 5]) == (5, "test", True)
+
+    assert doc(m.pair_passthrough) == """
+        pair_passthrough(arg0: Tuple[bool, str]) -> Tuple[str, bool]
+
+        Return a pair in reversed order
+    """
+    assert doc(m.tuple_passthrough) == """
+        tuple_passthrough(arg0: Tuple[bool, str, int]) -> Tuple[int, str, bool]
+
+        Return a triple in reversed order
+    """
+
+
+def test_builtins_cast_return_none():
+    """Casters produced with PYBIND11_TYPE_CASTER() should convert nullptr to None"""
+    assert m.return_none_string() is None
+    assert m.return_none_char() is None
+    assert m.return_none_bool() is None
+    assert m.return_none_int() is None
+    assert m.return_none_float() is None
+
+
+def test_none_deferred():
+    """None passed as various argument types should defer to other overloads"""
+    assert not m.defer_none_cstring("abc")
+    assert m.defer_none_cstring(None)
+    assert not m.defer_none_custom(UserType())
+    assert m.defer_none_custom(None)
+    assert m.nodefer_none_void(None)
+
+
+def test_void_caster():
+    assert m.load_nullptr_t(None) is None
+    assert m.cast_nullptr_t() is None
+
+
+def test_reference_wrapper():
+    """std::reference_wrapper for builtin and user types"""
+    assert m.refwrap_builtin(42) == 420
+    assert m.refwrap_usertype(UserType(42)) == 42
+
+    with pytest.raises(TypeError) as excinfo:
+        m.refwrap_builtin(None)
+    assert "incompatible function arguments" in str(excinfo.value)
+
+    with pytest.raises(TypeError) as excinfo:
+        m.refwrap_usertype(None)
+    assert "incompatible function arguments" in str(excinfo.value)
+
+    a1 = m.refwrap_list(copy=True)
+    a2 = m.refwrap_list(copy=True)
+    assert [x.value for x in a1] == [2, 3]
+    assert [x.value for x in a2] == [2, 3]
+    assert not a1[0] is a2[0] and not a1[1] is a2[1]
+
+    b1 = m.refwrap_list(copy=False)
+    b2 = m.refwrap_list(copy=False)
+    assert [x.value for x in b1] == [1, 2]
+    assert [x.value for x in b2] == [1, 2]
+    assert b1[0] is b2[0] and b1[1] is b2[1]
+
+    assert m.refwrap_iiw(IncType(5)) == 5
+    assert m.refwrap_call_iiw(IncType(10), m.refwrap_iiw) == [10, 10, 10, 10]
+
+
+def test_complex_cast():
+    """std::complex casts"""
+    assert m.complex_cast(1) == "1.0"
+    assert m.complex_cast(2j) == "(0.0, 2.0)"
diff --git a/tests/test_callbacks.cpp b/tests/test_callbacks.cpp
index 4111008..f26f6c3 100644
--- a/tests/test_callbacks.cpp
+++ b/tests/test_callbacks.cpp
@@ -71,9 +71,6 @@
     }
 };
 
-/// Something to trigger a conversion error
-struct Unregistered {};
-
 class AbstractBase {
 public:
   virtual unsigned int func() = 0;
@@ -144,11 +141,11 @@
     });
 
     m.def("test_arg_conversion_error1", [](py::function f) {
-        f(234, Unregistered(), "kw"_a=567);
+        f(234, UnregisteredType(), "kw"_a=567);
     });
 
     m.def("test_arg_conversion_error2", [](py::function f) {
-        f(234, "expected_name"_a=Unregistered(), "kw"_a=567);
+        f(234, "expected_name"_a=UnregisteredType(), "kw"_a=567);
     });
 
     /* Test cleanup of lambda closure */
diff --git a/tests/test_class_args.cpp b/tests/test_class.cpp
similarity index 86%
rename from tests/test_class_args.cpp
rename to tests/test_class.cpp
index e18b39d..1af9740 100644
--- a/tests/test_class_args.cpp
+++ b/tests/test_class.cpp
@@ -1,5 +1,5 @@
 /*
-    tests/test_class_args.cpp -- tests that various way of defining a class work
+    tests/test_class.cpp -- test py::class_ definitions and basic functionality
 
     Copyright (c) 2016 Wenzel Jakob <wenzel.jakob@epfl.ch>
 
@@ -8,7 +8,22 @@
 */
 
 #include "pybind11_tests.h"
+#include "constructor_stats.h"
 
+TEST_SUBMODULE(class_, m) {
+    // test_instance
+    struct NoConstructor {
+        static NoConstructor *new_instance() {
+            auto *ptr = new NoConstructor();
+            print_created(ptr, "via new_instance");
+            return ptr;
+        }
+        ~NoConstructor() { print_destroyed(this); }
+    };
+
+    py::class_<NoConstructor>(m, "NoConstructor")
+        .def_static("new_instance", &NoConstructor::new_instance, "Return an instance");
+}
 
 template <int N> class BreaksBase {};
 template <int N> class BreaksTramp : public BreaksBase<N> {};
@@ -61,8 +76,3 @@
 //template <> struct BreaksBase<-8> : BreaksBase<-6>, BreaksBase<-7> {};
 //typedef py::class_<BreaksBase<-8>, BreaksBase<-6>, BreaksBase<-7>> Breaks8;
 //CHECK_BROKEN(8);
-
-test_initializer class_args([](py::module &m) {
-    // Just test that this compiled okay
-    m.def("class_args_noop", []() {});
-});
diff --git a/tests/test_class.py b/tests/test_class.py
new file mode 100644
index 0000000..6e84913
--- /dev/null
+++ b/tests/test_class.py
@@ -0,0 +1,44 @@
+import pytest
+
+from pybind11_tests import class_ as m
+from pybind11_tests import UserType, ConstructorStats
+
+
+def test_repr():
+    # In Python 3.3+, repr() accesses __qualname__
+    assert "pybind11_type" in repr(type(UserType))
+    assert "UserType" in repr(UserType)
+
+
+def test_instance(msg):
+    with pytest.raises(TypeError) as excinfo:
+        m.NoConstructor()
+    assert msg(excinfo.value) == "m.class_.NoConstructor: No constructor defined!"
+
+    instance = m.NoConstructor.new_instance()
+
+    cstats = ConstructorStats.get(m.NoConstructor)
+    assert cstats.alive() == 1
+    del instance
+    assert cstats.alive() == 0
+
+
+def test_docstrings(doc):
+    assert doc(UserType) == "A `py::class_` type for testing"
+    assert UserType.__name__ == "UserType"
+    assert UserType.__module__ == "pybind11_tests"
+    assert UserType.get_value.__name__ == "get_value"
+    assert UserType.get_value.__module__ == "pybind11_tests"
+
+    assert doc(UserType.get_value) == """
+        get_value(self: m.UserType) -> int
+
+        Get value using a method
+    """
+    assert doc(UserType.value) == "Get value using a property"
+
+    assert doc(m.NoConstructor.new_instance) == """
+        new_instance() -> m.class_.NoConstructor
+
+        Return an instance
+    """
diff --git a/tests/test_class_args.py b/tests/test_class_args.py
deleted file mode 100644
index 40cbcec..0000000
--- a/tests/test_class_args.py
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-def test_class_args():
-    """There's basically nothing to test here; just make sure the code compiled
-    and declared its definition
-    """
-    from pybind11_tests import class_args_noop
-    class_args_noop()
diff --git a/tests/test_exceptions.cpp b/tests/test_exceptions.cpp
index ea6bdb9..01efc70 100644
--- a/tests/test_exceptions.cpp
+++ b/tests/test_exceptions.cpp
@@ -108,6 +108,10 @@
 };
 
 test_initializer custom_exceptions([](py::module &m) {
+    m.def("throw_std_exception", []() {
+        throw std::runtime_error("This exception was intentionally thrown.");
+    });
+
     // make a new custom exception and use it as a translation target
     static py::exception<MyException> ex(m, "MyException");
     py::register_exception_translator([](std::exception_ptr p) {
diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py
index 887ba64..15d4787 100644
--- a/tests/test_exceptions.py
+++ b/tests/test_exceptions.py
@@ -1,6 +1,14 @@
 import pytest
 
 
+def test_std_exception(msg):
+    from pybind11_tests import throw_std_exception
+
+    with pytest.raises(RuntimeError) as excinfo:
+        throw_std_exception()
+    assert msg(excinfo.value) == "This exception was intentionally thrown."
+
+
 def test_error_already_set(msg):
     from pybind11_tests import throw_already_set
 
diff --git a/tests/test_inheritance.cpp b/tests/test_inheritance.cpp
index 6129b3f..fce537d 100644
--- a/tests/test_inheritance.cpp
+++ b/tests/test_inheritance.cpp
@@ -97,8 +97,6 @@
     m.def("return_none", []() -> BaseClass* { return nullptr; });
 
     m.def("test_isinstance", [](py::list l) {
-        struct Unregistered { }; // checks missing type_info code path
-
         return py::make_tuple(
             py::isinstance<py::tuple>(l[0]),
             py::isinstance<py::dict>(l[1]),
@@ -106,7 +104,7 @@
             py::isinstance<Pet>(l[3]),
             py::isinstance<Dog>(l[4]),
             py::isinstance<Rabbit>(l[5]),
-            py::isinstance<Unregistered>(l[6])
+            py::isinstance<UnregisteredType>(l[6])
         );
     });
 
diff --git a/tests/test_modules.py b/tests/test_modules.py
index 1cb177f..17c00c8 100644
--- a/tests/test_modules.py
+++ b/tests/test_modules.py
@@ -59,6 +59,7 @@
     import pybind11_tests
     import pydoc
 
+    assert pybind11_tests.__name__ == "pybind11_tests"
     assert pybind11_tests.__doc__ == "pybind11 test module"
     assert pydoc.text.docmodule(pybind11_tests)
 
diff --git a/tests/test_python_types.cpp b/tests/test_python_types.cpp
deleted file mode 100644
index 46a23ee..0000000
--- a/tests/test_python_types.cpp
+++ /dev/null
@@ -1,695 +0,0 @@
-/*
-    tests/test_python_types.cpp -- singleton design pattern, static functions and
-    variables, passing and interacting with Python types
-
-    Copyright (c) 2016 Wenzel Jakob <wenzel.jakob@epfl.ch>
-
-    All rights reserved. Use of this source code is governed by a
-    BSD-style license that can be found in the LICENSE file.
-*/
-
-#include "pybind11_tests.h"
-#include "constructor_stats.h"
-#include <pybind11/stl.h>
-#include <pybind11/complex.h>
-
-#ifdef _WIN32
-#  include <io.h>
-#  include <fcntl.h>
-#endif
-
-#if defined(_MSC_VER)
-#  pragma warning(push)
-#  pragma warning(disable: 4127) // warning C4127: Conditional expression is constant
-#endif
-
-class ExamplePythonTypes {
-public:
-    static ExamplePythonTypes *new_instance() {
-        auto *ptr = new ExamplePythonTypes();
-        print_created(ptr, "via new_instance");
-        return ptr;
-    }
-    ~ExamplePythonTypes() { print_destroyed(this); }
-
-    /* Create and return a Python dictionary */
-    py::dict get_dict() {
-        py::dict dict;
-        dict[py::str("key")] = py::str("value");
-        return dict;
-    }
-
-    /* Create and return a Python set */
-    py::set get_set() {
-        py::set set;
-        set.add(py::str("key1"));
-        set.add("key2");
-        set.add(std::string("key3"));
-        return set;
-    }
-
-    /* Create and return a C++ dictionary */
-    std::map<std::string, std::string> get_dict_2() {
-        std::map<std::string, std::string> result;
-        result["key"] = "value";
-        return result;
-    }
-
-    /* Create and return a C++ set */
-    std::set<std::string> get_set_2() {
-        std::set<std::string> result;
-        result.insert("key1");
-        result.insert("key2");
-        return result;
-    }
-
-    /* Create, manipulate, and return a Python list */
-    py::list get_list() {
-        py::list list;
-        list.append("value");
-        py::print("Entry at position 0:", list[0]);
-        list[0] = py::str("overwritten");
-        return list;
-    }
-
-    /* C++ STL data types are automatically casted */
-    std::vector<std::wstring> get_list_2() {
-        std::vector<std::wstring> list;
-        list.push_back(L"value");
-        return list;
-    }
-
-    /* C++ STL data types are automatically casted */
-    std::array<std::string, 2> get_array() {
-        return std::array<std::string, 2> {{ "array entry 1" , "array entry 2"}};
-    }
-
-    std::valarray<int> get_valarray() {
-        return std::valarray<int>({ 1, 4, 9 });
-    }
-
-    /* Easily iterate over a dictionary using a C++11 range-based for loop */
-    void print_dict(py::dict dict) {
-        for (auto item : dict)
-            py::print("key: {}, value={}"_s.format(item.first, item.second));
-    }
-
-    /* Easily iterate over a set using a C++11 range-based for loop */
-    void print_set(py::set set) {
-        for (auto item : set)
-            py::print("key:", item);
-    }
-
-    /* Easily iterate over a list using a C++11 range-based for loop */
-    void print_list(py::list list) {
-        int index = 0;
-        for (auto item : list)
-            py::print("list item {}: {}"_s.format(index++, item));
-    }
-
-    /* STL data types (such as maps) are automatically casted from Python */
-    void print_dict_2(const std::map<std::string, std::string> &dict) {
-        for (auto item : dict)
-            py::print("key: {}, value={}"_s.format(item.first, item.second));
-    }
-
-    /* STL data types (such as sets) are automatically casted from Python */
-    void print_set_2(const std::set<std::string> &set) {
-        for (auto item : set)
-            py::print("key:", item);
-    }
-
-    /* STL data types (such as vectors) are automatically casted from Python */
-    void print_list_2(std::vector<std::wstring> &list) {
-        int index = 0;
-        for (auto item : list)
-            py::print("list item {}: {}"_s.format(index++, item));
-    }
-
-    /* pybind automatically translates between C++11 and Python tuples */
-    std::pair<std::string, bool> pair_passthrough(std::pair<bool, std::string> input) {
-        return std::make_pair(input.second, input.first);
-    }
-
-    /* pybind automatically translates between C++11 and Python tuples */
-    std::tuple<int, std::string, bool> tuple_passthrough(std::tuple<bool, std::string, int> input) {
-        return std::make_tuple(std::get<2>(input), std::get<1>(input), std::get<0>(input));
-    }
-
-    /* STL data types (such as arrays) are automatically casted from Python */
-    void print_array(std::array<std::string, 2> &array) {
-        int index = 0;
-        for (auto item : array)
-            py::print("array item {}: {}"_s.format(index++, item));
-    }
-
-    void print_valarray(std::valarray<int> &varray) {
-        int index = 0;
-        for (auto item : varray)
-            py::print("valarray item {}: {}"_s.format(index++, item));
-    }
-
-    void throw_exception() {
-        throw std::runtime_error("This exception was intentionally thrown.");
-    }
-
-    py::bytes get_bytes_from_string() {
-        return (py::bytes) std::string("foo");
-    }
-
-    py::bytes get_bytes_from_str() {
-        return (py::bytes) py::str("bar", 3);
-    }
-
-    py::str get_str_from_string() {
-        return (py::str) std::string("baz");
-    }
-
-    py::str get_str_from_bytes() {
-        return (py::str) py::bytes("boo", 3);
-    }
-
-    void test_print(const py::object& obj) {
-        py::print(py::str(obj));
-        py::print(py::repr(obj));
-    }
-
-    static int value;
-    static const int value2;
-};
-
-int ExamplePythonTypes::value = 0;
-const int ExamplePythonTypes::value2 = 5;
-
-struct MoveOutContainer {
-    struct Value { int value; };
-
-    std::list<Value> move_list() const { return {{0}, {1}, {2}}; }
-};
-
-struct UnregisteredType { };
-
-// Class that can be move- and copy-constructed, but not assigned
-struct NoAssign {
-    int value;
-
-    explicit NoAssign(int value = 0) : value(value) {}
-    NoAssign(const NoAssign &) = default;
-    NoAssign(NoAssign &&) = default;
-
-    NoAssign &operator=(const NoAssign &) = delete;
-    NoAssign &operator=(NoAssign &&) = delete;
-};
-
-// Increments on copy
-struct IncrIntWrapper {
-    int i;
-    IncrIntWrapper(int i) : i(i) {}
-    IncrIntWrapper(const IncrIntWrapper &copy) : i(copy.i + 1) {}
-};
-
-std::vector<std::reference_wrapper<IncrIntWrapper>> incr_int_wrappers() {
-    static IncrIntWrapper x1(1), x2(2);
-    std::vector<std::reference_wrapper<IncrIntWrapper>> r;
-    r.emplace_back(x1);
-    r.emplace_back(x2);
-    return r;
-};
-
-/// Issue #528: templated constructor
-struct TplCtorClass {
-    template <typename T> TplCtorClass(const T &) { }
-    bool operator==(const TplCtorClass &) const { return true; }
-};
-
-namespace std {
-    template <>
-    struct hash<TplCtorClass> { size_t operator()(const TplCtorClass &) const { return 0; } };
-}
-
-test_initializer python_types([](py::module &m) {
-    /* No constructor is explicitly defined below. An exception is raised when
-       trying to construct it directly from Python */
-    py::class_<ExamplePythonTypes>(m, "ExamplePythonTypes", "Example 2 documentation")
-        .def("get_dict", &ExamplePythonTypes::get_dict, "Return a Python dictionary")
-        .def("get_dict_2", &ExamplePythonTypes::get_dict_2, "Return a C++ dictionary")
-        .def("get_list", &ExamplePythonTypes::get_list, "Return a Python list")
-        .def("get_list_2", &ExamplePythonTypes::get_list_2, "Return a C++ list")
-        .def("get_set", &ExamplePythonTypes::get_set, "Return a Python set")
-        .def("get_set2", &ExamplePythonTypes::get_set_2, "Return a C++ set")
-        .def("get_array", &ExamplePythonTypes::get_array, "Return a C++ array")
-        .def("get_valarray", &ExamplePythonTypes::get_valarray, "Return a C++ valarray")
-        .def("print_dict", &ExamplePythonTypes::print_dict, "Print entries of a Python dictionary")
-        .def("print_dict_2", &ExamplePythonTypes::print_dict_2, "Print entries of a C++ dictionary")
-        .def("print_set", &ExamplePythonTypes::print_set, "Print entries of a Python set")
-        .def("print_set_2", &ExamplePythonTypes::print_set_2, "Print entries of a C++ set")
-        .def("print_list", &ExamplePythonTypes::print_list, "Print entries of a Python list")
-        .def("print_list_2", &ExamplePythonTypes::print_list_2, "Print entries of a C++ list")
-        .def("print_array", &ExamplePythonTypes::print_array, "Print entries of a C++ array")
-        .def("print_valarray", &ExamplePythonTypes::print_valarray, "Print entries of a C++ valarray")
-        .def("pair_passthrough", &ExamplePythonTypes::pair_passthrough, "Return a pair in reversed order")
-        .def("tuple_passthrough", &ExamplePythonTypes::tuple_passthrough, "Return a triple in reversed order")
-        .def("throw_exception", &ExamplePythonTypes::throw_exception, "Throw an exception")
-        .def("get_bytes_from_string", &ExamplePythonTypes::get_bytes_from_string, "py::bytes from std::string")
-        .def("get_bytes_from_str", &ExamplePythonTypes::get_bytes_from_str, "py::bytes from py::str")
-        .def("get_str_from_string", &ExamplePythonTypes::get_str_from_string, "py::str from std::string")
-        .def("get_str_from_bytes", &ExamplePythonTypes::get_str_from_bytes, "py::str from py::bytes")
-        .def("test_print", &ExamplePythonTypes::test_print, "test the print function")
-        .def_static("new_instance", &ExamplePythonTypes::new_instance, "Return an instance")
-        .def_readwrite_static("value", &ExamplePythonTypes::value, "Static value member")
-        .def_readonly_static("value2", &ExamplePythonTypes::value2, "Static value member (readonly)");
-
-    m.def("test_print_function", []() {
-        py::print("Hello, World!");
-        py::print(1, 2.0, "three", true, std::string("-- multiple args"));
-        auto args = py::make_tuple("and", "a", "custom", "separator");
-        py::print("*args", *args, "sep"_a="-");
-        py::print("no new line here", "end"_a=" -- ");
-        py::print("next print");
-
-        auto py_stderr = py::module::import("sys").attr("stderr");
-        py::print("this goes to stderr", "file"_a=py_stderr);
-
-        py::print("flush", "flush"_a=true);
-
-        py::print("{a} + {b} = {c}"_s.format("a"_a="py::print", "b"_a="str.format", "c"_a="this"));
-    });
-
-    py::class_<NoAssign>(m, "NoAssign", "Class with no C++ assignment operators")
-        .def(py::init<>())
-        .def(py::init<int>());
-
-    m.def("test_print_failure", []() { py::print(42, UnregisteredType()); });
-#if !defined(NDEBUG)
-    m.attr("debug_enabled") = true;
-#else
-    m.attr("debug_enabled") = false;
-#endif
-
-    m.def("test_str_format", []() {
-        auto s1 = "{} + {} = {}"_s.format(1, 2, 3);
-        auto s2 = "{a} + {b} = {c}"_s.format("a"_a=1, "b"_a=2, "c"_a=3);
-        return py::make_tuple(s1, s2);
-    });
-
-    m.def("test_dict_keyword_constructor", []() {
-        auto d1 = py::dict("x"_a=1, "y"_a=2);
-        auto d2 = py::dict("z"_a=3, **d1);
-        return d2;
-    });
-
-    m.def("test_accessor_api", [](py::object o) {
-        auto d = py::dict();
-
-        d["basic_attr"] = o.attr("basic_attr");
-
-        auto l = py::list();
-        for (const auto &item : o.attr("begin_end")) {
-            l.append(item);
-        }
-        d["begin_end"] = l;
-
-        d["operator[object]"] = o.attr("d")["operator[object]"_s];
-        d["operator[char *]"] = o.attr("d")["operator[char *]"];
-
-        d["attr(object)"] = o.attr("sub").attr("attr_obj");
-        d["attr(char *)"] = o.attr("sub").attr("attr_char");
-        try {
-            o.attr("sub").attr("missing").ptr();
-        } catch (const py::error_already_set &) {
-            d["missing_attr_ptr"] = "raised"_s;
-        }
-        try {
-            o.attr("missing").attr("doesn't matter");
-        } catch (const py::error_already_set &) {
-            d["missing_attr_chain"] = "raised"_s;
-        }
-
-        d["is_none"] = o.attr("basic_attr").is_none();
-
-        d["operator()"] = o.attr("func")(1);
-        d["operator*"] = o.attr("func")(*o.attr("begin_end"));
-
-        return d;
-    });
-
-    m.def("test_tuple_accessor", [](py::tuple existing_t) {
-        try {
-            existing_t[0] = 1;
-        } catch (const py::error_already_set &) {
-            // --> Python system error
-            // Only new tuples (refcount == 1) are mutable
-            auto new_t = py::tuple(3);
-            for (size_t i = 0; i < new_t.size(); ++i) {
-                new_t[i] = i;
-            }
-            return new_t;
-        }
-        return py::tuple();
-    });
-
-    m.def("test_accessor_assignment", []() {
-        auto l = py::list(1);
-        l[0] = 0;
-
-        auto d = py::dict();
-        d["get"] = l[0];
-        auto var = l[0];
-        d["deferred_get"] = var;
-        l[0] = 1;
-        d["set"] = l[0];
-        var = 99; // this assignment should not overwrite l[0]
-        d["deferred_set"] = l[0];
-        d["var"] = var;
-
-        return d;
-    });
-
-    bool has_optional = false, has_exp_optional = false;
-#ifdef PYBIND11_HAS_OPTIONAL
-    has_optional = true;
-    using opt_int = std::optional<int>;
-    using opt_no_assign = std::optional<NoAssign>;
-    m.def("double_or_zero", [](const opt_int& x) -> int {
-        return x.value_or(0) * 2;
-    });
-    m.def("half_or_none", [](int x) -> opt_int {
-        return x ? opt_int(x / 2) : opt_int();
-    });
-    m.def("test_nullopt", [](opt_int x) {
-        return x.value_or(42);
-    }, py::arg_v("x", std::nullopt, "None"));
-    m.def("test_no_assign", [](const opt_no_assign &x) {
-        return x ? x->value : 42;
-    }, py::arg_v("x", std::nullopt, "None"));
-#endif
-
-#ifdef PYBIND11_HAS_EXP_OPTIONAL
-    has_exp_optional = true;
-    using exp_opt_int = std::experimental::optional<int>;
-    using exp_opt_no_assign = std::experimental::optional<NoAssign>;
-    m.def("double_or_zero_exp", [](const exp_opt_int& x) -> int {
-        return x.value_or(0) * 2;
-    });
-    m.def("half_or_none_exp", [](int x) -> exp_opt_int {
-        return x ? exp_opt_int(x / 2) : exp_opt_int();
-    });
-    m.def("test_nullopt_exp", [](exp_opt_int x) {
-        return x.value_or(42);
-    }, py::arg_v("x", std::experimental::nullopt, "None"));
-    m.def("test_no_assign_exp", [](const exp_opt_no_assign &x) {
-        return x ? x->value : 42;
-    }, py::arg_v("x", std::experimental::nullopt, "None"));
-#endif
-
-    m.attr("has_optional") = has_optional;
-    m.attr("has_exp_optional") = has_exp_optional;
-
-#ifdef PYBIND11_HAS_VARIANT
-    struct visitor {
-        const char *operator()(int) { return "int"; }
-        const char *operator()(std::string) { return "std::string"; }
-        const char *operator()(double) { return "double"; }
-        const char *operator()(std::nullptr_t) { return "std::nullptr_t"; }
-    };
-
-    m.def("load_variant", [](std::variant<int, std::string, double, std::nullptr_t> v) {
-        return std::visit(visitor(), v);
-    });
-
-    m.def("load_variant_2pass", [](std::variant<double, int> v) {
-        return std::visit(visitor(), v);
-    });
-
-    m.def("cast_variant", []() {
-        using V = std::variant<int, std::string>;
-        return py::make_tuple(V(5), V("Hello"));
-    });
-#endif
-
-    m.def("test_default_constructors", []() {
-        return py::dict(
-            "str"_a=py::str(),
-            "bool"_a=py::bool_(),
-            "int"_a=py::int_(),
-            "float"_a=py::float_(),
-            "tuple"_a=py::tuple(),
-            "list"_a=py::list(),
-            "dict"_a=py::dict(),
-            "set"_a=py::set()
-        );
-    });
-
-    m.def("test_converting_constructors", [](py::dict d) {
-        return py::dict(
-            "str"_a=py::str(d["str"]),
-            "bool"_a=py::bool_(d["bool"]),
-            "int"_a=py::int_(d["int"]),
-            "float"_a=py::float_(d["float"]),
-            "tuple"_a=py::tuple(d["tuple"]),
-            "list"_a=py::list(d["list"]),
-            "dict"_a=py::dict(d["dict"]),
-            "set"_a=py::set(d["set"]),
-            "memoryview"_a=py::memoryview(d["memoryview"])
-        );
-    });
-
-    m.def("test_cast_functions", [](py::dict d) {
-        // When converting between Python types, obj.cast<T>() should be the same as T(obj)
-        return py::dict(
-            "str"_a=d["str"].cast<py::str>(),
-            "bool"_a=d["bool"].cast<py::bool_>(),
-            "int"_a=d["int"].cast<py::int_>(),
-            "float"_a=d["float"].cast<py::float_>(),
-            "tuple"_a=d["tuple"].cast<py::tuple>(),
-            "list"_a=d["list"].cast<py::list>(),
-            "dict"_a=d["dict"].cast<py::dict>(),
-            "set"_a=d["set"].cast<py::set>(),
-            "memoryview"_a=d["memoryview"].cast<py::memoryview>()
-        );
-    });
-
-    py::class_<MoveOutContainer::Value>(m, "MoveOutContainerValue")
-        .def_readonly("value", &MoveOutContainer::Value::value);
-
-    py::class_<MoveOutContainer>(m, "MoveOutContainer")
-        .def(py::init<>())
-        .def_property_readonly("move_list", &MoveOutContainer::move_list);
-
-    m.def("get_implicit_casting", []() {
-        py::dict d;
-        d["char*_i1"] = "abc";
-        const char *c2 = "abc";
-        d["char*_i2"] = c2;
-        d["char*_e"] = py::cast(c2);
-        d["char*_p"] = py::str(c2);
-
-        d["int_i1"] = 42;
-        int i = 42;
-        d["int_i2"] = i;
-        i++;
-        d["int_e"] = py::cast(i);
-        i++;
-        d["int_p"] = py::int_(i);
-
-        d["str_i1"] = std::string("str");
-        std::string s2("str1");
-        d["str_i2"] = s2;
-        s2[3] = '2';
-        d["str_e"] = py::cast(s2);
-        s2[3] = '3';
-        d["str_p"] = py::str(s2);
-
-        py::list l(2);
-        l[0] = 3;
-        l[1] = py::cast(6);
-        l.append(9);
-        l.append(py::cast(12));
-        l.append(py::int_(15));
-
-        return py::dict(
-            "d"_a=d,
-            "l"_a=l
-        );
-    });
-
-    m.def("string_roundtrip", [](const char *s) { return s; });
-
-    // Some test characters in utf16 and utf32 encodings.  The last one (the 𝐀) contains a null byte
-    char32_t a32 = 0x61 /*a*/, z32 = 0x7a /*z*/, ib32 = 0x203d /*β€½*/, cake32 = 0x1f382 /*πŸŽ‚*/,              mathbfA32 = 0x1d400 /*𝐀*/;
-    char16_t b16 = 0x62 /*b*/, z16 = 0x7a,       ib16 = 0x203d,       cake16_1 = 0xd83c, cake16_2 = 0xdf82, mathbfA16_1 = 0xd835, mathbfA16_2 = 0xdc00;
-    std::wstring wstr;
-    wstr.push_back(0x61); // a
-    wstr.push_back(0x2e18); // ⸘
-    if (sizeof(wchar_t) == 2) { wstr.push_back(mathbfA16_1); wstr.push_back(mathbfA16_2); } // 𝐀, utf16
-    else { wstr.push_back((wchar_t) mathbfA32); } // 𝐀, utf32
-    wstr.push_back(0x7a); // z
-
-    m.def("good_utf8_string", []() { return std::string(u8"Say utf8\u203d \U0001f382 \U0001d400"); }); // Say utf8β€½ πŸŽ‚ 𝐀
-    m.def("good_utf16_string", [=]() { return std::u16string({ b16, ib16, cake16_1, cake16_2, mathbfA16_1, mathbfA16_2, z16 }); }); // bβ€½πŸŽ‚π€z
-    m.def("good_utf32_string", [=]() { return std::u32string({ a32, mathbfA32, cake32, ib32, z32 }); }); // aπ€πŸŽ‚β€½z
-    m.def("good_wchar_string", [=]() { return wstr; }); // a‽𝐀z
-    m.def("bad_utf8_string", []()  { return std::string("abc\xd0" "def"); });
-    m.def("bad_utf16_string", [=]() { return std::u16string({ b16, char16_t(0xd800), z16 }); });
-    // Under Python 2.7, invalid unicode UTF-32 characters don't appear to trigger UnicodeDecodeError
-    if (PY_MAJOR_VERSION >= 3)
-        m.def("bad_utf32_string", [=]() { return std::u32string({ a32, char32_t(0xd800), z32 }); });
-    if (PY_MAJOR_VERSION >= 3 || sizeof(wchar_t) == 2)
-        m.def("bad_wchar_string", [=]() { return std::wstring({ wchar_t(0x61), wchar_t(0xd800) }); });
-    m.def("u8_Z", []() -> char { return 'Z'; });
-    m.def("u8_eacute", []() -> char { return '\xe9'; });
-    m.def("u16_ibang", [=]() -> char16_t { return ib16; });
-    m.def("u32_mathbfA", [=]() -> char32_t { return mathbfA32; });
-    m.def("wchar_heart", []() -> wchar_t { return 0x2665; });
-
-    m.attr("wchar_size") = py::cast(sizeof(wchar_t));
-    m.def("ord_char", [](char c) -> int { return static_cast<unsigned char>(c); });
-    m.def("ord_char16", [](char16_t c) -> uint16_t { return c; });
-    m.def("ord_char32", [](char32_t c) -> uint32_t { return c; });
-    m.def("ord_wchar", [](wchar_t c) -> int { return c; });
-
-    m.def("strlen", [](char *s) { return strlen(s); });
-    m.def("string_length", [](std::string s) { return s.length(); });
-
-    m.def("return_none_string", []() -> std::string * { return nullptr; });
-    m.def("return_none_char",   []() -> const char *  { return nullptr; });
-    m.def("return_none_bool",   []() -> bool *        { return nullptr; });
-    m.def("return_none_int",    []() -> int *         { return nullptr; });
-    m.def("return_none_float",  []() -> float *       { return nullptr; });
-
-    m.def("defer_none_cstring", [](char *) { return false; });
-    m.def("defer_none_cstring", [](py::none) { return true; });
-    m.def("defer_none_custom", [](ExamplePythonTypes *) { return false; });
-    m.def("defer_none_custom", [](py::none) { return true; });
-    // void and optional, however, don't defer:
-    m.def("nodefer_none_void", [](void *) { return true; });
-    m.def("nodefer_none_void", [](py::none) { return false; });
-#ifdef PYBIND11_HAS_OPTIONAL
-    m.def("nodefer_none_optional", [](std::optional<int>) { return true; });
-    m.def("nodefer_none_optional", [](py::none) { return false; });
-#endif
-
-#ifdef PYBIND11_HAS_STRING_VIEW
-    m.attr("has_string_view") = true;
-    m.def("string_view_print",   [](std::string_view s)    { py::print(s, s.size()); });
-    m.def("string_view16_print", [](std::u16string_view s) { py::print(s, s.size()); });
-    m.def("string_view32_print", [](std::u32string_view s) { py::print(s, s.size()); });
-    m.def("string_view_chars",   [](std::string_view s)    { py::list l; for (auto c : s) l.append((std::uint8_t) c); return l; });
-    m.def("string_view16_chars", [](std::u16string_view s) { py::list l; for (auto c : s) l.append((int) c); return l; });
-    m.def("string_view32_chars", [](std::u32string_view s) { py::list l; for (auto c : s) l.append((int) c); return l; });
-    m.def("string_view_return",   []() { return std::string_view(u8"utf8 secret \U0001f382"); });
-    m.def("string_view16_return", []() { return std::u16string_view(u"utf16 secret \U0001f382"); });
-    m.def("string_view32_return", []() { return std::u32string_view(U"utf32 secret \U0001f382"); });
-#else
-    m.attr("has_string_view") = false;
-#endif
-
-    m.def("return_capsule_with_destructor",
-        []() {
-            py::print("creating capsule");
-            return py::capsule([]() {
-                py::print("destructing capsule");
-            });
-        }
-    );
-
-    m.def("return_capsule_with_destructor_2",
-        []() {
-            py::print("creating capsule");
-            return py::capsule((void *) 1234, [](void *ptr) {
-                py::print("destructing capsule: {}"_s.format((size_t) ptr));
-            });
-        }
-    );
-
-    m.def("return_capsule_with_name_and_destructor_3",
-        []() {
-            py::print("creating capsule");
-            auto capsule=py::capsule((void *) 1234, "pointer type description",
-                [](PyObject *ptr) {
-                 if (ptr) {
-                    py::print("destructing capsule");
-                 }
-            });
-            auto name = capsule.name();
-            void *contents = capsule;
-            py::print("created capsule with name --{}-- and contents {}"_s.format(name,(size_t) contents));
-            return capsule;
-        }
-    );
-
-    m.def("load_nullptr_t", [](std::nullptr_t) {}); // not useful, but it should still compile
-    m.def("cast_nullptr_t", []() { return std::nullptr_t{}; });
-
-    struct IntWrapper { int i; IntWrapper(int i) : i(i) { } };
-    py::class_<IntWrapper>(m, "IntWrapper")
-        .def(py::init<int>())
-        .def("__repr__", [](const IntWrapper &p) { return "IntWrapper[" + std::to_string(p.i) + "]"; });
-
-    // #171: Can't return reference wrappers (or STL datastructures containing them)
-    // Also used to test #848: reference_wrapper shouldn't allow None
-    m.def("return_vec_of_reference_wrapper", [](std::reference_wrapper<IntWrapper> p4) {
-        IntWrapper *p1 = new IntWrapper{1};
-        IntWrapper *p2 = new IntWrapper{2};
-        IntWrapper *p3 = new IntWrapper{3};
-        std::vector<std::reference_wrapper<IntWrapper>> v;
-        v.push_back(std::ref(*p1));
-        v.push_back(std::ref(*p2));
-        v.push_back(std::ref(*p3));
-        v.push_back(p4);
-        return v;
-    });
-
-    // Reference-wrapper to non-generic type caster type:
-    m.def("refwrap_int", [](std::reference_wrapper<int> p) { return 10 * p.get(); });
-
-    // Not currently supported (std::pair caster has return-by-value cast operator);
-    // triggers static_assert failure.
-    //m.def("refwrap_pair", [](std::reference_wrapper<std::pair<int, int>>) { });
-
-    // Test that copying/referencing is working as expected with reference_wrappers:
-    py::class_<IncrIntWrapper>(m, "IncrIntWrapper")
-        .def(py::init<int>())
-        .def_readonly("i", &IncrIntWrapper::i);
-
-    m.def("refwrap_list_refs", []() {
-        py::list l;
-        for (auto &f : incr_int_wrappers()) l.append(py::cast(f, py::return_value_policy::reference));
-        return l;
-    });
-    m.def("refwrap_list_copies", []() {
-        py::list l;
-        for (auto &f : incr_int_wrappers()) l.append(py::cast(f, py::return_value_policy::copy));
-        return l;
-    });
-    m.def("refwrap_iiw", [](const IncrIntWrapper &w) { return w.i; });
-    m.def("refwrap_call_iiw", [](IncrIntWrapper &w, py::function f) {
-        py::list l;
-        l.append(f(std::ref(w)));
-        l.append(f(std::cref(w)));
-        IncrIntWrapper x(w.i);
-        l.append(f(std::ref(x)));
-        IncrIntWrapper y(w.i);
-        auto r3 = std::ref(y);
-        l.append(f(r3));
-        return l;
-    });
-
-    /// Issue #484: number conversion generates unhandled exceptions
-    m.def("test_complex", [](float x) { return "{}"_s.format(x); });
-    m.def("test_complex", [](std::complex<float> x) { return "({}, {})"_s.format(x.real(), x.imag()); });
-
-    /// Issue #528: templated constructor
-    m.def("tpl_ctor_vector", [](std::vector<TplCtorClass> &) {});
-    m.def("tpl_ctor_map", [](std::unordered_map<TplCtorClass, TplCtorClass> &) {});
-    m.def("tpl_ctor_set", [](std::unordered_set<TplCtorClass> &) {});
-#if defined(PYBIND11_HAS_OPTIONAL)
-    m.def("tpl_constr_optional", [](std::optional<TplCtorClass> &) {});
-#elif defined(PYBIND11_HAS_EXP_OPTIONAL)
-    m.def("tpl_constr_optional", [](std::experimental::optional<TplCtorClass> &) {});
-#endif
-});
-
-#if defined(_MSC_VER)
-#  pragma warning(pop)
-#endif
diff --git a/tests/test_python_types.py b/tests/test_python_types.py
deleted file mode 100644
index 7932602..0000000
--- a/tests/test_python_types.py
+++ /dev/null
@@ -1,715 +0,0 @@
-# Python < 3 needs this: coding=utf-8
-import pytest
-import pybind11_tests
-
-from pybind11_tests import (ExamplePythonTypes, ConstructorStats, has_optional, has_exp_optional,
-                            has_string_view)
-
-
-def test_repr():
-    # In Python 3.3+, repr() accesses __qualname__
-    assert "pybind11_type" in repr(type(ExamplePythonTypes))
-    assert "ExamplePythonTypes" in repr(ExamplePythonTypes)
-
-
-def test_static():
-    ExamplePythonTypes.value = 15
-    assert ExamplePythonTypes.value == 15
-    assert ExamplePythonTypes.value2 == 5
-
-    with pytest.raises(AttributeError) as excinfo:
-        ExamplePythonTypes.value2 = 15
-    assert str(excinfo.value) == "can't set attribute"
-
-
-def test_instance(capture):
-    with pytest.raises(TypeError) as excinfo:
-        ExamplePythonTypes()
-    assert str(excinfo.value) == "pybind11_tests.ExamplePythonTypes: No constructor defined!"
-
-    instance = ExamplePythonTypes.new_instance()
-
-    with capture:
-        dict_result = instance.get_dict()
-        dict_result['key2'] = 'value2'
-        instance.print_dict(dict_result)
-    assert capture.unordered == """
-        key: key, value=value
-        key: key2, value=value2
-    """
-    with capture:
-        dict_result = instance.get_dict_2()
-        dict_result['key2'] = 'value2'
-        instance.print_dict_2(dict_result)
-    assert capture.unordered == """
-        key: key, value=value
-        key: key2, value=value2
-    """
-    with capture:
-        set_result = instance.get_set()
-        set_result.add('key4')
-        instance.print_set(set_result)
-    assert capture.unordered == """
-        key: key1
-        key: key2
-        key: key3
-        key: key4
-    """
-    with capture:
-        set_result = instance.get_set2()
-        set_result.add('key3')
-        instance.print_set_2(set_result)
-    assert capture.unordered == """
-        key: key1
-        key: key2
-        key: key3
-    """
-    with capture:
-        list_result = instance.get_list()
-        list_result.append('value2')
-        instance.print_list(list_result)
-    assert capture.unordered == """
-        Entry at position 0: value
-        list item 0: overwritten
-        list item 1: value2
-    """
-    with capture:
-        list_result = instance.get_list_2()
-        list_result.append('value2')
-        instance.print_list_2(list_result)
-    assert capture.unordered == """
-        list item 0: value
-        list item 1: value2
-    """
-    with capture:
-        list_result = instance.get_list_2()
-        list_result.append('value2')
-        instance.print_list_2(tuple(list_result))
-    assert capture.unordered == """
-        list item 0: value
-        list item 1: value2
-    """
-    array_result = instance.get_array()
-    assert array_result == ['array entry 1', 'array entry 2']
-    with capture:
-        instance.print_array(array_result)
-    assert capture.unordered == """
-        array item 0: array entry 1
-        array item 1: array entry 2
-    """
-    varray_result = instance.get_valarray()
-    assert varray_result == [1, 4, 9]
-    with capture:
-        instance.print_valarray(varray_result)
-    assert capture.unordered == """
-        valarray item 0: 1
-        valarray item 1: 4
-        valarray item 2: 9
-    """
-    with pytest.raises(RuntimeError) as excinfo:
-        instance.throw_exception()
-    assert str(excinfo.value) == "This exception was intentionally thrown."
-
-    assert instance.pair_passthrough((True, "test")) == ("test", True)
-    assert instance.tuple_passthrough((True, "test", 5)) == (5, "test", True)
-    # Any sequence can be cast to a std::pair or std::tuple
-    assert instance.pair_passthrough([True, "test"]) == ("test", True)
-    assert instance.tuple_passthrough([True, "test", 5]) == (5, "test", True)
-
-    assert instance.get_bytes_from_string().decode() == "foo"
-    assert instance.get_bytes_from_str().decode() == "bar"
-    assert instance.get_str_from_string().encode().decode() == "baz"
-    assert instance.get_str_from_bytes().encode().decode() == "boo"
-
-    class A(object):
-        def __str__(self):
-            return "this is a str"
-
-        def __repr__(self):
-            return "this is a repr"
-
-    with capture:
-        instance.test_print(A())
-    assert capture == """
-        this is a str
-        this is a repr
-    """
-
-    cstats = ConstructorStats.get(ExamplePythonTypes)
-    assert cstats.alive() == 1
-    del instance
-    assert cstats.alive() == 0
-
-
-# PyPy does not seem to propagate the tp_docs field at the moment
-def test_class_docs(doc):
-    assert doc(ExamplePythonTypes) == "Example 2 documentation"
-
-
-def test_method_docs(doc):
-    assert doc(ExamplePythonTypes.get_dict) == """
-        get_dict(self: m.ExamplePythonTypes) -> dict
-
-        Return a Python dictionary
-    """
-    assert doc(ExamplePythonTypes.get_dict_2) == """
-        get_dict_2(self: m.ExamplePythonTypes) -> Dict[str, str]
-
-        Return a C++ dictionary
-    """
-    assert doc(ExamplePythonTypes.get_list) == """
-        get_list(self: m.ExamplePythonTypes) -> list
-
-        Return a Python list
-    """
-    assert doc(ExamplePythonTypes.get_list_2) == """
-        get_list_2(self: m.ExamplePythonTypes) -> List[str]
-
-        Return a C++ list
-    """
-    assert doc(ExamplePythonTypes.get_dict) == """
-        get_dict(self: m.ExamplePythonTypes) -> dict
-
-        Return a Python dictionary
-    """
-    assert doc(ExamplePythonTypes.get_set) == """
-        get_set(self: m.ExamplePythonTypes) -> set
-
-        Return a Python set
-    """
-    assert doc(ExamplePythonTypes.get_set2) == """
-        get_set2(self: m.ExamplePythonTypes) -> Set[str]
-
-        Return a C++ set
-    """
-    assert doc(ExamplePythonTypes.get_array) == """
-        get_array(self: m.ExamplePythonTypes) -> List[str[2]]
-
-        Return a C++ array
-    """
-    assert doc(ExamplePythonTypes.get_valarray) == """
-        get_valarray(self: m.ExamplePythonTypes) -> List[int]
-
-        Return a C++ valarray
-    """
-    assert doc(ExamplePythonTypes.print_dict) == """
-        print_dict(self: m.ExamplePythonTypes, arg0: dict) -> None
-
-        Print entries of a Python dictionary
-    """
-    assert doc(ExamplePythonTypes.print_dict_2) == """
-        print_dict_2(self: m.ExamplePythonTypes, arg0: Dict[str, str]) -> None
-
-        Print entries of a C++ dictionary
-    """
-    assert doc(ExamplePythonTypes.print_set) == """
-        print_set(self: m.ExamplePythonTypes, arg0: set) -> None
-
-        Print entries of a Python set
-    """
-    assert doc(ExamplePythonTypes.print_set_2) == """
-        print_set_2(self: m.ExamplePythonTypes, arg0: Set[str]) -> None
-
-        Print entries of a C++ set
-    """
-    assert doc(ExamplePythonTypes.print_list) == """
-        print_list(self: m.ExamplePythonTypes, arg0: list) -> None
-
-        Print entries of a Python list
-    """
-    assert doc(ExamplePythonTypes.print_list_2) == """
-        print_list_2(self: m.ExamplePythonTypes, arg0: List[str]) -> None
-
-        Print entries of a C++ list
-    """
-    assert doc(ExamplePythonTypes.print_array) == """
-        print_array(self: m.ExamplePythonTypes, arg0: List[str[2]]) -> None
-
-        Print entries of a C++ array
-    """
-    assert doc(ExamplePythonTypes.pair_passthrough) == """
-        pair_passthrough(self: m.ExamplePythonTypes, arg0: Tuple[bool, str]) -> Tuple[str, bool]
-
-        Return a pair in reversed order
-    """
-    assert doc(ExamplePythonTypes.tuple_passthrough) == """
-        tuple_passthrough(self: m.ExamplePythonTypes, arg0: Tuple[bool, str, int]) -> Tuple[int, str, bool]
-
-        Return a triple in reversed order
-    """  # noqa: E501 line too long
-    assert doc(ExamplePythonTypes.throw_exception) == """
-        throw_exception(self: m.ExamplePythonTypes) -> None
-
-        Throw an exception
-    """
-    assert doc(ExamplePythonTypes.new_instance) == """
-        new_instance() -> m.ExamplePythonTypes
-
-        Return an instance
-    """
-
-
-def test_module():
-    import pybind11_tests
-
-    assert pybind11_tests.__name__ == "pybind11_tests"
-    assert ExamplePythonTypes.__name__ == "ExamplePythonTypes"
-    assert ExamplePythonTypes.__module__ == "pybind11_tests"
-    assert ExamplePythonTypes.get_set.__name__ == "get_set"
-    assert ExamplePythonTypes.get_set.__module__ == "pybind11_tests"
-
-
-def test_print(capture):
-    from pybind11_tests import test_print_function, test_print_failure, debug_enabled
-
-    with capture:
-        test_print_function()
-    assert capture == """
-        Hello, World!
-        1 2.0 three True -- multiple args
-        *args-and-a-custom-separator
-        no new line here -- next print
-        flush
-        py::print + str.format = this
-    """
-    assert capture.stderr == "this goes to stderr"
-
-    with pytest.raises(RuntimeError) as excinfo:
-        test_print_failure()
-    assert str(excinfo.value) == "make_tuple(): unable to convert " + (
-        "argument of type 'UnregisteredType' to Python object"
-        if debug_enabled else
-        "arguments to Python object (compile in debug mode for details)"
-    )
-
-
-def test_str_api():
-    from pybind11_tests import test_str_format
-
-    s1, s2 = test_str_format()
-    assert s1 == "1 + 2 = 3"
-    assert s1 == s2
-
-
-def test_dict_api():
-    from pybind11_tests import test_dict_keyword_constructor
-
-    assert test_dict_keyword_constructor() == {"x": 1, "y": 2, "z": 3}
-
-
-def test_accessors():
-    from pybind11_tests import test_accessor_api, test_tuple_accessor, test_accessor_assignment
-
-    class SubTestObject:
-        attr_obj = 1
-        attr_char = 2
-
-    class TestObject:
-        basic_attr = 1
-        begin_end = [1, 2, 3]
-        d = {"operator[object]": 1, "operator[char *]": 2}
-        sub = SubTestObject()
-
-        def func(self, x, *args):
-            return self.basic_attr + x + sum(args)
-
-    d = test_accessor_api(TestObject())
-    assert d["basic_attr"] == 1
-    assert d["begin_end"] == [1, 2, 3]
-    assert d["operator[object]"] == 1
-    assert d["operator[char *]"] == 2
-    assert d["attr(object)"] == 1
-    assert d["attr(char *)"] == 2
-    assert d["missing_attr_ptr"] == "raised"
-    assert d["missing_attr_chain"] == "raised"
-    assert d["is_none"] is False
-    assert d["operator()"] == 2
-    assert d["operator*"] == 7
-
-    assert test_tuple_accessor(tuple()) == (0, 1, 2)
-
-    d = test_accessor_assignment()
-    assert d["get"] == 0
-    assert d["deferred_get"] == 0
-    assert d["set"] == 1
-    assert d["deferred_set"] == 1
-    assert d["var"] == 99
-
-
-@pytest.mark.skipif(not has_optional, reason='no <optional>')
-def test_optional():
-    from pybind11_tests import (double_or_zero, half_or_none, test_nullopt,
-                                test_no_assign, NoAssign)
-
-    assert double_or_zero(None) == 0
-    assert double_or_zero(42) == 84
-    pytest.raises(TypeError, double_or_zero, 'foo')
-
-    assert half_or_none(0) is None
-    assert half_or_none(42) == 21
-    pytest.raises(TypeError, half_or_none, 'foo')
-
-    assert test_nullopt() == 42
-    assert test_nullopt(None) == 42
-    assert test_nullopt(42) == 42
-    assert test_nullopt(43) == 43
-
-    assert test_no_assign() == 42
-    assert test_no_assign(None) == 42
-    assert test_no_assign(NoAssign(43)) == 43
-    pytest.raises(TypeError, test_no_assign, 43)
-
-
-@pytest.mark.skipif(not has_exp_optional, reason='no <experimental/optional>')
-def test_exp_optional():
-    from pybind11_tests import (double_or_zero_exp, half_or_none_exp, test_nullopt_exp,
-                                test_no_assign_exp, NoAssign)
-
-    assert double_or_zero_exp(None) == 0
-    assert double_or_zero_exp(42) == 84
-    pytest.raises(TypeError, double_or_zero_exp, 'foo')
-
-    assert half_or_none_exp(0) is None
-    assert half_or_none_exp(42) == 21
-    pytest.raises(TypeError, half_or_none_exp, 'foo')
-
-    assert test_nullopt_exp() == 42
-    assert test_nullopt_exp(None) == 42
-    assert test_nullopt_exp(42) == 42
-    assert test_nullopt_exp(43) == 43
-
-    assert test_no_assign_exp() == 42
-    assert test_no_assign_exp(None) == 42
-    assert test_no_assign_exp(NoAssign(43)) == 43
-    pytest.raises(TypeError, test_no_assign_exp, 43)
-
-
-@pytest.mark.skipif(not hasattr(pybind11_tests, "load_variant"), reason='no <variant>')
-def test_variant(doc):
-    from pybind11_tests import load_variant, load_variant_2pass, cast_variant
-
-    assert load_variant(1) == "int"
-    assert load_variant("1") == "std::string"
-    assert load_variant(1.0) == "double"
-    assert load_variant(None) == "std::nullptr_t"
-
-    assert load_variant_2pass(1) == "int"
-    assert load_variant_2pass(1.0) == "double"
-
-    assert cast_variant() == (5, "Hello")
-
-    assert doc(load_variant) == "load_variant(arg0: Union[int, str, float, None]) -> str"
-
-
-def test_constructors():
-    """C++ default and converting constructors are equivalent to type calls in Python"""
-    from pybind11_tests import (test_default_constructors, test_converting_constructors,
-                                test_cast_functions)
-
-    types = [str, bool, int, float, tuple, list, dict, set]
-    expected = {t.__name__: t() for t in types}
-    assert test_default_constructors() == expected
-
-    data = {
-        str: 42,
-        bool: "Not empty",
-        int: "42",
-        float: "+1e3",
-        tuple: range(3),
-        list: range(3),
-        dict: [("two", 2), ("one", 1), ("three", 3)],
-        set: [4, 4, 5, 6, 6, 6],
-        memoryview: b'abc'
-    }
-    inputs = {k.__name__: v for k, v in data.items()}
-    expected = {k.__name__: k(v) for k, v in data.items()}
-    assert test_converting_constructors(inputs) == expected
-    assert test_cast_functions(inputs) == expected
-
-
-def test_move_out_container():
-    """Properties use the `reference_internal` policy by default. If the underlying function
-    returns an rvalue, the policy is automatically changed to `move` to avoid referencing
-    a temporary. In case the return value is a container of user-defined types, the policy
-    also needs to be applied to the elements, not just the container."""
-    from pybind11_tests import MoveOutContainer
-
-    c = MoveOutContainer()
-    moved_out_list = c.move_list
-    assert [x.value for x in moved_out_list] == [0, 1, 2]
-
-
-def test_implicit_casting():
-    """Tests implicit casting when assigning or appending to dicts and lists."""
-    from pybind11_tests import get_implicit_casting
-
-    z = get_implicit_casting()
-    assert z['d'] == {
-        'char*_i1': 'abc', 'char*_i2': 'abc', 'char*_e': 'abc', 'char*_p': 'abc',
-        'str_i1': 'str', 'str_i2': 'str1', 'str_e': 'str2', 'str_p': 'str3',
-        'int_i1': 42, 'int_i2': 42, 'int_e': 43, 'int_p': 44
-    }
-    assert z['l'] == [3, 6, 9, 12, 15]
-
-
-def test_simple_string():
-    from pybind11_tests import string_roundtrip
-
-    assert string_roundtrip("const char *") == "const char *"
-
-
-def test_unicode_conversion():
-    """Tests unicode conversion and error reporting."""
-    import pybind11_tests
-    from pybind11_tests import (good_utf8_string, bad_utf8_string,
-                                good_utf16_string, bad_utf16_string,
-                                good_utf32_string,  # bad_utf32_string,
-                                good_wchar_string,  # bad_wchar_string,
-                                u8_Z, u8_eacute, u16_ibang, u32_mathbfA, wchar_heart)
-
-    assert good_utf8_string() == u"Say utf8β€½ πŸŽ‚ 𝐀"
-    assert good_utf16_string() == u"bβ€½πŸŽ‚π€z"
-    assert good_utf32_string() == u"aπ€πŸŽ‚β€½z"
-    assert good_wchar_string() == u"aβΈ˜π€z"
-
-    with pytest.raises(UnicodeDecodeError):
-        bad_utf8_string()
-
-    with pytest.raises(UnicodeDecodeError):
-        bad_utf16_string()
-
-    # These are provided only if they actually fail (they don't when 32-bit and under Python 2.7)
-    if hasattr(pybind11_tests, "bad_utf32_string"):
-        with pytest.raises(UnicodeDecodeError):
-            pybind11_tests.bad_utf32_string()
-    if hasattr(pybind11_tests, "bad_wchar_string"):
-        with pytest.raises(UnicodeDecodeError):
-            pybind11_tests.bad_wchar_string()
-
-    assert u8_Z() == 'Z'
-    assert u8_eacute() == u'é'
-    assert u16_ibang() == u'β€½'
-    assert u32_mathbfA() == u'𝐀'
-    assert wchar_heart() == u'♥'
-
-
-def test_single_char_arguments():
-    """Tests failures for passing invalid inputs to char-accepting functions"""
-    from pybind11_tests import ord_char, ord_char16, ord_char32, ord_wchar, wchar_size
-
-    def toobig_message(r):
-        return "Character code point not in range({0:#x})".format(r)
-    toolong_message = "Expected a character, but multi-character string found"
-
-    assert ord_char(u'a') == 0x61  # simple ASCII
-    assert ord_char(u'é') == 0xE9  # requires 2 bytes in utf-8, but can be stuffed in a char
-    with pytest.raises(ValueError) as excinfo:
-        assert ord_char(u'Δ€') == 0x100  # requires 2 bytes, doesn't fit in a char
-    assert str(excinfo.value) == toobig_message(0x100)
-    with pytest.raises(ValueError) as excinfo:
-        assert ord_char(u'ab')
-    assert str(excinfo.value) == toolong_message
-
-    assert ord_char16(u'a') == 0x61
-    assert ord_char16(u'é') == 0xE9
-    assert ord_char16(u'Δ€') == 0x100
-    assert ord_char16(u'β€½') == 0x203d
-    assert ord_char16(u'♥') == 0x2665
-    with pytest.raises(ValueError) as excinfo:
-        assert ord_char16(u'πŸŽ‚') == 0x1F382  # requires surrogate pair
-    assert str(excinfo.value) == toobig_message(0x10000)
-    with pytest.raises(ValueError) as excinfo:
-        assert ord_char16(u'aa')
-    assert str(excinfo.value) == toolong_message
-
-    assert ord_char32(u'a') == 0x61
-    assert ord_char32(u'é') == 0xE9
-    assert ord_char32(u'Δ€') == 0x100
-    assert ord_char32(u'β€½') == 0x203d
-    assert ord_char32(u'♥') == 0x2665
-    assert ord_char32(u'πŸŽ‚') == 0x1F382
-    with pytest.raises(ValueError) as excinfo:
-        assert ord_char32(u'aa')
-    assert str(excinfo.value) == toolong_message
-
-    assert ord_wchar(u'a') == 0x61
-    assert ord_wchar(u'é') == 0xE9
-    assert ord_wchar(u'Δ€') == 0x100
-    assert ord_wchar(u'β€½') == 0x203d
-    assert ord_wchar(u'♥') == 0x2665
-    if wchar_size == 2:
-        with pytest.raises(ValueError) as excinfo:
-            assert ord_wchar(u'πŸŽ‚') == 0x1F382  # requires surrogate pair
-        assert str(excinfo.value) == toobig_message(0x10000)
-    else:
-        assert ord_wchar(u'πŸŽ‚') == 0x1F382
-    with pytest.raises(ValueError) as excinfo:
-        assert ord_wchar(u'aa')
-    assert str(excinfo.value) == toolong_message
-
-
-def test_bytes_to_string():
-    """Tests the ability to pass bytes to C++ string-accepting functions.  Note that this is
-    one-way: the only way to return bytes to Python is via the pybind11::bytes class."""
-    # Issue #816
-    from pybind11_tests import strlen, string_length
-    import sys
-    byte = bytes if sys.version_info[0] < 3 else str
-
-    assert strlen(byte("hi")) == 2
-    assert string_length(byte("world")) == 5
-    assert string_length(byte("a\x00b")) == 3
-    assert strlen(byte("a\x00b")) == 1  # C-string limitation
-
-    # passing in a utf8 encoded string should work
-    assert string_length(u'πŸ’©'.encode("utf8")) == 4
-
-
-@pytest.mark.skipif(not has_string_view, reason='no <string_view>')
-def test_string_view(capture):
-    """Tests support for C++17 string_view arguments and return values"""
-    from pybind11_tests import (string_view_print, string_view16_print, string_view32_print,
-                                string_view_chars, string_view16_chars, string_view32_chars,
-                                string_view_return, string_view16_return, string_view32_return)
-
-    assert string_view_chars("Hi") == [72, 105]
-    assert string_view_chars("Hi πŸŽ‚") == [72, 105, 32, 0xf0, 0x9f, 0x8e, 0x82]
-    assert string_view16_chars("Hi πŸŽ‚") == [72, 105, 32, 0xd83c, 0xdf82]
-    assert string_view32_chars("Hi πŸŽ‚") == [72, 105, 32, 127874]
-
-    assert string_view_return() == "utf8 secret πŸŽ‚"
-    assert string_view16_return() == "utf16 secret πŸŽ‚"
-    assert string_view32_return() == "utf32 secret πŸŽ‚"
-
-    with capture:
-        string_view_print("Hi")
-        string_view_print("utf8 πŸŽ‚")
-        string_view16_print("utf16 πŸŽ‚")
-        string_view32_print("utf32 πŸŽ‚")
-
-    assert capture == """
-        Hi 2
-        utf8 πŸŽ‚ 9
-        utf16 πŸŽ‚ 8
-        utf32 πŸŽ‚ 7
-    """
-
-    with capture:
-        string_view_print("Hi, ascii")
-        string_view_print("Hi, utf8 πŸŽ‚")
-        string_view16_print("Hi, utf16 πŸŽ‚")
-        string_view32_print("Hi, utf32 πŸŽ‚")
-    assert capture == """
-        Hi, ascii 9
-        Hi, utf8 πŸŽ‚ 13
-        Hi, utf16 πŸŽ‚ 12
-        Hi, utf32 πŸŽ‚ 11
-    """
-
-
-def test_builtins_cast_return_none():
-    """Casters produced with PYBIND11_TYPE_CASTER() should convert nullptr to None"""
-    import pybind11_tests as m
-
-    assert m.return_none_string() is None
-    assert m.return_none_char() is None
-    assert m.return_none_bool() is None
-    assert m.return_none_int() is None
-    assert m.return_none_float() is None
-
-
-def test_none_deferred():
-    """None passed as various argument types should defer to other overloads"""
-    import pybind11_tests as m
-
-    assert not m.defer_none_cstring("abc")
-    assert m.defer_none_cstring(None)
-    assert not m.defer_none_custom(m.ExamplePythonTypes.new_instance())
-    assert m.defer_none_custom(None)
-    assert m.nodefer_none_void(None)
-    if has_optional:
-        assert m.nodefer_none_optional(None)
-
-
-def test_capsule_with_destructor(capture):
-    import pybind11_tests as m
-    pytest.gc_collect()
-    with capture:
-        a = m.return_capsule_with_destructor()
-        del a
-        pytest.gc_collect()
-    assert capture.unordered == """
-        creating capsule
-        destructing capsule
-    """
-
-    with capture:
-        a = m.return_capsule_with_destructor_2()
-        del a
-        pytest.gc_collect()
-    assert capture.unordered == """
-        creating capsule
-        destructing capsule: 1234
-    """
-
-    with capture:
-        a = m.return_capsule_with_name_and_destructor_3()
-        del a
-        pytest.gc_collect()
-    assert capture.unordered == """
-        created capsule with name --pointer type description-- and contents 1234
-        creating capsule
-        destructing capsule
-    """
-
-
-def test_void_caster():
-    import pybind11_tests as m
-    assert m.load_nullptr_t(None) is None
-    assert m.cast_nullptr_t() is None
-
-
-def test_reference_wrapper():
-    """std::reference_wrapper<T> tests.
-
-    #171: Can't return reference wrappers (or STL data structures containing them)
-    #848: std::reference_wrapper accepts nullptr / None arguments [but shouldn't]
-    (no issue): reference_wrappers should work for types with custom type casters
-    """
-    from pybind11_tests import (IntWrapper, return_vec_of_reference_wrapper, refwrap_int,
-                                IncrIntWrapper, refwrap_iiw, refwrap_call_iiw,
-                                refwrap_list_copies, refwrap_list_refs)
-
-    # 171:
-    assert str(return_vec_of_reference_wrapper(IntWrapper(4))) == \
-        "[IntWrapper[1], IntWrapper[2], IntWrapper[3], IntWrapper[4]]"
-
-    # 848:
-    with pytest.raises(TypeError) as excinfo:
-        return_vec_of_reference_wrapper(None)
-    assert "incompatible function arguments" in str(excinfo.value)
-
-    assert refwrap_int(42) == 420
-
-    a1 = refwrap_list_copies()
-    a2 = refwrap_list_copies()
-    assert [x.i for x in a1] == [2, 3]
-    assert [x.i for x in a2] == [2, 3]
-    assert not a1[0] is a2[0] and not a1[1] is a2[1]
-
-    b1 = refwrap_list_refs()
-    b2 = refwrap_list_refs()
-    assert [x.i for x in b1] == [1, 2]
-    assert [x.i for x in b2] == [1, 2]
-    assert b1[0] is b2[0] and b1[1] is b2[1]
-
-    assert refwrap_iiw(IncrIntWrapper(5)) == 5
-    assert refwrap_call_iiw(IncrIntWrapper(10), refwrap_iiw) == [10, 10, 10, 10]
-
-
-def test_complex_cast():
-    """#484: number conversion generates unhandled exceptions"""
-    from pybind11_tests import test_complex
-
-    assert test_complex(1) == "1.0"
-    assert test_complex(2j) == "(0.0, 2.0)"
diff --git a/tests/test_pytypes.cpp b/tests/test_pytypes.cpp
new file mode 100644
index 0000000..edc1e99
--- /dev/null
+++ b/tests/test_pytypes.cpp
@@ -0,0 +1,264 @@
+/*
+    tests/test_pytypes.cpp -- Python type casters
+
+    Copyright (c) 2017 Wenzel Jakob <wenzel.jakob@epfl.ch>
+
+    All rights reserved. Use of this source code is governed by a
+    BSD-style license that can be found in the LICENSE file.
+*/
+
+#include "pybind11_tests.h"
+
+
+TEST_SUBMODULE(pytypes, m) {
+    // test_list
+    m.def("get_list", []() {
+        py::list list;
+        list.append("value");
+        py::print("Entry at position 0:", list[0]);
+        list[0] = py::str("overwritten");
+        return list;
+    });
+    m.def("print_list", [](py::list list) {
+        int index = 0;
+        for (auto item : list)
+            py::print("list item {}: {}"_s.format(index++, item));
+    });
+
+    // test_set
+    m.def("get_set", []() {
+        py::set set;
+        set.add(py::str("key1"));
+        set.add("key2");
+        set.add(std::string("key3"));
+        return set;
+    });
+    m.def("print_set", [](py::set set) {
+        for (auto item : set)
+            py::print("key:", item);
+    });
+
+    // test_dict
+    m.def("get_dict", []() { return py::dict("key"_a="value"); });
+    m.def("print_dict", [](py::dict dict) {
+        for (auto item : dict)
+            py::print("key: {}, value={}"_s.format(item.first, item.second));
+    });
+    m.def("dict_keyword_constructor", []() {
+        auto d1 = py::dict("x"_a=1, "y"_a=2);
+        auto d2 = py::dict("z"_a=3, **d1);
+        return d2;
+    });
+
+    // test_str
+    m.def("str_from_string", []() { return py::str(std::string("baz")); });
+    m.def("str_from_bytes", []() { return py::str(py::bytes("boo", 3)); });
+    m.def("str_from_object", [](const py::object& obj) { return py::str(obj); });
+    m.def("repr_from_object", [](const py::object& obj) { return py::repr(obj); });
+
+    m.def("str_format", []() {
+        auto s1 = "{} + {} = {}"_s.format(1, 2, 3);
+        auto s2 = "{a} + {b} = {c}"_s.format("a"_a=1, "b"_a=2, "c"_a=3);
+        return py::make_tuple(s1, s2);
+    });
+
+    // test_bytes
+    m.def("bytes_from_string", []() { return py::bytes(std::string("foo")); });
+    m.def("bytes_from_str", []() { return py::bytes(py::str("bar", 3)); });
+
+    // test_capsule
+    m.def("return_capsule_with_destructor", []() {
+        py::print("creating capsule");
+        return py::capsule([]() {
+            py::print("destructing capsule");
+        });
+    });
+
+    m.def("return_capsule_with_destructor_2", []() {
+        py::print("creating capsule");
+        return py::capsule((void *) 1234, [](void *ptr) {
+            py::print("destructing capsule: {}"_s.format((size_t) ptr));
+        });
+    });
+
+    m.def("return_capsule_with_name_and_destructor", []() {
+        auto capsule = py::capsule((void *) 1234, "pointer type description", [](PyObject *ptr) {
+            if (ptr) {
+                auto name = PyCapsule_GetName(ptr);
+                py::print("destructing capsule ({}, '{}')"_s.format(
+                    (size_t) PyCapsule_GetPointer(ptr, name), name
+                ));
+            }
+        });
+        void *contents = capsule;
+        py::print("created capsule ({}, '{}')"_s.format((size_t) contents, capsule.name()));
+        return capsule;
+    });
+
+    // test_accessors
+    m.def("accessor_api", [](py::object o) {
+        auto d = py::dict();
+
+        d["basic_attr"] = o.attr("basic_attr");
+
+        auto l = py::list();
+        for (const auto &item : o.attr("begin_end")) {
+            l.append(item);
+        }
+        d["begin_end"] = l;
+
+        d["operator[object]"] = o.attr("d")["operator[object]"_s];
+        d["operator[char *]"] = o.attr("d")["operator[char *]"];
+
+        d["attr(object)"] = o.attr("sub").attr("attr_obj");
+        d["attr(char *)"] = o.attr("sub").attr("attr_char");
+        try {
+            o.attr("sub").attr("missing").ptr();
+        } catch (const py::error_already_set &) {
+            d["missing_attr_ptr"] = "raised"_s;
+        }
+        try {
+            o.attr("missing").attr("doesn't matter");
+        } catch (const py::error_already_set &) {
+            d["missing_attr_chain"] = "raised"_s;
+        }
+
+        d["is_none"] = o.attr("basic_attr").is_none();
+
+        d["operator()"] = o.attr("func")(1);
+        d["operator*"] = o.attr("func")(*o.attr("begin_end"));
+
+        return d;
+    });
+
+    m.def("tuple_accessor", [](py::tuple existing_t) {
+        try {
+            existing_t[0] = 1;
+        } catch (const py::error_already_set &) {
+            // --> Python system error
+            // Only new tuples (refcount == 1) are mutable
+            auto new_t = py::tuple(3);
+            for (size_t i = 0; i < new_t.size(); ++i) {
+                new_t[i] = i;
+            }
+            return new_t;
+        }
+        return py::tuple();
+    });
+
+    m.def("accessor_assignment", []() {
+        auto l = py::list(1);
+        l[0] = 0;
+
+        auto d = py::dict();
+        d["get"] = l[0];
+        auto var = l[0];
+        d["deferred_get"] = var;
+        l[0] = 1;
+        d["set"] = l[0];
+        var = 99; // this assignment should not overwrite l[0]
+        d["deferred_set"] = l[0];
+        d["var"] = var;
+
+        return d;
+    });
+
+    // test_constructors
+    m.def("default_constructors", []() {
+        return py::dict(
+            "str"_a=py::str(),
+            "bool"_a=py::bool_(),
+            "int"_a=py::int_(),
+            "float"_a=py::float_(),
+            "tuple"_a=py::tuple(),
+            "list"_a=py::list(),
+            "dict"_a=py::dict(),
+            "set"_a=py::set()
+        );
+    });
+
+    m.def("converting_constructors", [](py::dict d) {
+        return py::dict(
+            "str"_a=py::str(d["str"]),
+            "bool"_a=py::bool_(d["bool"]),
+            "int"_a=py::int_(d["int"]),
+            "float"_a=py::float_(d["float"]),
+            "tuple"_a=py::tuple(d["tuple"]),
+            "list"_a=py::list(d["list"]),
+            "dict"_a=py::dict(d["dict"]),
+            "set"_a=py::set(d["set"]),
+            "memoryview"_a=py::memoryview(d["memoryview"])
+        );
+    });
+
+    m.def("cast_functions", [](py::dict d) {
+        // When converting between Python types, obj.cast<T>() should be the same as T(obj)
+        return py::dict(
+            "str"_a=d["str"].cast<py::str>(),
+            "bool"_a=d["bool"].cast<py::bool_>(),
+            "int"_a=d["int"].cast<py::int_>(),
+            "float"_a=d["float"].cast<py::float_>(),
+            "tuple"_a=d["tuple"].cast<py::tuple>(),
+            "list"_a=d["list"].cast<py::list>(),
+            "dict"_a=d["dict"].cast<py::dict>(),
+            "set"_a=d["set"].cast<py::set>(),
+            "memoryview"_a=d["memoryview"].cast<py::memoryview>()
+        );
+    });
+
+    m.def("get_implicit_casting", []() {
+        py::dict d;
+        d["char*_i1"] = "abc";
+        const char *c2 = "abc";
+        d["char*_i2"] = c2;
+        d["char*_e"] = py::cast(c2);
+        d["char*_p"] = py::str(c2);
+
+        d["int_i1"] = 42;
+        int i = 42;
+        d["int_i2"] = i;
+        i++;
+        d["int_e"] = py::cast(i);
+        i++;
+        d["int_p"] = py::int_(i);
+
+        d["str_i1"] = std::string("str");
+        std::string s2("str1");
+        d["str_i2"] = s2;
+        s2[3] = '2';
+        d["str_e"] = py::cast(s2);
+        s2[3] = '3';
+        d["str_p"] = py::str(s2);
+
+        py::list l(2);
+        l[0] = 3;
+        l[1] = py::cast(6);
+        l.append(9);
+        l.append(py::cast(12));
+        l.append(py::int_(15));
+
+        return py::dict(
+            "d"_a=d,
+            "l"_a=l
+        );
+    });
+
+    // test_print
+    m.def("print_function", []() {
+        py::print("Hello, World!");
+        py::print(1, 2.0, "three", true, std::string("-- multiple args"));
+        auto args = py::make_tuple("and", "a", "custom", "separator");
+        py::print("*args", *args, "sep"_a="-");
+        py::print("no new line here", "end"_a=" -- ");
+        py::print("next print");
+
+        auto py_stderr = py::module::import("sys").attr("stderr");
+        py::print("this goes to stderr", "file"_a=py_stderr);
+
+        py::print("flush", "flush"_a=true);
+
+        py::print("{a} + {b} = {c}"_s.format("a"_a="py::print", "b"_a="str.format", "c"_a="this"));
+    });
+
+    m.def("print_failure", []() { py::print(42, UnregisteredType()); });
+}
diff --git a/tests/test_pytypes.py b/tests/test_pytypes.py
new file mode 100644
index 0000000..1c75c47
--- /dev/null
+++ b/tests/test_pytypes.py
@@ -0,0 +1,211 @@
+import pytest
+import sys
+
+from pybind11_tests import pytypes as m
+from pybind11_tests import debug_enabled
+
+
+def test_list(capture, doc):
+    with capture:
+        l = m.get_list()
+        assert l == ["overwritten"]
+
+        l.append("value2")
+        m.print_list(l)
+    assert capture.unordered == """
+        Entry at position 0: value
+        list item 0: overwritten
+        list item 1: value2
+    """
+
+    assert doc(m.get_list) == "get_list() -> list"
+    assert doc(m.print_list) == "print_list(arg0: list) -> None"
+
+
+def test_set(capture, doc):
+    s = m.get_set()
+    assert s == {"key1", "key2", "key3"}
+
+    with capture:
+        s.add("key4")
+        m.print_set(s)
+    assert capture.unordered == """
+        key: key1
+        key: key2
+        key: key3
+        key: key4
+    """
+
+    assert doc(m.get_list) == "get_list() -> list"
+    assert doc(m.print_list) == "print_list(arg0: list) -> None"
+
+
+def test_dict(capture, doc):
+    d = m.get_dict()
+    assert d == {"key": "value"}
+
+    with capture:
+        d["key2"] = "value2"
+        m.print_dict(d)
+    assert capture.unordered == """
+        key: key, value=value
+        key: key2, value=value2
+    """
+
+    assert doc(m.get_dict) == "get_dict() -> dict"
+    assert doc(m.print_dict) == "print_dict(arg0: dict) -> None"
+
+    assert m.dict_keyword_constructor() == {"x": 1, "y": 2, "z": 3}
+
+
+def test_str(doc):
+    assert m.str_from_string().encode().decode() == "baz"
+    assert m.str_from_bytes().encode().decode() == "boo"
+
+    assert doc(m.str_from_bytes) == "str_from_bytes() -> str"
+
+    class A(object):
+        def __str__(self):
+            return "this is a str"
+
+        def __repr__(self):
+            return "this is a repr"
+
+    assert m.str_from_object(A()) == "this is a str"
+    assert m.repr_from_object(A()) == "this is a repr"
+
+    s1, s2 = m.str_format()
+    assert s1 == "1 + 2 = 3"
+    assert s1 == s2
+
+
+def test_bytes(doc):
+    assert m.bytes_from_string().decode() == "foo"
+    assert m.bytes_from_str().decode() == "bar"
+
+    assert doc(m.bytes_from_str) == "bytes_from_str() -> {}".format(
+        "bytes" if sys.version_info[0] == 3 else "str"
+    )
+
+
+def test_capsule(capture):
+    pytest.gc_collect()
+    with capture:
+        a = m.return_capsule_with_destructor()
+        del a
+        pytest.gc_collect()
+    assert capture.unordered == """
+        creating capsule
+        destructing capsule
+    """
+
+    with capture:
+        a = m.return_capsule_with_destructor_2()
+        del a
+        pytest.gc_collect()
+    assert capture.unordered == """
+        creating capsule
+        destructing capsule: 1234
+    """
+
+    with capture:
+        a = m.return_capsule_with_name_and_destructor()
+        del a
+        pytest.gc_collect()
+    assert capture.unordered == """
+        created capsule (1234, 'pointer type description')
+        destructing capsule (1234, 'pointer type description')
+    """
+
+
+def test_accessors():
+    class SubTestObject:
+        attr_obj = 1
+        attr_char = 2
+
+    class TestObject:
+        basic_attr = 1
+        begin_end = [1, 2, 3]
+        d = {"operator[object]": 1, "operator[char *]": 2}
+        sub = SubTestObject()
+
+        def func(self, x, *args):
+            return self.basic_attr + x + sum(args)
+
+    d = m.accessor_api(TestObject())
+    assert d["basic_attr"] == 1
+    assert d["begin_end"] == [1, 2, 3]
+    assert d["operator[object]"] == 1
+    assert d["operator[char *]"] == 2
+    assert d["attr(object)"] == 1
+    assert d["attr(char *)"] == 2
+    assert d["missing_attr_ptr"] == "raised"
+    assert d["missing_attr_chain"] == "raised"
+    assert d["is_none"] is False
+    assert d["operator()"] == 2
+    assert d["operator*"] == 7
+
+    assert m.tuple_accessor(tuple()) == (0, 1, 2)
+
+    d = m.accessor_assignment()
+    assert d["get"] == 0
+    assert d["deferred_get"] == 0
+    assert d["set"] == 1
+    assert d["deferred_set"] == 1
+    assert d["var"] == 99
+
+
+def test_constructors():
+    """C++ default and converting constructors are equivalent to type calls in Python"""
+    types = [str, bool, int, float, tuple, list, dict, set]
+    expected = {t.__name__: t() for t in types}
+    assert m.default_constructors() == expected
+
+    data = {
+        str: 42,
+        bool: "Not empty",
+        int: "42",
+        float: "+1e3",
+        tuple: range(3),
+        list: range(3),
+        dict: [("two", 2), ("one", 1), ("three", 3)],
+        set: [4, 4, 5, 6, 6, 6],
+        memoryview: b'abc'
+    }
+    inputs = {k.__name__: v for k, v in data.items()}
+    expected = {k.__name__: k(v) for k, v in data.items()}
+    assert m.converting_constructors(inputs) == expected
+    assert m.cast_functions(inputs) == expected
+
+
+def test_implicit_casting():
+    """Tests implicit casting when assigning or appending to dicts and lists."""
+    z = m.get_implicit_casting()
+    assert z['d'] == {
+        'char*_i1': 'abc', 'char*_i2': 'abc', 'char*_e': 'abc', 'char*_p': 'abc',
+        'str_i1': 'str', 'str_i2': 'str1', 'str_e': 'str2', 'str_p': 'str3',
+        'int_i1': 42, 'int_i2': 42, 'int_e': 43, 'int_p': 44
+    }
+    assert z['l'] == [3, 6, 9, 12, 15]
+
+
+def test_print(capture):
+    with capture:
+        m.print_function()
+    assert capture == """
+        Hello, World!
+        1 2.0 three True -- multiple args
+        *args-and-a-custom-separator
+        no new line here -- next print
+        flush
+        py::print + str.format = this
+    """
+    assert capture.stderr == "this goes to stderr"
+
+    with pytest.raises(RuntimeError) as excinfo:
+        m.print_failure()
+    assert str(excinfo.value) == "make_tuple(): unable to convert " + (
+        "argument of type 'UnregisteredType' to Python object"
+        if debug_enabled else
+        "arguments to Python object (compile in debug mode for details)"
+    )
diff --git a/tests/test_sequences_and_iterators.py b/tests/test_sequences_and_iterators.py
index 012e97d..2ce2e60 100644
--- a/tests/test_sequences_and_iterators.py
+++ b/tests/test_sequences_and_iterators.py
@@ -163,4 +163,3 @@
     assert list(m.make_iterator_1()) == [1, 2, 3]
     assert list(m.make_iterator_2()) == [1, 2, 3]
     assert not isinstance(m.make_iterator_1(), type(m.make_iterator_2()))
-
diff --git a/tests/test_stl.cpp b/tests/test_stl.cpp
new file mode 100644
index 0000000..5288097
--- /dev/null
+++ b/tests/test_stl.cpp
@@ -0,0 +1,163 @@
+/*
+    tests/test_stl.cpp -- STL type casters
+
+    Copyright (c) 2017 Wenzel Jakob <wenzel.jakob@epfl.ch>
+
+    All rights reserved. Use of this source code is governed by a
+    BSD-style license that can be found in the LICENSE file.
+*/
+
+#include "pybind11_tests.h"
+#include <pybind11/stl.h>
+
+// Class that can be move- and copy-constructed, but not assigned
+struct NoAssign {
+    int value;
+
+    explicit NoAssign(int value = 0) : value(value) { }
+    NoAssign(const NoAssign &) = default;
+    NoAssign(NoAssign &&) = default;
+
+    NoAssign &operator=(const NoAssign &) = delete;
+    NoAssign &operator=(NoAssign &&) = delete;
+};
+
+/// Issue #528: templated constructor
+struct TplCtorClass {
+    template <typename T> TplCtorClass(const T &) { }
+    bool operator==(const TplCtorClass &) const { return true; }
+};
+
+namespace std {
+    template <>
+    struct hash<TplCtorClass> { size_t operator()(const TplCtorClass &) const { return 0; } };
+}
+
+
+TEST_SUBMODULE(stl, m) {
+    // test_vector
+    m.def("cast_vector", []() { return std::vector<int>{1}; });
+    m.def("load_vector", [](const std::vector<int> &v) { return v.at(0) == 1 && v.at(1) == 2; });
+
+    // test_array
+    m.def("cast_array", []() { return std::array<int, 2> {{1 , 2}}; });
+    m.def("load_array", [](const std::array<int, 2> &a) { return a[0] == 1 && a[1] == 2; });
+
+    // test_valarray
+    m.def("cast_valarray", []() { return std::valarray<int>{1, 4, 9}; });
+    m.def("load_valarray", [](const std::valarray<int>& v) {
+        return v.size() == 3 && v[0] == 1 && v[1] == 4 && v[2] == 9;
+    });
+
+    // test_map
+    m.def("cast_map", []() { return std::map<std::string, std::string>{{"key", "value"}}; });
+    m.def("load_map", [](const std::map<std::string, std::string> &map) {
+        return map.at("key") == "value" && map.at("key2") == "value2";
+    });
+
+    // test_set
+    m.def("cast_set", []() { return std::set<std::string>{"key1", "key2"}; });
+    m.def("load_set", [](const std::set<std::string> &set) {
+        return set.count("key1") && set.count("key2") && set.count("key3");
+    });
+
+    struct MoveOutContainer {
+        struct Value { int value; };
+
+        std::list<Value> move_list() const { return {{0}, {1}, {2}}; }
+    };
+
+    py::class_<MoveOutContainer::Value>(m, "MoveOutContainerValue")
+        .def_readonly("value", &MoveOutContainer::Value::value);
+
+    py::class_<MoveOutContainer>(m, "MoveOutContainer")
+        .def(py::init<>())
+        .def_property_readonly("move_list", &MoveOutContainer::move_list);
+
+    py::class_<NoAssign>(m, "NoAssign", "Class with no C++ assignment operators")
+        .def(py::init<>())
+        .def(py::init<int>());
+
+#ifdef PYBIND11_HAS_OPTIONAL
+    m.attr("has_optional") = true;
+
+    using opt_int = std::optional<int>;
+    using opt_no_assign = std::optional<NoAssign>;
+    m.def("double_or_zero", [](const opt_int& x) -> int {
+        return x.value_or(0) * 2;
+    });
+    m.def("half_or_none", [](int x) -> opt_int {
+        return x ? opt_int(x / 2) : opt_int();
+    });
+    m.def("test_nullopt", [](opt_int x) {
+        return x.value_or(42);
+    }, py::arg_v("x", std::nullopt, "None"));
+    m.def("test_no_assign", [](const opt_no_assign &x) {
+        return x ? x->value : 42;
+    }, py::arg_v("x", std::nullopt, "None"));
+
+    m.def("nodefer_none_optional", [](std::optional<int>) { return true; });
+    m.def("nodefer_none_optional", [](py::none) { return false; });
+#endif
+
+#ifdef PYBIND11_HAS_EXP_OPTIONAL
+    m.attr("has_exp_optional") = true;
+
+    using exp_opt_int = std::experimental::optional<int>;
+    using exp_opt_no_assign = std::experimental::optional<NoAssign>;
+    m.def("double_or_zero_exp", [](const exp_opt_int& x) -> int {
+        return x.value_or(0) * 2;
+    });
+    m.def("half_or_none_exp", [](int x) -> exp_opt_int {
+        return x ? exp_opt_int(x / 2) : exp_opt_int();
+    });
+    m.def("test_nullopt_exp", [](exp_opt_int x) {
+        return x.value_or(42);
+    }, py::arg_v("x", std::experimental::nullopt, "None"));
+    m.def("test_no_assign_exp", [](const exp_opt_no_assign &x) {
+        return x ? x->value : 42;
+    }, py::arg_v("x", std::experimental::nullopt, "None"));
+#endif
+
+#ifdef PYBIND11_HAS_VARIANT
+    struct visitor {
+        const char *operator()(int) { return "int"; }
+        const char *operator()(std::string) { return "std::string"; }
+        const char *operator()(double) { return "double"; }
+        const char *operator()(std::nullptr_t) { return "std::nullptr_t"; }
+    };
+
+    m.def("load_variant", [](std::variant<int, std::string, double, std::nullptr_t> v) {
+        return std::visit(visitor(), v);
+    });
+
+    m.def("load_variant_2pass", [](std::variant<double, int> v) {
+        return std::visit(visitor(), v);
+    });
+
+    m.def("cast_variant", []() {
+        using V = std::variant<int, std::string>;
+        return py::make_tuple(V(5), V("Hello"));
+    });
+#endif
+
+    /// #528: templated constructor
+    m.def("tpl_ctor_vector", [](std::vector<TplCtorClass> &) {});
+    m.def("tpl_ctor_map", [](std::unordered_map<TplCtorClass, TplCtorClass> &) {});
+    m.def("tpl_ctor_set", [](std::unordered_set<TplCtorClass> &) {});
+#if defined(PYBIND11_HAS_OPTIONAL)
+    m.def("tpl_constr_optional", [](std::optional<TplCtorClass> &) {});
+#elif defined(PYBIND11_HAS_EXP_OPTIONAL)
+    m.def("tpl_constr_optional", [](std::experimental::optional<TplCtorClass> &) {});
+#endif
+
+    // test_vec_of_reference_wrapper
+    // #171: Can't return STL structures containing reference wrapper
+    m.def("return_vec_of_reference_wrapper", [](std::reference_wrapper<UserType> p4) {
+        static UserType p1{1}, p2{2}, p3{3};
+        return std::vector<std::reference_wrapper<UserType>> {
+            std::ref(p1), std::ref(p2), std::ref(p3), p4
+        };
+    });
+
+}
diff --git a/tests/test_stl.py b/tests/test_stl.py
new file mode 100644
index 0000000..1c98258
--- /dev/null
+++ b/tests/test_stl.py
@@ -0,0 +1,133 @@
+import pytest
+
+from pybind11_tests import stl as m
+from pybind11_tests import UserType
+
+
+def test_vector(doc):
+    """std::vector <-> list"""
+    l = m.cast_vector()
+    assert l == [1]
+    l.append(2)
+    assert m.load_vector(l)
+    assert m.load_vector(tuple(l))
+
+    assert doc(m.cast_vector) == "cast_vector() -> List[int]"
+    assert doc(m.load_vector) == "load_vector(arg0: List[int]) -> bool"
+
+
+def test_array(doc):
+    """std::array <-> list"""
+    l = m.cast_array()
+    assert l == [1, 2]
+    assert m.load_array(l)
+
+    assert doc(m.cast_array) == "cast_array() -> List[int[2]]"
+    assert doc(m.load_array) == "load_array(arg0: List[int[2]]) -> bool"
+
+
+def test_valarray(doc):
+    """std::valarray <-> list"""
+    l = m.cast_valarray()
+    assert l == [1, 4, 9]
+    assert m.load_valarray(l)
+
+    assert doc(m.cast_valarray) == "cast_valarray() -> List[int]"
+    assert doc(m.load_valarray) == "load_valarray(arg0: List[int]) -> bool"
+
+
+def test_map(doc):
+    """std::map <-> dict"""
+    d = m.cast_map()
+    assert d == {"key": "value"}
+    d["key2"] = "value2"
+    assert m.load_map(d)
+
+    assert doc(m.cast_map) == "cast_map() -> Dict[str, str]"
+    assert doc(m.load_map) == "load_map(arg0: Dict[str, str]) -> bool"
+
+
+def test_set(doc):
+    """std::set <-> set"""
+    s = m.cast_set()
+    assert s == {"key1", "key2"}
+    s.add("key3")
+    assert m.load_set(s)
+
+    assert doc(m.cast_set) == "cast_set() -> Set[str]"
+    assert doc(m.load_set) == "load_set(arg0: Set[str]) -> bool"
+
+
+def test_move_out_container():
+    """Properties use the `reference_internal` policy by default. If the underlying function
+    returns an rvalue, the policy is automatically changed to `move` to avoid referencing
+    a temporary. In case the return value is a container of user-defined types, the policy
+    also needs to be applied to the elements, not just the container."""
+    c = m.MoveOutContainer()
+    moved_out_list = c.move_list
+    assert [x.value for x in moved_out_list] == [0, 1, 2]
+
+
+@pytest.mark.skipif(not hasattr(m, "has_optional"), reason='no <optional>')
+def test_optional():
+    assert m.double_or_zero(None) == 0
+    assert m.double_or_zero(42) == 84
+    pytest.raises(TypeError, m.double_or_zero, 'foo')
+
+    assert m.half_or_none(0) is None
+    assert m.half_or_none(42) == 21
+    pytest.raises(TypeError, m.half_or_none, 'foo')
+
+    assert m.test_nullopt() == 42
+    assert m.test_nullopt(None) == 42
+    assert m.test_nullopt(42) == 42
+    assert m.test_nullopt(43) == 43
+
+    assert m.test_no_assign() == 42
+    assert m.test_no_assign(None) == 42
+    assert m.test_no_assign(m.NoAssign(43)) == 43
+    pytest.raises(TypeError, m.test_no_assign, 43)
+
+    assert m.nodefer_none_optional(None)
+
+
+@pytest.mark.skipif(not hasattr(m, "has_exp_optional"), reason='no <experimental/optional>')
+def test_exp_optional():
+    assert m.double_or_zero_exp(None) == 0
+    assert m.double_or_zero_exp(42) == 84
+    pytest.raises(TypeError, m.double_or_zero_exp, 'foo')
+
+    assert m.half_or_none_exp(0) is None
+    assert m.half_or_none_exp(42) == 21
+    pytest.raises(TypeError, m.half_or_none_exp, 'foo')
+
+    assert m.test_nullopt_exp() == 42
+    assert m.test_nullopt_exp(None) == 42
+    assert m.test_nullopt_exp(42) == 42
+    assert m.test_nullopt_exp(43) == 43
+
+    assert m.test_no_assign_exp() == 42
+    assert m.test_no_assign_exp(None) == 42
+    assert m.test_no_assign_exp(m.NoAssign(43)) == 43
+    pytest.raises(TypeError, m.test_no_assign_exp, 43)
+
+
+@pytest.mark.skipif(not hasattr(m, "load_variant"), reason='no <variant>')
+def test_variant(doc):
+    assert m.load_variant(1) == "int"
+    assert m.load_variant("1") == "std::string"
+    assert m.load_variant(1.0) == "double"
+    assert m.load_variant(None) == "std::nullptr_t"
+
+    assert m.load_variant_2pass(1) == "int"
+    assert m.load_variant_2pass(1.0) == "double"
+
+    assert m.cast_variant() == (5, "Hello")
+
+    assert doc(m.load_variant) == "load_variant(arg0: Union[int, str, float, None]) -> str"
+
+
+def test_vec_of_reference_wrapper():
+    """#171: Can't return reference wrappers (or STL structures containing them)"""
+    assert str(m.return_vec_of_reference_wrapper(UserType(4))) == \
+        "[UserType(1), UserType(2), UserType(3), UserType(4)]"