#![doc = include_str!("../README.md")]
pub mod credentials;
#[cfg(doctest)]
mod docs;
pub mod http;
pub mod perf;
pub mod proxy;
pub mod recorded;
mod recording;
#[cfg(doctest)]
mod root_readme;
pub mod stream;
pub mod tracing;
use azure_core::Error;
pub use azure_core::{error::ErrorKind, test::TestMode};
pub use proxy::{matchers::*, sanitizers::*};
pub use recording::*;
use std::path::{Path, PathBuf};
const ASSETS_FILE: &str = "assets.json";
#[derive(Debug)]
pub struct TestContext {
repo_dir: &'static Path,
crate_dir: &'static Path,
service_dir: &'static str,
module_name: &'static str,
name: &'static str,
recording: Option<Recording>,
}
impl TestContext {
pub(crate) fn new(
crate_dir: &'static str,
module_dir: &'static str,
name: &'static str,
) -> azure_core::Result<Self> {
let service_dir = parent_of(crate_dir, "sdk").ok_or_else(|| {
Error::with_message(ErrorKind::Other, "not under 'sdk' folder in repo")
})?;
let test_module = Path::new(module_dir)
.file_stem()
.ok_or_else(|| Error::with_message(ErrorKind::Other, "invalid test module"))?
.to_str()
.ok_or_else(|| Error::with_message(ErrorKind::Other, "invalid test module"))?;
Ok(Self {
repo_dir: find_ancestor_of(crate_dir, ".git")?,
crate_dir: Path::new(crate_dir),
service_dir,
module_name: test_module,
name,
recording: None,
})
}
pub fn crate_dir(&self) -> &'static Path {
self.crate_dir
}
pub fn recording(&self) -> &Recording {
self.recording
.as_ref()
.expect("not recording or playback started")
}
pub fn repo_dir(&self) -> &'static Path {
self.repo_dir
}
pub fn service_dir(&self) -> &'static str {
self.service_dir
}
pub fn test_data_dir(&self) -> PathBuf {
self.crate_dir
.join("tests/data")
.strip_prefix(self.repo_dir)
.expect("not rooted within repo")
.to_path_buf()
}
pub fn module_name(&self) -> &'static str {
self.module_name
}
pub fn name(&self) -> &'static str {
self.name
}
pub(crate) fn test_recording_assets_file(&self, mode: TestMode) -> Option<String> {
if mode == TestMode::Live {
return None;
}
let path = match find_ancestor_file(self.crate_dir, ASSETS_FILE) {
Ok(path) => path,
Err(_) if mode == TestMode::Record => {
return Path::new("sdk")
.join(self.service_dir)
.join(ASSETS_FILE)
.as_path()
.to_str()
.map(String::from);
}
Err(err) => panic!("{err}"),
};
path.strip_prefix(self.repo_dir)
.expect("not rooted within repo")
.to_str()
.map(String::from)
}
pub(crate) fn test_recording_file(&self) -> String {
let path = self
.test_data_dir()
.join(self.module_name)
.join(self.name)
.as_path()
.with_extension("json");
path.to_str()
.map(String::from)
.unwrap_or_else(|| panic!("{path:?} is invalid"))
}
}
pub fn load_dotenv_file(cargo_dir: impl AsRef<Path>) -> azure_core::Result<()> {
if let Ok(path) = find_ancestor_file(cargo_dir, ".env") {
::tracing::debug!("loading environment variables from {}", path.display());
use azure_core::error::ResultExt as _;
dotenvy::from_filename(&path).with_context_fn(azure_core::error::ErrorKind::Io, || {
format!(
"failed to load environment variables from {}",
path.display()
)
})?;
}
Ok(())
}
fn parent_of<'a>(dir: &'a str, name: &'static str) -> Option<&'a str> {
let mut child = None;
let dir = Path::new(dir);
let components = dir.components().rev();
for dir in components {
if dir.as_os_str() == name {
return child;
}
child = dir.as_os_str().to_str();
}
None
}
fn find_ancestor_file(dir: impl AsRef<Path>, name: &str) -> azure_core::Result<PathBuf> {
for dir in dir.as_ref().ancestors() {
let path = dir.join(name);
if path.exists() {
return Ok(path);
}
let path = dir.join(".git");
if path.exists() {
return Err(azure_core::Error::with_message(
ErrorKind::Io,
format!("{name} not found under repo {}", dir.display()),
));
}
}
Err(azure_core::Error::new::<std::io::Error>(
azure_core::error::ErrorKind::Io,
std::io::ErrorKind::NotFound.into(),
))
}
fn find_ancestor_of(dir: &'static str, name: &'static str) -> azure_core::Result<&'static Path> {
let dir = Path::new(dir);
for dir in dir.ancestors() {
let path = dir.join(name);
if path.exists() {
return Ok(dir);
}
}
Err(azure_core::Error::new::<std::io::Error>(
azure_core::error::ErrorKind::Io,
std::io::ErrorKind::NotFound.into(),
))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_context_new() {
let ctx =
TestContext::new(env!("CARGO_MANIFEST_DIR"), file!(), "test_context_new").unwrap();
assert!(ctx.recording.is_none());
assert!(ctx
.crate_dir()
.to_str()
.unwrap()
.replace("\\", "/")
.ends_with("sdk/core/azure_core_test"));
assert_eq!(ctx.module_name(), "lib");
assert_eq!(ctx.name(), "test_context_new");
assert_eq!(
ctx.test_recording_file().replace("\\", "/"),
"sdk/core/azure_core_test/tests/data/lib/test_context_new.json"
);
}
#[test]
fn test_parent_of() {
assert_eq!(
parent_of("~/src/azure-sdk-for-rust/sdk/core", "sdk"),
Some("core"),
);
assert!(parent_of("~/src/azure-sdk-for-rust/sdk/", "sdk").is_none());
assert!(parent_of("~/src/azure-sdk-for-rust/sdk/core", "should_not_exist").is_none());
}
}