Skip to main content

cf_integration/
lib.rs

1//! Standalone entrypoint for the `cf-integration` executable.
2
3#[cfg(test)]
4extern crate self as cf_integration;
5
6use std::{io::Write, process::ExitCode};
7
8use clap::Parser;
9
10mod app;
11mod cli;
12mod conformance;
13mod error;
14mod infrastructure;
15mod mcp;
16mod output;
17mod performance;
18mod runtime;
19
20use app::resolve_action;
21use cli::Cli;
22use error::AppFailure;
23use infrastructure::config::{AppConfig, ConfigBootstrap, ConfigRequirements, Environment};
24use infrastructure::process::SystemProcessRunner;
25pub(crate) use output::{Activity, OutputStyle, TestStatus};
26use runtime::RuntimeDispatcher;
27
28/// Runs the CLI using the current process arguments and environment.
29pub async fn run() -> ExitCode {
30    let arguments = std::env::args_os().collect::<Vec<_>>();
31    if conformance::client::is_internal_client_invocation(&arguments) {
32        return match conformance::client::run_internal_client(&arguments).await {
33            Ok(()) => ExitCode::SUCCESS,
34            Err(error) => {
35                eprintln!(
36                    "{}: {}",
37                    conformance::client::CLIENT_DRIVER_FAILURE_PREFIX,
38                    OutputStyle::stderr().failure(&format!("{error:#}"))
39                );
40                ExitCode::FAILURE
41            }
42        };
43    }
44    let cli = Cli::parse_from(arguments);
45    let environment: Environment = std::env::vars_os().collect();
46    let cwd = match std::env::current_dir() {
47        Ok(path) => path,
48        Err(error) => {
49            eprintln!(
50                "{}",
51                OutputStyle::stderr()
52                    .failure(&format!("failed to determine current directory: {error}"))
53            );
54            return ExitCode::FAILURE;
55        }
56    };
57    let bootstrap = match ConfigBootstrap::load(&environment, &cwd) {
58        Ok(loaded) => loaded,
59        Err(error) => {
60            eprintln!("{}", OutputStyle::stderr().failure(&format!("{error:#}")));
61            return ExitCode::FAILURE;
62        }
63    };
64    for warning in bootstrap.warnings() {
65        eprintln!(
66            "{}",
67            OutputStyle::stderr().warning(&format!("warning: {warning}"))
68        );
69    }
70
71    let effective_environment = bootstrap
72        .environment()
73        .iter()
74        .map(|(key, value)| (key.clone(), value.value.clone()))
75        .collect::<Environment>();
76    let action = match resolve_action(cli, &effective_environment) {
77        Ok(action) => action,
78        Err(error) => return report_failure(AppFailure::from(error)),
79    };
80    let requirements = if action.requires_runtime_assets() {
81        ConfigRequirements::RUNTIME
82    } else {
83        ConfigRequirements::READ_ONLY
84    };
85    eprintln!("{}", OutputStyle::stderr().info(&action.startup_summary()));
86    let activity = action
87        .uses_global_activity()
88        .then(|| Activity::spinner(action.description()));
89    let config = match AppConfig::load(bootstrap, requirements) {
90        Ok(config) => config,
91        Err(error) => {
92            if let Some(activity) = activity {
93                activity.finish(false);
94            }
95            return report_failure(AppFailure::from(error));
96        }
97    };
98    let runtime = RuntimeDispatcher::new(config, SystemProcessRunner);
99    let result = runtime.execute(action).await;
100    if let Some(activity) = activity {
101        activity.finish(result.is_ok());
102    }
103    match result {
104        Ok(()) => ExitCode::SUCCESS,
105        Err(error) => report_failure(error),
106    }
107}
108
109fn report_failure(error: AppFailure) -> ExitCode {
110    // Keep completed result output ahead of wrapper diagnostics such as Make's
111    // nonzero-exit message when stdout and stderr are captured separately.
112    let _ = std::io::stdout().flush();
113    if !error.is_reported() {
114        eprintln!("{}", OutputStyle::stderr().failure(&error.to_string()));
115    }
116    exit_code(error.exit_code())
117}
118
119fn exit_code(code: i32) -> ExitCode {
120    u8::try_from(code)
121        .map(ExitCode::from)
122        .unwrap_or(ExitCode::FAILURE)
123}
124
125#[cfg(test)]
126#[path = "app_tests.rs"]
127mod app_tests;
128#[cfg(test)]
129#[path = "cli_public_tests.rs"]
130mod cli_public_tests;
131#[cfg(test)]
132#[path = "conformance/fixture_tests.rs"]
133mod conformance_fixture_tests;
134#[cfg(test)]
135#[path = "conformance/results_tests.rs"]
136mod conformance_tests;
137#[cfg(test)]
138#[path = "infrastructure/checkout_integration_tests.rs"]
139mod infrastructure_checkout_tests;
140#[cfg(test)]
141#[path = "infrastructure/compose_integration_tests.rs"]
142mod infrastructure_compose_tests;
143#[cfg(test)]
144#[path = "infrastructure/config_integration_tests.rs"]
145mod infrastructure_config_tests;
146#[cfg(test)]
147#[path = "infrastructure/process_integration_tests.rs"]
148mod infrastructure_process_tests;
149#[cfg(test)]
150#[path = "infrastructure/stack_integration_tests.rs"]
151mod infrastructure_stack_tests;
152#[cfg(test)]
153#[path = "mcp/auth_proxy_integration_tests.rs"]
154mod mcp_auth_proxy_tests;
155#[cfg(test)]
156#[path = "mcp/backend_identity_integration_tests.rs"]
157mod mcp_backend_identity_tests;
158#[cfg(test)]
159#[path = "mcp/gateway_integration_tests.rs"]
160mod mcp_gateway_tests;
161#[cfg(test)]
162#[path = "mcp/protocol_integration_tests.rs"]
163mod mcp_protocol_tests;
164#[cfg(test)]
165#[path = "performance/locust_integration_tests.rs"]
166mod performance_locust_tests;
167#[cfg(test)]
168#[path = "performance/python_adapter_tests.rs"]
169mod performance_python_adapter_tests;