Further test improvements (#1166)

This commit is contained in:
Firstyear 2022-11-02 19:46:09 +10:00 committed by GitHub
parent df4043cf10
commit 692c0a3978
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 733 additions and 649 deletions

View file

@ -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()
}

View file

@ -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)
}

View file

@ -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

View file

@ -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) {

View file

@ -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(&gte, ct)
.expect("failed to generate new api token");
let api_token = idms_prox_write
.service_account_generate_api_token(&gte, 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());
}
}

View file

@ -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,

View file

@ -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)]

View file

@ -1603,6 +1603,7 @@ impl Schema {
}
}
#[cfg(any(test))]
pub(crate) fn write_blocking(&self) -> SchemaWriteTransaction<'_> {
self.write()
}

View file

@ -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

View file

@ -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")
}