Add Catch framework for testing embedding support and C++-side features

At this point, there is only a single test for interpreter basics.

Apart from embedding itself, having a C++ test framework will also
benefit the C++-side features by allowing them to be tested directly.
diff --git a/.appveyor.yml b/.appveyor.yml
index 026e761..8d30cf3 100644
--- a/.appveyor.yml
+++ b/.appveyor.yml
@@ -39,6 +39,7 @@
       if ($env:CONDA -eq "27") { $env:CONDA = "" }
       if ($env:PLATFORM -eq "x64") { $env:CONDA = "$env:CONDA-x64" }
       $env:PATH = "C:\Miniconda$env:CONDA\;C:\Miniconda$env:CONDA\Scripts\;$env:PATH"
+      $env:PYTHONHOME = "C:\Miniconda$env:CONDA"
       conda install -y -q pytest numpy scipy
     }
 - ps: |
@@ -46,8 +47,13 @@
     7z x 3.3.3.zip -y > $null
     $env:CMAKE_INCLUDE_PATH = "eigen-eigen-67e894c6cd8f"
 build_script:
-- cmake -G "%CMAKE_GENERATOR%" -A "%CMAKE_ARCH%" -DPYBIND11_CPP_STANDARD=/std:c++%CPP% -DPYBIND11_WERROR=ON -DCMAKE_SUPPRESS_REGENERATION=1
+- cmake -G "%CMAKE_GENERATOR%" -A "%CMAKE_ARCH%"
+    -DPYBIND11_CPP_STANDARD=/std:c++%CPP%
+    -DPYBIND11_WERROR=ON
+    -DDOWNLOAD_CATCH=ON
+    -DCMAKE_SUPPRESS_REGENERATION=1
 - set MSBuildLogger="C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll"
 - cmake --build . --config Release --target pytest -- /v:m /logger:%MSBuildLogger%
+- cmake --build . --config Release --target cpptest -- /v:m /logger:%MSBuildLogger%
 - cmake --build . --config Release --target test_cmake_build -- /v:m /logger:%MSBuildLogger%
-on_failure: if exist "tests\test_cmake_build" type tests\test_cmake_build\*.log
+on_failure: if exist "tests\test_cmake_build" type tests\test_cmake_build\*.log*
diff --git a/.travis.yml b/.travis.yml
index 73da10c..aeb3c86 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -192,8 +192,10 @@
     -DPYBIND11_PYTHON_VERSION=$PYTHON
     -DPYBIND11_CPP_STANDARD=$CPP
     -DPYBIND11_WERROR=${WERROR:-ON}
+    -DDOWNLOAD_CATCH=ON
 - $SCRIPT_RUN_PREFIX make pytest -j 2
+- $SCRIPT_RUN_PREFIX make cpptest -j 2
 - $SCRIPT_RUN_PREFIX make test_cmake_build
