mod and;
mod attribute;
mod error;
mod or;
mod prefix;
use std::{collections::VecDeque, time::Duration};
use and::And;
use attribute::ProcessAttribute;
use error::{QueryError, QueryResult};
use or::Or;
use prefix::Prefix;
use regex::Regex;
use crate::{collection::processes::ProcessHarvest, multi_eq_ignore_ascii_case};
const DELIMITER_LIST: [char; 7] = ['=', '>', '<', '!', '(', ')', '\"'];
const COMPARISON_LIST: [&str; 4] = [">", "=", "<", "!="];
trait QueryProcessor {
fn process(query: &mut VecDeque<String>, regex_options: &QueryOptions) -> QueryResult<Self>
where
Self: Sized;
}
fn new_regex(base: &str, regex_options: &QueryOptions) -> QueryResult<Regex> {
let QueryOptions {
whole_word: is_searching_whole_word,
ignore_case: is_ignoring_case,
use_regex: is_searching_with_regex,
} = regex_options;
let escaped_regex: String;
let final_regex_string = &format!(
"{}{}{}{}",
if *is_searching_whole_word { "^" } else { "" },
if *is_ignoring_case { "(?i)" } else { "" },
if !(*is_searching_with_regex) {
escaped_regex = regex::escape(base);
&escaped_regex
} else {
base
},
if *is_searching_whole_word { "$" } else { "" },
);
Ok(Regex::new(final_regex_string)?)
}
#[derive(PartialEq, Eq)]
pub struct QueryOptions {
pub whole_word: bool,
pub ignore_case: bool,
pub use_regex: bool,
}
impl Default for QueryOptions {
fn default() -> Self {
Self {
ignore_case: true,
whole_word: false,
use_regex: false,
}
}
}
pub(crate) fn parse_query(search_query: &str, options: &QueryOptions) -> QueryResult<ProcessQuery> {
fn process_string_to_filter(
query: &mut VecDeque<String>, options: &QueryOptions,
) -> QueryResult<ProcessQuery> {
let lhs = Or::process(query, options)?;
let mut list_of_ors = vec![lhs];
while query.front().is_some() {
list_of_ors.push(Or::process(query, options)?);
}
Ok(ProcessQuery { query: list_of_ors })
}
let mut split_query = VecDeque::new();
let mut in_quotes = false;
search_query.split_whitespace().for_each(|s| {
let mut last = 0;
for (index, matched) in s.match_indices(|x| DELIMITER_LIST.contains(&x)) {
if matched == "\"" {
if last != index {
split_query.push_back(s[last..index].to_owned());
}
split_query.push_back(matched.to_owned());
last = index + matched.len();
in_quotes = !in_quotes;
} else if !in_quotes {
if last != index {
split_query.push_back(s[last..index].to_owned());
}
split_query.push_back(matched.to_owned());
last = index + matched.len();
}
}
if last < s.len() {
split_query.push_back(s[last..].to_owned());
}
});
let mut i = 0;
while i + 1 < split_query.len() {
if split_query[i] == "!" && split_query[i + 1] == "=" {
split_query[i] = "!=".to_owned();
split_query.remove(i + 1);
}
i += 1;
}
process_string_to_filter(&mut split_query, options)
}
#[derive(Debug)]
pub struct ProcessQuery {
query: Vec<Or>,
}
impl ProcessQuery {
pub(crate) fn check(&self, process: &ProcessHarvest, is_using_command: bool) -> bool {
self.query
.iter()
.all(|ok| ok.check(process, is_using_command))
}
}
#[derive(Debug)]
enum PrefixType {
Pid,
CpuPercentage,
MemBytes,
MemPercentage,
ReadPerSecond,
WritePerSecond,
TotalRead,
TotalWrite,
Name,
State,
User,
Time,
#[cfg(unix)]
Nice,
Priority,
#[cfg(feature = "gpu")]
GpuPercentage,
#[cfg(feature = "gpu")]
GpuMemoryBytes,
#[cfg(feature = "gpu")]
GpuMemoryPercentage,
}
impl std::str::FromStr for PrefixType {
type Err = QueryError;
fn from_str(s: &str) -> QueryResult<Self> {
use PrefixType::*;
let mut result = Name;
if multi_eq_ignore_ascii_case!(s, "cpu" | "cpu%") {
result = CpuPercentage;
} else if multi_eq_ignore_ascii_case!(s, "mem" | "mem%") {
result = MemPercentage;
} else if multi_eq_ignore_ascii_case!(s, "memb") {
result = MemBytes;
} else if multi_eq_ignore_ascii_case!(s, "read" | "r/s" | "rps") {
result = ReadPerSecond;
} else if multi_eq_ignore_ascii_case!(s, "write" | "w/s" | "wps") {
result = WritePerSecond;
} else if multi_eq_ignore_ascii_case!(s, "tread" | "t.read") {
result = TotalRead;
} else if multi_eq_ignore_ascii_case!(s, "twrite" | "t.write") {
result = TotalWrite;
} else if multi_eq_ignore_ascii_case!(s, "pid") {
result = Pid;
} else if multi_eq_ignore_ascii_case!(s, "state") {
result = State;
} else if multi_eq_ignore_ascii_case!(s, "user") {
result = User;
} else if multi_eq_ignore_ascii_case!(s, "time") {
result = Time;
} else if multi_eq_ignore_ascii_case!(s, "nice") {
#[cfg(unix)]
{
result = Nice;
}
} else if multi_eq_ignore_ascii_case!(s, "priority") {
result = Priority;
}
#[cfg(feature = "gpu")]
{
if multi_eq_ignore_ascii_case!(s, "gmem") {
result = GpuMemoryBytes;
} else if multi_eq_ignore_ascii_case!(s, "gmem%") {
result = GpuMemoryPercentage;
} else if multi_eq_ignore_ascii_case!(s, "gpu%") {
result = GpuPercentage;
}
}
Ok(result)
}
}
#[derive(Debug)]
enum QueryComparison {
Equal,
NotEqual,
Less,
Greater,
LessOrEqual,
GreaterOrEqual,
}
#[derive(Debug)]
struct NumericalQuery {
condition: QueryComparison,
value: f64,
}
impl NumericalQuery {
fn check<I: Into<f64>>(&self, lhs: I) -> bool {
let lhs: f64 = lhs.into();
let rhs: f64 = self.value;
match self.condition {
QueryComparison::Equal => (lhs - rhs).abs() < f64::EPSILON,
QueryComparison::NotEqual => (lhs - rhs).abs() >= f64::EPSILON,
QueryComparison::Less => lhs < rhs,
QueryComparison::Greater => lhs > rhs,
QueryComparison::LessOrEqual => lhs <= rhs,
QueryComparison::GreaterOrEqual => lhs >= rhs,
}
}
}
#[derive(Debug)]
struct TimeQuery {
condition: QueryComparison,
duration: Duration,
}
impl TimeQuery {
fn check(&self, lhs: Duration) -> bool {
let rhs = self.duration;
match self.condition {
QueryComparison::Equal => lhs == rhs,
QueryComparison::NotEqual => lhs != rhs,
QueryComparison::Less => lhs < rhs,
QueryComparison::Greater => lhs > rhs,
QueryComparison::LessOrEqual => lhs <= rhs,
QueryComparison::GreaterOrEqual => lhs >= rhs,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn simple_process(name: &str) -> ProcessHarvest {
ProcessHarvest {
name: name.into(),
..Default::default()
}
}
fn parse_query_no_options(query: &str) -> QueryResult<ProcessQuery> {
parse_query(
query,
&QueryOptions {
whole_word: false,
ignore_case: false,
use_regex: false,
},
)
}
#[test]
fn basic_query() {
let query = parse_query_no_options("test").unwrap();
let exact_match = simple_process("test");
let contains = simple_process("test string");
let invalid = simple_process("no");
assert!(query.check(&exact_match, false));
assert!(query.check(&contains, false));
assert!(!query.check(&invalid, false));
}
#[test]
fn basic_or_query() {
let query = parse_query_no_options("a or b").unwrap();
let a = simple_process("a");
let b = simple_process("b");
let invalid = simple_process("c");
assert!(query.check(&a, false));
assert!(query.check(&b, false));
assert!(!query.check(&invalid, false));
}
#[test]
fn basic_and_query() {
let query = parse_query_no_options("a and b").unwrap();
let a = simple_process("a");
let b = simple_process("b");
let c = simple_process("c");
let a_and_b = simple_process("a and b");
assert!(!query.check(&a, false));
assert!(!query.check(&b, false));
assert!(!query.check(&c, false));
assert!(query.check(&a_and_b, false));
}
#[test]
fn implied_and_query() {
let query = parse_query_no_options("a b c").unwrap();
let a = simple_process("a");
let b = simple_process("b");
let c = simple_process("c");
let all = simple_process("a b c");
assert!(!query.check(&a, false));
assert!(!query.check(&b, false));
assert!(!query.check(&c, false));
assert!(query.check(&all, false));
}
#[test]
fn quoted_query() {
let query = parse_query_no_options("a \"or\" b").unwrap();
let a = simple_process("a");
let b = simple_process("b");
let or = simple_process("or");
let valid = simple_process("a \"or\" b"); let valid_2 = simple_process("a or b");
let valid_3 = simple_process("a \"or\" b \"or\" c");
assert!(!query.check(&a, false));
assert!(!query.check(&b, false));
assert!(!query.check(&or, false));
assert!(query.check(&valid, false));
assert!(query.check(&valid_2, false));
assert!(query.check(&valid_3, false));
}
#[test]
fn quoted_multi_word_query() {
let query = parse_query_no_options("\"a or b\"").unwrap();
let a = simple_process("a");
let b = simple_process("b");
let or = simple_process("or");
let valid = simple_process("a or b");
let valid_2 = simple_process("a or b \"or\" c");
let invalid_no_regex = simple_process("a \"or\" b");
assert!(!query.check(&a, false));
assert!(!query.check(&b, false));
assert!(!query.check(&or, false));
assert!(query.check(&valid, false));
assert!(query.check(&valid_2, false));
assert!(!query.check(&invalid_no_regex, false));
}
#[test]
fn basic_cpu_query() {
let query = parse_query_no_options("cpu > 50").unwrap();
let mut over = simple_process("a");
over.cpu_usage_percent = 60.0;
let mut under = simple_process("a");
under.cpu_usage_percent = 40.0;
let mut exact = simple_process("a");
exact.cpu_usage_percent = 50.0;
assert!(query.check(&over, false));
assert!(!query.check(&under, false));
assert!(!query.check(&exact, false));
}
#[test]
fn basic_mem_query() {
let query = parse_query_no_options("memb > 1 GiB").unwrap();
let mut over = simple_process("a");
over.mem_usage = 2 * 1024 * 1024 * 1024;
let mut under = simple_process("a");
under.mem_usage = 0;
let mut exact = simple_process("a");
exact.mem_usage = 1024 * 1024 * 1024;
assert!(query.check(&over, false));
assert!(!query.check(&under, false));
assert!(!query.check(&exact, false));
}
#[test]
fn nested_query_1() {
let query = parse_query_no_options("(a or b) and (c or a)").unwrap();
let a = simple_process("a");
let b = simple_process("b");
let c = simple_process("c");
let d = simple_process("d");
assert!(query.check(&a, false));
assert!(!query.check(&b, false));
assert!(!query.check(&c, false));
assert!(!query.check(&d, false));
}
#[test]
fn nested_query_2() {
let query = parse_query_no_options("(cpu > 10 or cpu < 5) and (c or a)").unwrap();
let mut a_valid_1 = simple_process("a");
a_valid_1.cpu_usage_percent = 100.0;
let mut a_valid_2 = simple_process("a");
a_valid_2.cpu_usage_percent = 1.0;
let mut a_invalid = simple_process("a");
a_invalid.cpu_usage_percent = 6.0;
let mut c = simple_process("c");
c.cpu_usage_percent = 50.0;
let mut b = simple_process("b");
b.cpu_usage_percent = 50.0;
let mut d = simple_process("d");
d.cpu_usage_percent = 6.0;
assert!(query.check(&a_valid_1, false));
assert!(query.check(&a_valid_2, false));
assert!(query.check(&c, false));
assert!(!query.check(&a_invalid, false));
assert!(!query.check(&b, false));
assert!(!query.check(&d, false));
}
#[test]
fn nested_query_3() {
let query =
parse_query_no_options("((cpu > 10 or cpu < 5) or d) and ((c or a) or d)").unwrap();
let mut a_valid_1 = simple_process("a");
a_valid_1.cpu_usage_percent = 100.0;
let mut a_valid_2 = simple_process("a");
a_valid_2.cpu_usage_percent = 1.0;
let mut a_invalid = simple_process("a");
a_invalid.cpu_usage_percent = 6.0;
let mut c = simple_process("c");
c.cpu_usage_percent = 50.0;
let mut b = simple_process("b");
b.cpu_usage_percent = 50.0;
let mut d = simple_process("d");
d.cpu_usage_percent = 6.0;
assert!(query.check(&a_valid_1, false));
assert!(query.check(&a_valid_2, false));
assert!(query.check(&c, false));
assert!(query.check(&d, false));
assert!(!query.check(&a_invalid, false));
assert!(!query.check(&b, false));
}
#[test]
fn ambiguous_precedence_1() {
let query = parse_query_no_options("a and b or c").unwrap();
let a = simple_process("a");
let b = simple_process("b");
let c = simple_process("c");
assert!(!query.check(&a, false));
assert!(!query.check(&b, false));
assert!(query.check(&c, false));
}
#[test]
fn ambiguous_precedence_2() {
let query = parse_query_no_options("a or b and c").unwrap();
let a = simple_process("a");
let b = simple_process("b");
let c = simple_process("c");
assert!(query.check(&a, false));
assert!(!query.check(&b, false));
assert!(!query.check(&c, false));
}
#[test]
fn parse_complicated_query() {
parse_query_no_options(
"cpu > 10.5 AND (memb = 1 MiB || state = sleeping) and (a or b) and (read >= 0 or write >= 0)",
)
.unwrap();
}
#[test]
fn parse_empty_quotes() {
parse_query_no_options("\"\"").unwrap();
parse_query_no_options("\"\"\"\"").unwrap();
parse_query_no_options("\"\" OR \"\"").unwrap();
}
#[test]
fn search_empty_quotes() {
let a = parse_query_no_options("\"\"").unwrap();
let b = parse_query_no_options("\"\" OR test").unwrap();
let process = simple_process("test");
assert!(a.check(&process, false));
assert!(b.check(&process, false));
}
#[test]
fn parse_unfinished_quotes() {
parse_query_no_options("\"").unwrap_err();
parse_query_no_options("\"asdf").unwrap_err();
parse_query_no_options("asdf\"").unwrap_err();
}
#[test]
fn parse_nested_closing_quotes() {
parse_query_no_options("state = \"test\"").unwrap();
parse_query_no_options("state = \"2 words\"").unwrap();
parse_query_no_options("(memb = 1 MiB || state = \"test\")").unwrap();
parse_query_no_options("(memb = 1 MiB || state = \"2 words\")").unwrap();
}
#[test]
fn invalid_uncompleted_queries_1() {
parse_query_no_options("state =").unwrap_err();
parse_query_no_options("a or").unwrap_err();
parse_query_no_options("a >").unwrap_err();
}
#[track_caller]
fn invalid_lhs_rhs(op: &str) {
parse_query_no_options(&format!("a {op} asdf = 100")).unwrap_err();
parse_query_no_options(&format!("asdf = 100 {op} b")).unwrap_err();
parse_query_no_options(&format!("a {op} asdf = 100 {op} b")).unwrap_err();
parse_query_no_options(&format!("asdf = 100 {op} bsdf = \"")).unwrap_err();
parse_query_no_options(&format!("a {op} bsdf = \"")).unwrap_err();
}
#[test]
fn invalid_or() {
invalid_lhs_rhs("OR");
invalid_lhs_rhs("||");
}
#[test]
fn invalid_and() {
invalid_lhs_rhs("AND");
invalid_lhs_rhs("&&");
invalid_lhs_rhs("");
}
#[test]
fn test_command_check() {
let query = parse_query_no_options("command").unwrap();
let mut process_a = simple_process("test");
process_a.command = "command".into();
let mut process_b = simple_process("test");
process_b.command = "no".into();
assert!(query.check(&process_a, true));
assert!(!query.check(&process_b, true));
}
#[test]
fn test_non_ascii_only_1() {
let query = parse_query_no_options("食").unwrap();
let process_a = simple_process("施氏食獅史");
let process_b = simple_process("沒有");
assert!(query.check(&process_a, false));
assert!(!query.check(&process_b, false));
}
#[test]
fn test_non_ascii_only_2() {
let query = parse_query_no_options("परीक्षा").unwrap();
let process_a = simple_process("परीक्षा");
let process_b = simple_process("उपलब्ध नहीं है");
assert!(query.check(&process_a, false));
assert!(!query.check(&process_b, false));
}
#[test]
fn test_non_ascii_only_3() {
let query = parse_query_no_options("🇨🇦").unwrap();
let process_a = simple_process("🇨🇦");
let process_b = simple_process("❤️🇨🇦❤️");
let process_c = simple_process("❤️");
assert!(query.check(&process_a, false));
assert!(query.check(&process_b, false));
assert!(!query.check(&process_c, false));
}
#[test]
fn test_non_ascii_only_4() {
let query = parse_query_no_options("獅 or 狮").unwrap();
let process_a = simple_process("施氏食獅史");
let process_b = simple_process("施氏食狮史");
let process_c = simple_process("沒有");
assert!(query.check(&process_a, false));
assert!(query.check(&process_b, false));
assert!(!query.check(&process_c, false));
}
#[test]
fn test_invalid_non_ascii() {
parse_query_no_options("cpu = 食").unwrap_err();
}
#[test]
fn test_mixed_unicode() {
let query = parse_query_no_options("食 or test").unwrap();
let process_a = simple_process("施氏食獅史");
let process_b = simple_process("test");
let process_c = simple_process("施氏食獅史test");
let process_d = simple_process("沒有");
let process_e = simple_process("nope");
assert!(query.check(&process_a, false));
assert!(query.check(&process_b, false));
assert!(query.check(&process_c, false));
assert!(!query.check(&process_d, false));
assert!(!query.check(&process_e, false));
}
#[test]
fn test_regex_1() {
let query = parse_query(
"(a|b)",
&QueryOptions {
whole_word: false,
ignore_case: true,
use_regex: true,
},
)
.unwrap();
let process_a = simple_process("abc");
let process_b = simple_process("test");
assert!(query.check(&process_a, false));
assert!(!query.check(&process_b, false));
}
#[test]
fn test_regex_2() {
let query = parse_query(
"^a.*z$",
&QueryOptions {
whole_word: false,
ignore_case: true,
use_regex: true,
},
)
.unwrap();
let process_a = simple_process("atoz");
let process_b = simple_process("atob");
let process_c = simple_process("ytoz");
let process_d = simple_process("atozoops");
assert!(query.check(&process_a, false));
assert!(!query.check(&process_b, false));
assert!(!query.check(&process_c, false));
assert!(!query.check(&process_d, false));
}
#[test]
fn test_whole_word_1() {
let query = parse_query(
"test",
&QueryOptions {
whole_word: true,
ignore_case: true,
use_regex: false,
},
)
.unwrap();
let process_a = simple_process("test");
let process_b = simple_process("testa");
let process_c = simple_process("atest");
assert!(query.check(&process_a, false));
assert!(!query.check(&process_b, false));
assert!(!query.check(&process_c, false));
}
#[test]
fn test_case_sensitive_1() {
let query = parse_query(
"tEsT",
&QueryOptions {
whole_word: false,
ignore_case: false,
use_regex: false,
},
)
.unwrap();
let process_a = simple_process("tEsT");
let process_b = simple_process("tEsT a");
let process_c = simple_process("a tEsT");
assert!(query.check(&process_a, false));
assert!(query.check(&process_b, false));
assert!(query.check(&process_c, false));
let process_d = simple_process("test");
let process_e = simple_process("test a");
let process_f = simple_process("a test");
assert!(!query.check(&process_d, false));
assert!(!query.check(&process_e, false));
assert!(!query.check(&process_f, false));
}
#[cfg(feature = "gpu")]
#[test]
fn test_gpu_queries() {
let mem = parse_query_no_options("gmem > 50 b").unwrap();
let mem_percent = parse_query_no_options("gmem% = 50").unwrap();
let use_percent = parse_query_no_options("gpu% = 50").unwrap();
let mut process_a = simple_process("test");
process_a.gpu_mem = 100;
process_a.gpu_mem_percent = 50.0;
process_a.gpu_util = 50;
assert!(mem.check(&process_a, false));
assert!(mem_percent.check(&process_a, false));
assert!(use_percent.check(&process_a, false));
let mut process_b = simple_process("test");
process_b.gpu_mem = 0;
process_b.gpu_mem_percent = 10.0;
process_b.gpu_util = 10;
assert!(!mem.check(&process_b, false));
assert!(!mem_percent.check(&process_b, false));
assert!(!use_percent.check(&process_b, false));
}
#[cfg(feature = "gpu")]
#[test]
fn test_invalid_gpu_queries() {
parse_query_no_options("gmem = \"what\"").unwrap_err();
parse_query_no_options("gmem% = \"the\"").unwrap_err();
parse_query_no_options("gpu% = \"heck\"").unwrap_err();
}
#[test]
fn numerical_not_equal_query() {
let query = parse_query_no_options("cpu != 50").unwrap();
let mut equal = simple_process("a");
equal.cpu_usage_percent = 50.0;
let mut other = simple_process("a");
other.cpu_usage_percent = 40.0;
assert!(!query.check(&equal, false));
assert!(query.check(&other, false));
}
#[test]
fn not_equal_tokenizes_without_spaces() {
let spaced = parse_query_no_options("cpu != 50").unwrap();
let joined = parse_query_no_options("cpu!=50").unwrap();
let mut proc = simple_process("a");
proc.cpu_usage_percent = 40.0;
assert!(spaced.check(&proc, false));
assert!(joined.check(&proc, false));
}
#[test]
fn time_not_equal_query() {
let query = parse_query_no_options("time != 1h").unwrap();
let mut equal = simple_process("a");
equal.time = Duration::from_secs(3600);
let mut other = simple_process("a");
other.time = Duration::from_secs(60);
assert!(!query.check(&equal, false));
assert!(query.check(&other, false));
}
#[test]
fn state_not_equal_query() {
let query = parse_query_no_options("state != sleeping").unwrap();
let mut sleeping = simple_process("a");
sleeping.process_state.0 = "sleeping";
let mut running = simple_process("a");
running.process_state.0 = "running";
assert!(!query.check(&sleeping, false));
assert!(query.check(&running, false));
}
#[test]
fn mem_bytes_not_equal_with_unit() {
let query = parse_query_no_options("memb != 1 GiB").unwrap();
let mut equal = simple_process("a");
equal.mem_usage = 1024 * 1024 * 1024;
let mut other = simple_process("a");
other.mem_usage = 0;
assert!(!query.check(&equal, false));
assert!(query.check(&other, false));
}
#[test]
fn negate_group_query() {
let query = parse_query_no_options("!(cpu > 5 or a)").unwrap();
let mut high_cpu = simple_process("b");
high_cpu.cpu_usage_percent = 10.0;
let name_match = simple_process("a");
let mut low_cpu_no_match = simple_process("b");
low_cpu_no_match.cpu_usage_percent = 1.0;
assert!(!query.check(&high_cpu, false));
assert!(!query.check(&name_match, false));
assert!(query.check(&low_cpu_no_match, false));
}
#[test]
fn negate_bare_name_query() {
let query = parse_query_no_options("!firefox").unwrap();
let firefox = simple_process("firefox");
let other = simple_process("btm");
assert!(!query.check(&firefox, false));
assert!(query.check(&other, false));
}
#[test]
fn negate_quoted_name_query() {
let query = parse_query_no_options("!\"a or b\"").unwrap();
let exact = simple_process("a or b");
let other = simple_process("c");
assert!(!query.check(&exact, false));
assert!(query.check(&other, false));
}
#[test]
fn double_negate_query_a() {
let query = parse_query_no_options("!!firefox").unwrap();
let firefox = simple_process("firefox");
let other = simple_process("btm");
assert!(query.check(&firefox, false));
assert!(!query.check(&other, false));
}
#[test]
fn double_negate_query_b() {
let query = parse_query_no_options("!!(cpu > 5) ").unwrap();
let mut big = simple_process("big");
big.cpu_usage_percent = 10.0;
let mut small = simple_process("small");
small.cpu_usage_percent = 1.0;
assert!(query.check(&big, false));
assert!(!query.check(&small, false));
}
#[test]
fn negate_with_boolean_operators() {
let query = parse_query_no_options("!firefox and cpu > 5").unwrap();
let mut firefox = simple_process("firefox");
firefox.cpu_usage_percent = 10.0;
let mut other_high = simple_process("btm");
other_high.cpu_usage_percent = 10.0;
let mut other_low = simple_process("btm");
other_low.cpu_usage_percent = 1.0;
assert!(!query.check(&firefox, false));
assert!(query.check(&other_high, false));
assert!(!query.check(&other_low, false));
}
#[test]
fn lone_bang_is_rejected() {
parse_query_no_options("!").unwrap_err();
parse_query_no_options("! ").unwrap_err();
parse_query_no_options("!)").unwrap_err();
parse_query_no_options("(!)").unwrap_err();
}
#[test]
fn user_equals_quoted_bang() {
let query = parse_query_no_options("user = \"!\"").unwrap();
let mut with_bang = simple_process("a");
with_bang.user = Some("!".into());
let mut other = simple_process("a");
other.user = Some("root".into());
assert!(query.check(&with_bang, false));
assert!(!query.check(&other, false));
}
#[test]
fn user_negated_equals_quoted_bang() {
let query = parse_query_no_options("user != \"!\"").unwrap();
let mut with_bang = simple_process("a");
with_bang.user = Some("!".into());
let mut other = simple_process("a");
other.user = Some("root".into());
assert!(!query.check(&with_bang, false));
assert!(query.check(&other, false));
}
#[test]
fn quoted_bang_matches_literal() {
let query = parse_query_no_options("\"!\"").unwrap();
let with_bang = simple_process("foo!");
let without = simple_process("foo");
assert!(query.check(&with_bang, false));
assert!(!query.check(&without, false));
}
#[test]
fn quoted_angle_brackets_match_literal() {
let query = parse_query_no_options("\"Thread<15>\"").unwrap();
let matching = simple_process("Thread<15>");
let non_matching = simple_process("Thread");
assert!(query.check(&matching, false));
assert!(!query.check(&non_matching, false));
}
#[test]
fn quoted_equals_matches_literal() {
let query = parse_query_no_options("\"a=b\"").unwrap();
let matching = simple_process("a=b");
let non_matching = simple_process("ab");
assert!(query.check(&matching, false));
assert!(!query.check(&non_matching, false));
}
#[test]
fn quoted_not_equals_matches_literal() {
let query = parse_query_no_options("\"a!=b\"").unwrap();
let matching = simple_process("a!=b");
let non_matching = simple_process("ab");
assert!(query.check(&matching, false));
assert!(!query.check(&non_matching, false));
}
#[test]
fn quoted_parens_match_literal() {
let query = parse_query_no_options("\"a(b)\"").unwrap();
let matching = simple_process("a(b)");
let non_matching = simple_process("ab");
assert!(query.check(&matching, false));
assert!(!query.check(&non_matching, false));
}
#[test]
fn not_equal_missing_value_is_rejected() {
parse_query_no_options("user !=").unwrap_err();
parse_query_no_options("state !=").unwrap_err();
parse_query_no_options("time !=").unwrap_err();
}
#[test]
fn misc_invalid_bang_search() {
parse_query_no_options("user = !").unwrap_err();
parse_query_no_options("user != !").unwrap_err();
parse_query_no_options("pid = !").unwrap_err();
parse_query_no_options("state !").unwrap_err();
parse_query_no_options("!cpu > 5").unwrap_err();
parse_query_no_options("! cpu > 5").unwrap_err();
}
#[test]
fn reversed_bang_equal_is_rejected() {
parse_query_no_options("cpu =!").unwrap_err();
parse_query_no_options("cpu = !").unwrap_err();
parse_query_no_options("cpu = ! 5").unwrap_err();
}
#[test]
fn not_equal_non_numeric_rhs_is_rejected() {
parse_query_no_options("cpu != abc").unwrap_err();
}
}