Browse Source

enable statuscode descriptions by default; split functionality across
less files

Julius Pfrommer 8 years ago
parent
commit
6529137c26

+ 12 - 15
CMakeLists.txt

@@ -48,7 +48,6 @@ option(UA_ENABLE_MULTITHREADING "Enable multithreading" OFF)
 option(UA_ENABLE_AMALGAMATION "Concatenate the library to a single file open62541.h/.c" OFF)
 option(UA_ENABLE_AMALGAMATION "Concatenate the library to a single file open62541.h/.c" OFF)
 option(UA_ENABLE_COVERAGE "Enable gcov coverage" OFF)
 option(UA_ENABLE_COVERAGE "Enable gcov coverage" OFF)
 option(BUILD_SHARED_LIBS "Enable building of shared libraries (dll/so)" OFF)
 option(BUILD_SHARED_LIBS "Enable building of shared libraries (dll/so)" OFF)
-option(UA_ENABLE_STATUSCODE_MSG "Enable conversion of StatusCode to human-readable error message" OFF)
 
 
 if(UA_ENABLE_COVERAGE)
 if(UA_ENABLE_COVERAGE)
   set(CMAKE_BUILD_TYPE DEBUG)
   set(CMAKE_BUILD_TYPE DEBUG)
@@ -58,7 +57,10 @@ if(UA_ENABLE_COVERAGE)
 endif()
 endif()
 
 
 # Advanced options
 # Advanced options
-option(UA_ENABLE_TYPENAMES "Add the type and member names to the UA_DataType structure" OFF)
+option(UA_ENABLE_STATUSCODE_DESCRIPTIONS "Enable conversion of StatusCode to human-readable error message" ON)
+mark_as_advanced(UA_ENABLE_STATUSCODE_DESCRIPTIONS)
+
+option(UA_ENABLE_TYPENAMES "Add the type and member names to the UA_DataType structure" ON)
 mark_as_advanced(UA_ENABLE_TYPENAMES)
 mark_as_advanced(UA_ENABLE_TYPENAMES)
 
 
 option(UA_ENABLE_EMBEDDED_LIBC "Use a custom implementation of some libc functions that might be missing on embedded targets (e.g. string handling)." OFF)
 option(UA_ENABLE_EMBEDDED_LIBC "Use a custom implementation of some libc functions that might be missing on embedded targets (e.g. string handling)." OFF)
@@ -295,19 +297,14 @@ add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/src_generated/ua_nodeids.h
                    DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/tools/generate_nodeids.py
                    DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/tools/generate_nodeids.py
                            ${CMAKE_CURRENT_SOURCE_DIR}/tools/schema/NodeIds.csv)
                            ${CMAKE_CURRENT_SOURCE_DIR}/tools/schema/NodeIds.csv)
 
 
-if(UA_ENABLE_STATUSCODE_MSG)
-    # StatusCode to String
-    add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/src_generated/ua_statuscode_msg_table.c
-            PRE_BUILD
-            COMMAND ${PYTHON_EXECUTABLE} ${PROJECT_SOURCE_DIR}/tools/generate_statuscode_msg.py
-            ${PROJECT_SOURCE_DIR}/tools/schema/Opc.Ua.StatusCodes.csv ${PROJECT_BINARY_DIR}/src_generated/ua_statuscode_msg_table
-            DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/tools/generate_statuscode_msg.py
-            ${CMAKE_CURRENT_SOURCE_DIR}/tools/schema/Opc.Ua.StatusCodes.csv)
-
-    list(APPEND exported_headers ${PROJECT_SOURCE_DIR}/include/ua_statuscode_msg.h)
-    list(APPEND lib_sources ${PROJECT_BINARY_DIR}/src_generated/ua_statuscode_msg_table.c)
-    list(APPEND lib_sources ${PROJECT_SOURCE_DIR}/src/ua_statuscode_msg.c)
-endif()
+# statuscode explanation
+add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/src_generated/ua_statuscode_descriptions.c
+        PRE_BUILD
+        COMMAND ${PYTHON_EXECUTABLE} ${PROJECT_SOURCE_DIR}/tools/generate_statuscode_descriptions.py
+        ${PROJECT_SOURCE_DIR}/tools/schema/Opc.Ua.StatusCodes.csv ${PROJECT_BINARY_DIR}/src_generated/ua_statuscode_descriptions
+        DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/tools/generate_statuscode_descriptions.py
+        ${CMAKE_CURRENT_SOURCE_DIR}/tools/schema/Opc.Ua.StatusCodes.csv)
+list(APPEND lib_sources ${PROJECT_BINARY_DIR}/src_generated/ua_statuscode_descriptions.c)
 
 
 # generated namespace 0
 # generated namespace 0
 add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/src_generated/ua_namespaceinit_generated.c
 add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/src_generated/ua_namespaceinit_generated.c

