Skip to content

Commit 67ca2cf

Browse files
committed
tests: Add unit tests for AT#XLISTEN and AT#XACCEPT
Basic unit tests for AT#XLISTEN and AT#XACCEPT. Signed-off-by: Markus Lassila <markus.lassila@nordicsemi.no>
1 parent c698367 commit 67ca2cf

File tree

4 files changed

+250
-1
lines changed

4 files changed

+250
-1
lines changed

app/tests/at_socket/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ target_sources(app PRIVATE
6666
../stubs/control_pin_stubs.c
6767
../stubs/pm_stubs.c
6868
../stubs/at_cmd_custom_stubs.c
69+
../stubs/kernel_stubs.c
6970
../../src/sm_util.c
7071
../../src/sm_at_socket.c
7172
../../src/sm_at_host.c

app/tests/at_socket/src/nrf_modem_at_wrapper.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ extern int handle_at_recvcfg_wrapper_xrecvcfg(char *buf, size_t len, char *at_cm
3838
extern int handle_at_socketopt_wrapper_xsocketopt(char *buf, size_t len, char *at_cmd);
3939
extern int handle_at_secure_socket_wrapper_xssocket(char *buf, size_t len, char *at_cmd);
4040
extern int handle_at_secure_socketopt_wrapper_xssocketopt(char *buf, size_t len, char *at_cmd);
41+
extern int handle_at_listen_wrapper_xlisten(char *buf, size_t len, char *at_cmd);
42+
extern int handle_at_accept_wrapper_xaccept(char *buf, size_t len, char *at_cmd);
4143

4244
/* Wrapper for nrf_modem_at_cmd that handles custom commands */
4345
int nrf_modem_at_cmd(void *buf, size_t buf_size, const char *fmt, ...)
@@ -73,6 +75,10 @@ int nrf_modem_at_cmd(void *buf, size_t buf_size, const char *fmt, ...)
7375
ret = handle_at_close_wrapper_xclose((char *)buf, buf_size, at_cmd);
7476
} else if (strncasecmp(at_cmd, "AT#XBIND", 8) == 0) {
7577
ret = handle_at_bind_wrapper_xbind((char *)buf, buf_size, at_cmd);
78+
} else if (strncasecmp(at_cmd, "AT#XLISTEN", 10) == 0) {
79+
ret = handle_at_listen_wrapper_xlisten((char *)buf, buf_size, at_cmd);
80+
} else if (strncasecmp(at_cmd, "AT#XACCEPT", 10) == 0) {
81+
ret = handle_at_accept_wrapper_xaccept((char *)buf, buf_size, at_cmd);
7682
} else if (strncasecmp(at_cmd, "AT#XCONNECT", 11) == 0) {
7783
ret = handle_at_connect_wrapper_xconnect((char *)buf, buf_size, at_cmd);
7884
} else if (strncasecmp(at_cmd, "AT#XSENDTO", 10) == 0) {

app/tests/at_socket/src/test_at_socket.c

Lines changed: 218 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -392,7 +392,7 @@ void test_xsocket_invalid_type(void)
392392

393393
/*
394394
* Test: Create maximum number of sockets
395-
* - Tests that the application can create up to CONFIG_POSIX_OPEN_MAX-1 sockets
395+
* - Tests that the application can create up to CONFIG_POSIX_OPEN_MAX sockets
396396
* - Verifies that attempting to create one more socket fails
397397
*/
398398
void test_xsocket_max_sockets(void)
@@ -776,6 +776,223 @@ void test_xconnect_operation(void)
776776
send_at_command("AT#XCLOSE=1\r\n");
777777
}
778778

779+
/*
780+
* Test: Socket listen operation via AT command
781+
* - Command: AT#XLISTEN=<handle>\r\n
782+
* - Tests: Putting a TCP socket into listening mode
783+
*/
784+
void test_xlisten_operation(void)
785+
{
786+
const char *response;
787+
const char *cgpaddr_resp = "+CGPADDR: 0,\"10.0.0.1\",\"\"\r\nOK\r\n";
788+
789+
/* Create TCP server socket first */
790+
__cmock_nrf_socket_ExpectAndReturn(NRF_AF_INET, NRF_SOCK_STREAM, NRF_IPPROTO_TCP, 5);
791+
__cmock_nrf_setsockopt_ExpectAnyArgsAndReturn(0); /* SO_SNDTIMEO */
792+
__cmock_nrf_setsockopt_ExpectAnyArgsAndReturn(0); /* SO_POLLCB */
793+
send_at_command("AT#XSOCKET=1,1,1\r\n"); /* family=1, type=1, role=1 (server) */
794+
response = get_captured_response();
795+
TEST_ASSERT_TRUE(strstr(response, "#XSOCKET: 5,1,6") != NULL);
796+
clear_captured_response();
797+
798+
/* Bind to port 8080 before listening */
799+
__cmock_nrf_modem_at_cmd_CMockExpectAnyArgsAndReturn(__LINE__, 0);
800+
__cmock_nrf_modem_at_cmd_CMockReturnMemThruPtr_buf(__LINE__, (void *)cgpaddr_resp,
801+
strlen(cgpaddr_resp) + 1);
802+
__cmock_nrf_modem_at_cmd_CMockIgnoreArg_len(__LINE__);
803+
__cmock_nrf_modem_at_cmd_CMockIgnoreArg_fmt(__LINE__);
804+
__cmock_zsock_inet_pton_ExpectAnyArgsAndReturn(1);
805+
__cmock_nrf_inet_pton_ExpectAnyArgsAndReturn(1);
806+
__cmock_nrf_bind_ExpectAndReturn(5, NULL, sizeof(struct nrf_sockaddr_in), 0);
807+
__cmock_nrf_bind_IgnoreArg_address();
808+
__cmock_nrf_bind_IgnoreArg_address_len();
809+
send_at_command("AT#XBIND=5,8080\r\n");
810+
response = get_captured_response();
811+
TEST_ASSERT_TRUE(strstr(response, "OK") != NULL);
812+
clear_captured_response();
813+
814+
/* Mock fcntl to set non-blocking mode and listen */
815+
__cmock_nrf_fcntl_ExpectAndReturn(5, NRF_F_SETFL, NRF_O_NONBLOCK, 0);
816+
__cmock_nrf_listen_ExpectAndReturn(5, 2, 0); /* backlog=2 (fixed in modem) */
817+
818+
/* Execute listen command */
819+
send_at_command("AT#XLISTEN=5\r\n");
820+
821+
/* Verify successful listen response */
822+
response = get_captured_response();
823+
if (strstr(response, "OK") == NULL) {
824+
printf("XLISTEN response: %s\n", response);
825+
}
826+
TEST_ASSERT_TRUE(strstr(response, "OK") != NULL);
827+
828+
/* Close socket */
829+
__cmock_nrf_close_ExpectAndReturn(5, 0);
830+
send_at_command("AT#XCLOSE=5\r\n");
831+
}
832+
833+
/*
834+
* Test: AT#XLISTEN? (READ command type)
835+
* - Verifies READ command lists all listening sockets
836+
*/
837+
void test_xlisten_read_command(void)
838+
{
839+
const char *response;
840+
const char *cgpaddr_resp = "+CGPADDR: 0,\"10.0.0.1\",\"\"\r\nOK\r\n";
841+
842+
/* Create TCP server socket */
843+
__cmock_nrf_socket_ExpectAndReturn(NRF_AF_INET, NRF_SOCK_STREAM, NRF_IPPROTO_TCP, 3);
844+
__cmock_nrf_setsockopt_ExpectAnyArgsAndReturn(0);
845+
__cmock_nrf_setsockopt_ExpectAnyArgsAndReturn(0);
846+
send_at_command("AT#XSOCKET=1,1,1\r\n");
847+
clear_captured_response();
848+
849+
/* Bind to port 9000 */
850+
__cmock_nrf_modem_at_cmd_CMockExpectAnyArgsAndReturn(__LINE__, 0);
851+
__cmock_nrf_modem_at_cmd_CMockReturnMemThruPtr_buf(__LINE__, (void *)cgpaddr_resp,
852+
strlen(cgpaddr_resp) + 1);
853+
__cmock_nrf_modem_at_cmd_CMockIgnoreArg_len(__LINE__);
854+
__cmock_nrf_modem_at_cmd_CMockIgnoreArg_fmt(__LINE__);
855+
__cmock_zsock_inet_pton_ExpectAnyArgsAndReturn(1);
856+
__cmock_nrf_inet_pton_ExpectAnyArgsAndReturn(1);
857+
__cmock_nrf_bind_ExpectAndReturn(3, NULL, sizeof(struct nrf_sockaddr_in), 0);
858+
__cmock_nrf_bind_IgnoreArg_address();
859+
__cmock_nrf_bind_IgnoreArg_address_len();
860+
send_at_command("AT#XBIND=3,9000\r\n");
861+
clear_captured_response();
862+
863+
/* Put socket in listening mode */
864+
__cmock_nrf_fcntl_ExpectAndReturn(3, NRF_F_SETFL, NRF_O_NONBLOCK, 0);
865+
__cmock_nrf_listen_ExpectAndReturn(3, 2, 0);
866+
send_at_command("AT#XLISTEN=3\r\n");
867+
clear_captured_response();
868+
869+
/* Execute read command */
870+
send_at_command("AT#XLISTEN?\r\n");
871+
872+
/* Verify response contains listening socket details */
873+
response = get_captured_response();
874+
/* Format: #XLISTEN: <handle>,<cid>,<local_port> */
875+
TEST_ASSERT_TRUE(strstr(response, "#XLISTEN: 3,0,9000") != NULL);
876+
TEST_ASSERT_TRUE(strstr(response, "OK") != NULL);
877+
878+
/* Close socket */
879+
__cmock_nrf_close_ExpectAndReturn(3, 0);
880+
send_at_command("AT#XCLOSE=3\r\n");
881+
}
882+
883+
/*
884+
* Test: AT#XLISTEN with socket that is not bound
885+
* - Should fail because socket must be bound before listen
886+
*/
887+
void test_xlisten_not_bound(void)
888+
{
889+
const char *response;
890+
891+
/* Create TCP server socket but don't bind it */
892+
__cmock_nrf_socket_ExpectAndReturn(NRF_AF_INET, NRF_SOCK_STREAM, NRF_IPPROTO_TCP, 2);
893+
__cmock_nrf_setsockopt_ExpectAnyArgsAndReturn(0);
894+
__cmock_nrf_setsockopt_ExpectAnyArgsAndReturn(0);
895+
send_at_command("AT#XSOCKET=1,1,1\r\n");
896+
clear_captured_response();
897+
898+
/* Try to listen without binding - should fail */
899+
send_at_command("AT#XLISTEN=2\r\n");
900+
901+
/* Verify error response */
902+
response = get_captured_response();
903+
TEST_ASSERT_TRUE(strstr(response, "ERROR") != NULL);
904+
905+
/* Close socket */
906+
__cmock_nrf_close_ExpectAndReturn(2, 0);
907+
send_at_command("AT#XCLOSE=2\r\n");
908+
}
909+
910+
/*
911+
* Test: Socket accept operation via AT command
912+
* - Command: AT#XACCEPT=<handle>\r\n
913+
* - Tests: Accepting an incoming connection on a listening socket
914+
*/
915+
void test_xaccept_operation(void)
916+
{
917+
const char *response;
918+
const char *cgpaddr_resp = "+CGPADDR: 0,\"10.0.0.1\",\"\"\r\nOK\r\n";
919+
920+
/* Create TCP server socket */
921+
__cmock_nrf_socket_ExpectAndReturn(NRF_AF_INET, NRF_SOCK_STREAM, NRF_IPPROTO_TCP, 1);
922+
__cmock_nrf_setsockopt_ExpectAnyArgsAndReturn(0);
923+
__cmock_nrf_setsockopt_ExpectAnyArgsAndReturn(0);
924+
send_at_command("AT#XSOCKET=1,1,1\r\n");
925+
clear_captured_response();
926+
927+
/* Bind to port 7000 */
928+
__cmock_nrf_modem_at_cmd_CMockExpectAnyArgsAndReturn(__LINE__, 0);
929+
__cmock_nrf_modem_at_cmd_CMockReturnMemThruPtr_buf(__LINE__, (void *)cgpaddr_resp,
930+
strlen(cgpaddr_resp) + 1);
931+
__cmock_nrf_modem_at_cmd_CMockIgnoreArg_len(__LINE__);
932+
__cmock_nrf_modem_at_cmd_CMockIgnoreArg_fmt(__LINE__);
933+
__cmock_zsock_inet_pton_ExpectAnyArgsAndReturn(1);
934+
__cmock_nrf_inet_pton_ExpectAnyArgsAndReturn(1);
935+
__cmock_nrf_bind_ExpectAndReturn(1, NULL, sizeof(struct nrf_sockaddr_in), 0);
936+
__cmock_nrf_bind_IgnoreArg_address();
937+
__cmock_nrf_bind_IgnoreArg_address_len();
938+
send_at_command("AT#XBIND=1,7000\r\n");
939+
clear_captured_response();
940+
941+
/* Put socket in listening mode */
942+
__cmock_nrf_fcntl_ExpectAndReturn(1, NRF_F_SETFL, NRF_O_NONBLOCK, 0);
943+
__cmock_nrf_listen_ExpectAndReturn(1, 2, 0);
944+
send_at_command("AT#XLISTEN=1\r\n");
945+
clear_captured_response();
946+
947+
/* Mock successful accept - returns new socket fd=7 */
948+
__cmock_nrf_accept_ExpectAnyArgsAndReturn(7);
949+
__cmock_zsock_inet_ntop_ExpectAnyArgsAndReturn("192.168.1.100"); /* util_get_peer_addr */
950+
__cmock_nrf_setsockopt_ExpectAnyArgsAndReturn(0); /* POLLCB for new socket */
951+
__cmock_nrf_setsockopt_ExpectAnyArgsAndReturn(0); /* POLLCB restore for listening socket */
952+
953+
/* Execute accept command */
954+
send_at_command("AT#XACCEPT=1\r\n");
955+
956+
/* Verify successful accept response */
957+
/* Format: #XACCEPT: <new_handle>,<cid>,"<peer_addr>",<peer_port> */
958+
response = get_captured_response();
959+
TEST_ASSERT_TRUE(strstr(response, "#XACCEPT: 7,0,") != NULL);
960+
TEST_ASSERT_TRUE(strstr(response, "OK") != NULL);
961+
962+
/* Close sockets */
963+
__cmock_nrf_close_ExpectAndReturn(7, 0);
964+
send_at_command("AT#XCLOSE=7\r\n");
965+
__cmock_nrf_close_ExpectAndReturn(1, 0);
966+
send_at_command("AT#XCLOSE=1\r\n");
967+
}
968+
969+
/*
970+
* Test: AT#XACCEPT with socket that is not listening
971+
* - Should fail because socket must be in listening mode
972+
*/
973+
void test_xaccept_not_listening(void)
974+
{
975+
const char *response;
976+
977+
/* Create TCP client socket (not server) */
978+
__cmock_nrf_socket_ExpectAndReturn(NRF_AF_INET, NRF_SOCK_STREAM, NRF_IPPROTO_TCP, 4);
979+
__cmock_nrf_setsockopt_ExpectAnyArgsAndReturn(0);
980+
__cmock_nrf_setsockopt_ExpectAnyArgsAndReturn(0);
981+
send_at_command("AT#XSOCKET=1,1,0\r\n"); /* role=0 (client) */
982+
clear_captured_response();
983+
984+
/* Try to accept on a client socket - should fail */
985+
send_at_command("AT#XACCEPT=4\r\n");
986+
987+
/* Verify error response */
988+
response = get_captured_response();
989+
TEST_ASSERT_TRUE(strstr(response, "ERROR") != NULL);
990+
991+
/* Close socket */
992+
__cmock_nrf_close_ExpectAndReturn(4, 0);
993+
send_at_command("AT#XCLOSE=4\r\n");
994+
}
995+
779996
/*
780997
* Test: Send data via AT#XSEND with unformatted string
781998
* - Command: AT#XSEND=<handle>,<mode>,<flags>,"<data>"\r\n

app/tests/stubs/kernel_stubs.c

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Copyright (c) 2025 Nordic Semiconductor ASA
3+
*
4+
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
5+
*/
6+
7+
/**
8+
* @file kernel_stubs.c
9+
* Stub implementations for kernel memory allocation functions
10+
*/
11+
12+
#include <zephyr/kernel.h>
13+
#include <stdlib.h>
14+
15+
/* Stub for k_malloc - use standard malloc for testing */
16+
void *k_malloc(size_t size)
17+
{
18+
return malloc(size);
19+
}
20+
21+
/* Stub for k_free - use standard free for testing */
22+
void k_free(void *ptr)
23+
{
24+
free(ptr);
25+
}

0 commit comments

Comments
 (0)