use std::{fs::metadata, path::Path};
#[cfg(feature = "icmp")]
use std::net::IpAddr;
use std::net::TcpStream;
use proc_macro::TokenStream;
#[cfg(any(feature = "resource", feature = "icmp"))]
use proc_macro_error::abort_call_site;
use proc_macro_error::proc_macro_error;
use syn::{parse_macro_input, ItemFn, ItemMod};
#[cfg(feature = "runtime")]
use syn::Item;
#[cfg(feature = "resource")]
use sysinfo::SystemExt;
#[cfg(feature = "executable")]
use which::which;
use crate::utils::{fn_macro, is_module, mod_macro};
mod utils;
#[proc_macro_attribute]
#[proc_macro_error]
pub fn env(attr: TokenStream, stream: TokenStream) -> TokenStream {
if is_module(&stream) {
mod_macro(
attr,
parse_macro_input!(stream as ItemMod),
check_env_condition,
)
} else {
fn_macro(
attr,
parse_macro_input!(stream as ItemFn),
check_env_condition,
)
}
}
fn check_env_condition(attr_str: String) -> (bool, String) {
let var_names: Vec<&str> = attr_str.split(',').collect();
let mut missing_vars = vec![];
for var in var_names.iter() {
if std::env::var(var).is_err() {
missing_vars.push(var.to_string());
}
}
let ignore_msg = if missing_vars.len() == 1 {
format!("because variable {} not found", missing_vars[0])
} else {
format!(
"because following variables not found:\n{}\n",
missing_vars.join(", ")
)
};
(missing_vars.is_empty(), ignore_msg)
}
#[cfg(not(feature = "runtime"))]
#[proc_macro_attribute]
#[proc_macro_error]
pub fn runtime_env(_attr: TokenStream, _stream: TokenStream) -> TokenStream {
panic!("should be used with runtime feature")
}
#[cfg(feature = "runtime")]
#[proc_macro_attribute]
#[proc_macro_error]
pub fn runtime_env(attr: TokenStream, stream: TokenStream) -> TokenStream {
let attr_str = attr.to_string().replace(' ', "");
let var_names: Vec<&str> = attr_str.split(',').collect();
let ItemFn {
attrs,
vis,
sig,
block,
} = parse_macro_input!(stream as ItemFn);
let syn::Signature { ident, .. } = sig.clone();
let check_ident = syn::Ident::new(
&format!("_check_{}", ident.to_string()),
proc_macro2::Span::call_site(),
);
quote::quote! {
fn #check_ident() -> Result<(), libtest_with::Failed> {
let mut missing_vars = vec![];
#(
if std::env::var(#var_names).is_err() {
missing_vars.push(#var_names);
}
)*
match missing_vars.len() {
0 => {
#ident();
Ok(())
},
1 => Err(
format!("{}because variable {} not found",
libtest_with::RUNTIME_IGNORE_PREFIX, missing_vars[0]
).into()),
_ => Err(
format!("{}because following variables not found:\n{}\n",
libtest_with::RUNTIME_IGNORE_PREFIX, missing_vars.join(", ")
).into()),
}
}
#(#attrs)*
#vis #sig #block
}
.into()
}
#[proc_macro_attribute]
#[proc_macro_error]
pub fn no_env(attr: TokenStream, stream: TokenStream) -> TokenStream {
if is_module(&stream) {
mod_macro(
attr,
parse_macro_input!(stream as ItemMod),
check_no_env_condition,
)
} else {
fn_macro(
attr,
parse_macro_input!(stream as ItemFn),
check_no_env_condition,
)
}
}
fn check_no_env_condition(attr_str: String) -> (bool, String) {
let var_names: Vec<&str> = attr_str.split(',').collect();
for var in var_names.iter() {
if std::env::var(var).is_ok() {
return (
false,
format!("because the environment with variable {var:} will ignore"),
);
}
}
(true, String::new())
}
#[cfg(not(feature = "runtime"))]
#[proc_macro_attribute]
#[proc_macro_error]
pub fn runtime_no_env(_attr: TokenStream, _stream: TokenStream) -> TokenStream {
panic!("should be used with runtime feature")
}
#[cfg(feature = "runtime")]
#[proc_macro_attribute]
#[proc_macro_error]
pub fn runtime_no_env(attr: TokenStream, stream: TokenStream) -> TokenStream {
let attr_str = attr.to_string().replace(' ', "");
let var_names: Vec<&str> = attr_str.split(',').collect();
let ItemFn {
attrs,
vis,
sig,
block,
} = parse_macro_input!(stream as ItemFn);
let syn::Signature { ident, .. } = sig.clone();
let check_ident = syn::Ident::new(
&format!("_check_{}", ident.to_string()),
proc_macro2::Span::call_site(),
);
quote::quote! {
fn #check_ident() -> Result<(), libtest_with::Failed> {
let mut should_no_exist_vars = vec![];
#(
if std::env::var(#var_names).is_ok() {
should_no_exist_vars.push(#var_names);
}
)*
match should_no_exist_vars.len() {
0 => {
#ident();
Ok(())
},
1 => Err(
format!("{}because variable {} found",
libtest_with::RUNTIME_IGNORE_PREFIX, should_no_exist_vars[0]
).into()),
_ => Err(
format!("{}because following variables found:\n{}\n",
libtest_with::RUNTIME_IGNORE_PREFIX, should_no_exist_vars.join(", ")
).into()),
}
}
#(#attrs)*
#vis #sig #block
}
.into()
}
#[proc_macro_attribute]
#[proc_macro_error]
pub fn file(attr: TokenStream, stream: TokenStream) -> TokenStream {
if is_module(&stream) {
mod_macro(
attr,
parse_macro_input!(stream as ItemMod),
check_file_condition,
)
} else {
fn_macro(
attr,
parse_macro_input!(stream as ItemFn),
check_file_condition,
)
}
}
fn check_file_condition(attr_str: String) -> (bool, String) {
let files: Vec<&str> = attr_str.split(',').collect();
let mut missing_files = vec![];
for file in files.iter() {
if !Path::new(file.trim_matches('"')).is_file() {
missing_files.push(file.to_string());
}
}
let ignore_msg = if missing_files.len() == 1 {
format!("because file not found: {}", missing_files[0])
} else {
format!(
"because following files not found: \n{}\n",
missing_files.join("\n")
)
};
(missing_files.is_empty(), ignore_msg)
}
#[cfg(not(feature = "runtime"))]
#[proc_macro_attribute]
#[proc_macro_error]
pub fn runtime_file(_attr: TokenStream, _stream: TokenStream) -> TokenStream {
panic!("should be used with runtime feature")
}
#[cfg(feature = "runtime")]
#[proc_macro_attribute]
#[proc_macro_error]
pub fn runtime_file(attr: TokenStream, stream: TokenStream) -> TokenStream {
let attr_str = attr.to_string().replace(' ', "");
let files: Vec<&str> = attr_str.split(',').collect();
let ItemFn {
attrs,
vis,
sig,
block,
} = parse_macro_input!(stream as ItemFn);
let syn::Signature { ident, .. } = sig.clone();
let check_ident = syn::Ident::new(
&format!("_check_{}", ident.to_string()),
proc_macro2::Span::call_site(),
);
quote::quote! {
fn #check_ident() -> Result<(), libtest_with::Failed> {
let mut missing_files = vec![];
#(
if !std::path::Path::new(#files.trim_matches('"')).is_file() {
missing_files.push(#files);
}
)*
match missing_files.len() {
0 => {
#ident();
Ok(())
},
1 => Err(
format!("{}because file not found: {}",
libtest_with::RUNTIME_IGNORE_PREFIX, missing_files[0]
).into()),
_ => Err(
format!("{}because following files not found: \n{}\n",
libtest_with::RUNTIME_IGNORE_PREFIX, missing_files.join(", ")
).into()),
}
}
#(#attrs)*
#vis #sig #block
}
.into()
}
#[proc_macro_attribute]
#[proc_macro_error]
pub fn path(attr: TokenStream, stream: TokenStream) -> TokenStream {
if is_module(&stream) {
mod_macro(
attr,
parse_macro_input!(stream as ItemMod),
check_path_condition,
)
} else {
fn_macro(
attr,
parse_macro_input!(stream as ItemFn),
check_path_condition,
)
}
}
fn check_path_condition(attr_str: String) -> (bool, String) {
let paths: Vec<&str> = attr_str.split(',').collect();
let mut missing_paths = vec![];
for path in paths.iter() {
if metadata(path.trim_matches('"')).is_err() {
missing_paths.push(path.to_string());
}
}
let ignore_msg = if missing_paths.len() == 1 {
format!("because path not found: {}", missing_paths[0])
} else {
format!(
"because following paths not found: \n{}\n",
missing_paths.join("\n")
)
};
(missing_paths.is_empty(), ignore_msg)
}
#[cfg(not(feature = "runtime"))]
#[proc_macro_attribute]
#[proc_macro_error]
pub fn runtime_path(_attr: TokenStream, _stream: TokenStream) -> TokenStream {
panic!("should be used with runtime feature")
}
#[cfg(feature = "runtime")]
#[proc_macro_attribute]
#[proc_macro_error]
pub fn runtime_path(attr: TokenStream, stream: TokenStream) -> TokenStream {
let attr_str = attr.to_string().replace(' ', "");
let paths: Vec<&str> = attr_str.split(',').collect();
let ItemFn {
attrs,
vis,
sig,
block,
} = parse_macro_input!(stream as ItemFn);
let syn::Signature { ident, .. } = sig.clone();
let check_ident = syn::Ident::new(
&format!("_check_{}", ident.to_string()),
proc_macro2::Span::call_site(),
);
quote::quote! {
fn #check_ident() -> Result<(), libtest_with::Failed> {
let mut missing_paths = vec![];
#(
if std::fs::metadata(#paths.trim_matches('"')).is_err() {
missing_paths.push(#paths.to_string());
}
)*
match missing_paths.len() {
0 => {
#ident();
Ok(())
},
1 => Err(
format!("{}because path not found: {}",
libtest_with::RUNTIME_IGNORE_PREFIX, missing_paths[0]
).into()),
_ => Err(
format!("{}because following paths not found: \n{}\n",
libtest_with::RUNTIME_IGNORE_PREFIX, missing_paths.join(", ")
).into()),
}
}
#(#attrs)*
#vis #sig #block
}
.into()
}
#[proc_macro_attribute]
#[proc_macro_error]
#[cfg(feature = "http")]
pub fn http(attr: TokenStream, stream: TokenStream) -> TokenStream {
if is_module(&stream) {
mod_macro(
attr,
parse_macro_input!(stream as ItemMod),
check_http_condition,
)
} else {
fn_macro(
attr,
parse_macro_input!(stream as ItemFn),
check_http_condition,
)
}
}
#[cfg(feature = "http")]
fn check_http_condition(attr_str: String) -> (bool, String) {
let links: Vec<&str> = attr_str.split(',').collect();
let mut missing_links = vec![];
let client = reqwest::blocking::Client::new();
for link in links.iter() {
if client.head(&format!("http://{}", link)).send().is_err() {
missing_links.push(format!("http://{link:}"));
}
}
let ignore_msg = if missing_links.len() == 1 {
format!("because {} not response", missing_links[0])
} else {
format!(
"because following links not response: \n{}\n",
missing_links.join("\n")
)
};
(missing_links.is_empty(), ignore_msg)
}
#[cfg(not(feature = "runtime"))]
#[proc_macro_attribute]
#[proc_macro_error]
pub fn runtime_http(_attr: TokenStream, _stream: TokenStream) -> TokenStream {
panic!("should be used with runtime feature")
}
#[cfg(all(feature = "runtime", feature = "http"))]
#[proc_macro_attribute]
#[proc_macro_error]
pub fn runtime_http(attr: TokenStream, stream: TokenStream) -> TokenStream {
let attr_str = attr.to_string().replace(' ', "");
let links: Vec<&str> = attr_str.split(',').collect();
let ItemFn {
attrs,
vis,
sig,
block,
} = parse_macro_input!(stream as ItemFn);
let syn::Signature { ident, .. } = sig.clone();
let check_ident = syn::Ident::new(
&format!("_check_{}", ident.to_string()),
proc_macro2::Span::call_site(),
);
quote::quote! {
fn #check_ident() -> Result<(), libtest_with::Failed> {
let mut missing_links = vec![];
let client = libtest_with::reqwest::blocking::Client::new();
#(
if client.head(&format!("http://{}", #links)).send().is_err() {
missing_links.push(format!("http://{}", #links));
}
)*
match missing_links.len() {
0 => {
#ident();
Ok(())
},
1 => Err(
format!("{}because {} not response",
libtest_with::RUNTIME_IGNORE_PREFIX, missing_links[0]
).into()),
_ => Err(
format!("{}because following links not response: \n{}\n",
libtest_with::RUNTIME_IGNORE_PREFIX, missing_links.join(", ")
).into()),
}
}
#(#attrs)*
#vis #sig #block
}
.into()
}
#[proc_macro_attribute]
#[proc_macro_error]
#[cfg(feature = "http")]
pub fn https(attr: TokenStream, stream: TokenStream) -> TokenStream {
if is_module(&stream) {
mod_macro(
attr,
parse_macro_input!(stream as ItemMod),
check_https_condition,
)
} else {
fn_macro(
attr,
parse_macro_input!(stream as ItemFn),
check_https_condition,
)
}
}
#[cfg(feature = "http")]
fn check_https_condition(attr_str: String) -> (bool, String) {
let links: Vec<&str> = attr_str.split(',').collect();
let mut missing_links = vec![];
let client = reqwest::blocking::Client::new();
for link in links.iter() {
if client.head(&format!("https://{}", link)).send().is_err() {
missing_links.push(format!("https://{link:}"));
}
}
let ignore_msg = if missing_links.len() == 1 {
format!("because {} not response", missing_links[0])
} else {
format!(
"because following links not response: \n{}\n",
missing_links.join("\n")
)
};
(missing_links.is_empty(), ignore_msg)
}
#[cfg(not(feature = "runtime"))]
#[proc_macro_attribute]
#[proc_macro_error]
pub fn runtime_https(_attr: TokenStream, _stream: TokenStream) -> TokenStream {
panic!("should be used with runtime feature")
}
#[cfg(all(feature = "runtime", feature = "http"))]
#[proc_macro_attribute]
#[proc_macro_error]
pub fn runtime_https(attr: TokenStream, stream: TokenStream) -> TokenStream {
let attr_str = attr.to_string().replace(' ', "");
let links: Vec<&str> = attr_str.split(',').collect();
let ItemFn {
attrs,
vis,
sig,
block,
} = parse_macro_input!(stream as ItemFn);
let syn::Signature { ident, .. } = sig.clone();
let check_ident = syn::Ident::new(
&format!("_check_{}", ident.to_string()),
proc_macro2::Span::call_site(),
);
quote::quote! {
fn #check_ident() -> Result<(), libtest_with::Failed> {
let mut missing_links = vec![];
let client = libtest_with::reqwest::blocking::Client::new();
#(
if client.head(&format!("https://{}", #links)).send().is_err() {
missing_links.push(format!("https://{}", #links));
}
)*
match missing_links.len() {
0 => {
#ident();
Ok(())
},
1 => Err(
format!("{}because {} not response",
libtest_with::RUNTIME_IGNORE_PREFIX, missing_links[0]
).into()),
_ => Err(
format!("{}because following links not response: \n{}\n",
libtest_with::RUNTIME_IGNORE_PREFIX, missing_links.join(", ")
).into()),
}
}
#(#attrs)*
#vis #sig #block
}
.into()
}
#[proc_macro_attribute]
#[proc_macro_error]
#[cfg(feature = "icmp")]
pub fn icmp(attr: TokenStream, stream: TokenStream) -> TokenStream {
if is_module(&stream) {
mod_macro(
attr,
parse_macro_input!(stream as ItemMod),
check_icmp_condition,
)
} else {
fn_macro(
attr,
parse_macro_input!(stream as ItemFn),
check_icmp_condition,
)
}
}
#[cfg(feature = "icmp")]
fn check_icmp_condition(attr_str: String) -> (bool, String) {
let ips: Vec<&str> = attr_str.split(',').collect();
let mut missing_ips = vec![];
for ip in ips.iter() {
if let Ok(addr) = ip.parse::<IpAddr>() {
if ping::ping(addr, None, None, None, None, None).is_err() {
missing_ips.push(ip.to_string());
}
} else {
abort_call_site!("ip address malformat")
}
}
let ignore_msg = if missing_ips.len() == 1 {
format!("because ip {} not response", missing_ips[0])
} else {
format!(
"because following ip not response: \n{}\n",
missing_ips.join(", ")
)
};
(missing_ips.is_empty(), ignore_msg)
}
#[cfg(not(feature = "runtime"))]
#[proc_macro_attribute]
#[proc_macro_error]
pub fn runtime_icmp(_attr: TokenStream, _stream: TokenStream) -> TokenStream {
panic!("should be used with runtime feature")
}
#[cfg(all(feature = "runtime", feature = "icmp"))]
#[proc_macro_attribute]
#[proc_macro_error]
pub fn runtime_icmp(attr: TokenStream, stream: TokenStream) -> TokenStream {
let attr_str = attr.to_string().replace(' ', "");
let ips: Vec<&str> = attr_str.split(',').collect();
let ItemFn {
attrs,
vis,
sig,
block,
} = parse_macro_input!(stream as ItemFn);
let syn::Signature { ident, .. } = sig.clone();
let check_ident = syn::Ident::new(
&format!("_check_{}", ident.to_string()),
proc_macro2::Span::call_site(),
);
quote::quote! {
fn #check_ident() -> Result<(), libtest_with::Failed> {
let mut missing_ips = vec![];
#(
if libtest_with::ping::ping(#ips.parse().expect("ip address is invalid"), None, None, None, None, None).is_err() {
missing_ips.push(#ips);
}
)*
match missing_ips.len() {
0 => {
#ident();
Ok(())
}
,
1 => Err(
format!("{}because {} not response",
libtest_with::RUNTIME_IGNORE_PREFIX, missing_ips[0]
).into()),
_ => Err(
format!("{}because following ips not response: \n{}\n",
libtest_with::RUNTIME_IGNORE_PREFIX, missing_ips.join(", ")
).into()),
}
}
#(#attrs)*
#vis #sig #block
}
.into()
}
#[proc_macro_attribute]
#[proc_macro_error]
pub fn tcp(attr: TokenStream, stream: TokenStream) -> TokenStream {
if is_module(&stream) {
mod_macro(
attr,
parse_macro_input!(stream as ItemMod),
check_tcp_condition,
)
} else {
fn_macro(
attr,
parse_macro_input!(stream as ItemFn),
check_tcp_condition,
)
}
}
fn check_tcp_condition(attr_str: String) -> (bool, String) {
let sockets: Vec<&str> = attr_str.split(',').collect();
let mut missing_sockets = vec![];
for socket in sockets.iter() {
if TcpStream::connect(socket).is_err() {
missing_sockets.push(socket.to_string());
}
}
let ignore_msg = if missing_sockets.len() == 1 {
format!("because fail to connect socket {}", missing_sockets[0])
} else {
format!(
"because follow sockets can not connect\n{}\n",
missing_sockets.join(", ")
)
};
(missing_sockets.is_empty(), ignore_msg)
}
#[cfg(not(feature = "runtime"))]
#[proc_macro_attribute]
#[proc_macro_error]
pub fn runtime_tcp(_attr: TokenStream, _stream: TokenStream) -> TokenStream {
panic!("should be used with runtime feature")
}
#[cfg(all(feature = "runtime"))]
#[proc_macro_attribute]
#[proc_macro_error]
pub fn runtime_tcp(attr: TokenStream, stream: TokenStream) -> TokenStream {
let attr_str = attr.to_string().replace(' ', "");
let sockets: Vec<&str> = attr_str.split(',').collect();
let ItemFn {
attrs,
vis,
sig,
block,
} = parse_macro_input!(stream as ItemFn);
let syn::Signature { ident, .. } = sig.clone();
let check_ident = syn::Ident::new(
&format!("_check_{}", ident.to_string()),
proc_macro2::Span::call_site(),
);
quote::quote! {
fn #check_ident() -> Result<(), libtest_with::Failed> {
let mut missing_sockets = vec![];
#(
if std::net::TcpStream::connect(#sockets).is_err() {
missing_sockets.push(#sockets);
}
)*
match missing_sockets.len() {
0 => {
#ident();
Ok(())
},
1 => Err(
format!("{}because {} not response",
libtest_with::RUNTIME_IGNORE_PREFIX, missing_sockets[0]
).into()),
_ => Err(
format!("{}because following sockets not response: \n{}\n",
libtest_with::RUNTIME_IGNORE_PREFIX, missing_sockets.join(", ")
).into()),
}
}
#(#attrs)*
#vis #sig #block
}
.into()
}
#[proc_macro_attribute]
#[proc_macro_error]
#[cfg(all(feature = "user", not(target_os = "windows")))]
pub fn root(attr: TokenStream, stream: TokenStream) -> TokenStream {
if is_module(&stream) {
mod_macro(
attr,
parse_macro_input!(stream as ItemMod),
check_root_condition,
)
} else {
fn_macro(
attr,
parse_macro_input!(stream as ItemFn),
check_root_condition,
)
}
}
#[cfg(all(feature = "user", not(target_os = "windows")))]
fn check_root_condition(_attr_str: String) -> (bool, String) {
let current_user_id = users::get_current_uid();
(
current_user_id == 0,
"because this case should run with root".into(),
)
}
#[cfg(not(feature = "runtime"))]
#[proc_macro_attribute]
#[proc_macro_error]
pub fn runtime_root(_attr: TokenStream, _stream: TokenStream) -> TokenStream {
panic!("should be used with runtime feature")
}
#[cfg(all(feature = "runtime", feature = "user", not(target_os = "windows")))]
#[proc_macro_attribute]
#[proc_macro_error]
pub fn runtime_root(_attr: TokenStream, stream: TokenStream) -> TokenStream {
let ItemFn {
attrs,
vis,
sig,
block,
} = parse_macro_input!(stream as ItemFn);
let syn::Signature { ident, .. } = sig.clone();
let check_ident = syn::Ident::new(
&format!("_check_{}", ident.to_string()),
proc_macro2::Span::call_site(),
);
quote::quote! {
fn #check_ident() -> Result<(), libtest_with::Failed> {
if 0 == libtest_with::users::get_current_uid() {
#ident();
Ok(())
} else {
Err(format!("{}because this case should run with root", libtest_with::RUNTIME_IGNORE_PREFIX).into())
}
}
#(#attrs)*
#vis #sig #block
}
.into()
}
#[proc_macro_attribute]
#[proc_macro_error]
#[cfg(all(feature = "user", not(target_os = "windows")))]
pub fn group(attr: TokenStream, stream: TokenStream) -> TokenStream {
if is_module(&stream) {
mod_macro(
attr,
parse_macro_input!(stream as ItemMod),
check_group_condition,
)
} else {
fn_macro(
attr,
parse_macro_input!(stream as ItemFn),
check_group_condition,
)
}
}
#[cfg(feature = "user")]
#[cfg(all(feature = "user", not(target_os = "windows")))]
fn check_group_condition(group_name: String) -> (bool, String) {
let current_user_id = users::get_current_uid();
let in_group = match users::get_user_by_uid(current_user_id) {
Some(user) => {
let mut in_group = false;
for group in user.groups().expect("user not found") {
if in_group {
break;
}
in_group |= group.name().to_string_lossy() == group_name;
}
in_group
}
None => false,
};
(
in_group,
format!("because this case should run user in group {}", group_name),
)
}
#[cfg(not(feature = "runtime"))]
#[proc_macro_attribute]
#[proc_macro_error]
pub fn runtime_group(_attr: TokenStream, _stream: TokenStream) -> TokenStream {
panic!("should be used with runtime feature")
}
#[cfg(all(feature = "runtime", feature = "user", not(target_os = "windows")))]
#[proc_macro_attribute]
#[proc_macro_error]
pub fn runtime_group(attr: TokenStream, stream: TokenStream) -> TokenStream {
let group_name = attr.to_string().replace(' ', "");
let ItemFn {
attrs,
vis,
sig,
block,
} = parse_macro_input!(stream as ItemFn);
let syn::Signature { ident, .. } = sig.clone();
let check_ident = syn::Ident::new(
&format!("_check_{}", ident.to_string()),
proc_macro2::Span::call_site(),
);
quote::quote! {
fn #check_ident() -> Result<(), libtest_with::Failed> {
let current_user_id = libtest_with::users::get_current_uid();
let in_group = match libtest_with::users::get_user_by_uid(current_user_id) {
Some(user) => {
let mut in_group = false;
for group in user.groups().expect("user not found") {
if in_group {
break;
}
in_group |= group.name().to_string_lossy() == #group_name;
}
in_group
}
None => false,
};
if in_group {
#ident();
Ok(())
} else {
Err(format!("{}because this case should run user in group {}",
libtest_with::RUNTIME_IGNORE_PREFIX, #group_name).into())
}
}
#(#attrs)*
#vis #sig #block
}
.into()
}
#[proc_macro_attribute]
#[proc_macro_error]
#[cfg(all(feature = "user", not(target_os = "windows")))]
pub fn user(attr: TokenStream, stream: TokenStream) -> TokenStream {
if is_module(&stream) {
mod_macro(
attr,
parse_macro_input!(stream as ItemMod),
check_user_condition,
)
} else {
fn_macro(
attr,
parse_macro_input!(stream as ItemFn),
check_user_condition,
)
}
}
#[cfg(feature = "user")]
#[cfg(all(feature = "user", not(target_os = "windows")))]
fn check_user_condition(user_name: String) -> (bool, String) {
let is_user = match users::get_current_username() {
Some(uname) => uname.to_string_lossy() == user_name,
None => false,
};
(
is_user,
format!("because this case should run with user {}", user_name),
)
}
#[cfg(not(feature = "runtime"))]
#[proc_macro_attribute]
#[proc_macro_error]
pub fn runtime_user(_attr: TokenStream, _stream: TokenStream) -> TokenStream {
panic!("should be used with runtime feature")
}
#[cfg(all(feature = "runtime", feature = "user", not(target_os = "windows")))]
#[proc_macro_attribute]
#[proc_macro_error]
pub fn runtime_user(attr: TokenStream, stream: TokenStream) -> TokenStream {
let user_name = attr.to_string().replace(' ', "");
let ItemFn {
attrs,
vis,
sig,
block,
} = parse_macro_input!(stream as ItemFn);
let syn::Signature { ident, .. } = sig.clone();
let check_ident = syn::Ident::new(
&format!("_check_{}", ident.to_string()),
proc_macro2::Span::call_site(),
);
quote::quote! {
fn #check_ident() -> Result<(), libtest_with::Failed> {
let is_user = match libtest_with::users::get_current_username() {
Some(uname) => uname.to_string_lossy() == #user_name,
None => false,
};
if is_user {
#ident();
Ok(())
} else {
Err(format!("{}because this case should run with user {}",
libtest_with::RUNTIME_IGNORE_PREFIX, #user_name).into())
}
}
#(#attrs)*
#vis #sig #block
}
.into()
}
#[proc_macro_attribute]
#[proc_macro_error]
#[cfg(feature = "resource")]
pub fn mem(attr: TokenStream, stream: TokenStream) -> TokenStream {
if is_module(&stream) {
mod_macro(
attr,
parse_macro_input!(stream as ItemMod),
check_mem_condition,
)
} else {
fn_macro(
attr,
parse_macro_input!(stream as ItemFn),
check_mem_condition,
)
}
}
#[cfg(feature = "resource")]
fn check_mem_condition(mem_size_str: String) -> (bool, String) {
let mut sys = sysinfo::System::new_all();
sys.refresh_all();
let mem_size = match byte_unit::Byte::from_str(format!("{} B", sys.total_memory())) {
Ok(b) => b,
Err(_) => abort_call_site!("memory size description is not correct"),
};
let mem_size_limitation = match byte_unit::Byte::from_str(&mem_size_str) {
Ok(b) => b,
Err(_) => abort_call_site!("system memory size can not get"),
};
(
mem_size >= mem_size_limitation,
format!("because the memory less than {}", mem_size_str),
)
}
#[cfg(not(feature = "runtime"))]
#[proc_macro_attribute]
#[proc_macro_error]
pub fn runtime_mem(_attr: TokenStream, _stream: TokenStream) -> TokenStream {
panic!("should be used with runtime feature")
}
#[cfg(all(feature = "runtime", feature = "resource"))]
#[proc_macro_attribute]
#[proc_macro_error]
pub fn runtime_mem(attr: TokenStream, stream: TokenStream) -> TokenStream {
let mem_limitation_str = attr.to_string().replace(' ', "");
if byte_unit::Byte::from_str(&mem_limitation_str).is_err() {
abort_call_site!("memory size description is not correct")
}
let ItemFn {
attrs,
vis,
sig,
block,
} = parse_macro_input!(stream as ItemFn);
let syn::Signature { ident, .. } = sig.clone();
let check_ident = syn::Ident::new(
&format!("_check_{}", ident.to_string()),
proc_macro2::Span::call_site(),
);
quote::quote! {
fn #check_ident() -> Result<(), libtest_with::Failed> {
use libtest_with::sysinfo::SystemExt;
let mut sys = libtest_with::sysinfo::System::new_all();
sys.refresh_all();
let mem_size = match libtest_with::byte_unit::Byte::from_str(format!("{} B", sys.total_memory())) {
Ok(b) => b,
Err(_) => panic!("system memory size can not get"),
};
let mem_size_limitation = libtest_with::byte_unit::Byte::from_str(#mem_limitation_str).expect("mem limitation should correct");
if mem_size >= mem_size_limitation {
#ident();
Ok(())
} else {
Err(format!("{}because the memory less than {}",
libtest_with::RUNTIME_IGNORE_PREFIX, #mem_limitation_str).into())
}
}
#(#attrs)*
#vis #sig #block
}
.into()
}
#[cfg(not(feature = "runtime"))]
#[proc_macro_attribute]
#[proc_macro_error]
pub fn runtime_free_mem(_attr: TokenStream, _stream: TokenStream) -> TokenStream {
panic!("should be used with runtime feature")
}
#[cfg(all(feature = "runtime", feature = "resource"))]
#[proc_macro_attribute]
#[proc_macro_error]
pub fn runtime_free_mem(attr: TokenStream, stream: TokenStream) -> TokenStream {
let mem_limitation_str = attr.to_string().replace(' ', "");
if byte_unit::Byte::from_str(&mem_limitation_str).is_err() {
abort_call_site!("memory size description is not correct")
}
let ItemFn {
attrs,
vis,
sig,
block,
} = parse_macro_input!(stream as ItemFn);
let syn::Signature { ident, .. } = sig.clone();
let check_ident = syn::Ident::new(
&format!("_check_{}", ident.to_string()),
proc_macro2::Span::call_site(),
);
quote::quote! {
fn #check_ident() -> Result<(), libtest_with::Failed> {
use libtest_with::sysinfo::SystemExt;
let mut sys = libtest_with::sysinfo::System::new_all();
sys.refresh_all();
let mem_size = match libtest_with::byte_unit::Byte::from_str(format!("{} B", sys.free_memory())) {
Ok(b) => b,
Err(_) => panic!("system memory size can not get"),
};
let mem_size_limitation = libtest_with::byte_unit::Byte::from_str(#mem_limitation_str).expect("mem limitation should correct");
if mem_size >= mem_size_limitation {
#ident();
Ok(())
} else {
Err(format!("{}because the memory less than {}",
libtest_with::RUNTIME_IGNORE_PREFIX, #mem_limitation_str).into())
}
}
#(#attrs)*
#vis #sig #block
}
.into()
}
#[cfg(not(feature = "runtime"))]
#[proc_macro_attribute]
#[proc_macro_error]
pub fn runtime_available_mem(_attr: TokenStream, _stream: TokenStream) -> TokenStream {
panic!("should be used with runtime feature")
}
#[cfg(all(feature = "runtime", feature = "resource"))]
#[proc_macro_attribute]
#[proc_macro_error]
pub fn runtime_available_mem(attr: TokenStream, stream: TokenStream) -> TokenStream {
let mem_limitation_str = attr.to_string().replace(' ', "");
if byte_unit::Byte::from_str(&mem_limitation_str).is_err() {
abort_call_site!("memory size description is not correct")
}
let ItemFn {
attrs,
vis,
sig,
block,
} = parse_macro_input!(stream as ItemFn);
let syn::Signature { ident, .. } = sig.clone();
let check_ident = syn::Ident::new(
&format!("_check_{}", ident.to_string()),
proc_macro2::Span::call_site(),
);
quote::quote! {
fn #check_ident() -> Result<(), libtest_with::Failed> {
use libtest_with::sysinfo::SystemExt;
let mut sys = libtest_with::sysinfo::System::new_all();
sys.refresh_all();
let mem_size = match libtest_with::byte_unit::Byte::from_str(format!("{} B", sys.available_memory())) {
Ok(b) => b,
Err(_) => panic!("system memory size can not get"),
};
let mem_size_limitation = libtest_with::byte_unit::Byte::from_str(#mem_limitation_str).expect("mem limitation should correct");
if mem_size >= mem_size_limitation {
#ident();
Ok(())
} else {
Err(format!("{}because the memory less than {}",
libtest_with::RUNTIME_IGNORE_PREFIX, #mem_limitation_str).into())
}
}
#(#attrs)*
#vis #sig #block
}
.into()
}
#[proc_macro_attribute]
#[proc_macro_error]
#[cfg(feature = "resource")]
pub fn swap(attr: TokenStream, stream: TokenStream) -> TokenStream {
if is_module(&stream) {
mod_macro(
attr,
parse_macro_input!(stream as ItemMod),
check_swap_condition,
)
} else {
fn_macro(
attr,
parse_macro_input!(stream as ItemFn),
check_swap_condition,
)
}
}
#[cfg(feature = "resource")]
fn check_swap_condition(swap_size_str: String) -> (bool, String) {
let mut sys = sysinfo::System::new_all();
sys.refresh_all();
let swap_size = match byte_unit::Byte::from_str(format!("{} B", sys.total_swap())) {
Ok(b) => b,
Err(_) => abort_call_site!("Swap size description is not correct"),
};
let swap_size_limitation = match byte_unit::Byte::from_str(&swap_size_str) {
Ok(b) => b,
Err(_) => abort_call_site!("Can not get system swap size"),
};
(
swap_size >= swap_size_limitation,
format!("because the swap less than {}", swap_size_str),
)
}
#[cfg(not(feature = "runtime"))]
#[proc_macro_attribute]
#[proc_macro_error]
pub fn runtime_swap(_attr: TokenStream, _stream: TokenStream) -> TokenStream {
panic!("should be used with runtime feature")
}
#[cfg(all(feature = "runtime", feature = "resource"))]
#[proc_macro_attribute]
#[proc_macro_error]
pub fn runtime_swap(attr: TokenStream, stream: TokenStream) -> TokenStream {
let swap_limitation_str = attr.to_string().replace(' ', "");
if byte_unit::Byte::from_str(&swap_limitation_str).is_err() {
abort_call_site!("swap size description is not correct")
}
let ItemFn {
attrs,
vis,
sig,
block,
} = parse_macro_input!(stream as ItemFn);
let syn::Signature { ident, .. } = sig.clone();
let check_ident = syn::Ident::new(
&format!("_check_{}", ident.to_string()),
proc_macro2::Span::call_site(),
);
quote::quote! {
fn #check_ident() -> Result<(), libtest_with::Failed> {
use libtest_with::sysinfo::SystemExt;
let mut sys = libtest_with::sysinfo::System::new_all();
sys.refresh_all();
let swap_size = match libtest_with::byte_unit::Byte::from_str(format!("{} B", sys.total_swap())) {
Ok(b) => b,
Err(_) => panic!("system swap size can not get"),
};
let swap_size_limitation = libtest_with::byte_unit::Byte::from_str(#swap_limitation_str).expect("swap limitation should correct");
if swap_size >= swap_size_limitation {
#ident();
Ok(())
} else {
Err(format!("{}because the swap less than {}",
libtest_with::RUNTIME_IGNORE_PREFIX, #swap_limitation_str).into())
}
}
#(#attrs)*
#vis #sig #block
}
.into()
}
#[cfg(not(feature = "runtime"))]
#[proc_macro_attribute]
#[proc_macro_error]
pub fn runtime_free_swap(_attr: TokenStream, _stream: TokenStream) -> TokenStream {
panic!("should be used with runtime feature")
}
#[cfg(all(feature = "runtime", feature = "resource"))]
#[proc_macro_attribute]
#[proc_macro_error]
pub fn runtime_free_swap(attr: TokenStream, stream: TokenStream) -> TokenStream {
let swap_limitation_str = attr.to_string().replace(' ', "");
if byte_unit::Byte::from_str(&swap_limitation_str).is_err() {
abort_call_site!("swap size description is not correct")
}
let ItemFn {
attrs,
vis,
sig,
block,
} = parse_macro_input!(stream as ItemFn);
let syn::Signature { ident, .. } = sig.clone();
let check_ident = syn::Ident::new(
&format!("_check_{}", ident.to_string()),
proc_macro2::Span::call_site(),
);
quote::quote! {
fn #check_ident() -> Result<(), libtest_with::Failed> {
use libtest_with::sysinfo::SystemExt;
let mut sys = libtest_with::sysinfo::System::new_all();
sys.refresh_all();
let swap_size = match libtest_with::byte_unit::Byte::from_str(format!("{} B", sys.free_swap())) {
Ok(b) => b,
Err(_) => panic!("system swap size can not get"),
};
let swap_size_limitation = libtest_with::byte_unit::Byte::from_str(#swap_limitation_str).expect("swap limitation should correct");
if swap_size >= swap_size_limitation {
#ident();
Ok(())
} else {
Err(format!("{}because the swap less than {}",
libtest_with::RUNTIME_IGNORE_PREFIX, #swap_limitation_str).into())
}
}
#(#attrs)*
#vis #sig #block
}
.into()
}
#[proc_macro_attribute]
#[proc_macro_error]
#[cfg(feature = "resource")]
pub fn cpu_core(attr: TokenStream, stream: TokenStream) -> TokenStream {
if is_module(&stream) {
mod_macro(
attr,
parse_macro_input!(stream as ItemMod),
check_cpu_core_condition,
)
} else {
fn_macro(
attr,
parse_macro_input!(stream as ItemFn),
check_cpu_core_condition,
)
}
}
#[cfg(feature = "resource")]
fn check_cpu_core_condition(core_limitation_str: String) -> (bool, String) {
(
match core_limitation_str.parse::<usize>() {
Ok(c) => num_cpus::get() >= c,
Err(_) => abort_call_site!("core limitation is incorrect"),
},
format!("because the cpu core less than {}", core_limitation_str),
)
}
#[cfg(not(feature = "runtime"))]
#[proc_macro_attribute]
#[proc_macro_error]
pub fn runtime_cpu_core(_attr: TokenStream, _stream: TokenStream) -> TokenStream {
panic!("should be used with runtime feature")
}
#[cfg(all(feature = "runtime", feature = "resource"))]
#[proc_macro_attribute]
#[proc_macro_error]
pub fn runtime_cpu_core(attr: TokenStream, stream: TokenStream) -> TokenStream {
let attr_str = attr.to_string().replace(' ', "");
let core_limitation = match attr_str.parse::<usize>() {
Ok(c) => c,
Err(_) => abort_call_site!("core limitation is incorrect"),
};
let ItemFn {
attrs,
vis,
sig,
block,
} = parse_macro_input!(stream as ItemFn);
let syn::Signature { ident, .. } = sig.clone();
let check_ident = syn::Ident::new(
&format!("_check_{}", ident.to_string()),
proc_macro2::Span::call_site(),
);
quote::quote! {
fn #check_ident() -> Result<(), libtest_with::Failed> {
if libtest_with::num_cpus::get() >= #core_limitation {
#ident();
Ok(())
} else {
Err(format!("{}because the cpu core less than {}",
libtest_with::RUNTIME_IGNORE_PREFIX, #core_limitation).into())
}
}
#(#attrs)*
#vis #sig #block
}
.into()
}
#[proc_macro_attribute]
#[proc_macro_error]
#[cfg(feature = "resource")]
pub fn phy_core(attr: TokenStream, stream: TokenStream) -> TokenStream {
if is_module(&stream) {
mod_macro(
attr,
parse_macro_input!(stream as ItemMod),
check_cpu_core_condition,
)
} else {
fn_macro(
attr,
parse_macro_input!(stream as ItemFn),
check_phy_core_condition,
)
}
}
#[cfg(feature = "resource")]
fn check_phy_core_condition(core_limitation_str: String) -> (bool, String) {
(
match core_limitation_str.parse::<usize>() {
Ok(c) => num_cpus::get_physical() >= c,
Err(_) => abort_call_site!("physical core limitation is incorrect"),
},
format!(
"because the physical cpu core less than {}",
core_limitation_str
),
)
}
#[cfg(not(feature = "runtime"))]
#[proc_macro_attribute]
#[proc_macro_error]
pub fn runtime_phy_cpu_core(_attr: TokenStream, _stream: TokenStream) -> TokenStream {
panic!("should be used with runtime feature")
}
#[cfg(all(feature = "runtime", feature = "resource"))]
#[proc_macro_attribute]
#[proc_macro_error]
pub fn runtime_phy_cpu_core(attr: TokenStream, stream: TokenStream) -> TokenStream {
let attr_str = attr.to_string().replace(' ', "");
let core_limitation = match attr_str.parse::<usize>() {
Ok(c) => c,
Err(_) => abort_call_site!("physical core limitation is incorrect"),
};
let ItemFn {
attrs,
vis,
sig,
block,
} = parse_macro_input!(stream as ItemFn);
let syn::Signature { ident, .. } = sig.clone();
let check_ident = syn::Ident::new(
&format!("_check_{}", ident.to_string()),
proc_macro2::Span::call_site(),
);
quote::quote! {
fn #check_ident() -> Result<(), libtest_with::Failed> {
if libtest_with::num_cpus::get_physical() >= #core_limitation {
#ident();
Ok(())
} else {
Err(format!("{}because the physical cpu core less than {}",
libtest_with::RUNTIME_IGNORE_PREFIX, #core_limitation).into())
}
}
#(#attrs)*
#vis #sig #block
}
.into()
}
#[proc_macro_attribute]
#[proc_macro_error]
#[cfg(feature = "executable")]
pub fn executable(attr: TokenStream, stream: TokenStream) -> TokenStream {
if is_module(&stream) {
mod_macro(
attr,
parse_macro_input!(stream as ItemMod),
check_executable_condition,
)
} else {
fn_macro(
attr,
parse_macro_input!(stream as ItemFn),
check_executable_condition,
)
}
}
#[cfg(feature = "executable")]
fn check_executable_condition(attr_str: String) -> (bool, String) {
let executables: Vec<&str> = attr_str.split(',').collect();
let mut missing_executables = vec![];
for exe in executables.iter() {
if which(exe.trim_matches('"')).is_err() {
missing_executables.push(exe.to_string());
}
}
let ignore_msg = if missing_executables.len() == 1 {
format!("because executable not found: {}", missing_executables[0])
} else {
format!(
"because following executables not found: \n{}\n",
missing_executables.join("\n")
)
};
(missing_executables.is_empty(), ignore_msg)
}
#[cfg(not(feature = "runtime"))]
#[proc_macro_attribute]
#[proc_macro_error]
pub fn runtime_executable(_attr: TokenStream, _stream: TokenStream) -> TokenStream {
panic!("should be used with runtime feature")
}
#[cfg(all(feature = "runtime", feature = "executable"))]
#[proc_macro_attribute]
#[proc_macro_error]
pub fn runtime_executable(attr: TokenStream, stream: TokenStream) -> TokenStream {
let attr_str = attr.to_string().replace(' ', "");
let executables: Vec<&str> = attr_str.split(',').collect();
let ItemFn {
attrs,
vis,
sig,
block,
} = parse_macro_input!(stream as ItemFn);
let syn::Signature { ident, .. } = sig.clone();
let check_ident = syn::Ident::new(
&format!("_check_{}", ident.to_string()),
proc_macro2::Span::call_site(),
);
quote::quote! {
fn #check_ident() -> Result<(), libtest_with::Failed> {
let mut missing_executables = vec![];
#(
if libtest_with::which::which(#executables).is_err() {
missing_executables.push(#executables);
}
)*
match missing_executables.len() {
0 => {
#ident();
Ok(())
},
1 => Err(
format!("{}because executable {} not found",
libtest_with::RUNTIME_IGNORE_PREFIX, missing_executables[0]
).into()),
_ => Err(
format!("{}because following executables not found:\n{}\n",
libtest_with::RUNTIME_IGNORE_PREFIX, missing_executables.join(", ")
).into()),
}
}
#(#attrs)*
#vis #sig #block
}
.into()
}
#[cfg(not(feature = "runtime"))]
#[proc_macro]
pub fn runner(_input: TokenStream) -> TokenStream {
panic!("should be used with runtime feature")
}
#[cfg(feature = "runtime")]
#[proc_macro]
pub fn runner(input: TokenStream) -> TokenStream {
let input_str = input.to_string();
let mod_names: Vec<syn::Ident> = input_str
.split(",")
.map(|s| syn::Ident::new(s.trim(), proc_macro2::Span::call_site()))
.collect();
quote::quote! {
fn main() {
let args = libtest_with::Arguments::from_args();
let mut tests = Vec::new();
#(
tests.append(&mut #mod_names::_runtime_tests());
)*
libtest_with::run(&args, tests).exit();
}
}
.into()
}
#[cfg(not(feature = "runtime"))]
#[proc_macro_attribute]
#[proc_macro_error]
pub fn module(_attr: TokenStream, _stream: TokenStream) -> TokenStream {
panic!("should be used with runtime feature")
}
#[cfg(feature = "runtime")]
#[proc_macro_attribute]
#[proc_macro_error]
pub fn module(_attr: TokenStream, stream: TokenStream) -> TokenStream {
let ItemMod {
attrs,
vis,
mod_token,
ident,
content,
..
} = parse_macro_input!(stream as ItemMod);
if let Some(content) = content {
let content = content.1;
if crate::utils::has_test_cfg(&attrs) {
abort_call_site!("should not use `#[cfg(test)]` on the mod with `#[test_with::module]`")
} else {
let test_names: Vec<String> = content
.iter()
.filter_map(|c| match c {
Item::Fn(ItemFn {
sig: syn::Signature { ident, .. },
attrs,
..
}) => match crate::utils::test_with_attrs(&attrs) {
(true, true, _) => abort_call_site!(
"should not use #[test] for method in `#[test_with::module]`"
),
(_, true, false) => abort_call_site!(
"use `#[test_with::runtime_*]` for method in `#[test_with::module]`"
),
(false, true, true) => Some(ident.to_string()),
_ => None,
},
_ => None,
})
.collect();
let check_names: Vec<syn::Ident> = test_names
.iter()
.map(|c| {
syn::Ident::new(
&format!("_check_{}", c.to_string()),
proc_macro2::Span::call_site(),
)
})
.collect();
quote::quote! {
#(#attrs)*
#vis #mod_token #ident {
pub fn _runtime_tests() -> Vec<libtest_with::Trial> {
use libtest_with::Trial;
vec![
#(Trial::test(#test_names, #check_names),)*
]
}
#(#content)*
}
}
.into()
}
} else {
abort_call_site!("should use on mod with context")
}
}