Skip to content

Conversation

@parth21999
Copy link
Member

@parth21999 parth21999 commented Jan 21, 2026

Summary

  • Add TIMED_TEST_SUITE_INITIALIZE and TIMED_TEST_SUITE_CLEANUP macros in common/inc/c_pal/timed_test_suite.h
  • Provides automatic timeout protection for integration tests using process_watchdog
  • Watchdog init runs BEFORE user suite init code
  • Watchdog deinit runs AFTER user suite cleanup code
  • Add unit tests to verify fixture ordering

Dependencies

@parth21999
Copy link
Member Author

parth21999 commented Jan 21, 2026

/AzurePipelines run #Resolved

@azure-pipelines
Copy link

azure-pipelines bot commented Jan 21, 2026

Azure Pipelines successfully started running 1 pipeline(s).

#Resolved

@parth21999
Copy link
Member Author

parth21999 commented Jan 21, 2026

/AzurePipelines run #Resolved

@azure-pipelines
Copy link

azure-pipelines bot commented Jan 21, 2026

Azure Pipelines successfully started running 1 pipeline(s).

#Resolved

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@parth21999
Copy link
Member Author

parth21999 commented Jan 21, 2026

/AzurePipelines run #Resolved

@azure-pipelines
Copy link

azure-pipelines bot commented Jan 21, 2026

Azure Pipelines successfully started running 1 pipeline(s).

#Resolved

@parth21999
Copy link
Member Author

parth21999 commented Jan 21, 2026

/AzurePipelines run #Resolved

@azure-pipelines
Copy link

azure-pipelines bot commented Jan 21, 2026

Azure Pipelines successfully started running 1 pipeline(s).

#Resolved

build_test_folder(thandle_ptr_ut)
build_test_folder(real_thandle_log_context_handle_ut)
build_test_folder(tqueue_ut)
add_subdirectory(timed_test_suite_ut)
Copy link
Contributor

@mattdurak mattdurak Jan 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add_subdirectory

Maybe should have a comment that it intentionally doesn't use c-testrunnerswitcher #Closed

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to use c-testrunnerswitcher.

if (timed_test_suite_ut_succeeded() != 0)
{
// Final validation of fixture ordering has failed
failedTests++;
Copy link
Contributor

@mattdurak mattdurak Jan 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we Log something here? #Resolved

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added log

add_subdirectory(timed_test_suite_ut)
endif()

if(${run_int_tests})
Copy link
Contributor

@mattdurak mattdurak Jan 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider an int test that demonstrates the timeout works. I guess since the process_watchdog will terminate, the test would probably need to launch that process and make sure it gets terminated within the timeout (+ delta) #Resolved

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added int test.

mattdurak
mattdurak previously approved these changes Jan 21, 2026
Copy link
Contributor

@mattdurak mattdurak left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

static int call_counter = 0;

// Mock process_watchdog functions
int process_watchdog_init(uint32_t timeout_ms)
Copy link
Member

@dcristoloveanu dcristoloveanu Jan 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

process_watchdog_init

Wait, where is this used? #Resolved

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is called inside TIMED_TEST_SUITE_INITIALIZE macro.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added specs and comment.

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).


// The child should NOT have exited cleanly (it was terminated by watchdog)
// On Windows, LogCriticalAndTerminate calls TerminateProcess which can give various exit codes
ASSERT_ARE_NOT_EQUAL(int, CHILD_EXIT_CODE_SURVIVED_TIMEOUT, exit_code,
Copy link
Member Author

@parth21999 parth21999 Jan 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's not true, it calls abort(). #Resolved

ASSERT_IS_NOT_NULL(dir);
int snprintf_result = snprintf(buffer, buffer_size, "%s/../process_watchdog_int_child/process_watchdog_int_child", dir);
ASSERT_IS_TRUE(snprintf_result > 0 && (size_t)snprintf_result < buffer_size);
}
Copy link
Member Author

@parth21999 parth21999 Jan 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add comment explaining this. in windows too. #Resolved

{
// Child process - exec the test program
char timeout_str[32];
(void)snprintf(timeout_str, sizeof(timeout_str), "%" PRIu32, timeout_ms);
Copy link
Member Author

@parth21999 parth21999 Jan 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(void)snprin

Can this be asserted? #Resolved

execl(exe_path, exe_path, timeout_str, NULL);

// If execl returns, it failed
(void)fprintf(stderr, "execl failed: %s\n", strerror(errno));
Copy link
Member Author

@parth21999 parth21999 Jan 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(void)

Can this be asserted. #Resolved

LogError("waitpid failed with error: %s", strerror(errno));
result = -1;
}
else if (WIFEXITED(status))
Copy link
Member Author

