diff --git a/include/highlevel/bidib_highlevel_util.h b/include/highlevel/bidib_highlevel_util.h index 1214b6e..ddccb7e 100644 --- a/include/highlevel/bidib_highlevel_util.h +++ b/include/highlevel/bidib_highlevel_util.h @@ -97,12 +97,20 @@ uint8_t *bidib_read_error_message(void); */ void bidib_flush(void); +/** + * Check if bidib is currently running. + * + * @return true if bidib is running + * @return false otherwise + */ +bool bidib_is_running(); + /** * Customised syslog function to prepend libbidib to log messages. * This is needed to differentiate log messages that originate from this bidib * library or from a user application. */ - void syslog_libbidib(int priority, const char *format, ...); +void syslog_libbidib(int priority, const char *format, ...); #endif diff --git a/src/highlevel/bidib_highlevel_util.c b/src/highlevel/bidib_highlevel_util.c index 71430ab..de7fb33 100644 --- a/src/highlevel/bidib_highlevel_util.c +++ b/src/highlevel/bidib_highlevel_util.c @@ -257,6 +257,10 @@ void bidib_stop(void) { } } +bool bidib_is_running() { + return bidib_running; +} + void syslog_libbidib(int priority, const char *format, ...) { char string[1024]; va_list arg; diff --git a/src/state/bidib_state_setter.c b/src/state/bidib_state_setter.c index 774ae2f..fac8f0d 100644 --- a/src/state/bidib_state_setter.c +++ b/src/state/bidib_state_setter.c @@ -138,7 +138,7 @@ void bidib_state_accessory_state(t_bidib_node_address node_address, uint8_t numb free(accessory_state->data.state_id); } accessory_state->data.state_id = NULL; - t_bidib_aspect *aspect_mapping; + t_bidib_aspect *aspect_mapping = NULL; for (size_t i = 0; i < accessory_mapping->aspects->len; i++) { aspect_mapping = &g_array_index(accessory_mapping->aspects, t_bidib_aspect, i); if (aspect_mapping->value == aspect) { @@ -146,6 +146,14 @@ void bidib_state_accessory_state(t_bidib_node_address node_address, uint8_t numb break; } } + if (aspect_mapping == NULL) { + syslog_libbidib(LOG_ERR, + "bidib_state_accessory_state: aspect mapping for accessory %s is NULL", + accessory_mapping->id->str); + pthread_rwlock_unlock(&bidib_boards_rwlock); + pthread_mutex_unlock(&trackstate_accessories_mutex); + return; + } if (accessory_state->data.state_id == NULL) { syslog_libbidib(LOG_WARNING, "Aspect 0x%02x of accessory %s is not mapped in config files", diff --git a/src/transmission/bidib_transmission_node_states.c b/src/transmission/bidib_transmission_node_states.c index 8f375eb..9d35418 100644 --- a/src/transmission/bidib_transmission_node_states.c +++ b/src/transmission/bidib_transmission_node_states.c @@ -123,8 +123,8 @@ static bool bidib_node_stall_ready(const uint8_t *const addr_stack) { if (state != NULL && state->stall) { // Node at addr_cpy is stalled -> search its stall_affected_nodes_queue to see // if the queue contains the (node at) addr_stack. - if (!g_queue_find_custom(state->stall_affected_nodes_queue, addr_stack, - (GCompareFunc)bidib_node_stall_queue_entry_equals)) + if (g_queue_find_custom(state->stall_affected_nodes_queue, addr_stack, + (GCompareFunc)bidib_node_stall_queue_entry_equals) == NULL) { // stalled subnode (addr_stack) is not yet in stall_affected_nodes_queue, // so add it @@ -192,7 +192,7 @@ static void bidib_node_try_queued_messages(t_bidib_node_state *state) { free(queued_msg); sent_count++; } else { - syslog_libbidib(LOG_WARNING, + syslog_libbidib(LOG_INFO, "bidib_node_try_queued_messages - Unable to send queued msg, " "not enough space in response queue. Message info: " "type: 0x%02x addressed to: 0x%02x 0x%02x 0x%02x 0x%02x" diff --git a/test/physical/Readme.md b/test/physical/Readme.md index 9797e06..b8cdcf8 100644 --- a/test/physical/Readme.md +++ b/test/physical/Readme.md @@ -67,7 +67,7 @@ For each point and signal, the `bidib_switch_point()` and `bidib_set_signal()` functions are used to set their aspect. The `t_testsuite_point_result` struct records the feedback state returned by a point, and is -updated by the `testsuite_logTestResult()` function. The following is an example of the +updated by the `testsuite_recordTestResult()` function. The following is an example of the feedback that is logged for a point: ``` @@ -80,7 +80,6 @@ point10 -> unknownState: 0 ``` - ## Dependencies **SWTbahn Platform** diff --git a/test/physical/swtbahn-full/main.c b/test/physical/swtbahn-full/main.c index 98efab6..6ded28e 100755 --- a/test/physical/swtbahn-full/main.c +++ b/test/physical/swtbahn-full/main.c @@ -70,8 +70,16 @@ int main(int argc, char **argv) { printf("testsuite: Test case %d\n", atoi(argv[1])); t_testsuite_test_result *result = testsuite_initTestSuite(); - const int repetitions = atoi(argv[2]); + + // For cases 1-3, the track output state is set to OFF, + // as they do not involve any trains that should be driving around. + // If a previous test was aborted/failed/crashed, the system might not have + // been shut down properly, so trains might still have a set speed. + // In a new test execution, trains would then start to drive around. + // With the track output set to off, that's not a problem, and thus + // test cases 1-3 can be run even when a prior execution did not shut + // down properly. switch (atoi(argv[1])) { case 1: bidib_set_track_output_state_all(BIDIB_CS_OFF); @@ -168,7 +176,7 @@ void printWelcome() { "* SWTbahn-testsuite *", "* *", "************************", - "* UniBa-SWT-2023 *", + "* UniBa-SWT-2024 *", "************************", "" }; diff --git a/test/physical/swtbahn-full/testsuite.c b/test/physical/swtbahn-full/testsuite.c index 4a7c0b5..40ff8ed 100644 --- a/test/physical/swtbahn-full/testsuite.c +++ b/test/physical/swtbahn-full/testsuite.c @@ -42,8 +42,7 @@ t_testsuite_test_result *testsuite_initTestSuite() { char *excludedSignalAccessories[4] = {"platformlight1", "platformlight2", "platformlight4a", "platformlight4b"}; - t_testsuite_test_result *result = testsuite_initTestSuite_common(excludedSignalAccessories, 4); - return result; + return testsuite_initTestSuite_common(excludedSignalAccessories, 4); } void testsuite_case_signal() { @@ -59,37 +58,118 @@ void testsuite_case_pointSerial(t_testsuite_test_result *result) { testsuite_case_pointSerial_common(result); } -bool route1(const char *train) { - if (!testsuite_trainReady(train, "seg58")) { +typedef struct { + char *train; + char *forbidden_segment; + // if true, only checks whether any of the forbidden segments gets occupied, + // not if it gets occupied *by the train*. + bool check_occ_only; + // To be set by the caller to request the observer to terminate. + volatile bool stop_requested; +} t_bidib_occ_observer_info; + +static void free_obs_info_util(t_bidib_occ_observer_info *obs_info_ptr) { + if (obs_info_ptr != NULL) { + if (obs_info_ptr->forbidden_segment != NULL) { + free(obs_info_ptr->forbidden_segment); + } + if (obs_info_ptr->train != NULL) { + free(obs_info_ptr->train); + } + free(obs_info_ptr); + obs_info_ptr = NULL; + } +} + +// Expects the arg pointer to be a pointer to a t_bidib_occ_observer_info struct. +static void *occupancy_observer(void *arg) { + if (arg == NULL) { + printf("testsuite: Occ-Observer exit: arg is NULL\n"); + pthread_exit(NULL); + } + t_bidib_occ_observer_info *obs_info = arg; + t_bidib_dcc_address_query tr_dcc_addr = bidib_get_train_dcc_addr(obs_info->train); + if (!tr_dcc_addr.known) { + printf("testsuite: Occ-Observer exit: tr_dcc_addr unknown\n"); + pthread_exit(NULL); + } + long counter = 0; + while (!obs_info->stop_requested && bidib_is_running()) { + const char *i_segmt = obs_info->forbidden_segment; + bool violation = false; + if (i_segmt == NULL) { + pthread_exit(NULL); + } else if (obs_info->check_occ_only) { + violation = testsuite_is_segment_occupied(i_segmt); + } else { + violation = testsuite_is_segment_occupied_by_dcc_addr(i_segmt, tr_dcc_addr.dcc_address); + } + if (violation) { + printf("!!!\nOccupancy Observer: Train %s (or something else) occupies forbidden " + "segment %s! Stopping train, then stopping bidib.\n!!!\n", + obs_info->train, i_segmt); + bidib_set_train_speed(obs_info->train, 0, "master"); + bidib_flush(); + // Stop via the signal handler for consistent debugging + testsuite_signal_callback_handler(-1); + pthread_exit(NULL); + } + usleep(100000); // 0.1s + if (counter++ % 10 == 0) { + printf("testsuite: Observer heartbeat - observing %s\n", i_segmt); + } + } + pthread_exit(NULL); +} + +static void prep_observer_segment_info(t_bidib_occ_observer_info *obs_info, const char *segment) { + obs_info->stop_requested = false; + if (obs_info->forbidden_segment != NULL) { + free(obs_info->forbidden_segment); + } + obs_info->forbidden_segment = strdup(segment); +} + +static bool stop_observer_and_check_still_running(t_bidib_occ_observer_info *obs_info, + pthread_t observer_thread, + const char *logname) { + obs_info->stop_requested = true; + pthread_join(observer_thread, NULL); + if (!bidib_is_running()) { + printf("testsuite: %s - stop, bidib is not running anymore.\n", logname); + free_obs_info_util(obs_info); + return false; + } + return true; +} + +static bool route1(const char *train) { + if (train == NULL || !testsuite_trainReady(train, "seg58")) { + return false; + } + + const char *points_normal[4] = { + "point15", "point23", "point20", "point19" + }; + const char *points_reverse[8] = { + "point22", "point24", "point12", "point13", "point14", "point16", "point21", "point18b" + }; + + if (!testsuite_set_and_check_points(points_normal, 4, points_reverse, 8)) { + printf("testsuite: route1 - one or more points are not in expected aspect.\n"); return false; } - testsuite_switch_point("point22", "reverse"); - testsuite_switch_point("point23", "normal"); - testsuite_switch_point("point24", "reverse"); - testsuite_switch_point("point12", "reverse"); - testsuite_switch_point("point13", "reverse"); - testsuite_switch_point("point14", "reverse"); - testsuite_switch_point("point15", "normal"); - testsuite_switch_point("point16", "reverse"); - testsuite_switch_point("point21", "reverse"); - testsuite_switch_point("point20", "normal"); - testsuite_switch_point("point19", "normal"); - testsuite_switch_point("point18b", "reverse"); - - testsuite_set_signal("signal30", "aspect_go"); - testsuite_set_signal("signal33", "aspect_go"); - testsuite_set_signal("signal35a", "aspect_go"); - testsuite_set_signal("signal35b", "aspect_go"); - testsuite_set_signal("signal37", "aspect_go"); + const char* signals_go_1[5] = {"signal30", "signal33", "signal35a", "signal35b", "signal37"}; + testsuite_set_signals_to(signals_go_1, 5, "aspect_go"); testsuite_driveTo("seg57", 50, train); testsuite_set_signal("signal30", "aspect_stop"); testsuite_driveTo("seg64", 50, train); - testsuite_set_signal("signal33", "aspect_stop"); - testsuite_set_signal("signal35a", "aspect_stop"); - testsuite_set_signal("signal35b", "aspect_stop"); + + const char* signals_stop_1[3] = {"signal33", "signal35a", "signal35b"}; + testsuite_set_signals_to(signals_stop_1, 3, "aspect_stop"); testsuite_driveTo("seg69", 50, train); testsuite_set_signal("signal37", "aspect_stop"); @@ -98,11 +178,8 @@ bool route1(const char *train) { testsuite_driveToStop("seg47", 20, train); // Drive backwards through the route - testsuite_set_signal("signal26", "aspect_go"); - testsuite_set_signal("signal38", "aspect_go"); - testsuite_set_signal("signal36", "aspect_go"); - testsuite_set_signal("signal34", "aspect_go"); - testsuite_set_signal("signal32", "aspect_go"); + const char* signals_go_2[5] = {"signal26", "signal38", "signal36", "signal34", "signal32"}; + testsuite_set_signals_to(signals_go_2, 5, "aspect_go"); testsuite_driveTo("seg45", -50, train); testsuite_set_signal("signal26", "aspect_stop"); @@ -120,83 +197,71 @@ bool route1(const char *train) { return true; } -bool route2(const char *train) { - if (!testsuite_trainReady(train, "seg58")) { +static bool route2(const char *train) { + if (train == NULL || !testsuite_trainReady(train, "seg58")) { return false; } - testsuite_switch_point("point22", "normal"); - testsuite_switch_point("point20", "reverse"); - testsuite_switch_point("point21", "reverse"); - testsuite_switch_point("point16", "reverse"); - testsuite_switch_point("point15", "reverse"); - testsuite_switch_point("point5", "reverse"); - testsuite_switch_point("point4", "reverse"); - testsuite_switch_point("point12", "reverse"); - testsuite_switch_point("point11", "reverse"); - testsuite_switch_point("point27", "reverse"); - testsuite_switch_point("point29", "reverse"); - testsuite_switch_point("point28", "reverse"); - testsuite_switch_point("point26", "normal"); - testsuite_switch_point("point9", "reverse"); - testsuite_switch_point("point8", "reverse"); - testsuite_switch_point("point1", "reverse"); - testsuite_switch_point("point7", "normal"); - testsuite_switch_point("point6", "reverse"); - testsuite_switch_point("point17", "reverse"); - - testsuite_driveTo("seg42b", 50, train); - - testsuite_switch_point("point16", "normal"); - testsuite_switch_point("point15", "normal"); - testsuite_switch_point("point14", "reverse"); - testsuite_switch_point("point13", "reverse"); - testsuite_switch_point("point12", "reverse"); - testsuite_switch_point("point24", "reverse"); - testsuite_switch_point("point23", "reverse"); - testsuite_switch_point("point19", "reverse"); - testsuite_switch_point("point18b", "normal"); - testsuite_switch_point("point18a", "reverse"); - testsuite_switch_point("point8", "normal"); - testsuite_switch_point("point9", "reverse"); - testsuite_switch_point("point26", "reverse"); - testsuite_switch_point("point27", "normal"); - testsuite_switch_point("point11", "normal"); - testsuite_switch_point("point3", "reverse"); - testsuite_switch_point("point4", "normal"); - testsuite_switch_point("point5", "normal"); + const char *points_normal_c1[3] = { + "point22", "point26", "point7" + }; + const char *points_reverse_c1[16] = { + "point20", "point21", "point16", "point15", "point5", "point4", "point12", "point11", + "point27", "point29", "point28", "point9", "point8", "point1", "point6", "point17", + }; - testsuite_driveTo("seg69", 50, train); - testsuite_switch_point("point6", "normal"); - testsuite_switch_point("point7", "reverse"); + if (!testsuite_set_and_check_points(points_normal_c1, 3, points_reverse_c1, 16)) { + printf("testsuite: route2 - one or more points are not in expected aspect (1st check).\n"); + return false; + } + + testsuite_driveToStop("seg42b", 50, train); + + const char *points_normal_c2[8] = { + "point16", "point15", "point8", "point18b", "point27", "point11", "point4", "point5" + }; + const char *points_reverse_c2[10] = { + "point14", "point13", "point12", "point24", "point23", + "point19", "point18a", "point9", "point26", "point3" + }; + + if (!testsuite_set_and_check_points(points_normal_c2, 8, points_reverse_c2, 10)) { + printf("testsuite: route2 - one or more points are not in expected aspect (2nd check).\n"); + return false; + } + + testsuite_driveToStop("seg69", 50, train); + + const char *points_normal_c3[1] = { "point6" }; + const char *points_reverse_c3[1] = { "point7" }; + + if (!testsuite_set_and_check_points(points_normal_c3, 1, points_reverse_c3, 1)) { + printf("testsuite: route2 - one or more points are not in expected aspect (3rd check).\n"); + return false; + } testsuite_driveTo("seg46", 50, train); testsuite_driveToStop("seg47", 20, train); - return true; } -bool route3(const char *train) { - if (!testsuite_trainReady(train, "seg46")) { +static bool route3(const char *train) { + if (train == NULL || !testsuite_trainReady(train, "seg46")) { return false; } - testsuite_switch_point("point18b", "reverse"); - testsuite_switch_point("point19", "reverse"); - testsuite_switch_point("point23", "reverse"); - testsuite_switch_point("point24", "reverse"); - testsuite_switch_point("point12", "normal"); - testsuite_switch_point("point4", "reverse"); - testsuite_switch_point("point5", "reverse"); - testsuite_switch_point("point15", "reverse"); - testsuite_switch_point("point16", "normal"); - testsuite_switch_point("point17", "reverse"); - testsuite_switch_point("point6", "reverse"); - testsuite_switch_point("point7", "normal"); - testsuite_switch_point("point1", "reverse"); - testsuite_switch_point("point8", "reverse"); - testsuite_switch_point("point9", "normal"); - testsuite_switch_point("point10", "reverse"); + const char *points_normal[4] = { + "point7", "point12", "point16", "point9" + }; + const char *points_reverse[12] = { + "point18b", "point19", "point23", "point24", "point4", "point5", + "point15", "point17", "point6", "point1", "point8", "point10", + }; + + if (!testsuite_set_and_check_points(points_normal, 4, points_reverse, 12)) { + printf("testsuite: route3 - one or more points are not in expected aspect.\n"); + return false; + } testsuite_driveTo("seg29", -50, train); testsuite_driveToStop("seg78b", -20, train); @@ -204,26 +269,23 @@ bool route3(const char *train) { return true; } -bool route4(const char *train) { - if (!testsuite_trainReady(train, "seg78a")) { +static bool route4(const char *train) { + if (train == NULL || !testsuite_trainReady(train, "seg78a")) { return false; } - testsuite_switch_point("point10", "reverse"); - testsuite_switch_point("point9", "normal"); - testsuite_switch_point("point8", "reverse"); - testsuite_switch_point("point1", "reverse"); - testsuite_switch_point("point7", "normal"); - testsuite_switch_point("point6", "reverse"); - testsuite_switch_point("point17", "reverse"); - testsuite_switch_point("point16", "normal"); - testsuite_switch_point("point15", "normal"); - testsuite_switch_point("point14", "reverse"); - testsuite_switch_point("point13", "reverse"); - testsuite_switch_point("point12", "reverse"); - testsuite_switch_point("point24", "reverse"); - testsuite_switch_point("point23", "normal"); - testsuite_switch_point("point22", "reverse"); + const char *points_normal[5] = { + "point9", "point7", "point16", "point15", "point23" + }; + const char *points_reverse[10] = { + "point10", "point8", "point1", "point6", "point17", + "point14", "point13", "point12", "point24", "point22" + }; + + if (!testsuite_set_and_check_points(points_normal, 5, points_reverse, 10)) { + printf("testsuite: route4 - one or more points are not in expected aspect.\n"); + return false; + } testsuite_driveTo("seg58", 50, train); testsuite_driveToStop("seg59", 20, train); @@ -231,24 +293,31 @@ bool route4(const char *train) { return true; } -bool route5(const char *train) { - if (!testsuite_trainReady(train, "seg58")) { +static bool route5(const char *train) { + if (train == NULL || !testsuite_trainReady(train, "seg58")) { + return false; + } + + const char *points_normal_c1[2] = { + "point23", "point15" + }; + const char *points_reverse_c1[8] = { + "point22", "point24", "point12", "point13", "point14", "point16", "point21", "point20" + }; + + if (!testsuite_set_and_check_points(points_normal_c1, 2, points_reverse_c1, 8)) { + printf("testsuite: route5 - one or more points are not in expected aspect (1st check).\n"); return false; } - testsuite_switch_point("point22", "reverse"); - testsuite_switch_point("point23", "normal"); - testsuite_switch_point("point24", "reverse"); - testsuite_switch_point("point12", "reverse"); - testsuite_switch_point("point13", "reverse"); - testsuite_switch_point("point14", "reverse"); - testsuite_switch_point("point15", "normal"); - testsuite_switch_point("point16", "reverse"); - testsuite_switch_point("point21", "reverse"); - testsuite_switch_point("point20", "reverse"); - - testsuite_driveTo("seg64", -50, train); + testsuite_driveToStop("seg64", -50, train); testsuite_switch_point("point22", "normal"); + sleep(3); + // Check that point is in desired position + if (!testsuite_check_point_aspect("point21", "reverse")) { + printf("testsuite: route5 - point 21 is not in expected aspect (2nd check).\n"); + return false; + } testsuite_driveTo("seg58", -50, train); testsuite_driveToStop("seg59", -20, train); @@ -257,197 +326,348 @@ bool route5(const char *train) { } void testsuite_case_swtbahnFullTrackCoverage(const char *train) { + if (train == NULL) { + printf("testsuite: swtbahn-full track coverage single train - train is NULL\n"); + return; + } + if (!route1(train)) { + printf("testsuite: swtbahn-full track coverage single train - route1 failed.\n"); return; } if (!route2(train)) { + printf("testsuite: swtbahn-full track coverage single train - route2 failed.\n"); return; } if (!route3(train)) { + printf("testsuite: swtbahn-full track coverage single train - route3 failed.\n"); return; } if (!route4(train)) { + printf("testsuite: swtbahn-full track coverage single train - route4 failed.\n"); return; } if (!route5(train)) { + printf("testsuite: swtbahn-full track coverage single train - route5 failed.\n"); return; } } +static bool wrap_drive_and_observe(t_bidib_occ_observer_info *obs_info, + int speed, const char *train, bool driveToStop, + const char *target_segment, const char *observe_segment, + const char *logname) +{ + pthread_t r_obs_thr; + prep_observer_segment_info(obs_info, observe_segment); + pthread_create(&r_obs_thr, NULL, occupancy_observer, (void*) obs_info); + + if (driveToStop) { + testsuite_driveToStop(target_segment, speed, train); + } else { + testsuite_driveTo(target_segment, speed, train); + } + + return stop_observer_and_check_still_running(obs_info, r_obs_thr, logname); +} + static void *route99(void *arg) { const char *train1 = arg; - if (!testsuite_trainReady(train1, "seg58")) { + if (train1 == NULL || !testsuite_trainReady(train1, "seg58")) { pthread_exit(NULL); } - + // train1: forwards - testsuite_switch_point("point22", "reverse"); - testsuite_switch_point("point23", "normal"); - testsuite_switch_point("point24", "reverse"); - testsuite_switch_point("point12", "reverse"); - testsuite_switch_point("point13", "reverse"); - testsuite_switch_point("point14", "reverse"); - testsuite_switch_point("point15", "normal"); - testsuite_switch_point("point16", "reverse"); - testsuite_switch_point("point21", "reverse"); - testsuite_switch_point("point20", "normal"); - testsuite_switch_point("point19", "normal"); - testsuite_switch_point("point18b", "reverse"); - sleep(1); - - testsuite_set_signal("signal30", "aspect_go"); - testsuite_set_signal("signal33", "aspect_go"); - testsuite_set_signal("signal35a", "aspect_go"); - testsuite_set_signal("signal35b", "aspect_go"); - testsuite_set_signal("signal37", "aspect_go"); + const char *points_normal[4] = { + "point23", "point15", "point20", "point19" + }; + const char *points_reverse[8] = { + "point22", "point24", "point12", "point13", "point14", "point16", "point21", "point18b" + }; - sleep(1); + if (!testsuite_set_and_check_points(points_normal, 4, points_reverse, 8)) { + printf("testsuite: route99 - one or more points are not in expected aspect.\n"); + pthread_exit(NULL); + } + + const char* signals_go_1[5] = {"signal30", "signal33", "signal35a", "signal35b", "signal37"}; + testsuite_set_signals_to(signals_go_1, 5, "aspect_go"); - testsuite_driveTo("seg57", 50, train1); + sleep(1); + + + // Note: Most but not all segments the observer is configured to observe + // for this route are not the immediate successors of the segment in driveTo, + // but rather the successor of the successor -> to prevent the observer + // from falsely recognizing a violation due to the combination of sleep + // statements (observer sleeps 0.1s, driveTo 0.25s -> fast train could cause issues). + t_bidib_occ_observer_info *obs_i = malloc(sizeof(t_bidib_occ_observer_info)); + obs_i->check_occ_only = false; + obs_i->train = strdup(train1); + obs_i->stop_requested = false; + obs_i->forbidden_segment = NULL; + const char *l_name = "route99"; + + if (!wrap_drive_and_observe(obs_i, 50, train1, false, "seg57", "seg60", l_name)) { + pthread_exit(NULL); + } testsuite_set_signal("signal30", "aspect_stop"); - testsuite_driveTo("seg64", 50, train1); - testsuite_set_signal("signal33", "aspect_stop"); - testsuite_set_signal("signal35a", "aspect_stop"); - testsuite_set_signal("signal35b", "aspect_stop"); + if (!wrap_drive_and_observe(obs_i, 50, train1, false, "seg64", "seg66", l_name)) { + pthread_exit(NULL); + } + const char* signals_stop_1[3] = {"signal33", "signal35a", "signal35b"}; + testsuite_set_signals_to(signals_stop_1, 3, "aspect_stop"); + - testsuite_driveTo("seg69", 50, train1); + if (!wrap_drive_and_observe(obs_i, 50, train1, false, "seg69", "seg40", l_name)) { + pthread_exit(NULL); + } testsuite_set_signal("signal37", "aspect_stop"); - testsuite_driveTo("seg46", 50, train1); + + if (!wrap_drive_and_observe(obs_i, 40, train1, false, "seg46", "seg47", l_name)) { + pthread_exit(NULL); + } + sleep(1); - testsuite_driveTo("seg46", 40, train1); + // -> last segment before end of that platform - can't observe any overrun. testsuite_driveToStop("seg47", 20, train1); - sleep(5); + sleep(4); + if (!bidib_is_running()) { + printf("testsuite: route99 - stop, bidib is not running anymore\n"); + free_obs_info_util(obs_i); + pthread_exit(NULL); + } // train1: backwards - testsuite_set_signal("signal26", "aspect_go"); - testsuite_set_signal("signal38", "aspect_go"); - testsuite_set_signal("signal36", "aspect_go"); - testsuite_set_signal("signal34", "aspect_go"); - testsuite_set_signal("signal32", "aspect_go"); + const char* signals_go_2[5] = {"signal26", "signal38", "signal36", "signal34", "signal32"}; + testsuite_set_signals_to(signals_go_2, 5, "aspect_go"); sleep(1); - testsuite_driveTo("seg45", -50, train1); + if (!wrap_drive_and_observe(obs_i, -50, train1, false, "seg45", "seg48", l_name)) { + pthread_exit(NULL); + } testsuite_set_signal("signal26", "aspect_stop"); - testsuite_driveTo("seg67", -50, train1); + if (!wrap_drive_and_observe(obs_i, -50, train1, false, "seg67", "seg34", l_name)) { + pthread_exit(NULL); + } testsuite_set_signal("signal38", "aspect_stop"); testsuite_set_signal("signal36", "aspect_stop"); - testsuite_driveTo("seg62", -50, train1); + + if (!wrap_drive_and_observe(obs_i, -50, train1, false, "seg62", "seg60", l_name)) { + pthread_exit(NULL); + } testsuite_set_signal("signal34", "aspect_stop"); testsuite_set_signal("signal32", "aspect_stop"); - testsuite_driveTo("seg60", -50, train1); - testsuite_driveTo("seg53", -40, train1); - testsuite_driveTo("seg57", -30, train1); - testsuite_driveTo("seg58", -20, train1); + + if (!wrap_drive_and_observe(obs_i, -30, train1, false, "seg58", "seg59", l_name)) { + pthread_exit(NULL); + } + sleep(2); testsuite_driveToStop("seg58", -20, train1); + sleep(1); - sleep(5); + free_obs_info_util(obs_i); pthread_exit(NULL); } static void *route100(void *arg) { const char *train2 = arg; - if (!testsuite_trainReady(train2, "seg78a")) { + if (train2 == NULL || !testsuite_trainReady(train2, "seg78a")) { pthread_exit(NULL); } // train2: forwards - testsuite_switch_point("point10", "reverse"); - testsuite_switch_point("point9", "normal"); - testsuite_switch_point("point8", "reverse"); - testsuite_switch_point("point1", "reverse"); - testsuite_switch_point("point7", "normal"); - testsuite_switch_point("point6", "normal"); - testsuite_switch_point("point5", "normal"); - testsuite_switch_point("point4", "normal"); - testsuite_switch_point("point3", "reverse"); - testsuite_switch_point("point11", "reverse"); - - sleep(1); + const char *points_normal[5] = { + "point9", "point7", "point6", "point5", "point4" + }; + const char *points_reverse[5] = { + "point10", "point8", "point1", "point3", "point11" + }; + + if (!testsuite_set_and_check_points(points_normal, 5, points_reverse, 5)) { + printf("testsuite: route100 - one or more points are not in expected aspect.\n"); + pthread_exit(NULL); + } + + const char* signals_go_1[7] = { + "signal19", "signal3", "signal1", "signal13", "signal11", "signal10", "signal8" + }; + testsuite_set_signals_to(signals_go_1, 7, "aspect_go"); testsuite_set_signal("signal43", "aspect_shunt"); - testsuite_set_signal("signal19", "aspect_go"); - testsuite_set_signal("signal3", "aspect_go"); - testsuite_set_signal("signal1", "aspect_go"); - testsuite_set_signal("signal13", "aspect_go"); - testsuite_set_signal("signal11", "aspect_go"); - testsuite_set_signal("signal10", "aspect_go"); - testsuite_set_signal("signal8", "aspect_go"); sleep(1); + + static pthread_t route_observer_thread; + // Note: Most but not all segments the observer is configured to observe + // for this route are not the immediate successors of the segment in driveTo, + // but rather the successor of the successor -> to prevent the observer + // from falsely recognizing a violation due to the combination of sleep + // statements (observer sleeps 0.1s, driveTo 0.25s -> fast train could cause issues). + t_bidib_occ_observer_info *obs1_info = malloc(sizeof(t_bidib_occ_observer_info)); + obs1_info->check_occ_only = false; + obs1_info->train = strdup(train2); + obs1_info->stop_requested = false; + obs1_info->forbidden_segment = strdup("seg28"); + + pthread_create(&route_observer_thread, NULL, occupancy_observer, (void*) obs1_info); + testsuite_driveTo("seg77", 60, train2); + if (!stop_observer_and_check_still_running(obs1_info, route_observer_thread, "route100")) { + pthread_exit(NULL); + } testsuite_set_signal("signal43", "aspect_stop"); + + + prep_observer_segment_info(obs1_info, "seg4"); + pthread_create(&route_observer_thread, NULL, occupancy_observer, (void*) obs1_info); testsuite_driveTo("seg26", 60, train2); + if (!stop_observer_and_check_still_running(obs1_info, route_observer_thread, "route100")) { + pthread_exit(NULL); + } testsuite_set_signal("signal19", "aspect_stop"); + + + prep_observer_segment_info(obs1_info, "seg18"); + pthread_create(&route_observer_thread, NULL, occupancy_observer, (void*) obs1_info); testsuite_driveTo("seg1", 60, train2); - testsuite_set_signal("signal3", "aspect_stop"); + if (!stop_observer_and_check_still_running(obs1_info, route_observer_thread, "route100")) { + pthread_exit(NULL); + } testsuite_set_signal("signal1", "aspect_stop"); - + testsuite_set_signal("signal3", "aspect_stop"); + + + prep_observer_segment_info(obs1_info, "seg13"); + pthread_create(&route_observer_thread, NULL, occupancy_observer, (void*) obs1_info); + testsuite_driveTo("seg15", 60, train2); + if (!stop_observer_and_check_still_running(obs1_info, route_observer_thread, "route100")) { + pthread_exit(NULL); + } testsuite_set_signal("signal13", "aspect_stop"); testsuite_set_signal("signal11", "aspect_stop"); - + + + prep_observer_segment_info(obs1_info, "seg9"); // seg11 -> seg10 -> *seg9* + pthread_create(&route_observer_thread, NULL, occupancy_observer, (void*) obs1_info); + testsuite_driveTo("seg11", 60, train2); + if (!stop_observer_and_check_still_running(obs1_info, route_observer_thread, "route100")) { + pthread_exit(NULL); + } testsuite_set_signal("signal10", "aspect_stop"); testsuite_set_signal("signal8", "aspect_stop"); - - testsuite_driveTo("seg31b", 50, train2); - testsuite_driveToStop("seg31a", 40, train2); - - sleep(5); + + + prep_observer_segment_info(obs1_info, "seg30"); // seg31a -> *seg30* + pthread_create(&route_observer_thread, NULL, occupancy_observer, (void*) obs1_info); + + testsuite_driveTo("seg31b", 60, train2); + testsuite_driveTo("seg31b", 40, train2); + sleep(1); + testsuite_driveTo("seg31a", 20, train2); + sleep(1); + testsuite_driveToStop("seg31a", 20, train2); + if (!stop_observer_and_check_still_running(obs1_info, route_observer_thread, "route100")) { + pthread_exit(NULL); + } + + sleep(1); // train2: backwards - testsuite_set_signal("signal22a", "aspect_go"); - testsuite_set_signal("signal22b", "aspect_go"); - testsuite_set_signal("signal9", "aspect_go"); - testsuite_set_signal("signal12", "aspect_go"); - testsuite_set_signal("signal14", "aspect_go"); - testsuite_set_signal("signal2", "aspect_go"); - testsuite_set_signal("signal4a", "aspect_go"); - testsuite_set_signal("signal4b", "aspect_go"); + + const char* signals_go_2[8] = { + "signal22a", "signal22b", "signal9", "signal12", + "signal14", "signal2", "signal4a", "signal4b" + }; + testsuite_set_signals_to(signals_go_2, 8, "aspect_go"); testsuite_set_signal("signal20", "aspect_shunt"); sleep(1); + prep_observer_segment_info(obs1_info, "seg9"); // seg32 -> seg33 -> *seg9* + pthread_create(&route_observer_thread, NULL, occupancy_observer, (void*) obs1_info); + testsuite_driveTo("seg32", -60, train2); + if (!stop_observer_and_check_still_running(obs1_info, route_observer_thread, "route100")) { + pthread_exit(NULL); + } testsuite_set_signal("signal22a", "aspect_stop"); testsuite_set_signal("signal22b", "aspect_stop"); - + + + prep_observer_segment_info(obs1_info, "seg15"); // seg13 -> seg14 -> *seg15* + pthread_create(&route_observer_thread, NULL, occupancy_observer, (void*) obs1_info); + testsuite_driveTo("seg13", -60, train2); + if (!stop_observer_and_check_still_running(obs1_info, route_observer_thread, "route100")) { + pthread_exit(NULL); + } testsuite_set_signal("signal9", "aspect_stop"); - + + + prep_observer_segment_info(obs1_info, "seg19"); // seg17 -> seg18 -> *seg19* + pthread_create(&route_observer_thread, NULL, occupancy_observer, (void*) obs1_info); + testsuite_driveTo("seg17", -60, train2); + if (!stop_observer_and_check_still_running(obs1_info, route_observer_thread, "route100")) { + pthread_exit(NULL); + } testsuite_set_signal("signal12", "aspect_stop"); testsuite_set_signal("signal14", "aspect_stop"); - + + + prep_observer_segment_info(obs1_info, "seg24"); // seg3 -> seg4 -> *seg24* + pthread_create(&route_observer_thread, NULL, occupancy_observer, (void*) obs1_info); + testsuite_driveTo("seg3", -60, train2); + if (!stop_observer_and_check_still_running(obs1_info, route_observer_thread, "route100")) { + pthread_exit(NULL); + } testsuite_set_signal("signal2", "aspect_stop"); testsuite_set_signal("signal4a", "aspect_stop"); testsuite_set_signal("signal4b", "aspect_stop"); - - testsuite_driveTo("seg28", -40, train2); + + + prep_observer_segment_info(obs1_info, "seg77"); // seg28 -> seg29 -> *seg77* + pthread_create(&route_observer_thread, NULL, occupancy_observer, (void*) obs1_info); + + testsuite_driveTo("seg28", -50, train2); + if (!stop_observer_and_check_still_running(obs1_info, route_observer_thread, "route100")) { + pthread_exit(NULL); + } testsuite_set_signal("signal20", "aspect_stop"); + + + prep_observer_segment_info(obs1_info, "seg78b"); // seg78a -> *seg78b* + pthread_create(&route_observer_thread, NULL, occupancy_observer, (void*) obs1_info); - testsuite_driveToStop("seg78a", -40, train2); + testsuite_driveToStop("seg78a", -50, train2); + if (!stop_observer_and_check_still_running(obs1_info, route_observer_thread, "route100")) { + pthread_exit(NULL); + } + sleep(1); - sleep(5); + free_obs_info_util(obs1_info); pthread_exit(NULL); } @@ -463,19 +683,20 @@ void testsuite_case_swtbahnFullMultipleTrains(const char *train1, const char *tr } bool route_custom_short(const char *train) { - if (!testsuite_trainReady(train, "seg7a")) { + if (!testsuite_trainReady(train, "seg7b")) { return false; } - testsuite_switch_point("point2", "normal"); - testsuite_switch_point("point1", "normal"); - testsuite_switch_point("point7", "normal"); - testsuite_switch_point("point6", "normal"); - testsuite_switch_point("point5", "normal"); - testsuite_switch_point("point4", "normal"); - testsuite_switch_point("point3", "normal"); + const char *points_normal[7] = { + "point2", "point1", "point7", "point6", "point5", "point4", "point3" + }; + if (!testsuite_set_and_check_points(points_normal, 7, NULL, 0)) { + printf("testsuite: route_custom_short - one or more points are not in expected aspect.\n"); + return false; + } - testsuite_driveTo("drive_forever", 50, train); + testsuite_driveTo("seg7a", 50, train); + testsuite_driveToStop("seg7b", 50, train); return true; } diff --git a/test/physical/swtbahn-standard/main.c b/test/physical/swtbahn-standard/main.c index f551f68..04d5856 100755 --- a/test/physical/swtbahn-standard/main.c +++ b/test/physical/swtbahn-standard/main.c @@ -23,6 +23,7 @@ * present libbidib (in alphabetic order by surname): * * - Christof Lehanka + * - Bernhard Luedtke * - Eugene Yip * */ @@ -57,16 +58,26 @@ int main(int argc, char **argv) { return 0; } - if (bidib_start_serial("/dev/ttyUSB0", "../../swtbahn-cli/configurations/swtbahn-standard", 200)) { + if (bidib_start_serial("/dev/ttyUSB0", "../../swtbahn-cli/configurations/swtbahn-standard", 0)) { printf("testsuite: libbidib failed to start\n"); return 0; + } else { + printf("testsuite: libbidib started\n"); } sleep(2); // Wait for the points to finish switching to their default positions. printf("testsuite: Test case %d\n", atoi(argv[1])); t_testsuite_test_result *result = testsuite_initTestSuite(); - const int repetitions = atoi(argv[2]); + + // For cases 1-3, the track output state is set to OFF, + // as they do not involve any trains that should be driving around. + // If a previous test was aborted/failed/crashed, the system might not have + // been shut down properly, so trains might still have a set speed. + // In a new test execution, trains would then start to drive around. + // With the track output set to off, that's not a problem, and thus + // test cases 1-3 can be run even when a prior execution did not shut + // down properly. switch (atoi(argv[1])) { case 1: bidib_set_track_output_state_all(BIDIB_CS_OFF); @@ -131,7 +142,7 @@ void printWelcome() { "* SWTbahn-testsuite *", "* *", "************************", - "* UniBa-SWT-2023 *", + "* UniBa-SWT-2024 *", "************************", "" }; diff --git a/test/physical/swtbahn-standard/testsuite.c b/test/physical/swtbahn-standard/testsuite.c index c644da2..3825fa9 100644 --- a/test/physical/swtbahn-standard/testsuite.c +++ b/test/physical/swtbahn-standard/testsuite.c @@ -39,8 +39,7 @@ // This initialisation function is specific to SWTbahn Standard. t_testsuite_test_result *testsuite_initTestSuite() { char *excludedSignalAccessories[1] = {"platformlights"}; - t_testsuite_test_result *result = testsuite_initTestSuite_common(excludedSignalAccessories, 1); - return result; + return testsuite_initTestSuite_common(excludedSignalAccessories, 1); } void testsuite_case_signal() { @@ -111,8 +110,19 @@ void testsuite_case_swtbahnStandardTrackCoverage(const char *train) { testsuite_switch_point("point1", "normal"); testsuite_switch_point("point2", "normal"); testsuite_switch_point("point3", "normal"); + + sleep(2); + // Check that points are in desired position + bool point_check = true; + point_check &= testsuite_check_point_aspect("point1", "normal"); + point_check &= testsuite_check_point_aspect("point2", "normal"); + point_check &= testsuite_check_point_aspect("point3", "normal"); + if (!point_check) { + printf("testsuite: standard track coverage - one or more points are not in expected aspect (1st check)."); + return; + } - testsuite_driveTo("seg12", 30, train); + testsuite_driveToStop("seg12", 30, train); testsuite_switch_point("point6", "reverse"); testsuite_switch_point("point8", "reverse"); @@ -125,25 +135,74 @@ void testsuite_case_swtbahnStandardTrackCoverage(const char *train) { testsuite_switch_point("point9", "reverse"); testsuite_switch_point("point11", "reverse"); + sleep(2); + // Check that points are in desired position + point_check &= testsuite_check_point_aspect("point6", "reverse"); + point_check &= testsuite_check_point_aspect("point8", "reverse"); + point_check &= testsuite_check_point_aspect("point2", "reverse"); + point_check &= testsuite_check_point_aspect("point3", "reverse"); + point_check &= testsuite_check_point_aspect("point4", "reverse"); + point_check &= testsuite_check_point_aspect("point5", "reverse"); + point_check &= testsuite_check_point_aspect("point12", "normal"); + point_check &= testsuite_check_point_aspect("point10", "reverse"); + point_check &= testsuite_check_point_aspect("point9", "reverse"); + point_check &= testsuite_check_point_aspect("point11", "reverse"); + if (!point_check) { + printf("testsuite: standard track coverage - one or more points are not in expected aspect (2nd check)."); + return; + } + testsuite_driveToStop("seg37", 30, train); testsuite_switch_point("point12", "reverse"); + sleep(2); + // Check that point is in desired position + if (!testsuite_check_point_aspect("point12", "reverse")) { + printf("testsuite: route5 - point 12 is not in expected aspect (3rd check)."); + return; + } testsuite_driveToStop("seg40", -30, train); testsuite_switch_point("point12", "normal"); testsuite_switch_point("point11", "normal"); testsuite_switch_point("point10", "normal"); + + sleep(2); + // Check that points are in desired position + point_check &= testsuite_check_point_aspect("point12", "normal"); + point_check &= testsuite_check_point_aspect("point11", "normal"); + point_check &= testsuite_check_point_aspect("point10", "normal"); + if (!point_check) { + printf("testsuite: standard track coverage - one or more points are not in expected aspect (4th check)."); + return; + } - testsuite_driveTo("seg28", 30, train); + testsuite_driveToStop("seg28", 30, train); testsuite_switch_point("point7", "normal"); testsuite_switch_point("point4", "normal"); testsuite_switch_point("point9", "normal"); + + sleep(2); + // Check that points are in desired position + point_check &= testsuite_check_point_aspect("point7", "normal"); + point_check &= testsuite_check_point_aspect("point4", "normal"); + point_check &= testsuite_check_point_aspect("point9", "normal"); + if (!point_check) { + printf("testsuite: standard track coverage - one or more points are not in expected aspect (5th check)."); + return; + } - testsuite_driveTo("seg21", 30, train); + testsuite_driveToStop("seg21", 30, train); testsuite_switch_point("point5", "normal"); + sleep(2); + // Check that point is in desired position + if (!testsuite_check_point_aspect("point5", "normal")) { + printf("testsuite: route5 - point 5 is not in expected aspect (6th check)."); + return; + } testsuite_driveTo("seg28", 30, train); @@ -153,10 +212,29 @@ void testsuite_case_swtbahnStandardTrackCoverage(const char *train) { testsuite_switch_point("point3", "normal"); testsuite_switch_point("point6", "normal"); testsuite_switch_point("point1", "reverse"); + + sleep(2); + // Check that points are in desired position + point_check &= testsuite_check_point_aspect("point7", "reverse"); + point_check &= testsuite_check_point_aspect("point8", "normal"); + point_check &= testsuite_check_point_aspect("point2", "reverse"); + point_check &= testsuite_check_point_aspect("point3", "normal"); + point_check &= testsuite_check_point_aspect("point6", "normal"); + point_check &= testsuite_check_point_aspect("point1", "reverse"); + if (!point_check) { + printf("testsuite: standard track coverage - one or more points are not in expected aspect (7th check)."); + return; + } testsuite_driveToStop("seg4", 30, train); testsuite_switch_point("point1", "normal"); + sleep(2); + // Check that point is in desired position + if (!testsuite_check_point_aspect("point1", "normal")) { + printf("testsuite: route5 - point 1 is not in expected aspect (8th check)."); + return; + } testsuite_driveTo("seg1", -20, train); sleep(1); diff --git a/test/physical/test_common.c b/test/physical/test_common.c index e1594c1..5da6564 100644 --- a/test/physical/test_common.c +++ b/test/physical/test_common.c @@ -72,22 +72,26 @@ t_testsuite_test_result *testsuite_initTestSuite_common(char **excludedSignalAcc } t_bidib_id_list_query testsuite_filterOutIds(t_bidib_id_list_query inputIdQuery, t_testsuite_ids filterOutIds) { - const size_t count = inputIdQuery.length - filterOutIds.length; + ssize_t count = inputIdQuery.length - filterOutIds.length; if (count <= 0) { - printf("testsuite: No IDs will be left after filtering\n"); + printf("testsuite: No IDs might be left after filtering\n"); + count = 0; } t_bidib_id_list_query outputIdQuery; outputIdQuery.length = 0; - outputIdQuery.ids = malloc(sizeof(char *) * count); + // the outputIdQuery will likely be smaller, but we don't know if the IDs + // in filterOutIds are actually contained in inputIdQuery, thus theoretically + // the length of outputIdQuery can be that of inputIdQuery at most. + outputIdQuery.ids = malloc(sizeof(char *) * inputIdQuery.length); bool isFilteredOut = false; for (size_t i = 0; i < inputIdQuery.length; i++) { isFilteredOut = false; for (size_t j = 0; j < filterOutIds.length; j++) { - if (!strcmp(inputIdQuery.ids[i], filterOutIds.ids[j])) { + if (strcmp(inputIdQuery.ids[i], filterOutIds.ids[j]) == 0) { isFilteredOut = true; break; } @@ -99,8 +103,9 @@ t_bidib_id_list_query testsuite_filterOutIds(t_bidib_id_list_query inputIdQuery, } } - if (outputIdQuery.length != count) { - printf("testsuite: Error: %zu IDs were to be filtered, but %d IDs filtered instead\n", + if (outputIdQuery.length != (size_t) count) { + // can occur if the IDs to be filtered are those of accessories currently not connected. + printf("testsuite: Notice: %zu IDs were to be filtered, %d IDs were actually filtered\n", filterOutIds.length, (int)inputIdQuery.length - (int)outputIdQuery.length); } @@ -114,14 +119,22 @@ void testsuite_stopBidib(void) { } void testsuite_signal_callback_handler(int signum) { + printf("testsuite: SIG %d - before stopping, debug logs:\n", signum); + printf(" Track output states:\n"); + testsuite_logAllTrackOutputStates(); + printf("\n"); + printf(" Booster power states:\n"); + testsuite_logAllBoosterPowerStates(); + printf("\n"); + printf("testsuite: SIG %d - now stopping libbidib.\n", signum); testsuite_stopBidib(); - printf("testsuite: SIGINT - stopping libbidib \n"); + printf("testsuite: SIG %d - libbidib stopped.\n", signum); exit(signum); } -void testsuite_logTestResult(t_testsuite_test_result *result, - t_bidib_unified_accessory_state_query state, - int accessory_index) { +void testsuite_recordTestResult(t_testsuite_test_result *result, + t_bidib_unified_accessory_state_query state, + int accessory_index) { if (state.known) { switch (state.board_accessory_state.execution_state) { case BIDIB_EXEC_STATE_ERROR: @@ -188,14 +201,14 @@ void testsuite_driveTo_legacy(const char *segment, int speed, const char *train) bidib_set_train_speed(train, speed, "master"); bidib_flush(); long counter = 0; - while (1) { + while (bidib_is_running()) { t_bidib_train_position_query trainPosition = bidib_get_train_position(train); for (size_t i = 0; i < trainPosition.length; i++) { - if (!strcmp(segment, trainPosition.segments[i])) { + if (strcmp(segment, trainPosition.segments[i]) == 0) { struct timespec tv; clock_gettime(CLOCK_MONOTONIC, &tv); bidib_free_train_position_query(trainPosition); - printf("testsuite: Drive %s to %s at speed %d - REACHED TARGET - detected at time %ld.%.ld", + printf("testsuite: Drive %s to %s at speed %d - REACHED TARGET - detected at time %ld.%.ld\n", train, segment, speed, tv.tv_sec, tv.tv_nsec); return; } @@ -205,7 +218,7 @@ void testsuite_driveTo_legacy(const char *segment, int speed, const char *train) if (counter++ % 8 == 0) { struct timespec tv; clock_gettime(CLOCK_MONOTONIC, &tv); - printf("testsuite: Drive %s to %s at speed %d - waiting for train to arrive, time %ld.%.ld", + printf("testsuite: Drive %s to %s at speed %d - waiting for train to arrive, time %ld.%.ld\n", train, segment, speed, tv.tv_sec, tv.tv_nsec); } @@ -223,7 +236,7 @@ void testsuite_driveTo(const char *segment, int speed, const char *train) { t_bidib_dcc_address_query tr_dcc_addr = bidib_get_train_dcc_addr(train); t_bidib_dcc_address dcc_address; long counter = 0; - while (1) { + while (bidib_is_running()) { t_bidib_segment_state_query seg_query = bidib_get_segment_state(segment); for (size_t j = 0; j < seg_query.data.dcc_address_cnt; j++) { dcc_address = seg_query.data.dcc_addresses[j]; @@ -256,6 +269,35 @@ void testsuite_driveToStop(const char *segment, int speed, const char *train) { bidib_flush(); } +bool testsuite_is_segment_occupied(const char *segment) { + t_bidib_segment_state_query seg_query = bidib_get_segment_state(segment); + bool ret = seg_query.known && seg_query.data.occupied; + bidib_free_segment_state_query(seg_query); + return ret; +} + +bool testsuite_is_segment_occupied_by_train(const char *segment, const char *train) { + t_bidib_dcc_address_query tr_dcc_addr = bidib_get_train_dcc_addr(train); + return testsuite_is_segment_occupied_by_dcc_addr(segment, tr_dcc_addr.dcc_address); +} + +bool testsuite_is_segment_occupied_by_dcc_addr(const char *segment, t_bidib_dcc_address dcc_address) { + t_bidib_segment_state_query seg_query = bidib_get_segment_state(segment); + if (!(seg_query.known && seg_query.data.occupied)) { + bidib_free_segment_state_query(seg_query); + return false; + } + for (size_t j = 0; j < seg_query.data.dcc_address_cnt; j++) { + t_bidib_dcc_address *seg_dcc_j = &seg_query.data.dcc_addresses[j]; + if (dcc_address.addrh == seg_dcc_j->addrh && dcc_address.addrl == seg_dcc_j->addrl) { + bidib_free_segment_state_query(seg_query); + return true; + } + } + bidib_free_segment_state_query(seg_query); + return false; +} + void testsuite_set_signal(const char *signal, const char *aspect) { bidib_set_signal(signal, aspect); bidib_flush(); @@ -266,6 +308,74 @@ void testsuite_switch_point(const char *point, const char *aspect) { bidib_flush(); } +bool testsuite_check_point_aspect(const char *point, const char *aspect) { + if (point == NULL || aspect == NULL) { + printf("testsuite: check point aspect - invalid parameters\n"); + return false; + } + t_bidib_unified_accessory_state_query state = bidib_get_point_state(point); + if (!state.known) { + printf("testsuite: check point aspect - unknown point %s\n", point); + bidib_free_unified_accessory_state_query(state); + return false; + } + if (state.type == BIDIB_ACCESSORY_BOARD) { + if (strcmp(state.board_accessory_state.state_id, aspect) == 0) { + bidib_free_unified_accessory_state_query(state); + return true; + } else { + printf("testsuite: check point aspect - point %s is in aspect %s, not in %s\n", + point, state.board_accessory_state.state_id, aspect); + } + } else { + if (strcmp(state.dcc_accessory_state.state_id, aspect) == 0) { + bidib_free_unified_accessory_state_query(state); + return true; + } else { + printf("testsuite: check point aspect - point %s is in aspect %s, not in %s\n", + point, state.dcc_accessory_state.state_id, aspect); + } + } + + bidib_free_unified_accessory_state_query(state); + return false; +} + +bool testsuite_set_and_check_points(const char **points_normal, int points_normal_len, + const char **points_reverse, int points_reverse_len) { + if (points_normal_len > 0 && points_normal != NULL) { + for (int i = 0; i < points_normal_len; i++) { + testsuite_switch_point(points_normal[i], "normal"); + } + } + if (points_reverse_len > 0 && points_reverse != NULL) { + for (int i = 0; i < points_reverse_len; i++) { + testsuite_switch_point(points_reverse[i], "reverse"); + } + } + sleep(POINT_WAITING_TIME_S); + bool point_check = true; + if (points_normal_len > 0 && points_normal != NULL) { + for (int i = 0; i < points_normal_len; i++) { + point_check &= testsuite_check_point_aspect(points_normal[i], "normal"); + } + } + if (points_reverse_len > 0 && points_reverse != NULL) { + for (int i = 0; i < points_reverse_len; i++) { + point_check &= testsuite_check_point_aspect(points_reverse[i], "reverse"); + } + } + return point_check; +} + +void testsuite_set_signals_to(const char **signals, int signals_len, const char *aspect) { + if (signals_len > 0 && signals != NULL && aspect != NULL) { + for (int i = 0; i < signals_len; i++) { + testsuite_set_signal(signals[i], aspect); + } + } +} + void testsuite_case_signal_common(char **aspects, size_t aspects_len) { for (size_t i = 0; i < aspects_len; i++) { for (size_t n = 0; n < signals.length; n++) { @@ -276,10 +386,20 @@ void testsuite_case_signal_common(char **aspects, size_t aspects_len) { } void testsuite_case_pointParallel_common(t_testsuite_test_result *result) { + // like the old pointParallel, but here an appropriate amount of time is waited + // after setting the points until feedback is gathered. Because point switchting + // takes some time, it doesn't make sense to get the state/feedback immediately + // after sending the command to switch the point. + for (size_t i = 0; i < points.length; i++) { testsuite_switch_point(points.ids[i], "reverse"); + } + + sleep(POINT_WAITING_TIME_S); + + for (size_t i = 0; i < points.length; i++) { t_bidib_unified_accessory_state_query state = bidib_get_point_state(points.ids[i]); - testsuite_logTestResult(result, state, i); + testsuite_recordTestResult(result, state, i); bidib_free_unified_accessory_state_query(state); } @@ -287,26 +407,116 @@ void testsuite_case_pointParallel_common(t_testsuite_test_result *result) { for (size_t i = 0; i < points.length; i++) { testsuite_switch_point(points.ids[i], "normal"); - t_bidib_unified_accessory_state_query state = bidib_get_point_state(points.ids[i]); - testsuite_logTestResult(result, state, i); - bidib_free_unified_accessory_state_query(state); } sleep(POINT_WAITING_TIME_S); -} - -void testsuite_case_pointSerial_common(t_testsuite_test_result *result) { + for (size_t i = 0; i < points.length; i++) { - testsuite_switch_point(points.ids[i], "reverse"); t_bidib_unified_accessory_state_query state = bidib_get_point_state(points.ids[i]); - testsuite_logTestResult(result, state, i); + testsuite_recordTestResult(result, state, i); bidib_free_unified_accessory_state_query(state); - sleep(POINT_WAITING_TIME_S); + } +} - testsuite_switch_point(points.ids[i], "normal"); - state = bidib_get_point_state(points.ids[i]); - testsuite_logTestResult(result, state, i); - bidib_free_unified_accessory_state_query(state); + +void testsuite_case_pointSerial_common(t_testsuite_test_result *result) { + // The points are first all switched to reverse one after the other. + // In iteration k, the switch for point with id at .ids[k] is commanded, + // AND the feedback for the point that was switched in iteration k-1 is gathered. + // -> this is done to avoid having to wait 4x3 seconds per point. + for (size_t i = 0; i <= points.length; i++) { + if (i < points.length) { + testsuite_switch_point(points.ids[i], "reverse"); + } + if (i >= 1) { + t_bidib_unified_accessory_state_query state = bidib_get_point_state(points.ids[i-1]); + testsuite_recordTestResult(result, state, i-1); + bidib_free_unified_accessory_state_query(state); + } + sleep(POINT_WAITING_TIME_S); + } + + // Now the same for switching the points to normal. + for (size_t i = 0; i <= points.length; i++) { + if (i < points.length) { + testsuite_switch_point(points.ids[i], "normal"); + } + if (i >= 1) { + t_bidib_unified_accessory_state_query state = bidib_get_point_state(points.ids[i-1]); + testsuite_recordTestResult(result, state, i-1); + bidib_free_unified_accessory_state_query(state); + } sleep(POINT_WAITING_TIME_S); } } + +const char * const testsuite_cs_state_str[] = { + [BIDIB_CS_OFF] = "BIDIB_CS_OFF", + [BIDIB_CS_STOP] = "BIDIB_CS_STOP", + [BIDIB_CS_SOFTSTOP] = "BIDIB_CS_SOFTSTOP", + [BIDIB_CS_GO] = "BIDIB_CS_GO", + [BIDIB_CS_GO_IGN_WD] = "BIDIB_CS_GO_IGN_WD", + [BIDIB_CS_PROG] = "BIDIB_CS_PROG", + [BIDIB_CS_PROGBUSY] = "BIDIB_CS_PROGBUSY", + [BIDIB_CS_BUSY] = "BIDIB_CS_BUSY", + [BIDIB_CS_QUERY] = "BIDIB_CS_QUERY" +}; + +void testsuite_logAllTrackOutputStates() { + t_bidib_id_list_query tr_outpts_query = bidib_get_track_outputs(); + if (tr_outpts_query.ids == NULL || tr_outpts_query.length == 0) { + printf("testsuite: list of track outputs ids is null or empty\n"); + return; + } + + for (size_t i = 0; i < tr_outpts_query.length; i++) { + t_bidib_track_output_state_query state_q; + state_q = bidib_get_track_output_state(tr_outpts_query.ids[i]); + if (!state_q.known) { + printf("testsuite: track output %s is unknown and/or has unknown state\n", + tr_outpts_query.ids[i]); + } else { + printf("testsuite: track output %s has state %s\n", + tr_outpts_query.ids[i], testsuite_cs_state_str[state_q.cs_state]); + } + } + + bidib_free_id_list_query(tr_outpts_query); +} + +// See also https://bidib.org/protokoll/bidib_booster.html -> MSG_BOOST_STAT +const char * const testsuite_bstr_powerstate_str[] = { + [BIDIB_BSTR_OFF] = "BIDIB_BSTR_OFF", + [BIDIB_BSTR_OFF_SHORT] = "BIDIB_BSTR_OFF_SHORT", + [BIDIB_BSTR_OFF_HOT] = "BIDIB_BSTR_OFF_HOT", + [BIDIB_BSTR_OFF_NOPOWER] = "BIDIB_BSTR_OFF_NOPOWER", + [BIDIB_BSTR_OFF_GO_REQ] = "BIDIB_BSTR_OFF_GO_REQ", + [BIDIB_BSTR_OFF_HERE] = "BIDIB_BSTR_OFF_HERE", + [BIDIB_BSTR_OFF_NO_DCC] = "BIDIB_BSTR_OFF_NO_DCC", + [BIDIB_BSTR_ON] = "BIDIB_BSTR_ON", + [BIDIB_BSTR_ON_LIMIT] = "BIDIB_BSTR_ON_LIMIT", + [BIDIB_BSTR_ON_HOT] = "BIDIB_BSTR_ON_HOT", + [BIDIB_BSTR_ON_STOP_REQ] = "BIDIB_BSTR_ON_STOP_REQ", + [BIDIB_BSTR_ON_HERE] = "BIDIB_BSTR_ON_HERE" +}; + +void testsuite_logAllBoosterPowerStates() { + t_bidib_id_list_query boosters_query = bidib_get_boosters(); + if (boosters_query.ids == NULL || boosters_query.length == 0) { + printf("testsuite: list of booster id query is null or empty\n"); + return; + } + + for (size_t i = 0; i < boosters_query.length; i++) { + t_bidib_booster_state_query b_state = bidib_get_booster_state(boosters_query.ids[i]); + if (!b_state.known) { + printf("testsuite: booster %s is unknown and/or has unknown state\n", + boosters_query.ids[i]); + } else { + printf("testsuite: booster %s has power state %s\n", + boosters_query.ids[i], testsuite_bstr_powerstate_str[b_state.data.power_state]); + } + + } + bidib_free_id_list_query(boosters_query); +} \ No newline at end of file diff --git a/test/physical/test_common.h b/test/physical/test_common.h index 60accbc..9d8ab8e 100644 --- a/test/physical/test_common.h +++ b/test/physical/test_common.h @@ -66,17 +66,27 @@ void testsuite_stopBidib(void); void testsuite_signal_callback_handler(int signum); // Logging -void testsuite_logTestResult(t_testsuite_test_result *result, t_bidib_unified_accessory_state_query state, int accessory_index); +void testsuite_recordTestResult(t_testsuite_test_result *result, t_bidib_unified_accessory_state_query state, int accessory_index); void testsuite_printTestResults(t_testsuite_test_result *result); +void testsuite_logAllTrackOutputStates(); +void testsuite_logAllBoosterPowerStates(); // Driving bool testsuite_trainReady(const char *train, const char *segment); void testsuite_driveTo(const char *segment, int speed, const char *train); void testsuite_driveToStop(const char *segment, int speed, const char *train); +bool testsuite_is_segment_occupied(const char *segment); +bool testsuite_is_segment_occupied_by_train(const char *segment, const char *train); +bool testsuite_is_segment_occupied_by_dcc_addr(const char *segment, t_bidib_dcc_address dcc_address); // Accessories void testsuite_set_signal(const char *signal, const char *aspect); void testsuite_switch_point(const char *point, const char *aspect); +// Returns true if the point's aspect matches a certain value/aspect. Otherwise returns false. +bool testsuite_check_point_aspect(const char *point, const char *aspect); +bool testsuite_set_and_check_points(const char **points_normal, int points_normal_len, + const char **points_reverse, int points_reverse_len); +void testsuite_set_signals_to(const char **signals, int signals_len, const char *aspect); // Common test base void testsuite_case_signal_common(char **aspects, size_t aspects_len);