Repository: f3d-app/f3d
Pull request #2064: context: Add exception handling
Author: login: exbluesbreaker
Created: 2025-03-14T21:06:01Z | Merged: 2025-03-16T15:31:44Z

Related to https://github.com/f3d-app/f3d/issues/1727


Diff:
diff --git a/application/F3DStarter.cxx b/application/F3DStarter.cxx
index 3995f1cd4d..b72c5509e7 100644
--- a/application/F3DStarter.cxx
+++ b/application/F3DStarter.cxx
@@ -936,29 +936,52 @@ int F3DStarter::Start(int argc, char** argv)
     bool offscreen = !this->Internals->AppOptions.Reference.empty() ||
       !this->Internals->AppOptions.Output.empty() || this->Internals->AppOptions.BindingsList;
 
-    if (this->Internals->AppOptions.RenderingBackend == "egl")
+    try
     {
-      this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createEGL());
+      if (this->Internals->AppOptions.RenderingBackend == "egl")
+      {
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createEGL());
+      }
+      else if (this->Internals->AppOptions.RenderingBackend == "osmesa")
+      {
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createOSMesa());
+      }
+      else if (this->Internals->AppOptions.RenderingBackend == "glx")
+      {
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createGLX(offscreen));
+      }
+      else if (this->Internals->AppOptions.RenderingBackend == "wgl")
+      {
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createWGL(offscreen));
+      }
+      else
+      {
+        if (this->Internals->AppOptions.RenderingBackend != "auto")
+        {
+          f3d::log::warn("--rendering-backend value is invalid, falling back to \"auto\"");
+        }
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::create(offscreen));
+      }
     }
-    else if (this->Internals->AppOptions.RenderingBackend == "osmesa")
+    catch (const f3d::context::loading_exception& ex)
     {
-      this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createOSMesa());
+      f3d::log::error("Could not load graphic library: ", ex.what());
+      return EXIT_FAILURE;
     }
-    else if (this->Internals->AppOptions.RenderingBackend == "glx")
+    catch (const f3d::context::symbol_exception& ex)
     {
-      this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createGLX(offscreen));
+      f3d::log::error("Could not find needed symbol in graphic library: ", ex.what());
+      return EXIT_FAILURE;
     }
-    else if (this->Internals->AppOptions.RenderingBackend == "wgl")
+    catch (const f3d::engine::no_window_exception& ex)
     {
-      this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createWGL(offscreen));
+      f3d::log::error("Could not create the window: ", ex.what());
+      return EXIT_FAILURE;
     }
-    else
+    catch (const f3d::engine::cache_exception& ex)
     {
-      if (this->Internals->AppOptions.RenderingBackend != "auto")
-      {
-        f3d::log::warn("--rendering-backend value is invalid, falling back to \"auto\"");
-      }
-      this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::create(offscreen));
+      f3d::log::error("Could not use default cache directory: ", ex.what());
+      return EXIT_FAILURE;
     }
 
     this->ResetWindowName();
diff --git a/application/testing/CMakeLists.txt b/application/testing/CMakeLists.txt
index 55f8fec39a..df948e74a2 100644
--- a/application/testing/CMakeLists.txt
+++ b/application/testing/CMakeLists.txt
@@ -1187,6 +1187,8 @@ set_tests_properties(f3d::TestNoFileFileNameTemplate PROPERTIES PASS_REGULAR_EXP
 set_tests_properties(f3d::TestNoFileFileNameTemplate PROPERTIES ENVIRONMENT "CTEST_F3D_NO_DATA_FORCE_RENDER=1")
 
 if(NOT WIN32)
+  f3d_test(NAME TestInvalidCache ENV "HOME=" REGEXP "Could not use default cache directory" NO_BASELINE)
+
   add_test(NAME f3d::TestHOMEOutput COMMAND $<TARGET_FILE:f3d> ${F3D_SOURCE_DIR}/testing/data/suzanne.stl --output=~/Testing/Temporary/TestHOMEOutput.png --resolution=300,300 --no-config)
   set_tests_properties(f3d::TestHOMEOutput PROPERTIES ENVIRONMENT "HOME=${CMAKE_BINARY_DIR}")
   set_tests_properties(f3d::TestHOMEOutput PROPERTIES FIXTURES_SETUP f3d::TestHOMEOutput_FIXTURE)
diff --git a/library/src/engine.cxx b/library/src/engine.cxx
index 3557b0bc38..8d106273c5 100644
--- a/library/src/engine.cxx
+++ b/library/src/engine.cxx
@@ -85,6 +85,7 @@ engine::engine(
 #endif
   if (cachePath.empty())
   {
+    delete Internals;
     throw engine::cache_exception(
       "Could not setup cache, please set the appropriate environment variable");
   }


Reviews:
user: login: mwestphal
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-14T21:30:01Z
user: login: mwestphal
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-14T21:47:11Z
user: login: exbluesbreaker
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-14T21:48:37Z
user: login: exbluesbreaker
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-14T21:54:00Z
user: login: mwestphal
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-14T21:54:26Z
user: login: mwestphal
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-14T21:55:39Z
user: login: exbluesbreaker
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-14T22:01:50Z
user: login: mwestphal
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-15T06:21:24Z
user: login: mwestphal
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-15T06:25:03Z
user: login: exbluesbreaker
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-15T09:08:30Z
user: login: mwestphal
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-15T09:10:50Z
user: login: mwestphal
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-15T09:12:16Z
user: login: mwestphal
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-15T09:55:00Z
user: login: mwestphal
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-16T08:03:25Z
user: login: mwestphal
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-16T08:05:06Z
user: login: mwestphal
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-16T08:05:11Z
user: login: mwestphal
body: Approved but lets give some time for @Meakk to review
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-16T08:07:23Z
user: login: exbluesbreaker
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-16T10:27:16Z

Review comments:
diff_hunk: @@ -936,29 +936,52 @@ int F3DStarter::Start(int argc, char** argv)
     bool offscreen = !this->Internals->AppOptions.Reference.empty() ||
       !this->Internals->AppOptions.Output.empty() || this->Internals->AppOptions.BindingsList;
 
-    if (this->Internals->AppOptions.RenderingBackend == "egl")
+    try
     {
-      this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createEGL());
+      if (this->Internals->AppOptions.RenderingBackend == "egl")
+      {
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createEGL());
+      }
+      else if (this->Internals->AppOptions.RenderingBackend == "osmesa")
+      {
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createOSMesa());
+      }
+      else if (this->Internals->AppOptions.RenderingBackend == "glx")
+      {
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createGLX(offscreen));
+      }
+      else if (this->Internals->AppOptions.RenderingBackend == "wgl")
+      {
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createWGL(offscreen));
+      }
+      else
+      {
+        if (this->Internals->AppOptions.RenderingBackend != "auto")
+        {
+          f3d::log::warn("--rendering-backend value is invalid, falling back to \"auto\"");
+        }
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::create(offscreen));
+      }
     }
