feat(nonlinear-static-newton-raphson): step 0 - cli-output-lease-and-derived-names
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
#include "fesa/app/output_paths.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "fesa/analysis/analysis.h"
|
||||
|
||||
namespace {
|
||||
|
||||
class TempDirectory {
|
||||
public:
|
||||
explicit TempDirectory(const std::string& label) {
|
||||
const auto tick =
|
||||
std::chrono::steady_clock::now().time_since_epoch().count();
|
||||
path_ = std::filesystem::temp_directory_path() /
|
||||
("fesa-output-paths-" + label + "-" + std::to_string(tick));
|
||||
std::error_code error;
|
||||
if (!std::filesystem::create_directory(path_, error) || error) {
|
||||
throw std::runtime_error{"Unable to create an output-path test fixture."};
|
||||
}
|
||||
}
|
||||
|
||||
TempDirectory(const TempDirectory&) = delete;
|
||||
TempDirectory& operator=(const TempDirectory&) = delete;
|
||||
|
||||
~TempDirectory() {
|
||||
std::error_code ignored;
|
||||
std::filesystem::remove_all(path_, ignored);
|
||||
}
|
||||
|
||||
const std::filesystem::path& Path() const noexcept { return path_; }
|
||||
|
||||
private:
|
||||
std::filesystem::path path_;
|
||||
};
|
||||
|
||||
void ExpectFailureCode(const fesa::Status& status, const char* code) {
|
||||
ASSERT_FALSE(status.IsOk());
|
||||
ASSERT_EQ(status.Diagnostics().size(), 1U);
|
||||
EXPECT_EQ(status.Diagnostics().front().code, code);
|
||||
}
|
||||
|
||||
// NSNR-CLI-001 and NSNR-CLI-002: a token containing the literal substring
|
||||
// must fail before input path validation or output derivation.
|
||||
TEST(OutputPaths, RejectsFirstTokenContainingOutputSubstring) {
|
||||
const auto paths = fesa::DeriveSingleInputOutputPaths(
|
||||
{"abc--outputdef", "model.inp", "--output=ignored"},
|
||||
std::filesystem::path{"C:/invocation"});
|
||||
|
||||
ASSERT_FALSE(paths.HasValue());
|
||||
ExpectFailureCode(paths.GetStatus(), "cli-usage");
|
||||
EXPECT_EQ(paths.GetStatus().Diagnostics().front().entity_identity,
|
||||
"argument_index=0;argument=abc--outputdef");
|
||||
}
|
||||
|
||||
TEST(OutputPaths, RejectsEveryOutputSubstringVariant) {
|
||||
for (const char* argument :
|
||||
{"abc--outputdef", "--output-path", "--output=foo", "--output"}) {
|
||||
const auto paths = fesa::DeriveSingleInputOutputPaths(
|
||||
{"model.inp", argument}, std::filesystem::path{"C:/invocation"});
|
||||
|
||||
ASSERT_FALSE(paths.HasValue()) << argument;
|
||||
ExpectFailureCode(paths.GetStatus(), "cli-usage");
|
||||
}
|
||||
}
|
||||
|
||||
// NSNR-CLI-004: derivation is lexical and does not inspect the filesystem.
|
||||
TEST(OutputPaths, DerivesNormalizedSiblingNamesFromRelativeInput) {
|
||||
const auto paths = fesa::DeriveSingleInputOutputPaths(
|
||||
{"inputs/../models/Beam.Model.INP"},
|
||||
std::filesystem::path{"C:/invocation/current"});
|
||||
|
||||
ASSERT_TRUE(paths.HasValue()) << paths.GetStatus().Diagnostics().size();
|
||||
EXPECT_EQ(
|
||||
paths.Value().input_path,
|
||||
std::filesystem::path{"C:/invocation/current/models/Beam.Model.INP"});
|
||||
EXPECT_EQ(paths.Value().results_path,
|
||||
std::filesystem::path{
|
||||
"C:/invocation/current/models/Beam.Model_results.h5"});
|
||||
EXPECT_EQ(paths.Value().log_path,
|
||||
std::filesystem::path{
|
||||
"C:/invocation/current/models/Beam.Model_log.txt"});
|
||||
}
|
||||
|
||||
// NSNR-CLI-005: only the final, case-insensitive input extension is removed.
|
||||
TEST(OutputPaths, PreservesBasenameCaseAndPrecedingDots) {
|
||||
const auto paths = fesa::DeriveSingleInputOutputPaths(
|
||||
{"C:/models/.Beam.v2.iNp"}, std::filesystem::path{"C:/unused"});
|
||||
|
||||
ASSERT_TRUE(paths.HasValue());
|
||||
EXPECT_EQ(paths.Value().results_path,
|
||||
std::filesystem::path{"C:/models/.Beam.v2_results.h5"});
|
||||
EXPECT_EQ(paths.Value().log_path,
|
||||
std::filesystem::path{"C:/models/.Beam.v2_log.txt"});
|
||||
}
|
||||
|
||||
TEST(OutputPaths, RejectsInputWithoutInpFinalExtension) {
|
||||
const auto paths = fesa::DeriveSingleInputOutputPaths(
|
||||
{"model.inp.bak"}, std::filesystem::path{"C:/invocation"});
|
||||
|
||||
ASSERT_FALSE(paths.HasValue());
|
||||
ExpectFailureCode(paths.GetStatus(), "cli-usage");
|
||||
}
|
||||
|
||||
// NSNR-CLI-006: the lease is exclusive and does not create public outputs.
|
||||
TEST(OutputPaths, LeaseCollisionPreservesPublicOutputPaths) {
|
||||
TempDirectory directory{"lease"};
|
||||
const fesa::DerivedOutputPaths paths{
|
||||
directory.Path() / "model.inp", directory.Path() / "model_results.h5",
|
||||
directory.Path() / "model_log.txt"};
|
||||
|
||||
auto first = fesa::AcquireOutputWriteLease(paths);
|
||||
ASSERT_TRUE(first.HasValue()) << first.GetStatus().Diagnostics().size();
|
||||
EXPECT_FALSE(std::filesystem::exists(paths.results_path));
|
||||
EXPECT_FALSE(std::filesystem::exists(paths.log_path));
|
||||
|
||||
const auto second = fesa::AcquireOutputWriteLease(paths);
|
||||
ASSERT_FALSE(second.HasValue());
|
||||
ExpectFailureCode(second.GetStatus(), "output-busy");
|
||||
EXPECT_FALSE(std::filesystem::exists(paths.results_path));
|
||||
EXPECT_FALSE(std::filesystem::exists(paths.log_path));
|
||||
}
|
||||
|
||||
// NSNR-CLI-009: procedure calls receive all derived output identities.
|
||||
TEST(OutputPaths, AnalysisRequestCarriesInputResultAndLogPaths) {
|
||||
const fesa::AnalysisRequest request{
|
||||
std::filesystem::path{"C:/models/model.inp"},
|
||||
std::filesystem::path{"C:/models/model_results.h5"},
|
||||
std::filesystem::path{"C:/models/model_log.txt"}};
|
||||
|
||||
EXPECT_EQ(request.input_path, "C:/models/model.inp");
|
||||
EXPECT_EQ(request.output_path, "C:/models/model_results.h5");
|
||||
EXPECT_EQ(request.log_path, "C:/models/model_log.txt");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
Reference in New Issue
Block a user