Skip to content

Commit 445363a

Browse files
Added test proxy support (Azure#3217)
Fixes Azure#3099
1 parent e47a38f commit 445363a

File tree

11 files changed

+124
-66
lines changed

11 files changed

+124
-66
lines changed

sdk/core/azure_core/src/test.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ impl TestMode {
3131
pub fn current() -> typespec::Result<Self> {
3232
std::env::var("AZURE_TEST_MODE").map_or_else(|_| Ok(TestMode::default()), |v| v.parse())
3333
}
34+
35+
/// Gets the `TestMode` from the `AZURE_TEST_MODE` environment variable or returns `None` if undefined.
36+
pub fn current_opt() -> typespec::Result<Option<Self>> {
37+
std::env::var("AZURE_TEST_MODE").map_or_else(|_| Ok(None), |v| v.parse().map(Some))
38+
}
3439
}
3540

3641
impl fmt::Debug for TestMode {

sdk/core/azure_core_test/src/perf/README.md

Lines changed: 11 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@ Each performance test has the following standardized parameters:
2525

2626
Each test has its own set of parameters which are specific to the test.
2727

28+
***NOTE: Performance Tests are "recorded" tests***
29+
30+
This means that they follow the same rules as tests annotated with the `#[recorded::test]` attribute. There is one difference between perf tests and tests with the `recorded::test` attribute: perf tests default to `live` mode, and normal `recorded::test` tests default to `playback` mode.
31+
32+
To configure the tests for record mode tests, set `AZURE_TEST_MODE` to `record` before running your performance tests, and to run your tests using the test proxy, set `AZURE_TEST_MODE` to `playback`
33+
2834
## Test authoring
2935

3036
Performance tests have three phases:
@@ -43,64 +49,17 @@ A perf test has a name (`get_secret`, `list_blobs`, `upload_blob`, etc), a short
4349

4450
Each perf test also has a set of command line options that are specific to the individual test, these are defined by a `PerfTestOptions` structure. It contains fields like help text for the option, activators
4551

46-
Here is an example of test metadata for a performance test:
47-
48-
```rust
49-
PerfTestMetadata {
50-
name: "get_secret",
51-
description: "Get a secret from Key Vault",
52-
options: vec![PerfTestOption {
53-
name: "vault_url",
54-
display_message: "The URL of the Key Vault to use in the test",
55-
mandatory: true,
56-
short_activator: 'u',
57-
long_activator: "vault-url",
58-
expected_args_len: 1,
59-
..Default::default()
60-
}],
61-
create_test: Self::create_new_test,
62-
}
63-
```
52+
An example of perf test metadata [can be found here](https://github.com/Azure/azure-sdk-for-rust/blob/e47a38f93e7ac2797754c103da7fe8b177e46365/sdk/keyvault/azure_security_keyvault_keys/perf/create_key.rs#L26C1-L41C1)
6453

65-
This defines a test named `get_secret` with a single required "vault_url" option.
66-
67-
For this test, the `create_new_test` function looks like:
68-
69-
```rust
70-
fn create_new_test(runner: PerfRunner) -> CreatePerfTestReturn {
71-
async move {
72-
let vault_url_ref: Option<&String> = runner.try_get_test_arg("vault_url")?;
73-
let vault_url = vault_url_ref
74-
.expect("vault_url argument is mandatory")
75-
.clone();
76-
Ok(Box::new(GetSecrets {
77-
vault_url,
78-
random_key_name: OnceLock::new(),
79-
client: OnceLock::new(),
80-
}) as Box<dyn PerfTest>)
81-
}
82-
.boxed()
83-
}
84-
```
54+
This defines a test named `create_key` with a single required "vault_url" option.
55+
56+
An example of the `create_new_test` function [can be found here](https://github.com/Azure/azure-sdk-for-rust/blob/e47a38f93e7ac2797754c103da7fe8b177e46365/sdk/keyvault/azure_security_keyvault_keys/perf/get_key.rs#L42-L58)
8557

8658
### Test invocation
8759

8860
The final piece of code which is necessary to run the performance tests is logic to hook up the tests with a test runner.
8961

90-
```rust
91-
#[tokio::main]
92-
async fn main() -> azure_core::Result<()> {
93-
let runner = PerfRunner::new(
94-
env!("CARGO_MANIFEST_DIR"),
95-
file!(),
96-
vec![GetSecrets::test_metadata()],
97-
)?;
98-
99-
runner.run().await?;
100-
101-
Ok(())
102-
}
103-
```
62+
An example of this, from the Keyvault Keys performance tests [can be found here](https://github.com/Azure/azure-sdk-for-rust/blob/e47a38f93e7ac2797754c103da7fe8b177e46365/sdk/keyvault/azure_security_keyvault_keys/perf/perf_tests.rs#L24-L35)
10463

10564
This declares a perf test runner with a set of defined test metadata and runs the performance test. If your performance test suite has more than one performance test, then it should be added to the final parameter to the `PerfRunner::new()` function.
10665

sdk/core/azure_core_test/src/perf/mod.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT License.
33

4-
#![doc = include_str!("README.md")]
54
#![cfg(not(target_arch = "wasm32"))]
65

76
use crate::TestContext;
@@ -298,7 +297,7 @@ impl PerfRunner {
298297
let test_instance = (test.create_test)(self.clone()).await?;
299298
let test_instance: Arc<dyn PerfTest> = Arc::from(test_instance);
300299

301-
let test_mode = crate::TestMode::current()?;
300+
let test_mode = crate::TestMode::current_opt()?.unwrap_or(crate::TestMode::Live);
302301

303302
let context = Arc::new(
304303
crate::recorded::start(
@@ -479,10 +478,17 @@ impl PerfRunner {
479478
.value_parser(clap::value_parser!(u32))
480479
.global(false),
481480
)
482-
.arg(clap::arg!(--sync).global(true).required(false))
481+
.arg(clap::arg!(--sync "Run synchronous tests (ignored)")
482+
.global(true)
483+
.required(false))
484+
.arg(clap::arg!(--"test-proxy" <URL> "The URL of the test proxy, ignored.")
485+
.global(true)
486+
.value_parser(clap::value_parser!(String))
487+
.required(false))
483488
.arg(
484489
clap::arg!(--parallel <COUNT> "The number of concurrent tasks to use when running each test")
485490
.required(false)
491+
.short('p')
486492
.default_value("1")
487493
.value_parser(clap::value_parser!(u32))
488494
.global(true),
@@ -491,6 +497,7 @@ impl PerfRunner {
491497
.arg(
492498
clap::arg!(--duration <SECONDS> "The duration of each test in seconds")
493499
.required(false)
500+
.short('d')
494501
.default_value("30")
495502
.value_parser(clap::value_parser!(i64))
496503
.global(true),

sdk/core/azure_core_test/src/proxy/policy.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
use crate::{
55
proxy::{RecordingId, RECORDING_MODE, RECORDING_UPSTREAM_BASE_URI},
6-
Skip,
6+
RemoveRecording, Skip,
77
};
88
use async_trait::async_trait;
99
use azure_core::{
@@ -118,13 +118,18 @@ impl Policy for RecordingPolicy {
118118
#[derive(Debug, Default)]
119119
pub struct RecordingOptions {
120120
pub skip: Option<Skip>,
121+
pub remove_recording: Option<bool>,
121122
}
122123

123124
impl AsHeaders for RecordingOptions {
124125
type Error = Infallible;
125126
type Iter = std::vec::IntoIter<(HeaderName, HeaderValue)>;
126127

127128
fn as_headers(&self) -> Result<Self::Iter, Self::Error> {
128-
self.skip.as_headers()
129+
let mut headers: Vec<_> = self.skip.as_headers()?.collect();
130+
if let Some(remove) = self.remove_recording {
131+
headers.extend(RemoveRecording(remove).as_headers()?);
132+
}
133+
Ok(headers.into_iter())
129134
}
130135
}

sdk/core/azure_core_test/src/recording.rs

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ impl Recording {
116116
/// let recording = ctx.recording();
117117
///
118118
/// let mut options = MyClientOptions::default();
119-
/// ctx.instrument(&mut options.client_options);
119+
/// recording.instrument(&mut options.client_options);
120120
///
121121
/// let client = MyClient::new("https://azure.net", Some(options));
122122
/// client.invoke().await
@@ -157,6 +157,48 @@ impl Recording {
157157
options.per_try_policies.push(recording_policy);
158158
}
159159

160+
/// Update a recording with settings appropriate for a performance test.
161+
///
162+
/// Instruments the [`ClientOptions`] to support recording and playing back of session records.
163+
///
164+
/// # Examples
165+
///
166+
/// ```no_run
167+
/// use azure_core_test::{recorded, perf::PerfTest, TestContext};
168+
/// # use std::sync::{OnceLock, Arc};
169+
/// # struct MyServiceClient;
170+
/// # impl MyServiceClient {
171+
/// # fn new(endpoint: impl AsRef<str>, options: Option<MyServiceClientOptions>) -> Self { todo!() }
172+
/// # async fn invoke(&self) -> azure_core::Result<()> { todo!() }
173+
/// # }
174+
/// # #[derive(Default)]
175+
/// # struct MyServiceClientOptions { client_options: azure_core::http::ClientOptions };
176+
/// # #[derive(Default)]
177+
/// # struct MyPerfTest { client: OnceLock<MyServiceClient> };
178+
/// #[async_trait::async_trait]
179+
/// impl PerfTest for MyPerfTest {
180+
/// async fn setup(&self, ctx: Arc<TestContext>) -> azure_core::Result<()> {
181+
/// let recording = ctx.recording();
182+
///
183+
/// let mut options = MyServiceClientOptions::default();
184+
/// recording.instrument_perf(&mut options.client_options)?;
185+
///
186+
/// let client = MyServiceClient::new("https://azure.net", Some(options));
187+
/// client.invoke().await
188+
/// }
189+
/// async fn run(&self, ctx: Arc<TestContext>) -> azure_core::Result<()>{ todo!()}
190+
/// async fn cleanup(&self, ctx: Arc<TestContext>) -> azure_core::Result<()>{ todo!()}
191+
/// }
192+
/// ```
193+
///
194+
/// Note that this function is a no-op for live tests - it only affects recorded tests
195+
/// in playback mode.
196+
///
197+
pub fn instrument_perf(&self, options: &mut ClientOptions) -> azure_core::Result<()> {
198+
self.instrument(options);
199+
self.remove_recording(false)
200+
}
201+
160202
/// Get random data from the OS or recording.
161203
///
162204
/// This will always be the OS cryptographically secure pseudo-random number generator (CSPRNG) when running live.
@@ -305,6 +347,11 @@ impl Recording {
305347
Ok(SkipGuard(self))
306348
}
307349

350+
pub(crate) fn remove_recording(&self, remove: bool) -> azure_core::Result<()> {
351+
self.set_remove_recording(Some(remove))?;
352+
Ok(())
353+
}
354+
308355
/// Gets the current [`TestMode`].
309356
pub fn test_mode(&self) -> TestMode {
310357
self.test_mode
@@ -461,6 +508,20 @@ impl Recording {
461508
Ok(())
462509
}
463510

511+
fn set_remove_recording(&self, remove: Option<bool>) -> azure_core::Result<()> {
512+
let Some(policy) = self.recording_policy.get() else {
513+
return Ok(());
514+
};
515+
516+
let mut options = policy
517+
.options
518+
.write()
519+
.map_err(|err| azure_core::Error::with_message(ErrorKind::Other, err.to_string()))?;
520+
options.remove_recording = remove;
521+
522+
Ok(())
523+
}
524+
464525
/// Starts recording or playback.
465526
///
466527
/// If playing back a recording, environment variable that were recorded will be reloaded.
@@ -587,6 +648,24 @@ impl Drop for SkipGuard<'_> {
587648
}
588649
}
589650

651+
/// Whether to remove records during recording playback.
652+
///
653+
/// This option is used for test recordings, if true, the recording will be removed from the test-proxy when retrieved,
654+
/// otherwise it will be kept. The default is true.
655+
///
656+
#[derive(Debug)]
657+
pub struct RemoveRecording(pub bool);
658+
659+
impl Header for RemoveRecording {
660+
fn name(&self) -> HeaderName {
661+
HeaderName::from_static("x-recording-remove")
662+
}
663+
664+
fn value(&self) -> HeaderValue {
665+
HeaderValue::from_static(if self.0 { "true" } else { "false" })
666+
}
667+
}
668+
590669
/// Options for getting variables from a [`Recording`].
591670
#[derive(Clone, Debug)]
592671
pub struct VarOptions {

sdk/core/typespec_client_core/src/http/pipeline.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,11 @@ impl Pipeline {
7676
let retry_policy = options.retry.to_policy(pipeline_options.retry_headers);
7777
pipeline.push(retry_policy);
7878

79-
pipeline.push(Arc::new(LoggingPolicy::new(options.logging)));
80-
8179
pipeline.extend_from_slice(&per_try_policies);
8280
pipeline.extend_from_slice(&options.per_try_policies);
8381

82+
pipeline.push(Arc::new(LoggingPolicy::new(options.logging)));
83+
8484
let transport: Arc<dyn Policy> =
8585
Arc::new(TransportPolicy::new(options.transport.unwrap_or_default()));
8686
pipeline.push(transport);

sdk/keyvault/assets.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22
"AssetsRepo": "Azure/azure-sdk-assets",
33
"AssetsRepoPrefixPath": "rust",
44
"TagPrefix": "rust/keyvault",
5-
"Tag": "rust/keyvault_3dde96c6e5"
5+
"Tag": "rust/keyvault_568367d8ae"
66
}

sdk/keyvault/azure_security_keyvault_keys/perf/create_key.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ impl PerfTest for CreateKey {
7474
let credential = recording.credential();
7575

7676
let mut client_options = KeyClientOptions::default();
77-
recording.instrument(&mut client_options.client_options);
77+
recording.instrument_perf(&mut client_options.client_options)?;
7878

7979
let vault_url = self
8080
.vault_url

sdk/keyvault/azure_security_keyvault_keys/perf/get_key.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ impl PerfTest for GetKey {
7474
let credential = recording.credential();
7575

7676
let mut client_options = KeyClientOptions::default();
77-
recording.instrument(&mut client_options.client_options);
77+
recording.instrument_perf(&mut client_options.client_options)?;
7878

7979
let vault_url = self
8080
.vault_url

sdk/keyvault/azure_security_keyvault_secrets/perf/get_secret.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
//!
1111
//! To run the test, use the following command line arguments:
1212
//!
13-
//! cargo test --package azure_security_keyvault_secrets --test performance_tests -- --duration 10 --parallel 20 get_secret -u https://<my_vault>.vault.azure.net/
13+
//! cargo test --package azure_security_keyvault_secrets --test perf -- --duration 10 --parallel 20 get_secret -u https://<my_vault>.vault.azure.net/
1414
//!
1515
1616
use std::sync::{Arc, OnceLock};
@@ -79,7 +79,7 @@ impl PerfTest for GetSecrets {
7979
let credential = recording.credential();
8080

8181
let mut client_options = SecretClientOptions::default();
82-
recording.instrument(&mut client_options.client_options);
82+
recording.instrument_perf(&mut client_options.client_options)?;
8383

8484
let vault_url = self
8585
.vault_url

0 commit comments

Comments
 (0)