Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bootstrap test-proxy service #1956

Merged
merged 3 commits into from
Dec 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
# Build output.
target/

# Track only workspace Cargo.lock
Cargo.lock
!/Cargo.lock

# User secrets.
.env

# Test artifacts.
.assets/

# Editor user customizations.
.vscode/launch.json
.idea/
Expand Down
6 changes: 6 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 11 additions & 1 deletion sdk/core/azure_core_test/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,18 @@ edition.workspace = true
rust-version.workspace = true

[dependencies]
async-trait.workspace = true
azure_core = { workspace = true, features = ["test"] }
azure_core_test_macros.workspace = true
serde.workspace = true
tracing.workspace = true

[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
tokio = { workspace = true, features = ["io-util", "process", "sync"] }

[dev-dependencies]
tokio.workspace = true
clap.workspace = true
tracing-subscriber = { workspace = true, features = ["env-filter", "fmt"] }

[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
tokio = { workspace = true, features = ["signal"] }
83 changes: 83 additions & 0 deletions sdk/core/azure_core_test/examples/test_proxy.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

use clap::Parser;

#[cfg(not(target_arch = "wasm32"))]
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
use azure_core_test::proxy;

// cspell:ignore ECANCELED ECHILD
const ECANCELED: i32 = 4;
const ECHILD: i32 = 5;

let args = Args::parse();

tracing_subscriber::fmt()
// Default trace level based on command line arguments.
.with_max_level(args.trace_level())
// RUST_LOG environment variable can override trace level.
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
.init();

let mut proxy = proxy::start(env!("CARGO_MANIFEST_DIR"), Some(args.into())).await?;

let code = tokio::select! {
_ = tokio::signal::ctrl_c() => {
// Try to shutdown the test-proxy.
proxy.stop().await?;

ECANCELED
},
v = proxy.wait() => {
let code = v.map_or_else(|_| ECHILD, |v| v.code().unwrap_or_default());
println!("test-proxy exited with status code {code}");

code
},
};

if code != 0 {
std::process::exit(code);
}

Ok(())
}

#[derive(Debug, Parser)]
#[command(about = "Starts the Test-Proxy service", version)]
struct Args {
/// Allow insecure upstream SSL certs.
#[arg(long)]
insecure: bool,

/// Enable verbose logging.
#[arg(short, long)]
verbose: bool,
}

#[cfg(not(target_arch = "wasm32"))]
impl Args {
fn trace_level(&self) -> tracing::level_filters::LevelFilter {
if self.verbose {
return tracing::level_filters::LevelFilter::DEBUG;
}
tracing::level_filters::LevelFilter::INFO
}
}

#[cfg(not(target_arch = "wasm32"))]
impl From<Args> for azure_core_test::proxy::ProxyOptions {
fn from(args: Args) -> Self {
Self {
insecure: args.insecure,
}
}
}

#[cfg(target_arch = "wasm32")]
fn main() {
let _ = Args::parse();
println!("wasm32 target architecture not supported");
}
84 changes: 74 additions & 10 deletions sdk/core/azure_core_test/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,22 @@

#![doc = include_str!("../README.md")]

/// Live recording and playing back of client library tests.
pub mod recorded {
pub use azure_core_test_macros::test;
}
#[cfg(not(target_arch = "wasm32"))]
pub mod proxy;
#[cfg(not(target_arch = "wasm32"))]
pub mod recorded;
mod sanitizers;
mod transport;

pub use azure_core::test::TestMode;
use azure_core::{ClientOptions, TransportOptions};
pub use sanitizers::*;
use std::{
path::{Path, PathBuf},
sync::Arc,
};

const SPAN_TARGET: &str = "test-proxy";

/// Context information required by recorded client library tests.
///
Expand All @@ -17,7 +27,7 @@ pub use azure_core::test::TestMode;
#[derive(Clone, Debug)]
pub struct TestContext {
test_mode: TestMode,
crate_dir: &'static str,
crate_dir: &'static Path,
test_name: &'static str,
}

Expand All @@ -27,27 +37,79 @@ impl TestContext {
pub fn new(test_mode: TestMode, crate_dir: &'static str, test_name: &'static str) -> Self {
Self {
test_mode,
crate_dir,
crate_dir: Path::new(crate_dir),
test_name,
}
}

/// Gets the current [`TestMode`].
pub fn test_mode(&self) -> TestMode {
self.test_mode
/// Instruments the [`ClientOptions`] to support recording and playing back of session records.
///
/// # Examples
///
/// ```no_run
/// use azure_core_test::{recorded, TestContext};
///
/// # struct MyClient;
/// # #[derive(Default)]
/// # struct MyClientOptions { client_options: azure_core::ClientOptions };
/// # impl MyClient {
/// # fn new(endpoint: impl AsRef<str>, options: Option<MyClientOptions>) -> Self { todo!() }
/// # async fn invoke(&self) -> azure_core::Result<()> { todo!() }
/// # }
/// #[recorded::test]
/// async fn test_invoke(ctx: TestContext) -> azure_core::Result<()> {
/// let mut options = MyClientOptions::default();
/// ctx.instrument(&mut options.client_options);
///
/// let client = MyClient::new("https://azure.net", Some(options));
/// client.invoke().await
/// }
/// ```
pub fn instrument(&self, options: &mut ClientOptions) {
let transport = options.transport.clone().unwrap_or_default();
options.transport = Some(TransportOptions::new_custom_policy(Arc::new(
transport::ProxyTransportPolicy {
inner: transport,
mode: self.test_mode,
},
)));
}

/// Gets the root directory of the crate under test.
pub fn crate_dir(&self) -> &'static str {
pub fn crate_dir(&self) -> &'static Path {
self.crate_dir
}

/// Gets the test data directory under [`Self::crate_dir`].
pub fn test_data_dir(&self) -> PathBuf {
self.crate_dir.join("tests/data")
}

/// Gets the current [`TestMode`].
pub fn test_mode(&self) -> TestMode {
self.test_mode
}

/// Gets the current test function name.
pub fn test_name(&self) -> &'static str {
self.test_name
}
}

#[cfg(not(target_arch = "wasm32"))]
fn find_ancestor(dir: impl AsRef<Path>, name: &str) -> azure_core::Result<PathBuf> {
for dir in dir.as_ref().ancestors() {
let path = dir.join(name);
if path.exists() {
return Ok(path);
}
}
Err(azure_core::Error::new::<std::io::Error>(
azure_core::error::ErrorKind::Io,
std::io::ErrorKind::NotFound.into(),
))
}

#[cfg(test)]
mod tests {
use super::*;
Expand All @@ -62,6 +124,8 @@ mod tests {
assert_eq!(ctx.test_mode(), TestMode::Playback);
assert!(ctx
.crate_dir()
.to_str()
.unwrap()
.replace("\\", "/")
.ends_with("sdk/core/azure_core_test"));
assert_eq!(ctx.test_name(), "test_content_new");
Expand Down
Loading