#include "fesa/analysis/newton_iteration_log.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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-newton-log-" + label + "-" + std::to_string(tick)); std::error_code error; if (!std::filesystem::create_directory(path_, error) || error) { throw std::runtime_error{"Unable to create Newton log 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 ExpectLogOutputFailure(const fesa::Status& status) { ASSERT_FALSE(status.IsOk()); ASSERT_EQ(status.Category(), fesa::FailureCategory::kOutput); ASSERT_EQ(status.Diagnostics().size(), 1U); EXPECT_EQ(status.Diagnostics().front().code, "log-output-failure"); } std::vector SplitTabs(const std::string& line) { std::vector fields; std::size_t start = 0U; while (start <= line.size()) { const std::size_t end = line.find('\t', start); if (end == std::string::npos) { fields.push_back(line.substr(start)); return fields; } fields.push_back(line.substr(start, end - start)); start = end + 1U; } return fields; } const std::string& FieldValue(const std::vector& fields, const std::string_view label) { const std::string prefix = std::string{label} + "="; for (const std::string& field : fields) { if (field.compare(0U, prefix.size(), prefix) == 0) { return field; } } throw std::runtime_error{"Newton log field is missing."}; } std::string ValueAfterEquals(const std::string& field) { return field.substr(field.find('=') + 1U); } fesa::Vector VectorFrom(const std::vector& values) { fesa::Vector result{values.size()}; for (std::size_t index = 0U; index < values.size(); ++index) { result[index] = values[index]; } return result; } fesa::NewtonChannelDecision MakeDecision(const std::string& identity) { return {identity, 0.0012345678901234567, 1.2345678901234567, fesa::NewtonConvergenceBranch::kRelative, fesa::kNewtonRelativeThreshold, fesa::NewtonChannelUnit::kDimensionless, true}; } fesa::NewtonIterationLogRecord CompleteRecord() { return {"Step-1", 3U, 0.12345678901234567, 2U, fesa::NewtonIterationStage::kConvergenceEvaluated, {false, fesa::NewtonIterationStage::kFailed, "", ""}, "accept", "accepted", "none", MakeDecision("residual_force"), MakeDecision("residual_moment"), MakeDecision("correction_translation"), MakeDecision("correction_rotation"), true}; } std::vector WriteAndRead( const std::filesystem::path& path, const fesa::NewtonIterationLogRecord& record) { fesa::NewtonIterationLogWriter writer; if (!writer.Open(path).IsOk() || !writer.Write(record).IsOk() || !writer.Flush().IsOk() || !writer.Close().IsOk()) { throw std::runtime_error{"Unable to write Newton log test record."}; } std::ifstream input{path, std::ios::binary}; if (!input.is_open()) { throw std::runtime_error{"Unable to read Newton log test record."}; } const std::string contents{std::istreambuf_iterator{input}, std::istreambuf_iterator{}}; if (contents.empty()) { throw std::runtime_error{"Newton log test record is empty."}; } EXPECT_EQ(contents.back(), '\n'); EXPECT_EQ(std::count(contents.begin(), contents.end(), '\n'), 1); EXPECT_EQ(contents.find('\r'), std::string::npos); EXPECT_FALSE(contents.size() >= 3U && static_cast(contents[0U]) == 0xEFU && static_cast(contents[1U]) == 0xBBU && static_cast(contents[2U]) == 0xBFU); return SplitTabs(contents.substr(0U, contents.size() - 1U)); } // NSNR-LOG-001, NSNR-LOG-002, and NSNR-LOG-003 TEST(NewtonIterationLog, WritesOneLfTerminated39FieldRecordInApprovedOrder) { TempDirectory directory{"complete"}; const auto fields = WriteAndRead(directory.Path() / "model_log.txt", CompleteRecord()); const std::vector labels{ "record", "schema", "increment", "target_factor", "iteration", "last_completed_stage", "failing_stage", "residual_force_channel", "residual_force_numerator_norm", "residual_force_denominator_scale", "residual_force_branch", "residual_force_threshold", "residual_force_threshold_unit", "residual_force_pass", "residual_moment_channel", "residual_moment_numerator_norm", "residual_moment_denominator_scale", "residual_moment_branch", "residual_moment_threshold", "residual_moment_threshold_unit", "residual_moment_pass", "correction_translation_channel", "correction_translation_numerator_norm", "correction_translation_denominator_scale", "correction_translation_branch", "correction_translation_threshold", "correction_translation_threshold_unit", "correction_translation_pass", "correction_rotation_channel", "correction_rotation_numerator_norm", "correction_rotation_denominator_scale", "correction_rotation_branch", "correction_rotation_threshold", "correction_rotation_threshold_unit", "correction_rotation_pass", "combined_pass", "transition", "outcome", "diagnostic_code"}; ASSERT_EQ(fields.size(), labels.size()); for (std::size_t index = 0U; index < labels.size(); ++index) { EXPECT_EQ(fields[index].substr(0U, fields[index].find('=')), labels[index]); } EXPECT_EQ(FieldValue(fields, "record"), "record=newton-iteration"); EXPECT_EQ(FieldValue(fields, "schema"), "schema=nsnr-log-v1"); } // NSNR-LOG-004 TEST(NewtonIterationLog, FormatsFiniteNumbersForLocaleIndependentRoundTrip) { TempDirectory directory{"numbers"}; const auto fields = WriteAndRead(directory.Path() / "model_log.txt", CompleteRecord()); const std::string value = ValueAfterEquals(FieldValue(fields, "target_factor")); double parsed = 0.0; const auto result = std::from_chars(value.data(), value.data() + value.size(), parsed, std::chars_format::general); EXPECT_EQ(result.ec, std::errc{}); EXPECT_EQ(result.ptr, value.data() + value.size()); EXPECT_DOUBLE_EQ(parsed, 0.12345678901234567); EXPECT_NE(value.find('.'), std::string::npos); EXPECT_EQ(value.find(','), std::string::npos); } // NSNR-LOG-005 and NSNR-LOG-007 TEST(NewtonIterationLog, SerializesChannelDecisionsAndExplicitOutcomeFields) { TempDirectory directory{"channels"}; const auto fields = WriteAndRead(directory.Path() / "model_log.txt", CompleteRecord()); for (const std::string_view prefix : {"residual_force", "residual_moment", "correction_translation", "correction_rotation"}) { EXPECT_EQ(FieldValue(fields, std::string{prefix} + "_channel"), std::string{prefix} + "_channel=" + std::string{prefix}); EXPECT_EQ(FieldValue(fields, std::string{prefix} + "_numerator_norm"), std::string{prefix} + "_numerator_norm=0.0012345678901234567"); EXPECT_EQ(FieldValue(fields, std::string{prefix} + "_denominator_scale"), std::string{prefix} + "_denominator_scale=1.2345678901234567"); EXPECT_EQ(FieldValue(fields, std::string{prefix} + "_branch"), std::string{prefix} + "_branch=relative"); EXPECT_EQ(FieldValue(fields, std::string{prefix} + "_threshold"), std::string{prefix} + "_threshold=0.01"); EXPECT_EQ(FieldValue(fields, std::string{prefix} + "_threshold_unit"), std::string{prefix} + "_threshold_unit=1"); EXPECT_EQ(FieldValue(fields, std::string{prefix} + "_pass"), std::string{prefix} + "_pass=1"); } EXPECT_EQ(FieldValue(fields, "last_completed_stage"), "last_completed_stage=convergence-gates"); EXPECT_EQ(FieldValue(fields, "failing_stage"), "failing_stage=none"); EXPECT_EQ(FieldValue(fields, "combined_pass"), "combined_pass=1"); EXPECT_EQ(FieldValue(fields, "transition"), "transition=accept"); EXPECT_EQ(FieldValue(fields, "outcome"), "outcome=accepted"); EXPECT_EQ(FieldValue(fields, "diagnostic_code"), "diagnostic_code=none"); } // NSNR-LOG-006 TEST(NewtonIterationLog, SerializesEmptyChannelsAsTypedZeroScaleDecisions) { TempDirectory directory{"zero-scale"}; auto record = CompleteRecord(); const std::vector units{ fesa::NewtonChannelUnit::kForce, fesa::NewtonChannelUnit::kForceLength, fesa::NewtonChannelUnit::kLength, fesa::NewtonChannelUnit::kDimensionless}; fesa::NewtonChannelDecision* decisions[]{ &record.force_residual, &record.moment_residual, &record.translation_correction, &record.rotation_correction}; for (std::size_t index = 0U; index < std::size(decisions); ++index) { decisions[index]->numerator_norm = 0.0; decisions[index]->denominator_scale = 0.0; decisions[index]->branch = fesa::NewtonConvergenceBranch::kZeroScale; decisions[index]->threshold = fesa::kNewtonZeroScaleThreshold; decisions[index]->threshold_unit = units[index]; decisions[index]->passed = true; } const auto fields = WriteAndRead(directory.Path() / "model_log.txt", record); for (const std::string_view prefix : {"residual_force", "residual_moment", "correction_translation", "correction_rotation"}) { EXPECT_EQ(FieldValue(fields, std::string{prefix} + "_numerator_norm"), std::string{prefix} + "_numerator_norm=0"); EXPECT_EQ(FieldValue(fields, std::string{prefix} + "_denominator_scale"), std::string{prefix} + "_denominator_scale=0"); EXPECT_EQ(FieldValue(fields, std::string{prefix} + "_branch"), std::string{prefix} + "_branch=zero-scale"); EXPECT_EQ(FieldValue(fields, std::string{prefix} + "_threshold"), std::string{prefix} + "_threshold=0.001"); } EXPECT_EQ(FieldValue(fields, "residual_force_threshold_unit"), "residual_force_threshold_unit=force"); EXPECT_EQ(FieldValue(fields, "residual_moment_threshold_unit"), "residual_moment_threshold_unit=force*length"); EXPECT_EQ(FieldValue(fields, "correction_translation_threshold_unit"), "correction_translation_threshold_unit=length"); EXPECT_EQ(FieldValue(fields, "correction_rotation_threshold_unit"), "correction_rotation_threshold_unit=1"); } // NSNR-LOG-008 and NSNR-LOG-009 TEST(NewtonIterationLog, EscapesStringsAndOmitsUnevaluatedFailureFields) { TempDirectory directory{"failure"}; auto record = CompleteRecord(); record.last_completed_stage = fesa::NewtonIterationStage::kInternalAssembled; record.failure = {true, fesa::NewtonIterationStage::kExternalAssembled, "load\\failure", std::string{"external\tload\nmissing\r"} + '\0'}; record.transition = "fail\ttransition"; record.outcome = "failed\nrecord"; record.diagnostic_code = std::string{"external\\load\r"} + '\0'; const auto fields = WriteAndRead(directory.Path() / "model_log.txt", record); const std::string not_evaluated = "not-evaluated(external\\tload\\nmissing\\r\\0)"; for (std::size_t index = 7U; index < 36U; ++index) { EXPECT_EQ(ValueAfterEquals(fields[index]), not_evaluated); } EXPECT_EQ(FieldValue(fields, "failing_stage"), "failing_stage=load-boundary"); EXPECT_EQ(FieldValue(fields, "transition"), "transition=fail\\ttransition"); EXPECT_EQ(FieldValue(fields, "outcome"), "outcome=failed\\nrecord"); EXPECT_EQ(FieldValue(fields, "diagnostic_code"), "diagnostic_code=external\\\\load\\r\\0"); } // NSNR-LOG-010 TEST(NewtonIterationLog, ReportsWriteFlushAndCloseStateFailures) { TempDirectory directory{"failures"}; const auto path = directory.Path() / "model_log.txt"; fesa::NewtonIterationLogWriter writer; ExpectLogOutputFailure(writer.Write(CompleteRecord())); ExpectLogOutputFailure(writer.Flush()); ExpectLogOutputFailure(writer.Close()); fesa::NewtonIterationLogWriter invalid_open_writer; ExpectLogOutputFailure( invalid_open_writer.Open(directory.Path() / "missing" / "log.txt")); fesa::NewtonIterationLogWriter closed_writer; ASSERT_TRUE(closed_writer.Open(path).IsOk()); ASSERT_TRUE(closed_writer.Close().IsOk()); ExpectLogOutputFailure(closed_writer.Write(CompleteRecord())); ExpectLogOutputFailure(closed_writer.Flush()); ExpectLogOutputFailure(closed_writer.Close()); } // NSNR-LOG-011 TEST(NewtonIterationLog, LeavesZeroByteLogForNonNewtonControlledFailurePath) { TempDirectory directory{"zero-byte"}; const auto path = directory.Path() / "model_log.txt"; fesa::NewtonIterationLogWriter writer; ASSERT_TRUE(writer.Open(path).IsOk()); ASSERT_TRUE(writer.Close().IsOk()); EXPECT_TRUE(std::filesystem::exists(path)); EXPECT_EQ(std::filesystem::file_size(path), 0U); } // NSNR-LOG-012 TEST(NewtonIterationLog, SerializesEvaluatorProducedExternalScaleResidualDecisions) { TempDirectory directory{"external-scale"}; const fesa::NewtonConvergenceInput zero_external{ {"residual_force", VectorFrom({1.0e-3}), VectorFrom({2.0}), VectorFrom({0.0})}, {"residual_moment", VectorFrom({1.0e-3}), VectorFrom({3.0}), VectorFrom({0.0})}, {"correction_translation", VectorFrom({1.0e-2}), VectorFrom({1.0})}, {"correction_rotation", VectorFrom({1.0e-2}), VectorFrom({1.0})}, }; const auto zero_decisions = fesa::EvaluateNewtonConvergence(zero_external); auto zero_record = CompleteRecord(); zero_record.force_residual = zero_decisions.force_residual; zero_record.moment_residual = zero_decisions.moment_residual; zero_record.translation_correction = zero_decisions.translation_correction; zero_record.rotation_correction = zero_decisions.rotation_correction; zero_record.combined_passed = zero_decisions.combined_passed; const auto zero_fields = WriteAndRead(directory.Path() / "zero_log.txt", zero_record); ASSERT_EQ(zero_fields.size(), 39U); EXPECT_EQ(FieldValue(zero_fields, "schema"), "schema=nsnr-log-v1"); EXPECT_EQ(FieldValue(zero_fields, "residual_force_denominator_scale"), "residual_force_denominator_scale=0"); EXPECT_EQ(FieldValue(zero_fields, "residual_moment_denominator_scale"), "residual_moment_denominator_scale=0"); EXPECT_EQ(FieldValue(zero_fields, "residual_force_branch"), "residual_force_branch=zero-scale"); EXPECT_EQ(FieldValue(zero_fields, "residual_moment_branch"), "residual_moment_branch=zero-scale"); EXPECT_EQ(FieldValue(zero_fields, "residual_force_threshold"), "residual_force_threshold=0.001"); EXPECT_EQ(FieldValue(zero_fields, "residual_moment_threshold"), "residual_moment_threshold=0.001"); EXPECT_EQ(FieldValue(zero_fields, "residual_force_threshold_unit"), "residual_force_threshold_unit=force"); EXPECT_EQ(FieldValue(zero_fields, "residual_moment_threshold_unit"), "residual_moment_threshold_unit=force*length"); const fesa::NewtonConvergenceInput nonzero_external{ {"residual_force", VectorFrom({2.0e-2}), VectorFrom({2.0}), VectorFrom({1.0})}, {"residual_moment", VectorFrom({2.0e-2}), VectorFrom({1.0}), VectorFrom({2.0})}, {"correction_translation", VectorFrom({1.0e-2}), VectorFrom({1.0})}, {"correction_rotation", VectorFrom({1.0e-2}), VectorFrom({1.0})}, }; const auto relative_decisions = fesa::EvaluateNewtonConvergence(nonzero_external); auto relative_record = CompleteRecord(); relative_record.force_residual = relative_decisions.force_residual; relative_record.moment_residual = relative_decisions.moment_residual; relative_record.translation_correction = relative_decisions.translation_correction; relative_record.rotation_correction = relative_decisions.rotation_correction; relative_record.combined_passed = relative_decisions.combined_passed; const auto relative_fields = WriteAndRead(directory.Path() / "relative_log.txt", relative_record); ASSERT_EQ(relative_fields.size(), 39U); EXPECT_EQ(FieldValue(relative_fields, "residual_force_denominator_scale"), "residual_force_denominator_scale=2"); EXPECT_EQ(FieldValue(relative_fields, "residual_moment_denominator_scale"), "residual_moment_denominator_scale=2"); EXPECT_EQ(FieldValue(relative_fields, "residual_force_branch"), "residual_force_branch=relative"); EXPECT_EQ(FieldValue(relative_fields, "residual_moment_branch"), "residual_moment_branch=relative"); EXPECT_EQ(FieldValue(relative_fields, "residual_force_threshold"), "residual_force_threshold=0.01"); EXPECT_EQ(FieldValue(relative_fields, "residual_moment_threshold"), "residual_moment_threshold=0.01"); EXPECT_EQ(FieldValue(relative_fields, "residual_force_threshold_unit"), "residual_force_threshold_unit=1"); EXPECT_EQ(FieldValue(relative_fields, "residual_moment_threshold_unit"), "residual_moment_threshold_unit=1"); } fesa::DynamicNewtonIterationLogRecord CompleteDynamicRecord() { return {2U, 3U, 0.25, "convergence", "accepted", "", {2.0e-12, 0.0}, {2.0, 0.0}, {1.0e-11, 2.0e-11}, {3.0e-11, 4.0e-11}, {5.0e-11, 6.0e-11}, {7.0e-11, 8.0e-11}, true, true}; } std::string ReadLogBytes(const std::filesystem::path& path) { std::ifstream input{path, std::ios::binary}; return std::string{std::istreambuf_iterator{input}, std::istreambuf_iterator{}}; } TEST(DynamicNewtonIterationLog, WritesTimeAndAllTwoChannelMetrics) { TempDirectory directory{"dynamic-complete"}; const auto path = directory.Path() / "dynamic_log.txt"; fesa::NewtonIterationLogWriter writer; const auto record = CompleteDynamicRecord(); ASSERT_TRUE(writer.Open(path).IsOk()); ASSERT_TRUE(writer.Write(record).IsOk()); ASSERT_TRUE(writer.Flush().IsOk()); ASSERT_TRUE(writer.Close().IsOk()); const std::string bytes = ReadLogBytes(path); ASSERT_FALSE(bytes.empty()); EXPECT_EQ(bytes.back(), '\n'); EXPECT_EQ(std::count(bytes.begin(), bytes.end(), '\n'), 1); EXPECT_EQ(bytes.find('\r'), std::string::npos); EXPECT_EQ(bytes.find("target_factor"), std::string::npos); EXPECT_EQ(bytes.find("nsnr-log-v1"), std::string::npos); const auto fields = SplitTabs(bytes.substr(0U, bytes.size() - 1U)); EXPECT_EQ(fields.size(), 24U); EXPECT_EQ(FieldValue(fields, "record"), "record=dynamic-newton-iteration"); EXPECT_EQ(FieldValue(fields, "schema"), "schema=hht-log-v1"); EXPECT_EQ(FieldValue(fields, "increment"), "increment=2"); EXPECT_EQ(FieldValue(fields, "iteration"), "iteration=3"); EXPECT_EQ(FieldValue(fields, "time"), "time=0.25"); EXPECT_EQ(FieldValue(fields, "stage"), "stage=convergence"); EXPECT_EQ(FieldValue(fields, "residual_u_branch"), "residual_u_branch=relative"); EXPECT_EQ(FieldValue(fields, "residual_phi_branch"), "residual_phi_branch=zero-scale"); EXPECT_EQ(FieldValue(fields, "residual_pass"), "residual_pass=1"); EXPECT_EQ(FieldValue(fields, "correction_pass"), "correction_pass=1"); const std::vector> expected{ {"residual_u_numerator", 2.0e-12}, {"residual_u_scale", 2.0}, {"residual_phi_numerator", 0.0}, {"residual_phi_scale", 0.0}, {"coordinate_correction_u", 1.0e-11}, {"coordinate_correction_phi", 2.0e-11}, {"velocity_correction_u", 3.0e-11}, {"velocity_correction_phi", 4.0e-11}, {"acceleration_correction_u", 5.0e-11}, {"acceleration_correction_phi", 6.0e-11}, {"algorithmic_correction_u", 7.0e-11}, {"algorithmic_correction_phi", 8.0e-11}}; for (const auto& metric : expected) { const auto text = ValueAfterEquals(FieldValue(fields, metric.first)); double value = 0.0; const auto parsed = std::from_chars(text.data(), text.data() + text.size(), value); EXPECT_EQ(parsed.ec, std::errc{}); EXPECT_EQ(parsed.ptr, text.data() + text.size()); EXPECT_DOUBLE_EQ(value, metric.second); } } TEST(DynamicNewtonIterationLog, EscapesFailureContextAndKeepsZeroScaleFailure) { TempDirectory directory{"dynamic-failure"}; const auto path = directory.Path() / "dynamic_log.txt"; auto record = CompleteDynamicRecord(); record.stage = "mass\tcheck"; record.outcome = "failed\ntrial"; record.failure_code = "dynamic\\mass\rregularity"; record.residual_numerator = {1.0, 0.0}; record.residual_scale = {0.0, 0.0}; record.residual_pass = false; record.correction_pass = false; fesa::NewtonIterationLogWriter writer; ASSERT_TRUE(writer.Open(path).IsOk()); ASSERT_TRUE(writer.Write(record).IsOk()); ASSERT_TRUE(writer.Close().IsOk()); const auto bytes = ReadLogBytes(path); EXPECT_EQ(std::count(bytes.begin(), bytes.end(), '\n'), 1); const auto fields = SplitTabs(bytes.substr(0U, bytes.size() - 1U)); EXPECT_EQ(FieldValue(fields, "stage"), "stage=mass\\tcheck"); EXPECT_EQ(FieldValue(fields, "outcome"), "outcome=failed\\ntrial"); EXPECT_EQ(FieldValue(fields, "failure_code"), "failure_code=dynamic\\\\mass\\rregularity"); EXPECT_EQ(FieldValue(fields, "residual_u_numerator"), "residual_u_numerator=1"); EXPECT_EQ(FieldValue(fields, "residual_u_scale"), "residual_u_scale=0"); EXPECT_EQ(FieldValue(fields, "residual_u_branch"), "residual_u_branch=zero-scale"); EXPECT_EQ(FieldValue(fields, "residual_pass"), "residual_pass=0"); } TEST(DynamicNewtonIterationLog, InvalidEvidencePreservesFlushedPrefixAndFailsLifecycle) { using Record = fesa::DynamicNewtonIterationLogRecord; const double nan = std::numeric_limits::quiet_NaN(); const double infinity = std::numeric_limits::infinity(); const std::vector> mutations{ [nan](Record& record) { record.time = nan; }, [nan](Record& record) { record.residual_numerator[0U] = nan; }, [infinity](Record& record) { record.residual_scale[1U] = infinity; }, [nan](Record& record) { record.coordinate_correction[0U] = nan; }, [infinity](Record& record) { record.velocity_correction[1U] = infinity; }, [nan](Record& record) { record.acceleration_correction[0U] = nan; }, [infinity](Record& record) { record.algorithmic_correction[1U] = infinity; }, [](Record& record) { record.time = -0.1; }, [](Record& record) { record.residual_scale[0U] = -1.0; }, [](Record& record) { record.coordinate_correction[0U] = -1.0; }, [](Record& record) { record.stage.clear(); }, [](Record& record) { record.outcome.clear(); }}; TempDirectory directory{"dynamic-invalid"}; const auto path = directory.Path() / "dynamic_log.txt"; for (std::size_t index = 0U; index < mutations.size(); ++index) { SCOPED_TRACE(index); fesa::NewtonIterationLogWriter writer; ASSERT_TRUE(writer.Open(path).IsOk()); ASSERT_TRUE(writer.Write(CompleteDynamicRecord()).IsOk()); ASSERT_TRUE(writer.Flush().IsOk()); const std::string prefix = ReadLogBytes(path); auto invalid = CompleteDynamicRecord(); mutations[index](invalid); ExpectLogOutputFailure(writer.Write(invalid)); ExpectLogOutputFailure(writer.Write(CompleteDynamicRecord())); ExpectLogOutputFailure(writer.Flush()); ExpectLogOutputFailure(writer.Close()); EXPECT_EQ(ReadLogBytes(path), prefix); } fesa::NewtonIterationLogWriter unopened; ExpectLogOutputFailure(unopened.Write(CompleteDynamicRecord())); } } // namespace