feat(nonlinear-static-newton-raphson): step 0 - cli-output-lease-and-derived-names

This commit is contained in:
KOKO\Mimi
2026-08-24 10:42:14 +09:00
parent 41816c8aef
commit fc16ef6221
61 changed files with 10890 additions and 115 deletions
+141
View File
@@ -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