#include #include #include #include #include #include #include #include #include #include #include #include #include namespace { class TemporaryPath final { public: explicit TemporaryPath(std::filesystem::path path) : path_{std::move(path)} { std::error_code error; std::filesystem::create_directories(path_.parent_path(), error); if (error) { throw std::runtime_error{"Failed to create pipeline test directory."}; } std::filesystem::remove(path_, error); } ~TemporaryPath() { std::error_code error; std::filesystem::remove(path_, error); } TemporaryPath(const TemporaryPath&) = delete; TemporaryPath& operator=(const TemporaryPath&) = delete; [[nodiscard]] const std::filesystem::path& path() const noexcept { return path_; } private: std::filesystem::path path_; }; std::filesystem::path fixture_path(const std::string_view name) { return std::filesystem::path{FESA_TEST_SOURCE_DIR} / "fixtures" / "abaqus" / name; } std::filesystem::path test_output_path(const std::string_view name) { return std::filesystem::path{FESA_TEST_BINARY_DIR} / "testing" / name; } void write_text( const std::filesystem::path& path, const std::string_view contents) { std::filesystem::create_directories(path.parent_path()); std::ofstream output{path, std::ios::binary}; output.write( contents.data(), static_cast(contents.size())); if (!output) { throw std::runtime_error{"Failed to write pipeline test input."}; } } std::string quote(const std::filesystem::path& path) { return '"' + path.string() + '"'; } std::string read_text(const std::filesystem::path& path) { std::ifstream input{path, std::ios::binary}; return { std::istreambuf_iterator{input}, std::istreambuf_iterator{}, }; } TEST(MinimalCantileverPipeline, WritesReadableFiniteEquilibratedResults) { const TemporaryPath output{ test_output_path("minimal-cantilever-pipeline.h5")}; const fesa::AnalysisRunResult run = fesa::run_solver({ fixture_path("minimal_cantilever.inp"), output.path(), }); ASSERT_TRUE(run.succeeded); EXPECT_TRUE(run.diagnostics.empty()); const fesa::Hdf5ReadResult read = fesa::read_hdf5_results(output.path()); ASSERT_TRUE(read.database.has_value()); ASSERT_TRUE(read.model.has_value()); EXPECT_TRUE(read.diagnostics.empty()); EXPECT_EQ(read.database->schema_version, "1.0.0"); ASSERT_EQ(read.model->nodes.size(), 2U); EXPECT_EQ(read.model->nodes[0].id, fesa::NodeId{0}); EXPECT_EQ(read.model->nodes[1].id, fesa::NodeId{1}); ASSERT_EQ(read.database->steps.size(), 1U); ASSERT_EQ(read.database->steps[0].frames.size(), 1U); const fesa::NodalFrame& nodal = read.database->steps[0].frames[0].nodal; ASSERT_EQ(nodal.node_ids.size(), 2U); ASSERT_EQ(nodal.displacement.size(), 2U); ASSERT_EQ(nodal.reaction.size(), 2U); EXPECT_EQ(nodal.node_ids[0], fesa::NodeId{0}); EXPECT_EQ(nodal.node_ids[1], fesa::NodeId{1}); for (const auto& values : nodal.displacement) { for (const double value : values) { EXPECT_TRUE(std::isfinite(value)); } } for (const auto& values : nodal.reaction) { for (const double value : values) { EXPECT_TRUE(std::isfinite(value)); } } const double total_y_reaction = nodal.reaction[0][1] + nodal.reaction[1][1]; EXPECT_NEAR(total_y_reaction - 1.0, 0.0, 1.0e-12); EXPECT_LT(nodal.displacement[1][1], 0.0); } TEST(MinimalCantileverPipeline, CliPreservesFailureSourceDiagnostic) { const TemporaryPath input{ test_output_path("minimal-cantilever-invalid.inp")}; const TemporaryPath output{ test_output_path("minimal-cantilever-invalid.h5")}; const TemporaryPath error_output{ test_output_path("minimal-cantilever-invalid.stderr.txt")}; write_text( input.path(), "*NODE\n" "*UNSUPPORTED\n"); const std::string command = '"' + quote(std::filesystem::path{FESA_CLI_PATH}) + " solve " + quote(input.path()) + " --output " + quote(output.path()) + " 1>NUL 2>" + quote(error_output.path()) + '"'; const int exit_code = std::system(command.c_str()); EXPECT_NE(exit_code, 0); EXPECT_FALSE(std::filesystem::exists(output.path())); const std::string diagnostic = read_text(error_output.path()); EXPECT_NE(diagnostic.find("syntax"), std::string::npos); EXPECT_NE( diagnostic.find("abaqus.unsupported_keyword"), std::string::npos); EXPECT_NE(diagnostic.find(input.path().string()), std::string::npos); EXPECT_NE(diagnostic.find(":2:1"), std::string::npos); } } // namespace