@parth21999 parth21999 Jan 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WIFEXITED

Add one line comments explaining what platform APIs are doing.

In windows too.

Test should be self documenting. #Resolved

…ests

- Fix comment: watchdog calls abort(), not TerminateProcess
- Add comments explaining WIFEXITED/WIFSIGNALED/WTERMSIG on Linux
- Add comments explaining WaitForSingleObject/GetExitCodeProcess on Windows
- Add comments explaining get_child_exe_path path construction logic
- Add snprintf error handling in forked child (can't use ASSERT in child)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@parth21999
Copy link
Member Author

/AzurePipelines run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

"Process terminated too late: %.0f ms > %d ms", elapsed_ms, WATCHDOG_TIMEOUT_MS + TOLERANCE_MS);

// The child should NOT have exited cleanly (it was terminated by watchdog)
// On Windows, the watchdog calls abort() which terminates the process
Copy link
Member Author

@parth21999 parth21999 Jan 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// On Windows, the watchdog calls abort() which terminates the process

This comment is not necessary any more, remove it. In linux too. #Resolved

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@parth21999
Copy link
Member Author

/AzurePipelines run

@azure-pipelines
Copy link

Azure Pipelines will not run the associated pipelines, because the pull request was updated after the run command was issued. Review the pull request again and issue a new run command.

@parth21999
Copy link
Member Author

/AzurePipelines run

@azure-pipelines
Copy link

Azure Pipelines will not run the associated pipelines, because the pull request was updated after the run command was issued. Review the pull request again and issue a new run command.

@parth21999
Copy link
Member Author

/AzurePipelines run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

/*Codes_SRS_TIMED_TEST_SUITE_43_003: [ TIMED_TEST_SUITE_INITIALIZE shall call TEST_SUITE_INITIALIZE with the watchdog init fixture as the first fixture, followed by any additional fixtures passed via variadic arguments. ]*/
/*Codes_SRS_TIMED_TEST_SUITE_43_004: [ The watchdog init fixture shall execute before the user's initialization code. ]*/
#define TIMED_TEST_SUITE_INITIALIZE(name, timeout_ms, ...) \
static void MU_C2(timed_test_watchdog_init_, name)(void) { (void)process_watchdog_init(timeout_ms); } \
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(void)

Should we not assert this?

{
int result;

LogInfo("Launching child process: %s %" PRIu32, exe_path, timeout_ms);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2, e

Should have an "" at the end after PRIu32

pid_t pid = fork();
if (pid < 0)
{
LogError("fork failed with error: %s", strerror(errno));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LogError

Don't we have a special LogErrorNo?

if (snprintf_result < 0 || (size_t)snprintf_result >= sizeof(timeout_str))
{
(void)fprintf(stderr, "snprintf failed for timeout_ms\n");
_exit(126);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

126

Why 126? Maybe use MU_FAILURE?

}
else
{
execl(exe_path, exe_path, timeout_str, NULL);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

execl

Doesn't this return something?

execl(exe_path, exe_path, timeout_str, NULL);

// If execl returns, it failed. fprintf return value is ignored since we're exiting immediately
(void)fprintf(stderr, "execl failed: %s\n", strerror(errno));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fprintf

Why not LogError?


// Sleep longer than the timeout - the watchdog should terminate us
uint32_t sleep_time_ms = timeout_ms * 2;
usleep(sleep_time_ms * 1000);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

usleep

Why not use the ThareadAPI_SLeep?

}
else
{
uint32_t timeout_ms = (uint32_t)atoi(argv[1]);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

argv[1]

Is this guaranteed to be non NULL?


// Sleep longer than the timeout - the watchdog should terminate us
uint32_t sleep_time_ms = timeout_ms * 2;
Sleep(sleep_time_ms);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sleep

ThreadAPI_SLeep

Copy link
Member

@dcristoloveanu dcristoloveanu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

// Navigate up one level (..) then into the child test directory
int snprintf_result = snprintf(buffer, buffer_size, "%s\\..\\process_watchdog_int_child\\process_watchdog_int_child.exe", module_path);
ASSERT_IS_TRUE(snprintf_result > 0 && (size_t)snprintf_result < buffer_size);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

consider just setting the exe as a dependency in cmake (which is better anyway so you can't build this test without the dependent exe) and then the exe ends up in the same directory as the test (so no directory navigation needed here)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems the dependency is added, so the exe should be present in the current directory

// Navigate up one level (..) then into the child test directory
int snprintf_result = snprintf(buffer, buffer_size, "%s/../process_watchdog_int_child/process_watchdog_int_child", dir);
ASSERT_IS_TRUE(snprintf_result > 0 && (size_t)snprintf_result < buffer_size);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same comment as in windows that this should just be in the current directory already

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants