diff --git a/application/F3DOptionsTools.cxx b/application/F3DOptionsTools.cxx index ccd9855736..e57c749bf4 100644 --- a/application/F3DOptionsTools.cxx +++ b/application/F3DOptionsTools.cxx @@ -236,7 +236,7 @@ void PrintPluginsScan() appPath /= "share/f3d/plugins"; - auto plugins = f3d::engine::getPluginsList(appPath.string()); + auto plugins = f3d::engine::getPluginsList(appPath); f3d::log::info("Found ", plugins.size(), " plugins:"); diff --git a/application/F3DPluginsTools.cxx b/application/F3DPluginsTools.cxx index 9b15f4285a..f50307377f 100644 --- a/application/F3DPluginsTools.cxx +++ b/application/F3DPluginsTools.cxx @@ -6,13 +6,19 @@ #include #include +namespace fs = std::filesystem; + namespace { //---------------------------------------------------------------------------- -std::vector GetPluginSearchPaths() +std::vector GetPluginSearchPaths() { + std::vector searchPaths; + // Recover F3D_PLUGINS_PATH first - auto searchPaths = F3DSystemTools::GetVectorEnvironnementVariable("F3D_PLUGINS_PATH"); + std::vector stringPaths = + F3DSystemTools::GetVectorEnvironnementVariable("F3D_PLUGINS_PATH"); + std::copy(stringPaths.begin(), stringPaths.end(), std::back_inserter(searchPaths)); #if F3D_MACOS_BUNDLE return searchPaths; #else @@ -20,7 +26,7 @@ std::vector GetPluginSearchPaths() auto tmpPath = F3DSystemTools::GetApplicationPath(); tmpPath = tmpPath.parent_path().parent_path(); tmpPath /= F3D::PluginsInstallDir; - searchPaths.push_back(tmpPath.string()); + searchPaths.emplace_back(tmpPath); return searchPaths; #endif } diff --git a/application/testing/CMakeLists.txt b/application/testing/CMakeLists.txt index 5afb494d70..fb3f07472c 100644 --- a/application/testing/CMakeLists.txt +++ b/application/testing/CMakeLists.txt @@ -328,7 +328,19 @@ endif() # Test plugin fail code path f3d_test(NAME TestPluginVerbose ARGS --verbose REGEXP "Loading plugin \"native\"" NO_BASELINE) f3d_test(NAME TestPluginNonExistent ARGS --load-plugins=dummy REGEXP "Plugin failed to load" NO_BASELINE) -f3d_test(NAME TestPluginInvalid ARGS --load-plugins=${F3D_SOURCE_DIR}/testing/data/invalid.so REGEXP "Cannot open the library" NO_BASELINE) +if(WIN32) + set(_TEST_PLUGIN_INVALID_REGEXP "is not a valid Win32 application") +elseif(APPLE) + set(_TEST_PLUGIN_INVALID_REGEXP "not a mach-o file") +else() + set(_TEST_PLUGIN_INVALID_REGEXP "file too short") +endif() +f3d_test(NAME TestPluginInvalid ARGS --load-plugins=${F3D_SOURCE_DIR}/testing/data/invalid.so REGEXP ${_TEST_PLUGIN_INVALID_REGEXP} NO_BASELINE) + +if(UNIX AND NOT APPLE) + f3d_test(NAME TestPluginInvalidSystem ARGS --verbose --load-plugins=invalid REGEXP "file too short" ENV "LD_LIBRARY_PATH=${F3D_SOURCE_DIR}/testing/data" NO_BASELINE) +endif() + if(WIN32) set(_dirname "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") else() diff --git a/java/F3DJavaBindings.cxx b/java/F3DJavaBindings.cxx index 9f9391949b..adc1b907f2 100644 --- a/java/F3DJavaBindings.cxx +++ b/java/F3DJavaBindings.cxx @@ -12,6 +12,8 @@ #define JAVA_BIND(Cls, Func) JNICALL Java_app_f3d_F3D_##Cls##_##Func +namespace fs = std::filesystem; + inline f3d::engine* GetEngine(JNIEnv* env, jobject self) { jclass cls = env->GetObjectClass(self); @@ -39,7 +41,7 @@ extern "C" JNIEXPORT void JAVA_BIND(Engine, setCachePath)(JNIEnv* env, jobject self, jstring path) { const char* str = env->GetStringUTFChars(path, nullptr); - GetEngine(env, self)->setCachePath(str); + GetEngine(env, self)->setCachePath(fs::path(str)); env->ReleaseStringUTFChars(path, str); } diff --git a/library/private/window_impl.h b/library/private/window_impl.h index 872d2ffac6..addea7e956 100644 --- a/library/private/window_impl.h +++ b/library/private/window_impl.h @@ -15,6 +15,7 @@ #include "log.h" #include "window.h" +#include #include #include @@ -108,7 +109,7 @@ class window_impl : public window * Implementation only API. * Set the cache path. */ - void SetCachePath(const std::string& cachePath); + void SetCachePath(const std::filesystem::path& cachePath); /** * Implementation only API. diff --git a/library/public/engine.h b/library/public/engine.h index 311809b535..32760408f9 100644 --- a/library/public/engine.h +++ b/library/public/engine.h @@ -44,11 +44,17 @@ class F3D_EXPORT engine * Linux: Try GLX, then EGL, then OSMesa * Windows: Try Win32, then EGL, then OSMesa * macOS: Always use Cocoa + * Throws a context::loading_exception if a needed graphic library cannot be loaded + * Throws a context::symbol_exception if a needed symbol cannot be found in graphic library + * Throws a engine::no_window_exception if the window cannot be created for another reason + * Throws a engine::cache_exception if the default cache directory cannot be used */ [[nodiscard]] static engine create(bool offscreen = false); /** * Create an engine with no window. + * Throws a engine::no_window_exception if the window cannot be created for another reason + * Throws a engine::cache_exception if the default cache directory cannot be used */ [[nodiscard]] static engine createNone(); @@ -57,7 +63,10 @@ class F3D_EXPORT engine * Works on Linux only. * VTK >= 9.4 required. * Optionally, the window can be hidden by setting offscreen to true. - * Throws engine::loading_exception in case of window creation failure. + * Throws a context::loading_exception if a needed graphic library cannot be loaded + * Throws a context::symbol_exception if a needed symbol cannot be found in graphic library + * Throws a engine::no_window_exception if the window cannot be created for another reason + * Throws a engine::cache_exception if the default cache directory cannot be used */ [[nodiscard]] static engine createGLX(bool offscreen = false); @@ -66,7 +75,10 @@ class F3D_EXPORT engine * Works on Windows only. * VTK >= 9.4 required. * Optionally, the window can be hidden by setting offscreen to true. - * Throws engine::loading_exception in case of window creation failure. + * Throws a context::loading_exception if a needed graphic library cannot be loaded + * Throws a context::symbol_exception if a needed symbol cannot be found in graphic library + * Throws a engine::no_window_exception if the window cannot be created for another reason + * Throws a engine::cache_exception if the default cache directory cannot be used */ [[nodiscard]] static engine createWGL(bool offscreen = false); @@ -75,14 +87,20 @@ class F3D_EXPORT engine * VTK >= 9.4 required. * If several GPU are available, the environment variable * `VTK_DEFAULT_EGL_DEVICE_INDEX` allows its selection. - * Throws engine::loading_exception in case of failure. + * Throws a context::loading_exception if a needed graphic library cannot be loaded + * Throws a context::symbol_exception if a needed symbol cannot be found in graphic library + * Throws a engine::no_window_exception if the window cannot be created for another reason + * Throws a engine::cache_exception if the default cache directory cannot be used */ [[nodiscard]] static engine createEGL(); /** * Create an engine with an offscreen OSMesa window. * VTK >= 9.4 required. - * Throws engine::loading_exception in case of window creation failure. + * Throws a context::loading_exception if a needed graphic library cannot be loaded + * Throws a context::symbol_exception if a needed symbol cannot be found in graphic library + * Throws a engine::no_window_exception if the window cannot be created for another reason + * Throws a engine::cache_exception if the default cache directory cannot be used */ [[nodiscard]] static engine createOSMesa(); @@ -94,6 +112,8 @@ class F3D_EXPORT engine * \code{.cpp} * f3d::engine eng = f3d::engine::createExternal(glfwGetProcAddress); * \endcode + * Throws a engine::no_window_exception if the window cannot be created for another reason + * Throws a engine::cache_exception if the default cache directory cannot be used */ [[nodiscard]] static engine createExternal(const context::function& getProcAddress); @@ -101,7 +121,10 @@ class F3D_EXPORT engine * Create an engine with an external GLX context. * Equivalent to createExternal(f3d::context::glx()); * VTK >= 9.4 required. - * Throws context::loading_exception if GLX library is not found or if not running on Linux. + * Throws a context::loading_exception if a needed graphic library cannot be loaded + * Throws a context::symbol_exception if a needed symbol cannot be found in graphic library + * Throws a engine::no_window_exception if the window cannot be created for another reason + * Throws a engine::cache_exception if the default cache directory cannot be used */ [[nodiscard]] static engine createExternalGLX(); @@ -109,7 +132,10 @@ class F3D_EXPORT engine * Create an engine with an external WGL context. * Equivalent to createExternal(f3d::context::wgl()); * VTK >= 9.4 required. - * Throws context::loading_exception if WGL library is not found or if not running on Windows. + * Throws a context::loading_exception if a needed graphic library cannot be loaded + * Throws a context::symbol_exception if a needed symbol cannot be found in graphic library + * Throws a engine::no_window_exception if the window cannot be created for another reason + * Throws a engine::cache_exception if the default cache directory cannot be used */ [[nodiscard]] static engine createExternalWGL(); @@ -117,7 +143,10 @@ class F3D_EXPORT engine * Create an engine with an external COCOA context. * Equivalent to createExternal(f3d::context::cocoa()); * VTK >= 9.4 required. - * Throws context::loading_exception if WGL library is not found or if not running on Windows. + * Throws a context::loading_exception if a needed graphic library cannot be loaded + * Throws a context::symbol_exception if a needed symbol cannot be found in graphic library + * Throws a engine::no_window_exception if the window cannot be created for another reason + * Throws a engine::cache_exception if the default cache directory cannot be used */ [[nodiscard]] static engine createExternalCOCOA(); @@ -125,7 +154,10 @@ class F3D_EXPORT engine * Create an engine with an external EGL context. * Equivalent to createExternal(f3d::context::egl()); * VTK >= 9.4 required. - * Throws context::loading_exception if EGL library is not found. + * Throws a context::loading_exception if a needed graphic library cannot be loaded + * Throws a context::symbol_exception if a needed symbol cannot be found in graphic library + * Throws a engine::no_window_exception if the window cannot be created for another reason + * Throws a engine::cache_exception if the default cache directory cannot be used */ [[nodiscard]] static engine createExternalEGL(); @@ -133,7 +165,10 @@ class F3D_EXPORT engine * Create an engine with an external OSMesa context. * Equivalent to createExternal(f3d::context::osmesa()); * VTK >= 9.4 required. - * Throws context::loading_exception if OSMesa library is not found. + * Throws a context::loading_exception if a needed graphic library cannot be loaded + * Throws a context::symbol_exception if a needed symbol cannot be found in graphic library + * Throws a engine::no_window_exception if the window cannot be created for another reason + * Throws a engine::cache_exception if the default cache directory cannot be used */ [[nodiscard]] static engine createExternalOSMesa(); @@ -153,14 +188,15 @@ class F3D_EXPORT engine //@} /** - * Set the cache path. Must be an absolute path. + * Set the cache path. The provided path is used as is. * Currently, it's only used to store HDRI baked textures. * By default, the cache path is: * - Windows: %LOCALAPPDATA%\f3d * - Linux: ~/.cache/f3d * - macOS: ~/Library/Caches/f3d + * Throw a cache_exception if the provided cachePath cannot be used. */ - engine& setCachePath(const std::string& cachePath); + engine& setCachePath(const std::filesystem::path& cachePath); /** * Engine provide a default options that you can use using engine::getOptions(). @@ -206,19 +242,19 @@ class F3D_EXPORT engine /** * Load a plugin. - * Supports full path, relative path, and plugin name. + * The provided pathOrName can be a full path, relative path, or plugin name. * First try to load the plugin by name from the static plugins. * Then try to load the path provided as if it is a full path to a plugin. - * Then try to load a plugin by its name looking into the provided plugin search paths. - * Then try to load a plugin by its name relying on internal system (eg: LD_LIBRARY_PATH). + * Then try to load a plugin by its name looking into the provided plugin search paths (used as + * is). Then try to load a plugin by its name relying on internal system (eg: LD_LIBRARY_PATH). * The plugin "native" is always available and includes native VTK readers. * If built and available in your build, F3D is providing 5 additional plugins: * "alembic", "assimp", "draco", "exodus", "occt", "usd". * Custom plugins can also be available that F3D is not supporting officially. * Throw a plugin_exception if the plugin can't be loaded for some reason. */ - static void loadPlugin( - const std::string& nameOrPath, const std::vector& pluginSearchPaths = {}); + static void loadPlugin(const std::string& pathOrName, + const std::vector& pluginSearchPaths = {}); /** * Automatically load all the static plugins. @@ -227,11 +263,14 @@ class F3D_EXPORT engine static void autoloadPlugins(); /** - * List plugins based on associated json files located in the given directory. + * List plugins based on associated json files located in the given directory, used as is. * Listed plugins can be loaded using engine::loadPlugin function. * Note that the listed plugins may fail to load if the library is not found or incompatible. + * Return available plugins if any, or an empty vector if there are none or the provided path does + * not exist. */ - [[nodiscard]] static std::vector getPluginsList(const std::string& pluginPath); + [[nodiscard]] static std::vector getPluginsList( + const std::filesystem::path& pluginPath); /** * A structure providing information about the libf3d. @@ -302,6 +341,15 @@ class F3D_EXPORT engine explicit plugin_exception(const std::string& what = ""); }; + /** + * An exception that can be thrown by the engine + * when the cache cannot be used + */ + struct cache_exception : public exception + { + explicit cache_exception(const std::string& what = ""); + }; + private: class internals; internals* Internals; diff --git a/library/src/engine.cxx b/library/src/engine.cxx index d1f2197422..5d083d529f 100644 --- a/library/src/engine.cxx +++ b/library/src/engine.cxx @@ -13,13 +13,13 @@ #include -#include #include -#include #include #include +namespace fs = std::filesystem; + namespace f3d { class engine::internals @@ -52,18 +52,29 @@ engine::engine( // Ensure all lib initialization is done (once) detail::init::initialize(); - // build default cache path #if defined(_WIN32) - std::string cachePath = vtksys::SystemTools::GetEnv("LOCALAPPDATA"); - cachePath = cachePath + "/f3d"; + static constexpr const char* CACHE_ENV_VAR = "LOCALAPPDATA"; +#else + static constexpr const char* CACHE_ENV_VAR = "HOME"; +#endif + + char* env = std::getenv(CACHE_ENV_VAR); + if (!env) + { + throw engine::cache_exception( + std::string("Could not setup cache, please set ") + CACHE_ENV_VAR + " environment variable"); + } + + fs::path cachePath(env); + +#if defined(_WIN32) + cachePath /= "f3d"; #elif defined(__APPLE__) - std::string cachePath = vtksys::SystemTools::GetEnv("HOME"); - cachePath += "/Library/Caches/f3d"; + cachePath = cachePath / "Library" / "Caches" / "f3d"; #elif defined(__ANDROID__) - std::string cachePath = ""; // no default + // XXX: Android does not have a default cache location for now #elif defined(__unix__) - std::string cachePath = vtksys::SystemTools::GetEnv("HOME"); - cachePath += "/.cache/f3d"; + cachePath = cachePath / ".cache" / "f3d"; #else #error "Unsupported platform" #endif @@ -240,7 +251,7 @@ std::map engine::getRenderingBackendList() } //---------------------------------------------------------------------------- -void engine::loadPlugin(const std::string& pathOrName, const std::vector& searchPaths) +void engine::loadPlugin(const std::string& pathOrName, const std::vector& searchPaths) { if (pathOrName.empty()) { @@ -266,73 +277,78 @@ void engine::loadPlugin(const std::string& pathOrName, const std::vector( vtksys::DynamicLoader::GetSymbolAddress(handle, "init_plugin")); @@ -356,23 +372,17 @@ void engine::autoloadPlugins() } //---------------------------------------------------------------------------- -std::vector engine::getPluginsList(const std::string& pluginPath) +std::vector engine::getPluginsList(const fs::path& pluginPath) { - vtksys::Directory dir; constexpr std::string_view ext = ".json"; std::vector pluginNames; - - if (dir.Load(pluginPath)) + try { - for (unsigned long i = 0; i < dir.GetNumberOfFiles(); i++) + for (auto& entry : fs::directory_iterator(pluginPath)) { - std::string currentFile = dir.GetFile(i); - if (std::equal(ext.rbegin(), ext.rend(), currentFile.rbegin())) + const fs::path& fullPath = entry.path(); + if (fullPath.extension() == ext) { - std::string fullPath = dir.GetPath(); - fullPath += "/"; - fullPath += currentFile; - try { auto root = nlohmann::json::parse(std::ifstream(fullPath)); @@ -391,6 +401,9 @@ std::vector engine::getPluginsList(const std::string& pluginPath) } } } + catch (const fs::filesystem_error&) + { + } return pluginNames; } @@ -469,7 +482,7 @@ std::vector engine::getReadersInfo() } //---------------------------------------------------------------------------- -engine& engine::setCachePath(const std::string& cachePath) +engine& engine::setCachePath(const fs::path& cachePath) { this->Internals->Window->SetCachePath(cachePath); return *this; @@ -493,4 +506,10 @@ engine::plugin_exception::plugin_exception(const std::string& what) { } +//---------------------------------------------------------------------------- +engine::cache_exception::cache_exception(const std::string& what) + : exception(what) +{ +} + } diff --git a/library/src/window_impl.cxx b/library/src/window_impl.cxx index db1e85d5b4..53107918a0 100644 --- a/library/src/window_impl.cxx +++ b/library/src/window_impl.cxx @@ -22,7 +22,6 @@ #include #include #include -#include #ifdef VTK_USE_X #include @@ -42,6 +41,8 @@ #include +namespace fs = std::filesystem; + namespace f3d::detail { class window_impl::internals @@ -52,14 +53,6 @@ class window_impl::internals { } - std::string GetCachePath() - { - // create directories if they do not exist - vtksys::SystemTools::MakeDirectory(this->CachePath); - - return this->CachePath; - } - static context::fptr SymbolLoader(void* userptr, const char* name) { assert(userptr != nullptr); @@ -106,7 +99,7 @@ class window_impl::internals vtkNew Renderer; const options& Options; interactor_impl* Interactor = nullptr; - std::string CachePath; + fs::path CachePath; context::function GetProcAddress; }; @@ -129,7 +122,7 @@ window_impl::window_impl(const options& options, const std::optional& type #if defined(VTK_OPENGL_HAS_EGL) this->Internals->RenWin = vtkSmartPointer::New(); #else - throw engine::no_window_exception("Window type is EGL but VTK EGL support is not enabled"); + assert(false); // Unreachable #endif } else if (type == Type::OSMESA) @@ -138,7 +131,7 @@ window_impl::window_impl(const options& options, const std::optional& type this->Internals->RenWin = vtkSmartPointer::New(); #else throw engine::no_window_exception( - "Window type is OSMesa but VTK OSMesa support is not enabled"); + "Window type is OSMesa but the underlying VTK version is not recent enough to support it"); #endif } else if (type == Type::GLX) @@ -146,13 +139,15 @@ window_impl::window_impl(const options& options, const std::optional& type #if defined(VTK_USE_X) this->Internals->RenWin = vtkSmartPointer::New(); #else - throw engine::no_window_exception("Window type is GLX but VTK GLX support is not enabled"); + assert(false); // Unreachable #endif } else if (type == Type::WGL) { #ifdef _WIN32 this->Internals->RenWin = vtkSmartPointer::New(); +#else + assert(false); // Unreachable #endif } else if (!type.has_value()) @@ -160,6 +155,8 @@ window_impl::window_impl(const options& options, const std::optional& type this->Internals->RenWin = internals::AutoBackendWindow(); } + // COCOA and WASM are not handled explicitly + // as there is no helper method to create them in engine if (this->Internals->RenWin == nullptr) { throw engine::no_window_exception("Cannot create a window"); @@ -374,7 +371,7 @@ void window_impl::UpdateDynamicOptions() } // Set the cache path if not already - renderer->SetCachePath(this->Internals->GetCachePath()); + renderer->SetCachePath(this->Internals->CachePath.string()); // Make sure lights are created before we take options into account renderer->UpdateLights(); @@ -552,8 +549,18 @@ void window_impl::SetImporter(vtkF3DMetaImporter* importer) } //---------------------------------------------------------------------------- -void window_impl::SetCachePath(const std::string& cachePath) +void window_impl::SetCachePath(const fs::path& cachePath) { + try + { + // create directories if they do not exist + fs::create_directories(cachePath); + } + catch (const fs::filesystem_error& ex) + { + throw engine::cache_exception(std::string("Could not use cache: ") + ex.what()); + } + this->Internals->CachePath = cachePath; } diff --git a/library/testing/TestSDKEngine.cxx b/library/testing/TestSDKEngine.cxx index 7aed0478b3..bb7a4f4882 100644 --- a/library/testing/TestSDKEngine.cxx +++ b/library/testing/TestSDKEngine.cxx @@ -12,7 +12,8 @@ int TestSDKEngine(int argc, char* argv[]) f3d::log::setVerboseLevel(f3d::log::VerboseLevel::DEBUG); // clang-format off - // Load all plugins, built or not + // Load all plugins, built or not, already loaded or not + try { f3d::engine::loadPlugin("native", {argv[3]}); } catch (...) {} try { f3d::engine::loadPlugin("alembic", {argv[3]}); } catch (...) {} try { f3d::engine::loadPlugin("assimp", {argv[3]}); } catch (...) {} try { f3d::engine::loadPlugin("draco", {argv[3]}); } catch (...) {} @@ -71,5 +72,9 @@ int TestSDKEngine(int argc, char* argv[]) return EXIT_FAILURE; } + // coverage for getPluginList + std::ignore = f3d::engine::getPluginsList(std::string(argv[1]) + "configs"); + std::ignore = f3d::engine::getPluginsList("inexistent"); + return EXIT_SUCCESS; } diff --git a/library/testing/TestSDKEngineExceptions.cxx b/library/testing/TestSDKEngineExceptions.cxx index f5f4dc60fb..d971404bc9 100644 --- a/library/testing/TestSDKEngineExceptions.cxx +++ b/library/testing/TestSDKEngineExceptions.cxx @@ -1,99 +1,68 @@ +#include "PseudoUnitTest.h" + #include #include #include -#include +#include + +#include + +namespace fs = std::filesystem; int TestSDKEngineExceptions(int argc, char* argv[]) { - f3d::engine eng = f3d::engine::createNone(); + PseudoUnitTest test; - try - { - const f3d::window& win = eng.getWindow(); - std::cerr << "An exception has not been thrown when getting a non-existent window" << std::endl; - return EXIT_FAILURE; - } - catch (const f3d::engine::no_window_exception& ex) { - std::cout << ex.what() << std::endl; - } + f3d::engine eng = f3d::engine::createNone(); + test.expect( + "get non-existent window", [&]() { std::ignore = eng.getWindow(); }); + test.expect( + "get non-existent interactor", [&]() { std::ignore = eng.getInteractor(); }); - try - { - const f3d::interactor& inter = eng.getInteractor(); - std::cerr << "An exception has not been thrown when getting a non-existent interactor" - << std::endl; - return EXIT_FAILURE; - } - catch (const f3d::engine::no_interactor_exception& ex) - { - std::cout << ex.what() << std::endl; - } + // Test setCachePath error handling + test.expect("set cache path with invalid long name", + [&]() { eng.setCachePath("/" + std::string(257, 'x')); }); -// These tests are defined for coverage -#ifdef __linux__ - try - { - eng = f3d::engine::createWGL(); - std::cerr << "An exception has not been thrown when creating a WGL engine on Linux" - << std::endl; - return EXIT_FAILURE; - } - catch (const f3d::context::loading_exception& ex) - { - std::cout << ex.what() << std::endl; + // cover operator=(engine&&) + eng = f3d::engine::create(false); + test("engine assignment operator", eng.getWindow().isOffscreen() == false); } - try - { - eng = f3d::engine::createExternalWGL(); - std::cerr << "An exception has not been thrown when creating an external WGL engine on Linux" - << std::endl; - return EXIT_FAILURE; - } - catch (const f3d::context::loading_exception& ex) - { - std::cout << ex.what() << std::endl; - } +#ifdef __linux__ + // Test incorrect engine configuration + test.expect( + "create WGL engine on linux", [&]() { std::ignore = f3d::engine::createWGL(); }); + test.expect("create external WGL engine on linux", + [&]() { std::ignore = f3d::engine::createExternalWGL(); }); + test.expect("create external COCOA engine on linux", + [&]() { std::ignore = f3d::engine::createExternalCOCOA(); }); + test.expect( + "create external engine with invalid library", [&]() { + std::ignore = f3d::engine::createExternal(f3d::context::getSymbol("invalid", "invalid")); + }); + test.expect("create external engine with invalid symbol", [&]() { + std::ignore = f3d::engine::createExternal(f3d::context::getSymbol("GLX", "invalid")); + }); +#endif - try - { - eng = f3d::engine::createExternalCOCOA(); - std::cerr << "An exception has not been thrown when creating an external COCOA engine on Linux" - << std::endl; - return EXIT_FAILURE; - } - catch (const f3d::context::loading_exception& ex) - { - std::cout << ex.what() << std::endl; - } + // Test loadPlugin error handling + test.expect("load plugin with invalid library", + [&]() { f3d::engine::loadPlugin(std::string(argv[1]) + "data/invalid.so"); }); - try - { - eng = f3d::engine::createExternal(f3d::context::getSymbol("invalid", "invalid")); - std::cerr << "An exception has not been thrown when loading an invalid library" << std::endl; - return EXIT_FAILURE; - } - catch (const f3d::context::loading_exception& ex) - { - std::cout << ex.what() << std::endl; - } + test.expect("load plugin with invalid library from search paths", + [&]() { f3d::engine::loadPlugin("invalid", { fs::path(std::string(argv[1]) + "data") }); }); - try - { - eng = f3d::engine::createExternal(f3d::context::getSymbol("GLX", "invalid")); - std::cerr << "An exception has not been thrown when loading an invalid symbol" << std::endl; - return EXIT_FAILURE; - } - catch (const f3d::context::symbol_exception& ex) - { - std::cout << ex.what() << std::endl; - } + test.expect("load plugin with invalid long name", + [&]() { f3d::engine::loadPlugin("/" + std::string(257, 'x') + "/file.ext"); }); - // cover operator=(engine&&) - eng = f3d::engine::create(false); +#ifdef __linux__ + // Test error handling without "HOME" set + unsetenv("HOME"); + test.expect( + "Create engine without HOME set", [&]() { std::ignore = f3d::engine::createNone(); }); #endif - return EXIT_SUCCESS; + return test.result(); } diff --git a/testing/data/libf3d-plugin-invalid.so b/testing/data/libf3d-plugin-invalid.so new file mode 100644 index 0000000000..8575163cc5 --- /dev/null +++ b/testing/data/libf3d-plugin-invalid.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e2df53aec82f9d93ad26e57748dd2e61299bb3a1cf14f55005f180e75e960789 +size 28