feat(thermal): connect transient heat application execution
This commit is contained in:
@@ -0,0 +1,491 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <hdf5.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <random>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "fesa/app/fesa_application.h"
|
||||
|
||||
#define NOMINMAX
|
||||
#include <Windows.h>
|
||||
|
||||
#include "fesa/analysis/linear_frequency_procedure.h"
|
||||
#include "fesa/analysis/linear_static_procedure.h"
|
||||
#include "fesa/analysis/newton_iteration_log.h"
|
||||
#include "fesa/analysis/nonlinear_static_procedure.h"
|
||||
#include "fesa/analysis/prepared_analysis.h"
|
||||
#include "fesa/app/output_paths.h"
|
||||
#include "fesa/assembly/parallel_for.h"
|
||||
#include "fesa/io/hdf5/eigen_hdf5_results_writer.h"
|
||||
#include "fesa/io/hdf5/hdf5_results_writer.h"
|
||||
#include "fesa/io/hdf5/nonlinear_hdf5_results_writer.h"
|
||||
#include "fesa/solvers/linear/mkl_pardiso_solver.h"
|
||||
#include "fesa/solvers/linear/mkl_pardiso_tangent_solver.h"
|
||||
|
||||
namespace {
|
||||
|
||||
class HdfHandle {
|
||||
public:
|
||||
HdfHandle(hid_t value, herr_t (*close)(hid_t))
|
||||
: value_(value), close_(close) {
|
||||
if (value < 0) throw std::runtime_error{"Cannot open HDF test object."};
|
||||
}
|
||||
~HdfHandle() { close_(value_); }
|
||||
HdfHandle(const HdfHandle&) = delete;
|
||||
HdfHandle& operator=(const HdfHandle&) = delete;
|
||||
hid_t Get() const { return value_; }
|
||||
|
||||
private:
|
||||
hid_t value_;
|
||||
herr_t (*close_)(hid_t);
|
||||
};
|
||||
|
||||
std::vector<hsize_t> Shape(hid_t file, const std::string& path) {
|
||||
HdfHandle data{H5Dopen2(file, path.c_str(), H5P_DEFAULT), H5Dclose};
|
||||
HdfHandle space{H5Dget_space(data.Get()), H5Sclose};
|
||||
const int rank = H5Sget_simple_extent_ndims(space.Get());
|
||||
if (rank < 0) throw std::runtime_error{"Invalid HDF test shape."};
|
||||
std::vector<hsize_t> shape(static_cast<std::size_t>(rank));
|
||||
EXPECT_GE(H5Sget_simple_extent_dims(space.Get(), shape.data(), nullptr), 0);
|
||||
return shape;
|
||||
}
|
||||
|
||||
std::vector<double> Doubles(hid_t file, const std::string& path) {
|
||||
std::size_t count = 1;
|
||||
for (auto extent : Shape(file, path))
|
||||
count *= static_cast<std::size_t>(extent);
|
||||
std::vector<double> result(count);
|
||||
HdfHandle data{H5Dopen2(file, path.c_str(), H5P_DEFAULT), H5Dclose};
|
||||
EXPECT_GE(H5Dread(data.Get(), H5T_NATIVE_DOUBLE, H5S_ALL, H5S_ALL,
|
||||
H5P_DEFAULT, result.data()),
|
||||
0);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string StringAttribute(hid_t object, const char* name) {
|
||||
HdfHandle attribute{H5Aopen(object, name, H5P_DEFAULT), H5Aclose};
|
||||
HdfHandle type{H5Aget_type(attribute.Get()), H5Tclose};
|
||||
char* value = nullptr;
|
||||
EXPECT_GE(H5Aread(attribute.Get(), type.Get(), &value), 0);
|
||||
const std::string result = value == nullptr ? "" : value;
|
||||
if (value != nullptr) H5free_memory(value);
|
||||
return result;
|
||||
}
|
||||
|
||||
struct FrameRow {
|
||||
std::uint64_t frame_index;
|
||||
std::uint64_t increment_index;
|
||||
double time;
|
||||
double time_increment;
|
||||
std::uint8_t is_initial;
|
||||
};
|
||||
|
||||
std::vector<FrameRow> Frames(hid_t file) {
|
||||
const std::string path = "/steps/Step-1/accepted_frames";
|
||||
const auto shape = Shape(file, path);
|
||||
if (shape.size() != 1) throw std::runtime_error{"Invalid frame shape."};
|
||||
std::vector<FrameRow> frames(static_cast<std::size_t>(shape[0]));
|
||||
HdfHandle type{H5Tcreate(H5T_COMPOUND, sizeof(FrameRow)), H5Tclose};
|
||||
EXPECT_GE(H5Tinsert(type.Get(), "frame_index", HOFFSET(FrameRow, frame_index),
|
||||
H5T_NATIVE_UINT64),
|
||||
0);
|
||||
EXPECT_GE(H5Tinsert(type.Get(), "increment_index",
|
||||
HOFFSET(FrameRow, increment_index), H5T_NATIVE_UINT64),
|
||||
0);
|
||||
EXPECT_GE(
|
||||
H5Tinsert(type.Get(), "time", HOFFSET(FrameRow, time), H5T_NATIVE_DOUBLE),
|
||||
0);
|
||||
EXPECT_GE(H5Tinsert(type.Get(), "time_increment",
|
||||
HOFFSET(FrameRow, time_increment), H5T_NATIVE_DOUBLE),
|
||||
0);
|
||||
EXPECT_GE(H5Tinsert(type.Get(), "is_initial", HOFFSET(FrameRow, is_initial),
|
||||
H5T_NATIVE_UINT8),
|
||||
0);
|
||||
HdfHandle data{H5Dopen2(file, path.c_str(), H5P_DEFAULT), H5Dclose};
|
||||
EXPECT_GE(H5Dread(data.Get(), type.Get(), H5S_ALL, H5S_ALL, H5P_DEFAULT,
|
||||
frames.data()),
|
||||
0);
|
||||
return frames;
|
||||
}
|
||||
|
||||
std::string ReadBytes(const std::filesystem::path& path) {
|
||||
std::ifstream input{path, std::ios::binary};
|
||||
return {std::istreambuf_iterator<char>{input},
|
||||
std::istreambuf_iterator<char>{}};
|
||||
}
|
||||
|
||||
std::string Replace(std::string text, const std::string& from,
|
||||
const std::string& to) {
|
||||
const auto position = text.find(from);
|
||||
if (position == std::string::npos)
|
||||
throw std::runtime_error{"Missing fixture text."};
|
||||
text.replace(position, from.size(), to);
|
||||
return text;
|
||||
}
|
||||
|
||||
struct AppRun {
|
||||
int exit_code;
|
||||
std::string error;
|
||||
};
|
||||
AppRun RunApplication(const std::filesystem::path& input) {
|
||||
testing::internal::CaptureStderr();
|
||||
const int result = fesa::FesaApplication{}.Run({input.string()});
|
||||
return {result, testing::internal::GetCapturedStderr()};
|
||||
}
|
||||
|
||||
class ThermalApplicationTest : public testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
static std::atomic<unsigned long> counter{0};
|
||||
do {
|
||||
directory_ =
|
||||
std::filesystem::temp_directory_path() /
|
||||
("fesa-thermal-app-" + std::to_string(std::random_device{}()) + "-" +
|
||||
std::to_string(counter.fetch_add(1)));
|
||||
} while (!std::filesystem::create_directory(directory_));
|
||||
}
|
||||
void TearDown() override {
|
||||
std::error_code error;
|
||||
std::filesystem::remove_all(directory_, error);
|
||||
}
|
||||
std::filesystem::path Path(const std::string& name) const {
|
||||
return directory_ / name;
|
||||
}
|
||||
void Write(const std::string& name, const std::string& text) {
|
||||
std::ofstream output{Path(name), std::ios::binary};
|
||||
output << text;
|
||||
ASSERT_TRUE(output.good());
|
||||
}
|
||||
std::filesystem::path directory_;
|
||||
};
|
||||
|
||||
std::string Deck(const std::string& controls = ".3,.7",
|
||||
const std::string& initial = "-2") {
|
||||
return R"inp(*Part,name=P
|
||||
*Node
|
||||
1,0,0,0
|
||||
2,1,0,0
|
||||
3,1,1,0
|
||||
4,0,1,0
|
||||
*Element,type=DS4
|
||||
1,1,2,3,4
|
||||
*Elset,elset=E
|
||||
1
|
||||
*Shell Section,elset=E,material=M
|
||||
1,5
|
||||
*End Part
|
||||
*Assembly,name=A
|
||||
*Instance,name=I,part=P
|
||||
*End Instance
|
||||
*Nset,nset=All,instance=I
|
||||
1,2,3,4
|
||||
*End Assembly
|
||||
*Material,name=M
|
||||
*Density
|
||||
1
|
||||
*Conductivity
|
||||
1
|
||||
*Specific Heat
|
||||
1
|
||||
*Initial Conditions,type=TEMPERATURE
|
||||
All,)inp" +
|
||||
initial + R"inp(
|
||||
*Step,name=Raw Heat,inc=10
|
||||
*Heat Transfer
|
||||
)inp" + controls +
|
||||
R"inp(
|
||||
*Output,field,variable=PRESELECT
|
||||
*End Step
|
||||
)inp";
|
||||
}
|
||||
|
||||
TEST_F(ThermalApplicationTest, ExecutesThermalInputThroughAtomicOutput) {
|
||||
Write("Heat.Model.INP", Deck());
|
||||
const auto run = RunApplication(Path("Heat.Model.INP"));
|
||||
ASSERT_EQ(run.exit_code, 0) << run.error;
|
||||
EXPECT_GT(H5Fis_hdf5(Path("Heat.Model_results.h5").string().c_str()), 0);
|
||||
EXPECT_EQ(std::filesystem::file_size(Path("Heat.Model_log.txt")), 0U);
|
||||
HdfHandle file{H5Fopen(Path("Heat.Model_results.h5").string().c_str(),
|
||||
H5F_ACC_RDONLY, H5P_DEFAULT),
|
||||
H5Fclose};
|
||||
HdfHandle metadata{H5Gopen2(file.Get(), "/metadata", H5P_DEFAULT), H5Gclose};
|
||||
EXPECT_EQ(StringAttribute(metadata.Get(), "source_step_name"), "Raw Heat");
|
||||
EXPECT_EQ(StringAttribute(metadata.Get(), "feature_id"),
|
||||
"shell-transient-heat-transfer");
|
||||
EXPECT_EQ(StringAttribute(metadata.Get(), "analysis_procedure"),
|
||||
"uncoupled-transient-heat-transfer");
|
||||
EXPECT_EQ(StringAttribute(metadata.Get(), "source_input_identity")
|
||||
.find("path=" + Path("Heat.Model.INP").generic_u8string() +
|
||||
";content_identity="),
|
||||
0U);
|
||||
const auto frames = Frames(file.Get());
|
||||
ASSERT_EQ(frames.size(), 4U);
|
||||
for (std::size_t index = 0; index < frames.size(); ++index) {
|
||||
const auto& frame = frames[index];
|
||||
EXPECT_EQ(frame.frame_index, index);
|
||||
EXPECT_EQ(frame.increment_index, index);
|
||||
EXPECT_EQ(frame.is_initial, index == 0 ? 1 : 0);
|
||||
EXPECT_DOUBLE_EQ(frame.time,
|
||||
index == 3 ? .7 : static_cast<double>(index) * .3);
|
||||
EXPECT_DOUBLE_EQ(frame.time_increment, index == 0 ? 0
|
||||
: index == 3 ? std::fma(-2., .3, .7)
|
||||
: .3);
|
||||
const auto root = "/steps/Step-1/frames/" + std::to_string(index);
|
||||
EXPECT_EQ(Shape(file.Get(), root + "/nodal/temperature"),
|
||||
(std::vector<hsize_t>{4, 5}));
|
||||
for (double value : Doubles(file.Get(), root + "/nodal/temperature")) {
|
||||
EXPECT_TRUE(std::isfinite(value));
|
||||
EXPECT_LT(value, 0.);
|
||||
if (index == 0) EXPECT_DOUBLE_EQ(value, -2.);
|
||||
}
|
||||
if (index == 0) {
|
||||
EXPECT_EQ(
|
||||
H5Lexists(file.Get(), (root + "/nodal/reaction_heat_rate").c_str(),
|
||||
H5P_DEFAULT),
|
||||
0);
|
||||
EXPECT_EQ(H5Lexists(file.Get(), (root + "/element").c_str(), H5P_DEFAULT),
|
||||
0);
|
||||
} else {
|
||||
EXPECT_EQ(Shape(file.Get(), root + "/nodal/reaction_heat_rate"),
|
||||
(std::vector<hsize_t>{0}));
|
||||
EXPECT_EQ(Shape(file.Get(), root + "/element/heat_flux"),
|
||||
(std::vector<hsize_t>{1, 4, 6, 3}));
|
||||
}
|
||||
}
|
||||
EXPECT_EQ(Shape(file.Get(), "/diagnostics"), (std::vector<hsize_t>{1}));
|
||||
HdfHandle diagnostics{H5Dopen2(file.Get(), "/diagnostics", H5P_DEFAULT),
|
||||
H5Dclose};
|
||||
HdfHandle string_type{H5Tcopy(H5T_C_S1), H5Tclose};
|
||||
ASSERT_GE(H5Tset_size(string_type.Get(), H5T_VARIABLE), 0);
|
||||
ASSERT_GE(H5Tset_cset(string_type.Get(), H5T_CSET_UTF8), 0);
|
||||
HdfHandle row_type{H5Tcreate(H5T_COMPOUND, sizeof(char*)), H5Tclose};
|
||||
ASSERT_GE(H5Tinsert(row_type.Get(), "code", 0, string_type.Get()), 0);
|
||||
char* code = nullptr;
|
||||
ASSERT_GE(H5Dread(diagnostics.Get(), row_type.Get(), H5S_ALL, H5S_ALL,
|
||||
H5P_DEFAULT, &code),
|
||||
0);
|
||||
EXPECT_STREQ(code, "ignored-input-keyword");
|
||||
H5free_memory(code);
|
||||
}
|
||||
|
||||
TEST_F(ThermalApplicationTest,
|
||||
EndpointCloseUsesActualDeltaAndLargeIncrementUsesPeriod) {
|
||||
for (const auto& controls : {".3,.9", "2,.7"}) {
|
||||
SCOPED_TRACE(controls);
|
||||
Write("model.inp", Deck(controls, "0"));
|
||||
const auto run = RunApplication(Path("model.inp"));
|
||||
ASSERT_EQ(run.exit_code, 0) << run.error;
|
||||
HdfHandle file{H5Fopen(Path("model_results.h5").string().c_str(),
|
||||
H5F_ACC_RDONLY, H5P_DEFAULT),
|
||||
H5Fclose};
|
||||
const auto frames = Frames(file.Get());
|
||||
if (std::string{controls} == ".3,.9") {
|
||||
ASSERT_EQ(frames.size(), 4U);
|
||||
EXPECT_DOUBLE_EQ(frames.back().time, .9);
|
||||
EXPECT_DOUBLE_EQ(frames.back().time_increment, .3);
|
||||
EXPECT_NE(frames.back().time_increment, frames[3].time - frames[2].time);
|
||||
} else {
|
||||
ASSERT_EQ(frames.size(), 2U);
|
||||
EXPECT_DOUBLE_EQ(frames.back().time, .7);
|
||||
EXPECT_DOUBLE_EQ(frames.back().time_increment, .7);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ThermalApplicationTest, RawInitialPrecedesPrescribedBoundaryProjection) {
|
||||
auto deck = Replace(Deck(".1,.2"), "*Output,field",
|
||||
"*Boundary\nAll,11,11,1\nAll,15,15,1\n*Output,field");
|
||||
Write("model.inp", deck);
|
||||
const auto run = RunApplication(Path("model.inp"));
|
||||
ASSERT_EQ(run.exit_code, 0) << run.error;
|
||||
HdfHandle file{H5Fopen(Path("model_results.h5").string().c_str(),
|
||||
H5F_ACC_RDONLY, H5P_DEFAULT),
|
||||
H5Fclose};
|
||||
for (double value :
|
||||
Doubles(file.Get(), "/steps/Step-1/frames/0/nodal/temperature"))
|
||||
EXPECT_DOUBLE_EQ(value, -2);
|
||||
for (int frame = 1; frame <= 2; ++frame) {
|
||||
const auto root = "/steps/Step-1/frames/" + std::to_string(frame);
|
||||
const auto temperature = Doubles(file.Get(), root + "/nodal/temperature");
|
||||
for (std::size_t node = 0; node < 4; ++node) {
|
||||
EXPECT_DOUBLE_EQ(temperature[5 * node], 1);
|
||||
EXPECT_DOUBLE_EQ(temperature[5 * node + 4], 1);
|
||||
}
|
||||
EXPECT_EQ(Shape(file.Get(), root + "/nodal/reaction_heat_rate"),
|
||||
(std::vector<hsize_t>{8}));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ThermalApplicationTest, InputAndSolverFailuresPreservePreviousFinal) {
|
||||
const std::string previous{"previous final\0bytes", 20};
|
||||
for (const auto& test : std::vector<std::pair<std::string, int>>{
|
||||
{Replace(Deck(), "*Output,field",
|
||||
"*Boundary\nAll,12,12,1\n*Output,field"),
|
||||
3},
|
||||
{Deck("1.e-300,1.e-300", "1.e200"), 5}}) {
|
||||
Write("model.inp", test.first);
|
||||
Write("model_results.h5", previous);
|
||||
const auto run = RunApplication(Path("model.inp"));
|
||||
EXPECT_EQ(run.exit_code, test.second) << run.error;
|
||||
EXPECT_FALSE(run.error.empty());
|
||||
EXPECT_EQ(ReadBytes(Path("model_results.h5")), previous);
|
||||
EXPECT_EQ(std::filesystem::file_size(Path("model_log.txt")), 0U);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ThermalApplicationTest,
|
||||
BusyLeasePrecedesParsingAndPreservesBothOutputs) {
|
||||
Write("model.inp", "deliberately invalid input");
|
||||
Write("model_results.h5", "previous final");
|
||||
Write("model_log.txt", "previous log");
|
||||
fesa::DerivedOutputPaths paths{Path("model.inp"), Path("model_results.h5"),
|
||||
Path("model_log.txt")};
|
||||
auto lease = fesa::AcquireOutputWriteLease(paths);
|
||||
ASSERT_TRUE(lease.HasValue());
|
||||
const auto run = RunApplication(paths.input_path);
|
||||
EXPECT_EQ(run.exit_code, 6);
|
||||
EXPECT_NE(run.error.find("code=output-busy"), std::string::npos);
|
||||
EXPECT_EQ(ReadBytes(paths.results_path), "previous final");
|
||||
EXPECT_EQ(ReadBytes(paths.log_path), "previous log");
|
||||
}
|
||||
|
||||
TEST_F(ThermalApplicationTest,
|
||||
RealFinalizeFailurePreservesFinalAndRemovesCandidate) {
|
||||
Write("model.inp", Deck(".1,.1", "0"));
|
||||
Write("model_results.h5", "previous final");
|
||||
// Deny replacement of this final while permitting reads. The output lease is
|
||||
// separate; the candidate must reach the actual atomic finalize operation.
|
||||
const HANDLE locked =
|
||||
CreateFileW(Path("model_results.h5").c_str(), GENERIC_READ,
|
||||
FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, nullptr);
|
||||
ASSERT_NE(locked, INVALID_HANDLE_VALUE);
|
||||
const auto run = RunApplication(Path("model.inp"));
|
||||
EXPECT_TRUE(CloseHandle(locked));
|
||||
EXPECT_EQ(run.exit_code, 6) << run.error;
|
||||
EXPECT_EQ(ReadBytes(Path("model_results.h5")), "previous final");
|
||||
EXPECT_EQ(std::filesystem::file_size(Path("model_log.txt")), 0U);
|
||||
for (const auto& entry : std::filesystem::directory_iterator{directory_}) {
|
||||
EXPECT_EQ(entry.path().filename().string().find(".tmp."),
|
||||
std::string::npos);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ThermalApplicationTest, MissingAdaptersHaveSpecificFailureCategories) {
|
||||
Write("model.inp", Deck(".1,.1", "0"));
|
||||
auto prepared = fesa::PrepareAnalysisInput(Path("model.inp"));
|
||||
ASSERT_TRUE(prepared.HasValue());
|
||||
ASSERT_EQ(prepared.Value().domain.Steps().Size(), 0U);
|
||||
fesa::SerialParallelFor parallel;
|
||||
fesa::MklPardisoSolver solver;
|
||||
fesa::MklPardisoTangentSolver tangent;
|
||||
fesa::LinearStaticProcedureExecutor linear{parallel, solver};
|
||||
fesa::NonlinearStaticProcedureExecutor nonlinear{parallel, tangent};
|
||||
fesa::LinearFrequencyProcedureExecutor frequency;
|
||||
fesa::TransientHeatProcedureExecutor thermal{parallel, solver};
|
||||
fesa::NewtonIterationLogWriter log;
|
||||
ASSERT_TRUE(log.Open(Path("model_log.txt")).IsOk());
|
||||
auto missing = fesa::ExecutePreparedAnalysis(prepared.Value().domain, linear,
|
||||
nonlinear, frequency, log);
|
||||
ASSERT_FALSE(missing.HasValue());
|
||||
EXPECT_EQ(missing.GetStatus().Category(), fesa::FailureCategory::kSolver);
|
||||
ASSERT_EQ(missing.GetStatus().Diagnostics().size(), 1U);
|
||||
EXPECT_EQ(missing.GetStatus().Diagnostics()[0].code,
|
||||
"missing-thermal-executor");
|
||||
auto outcome =
|
||||
fesa::ExecutePreparedAnalysis(prepared.Value().domain, linear, nonlinear,
|
||||
frequency, log, nullptr, &thermal);
|
||||
ASSERT_TRUE(outcome.HasValue());
|
||||
ASSERT_TRUE(
|
||||
std::holds_alternative<fesa::ThermalExecutionResult>(outcome.Value()));
|
||||
ASSERT_TRUE(log.Close().IsOk());
|
||||
fesa::Hdf5ResultsWriter linear_writer;
|
||||
fesa::NonlinearHdf5ResultsWriter nonlinear_writer;
|
||||
fesa::EigenHdf5ResultsWriter eigen_writer;
|
||||
const fesa::DerivedOutputPaths paths{
|
||||
Path("model.inp"), Path("model_results.h5"), Path("model_log.txt")};
|
||||
Write("model_results.h5", "previous final");
|
||||
const auto status = fesa::FinalizePreparedAnalysisResult(
|
||||
paths, prepared.Value().domain, outcome.Value(),
|
||||
prepared.Value().diagnostics, linear_writer, nonlinear_writer,
|
||||
eigen_writer);
|
||||
EXPECT_EQ(status.Category(), fesa::FailureCategory::kOutput);
|
||||
ASSERT_EQ(status.Diagnostics().size(), 1U);
|
||||
EXPECT_EQ(status.Diagnostics()[0].code, "missing-thermal-writer");
|
||||
EXPECT_EQ(ReadBytes(paths.results_path), "previous final");
|
||||
}
|
||||
|
||||
TEST_F(ThermalApplicationTest,
|
||||
ExistingStructuralStaticNonlinearAndHhtRoutesRemainAvailable) {
|
||||
auto base = Replace(Deck(), "type=DS4", "type=S4");
|
||||
base = Replace(base, "*Conductivity\n1\n*Specific Heat\n1",
|
||||
"*Elastic\n1000,.25");
|
||||
base.erase(base.find("*Initial Conditions"));
|
||||
base += "*Boundary\nAll,1,6\n";
|
||||
for (const auto& step :
|
||||
{"*Step,name=Structural,nlgeom=NO\n*Static\n.1,.1,.01,.1\n*End Step\n",
|
||||
"*Step,name=Structural,nlgeom=YES\n*Static\n.1,1.,.01,.1\n*End Step\n",
|
||||
"*Step,name=Structural,nlgeom=YES,inc=10\n*Dynamic,direct\n.1,.2\n*End "
|
||||
"Step\n"}) {
|
||||
SCOPED_TRACE(step);
|
||||
const bool dynamic =
|
||||
std::string{step}.find("*Dynamic") != std::string::npos;
|
||||
Write("model.inp",
|
||||
(dynamic ? base : Replace(base, "*Density\n1\n", "")) + step);
|
||||
const auto run = RunApplication(Path("model.inp"));
|
||||
ASSERT_EQ(run.exit_code, 0) << run.error;
|
||||
EXPECT_GT(H5Fis_hdf5(Path("model_results.h5").string().c_str()), 0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ThermalApplicationTest,
|
||||
NativeExecutableAcceptsThermalInputAndRejectsInteriorBoundary) {
|
||||
std::vector<wchar_t> module(32768);
|
||||
const DWORD length = GetModuleFileNameW(nullptr, module.data(),
|
||||
static_cast<DWORD>(module.size()));
|
||||
ASSERT_GT(length, 0U);
|
||||
ASSERT_LT(length, module.size());
|
||||
const auto executable =
|
||||
std::filesystem::path{std::wstring{module.data(), length}}
|
||||
.parent_path()
|
||||
.parent_path() /
|
||||
"src/fesa/fesa.exe";
|
||||
ASSERT_TRUE(std::filesystem::exists(executable));
|
||||
std::string previous;
|
||||
for (const bool valid : {true, false}) {
|
||||
Write("native.inp", valid
|
||||
? Deck(".1,.1", "0")
|
||||
: Replace(Deck(), "*Output,field",
|
||||
"*Boundary\nAll,12,12,1\n*Output,field"));
|
||||
std::wstring command = L"\"" + executable.wstring() + L"\" \"" +
|
||||
Path("native.inp").wstring() + L"\"";
|
||||
STARTUPINFOW startup{};
|
||||
startup.cb = sizeof(startup);
|
||||
PROCESS_INFORMATION process{};
|
||||
ASSERT_TRUE(CreateProcessW(executable.c_str(), command.data(), nullptr,
|
||||
nullptr, FALSE, CREATE_NO_WINDOW, nullptr,
|
||||
nullptr, &startup, &process));
|
||||
const DWORD wait = WaitForSingleObject(process.hProcess, 60000);
|
||||
if (wait != WAIT_OBJECT_0) TerminateProcess(process.hProcess, 99);
|
||||
DWORD exit = 99;
|
||||
EXPECT_TRUE(GetExitCodeProcess(process.hProcess, &exit));
|
||||
CloseHandle(process.hThread);
|
||||
CloseHandle(process.hProcess);
|
||||
ASSERT_EQ(wait, WAIT_OBJECT_0);
|
||||
EXPECT_EQ(exit, valid ? 0U : 3U);
|
||||
EXPECT_GT(H5Fis_hdf5(Path("native_results.h5").string().c_str()), 0);
|
||||
if (valid) {
|
||||
previous = ReadBytes(Path("native_results.h5"));
|
||||
} else {
|
||||
EXPECT_EQ(ReadBytes(Path("native_results.h5")), previous);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
Reference in New Issue
Block a user