From f395b74ed143b2edbaefb975182be56996a6ecf0 Mon Sep 17 00:00:00 2001 From: xxxxl_sun <31622273+sunxilin@users.noreply.github.com> Date: Fri, 13 Dec 2024 09:34:44 +0800 Subject: [PATCH] feat: provide a way to set property during testing (#395) * fix: make tester init_test_app_property_from_json internal * feat: support set property for single extension testing * feat: add more python standalone test case * fix: refine codes * fix: refine codes --------- Co-authored-by: Hu Yueh-Wei --- .../cpp/detail/test/extension_tester.h | 16 ++-- .../cpp/experimental/ten_client_proxy.h | 5 -- .../ten_runtime/test/extension_tester.h | 3 +- .../test/extension_tester_internal_accessor.h | 23 ++++++ .../ten_runtime/binding/cpp/ten.h | 1 + .../ten_runtime/test/extension_tester.h | 1 + .../interface/ten/libten_runtime_python.pyi | 6 +- .../binding/python/interface/ten/test.py | 8 +- .../python/native/test/extension_tester.c | 7 +- core/src/ten_runtime/test/extension_tester.c | 37 ++++++++- .../default_extension_python/extension.py | 5 ++ .../default_extension_python/tests/bin/start | 3 +- .../tests/test_set_property.py | 64 +++++++++++++++ .../smoke/standalone_test/basic_c.cc | 2 +- .../standalone_test/basic_graph_cross_app.cc | 4 +- .../smoke/standalone_test/on_cmd_c.cc | 2 +- .../set_property_for_single_ext.cc | 80 +++++++++++++++++++ 17 files changed, 238 insertions(+), 29 deletions(-) create mode 100644 core/include_internal/ten_runtime/binding/cpp/detail/test/extension_tester_internal_accessor.h create mode 100644 tests/ten_runtime/integration/python/standalone_test_python/default_extension_python/tests/test_set_property.py create mode 100644 tests/ten_runtime/smoke/standalone_test/set_property_for_single_ext.cc diff --git a/core/include/ten_runtime/binding/cpp/detail/test/extension_tester.h b/core/include/ten_runtime/binding/cpp/detail/test/extension_tester.h index 76f307e275..ac2ca882d5 100644 --- a/core/include/ten_runtime/binding/cpp/detail/test/extension_tester.h +++ b/core/include/ten_runtime/binding/cpp/detail/test/extension_tester.h @@ -19,6 +19,8 @@ namespace ten { +class extension_tester_internal_accessor_t; + class extension_tester_t { public: virtual ~extension_tester_t() { @@ -36,9 +38,11 @@ class extension_tester_t { extension_tester_t &operator=(const extension_tester_t &&) = delete; // @} - void set_test_mode_single(const char *addon_name) { + void set_test_mode_single(const char *addon_name, + const char *property_json_str = nullptr) { TEN_ASSERT(addon_name, "Invalid argument."); - ten_extension_tester_set_test_mode_single(c_extension_tester, addon_name); + ten_extension_tester_set_test_mode_single(c_extension_tester, addon_name, + property_json_str); } void set_test_mode_graph(const char *graph_json) { @@ -51,12 +55,6 @@ class extension_tester_t { ten_extension_tester_add_addon_base_dir(c_extension_tester, addon_path); } - void init_test_app_property_from_json(const char *property_json_str) { - TEN_ASSERT(property_json_str, "Invalid argument."); - ten_extension_tester_init_test_app_property_from_json(c_extension_tester, - property_json_str); - } - bool run(error_t *err = nullptr) { TEN_ASSERT(c_extension_tester, "Should not happen."); return ten_extension_tester_run(c_extension_tester); @@ -102,6 +100,8 @@ class extension_tester_t { std::unique_ptr video_frame) {} private: + friend class extension_tester_internal_accessor_t; + void invoke_cpp_extension_tester_on_start( ten_env_tester_t &cpp_ten_env_tester) { on_start(cpp_ten_env_tester); diff --git a/core/include/ten_runtime/binding/cpp/experimental/ten_client_proxy.h b/core/include/ten_runtime/binding/cpp/experimental/ten_client_proxy.h index e443ab0087..78e5edeb6c 100644 --- a/core/include/ten_runtime/binding/cpp/experimental/ten_client_proxy.h +++ b/core/include/ten_runtime/binding/cpp/experimental/ten_client_proxy.h @@ -221,11 +221,6 @@ class ten_client_proxy_t { impl_.add_addon_base_dir(addon_path); } - void init_app_property_json(const char *app_property_json) { - TEN_ASSERT(app_property_json, "Invalid argument."); - impl_.init_test_app_property_from_json(app_property_json); - } - void start_graph(const char *graph_json) { TEN_ASSERT(graph_json, "Invalid argument."); impl_.set_test_mode_graph(graph_json); diff --git a/core/include/ten_runtime/test/extension_tester.h b/core/include/ten_runtime/test/extension_tester.h index 295abc1662..3da5c65b69 100644 --- a/core/include/ten_runtime/test/extension_tester.h +++ b/core/include/ten_runtime/test/extension_tester.h @@ -52,7 +52,8 @@ TEN_RUNTIME_API void ten_extension_tester_destroy(ten_extension_tester_t *self); // to this extension, and all outputs from the extension will be sent back to // the tester. TEN_RUNTIME_API void ten_extension_tester_set_test_mode_single( - ten_extension_tester_t *self, const char *addon_name); + ten_extension_tester_t *self, const char *addon_name, + const char *property_json_str); // Testing a complete graph which must contain exactly one proxy extension. All // messages input by the tester will be directed to this proxy extension, and diff --git a/core/include_internal/ten_runtime/binding/cpp/detail/test/extension_tester_internal_accessor.h b/core/include_internal/ten_runtime/binding/cpp/detail/test/extension_tester_internal_accessor.h new file mode 100644 index 0000000000..5ee45b8956 --- /dev/null +++ b/core/include_internal/ten_runtime/binding/cpp/detail/test/extension_tester_internal_accessor.h @@ -0,0 +1,23 @@ +// +// Copyright © 2024 Agora +// This file is part of TEN Framework, an open source project. +// Licensed under the Apache License, Version 2.0, with certain conditions. +// Refer to the "LICENSE" file in the root directory for more information. +// +#pragma once + +#include "ten_runtime/binding/cpp/detail/test/extension_tester.h" + +namespace ten { + +class extension_tester_internal_accessor_t { + public: + static void init_test_app_property_from_json(extension_tester_t &tester, + const char *property_json_str) { + TEN_ASSERT(property_json_str, "Invalid argument."); + ten_extension_tester_init_test_app_property_from_json( + tester.c_extension_tester, property_json_str); + } +}; + +} // namespace ten diff --git a/core/include_internal/ten_runtime/binding/cpp/ten.h b/core/include_internal/ten_runtime/binding/cpp/ten.h index e098beac4c..73e3c731b7 100644 --- a/core/include_internal/ten_runtime/binding/cpp/ten.h +++ b/core/include_internal/ten_runtime/binding/cpp/ten.h @@ -15,6 +15,7 @@ #include "include_internal/ten_runtime/binding/cpp/detail/msg/cmd/timer.h" // IWYU pragma: export #include "include_internal/ten_runtime/binding/cpp/detail/ten_env_impl.h" // IWYU pragma: export #include "include_internal/ten_runtime/binding/cpp/detail/ten_env_internal_accessor.h" // IWYU pragma: export +#include "include_internal/ten_runtime/binding/cpp/detail/test/extension_tester_internal_accessor.h" // IWYU pragma: export #include "ten_runtime/addon/extension/extension.h" // IWYU pragma: export #include "ten_runtime/binding/cpp/detail/addon.h" // IWYU pragma: export #include "ten_runtime/binding/cpp/detail/addon_manager.h" // IWYU pragma: export diff --git a/core/include_internal/ten_runtime/test/extension_tester.h b/core/include_internal/ten_runtime/test/extension_tester.h index 357806dc53..07a3f6723b 100644 --- a/core/include_internal/ten_runtime/test/extension_tester.h +++ b/core/include_internal/ten_runtime/test/extension_tester.h @@ -37,6 +37,7 @@ struct ten_extension_tester_t { union { struct { ten_string_t addon_name; + ten_string_t property_json; } addon; struct { diff --git a/core/src/ten_runtime/binding/python/interface/ten/libten_runtime_python.pyi b/core/src/ten_runtime/binding/python/interface/ten/libten_runtime_python.pyi index 7cc470eddb..09753fa990 100644 --- a/core/src/ten_runtime/binding/python/interface/ten/libten_runtime_python.pyi +++ b/core/src/ten_runtime/binding/python/interface/ten/libten_runtime_python.pyi @@ -27,7 +27,7 @@ class _Msg: extension_group: str | None, extension: str | None, ) -> None: ... - def set_property_from_json(self, path: str, json: str) -> None: ... + def set_property_from_json(self, path: str, json_str: str) -> None: ... def get_property_to_json(self, path: str | None = None) -> str: ... def get_property_int(self, path: str) -> int: ... def set_property_int(self, path: str, value: int) -> None: ... @@ -204,7 +204,9 @@ class _TenEnvTester: def stop_test(self) -> None: ... class _ExtensionTester: - def set_test_mode_single(self, addon_name: str) -> None: ... + def set_test_mode_single( + self, addon_name: str, property_json_str: str | None + ) -> None: ... def run(self) -> None: ... def _register_addon_as_extension( diff --git a/core/src/ten_runtime/binding/python/interface/ten/test.py b/core/src/ten_runtime/binding/python/interface/ten/test.py index 31db34f4d5..517f3bab2d 100644 --- a/core/src/ten_runtime/binding/python/interface/ten/test.py +++ b/core/src/ten_runtime/binding/python/interface/ten/test.py @@ -85,8 +85,12 @@ def add_addon_base_dir(self, base_dir: str) -> None: self.addon_base_dirs.append(base_dir) @final - def set_test_mode_single(self, addon_name: str) -> None: - return _ExtensionTester.set_test_mode_single(self, addon_name) + def set_test_mode_single( + self, addon_name: str, property_json_str: str | None = None + ) -> None: + return _ExtensionTester.set_test_mode_single( + self, addon_name, property_json_str + ) @final def run(self) -> None: diff --git a/core/src/ten_runtime/binding/python/native/test/extension_tester.c b/core/src/ten_runtime/binding/python/native/test/extension_tester.c index e1f2036166..0742b22cf8 100644 --- a/core/src/ten_runtime/binding/python/native/test/extension_tester.c +++ b/core/src/ten_runtime/binding/python/native/test/extension_tester.c @@ -307,20 +307,21 @@ static PyObject *ten_py_extension_tester_set_test_mode_single(PyObject *self, ten_py_extension_tester_check_integrity(py_extension_tester), "Invalid argument."); - if (PyTuple_GET_SIZE(args) != 1) { + if (PyTuple_GET_SIZE(args) != 2) { return ten_py_raise_py_value_error_exception( "Invalid argument count when extension_tester.set_test_mode_single."); } const char *addon_name = NULL; - if (!PyArg_ParseTuple(args, "s", &addon_name)) { + const char *property_json_str = NULL; + if (!PyArg_ParseTuple(args, "sz", &addon_name, &property_json_str)) { return ten_py_raise_py_value_error_exception( "Failed to parse arguments when " "extension_tester.set_test_mode_single."); } ten_extension_tester_set_test_mode_single( - py_extension_tester->c_extension_tester, addon_name); + py_extension_tester->c_extension_tester, addon_name, property_json_str); Py_RETURN_NONE; } diff --git a/core/src/ten_runtime/test/extension_tester.c b/core/src/ten_runtime/test/extension_tester.c index 3fdc5e0ad5..85f9573e72 100644 --- a/core/src/ten_runtime/test/extension_tester.c +++ b/core/src/ten_runtime/test/extension_tester.c @@ -27,7 +27,9 @@ #include "ten_utils/container/list.h" #include "ten_utils/container/list_str.h" #include "ten_utils/io/runloop.h" +#include "ten_utils/lib/error.h" #include "ten_utils/lib/event.h" +#include "ten_utils/lib/json.h" #include "ten_utils/lib/signature.h" #include "ten_utils/lib/smart_ptr.h" #include "ten_utils/lib/string.h" @@ -96,7 +98,8 @@ ten_extension_tester_t *ten_extension_tester_create( } void ten_extension_tester_set_test_mode_single(ten_extension_tester_t *self, - const char *addon_name) { + const char *addon_name, + const char *property_json_str) { TEN_ASSERT(self && ten_extension_tester_check_integrity(self, true), "Invalid argument."); TEN_ASSERT(addon_name, "Invalid argument."); @@ -104,6 +107,28 @@ void ten_extension_tester_set_test_mode_single(ten_extension_tester_t *self, self->test_mode = TEN_EXTENSION_TESTER_TEST_MODE_SINGLE; ten_string_init_from_c_str(&self->test_target.addon.addon_name, addon_name, strlen(addon_name)); + + if (property_json_str && strlen(property_json_str) > 0) { + ten_error_t err; + ten_error_init(&err); + + ten_json_t *json = ten_json_from_string(property_json_str, &err); + if (json) { + ten_json_destroy(json); + } else { + TEN_ASSERT(0, "Failed to parse property json: %s", + ten_error_errmsg(&err)); + } + + ten_error_deinit(&err); + + ten_string_init_from_c_str(&self->test_target.addon.property_json, + property_json_str, strlen(property_json_str)); + } else { + const char *empty_json = "{}"; + ten_string_init_from_c_str(&self->test_target.addon.property_json, + empty_json, strlen(empty_json)); + } } void ten_extension_tester_set_test_mode_graph(ten_extension_tester_t *self, @@ -154,6 +179,7 @@ static void ten_extension_tester_destroy_test_target( if (self->test_mode == TEN_EXTENSION_TESTER_TEST_MODE_SINGLE) { ten_string_deinit(&self->test_target.addon.addon_name); + ten_string_deinit(&self->test_target.addon.property_json); } else if (self->test_mode == TEN_EXTENSION_TESTER_TEST_MODE_GRAPH) { ten_string_deinit(&self->test_target.graph.graph_json); } @@ -233,6 +259,9 @@ static void ten_extension_tester_create_and_start_graph( const char *addon_name = ten_string_get_raw_str(&self->test_target.addon.addon_name); + const char *property_json_str = + ten_string_get_raw_str(&self->test_target.addon.property_json); + ten_string_t graph_json_str; ten_string_init_formatted(&graph_json_str, "{\ @@ -247,7 +276,8 @@ static void ten_extension_tester_create_and_start_graph( \"name\": \"%s\",\ \"addon\": \"%s\",\ \"extension_group\": \"test_extension_group_2\",\ - \"app\": \"localhost\"\ + \"app\": \"localhost\",\ + \"property\": %s\ }],\ \"connections\": [{\ \"app\": \"localhost\",\ @@ -323,8 +353,9 @@ static void ten_extension_tester_create_and_start_graph( }]\ }]\ }", + addon_name, addon_name, property_json_str, addon_name, addon_name, addon_name, addon_name, - addon_name, addon_name, addon_name); + addon_name); rc = ten_cmd_start_graph_set_graph_from_json_str( start_graph_cmd, ten_string_get_raw_str(&graph_json_str), NULL); TEN_ASSERT(rc, "Should not happen."); diff --git a/tests/ten_runtime/integration/python/standalone_test_python/default_extension_python/extension.py b/tests/ten_runtime/integration/python/standalone_test_python/default_extension_python/extension.py index ed5d722524..4bda900401 100644 --- a/tests/ten_runtime/integration/python/standalone_test_python/default_extension_python/extension.py +++ b/tests/ten_runtime/integration/python/standalone_test_python/default_extension_python/extension.py @@ -63,3 +63,8 @@ def on_cmd(self, ten_env: TenEnv, cmd: Cmd) -> None: if cmd.get_name() == "hello_world": self.cached_cmd = cmd self.return_if_all_data_received(ten_env) + elif cmd.get_name() == "greeting": + greeting = ten_env.get_property_string("greeting") + cmd_result = CmdResult.create(StatusCode.OK) + cmd_result.set_property_string("detail", greeting) + ten_env.return_result(cmd_result, cmd) diff --git a/tests/ten_runtime/integration/python/standalone_test_python/default_extension_python/tests/bin/start b/tests/ten_runtime/integration/python/standalone_test_python/default_extension_python/tests/bin/start index 070a90873e..e3352ec7dd 100755 --- a/tests/ten_runtime/integration/python/standalone_test_python/default_extension_python/tests/bin/start +++ b/tests/ten_runtime/integration/python/standalone_test_python/default_extension_python/tests/bin/start @@ -18,4 +18,5 @@ export PYTHONPATH=.ten/app/ten_packages/system/ten_runtime_python/lib:.ten/app/t # # Refer to https://github.com/pytorch/pytorch/issues/102360?from_wecom=1#issuecomment-1708989096 -python -m pytest -s tests/ +python -m pytest -s tests/test_basic.py +python -m pytest -s tests/test_set_property.py diff --git a/tests/ten_runtime/integration/python/standalone_test_python/default_extension_python/tests/test_set_property.py b/tests/ten_runtime/integration/python/standalone_test_python/default_extension_python/tests/test_set_property.py new file mode 100644 index 0000000000..ae4c7b9216 --- /dev/null +++ b/tests/ten_runtime/integration/python/standalone_test_python/default_extension_python/tests/test_set_property.py @@ -0,0 +1,64 @@ +# +# Copyright © 2024 Agora +# This file is part of TEN Framework, an open source project. +# Licensed under the Apache License, Version 2.0, with certain conditions. +# Refer to the "LICENSE" file in the root directory for more information. +# +from pathlib import Path +from typing import Optional +from ten import ( + ExtensionTester, + TenEnvTester, + Cmd, + CmdResult, + StatusCode, + TenError, +) + + +class ExtensionTesterSetProperty(ExtensionTester): + def check_greeting( + self, + ten_env: TenEnvTester, + result: Optional[CmdResult], + error: Optional[TenError], + ): + if error is not None: + assert False, error + + assert result is not None + + statusCode = result.get_status_code() + print("receive hello_world, status:" + str(statusCode)) + + if statusCode == StatusCode.OK: + detail = result.get_property_string("detail") + assert detail == "hola" + + ten_env.stop_test() + + def on_start(self, ten_env: TenEnvTester) -> None: + cmd = Cmd.create("greeting") + + ten_env.send_cmd( + cmd, + lambda ten_env, result, error: self.check_greeting( + ten_env, result, error + ), + ) + + print("tester on_start_done") + ten_env.on_start_done() + + +def test_set_property(): + tester = ExtensionTesterSetProperty() + tester.add_addon_base_dir(str(Path(__file__).resolve().parent.parent)) + tester.set_test_mode_single( + "default_extension_python", '{"greeting": "hola"}' + ) + tester.run() + + +if __name__ == "__main__": + test_set_property() diff --git a/tests/ten_runtime/smoke/standalone_test/basic_c.cc b/tests/ten_runtime/smoke/standalone_test/basic_c.cc index d496baa533..409ccec5e3 100644 --- a/tests/ten_runtime/smoke/standalone_test/basic_c.cc +++ b/tests/ten_runtime/smoke/standalone_test/basic_c.cc @@ -76,7 +76,7 @@ TEST(StandaloneTest, BasicC) { // NOLINT ten_extension_tester_t *tester = ten_extension_tester_create( ten_extension_tester_on_start, nullptr, nullptr, nullptr, nullptr); ten_extension_tester_set_test_mode_single( - tester, "standalone_test_basic_c__test_extension_1"); + tester, "standalone_test_basic_c__test_extension_1", nullptr); bool rc = ten_extension_tester_run(tester); TEN_ASSERT(rc, "Should not happen."); diff --git a/tests/ten_runtime/smoke/standalone_test/basic_graph_cross_app.cc b/tests/ten_runtime/smoke/standalone_test/basic_graph_cross_app.cc index 6fa6ebc9a6..2139260a0d 100644 --- a/tests/ten_runtime/smoke/standalone_test/basic_graph_cross_app.cc +++ b/tests/ten_runtime/smoke/standalone_test/basic_graph_cross_app.cc @@ -152,8 +152,8 @@ TEST(StandaloneTest, BasicGraphCrossApp) { // NOLINT auto *tester = new extension_tester_1(); - tester->init_test_app_property_from_json( - R"({ + ten::extension_tester_internal_accessor_t::init_test_app_property_from_json( + *tester, R"({ "_ten": { "uri": "client:aaa" } diff --git a/tests/ten_runtime/smoke/standalone_test/on_cmd_c.cc b/tests/ten_runtime/smoke/standalone_test/on_cmd_c.cc index 1f8fb8b3eb..82a7b7befc 100644 --- a/tests/ten_runtime/smoke/standalone_test/on_cmd_c.cc +++ b/tests/ten_runtime/smoke/standalone_test/on_cmd_c.cc @@ -110,7 +110,7 @@ TEST(StandaloneTest, OnCmdC) { // NOLINT ten_extension_tester_on_start, ten_extension_tester_on_cmd, nullptr, nullptr, nullptr); ten_extension_tester_set_test_mode_single( - tester, "standalone_test_on_cmd_c__test_extension_1"); + tester, "standalone_test_on_cmd_c__test_extension_1", nullptr); bool rc = ten_extension_tester_run(tester); TEN_ASSERT(rc, "Should not happen."); diff --git a/tests/ten_runtime/smoke/standalone_test/set_property_for_single_ext.cc b/tests/ten_runtime/smoke/standalone_test/set_property_for_single_ext.cc new file mode 100644 index 0000000000..e959097ae3 --- /dev/null +++ b/tests/ten_runtime/smoke/standalone_test/set_property_for_single_ext.cc @@ -0,0 +1,80 @@ +// +// Copyright © 2024 Agora +// This file is part of TEN Framework, an open source project. +// Licensed under the Apache License, Version 2.0, with certain conditions. +// Refer to the "LICENSE" file in the root directory for more information. +// +#include "gtest/gtest.h" +#include "include_internal/ten_runtime/binding/cpp/ten.h" +#include "nlohmann/json_fwd.hpp" +#include "ten_runtime/binding/cpp/detail/extension.h" +#include "ten_runtime/common/status_code.h" +#include "ten_utils/lang/cpp/lib/value.h" +#include "tests/ten_runtime/smoke/extension_test/util/binding/cpp/check.h" + +namespace { + +// This part is the extension codes written by the developer, maintained in its +// final release form, and will not change due to testing requirements. + +class test_extension_1 : public ten::extension_t { + public: + explicit test_extension_1(const std::string &name) : ten::extension_t(name) {} + + void on_cmd(ten::ten_env_t &ten_env, + std::unique_ptr cmd) override { + if (std::string(cmd->get_name()) == "hello_world") { + auto greeting_words = ten_env.get_property_string("greeting_words"); + + auto cmd_result = ten::cmd_result_t::create(TEN_STATUS_CODE_OK); + cmd_result->set_property("detail", greeting_words); + bool rc = ten_env.return_result(std::move(cmd_result), std::move(cmd)); + EXPECT_EQ(rc, true); + } + } +}; + +TEN_CPP_REGISTER_ADDON_AS_EXTENSION( + standalone_test_set_property_for_single_ext__test_extension_1, + test_extension_1); + +} // namespace + +namespace { + +class extension_tester_1 : public ten::extension_tester_t { + public: + void on_start(ten::ten_env_tester_t &ten_env) override { + // Send the first command to the extension. + auto new_cmd = ten::cmd_t::create("hello_world"); + + ten_env.send_cmd( + std::move(new_cmd), + [](ten::ten_env_tester_t &ten_env, + std::unique_ptr result, ten::error_t *err) { + if (result->get_status_code() == TEN_STATUS_CODE_OK) { + auto detail = result->get_property_string("detail"); + EXPECT_EQ(detail, "nice to meet you"); + ten_env.stop_test(); + } + }); + + ten_env.on_start_done(); + } +}; + +} // namespace + +TEST(StandaloneTest, SetPropertyForSingleExt) { // NOLINT + auto *tester = new extension_tester_1(); + tester->set_test_mode_single( + "standalone_test_set_property_for_single_ext__test_extension_1", + R"({ + "greeting_words": "nice to meet you" + })"); + + bool rc = tester->run(); + TEN_ASSERT(rc, "Should not happen."); + + delete tester; +}