-    else if (this->Internals->AppOptions.RenderingBackend == "osmesa")
+    catch (const f3d::context::loading_exception& ex)
     {
-      this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createOSMesa());
+      f3d::log::error("Could not load graphic library: ", ex.what());
+      return EXIT_FAILURE;
     }
-    else if (this->Internals->AppOptions.RenderingBackend == "glx")
+    catch (const f3d::context::symbol_exception& ex)
     {
-      this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createGLX(offscreen));
+      f3d::log::error("Could not find needed symbol in graphic library: ", ex.what());
+      return EXIT_FAILURE;
     }
-    else if (this->Internals->AppOptions.RenderingBackend == "wgl")
+    catch (const f3d::engine::no_window_exception& ex)
     {
-      this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createWGL(offscreen));
+      f3d::log::error("Could not create the window: ", ex.what());
+      return EXIT_FAILURE;
     }
-    else
+    catch (const f3d::engine::cache_exception& ex)
     {
-      if (this->Internals->AppOptions.RenderingBackend != "auto")
-      {
-        f3d::log::warn("--rendering-backend value is invalid, falling back to \"auto\"");
-      }
-      this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::create(offscreen));
+      f3d::log::error("Could not use default cache directory: ", ex.what());
+      return EXIT_FAILURE;
path: application/F3DStarter.cxx
user: login: mwestphal
body: I think a test like this would cover it:

`f3d_test(NAME TestInvalidCache ENV "HOME=" REGEX "Could not use default cache directory" NO_BASELINE)`

created_at: 2025-03-14T21:30:01Z
author_association: MEMBER
diff_hunk: @@ -936,29 +936,52 @@ int F3DStarter::Start(int argc, char** argv)
     bool offscreen = !this->Internals->AppOptions.Reference.empty() ||
       !this->Internals->AppOptions.Output.empty() || this->Internals->AppOptions.BindingsList;
 
-    if (this->Internals->AppOptions.RenderingBackend == "egl")
+    try
     {
-      this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createEGL());
+      if (this->Internals->AppOptions.RenderingBackend == "egl")
+      {
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createEGL());
+      }
+      else if (this->Internals->AppOptions.RenderingBackend == "osmesa")
+      {
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createOSMesa());
+      }
+      else if (this->Internals->AppOptions.RenderingBackend == "glx")
+      {
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createGLX(offscreen));
+      }
+      else if (this->Internals->AppOptions.RenderingBackend == "wgl")
+      {
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createWGL(offscreen));
+      }
+      else
+      {
+        if (this->Internals->AppOptions.RenderingBackend != "auto")
+        {
+          f3d::log::warn("--rendering-backend value is invalid, falling back to \"auto\"");
+        }
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::create(offscreen));
+      }
     }
-    else if (this->Internals->AppOptions.RenderingBackend == "osmesa")
+    catch (const f3d::context::loading_exception& ex)
     {
-      this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createOSMesa());
+      f3d::log::error("Could not load graphic library: ", ex.what());
+      return EXIT_FAILURE;
     }
-    else if (this->Internals->AppOptions.RenderingBackend == "glx")
+    catch (const f3d::context::symbol_exception& ex)
     {
-      this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createGLX(offscreen));
+      f3d::log::error("Could not find needed symbol in graphic library: ", ex.what());
+      return EXIT_FAILURE;
path: application/F3DStarter.cxx
user: login: mwestphal
body: I wonder which test cover this one ? Any ideas @exbluesbreaker ?
created_at: 2025-03-14T21:47:10Z
author_association: MEMBER
diff_hunk: @@ -936,29 +936,52 @@ int F3DStarter::Start(int argc, char** argv)
     bool offscreen = !this->Internals->AppOptions.Reference.empty() ||
       !this->Internals->AppOptions.Output.empty() || this->Internals->AppOptions.BindingsList;
 
-    if (this->Internals->AppOptions.RenderingBackend == "egl")
+    try
     {
-      this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createEGL());
+      if (this->Internals->AppOptions.RenderingBackend == "egl")
+      {
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createEGL());
+      }
+      else if (this->Internals->AppOptions.RenderingBackend == "osmesa")
+      {
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createOSMesa());
+      }
+      else if (this->Internals->AppOptions.RenderingBackend == "glx")
+      {
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createGLX(offscreen));
+      }
+      else if (this->Internals->AppOptions.RenderingBackend == "wgl")
+      {
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createWGL(offscreen));
+      }
+      else
+      {
+        if (this->Internals->AppOptions.RenderingBackend != "auto")
+        {
+          f3d::log::warn("--rendering-backend value is invalid, falling back to \"auto\"");
+        }
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::create(offscreen));
+      }
     }
-    else if (this->Internals->AppOptions.RenderingBackend == "osmesa")
+    catch (const f3d::context::loading_exception& ex)
     {
-      this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createOSMesa());
+      f3d::log::error("Could not load graphic library: ", ex.what());
+      return EXIT_FAILURE;
     }
-    else if (this->Internals->AppOptions.RenderingBackend == "glx")
+    catch (const f3d::context::symbol_exception& ex)
     {
-      this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createGLX(offscreen));
+      f3d::log::error("Could not find needed symbol in graphic library: ", ex.what());
+      return EXIT_FAILURE;
     }
-    else if (this->Internals->AppOptions.RenderingBackend == "wgl")
+    catch (const f3d::engine::no_window_exception& ex)
     {
-      this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createWGL(offscreen));
+      f3d::log::error("Could not create the window: ", ex.what());
+      return EXIT_FAILURE;
     }
-    else
+    catch (const f3d::engine::cache_exception& ex)
     {
-      if (this->Internals->AppOptions.RenderingBackend != "auto")
-      {
-        f3d::log::warn("--rendering-backend value is invalid, falling back to \"auto\"");
-      }
-      this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::create(offscreen));
+      f3d::log::error("Could not use default cache directory: ", ex.what());
+      return EXIT_FAILURE;
path: application/F3DStarter.cxx
user: login: exbluesbreaker
body: > I think a test like this would cover it:
> 
> `f3d_test(NAME TestInvalidCache ENV "HOME=" REGEX "Could not use default cache directory" NO_BASELINE)`

I actually catch a memory leak with this test. This is asan traces:

```
555: Could not use default cache directory: Could not setup cache, please set the appropriate environment variable
555: 
555: =================================================================
555: ==21824==ERROR: LeakSanitizer: detected memory leaks
555: 
555: Direct leak of 32 byte(s) in 1 object(s) allocated from:
555:     #0 0x7ffff78fe548 in operator new(unsigned long) ../../../../src/libsanitizer/asan/asan_new_delete.cpp:95
555:     #1 0x7ffff6ed1d47 in f3d::engine::engine(std::optional<f3d::window::Type> const&, bool, std::function<void (*(char const*))()> const&) /home/bluesbreaker/Dev/contrib_repos/f3d/library/src/engine.cxx:50
555:     #2 0x7ffff6ed29c3 in f3d::engine::create(bool) /home/bluesbreaker/Dev/contrib_repos/f3d/library/src/engine.cxx:113
555:     #3 0x55555578403e in F3DStarter::Start(int, char**) /home/bluesbreaker/Dev/contrib_repos/f3d/application/F3DStarter.cxx:963
555:     #4 0x5555557ef7dc in main /home/bluesbreaker/Dev/contrib_repos/f3d/application/main.cxx:13
555:     #5 0x7ffff642a1c9 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
555:     #6 0x7ffff642a28a in __libc_start_main_impl ../csu/libc-start.c:360
555:     #7 0x55555560ebc4 in _start (/home/bluesbreaker/Dev/contrib_repos/f3d/build/bin/f3d+0xbabc4) (BuildId: 499edc7daeecb48f608acea2f6a45ca584e14aa5)
555: 
555: SUMMARY: AddressSanitizer: 32 byte(s) leaked in 1 allocation(s).
```

As I understood it is related to this really weird to passing new in constructor initializer:

```
engine::engine(
  const std::optional<window::Type>& windowType, bool offscreen, const context::function& loader)
  : Internals(new engine::internals)
```
I guess the right thing without reworking a lot is to do delete on  throwing exception, though it is ugly and smart_pointer-like approach is better.
created_at: 2025-03-14T21:48:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -936,29 +936,52 @@ int F3DStarter::Start(int argc, char** argv)
     bool offscreen = !this->Internals->AppOptions.Reference.empty() ||
       !this->Internals->AppOptions.Output.empty() || this->Internals->AppOptions.BindingsList;
 
-    if (this->Internals->AppOptions.RenderingBackend == "egl")
+    try
     {
-      this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createEGL());
+      if (this->Internals->AppOptions.RenderingBackend == "egl")
+      {
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createEGL());
+      }
+      else if (this->Internals->AppOptions.RenderingBackend == "osmesa")
+      {
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createOSMesa());
+      }
+      else if (this->Internals->AppOptions.RenderingBackend == "glx")
+      {
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createGLX(offscreen));
+      }
+      else if (this->Internals->AppOptions.RenderingBackend == "wgl")
+      {
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createWGL(offscreen));
+      }
+      else
+      {
+        if (this->Internals->AppOptions.RenderingBackend != "auto")
+        {
+          f3d::log::warn("--rendering-backend value is invalid, falling back to \"auto\"");
+        }
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::create(offscreen));
+      }
     }
-    else if (this->Internals->AppOptions.RenderingBackend == "osmesa")
+    catch (const f3d::context::loading_exception& ex)
     {
-      this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createOSMesa());
+      f3d::log::error("Could not load graphic library: ", ex.what());
+      return EXIT_FAILURE;
     }
