diff --git a/core/src/ten_runtime/extension/internal/metadata.c b/core/src/ten_runtime/extension/internal/metadata.c index 6028d9166..03a700c2f 100644 --- a/core/src/ten_runtime/extension/internal/metadata.c +++ b/core/src/ten_runtime/extension/internal/metadata.c @@ -197,10 +197,12 @@ static bool ten_extension_graph_property_resolve_placeholders( ten_placeholder_init(&placeholder); if (!ten_placeholder_parse(&placeholder, str_value, err)) { + ten_placeholder_deinit(&placeholder); return false; } if (!ten_placeholder_resolve(&placeholder, curr_value, err)) { + ten_placeholder_deinit(&placeholder); return false; } diff --git a/core/src/ten_runtime/extension/ten_env/on_xxx.c b/core/src/ten_runtime/extension/ten_env/on_xxx.c index 853743944..22e163350 100644 --- a/core/src/ten_runtime/extension/ten_env/on_xxx.c +++ b/core/src/ten_runtime/extension/ten_env/on_xxx.c @@ -137,7 +137,12 @@ bool ten_extension_on_configure_done(ten_env_t *self) { } rc = ten_extension_resolve_properties_in_graph(extension, &err); - TEN_ASSERT(rc, "Failed to resolve properties in graph."); + if (!rc) { + TEN_LOGW( + "Failed to resolve properties in graph: %s, use the raw property data " + "instead.", + ten_error_message(&err)); + } ten_extension_merge_properties_from_graph(extension); diff --git a/core/src/ten_utils/lib/sys/general/placeholder.c b/core/src/ten_utils/lib/sys/general/placeholder.c index e1022fa1a..398570930 100644 --- a/core/src/ten_utils/lib/sys/general/placeholder.c +++ b/core/src/ten_utils/lib/sys/general/placeholder.c @@ -86,7 +86,6 @@ static TEN_PLACEHOLDER_SCOPE ten_placeholder_scope_from_string( if (!strcmp(scope_str, TEN_STR_ENV)) { return TEN_PLACEHOLDER_SCOPE_ENV; } else { - TEN_ASSERT(0, "Should not happen."); return TEN_PLACEHOLDER_SCOPE_INVALID; } } @@ -130,6 +129,11 @@ bool ten_placeholder_parse(ten_placeholder_t *self, const char *input, char *scope_end = strchr(content, TEN_STR_PLACEHOLDER_SCOPE_DELIMITER); if (!scope_end) { TEN_FREE(content); + if (err) { + ten_error_set(err, TEN_ERROR_CODE_GENERIC, + "Invalid placeholder format: %s, missing scope delimiter.", + input); + } return false; } @@ -215,7 +219,6 @@ bool ten_placeholder_resolve(ten_placeholder_t *self, ten_error_set(err, TEN_ERROR_CODE_GENERIC, "Unsupported placeholder scope: %d", self->scope); } - TEN_ASSERT(0, "Should not happen."); return false; } diff --git a/tests/ten_runtime/integration/cpp/BUILD.gn b/tests/ten_runtime/integration/cpp/BUILD.gn index 70ef73048..eb393bd50 100644 --- a/tests/ten_runtime/integration/cpp/BUILD.gn +++ b/tests/ten_runtime/integration/cpp/BUILD.gn @@ -12,6 +12,7 @@ group("cpp") { "graph_env_var_1", "graph_env_var_2", "graph_env_var_3", + "graph_env_var_4", "hello_world", "large_result", "restful", diff --git a/tests/ten_runtime/integration/cpp/graph_env_var_4/BUILD.gn b/tests/ten_runtime/integration/cpp/graph_env_var_4/BUILD.gn new file mode 100644 index 000000000..9b7143079 --- /dev/null +++ b/tests/ten_runtime/integration/cpp/graph_env_var_4/BUILD.gn @@ -0,0 +1,85 @@ +# +# 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("graph_env_var_4_app") { + src_app = "default_app_cpp" + src_app_language = "cpp" + generated_app_src_root_dir_name = "graph_env_var_4_app" + + replace_paths_after_install_app = [ + "graph_env_var_4_app/manifest.json", + "graph_env_var_4_app/property.json", + ] + + replace_paths_after_install_all = [ + "graph_env_var_4_app/ten_packages/extension/default_extension_cpp/manifest.json", + "graph_env_var_4_app/ten_packages/extension/default_extension_cpp/property.json", + "graph_env_var_4_app/ten_packages/extension/default_extension_cpp/src/main.cc", + "graph_env_var_4_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("graph_env_var_4_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("graph_env_var_4_app_test_files") { + resources = [ + "__init__.py", + "test_case.py", + ] + + common_files = + exec_script("//.gnfiles/build/scripts/glob_file.py", + [ + "--dir", + rebase_path("//tests/ten_runtime/integration/common/**/*"), + "--dir-base", + rebase_path("//tests/ten_runtime/integration/common"), + "--recursive", + "--only-output-file", + ], + "json") + + foreach(common_file, common_files) { + common_file_rel_path = common_file.relative_path + resources += [ "//tests/ten_runtime/integration/common/${common_file_rel_path}=>common/${common_file_rel_path}" ] + } +} + +group("graph_env_var_4") { + deps = [ + ":graph_env_var_4_app", + ":graph_env_var_4_app_client", + ":graph_env_var_4_app_test_files", + ] +} diff --git a/tests/ten_runtime/integration/cpp/graph_env_var_4/__init__.py b/tests/ten_runtime/integration/cpp/graph_env_var_4/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/ten_runtime/integration/cpp/graph_env_var_4/client/client.cc b/tests/ten_runtime/integration/cpp/graph_env_var_4/client/client.cc new file mode 100644 index 000000000..87d07ac2d --- /dev/null +++ b/tests/ten_runtime/integration/cpp/graph_env_var_4/client/client.cc @@ -0,0 +1,46 @@ +// +// 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", + "property": { + "prop": "${abc}" + } + }] + })"); + 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_group", "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/graph_env_var_4/graph_env_var_4_app/manifest.json b/tests/ten_runtime/integration/cpp/graph_env_var_4/graph_env_var_4_app/manifest.json new file mode 100644 index 000000000..6f407e87a --- /dev/null +++ b/tests/ten_runtime/integration/cpp/graph_env_var_4/graph_env_var_4_app/manifest.json @@ -0,0 +1,22 @@ +{ + "type": "app", + "name": "default_app_cpp", + "version": "0.8.18", + "dependencies": [ + { + "type": "system", + "name": "ten_runtime", + "version": "0.8.18" + }, + { + "type": "extension", + "name": "default_extension_cpp", + "version": "0.8.18" + }, + { + "type": "protocol", + "name": "msgpack", + "version": "0.8.18" + } + ] +} \ No newline at end of file diff --git a/tests/ten_runtime/integration/cpp/graph_env_var_4/graph_env_var_4_app/property.json b/tests/ten_runtime/integration/cpp/graph_env_var_4/graph_env_var_4_app/property.json new file mode 100644 index 000000000..43cdb3460 --- /dev/null +++ b/tests/ten_runtime/integration/cpp/graph_env_var_4/graph_env_var_4_app/property.json @@ -0,0 +1,5 @@ +{ + "_ten": { + "uri": "msgpack://127.0.0.1:8001/" + } +} \ No newline at end of file diff --git a/tests/ten_runtime/integration/cpp/graph_env_var_4/graph_env_var_4_app/ten_packages/extension/default_extension_cpp/BUILD.gn b/tests/ten_runtime/integration/cpp/graph_env_var_4/graph_env_var_4_app/ten_packages/extension/default_extension_cpp/BUILD.gn new file mode 100644 index 000000000..9e148e2c6 --- /dev/null +++ b/tests/ten_runtime/integration/cpp/graph_env_var_4/graph_env_var_4_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/graph_env_var_4/graph_env_var_4_app/ten_packages/extension/default_extension_cpp/manifest.json b/tests/ten_runtime/integration/cpp/graph_env_var_4/graph_env_var_4_app/ten_packages/extension/default_extension_cpp/manifest.json new file mode 100644 index 000000000..e3163045b --- /dev/null +++ b/tests/ten_runtime/integration/cpp/graph_env_var_4/graph_env_var_4_app/ten_packages/extension/default_extension_cpp/manifest.json @@ -0,0 +1,32 @@ +{ + "type": "extension", + "name": "default_extension_cpp", + "version": "0.8.18", + "dependencies": [ + { + "type": "system", + "name": "ten_runtime", + "version": "0.8.18" + } + ], + "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": [], + "interface_in": [] + } +} \ No newline at end of file diff --git a/tests/ten_runtime/integration/cpp/graph_env_var_4/graph_env_var_4_app/ten_packages/extension/default_extension_cpp/property.json b/tests/ten_runtime/integration/cpp/graph_env_var_4/graph_env_var_4_app/ten_packages/extension/default_extension_cpp/property.json new file mode 100644 index 000000000..17a9ab8da --- /dev/null +++ b/tests/ten_runtime/integration/cpp/graph_env_var_4/graph_env_var_4_app/ten_packages/extension/default_extension_cpp/property.json @@ -0,0 +1,3 @@ +{ + "prop": "default_value" +} \ No newline at end of file diff --git a/tests/ten_runtime/integration/cpp/graph_env_var_4/graph_env_var_4_app/ten_packages/extension/default_extension_cpp/src/main.cc b/tests/ten_runtime/integration/cpp/graph_env_var_4/graph_env_var_4_app/ten_packages/extension/default_extension_cpp/src/main.cc new file mode 100644 index 000000000..d63c1f244 --- /dev/null +++ b/tests/ten_runtime/integration/cpp/graph_env_var_4/graph_env_var_4_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 `prop` should be available here. + auto prop = ten_env.get_property_string("prop"); + if (prop != "${abc}") { + 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_result->set_property("detail", "hello world, too"); + ten_env.return_result(std::move(cmd_result), std::move(cmd)); + } + } +}; + +TEN_CPP_REGISTER_ADDON_AS_EXTENSION(default_extension_cpp, test_extension); diff --git a/tests/ten_runtime/integration/cpp/graph_env_var_4/test_case.py b/tests/ten_runtime/integration/cpp/graph_env_var_4/test_case.py new file mode 100644 index 000000000..9f1172026 --- /dev/null +++ b/tests/ten_runtime/integration/cpp/graph_env_var_4/test_case.py @@ -0,0 +1,164 @@ +""" +Test graph_env_var_4_app. +""" + +import subprocess +import os +import sys +from sys import stdout +from .common import msgpack, build_config, build_pkg + + +def test_graph_env_var_4_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 = "graph_env_var_4_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. + build_pkg.cleanup(app_root_path) + + print('Assembling and building package "{}".'.format(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, + "graph_env_var_4_app/ten_packages/system/ten_runtime/lib", + ) + + ";" + + my_env["PATH"] + ) + server_cmd = os.path.join( + base_path, + "graph_env_var_4_app/bin/graph_env_var_4_app.exe", + ) + client_cmd = os.path.join(base_path, "graph_env_var_4_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, + "graph_env_var_4_app/ten_packages/system/ten_runtime/lib", + ) + server_cmd = os.path.join( + base_path, + "graph_env_var_4_app/bin/graph_env_var_4_app", + ) + client_cmd = os.path.join(base_path, "graph_env_var_4_app_client") + else: + # client depends on some libraries in the TEN app. + my_env["LD_LIBRARY_PATH"] = os.path.join( + base_path, + "graph_env_var_4_app/ten_packages/system/ten_runtime/lib", + ) + server_cmd = os.path.join( + base_path, + "graph_env_var_4_app/bin/graph_env_var_4_app", + ) + client_cmd = os.path.join(base_path, "graph_env_var_4_app_client") + + if ( + build_config_args.enable_sanitizer + and not build_config_args.is_clang + ): + libasan_path = os.path.join( + base_path, + "graph_env_var_4_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 + + my_env["TEST_ENV_VAR"] = "set_from_real_env_var" + + 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 graph_env_var_4_app is not started after 10 seconds.") + + server.kill() + exit_code = server.wait() + print( + "The exit code of graph_env_var_4_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_integration_tests_prebuilt is False: + # Testing complete. If builds are only created during the testing phase, + # we can clear the build results to save disk space. + build_pkg.cleanup(app_root_path)