+ 0 - 6
examples/client_firstSteps.c

@@ -7,9 +7,6 @@
 #ifdef UA_NO_AMALGAMATION
 #ifdef UA_NO_AMALGAMATION
 #include "ua_client.h"
 #include "ua_client.h"
 #include "ua_config_standard.h"
 #include "ua_config_standard.h"
-#ifdef UA_ENABLE_STATUSCODE_MSG
-#include "ua_statuscode_msg.h"
-#endif
 #else
 #else
 #include "open62541.h"
 #include "open62541.h"
 #endif
 #endif
@@ -18,9 +15,6 @@ int main(void) {
     UA_Client *client = UA_Client_new(UA_ClientConfig_standard);
     UA_Client *client = UA_Client_new(UA_ClientConfig_standard);
     UA_StatusCode retval = UA_Client_connect(client, "opc.tcp://localhost:16664");
     UA_StatusCode retval = UA_Client_connect(client, "opc.tcp://localhost:16664");
     if(retval != UA_STATUSCODE_GOOD) {
     if(retval != UA_STATUSCODE_GOOD) {
-#ifdef UA_ENABLE_STATUSCODE_MSG
-	    printf("Client connect failed: %s - %s", UA_StatusCode_name(retval), UA_StatusCode_msg(retval));
-#endif
         UA_Client_delete(client);
         UA_Client_delete(client);
         return (int)retval;
         return (int)retval;
     }
     }

+ 21 - 12
include/ua_config.h.in

@@ -20,21 +20,30 @@
 extern "C" {
 extern "C" {
 #endif
 #endif
 
 
-#define UA_LOGLEVEL ${UA_LOGLEVEL}
+/**
+ * Library Version
+ * --------------- */
 #define UA_GIT_COMMIT_ID "${GIT_COMMIT_ID}"
 #define UA_GIT_COMMIT_ID "${GIT_COMMIT_ID}"
-#cmakedefine UA_ENABLE_MULTITHREADING
+
+/**
+ * Options
+ * ------- */
+#define UA_LOGLEVEL ${UA_LOGLEVEL}
 #cmakedefine UA_ENABLE_METHODCALLS
 #cmakedefine UA_ENABLE_METHODCALLS
+#cmakedefine UA_ENABLE_NODEMANAGEMENT
 #cmakedefine UA_ENABLE_SUBSCRIPTIONS
 #cmakedefine UA_ENABLE_SUBSCRIPTIONS
+#cmakedefine UA_ENABLE_MULTITHREADING
+
+/**
+ * Advanced Options
+ * ---------------- */
+#cmakedefine UA_ENABLE_STATUSCODE_DESCRIPTIONS
 #cmakedefine UA_ENABLE_TYPENAMES
 #cmakedefine UA_ENABLE_TYPENAMES
+#cmakedefine UA_ENABLE_EMBEDDED_LIBC
 #cmakedefine UA_ENABLE_GENERATE_NAMESPACE0
 #cmakedefine UA_ENABLE_GENERATE_NAMESPACE0
 #cmakedefine UA_ENABLE_EXTERNAL_NAMESPACES
 #cmakedefine UA_ENABLE_EXTERNAL_NAMESPACES
-#cmakedefine UA_ENABLE_NODEMANAGEMENT
-#cmakedefine UA_ENABLE_STATUSCODE_MSG
-
-#cmakedefine UA_ENABLE_EMBEDDED_LIBC
-
-#cmakedefine UA_ENABLE_NONSTANDARD_UDP
 #cmakedefine UA_ENABLE_NONSTANDARD_STATELESS
 #cmakedefine UA_ENABLE_NONSTANDARD_STATELESS
+#cmakedefine UA_ENABLE_NONSTANDARD_UDP
 
 
 /**
 /**
  * Standard Includes
  * Standard Includes
@@ -50,10 +59,10 @@ extern "C" {
 
 
 /**
 /**
  * Function Export
  * Function Export
- * --------------- */
-/* On Win32: Define UA_DYNAMIC_LINKING and UA_DYNAMIC_LINKING_EXPORT in order to
-   export symbols for a DLL. Define UA_DYNAMIC_LINKING only to import symbols
-   from a DLL.*/
+ * ---------------
+ * On Win32: Define ``UA_DYNAMIC_LINKING`` and ``UA_DYNAMIC_LINKING_EXPORT`` in
+ * order to export symbols for a DLL. Define ``UA_DYNAMIC_LINKING`` only to
+ * import symbols from a DLL.*/
 #cmakedefine UA_DYNAMIC_LINKING
 #cmakedefine UA_DYNAMIC_LINKING
 #if defined(_WIN32) && defined(UA_DYNAMIC_LINKING)
 #if defined(_WIN32) && defined(UA_DYNAMIC_LINKING)
 # ifdef UA_DYNAMIC_LINKING_EXPORT /* export dll */
 # ifdef UA_DYNAMIC_LINKING_EXPORT /* export dll */

+ 0 - 44
include/ua_statuscode_msg.h

@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2016 the contributors as stated in the AUTHORS file
- *
- * This file is part of open62541. open62541 is free software: you can
- * redistribute it and/or modify it under the terms of the GNU Lesser General
- * Public License, version 3 (as published by the Free Software Foundation) with
- * a static linking exception as stated in the LICENSE file provided with
- * open62541.
- *
- * open62541 is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
- * details.
- */
-
-#ifndef UA_STATUSCODE_MSG_H_
-#define UA_STATUSCODE_MSG_H_
-
-#include "ua_types.h"
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-struct UA_StatusCode_msg_info {
-	UA_StatusCode value;        /* The numeric value from UA_StatusCode */
-	const char* name;    /* The equivalent symbolic value */
-	const char* msg;    /* Short message about this value */
-};
-
-extern const struct UA_StatusCode_msg_info UA_StatusCode_msg_table[];
-
-extern const unsigned int UA_StatusCode_msg_table_size;
-
-UA_EXPORT const char * UA_StatusCode_msg(UA_StatusCode code);
-UA_EXPORT const char * UA_StatusCode_name(UA_StatusCode code);
-UA_StatusCode UA_EXPORT UA_StatusCode_from_name(const char* name);
-
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif /* UA_STATUSCODE_MSG_H_ */

+ 21 - 0
include/ua_types.h

@@ -154,6 +154,27 @@ typedef double UA_Double;
  * specific code. */
  * specific code. */
 typedef uint32_t UA_StatusCode;
 typedef uint32_t UA_StatusCode;
 
 
+typedef struct {
+	UA_StatusCode code;      /* The numeric value of the StatusCode */
+	const char* name;        /* The symbolic name */
+	const char* explanation; /* Short message explaining the StatusCode */
+} UA_StatusCodeDescription;
+
+/* Returns the description of the StatusCode. Never returns NULL, but a generic
+ * description for invalid StatusCodes instead. */
+UA_EXPORT const UA_StatusCodeDescription *
+UA_StatusCode_description(UA_StatusCode code);
+
+static UA_INLINE const char *
+UA_StatusCode_name(UA_StatusCode code) {
+    return UA_StatusCode_description(code)->name;
+}
+
+static UA_INLINE const char *
+UA_StatusCode_explanation(UA_StatusCode code) {
+    return UA_StatusCode_description(code)->explanation;
+}
+
 /**
 /**
  * String
  * String
  * ^^^^^^
  * ^^^^^^

+ 0 - 60
src/ua_statuscode_msg.c

@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2016 the contributors as stated in the AUTHORS file
- *
- * This file is part of open62541. open62541 is free software: you can
- * redistribute it and/or modify it under the terms of the GNU Lesser General
- * Public License, version 3 (as published by the Free Software Foundation) with
- * a static linking exception as stated in the LICENSE file provided with
- * open62541.
- *
- * open62541 is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
- * details.
- */
-
-#include "ua_config.h"
-#include "ua_statuscode_msg.h"
-#include "ua_util.h"
-
-/*
-
- From generated file:
-
-static const struct UA_StatusCode_msg_info UA_StatusCode_msg_table[] =
-{
-		{UA_STATUSCODE_GOOD, "EPERM", "Not owner"},
-		{UA_STATUSCODE_BADINVALIDARGUMENT, "EPERM", "Not owner"},
-		{UA_STATUSCODE_BADOUTOFMEMORY, "EPERM", "Not owner"}
-};
-
-static const unsigned int UA_StatusCode_msg_table_size = 0;
- */
-
-UA_EXPORT const char* UA_StatusCode_msg(UA_StatusCode code) {
-	for (unsigned int i=0; i<UA_StatusCode_msg_table_size; i++) {
-		if (UA_StatusCode_msg_table[i].value == code)
-			return UA_StatusCode_msg_table[i].msg;
-	}
-	return NULL;
-}
-
-
-UA_EXPORT const char* UA_StatusCode_name(UA_StatusCode code) {
-	for (unsigned int i=0; i<UA_StatusCode_msg_table_size; i++) {
-		if (UA_StatusCode_msg_table[i].value == code)
-			return UA_StatusCode_msg_table[i].name;
-	}
-	return NULL;
-}
-
-
-UA_StatusCode UA_EXPORT UA_StatusCode_from_name(const char* name) {
-	if (name != NULL) {
-		for (unsigned int i=0; i<UA_StatusCode_msg_table_size; i++) {
-			if (strcmp(UA_StatusCode_msg_table[i].name, name) == 0)
-				return UA_StatusCode_msg_table[i].value;
-		}
-	}
-	return UA_UINT32_MAX;
-}

+ 17 - 11
tests/check_utils.c

@@ -2,7 +2,6 @@
 
 
 #include "ua_types.h"
 #include "ua_types.h"
 #include "ua_client.h"
 #include "ua_client.h"
-#include "ua_statuscode_msg.h"
 #include "check.h"
 #include "check.h"
 
 
 START_TEST(EndpointUrl_split) {
 START_TEST(EndpointUrl_split) {
@@ -115,26 +114,33 @@ END_TEST
 
 
 
 
 START_TEST(StatusCode_msg) {
 START_TEST(StatusCode_msg) {
+#ifndef UA_ENABLE_STATUSCODE_DESCRIPTIONS
+    ck_assert_str_eq(UA_StatusCode_msg(UA_STATUSCODE_GOOD), "StatusCode descriptions not available");
+    return;
+#endif
 		// first element in table
 		// first element in table
-    ck_assert_str_eq(UA_StatusCode_msg(UA_STATUSCODE_GOOD), "No error");
+    ck_assert_str_eq(UA_StatusCode_explanation(UA_STATUSCODE_GOOD), "Success / No error");
     ck_assert_str_eq(UA_StatusCode_name(UA_STATUSCODE_GOOD), "Good");
     ck_assert_str_eq(UA_StatusCode_name(UA_STATUSCODE_GOOD), "Good");
 
 
-
 		// just some randomly picked status codes
 		// just some randomly picked status codes
-    ck_assert_str_eq(UA_StatusCode_msg(UA_STATUSCODE_BADNOCOMMUNICATION), "Communication with the data source is defined");
-    ck_assert_str_eq(UA_StatusCode_name(UA_STATUSCODE_BADNOCOMMUNICATION), "BadNoCommunication");
+    ck_assert_str_eq(UA_StatusCode_explanation(UA_STATUSCODE_BADNOCOMMUNICATION),
+                     "Communication with the data source is defined");
+    ck_assert_str_eq(UA_StatusCode_name(UA_STATUSCODE_BADNOCOMMUNICATION),
+                     "BadNoCommunication");
 
 
-    ck_assert_str_eq(UA_StatusCode_msg(UA_STATUSCODE_GOODNODATA), "No data exists for the requested time range or event filter.");
+    ck_assert_str_eq(UA_StatusCode_explanation(UA_STATUSCODE_GOODNODATA),
+                     "No data exists for the requested time range or event filter.");
     ck_assert_str_eq(UA_StatusCode_name(UA_STATUSCODE_GOODNODATA), "GoodNoData");
     ck_assert_str_eq(UA_StatusCode_name(UA_STATUSCODE_GOODNODATA), "GoodNoData");
 
 
 		// last element in table
 		// last element in table
-    ck_assert_str_eq(UA_StatusCode_msg(UA_STATUSCODE_BADMAXCONNECTIONSREACHED), "The operation could not be finished because all available connections are in use.");
-    ck_assert_str_eq(UA_StatusCode_name(UA_STATUSCODE_BADMAXCONNECTIONSREACHED), "BadMaxConnectionsReached");
+    ck_assert_str_eq(UA_StatusCode_explanation(UA_STATUSCODE_BADMAXCONNECTIONSREACHED),
+                     "The operation could not be finished because all available connections are in use.");
+    ck_assert_str_eq(UA_StatusCode_name(UA_STATUSCODE_BADMAXCONNECTIONSREACHED),
+                     "BadMaxConnectionsReached");
 
 
 		// an invalid status code
 		// an invalid status code
-	ck_assert_ptr_eq(UA_StatusCode_msg(0x80123456), NULL);
-	ck_assert_ptr_eq(UA_StatusCode_name(0x80123456), NULL);
-
+	ck_assert_str_eq(UA_StatusCode_explanation(0x80123456), "Unknown StatusCode");
+	ck_assert_str_eq(UA_StatusCode_name(0x80123456), "Unknown");
 }
 }
 END_TEST
 END_TEST
 
 

+ 65 - 0
tools/generate_statuscode_descriptions.py

@@ -0,0 +1,65 @@
+from __future__ import print_function
+import sys
+import platform
+import getpass
+import time
+import argparse
+
+parser = argparse.ArgumentParser()
+parser.add_argument('statuscodes', help='path/to/Opc.Ua.StatusCodes.csv')
+parser.add_argument('outfile', help='outfile w/o extension')
+args = parser.parse_args()
+
+f = open(args.statuscodes)
+input_str = f.read()
+f.close()
+input_str = input_str.replace('\r','')
+rows = map(lambda x:tuple(x.split(',')), input_str.split('\n'))
+
+fc = open(args.outfile + ".c",'w')
+def printc(string):
+    print(string, end='\n', file=fc)
+
+printc('''/**********************************************************
+ * '''+args.outfile+'''.hgen -- do not modify
+ **********************************************************
+ * Generated from '''+args.statuscodes+''' with script '''+sys.argv[0]+'''
+ * on host '''+platform.uname()[1]+''' by user '''+getpass.getuser()+''' at '''+
+       time.strftime("%Y-%m-%d %I:%M:%S")+'''
+ **********************************************************/\n
+
+#include "ua_types.h"''')
+
+count = 2
+for row in rows:
+    count += 1
+
+printc('''
+#ifndef UA_ENABLE_STATUSCODE_DESCRIPTIONS
+static const size_t statusCodeDescriptionsSize = 1;
+static const UA_StatusCodeDescription statusCodeDescriptions[1] = {
+{0xffffffff, \"StatusCode descriptions not available\", \"open62541 was compiled without support for statuscode descriptions\"}
+};
+#else
+static const size_t statusCodeDescriptionsSize = %s;
+static const UA_StatusCodeDescription statusCodeDescriptions[%i] =
+{''' % (count, count))
+
+printc(" {UA_STATUSCODE_GOOD, \"Good\", \"Success / No error\"},")
+for row in rows:
+    printc(" {UA_STATUSCODE_%s, \"%s\", \"%s\"}," % (row[0].upper(), row[0], row[2]))
+printc(" {0xffffffff, \"Unknown\", \"Unknown StatusCode\"},")
+printc('''\n};
+#endif''')
+
+printc('''
+const UA_StatusCodeDescription * UA_StatusCode_description(UA_StatusCode code) {
+    for(size_t i = 0; i < statusCodeDescriptionsSize; i++) {
+        if(statusCodeDescriptions[i].code == code)
+            return &statusCodeDescriptions[i];
+    }
+    return &statusCodeDescriptions[statusCodeDescriptionsSize-1];
+}
+''')
+
+fc.close()

+ 0 - 51
tools/generate_statuscode_msg.py

@@ -1,51 +0,0 @@
-from __future__ import print_function
-import sys
-import platform
-import getpass
-import time
-import argparse
-
-parser = argparse.ArgumentParser()
-parser.add_argument('statuscodes', help='path/to/Opc.Ua.StatusCodes.csv')
-parser.add_argument('outfile', help='outfile w/o extension')
-args = parser.parse_args()
-
-f = open(args.statuscodes)
-input_str = f.read()
-f.close()
-input_str = input_str.replace('\r','')
-rows = map(lambda x:tuple(x.split(',')), input_str.split('\n'))
-
-fh = open(args.outfile + ".c",'w')
-def printh(string):
-    print(string, end='\n', file=fh)
-
-printh('''/**********************************************************
- * '''+args.outfile+'''.hgen -- do not modify
- **********************************************************
- * Generated from '''+args.statuscodes+''' with script '''+sys.argv[0]+'''
- * on host '''+platform.uname()[1]+''' by user '''+getpass.getuser()+''' at '''+
-       time.strftime("%Y-%m-%d %I:%M:%S")+'''
- **********************************************************/\n
-
-#include "ua_statuscode_msg.h"
-
-const struct UA_StatusCode_msg_info UA_StatusCode_msg_table[] =
-{''')
-
-#for row in rows:
-#    printh("#define UA_STATUSCODE_%s %s // %s" % (row[0].upper(), row[1].lower(), row[2]))
-
-count = 1
-printh(" {UA_STATUSCODE_GOOD, \"Good\", \"No error\"},")
-
-for row in rows:
-    printh(" {UA_STATUSCODE_%s, \"%s\", \"%s\"}," % (row[0].upper(), row[0], row[2]))
-    count += 1
-
-printh('\n};\n')
-
-printh('const unsigned int UA_StatusCode_msg_table_size = ' + str(count) + ';')
-
-
-fh.close()

+ 1 - 1
tools/travis/travis_linux_script.sh

@@ -111,7 +111,7 @@ else
     #this run inclides full examples and methodcalls
     #this run inclides full examples and methodcalls
     echo "Debug build and unit tests (64 bit)"
     echo "Debug build and unit tests (64 bit)"
     mkdir -p build && cd build
     mkdir -p build && cd build
-    cmake -DCMAKE_BUILD_TYPE=Debug -DUA_BUILD_EXAMPLES=ON -DUA_BUILD_UNIT_TESTS=ON -DUA_ENABLE_COVERAGE=ON -DUA_ENABLE_STATUSCODE_MSG=ON ..
+    cmake -DCMAKE_BUILD_TYPE=Debug -DUA_BUILD_EXAMPLES=ON -DUA_BUILD_UNIT_TESTS=ON -DUA_ENABLE_COVERAGE=ON ..
     make -j8 && make test ARGS="-V"
     make -j8 && make test ARGS="-V"
     echo "Run valgrind to see if the server leaks memory (just starting up and closing..)"
     echo "Run valgrind to see if the server leaks memory (just starting up and closing..)"
     (valgrind --leak-check=yes --error-exitcode=3 ./examples/server & export pid=$!; sleep 2; kill -INT $pid; wait $pid);
     (valgrind --leak-check=yes --error-exitcode=3 ./examples/server & export pid=$!; sleep 2; kill -INT $pid; wait $pid);