mirror of
https://github.com/kanidm/kanidm.git
synced 2025-02-23 12:37:00 +01:00
Further test improvements (#1166)
This commit is contained in:
parent
df4043cf10
commit
692c0a3978
|
@ -97,3 +97,84 @@ pub(crate) fn qs_test(_args: &TokenStream, item: TokenStream, with_init: bool) -
|
|||
|
||||
result.into()
|
||||
}
|
||||
|
||||
pub(crate) fn idm_test(_args: &TokenStream, item: TokenStream) -> TokenStream {
|
||||
let input: syn::ItemFn = match syn::parse(item.clone()) {
|
||||
Ok(it) => it,
|
||||
Err(e) => return token_stream_with_error(item, e),
|
||||
};
|
||||
|
||||
if let Some(attr) = input.attrs.iter().find(|attr| attr.path.is_ident("test")) {
|
||||
let msg = "second test attribute is supplied";
|
||||
return token_stream_with_error(item, syn::Error::new_spanned(&attr, msg));
|
||||
};
|
||||
|
||||
if input.sig.asyncness.is_none() {
|
||||
let msg = "the `async` keyword is missing from the function declaration";
|
||||
return token_stream_with_error(item, syn::Error::new_spanned(input.sig.fn_token, msg));
|
||||
}
|
||||
|
||||
// If type mismatch occurs, the current rustc points to the last statement.
|
||||
let (last_stmt_start_span, _last_stmt_end_span) = {
|
||||
let mut last_stmt = input
|
||||
.block
|
||||
.stmts
|
||||
.last()
|
||||
.map(ToTokens::into_token_stream)
|
||||
.unwrap_or_default()
|
||||
.into_iter();
|
||||
// `Span` on stable Rust has a limitation that only points to the first
|
||||
// token, not the whole tokens. We can work around this limitation by
|
||||
// using the first/last span of the tokens like
|
||||
// `syn::Error::new_spanned` does.
|
||||
let start = last_stmt.next().map_or_else(Span::call_site, |t| t.span());
|
||||
let end = last_stmt.last().map_or(start, |t| t.span());
|
||||
(start, end)
|
||||
};
|
||||
|
||||
let rt = quote_spanned! {last_stmt_start_span=>
|
||||
tokio::runtime::Builder::new_current_thread()
|
||||
};
|
||||
|
||||
let header = quote! {
|
||||
#[::core::prelude::v1::test]
|
||||
};
|
||||
|
||||
let test_fn = &input.sig.ident;
|
||||
let test_driver = Ident::new(&format!("idm_{}", test_fn), input.sig.span());
|
||||
|
||||
// Effectively we are just injecting a real test function around this which we will
|
||||
// call.
|
||||
|
||||
let result = quote! {
|
||||
#input
|
||||
|
||||
#header
|
||||
fn #test_driver() {
|
||||
let body = async {
|
||||
let (test_server, mut idms_delayed) = crate::testkit::setup_idm_test().await;
|
||||
|
||||
#test_fn(&test_server, &mut idms_delayed).await;
|
||||
|
||||
// Any needed teardown?
|
||||
// Make sure there are no errors.
|
||||
let mut idm_read_txn = test_server.proxy_read().await;
|
||||
let verifications = idm_read_txn.qs_read.verify();
|
||||
trace!("Verification result: {:?}", verifications);
|
||||
assert!(verifications.len() == 0);
|
||||
|
||||
idms_delayed.check_is_empty_or_panic();
|
||||
};
|
||||
#[allow(clippy::expect_used, clippy::diverging_sub_expression)]
|
||||
{
|
||||
return #rt
|
||||
.enable_all()
|
||||
.build()
|
||||
.expect("Failed building the Runtime")
|
||||
.block_on(body);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
result.into()
|
||||
}
|
||||
|
|
|
@ -26,3 +26,8 @@ pub fn qs_test(args: TokenStream, item: TokenStream) -> TokenStream {
|
|||
pub fn qs_test_no_init(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
entry::qs_test(&args, item, false)
|
||||
}
|
||||
|
||||
#[proc_macro_attribute]
|
||||
pub fn idm_test(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
entry::idm_test(&args, item)
|
||||
}
|
||||
|
|
|
@ -1,15 +1,11 @@
|
|||
use std::time::{Duration, Instant};
|
||||
|
||||
use async_std::task;
|
||||
use criterion::{
|
||||
criterion_group, criterion_main, BenchmarkId, Criterion, SamplingMode, Throughput,
|
||||
};
|
||||
use kanidmd_lib;
|
||||
use kanidmd_lib::entry::{Entry, EntryInit, EntryNew};
|
||||
use kanidmd_lib::entry_init;
|
||||
use kanidmd_lib::idm::server::{IdmServer, IdmServerDelayed};
|
||||
use kanidmd_lib::macros::run_idm_test_no_logging;
|
||||
use kanidmd_lib::server::QueryServer;
|
||||
use kanidmd_lib::utils::duration_from_epoch_now;
|
||||
use kanidmd_lib::value::Value;
|
||||
|
||||
|
@ -28,14 +24,19 @@ pub fn scaling_user_create_single(c: &mut Criterion) {
|
|||
println!("iters, size -> {:?}, {:?}", iters, size);
|
||||
|
||||
for _i in 0..iters {
|
||||
run_idm_test_no_logging(
|
||||
|_qs: &QueryServer, idms: &IdmServer, _idms_delayed: &IdmServerDelayed| {
|
||||
let ct = duration_from_epoch_now();
|
||||
let mut rt = tokio::runtime::Builder::new_current_thread();
|
||||
elapsed = rt
|
||||
.enable_all()
|
||||
.build()
|
||||
.expect("Failed building the Runtime")
|
||||
.block_on(async {
|
||||
let (idms, _idms_delayed) =
|
||||
kanidmd_lib::testkit::setup_idm_test().await;
|
||||
|
||||
let ct = duration_from_epoch_now();
|
||||
let start = Instant::now();
|
||||
for counter in 0..size {
|
||||
let idms_prox_write = task::block_on(idms.proxy_write_async(ct));
|
||||
|
||||
let mut idms_prox_write = idms.proxy_write(ct).await;
|
||||
let name = format!("testperson_{}", counter);
|
||||
let e1 = entry_init!(
|
||||
("class", Value::new_class("object")),
|
||||
|
@ -51,9 +52,8 @@ pub fn scaling_user_create_single(c: &mut Criterion) {
|
|||
|
||||
idms_prox_write.commit().expect("Must not fail");
|
||||
}
|
||||
elapsed = elapsed.checked_add(start.elapsed()).unwrap();
|
||||
},
|
||||
);
|
||||
elapsed.checked_add(start.elapsed()).unwrap()
|
||||
});
|
||||
}
|
||||
elapsed
|
||||
});
|
||||
|
@ -92,20 +92,25 @@ pub fn scaling_user_create_batched(c: &mut Criterion) {
|
|||
.collect();
|
||||
|
||||
for _i in 0..iters {
|
||||
kanidmd_lib::macros::run_idm_test_no_logging(
|
||||
|_qs: &QueryServer, idms: &IdmServer, _idms_delayed: &IdmServerDelayed| {
|
||||
let ct = duration_from_epoch_now();
|
||||
let mut rt = tokio::runtime::Builder::new_current_thread();
|
||||
elapsed = rt
|
||||
.enable_all()
|
||||
.build()
|
||||
.expect("Failed building the Runtime")
|
||||
.block_on(async {
|
||||
let (idms, _idms_delayed) =
|
||||
kanidmd_lib::testkit::setup_idm_test().await;
|
||||
|
||||
let ct = duration_from_epoch_now();
|
||||
let start = Instant::now();
|
||||
|
||||
let idms_prox_write = task::block_on(idms.proxy_write_async(ct));
|
||||
let mut idms_prox_write = idms.proxy_write(ct).await;
|
||||
let cr = idms_prox_write.qs_write.internal_create(data.clone());
|
||||
assert!(cr.is_ok());
|
||||
|
||||
idms_prox_write.commit().expect("Must not fail");
|
||||
elapsed = elapsed.checked_add(start.elapsed()).unwrap();
|
||||
},
|
||||
);
|
||||
elapsed.checked_add(start.elapsed()).unwrap()
|
||||
});
|
||||
}
|
||||
elapsed
|
||||
});
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,4 +1,3 @@
|
|||
use core::task::{Context, Poll};
|
||||
use std::convert::TryFrom;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
|
@ -11,8 +10,6 @@ use concread::cowcell::{CowCellReadTxn, CowCellWriteTxn};
|
|||
use concread::hashmap::HashMap;
|
||||
use concread::CowCell;
|
||||
use fernet::Fernet;
|
||||
// #[cfg(any(test,bench))]
|
||||
use futures::task as futures_task;
|
||||
use hashbrown::HashSet;
|
||||
use kanidm_proto::v1::{
|
||||
ApiToken, BackupCodesView, CredentialStatus, PasswordFeedback, RadiusAuthToken, UatPurpose,
|
||||
|
@ -343,8 +340,12 @@ impl IdmServer {
|
|||
}
|
||||
|
||||
impl IdmServerDelayed {
|
||||
// #[cfg(any(test,bench))]
|
||||
// I think we can just make this async in the future?
|
||||
#[cfg(test)]
|
||||
pub(crate) fn check_is_empty_or_panic(&mut self) {
|
||||
use core::task::{Context, Poll};
|
||||
use futures::task as futures_task;
|
||||
|
||||
let waker = futures_task::noop_waker();
|
||||
let mut cx = Context::from_waker(&waker);
|
||||
match self.async_rx.poll_recv(&mut cx) {
|
||||
|
@ -365,6 +366,9 @@ impl IdmServerDelayed {
|
|||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn try_recv(&mut self) -> Result<DelayedAction, OperationError> {
|
||||
use core::task::{Context, Poll};
|
||||
use futures::task as futures_task;
|
||||
|
||||
let waker = futures_task::noop_waker();
|
||||
let mut cx = Context::from_waker(&waker);
|
||||
match self.async_rx.poll_recv(&mut cx) {
|
||||
|
|
|
@ -375,98 +375,93 @@ mod tests {
|
|||
use kanidm_proto::v1::ApiToken;
|
||||
|
||||
use super::{DestroyApiTokenEvent, GenerateApiTokenEvent};
|
||||
// use crate::prelude::*;
|
||||
use crate::event::CreateEvent;
|
||||
use crate::idm::server::IdmServerTransaction;
|
||||
|
||||
use async_std::task;
|
||||
use crate::prelude::*;
|
||||
|
||||
const TEST_CURRENT_TIME: u64 = 6000;
|
||||
|
||||
#[test]
|
||||
fn test_idm_service_account_api_token() {
|
||||
run_idm_test!(|_qs: &QueryServer,
|
||||
idms: &IdmServer,
|
||||
_idms_delayed: &mut IdmServerDelayed| {
|
||||
let ct = Duration::from_secs(TEST_CURRENT_TIME);
|
||||
let past_grc = Duration::from_secs(TEST_CURRENT_TIME + 1) + GRACE_WINDOW;
|
||||
let exp = Duration::from_secs(TEST_CURRENT_TIME + 6000);
|
||||
let post_exp = Duration::from_secs(TEST_CURRENT_TIME + 6010);
|
||||
let mut idms_prox_write = task::block_on(idms.proxy_write(ct));
|
||||
#[idm_test]
|
||||
async fn test_idm_service_account_api_token(
|
||||
idms: &IdmServer,
|
||||
_idms_delayed: &mut IdmServerDelayed,
|
||||
) {
|
||||
let ct = Duration::from_secs(TEST_CURRENT_TIME);
|
||||
let past_grc = Duration::from_secs(TEST_CURRENT_TIME + 1) + GRACE_WINDOW;
|
||||
let exp = Duration::from_secs(TEST_CURRENT_TIME + 6000);
|
||||
let post_exp = Duration::from_secs(TEST_CURRENT_TIME + 6010);
|
||||
let mut idms_prox_write = idms.proxy_write(ct).await;
|
||||
|
||||
let testaccount_uuid = Uuid::new_v4();
|
||||
let testaccount_uuid = Uuid::new_v4();
|
||||
|
||||
let e1 = entry_init!(
|
||||
("class", Value::new_class("object")),
|
||||
("class", Value::new_class("account")),
|
||||
("class", Value::new_class("service_account")),
|
||||
("name", Value::new_iname("test_account_only")),
|
||||
("uuid", Value::new_uuid(testaccount_uuid)),
|
||||
("description", Value::new_utf8s("testaccount")),
|
||||
("displayname", Value::new_utf8s("testaccount"))
|
||||
);
|
||||
let e1 = entry_init!(
|
||||
("class", Value::new_class("object")),
|
||||
("class", Value::new_class("account")),
|
||||
("class", Value::new_class("service_account")),
|
||||
("name", Value::new_iname("test_account_only")),
|
||||
("uuid", Value::new_uuid(testaccount_uuid)),
|
||||
("description", Value::new_utf8s("testaccount")),
|
||||
("displayname", Value::new_utf8s("testaccount"))
|
||||
);
|
||||
|
||||
let ce = CreateEvent::new_internal(vec![e1]);
|
||||
let cr = idms_prox_write.qs_write.create(&ce);
|
||||
assert!(cr.is_ok());
|
||||
let ce = CreateEvent::new_internal(vec![e1]);
|
||||
let cr = idms_prox_write.qs_write.create(&ce);
|
||||
assert!(cr.is_ok());
|
||||
|
||||
let gte = GenerateApiTokenEvent::new_internal(testaccount_uuid, "TestToken", Some(exp));
|
||||
let gte = GenerateApiTokenEvent::new_internal(testaccount_uuid, "TestToken", Some(exp));
|
||||
|
||||
let api_token = idms_prox_write
|
||||
.service_account_generate_api_token(>e, ct)
|
||||
.expect("failed to generate new api token");
|
||||
let api_token = idms_prox_write
|
||||
.service_account_generate_api_token(>e, ct)
|
||||
.expect("failed to generate new api token");
|
||||
|
||||
trace!(?api_token);
|
||||
trace!(?api_token);
|
||||
|
||||
// Deserialise it.
|
||||
let apitoken_unverified =
|
||||
JwsUnverified::from_str(&api_token).expect("Failed to parse apitoken");
|
||||
let apitoken_inner: Jws<ApiToken> = apitoken_unverified
|
||||
.validate_embeded()
|
||||
.expect("Embedded jwk not found");
|
||||
let apitoken_inner = apitoken_inner.into_inner();
|
||||
// Deserialise it.
|
||||
let apitoken_unverified =
|
||||
JwsUnverified::from_str(&api_token).expect("Failed to parse apitoken");
|
||||
let apitoken_inner: Jws<ApiToken> = apitoken_unverified
|
||||
.validate_embeded()
|
||||
.expect("Embedded jwk not found");
|
||||
let apitoken_inner = apitoken_inner.into_inner();
|
||||
|
||||
let ident = idms_prox_write
|
||||
.validate_and_parse_token_to_ident(Some(&api_token), ct)
|
||||
.expect("Unable to verify api token.");
|
||||
let ident = idms_prox_write
|
||||
.validate_and_parse_token_to_ident(Some(&api_token), ct)
|
||||
.expect("Unable to verify api token.");
|
||||
|
||||
assert!(ident.get_uuid() == Some(testaccount_uuid));
|
||||
assert!(ident.get_uuid() == Some(testaccount_uuid));
|
||||
|
||||
// Woohoo! Okay lets test the other edge cases.
|
||||
// Woohoo! Okay lets test the other edge cases.
|
||||
|
||||
// Check the expiry
|
||||
assert!(
|
||||
idms_prox_write
|
||||
.validate_and_parse_token_to_ident(Some(&api_token), post_exp)
|
||||
.expect_err("Should not succeed")
|
||||
== OperationError::SessionExpired
|
||||
);
|
||||
// Check the expiry
|
||||
assert!(
|
||||
idms_prox_write
|
||||
.validate_and_parse_token_to_ident(Some(&api_token), post_exp)
|
||||
.expect_err("Should not succeed")
|
||||
== OperationError::SessionExpired
|
||||
);
|
||||
|
||||
// Delete session
|
||||
let dte = DestroyApiTokenEvent::new_internal(
|
||||
apitoken_inner.account_id,
|
||||
apitoken_inner.token_id,
|
||||
);
|
||||
assert!(idms_prox_write
|
||||
.service_account_destroy_api_token(&dte)
|
||||
.is_ok());
|
||||
// Delete session
|
||||
let dte =
|
||||
DestroyApiTokenEvent::new_internal(apitoken_inner.account_id, apitoken_inner.token_id);
|
||||
assert!(idms_prox_write
|
||||
.service_account_destroy_api_token(&dte)
|
||||
.is_ok());
|
||||
|
||||
// Within gracewindow?
|
||||
// This is okay, because we are within the gracewindow.
|
||||
let ident = idms_prox_write
|
||||
.validate_and_parse_token_to_ident(Some(&api_token), ct)
|
||||
.expect("Unable to verify api token.");
|
||||
assert!(ident.get_uuid() == Some(testaccount_uuid));
|
||||
// Within gracewindow?
|
||||
// This is okay, because we are within the gracewindow.
|
||||
let ident = idms_prox_write
|
||||
.validate_and_parse_token_to_ident(Some(&api_token), ct)
|
||||
.expect("Unable to verify api token.");
|
||||
assert!(ident.get_uuid() == Some(testaccount_uuid));
|
||||
|
||||
// Past gracewindow?
|
||||
assert!(
|
||||
idms_prox_write
|
||||
.validate_and_parse_token_to_ident(Some(&api_token), past_grc)
|
||||
.expect_err("Should not succeed")
|
||||
== OperationError::SessionExpired
|
||||
);
|
||||
// Past gracewindow?
|
||||
assert!(
|
||||
idms_prox_write
|
||||
.validate_and_parse_token_to_ident(Some(&api_token), past_grc)
|
||||
.expect_err("Should not succeed")
|
||||
== OperationError::SessionExpired
|
||||
);
|
||||
|
||||
assert!(idms_prox_write.commit().is_ok());
|
||||
});
|
||||
assert!(idms_prox_write.commit().is_ok());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -52,8 +52,7 @@ mod repl;
|
|||
pub mod schema;
|
||||
pub mod server;
|
||||
pub mod status;
|
||||
#[cfg(test)]
|
||||
mod testkit;
|
||||
pub mod testkit;
|
||||
|
||||
/// A prelude of imports that should be imported by all other Kanidm modules to
|
||||
/// help make imports cleaner.
|
||||
|
@ -79,6 +78,7 @@ pub mod prelude {
|
|||
FilterInvalid, FC,
|
||||
};
|
||||
pub use crate::identity::{AccessScope, IdentType, Identity, IdentityId};
|
||||
pub use crate::idm::server::{IdmServer, IdmServerDelayed};
|
||||
pub use crate::modify::{m_pres, m_purge, m_remove, Modify, ModifyInvalid, ModifyList};
|
||||
pub use crate::server::{
|
||||
QueryServer, QueryServerReadTransaction, QueryServerTransaction,
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#[cfg(test)]
|
||||
macro_rules! setup_test {
|
||||
() => {{
|
||||
let _ = sketching::test_init();
|
||||
|
@ -71,6 +72,7 @@ macro_rules! entry_str_to_account {
|
|||
}};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
macro_rules! run_idm_test_inner {
|
||||
($test_fn:expr) => {{
|
||||
#[allow(unused_imports)]
|
||||
|
@ -112,18 +114,6 @@ macro_rules! run_idm_test {
|
|||
}};
|
||||
}
|
||||
|
||||
pub fn run_idm_test_no_logging<F>(mut test_fn: F)
|
||||
where
|
||||
F: FnMut(
|
||||
&crate::server::QueryServer,
|
||||
&crate::idm::server::IdmServer,
|
||||
&crate::idm::server::IdmServerDelayed,
|
||||
),
|
||||
{
|
||||
sketching::test_init();
|
||||
run_idm_test_inner!(test_fn);
|
||||
}
|
||||
|
||||
// Test helpers for all plugins.
|
||||
// #[macro_export]
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -1603,6 +1603,7 @@ impl Schema {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test))]
|
||||
pub(crate) fn write_blocking(&self) -> SchemaWriteTransaction<'_> {
|
||||
self.write()
|
||||
}
|
||||
|
|
|
@ -820,7 +820,7 @@ impl<'a> QueryServerReadTransaction<'a> {
|
|||
// Verify the data content of the server is as expected. This will probably
|
||||
// call various functions for validation, including possibly plugin
|
||||
// verifications.
|
||||
fn verify(&mut self) -> Vec<Result<(), ConsistencyError>> {
|
||||
pub(crate) fn verify(&mut self) -> Vec<Result<(), ConsistencyError>> {
|
||||
// If we fail after backend, we need to return NOW because we can't
|
||||
// assert any other faith in the DB states.
|
||||
// * backend
|
||||
|
|
|
@ -19,3 +19,13 @@ pub async fn setup_test() -> QueryServer {
|
|||
// Init is called via the proc macro
|
||||
qs
|
||||
}
|
||||
|
||||
pub async fn setup_idm_test() -> (IdmServer, IdmServerDelayed) {
|
||||
let qs = setup_test().await;
|
||||
|
||||
qs.initialise_helper(duration_from_epoch_now())
|
||||
.await
|
||||
.expect("init failed!");
|
||||
|
||||
IdmServer::new(qs, "https://idm.example.com").expect("Failed to setup idms")
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue