diff --git a/.vscode/launch.json b/.vscode/launch.json index 5f0907e094..78570e4d1c 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -398,6 +398,17 @@ "hello_world", ], }, + { + "name": "tman check_graph (lldb)", + "type": "lldb", + "request": "launch", + "program": "${workspaceFolder}/core/src/ten_manager/target/x86_64-unknown-linux-gnu/debug/tman", + "cwd": "${workspaceFolder}/core/src/ten_manager/", + "args": [ + "check-graph", + "/home/workspace/pcm-pusher" + ] + }, { "name": "ten_rust (rust)", "type": "lldb", diff --git a/core/src/ten_manager/BUILD.gn b/core/src/ten_manager/BUILD.gn index bf898b3ee3..bfc344a053 100644 --- a/core/src/ten_manager/BUILD.gn +++ b/core/src/ten_manager/BUILD.gn @@ -13,10 +13,16 @@ declare_args() { use_shared_lib = false } +copy("ten_manager_test_data") { + sources = [ "//core/src/ten_manager/tests/test_data" ] + outputs = [ "${root_out_dir}/tests/standalone/ten_manager/tests/test_data" ] +} + if (ten_enable_package_manager) { if (ten_package_manager_enable_tests) { rust_test("tman_test") { project_path = "//core/src/ten_manager" + integration_test_output_name = "integration_test" clingo_lib_folder = rebase_path("${root_out_dir}/gen/cmake/clingo/install/lib") @@ -37,6 +43,7 @@ if (ten_enable_package_manager) { test_output_dir = "${root_out_dir}/tests/standalone/ten_manager" deps = [ + ":ten_manager_test_data", "//core/src/ten_rust:ten_rust_static_lib", "//third_party/clingo", ] diff --git a/core/src/ten_manager/src/cmd/cmd_check/cmd_check_graph.rs b/core/src/ten_manager/src/cmd/cmd_check/cmd_check_graph.rs new file mode 100644 index 0000000000..29020df052 --- /dev/null +++ b/core/src/ten_manager/src/cmd/cmd_check/cmd_check_graph.rs @@ -0,0 +1,217 @@ +// +// 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, fs, path}; + +use anyhow::{Context, Result}; +use clap::{Arg, ArgMatches, Command}; +use console::Emoji; +use ten_rust::pkg_info::{ + default_app_loc, get_all_existed_pkgs_info_of_app, graph::Graph, + property::parse_property_in_folder, PkgInfo, +}; + +use crate::config::TmanConfig; + +#[derive(Debug)] +pub struct CheckGraphCommand { + pub app: Vec, + pub predefined_graph_name: Option, + pub graph: Option, +} + +pub fn create_sub_cmd(_args_cfg: &crate::cmd_line::ArgsCfg) -> Command { + Command::new("graph") + .about( + "Check the predefined graph or start_graph cmd for the primary \ + app. For more detailed usage, run 'graph -h'", + ) + .arg( + Arg::new("APP") + .long("app") + .help( + "The absolute path of the app declared in the graph. By \ + default, the predefined graph will be read from the first \ + one in the list. ", + ) + .required(true), + ) + .arg( + Arg::new("PREDEFINED_GRAPH_NAME") + .long("predefined-graph-name") + .help( + "Specify the predefined graph name only to be checked, \ + otherwise, all graphs will be checked.", + ) + .required(false) + .conflicts_with("GRAPH"), + ) + .arg( + Arg::new("GRAPH") + .long("graph") + .help( + "Specify the json string of a 'start_graph' cmd to be \ + checked. If not specified, the predefined graph in the \ + primary app will be checked.", + ) + .required(false) + .conflicts_with("PREDEFINED_GRAPH_NAME"), + ) +} + +pub fn parse_sub_cmd( + _sub_cmd_args: &ArgMatches, +) -> crate::cmd::cmd_check::cmd_check_graph::CheckGraphCommand { + let cmd = CheckGraphCommand { + app: _sub_cmd_args + .get_many::("APP") + .unwrap_or_default() + .map(|s| s.to_string()) + .collect(), + predefined_graph_name: _sub_cmd_args + .get_one::("PREDEFINED_GRAPH_NAME") + .cloned(), + graph: _sub_cmd_args.get_one::("GRAPH").cloned(), + }; + + cmd +} + +fn validate_cmd_args(command: &CheckGraphCommand) -> Result<()> { + for app in &command.app { + let stat = fs::metadata(app).with_context(|| { + format!("Failed to get metadata of app path [{}].", app) + })?; + if !stat.is_dir() { + return Err(anyhow::anyhow!( + "App path [{}] is not a directory.", + app + )); + } + } + + Ok(()) +} + +fn get_all_pkg_infos( + command: &CheckGraphCommand, +) -> Result>> { + let mut pkgs_info: HashMap> = HashMap::new(); + + let single_app = command.app.len() == 1; + + for app in &command.app { + let app_path = path::Path::new(app); + + // TODO(Liu): Add a limitation in the schema to ensure that the 'uri' in + // the property.json is not 'localhost'. + let app_property = parse_property_in_folder(app_path)?; + let app_pkgs = get_all_existed_pkgs_info_of_app(app_path)?; + + let app_uri = app_property.get_app_uri(); + if !single_app && app_uri.as_str() == default_app_loc() { + return Err(anyhow::anyhow!( + "The app uri should be some string other than 'localhost' when + using in multi-apps graph." + )); + } + + let present_pkg = pkgs_info.insert(app_uri.clone(), app_pkgs); + if present_pkg.is_some() { + return Err(anyhow::anyhow!( + "All apps should have a unique uri, but uri [{}] is duplicated.", + app_uri + )); + } + } + + Ok(pkgs_info) +} + +fn get_graphs_to_be_checked(command: &CheckGraphCommand) -> Result> { + let mut graphs_to_be_checked: Vec = Vec::new(); + + if let Some(graph_str) = &command.graph { + let graph: Graph = serde_json::from_str(graph_str) + .with_context(|| "The graph json string is invalid")?; + graphs_to_be_checked.push(graph); + } else { + let app_path = path::Path::new(&command.app[0]); + let app_property = parse_property_in_folder(app_path)?; + let predefined_graphs = app_property + ._ten + .and_then(|p| p.predefined_graphs) + .ok_or_else(|| { + anyhow::anyhow!( + "No predefined graph is found in the primary app." + ) + })?; + + if let Some(predefined_graph_name) = &command.predefined_graph_name { + let predefined_graph = predefined_graphs + .iter() + .find(|g| g.name == predefined_graph_name.as_str()) + .ok_or_else(|| { + anyhow::anyhow!( + "Predefined graph [{}] is not found.", + predefined_graph_name + ) + })?; + graphs_to_be_checked.push(predefined_graph.graph.clone()); + } else { + for predefined_graph in predefined_graphs { + graphs_to_be_checked.push(predefined_graph.graph.clone()); + } + } + } + + Ok(graphs_to_be_checked) +} + +fn display_error(e: &anyhow::Error) { + e.to_string().lines().for_each(|l| { + println!(" {}", l); + }); +} + +pub async fn execute_cmd( + _tman_config: &TmanConfig, + command_data: CheckGraphCommand, +) -> Result<()> { + validate_cmd_args(&command_data)?; + + let all_pkgs = get_all_pkg_infos(&command_data)?; + let graphs = get_graphs_to_be_checked(&command_data)?; + + let mut err_count = 0; + + for (graph_idx, graph) in graphs.iter().enumerate() { + print!("Checking graph[{}]... ", graph_idx); + + match graph.check(&all_pkgs) { + Ok(_) => println!("{}", Emoji("✅", "Passed")), + Err(e) => { + err_count += 1; + println!("{}. Details:", Emoji("❌", "Failed")); + display_error(&e); + println!(); + } + } + } + + println!("All is done."); + + if err_count > 0 { + Err(anyhow::anyhow!( + "{}/{} graphs failed.", + err_count, + graphs.len() + )) + } else { + Ok(()) + } +} diff --git a/core/src/ten_manager/src/cmd/cmd_check/mod.rs b/core/src/ten_manager/src/cmd/cmd_check/mod.rs new file mode 100644 index 0000000000..0aec165820 --- /dev/null +++ b/core/src/ten_manager/src/cmd/cmd_check/mod.rs @@ -0,0 +1,54 @@ +// +// 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. +// +pub mod cmd_check_graph; + +use anyhow::Result; +use clap::{ArgMatches, Command}; + +use crate::config::TmanConfig; + +#[derive(Debug)] +pub enum CheckCommandData { + CheckGraph(crate::cmd::cmd_check::cmd_check_graph::CheckGraphCommand), +} + +pub fn create_sub_cmd(args_cfg: &crate::cmd_line::ArgsCfg) -> Command { + Command::new("check") + .about("Check cmd group. For more detailed usage, run 'check -h'") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand(crate::cmd::cmd_check::cmd_check_graph::create_sub_cmd( + args_cfg, + )) +} + +pub fn parse_sub_cmd(sub_cmd_args: &ArgMatches) -> CheckCommandData { + match sub_cmd_args.subcommand() { + Some(("graph", graph_cmd_args)) => CheckCommandData::CheckGraph( + crate::cmd::cmd_check::cmd_check_graph::parse_sub_cmd( + graph_cmd_args, + ), + ), + + _ => unreachable!("Command not found"), + } +} + +pub async fn execute_cmd( + tman_config: &TmanConfig, + command_data: CheckCommandData, +) -> Result<()> { + match command_data { + CheckCommandData::CheckGraph(cmd) => { + crate::cmd::cmd_check::cmd_check_graph::execute_cmd( + tman_config, + cmd, + ) + .await + } + } +} diff --git a/core/src/ten_manager/src/cmd/mod.rs b/core/src/ten_manager/src/cmd/mod.rs index 59616039a2..cfa905b388 100644 --- a/core/src/ten_manager/src/cmd/mod.rs +++ b/core/src/ten_manager/src/cmd/mod.rs @@ -4,6 +4,7 @@ // Licensed under the Apache License, Version 2.0, with certain conditions. // Refer to the "LICENSE" file in the root directory for more information. // +pub mod cmd_check; pub mod cmd_delete; pub mod cmd_dev_server; pub mod cmd_install; @@ -22,6 +23,7 @@ pub enum CommandData { Publish(self::cmd_publish::PublishCommand), Delete(self::cmd_delete::DeleteCommand), DevServer(self::cmd_dev_server::DevServerCommand), + Check(self::cmd_check::CheckCommandData), } pub async fn execute_cmd( @@ -47,5 +49,8 @@ pub async fn execute_cmd( CommandData::DevServer(cmd) => { crate::cmd::cmd_dev_server::execute_cmd(tman_config, cmd).await } + CommandData::Check(cmd) => { + crate::cmd::cmd_check::execute_cmd(tman_config, cmd).await + } } } diff --git a/core/src/ten_manager/src/cmd_line.rs b/core/src/ten_manager/src/cmd_line.rs index beaced7814..e38d3e38a6 100644 --- a/core/src/ten_manager/src/cmd_line.rs +++ b/core/src/ten_manager/src/cmd_line.rs @@ -101,6 +101,7 @@ fn create_cmd() -> clap::ArgMatches { .subcommand(crate::cmd::cmd_publish::create_sub_cmd(&args_cfg)) .subcommand(crate::cmd::cmd_delete::create_sub_cmd(&args_cfg)) .subcommand(crate::cmd::cmd_dev_server::create_sub_cmd(&args_cfg)) + .subcommand(crate::cmd::cmd_check::create_sub_cmd(&args_cfg)) .get_matches() } @@ -139,6 +140,9 @@ pub fn parse_cmd( crate::cmd::cmd_dev_server::parse_sub_cmd(sub_cmd_args), ) } + Some(("check", sub_cmd_args)) => crate::cmd::CommandData::Check( + crate::cmd::cmd_check::parse_sub_cmd(sub_cmd_args), + ), _ => unreachable!("Command not found"), } } diff --git a/core/src/ten_manager/src/dev_server/graphs/connections.rs b/core/src/ten_manager/src/dev_server/graphs/connections.rs index f9edacafc0..31dc6b4b63 100644 --- a/core/src/ten_manager/src/dev_server/graphs/connections.rs +++ b/core/src/ten_manager/src/dev_server/graphs/connections.rs @@ -40,7 +40,7 @@ pub struct DevServerConnection { impl From for DevServerConnection { fn from(conn: GraphConnection) -> Self { DevServerConnection { - app: conn.app, + app: conn.get_app_uri().to_string(), extension_group: conn.extension_group, extension: conn.extension, @@ -94,7 +94,7 @@ pub struct DevServerDestination { impl From for DevServerDestination { fn from(destination: GraphDestination) -> Self { DevServerDestination { - app: destination.app, + app: destination.get_app_uri().to_string(), extension_group: destination.extension_group, extension: destination.extension, } diff --git a/core/src/ten_manager/src/dev_server/graphs/nodes.rs b/core/src/ten_manager/src/dev_server/graphs/nodes.rs index afe5211177..a4647ea7db 100644 --- a/core/src/ten_manager/src/dev_server/graphs/nodes.rs +++ b/core/src/ten_manager/src/dev_server/graphs/nodes.rs @@ -259,7 +259,7 @@ pub async fn get_graph_nodes( addon: extension.addon.clone(), name: extension.name.clone(), extension_group: extension.extension_group.clone().unwrap(), - app: extension.app.clone(), + app: extension.app.as_ref().unwrap().clone(), api: pkg_info.api.as_ref().map(|api| DevServerApi { property: if api.property.is_empty() { None diff --git a/core/src/ten_manager/src/dev_server/messages/compatible.rs b/core/src/ten_manager/src/dev_server/messages/compatible.rs index 72c289f214..f0653142b5 100644 --- a/core/src/ten_manager/src/dev_server/messages/compatible.rs +++ b/core/src/ten_manager/src/dev_server/messages/compatible.rs @@ -50,7 +50,7 @@ pub struct DevServerCompatibleMsg { impl From> for DevServerCompatibleMsg { fn from(compatible: CompatibleExtensionAndMsg) -> Self { DevServerCompatibleMsg { - app: compatible.extension.app.clone(), + app: compatible.extension.app.as_ref().unwrap().clone(), extension_group: compatible .extension .extension_group diff --git a/core/src/ten_manager/src/lib.rs b/core/src/ten_manager/src/lib.rs new file mode 100644 index 0000000000..db8645d738 --- /dev/null +++ b/core/src/ten_manager/src/lib.rs @@ -0,0 +1,58 @@ +// +// 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. +// + +// The `ten_manager` crate is only a binary crate, and does not need to provide +// a `lib.rs` file. However, we can _NOT_ write integration tests (test cases in +// the `tests` folder) for `ten_manager` if it does not have a `lib.rs` file. In +// other words, `use` directive does not work for binary crates. According to +// the official reference, we can provide this `lib.rs` file to export the +// important functions for integration tests, and the `main.rs` should be a thin +// wrapper around the library crate, to provide the binary functionality. Refer +// to: https://doc.rust-lang.org/book/ch11-03-test-organization.html#integration-tests-for-binary-crates. +// +// The `lib.rs` does not affect the final output of the binary crate, as the +// output name of the library is different from the binary crate. +// +// Because of the existence of this `lib.rs` file, the unit test will be +// compiled with the `lib.rs`, but not `main.rs`, and some common settings such +// as the allocator below should be added to the `lib.rs` file. + +pub mod cmd; +pub mod cmd_line; +pub mod config; +pub mod constants; +mod dep_and_candidate; +mod dev_server; +mod error; +mod fs; +mod install; +mod log; +mod manifest_lock; +mod package_file; +mod package_info; +mod registry; +mod solver; +mod utils; +mod version; + +#[cfg(not(target_os = "windows"))] +use mimalloc::MiMalloc; + +// TODO(Wei): When adding a URL route with variables (e.g., /api/{name}) in +// actix-web, using the default allocator can lead to a memory leak. According +// to the information in the internet, this leak is likely a false positive and +// may be related to the caching mechanism in actix-web. However, if we use +// mimalloc or jemalloc, there won't be any leak. Use this method to avoid the +// issue for now and study it in more detail in the future. +// +// Refer to the following posts: +// https://github.com/hyperium/hyper/issues/1790#issuecomment-2170644852 +// https://github.com/actix/actix-web/issues/1780 +// https://news.ycombinator.com/item?id=21962195 +#[cfg(not(target_os = "windows"))] +#[global_allocator] +static GLOBAL: MiMalloc = MiMalloc; diff --git a/core/src/ten_manager/src/main.rs b/core/src/ten_manager/src/main.rs index 4d00337d34..9d823fbbcc 100644 --- a/core/src/ten_manager/src/main.rs +++ b/core/src/ten_manager/src/main.rs @@ -8,48 +8,14 @@ // Enable this when development. // #![allow(dead_code)] -pub mod cmd; -pub mod cmd_line; -pub mod config; -pub mod constants; -mod dep_and_candidate; -mod dev_server; -mod error; -mod fs; -mod install; -mod log; -mod manifest_lock; -mod package_file; -mod package_info; -mod registry; -mod solver; -mod utils; -mod version; - use std::process; use console::Emoji; use tokio::runtime::Runtime; -use config::TmanConfig; - -#[cfg(not(target_os = "windows"))] -use mimalloc::MiMalloc; - -// TODO(Wei): When adding a URL route with variables (e.g., /api/{name}) in -// actix-web, using the default allocator can lead to a memory leak. According -// to the information in the internet, this leak is likely a false positive and -// may be related to the caching mechanism in actix-web. However, if we use -// mimalloc or jemalloc, there won't be any leak. Use this method to avoid the -// issue for now and study it in more detail in the future. -// -// Refer to the following posts: -// https://github.com/hyperium/hyper/issues/1790#issuecomment-2170644852 -// https://github.com/actix/actix-web/issues/1780 -// https://news.ycombinator.com/item?id=21962195 -#[cfg(not(target_os = "windows"))] -#[global_allocator] -static GLOBAL: MiMalloc = MiMalloc; +use ten_manager::cmd; +use ten_manager::cmd_line; +use ten_manager::config::TmanConfig; fn merge(cmd_line: TmanConfig, config_file: TmanConfig) -> TmanConfig { TmanConfig { @@ -67,8 +33,9 @@ fn main() { let mut tman_config_from_cmd_line = TmanConfig::default(); let command_data = cmd_line::parse_cmd(&mut tman_config_from_cmd_line); - let tman_config_from_config_file = - crate::config::read_config(&tman_config_from_cmd_line.config_file); + let tman_config_from_config_file = ten_manager::config::read_config( + &tman_config_from_cmd_line.config_file, + ); let tman_config = merge(tman_config_from_cmd_line, tman_config_from_config_file); diff --git a/core/src/ten_manager/src/package_info/predefined_graphs/connection.rs b/core/src/ten_manager/src/package_info/predefined_graphs/connection.rs index bb08be572d..293def6b99 100644 --- a/core/src/ten_manager/src/package_info/predefined_graphs/connection.rs +++ b/core/src/ten_manager/src/package_info/predefined_graphs/connection.rs @@ -14,7 +14,7 @@ use ten_rust::pkg_info::graph::{ impl From for GraphConnection { fn from(dev_server_connection: DevServerConnection) -> Self { GraphConnection { - app: dev_server_connection.app, + app: Some(dev_server_connection.app), extension_group: dev_server_connection.extension_group, extension: dev_server_connection.extension, @@ -56,7 +56,7 @@ impl From for GraphMessageFlow { impl From for GraphDestination { fn from(dev_server_destination: DevServerDestination) -> Self { GraphDestination { - app: dev_server_destination.app, + app: Some(dev_server_destination.app), extension_group: dev_server_destination.extension_group, extension: dev_server_destination.extension, } diff --git a/core/src/ten_manager/src/package_info/predefined_graphs/node.rs b/core/src/ten_manager/src/package_info/predefined_graphs/node.rs index b6ca6cacc6..7dad589b0c 100644 --- a/core/src/ten_manager/src/package_info/predefined_graphs/node.rs +++ b/core/src/ten_manager/src/package_info/predefined_graphs/node.rs @@ -15,7 +15,7 @@ impl From for GraphNode { name: dev_server_extension.name, addon: dev_server_extension.addon, extension_group: Some(dev_server_extension.extension_group.clone()), - app: dev_server_extension.app, + app: Some(dev_server_extension.app), property: dev_server_extension.property, } } diff --git a/core/src/ten_manager/tests/cmd_check_graph.rs b/core/src/ten_manager/tests/cmd_check_graph.rs new file mode 100644 index 0000000000..8f87d3daf3 --- /dev/null +++ b/core/src/ten_manager/tests/cmd_check_graph.rs @@ -0,0 +1,127 @@ +// +// 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_manager::{ + cmd::cmd_check::cmd_check_graph::CheckGraphCommand, config::TmanConfig, +}; + +#[actix_rt::test] +async fn test_cmd_check_predefined_graph_success() { + let tman_config = TmanConfig::default(); + let command = CheckGraphCommand { + app: vec![ + "tests/test_data/cmd_check_predefined_graph_success".to_string() + ], + graph: None, + predefined_graph_name: None, + }; + + let result = ten_manager::cmd::cmd_check::cmd_check_graph::execute_cmd( + &tman_config, + command, + ) + .await; + assert!(result.is_ok()); +} + +#[actix_rt::test] +async fn test_cmd_check_start_graph_multi_apps() { + let tman_config = TmanConfig::default(); + let command = CheckGraphCommand { + app: vec![ + "tests/test_data/cmd_check_start_graph_multi_apps/app_1" + .to_string(), + "tests/test_data/cmd_check_start_graph_multi_apps/app_2" + .to_string(), + ], + graph: Some( + include_str!( + "test_data/cmd_check_start_graph_multi_apps/start_graph.json" + ) + .to_string(), + ), + predefined_graph_name: None, + }; + + let result = ten_manager::cmd::cmd_check::cmd_check_graph::execute_cmd( + &tman_config, + command, + ) + .await; + + assert!(result.is_err()); + eprintln!("{:?}", result); +} + +#[actix_rt::test] +async fn test_cmd_check_app_in_graph_cannot_be_localhost() { + let tman_config = TmanConfig::default(); + let command = CheckGraphCommand { + app: vec!["tests/test_data/cmd_check_app_in_graph_cannot_be_localhost" + .to_string()], + graph: None, + predefined_graph_name: None, + }; + + let result = ten_manager::cmd::cmd_check::cmd_check_graph::execute_cmd( + &tman_config, + command, + ) + .await; + + assert!(result.is_err()); + eprintln!("{:?}", result); + + let msg = result.err().unwrap().to_string(); + assert!(msg + .contains("the app uri should be some string other than 'localhost'")); +} + +#[actix_rt::test] +async fn test_cmd_check_predefined_graph_only_check_specified() { + let tman_config = TmanConfig::default(); + let command = CheckGraphCommand { + app: vec![ + "tests/test_data/cmd_check_predefined_graph_only_check_specified" + .to_string(), + ], + graph: None, + predefined_graph_name: Some("default".to_string()), + }; + + let result = ten_manager::cmd::cmd_check::cmd_check_graph::execute_cmd( + &tman_config, + command, + ) + .await; + + assert!(result.is_ok()); +} + +#[actix_rt::test] +async fn test_cmd_check_predefined_graph_check_all() { + let tman_config = TmanConfig::default(); + let command = CheckGraphCommand { + app: vec![ + "tests/test_data/cmd_check_predefined_graph_only_check_specified" + .to_string(), + ], + graph: None, + predefined_graph_name: None, + }; + + let result = ten_manager::cmd::cmd_check::cmd_check_graph::execute_cmd( + &tman_config, + command, + ) + .await; + + assert!(result.is_err()); + eprintln!("{:?}", result); + + let msg = result.err().unwrap().to_string(); + assert!(msg.contains("1/2 graphs failed")); +} diff --git a/core/src/ten_manager/tests/integration_test.rs b/core/src/ten_manager/tests/integration_test.rs new file mode 100644 index 0000000000..129ff0602f --- /dev/null +++ b/core/src/ten_manager/tests/integration_test.rs @@ -0,0 +1,18 @@ +// +// 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 +// an executable. We need a all-in-one executable to run all the tests, and we +// will copy the all-in-one executable to the out directory. We use this main +// file to achieve this purpose. + +fn main() { + println!("Running integration tests of ten_man..."); +} + +// Those following mods will be compiled in one executable. +mod cmd_check_graph; diff --git a/core/src/ten_manager/tests/test_data/cmd_check_app_in_graph_cannot_be_localhost/manifest.json b/core/src/ten_manager/tests/test_data/cmd_check_app_in_graph_cannot_be_localhost/manifest.json new file mode 100644 index 0000000000..537723c67f --- /dev/null +++ b/core/src/ten_manager/tests/test_data/cmd_check_app_in_graph_cannot_be_localhost/manifest.json @@ -0,0 +1,15 @@ +{ + "type": "app", + "name": "app", + "version": "0.1.0", + "dependencies": [ + { + "type": "extension", + "name": "addon_a", + "version": "0.1.0" + } + ], + "package": { + "include": ["**"] + } +} diff --git a/core/src/ten_manager/tests/test_data/cmd_check_app_in_graph_cannot_be_localhost/property.json b/core/src/ten_manager/tests/test_data/cmd_check_app_in_graph_cannot_be_localhost/property.json new file mode 100644 index 0000000000..db9f0c18c8 --- /dev/null +++ b/core/src/ten_manager/tests/test_data/cmd_check_app_in_graph_cannot_be_localhost/property.json @@ -0,0 +1,19 @@ +{ + "_ten": { + "predefined_graphs": [ + { + "name": "default", + "auto_start": false, + "nodes": [ + { + "type": "extension", + "name": "ext_a", + "addon": "addon_a", + "extension_group": "some_group", + "app": "localhost" + } + ] + } + ] + } +} diff --git a/core/src/ten_manager/tests/test_data/cmd_check_predefined_graph_only_check_specified/manifest.json b/core/src/ten_manager/tests/test_data/cmd_check_predefined_graph_only_check_specified/manifest.json new file mode 100644 index 0000000000..7cb8a2ca0c --- /dev/null +++ b/core/src/ten_manager/tests/test_data/cmd_check_predefined_graph_only_check_specified/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_manager/tests/test_data/cmd_check_predefined_graph_only_check_specified/property.json b/core/src/ten_manager/tests/test_data/cmd_check_predefined_graph_only_check_specified/property.json new file mode 100644 index 0000000000..ee386b3d94 --- /dev/null +++ b/core/src/ten_manager/tests/test_data/cmd_check_predefined_graph_only_check_specified/property.json @@ -0,0 +1,76 @@ +{ + "_ten": { + "predefined_graphs": [ + { + "name": "default", + "auto_start": false, + "nodes": [ + { + "type": "extension", + "name": "ext_a", + "addon": "addon_a", + "extension_group": "some_group" + }, + { + "type": "extension", + "name": "ext_b", + "addon": "addon_b", + "extension_group": "some_group" + } + ], + "connections": [ + { + "extension_group": "some_group", + "extension": "ext_a", + "cmd": [ + { + "name": "cmd_1", + "dest": [ + { + "extension_group": "some_group", + "extension": "ext_b" + } + ] + } + ] + } + ] + }, + { + "name": "not_checked", + "auto_start": false, + "nodes": [ + { + "type": "extension", + "name": "ext_a", + "addon": "addon_a", + "extension_group": "some_group" + }, + { + "type": "extension", + "name": "ext_b", + "addon": "addon_b", + "extension_group": "some_group" + } + ], + "connections": [ + { + "extension_group": "some_group", + "extension": "ext_a", + "cmd": [ + { + "name": "cmd_1", + "dest": [ + { + "extension_group": "some_group", + "extension": "ext_c" + } + ] + } + ] + } + ] + } + ] + } +} diff --git a/core/src/ten_manager/tests/test_data/cmd_check_predefined_graph_only_check_specified/ten_packages/extension/addon_a/manifest.json b/core/src/ten_manager/tests/test_data/cmd_check_predefined_graph_only_check_specified/ten_packages/extension/addon_a/manifest.json new file mode 100644 index 0000000000..05434a5648 --- /dev/null +++ b/core/src/ten_manager/tests/test_data/cmd_check_predefined_graph_only_check_specified/ten_packages/extension/addon_a/manifest.json @@ -0,0 +1,18 @@ +{ + "type": "extension", + "name": "addon_a", + "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/core/src/ten_manager/tests/test_data/cmd_check_predefined_graph_only_check_specified/ten_packages/extension/addon_b/manifest.json b/core/src/ten_manager/tests/test_data/cmd_check_predefined_graph_only_check_specified/ten_packages/extension/addon_b/manifest.json new file mode 100644 index 0000000000..9821e29f4d --- /dev/null +++ b/core/src/ten_manager/tests/test_data/cmd_check_predefined_graph_only_check_specified/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/core/src/ten_manager/tests/test_data/cmd_check_predefined_graph_success/manifest.json b/core/src/ten_manager/tests/test_data/cmd_check_predefined_graph_success/manifest.json new file mode 100644 index 0000000000..7cb8a2ca0c --- /dev/null +++ b/core/src/ten_manager/tests/test_data/cmd_check_predefined_graph_success/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_manager/tests/test_data/cmd_check_predefined_graph_success/property.json b/core/src/ten_manager/tests/test_data/cmd_check_predefined_graph_success/property.json new file mode 100644 index 0000000000..31f6bdd67a --- /dev/null +++ b/core/src/ten_manager/tests/test_data/cmd_check_predefined_graph_success/property.json @@ -0,0 +1,41 @@ +{ + "_ten": { + "predefined_graphs": [ + { + "name": "default", + "auto_start": false, + "nodes": [ + { + "type": "extension", + "name": "ext_a", + "addon": "addon_a", + "extension_group": "some_group" + }, + { + "type": "extension", + "name": "ext_b", + "addon": "addon_b", + "extension_group": "some_group" + } + ], + "connections": [ + { + "extension_group": "some_group", + "extension": "ext_a", + "cmd": [ + { + "name": "cmd_1", + "dest": [ + { + "extension_group": "some_group", + "extension": "ext_b" + } + ] + } + ] + } + ] + } + ] + } +} diff --git a/core/src/ten_manager/tests/test_data/cmd_check_predefined_graph_success/ten_packages/extension/addon_a/manifest.json b/core/src/ten_manager/tests/test_data/cmd_check_predefined_graph_success/ten_packages/extension/addon_a/manifest.json new file mode 100644 index 0000000000..05434a5648 --- /dev/null +++ b/core/src/ten_manager/tests/test_data/cmd_check_predefined_graph_success/ten_packages/extension/addon_a/manifest.json @@ -0,0 +1,18 @@ +{ + "type": "extension", + "name": "addon_a", + "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/core/src/ten_manager/tests/test_data/cmd_check_predefined_graph_success/ten_packages/extension/addon_b/manifest.json b/core/src/ten_manager/tests/test_data/cmd_check_predefined_graph_success/ten_packages/extension/addon_b/manifest.json new file mode 100644 index 0000000000..9821e29f4d --- /dev/null +++ b/core/src/ten_manager/tests/test_data/cmd_check_predefined_graph_success/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/core/src/ten_manager/tests/test_data/cmd_check_start_graph_multi_apps/app_1/manifest.json b/core/src/ten_manager/tests/test_data/cmd_check_start_graph_multi_apps/app_1/manifest.json new file mode 100644 index 0000000000..68cec56dcd --- /dev/null +++ b/core/src/ten_manager/tests/test_data/cmd_check_start_graph_multi_apps/app_1/manifest.json @@ -0,0 +1,15 @@ +{ + "type": "app", + "name": "app_1", + "version": "0.1.0", + "dependencies": [ + { + "type": "extension", + "name": "addon_a", + "version": "0.1.0" + } + ], + "package": { + "include": ["**"] + } +} diff --git a/core/src/ten_manager/tests/test_data/cmd_check_start_graph_multi_apps/app_1/property.json b/core/src/ten_manager/tests/test_data/cmd_check_start_graph_multi_apps/app_1/property.json new file mode 100644 index 0000000000..6ee844f45b --- /dev/null +++ b/core/src/ten_manager/tests/test_data/cmd_check_start_graph_multi_apps/app_1/property.json @@ -0,0 +1,5 @@ +{ + "_ten": { + "uri": "http://localhost:8001" + } +} diff --git a/core/src/ten_manager/tests/test_data/cmd_check_start_graph_multi_apps/app_1/ten_packages/extension/addon_a/manifest.json b/core/src/ten_manager/tests/test_data/cmd_check_start_graph_multi_apps/app_1/ten_packages/extension/addon_a/manifest.json new file mode 100644 index 0000000000..05434a5648 --- /dev/null +++ b/core/src/ten_manager/tests/test_data/cmd_check_start_graph_multi_apps/app_1/ten_packages/extension/addon_a/manifest.json @@ -0,0 +1,18 @@ +{ + "type": "extension", + "name": "addon_a", + "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/core/src/ten_manager/tests/test_data/cmd_check_start_graph_multi_apps/app_2/manifest.json b/core/src/ten_manager/tests/test_data/cmd_check_start_graph_multi_apps/app_2/manifest.json new file mode 100644 index 0000000000..3fbe2455af --- /dev/null +++ b/core/src/ten_manager/tests/test_data/cmd_check_start_graph_multi_apps/app_2/manifest.json @@ -0,0 +1,15 @@ +{ + "type": "app", + "name": "app_2", + "version": "0.1.0", + "dependencies": [ + { + "type": "extension", + "name": "addon_b", + "version": "0.1.0" + } + ], + "package": { + "include": ["**"] + } +} diff --git a/core/src/ten_manager/tests/test_data/cmd_check_start_graph_multi_apps/app_2/property.json b/core/src/ten_manager/tests/test_data/cmd_check_start_graph_multi_apps/app_2/property.json new file mode 100644 index 0000000000..04ae423248 --- /dev/null +++ b/core/src/ten_manager/tests/test_data/cmd_check_start_graph_multi_apps/app_2/property.json @@ -0,0 +1,5 @@ +{ + "_ten": { + "uri": "http://localhost:8002" + } +} diff --git a/core/src/ten_manager/tests/test_data/cmd_check_start_graph_multi_apps/app_2/ten_packages/extension/addon_b/manifest.json b/core/src/ten_manager/tests/test_data/cmd_check_start_graph_multi_apps/app_2/ten_packages/extension/addon_b/manifest.json new file mode 100644 index 0000000000..732d941358 --- /dev/null +++ b/core/src/ten_manager/tests/test_data/cmd_check_start_graph_multi_apps/app_2/ten_packages/extension/addon_b/manifest.json @@ -0,0 +1,27 @@ +{ + "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" + } + } + } + ] + } +} diff --git a/core/src/ten_manager/tests/test_data/cmd_check_start_graph_multi_apps/start_graph.json b/core/src/ten_manager/tests/test_data/cmd_check_start_graph_multi_apps/start_graph.json new file mode 100644 index 0000000000..538e30f5de --- /dev/null +++ b/core/src/ten_manager/tests/test_data/cmd_check_start_graph_multi_apps/start_graph.json @@ -0,0 +1,39 @@ +{ + "type": "start_graph", + "seq_id": "55", + "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: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:8002" + } + ] + } + ] + } + ] +} 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 b77f9d9fcc..f45dab89d0 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 @@ -30,7 +30,7 @@ impl Graph { .find_map(|node| { if node.node_type == PkgType::Extension && node.name.as_str() == extension - && node.app.as_str() == app + && node.get_app_uri() == app { Some(node.addon.as_str()) } else { @@ -56,12 +56,12 @@ impl Graph { let mut errors: Vec = Vec::new(); for dest in dests { let dest_addon = self.get_addon_name_of_extension( - dest.app.as_str(), + dest.get_app_uri(), dest.extension.as_str(), ); let dest_msg_schema = find_msg_schema_from_all_pkgs_info( all_needed_pkgs, - dest.app.as_str(), + dest.get_app_uri(), dest_addon, msg_name, msg_type, @@ -92,12 +92,12 @@ impl Graph { let mut errors: Vec = Vec::new(); for dest in dests { let dest_addon = self.get_addon_name_of_extension( - dest.app.as_str(), + dest.get_app_uri(), dest.extension.as_str(), ); let dest_cmd_schema = find_cmd_schema_from_all_pkgs_info( all_needed_pkgs, - dest.app.as_str(), + dest.get_app_uri(), dest_addon, cmd_name, MsgDirection::In, @@ -126,12 +126,12 @@ impl Graph { if let Some(cmd_flows) = &connection.cmd { for (flow_idx, flow) in cmd_flows.iter().enumerate() { let src_addon = self.get_addon_name_of_extension( - connection.app.as_str(), + connection.get_app_uri(), connection.extension.as_str(), ); let src_cmd_schema = find_cmd_schema_from_all_pkgs_info( all_needed_pkgs, - connection.app.as_str(), + connection.get_app_uri(), src_addon, flow.name.as_str(), MsgDirection::Out, @@ -143,7 +143,7 @@ impl Graph { src_cmd_schema, &flow.dest, ) { - errors.push(format!("- cmd[{}]: \n {}", flow_idx, e)); + errors.push(format!("- cmd[{}]: {}", flow_idx, e)); } } } @@ -151,12 +151,12 @@ impl Graph { if let Some(data_flows) = &connection.data { for (flow_idx, flow) in data_flows.iter().enumerate() { let src_addon = self.get_addon_name_of_extension( - connection.app.as_str(), + connection.get_app_uri(), connection.extension.as_str(), ); let src_msg_schema = find_msg_schema_from_all_pkgs_info( all_needed_pkgs, - connection.app.as_str(), + connection.get_app_uri(), src_addon, flow.name.as_str(), &MsgType::Data, @@ -170,7 +170,7 @@ impl Graph { src_msg_schema, &flow.dest, ) { - errors.push(format!("- data[{}]: \n {}", flow_idx, e)); + errors.push(format!("- data[{}]: {}", flow_idx, e)); } } } @@ -178,12 +178,12 @@ impl Graph { if let Some(video_frame_flows) = &connection.video_frame { for (flow_idx, flow) in video_frame_flows.iter().enumerate() { let src_addon = self.get_addon_name_of_extension( - connection.app.as_str(), + connection.get_app_uri(), connection.extension.as_str(), ); let src_msg_schema = find_msg_schema_from_all_pkgs_info( all_needed_pkgs, - connection.app.as_str(), + connection.get_app_uri(), src_addon, flow.name.as_str(), &MsgType::VideoFrame, @@ -197,10 +197,7 @@ impl Graph { src_msg_schema, &flow.dest, ) { - errors.push(format!( - "- video_frame[{}]: \n {}", - flow_idx, e - )); + errors.push(format!("- video_frame[{}]: {}", flow_idx, e)); } } } @@ -208,12 +205,12 @@ impl Graph { if let Some(audio_frame_flows) = &connection.audio_frame { for (flow_idx, flow) in audio_frame_flows.iter().enumerate() { let src_addon = self.get_addon_name_of_extension( - connection.app.as_str(), + connection.get_app_uri(), connection.extension.as_str(), ); let src_msg_schema = find_msg_schema_from_all_pkgs_info( all_needed_pkgs, - connection.app.as_str(), + connection.get_app_uri(), src_addon, flow.name.as_str(), &MsgType::AudioFrame, @@ -227,10 +224,7 @@ impl Graph { src_msg_schema, &flow.dest, ) { - errors.push(format!( - "- audio_frame[{}]: \n {}", - flow_idx, e - )); + errors.push(format!("- audio_frame[{}]: {}", flow_idx, e)); } } } diff --git a/core/src/ten_rust/src/pkg_info/graph/check/connections_nodes_are_defined.rs b/core/src/ten_rust/src/pkg_info/graph/check/connections_nodes_are_defined.rs index f968049fd8..96f6d2df2e 100644 --- a/core/src/ten_rust/src/pkg_info/graph/check/connections_nodes_are_defined.rs +++ b/core/src/ten_rust/src/pkg_info/graph/check/connections_nodes_are_defined.rs @@ -22,7 +22,9 @@ impl Graph { for dest in &flow.dest { let dest_extension = format!( "{}:{}:{}", - dest.app, dest.extension_group, dest.extension + dest.get_app_uri(), + dest.extension_group, + dest.extension ); if !all_extensions.contains(&dest_extension) { @@ -53,7 +55,7 @@ impl Graph { if node.node_type == PkgType::Extension { let unique_ext_name = format!( "{}:{}:{}", - node.app.as_str(), + node.get_app_uri(), node.extension_group.as_ref().unwrap(), node.name ); @@ -65,7 +67,7 @@ impl Graph { for (conn_idx, connection) in connections.iter().enumerate() { let src_extension = format!( "{}:{}:{}", - connection.app, + connection.get_app_uri(), connection.extension_group, connection.extension ); diff --git a/core/src/ten_rust/src/pkg_info/graph/check/duplicated_nodes.rs b/core/src/ten_rust/src/pkg_info/graph/check/duplicated_nodes.rs index 1c21d665ea..e71c9fd110 100644 --- a/core/src/ten_rust/src/pkg_info/graph/check/duplicated_nodes.rs +++ b/core/src/ten_rust/src/pkg_info/graph/check/duplicated_nodes.rs @@ -19,7 +19,7 @@ impl Graph { PkgType::Extension => { let unique_ext_name = format!( "{}:{}:{}", - node.app.as_str(), + node.get_app_uri(), node.extension_group.as_ref().unwrap(), node.name ); @@ -35,7 +35,7 @@ impl Graph { let ext_group = format!( "{}:{}", - node.app.as_str(), + node.get_app_uri(), node.extension_group.as_ref().unwrap() ); @@ -46,7 +46,7 @@ impl Graph { PkgType::ExtensionGroup => { let unique_ext_group_name = - format!("{}:{}", node.app.as_str(), node.name); + format!("{}:{}", node.get_app_uri(), node.name); if all_extension_groups.contains(&unique_ext_group_name) { return Err(anyhow::anyhow!( 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 7c130cb675..0704df053c 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 @@ -19,15 +19,16 @@ impl Graph { let mut not_installed_pkgs: Vec<(String, PkgType, String)> = Vec::new(); for node in &self.nodes { - if !all_needed_pkgs.contains_key(node.app.as_str()) { + let node_app = node.get_app_uri(); + if !all_needed_pkgs.contains_key(node_app) { not_installed_pkgs.push(( - node.app.clone(), + node_app.to_string(), node.node_type.clone(), node.addon.clone(), )); } - let pkgs_in_app = all_needed_pkgs.get(node.app.as_str()).unwrap(); + let pkgs_in_app = all_needed_pkgs.get(node_app).unwrap(); let found = pkgs_in_app.iter().find(|pkg| { pkg.pkg_identity.pkg_type == node.node_type && pkg.pkg_identity.name == node.addon @@ -35,7 +36,7 @@ impl Graph { }); if found.is_none() { not_installed_pkgs.push(( - node.app.clone(), + node_app.to_string(), node.node_type.clone(), node.addon.clone(), )); 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 324e386fb3..b39fe92def 100644 --- a/core/src/ten_rust/src/pkg_info/graph/mod.rs +++ b/core/src/ten_rust/src/pkg_info/graph/mod.rs @@ -38,16 +38,16 @@ impl FromStr for Graph { impl Graph { pub fn validate_and_complete(&mut self) -> Result<()> { - for node in &mut self.nodes { - node.validate_and_complete()?; + for (idx, node) in &mut self.nodes.iter_mut().enumerate() { + node.validate_and_complete() + .map_err(|e| anyhow::anyhow!("nodes[{}]: {}", idx, e))?; } - for (node_idx, node) in self.nodes.iter().enumerate() { - if node.app.is_empty() { - return Err(anyhow::anyhow!( - "'app' field is missing in nodes[{}].", - node_idx - )); + if let Some(connections) = &mut self.connections { + for (idx, connection) in connections.iter_mut().enumerate() { + connection.validate_and_complete().map_err(|e| { + anyhow::anyhow!("connections[{}].{}", idx, e) + })?; } } @@ -78,9 +78,8 @@ pub struct GraphNode { #[serde(skip_serializing_if = "Option::is_none")] pub extension_group: Option, - // Default is 'localhost'. - #[serde(default = "default_app_loc")] - pub app: String, + #[serde(skip_serializing_if = "is_app_default_loc_or_none")] + pub app: Option, #[serde(skip_serializing_if = "Option::is_none")] pub property: Option, @@ -98,15 +97,30 @@ impl GraphNode { )); } + if let Some(app) = &self.app { + if app.as_str() == default_app_loc() { + return Err(anyhow::anyhow!( + "the app uri should be some string other than 'localhost'" + )); + } + } else { + self.app = Some(default_app_loc().to_string()); + } + Ok(()) } + + pub fn get_app_uri(&self) -> &str { + // The 'app' should be assigned after 'validate_and_complete' is called, + // so it should not be None. + self.app.as_ref().unwrap().as_str() + } } #[derive(Serialize, Deserialize, Debug, Clone)] pub struct GraphConnection { - // If a connection does not specify an app URI, it defaults to localhost. - #[serde(default = "default_app_loc")] - pub app: String, + #[serde(skip_serializing_if = "is_app_default_loc_or_none")] + pub app: Option, pub extension_group: String, pub extension: String, @@ -121,22 +135,108 @@ pub struct GraphConnection { pub video_frame: Option>, } +impl GraphConnection { + fn validate_and_complete(&mut self) -> Result<()> { + if let Some(app) = &self.app { + if app.as_str() == default_app_loc() { + return Err(anyhow::anyhow!( + "the app uri should be some string other than 'localhost'" + )); + } + } else { + self.app = Some(default_app_loc().to_string()); + } + + if let Some(cmd) = &mut self.cmd { + for (idx, cmd_flow) in cmd.iter_mut().enumerate() { + cmd_flow + .validate_and_complete() + .map_err(|e| anyhow::anyhow!("cmd[{}].{}", idx, e))?; + } + } + + if let Some(data) = &mut self.data { + for (idx, data_flow) in data.iter_mut().enumerate() { + data_flow + .validate_and_complete() + .map_err(|e| anyhow::anyhow!("data[{}].{}", idx, e))?; + } + } + + if let Some(audio_frame) = &mut self.audio_frame { + for (idx, audio_flow) in audio_frame.iter_mut().enumerate() { + audio_flow.validate_and_complete().map_err(|e| { + anyhow::anyhow!("audio_frame[{}].{}", idx, e) + })?; + } + } + + if let Some(video_frame) = &mut self.video_frame { + for (idx, video_flow) in video_frame.iter_mut().enumerate() { + video_flow.validate_and_complete().map_err(|e| { + anyhow::anyhow!("video_frame[{}].{}", idx, e) + })?; + } + } + + Ok(()) + } + + pub fn get_app_uri(&self) -> &str { + self.app.as_ref().unwrap().as_str() + } +} + #[derive(Serialize, Deserialize, Debug, Clone)] pub struct GraphMessageFlow { pub name: String, pub dest: Vec, } +impl GraphMessageFlow { + fn validate_and_complete(&mut self) -> Result<()> { + for (idx, dest) in &mut self.dest.iter_mut().enumerate() { + dest.validate_and_complete() + .map_err(|e| anyhow::anyhow!("dest[{}]: {}", idx, e))?; + } + + Ok(()) + } +} + #[derive(Serialize, Deserialize, Debug, Clone)] pub struct GraphDestination { - // If a destination does not specify an app URI, it defaults to localhost. - #[serde(default = "default_app_loc")] - pub app: String, + #[serde(skip_serializing_if = "is_app_default_loc_or_none")] + pub app: Option, pub extension_group: String, pub extension: String, } +impl GraphDestination { + fn validate_and_complete(&mut self) -> Result<()> { + if let Some(app) = &self.app { + if app.as_str() == default_app_loc() { + return Err(anyhow::anyhow!( + "the app uri should be some string other than 'localhost'" + )); + } + } else { + self.app = Some(default_app_loc().to_string()); + } + + Ok(()) + } + + pub fn get_app_uri(&self) -> &str { + self.app.as_ref().unwrap().as_str() + } +} + +pub fn is_app_default_loc_or_none(app: &Option) -> bool { + app.is_none() || app.as_ref().unwrap().as_str() == default_app_loc() +} + #[cfg(test)] mod tests { use std::str::FromStr; @@ -149,9 +249,7 @@ mod tests { fn test_predefined_graph_has_no_extensions() { let property_str = include_str!("test_data_embed/predefined_graph_no_extensions.json"); - let mut property: Property = Property::from_str(property_str).unwrap(); - assert!(property.validate_and_complete().is_ok()); - + let property: Property = Property::from_str(property_str).unwrap(); let ten = property._ten.as_ref().unwrap(); let predefined_graph = ten.predefined_graphs.as_ref().unwrap().first().unwrap(); @@ -166,9 +264,7 @@ mod tests { let property_str = include_str!( "test_data_embed/predefined_graph_has_duplicated_extension.json" ); - let mut property: Property = Property::from_str(property_str).unwrap(); - assert!(property.validate_and_complete().is_ok()); - + let property: Property = Property::from_str(property_str).unwrap(); let ten = property._ten.as_ref().unwrap(); let predefined_graph = ten.predefined_graphs.as_ref().unwrap().first().unwrap(); @@ -184,9 +280,7 @@ mod tests { "test_data_embed/start_graph_cmd_has_duplicated_extension.json" ); - let mut graph: Graph = Graph::from_str(cmd_str).unwrap(); - assert!(graph.validate_and_complete().is_ok()); - + let graph: Graph = Graph::from_str(cmd_str).unwrap(); let result = graph.check_if_nodes_duplicated(); assert!(result.is_err()); println!("Error: {:?}", result.err().unwrap()); @@ -197,9 +291,7 @@ mod tests { let property_str = include_str!( "test_data_embed/predefined_graph_connection_src_not_found.json" ); - let mut property: Property = Property::from_str(property_str).unwrap(); - assert!(property.validate_and_complete().is_ok()); - + let property: Property = Property::from_str(property_str).unwrap(); let ten = property._ten.as_ref().unwrap(); let predefined_graph = ten.predefined_graphs.as_ref().unwrap().first().unwrap(); @@ -216,9 +308,7 @@ mod tests { let property_str = include_str!( "test_data_embed/predefined_graph_connection_dest_not_found.json" ); - let mut property: Property = Property::from_str(property_str).unwrap(); - assert!(property.validate_and_complete().is_ok()); - + let property: Property = Property::from_str(property_str).unwrap(); let ten = property._ten.as_ref().unwrap(); let predefined_graph = ten.predefined_graphs.as_ref().unwrap().first().unwrap(); @@ -229,4 +319,14 @@ mod tests { assert!(result.is_err()); println!("Error: {:?}", result.err().unwrap()); } + + #[test] + fn test_predefined_graph_connection_app_localhost() { + let property_str = include_str!( + "test_data_embed/predefined_graph_connection_app_localhost.json" + ); + let property = Property::from_str(property_str); + assert!(property.is_err()); + println!("Error: {:?}", property.err().unwrap()); + } } diff --git a/core/src/ten_rust/src/pkg_info/graph/test_data_embed/predefined_graph_connection_app_localhost.json b/core/src/ten_rust/src/pkg_info/graph/test_data_embed/predefined_graph_connection_app_localhost.json new file mode 100644 index 0000000000..7871bbaa61 --- /dev/null +++ b/core/src/ten_rust/src/pkg_info/graph/test_data_embed/predefined_graph_connection_app_localhost.json @@ -0,0 +1,41 @@ +{ + "_ten": { + "predefined_graphs": [ + { + "name": "default", + "auto_start": false, + "nodes": [ + { + "type": "extension", + "name": "some_extension", + "addon": "default_extension_go", + "extension_group": "some_group" + }, + { + "type": "extension_group", + "addon": "default_extension_group", + "name": "some_group" + } + ], + "connections": [ + { + "extension": "some_extension", + "extension_group": "producer", + "cmd": [ + { + "name": "hello", + "dest": [ + { + "extension_group": "some_group", + "extension": "some_extension", + "app": "localhost" + } + ] + } + ] + } + ] + } + ] + } +} diff --git a/core/src/ten_rust/src/pkg_info/graph/test_data_embed/predefined_graph_connection_dest_not_found.json b/core/src/ten_rust/src/pkg_info/graph/test_data_embed/predefined_graph_connection_dest_not_found.json index 4d1d84180f..ad65bf5d63 100644 --- a/core/src/ten_rust/src/pkg_info/graph/test_data_embed/predefined_graph_connection_dest_not_found.json +++ b/core/src/ten_rust/src/pkg_info/graph/test_data_embed/predefined_graph_connection_dest_not_found.json @@ -20,7 +20,6 @@ ], "connections": [ { - "app": "localhost", "extension": "some_extension", "extension_group": "some_group", "cmd": [ @@ -28,7 +27,6 @@ "name": "hello", "dest": [ { - "app": "localhost", "extension_group": "some_group", "extension": "some_extension_1" } @@ -38,12 +36,10 @@ "name": "world", "dest": [ { - "app": "localhost", "extension_group": "some_group", "extension": "some_extension_1" }, { - "app": "localhost", "extension_group": "some_group", "extension": "consumer" } @@ -53,7 +49,6 @@ } ] } - ], - "uri": "http://localhost:8001" + ] } -} \ No newline at end of file +} diff --git a/core/src/ten_rust/src/pkg_info/graph/test_data_embed/predefined_graph_connection_src_not_found.json b/core/src/ten_rust/src/pkg_info/graph/test_data_embed/predefined_graph_connection_src_not_found.json index cd7038cabd..a80eb46216 100644 --- a/core/src/ten_rust/src/pkg_info/graph/test_data_embed/predefined_graph_connection_src_not_found.json +++ b/core/src/ten_rust/src/pkg_info/graph/test_data_embed/predefined_graph_connection_src_not_found.json @@ -14,7 +14,6 @@ ], "connections": [ { - "app": "localhost", "extension": "some_extension", "extension_group": "producer", "cmd": [ @@ -22,7 +21,6 @@ "name": "hello", "dest": [ { - "app": "localhost", "extension_group": "some_group", "extension": "some_extension" } @@ -32,7 +30,6 @@ } ] } - ], - "uri": "http://localhost:8001" + ] } -} \ No newline at end of file +} diff --git a/core/src/ten_rust/src/pkg_info/graph/test_data_embed/predefined_graph_has_duplicated_extension.json b/core/src/ten_rust/src/pkg_info/graph/test_data_embed/predefined_graph_has_duplicated_extension.json index 096b8259f1..1b33b890fa 100644 --- a/core/src/ten_rust/src/pkg_info/graph/test_data_embed/predefined_graph_has_duplicated_extension.json +++ b/core/src/ten_rust/src/pkg_info/graph/test_data_embed/predefined_graph_has_duplicated_extension.json @@ -19,7 +19,6 @@ } ] } - ], - "uri": "http://localhost:8001" + ] } -} \ No newline at end of file +} diff --git a/core/src/ten_rust/src/pkg_info/graph/test_data_embed/predefined_graph_no_extensions.json b/core/src/ten_rust/src/pkg_info/graph/test_data_embed/predefined_graph_no_extensions.json index 4ced6010fb..5233e10e28 100644 --- a/core/src/ten_rust/src/pkg_info/graph/test_data_embed/predefined_graph_no_extensions.json +++ b/core/src/ten_rust/src/pkg_info/graph/test_data_embed/predefined_graph_no_extensions.json @@ -6,7 +6,6 @@ "auto_start": false, "nodes": [] } - ], - "uri": "http://localhost:8001" + ] } -} \ No newline at end of file +} diff --git a/core/src/ten_rust/src/pkg_info/predefined_graphs/extension.rs b/core/src/ten_rust/src/pkg_info/predefined_graphs/extension.rs index 379c1111cb..d8bcd29013 100644 --- a/core/src/ten_rust/src/pkg_info/predefined_graphs/extension.rs +++ b/core/src/ten_rust/src/pkg_info/predefined_graphs/extension.rs @@ -90,7 +90,7 @@ pub fn get_extension<'a>( .iter() .find(|ext| { ext.node_type == PkgType::Extension - && &ext.app == app + && ext.get_app_uri() == app && ext.extension_group.clone().unwrap_or("".to_string()) == *extension_group && &ext.name == extension diff --git a/core/src/ten_rust/src/pkg_info/property/mod.rs b/core/src/ten_rust/src/pkg_info/property/mod.rs index 8d16fd395a..22b1b4ddf9 100644 --- a/core/src/ten_rust/src/pkg_info/property/mod.rs +++ b/core/src/ten_rust/src/pkg_info/property/mod.rs @@ -21,6 +21,7 @@ use super::{ constants::{PROPERTY_JSON_FILENAME, TEN_FIELD_IN_PROPERTY}, utils::read_file_to_string, }; +use crate::pkg_info::graph::is_app_default_loc_or_none; use crate::{json_schema, pkg_info::default_app_loc}; use predefined_graph::PropertyPredefinedGraph; @@ -74,6 +75,16 @@ impl Property { Ok(()) } + + pub fn get_app_uri(&self) -> String { + if let Some(_ten) = &self._ten { + if let Some(uri) = &_ten.uri { + return uri.clone(); + } + } + + default_app_loc() + } } #[derive(Serialize, Deserialize, Debug, Clone)] @@ -81,7 +92,7 @@ pub struct TenInProperty { #[serde(skip_serializing_if = "Option::is_none")] pub predefined_graphs: Option>, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "is_app_default_loc_or_none")] pub uri: Option, #[serde(flatten)] @@ -272,4 +283,24 @@ mod tests { assert_eq!(saved_json, original_json); } + + #[test] + fn test_dump_property_without_localhost_app_in_graph() { + let json_data = include_str!("test_data_embed/property.json"); + let property: Property = json_data.parse().unwrap(); + assert!(property._ten.is_some()); + let ten = property._ten.as_ref().unwrap(); + let predefined_graphs = ten.predefined_graphs.as_ref().unwrap(); + let nodes = &predefined_graphs.first().as_ref().unwrap().graph.nodes; + let node = nodes.first().unwrap(); + assert_eq!(node.get_app_uri(), default_app_loc()); + + let dir = tempdir().unwrap(); + let file_path = dir.path().join("property.json"); + property.dump_property_to_file(&file_path).unwrap(); + + let saved_content = fs::read_to_string(file_path).unwrap(); + eprintln!("{}", saved_content); + assert_eq!(saved_content.find(default_app_loc().as_str()), None); + } } diff --git a/core/src/ten_rust/src/pkg_info/property/predefined_graph.rs b/core/src/ten_rust/src/pkg_info/property/predefined_graph.rs index c1bcc82902..f5dd9a63c4 100644 --- a/core/src/ten_rust/src/pkg_info/property/predefined_graph.rs +++ b/core/src/ten_rust/src/pkg_info/property/predefined_graph.rs @@ -47,9 +47,7 @@ mod tests { .unwrap(); assert!(predefined_graphs.len() == 1); - let mut predefined_graph = predefined_graphs.first().unwrap().clone(); - assert!(predefined_graph.validate_and_complete().is_ok()); - + let predefined_graph = predefined_graphs.first().unwrap().clone(); let property_json_value = serde_json::to_value(&predefined_graph).unwrap(); assert_eq!(