/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /* Subscription edge case and service tests */ #ifndef _GNU_SOURCE # define _GNU_SOURCE #endif #include #include #include #include #include #include #include "ua_client_internal.h" #include #include #include "testing_clock.h" #include "thread_wrapper.h" #include "test_helpers.h" static UA_Server *server; static UA_atomic(uintptr_t) running; static THREAD_HANDLE server_thread; THREAD_CALLBACK(serverloop) { while(UA_atomic_load(&running)) UA_Server_run_iterate(server, true); return 0; } static void setup(void) { server = UA_Server_newForUnitTest(); ck_assert(server != NULL); /* Add a variable for monitoring */ UA_VariableAttributes vattr = UA_VariableAttributes_default; UA_Int32 val = 42; UA_Variant_setScalar(&vattr.value, &val, &UA_TYPES[UA_TYPES_INT32]); vattr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE; UA_Server_addVariableNode(server, UA_NODEID_NUMERIC(1, 85001), UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER), UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES), UA_QUALIFIEDNAME(1, "SubExtVar"), UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), vattr, NULL, NULL); /* Second variable */ UA_VariableAttributes vattr2 = UA_VariableAttributes_default; UA_Int32 val2 = 100; UA_Variant_setScalar(&vattr2.value, &val2, &UA_TYPES[UA_TYPES_INT32]); vattr2.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE; UA_Server_addVariableNode(server, UA_NODEID_NUMERIC(1, 85002), UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER), UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES), UA_QUALIFIEDNAME(1, "SubExtVar2"), UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), vattr2, NULL, NULL); UA_atomic_store(&running, true); UA_Server_run_startup(server); THREAD_CREATE(server_thread, serverloop); } static void teardown(void) { UA_atomic_store(&running, false); THREAD_JOIN(server_thread); UA_Server_run_shutdown(server); UA_Server_delete(server); } static UA_Client *connectClient(void) { UA_Client *client = UA_Client_newForUnitTest(); UA_StatusCode res = UA_Client_connect(client, "opc.tcp://localhost:4840"); ck_assert_uint_eq(res, UA_STATUSCODE_GOOD); return client; } static void disconnectClient(UA_Client *client) { UA_Client_disconnect(client); UA_Client_delete(client); } /* Data change notification callback */ static UA_Boolean dataChangeReceived = false; static void dataChangeHandler(UA_Client *client, UA_UInt32 subId, void *subContext, UA_UInt32 monId, void *monContext, UA_DataValue *value) { (void)client; (void)subId; (void)subContext; (void)monId; (void)monContext; (void)value; dataChangeReceived = true; } /* === Create subscription + modify === */ START_TEST(createModifySubscription) { UA_Client *client = connectClient(); UA_CreateSubscriptionRequest req = UA_CreateSubscriptionRequest_default(); req.requestedPublishingInterval = 200.0; req.requestedMaxKeepAliveCount = 5; req.requestedLifetimeCount = 30; UA_CreateSubscriptionResponse resp = UA_Client_Subscriptions_create(client, req, NULL, NULL, NULL); ck_assert_uint_eq(resp.responseHeader.serviceResult, UA_STATUSCODE_GOOD); UA_UInt32 subId = resp.subscriptionId; /* Modify the subscription */ UA_ModifySubscriptionRequest modReq; UA_ModifySubscriptionRequest_init(&modReq); modReq.subscriptionId = subId; modReq.requestedPublishingInterval = 500.0; modReq.requestedMaxKeepAliveCount = 10; modReq.requestedLifetimeCount = 100; modReq.maxNotificationsPerPublish = 0; modReq.priority = 1; UA_ModifySubscriptionResponse modResp = UA_Client_Subscriptions_modify(client, modReq); ck_assert_uint_eq(modResp.responseHeader.serviceResult, UA_STATUSCODE_GOOD); UA_ModifySubscriptionResponse_clear(&modResp); UA_Client_Subscriptions_deleteSingle(client, subId); disconnectClient(client); } END_TEST /* === SetPublishingMode === */ START_TEST(setPublishingMode) { UA_Client *client = connectClient(); UA_CreateSubscriptionRequest req = UA_CreateSubscriptionRequest_default(); UA_CreateSubscriptionResponse resp = UA_Client_Subscriptions_create(client, req, NULL, NULL, NULL); ck_assert_uint_eq(resp.responseHeader.serviceResult, UA_STATUSCODE_GOOD); UA_UInt32 subId = resp.subscriptionId; /* Disable publishing */ UA_SetPublishingModeRequest pmReq; UA_SetPublishingModeRequest_init(&pmReq); pmReq.publishingEnabled = false; pmReq.subscriptionIds = &subId; pmReq.subscriptionIdsSize = 1; UA_SetPublishingModeResponse pmResp = UA_Client_Subscriptions_setPublishingMode(client, pmReq); ck_assert_uint_eq(pmResp.responseHeader.serviceResult, UA_STATUSCODE_GOOD); UA_SetPublishingModeResponse_clear(&pmResp); /* Re-enable */ pmReq.publishingEnabled = true; UA_SetPublishingModeResponse pmResp2 = UA_Client_Subscriptions_setPublishingMode(client, pmReq); ck_assert_uint_eq(pmResp2.responseHeader.serviceResult, UA_STATUSCODE_GOOD); UA_SetPublishingModeResponse_clear(&pmResp2); UA_Client_Subscriptions_deleteSingle(client, subId); disconnectClient(client); } END_TEST /* === MonitoredItem modify === */ START_TEST(modifyMonitoredItem) { UA_Client *client = connectClient(); UA_CreateSubscriptionRequest subReq = UA_CreateSubscriptionRequest_default(); UA_CreateSubscriptionResponse subResp = UA_Client_Subscriptions_create(client, subReq, NULL, NULL, NULL); ck_assert_uint_eq(subResp.responseHeader.serviceResult, UA_STATUSCODE_GOOD); UA_UInt32 subId = subResp.subscriptionId; /* Create monitored item */ UA_MonitoredItemCreateRequest miReq = UA_MonitoredItemCreateRequest_default(UA_NODEID_NUMERIC(1, 85001)); UA_MonitoredItemCreateResult miRes = UA_Client_MonitoredItems_createDataChange(client, subId, UA_TIMESTAMPSTORETURN_BOTH, miReq, NULL, dataChangeHandler, NULL); ck_assert_uint_eq(miRes.statusCode, UA_STATUSCODE_GOOD); /* Modify - change sampling interval */ UA_MonitoredItemModifyRequest modReq; UA_MonitoredItemModifyRequest_init(&modReq); modReq.monitoredItemId = miRes.monitoredItemId; modReq.requestedParameters.samplingInterval = 500.0; modReq.requestedParameters.queueSize = 5; UA_ModifyMonitoredItemsRequest mmReq; UA_ModifyMonitoredItemsRequest_init(&mmReq); mmReq.subscriptionId = subId; mmReq.itemsToModify = &modReq; mmReq.itemsToModifySize = 1; mmReq.timestampsToReturn = UA_TIMESTAMPSTORETURN_BOTH; UA_ModifyMonitoredItemsResponse mmResp = UA_Client_MonitoredItems_modify(client, mmReq); ck_assert_uint_eq(mmResp.responseHeader.serviceResult, UA_STATUSCODE_GOOD); if(mmResp.resultsSize > 0) ck_assert_uint_eq(mmResp.results[0].statusCode, UA_STATUSCODE_GOOD); UA_ModifyMonitoredItemsResponse_clear(&mmResp); UA_Client_Subscriptions_deleteSingle(client, subId); disconnectClient(client); } END_TEST /* === SetMonitoringMode === */ START_TEST(setMonitoringMode) { UA_Client *client = connectClient(); UA_CreateSubscriptionRequest subReq = UA_CreateSubscriptionRequest_default(); UA_CreateSubscriptionResponse subResp = UA_Client_Subscriptions_create(client, subReq, NULL, NULL, NULL); ck_assert_uint_eq(subResp.responseHeader.serviceResult, UA_STATUSCODE_GOOD); UA_UInt32 subId = subResp.subscriptionId; UA_MonitoredItemCreateRequest miReq = UA_MonitoredItemCreateRequest_default(UA_NODEID_NUMERIC(1, 85001)); UA_MonitoredItemCreateResult miRes = UA_Client_MonitoredItems_createDataChange(client, subId, UA_TIMESTAMPSTORETURN_BOTH, miReq, NULL, NULL, NULL); ck_assert_uint_eq(miRes.statusCode, UA_STATUSCODE_GOOD); /* Set to sampling */ UA_SetMonitoringModeRequest smReq; UA_SetMonitoringModeRequest_init(&smReq); smReq.subscriptionId = subId; smReq.monitoringMode = UA_MONITORINGMODE_SAMPLING; UA_UInt32 monId = miRes.monitoredItemId; smReq.monitoredItemIds = &monId; smReq.monitoredItemIdsSize = 1; UA_SetMonitoringModeResponse smResp = UA_Client_MonitoredItems_setMonitoringMode(client, smReq); ck_assert_uint_eq(smResp.responseHeader.serviceResult, UA_STATUSCODE_GOOD); UA_SetMonitoringModeResponse_clear(&smResp); /* Set to disabled */ smReq.monitoringMode = UA_MONITORINGMODE_DISABLED; UA_SetMonitoringModeResponse smResp2 = UA_Client_MonitoredItems_setMonitoringMode(client, smReq); ck_assert_uint_eq(smResp2.responseHeader.serviceResult, UA_STATUSCODE_GOOD); UA_SetMonitoringModeResponse_clear(&smResp2); /* Set back to reporting */ smReq.monitoringMode = UA_MONITORINGMODE_REPORTING; UA_SetMonitoringModeResponse smResp3 = UA_Client_MonitoredItems_setMonitoringMode(client, smReq); ck_assert_uint_eq(smResp3.responseHeader.serviceResult, UA_STATUSCODE_GOOD); UA_SetMonitoringModeResponse_clear(&smResp3); UA_Client_Subscriptions_deleteSingle(client, subId); disconnectClient(client); } END_TEST /* === Multiple monitored items === */ START_TEST(multipleMonitoredItems) { UA_Client *client = connectClient(); UA_CreateSubscriptionRequest subReq = UA_CreateSubscriptionRequest_default(); UA_CreateSubscriptionResponse subResp = UA_Client_Subscriptions_create(client, subReq, NULL, NULL, NULL); ck_assert_uint_eq(subResp.responseHeader.serviceResult, UA_STATUSCODE_GOOD); UA_UInt32 subId = subResp.subscriptionId; /* Create multiple monitored items at once */ UA_MonitoredItemCreateRequest items[2]; items[0] = UA_MonitoredItemCreateRequest_default(UA_NODEID_NUMERIC(1, 85001)); items[1] = UA_MonitoredItemCreateRequest_default(UA_NODEID_NUMERIC(1, 85002)); UA_CreateMonitoredItemsRequest ciReq; UA_CreateMonitoredItemsRequest_init(&ciReq); ciReq.subscriptionId = subId; ciReq.timestampsToReturn = UA_TIMESTAMPSTORETURN_BOTH; ciReq.itemsToCreate = items; ciReq.itemsToCreateSize = 2; UA_CreateMonitoredItemsResponse ciResp = UA_Client_MonitoredItems_createDataChanges(client, ciReq, NULL, NULL, NULL); ck_assert_uint_eq(ciResp.responseHeader.serviceResult, UA_STATUSCODE_GOOD); ck_assert_uint_eq(ciResp.resultsSize, 2); ck_assert_uint_eq(ciResp.results[0].statusCode, UA_STATUSCODE_GOOD); ck_assert_uint_eq(ciResp.results[1].statusCode, UA_STATUSCODE_GOOD); /* Delete specific monitored items */ UA_UInt32 monIds[2] = {ciResp.results[0].monitoredItemId, ciResp.results[1].monitoredItemId}; UA_DeleteMonitoredItemsRequest delReq; UA_DeleteMonitoredItemsRequest_init(&delReq); delReq.subscriptionId = subId; delReq.monitoredItemIds = monIds; delReq.monitoredItemIdsSize = 2; UA_DeleteMonitoredItemsResponse delResp = UA_Client_MonitoredItems_delete(client, delReq); ck_assert_uint_eq(delResp.responseHeader.serviceResult, UA_STATUSCODE_GOOD); UA_DeleteMonitoredItemsResponse_clear(&delResp); UA_CreateMonitoredItemsResponse_clear(&ciResp); UA_Client_Subscriptions_deleteSingle(client, subId); disconnectClient(client); } END_TEST /* === Republish === */ START_TEST(republish_test) { UA_Client *client = connectClient(); UA_CreateSubscriptionRequest subReq = UA_CreateSubscriptionRequest_default(); subReq.requestedPublishingInterval = 50.0; UA_CreateSubscriptionResponse subResp = UA_Client_Subscriptions_create(client, subReq, NULL, NULL, NULL); ck_assert_uint_eq(subResp.responseHeader.serviceResult, UA_STATUSCODE_GOOD); UA_UInt32 subId = subResp.subscriptionId; UA_MonitoredItemCreateRequest miReq = UA_MonitoredItemCreateRequest_default(UA_NODEID_NUMERIC(1, 85001)); UA_Client_MonitoredItems_createDataChange(client, subId, UA_TIMESTAMPSTORETURN_BOTH, miReq, NULL, dataChangeHandler, NULL); /* Generate some data */ for(int i = 0; i < 10; i++) UA_Client_run_iterate(client, 100); /* Try republish — even with seq 0 (expected to fail, but exercises code) */ UA_RepublishRequest repReq; UA_RepublishRequest_init(&repReq); repReq.subscriptionId = subId; repReq.retransmitSequenceNumber = 1; UA_RepublishResponse repResp; __UA_Client_Service(client, &repReq, &UA_TYPES[UA_TYPES_REPUBLISHREQUEST], &repResp, &UA_TYPES[UA_TYPES_REPUBLISHRESPONSE]); /* May fail if sequence was already acknowledged, that's fine */ (void)repResp.responseHeader.serviceResult; UA_RepublishResponse_clear(&repResp); UA_Client_Subscriptions_deleteSingle(client, subId); disconnectClient(client); } END_TEST /* === Multiple subscriptions === */ START_TEST(multipleSubscriptions) { UA_Client *client = connectClient(); /* Create two subscriptions */ UA_CreateSubscriptionRequest req1 = UA_CreateSubscriptionRequest_default(); req1.requestedPublishingInterval = 100.0; UA_CreateSubscriptionResponse resp1 = UA_Client_Subscriptions_create(client, req1, NULL, NULL, NULL); ck_assert_uint_eq(resp1.responseHeader.serviceResult, UA_STATUSCODE_GOOD); UA_CreateSubscriptionRequest req2 = UA_CreateSubscriptionRequest_default(); req2.requestedPublishingInterval = 200.0; UA_CreateSubscriptionResponse resp2 = UA_Client_Subscriptions_create(client, req2, NULL, NULL, NULL); ck_assert_uint_eq(resp2.responseHeader.serviceResult, UA_STATUSCODE_GOOD); /* Add MI to each */ UA_MonitoredItemCreateRequest miReq1 = UA_MonitoredItemCreateRequest_default(UA_NODEID_NUMERIC(1, 85001)); UA_Client_MonitoredItems_createDataChange(client, resp1.subscriptionId, UA_TIMESTAMPSTORETURN_BOTH, miReq1, NULL, dataChangeHandler, NULL); UA_MonitoredItemCreateRequest miReq2 = UA_MonitoredItemCreateRequest_default(UA_NODEID_NUMERIC(1, 85002)); UA_Client_MonitoredItems_createDataChange(client, resp2.subscriptionId, UA_TIMESTAMPSTORETURN_BOTH, miReq2, NULL, dataChangeHandler, NULL); /* Run iterations */ for(int i = 0; i < 10; i++) UA_Client_run_iterate(client, 100); /* Delete both */ UA_Client_Subscriptions_deleteSingle(client, resp1.subscriptionId); UA_Client_Subscriptions_deleteSingle(client, resp2.subscriptionId); disconnectClient(client); } END_TEST /* === Timestamps to return variants === */ START_TEST(timestampsToReturn_source) { UA_Client *client = connectClient(); UA_CreateSubscriptionRequest subReq = UA_CreateSubscriptionRequest_default(); UA_CreateSubscriptionResponse subResp = UA_Client_Subscriptions_create(client, subReq, NULL, NULL, NULL); ck_assert_uint_eq(subResp.responseHeader.serviceResult, UA_STATUSCODE_GOOD); /* Source timestamp only */ UA_MonitoredItemCreateRequest miReq = UA_MonitoredItemCreateRequest_default(UA_NODEID_NUMERIC(1, 85001)); UA_MonitoredItemCreateResult miRes = UA_Client_MonitoredItems_createDataChange(client, subResp.subscriptionId, UA_TIMESTAMPSTORETURN_SOURCE, miReq, NULL, dataChangeHandler, NULL); ck_assert_uint_eq(miRes.statusCode, UA_STATUSCODE_GOOD); for(int i = 0; i < 5; i++) UA_Client_run_iterate(client, 100); UA_Client_Subscriptions_deleteSingle(client, subResp.subscriptionId); disconnectClient(client); } END_TEST START_TEST(timestampsToReturn_server) { UA_Client *client = connectClient(); UA_CreateSubscriptionRequest subReq = UA_CreateSubscriptionRequest_default(); UA_CreateSubscriptionResponse subResp = UA_Client_Subscriptions_create(client, subReq, NULL, NULL, NULL); ck_assert_uint_eq(subResp.responseHeader.serviceResult, UA_STATUSCODE_GOOD); /* Server timestamp only */ UA_MonitoredItemCreateRequest miReq = UA_MonitoredItemCreateRequest_default(UA_NODEID_NUMERIC(1, 85001)); UA_MonitoredItemCreateResult miRes = UA_Client_MonitoredItems_createDataChange(client, subResp.subscriptionId, UA_TIMESTAMPSTORETURN_SERVER, miReq, NULL, dataChangeHandler, NULL); ck_assert_uint_eq(miRes.statusCode, UA_STATUSCODE_GOOD); for(int i = 0; i < 5; i++) UA_Client_run_iterate(client, 100); UA_Client_Subscriptions_deleteSingle(client, subResp.subscriptionId); disconnectClient(client); } END_TEST START_TEST(timestampsToReturn_neither) { UA_Client *client = connectClient(); UA_CreateSubscriptionRequest subReq = UA_CreateSubscriptionRequest_default(); UA_CreateSubscriptionResponse subResp = UA_Client_Subscriptions_create(client, subReq, NULL, NULL, NULL); ck_assert_uint_eq(subResp.responseHeader.serviceResult, UA_STATUSCODE_GOOD); /* Neither timestamp */ UA_MonitoredItemCreateRequest miReq = UA_MonitoredItemCreateRequest_default(UA_NODEID_NUMERIC(1, 85001)); UA_MonitoredItemCreateResult miRes = UA_Client_MonitoredItems_createDataChange(client, subResp.subscriptionId, UA_TIMESTAMPSTORETURN_NEITHER, miReq, NULL, dataChangeHandler, NULL); ck_assert_uint_eq(miRes.statusCode, UA_STATUSCODE_GOOD); for(int i = 0; i < 5; i++) UA_Client_run_iterate(client, 100); UA_Client_Subscriptions_deleteSingle(client, subResp.subscriptionId); disconnectClient(client); } END_TEST /* === Transfer subscription === */ START_TEST(transferSubscription) { UA_Client *client = connectClient(); UA_CreateSubscriptionRequest subReq = UA_CreateSubscriptionRequest_default(); UA_CreateSubscriptionResponse subResp = UA_Client_Subscriptions_create(client, subReq, NULL, NULL, NULL); ck_assert_uint_eq(subResp.responseHeader.serviceResult, UA_STATUSCODE_GOOD); UA_MonitoredItemCreateRequest miReq = UA_MonitoredItemCreateRequest_default(UA_NODEID_NUMERIC(1, 85001)); UA_Client_MonitoredItems_createDataChange(client, subResp.subscriptionId, UA_TIMESTAMPSTORETURN_BOTH, miReq, NULL, NULL, NULL); /* Try TransferSubscription (same session - expected to fail but exercises code) */ UA_TransferSubscriptionsRequest tReq; UA_TransferSubscriptionsRequest_init(&tReq); tReq.subscriptionIds = &subResp.subscriptionId; tReq.subscriptionIdsSize = 1; tReq.sendInitialValues = true; UA_TransferSubscriptionsResponse tResp; __UA_Client_Service(client, &tReq, &UA_TYPES[UA_TYPES_TRANSFERSUBSCRIPTIONSREQUEST], &tResp, &UA_TYPES[UA_TYPES_TRANSFERSUBSCRIPTIONSRESPONSE]); /* Will fail with BadNothingToDo or similar - that's OK */ (void)tResp.responseHeader.serviceResult; UA_TransferSubscriptionsResponse_clear(&tResp); UA_Client_Subscriptions_deleteSingle(client, subResp.subscriptionId); disconnectClient(client); } END_TEST /* === Delete non-existent subscription === */ START_TEST(deleteNonExistentSubscription) { UA_Client *client = connectClient(); /* Try deleting a non-existent subscription ID */ UA_UInt32 badSubId = 999999; UA_StatusCode res = UA_Client_Subscriptions_deleteSingle(client, badSubId); ck_assert(res != UA_STATUSCODE_GOOD); disconnectClient(client); } END_TEST /* === Trigger data changes === */ START_TEST(triggerDataChange) { UA_Client *client = connectClient(); UA_CreateSubscriptionRequest subReq = UA_CreateSubscriptionRequest_default(); subReq.requestedPublishingInterval = 50.0; UA_CreateSubscriptionResponse subResp = UA_Client_Subscriptions_create(client, subReq, NULL, NULL, NULL); ck_assert_uint_eq(subResp.responseHeader.serviceResult, UA_STATUSCODE_GOOD); dataChangeReceived = false; UA_MonitoredItemCreateRequest miReq = UA_MonitoredItemCreateRequest_default(UA_NODEID_NUMERIC(1, 85001)); miReq.requestedParameters.samplingInterval = 50.0; UA_MonitoredItemCreateResult miRes = UA_Client_MonitoredItems_createDataChange(client, subResp.subscriptionId, UA_TIMESTAMPSTORETURN_BOTH, miReq, NULL, dataChangeHandler, NULL); ck_assert_uint_eq(miRes.statusCode, UA_STATUSCODE_GOOD); /* Write a value to trigger data change */ UA_Int32 newVal = 999; UA_Variant wv; UA_Variant_setScalar(&wv, &newVal, &UA_TYPES[UA_TYPES_INT32]); UA_Client_writeValueAttribute(client, UA_NODEID_NUMERIC(1, 85001), &wv); /* Run iterations to receive notification */ for(int i = 0; i < 50; i++) UA_Client_run_iterate(client, 100); /* Data change may not always be received in test due to timing; * the primary goal is to exercise the subscription code paths */ (void)dataChangeReceived; UA_Client_Subscriptions_deleteSingle(client, subResp.subscriptionId); disconnectClient(client); } END_TEST /* === Suite definition === */ static Suite *testSuite_subscriptionExt(void) { TCase *tc_sub = tcase_create("SubscriptionOps"); tcase_add_checked_fixture(tc_sub, setup, teardown); tcase_set_timeout(tc_sub, 30); tcase_add_test(tc_sub, createModifySubscription); tcase_add_test(tc_sub, setPublishingMode); tcase_add_test(tc_sub, multipleSubscriptions); tcase_add_test(tc_sub, deleteNonExistentSubscription); TCase *tc_mi = tcase_create("MonitoredItemOps"); tcase_add_checked_fixture(tc_mi, setup, teardown); tcase_set_timeout(tc_mi, 30); tcase_add_test(tc_mi, modifyMonitoredItem); tcase_add_test(tc_mi, setMonitoringMode); tcase_add_test(tc_mi, multipleMonitoredItems); TCase *tc_ts = tcase_create("Timestamps"); tcase_add_checked_fixture(tc_ts, setup, teardown); tcase_set_timeout(tc_ts, 30); tcase_add_test(tc_ts, timestampsToReturn_source); tcase_add_test(tc_ts, timestampsToReturn_server); tcase_add_test(tc_ts, timestampsToReturn_neither); TCase *tc_adv = tcase_create("Advanced"); tcase_add_checked_fixture(tc_adv, setup, teardown); tcase_set_timeout(tc_adv, 30); tcase_add_test(tc_adv, republish_test); tcase_add_test(tc_adv, transferSubscription); tcase_add_test(tc_adv, triggerDataChange); Suite *s = suite_create("Subscription Extended"); suite_add_tcase(s, tc_sub); suite_add_tcase(s, tc_mi); suite_add_tcase(s, tc_ts); suite_add_tcase(s, tc_adv); return s; } int main(void) { int number_failed = 0; Suite *s = testSuite_subscriptionExt(); SRunner *sr = srunner_create(s); srunner_set_fork_status(sr, CK_NOFORK); srunner_run_all(sr, CK_NORMAL); number_failed += srunner_ntests_failed(sr); srunner_free(sr); return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; }