-    else if (this->Internals->AppOptions.RenderingBackend == "glx")
+    catch (const f3d::context::symbol_exception& ex)
     {
-      this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createGLX(offscreen));
+      f3d::log::error("Could not find needed symbol in graphic library: ", ex.what());
+      return EXIT_FAILURE;
path: application/F3DStarter.cxx
user: login: exbluesbreaker
body: I'm not entirely sure, but based on my understanding and the context, it seems that it essentially loads the symbol for the basic "findProc" symbol in the library. This is nearly impossible to retrieve without missing the whole library, unless you create a fake library specifically for testing purposes. 

In this particular case, loading and symbol exceptions appear to be closely linked, as it checks for the most fundamental symbol in the library.
created_at: 2025-03-14T21:54:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -936,29 +936,52 @@ int F3DStarter::Start(int argc, char** argv)
     bool offscreen = !this->Internals->AppOptions.Reference.empty() ||
       !this->Internals->AppOptions.Output.empty() || this->Internals->AppOptions.BindingsList;
 
-    if (this->Internals->AppOptions.RenderingBackend == "egl")
+    try
     {
-      this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createEGL());
+      if (this->Internals->AppOptions.RenderingBackend == "egl")
+      {
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createEGL());
+      }
+      else if (this->Internals->AppOptions.RenderingBackend == "osmesa")
+      {
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createOSMesa());
+      }
+      else if (this->Internals->AppOptions.RenderingBackend == "glx")
+      {
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createGLX(offscreen));
+      }
+      else if (this->Internals->AppOptions.RenderingBackend == "wgl")
+      {
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createWGL(offscreen));
+      }
+      else
+      {
+        if (this->Internals->AppOptions.RenderingBackend != "auto")
+        {
+          f3d::log::warn("--rendering-backend value is invalid, falling back to \"auto\"");
+        }
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::create(offscreen));
+      }
     }
-    else if (this->Internals->AppOptions.RenderingBackend == "osmesa")
+    catch (const f3d::context::loading_exception& ex)
     {
-      this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createOSMesa());
+      f3d::log::error("Could not load graphic library: ", ex.what());
+      return EXIT_FAILURE;
     }
-    else if (this->Internals->AppOptions.RenderingBackend == "glx")
+    catch (const f3d::context::symbol_exception& ex)
     {
-      this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createGLX(offscreen));
+      f3d::log::error("Could not find needed symbol in graphic library: ", ex.what());
+      return EXIT_FAILURE;
     }
-    else if (this->Internals->AppOptions.RenderingBackend == "wgl")
+    catch (const f3d::engine::no_window_exception& ex)
     {
-      this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createWGL(offscreen));
+      f3d::log::error("Could not create the window: ", ex.what());
+      return EXIT_FAILURE;
     }
-    else
+    catch (const f3d::engine::cache_exception& ex)
     {
-      if (this->Internals->AppOptions.RenderingBackend != "auto")
-      {
-        f3d::log::warn("--rendering-backend value is invalid, falling back to \"auto\"");
-      }
-      this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::create(offscreen));
+      f3d::log::error("Could not use default cache directory: ", ex.what());
+      return EXIT_FAILURE;
path: application/F3DStarter.cxx
user: login: mwestphal
body: > I guess the right thing without reworking a lot is to do delete on throwing exception, though it is ugly and smart_pointer-like approach is better.

Indedd. Both solutions are okay but a std::unique_ptr on the Internals should fix this easily I think
created_at: 2025-03-14T21:54:26Z
author_association: MEMBER
diff_hunk: @@ -936,29 +936,52 @@ int F3DStarter::Start(int argc, char** argv)
     bool offscreen = !this->Internals->AppOptions.Reference.empty() ||
       !this->Internals->AppOptions.Output.empty() || this->Internals->AppOptions.BindingsList;
 
-    if (this->Internals->AppOptions.RenderingBackend == "egl")
+    try
     {
-      this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createEGL());
+      if (this->Internals->AppOptions.RenderingBackend == "egl")
+      {
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createEGL());
+      }
+      else if (this->Internals->AppOptions.RenderingBackend == "osmesa")
+      {
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createOSMesa());
+      }
+      else if (this->Internals->AppOptions.RenderingBackend == "glx")
+      {
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createGLX(offscreen));
+      }
+      else if (this->Internals->AppOptions.RenderingBackend == "wgl")
+      {
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createWGL(offscreen));
+      }
+      else
+      {
+        if (this->Internals->AppOptions.RenderingBackend != "auto")
+        {
+          f3d::log::warn("--rendering-backend value is invalid, falling back to \"auto\"");
+        }
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::create(offscreen));
+      }
     }
-    else if (this->Internals->AppOptions.RenderingBackend == "osmesa")
+    catch (const f3d::context::loading_exception& ex)
     {
-      this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createOSMesa());
+      f3d::log::error("Could not load graphic library: ", ex.what());
+      return EXIT_FAILURE;
     }
-    else if (this->Internals->AppOptions.RenderingBackend == "glx")
+    catch (const f3d::context::symbol_exception& ex)
     {
-      this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createGLX(offscreen));
+      f3d::log::error("Could not find needed symbol in graphic library: ", ex.what());
+      return EXIT_FAILURE;
path: application/F3DStarter.cxx
user: login: mwestphal
body: Yes I agree, however codecov report this line to be covered, and I wonder how is that possible.
created_at: 2025-03-14T21:55:39Z
author_association: MEMBER
diff_hunk: @@ -936,29 +936,52 @@ int F3DStarter::Start(int argc, char** argv)
     bool offscreen = !this->Internals->AppOptions.Reference.empty() ||
       !this->Internals->AppOptions.Output.empty() || this->Internals->AppOptions.BindingsList;
 
-    if (this->Internals->AppOptions.RenderingBackend == "egl")
+    try
     {
-      this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createEGL());
+      if (this->Internals->AppOptions.RenderingBackend == "egl")
+      {
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createEGL());
+      }
+      else if (this->Internals->AppOptions.RenderingBackend == "osmesa")
+      {
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createOSMesa());
+      }
+      else if (this->Internals->AppOptions.RenderingBackend == "glx")
+      {
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createGLX(offscreen));
+      }
+      else if (this->Internals->AppOptions.RenderingBackend == "wgl")
+      {
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createWGL(offscreen));
+      }
+      else
+      {
+        if (this->Internals->AppOptions.RenderingBackend != "auto")
+        {
+          f3d::log::warn("--rendering-backend value is invalid, falling back to \"auto\"");
+        }
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::create(offscreen));
+      }
     }
-    else if (this->Internals->AppOptions.RenderingBackend == "osmesa")
+    catch (const f3d::context::loading_exception& ex)
     {
-      this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createOSMesa());
+      f3d::log::error("Could not load graphic library: ", ex.what());
+      return EXIT_FAILURE;
     }
-    else if (this->Internals->AppOptions.RenderingBackend == "glx")
+    catch (const f3d::context::symbol_exception& ex)
     {
-      this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createGLX(offscreen));
+      f3d::log::error("Could not find needed symbol in graphic library: ", ex.what());
+      return EXIT_FAILURE;
path: application/F3DStarter.cxx
user: login: exbluesbreaker
body: I guess I should remove this exception handling because in this place only loading of library could fail, missing symbol in library could be simulated only by "fabricated" library, it can be raised at some other path working with symbol load when it tries to load some speicfic functions which could be missing by some reason.

I will try to fix it, add memory leak fix and also add test case as you proposed.
created_at: 2025-03-14T22:01:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -936,29 +936,52 @@ int F3DStarter::Start(int argc, char** argv)
     bool offscreen = !this->Internals->AppOptions.Reference.empty() ||
       !this->Internals->AppOptions.Output.empty() || this->Internals->AppOptions.BindingsList;
 
-    if (this->Internals->AppOptions.RenderingBackend == "egl")
+    try
     {
-      this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createEGL());
+      if (this->Internals->AppOptions.RenderingBackend == "egl")
+      {
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createEGL());
+      }
+      else if (this->Internals->AppOptions.RenderingBackend == "osmesa")
+      {
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createOSMesa());
+      }
+      else if (this->Internals->AppOptions.RenderingBackend == "glx")
+      {
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createGLX(offscreen));
+      }
+      else if (this->Internals->AppOptions.RenderingBackend == "wgl")
+      {
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createWGL(offscreen));
+      }
+      else
+      {
+        if (this->Internals->AppOptions.RenderingBackend != "auto")
+        {
+          f3d::log::warn("--rendering-backend value is invalid, falling back to \"auto\"");
+        }
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::create(offscreen));
+      }
     }
-    else if (this->Internals->AppOptions.RenderingBackend == "osmesa")
+    catch (const f3d::context::loading_exception& ex)
     {
-      this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createOSMesa());
+      f3d::log::error("Could not load graphic library: ", ex.what());
+      return EXIT_FAILURE;
     }
-    else if (this->Internals->AppOptions.RenderingBackend == "glx")
+    catch (const f3d::context::symbol_exception& ex)
     {
-      this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createGLX(offscreen));
+      f3d::log::error("Could not find needed symbol in graphic library: ", ex.what());
+      return EXIT_FAILURE;
path: application/F3DStarter.cxx
user: login: mwestphal
body: I'm not following why you should not catch this. Indeed, it may not be testable but if someones as a faulty lib this definitely can happen.
created_at: 2025-03-15T06:21:24Z
author_association: MEMBER
diff_hunk: @@ -936,29 +936,52 @@ int F3DStarter::Start(int argc, char** argv)
     bool offscreen = !this->Internals->AppOptions.Reference.empty() ||
       !this->Internals->AppOptions.Output.empty() || this->Internals->AppOptions.BindingsList;
 
-    if (this->Internals->AppOptions.RenderingBackend == "egl")
+    try
     {
-      this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createEGL());
+      if (this->Internals->AppOptions.RenderingBackend == "egl")
+      {
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createEGL());
+      }
+      else if (this->Internals->AppOptions.RenderingBackend == "osmesa")
+      {
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createOSMesa());
+      }
+      else if (this->Internals->AppOptions.RenderingBackend == "glx")
+      {
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createGLX(offscreen));
+      }
+      else if (this->Internals->AppOptions.RenderingBackend == "wgl")
+      {
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createWGL(offscreen));
+      }
+      else
+      {
+        if (this->Internals->AppOptions.RenderingBackend != "auto")
+        {
+          f3d::log::warn("--rendering-backend value is invalid, falling back to \"auto\"");
+        }
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::create(offscreen));
+      }
     }
-    else if (this->Internals->AppOptions.RenderingBackend == "osmesa")
+    catch (const f3d::context::loading_exception& ex)
     {
-      this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createOSMesa());
+      f3d::log::error("Could not load graphic library: ", ex.what());
+      return EXIT_FAILURE;
     }
-    else if (this->Internals->AppOptions.RenderingBackend == "glx")
+    catch (const f3d::context::symbol_exception& ex)
     {
-      this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createGLX(offscreen));
+      f3d::log::error("Could not find needed symbol in graphic library: ", ex.what());
+      return EXIT_FAILURE;
path: application/F3DStarter.cxx
user: login: mwestphal
body: Here is how I was able to reach this:

```
 sudo mv /usr/lib/libEGL.so.1.1.0 /usr/lib/libEGL.so.1.1.0.old
 sudo cp /usr/lib/libtwhatever.so /usr/lib/libEGL.so.1.1.0
 ./bin/f3d --rendering-backend=egl ../src/testing/data/dragon.vtu --output=a.png
 ```
 
