diff --git a/build/ten_runtime/feature/test.gni b/build/ten_runtime/feature/test.gni index ab3e998c1..ecc0665a5 100644 --- a/build/ten_runtime/feature/test.gni +++ b/build/ten_runtime/feature/test.gni @@ -28,7 +28,7 @@ template("ten_package_test_prepare_app") { app_run_root_dir = "${test_case_root_dir}/${_target_name}" install_app_dummy_output_file = - "${target_gen_dir}/install_app_dummy_output_file" + "${target_gen_dir}/${_target_name}/install_app_dummy_output_file" install_app_depfile = "${target_gen_dir}/install_app_depfile" action("${test_case_unique_target_name}_${_target_name}_install_app") { @@ -168,7 +168,7 @@ template("ten_package_test_prepare_app") { } install_all_dummy_output_file = - "${target_gen_dir}/install_all_dummy_output_file" + "${target_gen_dir}/${_target_name}/install_all_dummy_output_file" action("${test_case_unique_target_name}_${_target_name}_install_all") { script = "//build/ten_runtime/feature/install_all.py" @@ -290,7 +290,8 @@ template("ten_package_test_prepare_app") { } # App building phase. - build_app_dummy_output_file = "${target_gen_dir}/build_app_dummy_output_file" + build_app_dummy_output_file = + "${target_gen_dir}/${_target_name}/build_app_dummy_output_file" action("${test_case_unique_target_name}_${_target_name}_build") { script = "//build/ten_runtime/feature/build_pkg.py" @@ -543,7 +544,7 @@ template("ten_package_standalone_pkg") { pkg_root_dir = "${test_case_root_dir}/${_target_name}" install_standalone_dummy_output_file = - "${target_gen_dir}/install_standalone_dummy_output_file" + "${target_gen_dir}/${_target_name}/install_standalone_dummy_output_file" install_standalone_depfile = "${target_gen_dir}/install_standalone_depfile" action("${test_case_unique_target_name}_${_target_name}_install_pkg") { @@ -624,7 +625,8 @@ template("ten_package_standalone_pkg") { ] } - install_all_dummy_output_file = "${target_gen_dir}/install_dummy_output_file" + install_all_dummy_output_file = + "${target_gen_dir}/${_target_name}/install_dummy_output_file" # Install dependencies of the standalone package. action("${test_case_unique_target_name}_${_target_name}_install") { @@ -689,7 +691,8 @@ template("ten_package_standalone_pkg") { } # Build this standalone package. - build_pkg_dummy_output_file = "${target_gen_dir}/build_pkg_dummy_output_file" + build_pkg_dummy_output_file = + "${target_gen_dir}/${_target_name}/build_pkg_dummy_output_file" action("${test_case_unique_target_name}_${_target_name}_build") { script = "//build/ten_runtime/feature/build_pkg.py" diff --git a/core/include_internal/ten_runtime/app/graph.h b/core/include_internal/ten_runtime/app/graph.h new file mode 100644 index 000000000..59a10ccb5 --- /dev/null +++ b/core/include_internal/ten_runtime/app/graph.h @@ -0,0 +1,19 @@ +// +// 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/ten_config.h" + +#include + +#include "ten_utils/lib/error.h" +#include "ten_utils/lib/json.h" + +typedef struct ten_app_t ten_app_t; + +TEN_RUNTIME_PRIVATE_API bool ten_app_check_start_graph_cmd_json( + ten_app_t *self, ten_json_t *start_graph_cmd_json, ten_error_t *err); diff --git a/core/src/ten_runtime/app/BUILD.gn b/core/src/ten_runtime/app/BUILD.gn index fbe84b550..5ec087667 100644 --- a/core/src/ten_runtime/app/BUILD.gn +++ b/core/src/ten_runtime/app/BUILD.gn @@ -12,4 +12,6 @@ glob("app") { "msg_interface", "ten_env", ] + + public_deps = [ "//core/src/ten_rust:ten_rust_binding" ] } diff --git a/core/src/ten_runtime/app/graph.c b/core/src/ten_runtime/app/graph.c new file mode 100644 index 000000000..fa0f2be50 --- /dev/null +++ b/core/src/ten_runtime/app/graph.c @@ -0,0 +1,51 @@ +// +// 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 "include_internal/ten_runtime/app/graph.h" + +#include "include_internal/ten_runtime/app/app.h" +#include "include_internal/ten_runtime/app/base_dir.h" +#include "include_internal/ten_runtime/common/constant_str.h" +#include "include_internal/ten_rust/ten_rust.h" +#include "ten_runtime/app/app.h" +#include "ten_utils/macro/memory.h" + +bool ten_app_check_start_graph_cmd_json(ten_app_t *self, + ten_json_t *start_graph_cmd_json, + ten_error_t *err) { + TEN_ASSERT(self && ten_app_check_integrity(self, true), "Should not happen."); + TEN_ASSERT(start_graph_cmd_json, "Invalid argument."); + + const char *base_dir = ten_app_get_base_dir(self); + + // The pkg_info of extensions in the graph is read from the ten_packages + // directory under the base dir of app. If the base dir is not set, the app + // might be running in a thread, ex: the smoke testing. In this case, we can + // not retrieve the enough information to check the graph. + if (!base_dir || ten_c_string_is_empty(base_dir)) { + TEN_LOGD("The base dir of app [%s] is not set, skip checking graph.", + ten_app_get_uri(self)); + return true; + } + + bool free_json_string = false; + const char *graph_json_str = ten_json_to_string( + start_graph_cmd_json, TEN_STR_UNDERLINE_TEN, &free_json_string); + + const char *err_msg = NULL; + bool rc = ten_rust_check_graph_for_app(base_dir, graph_json_str, &err_msg); + + if (free_json_string) { + TEN_FREE(graph_json_str); + } + + if (!rc) { + ten_error_set(err, TEN_ERRNO_INVALID_GRAPH, err_msg); + ten_rust_free_cstring(err_msg); + } + + return rc; +} diff --git a/core/src/ten_runtime/app/msg_interface/start_graph.c b/core/src/ten_runtime/app/msg_interface/start_graph.c index 2eaec8528..20ccb5c4a 100644 --- a/core/src/ten_runtime/app/msg_interface/start_graph.c +++ b/core/src/ten_runtime/app/msg_interface/start_graph.c @@ -12,6 +12,7 @@ #include "include_internal/ten_runtime/app/app.h" #include "include_internal/ten_runtime/app/close.h" #include "include_internal/ten_runtime/app/engine_interface.h" +#include "include_internal/ten_runtime/app/graph.h" #include "include_internal/ten_runtime/app/metadata.h" #include "include_internal/ten_runtime/app/msg_interface/common.h" #include "include_internal/ten_runtime/app/predefined_graph.h" @@ -24,7 +25,12 @@ #include "include_internal/ten_runtime/msg/msg.h" #include "include_internal/ten_runtime/protocol/protocol.h" #include "ten_runtime/app/app.h" +#include "ten_runtime/common/status_code.h" +#include "ten_runtime/msg/cmd_result/cmd_result.h" +#include "ten_runtime/msg/msg.h" +#include "ten_utils/lib/json.h" #include "ten_utils/lib/smart_ptr.h" +#include "ten_utils/log/log.h" #include "ten_utils/macro/check.h" static bool ten_app_fill_start_graph_cmd_extensions_info_from_predefined_graph( @@ -53,6 +59,39 @@ static bool ten_app_fill_start_graph_cmd_extensions_info_from_predefined_graph( return true; } +bool ten_app_check_start_graph_cmd(ten_app_t *self, + ten_connection_t *connection, + ten_shared_ptr_t *cmd, ten_error_t *err) { + TEN_ASSERT(self && ten_app_check_integrity(self, true), "Invalid argument."); + TEN_ASSERT(cmd && ten_cmd_base_check_integrity(cmd), "Invalid argument."); + TEN_ASSERT(ten_msg_get_type(cmd) == TEN_MSG_TYPE_CMD_START_GRAPH, + "Invalid argument."); + TEN_ASSERT(err && ten_error_check_integrity(err), "Invalid argument."); + + ten_json_t *cmd_json = ten_msg_to_json(cmd, err); + if (!cmd_json) { + TEN_ASSERT(0, + "Failed to convert start graph cmd to json, should not happen."); + return false; + } + + bool rc = ten_app_check_start_graph_cmd_json(self, cmd_json, err); + + ten_json_destroy(cmd_json); + + if (!rc && connection) { + ten_shared_ptr_t *ret_cmd = + ten_cmd_result_create_from_cmd(TEN_STATUS_CODE_ERROR, cmd); + ten_msg_set_property(ret_cmd, TEN_STR_DETAIL, + ten_value_create_string(ten_error_errmsg(err)), NULL); + ten_msg_clear_and_set_dest_from_msg_src(ret_cmd, cmd); + ten_connection_send_msg(connection, ret_cmd); + ten_shared_ptr_destroy(ret_cmd); + } + + return rc; +} + bool ten_app_handle_start_graph_cmd(ten_app_t *self, ten_connection_t *connection, ten_shared_ptr_t *cmd, ten_error_t *err) { @@ -75,6 +114,13 @@ bool ten_app_handle_start_graph_cmd(ten_app_t *self, ten_engine_t *engine = ten_app_get_engine_based_on_dest_graph_id_from_msg(self, cmd); if (engine == NULL) { + // The graph should be only checked once. + if (!ten_app_check_start_graph_cmd(self, connection, cmd, err)) { + TEN_LOGE("[%s] Failed to check start_graph cmd, %s", + ten_app_get_uri(self), ten_error_errmsg(err)); + return false; + } + // The engine does not exist, create one, and send 'cmd' to the newly // created engine. engine = ten_app_create_engine(self, cmd); diff --git a/core/src/ten_runtime/app/predefined_graph.c b/core/src/ten_runtime/app/predefined_graph.c index 20ec3b46a..b9a7575fa 100644 --- a/core/src/ten_runtime/app/predefined_graph.c +++ b/core/src/ten_runtime/app/predefined_graph.c @@ -8,6 +8,7 @@ #include "include_internal/ten_runtime/app/app.h" #include "include_internal/ten_runtime/app/engine_interface.h" +#include "include_internal/ten_runtime/app/graph.h" #include "include_internal/ten_runtime/app/metadata.h" #include "include_internal/ten_runtime/common/constant_str.h" #include "include_internal/ten_runtime/engine/engine.h" @@ -25,6 +26,7 @@ #include "ten_utils/lib/error.h" #include "ten_utils/lib/json.h" #include "ten_utils/lib/string.h" +#include "ten_utils/log/log.h" #include "ten_utils/macro/check.h" #include "ten_utils/value/value_get.h" @@ -149,6 +151,13 @@ bool ten_app_start_predefined_graph( return false; } + if (!ten_app_check_start_graph_cmd_json(self, start_graph_cmd_json, err)) { + // TODO(Wei): The graph check does not support message conversion now, so we + // can not return false here. WIP: issues#160. + TEN_LOGW("[%s] The predefined graph is invalid, %s", ten_app_get_uri(self), + ten_error_errmsg(err)); + } + ten_shared_ptr_t *start_graph_cmd = ten_msg_create_from_json(start_graph_cmd_json, NULL); diff --git a/core/src/ten_runtime/app/ten_env/on_xxx.c b/core/src/ten_runtime/app/ten_env/on_xxx.c index e85fdbf3b..9bd070a7a 100644 --- a/core/src/ten_runtime/app/ten_env/on_xxx.c +++ b/core/src/ten_runtime/app/ten_env/on_xxx.c @@ -106,7 +106,7 @@ static void ten_app_on_configure_done_internal(ten_app_t *self) { } rc = ten_app_start_auto_start_predefined_graph(self, &err); - TEN_ASSERT(rc, "Should not happen."); + TEN_ASSERT(rc, "Should not happen, %s.", ten_error_errmsg(&err)); ten_error_deinit(&err); } diff --git a/core/src/ten_runtime/engine/msg_interface/cmd_result.c b/core/src/ten_runtime/engine/msg_interface/cmd_result.c index c7c177282..6c907515b 100644 --- a/core/src/ten_runtime/engine/msg_interface/cmd_result.c +++ b/core/src/ten_runtime/engine/msg_interface/cmd_result.c @@ -26,6 +26,7 @@ #include "ten_utils/lib/smart_ptr.h" #include "ten_utils/lib/string.h" #include "ten_utils/macro/check.h" +#include "ten_utils/value/value_get.h" #include "ten_utils/value/value_is.h" static bool ten_engine_close_duplicated_remote_or_upgrade_it_to_normal( @@ -156,9 +157,17 @@ static bool ten_engine_handle_cmd_result_for_cmd_start_graph( "The engine should be started because of receiving a 'start_graph' " "command."); - ten_engine_return_error_for_cmd_start_graph( - self, original_start_graph_cmd, "Failed to connect to %s", - ten_msg_get_src_app_uri(cmd_result)); + ten_value_t *err_msg_value = + ten_msg_peek_property(cmd_result, TEN_STR_DETAIL, NULL); + if (err_msg_value) { + TEN_ASSERT(ten_value_is_string(err_msg_value), "Should not happen."); + ten_engine_return_error_for_cmd_start_graph( + self, original_start_graph_cmd, ten_value_peek_string(err_msg_value)); + } else { + ten_engine_return_error_for_cmd_start_graph( + self, original_start_graph_cmd, "Failed to start engine in app [%s].", + ten_msg_get_src_app_uri(cmd_result)); + } } else { TEN_ASSERT(0, "Should not happen."); } diff --git a/core/src/ten_runtime/metadata/metadata.c b/core/src/ten_runtime/metadata/metadata.c index 6ee00bf59..3bc13c1e5 100644 --- a/core/src/ten_runtime/metadata/metadata.c +++ b/core/src/ten_runtime/metadata/metadata.c @@ -25,7 +25,7 @@ static bool ten_metadata_load_from_json_string(ten_value_t *metadata, TEN_ASSERT(metadata && ten_value_check_integrity(metadata) && json_str, "Should not happen."); - ten_json_t *json = ten_json_from_string(json_str, NULL); + ten_json_t *json = ten_json_from_string(json_str, err); if (!json) { return false; } diff --git a/core/src/ten_rust/src/pkg_info/binding.rs b/core/src/ten_rust/src/pkg_info/binding.rs new file mode 100644 index 000000000..56a29457c --- /dev/null +++ b/core/src/ten_rust/src/pkg_info/binding.rs @@ -0,0 +1,38 @@ +// +// 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. +// +use std::ffi::{c_char, CStr, CString}; + +#[no_mangle] +pub extern "C" fn ten_rust_check_graph_for_app( + app_base_dir: *const c_char, + graph_json: *const c_char, + out_err_msg: *mut *const c_char, +) -> bool { + assert!(!app_base_dir.is_null(), "Invalid argument."); + assert!(!graph_json.is_null(), "Invalid argument."); + + let c_app_base_dir = unsafe { CStr::from_ptr(app_base_dir) }; + let c_graph_json = unsafe { CStr::from_ptr(graph_json) }; + + let rust_app_base_dir = c_app_base_dir.to_str().unwrap(); + let rust_graph_json = c_graph_json.to_str().unwrap(); + + let ret = crate::pkg_info::ten_rust_check_graph_for_app( + rust_app_base_dir, + rust_graph_json, + ); + if ret.is_err() { + let err_msg = ret.err().unwrap().to_string(); + let c_err_msg = + CString::new(err_msg).expect("Failed to allocate memory."); + unsafe { + *out_err_msg = c_err_msg.into_raw(); + } + return false; + } + true +} diff --git a/core/src/ten_rust/src/pkg_info/graph/check/connections_are_compatible.rs b/core/src/ten_rust/src/pkg_info/graph/check/connections_are_compatible.rs index f45dab89d..d0e26111a 100644 --- a/core/src/ten_rust/src/pkg_info/graph/check/connections_are_compatible.rs +++ b/core/src/ten_rust/src/pkg_info/graph/check/connections_are_compatible.rs @@ -47,7 +47,7 @@ impl Graph { fn check_msg_flow_compatible( &self, - all_needed_pkgs: &HashMap>, + existed_pkgs_of_all_apps: &HashMap>, msg_name: &str, msg_type: &MsgType, src_msg_schema: Option<&TenSchema>, @@ -60,7 +60,7 @@ impl Graph { dest.extension.as_str(), ); let dest_msg_schema = find_msg_schema_from_all_pkgs_info( - all_needed_pkgs, + existed_pkgs_of_all_apps, dest.get_app_uri(), dest_addon, msg_name, @@ -84,10 +84,11 @@ impl Graph { fn check_cmd_flow_compatible( &self, - all_needed_pkgs: &HashMap>, + existed_pkgs_of_all_apps: &HashMap>, cmd_name: &str, src_cmd_schema: Option<&CmdSchema>, dests: &[GraphDestination], + skip_if_app_not_exist: bool, ) -> Result<()> { let mut errors: Vec = Vec::new(); for dest in dests { @@ -95,9 +96,22 @@ impl Graph { dest.get_app_uri(), dest.extension.as_str(), ); + + let existed_pkgs_of_dest_app = + existed_pkgs_of_all_apps.get(dest.get_app_uri()); + if existed_pkgs_of_dest_app.is_none() { + if skip_if_app_not_exist { + continue; + } else { + return Err(anyhow::anyhow!( + "App [{}] is not found in the pkgs map, should not happen.", + dest.get_app_uri() + )); + } + } + let dest_cmd_schema = find_cmd_schema_from_all_pkgs_info( - all_needed_pkgs, - dest.get_app_uri(), + existed_pkgs_of_dest_app.unwrap(), dest_addon, cmd_name, MsgDirection::In, @@ -119,8 +133,9 @@ impl Graph { fn check_connection_compatible( &self, - all_needed_pkgs: &HashMap>, + existed_pkgs_of_all_apps: &HashMap>, connection: &GraphConnection, + skip_if_app_not_exist: bool, ) -> Result<()> { let mut errors: Vec = Vec::new(); if let Some(cmd_flows) = &connection.cmd { @@ -129,19 +144,30 @@ impl Graph { connection.get_app_uri(), connection.extension.as_str(), ); + + let existed_pkgs_of_src_app = + existed_pkgs_of_all_apps.get(connection.get_app_uri()); + if existed_pkgs_of_src_app.is_none() { + if skip_if_app_not_exist { + continue; + } else { + return Err(anyhow::anyhow!("App [{}] is not found in the pkgs map, should not happen.", connection.get_app_uri())); + } + } + let src_cmd_schema = find_cmd_schema_from_all_pkgs_info( - all_needed_pkgs, - connection.get_app_uri(), + existed_pkgs_of_src_app.unwrap(), src_addon, flow.name.as_str(), MsgDirection::Out, ); if let Err(e) = self.check_cmd_flow_compatible( - all_needed_pkgs, + existed_pkgs_of_all_apps, flow.name.as_str(), src_cmd_schema, &flow.dest, + skip_if_app_not_exist, ) { errors.push(format!("- cmd[{}]: {}", flow_idx, e)); } @@ -155,7 +181,7 @@ impl Graph { connection.extension.as_str(), ); let src_msg_schema = find_msg_schema_from_all_pkgs_info( - all_needed_pkgs, + existed_pkgs_of_all_apps, connection.get_app_uri(), src_addon, flow.name.as_str(), @@ -164,7 +190,7 @@ impl Graph { ); if let Err(e) = self.check_msg_flow_compatible( - all_needed_pkgs, + existed_pkgs_of_all_apps, flow.name.as_str(), &MsgType::Data, src_msg_schema, @@ -182,7 +208,7 @@ impl Graph { connection.extension.as_str(), ); let src_msg_schema = find_msg_schema_from_all_pkgs_info( - all_needed_pkgs, + existed_pkgs_of_all_apps, connection.get_app_uri(), src_addon, flow.name.as_str(), @@ -191,7 +217,7 @@ impl Graph { ); if let Err(e) = self.check_msg_flow_compatible( - all_needed_pkgs, + existed_pkgs_of_all_apps, flow.name.as_str(), &MsgType::VideoFrame, src_msg_schema, @@ -209,7 +235,7 @@ impl Graph { connection.extension.as_str(), ); let src_msg_schema = find_msg_schema_from_all_pkgs_info( - all_needed_pkgs, + existed_pkgs_of_all_apps, connection.get_app_uri(), src_addon, flow.name.as_str(), @@ -218,7 +244,7 @@ impl Graph { ); if let Err(e) = self.check_msg_flow_compatible( - all_needed_pkgs, + existed_pkgs_of_all_apps, flow.name.as_str(), &MsgType::AudioFrame, src_msg_schema, @@ -238,7 +264,8 @@ impl Graph { pub fn check_connections_compatible( &self, - all_needed_pkgs: &HashMap>, + existed_pkgs_of_all_apps: &HashMap>, + skip_if_app_not_exist: bool, ) -> Result<()> { if self.connections.is_none() { return Ok(()); @@ -248,9 +275,11 @@ impl Graph { let connections = self.connections.as_ref().unwrap(); for (conn_idx, connection) in connections.iter().enumerate() { - if let Err(e) = - self.check_connection_compatible(all_needed_pkgs, connection) - { + if let Err(e) = self.check_connection_compatible( + existed_pkgs_of_all_apps, + connection, + skip_if_app_not_exist, + ) { errors.push(format!("- connections[{}]: \n {}", conn_idx, e)); } } @@ -264,17 +293,12 @@ impl Graph { } fn find_cmd_schema_from_all_pkgs_info<'a>( - all_pkgs: &'a HashMap>, - app: &str, + existed_pkgs_of_app: &'a [PkgInfo], addon: &str, cmd_name: &str, direction: MsgDirection, ) -> Option<&'a CmdSchema> { - let pkgs_in_app = all_pkgs.get(app).unwrap_or_else(|| { - panic!("should not happen."); - }); - - let addon_pkg = pkgs_in_app + let addon_pkg = existed_pkgs_of_app .iter() .find(|pkg| { pkg.pkg_identity.pkg_type == PkgType::Extension @@ -293,18 +317,19 @@ fn find_cmd_schema_from_all_pkgs_info<'a>( } fn find_msg_schema_from_all_pkgs_info<'a>( - all_pkgs: &'a HashMap>, + existed_pkgs_of_all_apps: &'a HashMap>, app: &str, addon: &str, msg_name: &str, msg_type: &MsgType, direction: MsgDirection, ) -> Option<&'a TenSchema> { - let pkgs_in_app = all_pkgs.get(app).unwrap_or_else(|| { - panic!("should not happen."); - }); + let existed_pkgs_of_app = + existed_pkgs_of_all_apps.get(app).unwrap_or_else(|| { + panic!("should not happen."); + }); - let addon_pkg = pkgs_in_app + let addon_pkg = existed_pkgs_of_app .iter() .find(|pkg| { pkg.pkg_identity.pkg_type == PkgType::Extension diff --git a/core/src/ten_rust/src/pkg_info/graph/check/nodes_are_installed.rs b/core/src/ten_rust/src/pkg_info/graph/check/nodes_are_installed.rs index b343c4393..cb7d312bc 100644 --- a/core/src/ten_rust/src/pkg_info/graph/check/nodes_are_installed.rs +++ b/core/src/ten_rust/src/pkg_info/graph/check/nodes_are_installed.rs @@ -14,6 +14,7 @@ impl Graph { pub fn check_if_nodes_have_installed( &self, existed_pkgs_of_all_apps: &HashMap>, + skip_if_app_not_exist: bool, ) -> Result<()> { // app_uri, node_type, node_addon_name let mut not_installed_pkgs: Vec<(String, PkgType, String)> = Vec::new(); @@ -24,11 +25,15 @@ impl Graph { // Check if the app to which the graph node belongs has been // specified. if !existed_pkgs_of_all_apps.contains_key(node_app_uri) { - not_installed_pkgs.push(( - node_app_uri.to_string(), - node.node_type.clone(), - node.addon.clone(), - )); + if skip_if_app_not_exist { + continue; + } else { + not_installed_pkgs.push(( + node_app_uri.to_string(), + node.node_type.clone(), + node.addon.clone(), + )); + } } let existed_pkgs_of_app = diff --git a/core/src/ten_rust/src/pkg_info/graph/mod.rs b/core/src/ten_rust/src/pkg_info/graph/mod.rs index 13f7118bd..da172ce8f 100644 --- a/core/src/ten_rust/src/pkg_info/graph/mod.rs +++ b/core/src/ten_rust/src/pkg_info/graph/mod.rs @@ -73,8 +73,27 @@ impl Graph { ) -> Result<()> { self.check_if_nodes_duplicated()?; self.check_if_extensions_used_in_connections_have_defined_in_nodes()?; - self.check_if_nodes_have_installed(existed_pkgs_of_all_apps)?; - self.check_connections_compatible(existed_pkgs_of_all_apps)?; + self.check_if_nodes_have_installed(existed_pkgs_of_all_apps, false)?; + self.check_connections_compatible(existed_pkgs_of_all_apps, false)?; + self.check_extension_uniqueness_in_connections()?; + self.check_message_names()?; + + Ok(()) + } + + pub fn check_for_single_app( + &self, + existed_pkgs_of_app: &HashMap>, + ) -> Result<()> { + assert!(existed_pkgs_of_app.len() == 1); + + self.check_if_nodes_duplicated()?; + self.check_if_extensions_used_in_connections_have_defined_in_nodes()?; + self.check_if_nodes_have_installed(existed_pkgs_of_app, true)?; + + // In a single app, there is no information about pkg_info of other + // apps, neither the message schemas. + self.check_connections_compatible(existed_pkgs_of_app, true)?; self.check_extension_uniqueness_in_connections()?; self.check_message_names()?; diff --git a/core/src/ten_rust/src/pkg_info/mod.rs b/core/src/ten_rust/src/pkg_info/mod.rs index 9bd71dd68..45381dd98 100644 --- a/core/src/ten_rust/src/pkg_info/mod.rs +++ b/core/src/ten_rust/src/pkg_info/mod.rs @@ -5,6 +5,7 @@ // Refer to the "LICENSE" file in the root directory for more information. // pub mod api; +mod binding; mod constants; pub mod dependencies; pub mod graph; @@ -22,12 +23,14 @@ pub mod value_type; use std::{ cmp::Ordering, - collections::HashSet, + collections::{HashMap, HashSet}, hash::{Hash, Hasher}, path::{Path, PathBuf}, + str::FromStr, }; -use anyhow::Result; +use anyhow::{Context, Result}; +use graph::Graph; use semver::Version; use crate::schema::store::SchemaStore; @@ -400,3 +403,27 @@ pub fn find_to_be_replaced_local_pkgs<'a>( result } + +pub fn ten_rust_check_graph_for_app( + app_base_dir: &str, + graph_json: &str, +) -> Result<()> { + let app_path = Path::new(app_base_dir); + if !app_path.exists() { + return Err(anyhow::anyhow!( + "The app base dir [{}] is not found.", + app_base_dir + )); + } + + let property = parse_property_in_folder(app_path)?; + + let mut pkgs_of_app: HashMap> = HashMap::new(); + let pkgs_info = get_all_existed_pkgs_info_of_app(app_path)?; + pkgs_of_app.insert(property.get_app_uri(), pkgs_info); + + let graph = Graph::from_str(graph_json) + .with_context(|| "The graph json string is invalid.")?; + + graph.check_for_single_app(&pkgs_of_app) +} diff --git a/core/src/ten_rust/tests/graph_check.rs b/core/src/ten_rust/tests/graph_check.rs index b797a3f2a..bc0e1dae8 100644 --- a/core/src/ten_rust/tests/graph_check.rs +++ b/core/src/ten_rust/tests/graph_check.rs @@ -1,13 +1,15 @@ // -// This file is part of the TEN Framework project. -// See https://github.com/TEN-framework/ten_framework/LICENSE for license -// information. +// 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. // -use std::{collections::HashMap, path::Path}; +use std::{collections::HashMap, path::Path, str::FromStr}; use ten_rust::pkg_info::{ - get_all_existed_pkgs_info_of_app, localhost, pkg_type::PkgType, - property::predefined_graph::PropertyPredefinedGraph, PkgInfo, + get_all_existed_pkgs_info_of_app, graph::Graph, localhost, + pkg_type::PkgType, property::predefined_graph::PropertyPredefinedGraph, + PkgInfo, }; #[test] @@ -30,7 +32,7 @@ fn test_graph_check_extension_not_installed() { let mut pkg_info_map: HashMap> = HashMap::new(); pkg_info_map.insert(localhost(), pkg_infos); - let result = graph.check_if_nodes_have_installed(&pkg_info_map); + let result = graph.check_if_nodes_have_installed(&pkg_info_map, false); assert!(result.is_err()); println!("Error: {:?}", result.err().unwrap()); } @@ -83,3 +85,44 @@ fn test_graph_check_all_msgs_schema_incompatible() { assert!(result.is_err()); println!("Error: {:?}", result.err().unwrap()); } + +#[test] +fn test_graph_check_single_app() { + let app_dir = "tests/test_data/graph_check_single_app"; + let pkg_infos = + get_all_existed_pkgs_info_of_app(Path::new(app_dir)).unwrap(); + assert!(!pkg_infos.is_empty()); + + let graph_str = include_str!("test_data/graph_check_single_app/graph.json"); + let graph = Graph::from_str(graph_str).unwrap(); + + let mut pkg_info_map: HashMap> = HashMap::new(); + pkg_info_map.insert("http://localhost:8001".to_string(), pkg_infos); + + // The schema of 'ext_c' is not found, but it's OK because we only check + // for the app 'http://localhost:8001'. + let result = graph.check_for_single_app(&pkg_info_map); + assert!(result.is_ok()); +} + +#[test] +fn test_graph_check_single_app_schema_incompatible() { + let app_dir = "tests/test_data/graph_check_single_app_schema_incompatible"; + let pkg_infos = + get_all_existed_pkgs_info_of_app(Path::new(app_dir)).unwrap(); + assert!(!pkg_infos.is_empty()); + + let graph_str = include_str!( + "test_data/graph_check_single_app_schema_incompatible/graph.json" + ); + let graph = Graph::from_str(graph_str).unwrap(); + + let mut pkg_info_map: HashMap> = HashMap::new(); + pkg_info_map.insert("http://localhost:8001".to_string(), pkg_infos); + + let result = graph.check_for_single_app(&pkg_info_map); + + // Schema incompatible to [extension_group: some_group, extension: ext_b]. + assert!(result.is_err()); + println!("Error: {:?}", result.err().unwrap()); +} diff --git a/core/src/ten_rust/tests/integration_test.rs b/core/src/ten_rust/tests/integration_test.rs index cdd58feeb..79af7124a 100644 --- a/core/src/ten_rust/tests/integration_test.rs +++ b/core/src/ten_rust/tests/integration_test.rs @@ -1,7 +1,8 @@ // -// This file is part of the TEN Framework project. -// See https://github.com/TEN-framework/ten_framework/LICENSE for license -// information. +// 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. // // Each file in the tests directory is a separate crate, and will be compiled as diff --git a/core/src/ten_rust/tests/interface.rs b/core/src/ten_rust/tests/interface.rs index d0775457f..bd335528e 100644 --- a/core/src/ten_rust/tests/interface.rs +++ b/core/src/ten_rust/tests/interface.rs @@ -1,7 +1,8 @@ // -// This file is part of the TEN Framework project. -// See https://github.com/TEN-framework/ten_framework/LICENSE for license -// information. +// 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. // use ten_rust::interface::ten_interface_schema_resolve_definition; diff --git a/core/src/ten_rust/tests/test_data/graph_check_single_app/graph.json b/core/src/ten_rust/tests/test_data/graph_check_single_app/graph.json new file mode 100644 index 000000000..63746a303 --- /dev/null +++ b/core/src/ten_rust/tests/test_data/graph_check_single_app/graph.json @@ -0,0 +1,49 @@ +{ + "nodes": [ + { + "type": "extension", + "name": "ext_a", + "addon": "addon_a", + "extension_group": "some_group", + "app": "http://localhost:8001" + }, + { + "type": "extension", + "name": "ext_b", + "addon": "addon_b", + "extension_group": "some_group", + "app": "http://localhost:8001" + }, + { + "type": "extension", + "name": "ext_c", + "addon": "addon_c", + "extension_group": "some_group", + "app": "http://localhost:8002" + } + ], + "connections": [ + { + "extension_group": "some_group", + "extension": "ext_a", + "app": "http://localhost:8001", + "cmd": [ + { + "name": "cmd_1", + "dest": [ + { + "extension_group": "some_group", + "extension": "ext_b", + "app": "http://localhost:8001" + }, + { + "extension_group": "some_group", + "extension": "ext_c", + "app": "http://localhost:8002" + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/core/src/ten_rust/tests/test_data/graph_check_single_app/manifest.json b/core/src/ten_rust/tests/test_data/graph_check_single_app/manifest.json new file mode 100644 index 000000000..7cb8a2ca0 --- /dev/null +++ b/core/src/ten_rust/tests/test_data/graph_check_single_app/manifest.json @@ -0,0 +1,22 @@ +{ + "type": "app", + "name": "check_predefined_graph_success", + "version": "0.1.0", + "dependencies": [ + { + "type": "extension", + "name": "addon_a", + "version": "0.1.0" + }, + { + "type": "extension", + "name": "addon_b", + "version": "0.1.0" + } + ], + "package": { + "include": [ + "**" + ] + } +} \ No newline at end of file diff --git a/core/src/ten_rust/tests/test_data/graph_check_single_app/property.json b/core/src/ten_rust/tests/test_data/graph_check_single_app/property.json new file mode 100644 index 000000000..d2ce191d0 --- /dev/null +++ b/core/src/ten_rust/tests/test_data/graph_check_single_app/property.json @@ -0,0 +1,5 @@ +{ + "_ten": { + "uri": "http://localhost:8001" + } +} \ No newline at end of file diff --git a/core/src/ten_rust/tests/test_data/graph_check_single_app/ten_packages/extension/addon_a/manifest.json b/core/src/ten_rust/tests/test_data/graph_check_single_app/ten_packages/extension/addon_a/manifest.json new file mode 100644 index 000000000..06ea41478 --- /dev/null +++ b/core/src/ten_rust/tests/test_data/graph_check_single_app/ten_packages/extension/addon_a/manifest.json @@ -0,0 +1,29 @@ +{ + "type": "extension", + "name": "addon_a", + "version": "0.1.0", + "dependencies": [ + { + "type": "system", + "name": "ten_runtime_go", + "version": "0.1.0" + } + ], + "package": { + "include": [ + "**" + ] + }, + "api": { + "cmd_out": [ + { + "name": "cmd_1", + "property": { + "foo": { + "type": "string" + } + } + } + ] + } +} \ No newline at end of file diff --git a/core/src/ten_rust/tests/test_data/graph_check_single_app/ten_packages/extension/addon_b/manifest.json b/core/src/ten_rust/tests/test_data/graph_check_single_app/ten_packages/extension/addon_b/manifest.json new file mode 100644 index 000000000..f5f889708 --- /dev/null +++ b/core/src/ten_rust/tests/test_data/graph_check_single_app/ten_packages/extension/addon_b/manifest.json @@ -0,0 +1,29 @@ +{ + "type": "extension", + "name": "addon_b", + "version": "0.1.0", + "dependencies": [ + { + "type": "system", + "name": "ten_runtime_go", + "version": "0.1.0" + } + ], + "package": { + "include": [ + "**" + ] + }, + "api": { + "cmd_in": [ + { + "name": "cmd_1", + "property": { + "foo": { + "type": "string" + } + } + } + ] + } +} \ No newline at end of file diff --git a/core/src/ten_rust/tests/test_data/graph_check_single_app_schema_incompatible/graph.json b/core/src/ten_rust/tests/test_data/graph_check_single_app_schema_incompatible/graph.json new file mode 100644 index 000000000..63746a303 --- /dev/null +++ b/core/src/ten_rust/tests/test_data/graph_check_single_app_schema_incompatible/graph.json @@ -0,0 +1,49 @@ +{ + "nodes": [ + { + "type": "extension", + "name": "ext_a", + "addon": "addon_a", + "extension_group": "some_group", + "app": "http://localhost:8001" + }, + { + "type": "extension", + "name": "ext_b", + "addon": "addon_b", + "extension_group": "some_group", + "app": "http://localhost:8001" + }, + { + "type": "extension", + "name": "ext_c", + "addon": "addon_c", + "extension_group": "some_group", + "app": "http://localhost:8002" + } + ], + "connections": [ + { + "extension_group": "some_group", + "extension": "ext_a", + "app": "http://localhost:8001", + "cmd": [ + { + "name": "cmd_1", + "dest": [ + { + "extension_group": "some_group", + "extension": "ext_b", + "app": "http://localhost:8001" + }, + { + "extension_group": "some_group", + "extension": "ext_c", + "app": "http://localhost:8002" + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/core/src/ten_rust/tests/test_data/graph_check_single_app_schema_incompatible/manifest.json b/core/src/ten_rust/tests/test_data/graph_check_single_app_schema_incompatible/manifest.json new file mode 100644 index 000000000..7cb8a2ca0 --- /dev/null +++ b/core/src/ten_rust/tests/test_data/graph_check_single_app_schema_incompatible/manifest.json @@ -0,0 +1,22 @@ +{ + "type": "app", + "name": "check_predefined_graph_success", + "version": "0.1.0", + "dependencies": [ + { + "type": "extension", + "name": "addon_a", + "version": "0.1.0" + }, + { + "type": "extension", + "name": "addon_b", + "version": "0.1.0" + } + ], + "package": { + "include": [ + "**" + ] + } +} \ No newline at end of file diff --git a/core/src/ten_rust/tests/test_data/graph_check_single_app_schema_incompatible/property.json b/core/src/ten_rust/tests/test_data/graph_check_single_app_schema_incompatible/property.json new file mode 100644 index 000000000..d2ce191d0 --- /dev/null +++ b/core/src/ten_rust/tests/test_data/graph_check_single_app_schema_incompatible/property.json @@ -0,0 +1,5 @@ +{ + "_ten": { + "uri": "http://localhost:8001" + } +} \ No newline at end of file diff --git a/core/src/ten_rust/tests/test_data/graph_check_single_app_schema_incompatible/ten_packages/extension/addon_a/manifest.json b/core/src/ten_rust/tests/test_data/graph_check_single_app_schema_incompatible/ten_packages/extension/addon_a/manifest.json new file mode 100644 index 000000000..06ea41478 --- /dev/null +++ b/core/src/ten_rust/tests/test_data/graph_check_single_app_schema_incompatible/ten_packages/extension/addon_a/manifest.json @@ -0,0 +1,29 @@ +{ + "type": "extension", + "name": "addon_a", + "version": "0.1.0", + "dependencies": [ + { + "type": "system", + "name": "ten_runtime_go", + "version": "0.1.0" + } + ], + "package": { + "include": [ + "**" + ] + }, + "api": { + "cmd_out": [ + { + "name": "cmd_1", + "property": { + "foo": { + "type": "string" + } + } + } + ] + } +} \ No newline at end of file diff --git a/core/src/ten_rust/tests/test_data/graph_check_single_app_schema_incompatible/ten_packages/extension/addon_b/manifest.json b/core/src/ten_rust/tests/test_data/graph_check_single_app_schema_incompatible/ten_packages/extension/addon_b/manifest.json new file mode 100644 index 000000000..9821e29f4 --- /dev/null +++ b/core/src/ten_rust/tests/test_data/graph_check_single_app_schema_incompatible/ten_packages/extension/addon_b/manifest.json @@ -0,0 +1,18 @@ +{ + "type": "extension", + "name": "addon_b", + "version": "0.1.0", + "dependencies": [ + { + "type": "system", + "name": "ten_runtime_go", + "version": "0.1.0" + } + ], + "package": { + "include": [ + "**" + ] + }, + "api": {} +} \ No newline at end of file diff --git a/tests/ten_runtime/integration/cpp/BUILD.gn b/tests/ten_runtime/integration/cpp/BUILD.gn index 76dcc8528..469176238 100644 --- a/tests/ten_runtime/integration/cpp/BUILD.gn +++ b/tests/ten_runtime/integration/cpp/BUILD.gn @@ -14,6 +14,7 @@ group("cpp") { "hello_world", "http_basic", "large_result", + "multi_apps", "standalone_test_cpp", ] diff --git a/tests/ten_runtime/integration/cpp/multi_apps/BUILD.gn b/tests/ten_runtime/integration/cpp/multi_apps/BUILD.gn new file mode 100644 index 000000000..32d666e3a --- /dev/null +++ b/tests/ten_runtime/integration/cpp/multi_apps/BUILD.gn @@ -0,0 +1,89 @@ +# +# 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. +# +import("//build/ten_runtime/feature/test.gni") +import("//build/ten_runtime/ten.gni") + +ten_package_test_prepare_app("app_1") { + src_app = "default_app_cpp" + src_app_language = "cpp" + generated_app_src_root_dir_name = "app_1_source" + + replace_files_after_install_app = [ + "app_1_source/manifest.json", + "app_1_source/property.json", + "app_1_source/ten_packages", + ] + + if (ten_enable_package_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_app("app_2") { + src_app = "default_app_cpp" + src_app_language = "cpp" + generated_app_src_root_dir_name = "app_2_source" + + replace_files_after_install_app = [ + "app_2_source/manifest.json", + "app_2_source/property.json", + "app_2_source/ten_packages", + ] + + if (ten_enable_package_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("multi_apps_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("multi_apps_test_files") { + resources = [ + "//tests/ten_runtime/integration/common=>common", + "__init__.py", + "test_case.py", + ] + if (enable_sanitizer && !is_clang) { + resources += [ "//tests/ten_runtime/integration/tools/use_asan_lib_marker=>use_asan_lib_marker" ] + } +} + +group("multi_apps") { + deps = [ + ":app_1", + ":app_2", + ":multi_apps_client", + ":multi_apps_test_files", + ] +} diff --git a/tests/ten_runtime/integration/cpp/multi_apps/__init__.py b/tests/ten_runtime/integration/cpp/multi_apps/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/ten_runtime/integration/cpp/multi_apps/app_1_source/manifest.json b/tests/ten_runtime/integration/cpp/multi_apps/app_1_source/manifest.json new file mode 100644 index 000000000..33e2f1a42 --- /dev/null +++ b/tests/ten_runtime/integration/cpp/multi_apps/app_1_source/manifest.json @@ -0,0 +1,24 @@ +{ + "dependencies": [ + { + "type": "system", + "name": "ten_runtime", + "version": "0.3.0-alpha" + }, + { + "type": "extension", + "name": "ext_a", + "version": "0.3.0-alpha" + }, + { + "type": "extension", + "name": "ext_b", + "version": "0.3.0-alpha" + }, + { + "type": "protocol", + "name": "msgpack", + "version": "0.3.0-alpha" + } + ] +} diff --git a/tests/ten_runtime/integration/cpp/multi_apps/app_1_source/property.json b/tests/ten_runtime/integration/cpp/multi_apps/app_1_source/property.json new file mode 100644 index 000000000..5cd22bf8e --- /dev/null +++ b/tests/ten_runtime/integration/cpp/multi_apps/app_1_source/property.json @@ -0,0 +1,47 @@ +{ + "_ten": { + "long_running_mode": true, + "uri": "msgpack://127.0.0.1:8001/", + "predefined_graphs": [ + { + "name": "default", + "auto_start": true, + "nodes": [ + { + "type": "extension", + "name": "ext_a", + "addon": "ext_a", + "app": "msgpack://127.0.0.1:8001/", + "extension_group": "test_extension_group" + }, + { + "type": "extension", + "name": "ext_b", + "addon": "ext_b", + "app": "msgpack://127.0.0.1:8001/", + "extension_group": "test_extension_group" + } + ], + "connections": [ + { + "app": "msgpack://127.0.0.1:8001/", + "extension_group": "test_extension_group", + "extension": "ext_a", + "cmd": [ + { + "name": "hello_world", + "dest": [ + { + "app": "msgpack://127.0.0.1:8001/", + "extension_group": "test_extension_group", + "extension": "ext_b" + } + ] + } + ] + } + ] + } + ] + } +} \ No newline at end of file diff --git a/tests/ten_runtime/integration/cpp/multi_apps/app_1_source/ten_packages/extension/ext_a/BUILD.gn b/tests/ten_runtime/integration/cpp/multi_apps/app_1_source/ten_packages/extension/ext_a/BUILD.gn new file mode 100644 index 000000000..30638fb1d --- /dev/null +++ b/tests/ten_runtime/integration/cpp/multi_apps/app_1_source/ten_packages/extension/ext_a/BUILD.gn @@ -0,0 +1,20 @@ +# +# 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. +# +import("//build/feature/ten_package.gni") + +ten_package("ext_a") { + package_kind = "extension" + enable_build = true + + resources = [ + "manifest.json", + "property.json", + ] + + sources = [ "src/main.cc" ] + include_dirs = [ "include" ] +} diff --git a/tests/ten_runtime/integration/cpp/multi_apps/app_1_source/ten_packages/extension/ext_a/manifest.json b/tests/ten_runtime/integration/cpp/multi_apps/app_1_source/ten_packages/extension/ext_a/manifest.json new file mode 100644 index 000000000..c23b394da --- /dev/null +++ b/tests/ten_runtime/integration/cpp/multi_apps/app_1_source/ten_packages/extension/ext_a/manifest.json @@ -0,0 +1,27 @@ +{ + "type": "extension", + "name": "ext_a", + "version": "0.3.0-alpha", + "dependencies": [ + { + "type": "system", + "name": "ten_runtime", + "version": "0.3.0-alpha" + } + ], + "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": [] + } +} diff --git a/tests/ten_runtime/integration/cpp/multi_apps/app_1_source/ten_packages/extension/ext_a/property.json b/tests/ten_runtime/integration/cpp/multi_apps/app_1_source/ten_packages/extension/ext_a/property.json new file mode 100644 index 000000000..eeedd4007 --- /dev/null +++ b/tests/ten_runtime/integration/cpp/multi_apps/app_1_source/ten_packages/extension/ext_a/property.json @@ -0,0 +1,3 @@ +{ + "hello": "world" +} \ No newline at end of file diff --git a/tests/ten_runtime/integration/cpp/multi_apps/app_1_source/ten_packages/extension/ext_a/src/main.cc b/tests/ten_runtime/integration/cpp/multi_apps/app_1_source/ten_packages/extension/ext_a/src/main.cc new file mode 100644 index 000000000..4ae1e8a5e --- /dev/null +++ b/tests/ten_runtime/integration/cpp/multi_apps/app_1_source/ten_packages/extension/ext_a/src/main.cc @@ -0,0 +1,39 @@ +// +// 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 +#include + +#include "ten_runtime/binding/cpp/ten.h" + +class ext_a : public ten::extension_t { + public: + explicit ext_a(const std::string &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 (std::string(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(ext_a, ext_a); diff --git a/tests/ten_runtime/integration/cpp/multi_apps/app_1_source/ten_packages/extension/ext_b/BUILD.gn b/tests/ten_runtime/integration/cpp/multi_apps/app_1_source/ten_packages/extension/ext_b/BUILD.gn new file mode 100644 index 000000000..655be02c6 --- /dev/null +++ b/tests/ten_runtime/integration/cpp/multi_apps/app_1_source/ten_packages/extension/ext_b/BUILD.gn @@ -0,0 +1,20 @@ +# +# 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. +# +import("//build/feature/ten_package.gni") + +ten_package("ext_b") { + package_kind = "extension" + enable_build = true + + resources = [ + "manifest.json", + "property.json", + ] + + sources = [ "src/main.cc" ] + include_dirs = [ "include" ] +} diff --git a/tests/ten_runtime/integration/cpp/multi_apps/app_1_source/ten_packages/extension/ext_b/manifest.json b/tests/ten_runtime/integration/cpp/multi_apps/app_1_source/ten_packages/extension/ext_b/manifest.json new file mode 100644 index 000000000..155033937 --- /dev/null +++ b/tests/ten_runtime/integration/cpp/multi_apps/app_1_source/ten_packages/extension/ext_b/manifest.json @@ -0,0 +1,27 @@ +{ + "type": "extension", + "name": "ext_b", + "version": "0.3.0-alpha", + "dependencies": [ + { + "type": "system", + "name": "ten_runtime", + "version": "0.3.0-alpha" + } + ], + "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": [] + } +} diff --git a/tests/ten_runtime/integration/cpp/multi_apps/app_1_source/ten_packages/extension/ext_b/property.json b/tests/ten_runtime/integration/cpp/multi_apps/app_1_source/ten_packages/extension/ext_b/property.json new file mode 100644 index 000000000..eeedd4007 --- /dev/null +++ b/tests/ten_runtime/integration/cpp/multi_apps/app_1_source/ten_packages/extension/ext_b/property.json @@ -0,0 +1,3 @@ +{ + "hello": "world" +} \ No newline at end of file diff --git a/tests/ten_runtime/integration/cpp/multi_apps/app_1_source/ten_packages/extension/ext_b/src/main.cc b/tests/ten_runtime/integration/cpp/multi_apps/app_1_source/ten_packages/extension/ext_b/src/main.cc new file mode 100644 index 000000000..1185a8da9 --- /dev/null +++ b/tests/ten_runtime/integration/cpp/multi_apps/app_1_source/ten_packages/extension/ext_b/src/main.cc @@ -0,0 +1,39 @@ +// +// 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 +#include + +#include "ten_runtime/binding/cpp/ten.h" + +class ext_b : public ten::extension_t { + public: + explicit ext_b(const std::string &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 (std::string(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(ext_b, ext_b); diff --git a/tests/ten_runtime/integration/cpp/multi_apps/app_2_source/manifest.json b/tests/ten_runtime/integration/cpp/multi_apps/app_2_source/manifest.json new file mode 100644 index 000000000..c7d38b8d8 --- /dev/null +++ b/tests/ten_runtime/integration/cpp/multi_apps/app_2_source/manifest.json @@ -0,0 +1,24 @@ +{ + "dependencies": [ + { + "type": "system", + "name": "ten_runtime", + "version": "0.3.0-alpha" + }, + { + "type": "extension", + "name": "ext_c", + "version": "0.3.0-alpha" + }, + { + "type": "extension", + "name": "ext_d", + "version": "0.3.0-alpha" + }, + { + "type": "protocol", + "name": "msgpack", + "version": "0.3.0-alpha" + } + ] +} diff --git a/tests/ten_runtime/integration/cpp/multi_apps/app_2_source/property.json b/tests/ten_runtime/integration/cpp/multi_apps/app_2_source/property.json new file mode 100644 index 000000000..072fe116c --- /dev/null +++ b/tests/ten_runtime/integration/cpp/multi_apps/app_2_source/property.json @@ -0,0 +1,21 @@ +{ + "_ten": { + "long_running_mode": true, + "uri": "msgpack://127.0.0.1:8002/", + "predefined_graphs": [ + { + "name": "default", + "auto_start": true, + "nodes": [ + { + "type": "extension", + "name": "test_extension", + "addon": "ext_c", + "app": "msgpack://127.0.0.1:8002/", + "extension_group": "test_extension_group" + } + ] + } + ] + } +} diff --git a/tests/ten_runtime/integration/cpp/multi_apps/app_2_source/ten_packages/extension/ext_c/BUILD.gn b/tests/ten_runtime/integration/cpp/multi_apps/app_2_source/ten_packages/extension/ext_c/BUILD.gn new file mode 100644 index 000000000..081bdca9f --- /dev/null +++ b/tests/ten_runtime/integration/cpp/multi_apps/app_2_source/ten_packages/extension/ext_c/BUILD.gn @@ -0,0 +1,20 @@ +# +# 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. +# +import("//build/feature/ten_package.gni") + +ten_package("ext_c") { + package_kind = "extension" + enable_build = true + + resources = [ + "manifest.json", + "property.json", + ] + + sources = [ "src/main.cc" ] + include_dirs = [ "include" ] +} diff --git a/tests/ten_runtime/integration/cpp/multi_apps/app_2_source/ten_packages/extension/ext_c/manifest.json b/tests/ten_runtime/integration/cpp/multi_apps/app_2_source/ten_packages/extension/ext_c/manifest.json new file mode 100644 index 000000000..c1081321d --- /dev/null +++ b/tests/ten_runtime/integration/cpp/multi_apps/app_2_source/ten_packages/extension/ext_c/manifest.json @@ -0,0 +1,27 @@ +{ + "type": "extension", + "name": "ext_c", + "version": "0.3.0-alpha", + "dependencies": [ + { + "type": "system", + "name": "ten_runtime", + "version": "0.3.0-alpha" + } + ], + "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": [] + } +} diff --git a/tests/ten_runtime/integration/cpp/multi_apps/app_2_source/ten_packages/extension/ext_c/property.json b/tests/ten_runtime/integration/cpp/multi_apps/app_2_source/ten_packages/extension/ext_c/property.json new file mode 100644 index 000000000..eeedd4007 --- /dev/null +++ b/tests/ten_runtime/integration/cpp/multi_apps/app_2_source/ten_packages/extension/ext_c/property.json @@ -0,0 +1,3 @@ +{ + "hello": "world" +} \ No newline at end of file diff --git a/tests/ten_runtime/integration/cpp/multi_apps/app_2_source/ten_packages/extension/ext_c/src/main.cc b/tests/ten_runtime/integration/cpp/multi_apps/app_2_source/ten_packages/extension/ext_c/src/main.cc new file mode 100644 index 000000000..050d6eddc --- /dev/null +++ b/tests/ten_runtime/integration/cpp/multi_apps/app_2_source/ten_packages/extension/ext_c/src/main.cc @@ -0,0 +1,39 @@ +// +// 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 +#include + +#include "ten_runtime/binding/cpp/ten.h" + +class ext_c : public ten::extension_t { + public: + explicit ext_c(const std::string &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 (std::string(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(ext_c, ext_c); diff --git a/tests/ten_runtime/integration/cpp/multi_apps/app_2_source/ten_packages/extension/ext_d/BUILD.gn b/tests/ten_runtime/integration/cpp/multi_apps/app_2_source/ten_packages/extension/ext_d/BUILD.gn new file mode 100644 index 000000000..c4ecefddb --- /dev/null +++ b/tests/ten_runtime/integration/cpp/multi_apps/app_2_source/ten_packages/extension/ext_d/BUILD.gn @@ -0,0 +1,20 @@ +# +# 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. +# +import("//build/feature/ten_package.gni") + +ten_package("ext_d") { + package_kind = "extension" + enable_build = true + + resources = [ + "manifest.json", + "property.json", + ] + + sources = [ "src/main.cc" ] + include_dirs = [ "include" ] +} diff --git a/tests/ten_runtime/integration/cpp/multi_apps/app_2_source/ten_packages/extension/ext_d/manifest.json b/tests/ten_runtime/integration/cpp/multi_apps/app_2_source/ten_packages/extension/ext_d/manifest.json new file mode 100644 index 000000000..fd53f68f3 --- /dev/null +++ b/tests/ten_runtime/integration/cpp/multi_apps/app_2_source/ten_packages/extension/ext_d/manifest.json @@ -0,0 +1,27 @@ +{ + "type": "extension", + "name": "ext_d", + "version": "0.3.0-alpha", + "dependencies": [ + { + "type": "system", + "name": "ten_runtime", + "version": "0.3.0-alpha" + } + ], + "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": [] + } +} diff --git a/tests/ten_runtime/integration/cpp/multi_apps/app_2_source/ten_packages/extension/ext_d/property.json b/tests/ten_runtime/integration/cpp/multi_apps/app_2_source/ten_packages/extension/ext_d/property.json new file mode 100644 index 000000000..eeedd4007 --- /dev/null +++ b/tests/ten_runtime/integration/cpp/multi_apps/app_2_source/ten_packages/extension/ext_d/property.json @@ -0,0 +1,3 @@ +{ + "hello": "world" +} \ No newline at end of file diff --git a/tests/ten_runtime/integration/cpp/multi_apps/app_2_source/ten_packages/extension/ext_d/src/main.cc b/tests/ten_runtime/integration/cpp/multi_apps/app_2_source/ten_packages/extension/ext_d/src/main.cc new file mode 100644 index 000000000..980697c7b --- /dev/null +++ b/tests/ten_runtime/integration/cpp/multi_apps/app_2_source/ten_packages/extension/ext_d/src/main.cc @@ -0,0 +1,39 @@ +// +// 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 +#include + +#include "ten_runtime/binding/cpp/ten.h" + +class ext_d : public ten::extension_t { + public: + explicit ext_d(const std::string &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 (std::string(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(ext_d, ext_d); diff --git a/tests/ten_runtime/integration/cpp/multi_apps/client/client.cc b/tests/ten_runtime/integration/cpp/multi_apps/client/client.cc new file mode 100644 index 000000000..d2261fc94 --- /dev/null +++ b/tests/ten_runtime/integration/cpp/multi_apps/client/client.cc @@ -0,0 +1,154 @@ +// +// 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 + +#include "ten_utils/macro/check.h" +#include "tests/common/client/cpp/msgpack_tcp.h" + +namespace { + +void test_extension_in_app1_not_installed() { + // Create a client and connect to the app. + auto *client = new ten::msgpack_tcp_client_t("msgpack://127.0.0.1:8001/"); + + // Send a start_graph cmd to app 8001. However, because there is no extension + // addon named `ext_e` in app 8001, the `start_graph` command will fail. + nlohmann::json resp = client->send_json_and_recv_resp_in_json( + R"({ + "_ten": { + "type": "start_graph", + "seq_id": "55", + "nodes": [{ + "type": "extension", + "name": "ext_a", + "addon": "ext_e", + "app": "msgpack://127.0.0.1:8001/", + "extension_group": "test_extension_group" + }, { + "type": "extension", + "name": "ext_b", + "addon": "ext_b", + "app": "msgpack://127.0.0.1:8002/", + "extension_group": "test_extension_group" + }] + } + })"_json); + TEN_ASSERT(TEN_STATUS_CODE_ERROR == resp["_ten"]["status_code"], + "Should not happen."); + + auto detail = resp.value("detail", ""); + // NOLINTNEXTLINE + TEN_ASSERT(!detail.empty() && detail.find("ext_e") != std::string::npos, + "Should not happen."); + + delete client; +} + +void test_extension_in_app2_not_installed() { + // Create a client and connect to the app. + auto *client = new ten::msgpack_tcp_client_t("msgpack://127.0.0.1:8001/"); + + // Send a start_graph cmd to app 8001. However, because there is no extension + // addon named `ext_e` in app 8002, the `start_graph` command will fail. + auto resp = client->send_json_and_recv_resp_in_json( + R"({ + "_ten": { + "type": "start_graph", + "seq_id": "56", + "nodes": [{ + "type": "extension", + "name": "ext_a", + "addon": "ext_a", + "app": "msgpack://127.0.0.1:8001/", + "extension_group": "test_extension_group" + }, { + "type": "extension", + "name": "ext_b", + "addon": "ext_e", + "app": "msgpack://127.0.0.1:8002/", + "extension_group": "test_extension_group" + }], + "connections": [{ + "app": "msgpack://127.0.0.1:8001/", + "extension_group": "test_extension_group", + "extension": "ext_a", + "cmd": [{ + "name": "hello_world", + "dest": [{ + "app": "msgpack://127.0.0.1:8002/", + "extension_group": "test_extension_group", + "extension": "ext_b" + }] + }] + }] + } + })"_json); + TEN_ASSERT(TEN_STATUS_CODE_ERROR == resp["_ten"]["status_code"], + "Should not happen."); + + auto detail = resp.value("detail", ""); + // NOLINTNEXTLINE + TEN_ASSERT(!detail.empty() && detail.find("ext_e") != std::string::npos, + "Should not happen."); + + delete client; +} + +} // namespace + +int main(int argc, char **argv) { + test_extension_in_app1_not_installed(); + test_extension_in_app2_not_installed(); + + // Create a client and connect to the app. + auto *client = new ten::msgpack_tcp_client_t("msgpack://127.0.0.1:8001/"); + + auto resp = client->send_json_and_recv_resp_in_json( + R"({ + "_ten": { + "type": "start_graph", + "seq_id": "55", + "nodes": [{ + "type": "extension", + "name": "ext_a", + "addon": "ext_a", + "app": "msgpack://127.0.0.1:8001/", + "extension_group": "test_extension_group" + }] + } + })"_json); + TEN_ASSERT(TEN_STATUS_CODE_OK == resp["_ten"]["status_code"], + "Should not happen."); + + // Send a user-defined 'hello world' command. + resp = client->send_json_and_recv_resp_in_json( + R"({ + "_ten": { + "name": "hello_world", + "seq_id": "137", + "dest": [{ + "app": "msgpack://127.0.0.1:8001/", + "extension_group": "test_extension_group", + "extension": "ext_a" + }] + } + })"_json); + TEN_ASSERT(TEN_STATUS_CODE_OK == resp["_ten"]["status_code"], + "Should not happen."); + TEN_ASSERT(static_cast("137") == resp["_ten"]["seq_id"], + "Should not happen."); + TEN_ASSERT(static_cast("hello world, too") == resp["detail"], + "Should not happen."); + + client->close_app(); + + delete client; + + auto *client2 = new ten::msgpack_tcp_client_t("msgpack://127.0.0.1:8002/"); + client2->close_app(); + delete client2; +} diff --git a/tests/ten_runtime/integration/cpp/multi_apps/test_case.py b/tests/ten_runtime/integration/cpp/multi_apps/test_case.py new file mode 100644 index 000000000..029c5c727 --- /dev/null +++ b/tests/ten_runtime/integration/cpp/multi_apps/test_case.py @@ -0,0 +1,163 @@ +""" +Test hello_world_app. +""" + +import subprocess +import os +import sys +from sys import stdout +from .common import msgpack + + +def install_app(app_name: str): + base_path = os.path.dirname(os.path.abspath(__file__)) + root_dir = os.path.join(base_path, "../../../../../") + + my_env = os.environ.copy() + + app_root_path = os.path.join(base_path, app_name) + + tman_install_cmd = [ + os.path.join(root_dir, "ten_manager/bin/tman"), + "--config-file", + os.path.join(root_dir, "tests/local_registry/config.json"), + "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() + + +def start_app(app_name: str, port: int) -> subprocess.Popen: + base_path = os.path.dirname(os.path.abspath(__file__)) + app_root_path = os.path.join(base_path, app_name) + + my_env = os.environ.copy() + + if sys.platform == "win32": + my_env["PATH"] = ( + os.path.join( + base_path, f"{app_name}/ten_packages/system/ten_runtime/lib" + ) + + ";" + + my_env["PATH"] + ) + server_cmd = os.path.join( + base_path, f"{app_name}/bin/{app_name}_source.exe" + ) + elif sys.platform == "darwin": + my_env["DYLD_LIBRARY_PATH"] = os.path.join( + base_path, f"{app_name}/ten_packages/system/ten_runtime/lib" + ) + server_cmd = os.path.join( + base_path, f"{app_name}/bin/{app_name}_source" + ) + else: + # client depends on some libraries in the TEN app. + my_env["LD_LIBRARY_PATH"] = os.path.join( + base_path, f"{app_name}/ten_packages/system/ten_runtime/lib" + ) + server_cmd = os.path.join( + base_path, f"{app_name}/bin/{app_name}_source" + ) + + if os.path.exists(os.path.join(base_path, "use_asan_lib_marker")): + libasan_path = os.path.join( + base_path, + f"{app_name}/ten_packages/system/ten_runtime/lib/libasan.so", + ) + if os.path.exists(libasan_path): + my_env["LD_PRELOAD"] = libasan_path + + 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", port, 10) + if not is_started: + print(f"The {app_name} is not started after 30 seconds.") + + server.kill() + exit_code = server.wait() + print(f"The exit code of {app_name}: {exit_code}") + + assert 0 + + sock.close() + return server + + +def start_client(app_name: str) -> subprocess.Popen: + base_path = os.path.dirname(os.path.abspath(__file__)) + my_env = os.environ.copy() + + if sys.platform == "win32": + my_env["PATH"] = ( + os.path.join( + base_path, f"{app_name}/ten_packages/system/ten_runtime/lib" + ) + + ";" + + my_env["PATH"] + ) + client_cmd = os.path.join(base_path, "multi_apps_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, f"{app_name}/ten_packages/system/ten_runtime/lib" + ) + client_cmd = os.path.join(base_path, "multi_apps_client") + else: + # client depends on some libraries in the TEN app. + my_env["LD_LIBRARY_PATH"] = os.path.join( + base_path, f"{app_name}/ten_packages/system/ten_runtime/lib" + ) + client_cmd = os.path.join(base_path, "multi_apps_client") + + if os.path.exists(os.path.join(base_path, "use_asan_lib_marker")): + libasan_path = os.path.join( + base_path, + f"{app_name}/ten_packages/system/ten_runtime/lib/libasan.so", + ) + if os.path.exists(libasan_path): + my_env["LD_PRELOAD"] = libasan_path + + client = subprocess.Popen( + client_cmd, stdout=stdout, stderr=subprocess.STDOUT, env=my_env + ) + + return client + + +def test_hello_world_app(): + """Test client and app server.""" + install_app("app_1") + install_app("app_2") + + server1 = start_app("app_1", 8001) + server2 = start_app("app_2", 8002) + + client = start_client("app_1") + + # The client will quit after the test is completed. + client_rc = client.wait() + if client_rc != 0: + server1.kill() + server2.kill() + + server1_rc = server1.wait() + server2_rc = server2.wait() + print("server: ", server1_rc, server2_rc) + print("client: ", client_rc) + assert server1_rc == 0 + assert server2_rc == 0 + assert client_rc == 0