Refine permissions
parent
87d11d6ddc
commit
bed0d93628
@ -1,376 +0,0 @@
|
||||
/*
|
||||
* Created on Wed Mar 09 2022
|
||||
*
|
||||
* This file is a part of Skytable
|
||||
* Skytable (formerly known as TerrabaseDB or Skybase) is a free and open-source
|
||||
* NoSQL database written by Sayan Nandan ("the Author") with the
|
||||
* vision to provide flexibility in data modelling without compromising
|
||||
* on performance, queryability or scalability.
|
||||
*
|
||||
* Copyright (c) 2022, Sayan Nandan <ohsayan@outlook.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
use {crate::util, proc_macro::TokenStream, quote::quote, syn::AttributeArgs};
|
||||
|
||||
type OptString = Option<String>;
|
||||
|
||||
pub struct DBTestFunctionConfig {
|
||||
table_decl: String,
|
||||
port: u16,
|
||||
host: String,
|
||||
tls_cert: OptString,
|
||||
login: (OptString, OptString),
|
||||
testuser: bool,
|
||||
rootuser: bool,
|
||||
norun: bool,
|
||||
skip_cfg: quote::__private::TokenStream,
|
||||
}
|
||||
|
||||
impl DBTestFunctionConfig {
|
||||
pub fn default() -> Self {
|
||||
Self {
|
||||
table_decl: "(string, string)".to_owned(),
|
||||
port: 2003,
|
||||
host: "127.0.0.1".to_owned(),
|
||||
tls_cert: None,
|
||||
login: (None, None),
|
||||
testuser: false,
|
||||
rootuser: false,
|
||||
norun: false,
|
||||
skip_cfg: quote! {},
|
||||
}
|
||||
}
|
||||
pub fn get_connection_tokens(&self) -> impl quote::ToTokens {
|
||||
let DBTestFunctionConfig {
|
||||
port,
|
||||
host,
|
||||
tls_cert,
|
||||
..
|
||||
} = &self;
|
||||
match tls_cert {
|
||||
Some(cert) => {
|
||||
quote! {
|
||||
let certpath = ::std::format!("{}/{}", crate::ROOT_DIR, #cert);
|
||||
let mut con = skytable::aio::TlsConnection::new(
|
||||
#host, #port, &certpath
|
||||
).await.unwrap();
|
||||
}
|
||||
}
|
||||
None => quote! {
|
||||
let mut con = skytable::AsyncConnection::new(#host, #port).await.unwrap();
|
||||
},
|
||||
}
|
||||
}
|
||||
pub fn get_create_table_tokens(&self, table_name: &str) -> impl quote::ToTokens {
|
||||
let Self { table_decl, .. } = self;
|
||||
quote! {
|
||||
con.run_query_raw(
|
||||
&skytable::query!(format!("create model {}{} volatile", #table_name, #table_decl))
|
||||
).await.unwrap()
|
||||
}
|
||||
}
|
||||
pub fn get_skip_cfg_tokens(&self) -> &impl quote::ToTokens {
|
||||
&self.skip_cfg
|
||||
}
|
||||
pub fn get_login_tokens(&self) -> Option<impl quote::ToTokens> {
|
||||
let Self {
|
||||
login,
|
||||
testuser,
|
||||
rootuser,
|
||||
..
|
||||
} = self;
|
||||
let conflict = (*rootuser && *testuser)
|
||||
|| ((*rootuser || *testuser) && (login.0.is_some() || login.1.is_some()));
|
||||
if conflict {
|
||||
panic!("Expected either of `username` and `password`, or `auth_rootuser`, or `auth_testuser`");
|
||||
}
|
||||
let ret;
|
||||
if *testuser {
|
||||
ret = quote! {
|
||||
let __username__ = crate::auth::provider::testsuite_data::TESTSUITE_TEST_USER;
|
||||
let __password__ = crate::auth::provider::testsuite_data::TESTSUITE_TEST_TOKEN;
|
||||
};
|
||||
} else if *rootuser {
|
||||
ret = quote! {
|
||||
let __username__ = crate::auth::provider::testsuite_data::TESTSUITE_ROOT_USER;
|
||||
let __password__ = crate::auth::provider::testsuite_data::TESTSUITE_ROOT_TOKEN;
|
||||
};
|
||||
} else {
|
||||
let (username, password) = login;
|
||||
match (username, password) {
|
||||
(Some(username), Some(password)) => {
|
||||
ret = quote! {
|
||||
let __username__ = #username;
|
||||
let __password__ = #password;
|
||||
}
|
||||
}
|
||||
(None, None) => return None,
|
||||
_ => panic!("Expected both `username` and `password`"),
|
||||
}
|
||||
}
|
||||
Some(quote! {
|
||||
#ret
|
||||
let __loginquery__ = ::skytable::query!("auth", "login", __username__, __password__);
|
||||
assert_eq!(
|
||||
con.run_query_raw(&__loginquery__).await.unwrap(),
|
||||
::skytable::Element::RespCode(::skytable::RespCode::Okay),
|
||||
"Failed to login"
|
||||
);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_dbtest_func_args(
|
||||
arg: &str,
|
||||
lit: &syn::Lit,
|
||||
span: proc_macro2::Span,
|
||||
fcfg: &mut DBTestFunctionConfig,
|
||||
) {
|
||||
match arg {
|
||||
"table" => {
|
||||
// check if the user wants some special table declaration
|
||||
fcfg.table_decl =
|
||||
util::parse_string(lit, span, "table").expect("Expected a value for `table`");
|
||||
}
|
||||
"port" => {
|
||||
// check if we need a custom port
|
||||
fcfg.port = util::parse_number(lit, span, "port").expect("Expected a u16");
|
||||
}
|
||||
"host" => {
|
||||
fcfg.host = util::parse_string(lit, span, "host").expect("Expected a string");
|
||||
}
|
||||
"tls_cert" => {
|
||||
fcfg.tls_cert = Some(util::parse_string(lit, span, "host").expect("Expected a string"));
|
||||
}
|
||||
"username" => {
|
||||
fcfg.login.0 =
|
||||
Some(util::parse_string(lit, span, "username").expect("Expected a string"))
|
||||
}
|
||||
"password" => {
|
||||
fcfg.login.1 =
|
||||
Some(util::parse_string(lit, span, "password").expect("Expected a string"))
|
||||
}
|
||||
"auth_testuser" => {
|
||||
fcfg.testuser = util::parse_bool(lit, span, "auth_testuser").expect("Expected a bool")
|
||||
}
|
||||
"auth_rootuser" => {
|
||||
fcfg.rootuser = util::parse_bool(lit, span, "auth_testuser").expect("Expected a bool")
|
||||
}
|
||||
"norun" => fcfg.norun = util::parse_bool(lit, span, "norun").expect("Expected a bool"),
|
||||
"run_if_cfg" => {
|
||||
let cfg_name = util::parse_string(lit, span, "run_if_cfg").expect("Expected a string");
|
||||
fcfg.skip_cfg = quote! {
|
||||
#[cfg_attr(not(feature = #cfg_name), ignore)]
|
||||
};
|
||||
}
|
||||
"skip_if_cfg" => {
|
||||
let cfg_name = util::parse_string(lit, span, "run_if_cfg").expect("Expected a string");
|
||||
fcfg.skip_cfg = quote! {
|
||||
#[cfg_attr(feature = #cfg_name, ignore)]
|
||||
};
|
||||
}
|
||||
x => panic!("unknown attribute {x} specified"),
|
||||
}
|
||||
}
|
||||
|
||||
/// This parses a function within a `dbtest` module
|
||||
///
|
||||
/// This accepts an `async` function and returns a non-`async` version of it - by
|
||||
/// making the body of the function use the `tokio` runtime
|
||||
fn generate_dbtest(
|
||||
mut input: syn::ItemFn,
|
||||
rng: &mut impl rand::Rng,
|
||||
fcfg: &DBTestFunctionConfig,
|
||||
) -> Result<TokenStream, syn::Error> {
|
||||
let sig = &mut input.sig;
|
||||
let fname = sig.ident.to_string();
|
||||
let testbody = &input.block;
|
||||
let attrs = &input.attrs;
|
||||
let vis = &input.vis;
|
||||
let header = quote! {
|
||||
#[::core::prelude::v1::test]
|
||||
};
|
||||
if sig.asyncness.is_none() {
|
||||
let msg = "`dbtest` functions need to be async";
|
||||
return Err(syn::Error::new_spanned(sig.fn_token, msg));
|
||||
}
|
||||
sig.asyncness = None;
|
||||
let rand_string = util::get_rand_string(rng);
|
||||
let mut body = quote! {};
|
||||
|
||||
// first add connection tokens
|
||||
let connection_tokens = fcfg.get_connection_tokens();
|
||||
body = quote! {
|
||||
#body
|
||||
#connection_tokens
|
||||
};
|
||||
|
||||
// check if we need to log in
|
||||
if let Some(login_tokens) = fcfg.get_login_tokens() {
|
||||
body = quote! {
|
||||
#body
|
||||
#login_tokens
|
||||
};
|
||||
}
|
||||
|
||||
if !fcfg.norun {
|
||||
// now create keyspace
|
||||
body = quote! {
|
||||
#body
|
||||
let __create_ks =
|
||||
con.run_query_raw(
|
||||
&skytable::query!("create space testsuite")
|
||||
).await.unwrap();
|
||||
if !(
|
||||
__create_ks == skytable::Element::RespCode(skytable::RespCode::Okay) ||
|
||||
__create_ks == skytable::Element::RespCode(
|
||||
skytable::RespCode::ErrorString(
|
||||
skytable::error::errorstring::ERR_ALREADY_EXISTS.to_owned()
|
||||
)
|
||||
)
|
||||
) {
|
||||
panic!("Failed to create keyspace: {:?}", __create_ks);
|
||||
}
|
||||
};
|
||||
// now switch keyspace
|
||||
body = quote! {
|
||||
#body
|
||||
let __switch_ks =
|
||||
con.run_query_raw(
|
||||
&skytable::query!("use testsuite")
|
||||
).await.unwrap();
|
||||
if (__switch_ks != skytable::Element::RespCode(skytable::RespCode::Okay)) {
|
||||
panic!("Failed to switch keyspace: {:?}", __switch_ks);
|
||||
}
|
||||
};
|
||||
// now create table
|
||||
let create_table_tokens = fcfg.get_create_table_tokens(&rand_string);
|
||||
body = quote! {
|
||||
#body
|
||||
assert_eq!(
|
||||
#create_table_tokens,
|
||||
skytable::Element::RespCode(skytable::RespCode::Okay),
|
||||
"Failed to create table"
|
||||
);
|
||||
};
|
||||
// now generate the __MYENTITY__ string
|
||||
body = quote! {
|
||||
#body
|
||||
let mut __concat_entity = std::string::String::new();
|
||||
__concat_entity.push_str("testsuite.");
|
||||
__concat_entity.push_str(&#rand_string);
|
||||
let __MYTABLE__ : String = #rand_string.to_string();
|
||||
let __MYKS__: String = "testsuite".to_owned();
|
||||
let __MYENTITY__: String = __concat_entity.clone();
|
||||
};
|
||||
// now switch to the temporary table we created
|
||||
body = quote! {
|
||||
#body
|
||||
let __switch_entity =
|
||||
con.run_query_raw(
|
||||
&skytable::query!(format!("use {}", __concat_entity))
|
||||
).await.unwrap();
|
||||
assert_eq!(
|
||||
__switch_entity, skytable::Element::RespCode(skytable::RespCode::Okay), "Failed to switch"
|
||||
);
|
||||
};
|
||||
}
|
||||
// now give the query ghost variable
|
||||
body = quote! {
|
||||
#body
|
||||
let mut query = skytable::Query::new();
|
||||
};
|
||||
// IMPORTANT: now append the actual test body
|
||||
body = quote! {
|
||||
#body
|
||||
#testbody
|
||||
};
|
||||
if !fcfg.norun {
|
||||
// now we're done with the test so flush the table
|
||||
body = quote! {
|
||||
#body
|
||||
{
|
||||
let mut __flush__ = skytable::Query::from("flushdb");
|
||||
std::assert_eq!(
|
||||
con.run_query_raw(&__flush__).await.unwrap(),
|
||||
skytable::Element::RespCode(skytable::RespCode::Okay)
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
let skip_cfg = fcfg.get_skip_cfg_tokens();
|
||||
let result = quote! {
|
||||
#skip_cfg
|
||||
#header
|
||||
#(#attrs)*
|
||||
#vis #sig {
|
||||
tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(4)
|
||||
.thread_name(#fname)
|
||||
.thread_stack_size(3 * 1024 * 1024)
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap()
|
||||
.block_on(async { #body });
|
||||
}
|
||||
};
|
||||
Ok(result.into())
|
||||
}
|
||||
|
||||
/// This function checks if the current function is eligible to be a test and if so, returns
|
||||
/// the generated test
|
||||
pub fn generate_test(
|
||||
input: syn::ItemFn,
|
||||
rng: &mut impl rand::Rng,
|
||||
fcfg: &DBTestFunctionConfig,
|
||||
) -> TokenStream {
|
||||
for attr in &input.attrs {
|
||||
if attr.path.is_ident("test") {
|
||||
let msg = "second test attribute is supplied";
|
||||
return syn::Error::new_spanned(&attr, msg)
|
||||
.to_compile_error()
|
||||
.into();
|
||||
}
|
||||
}
|
||||
|
||||
if !input.sig.inputs.is_empty() {
|
||||
let msg = "the test function cannot accept arguments";
|
||||
return syn::Error::new_spanned(&input.sig.inputs, msg)
|
||||
.to_compile_error()
|
||||
.into();
|
||||
}
|
||||
generate_dbtest(input, rng, fcfg).unwrap_or_else(|e| e.to_compile_error().into())
|
||||
}
|
||||
|
||||
fn parse_dbtest_func_attrs(attrs: AttributeArgs) -> DBTestFunctionConfig {
|
||||
let mut fcfg = DBTestFunctionConfig::default();
|
||||
attrs.iter().for_each(|arg| {
|
||||
if let syn::NestedMeta::Meta(syn::Meta::NameValue(namevalue)) = arg {
|
||||
let (ident, lit, span) = util::get_metanamevalue_data(namevalue);
|
||||
parse_dbtest_func_args(&ident, lit, span, &mut fcfg)
|
||||
}
|
||||
});
|
||||
fcfg
|
||||
}
|
||||
|
||||
pub fn dbtest_func(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let input = syn::parse_macro_input!(item as syn::ItemFn);
|
||||
let attrs = syn::parse_macro_input!(args as AttributeArgs);
|
||||
let mut rng = rand::thread_rng();
|
||||
let fcfg = parse_dbtest_func_attrs(attrs);
|
||||
generate_dbtest(input, &mut rng, &fcfg).unwrap()
|
||||
}
|
@ -1,116 +0,0 @@
|
||||
/*
|
||||
* Created on Wed Mar 09 2022
|
||||
*
|
||||
* This file is a part of Skytable
|
||||
* Skytable (formerly known as TerrabaseDB or Skybase) is a free and open-source
|
||||
* NoSQL database written by Sayan Nandan ("the Author") with the
|
||||
* vision to provide flexibility in data modelling without compromising
|
||||
* on performance, queryability or scalability.
|
||||
*
|
||||
* Copyright (c) 2022, Sayan Nandan <ohsayan@outlook.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
use {
|
||||
crate::{
|
||||
dbtest_fn::{self, DBTestFunctionConfig},
|
||||
util,
|
||||
},
|
||||
proc_macro::TokenStream,
|
||||
quote::quote,
|
||||
std::collections::HashSet,
|
||||
syn::{self, AttributeArgs},
|
||||
};
|
||||
|
||||
struct DBTestModuleConfig {
|
||||
fcfg: DBTestFunctionConfig,
|
||||
skips: HashSet<String>,
|
||||
}
|
||||
|
||||
impl DBTestModuleConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
skips: HashSet::new(),
|
||||
fcfg: DBTestFunctionConfig::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_dbtest_module_args(args: AttributeArgs) -> DBTestModuleConfig {
|
||||
let mut modcfg = DBTestModuleConfig::default();
|
||||
for arg in args {
|
||||
if let syn::NestedMeta::Meta(syn::Meta::NameValue(namevalue)) = arg {
|
||||
let (ident, lit, span) = util::get_metanamevalue_data(&namevalue);
|
||||
match ident.as_str() {
|
||||
"skip" => {
|
||||
modcfg.skips = util::parse_string(lit, span, "skip")
|
||||
.expect("Expected a value for argument `skip`")
|
||||
.split_whitespace()
|
||||
.map(|val| val.to_string())
|
||||
.collect();
|
||||
}
|
||||
possibly_func_arg => dbtest_fn::parse_dbtest_func_args(
|
||||
possibly_func_arg,
|
||||
lit,
|
||||
span,
|
||||
&mut modcfg.fcfg,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
modcfg
|
||||
}
|
||||
|
||||
/// This function accepts an entire **inline** module which comprises of `dbtest` functions.
|
||||
/// It takes each function in turn, and generates `#[test]`able functions for them
|
||||
pub fn parse_test_module(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let input = syn::parse_macro_input!(item as syn::ItemMod);
|
||||
let content = match input.content {
|
||||
Some((_, c)) => c,
|
||||
None => {
|
||||
return syn::Error::new_spanned(&input, "Couldn't get the module content")
|
||||
.to_compile_error()
|
||||
.into()
|
||||
}
|
||||
};
|
||||
let modcfg = parse_dbtest_module_args(syn::parse_macro_input!(args as AttributeArgs));
|
||||
let mut result = quote! {};
|
||||
let mut rng = rand::thread_rng();
|
||||
for item in content {
|
||||
match item {
|
||||
// We just care about functions, so parse functions and ignore everything
|
||||
// else
|
||||
syn::Item::Fn(function) if !modcfg.skips.contains(&function.sig.ident.to_string()) => {
|
||||
let generated_fn = dbtest_fn::generate_test(function, &mut rng, &modcfg.fcfg);
|
||||
let __tok: syn::ItemFn = syn::parse_macro_input!(generated_fn as syn::ItemFn);
|
||||
let tok = quote! {
|
||||
#__tok
|
||||
};
|
||||
result = quote! {
|
||||
#result
|
||||
#tok
|
||||
};
|
||||
}
|
||||
token => {
|
||||
result = quote! {
|
||||
#result
|
||||
#token
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
result.into()
|
||||
}
|
Loading…
Reference in New Issue