```
terminate called after throwing an instance of 'f3d::context::symbol_exception'
  what():  Cannot find eglGetProcAddress symbol
```

Of course we cannot test like this in CI but codecov do report this catch as reached and I dont understand why.

(dont forget to mv back the libEGL.so after if you do this^^)
created_at: 2025-03-15T06:25:03Z
author_association: MEMBER
diff_hunk: @@ -936,29 +936,52 @@ int F3DStarter::Start(int argc, char** argv)
     bool offscreen = !this->Internals->AppOptions.Reference.empty() ||
       !this->Internals->AppOptions.Output.empty() || this->Internals->AppOptions.BindingsList;
 
-    if (this->Internals->AppOptions.RenderingBackend == "egl")
+    try
     {
-      this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createEGL());
+      if (this->Internals->AppOptions.RenderingBackend == "egl")
+      {
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createEGL());
+      }
+      else if (this->Internals->AppOptions.RenderingBackend == "osmesa")
+      {
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createOSMesa());
+      }
+      else if (this->Internals->AppOptions.RenderingBackend == "glx")
+      {
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createGLX(offscreen));
+      }
+      else if (this->Internals->AppOptions.RenderingBackend == "wgl")
+      {
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createWGL(offscreen));
+      }
+      else
+      {
+        if (this->Internals->AppOptions.RenderingBackend != "auto")
+        {
+          f3d::log::warn("--rendering-backend value is invalid, falling back to \"auto\"");
+        }
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::create(offscreen));
+      }
     }
-    else if (this->Internals->AppOptions.RenderingBackend == "osmesa")
+    catch (const f3d::context::loading_exception& ex)
     {
-      this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createOSMesa());
+      f3d::log::error("Could not load graphic library: ", ex.what());
+      return EXIT_FAILURE;
     }
-    else if (this->Internals->AppOptions.RenderingBackend == "glx")
+    catch (const f3d::context::symbol_exception& ex)
     {
-      this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createGLX(offscreen));
+      f3d::log::error("Could not find needed symbol in graphic library: ", ex.what());
+      return EXIT_FAILURE;
path: application/F3DStarter.cxx
user: login: exbluesbreaker
body: Yes, it definitely can cause exception in such situation, I could add catching this exception back, but then we need to somehow deal with having coverage.
created_at: 2025-03-15T09:08:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -936,29 +936,52 @@ int F3DStarter::Start(int argc, char** argv)
     bool offscreen = !this->Internals->AppOptions.Reference.empty() ||
       !this->Internals->AppOptions.Output.empty() || this->Internals->AppOptions.BindingsList;
 
-    if (this->Internals->AppOptions.RenderingBackend == "egl")
+    try
     {
-      this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createEGL());
+      if (this->Internals->AppOptions.RenderingBackend == "egl")
+      {
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createEGL());
+      }
+      else if (this->Internals->AppOptions.RenderingBackend == "osmesa")
+      {
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createOSMesa());
+      }
+      else if (this->Internals->AppOptions.RenderingBackend == "glx")
+      {
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createGLX(offscreen));
+      }
+      else if (this->Internals->AppOptions.RenderingBackend == "wgl")
+      {
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createWGL(offscreen));
+      }
+      else
+      {
+        if (this->Internals->AppOptions.RenderingBackend != "auto")
+        {
+          f3d::log::warn("--rendering-backend value is invalid, falling back to \"auto\"");
+        }
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::create(offscreen));
+      }
     }
-    else if (this->Internals->AppOptions.RenderingBackend == "osmesa")
+    catch (const f3d::context::loading_exception& ex)
     {
-      this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createOSMesa());
+      f3d::log::error("Could not load graphic library: ", ex.what());
+      return EXIT_FAILURE;
     }
-    else if (this->Internals->AppOptions.RenderingBackend == "glx")
+    catch (const f3d::context::symbol_exception& ex)
     {
-      this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createGLX(offscreen));
+      f3d::log::error("Could not find needed symbol in graphic library: ", ex.what());
+      return EXIT_FAILURE;
path: application/F3DStarter.cxx
user: login: mwestphal
body: its ok to not cover something if we cant. Coverage is only a metric :)

Please indeed add it back.

