Skip to content

Commit b663ed6

Browse files
authored
BUG: Treat a missing preferences file as a normal first run (#1827)
* Application::loadPreferences() returned an error Result when the preferences file did not exist. The Application constructor then printed "Error initializing application: ..." to stderr on the first run of every executable on a fresh account. The CTest build log parser matches that line and reports the build as a failure. * The missing-file case is expected. Default preferences are used and the file is written on shutdown. loadPreferences() now returns a valid Result for that case and keeps the error for unreadable or malformed files. * Add a loadPreferences(const std::filesystem::path&) overload so the behavior can be tested without the user's real preferences file. * Move the Preferences file error codes into the Preferences class as public constants so callers can identify the missing-file case. * Add tests for the missing-file, malformed-JSON, and direct Preferences::loadFromFile cases. Signed-off-by: Michael Jackson <mike.jackson@bluequartz.net>
1 parent 4d86385 commit b663ed6

5 files changed

Lines changed: 91 additions & 14 deletions

File tree

‎src/simplnx/Core/Application.cpp‎

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -195,17 +195,24 @@ std::filesystem::path Application::getCurrentDir() const
195195
}
196196

197197
Result<> Application::loadPreferences()
198+
{
199+
return loadPreferences(Preferences::DefaultFilePath(getApplicationName(this)));
200+
}
201+
202+
Result<> Application::loadPreferences(const std::filesystem::path& filepath)
198203
{
199204
if(m_Preferences == nullptr)
200205
{
201206
m_Preferences = std::make_unique<Preferences>();
202207
}
203-
std::string applicationName = getApplicationName(this);
204-
const auto filepath = Preferences::DefaultFilePath(applicationName);
205-
206208
auto result = m_Preferences->loadFromFile(filepath);
207209
if(result.invalid())
208210
{
211+
if(!result.errors().empty() && result.errors()[0].code == Preferences::k_FileDoesNotExist_Code)
212+
{
213+
// On the first run, defaults are used and the preferences file is written on shutdown.
214+
return {};
215+
}
209216
return MakeErrorResult(-1, fmt::format("Could not load preferences from '{}': {}", filepath.string(), result.errors().empty() ? "unknown error" : result.errors()[0].message));
210217
}
211218
return result;

‎src/simplnx/Core/Application.hpp‎

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,10 +114,21 @@ class SIMPLNX_EXPORT Application
114114

115115
/**
116116
* @brief Loads preferences from the default file path.
117-
* @return Error when the file cannot load or parse.
117+
* @return Success if the file loads or does not exist; error for other load or parse failures.
118+
*
119+
* On the first run, defaults are used and the preferences file is written on shutdown.
118120
*/
119121
Result<> loadPreferences();
120122

123+
/**
124+
* @brief Loads preferences from the specified file path.
125+
* @param filepath Preferences JSON file to load.
126+
* @return Success if the file loads or does not exist; error for other load or parse failures.
127+
*
128+
* A missing file leaves the current preferences and defaults unchanged.
129+
*/
130+
Result<> loadPreferences(const std::filesystem::path& filepath);
131+
121132
/**
122133
* @brief Returns no JSON pipeline builder.
123134
* @return nullptr.

‎src/simplnx/Core/Preferences.cpp‎

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,6 @@ constexpr StringLiteral k_DefaultFileName = "preferences.json";
2929
constexpr int64 k_ReducedDataStructureSize = 3221225472; // 3 GB
3030
constexpr bool k_AutoRangeComputationDefault = false;
3131

32-
constexpr int32 k_FailedToCreateDirectory_Code = -585;
33-
constexpr int32 k_FileDoesNotExist_Code = -586;
34-
constexpr int32 k_FileCouldNotOpen_Code = -587;
35-
constexpr int32 k_JsonParseError_Code = -588;
36-
3732
constexpr StringLiteral k_FailedToCreateDirectory_Message = "Failed to create the parent directory when saving Preferences. Check that the path is valid and writable.";
3833
constexpr StringLiteral k_FileDoesNotExist_Message = "Preferences file does not exist";
3934
constexpr StringLiteral k_FileCouldNotOpen_Message = "Could not open Preferences file";
@@ -208,13 +203,13 @@ Result<> Preferences::saveToFile(const std::filesystem::path& filepath) const
208203
{
209204
if(!std::filesystem::exists(filepath.parent_path()) && !std::filesystem::create_directories(filepath.parent_path()))
210205
{
211-
return MakeErrorResult(k_FailedToCreateDirectory_Code, k_FailedToCreateDirectory_Message);
206+
return MakeErrorResult(Preferences::k_FailedToCreateDirectory_Code, k_FailedToCreateDirectory_Message);
212207
}
213208

214209
std::ofstream fileStream(filepath);
215210
if(!fileStream.is_open())
216211
{
217-
return MakeErrorResult(k_FileCouldNotOpen_Code, k_FileCouldNotOpen_Message);
212+
return MakeErrorResult(Preferences::k_FileCouldNotOpen_Code, k_FileCouldNotOpen_Message);
218213
}
219214

220215
fileStream << m_Values;
@@ -225,19 +220,19 @@ Result<> Preferences::loadFromFile(const std::filesystem::path& filepath)
225220
{
226221
if(!std::filesystem::exists(filepath))
227222
{
228-
return MakeErrorResult(k_FileDoesNotExist_Code, k_FileDoesNotExist_Message);
223+
return MakeErrorResult(Preferences::k_FileDoesNotExist_Code, k_FileDoesNotExist_Message);
229224
}
230225

231226
std::ifstream fileStream(filepath);
232227
if(!fileStream.is_open())
233228
{
234-
return MakeErrorResult(k_FileCouldNotOpen_Code, k_FileCouldNotOpen_Message);
229+
return MakeErrorResult(Preferences::k_FileCouldNotOpen_Code, k_FileCouldNotOpen_Message);
235230
}
236231

237232
nlohmann::json parsedResult = nlohmann::json::parse(fileStream, nullptr, false);
238233
if(parsedResult.is_discarded())
239234
{
240-
return MakeErrorResult(k_JsonParseError_Code, k_JsonParseError_Message);
235+
return MakeErrorResult(Preferences::k_JsonParseError_Code, k_JsonParseError_Message);
241236
}
242237

243238
m_Values = parsedResult;

‎src/simplnx/Core/Preferences.hpp‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ class SIMPLNX_EXPORT Preferences
4141
friend class AbstractPlugin;
4242

4343
public:
44+
static inline constexpr int32 k_FailedToCreateDirectory_Code = -585;
45+
static inline constexpr int32 k_FileDoesNotExist_Code = -586;
46+
static inline constexpr int32 k_FileCouldNotOpen_Code = -587;
47+
static inline constexpr int32 k_JsonParseError_Code = -588;
48+
4449
static inline constexpr StringLiteral k_LargeDataSize_Key = "large_data_size";
4550

4651
// Migration reads this legacy key. The core does not write it.

‎test/AppTest.cpp‎

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11

2+
#include "simplnx/Common/ScopeGuard.hpp"
23
#include "simplnx/Core/Application.hpp"
34
#include "simplnx/Core/Preferences.hpp"
45

6+
#include "simplnx/unit_test/simplnx_test_dirs.hpp"
7+
58
#include <catch2/catch.hpp>
69

710
#include <filesystem>
811
#include <fstream>
12+
#include <system_error>
913

1014
using namespace nx::core;
1115
namespace fs = std::filesystem;
@@ -96,6 +100,61 @@ TEST_CASE("Application::loadPreferences", "[Application]")
96100
}
97101
}
98102

103+
TEST_CASE("Application::loadPreferences from a specified path", "[Application]")
104+
{
105+
const fs::path tempDir = fs::path(unit_test::k_BinaryTestOutputDir.view()) / ("ApplicationPreferences_" + Uuid::GenerateV4().str());
106+
REQUIRE(fs::create_directories(tempDir));
107+
auto cleanup = MakeScopeGuard([&tempDir]() noexcept {
108+
std::error_code error;
109+
fs::remove_all(tempDir, error);
110+
});
111+
const fs::path filepath = tempDir / "preferences.json";
112+
113+
SECTION("A missing preferences file is a normal first run")
114+
{
115+
auto app = Application::GetOrCreateInstance();
116+
REQUIRE_FALSE(fs::exists(filepath));
117+
118+
const auto result = app->loadPreferences(filepath);
119+
120+
REQUIRE(result.valid());
121+
REQUIRE(result.warnings().empty());
122+
REQUIRE(app->getPreferences() != nullptr);
123+
REQUIRE_FALSE(fs::exists(filepath));
124+
}
125+
126+
SECTION("Malformed JSON remains an application error")
127+
{
128+
{
129+
std::ofstream file(filepath);
130+
REQUIRE(file.is_open());
131+
file << "{ not json";
132+
file.close();
133+
REQUIRE(file.good());
134+
}
135+
auto app = Application::GetOrCreateInstance();
136+
137+
const auto result = app->loadPreferences(filepath);
138+
139+
REQUIRE(result.invalid());
140+
REQUIRE_FALSE(result.errors().empty());
141+
REQUIRE(result.errors()[0].code == -1);
142+
REQUIRE(result.errors()[0].message.find("Could not load preferences") != std::string::npos);
143+
}
144+
145+
SECTION("Preferences reports the missing-file error code directly")
146+
{
147+
Preferences preferences;
148+
REQUIRE_FALSE(fs::exists(filepath));
149+
150+
const auto result = preferences.loadFromFile(filepath);
151+
152+
REQUIRE(result.invalid());
153+
REQUIRE_FALSE(result.errors().empty());
154+
REQUIRE(result.errors()[0].code == Preferences::k_FileDoesNotExist_Code);
155+
}
156+
}
157+
99158
TEST_CASE("Application::savePreferences", "[Application]")
100159
{
101160
auto app = Application::GetOrCreateInstance();

0 commit comments

Comments
 (0)