diff --git a/core/src/ten_rust/src/json_schema/data/manifest.schema.json b/core/src/ten_rust/src/json_schema/data/manifest.schema.json index 07010d4ca2..5e2b2adf1b 100644 --- a/core/src/ten_rust/src/json_schema/data/manifest.schema.json +++ b/core/src/ten_rust/src/json_schema/data/manifest.schema.json @@ -473,7 +473,6 @@ "name", "version" ], - "additionalProperties": false, "if": { "anyOf": [ { diff --git a/core/src/ten_rust/src/json_schema/data/property.schema.json b/core/src/ten_rust/src/json_schema/data/property.schema.json index 38ff0ab016..87bc755fc6 100644 --- a/core/src/ten_rust/src/json_schema/data/property.schema.json +++ b/core/src/ten_rust/src/json_schema/data/property.schema.json @@ -499,7 +499,6 @@ }, "tenConfiguration": { "type": "object", - "additionalProperties": false, "properties": { "uri": { "$ref": "#/$defs/non_empty_non_localhost_uri" diff --git a/tests/ten_runtime/integration/cpp/BUILD.gn b/tests/ten_runtime/integration/cpp/BUILD.gn index b78c38f44a..884738fde2 100644 --- a/tests/ten_runtime/integration/cpp/BUILD.gn +++ b/tests/ten_runtime/integration/cpp/BUILD.gn @@ -15,6 +15,7 @@ group("cpp") { "hello_world", "large_result", "long_running", + "metadata_additional_field", "restful", ] diff --git a/tests/ten_runtime/integration/cpp/metadata_additional_field/BUILD.gn b/tests/ten_runtime/integration/cpp/metadata_additional_field/BUILD.gn new file mode 100644 index 0000000000..f4deb85145 --- /dev/null +++ b/tests/ten_runtime/integration/cpp/metadata_additional_field/BUILD.gn @@ -0,0 +1,86 @@ +# +# Copyright © 2025 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. +# +import("//build/ten_runtime/feature/test.gni") +import("//build/ten_runtime/ten.gni") + +ten_package_test_prepare_app("metadata_additional_field_app") { + src_app = "default_app_cpp" + src_app_language = "cpp" + generated_app_src_root_dir_name = "metadata_additional_field_app" + + replace_paths_after_install_app = [ + "metadata_additional_field_app/manifest.json", + "metadata_additional_field_app/property.json", + ] + + replace_paths_after_install_all = [ + "metadata_additional_field_app/ten_packages/extension/default_extension_cpp/manifest.json", + "metadata_additional_field_app/ten_packages/extension/default_extension_cpp/property.json", + "metadata_additional_field_app/ten_packages/extension/default_extension_cpp/src/main.cc", + "metadata_additional_field_app/ten_packages/extension/default_extension_cpp/BUILD.gn", + ] + + if (ten_enable_ten_manager) { + deps = [ + "//core/src/ten_manager", + "//core/src/ten_runtime:upload_ten_runtime_system_package_to_server", + "//packages/core_apps/default_app_cpp:upload_default_app_cpp_to_server", + "//packages/core_extensions/default_extension_cpp:upload_default_extension_cpp_to_server", + "//packages/core_protocols/msgpack:upload_protocol_msgpack_to_server", + ] + } +} + +ten_package_test_prepare_client("metadata_additional_field_app_client") { + sources = [ "client/client.cc" ] + include_dirs = [ + "//core/src", + "//core", + "//packages", + "//tests", + ] + deps = [ + "//core/src/ten_runtime", + "//packages/core_protocols/msgpack:msgpack_files", + "//tests/common/client:msgpack_client", + "//third_party/msgpack:msgpackc", + "//third_party/nlohmann_json", + ] +} + +ten_package_test_prepare_auxiliary_resources( + "metadata_additional_field_app_test_files") { + resources = [ + "__init__.py", + "test_case.py", + ] + + utils_files = exec_script("//.gnfiles/build/scripts/glob_file.py", + [ + "--dir", + rebase_path("//tests/utils/**/*"), + "--dir-base", + rebase_path("//tests/utils"), + "--recursive", + "--only-output-file", + ], + "json") + + foreach(utils_file, utils_files) { + utils_file_rel_path = utils_file.relative_path + resources += + [ "//tests/utils/${utils_file_rel_path}=>utils/${utils_file_rel_path}" ] + } +} + +group("metadata_additional_field") { + deps = [ + ":metadata_additional_field_app", + ":metadata_additional_field_app_client", + ":metadata_additional_field_app_test_files", + ] +} diff --git a/tests/ten_runtime/integration/cpp/metadata_additional_field/__init__.py b/tests/ten_runtime/integration/cpp/metadata_additional_field/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/ten_runtime/integration/cpp/metadata_additional_field/client/client.cc b/tests/ten_runtime/integration/cpp/metadata_additional_field/client/client.cc new file mode 100644 index 0000000000..cb862417d2 --- /dev/null +++ b/tests/ten_runtime/integration/cpp/metadata_additional_field/client/client.cc @@ -0,0 +1,43 @@ +// +// Copyright © 2025 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 + +#include "tests/common/client/cpp/msgpack_tcp.h" + +int main(int argc, char **argv) { + // Create a client and connect to the app. + auto *client = new ten::msgpack_tcp_client_t("msgpack://127.0.0.1:8001/"); + + // Send graph. + auto start_graph_cmd = ten::cmd_start_graph_t::create(); + start_graph_cmd->set_graph_from_json(R"({ + "nodes": [{ + "type": "extension", + "name": "test_extension", + "addon": "default_extension_cpp", + "app": "msgpack://127.0.0.1:8001/", + "extension_group": "test_extension_group" + }] + })"); + auto cmd_result = + client->send_cmd_and_recv_result(std::move(start_graph_cmd)); + TEN_ASSERT(TEN_STATUS_CODE_OK == cmd_result->get_status_code(), + "Should not happen."); + + // Send a user-defined 'hello world' command. + auto hello_world_cmd = ten::cmd_t::create("hello_world"); + hello_world_cmd->set_dest("msgpack://127.0.0.1:8001/", nullptr, + "test_extension"); + cmd_result = client->send_cmd_and_recv_result(std::move(hello_world_cmd)); + TEN_ASSERT(TEN_STATUS_CODE_OK == cmd_result->get_status_code(), + "Should not happen."); + TEN_ASSERT(static_cast("hello world, too") == + cmd_result->get_property_string("detail"), + "Should not happen."); + + delete client; +} diff --git a/tests/ten_runtime/integration/cpp/metadata_additional_field/metadata_additional_field_app/manifest.json b/tests/ten_runtime/integration/cpp/metadata_additional_field/metadata_additional_field_app/manifest.json new file mode 100644 index 0000000000..841126bc1e --- /dev/null +++ b/tests/ten_runtime/integration/cpp/metadata_additional_field/metadata_additional_field_app/manifest.json @@ -0,0 +1,29 @@ +{ + "type": "app", + "name": "default_app_cpp", + "version": "0.10.2", + "dependencies": [ + { + "type": "system", + "name": "ten_runtime", + "version": "0.10.2" + }, + { + "type": "extension", + "name": "default_extension_cpp", + "version": "0.10.2" + }, + { + "type": "protocol", + "name": "msgpack", + "version": "0.10.2" + } + ], + "api": { + "property": { + "hello": { + "type": "string" + } + } + } +} \ No newline at end of file diff --git a/tests/ten_runtime/integration/cpp/metadata_additional_field/metadata_additional_field_app/property.json b/tests/ten_runtime/integration/cpp/metadata_additional_field/metadata_additional_field_app/property.json new file mode 100644 index 0000000000..61494e733d --- /dev/null +++ b/tests/ten_runtime/integration/cpp/metadata_additional_field/metadata_additional_field_app/property.json @@ -0,0 +1,20 @@ +{ + "ten": { + "uri": "msgpack://127.0.0.1:8001/", + "predefined_graphs": [ + { + "name": "default", + "auto_start": false, + "nodes": [ + { + "type": "extension", + "name": "default_extension_cpp", + "addon": "default_extension_cpp", + "extension_group": "default_extension_group" + } + ] + } + ] + }, + "hello": "world" +} \ No newline at end of file diff --git a/tests/ten_runtime/integration/cpp/metadata_additional_field/metadata_additional_field_app/ten_packages/extension/default_extension_cpp/BUILD.gn b/tests/ten_runtime/integration/cpp/metadata_additional_field/metadata_additional_field_app/ten_packages/extension/default_extension_cpp/BUILD.gn new file mode 100644 index 0000000000..9e148e2c64 --- /dev/null +++ b/tests/ten_runtime/integration/cpp/metadata_additional_field/metadata_additional_field_app/ten_packages/extension/default_extension_cpp/BUILD.gn @@ -0,0 +1,20 @@ +# +# Copyright © 2025 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. +# +import("//build/feature/ten_package.gni") + +ten_package("default_extension_cpp") { + package_kind = "extension" + enable_build = true + + resources = [ + "manifest.json", + "property.json", + ] + + sources = [ "src/main.cc" ] + include_dirs = [ "//core/include" ] +} diff --git a/tests/ten_runtime/integration/cpp/metadata_additional_field/metadata_additional_field_app/ten_packages/extension/default_extension_cpp/manifest.json b/tests/ten_runtime/integration/cpp/metadata_additional_field/metadata_additional_field_app/ten_packages/extension/default_extension_cpp/manifest.json new file mode 100644 index 0000000000..e507bd37b3 --- /dev/null +++ b/tests/ten_runtime/integration/cpp/metadata_additional_field/metadata_additional_field_app/ten_packages/extension/default_extension_cpp/manifest.json @@ -0,0 +1,32 @@ +{ + "type": "extension", + "name": "default_extension_cpp", + "version": "0.10.2", + "dependencies": [ + { + "type": "system", + "name": "ten_runtime", + "version": "0.10.2" + } + ], + "package": { + "include": [ + "**" + ] + }, + "api": { + "cmd_in": [ + { + "name": "hello_world" + } + ], + "cmd_out": [], + "data_in": [], + "data_out": [], + "video_frame_in": [], + "video_frame_out": [], + "audio_frame_in": [], + "audio_frame_out": [] + }, + "unknown_field": "unknown_value" +} \ No newline at end of file diff --git a/tests/ten_runtime/integration/cpp/metadata_additional_field/metadata_additional_field_app/ten_packages/extension/default_extension_cpp/property.json b/tests/ten_runtime/integration/cpp/metadata_additional_field/metadata_additional_field_app/ten_packages/extension/default_extension_cpp/property.json new file mode 100644 index 0000000000..6be646ede5 --- /dev/null +++ b/tests/ten_runtime/integration/cpp/metadata_additional_field/metadata_additional_field_app/ten_packages/extension/default_extension_cpp/property.json @@ -0,0 +1,6 @@ +{ + "hello": "world", + "ten": { + "unknown_field": "unknown_value" + } +} \ No newline at end of file diff --git a/tests/ten_runtime/integration/cpp/metadata_additional_field/metadata_additional_field_app/ten_packages/extension/default_extension_cpp/src/main.cc b/tests/ten_runtime/integration/cpp/metadata_additional_field/metadata_additional_field_app/ten_packages/extension/default_extension_cpp/src/main.cc new file mode 100644 index 0000000000..3d6bdcbade --- /dev/null +++ b/tests/ten_runtime/integration/cpp/metadata_additional_field/metadata_additional_field_app/ten_packages/extension/default_extension_cpp/src/main.cc @@ -0,0 +1,39 @@ +// +// Copyright © 2025 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 +#include + +#include "ten_runtime/binding/cpp/ten.h" + +class test_extension : public ten::extension_t { + public: + explicit test_extension(const char *name) : ten::extension_t(name) {} + + void on_init(ten::ten_env_t &ten_env) override { ten_env.on_init_done(); } + + void on_start(ten::ten_env_t &ten_env) override { + // The property.json will be loaded by default during `on_init` phase, so + // the property `hello` should be available here. + auto prop = ten_env.get_property_string("hello"); + if (prop != "world") { + assert(0 && "Should not happen."); + } + + ten_env.on_start_done(); + } + + void on_cmd(ten::ten_env_t &ten_env, + std::unique_ptr cmd) override { + if (cmd->get_name() == "hello_world") { + auto cmd_result = ten::cmd_result_t::create(TEN_STATUS_CODE_OK, *cmd); + cmd_result->set_property("detail", "hello world, too"); + ten_env.return_result(std::move(cmd_result)); + } + } +}; + +TEN_CPP_REGISTER_ADDON_AS_EXTENSION(default_extension_cpp, test_extension); diff --git a/tests/ten_runtime/integration/cpp/metadata_additional_field/test_case.py b/tests/ten_runtime/integration/cpp/metadata_additional_field/test_case.py new file mode 100644 index 0000000000..889f3f3fe4 --- /dev/null +++ b/tests/ten_runtime/integration/cpp/metadata_additional_field/test_case.py @@ -0,0 +1,159 @@ +""" +Test metadata_additional_field_app. +""" + +import subprocess +import os +import sys +from sys import stdout +from .utils import msgpack, build_config, build_pkg, fs_utils + + +def test_metadata_additional_field_app(): + """Test client and app server.""" + base_path = os.path.dirname(os.path.abspath(__file__)) + root_dir = os.path.join(base_path, "../../../../../") + + my_env = os.environ.copy() + + app_dir_name = "metadata_additional_field_app" + app_root_path = os.path.join(base_path, app_dir_name) + app_language = "cpp" + + build_config_args = build_config.parse_build_config( + os.path.join(root_dir, "tgn_args.txt"), + ) + + if build_config_args.ten_enable_integration_tests_prebuilt is False: + # Before starting, cleanup the old app package. + fs_utils.remove_tree(app_root_path) + + print(f'Assembling and building package "{app_dir_name}".') + + rc = build_pkg.prepare_and_build_app( + build_config_args, + root_dir, + base_path, + app_dir_name, + app_language, + ) + if rc != 0: + assert False, "Failed to build package." + + tman_install_cmd = [ + os.path.join(root_dir, "ten_manager/bin/tman"), + "--config-file", + os.path.join(root_dir, "tests/local_registry/config.json"), + "--yes", + "install", + ] + + tman_install_process = subprocess.Popen( + tman_install_cmd, + stdout=stdout, + stderr=subprocess.STDOUT, + env=my_env, + cwd=app_root_path, + ) + tman_install_process.wait() + return_code = tman_install_process.returncode + if return_code != 0: + assert False, "Failed to install package." + + if sys.platform == "win32": + my_env["PATH"] = ( + os.path.join( + base_path, + "metadata_additional_field_app/ten_packages/system/ten_runtime/lib", + ) + + ";" + + my_env["PATH"] + ) + server_cmd = os.path.join( + base_path, "metadata_additional_field_app/bin/metadata_additional_field_app.exe" + ) + client_cmd = os.path.join(base_path, "metadata_additional_field_app_client.exe") + elif sys.platform == "darwin": + # client depends on some libraries in the TEN app. + my_env["DYLD_LIBRARY_PATH"] = os.path.join( + base_path, + "metadata_additional_field_app/ten_packages/system/ten_runtime/lib", + ) + server_cmd = os.path.join( + base_path, "metadata_additional_field_app/bin/metadata_additional_field_app" + ) + client_cmd = os.path.join(base_path, "metadata_additional_field_app_client") + else: + # client depends on some libraries in the TEN app. + my_env["LD_LIBRARY_PATH"] = os.path.join( + base_path, + "metadata_additional_field_app/ten_packages/system/ten_runtime/lib", + ) + server_cmd = os.path.join( + base_path, "metadata_additional_field_app/bin/metadata_additional_field_app" + ) + client_cmd = os.path.join(base_path, "metadata_additional_field_app_client") + + if ( + build_config_args.enable_sanitizer + and not build_config_args.is_clang + ): + libasan_path = os.path.join( + base_path, + ( + "metadata_additional_field_app/ten_packages/system/" + "ten_runtime/lib/libasan.so" + ), + ) + if os.path.exists(libasan_path): + print("Using AddressSanitizer library.") + my_env["LD_PRELOAD"] = libasan_path + + if not os.path.isfile(server_cmd): + print(f"Server command '{server_cmd}' does not exist.") + assert False + + server = subprocess.Popen( + server_cmd, + stdout=stdout, + stderr=subprocess.STDOUT, + env=my_env, + cwd=app_root_path, + ) + + is_started, sock = msgpack.is_app_started("127.0.0.1", 8001, 10) + if not is_started: + print("The metadata_additional_field_app is not started after 10 seconds.") + + server.kill() + exit_code = server.wait() + print("The exit code of metadata_additional_field_app: ", exit_code) + + assert exit_code == 0 + assert False + + return + + client = subprocess.Popen( + client_cmd, stdout=stdout, stderr=subprocess.STDOUT, env=my_env + ) + + # The client will quit after the test is completed. + client_rc = client.wait() + if client_rc != 0: + server.kill() + + # We cannot shutdown the socket before the client is closed, due to it will + # trigger the GC of the app server. + sock.close() + + server_rc = server.wait() + print("server: ", server_rc) + print("client: ", client_rc) + assert server_rc == 0 + assert client_rc == 0 + + if build_config_args.ten_enable_tests_cleanup is True: + # Testing complete. If builds are only created during the testing phase, + # we can clear the build results to save disk space. + fs_utils.remove_tree(app_root_path) diff --git a/tests/ten_runtime/smoke/path/path_timeout_2.cc b/tests/ten_runtime/smoke/path/path_timeout_2.cc index c1dc1d8c2a..cfa7c30cc8 100644 --- a/tests/ten_runtime/smoke/path/path_timeout_2.cc +++ b/tests/ten_runtime/smoke/path/path_timeout_2.cc @@ -24,14 +24,14 @@ class test_extension_1 : public ten::extension_t { // then they will be terminated. ten_env.init_property_from_json( R"({ - "ten": { - "path_check_interval": 1000000, - "path_timeout": { - "in_path": 60000000, - "out_path": 1000000 - } - } - })"); + "ten": { + "path_check_interval": 1000000, + "path_timeout": { + "in_path": 60000000, + "out_path": 1000000 + } + } + })"); ten_env.on_configure_done(); } diff --git a/tests/ten_runtime/smoke/property/property_additional_property.cc b/tests/ten_runtime/smoke/property/property_additional_property.cc new file mode 100644 index 0000000000..c454ffaece --- /dev/null +++ b/tests/ten_runtime/smoke/property/property_additional_property.cc @@ -0,0 +1,103 @@ +// +// Copyright © 2025 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 +#include + +#include "gtest/gtest.h" +#include "include_internal/ten_runtime/binding/cpp/ten.h" +#include "ten_utils/lib/thread.h" +#include "tests/common/client/cpp/msgpack_tcp.h" +#include "tests/ten_runtime/smoke/util/binding/cpp/check.h" + +namespace { + +class test_extension : public ten::extension_t { + public: + explicit test_extension(const char *name) : ten::extension_t(name) {} + + void on_cmd(ten::ten_env_t &ten_env, + std::unique_ptr cmd) override { + TEN_ENV_LOG_DEBUG(ten_env, + (std::string("on_cmd ") + cmd->get_name()).c_str()); + + if (cmd->get_name() == "hello_world") { + auto cmd_result = ten::cmd_result_t::create(TEN_STATUS_CODE_OK, *cmd); + cmd_result->set_property("detail", "hello world, too"); + ten_env.return_result(std::move(cmd_result)); + } + } +}; + +class test_app : public ten::app_t { + public: + void on_configure(ten::ten_env_t &ten_env) override { + bool rc = ten_env.init_property_from_json( + // clang-format off + R"({ + "ten": { + "uri": "msgpack://127.0.0.1:8001/", + "log": { + "level": 2 + }, + "unknown_property": "unknown_value" + } + })", + // clang-format on + nullptr); + ASSERT_EQ(rc, true); + + ten_env.on_configure_done(); + } +}; + +void *test_app_thread_main(TEN_UNUSED void *args) { + auto *app = new test_app(); + app->run(); + delete app; + + return nullptr; +} + +TEN_CPP_REGISTER_ADDON_AS_EXTENSION( + property_additional_property__test_extension, test_extension); + +} // namespace + +TEST(PropertyTest, AdditionalProperty) { // NOLINT + auto *app_thread = + ten_thread_create("app thread", test_app_thread_main, nullptr); + + // Create a client and connect to the app. + auto *client = new ten::msgpack_tcp_client_t("msgpack://127.0.0.1:8001/"); + + // Send graph. + auto start_graph_cmd = ten::cmd_start_graph_t::create(); + start_graph_cmd->set_graph_from_json(R"({ + "nodes": [{ + "type": "extension", + "name": "test_extension", + "addon": "property_additional_property__test_extension", + "extension_group": "test_extension_group", + "app": "msgpack://127.0.0.1:8001/" + }] + })"); + auto cmd_result = + client->send_cmd_and_recv_result(std::move(start_graph_cmd)); + ten_test::check_status_code(cmd_result, TEN_STATUS_CODE_OK); + + // Send a user-defined 'hello world' command. + auto hello_world_cmd = ten::cmd_t::create("hello_world"); + hello_world_cmd->set_dest("msgpack://127.0.0.1:8001/", nullptr, + "test_extension"); + cmd_result = client->send_cmd_and_recv_result(std::move(hello_world_cmd)); + ten_test::check_status_code(cmd_result, TEN_STATUS_CODE_OK); + ten_test::check_detail_with_string(cmd_result, "hello world, too"); + + delete client; + + ten_thread_join(app_thread, -1); +}