but the problem is that it is reported as covered by codecov and we need to understand why.
created_at: 2025-03-15T09:10:50Z
author_association: MEMBER
diff_hunk: @@ -1136,6 +1136,9 @@ f3d_test(NAME TestBindingsList ARGS --list-bindings REGEXP "Any.5        Toggle
 # Test rendering backends
 # For some reason the sanitizer detects leaks because of EGL and OSMesa
 f3d_test(NAME TestRenderingBackendAuto DATA cow.vtp RENDERING_BACKEND auto)
+if(NOT WIN32)
+  f3d_test(NAME TestInvalidCache ENV "HOME=" REGEXP "Could not use default cache directory" NO_BASELINE)
path: application/testing/CMakeLists.txt
user: login: mwestphal
body: this test should not be put here, lets put it with other linux specific tests, eg before `TestHOMEOutput` 
created_at: 2025-03-15T09:12:16Z
author_association: MEMBER
diff_hunk: @@ -1190,6 +1187,7 @@ set_tests_properties(f3d::TestNoFileFileNameTemplate PROPERTIES PASS_REGULAR_EXP
 set_tests_properties(f3d::TestNoFileFileNameTemplate PROPERTIES ENVIRONMENT "CTEST_F3D_NO_DATA_FORCE_RENDER=1")
 
 if(NOT WIN32)
+  f3d_test(NAME TestInvalidCache ENV "HOME=" REGEXP "Could not use default cache directory" NO_BASELINE)
path: application/testing/CMakeLists.txt
user: login: mwestphal
body: lets add an empty line after the test, clearer this way
created_at: 2025-03-15T09:55:00Z
author_association: MEMBER
diff_hunk: @@ -936,29 +936,52 @@ int F3DStarter::Start(int argc, char** argv)
     bool offscreen = !this->Internals->AppOptions.Reference.empty() ||
       !this->Internals->AppOptions.Output.empty() || this->Internals->AppOptions.BindingsList;
 
-    if (this->Internals->AppOptions.RenderingBackend == "egl")
+    try
     {
-      this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createEGL());
+      if (this->Internals->AppOptions.RenderingBackend == "egl")
+      {
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createEGL());
+      }
+      else if (this->Internals->AppOptions.RenderingBackend == "osmesa")
+      {
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createOSMesa());
+      }
+      else if (this->Internals->AppOptions.RenderingBackend == "glx")
+      {
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createGLX(offscreen));
+      }
+      else if (this->Internals->AppOptions.RenderingBackend == "wgl")
+      {
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createWGL(offscreen));
+      }
+      else
+      {
+        if (this->Internals->AppOptions.RenderingBackend != "auto")
+        {
+          f3d::log::warn("--rendering-backend value is invalid, falling back to \"auto\"");
+        }
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::create(offscreen));
+      }
     }
-    else if (this->Internals->AppOptions.RenderingBackend == "osmesa")
+    catch (const f3d::context::loading_exception& ex)
     {
-      this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createOSMesa());
+      f3d::log::error("Could not load graphic library: ", ex.what());
+      return EXIT_FAILURE;
     }
-    else if (this->Internals->AppOptions.RenderingBackend == "glx")
+    catch (const f3d::context::symbol_exception& ex)
     {
-      this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createGLX(offscreen));
+      f3d::log::error("Could not find needed symbol in graphic library: ", ex.what());
+      return EXIT_FAILURE;
path: application/F3DStarter.cxx
user: login: mwestphal
body: nevermind, it is not reported as covered, its just ignored by codecov for some reason. no worries.
created_at: 2025-03-16T08:03:25Z
author_association: MEMBER
diff_hunk: @@ -936,29 +936,52 @@ int F3DStarter::Start(int argc, char** argv)
     bool offscreen = !this->Internals->AppOptions.Reference.empty() ||
       !this->Internals->AppOptions.Output.empty() || this->Internals->AppOptions.BindingsList;
 
-    if (this->Internals->AppOptions.RenderingBackend == "egl")
+    try
     {
-      this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createEGL());
+      if (this->Internals->AppOptions.RenderingBackend == "egl")
+      {
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createEGL());
+      }
+      else if (this->Internals->AppOptions.RenderingBackend == "osmesa")
+      {
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createOSMesa());
+      }
+      else if (this->Internals->AppOptions.RenderingBackend == "glx")
+      {
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createGLX(offscreen));
+      }
+      else if (this->Internals->AppOptions.RenderingBackend == "wgl")
+      {
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createWGL(offscreen));
+      }
+      else
+      {
+        if (this->Internals->AppOptions.RenderingBackend != "auto")
+        {
+          f3d::log::warn("--rendering-backend value is invalid, falling back to \"auto\"");
+        }
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::create(offscreen));
+      }
     }
-    else if (this->Internals->AppOptions.RenderingBackend == "osmesa")
+    catch (const f3d::context::loading_exception& ex)
     {
-      this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createOSMesa());
+      f3d::log::error("Could not load graphic library: ", ex.what());
+      return EXIT_FAILURE;
     }
-    else if (this->Internals->AppOptions.RenderingBackend == "glx")
+    catch (const f3d::context::symbol_exception& ex)
     {
-      this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createGLX(offscreen));
+      f3d::log::error("Could not find needed symbol in graphic library: ", ex.what());
+      return EXIT_FAILURE;
     }
-    else if (this->Internals->AppOptions.RenderingBackend == "wgl")
+    catch (const f3d::engine::no_window_exception& ex)
     {
-      this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createWGL(offscreen));
+      f3d::log::error("Could not create the window: ", ex.what());
+      return EXIT_FAILURE;
     }
-    else
+    catch (const f3d::engine::cache_exception& ex)
     {
-      if (this->Internals->AppOptions.RenderingBackend != "auto")
-      {
-        f3d::log::warn("--rendering-backend value is invalid, falling back to \"auto\"");
-      }
-      this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::create(offscreen));
+      f3d::log::error("Could not use default cache directory: ", ex.what());
+      return EXIT_FAILURE;
path: application/F3DStarter.cxx
user: login: mwestphal
body: Somehow codecov still reports it as not covered but we can be sure it is. codecov bug I guess ? Lets ignore.
created_at: 2025-03-16T08:05:06Z
author_association: MEMBER
diff_hunk: @@ -936,29 +936,52 @@ int F3DStarter::Start(int argc, char** argv)
     bool offscreen = !this->Internals->AppOptions.Reference.empty() ||
       !this->Internals->AppOptions.Output.empty() || this->Internals->AppOptions.BindingsList;
 
-    if (this->Internals->AppOptions.RenderingBackend == "egl")
+    try
     {
-      this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createEGL());
+      if (this->Internals->AppOptions.RenderingBackend == "egl")
+      {
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createEGL());
+      }
+      else if (this->Internals->AppOptions.RenderingBackend == "osmesa")
+      {
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createOSMesa());
+      }
+      else if (this->Internals->AppOptions.RenderingBackend == "glx")
+      {
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createGLX(offscreen));
+      }
+      else if (this->Internals->AppOptions.RenderingBackend == "wgl")
+      {
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createWGL(offscreen));
+      }
+      else
+      {
+        if (this->Internals->AppOptions.RenderingBackend != "auto")
+        {
+          f3d::log::warn("--rendering-backend value is invalid, falling back to \"auto\"");
+        }
+        this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::create(offscreen));
+      }
     }
-    else if (this->Internals->AppOptions.RenderingBackend == "osmesa")
+    catch (const f3d::context::loading_exception& ex)
     {
-      this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createOSMesa());
+      f3d::log::error("Could not load graphic library: ", ex.what());
+      return EXIT_FAILURE;
     }
-    else if (this->Internals->AppOptions.RenderingBackend == "glx")
+    catch (const f3d::context::symbol_exception& ex)
     {
-      this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createGLX(offscreen));
+      f3d::log::error("Could not find needed symbol in graphic library: ", ex.what());
+      return EXIT_FAILURE;
     }
-    else if (this->Internals->AppOptions.RenderingBackend == "wgl")
+    catch (const f3d::engine::no_window_exception& ex)
     {
-      this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::createWGL(offscreen));
+      f3d::log::error("Could not create the window: ", ex.what());
+      return EXIT_FAILURE;
     }
-    else
+    catch (const f3d::engine::cache_exception& ex)
     {
-      if (this->Internals->AppOptions.RenderingBackend != "auto")
-      {
-        f3d::log::warn("--rendering-backend value is invalid, falling back to \"auto\"");
-      }
-      this->Internals->Engine = std::make_unique<f3d::engine>(f3d::engine::create(offscreen));
+      f3d::log::error("Could not use default cache directory: ", ex.what());
+      return EXIT_FAILURE;
path: application/F3DStarter.cxx
user: login: exbluesbreaker
body: > Somehow codecov still reports it as not covered but we can be sure it is. codecov bug I guess ? Lets ignore.

Yeah, test case for cache exception seems passing.
created_at: 2025-03-16T10:27:16Z
author_association: CONTRIBUTOR

PR comments:
user: login: exbluesbreaker
created_at: 2025-03-14T21:08:00Z
body: I do not know yet how to test all of the exceptions, I know there are already existing test case for not supported GL wrappers, but I am not sure how to test other exception being returned in f3d application. I saw some fabricated code in unit tests, only.
author_association: CONTRIBUTOR
user: login: mwestphal
created_at: 2025-03-14T21:17:58Z
body: Some of these are already tested:

 - TestRenderingBackendWGLFailure
 - TestRenderingBackendGLXNoDisplay

Lets see what coverage says and we will see what we need to add
author_association: MEMBER
user: login: codecov[bot]
created_at: 2025-03-14T21:20:23Z
body: ## [Codecov](https://app.codecov.io/gh/f3d-app/f3d/pull/2064?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=f3d-app) Report
Attention: Patch coverage is `88.88889%` with `2 lines` in your changes missing coverage. Please review.
> Project coverage is 95.81%. Comparing base [(`9d5bc10`)](https://app.codecov.io/gh/f3d-app/f3d/commit/9d5bc108cb3baded7f433974f4ef10750a7dd9fa?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=f3d-app) to head [(`4f5465b`)](https://app.codecov.io/gh/f3d-app/f3d/commit/4f5465b5a842afcb83bc2250965b4959d985180c?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=f3d-app).
> Report is 34 commits behind head on master.

| [Files with missing lines](https://app.codecov.io/gh/f3d-app/f3d/pull/2064?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=f3d-app) | Patch % | Lines |
|---|---|---|
| [application/F3DStarter.cxx](https://app.codecov.io/gh/f3d-app/f3d/pull/2064?src=pr&el=tree&filepath=application%2FF3DStarter.cxx&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=f3d-app#diff-YXBwbGljYXRpb24vRjNEU3RhcnRlci5jeHg=) | 88.23% | [2 Missing :warning: ](https://app.codecov.io/gh/f3d-app/f3d/pull/2064?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=f3d-app) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master    #2064      +/-   ##
==========================================
+ Coverage   95.79%   95.81%   +0.01%     
==========================================
  Files         128      128              
  Lines       10485    10558      +73     
==========================================
+ Hits        10044    10116      +72     
- Misses        441      442       +1     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/f3d-app/f3d/pull/2064?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=f3d-app).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=f3d-app).

<details><summary>🚀 New features to boost your workflow: </summary>

- ❄ [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
- 📦 [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
</details>
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-03-14T22:23:08Z
body: You are modifying libf3d public API! :warning:Please update bindings accordingly:warning:!
You can find them in their respective directories: `python`, `java`, `webassembly`.
<!-- Sticky Pull Request Comment -->
author_association: NONE
user: login: exbluesbreaker
created_at: 2025-03-14T22:23:35Z
body: > You are modifying libf3d public API! ⚠️Please update bindings accordingly⚠️! You can find them in their respective directories: `python`, `java`, `webassembly`.

Oops
author_association: CONTRIBUTOR
user: login: exbluesbreaker
created_at: 2025-03-14T22:37:40Z
body: Can somebody have a look about bindings. I changed `Internals` field in engine from raw to unique_ptr, it doesn't seem it is directly provided in bindings
author_association: CONTRIBUTOR
user: login: exbluesbreaker
created_at: 2025-03-14T22:43:14Z
body: Ok, Windows also complains about exporting now, I guess I will just went back to raw pointer and add deallocation.
author_association: CONTRIBUTOR
user: login: mwestphal
created_at: 2025-03-16T15:31:58Z
body: Thanks for the contribution @exbluesbreaker !
author_association: MEMBER