mirror of
https://github.com/open62541/open62541.git
synced 2026-10-24 09:29:15 +08:00
Use pointer-sized atomic run flags in threaded fixtures, including dynamically allocated discovery and browse flags. Pointer sizing supports the MSVC atomic implementation as well as C11 and GCC atomics. Serialize the client error-injection test with the server network callbacks before accessing the internal session and shared send buffer.
579 lines
23 KiB
C
579 lines
23 KiB
C
/* 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 <open62541/client.h>
|
|
#include <open62541/client_config_default.h>
|
|
#include <open62541/client_highlevel.h>
|
|
#include <open62541/client_subscriptions.h>
|
|
#include <open62541/server.h>
|
|
#include <open62541/server_config_default.h>
|
|
|
|
#include "ua_client_internal.h"
|
|
#include <open62541/types.h>
|
|
#include <check.h>
|
|
#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;
|
|
}
|