This is an automated email from the ASF dual-hosted git repository. sjanc pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/mynewt-core.git
commit 0729eb829be95f3c0d33fc0f274c964d2f74524f Author: Szymon Janc <[email protected]> AuthorDate: Wed Oct 7 12:32:36 2026 +0200 sys/shell: Add unit test for shell bridge This adds test that verifies that argv passed to shell command executed by shell bridge is NULL terminated. Assisted-by: Claude:claude-opus-5-5 --- sys/shell/selftest/pkg.yml | 33 ++++++++ sys/shell/selftest/src/shell_test.c | 148 ++++++++++++++++++++++++++++++++++++ sys/shell/selftest/syscfg.yml | 23 ++++++ 3 files changed, 204 insertions(+) diff --git a/sys/shell/selftest/pkg.yml b/sys/shell/selftest/pkg.yml new file mode 100644 index 000000000..048a21e3c --- /dev/null +++ b/sys/shell/selftest/pkg.yml @@ -0,0 +1,33 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + + +pkg.name: sys/shell/selftest +pkg.type: unittest +pkg.description: "Unit tests for shell." +pkg.author: "Apache Mynewt <[email protected]>" +pkg.homepage: "http://mynewt.apache.org/" +pkg.keywords: + +pkg.deps: + - "@apache-mynewt-core/sys/console/stub" + - "@apache-mynewt-core/sys/log/stub" + - "@apache-mynewt-core/sys/shell" + - "@apache-mynewt-core/encoding/tinycbor" + - "@apache-mynewt-core/test/testutil" diff --git a/sys/shell/selftest/src/shell_test.c b/sys/shell/selftest/src/shell_test.c new file mode 100644 index 000000000..5be0b15b1 --- /dev/null +++ b/sys/shell/selftest/src/shell_test.c @@ -0,0 +1,148 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include <string.h> +#include "os/mynewt.h" +#include "testutil/testutil.h" +#include "shell/shell.h" +#include "mgmt/mgmt.h" +#include "tinycbor/cbor.h" + +#define TEST_ARGC_MAX MYNEWT_VAL(SHELL_CMD_ARGC_MAX) + +static int test_cmd_calls; +static int test_cmd_argc; +static bool test_cmd_argv_terminated; + +static int +test_cmd(int argc, char **argv) +{ + test_cmd_calls++; + test_cmd_argc = argc; + test_cmd_argv_terminated = argv[argc] == NULL; + + return 0; +} + +/* Fill unused stack with non-zero pattern so that uninitialized stack + * variables of the handler called next do not happen to be zero. + */ +static void __attribute__((noinline)) +test_dirty_stack(void) +{ + volatile uint8_t buf[2048]; + + memset((void *)buf, 0xa5, sizeof(buf)); +} + +/* Executes "shell exec" mgmt request with specified arguments. */ +static int +test_shell_exec(const char **args, int argc) +{ + const struct mgmt_handler *handler; + struct mgmt_ctxt ctxt; + CborEncoder req_enc; + CborEncoder map; + CborEncoder arr; + CborEncoder payload; + uint8_t req[256]; + uint8_t rsp[512]; + int rc; + int i; + + /* Encode request: {"argv": [args...]} */ + cbor_encoder_init(&req_enc, req, sizeof(req), 0); + rc = cbor_encoder_create_map(&req_enc, &map, CborIndefiniteLength); + rc |= cbor_encode_text_stringz(&map, "argv"); + rc |= cbor_encoder_create_array(&map, &arr, CborIndefiniteLength); + for (i = 0; i < argc; i++) { + rc |= cbor_encode_text_stringz(&arr, args[i]); + } + rc |= cbor_encoder_close_container(&map, &arr); + rc |= cbor_encoder_close_container(&req_enc, &map); + TEST_ASSERT_FATAL(rc == 0); + + rc = cbor_parser_init(req, cbor_encoder_get_buffer_size(&req_enc, req), 0, + &ctxt.parser, &ctxt.it); + TEST_ASSERT_FATAL(rc == 0); + + cbor_encoder_init(&ctxt.encoder, rsp, sizeof(rsp), 0); + rc = cbor_encoder_create_map(&ctxt.encoder, &payload, CborIndefiniteLength); + TEST_ASSERT_FATAL(rc == 0); + + handler = mgmt_find_handler(MGMT_GROUP_ID_SHELL, SHELL_NMGR_OP_EXEC); + TEST_ASSERT_FATAL(handler != NULL); + TEST_ASSERT_FATAL(handler->mh_write != NULL); + + test_cmd_calls = 0; + test_cmd_argc = -1; + test_cmd_argv_terminated = false; + + test_dirty_stack(); + + return handler->mh_write(&ctxt); +} + +TEST_CASE_SELF(shell_bridge_test_case_exec) +{ + const char *args[TEST_ARGC_MAX]; + int rc; + int i; + + shell_register_app_cmd_handler(test_cmd); + + /* Command without arguments. */ + args[0] = "testcmd"; + rc = test_shell_exec(args, 1); + TEST_ASSERT(rc == 0); + TEST_ASSERT(test_cmd_calls == 1); + TEST_ASSERT(test_cmd_argv_terminated); + + /* Command with arguments. */ + args[1] = "arg1"; + args[2] = "arg2"; + rc = test_shell_exec(args, 3); + TEST_ASSERT(rc == 0); + TEST_ASSERT(test_cmd_calls == 1); + TEST_ASSERT(test_cmd_argv_terminated); + + /* Maximum number of arguments. */ + for (i = 1; i < TEST_ARGC_MAX; i++) { + args[i] = "arg"; + } + rc = test_shell_exec(args, TEST_ARGC_MAX); + TEST_ASSERT(rc == 0); + TEST_ASSERT(test_cmd_calls == 1); + TEST_ASSERT(test_cmd_argv_terminated); +} + +TEST_SUITE(shell_bridge_test_suite) +{ + /* Single test case only as shell bridge group is registered on each + * sysinit. + */ + shell_bridge_test_case_exec(); +} + +int +main(int argc, char **argv) +{ + shell_bridge_test_suite(); + return tu_any_failed; +} diff --git a/sys/shell/selftest/syscfg.yml b/sys/shell/selftest/syscfg.yml new file mode 100644 index 000000000..008ed1cad --- /dev/null +++ b/sys/shell/selftest/syscfg.yml @@ -0,0 +1,23 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + + +syscfg.vals: + SHELL_BRIDGE: 1 + SHELL_TASK: 1
