Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ sections to include in release notes:

### Fixed
- Return a non-zero exit status from the unit test runner when a test fails or is missing. (#432)
- Report restart checkpoint write or close failures and exit with file write error status 11, including in quiet mode. (#420)
- Restore format specifiers for stock value in `ensureNonNegative()` error logging to align arguments and prevent undefined behavior. (#406)
- Terminating harvest events will now remove all biomass at end of time step, instead of allowing small amounts to remain in the plant pools due to growth in that step. (#400)
- Leaf-off mechanics refined to ensure that leaf-off events do not remove more leaf carbon than is available in the leaf pool. (#400)
Expand Down
1 change: 1 addition & 0 deletions src/common/exitCodes.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ typedef enum {
EXIT_CODE_UNKNOWN_EVENT_TYPE_OR_PARAM = 4,
EXIT_CODE_INPUT_FILE_ERROR = 5,
EXIT_CODE_FILE_OPEN_OR_READ_ERROR = 6,
EXIT_CODE_FILE_CLOSE_OR_WRITE_ERROR = 11,
EXIT_CODE_INTERNAL_ERROR = 7,
EXIT_CODE_BAD_CLI_ARGUMENT = 8,
EXIT_CODE_BAD_RESTART_PARAMETER = 9,
Expand Down
8 changes: 7 additions & 1 deletion src/sipnet/restart.c
Original file line number Diff line number Diff line change
Expand Up @@ -829,7 +829,13 @@ static void writeRestartState(const char *restartOut, const RestartState *state,

fprintf(out, "end_restart 1\n");

fclose(out);
int writeFailed = ferror(out);
// Always close the stream: buffered writes can fail during the final flush.
int closeFailed = fclose(out);
if (writeFailed || closeFailed != 0) {
logError("Failed to write restart checkpoint '%s'\n", restartOut);
exit(EXIT_CODE_FILE_CLOSE_OR_WRITE_ERROR);
}
}

static void checkRestartContextCompatibility(void) {
Expand Down
35 changes: 35 additions & 0 deletions tests/sipnet/test_restart_infrastructure/testRestartMVP.c
Original file line number Diff line number Diff line change
Expand Up @@ -716,6 +716,40 @@ static int testMalformedCheckpointFails(void) {
return status;
}

static int testCheckpointWriteFailure(void) {
logTest("Starting testCheckpointWriteFailure\n");

if (access("/dev/full", W_OK) != 0) {
logTest("Skipping checkpoint write failure test: /dev/full unavailable\n");
return 0;
}

int status = prepRunFiles("restart_segment1.clim", "events_segment1.in");
if (status) {
logTest("Failed to prepare files for checkpoint write failure test\n");
return status;
Comment thread
ANAMASGARD marked this conversation as resolved.
Resolved
}

const char *args[] = {
"--restart-out /dev/full --no-do-main-output --no-quiet",
"--restart-out /dev/full --no-do-main-output --quiet"};
const char *logs[] = {"checkpoint_write_failure.log",
"checkpoint_write_failure_quiet.log"};
for (int i = 0; i < 2; ++i) {
int rc = runModelWithArgs("restart_seg1.in", logs[i], args[i]);
int failed = (rc != EXIT_CODE_FILE_CLOSE_OR_WRITE_ERROR);
failed |= !fileContains(logs[i], "[ERROR ]");
failed |= !fileContains(logs[i],
"Failed to write restart checkpoint '/dev/full'");
if (failed) {
logTest("Checkpoint write failure test failed (quiet=%d, rc=%d)\n", i,
rc);
}
status |= failed;
}
return status;
}

int run(void) {
int status = 0;

Expand All @@ -734,6 +768,7 @@ int run(void) {
status |= testBuildInfoMismatchWarnsAndSucceeds();
status |= testTruncatedCheckpointFails();
status |= testMalformedCheckpointFails();
status |= testCheckpointWriteFailure();

return status;
}
Expand Down
Loading