diff --git a/include/fprops/Exception.h b/include/fprops/Exception.h new file mode 100644 index 0000000..000ee43 --- /dev/null +++ b/include/fprops/Exception.h @@ -0,0 +1,24 @@ +#pragma once + +#include "fmt/format.h" +#include + +namespace fprops { + +class Exception : public std::exception { +public: + template + Exception(fmt::format_string format, T... args) : + msg(fmt::format(format, std::forward(args)...)) + { + } + + /// Get the exception message + [[nodiscard]] auto what() const noexcept -> const char * override; + +private: + /// Error message + std::string msg; +}; + +} // namespace fprops diff --git a/src/Exception.cpp b/src/Exception.cpp new file mode 100644 index 0000000..1af6834 --- /dev/null +++ b/src/Exception.cpp @@ -0,0 +1,11 @@ +#include "fprops/Exception.h" + +namespace fprops { + +auto +Exception::what() const noexcept -> const char * +{ + return this->msg.c_str(); +} + +} // namespace fprops diff --git a/src/Helmholtz.cpp b/src/Helmholtz.cpp index f7d4208..d60cc93 100644 --- a/src/Helmholtz.cpp +++ b/src/Helmholtz.cpp @@ -1,7 +1,7 @@ #include "fprops/Helmholtz.h" #include "fprops/Numerics.h" +#include "fprops/Exception.h" #include -#include namespace fprops { @@ -18,9 +18,9 @@ State Helmholtz::rho_T(double rho, double T) const { if (rho < 0) - throw std::domain_error("Negative density"); + throw Exception("Negative density"); if (T < 0) - throw std::domain_error("Negative temperature"); + throw Exception("Negative temperature"); const double delta = rho / this->rho_c; const double tau = this->T_c / T; @@ -50,7 +50,7 @@ State Helmholtz::rho_p(double rho, double p) const { if (rho < 0) - throw std::domain_error("Negative density"); + throw Exception("Negative density"); const double T = T_from_rho_p(rho, p); @@ -81,7 +81,7 @@ State Helmholtz::p_T(double p, double T) const { if (T < 0) - throw std::domain_error("Negative temperature"); + throw Exception("Negative temperature"); const double rho = rho_from_p_T(p, T); @@ -112,9 +112,9 @@ State Helmholtz::v_u(double v, double u) const { if (v <= 0.) - throw std::domain_error("Negative specific volume"); + throw Exception("Negative specific volume"); if (u <= 0.) - throw std::domain_error("Negative internal energy"); + throw Exception("Negative internal energy"); const double rho = 1. / v; const double delta = rho / this->rho_c; @@ -143,7 +143,7 @@ Helmholtz::v_u(double v, double u) const State Helmholtz::h_s(double h, double s) const { - throw std::domain_error("Not implemented"); + throw Exception("Not implemented"); } double diff --git a/src/IdealGas.cpp b/src/IdealGas.cpp index 50ca092..473dcbd 100644 --- a/src/IdealGas.cpp +++ b/src/IdealGas.cpp @@ -1,6 +1,6 @@ #include "fprops/IdealGas.h" +#include "fprops/Exception.h" #include -#include namespace fprops { @@ -35,9 +35,9 @@ State IdealGas::rho_T(double rho, double T) const { if (rho < 0) - throw std::domain_error("Negative density"); + throw Exception("Negative density"); if (T < 0) - throw std::domain_error("Negative temperature"); + throw Exception("Negative temperature"); State state; state.rho = rho; @@ -51,7 +51,7 @@ IdealGas::rho_T(double rho, double T) const state.v = 1. / state.rho; const double n = std::pow(T, this->gamma) / std::pow(state.p, this->gamma - 1.0); if (n <= 0) - throw std::domain_error("Invalid log base for computing entropy"); + throw Exception("Invalid log base for computing entropy"); state.s = this->cv * std::log(n); state.h = this->cp * T; state.w = std::sqrt(this->cp * R * T / (this->cv * this->molar_mass)); @@ -62,7 +62,7 @@ State IdealGas::rho_p(double rho, double p) const { if (rho < 0) - throw std::domain_error("Negative density"); + throw Exception("Negative density"); State state; state.rho = rho; @@ -76,7 +76,7 @@ IdealGas::rho_p(double rho, double p) const state.v = 1. / state.rho; const double n = std::pow(state.T, this->gamma) / std::pow(state.p, this->gamma - 1.0); if (n <= 0) - throw std::domain_error("Invalid log base for computing entropy"); + throw Exception("Invalid log base for computing entropy"); state.s = this->cv * std::log(n); state.h = this->cp * state.T; state.w = std::sqrt(this->cp * R * state.T / (this->cv * this->molar_mass)); @@ -87,7 +87,7 @@ State IdealGas::p_T(double p, double T) const { if (T < 0) - throw std::domain_error("Negative temperature"); + throw Exception("Negative temperature"); State state; state.p = p; @@ -101,7 +101,7 @@ IdealGas::p_T(double p, double T) const state.v = 1. / state.rho; const double n = std::pow(T, this->gamma) / std::pow(p, this->gamma - 1.0); if (n <= 0) - throw std::domain_error("Invalid log base for computing entropy"); + throw Exception("Invalid log base for computing entropy"); state.s = this->cv * std::log(n); state.h = this->cp * T; state.w = std::sqrt(this->cp * R * T / (this->cv * this->molar_mass)); @@ -112,9 +112,9 @@ State IdealGas::v_u(double v, double u) const { if (v <= 0.) - throw std::domain_error("Negative specific volume"); + throw Exception("Negative specific volume"); if (u <= 0.) - throw std::domain_error("Negative internal energy"); + throw Exception("Negative internal energy"); State state; state.v = v; @@ -128,7 +128,7 @@ IdealGas::v_u(double v, double u) const state.T = u / this->cv; const double n = std::pow(state.T, this->gamma) / std::pow(state.p, this->gamma - 1.0); if (n <= 0) - throw std::domain_error("Invalid log base for computing entropy"); + throw Exception("Invalid log base for computing entropy"); state.s = this->cv * std::log(n); state.h = this->cp * state.T; state.w = std::sqrt(this->gamma * this->R_specific * state.T); @@ -154,7 +154,7 @@ IdealGas::h_s(double h, double s) const state.v = 1. / state.rho; const double n = std::pow(state.T, this->gamma) / std::pow(state.p, this->gamma - 1.0); if (n <= 0) - throw std::domain_error("Invalid log base for computing entropy"); + throw Exception("Invalid log base for computing entropy"); state.w = std::sqrt(this->gamma * this->R_specific * state.T); return state; } diff --git a/src/InterpolatedFluidProperties.cpp b/src/InterpolatedFluidProperties.cpp index 948830d..0d154a4 100644 --- a/src/InterpolatedFluidProperties.cpp +++ b/src/InterpolatedFluidProperties.cpp @@ -1,6 +1,7 @@ #include "fprops/InterpolatedFluidProperties.h" #include "fprops/Utils.h" #include "fprops/Numerics.h" +#include "fprops/Exception.h" #include "h5pp/h5pp.h" #include "fmt/printf.h" #include "Eigen/Dense" @@ -50,7 +51,7 @@ InterpolatedFluidProperties::load(const std::string & file_name) this->hs_data = read_data(file, "h_s", { H, S }, { U, V, P, T, RHO, MU, CP, CV, K, W }); } else - throw std::runtime_error(fmt::format("File '{}' does not exist.", file_name)); + throw Exception("File '{}' does not exist.", file_name); } State @@ -62,7 +63,7 @@ InterpolatedFluidProperties::p_T(double p, double T) const vals[CP], vals[CV], vals[S], vals[K], vals[H], vals[W] }; } else - throw std::runtime_error("'p_T' data set is missing."); + throw Exception("'p_T' data set is missing."); } State @@ -74,7 +75,7 @@ InterpolatedFluidProperties::rho_T(double rho, double T) const vals[CP], vals[CV], vals[S], vals[K], vals[H], vals[W] }; } else - throw std::runtime_error("'rho_T' data set is missing."); + throw Exception("'rho_T' data set is missing."); } State @@ -86,7 +87,7 @@ InterpolatedFluidProperties::rho_p(double rho, double p) const vals[CP], vals[CV], vals[S], vals[K], vals[H], vals[W] }; } else - throw std::runtime_error("'rho_p' data set is missing."); + throw Exception("'rho_p' data set is missing."); } State @@ -98,7 +99,7 @@ InterpolatedFluidProperties::v_u(double v, double u) const vals[CP], vals[CV], vals[S], vals[K], vals[H], vals[W] }; } else - throw std::runtime_error("'v_u' data set is missing."); + throw Exception("'v_u' data set is missing."); } State @@ -110,7 +111,7 @@ InterpolatedFluidProperties::h_s(double h, double s) const vals[CP], vals[CV], s, vals[K], h, vals[W] }; } else - throw std::runtime_error("'h_s' data set is missing."); + throw Exception("'h_s' data set is missing."); } void @@ -120,7 +121,7 @@ InterpolatedFluidProperties::check_unit(const h5pp::File & file, { auto attr = file.readAttribute(dataset_name, "unit"); if (attr != unit) - throw std::runtime_error(fmt::format("Expected unit '{}' for '{}'.", unit, dataset_name)); + throw Exception("Expected unit '{}' for '{}'.", unit, dataset_name); } Eigen::VectorXd @@ -133,7 +134,7 @@ InterpolatedFluidProperties::read_var_range(const h5pp::File & file, check_unit(file, nm, this->var_units[var_idx]); file.readDataset(range, nm); if (range.size() < 2) - throw std::runtime_error( + throw Exception( fmt::format("'{}' range must have 2 or more grid points.", this->var_names[var_idx])); return range; } diff --git a/src/Numerics.cpp b/src/Numerics.cpp index 150e769..b7f2dd4 100644 --- a/src/Numerics.cpp +++ b/src/Numerics.cpp @@ -1,6 +1,6 @@ #include "fprops/Numerics.h" +#include "fprops/Exception.h" #include -#include namespace fprops { @@ -29,7 +29,7 @@ root(double x0, x0 = x1; } - throw std::runtime_error("Newton's method failed to converge"); + throw Exception("Newton's method failed to converge"); } } // namespace newton diff --git a/test/src/ExceptionTestMacros.h b/test/src/ExceptionTestMacros.h new file mode 100644 index 0000000..5f3f6da --- /dev/null +++ b/test/src/ExceptionTestMacros.h @@ -0,0 +1,28 @@ +#pragma once + +#include "fprops/Exception.h" + +/// Test that the `cmd` will throw `fprops::Exception` with message `msg` +#define EXPECT_THROW_MSG(cmd, msg) \ + try { \ + cmd; \ + FAIL(); \ + } \ + catch (Exception & e) { \ + EXPECT_STREQ(e.what(), msg); \ + } \ + catch (...) { \ + FAIL(); \ + } + +#define EXPECT_THAT_THROW_MSG(cmd, matcher) \ + try { \ + cmd; \ + FAIL(); \ + } \ + catch (Exception & e) { \ + EXPECT_THAT(e.what(), matcher); \ + } \ + catch (...) { \ + FAIL(); \ + } diff --git a/test/src/Helium_test.cpp b/test/src/Helium_test.cpp index 2d181e6..77a5720 100644 --- a/test/src/Helium_test.cpp +++ b/test/src/Helium_test.cpp @@ -1,4 +1,5 @@ #include "gtest/gtest.h" +#include "ExceptionTestMacros.h" #include "fprops/Helium.h" using namespace fprops; @@ -95,7 +96,7 @@ TEST(HeliumTest, v_u) double p = 1.0e6; auto state0 = fp.p_T(p, T); - EXPECT_THROW(auto f = fp.v_u(state0.v, state0.u), std::runtime_error); + EXPECT_THROW_MSG(auto f = fp.v_u(state0.v, state0.u), "Newton's method failed to converge"); /* State state = fp.v_u(state0.v, state0.u); diff --git a/test/src/Helmholtz_test.cpp b/test/src/Helmholtz_test.cpp index 329dd9d..d7ca9c0 100644 --- a/test/src/Helmholtz_test.cpp +++ b/test/src/Helmholtz_test.cpp @@ -1,4 +1,5 @@ #include "gmock/gmock.h" +#include "ExceptionTestMacros.h" #include "fprops/Helmholtz.h" using namespace fprops; @@ -26,37 +27,37 @@ TEST(HelmholtzTest, rho_T_incorrect) { MockHelmholtz fp; - EXPECT_THROW(auto p = fp.rho_T(-1, 300), std::domain_error); - EXPECT_THROW(auto p = fp.rho_T(1, -1), std::domain_error); + EXPECT_THROW_MSG(auto p = fp.rho_T(-1, 300), "Negative density"); + EXPECT_THROW_MSG(auto p = fp.rho_T(1, -1), "Negative temperature"); } TEST(HelmholtzTest, rho_p_incorrect) { MockHelmholtz fp; - EXPECT_THROW(auto p = fp.rho_p(-1, 300), std::domain_error); + EXPECT_THROW_MSG(auto p = fp.rho_p(-1, 300), "Negative density"); } TEST(HelmholtzTest, h_s) { MockHelmholtz fp; - EXPECT_THROW(auto p = fp.h_s(1, 1), std::domain_error); + EXPECT_THROW_MSG(auto p = fp.h_s(1, 1), "Not implemented"); } TEST(HelmholtzTest, p_T_incorrect) { MockHelmholtz fp; - EXPECT_THROW(auto p = fp.p_T(1e5, -1), std::domain_error); + EXPECT_THROW_MSG(auto p = fp.p_T(1e5, -1), "Negative temperature"); } TEST(HelmholtzTest, v_u_incorrect) { MockHelmholtz fp; - EXPECT_THROW(auto p = fp.v_u(-1, 1), std::domain_error); - EXPECT_THROW(auto p = fp.v_u(1, -1), std::domain_error); + EXPECT_THROW_MSG(auto st = fp.v_u(-1, 1), "Negative specific volume"); + EXPECT_THROW_MSG(auto st = fp.v_u(1, -1), "Negative internal energy"); } TEST(HelmholtzTest, ideal_gas_lead) diff --git a/test/src/IdealGas_test.cpp b/test/src/IdealGas_test.cpp index 44062ca..ec0be8b 100644 --- a/test/src/IdealGas_test.cpp +++ b/test/src/IdealGas_test.cpp @@ -1,4 +1,5 @@ #include "gtest/gtest.h" +#include "ExceptionTestMacros.h" #include "fprops/IdealGas.h" using namespace fprops; @@ -87,7 +88,7 @@ TEST(IdealGas, rho_p_incorrect) double molar_mass = 29.0e-3; IdealGas fp(gamma, molar_mass); - EXPECT_THROW(auto p = fp.rho_p(-1, 1e5), std::domain_error); + EXPECT_THROW_MSG(auto st = fp.rho_p(-1, 1e5), "Negative density"); } TEST(IdealGas, p_T) @@ -174,8 +175,8 @@ TEST(IdealGas, v_u_incorrect) double molar_mass = 29.0e-3; IdealGas fp(gamma, molar_mass); - EXPECT_THROW(auto p = fp.v_u(-1, 1), std::domain_error); - EXPECT_THROW(auto p = fp.v_u(1, -1), std::domain_error); + EXPECT_THROW_MSG(auto st = fp.v_u(-1, 1), "Negative specific volume"); + EXPECT_THROW_MSG(auto st = fp.v_u(1, -1), "Negative internal energy"); } TEST(IdealGas, p_T_incorrect) @@ -184,5 +185,5 @@ TEST(IdealGas, p_T_incorrect) double molar_mass = 29.0e-3; IdealGas fp(gamma, molar_mass); - EXPECT_THROW(auto p = fp.p_T(1e5, -1), std::domain_error); + EXPECT_THROW_MSG(auto st = fp.p_T(1e5, -1), "Negative temperature"); } diff --git a/test/src/InterpolatedFluidProperties_test.cpp b/test/src/InterpolatedFluidProperties_test.cpp index 3f746f7..626964b 100644 --- a/test/src/InterpolatedFluidProperties_test.cpp +++ b/test/src/InterpolatedFluidProperties_test.cpp @@ -1,4 +1,5 @@ #include "gmock/gmock.h" +#include "ExceptionTestMacros.h" #include "fprops/InterpolatedFluidProperties.h" #include @@ -123,13 +124,13 @@ TEST(InterpolatedFluidPropertiesTest, h_s) double h = 288084; double s = 6083; - EXPECT_THROW({ fp.h_s(h, s); }, std::runtime_error); + EXPECT_THROW_MSG(auto st = fp.h_s(h, s), "'h_s' data set is missing."); } TEST(InterpolatedFluidPropertiesTest, non_existent_file) { InterpolatedFluidProperties fp; - EXPECT_THROW({ fp.load("non-existent-file"); }, std::runtime_error); + EXPECT_THROW_MSG(fp.load("non-existent-file"), "File 'non-existent-file' does not exist."); } TEST(InterpolatedFluidPropertiesTest, empty_file) @@ -137,23 +138,23 @@ TEST(InterpolatedFluidPropertiesTest, empty_file) auto file_name = path(FPROPS_UNIT_TESTS_ROOT) / path("assets") / path("empty.fprops.h5"); InterpolatedFluidProperties fp; fp.load(file_name); - EXPECT_THROW({ fp.p_T(1e6, 280); }, std::runtime_error); - EXPECT_THROW({ fp.rho_T(0.1, 280); }, std::runtime_error); - EXPECT_THROW({ fp.rho_p(0.1, 1e6); }, std::runtime_error); - EXPECT_THROW({ fp.v_u(1, 1e5); }, std::runtime_error); - EXPECT_THROW({ fp.h_s(1e4, 1.1e4); }, std::runtime_error); + EXPECT_THROW_MSG(auto st = fp.p_T(1e6, 280), "'p_T' data set is missing."); + EXPECT_THROW_MSG(auto st = fp.rho_T(0.1, 280), "'rho_T' data set is missing."); + EXPECT_THROW_MSG(auto st = fp.rho_p(0.1, 1e6), "'rho_p' data set is missing."); + EXPECT_THROW_MSG(auto st = fp.v_u(1, 1e5), "'v_u' data set is missing."); + EXPECT_THROW_MSG(auto st = fp.h_s(1e4, 1.1e4), "'h_s' data set is missing."); } TEST(InterpolatedFluidPropertiesTest, grid_1_by_1_file) { auto file_name = path(FPROPS_UNIT_TESTS_ROOT) / path("assets") / path("grid-1x1.fprops.h5"); InterpolatedFluidProperties fp; - EXPECT_THROW({ fp.load(file_name); }, std::runtime_error); + EXPECT_THROW_MSG(fp.load(file_name), "'p' range must have 2 or more grid points."); } TEST(InterpolatedFluidPropertiesTest, wrong_units_file) { auto file_name = path(FPROPS_UNIT_TESTS_ROOT) / path("assets") / path("wrong-units.fprops.h5"); InterpolatedFluidProperties fp; - EXPECT_THROW({ fp.load(file_name); }, std::runtime_error); + EXPECT_THROW_MSG(fp.load(file_name), "Expected unit 'Pa' for '/p_T/p'."); } diff --git a/test/src/Numerics_test.cpp b/test/src/Numerics_test.cpp index 1092b7e..18452c4 100644 --- a/test/src/Numerics_test.cpp +++ b/test/src/Numerics_test.cpp @@ -1,4 +1,5 @@ #include "gtest/gtest.h" +#include "ExceptionTestMacros.h" #include "fprops/Numerics.h" using namespace fprops; @@ -46,5 +47,5 @@ TEST(NumericsTest, newton_root_diverge) return 3 * x * x - 2; }; - EXPECT_THROW(newton::root(0, f, df), std::runtime_error); + EXPECT_THROW_MSG(newton::root(0, f, df), "Newton's method failed to converge"); }