feat(cpp-object-oriented-modular-refactoring): step 6 - io-application-google-style
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
#include "fesa/app/fesa_application.hpp"
|
||||
#include "fesa/app/fesa_application.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <hdf5.h>
|
||||
@@ -19,101 +19,94 @@ namespace {
|
||||
constexpr const char* kStepRoot = "/steps/Step-1/frames/0";
|
||||
|
||||
class TempDirectory {
|
||||
public:
|
||||
explicit TempDirectory(const std::string& label) {
|
||||
static std::atomic<unsigned long long> sequence{0U};
|
||||
const auto tick = std::chrono::steady_clock::now()
|
||||
.time_since_epoch()
|
||||
.count();
|
||||
path_ = std::filesystem::temp_directory_path() /
|
||||
("fesa-step24-app-" + label + "-" +
|
||||
std::to_string(tick) + "-" +
|
||||
public:
|
||||
explicit TempDirectory(const std::string& label) {
|
||||
static std::atomic<unsigned long long> sequence{0U};
|
||||
const auto tick =
|
||||
std::chrono::steady_clock::now().time_since_epoch().count();
|
||||
path_ = std::filesystem::temp_directory_path() /
|
||||
("fesa-step24-app-" + label + "-" + std::to_string(tick) + "-" +
|
||||
std::to_string(sequence.fetch_add(1U)));
|
||||
std::error_code error;
|
||||
if (!std::filesystem::create_directory(path_, error) || error) {
|
||||
throw std::runtime_error{"Unable to create the Step 24 app fixture."};
|
||||
}
|
||||
std::error_code error;
|
||||
if (!std::filesystem::create_directory(path_, error) || error) {
|
||||
throw std::runtime_error{"Unable to create the Step 24 app fixture."};
|
||||
}
|
||||
}
|
||||
|
||||
TempDirectory(const TempDirectory&) = delete;
|
||||
TempDirectory& operator=(const TempDirectory&) = delete;
|
||||
TempDirectory(const TempDirectory&) = delete;
|
||||
TempDirectory& operator=(const TempDirectory&) = delete;
|
||||
|
||||
~TempDirectory() {
|
||||
std::error_code ignored;
|
||||
std::filesystem::remove_all(path_, ignored);
|
||||
}
|
||||
~TempDirectory() {
|
||||
std::error_code ignored;
|
||||
std::filesystem::remove_all(path_, ignored);
|
||||
}
|
||||
|
||||
const std::filesystem::path& path() const noexcept { return path_; }
|
||||
const std::filesystem::path& Path() const noexcept { return path_; }
|
||||
|
||||
private:
|
||||
std::filesystem::path path_;
|
||||
private:
|
||||
std::filesystem::path path_;
|
||||
};
|
||||
|
||||
class CurrentDirectoryGuard {
|
||||
public:
|
||||
explicit CurrentDirectoryGuard(const std::filesystem::path& replacement)
|
||||
: original_{std::filesystem::current_path()} {
|
||||
std::filesystem::current_path(replacement);
|
||||
}
|
||||
public:
|
||||
explicit CurrentDirectoryGuard(const std::filesystem::path& replacement)
|
||||
: original_{std::filesystem::current_path()} {
|
||||
std::filesystem::current_path(replacement);
|
||||
}
|
||||
|
||||
CurrentDirectoryGuard(const CurrentDirectoryGuard&) = delete;
|
||||
CurrentDirectoryGuard& operator=(const CurrentDirectoryGuard&) = delete;
|
||||
CurrentDirectoryGuard(const CurrentDirectoryGuard&) = delete;
|
||||
CurrentDirectoryGuard& operator=(const CurrentDirectoryGuard&) = delete;
|
||||
|
||||
~CurrentDirectoryGuard() {
|
||||
std::error_code ignored;
|
||||
std::filesystem::current_path(original_, ignored);
|
||||
}
|
||||
~CurrentDirectoryGuard() {
|
||||
std::error_code ignored;
|
||||
std::filesystem::current_path(original_, ignored);
|
||||
}
|
||||
|
||||
private:
|
||||
std::filesystem::path original_;
|
||||
private:
|
||||
std::filesystem::path original_;
|
||||
};
|
||||
|
||||
class Hdf5Handle {
|
||||
public:
|
||||
using Closer = herr_t (*)(hid_t);
|
||||
public:
|
||||
using Closer = herr_t (*)(hid_t);
|
||||
|
||||
Hdf5Handle(const hid_t value, Closer closer)
|
||||
: value_{value}, closer_{closer} {}
|
||||
Hdf5Handle(const Hdf5Handle&) = delete;
|
||||
Hdf5Handle& operator=(const Hdf5Handle&) = delete;
|
||||
Hdf5Handle(Hdf5Handle&& other) noexcept
|
||||
: value_{other.value_}, closer_{other.closer_} {
|
||||
other.value_ = -1;
|
||||
other.closer_ = nullptr;
|
||||
}
|
||||
~Hdf5Handle() {
|
||||
if (value_ >= 0 && closer_ != nullptr) {
|
||||
(void)closer_(value_);
|
||||
}
|
||||
Hdf5Handle(const hid_t value, Closer closer)
|
||||
: value_{value}, closer_{closer} {}
|
||||
Hdf5Handle(const Hdf5Handle&) = delete;
|
||||
Hdf5Handle& operator=(const Hdf5Handle&) = delete;
|
||||
Hdf5Handle(Hdf5Handle&& other) noexcept
|
||||
: value_{other.value_}, closer_{other.closer_} {
|
||||
other.value_ = -1;
|
||||
other.closer_ = nullptr;
|
||||
}
|
||||
~Hdf5Handle() {
|
||||
if (value_ >= 0 && closer_ != nullptr) {
|
||||
(void)closer_(value_);
|
||||
}
|
||||
}
|
||||
|
||||
hid_t get() const noexcept { return value_; }
|
||||
hid_t Get() const noexcept { return value_; }
|
||||
|
||||
private:
|
||||
hid_t value_{-1};
|
||||
Closer closer_{nullptr};
|
||||
private:
|
||||
hid_t value_{-1};
|
||||
Closer closer_{nullptr};
|
||||
};
|
||||
|
||||
void writeText(const std::filesystem::path& path, const std::string& text) {
|
||||
std::ofstream stream{path, std::ios::binary | std::ios::trunc};
|
||||
stream.write(text.data(), static_cast<std::streamsize>(text.size()));
|
||||
if (!stream) {
|
||||
throw std::runtime_error{"Unable to write the Step 24 app input."};
|
||||
}
|
||||
void WriteText(const std::filesystem::path& path, const std::string& text) {
|
||||
std::ofstream stream{path, std::ios::binary | std::ios::trunc};
|
||||
stream.write(text.data(), static_cast<std::streamsize>(text.size()));
|
||||
if (!stream) {
|
||||
throw std::runtime_error{"Unable to write the Step 24 app input."};
|
||||
}
|
||||
}
|
||||
|
||||
std::string axialDeck(
|
||||
const bool constrained,
|
||||
const bool zeroLength,
|
||||
const bool outputRequests) {
|
||||
const std::string secondNode = zeroLength
|
||||
? "2, 0., 0., 0.\n"
|
||||
: "2, 2., 0., 0.\n";
|
||||
const std::string boundaries = constrained
|
||||
? "*Boundary\nRoot, 1, 6\nTip, 2, 6\n"
|
||||
: "";
|
||||
const std::string outputs = outputRequests
|
||||
? R"inp(*Output, field
|
||||
std::string AxialDeck(const bool constrained, const bool zero_length,
|
||||
const bool output_requests) {
|
||||
const std::string second_node =
|
||||
zero_length ? "2, 0., 0., 0.\n" : "2, 2., 0., 0.\n";
|
||||
const std::string boundaries =
|
||||
constrained ? "*Boundary\nRoot, 1, 6\nTip, 2, 6\n" : "";
|
||||
const std::string outputs = output_requests ? R"inp(*Output, field
|
||||
*Node Output
|
||||
U, RF
|
||||
*Element Output, directions=YES
|
||||
@@ -121,12 +114,13 @@ S, SF
|
||||
*Output, history
|
||||
*Contact Output
|
||||
)inp"
|
||||
: "";
|
||||
: "";
|
||||
|
||||
return std::string{R"inp(*Part, name=BeamPart
|
||||
return std::string{R"inp(*Part, name=BeamPart
|
||||
*Node
|
||||
1, 0., 0., 0.
|
||||
)inp"} + secondNode + R"inp(*Element, type=B33
|
||||
)inp"} + second_node +
|
||||
R"inp(*Element, type=B33
|
||||
1, 1, 2
|
||||
*Elset, elset=BeamSet
|
||||
1
|
||||
@@ -145,17 +139,19 @@ S, SF
|
||||
*Material, name=Steel
|
||||
*Elastic
|
||||
100., 0.25
|
||||
)inp" + boundaries + R"inp(*Step, name=Load, nlgeom=NO
|
||||
)inp" + boundaries +
|
||||
R"inp(*Step, name=Load, nlgeom=NO
|
||||
*Static
|
||||
0.1, 1., 0.01, 1.
|
||||
*Cload
|
||||
Tip, 1, 10.
|
||||
)inp" + outputs + R"inp(*End Step
|
||||
)inp" + outputs +
|
||||
R"inp(*End Step
|
||||
)inp";
|
||||
}
|
||||
|
||||
std::string allConstrainedShellDeck() {
|
||||
return R"inp(*Part, name=ShellPart
|
||||
std::string AllConstrainedShellDeck() {
|
||||
return R"inp(*Part, name=ShellPart
|
||||
*Node
|
||||
1, 0., 0., 0.
|
||||
2, 1., 0., 0.
|
||||
@@ -186,299 +182,277 @@ All, 1, 6
|
||||
)inp";
|
||||
}
|
||||
|
||||
Hdf5Handle openFile(const std::filesystem::path& path) {
|
||||
const hid_t file = H5Fopen(
|
||||
path.string().c_str(), H5F_ACC_RDONLY, H5P_DEFAULT);
|
||||
if (file < 0) {
|
||||
throw std::runtime_error{"Unable to open the CLI HDF5 artifact."};
|
||||
}
|
||||
return Hdf5Handle{file, H5Fclose};
|
||||
Hdf5Handle OpenFile(const std::filesystem::path& path) {
|
||||
const hid_t file =
|
||||
H5Fopen(path.string().c_str(), H5F_ACC_RDONLY, H5P_DEFAULT);
|
||||
if (file < 0) {
|
||||
throw std::runtime_error{"Unable to open the CLI HDF5 artifact."};
|
||||
}
|
||||
return Hdf5Handle{file, H5Fclose};
|
||||
}
|
||||
|
||||
Hdf5Handle openDataset(const hid_t file, const std::string& path) {
|
||||
const hid_t dataset = H5Dopen2(file, path.c_str(), H5P_DEFAULT);
|
||||
if (dataset < 0) {
|
||||
throw std::runtime_error{"Unable to open mandatory dataset: " + path};
|
||||
}
|
||||
return Hdf5Handle{dataset, H5Dclose};
|
||||
Hdf5Handle OpenDataset(const hid_t file, const std::string& path) {
|
||||
const hid_t dataset = H5Dopen2(file, path.c_str(), H5P_DEFAULT);
|
||||
if (dataset < 0) {
|
||||
throw std::runtime_error{"Unable to open mandatory dataset: " + path};
|
||||
}
|
||||
return Hdf5Handle{dataset, H5Dclose};
|
||||
}
|
||||
|
||||
std::vector<hsize_t> datasetDimensions(
|
||||
const hid_t file, const std::string& path) {
|
||||
const auto dataset = openDataset(file, path);
|
||||
Hdf5Handle space{H5Dget_space(dataset.get()), H5Sclose};
|
||||
const int rank = H5Sget_simple_extent_ndims(space.get());
|
||||
if (space.get() < 0 || rank < 0) {
|
||||
throw std::runtime_error{"Unable to inspect mandatory dataset dimensions."};
|
||||
}
|
||||
std::vector<hsize_t> dimensions(static_cast<std::size_t>(rank));
|
||||
if (rank > 0 &&
|
||||
H5Sget_simple_extent_dims(space.get(), dimensions.data(), nullptr) < 0) {
|
||||
throw std::runtime_error{"Unable to read mandatory dataset dimensions."};
|
||||
}
|
||||
return dimensions;
|
||||
std::vector<hsize_t> DatasetDimensions(const hid_t file,
|
||||
const std::string& path) {
|
||||
const auto dataset = OpenDataset(file, path);
|
||||
Hdf5Handle space{H5Dget_space(dataset.Get()), H5Sclose};
|
||||
const int rank = H5Sget_simple_extent_ndims(space.Get());
|
||||
if (space.Get() < 0 || rank < 0) {
|
||||
throw std::runtime_error{"Unable to inspect mandatory dataset dimensions."};
|
||||
}
|
||||
std::vector<hsize_t> dimensions(static_cast<std::size_t>(rank));
|
||||
if (rank > 0 &&
|
||||
H5Sget_simple_extent_dims(space.Get(), dimensions.data(), nullptr) < 0) {
|
||||
throw std::runtime_error{"Unable to read mandatory dataset dimensions."};
|
||||
}
|
||||
return dimensions;
|
||||
}
|
||||
|
||||
std::vector<double> readDoubleDataset(
|
||||
const hid_t file, const std::string& path) {
|
||||
const auto dimensions = datasetDimensions(file, path);
|
||||
std::size_t count = 1U;
|
||||
for (const hsize_t dimension : dimensions) {
|
||||
count *= static_cast<std::size_t>(dimension);
|
||||
}
|
||||
const auto dataset = openDataset(file, path);
|
||||
std::vector<double> values(count);
|
||||
if (!values.empty() &&
|
||||
H5Dread(dataset.get(), H5T_NATIVE_DOUBLE, H5S_ALL, H5S_ALL,
|
||||
H5P_DEFAULT, values.data()) < 0) {
|
||||
throw std::runtime_error{"Unable to read mandatory numeric results."};
|
||||
}
|
||||
return values;
|
||||
std::vector<double> ReadDoubleDataset(const hid_t file,
|
||||
const std::string& path) {
|
||||
const auto dimensions = DatasetDimensions(file, path);
|
||||
std::size_t count = 1U;
|
||||
for (const hsize_t dimension : dimensions) {
|
||||
count *= static_cast<std::size_t>(dimension);
|
||||
}
|
||||
const auto dataset = OpenDataset(file, path);
|
||||
std::vector<double> values(count);
|
||||
if (!values.empty() && H5Dread(dataset.Get(), H5T_NATIVE_DOUBLE, H5S_ALL,
|
||||
H5S_ALL, H5P_DEFAULT, values.data()) < 0) {
|
||||
throw std::runtime_error{"Unable to read mandatory numeric results."};
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
std::string readStringAttribute(const hid_t object, const char* name) {
|
||||
Hdf5Handle attribute{H5Aopen(object, name, H5P_DEFAULT), H5Aclose};
|
||||
Hdf5Handle type{H5Aget_type(attribute.get()), H5Tclose};
|
||||
if (attribute.get() < 0 || type.get() < 0 ||
|
||||
H5Tget_class(type.get()) != H5T_STRING ||
|
||||
H5Tis_variable_str(type.get()) <= 0) {
|
||||
throw std::runtime_error{"Expected a variable-length string attribute."};
|
||||
}
|
||||
char* raw = nullptr;
|
||||
if (H5Aread(attribute.get(), type.get(), &raw) < 0 || raw == nullptr) {
|
||||
throw std::runtime_error{"Unable to read the HDF5 identity attribute."};
|
||||
}
|
||||
const std::string value{raw};
|
||||
(void)H5free_memory(raw);
|
||||
return value;
|
||||
std::string ReadStringAttribute(const hid_t object, const char* name) {
|
||||
Hdf5Handle attribute{H5Aopen(object, name, H5P_DEFAULT), H5Aclose};
|
||||
Hdf5Handle type{H5Aget_type(attribute.Get()), H5Tclose};
|
||||
if (attribute.Get() < 0 || type.Get() < 0 ||
|
||||
H5Tget_class(type.Get()) != H5T_STRING ||
|
||||
H5Tis_variable_str(type.Get()) <= 0) {
|
||||
throw std::runtime_error{"Expected a variable-length string attribute."};
|
||||
}
|
||||
char* raw = nullptr;
|
||||
if (H5Aread(attribute.Get(), type.Get(), &raw) < 0 || raw == nullptr) {
|
||||
throw std::runtime_error{"Unable to read the HDF5 identity attribute."};
|
||||
}
|
||||
const std::string value{raw};
|
||||
(void)H5free_memory(raw);
|
||||
return value;
|
||||
}
|
||||
|
||||
void expectMandatoryInventory(const hid_t file) {
|
||||
for (const char* path : {
|
||||
"/metadata",
|
||||
"/model/nodes",
|
||||
"/model/elements",
|
||||
"/steps/Step-1/frames/0/nodal/displacement",
|
||||
"/steps/Step-1/frames/0/nodal/reaction",
|
||||
"/steps/Step-1/frames/0/element/end_force_local",
|
||||
"/steps/Step-1/frames/0/element/section_resultant",
|
||||
"/steps/Step-1/frames/0/element/generalized_strain",
|
||||
"/steps/Step-1/frames/0/element/generalized_resultant",
|
||||
"/steps/Step-1/frames/0/element/stress_s11",
|
||||
"/diagnostics"}) {
|
||||
EXPECT_GT(H5Lexists(file, path, H5P_DEFAULT), 0) << path;
|
||||
}
|
||||
void ExpectMandatoryInventory(const hid_t file) {
|
||||
for (const char* path :
|
||||
{"/metadata", "/model/nodes", "/model/elements",
|
||||
"/steps/Step-1/frames/0/nodal/displacement",
|
||||
"/steps/Step-1/frames/0/nodal/reaction",
|
||||
"/steps/Step-1/frames/0/element/end_force_local",
|
||||
"/steps/Step-1/frames/0/element/section_resultant",
|
||||
"/steps/Step-1/frames/0/element/generalized_strain",
|
||||
"/steps/Step-1/frames/0/element/generalized_resultant",
|
||||
"/steps/Step-1/frames/0/element/stress_s11", "/diagnostics"}) {
|
||||
EXPECT_GT(H5Lexists(file, path, H5P_DEFAULT), 0) << path;
|
||||
}
|
||||
}
|
||||
|
||||
void expectFesaHdf5Identity(
|
||||
const std::filesystem::path& output,
|
||||
const std::filesystem::path& input) {
|
||||
ASSERT_TRUE(std::filesystem::exists(output));
|
||||
ASSERT_GT(H5Fis_hdf5(output.string().c_str()), 0);
|
||||
const auto file = openFile(output);
|
||||
expectMandatoryInventory(file.get());
|
||||
Hdf5Handle metadata{
|
||||
H5Gopen2(file.get(), "/metadata", H5P_DEFAULT), H5Gclose};
|
||||
ASSERT_GE(metadata.get(), 0);
|
||||
EXPECT_EQ(
|
||||
readStringAttribute(metadata.get(), "feature_id"),
|
||||
"linear-static-3d-euler-beam");
|
||||
const std::string normalizedInput =
|
||||
std::filesystem::absolute(input).lexically_normal().generic_u8string();
|
||||
EXPECT_EQ(
|
||||
readStringAttribute(metadata.get(), "source_input_identity").find(
|
||||
"path=" + normalizedInput + ";content_identity="),
|
||||
0U);
|
||||
void ExpectFesaHdf5Identity(const std::filesystem::path& output,
|
||||
const std::filesystem::path& input) {
|
||||
ASSERT_TRUE(std::filesystem::exists(output));
|
||||
ASSERT_GT(H5Fis_hdf5(output.string().c_str()), 0);
|
||||
const auto file = OpenFile(output);
|
||||
ExpectMandatoryInventory(file.Get());
|
||||
Hdf5Handle metadata{H5Gopen2(file.Get(), "/metadata", H5P_DEFAULT), H5Gclose};
|
||||
ASSERT_GE(metadata.Get(), 0);
|
||||
EXPECT_EQ(ReadStringAttribute(metadata.Get(), "feature_id"),
|
||||
"linear-static-3d-euler-beam");
|
||||
const std::string normalized_input =
|
||||
std::filesystem::absolute(input).lexically_normal().generic_u8string();
|
||||
EXPECT_EQ(ReadStringAttribute(metadata.Get(), "source_input_identity")
|
||||
.find("path=" + normalized_input + ";content_identity="),
|
||||
0U);
|
||||
}
|
||||
|
||||
void expectShellHdf5Identity(
|
||||
const std::filesystem::path& output,
|
||||
const std::filesystem::path& input) {
|
||||
ASSERT_TRUE(std::filesystem::exists(output));
|
||||
ASSERT_GT(H5Fis_hdf5(output.string().c_str()), 0);
|
||||
const auto file = openFile(output);
|
||||
Hdf5Handle metadata{
|
||||
H5Gopen2(file.get(), "/metadata", H5P_DEFAULT), H5Gclose};
|
||||
ASSERT_GE(metadata.get(), 0);
|
||||
EXPECT_EQ(
|
||||
readStringAttribute(metadata.get(), "feature_id"),
|
||||
"linear-static-mitc4-shell");
|
||||
EXPECT_GT(
|
||||
H5Lexists(
|
||||
file.get(),
|
||||
"/steps/Step-1/frames/0/element/shell/generalized_strain",
|
||||
H5P_DEFAULT),
|
||||
0);
|
||||
EXPECT_EQ(
|
||||
datasetDimensions(
|
||||
file.get(),
|
||||
"/steps/Step-1/frames/0/nodal/displacement"),
|
||||
(std::vector<hsize_t>{4U, 6U}));
|
||||
const std::string normalizedInput =
|
||||
std::filesystem::absolute(input).lexically_normal().generic_u8string();
|
||||
EXPECT_EQ(
|
||||
readStringAttribute(metadata.get(), "source_input_identity").find(
|
||||
"path=" + normalizedInput + ";content_identity="),
|
||||
0U);
|
||||
void ExpectShellHdf5Identity(const std::filesystem::path& output,
|
||||
const std::filesystem::path& input) {
|
||||
ASSERT_TRUE(std::filesystem::exists(output));
|
||||
ASSERT_GT(H5Fis_hdf5(output.string().c_str()), 0);
|
||||
const auto file = OpenFile(output);
|
||||
Hdf5Handle metadata{H5Gopen2(file.Get(), "/metadata", H5P_DEFAULT), H5Gclose};
|
||||
ASSERT_GE(metadata.Get(), 0);
|
||||
EXPECT_EQ(ReadStringAttribute(metadata.Get(), "feature_id"),
|
||||
"linear-static-mitc4-shell");
|
||||
EXPECT_GT(H5Lexists(file.Get(),
|
||||
"/steps/Step-1/frames/0/element/shell/generalized_strain",
|
||||
H5P_DEFAULT),
|
||||
0);
|
||||
EXPECT_EQ(DatasetDimensions(file.Get(),
|
||||
"/steps/Step-1/frames/0/nodal/displacement"),
|
||||
(std::vector<hsize_t>{4U, 6U}));
|
||||
const std::string normalized_input =
|
||||
std::filesystem::absolute(input).lexically_normal().generic_u8string();
|
||||
EXPECT_EQ(ReadStringAttribute(metadata.Get(), "source_input_identity")
|
||||
.find("path=" + normalized_input + ";content_identity="),
|
||||
0U);
|
||||
}
|
||||
|
||||
struct AppRun {
|
||||
int exitCode;
|
||||
std::string standardError;
|
||||
int exit_code;
|
||||
std::string standard_error;
|
||||
};
|
||||
|
||||
AppRun runApplication(const std::vector<std::string>& arguments) {
|
||||
testing::internal::CaptureStderr();
|
||||
try {
|
||||
const int exitCode = fesa::FesaApplication{}.run(arguments);
|
||||
return {exitCode, testing::internal::GetCapturedStderr()};
|
||||
} catch (...) {
|
||||
(void)testing::internal::GetCapturedStderr();
|
||||
throw;
|
||||
}
|
||||
AppRun RunApplication(const std::vector<std::string>& arguments) {
|
||||
testing::internal::CaptureStderr();
|
||||
try {
|
||||
const int exit_code = fesa::FesaApplication{}.Run(arguments);
|
||||
return {exit_code, testing::internal::GetCapturedStderr()};
|
||||
} catch (...) {
|
||||
(void)testing::internal::GetCapturedStderr();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void expectDiagnosticFieldOrder(const std::string& text) {
|
||||
ASSERT_FALSE(text.empty());
|
||||
std::size_t cursor = 0U;
|
||||
for (const char* field : {
|
||||
"severity", "code", "file", "line", "keyword",
|
||||
"entity_identity", "message"}) {
|
||||
const auto position = text.find(field, cursor);
|
||||
ASSERT_NE(position, std::string::npos)
|
||||
<< "Missing or out-of-order diagnostic field: " << field
|
||||
<< "\nstderr:\n" << text;
|
||||
cursor = position + std::string{field}.size();
|
||||
}
|
||||
void ExpectDiagnosticFieldOrder(const std::string& text) {
|
||||
ASSERT_FALSE(text.empty());
|
||||
std::size_t cursor = 0U;
|
||||
for (const char* field : {"severity", "code", "file", "line", "keyword",
|
||||
"entity_identity", "message"}) {
|
||||
const auto position = text.find(field, cursor);
|
||||
ASSERT_NE(position, std::string::npos)
|
||||
<< "Missing or out-of-order diagnostic field: " << field
|
||||
<< "\nstderr:\n"
|
||||
<< text;
|
||||
cursor = position + std::string{field}.size();
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> explicitOutputArguments(
|
||||
const std::filesystem::path& input,
|
||||
const std::filesystem::path& output) {
|
||||
// FesaApplication receives argv[0]-excluded operands and options.
|
||||
return {input.string(), "--output", output.string()};
|
||||
std::vector<std::string> ExplicitOutputArguments(
|
||||
const std::filesystem::path& input, const std::filesystem::path& output) {
|
||||
// FesaApplication receives argv[0]-excluded operands and options.
|
||||
return {input.string(), "--output", output.string()};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace
|
||||
|
||||
TEST(LinearStaticCli, DefaultAndExplicitOutputProduceFesaHdf5) {
|
||||
TempDirectory directory{"paths"};
|
||||
const auto input = directory.path() / "model.inp";
|
||||
writeText(input, axialDeck(true, false, false));
|
||||
TempDirectory directory{"paths"};
|
||||
const auto input = directory.Path() / "model.inp";
|
||||
WriteText(input, AxialDeck(true, false, false));
|
||||
|
||||
const auto defaultOutput = directory.path() / "results.h5";
|
||||
{
|
||||
CurrentDirectoryGuard currentDirectory{directory.path()};
|
||||
const auto result = runApplication({input.string()});
|
||||
ASSERT_EQ(result.exitCode, 0) << result.standardError;
|
||||
}
|
||||
expectFesaHdf5Identity(defaultOutput, input);
|
||||
const auto default_output = directory.Path() / "results.h5";
|
||||
{
|
||||
CurrentDirectoryGuard current_directory{directory.Path()};
|
||||
const auto result = RunApplication({input.string()});
|
||||
ASSERT_EQ(result.exit_code, 0) << result.standard_error;
|
||||
}
|
||||
ExpectFesaHdf5Identity(default_output, input);
|
||||
|
||||
const auto explicitOutput = directory.path() / "named-output.h5";
|
||||
const auto result = runApplication(
|
||||
explicitOutputArguments(input, explicitOutput));
|
||||
ASSERT_EQ(result.exitCode, 0) << result.standardError;
|
||||
expectFesaHdf5Identity(explicitOutput, input);
|
||||
const auto explicit_output = directory.Path() / "named-output.h5";
|
||||
const auto result =
|
||||
RunApplication(ExplicitOutputArguments(input, explicit_output));
|
||||
ASSERT_EQ(result.exit_code, 0) << result.standard_error;
|
||||
ExpectFesaHdf5Identity(explicit_output, input);
|
||||
}
|
||||
|
||||
TEST(LinearStaticCli, ReturnsEveryExactExitCodeAndOrderedDiagnostic) {
|
||||
TempDirectory directory{"exit-codes"};
|
||||
const auto validInput = directory.path() / "valid.inp";
|
||||
const auto modelInput = directory.path() / "invalid-model.inp";
|
||||
const auto solverInput = directory.path() / "singular.inp";
|
||||
writeText(validInput, axialDeck(true, false, false));
|
||||
writeText(modelInput, axialDeck(true, true, false));
|
||||
writeText(solverInput, axialDeck(false, false, false));
|
||||
TempDirectory directory{"exit-codes"};
|
||||
const auto valid_input = directory.Path() / "valid.inp";
|
||||
const auto model_input = directory.Path() / "invalid-model.inp";
|
||||
const auto solver_input = directory.Path() / "singular.inp";
|
||||
WriteText(valid_input, AxialDeck(true, false, false));
|
||||
WriteText(model_input, AxialDeck(true, true, false));
|
||||
WriteText(solver_input, AxialDeck(false, false, false));
|
||||
|
||||
const auto success = runApplication(explicitOutputArguments(
|
||||
validInput, directory.path() / "success.h5"));
|
||||
const auto usage = runApplication({});
|
||||
const auto missingInput = directory.path() / "missing.inp";
|
||||
const auto input = runApplication({missingInput.string()});
|
||||
const auto repeatedOutput = runApplication(
|
||||
{missingInput.string(), "--output", "--output"});
|
||||
const auto unknownOutputOption = runApplication(
|
||||
{missingInput.string(), "--output", "--bogus"});
|
||||
const auto model = runApplication(explicitOutputArguments(
|
||||
modelInput, directory.path() / "model-failure.h5"));
|
||||
const auto solver = runApplication(explicitOutputArguments(
|
||||
solverInput, directory.path() / "solver-failure.h5"));
|
||||
const auto output = runApplication(explicitOutputArguments(
|
||||
validInput,
|
||||
directory.path() / "nonexistent-parent" / "results.h5"));
|
||||
const auto success = RunApplication(
|
||||
ExplicitOutputArguments(valid_input, directory.Path() / "success.h5"));
|
||||
const auto usage = RunApplication({});
|
||||
const auto missing_input = directory.Path() / "missing.inp";
|
||||
const auto input = RunApplication({missing_input.string()});
|
||||
const auto repeated_output =
|
||||
RunApplication({missing_input.string(), "--output", "--output"});
|
||||
const auto unknown_output_option =
|
||||
RunApplication({missing_input.string(), "--output", "--bogus"});
|
||||
const auto model = RunApplication(ExplicitOutputArguments(
|
||||
model_input, directory.Path() / "model-failure.h5"));
|
||||
const auto solver = RunApplication(ExplicitOutputArguments(
|
||||
solver_input, directory.Path() / "solver-failure.h5"));
|
||||
const auto output = RunApplication(ExplicitOutputArguments(
|
||||
valid_input, directory.Path() / "nonexistent-parent" / "results.h5"));
|
||||
|
||||
EXPECT_EQ(success.exitCode, 0) << success.standardError;
|
||||
EXPECT_EQ(usage.exitCode, 2);
|
||||
EXPECT_EQ(input.exitCode, 3);
|
||||
EXPECT_EQ(model.exitCode, 4);
|
||||
EXPECT_EQ(solver.exitCode, 5);
|
||||
EXPECT_EQ(output.exitCode, 6);
|
||||
EXPECT_EQ(success.exit_code, 0) << success.standard_error;
|
||||
EXPECT_EQ(usage.exit_code, 2);
|
||||
EXPECT_EQ(input.exit_code, 3);
|
||||
EXPECT_EQ(model.exit_code, 4);
|
||||
EXPECT_EQ(solver.exit_code, 5);
|
||||
EXPECT_EQ(output.exit_code, 6);
|
||||
|
||||
expectDiagnosticFieldOrder(usage.standardError);
|
||||
expectDiagnosticFieldOrder(input.standardError);
|
||||
expectDiagnosticFieldOrder(model.standardError);
|
||||
expectDiagnosticFieldOrder(solver.standardError);
|
||||
expectDiagnosticFieldOrder(output.standardError);
|
||||
EXPECT_EQ(repeatedOutput.exitCode, 2);
|
||||
expectDiagnosticFieldOrder(repeatedOutput.standardError);
|
||||
EXPECT_NE(repeatedOutput.standardError.find("code=cli-usage"),
|
||||
std::string::npos);
|
||||
EXPECT_EQ(unknownOutputOption.exitCode, 2);
|
||||
expectDiagnosticFieldOrder(unknownOutputOption.standardError);
|
||||
EXPECT_NE(unknownOutputOption.standardError.find("code=cli-usage"),
|
||||
std::string::npos);
|
||||
ExpectDiagnosticFieldOrder(usage.standard_error);
|
||||
ExpectDiagnosticFieldOrder(input.standard_error);
|
||||
ExpectDiagnosticFieldOrder(model.standard_error);
|
||||
ExpectDiagnosticFieldOrder(solver.standard_error);
|
||||
ExpectDiagnosticFieldOrder(output.standard_error);
|
||||
EXPECT_EQ(repeated_output.exit_code, 2);
|
||||
ExpectDiagnosticFieldOrder(repeated_output.standard_error);
|
||||
EXPECT_NE(repeated_output.standard_error.find("code=cli-usage"),
|
||||
std::string::npos);
|
||||
EXPECT_EQ(unknown_output_option.exit_code, 2);
|
||||
ExpectDiagnosticFieldOrder(unknown_output_option.standard_error);
|
||||
EXPECT_NE(unknown_output_option.standard_error.find("code=cli-usage"),
|
||||
std::string::npos);
|
||||
}
|
||||
|
||||
TEST(LinearStaticCli, OutputRequestsDoNotFilterMandatoryResults) {
|
||||
TempDirectory directory{"output-requests"};
|
||||
const auto plainInput = directory.path() / "plain.inp";
|
||||
const auto requestedInput = directory.path() / "requested.inp";
|
||||
const auto plainOutput = directory.path() / "plain.h5";
|
||||
const auto requestedOutput = directory.path() / "requested.h5";
|
||||
writeText(plainInput, axialDeck(true, false, false));
|
||||
writeText(requestedInput, axialDeck(true, false, true));
|
||||
TempDirectory directory{"output-requests"};
|
||||
const auto plain_input = directory.Path() / "plain.inp";
|
||||
const auto requested_input = directory.Path() / "requested.inp";
|
||||
const auto plain_output = directory.Path() / "plain.h5";
|
||||
const auto requested_output = directory.Path() / "requested.h5";
|
||||
WriteText(plain_input, AxialDeck(true, false, false));
|
||||
WriteText(requested_input, AxialDeck(true, false, true));
|
||||
|
||||
const auto plain = runApplication(
|
||||
explicitOutputArguments(plainInput, plainOutput));
|
||||
const auto requested = runApplication(
|
||||
explicitOutputArguments(requestedInput, requestedOutput));
|
||||
ASSERT_EQ(plain.exitCode, 0) << plain.standardError;
|
||||
ASSERT_EQ(requested.exitCode, 0) << requested.standardError;
|
||||
const auto plain =
|
||||
RunApplication(ExplicitOutputArguments(plain_input, plain_output));
|
||||
const auto requested = RunApplication(
|
||||
ExplicitOutputArguments(requested_input, requested_output));
|
||||
ASSERT_EQ(plain.exit_code, 0) << plain.standard_error;
|
||||
ASSERT_EQ(requested.exit_code, 0) << requested.standard_error;
|
||||
|
||||
const auto plainFile = openFile(plainOutput);
|
||||
const auto requestedFile = openFile(requestedOutput);
|
||||
expectMandatoryInventory(plainFile.get());
|
||||
expectMandatoryInventory(requestedFile.get());
|
||||
const auto plain_file = OpenFile(plain_output);
|
||||
const auto requested_file = OpenFile(requested_output);
|
||||
ExpectMandatoryInventory(plain_file.Get());
|
||||
ExpectMandatoryInventory(requested_file.Get());
|
||||
|
||||
for (const char* suffix : {
|
||||
"/nodal/displacement",
|
||||
"/nodal/reaction",
|
||||
"/element/end_force_local",
|
||||
"/element/section_resultant",
|
||||
"/element/generalized_strain",
|
||||
"/element/generalized_resultant"}) {
|
||||
const std::string path = std::string{kStepRoot} + suffix;
|
||||
EXPECT_EQ(
|
||||
readDoubleDataset(requestedFile.get(), path),
|
||||
readDoubleDataset(plainFile.get(), path))
|
||||
<< path;
|
||||
}
|
||||
EXPECT_EQ(datasetDimensions(plainFile.get(), "/diagnostics"),
|
||||
std::vector<hsize_t>({0U}));
|
||||
const auto requestedDiagnostics =
|
||||
datasetDimensions(requestedFile.get(), "/diagnostics");
|
||||
ASSERT_EQ(requestedDiagnostics.size(), 1U);
|
||||
EXPECT_GT(requestedDiagnostics[0U], 0U);
|
||||
for (const char* suffix :
|
||||
{"/nodal/displacement", "/nodal/reaction", "/element/end_force_local",
|
||||
"/element/section_resultant", "/element/generalized_strain",
|
||||
"/element/generalized_resultant"}) {
|
||||
const std::string path = std::string{kStepRoot} + suffix;
|
||||
EXPECT_EQ(ReadDoubleDataset(requested_file.Get(), path),
|
||||
ReadDoubleDataset(plain_file.Get(), path))
|
||||
<< path;
|
||||
}
|
||||
EXPECT_EQ(DatasetDimensions(plain_file.Get(), "/diagnostics"),
|
||||
std::vector<hsize_t>({0U}));
|
||||
const auto requested_diagnostics =
|
||||
DatasetDimensions(requested_file.Get(), "/diagnostics");
|
||||
ASSERT_EQ(requested_diagnostics.size(), 1U);
|
||||
EXPECT_GT(requested_diagnostics[0U], 0U);
|
||||
}
|
||||
|
||||
// MITC4-FLOW-001: shell input uses the unchanged application route and syntax.
|
||||
TEST(Mitc4ShellCli, WritesShellHdf5ThroughTheExistingApplicationRoute) {
|
||||
TempDirectory directory{"shell-route"};
|
||||
const auto input = directory.path() / "shell.inp";
|
||||
const auto output = directory.path() / "shell-results.h5";
|
||||
writeText(input, allConstrainedShellDeck());
|
||||
TempDirectory directory{"shell-route"};
|
||||
const auto input = directory.Path() / "shell.inp";
|
||||
const auto output = directory.Path() / "shell-results.h5";
|
||||
WriteText(input, AllConstrainedShellDeck());
|
||||
|
||||
const auto result = runApplication(explicitOutputArguments(input, output));
|
||||
ASSERT_EQ(result.exitCode, 0) << result.standardError;
|
||||
expectShellHdf5Identity(output, input);
|
||||
const auto result = RunApplication(ExplicitOutputArguments(input, output));
|
||||
ASSERT_EQ(result.exit_code, 0) << result.standard_error;
|
||||
ExpectShellHdf5Identity(output, input);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user