-after_failure: cat tests/test_cmake_build/*.log
+after_failure: cat tests/test_cmake_build/*.log*
 after_script:
 - if [ -n "$DOCKER" ]; then docker stop "$containerid"; docker rm "$containerid"; fi
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 580b791..d40c25a 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -195,6 +195,9 @@
   COMMAND ${PYTHON_EXECUTABLE} ${PROJECT_SOURCE_DIR}/tools/libsize.py
   $<TARGET_FILE:pybind11_tests> ${CMAKE_CURRENT_BINARY_DIR}/sosize-$<TARGET_FILE_NAME:pybind11_tests>.txt)
 
+# Test embedding the interpreter. Provides the `cpptest` target.
+add_subdirectory(test_embed)
+
 # Test CMake build using functions and targets from subdirectory or installed location
 add_custom_target(test_cmake_build)
 if(NOT CMAKE_VERSION VERSION_LESS 3.1)
diff --git a/tests/test_embed/CMakeLists.txt b/tests/test_embed/CMakeLists.txt
new file mode 100644
index 0000000..a651031
--- /dev/null
+++ b/tests/test_embed/CMakeLists.txt
@@ -0,0 +1,31 @@
+if(${PYTHON_MODULE_EXTENSION} MATCHES "pypy")
+  add_custom_target(cpptest)  # Dummy target on PyPy. Embedding is not supported.
+  set(_suppress_unused_variable_warning "${DOWNLOAD_CATCH}")
+  return()
+endif()
+
+find_package(Catch 1.9.3)
+if(NOT CATCH_FOUND)
+  message(STATUS "Catch not detected. Interpreter tests will be skipped. Install Catch headers"
+                 " manually or use `cmake -DDOWNLOAD_CATCH=1` to fetch them automatically.")
+  return()
+endif()
+
+add_executable(test_embed
+  catch.cpp
+  test_interpreter.cpp
+)
+target_include_directories(test_embed PRIVATE ${CATCH_INCLUDE_DIR})
+pybind11_enable_warnings(test_embed)
+
+if(NOT CMAKE_VERSION VERSION_LESS 3.0)
+  target_link_libraries(test_embed PRIVATE pybind11::embed)
+else()
+  target_include_directories(test_embed PRIVATE ${PYBIND11_INCLUDE_DIR} ${PYTHON_INCLUDE_DIRS})
+  target_compile_options(test_embed PRIVATE ${PYBIND11_CPP_STANDARD})
+  target_link_libraries(test_embed PRIVATE ${PYTHON_LIBRARIES})
+endif()
+
+add_custom_target(cpptest COMMAND $<TARGET_FILE:test_embed>
+                  WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
+add_dependencies(check cpptest)
diff --git a/tests/test_embed/catch.cpp b/tests/test_embed/catch.cpp
new file mode 100644
index 0000000..f79fe17
--- /dev/null
+++ b/tests/test_embed/catch.cpp
@@ -0,0 +1,5 @@
+// Catch provides the `int main()` function here. This is a standalone
+// translation unit to avoid recompiling it for every test change.
+
+#define CATCH_CONFIG_MAIN
+#include <catch.hpp>
diff --git a/tests/test_embed/test_interpreter.cpp b/tests/test_embed/test_interpreter.cpp
new file mode 100644
index 0000000..97af7eb
--- /dev/null
+++ b/tests/test_embed/test_interpreter.cpp
@@ -0,0 +1,64 @@
+#include <pybind11/pybind11.h>
+#include <pybind11/eval.h>
+
+#include <catch.hpp>
+
+namespace py = pybind11;
+using namespace py::literals;
+
+class Widget {
+public:
+    Widget(std::string message) : message(message) { }
+    virtual ~Widget() = default;
+
+    std::string the_message() const { return message; }
+    virtual int the_answer() const = 0;
+
+private:
+    std::string message;
+};
+
+class PyWidget final : public Widget {
+    using Widget::Widget;
+
+    int the_answer() const override { PYBIND11_OVERLOAD_PURE(int, Widget, the_answer); }
+};
+
+PyObject *make_embedded_module() {
+    py::module m("widget_module");
+
+    py::class_<Widget, PyWidget>(m, "Widget")
+        .def(py::init<std::string>())
+        .def_property_readonly("the_message", &Widget::the_message);
+
+    return m.ptr();
+}
+
+py::object import_file(const std::string &module, const std::string &path, py::object globals) {
+    auto locals = py::dict("module_name"_a=module, "path"_a=path);
+    py::eval<py::eval_statements>(
+        "import imp\n"
+        "with open(path) as file:\n"
+        "    new_module = imp.load_module(module_name, file, path, ('py', 'U', imp.PY_SOURCE))",
+        globals, locals
+    );
+    return locals["new_module"];
+}
+
+TEST_CASE("Pass classes and data between modules defined in C++ and Python") {
+    PyImport_AppendInittab("widget_module", &make_embedded_module);
+    Py_Initialize();
+    {
+        auto globals = py::module::import("__main__").attr("__dict__");
+        auto module = import_file("widget", "test_interpreter.py", globals);
+        REQUIRE(py::hasattr(module, "DerivedWidget"));
+
+        auto py_widget = module.attr("DerivedWidget")("Hello, World!");
+        auto message = py_widget.attr("the_message");
+        REQUIRE(message.cast<std::string>() == "Hello, World!");
+
+        const auto &cpp_widget = py_widget.cast<const Widget &>();
+        REQUIRE(cpp_widget.the_answer() == 42);
+    }
+    Py_Finalize();
+}
diff --git a/tests/test_embed/test_interpreter.py b/tests/test_embed/test_interpreter.py
new file mode 100644
index 0000000..26a0479
--- /dev/null
+++ b/tests/test_embed/test_interpreter.py
@@ -0,0 +1,9 @@
+from widget_module import Widget
+
+
+class DerivedWidget(Widget):
+    def __init__(self, message):
+        super(DerivedWidget, self).__init__(message)
+
+    def the_answer(self):
+        return 42
diff --git a/tools/FindCatch.cmake b/tools/FindCatch.cmake
new file mode 100644
index 0000000..9d490c5
--- /dev/null
+++ b/tools/FindCatch.cmake
@@ -0,0 +1,57 @@
+# - Find the Catch test framework or download it (single header)
+#
+# This is a quick module for internal use. It assumes that Catch is
+# REQUIRED and that a minimum version is provided (not EXACT). If
+# a suitable version isn't found locally, the single header file
+# will be downloaded and placed in the build dir: PROJECT_BINARY_DIR.
+#
+# This code sets the following variables:
+#  CATCH_INCLUDE_DIR      - path to catch.hpp
+#  CATCH_VERSION          - version number
+
+if(NOT Catch_FIND_VERSION)
+  message(FATAL_ERROR "A version number must be specified.")
+elseif(Catch_FIND_REQUIRED)
+  message(FATAL_ERROR "This module assumes Catch is not required.")
+elseif(Catch_FIND_VERSION_EXACT)
+  message(FATAL_ERROR "Exact version numbers are not supported, only minimum.")
+endif()
+
+# Extract the version number from catch.hpp
+function(_get_catch_version)
+  file(STRINGS "${CATCH_INCLUDE_DIR}/catch.hpp" version_line REGEX "Catch v.*" LIMIT_COUNT 1)
+  if(version_line MATCHES "Catch v([0-9]+)\\.([0-9]+)\\.([0-9]+)")
+    set(CATCH_VERSION "${CMAKE_MATCH_1}.${CMAKE_MATCH_2}.${CMAKE_MATCH_3}" PARENT_SCOPE)
+  endif()
+endfunction()
+
+# Download the single-header version of Catch
+function(_download_catch version destination_dir)
+  message(STATUS "Downloading catch v${version}...")
+  set(url https://github.com/philsquared/Catch/releases/download/v${version}/catch.hpp)
+  file(DOWNLOAD ${url} "${destination_dir}/catch.hpp" STATUS status)
+  list(GET status 0 error)
+  if(error)
+    message(FATAL_ERROR "Could not download ${url}")
+  endif()
+  set(CATCH_INCLUDE_DIR "${destination_dir}" CACHE INTERNAL "")
+endfunction()
+
+# Look for catch locally
+find_path(CATCH_INCLUDE_DIR NAMES catch.hpp PATH_SUFFIXES catch)
+if(CATCH_INCLUDE_DIR)
+  _get_catch_version()
+endif()
+
+# Download the header if it wasn't found or if it's outdated
+if(NOT CATCH_VERSION OR CATCH_VERSION VERSION_LESS ${Catch_FIND_VERSION})
+  if(DOWNLOAD_CATCH)
+    _download_catch(${Catch_FIND_VERSION} "${PROJECT_BINARY_DIR}/catch/")
+    _get_catch_version()
+  else()
+    set(CATCH_FOUND FALSE)
+    return()
+  endif()
+endif()
+
+set(CATCH_FOUND TRUE)