From 23dd757edd5788ed2f54b77aa331bdb990c53579 Mon Sep 17 00:00:00 2001 From: Heath Stewart Date: Wed, 20 Nov 2024 12:37:04 -0800 Subject: [PATCH 1/7] Rename typespec_derive to typespec_macros Will be more consistent with upcoming azure_core_macros, and may not contain *just* derive macros anyway. Will keep feature as "derive", though, for derive macros. --- Cargo.toml | 6 +++--- sdk/typespec/typespec_client_core/Cargo.toml | 6 +++--- sdk/typespec/typespec_client_core/src/fmt.rs | 4 ++-- .../typespec_client_core/src/http/pager.rs | 2 +- .../typespec_client_core/src/http/pipeline.rs | 2 +- .../typespec_client_core/src/http/response.rs | 10 +++++----- .../Cargo.toml | 6 +++--- .../src/lib.rs | 6 +++--- .../src/model.rs | 0 .../src/safe_debug.rs | 0 .../tests/compilation-tests.rs | 2 +- .../tests/data/compilation-tests/Cargo.toml | 0 .../data/compilation-tests/src/.gitignore | 0 .../tests/data/compilation-tests/src/lib.rs | 0 .../src/model/bad_attributes.expected.json | 18 +++++++++--------- .../src/model/bad_attributes.rs | 0 .../data/compilation-tests/src/model/mod.rs | 0 .../model/not_derive_deserialize.expected.json | 4 ++-- .../src/model/not_derive_deserialize.rs | 0 .../src/model/unknown_format.expected.json | 4 ++-- .../src/model/unknown_format.rs | 0 21 files changed, 35 insertions(+), 35 deletions(-) rename sdk/typespec/{typespec_derive => typespec_macros}/Cargo.toml (72%) rename sdk/typespec/{typespec_derive => typespec_macros}/src/lib.rs (97%) rename sdk/typespec/{typespec_derive => typespec_macros}/src/model.rs (100%) rename sdk/typespec/{typespec_derive => typespec_macros}/src/safe_debug.rs (100%) rename sdk/typespec/{typespec_derive => typespec_macros}/tests/compilation-tests.rs (99%) rename sdk/typespec/{typespec_derive => typespec_macros}/tests/data/compilation-tests/Cargo.toml (100%) rename sdk/typespec/{typespec_derive => typespec_macros}/tests/data/compilation-tests/src/.gitignore (100%) rename sdk/typespec/{typespec_derive => typespec_macros}/tests/data/compilation-tests/src/lib.rs (100%) rename sdk/typespec/{typespec_derive => typespec_macros}/tests/data/compilation-tests/src/model/bad_attributes.expected.json (76%) rename sdk/typespec/{typespec_derive => typespec_macros}/tests/data/compilation-tests/src/model/bad_attributes.rs (100%) rename sdk/typespec/{typespec_derive => typespec_macros}/tests/data/compilation-tests/src/model/mod.rs (100%) rename sdk/typespec/{typespec_derive => typespec_macros}/tests/data/compilation-tests/src/model/not_derive_deserialize.expected.json (73%) rename sdk/typespec/{typespec_derive => typespec_macros}/tests/data/compilation-tests/src/model/not_derive_deserialize.rs (100%) rename sdk/typespec/{typespec_derive => typespec_macros}/tests/data/compilation-tests/src/model/unknown_format.expected.json (74%) rename sdk/typespec/{typespec_derive => typespec_macros}/tests/data/compilation-tests/src/model/unknown_format.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index 457ce3f1d3..e2adf6b019 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ 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/cosmos/azure_data_cosmos", @@ -29,8 +29,8 @@ 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" diff --git a/sdk/typespec/typespec_client_core/Cargo.toml b/sdk/typespec/typespec_client_core/Cargo.toml index c9b03827ee..4efabf2756 100644 --- a/sdk/typespec/typespec_client_core/Cargo.toml +++ b/sdk/typespec/typespec_client_core/Cargo.toml @@ -27,7 +27,7 @@ time.workspace = true tokio = { workspace = true, optional = true } tracing.workspace = true typespec = { workspace = true, default-features = false } -typespec_derive = { workspace = true, optional = true } +typespec_macros = { workspace = true, optional = true } uuid.workspace = true url.workspace = true @@ -43,11 +43,11 @@ once_cell.workspace = true tokio.workspace = true tracing.workspace = true tracing-subscriber.workspace = true -typespec_derive.workspace = true +typespec_macros.workspace = true [features] default = ["http", "json", "reqwest", "reqwest_gzip", "reqwest_rustls"] -derive = ["dep:typespec_derive"] +derive = ["dep:typespec_macros"] http = ["dep:http-types", "typespec/http"] json = ["typespec/json"] reqwest = ["dep:reqwest", "reqwest/default-tls"] diff --git a/sdk/typespec/typespec_client_core/src/fmt.rs b/sdk/typespec/typespec_client_core/src/fmt.rs index 09ea942f44..527feb1901 100644 --- a/sdk/typespec/typespec_client_core/src/fmt.rs +++ b/sdk/typespec/typespec_client_core/src/fmt.rs @@ -6,14 +6,14 @@ use std::{borrow::Cow, fmt::Debug}; #[cfg(feature = "derive")] -pub use typespec_derive::SafeDebug; +pub use typespec_macros::SafeDebug; /// When deriving this trait, helps prevent leaking personally identifiable information (PII) that deriving [`Debug`] might otherwise. /// /// # Examples /// /// ``` -/// use typespec_derive::SafeDebug; +/// use typespec_macros::SafeDebug; /// /// #[derive(SafeDebug)] /// struct MyModel { diff --git a/sdk/typespec/typespec_client_core/src/http/pager.rs b/sdk/typespec/typespec_client_core/src/http/pager.rs index e93eb6ce95..8bfee70ad0 100644 --- a/sdk/typespec/typespec_client_core/src/http/pager.rs +++ b/sdk/typespec/typespec_client_core/src/http/pager.rs @@ -152,7 +152,7 @@ mod tests { use futures::StreamExt; use serde::Deserialize; - use typespec_derive::Model; + use typespec_macros::Model; use crate::http::{ headers::{HeaderName, HeaderValue}, diff --git a/sdk/typespec/typespec_client_core/src/http/pipeline.rs b/sdk/typespec/typespec_client_core/src/http/pipeline.rs index 9b26bca570..aae9cc3872 100644 --- a/sdk/typespec/typespec_client_core/src/http/pipeline.rs +++ b/sdk/typespec/typespec_client_core/src/http/pipeline.rs @@ -99,7 +99,7 @@ mod tests { }; use bytes::Bytes; use serde::Deserialize; - use typespec_derive::Model; + use typespec_macros::Model; #[tokio::test] async fn deserializes_response() { diff --git a/sdk/typespec/typespec_client_core/src/http/response.rs b/sdk/typespec/typespec_client_core/src/http/response.rs index 2b51687444..58c55ee4cf 100644 --- a/sdk/typespec/typespec_client_core/src/http/response.rs +++ b/sdk/typespec/typespec_client_core/src/http/response.rs @@ -10,7 +10,7 @@ use std::{fmt, marker::PhantomData, pin::Pin}; use typespec::error::{ErrorKind, ResultExt}; #[cfg(feature = "derive")] -pub use typespec_derive::Model; +pub use typespec_macros::Model; #[cfg(not(target_arch = "wasm32"))] pub type PinnedStream = Pin> + Send + Sync>>; @@ -121,7 +121,7 @@ impl Response { /// # pub struct GetSecretResponse { } /// use typespec_client_core::http::{Model, Response}; /// # #[cfg(not(feature = "derive"))] - /// # use typespec_derive::Model; + /// # use typespec_macros::Model; /// use serde::Deserialize; /// use bytes::Bytes; /// @@ -175,7 +175,7 @@ impl Response { /// # use serde::Deserialize; /// # use typespec_client_core::http::Model; /// # #[cfg(not(feature = "derive"))] - /// # use typespec_derive::Model; + /// # use typespec_macros::Model; /// # #[derive(Model, Deserialize)] /// # pub struct GetSecretResponse { /// # name: String, @@ -331,7 +331,7 @@ mod tests { use crate::http::Response; use http_types::StatusCode; use serde::Deserialize; - use typespec_derive::Model; + use typespec_macros::Model; /// An example JSON-serialized response type. #[derive(Model, Deserialize)] @@ -422,7 +422,7 @@ mod tests { use crate::http::Response; use http_types::StatusCode; use serde::Deserialize; - use typespec_derive::Model; + use typespec_macros::Model; /// An example XML-serialized response type. #[derive(Model, Deserialize)] diff --git a/sdk/typespec/typespec_derive/Cargo.toml b/sdk/typespec/typespec_macros/Cargo.toml similarity index 72% rename from sdk/typespec/typespec_derive/Cargo.toml rename to sdk/typespec/typespec_macros/Cargo.toml index 777d61543a..5ca4948687 100644 --- a/sdk/typespec/typespec_derive/Cargo.toml +++ b/sdk/typespec/typespec_macros/Cargo.toml @@ -1,9 +1,9 @@ [package] -name = "typespec_derive" +name = "typespec_macros" version = "0.1.0" authors.workspace = true edition.workspace = true -description = "Macros used by TypeSpec-generated libraries." +description = "Procedural macros for client libraries built on typespec." homepage = "https://typespec.io" repository.workspace = true license.workspace = true @@ -20,7 +20,7 @@ proc-macro2.workspace = true [dev-dependencies] tokio.workspace = true -typespec_client_core = { workspace = true, features = [ "http", "json", "xml" ] } +typespec_client_core = { workspace = true, features = ["http", "json", "xml"] } serde.workspace = true serde_json.workspace = true cargo_metadata.workspace = true diff --git a/sdk/typespec/typespec_derive/src/lib.rs b/sdk/typespec/typespec_macros/src/lib.rs similarity index 97% rename from sdk/typespec/typespec_derive/src/lib.rs rename to sdk/typespec/typespec_macros/src/lib.rs index ba7d8d16c0..ba7da1f2e2 100644 --- a/sdk/typespec/typespec_derive/src/lib.rs +++ b/sdk/typespec/typespec_macros/src/lib.rs @@ -54,7 +54,7 @@ fn parse_literal_string(value: ParseStream) -> Result { /// If compiling with the `xml` feature, the value `xml` is also supported. /// /// ```rust -/// # use typespec_derive::Model; +/// # use typespec_macros::Model; /// # use serde::Deserialize; /// #[derive(Model, Deserialize)] /// #[typespec(format = "xml")] @@ -70,7 +70,7 @@ fn parse_literal_string(value: ParseStream) -> Result { /// The 'crate' attribute specifies an alternate module path, other than the default of `typespec_client_core`, to reference the typespec client crate. /// /// ```rust -/// # use typespec_derive::Model; +/// # use typespec_macros::Model; /// # use serde::Deserialize; /// extern crate typespec_client_core as my_typespec; /// @@ -93,7 +93,7 @@ pub fn derive_model(input: proc_macro::TokenStream) -> proc_macro::TokenStream { /// # Examples /// /// ``` -/// # use typespec_derive::SafeDebug; +/// # use typespec_macros::SafeDebug; /// #[derive(SafeDebug)] /// struct MyModel { /// name: Option, diff --git a/sdk/typespec/typespec_derive/src/model.rs b/sdk/typespec/typespec_macros/src/model.rs similarity index 100% rename from sdk/typespec/typespec_derive/src/model.rs rename to sdk/typespec/typespec_macros/src/model.rs diff --git a/sdk/typespec/typespec_derive/src/safe_debug.rs b/sdk/typespec/typespec_macros/src/safe_debug.rs similarity index 100% rename from sdk/typespec/typespec_derive/src/safe_debug.rs rename to sdk/typespec/typespec_macros/src/safe_debug.rs diff --git a/sdk/typespec/typespec_derive/tests/compilation-tests.rs b/sdk/typespec/typespec_macros/tests/compilation-tests.rs similarity index 99% rename from sdk/typespec/typespec_derive/tests/compilation-tests.rs rename to sdk/typespec/typespec_macros/tests/compilation-tests.rs index 17e1d7be2b..c702a31118 100644 --- a/sdk/typespec/typespec_derive/tests/compilation-tests.rs +++ b/sdk/typespec/typespec_macros/tests/compilation-tests.rs @@ -68,7 +68,7 @@ pub fn compilation_tests() { p }; let repo_root = { - let mut p = PathBuf::from(env!("CARGO_MANIFEST_DIR")); // [root]/sdk/typespec/typespec_derive + let mut p = PathBuf::from(env!("CARGO_MANIFEST_DIR")); // [root]/sdk/typespec/typespec_macros p.pop(); // [root]/sdk/typespec p.pop(); // [root]/sdk p.pop(); // [root] diff --git a/sdk/typespec/typespec_derive/tests/data/compilation-tests/Cargo.toml b/sdk/typespec/typespec_macros/tests/data/compilation-tests/Cargo.toml similarity index 100% rename from sdk/typespec/typespec_derive/tests/data/compilation-tests/Cargo.toml rename to sdk/typespec/typespec_macros/tests/data/compilation-tests/Cargo.toml diff --git a/sdk/typespec/typespec_derive/tests/data/compilation-tests/src/.gitignore b/sdk/typespec/typespec_macros/tests/data/compilation-tests/src/.gitignore similarity index 100% rename from sdk/typespec/typespec_derive/tests/data/compilation-tests/src/.gitignore rename to sdk/typespec/typespec_macros/tests/data/compilation-tests/src/.gitignore diff --git a/sdk/typespec/typespec_derive/tests/data/compilation-tests/src/lib.rs b/sdk/typespec/typespec_macros/tests/data/compilation-tests/src/lib.rs similarity index 100% rename from sdk/typespec/typespec_derive/tests/data/compilation-tests/src/lib.rs rename to sdk/typespec/typespec_macros/tests/data/compilation-tests/src/lib.rs diff --git a/sdk/typespec/typespec_derive/tests/data/compilation-tests/src/model/bad_attributes.expected.json b/sdk/typespec/typespec_macros/tests/data/compilation-tests/src/model/bad_attributes.expected.json similarity index 76% rename from sdk/typespec/typespec_derive/tests/data/compilation-tests/src/model/bad_attributes.expected.json rename to sdk/typespec/typespec_macros/tests/data/compilation-tests/src/model/bad_attributes.expected.json index 3363945cab..661c918f0d 100644 --- a/sdk/typespec/typespec_derive/tests/data/compilation-tests/src/model/bad_attributes.expected.json +++ b/sdk/typespec/typespec_macros/tests/data/compilation-tests/src/model/bad_attributes.expected.json @@ -5,7 +5,7 @@ "message": "invalid typespec attribute, expected attribute in form #[typespec(key = value)]", "spans": [ { - "file_name": "sdk/typespec/typespec_derive/tests/data/compilation-tests/src/model/bad_attributes.rs", + "file_name": "sdk/typespec/typespec_macros/tests/data/compilation-tests/src/model/bad_attributes.rs", "line": 8 } ] @@ -16,7 +16,7 @@ "message": "expected string literal", "spans": [ { - "file_name": "sdk/typespec/typespec_derive/tests/data/compilation-tests/src/model/bad_attributes.rs", + "file_name": "sdk/typespec/typespec_macros/tests/data/compilation-tests/src/model/bad_attributes.rs", "line": 12 } ] @@ -27,7 +27,7 @@ "message": "expected string literal", "spans": [ { - "file_name": "sdk/typespec/typespec_derive/tests/data/compilation-tests/src/model/bad_attributes.rs", + "file_name": "sdk/typespec/typespec_macros/tests/data/compilation-tests/src/model/bad_attributes.rs", "line": 16 } ] @@ -38,7 +38,7 @@ "message": "expected string literal", "spans": [ { - "file_name": "sdk/typespec/typespec_derive/tests/data/compilation-tests/src/model/bad_attributes.rs", + "file_name": "sdk/typespec/typespec_macros/tests/data/compilation-tests/src/model/bad_attributes.rs", "line": 20 } ] @@ -49,7 +49,7 @@ "message": "invalid module path: a b c", "spans": [ { - "file_name": "sdk/typespec/typespec_derive/tests/data/compilation-tests/src/model/bad_attributes.rs", + "file_name": "sdk/typespec/typespec_macros/tests/data/compilation-tests/src/model/bad_attributes.rs", "line": 24 } ] @@ -60,7 +60,7 @@ "message": "expected string literal", "spans": [ { - "file_name": "sdk/typespec/typespec_derive/tests/data/compilation-tests/src/model/bad_attributes.rs", + "file_name": "sdk/typespec/typespec_macros/tests/data/compilation-tests/src/model/bad_attributes.rs", "line": 28 } ] @@ -71,7 +71,7 @@ "message": "invalid typespec attribute, expected attribute in form #[typespec(key = value)]", "spans": [ { - "file_name": "sdk/typespec/typespec_derive/tests/data/compilation-tests/src/model/bad_attributes.rs", + "file_name": "sdk/typespec/typespec_macros/tests/data/compilation-tests/src/model/bad_attributes.rs", "line": 32 } ] @@ -82,9 +82,9 @@ "message": "invalid typespec attribute, expected attribute in form #[typespec(key = value)]", "spans": [ { - "file_name": "sdk/typespec/typespec_derive/tests/data/compilation-tests/src/model/bad_attributes.rs", + "file_name": "sdk/typespec/typespec_macros/tests/data/compilation-tests/src/model/bad_attributes.rs", "line": 36 } ] } -] \ No newline at end of file +] diff --git a/sdk/typespec/typespec_derive/tests/data/compilation-tests/src/model/bad_attributes.rs b/sdk/typespec/typespec_macros/tests/data/compilation-tests/src/model/bad_attributes.rs similarity index 100% rename from sdk/typespec/typespec_derive/tests/data/compilation-tests/src/model/bad_attributes.rs rename to sdk/typespec/typespec_macros/tests/data/compilation-tests/src/model/bad_attributes.rs diff --git a/sdk/typespec/typespec_derive/tests/data/compilation-tests/src/model/mod.rs b/sdk/typespec/typespec_macros/tests/data/compilation-tests/src/model/mod.rs similarity index 100% rename from sdk/typespec/typespec_derive/tests/data/compilation-tests/src/model/mod.rs rename to sdk/typespec/typespec_macros/tests/data/compilation-tests/src/model/mod.rs diff --git a/sdk/typespec/typespec_derive/tests/data/compilation-tests/src/model/not_derive_deserialize.expected.json b/sdk/typespec/typespec_macros/tests/data/compilation-tests/src/model/not_derive_deserialize.expected.json similarity index 73% rename from sdk/typespec/typespec_derive/tests/data/compilation-tests/src/model/not_derive_deserialize.expected.json rename to sdk/typespec/typespec_macros/tests/data/compilation-tests/src/model/not_derive_deserialize.expected.json index 06081f4526..40e0492973 100644 --- a/sdk/typespec/typespec_derive/tests/data/compilation-tests/src/model/not_derive_deserialize.expected.json +++ b/sdk/typespec/typespec_macros/tests/data/compilation-tests/src/model/not_derive_deserialize.expected.json @@ -5,9 +5,9 @@ "message": null, "spans": [ { - "file_name": "sdk/typespec/typespec_derive/tests/data/compilation-tests/src/model/not_derive_deserialize.rs", + "file_name": "sdk/typespec/typespec_macros/tests/data/compilation-tests/src/model/not_derive_deserialize.rs", "line": 6 } ] } -] \ No newline at end of file +] diff --git a/sdk/typespec/typespec_derive/tests/data/compilation-tests/src/model/not_derive_deserialize.rs b/sdk/typespec/typespec_macros/tests/data/compilation-tests/src/model/not_derive_deserialize.rs similarity index 100% rename from sdk/typespec/typespec_derive/tests/data/compilation-tests/src/model/not_derive_deserialize.rs rename to sdk/typespec/typespec_macros/tests/data/compilation-tests/src/model/not_derive_deserialize.rs diff --git a/sdk/typespec/typespec_derive/tests/data/compilation-tests/src/model/unknown_format.expected.json b/sdk/typespec/typespec_macros/tests/data/compilation-tests/src/model/unknown_format.expected.json similarity index 74% rename from sdk/typespec/typespec_derive/tests/data/compilation-tests/src/model/unknown_format.expected.json rename to sdk/typespec/typespec_macros/tests/data/compilation-tests/src/model/unknown_format.expected.json index c77bd01384..b40839b89b 100644 --- a/sdk/typespec/typespec_derive/tests/data/compilation-tests/src/model/unknown_format.expected.json +++ b/sdk/typespec/typespec_macros/tests/data/compilation-tests/src/model/unknown_format.expected.json @@ -5,9 +5,9 @@ "message": "Unknown format 'foobar'", "spans": [ { - "file_name": "sdk/typespec/typespec_derive/tests/data/compilation-tests/src/model/unknown_format.rs", + "file_name": "sdk/typespec/typespec_macros/tests/data/compilation-tests/src/model/unknown_format.rs", "line": 8 } ] } -] \ No newline at end of file +] diff --git a/sdk/typespec/typespec_derive/tests/data/compilation-tests/src/model/unknown_format.rs b/sdk/typespec/typespec_macros/tests/data/compilation-tests/src/model/unknown_format.rs similarity index 100% rename from sdk/typespec/typespec_derive/tests/data/compilation-tests/src/model/unknown_format.rs rename to sdk/typespec/typespec_macros/tests/data/compilation-tests/src/model/unknown_format.rs From 1db64d96ffff4cdb1affcd66fc661cfe2ba0d3bd Mon Sep 17 00:00:00 2001 From: Heath Stewart Date: Thu, 21 Nov 2024 16:15:46 -0800 Subject: [PATCH 2/7] Remove 128-bit number functions for Cosmos --- .../src/models/patch_operations.rs | 52 ------------------- 1 file changed, 52 deletions(-) diff --git a/sdk/cosmos/azure_data_cosmos/src/models/patch_operations.rs b/sdk/cosmos/azure_data_cosmos/src/models/patch_operations.rs index 16e256ed6d..3924c39b53 100644 --- a/sdk/cosmos/azure_data_cosmos/src/models/patch_operations.rs +++ b/sdk/cosmos/azure_data_cosmos/src/models/patch_operations.rs @@ -207,30 +207,6 @@ impl ToJsonNumber for f64 { } } -impl ToJsonNumber for i128 { - fn to_json_number(self) -> azure_core::Result { - serde_json::Number::from_i128(self).ok_or_else(|| { - Error::with_message(ErrorKind::DataConversion, || { - // The message here is a little different. JSON has no limit on the size of integers, but serde_json does. - // If a user enables the "arbitrary_precision" feature in serde_json within their own app, this error will not occur. - format!("{} cannot be represented as a valid JSON number", self) - }) - }) - } -} - -impl ToJsonNumber for u128 { - fn to_json_number(self) -> azure_core::Result { - serde_json::Number::from_u128(self).ok_or_else(|| { - azure_core::Error::with_message(ErrorKind::DataConversion, || { - // The message here is a little different. JSON has no limit on the size of integers, but serde_json does. - // If a user enables the "arbitrary_precision" feature in serde_json within their own app, this error will not occur. - format!("{} cannot be represented as a valid JSON number", self) - }) - }) - } -} - impl ToJsonNumber for serde_json::Number { fn to_json_number(self) -> azure_core::Result { Ok(self) @@ -435,14 +411,6 @@ mod tests { assert_eq!(serde_json::Number::from(-42i32), (-42i32).to_json_number()?); assert_eq!(serde_json::Number::from(42u64), (42u64).to_json_number()?); assert_eq!(serde_json::Number::from(-42i64), (-42i64).to_json_number()?); - assert_eq!( - serde_json::Number::from_u128(42u128).unwrap(), - (42u128).to_json_number()? - ); - assert_eq!( - serde_json::Number::from_i128(-42i128).unwrap(), - (-42i128).to_json_number()? - ); assert_eq!( serde_json::Number::from(42usize), (42usize).to_json_number()? @@ -453,24 +421,4 @@ mod tests { ); Ok(()) } - - #[test] - pub fn to_json_number_invalid_ints() -> Result<(), Box> { - // NOTE: This test will fail if the "arbitrary_precision" feature is enabled in serde_json. - // However, that shouldn't be possible when building the tests because we don't enable that feature. - assert_eq!( - "18446744073709551616 cannot be represented as a valid JSON number", - format!("{}", (u64::MAX as u128 + 1).to_json_number().unwrap_err()) - ); - assert_eq!( - "18446744073709551616 cannot be represented as a valid JSON number", - // Yes, we mean u64::MAX here. The range for serde_json::Number is i64::MIN to u64::MAX, because it can convert an i64 to a u64. - format!("{}", (u64::MAX as i128 + 1).to_json_number().unwrap_err()) - ); - assert_eq!( - "-9223372036854775809 cannot be represented as a valid JSON number", - format!("{}", (i64::MIN as i128 - 1).to_json_number().unwrap_err()) - ); - Ok(()) - } } From 65de2aca0f7a4d43d26e262aee185feb5d0b64ff Mon Sep 17 00:00:00 2001 From: Heath Stewart Date: Thu, 21 Nov 2024 16:13:30 -0800 Subject: [PATCH 3/7] Implement `#[recorded]` attribute macro --- .vscode/cspell.json | 3 +- CONTRIBUTING.md | 2 +- Cargo.toml | 13 +- rust-toolchain.toml | 2 +- sdk/core/azure_core_macros/Cargo.toml | 29 ++++ sdk/core/azure_core_macros/README.md | 21 +++ sdk/core/azure_core_macros/src/lib.rs | 34 ++++ sdk/core/azure_core_macros/src/recorded.rs | 175 +++++++++++++++++++++ sdk/core/azure_core_test/Cargo.toml | 19 +++ sdk/core/azure_core_test/README.md | 10 ++ sdk/core/azure_core_test/src/lib.rs | 99 ++++++++++++ 11 files changed, 401 insertions(+), 6 deletions(-) create mode 100644 sdk/core/azure_core_macros/Cargo.toml create mode 100644 sdk/core/azure_core_macros/README.md create mode 100644 sdk/core/azure_core_macros/src/lib.rs create mode 100644 sdk/core/azure_core_macros/src/recorded.rs create mode 100644 sdk/core/azure_core_test/Cargo.toml create mode 100644 sdk/core/azure_core_test/README.md create mode 100644 sdk/core/azure_core_test/src/lib.rs diff --git a/.vscode/cspell.json b/.vscode/cspell.json index 28ba03a7e6..d7ce9cfb7e 100644 --- a/.vscode/cspell.json +++ b/.vscode/cspell.json @@ -31,6 +31,7 @@ "datetime", "devicecode", "docsrs", + "doctest", "downcasted", "downcasting", "entra", @@ -128,4 +129,4 @@ ] } ] -} +} \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 437efa91eb..cd13d47de0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -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. diff --git a/Cargo.toml b/Cargo.toml index e2adf6b019..640627a74e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,8 @@ members = [ "sdk/typespec/typespec_macros", "sdk/core/azure_core", "sdk/core/azure_core_amqp", + "sdk/core/azure_core_macros", + "sdk/core/azure_core_test", "sdk/cosmos/azure_data_cosmos", "sdk/identity/azure_identity", "sdk/eventhubs/azure_messaging_eventhubs", @@ -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" @@ -38,6 +40,11 @@ path = "sdk/core/azure_core" [workspace.dependencies.azure_core_amqp] path = "sdk/core/azure_core_amqp" +[workspace.dependencies.azure_core_macros] +path = "sdk/core/azure_core_macros" + +[workspace.dependencies.azure_core_test] +path = "sdk/core/azure_core_test" [workspace.dependencies.azure_identity] path = "sdk/identity/azure_identity" @@ -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", @@ -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", diff --git a/rust-toolchain.toml b/rust-toolchain.toml index dc8833a7d4..b8f83b4088 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,5 +1,5 @@ [toolchain] -channel = "1.76.0" +channel = "1.80.0" components = [ "rustc", "rustfmt", diff --git a/sdk/core/azure_core_macros/Cargo.toml b/sdk/core/azure_core_macros/Cargo.toml new file mode 100644 index 0000000000..d1e1d5bf82 --- /dev/null +++ b/sdk/core/azure_core_macros/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "azure_core_macros" +version = "0.1.0" +description = "Procedural macros for 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 + +[dependencies] +typespec = { workspace = true, features = ["http", "json"] } +typespec_client_core = { workspace = true, features = ["http", "json"] } +azure_core_test.workspace = true +proc-macro2.workspace = true +quote.workspace = true +syn.workspace = true + +[dev-dependencies] +azure_core.workspace = true +tokio.workspace = true + +[lib] +proc-macro = true diff --git a/sdk/core/azure_core_macros/README.md b/sdk/core/azure_core_macros/README.md new file mode 100644 index 0000000000..d6a4ed9a33 --- /dev/null +++ b/sdk/core/azure_core_macros/README.md @@ -0,0 +1,21 @@ +# 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]` attribute: + +```rust +use azure_core_macros::recorded; +use azure_core_test::TestContext; +use azure_core::Result; + +#[recorded] +async fn get_secret(ctx: TestContext) -> Result<()> { + todo!() +} +``` + +[Test Proxy]: https://github.com/Azure/azure-sdk-tools/blob/main/tools/test-proxy/Azure.Sdk.Tools.TestProxy/README.md diff --git a/sdk/core/azure_core_macros/src/lib.rs b/sdk/core/azure_core_macros/src/lib.rs new file mode 100644 index 0000000000..fae062af2e --- /dev/null +++ b/sdk/core/azure_core_macros/src/lib.rs @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +mod recorded; + +#[cfg(doc)] +use azure_core_test::TestContext; +use proc_macro::TokenStream; + +/// Attribute client library tests to play back recordings, record sessions, or execute tests without recording. +/// +/// # Examples +/// +/// Recorded tests can be synchronous or asynchronous (recommended), can return a `Result`, +/// and must take a single [`TestContext`] parameter used to set up to record or play back the HTTP client. +/// +/// ``` +/// use azure_core_macros::recorded; +/// use azure_core_test::TestContext; +/// use azure_core::Result; +/// +/// #[recorded(live)] +/// async fn get_secret(ctx: TestContext) -> Result<()> { +/// todo!() +/// } +/// ``` +/// +/// To execute tests only when `AZURE_TEST_MODE` is "live", you can optionally pass `live` to the `#[recorded(live)]` attribute. + +#[proc_macro_attribute] +pub fn recorded(attr: TokenStream, item: TokenStream) -> TokenStream { + recorded::parse_recorded(attr.into(), item.into()) + .map_or_else(|e| e.into_compile_error().into(), |v| v.into()) +} diff --git a/sdk/core/azure_core_macros/src/recorded.rs b/sdk/core/azure_core_macros/src/recorded.rs new file mode 100644 index 0000000000..4077537ff9 --- /dev/null +++ b/sdk/core/azure_core_macros/src/recorded.rs @@ -0,0 +1,175 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +use azure_core_test::TestMode; +use proc_macro2::TokenStream; +use quote::quote; +use std::sync::LazyLock; +use syn::{parse::Parse, spanned::Spanned, FnArg, ItemFn, Meta, PatType, Result, Token}; + +const INVALID_RECORDED_ATTRIBUTE_MESSAGE: &str = "expected `#[recorded]` or `#[recorded(live)]`"; + +// cspell:ignore asyncness +pub fn parse_recorded(attr: TokenStream, item: TokenStream) -> Result { + let recorded_attrs: Attributes = syn::parse2(attr)?; + let ItemFn { + attrs, + vis, + mut sig, + block, + } = syn::parse2(item)?; + + // Use #[tokio::test] for async functions; otherwise, #[test]. + let mut test_attr: TokenStream = if sig.asyncness.is_some() { + quote! { #[::tokio::test] } + } else { + quote! { #[::core::prelude::v1::test] } + }; + + // Ignore live-only tests if not running live tests. + let test_mode = *TEST_MODE; + if recorded_attrs.live && test_mode < TestMode::Live { + test_attr.extend(quote! { + #[ignore = "skipping live tests"] + }); + } + + let Some(FnArg::Typed(PatType { pat, ty, .. })) = sig.inputs.first() else { + return Err(syn::Error::new( + sig.span(), + INVALID_RECORDED_ATTRIBUTE_MESSAGE, + )); + }; + + if !is_test_context(ty.as_ref()) || sig.inputs.len() > 1 { + return Err(syn::Error::new( + sig.span(), + INVALID_RECORDED_ATTRIBUTE_MESSAGE, + )); + } + + let test_mode = test_mode_to_tokens(test_mode); + let fn_name = &sig.ident; + let preamble = quote! { + #[allow(dead_code)] + let #pat = #ty::new(#test_mode, stringify!(#fn_name)); + }; + + // Empty the parameters and return our rewritten test function. + sig.inputs.clear(); + Ok(quote! { + #test_attr + #(#attrs)* + #vis #sig { + #preamble + #block + } + }) +} + +static TEST_MODE: LazyLock = LazyLock::new(|| { + // Okay to panic if AZURE_TEST_MODE is unsupported. + TestMode::current().unwrap() +}); + +#[derive(Debug, Default)] +struct Attributes { + live: bool, +} + +impl Parse for Attributes { + fn parse(input: syn::parse::ParseStream) -> Result { + let mut attrs = Self::default(); + for arg in input.parse_terminated(Meta::parse, Token![,])? { + match &arg { + Meta::Path(path) => { + let ident = path.get_ident().ok_or_else(|| { + syn::Error::new(arg.span(), INVALID_RECORDED_ATTRIBUTE_MESSAGE) + })?; + match ident.to_string().as_str() { + "live" => attrs.live = true, + _ => { + return Err(syn::Error::new( + arg.span(), + INVALID_RECORDED_ATTRIBUTE_MESSAGE, + )) + } + } + } + _ => { + return Err(syn::Error::new( + arg.span(), + INVALID_RECORDED_ATTRIBUTE_MESSAGE, + )) + } + } + } + + Ok(attrs) + } +} + +fn is_test_context(arg: &syn::Type) -> bool { + let path = match arg { + syn::Type::Path(syn::TypePath { path, .. }) => path, + _ => return false, + }; + + if path.leading_colon.is_none() + && path.segments.len() == 1 + && path.segments[0].ident == "TestContext" + { + return true; + } + + path.segments.len() == 2 + && path.segments[0].ident == "azure_core_test" + && path.segments[1].ident == "TestContext" +} + +fn test_mode_to_tokens(test_mode: TestMode) -> TokenStream { + match test_mode { + TestMode::Playback => quote! { ::azure_core_test::TestMode::Playback }, + TestMode::Record => quote! { ::azure_core_test::TestMode::Record }, + TestMode::Live => quote! { ::azure_core_test::TestMode::Live }, + } +} + +#[cfg(test)] +mod tests { + use super::*; + use syn::Attribute; + + #[test] + fn attributes_parse_live() { + let attr: Attribute = syn::parse_quote! { + #[recorded(live)] + }; + let attrs: Attributes = attr.parse_args().unwrap(); + assert!(attrs.live); + } + + #[test] + fn attributes_parse_other() { + let attr: Attribute = syn::parse_quote! { + #[recorded(other)] + }; + attr.parse_args::().unwrap_err(); + } + + #[test] + fn attributes_parse_multiple() { + let attr: Attribute = syn::parse_quote! { + #[recorded(live, other)] + }; + attr.parse_args::().unwrap_err(); + } + + #[test] + fn attributes_parse_live_value() { + let attr: Attribute = syn::parse_quote! { + #[recorded(live = true)] + }; + attr.parse_args::().unwrap_err(); + } +} diff --git a/sdk/core/azure_core_test/Cargo.toml b/sdk/core/azure_core_test/Cargo.toml new file mode 100644 index 0000000000..a7ad8c5eb3 --- /dev/null +++ b/sdk/core/azure_core_test/Cargo.toml @@ -0,0 +1,19 @@ +[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] +typespec = { workspace = true, features = ["http", "json"] } +typespec_client_core = { workspace = true, features = ["http", "json"] } +azure_core.workspace = true diff --git a/sdk/core/azure_core_test/README.md b/sdk/core/azure_core_test/README.md new file mode 100644 index 0000000000..62a9cee091 --- /dev/null +++ b/sdk/core/azure_core_test/README.md @@ -0,0 +1,10 @@ +# 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]` attribute. See examples in the `azure_core_macros` crate. + +[Test Proxy]: https://github.com/Azure/azure-sdk-tools/blob/main/tools/test-proxy/Azure.Sdk.Tools.TestProxy/README.md diff --git a/sdk/core/azure_core_test/src/lib.rs b/sdk/core/azure_core_test/src/lib.rs new file mode 100644 index 0000000000..67a8a31454 --- /dev/null +++ b/sdk/core/azure_core_test/src/lib.rs @@ -0,0 +1,99 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#![doc = include_str!("../README.md")] + +use std::{fmt, str::FromStr}; +use typespec::{error::ErrorKind, Error}; + +/// 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 { + 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 { + 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'", + )), + } + } +} + +#[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"); + } +} From 12a8be5f239859921532a8008238053c4608490f Mon Sep 17 00:00:00 2001 From: Heath Stewart Date: Thu, 21 Nov 2024 18:23:12 -0800 Subject: [PATCH 4/7] Allow live-only tests with no parameters --- sdk/core/azure_core_macros/README.md | 5 ++ sdk/core/azure_core_macros/src/recorded.rs | 92 ++++++++++++++++++---- 2 files changed, 82 insertions(+), 15 deletions(-) diff --git a/sdk/core/azure_core_macros/README.md b/sdk/core/azure_core_macros/README.md index d6a4ed9a33..60f84d8abc 100644 --- a/sdk/core/azure_core_macros/README.md +++ b/sdk/core/azure_core_macros/README.md @@ -18,4 +18,9 @@ async fn get_secret(ctx: TestContext) -> Result<()> { } ``` +The `TestContext` parameter is required unless your test function is attribute as `#[recorded(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 diff --git a/sdk/core/azure_core_macros/src/recorded.rs b/sdk/core/azure_core_macros/src/recorded.rs index 4077537ff9..940f7474c8 100644 --- a/sdk/core/azure_core_macros/src/recorded.rs +++ b/sdk/core/azure_core_macros/src/recorded.rs @@ -8,6 +8,7 @@ use std::sync::LazyLock; use syn::{parse::Parse, spanned::Spanned, FnArg, ItemFn, Meta, PatType, Result, Token}; const INVALID_RECORDED_ATTRIBUTE_MESSAGE: &str = "expected `#[recorded]` or `#[recorded(live)]`"; +const INVALID_RECORDED_FUNCTION_MESSAGE: &str = "expected `fn(TestContext)` function signature"; // cspell:ignore asyncness pub fn parse_recorded(attr: TokenStream, item: TokenStream) -> Result { @@ -34,27 +35,33 @@ pub fn parse_recorded(attr: TokenStream, item: TokenStream) -> Result TokenStream::new(), + Some(FnArg::Typed(PatType { pat, ty, .. })) if is_test_context(ty.as_ref()) => { + let test_mode = test_mode_to_tokens(test_mode); + let fn_name = &sig.ident; + + quote! { + #[allow(dead_code)] + let #pat = #ty::new(#test_mode, stringify!(#fn_name)); + } + } + _ => { + return Err(syn::Error::new( + sig.ident.span(), + INVALID_RECORDED_FUNCTION_MESSAGE, + )) + } }; - if !is_test_context(ty.as_ref()) || sig.inputs.len() > 1 { + if let Some(arg) = inputs.next() { return Err(syn::Error::new( - sig.span(), - INVALID_RECORDED_ATTRIBUTE_MESSAGE, + arg.span(), + format!("too many parameters; {INVALID_RECORDED_FUNCTION_MESSAGE}"), )); } - let test_mode = test_mode_to_tokens(test_mode); - let fn_name = &sig.ident; - let preamble = quote! { - #[allow(dead_code)] - let #pat = #ty::new(#test_mode, stringify!(#fn_name)); - }; - // Empty the parameters and return our rewritten test function. sig.inputs.clear(); Ok(quote! { @@ -172,4 +179,59 @@ mod tests { }; attr.parse_args::().unwrap_err(); } + + #[test] + fn parse_recorded_playback() { + let attr = TokenStream::new(); + let item = quote! { + async fn recorded() { + todo!() + } + }; + parse_recorded(attr, item).unwrap_err(); + } + + #[test] + fn parse_recorded_playback_with_context() { + let attr = TokenStream::new(); + let item = quote! { + async fn recorded(ctx: TestContext) { + todo!() + } + }; + parse_recorded(attr, item).unwrap(); + } + + #[test] + fn parse_recorded_playback_with_multiple() { + let attr = TokenStream::new(); + let item = quote! { + async fn recorded(ctx: TestContext, name: &'static str) { + todo!() + } + }; + parse_recorded(attr, item).unwrap_err(); + } + + #[test] + fn parse_recorded_live() { + let attr = quote! { live }; + let item = quote! { + async fn live_only() { + todo!() + } + }; + parse_recorded(attr, item).unwrap(); + } + + #[test] + fn parse_recorded_live_with_context() { + let attr = quote! { live }; + let item = quote! { + async fn live_only(ctx: TestContext) { + todo!() + } + }; + parse_recorded(attr, item).unwrap(); + } } From 91412996f219361d084289b765ea7b0af46e9945 Mon Sep 17 00:00:00 2001 From: Heath Stewart Date: Thu, 21 Nov 2024 18:23:49 -0800 Subject: [PATCH 5/7] Replace test_e2e feature with `recorded` attribute macro --- .../azure_messaging_eventhubs/Cargo.toml | 5 ++-- .../tests/consumer.rs | 17 ++++++------- .../tests/producer.rs | 25 +++++++++---------- .../tests/round_trip.rs | 5 ++-- 4 files changed, 24 insertions(+), 28 deletions(-) diff --git a/sdk/eventhubs/azure_messaging_eventhubs/Cargo.toml b/sdk/eventhubs/azure_messaging_eventhubs/Cargo.toml index a572619a71..3a27b00a39 100644 --- a/sdk/eventhubs/azure_messaging_eventhubs/Cargo.toml +++ b/sdk/eventhubs/azure_messaging_eventhubs/Cargo.toml @@ -32,12 +32,11 @@ rustc_version.workspace = true [dev-dependencies] tracing-subscriber = { workspace = true, features = ["env-filter", "fmt"] } +azure_core_macros.workspace = true +azure_core_test.workspace = true azure_identity.workspace = true tokio = { workspace = true, default-features = false, features = [ "rt-multi-thread", "macros", "time", ] } - -[features] -test_e2e = [] diff --git a/sdk/eventhubs/azure_messaging_eventhubs/tests/consumer.rs b/sdk/eventhubs/azure_messaging_eventhubs/tests/consumer.rs index 0d58d3c3ef..98a8af5666 100644 --- a/sdk/eventhubs/azure_messaging_eventhubs/tests/consumer.rs +++ b/sdk/eventhubs/azure_messaging_eventhubs/tests/consumer.rs @@ -3,9 +3,8 @@ //cspell: words eventdata -#![cfg(all(test, feature = "test_e2e"))] // to run this, do: `cargo test --features test_e2e` - use async_std::future::timeout; +use azure_core_macros::recorded; use azure_identity::DefaultAzureCredential; use azure_messaging_eventhubs::consumer::{ ConsumerClient, ConsumerClientOptions, ReceiveOptions, StartPosition, @@ -16,7 +15,7 @@ use tracing::{info, trace}; mod common; -#[tokio::test] +#[recorded(live)] async fn test_new() { common::setup(); let host = env::var("EVENTHUBS_HOST").unwrap(); @@ -33,7 +32,7 @@ async fn test_new() { ); } -#[tokio::test] +#[recorded(live)] async fn test_new_with_error() { common::setup(); trace!("test_new_with_error"); @@ -53,7 +52,7 @@ async fn test_new_with_error() { info!("Error: {:?}", result); } -#[tokio::test] +#[recorded(live)] async fn test_open() { common::setup(); let host = env::var("EVENTHUBS_HOST").unwrap(); @@ -70,7 +69,7 @@ async fn test_open() { ); client.open().await.unwrap(); } -#[tokio::test] +#[recorded(live)] async fn test_close() { common::setup(); let host = env::var("EVENTHUBS_HOST").unwrap(); @@ -89,7 +88,7 @@ async fn test_close() { client.close().await.unwrap(); } -#[tokio::test] +#[recorded(live)] async fn test_get_properties() { common::setup(); let host = env::var("EVENTHUBS_HOST").unwrap(); @@ -113,7 +112,7 @@ async fn test_get_properties() { assert_eq!(properties.name, eventhub); } -#[tokio::test] +#[recorded(live)] async fn test_get_partition_properties() { common::setup(); let host = env::var("EVENTHUBS_HOST").unwrap(); @@ -144,7 +143,7 @@ async fn test_get_partition_properties() { } } -#[tokio::test] +#[recorded(live)] async fn receive_lots_of_events() { common::setup(); diff --git a/sdk/eventhubs/azure_messaging_eventhubs/tests/producer.rs b/sdk/eventhubs/azure_messaging_eventhubs/tests/producer.rs index c38caeac51..45c975aa41 100644 --- a/sdk/eventhubs/azure_messaging_eventhubs/tests/producer.rs +++ b/sdk/eventhubs/azure_messaging_eventhubs/tests/producer.rs @@ -3,12 +3,11 @@ //cspell: words eventdata amqp -#![cfg(all(test, feature = "test_e2e"))] // to run this, do: `cargo test --features test_e2e` - use azure_core_amqp::{ messaging::{AmqpMessage, AmqpMessageBody}, value::{AmqpList, AmqpValue}, }; +use azure_core_macros::recorded; use azure_identity::DefaultAzureCredential; use azure_messaging_eventhubs::producer::{ batch::EventDataBatchOptions, ProducerClient, ProducerClientOptions, @@ -18,7 +17,7 @@ use tracing::{info, trace}; mod common; -#[tokio::test] +#[recorded(live)] async fn test_new() { common::setup(); let host = env::var("EVENTHUBS_HOST").unwrap(); @@ -34,7 +33,7 @@ async fn test_new() { ); } -#[tokio::test] +#[recorded(live)] async fn test_new_with_error() { common::setup(); let eventhub = env::var("EVENTHUB_NAME").unwrap(); @@ -52,7 +51,7 @@ async fn test_new_with_error() { info!("Error: {:?}", result); } -#[tokio::test] +#[recorded(live)] async fn test_open() { common::setup(); let host = env::var("EVENTHUBS_HOST").unwrap(); @@ -68,7 +67,7 @@ async fn test_open() { ); client.open().await.unwrap(); } -#[tokio::test] +#[recorded(live)] async fn test_close() { common::setup(); let host = env::var("EVENTHUBS_HOST").unwrap(); @@ -86,7 +85,7 @@ async fn test_close() { client.close().await.unwrap(); } -#[tokio::test] +#[recorded(live)] async fn test_get_properties() { common::setup(); let host = env::var("EVENTHUBS_HOST").unwrap(); @@ -109,7 +108,7 @@ async fn test_get_properties() { assert_eq!(properties.name, eventhub); } -#[tokio::test] +#[recorded(live)] async fn test_get_partition_properties() { common::setup(); let host = env::var("EVENTHUBS_HOST").unwrap(); @@ -139,7 +138,7 @@ async fn test_get_partition_properties() { } } -#[test] +#[recorded(live)] fn test_create_eventdata() { common::setup(); let data = b"hello world"; @@ -163,7 +162,7 @@ fn test_create_eventdata() { .build(); } -#[tokio::test] +#[recorded(live)] async fn test_create_batch() { common::setup(); let host = env::var("EVENTHUBS_HOST").unwrap(); @@ -187,7 +186,7 @@ async fn test_create_batch() { } } -#[tokio::test] +#[recorded(live)] async fn test_create_and_send_batch() { common::setup(); let host = env::var("EVENTHUBS_HOST").unwrap(); @@ -239,7 +238,7 @@ async fn test_create_and_send_batch() { } } -#[tokio::test] +#[recorded(live)] async fn test_add_amqp_messages_to_batch() -> Result<(), Box> { common::setup(); let host = env::var("EVENTHUBS_HOST").unwrap(); @@ -316,7 +315,7 @@ async fn test_add_amqp_messages_to_batch() -> Result<(), Box Date: Fri, 22 Nov 2024 14:55:24 -0800 Subject: [PATCH 6/7] Refactor so that tests need only import azure_core_tests Also makes the attribute `#[recorded::test]` which, IMO, looks a bit better. --- Cargo.toml | 8 +-- eng/scripts/Test-Packages.ps1 | 5 +- sdk/core/azure_core/Cargo.toml | 19 +++--- sdk/core/azure_core/src/lib.rs | 3 + sdk/core/azure_core/src/test.rs | 63 +++++++++++++++++++ sdk/core/azure_core_test/Cargo.toml | 8 ++- sdk/core/azure_core_test/README.md | 17 ++++- sdk/core/azure_core_test/src/lib.rs | 57 ++--------------- .../Cargo.toml | 16 +++-- .../README.md | 9 ++- .../src/lib.rs | 16 ++--- .../src/test.rs} | 29 ++++++--- .../src/clients/cosmos_client.rs | 2 +- .../src/pipeline/authorization_policy.rs | 4 +- .../src/pipeline/signature_target.rs | 2 +- .../azure_messaging_eventhubs/Cargo.toml | 1 - .../tests/consumer.rs | 16 ++--- .../tests/producer.rs | 24 +++---- .../tests/round_trip.rs | 4 +- 19 files changed, 174 insertions(+), 129 deletions(-) create mode 100644 sdk/core/azure_core/src/test.rs rename sdk/core/{azure_core_macros => azure_core_test_macros}/Cargo.toml (67%) rename sdk/core/{azure_core_macros => azure_core_test_macros}/README.md (82%) rename sdk/core/{azure_core_macros => azure_core_test_macros}/src/lib.rs (72%) rename sdk/core/{azure_core_macros/src/recorded.rs => azure_core_test_macros/src/test.rs} (88%) diff --git a/Cargo.toml b/Cargo.toml index 640627a74e..1da769009c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,8 +6,8 @@ members = [ "sdk/typespec/typespec_macros", "sdk/core/azure_core", "sdk/core/azure_core_amqp", - "sdk/core/azure_core_macros", "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", @@ -40,12 +40,12 @@ path = "sdk/core/azure_core" [workspace.dependencies.azure_core_amqp] path = "sdk/core/azure_core_amqp" -[workspace.dependencies.azure_core_macros] -path = "sdk/core/azure_core_macros" - [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" diff --git a/eng/scripts/Test-Packages.ps1 b/eng/scripts/Test-Packages.ps1 index 0c453f11d0..5816d2eaee 100755 --- a/eng/scripts/Test-Packages.ps1 +++ b/eng/scripts/Test-Packages.ps1 @@ -59,13 +59,14 @@ foreach ($package in $packagesToTest) { if ($FunctionalTests) { $targets += "--bins" $targets += "--examples" + $targets += "--tests" $targets += "--benches" } - Invoke-LoggedCommand "cargo +$Toolchain test $($targets -join ' ') --no-fail-fast" + Invoke-LoggedCommand "cargo +$Toolchain test --all-features $($targets -join ' ') --no-fail-fast" Write-Host "`n`n" - Invoke-LoggedCommand "cargo +$Toolchain test --doc --no-fail-fast" + Invoke-LoggedCommand "cargo +$Toolchain test --all-features --doc --no-fail-fast" Write-Host "`n`n" } finally { diff --git a/sdk/core/azure_core/Cargo.toml b/sdk/core/azure_core/Cargo.toml index c76b4bd7ed..a6e69a6d99 100644 --- a/sdk/core/azure_core/Cargo.toml +++ b/sdk/core/azure_core/Cargo.toml @@ -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", ] diff --git a/sdk/core/azure_core/src/lib.rs b/sdk/core/azure_core/src/lib.rs index df623f03f6..5e8194c9c7 100644 --- a/sdk/core/azure_core/src/lib.rs +++ b/sdk/core/azure_core/src/lib.rs @@ -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::*; diff --git a/sdk/core/azure_core/src/test.rs b/sdk/core/azure_core/src/test.rs new file mode 100644 index 0000000000..00e743b25c --- /dev/null +++ b/sdk/core/azure_core/src/test.rs @@ -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 { + 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 { + 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'", + )), + } + } +} diff --git a/sdk/core/azure_core_test/Cargo.toml b/sdk/core/azure_core_test/Cargo.toml index a7ad8c5eb3..e5b5485098 100644 --- a/sdk/core/azure_core_test/Cargo.toml +++ b/sdk/core/azure_core_test/Cargo.toml @@ -14,6 +14,8 @@ edition.workspace = true rust-version.workspace = true [dependencies] -typespec = { workspace = true, features = ["http", "json"] } -typespec_client_core = { workspace = true, features = ["http", "json"] } -azure_core.workspace = true +azure_core = { workspace = true, features = ["test"] } +azure_core_test_macros.workspace = true + +[dev-dependencies] +tokio.workspace = true diff --git a/sdk/core/azure_core_test/README.md b/sdk/core/azure_core_test/README.md index 62a9cee091..f26d5ce05b 100644 --- a/sdk/core/azure_core_test/README.md +++ b/sdk/core/azure_core_test/README.md @@ -5,6 +5,21 @@ The types and functions in this crate help test client libraries built on `azure ## Client methods To test client methods using our [Test Proxy], you can attribute both synchronous and asynchronous (recommend) tests -using the `#[recorded]` attribute. See examples in the `azure_core_macros` crate. +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 diff --git a/sdk/core/azure_core_test/src/lib.rs b/sdk/core/azure_core_test/src/lib.rs index 67a8a31454..9057c215ae 100644 --- a/sdk/core/azure_core_test/src/lib.rs +++ b/sdk/core/azure_core_test/src/lib.rs @@ -3,61 +3,12 @@ #![doc = include_str!("../README.md")] -use std::{fmt, str::FromStr}; -use typespec::{error::ErrorKind, Error}; - -/// 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 { - 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()) - } +/// Live recording and playing back of client library tests. +pub mod recorded { + pub use azure_core_test_macros::test; } -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 { - 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'", - )), - } - } -} +pub use azure_core::test::TestMode; #[derive(Clone, Debug)] pub struct TestContext { diff --git a/sdk/core/azure_core_macros/Cargo.toml b/sdk/core/azure_core_test_macros/Cargo.toml similarity index 67% rename from sdk/core/azure_core_macros/Cargo.toml rename to sdk/core/azure_core_test_macros/Cargo.toml index d1e1d5bf82..e04c7ef0f0 100644 --- a/sdk/core/azure_core_macros/Cargo.toml +++ b/sdk/core/azure_core_test_macros/Cargo.toml @@ -1,7 +1,7 @@ [package] -name = "azure_core_macros" +name = "azure_core_test_macros" version = "0.1.0" -description = "Procedural macros for client libraries built on azure_core." +description = "Procedural macros for testing client libraries built on azure_core." readme = "README.md" authors.workspace = true license.workspace = true @@ -13,17 +13,15 @@ categories = ["development-tools"] edition.workspace = true rust-version.workspace = true +[lib] +proc-macro = true + [dependencies] -typespec = { workspace = true, features = ["http", "json"] } -typespec_client_core = { workspace = true, features = ["http", "json"] } -azure_core_test.workspace = true +azure_core = { workspace = true, features = ["test"] } proc-macro2.workspace = true quote.workspace = true syn.workspace = true [dev-dependencies] -azure_core.workspace = true +azure_core_test.workspace = true tokio.workspace = true - -[lib] -proc-macro = true diff --git a/sdk/core/azure_core_macros/README.md b/sdk/core/azure_core_test_macros/README.md similarity index 82% rename from sdk/core/azure_core_macros/README.md rename to sdk/core/azure_core_test_macros/README.md index 60f84d8abc..96f49e90b7 100644 --- a/sdk/core/azure_core_macros/README.md +++ b/sdk/core/azure_core_test_macros/README.md @@ -5,20 +5,19 @@ 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]` attribute: +using the `#[recorded::test]` attribute: ```rust -use azure_core_macros::recorded; -use azure_core_test::TestContext; +use azure_core_test::{recorded, TestContext}; use azure_core::Result; -#[recorded] +#[recorded::test] async fn get_secret(ctx: TestContext) -> Result<()> { todo!() } ``` -The `TestContext` parameter is required unless your test function is attribute as `#[recorded(live)]` (live-only), +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. diff --git a/sdk/core/azure_core_macros/src/lib.rs b/sdk/core/azure_core_test_macros/src/lib.rs similarity index 72% rename from sdk/core/azure_core_macros/src/lib.rs rename to sdk/core/azure_core_test_macros/src/lib.rs index fae062af2e..2291e0b0f0 100644 --- a/sdk/core/azure_core_macros/src/lib.rs +++ b/sdk/core/azure_core_test_macros/src/lib.rs @@ -1,7 +1,9 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -mod recorded; +#![doc = include_str!("../README.md")] + +mod test; #[cfg(doc)] use azure_core_test::TestContext; @@ -15,20 +17,18 @@ use proc_macro::TokenStream; /// and must take a single [`TestContext`] parameter used to set up to record or play back the HTTP client. /// /// ``` -/// use azure_core_macros::recorded; -/// use azure_core_test::TestContext; +/// use azure_core_test::{recorded, TestContext}; /// use azure_core::Result; /// -/// #[recorded(live)] +/// #[recorded::test(live)] /// async fn get_secret(ctx: TestContext) -> Result<()> { /// todo!() /// } /// ``` /// -/// To execute tests only when `AZURE_TEST_MODE` is "live", you can optionally pass `live` to the `#[recorded(live)]` attribute. - +/// To execute tests only when `AZURE_TEST_MODE` is "live", you can optionally pass `live` to the `#[recorded::test(live)]` attribute. #[proc_macro_attribute] -pub fn recorded(attr: TokenStream, item: TokenStream) -> TokenStream { - recorded::parse_recorded(attr.into(), item.into()) +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()) } diff --git a/sdk/core/azure_core_macros/src/recorded.rs b/sdk/core/azure_core_test_macros/src/test.rs similarity index 88% rename from sdk/core/azure_core_macros/src/recorded.rs rename to sdk/core/azure_core_test_macros/src/test.rs index 940f7474c8..ec14b09d34 100644 --- a/sdk/core/azure_core_macros/src/recorded.rs +++ b/sdk/core/azure_core_test_macros/src/test.rs @@ -1,17 +1,18 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -use azure_core_test::TestMode; +use azure_core::test::TestMode; use proc_macro2::TokenStream; use quote::quote; use std::sync::LazyLock; use syn::{parse::Parse, spanned::Spanned, FnArg, ItemFn, Meta, PatType, Result, Token}; -const INVALID_RECORDED_ATTRIBUTE_MESSAGE: &str = "expected `#[recorded]` or `#[recorded(live)]`"; +const INVALID_RECORDED_ATTRIBUTE_MESSAGE: &str = + "expected `#[recorded::test]` or `#[recorded::test(live)]`"; const INVALID_RECORDED_FUNCTION_MESSAGE: &str = "expected `fn(TestContext)` function signature"; // cspell:ignore asyncness -pub fn parse_recorded(attr: TokenStream, item: TokenStream) -> Result { +pub fn parse_test(attr: TokenStream, item: TokenStream) -> Result { let recorded_attrs: Attributes = syn::parse2(attr)?; let ItemFn { attrs, @@ -180,6 +181,18 @@ mod tests { attr.parse_args::().unwrap_err(); } + #[test] + fn is_test_context() { + let types: Vec = vec![ + syn::parse_quote! { ::azure_core_test::TestContext }, + syn::parse_quote! { azure_core_test::TestContext }, + syn::parse_quote! { TestContext }, + ]; + for ty in types { + assert!(super::is_test_context(&ty)); + } + } + #[test] fn parse_recorded_playback() { let attr = TokenStream::new(); @@ -188,7 +201,7 @@ mod tests { todo!() } }; - parse_recorded(attr, item).unwrap_err(); + parse_test(attr, item).unwrap_err(); } #[test] @@ -199,7 +212,7 @@ mod tests { todo!() } }; - parse_recorded(attr, item).unwrap(); + parse_test(attr, item).unwrap(); } #[test] @@ -210,7 +223,7 @@ mod tests { todo!() } }; - parse_recorded(attr, item).unwrap_err(); + parse_test(attr, item).unwrap_err(); } #[test] @@ -221,7 +234,7 @@ mod tests { todo!() } }; - parse_recorded(attr, item).unwrap(); + parse_test(attr, item).unwrap(); } #[test] @@ -232,6 +245,6 @@ mod tests { todo!() } }; - parse_recorded(attr, item).unwrap(); + parse_test(attr, item).unwrap(); } } diff --git a/sdk/cosmos/azure_data_cosmos/src/clients/cosmos_client.rs b/sdk/cosmos/azure_data_cosmos/src/clients/cosmos_client.rs index 89ebb08ae8..61a64e2384 100644 --- a/sdk/cosmos/azure_data_cosmos/src/clients/cosmos_client.rs +++ b/sdk/cosmos/azure_data_cosmos/src/clients/cosmos_client.rs @@ -69,7 +69,7 @@ impl CosmosClient { /// ```rust,no_run /// use azure_data_cosmos::CosmosClient; /// - /// let client = CosmosClient::with_key("https://myaccount.documents.azure.com/", "my_key", None).unwrap(); + /// let client = CosmosClient::with_key("https://myaccount.documents.azure.com/", "my_key".into(), None).unwrap(); /// ``` #[cfg(feature = "key_auth")] pub fn with_key( diff --git a/sdk/cosmos/azure_data_cosmos/src/pipeline/authorization_policy.rs b/sdk/cosmos/azure_data_cosmos/src/pipeline/authorization_policy.rs index ede1bfbd12..5cabcb4dfd 100644 --- a/sdk/cosmos/azure_data_cosmos/src/pipeline/authorization_policy.rs +++ b/sdk/cosmos/azure_data_cosmos/src/pipeline/authorization_policy.rs @@ -4,7 +4,7 @@ //! Defines Cosmos DB's unique Authentication Policy. //! //! The Cosmos DB data plane doesn't use a standard `Authorization: Bearer` header for authentication. -//! Instead, it uses a custom header format, as defined in the [official documentation](https://docs.microsoft.com/en-us/rest/api/cosmos-db/access-control-on-cosmosdb-resources). +//! Instead, it uses a custom header format, as defined in the [official documentation](https://docs.microsoft.com/rest/api/cosmos-db/access-control-on-cosmosdb-resources). //! We implement that policy here, because we can't use any standard Azure SDK authentication policy. #[cfg_attr(not(feature = "key_auth"), allow(unused_imports))] @@ -237,7 +237,7 @@ mod tests { .unwrap(); let expected: String = - url_encode(b"type=master&ver=1.0&sig=Qkz/r+1N2+PEnNijxGbGB/ADvLsLBQmZ7uBBMuIwf4I="); + url_encode(b"type=master&ver=1.0&sig=vrHmd02almbIg1e4htVWH+Eg/OhEHip3VTwFivZLH0A="); assert_eq!(ret, expected); } diff --git a/sdk/cosmos/azure_data_cosmos/src/pipeline/signature_target.rs b/sdk/cosmos/azure_data_cosmos/src/pipeline/signature_target.rs index 1a3f220b96..f142140930 100644 --- a/sdk/cosmos/azure_data_cosmos/src/pipeline/signature_target.rs +++ b/sdk/cosmos/azure_data_cosmos/src/pipeline/signature_target.rs @@ -96,7 +96,7 @@ mod tests { assert_eq!( ret, "get -dbs +colls dbs/MyDatabase/colls/MyCollection mon, 01 jan 1900 01:00:00 gmt diff --git a/sdk/eventhubs/azure_messaging_eventhubs/Cargo.toml b/sdk/eventhubs/azure_messaging_eventhubs/Cargo.toml index 3a27b00a39..43c6d1f891 100644 --- a/sdk/eventhubs/azure_messaging_eventhubs/Cargo.toml +++ b/sdk/eventhubs/azure_messaging_eventhubs/Cargo.toml @@ -32,7 +32,6 @@ rustc_version.workspace = true [dev-dependencies] tracing-subscriber = { workspace = true, features = ["env-filter", "fmt"] } -azure_core_macros.workspace = true azure_core_test.workspace = true azure_identity.workspace = true tokio = { workspace = true, default-features = false, features = [ diff --git a/sdk/eventhubs/azure_messaging_eventhubs/tests/consumer.rs b/sdk/eventhubs/azure_messaging_eventhubs/tests/consumer.rs index 98a8af5666..d033e1c398 100644 --- a/sdk/eventhubs/azure_messaging_eventhubs/tests/consumer.rs +++ b/sdk/eventhubs/azure_messaging_eventhubs/tests/consumer.rs @@ -4,7 +4,7 @@ //cspell: words eventdata use async_std::future::timeout; -use azure_core_macros::recorded; +use azure_core_test::recorded; use azure_identity::DefaultAzureCredential; use azure_messaging_eventhubs::consumer::{ ConsumerClient, ConsumerClientOptions, ReceiveOptions, StartPosition, @@ -15,7 +15,7 @@ use tracing::{info, trace}; mod common; -#[recorded(live)] +#[recorded::test(live)] async fn test_new() { common::setup(); let host = env::var("EVENTHUBS_HOST").unwrap(); @@ -32,7 +32,7 @@ async fn test_new() { ); } -#[recorded(live)] +#[recorded::test(live)] async fn test_new_with_error() { common::setup(); trace!("test_new_with_error"); @@ -52,7 +52,7 @@ async fn test_new_with_error() { info!("Error: {:?}", result); } -#[recorded(live)] +#[recorded::test(live)] async fn test_open() { common::setup(); let host = env::var("EVENTHUBS_HOST").unwrap(); @@ -69,7 +69,7 @@ async fn test_open() { ); client.open().await.unwrap(); } -#[recorded(live)] +#[recorded::test(live)] async fn test_close() { common::setup(); let host = env::var("EVENTHUBS_HOST").unwrap(); @@ -88,7 +88,7 @@ async fn test_close() { client.close().await.unwrap(); } -#[recorded(live)] +#[recorded::test(live)] async fn test_get_properties() { common::setup(); let host = env::var("EVENTHUBS_HOST").unwrap(); @@ -112,7 +112,7 @@ async fn test_get_properties() { assert_eq!(properties.name, eventhub); } -#[recorded(live)] +#[recorded::test(live)] async fn test_get_partition_properties() { common::setup(); let host = env::var("EVENTHUBS_HOST").unwrap(); @@ -143,7 +143,7 @@ async fn test_get_partition_properties() { } } -#[recorded(live)] +#[recorded::test(live)] async fn receive_lots_of_events() { common::setup(); diff --git a/sdk/eventhubs/azure_messaging_eventhubs/tests/producer.rs b/sdk/eventhubs/azure_messaging_eventhubs/tests/producer.rs index 45c975aa41..0ca6752ef8 100644 --- a/sdk/eventhubs/azure_messaging_eventhubs/tests/producer.rs +++ b/sdk/eventhubs/azure_messaging_eventhubs/tests/producer.rs @@ -7,7 +7,7 @@ use azure_core_amqp::{ messaging::{AmqpMessage, AmqpMessageBody}, value::{AmqpList, AmqpValue}, }; -use azure_core_macros::recorded; +use azure_core_test::recorded; use azure_identity::DefaultAzureCredential; use azure_messaging_eventhubs::producer::{ batch::EventDataBatchOptions, ProducerClient, ProducerClientOptions, @@ -17,7 +17,7 @@ use tracing::{info, trace}; mod common; -#[recorded(live)] +#[recorded::test(live)] async fn test_new() { common::setup(); let host = env::var("EVENTHUBS_HOST").unwrap(); @@ -33,7 +33,7 @@ async fn test_new() { ); } -#[recorded(live)] +#[recorded::test(live)] async fn test_new_with_error() { common::setup(); let eventhub = env::var("EVENTHUB_NAME").unwrap(); @@ -51,7 +51,7 @@ async fn test_new_with_error() { info!("Error: {:?}", result); } -#[recorded(live)] +#[recorded::test(live)] async fn test_open() { common::setup(); let host = env::var("EVENTHUBS_HOST").unwrap(); @@ -67,7 +67,7 @@ async fn test_open() { ); client.open().await.unwrap(); } -#[recorded(live)] +#[recorded::test(live)] async fn test_close() { common::setup(); let host = env::var("EVENTHUBS_HOST").unwrap(); @@ -85,7 +85,7 @@ async fn test_close() { client.close().await.unwrap(); } -#[recorded(live)] +#[recorded::test(live)] async fn test_get_properties() { common::setup(); let host = env::var("EVENTHUBS_HOST").unwrap(); @@ -108,7 +108,7 @@ async fn test_get_properties() { assert_eq!(properties.name, eventhub); } -#[recorded(live)] +#[recorded::test(live)] async fn test_get_partition_properties() { common::setup(); let host = env::var("EVENTHUBS_HOST").unwrap(); @@ -138,7 +138,7 @@ async fn test_get_partition_properties() { } } -#[recorded(live)] +#[recorded::test(live)] fn test_create_eventdata() { common::setup(); let data = b"hello world"; @@ -162,7 +162,7 @@ fn test_create_eventdata() { .build(); } -#[recorded(live)] +#[recorded::test(live)] async fn test_create_batch() { common::setup(); let host = env::var("EVENTHUBS_HOST").unwrap(); @@ -186,7 +186,7 @@ async fn test_create_batch() { } } -#[recorded(live)] +#[recorded::test(live)] async fn test_create_and_send_batch() { common::setup(); let host = env::var("EVENTHUBS_HOST").unwrap(); @@ -238,7 +238,7 @@ async fn test_create_and_send_batch() { } } -#[recorded(live)] +#[recorded::test(live)] async fn test_add_amqp_messages_to_batch() -> Result<(), Box> { common::setup(); let host = env::var("EVENTHUBS_HOST").unwrap(); @@ -315,7 +315,7 @@ async fn test_add_amqp_messages_to_batch() -> Result<(), Box Date: Fri, 22 Nov 2024 16:49:47 -0800 Subject: [PATCH 7/7] Fix build breaks We may need to set env vars for our Windows agents to find OpenSSL, which does appear to be installed. See Azure/azure-sdk-for-rust#1746. For now, I'm removing `--all-features` from this PR since that issue is already tracking adding them separately. --- eng/scripts/Test-Packages.ps1 | 4 ++-- sdk/core/azure_core_test/src/lib.rs | 4 ++++ sdk/core/azure_core_test_macros/src/lib.rs | 19 +------------------ 3 files changed, 7 insertions(+), 20 deletions(-) diff --git a/eng/scripts/Test-Packages.ps1 b/eng/scripts/Test-Packages.ps1 index 5816d2eaee..d90a61e48e 100755 --- a/eng/scripts/Test-Packages.ps1 +++ b/eng/scripts/Test-Packages.ps1 @@ -63,10 +63,10 @@ foreach ($package in $packagesToTest) { $targets += "--benches" } - Invoke-LoggedCommand "cargo +$Toolchain test --all-features $($targets -join ' ') --no-fail-fast" + Invoke-LoggedCommand "cargo +$Toolchain test $($targets -join ' ') --no-fail-fast" Write-Host "`n`n" - Invoke-LoggedCommand "cargo +$Toolchain test --all-features --doc --no-fail-fast" + Invoke-LoggedCommand "cargo +$Toolchain test --doc --no-fail-fast" Write-Host "`n`n" } finally { diff --git a/sdk/core/azure_core_test/src/lib.rs b/sdk/core/azure_core_test/src/lib.rs index 9057c215ae..fba572f048 100644 --- a/sdk/core/azure_core_test/src/lib.rs +++ b/sdk/core/azure_core_test/src/lib.rs @@ -10,6 +10,10 @@ pub mod recorded { 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, diff --git a/sdk/core/azure_core_test_macros/src/lib.rs b/sdk/core/azure_core_test_macros/src/lib.rs index 2291e0b0f0..1c06c87c1b 100644 --- a/sdk/core/azure_core_test_macros/src/lib.rs +++ b/sdk/core/azure_core_test_macros/src/lib.rs @@ -5,28 +5,11 @@ mod test; -#[cfg(doc)] -use azure_core_test::TestContext; use proc_macro::TokenStream; /// Attribute client library tests to play back recordings, record sessions, or execute tests without recording. /// -/// # Examples -/// -/// Recorded tests can be synchronous or asynchronous (recommended), can return a `Result`, -/// and must take a single [`TestContext`] parameter used to set up to record or play back the HTTP client. -/// -/// ``` -/// use azure_core_test::{recorded, TestContext}; -/// use azure_core::Result; -/// -/// #[recorded::test(live)] -/// async fn get_secret(ctx: TestContext) -> Result<()> { -/// todo!() -/// } -/// ``` -/// -/// To execute tests only when `AZURE_TEST_MODE` is "live", you can optionally pass `live` to the `#[recorded::test(live)]` attribute. +/// 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())