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

Implement recorded::test macro for recorded tests #1926

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
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
3 changes: 2 additions & 1 deletion .vscode/cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"datetime",
"devicecode",
"docsrs",
"doctest",
"downcasted",
"downcasting",
"entra",
Expand Down Expand Up @@ -128,4 +129,4 @@
]
}
]
}
}
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

- [Rust toolchain](https://www.rust-lang.org/tools/install)

When you run `cargo build`, toolchain version [1.76](https://releases.rs/docs/1.76.0/) and necessary components will be installed automatically.
When you run `cargo build`, toolchain version [1.80](https://releases.rs/docs/1.80.0/) and necessary components will be installed automatically.

- (Recommended) If you use [Visual Studio Code](https://code.visualstudio.com), install recommended extensions to improve your development experience.

Expand Down
19 changes: 13 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ resolver = "2"
members = [
"sdk/typespec",
"sdk/typespec/typespec_client_core",
"sdk/typespec/typespec_derive",
"sdk/typespec/typespec_macros",
"sdk/core/azure_core",
"sdk/core/azure_core_amqp",
"sdk/core/azure_core_test",
"sdk/core/azure_core_test_macros",
"sdk/cosmos/azure_data_cosmos",
"sdk/identity/azure_identity",
"sdk/eventhubs/azure_messaging_eventhubs",
Expand All @@ -19,7 +21,7 @@ authors = ["Microsoft"]
edition = "2021"
license = "MIT"
repository = "https://github.com/azure/azure-sdk-for-rust"
rust-version = "1.76"
rust-version = "1.80"

[workspace.dependencies.typespec]
path = "sdk/typespec"
Expand All @@ -29,15 +31,20 @@ default-features = false
path = "sdk/typespec/typespec_client_core"
default-features = false

[workspace.dependencies.typespec_derive]
path = "sdk/typespec/typespec_derive"
[workspace.dependencies.typespec_macros]
path = "sdk/typespec/typespec_macros"

[workspace.dependencies.azure_core]
path = "sdk/core/azure_core"

[workspace.dependencies.azure_core_amqp]
path = "sdk/core/azure_core_amqp"

[workspace.dependencies.azure_core_test]
path = "sdk/core/azure_core_test"

[workspace.dependencies.azure_core_test_macros]
path = "sdk/core/azure_core_test_macros"

[workspace.dependencies.azure_identity]
path = "sdk/identity/azure_identity"
Expand Down Expand Up @@ -74,6 +81,7 @@ paste = "1.0"
pin-project = "1.0"
proc-macro2 = "1.0.86"
quick-xml = { version = "0.31", features = ["serialize", "serde-types"] }
quote = "1.0.37"
rand = "0.8"
reqwest = { version = "0.12", features = [
"json",
Expand All @@ -87,8 +95,7 @@ serde_json = "1.0"
serde_test = "1"
serial_test = "3.0"
sha2 = { version = "0.10" }
syn = { version = "2.0.74", features = ["full"] }
quote = "1.0.36"
syn = { version = "2.0.87", features = ["full"] }
thiserror = "1.0"
time = { version = "0.3.10", features = [
"serde-well-known",
Expand Down
1 change: 1 addition & 0 deletions eng/scripts/Test-Packages.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ foreach ($package in $packagesToTest) {
if ($FunctionalTests) {
$targets += "--bins"
$targets += "--examples"
$targets += "--tests"
$targets += "--benches"
}

Expand Down
2 changes: 1 addition & 1 deletion rust-toolchain.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[toolchain]
channel = "1.76.0"
channel = "1.80.0"
components = [
"rustc",
"rustfmt",
Expand Down
19 changes: 10 additions & 9 deletions sdk/core/azure_core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,24 +42,25 @@ thiserror.workspace = true

[features]
default = []
azurite_workaround = []
hmac_openssl = ["dep:openssl"]
hmac_rust = ["dep:sha2", "dep:hmac"]
reqwest = ["typespec_client_core/reqwest"]
reqwest_gzip = ["typespec_client_core/reqwest_gzip"]
reqwest_rustls = ["typespec_client_core/reqwest_rustls"]
hmac_rust = ["dep:sha2", "dep:hmac"]
hmac_openssl = ["dep:openssl"]
azurite_workaround = []
xml = ["typespec_client_core/xml"]
test = []
tokio_fs = ["typespec_client_core/tokio_fs"]
tokio_sleep = ["typespec_client_core/tokio_sleep"]
xml = ["typespec_client_core/xml"]

[package.metadata.docs.rs]
features = [
"xml",
"tokio_fs",
"reqwest",
"hmac_openssl",
"hmac_rust",
"reqwest_gzip",
"reqwest_rustls",
"hmac_rust",
"hmac_openssl",
"reqwest",
"test",
"tokio_fs",
"xml",
]
3 changes: 3 additions & 0 deletions sdk/core/azure_core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ pub mod headers;
pub mod lro;
pub mod request_options;

#[cfg(feature = "test")]
pub mod test;

pub mod tokio;

pub use constants::*;
Expand Down
63 changes: 63 additions & 0 deletions sdk/core/azure_core/src/test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

//! Shared utilities for testing client libraries built on `azure_core`.
//!
//! For a comprehensive suite of utilities for testing client libraries built on `azure_core`,
//! read documentation for the `azure_core_test` crate.

use crate::error::{Error, ErrorKind};
use std::{fmt, str::FromStr};

/// Whether to test client methods by playing back recordings, recording live sessions, or executing live sessions without recording.
#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)]
pub enum TestMode {
/// Test client methods by playing back recordings.
#[default]
Playback,

/// Record live sessions.
Record,

/// Execute live sessions without recording.
Live,
}

impl TestMode {
/// Gets the `TestMode` from the `AZURE_TEST_MODE` environment variable or returns the default if undefined.
pub fn current() -> typespec::Result<Self> {
std::env::var("AZURE_TEST_MODE").map_or_else(|_| Ok(TestMode::default()), |v| v.parse())
}
}

impl fmt::Debug for TestMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.into())
}
}

impl From<&TestMode> for &'static str {
fn from(mode: &TestMode) -> Self {
match mode {
TestMode::Playback => "playback",
TestMode::Record => "record",
TestMode::Live => "live",
}
}
}

impl FromStr for TestMode {
type Err = Error;

fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_ascii_lowercase().as_str() {
"playback" => Ok(Self::Playback),
"record" => Ok(Self::Record),
"live" => Ok(Self::Live),
_ => Err(Error::message(
ErrorKind::DataConversion,
"expected 'playback', 'record', or 'live'",
)),
}
}
}
21 changes: 21 additions & 0 deletions sdk/core/azure_core_test/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[package]
name = "azure_core_test"
version = "0.1.0"
description = "Utilities for testing client libraries built on azure_core."
readme = "README.md"
authors.workspace = true
license.workspace = true
repository.workspace = true
homepage = "https://github.com/azure/azure-sdk-for-rust"
documentation = "https://docs.rs/azure_core"
keywords = ["sdk", "azure", "rest", "iot", "cloud"]
categories = ["development-tools::testing"]
edition.workspace = true
rust-version.workspace = true

[dependencies]
azure_core = { workspace = true, features = ["test"] }
azure_core_test_macros.workspace = true

[dev-dependencies]
tokio.workspace = true
25 changes: 25 additions & 0 deletions sdk/core/azure_core_test/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Azure client library test utilities

The types and functions in this crate help test client libraries built on `azure_core`.

## Client methods

To test client methods using our [Test Proxy], you can attribute both synchronous and asynchronous (recommend) tests
using the `#[recorded::test]` attribute:

```rust
use azure_core_test::{recorded, TestContext};
use azure_core::Result;

#[recorded::test]
async fn get_secret(ctx: TestContext) -> Result<()> {
todo!()
}
```

The `TestContext` parameter is required unless your test function is attribute as `#[recorded::test(live)]` (live-only),
in which case it is optional. You can name the parameter whatever you want.
The `TestContext` parameter is used to initialize an HTTP client to play back or record tests
and provides other information to test functions that may be useful.

[Test Proxy]: https://github.com/Azure/azure-sdk-tools/blob/main/tools/test-proxy/Azure.Sdk.Tools.TestProxy/README.md
54 changes: 54 additions & 0 deletions sdk/core/azure_core_test/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

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

/// Live recording and playing back of client library tests.
pub mod recorded {
pub use azure_core_test_macros::test;
}

pub use azure_core::test::TestMode;

/// Context information required by recorded client library tests.
///
/// This context is required for any recorded tests not attributed as `#[recorded::test(live)]`
/// to setup up the HTTP client to record or play back session records.
#[derive(Clone, Debug)]
pub struct TestContext {
test_mode: TestMode,
test_name: &'static str,
}

impl TestContext {
/// Not intended for use outside the `azure_core` crates.
#[doc(hidden)]
pub fn new(test_mode: TestMode, test_name: &'static str) -> Self {
Self {
test_mode,
test_name,
}
}

/// 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(test)]
mod tests {
use super::*;

#[test]
fn test_content_new() {
let ctx = TestContext::new(TestMode::default(), "test_content_new");
assert_eq!(ctx.test_mode(), TestMode::Playback);
assert_eq!(ctx.test_name(), "test_content_new");
}
}
27 changes: 27 additions & 0 deletions sdk/core/azure_core_test_macros/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
[package]
name = "azure_core_test_macros"
version = "0.1.0"
description = "Procedural macros for testing client libraries built on azure_core."
readme = "README.md"
authors.workspace = true
license.workspace = true
repository.workspace = true
homepage = "https://github.com/azure/azure-sdk-for-rust"
documentation = "https://docs.rs/azure_core"
keywords = ["azure", "cloud", "iot", "rest", "sdk"]
categories = ["development-tools"]
edition.workspace = true
rust-version.workspace = true

[lib]
proc-macro = true

[dependencies]
azure_core = { workspace = true, features = ["test"] }
proc-macro2.workspace = true
quote.workspace = true
syn.workspace = true

[dev-dependencies]
azure_core_test.workspace = true
tokio.workspace = true
25 changes: 25 additions & 0 deletions sdk/core/azure_core_test_macros/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Azure client library test macros

Macros for testing client libraries built on `azure_core`.

## Client methods

To test client methods using our [Test Proxy], you can attribute both synchronous and asynchronous (recommend) tests
using the `#[recorded::test]` attribute:

```rust
use azure_core_test::{recorded, TestContext};
use azure_core::Result;

#[recorded::test]
async fn get_secret(ctx: TestContext) -> Result<()> {
todo!()
}
```

The `TestContext` parameter is required unless your test function is attribute as `#[recorded::test(live)]` (live-only),
in which case it is optional. You can name the parameter whatever you want.
The `TestContext` parameter is used to initialize an HTTP client to play back or record tests
and provides other information to test functions that may be useful.

[Test Proxy]: https://github.com/Azure/azure-sdk-tools/blob/main/tools/test-proxy/Azure.Sdk.Tools.TestProxy/README.md
17 changes: 17 additions & 0 deletions sdk/core/azure_core_test_macros/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

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

mod test;

use proc_macro::TokenStream;

/// Attribute client library tests to play back recordings, record sessions, or execute tests without recording.
///
/// Read documentation for `azure_core_test` for more information and examples.
#[proc_macro_attribute]
pub fn test(attr: TokenStream, item: TokenStream) -> TokenStream {
test::parse_test(attr.into(), item.into())
.map_or_else(|e| e.into_compile_error().into(), |v| v.into())
}
Loading