diff --git a/.github/workflows/correctness.yml b/.github/workflows/correctness.yml index 7ba0178..bf9f693 100644 --- a/.github/workflows/correctness.yml +++ b/.github/workflows/correctness.yml @@ -160,9 +160,3 @@ jobs: ~/.cargo/git target key: ${{ runner.os }}-cargo-correctness-${{ hashFiles('**/Cargo.lock', '**/Cargo.toml') }} - - - name: Run Rust pattern matching tests - working-directory: asap-common/tests/rust_pattern_matching - run: cargo test --release - env: - RUSTC_WRAPPER: sccache diff --git a/Cargo.lock b/Cargo.lock index e022aa2..9042bfd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3508,20 +3508,6 @@ dependencies = [ "regex", ] -[[package]] -name = "promql_pattern_matching_tests" -version = "0.1.0" -dependencies = [ - "chrono", - "promql-parser", - "promql_utilities", - "serde", - "serde_json", - "tokio", - "tracing", - "tracing-subscriber", -] - [[package]] name = "promql_token_matching_tests" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 0b1e9c8..89056ff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,6 @@ members = [ "asap-common/dependencies/rs/elastic_dsl_utilities", "asap-common/dependencies/rs/sketch_db_common", "asap-common/dependencies/rs/datafusion_summary_library", - "asap-common/tests/rust_pattern_matching", "asap-common/tests/compare_matched_tokens/rust_tests", "asap-common/tests/compare_patterns", "asap-query-engine", diff --git a/asap-common/dependencies/rs/promql_utilities/src/ast_matching/promql_pattern.rs b/asap-common/dependencies/rs/promql_utilities/src/ast_matching/promql_pattern.rs index 625fee5..18874e4 100644 --- a/asap-common/dependencies/rs/promql_utilities/src/ast_matching/promql_pattern.rs +++ b/asap-common/dependencies/rs/promql_utilities/src/ast_matching/promql_pattern.rs @@ -915,3 +915,448 @@ impl AggregationModifier { // } // } } + +// ============================================================================= +// Tests migrated from asap-common/tests/{compare_matched_tokens, +// rust_pattern_matching, compare_patterns} binary runners. +// +// The PatternTester struct and all build_* methods below are copied verbatim +// from compare_matched_tokens/rust_tests/src/pattern_tests.rs. Only the +// main() harness has been replaced with individual #[test] functions, and +// assertions come from the test_data/promql_queries.json file that was used +// by the binary runner. +// ============================================================================= +#[cfg(test)] +mod tests { + use super::*; + use crate::ast_matching::PromQLPatternBuilder; + use promql_parser::parser as promql; + use serde_json::Value; + + // ------------------------------------------------------------------ + // PatternTester — copied from compare_matched_tokens/rust_tests/src/pattern_tests.rs + // ------------------------------------------------------------------ + + struct PatternTester { + patterns: HashMap>, + } + + impl PatternTester { + fn new() -> Self { + let mut patterns = HashMap::new(); + + // ONLY_TEMPORAL patterns + let temporal_patterns = vec![ + // Rate pattern + PromQLPattern::new(Self::build_rate_pattern()), + // Quantile over time pattern + PromQLPattern::new(Self::build_quantile_over_time_pattern()), + ]; + + // ONLY_SPATIAL patterns + let spatial_patterns = vec![ + // Sum aggregation pattern + PromQLPattern::new(Self::build_sum_pattern()), + // Simple metric pattern + PromQLPattern::new(Self::build_metric_pattern()), + ]; + + // ONE_TEMPORAL_ONE_SPATIAL patterns + let combined_patterns = vec![ + // Aggregation of single-arg temporal functions + PromQLPattern::new(Self::build_one_temporal_one_spatial_pattern()), + // Aggregation of quantile_over_time (2-arg) + PromQLPattern::new(Self::build_combined_quantile_pattern()), + ]; + + patterns.insert("ONLY_SPATIAL".to_string(), spatial_patterns); + patterns.insert("ONLY_TEMPORAL".to_string(), temporal_patterns); + patterns.insert("ONE_TEMPORAL_ONE_SPATIAL".to_string(), combined_patterns); + + Self { patterns } + } + + fn classify_query(&self, query: &str) -> Option<(String, PromQLMatchResult)> { + let ast = promql::parse(query).expect("Failed to parse query"); + + for (pattern_type, pattern_list) in &self.patterns { + for pattern in pattern_list { + let match_result = pattern.matches(&ast); + if match_result.matches { + let final_type = if pattern_type == "ONLY_SPATIAL" { + if match_result.tokens.contains_key("aggregation") { + pattern_type.clone() + } else if match_result.tokens.contains_key("metric") { + "ONLY_VECTOR".to_string() + } else { + pattern_type.clone() + } + } else { + pattern_type.clone() + }; + return Some((final_type, match_result)); + } + } + } + None + } + + fn build_rate_pattern() -> Option> { + let ms = PromQLPatternBuilder::matrix_selector( + PromQLPatternBuilder::metric(None, None, None, Some("metric")), + None, + Some("range_vector"), + ); + + let args: Vec>> = vec![ms]; + + PromQLPatternBuilder::function( + vec![ + "rate", + "increase", + "avg_over_time", + "sum_over_time", + "count_over_time", + "min_over_time", + "max_over_time", + ], + args, + Some("function"), + None, + ) + } + + fn build_quantile_over_time_pattern() -> Option> { + let num = PromQLPatternBuilder::number(None, None); + let ms = PromQLPatternBuilder::matrix_selector( + PromQLPatternBuilder::metric(None, None, None, Some("metric")), + None, + Some("range_vector"), + ); + + let args: Vec>> = vec![num, ms]; + + PromQLPatternBuilder::function( + vec!["quantile_over_time"], + args, + Some("function"), + Some("function_args"), + ) + } + + fn build_sum_pattern() -> Option> { + PromQLPatternBuilder::aggregation( + vec!["sum", "count", "avg", "min", "max"], + PromQLPatternBuilder::metric(None, None, None, Some("metric")), + None, + None, + None, + Some("aggregation"), + ) + } + + fn build_metric_pattern() -> Option> { + PromQLPatternBuilder::metric(None, None, None, Some("metric")) + } + + fn build_one_temporal_one_spatial_pattern() -> Option> { + let ms = PromQLPatternBuilder::matrix_selector( + PromQLPatternBuilder::metric(None, None, None, Some("metric")), + None, + Some("range_vector"), + ); + + let func_args: Vec>> = vec![ms]; + + let func = PromQLPatternBuilder::function( + vec![ + "sum_over_time", + "count_over_time", + "avg_over_time", + "min_over_time", + "max_over_time", + "rate", + "increase", + ], + func_args, + Some("function"), + None, + ); + + PromQLPatternBuilder::aggregation( + vec!["sum", "count", "avg", "quantile", "min", "max"], + func, + None, + None, + None, + Some("aggregation"), + ) + } + + fn build_combined_quantile_pattern() -> Option> { + let num = PromQLPatternBuilder::number(None, None); + let ms = PromQLPatternBuilder::matrix_selector( + PromQLPatternBuilder::metric(None, None, None, Some("metric")), + None, + Some("range_vector"), + ); + let func_args: Vec>> = vec![num, ms]; + let func = PromQLPatternBuilder::function( + vec!["quantile_over_time"], + func_args, + Some("function"), + None, + ); + PromQLPatternBuilder::aggregation( + vec!["sum", "count", "avg", "quantile", "min", "max"], + func, + None, + None, + None, + Some("aggregation"), + ) + } + + #[allow(dead_code)] + fn build_sum_rate_pattern() -> Option> { + let ms = PromQLPatternBuilder::matrix_selector( + PromQLPatternBuilder::metric(None, None, None, Some("metric")), + None, + Some("range_vector"), + ); + + let func_args: Vec>> = vec![ms]; + + let func = PromQLPatternBuilder::function( + vec!["rate", "increase"], + func_args, + Some("function"), + None, + ); + + PromQLPatternBuilder::aggregation( + vec!["sum", "count", "avg", "min", "max"], + func, + None, + None, + None, + Some("aggregation"), + ) + } + } + + // ------------------------------------------------------------------ + // Tests from compare_matched_tokens/test_data/promql_queries.json + // ------------------------------------------------------------------ + + #[test] + fn temporal_rate_basic() { + let tester = PatternTester::new(); + let (cat, result) = tester + .classify_query("rate(http_requests_total{job=\"api\"}[5m])") + .unwrap(); + assert_eq!(cat, "ONLY_TEMPORAL"); + assert_eq!(result.get_metric_name().unwrap(), "http_requests_total"); + assert_eq!(result.get_function_name().unwrap(), "rate"); + assert_eq!(result.get_range_duration().unwrap(), Duration::minutes(5)); + let labels = &result.tokens["metric"].metric.as_ref().unwrap().labels; + assert_eq!(labels.get("job").unwrap(), "api"); + } + + #[test] + fn temporal_increase_basic() { + let tester = PatternTester::new(); + let (cat, result) = tester + .classify_query("increase(http_requests_total[1h])") + .unwrap(); + assert_eq!(cat, "ONLY_TEMPORAL"); + assert_eq!(result.get_metric_name().unwrap(), "http_requests_total"); + assert_eq!(result.get_function_name().unwrap(), "increase"); + assert_eq!(result.get_range_duration().unwrap(), Duration::hours(1)); + } + + #[test] + fn temporal_quantile_over_time() { + let tester = PatternTester::new(); + let (cat, result) = tester + .classify_query("quantile_over_time(0.95, cpu_usage{instance=\"host1\"}[10m])") + .unwrap(); + assert_eq!(cat, "ONLY_TEMPORAL"); + assert_eq!(result.get_metric_name().unwrap(), "cpu_usage"); + assert_eq!(result.get_function_name().unwrap(), "quantile_over_time"); + assert_eq!(result.get_range_duration().unwrap(), Duration::minutes(10)); + let labels = &result.tokens["metric"].metric.as_ref().unwrap().labels; + assert_eq!(labels.get("instance").unwrap(), "host1"); + } + + #[test] + fn temporal_avg_over_time() { + let tester = PatternTester::new(); + let (cat, result) = tester + .classify_query("avg_over_time(memory_bytes[30m])") + .unwrap(); + assert_eq!(cat, "ONLY_TEMPORAL"); + assert_eq!(result.get_metric_name().unwrap(), "memory_bytes"); + assert_eq!(result.get_function_name().unwrap(), "avg_over_time"); + assert_eq!(result.get_range_duration().unwrap(), Duration::minutes(30)); + } + + #[test] + fn spatial_sum_aggregation() { + let tester = PatternTester::new(); + let (cat, result) = tester + .classify_query("sum(http_requests_total{job=\"api\"})") + .unwrap(); + assert_eq!(cat, "ONLY_SPATIAL"); + assert_eq!(result.get_metric_name().unwrap(), "http_requests_total"); + assert_eq!(result.get_aggregation_op().unwrap(), "sum"); + let labels = &result.tokens["metric"].metric.as_ref().unwrap().labels; + assert_eq!(labels.get("job").unwrap(), "api"); + } + + #[test] + fn spatial_avg_aggregation() { + let tester = PatternTester::new(); + let (cat, result) = tester + .classify_query("avg by (instance) (cpu_usage)") + .unwrap(); + assert_eq!(cat, "ONLY_SPATIAL"); + assert_eq!(result.get_metric_name().unwrap(), "cpu_usage"); + assert_eq!(result.get_aggregation_op().unwrap(), "avg"); + } + + #[test] + fn spatial_count_aggregation() { + let tester = PatternTester::new(); + let (cat, result) = tester.classify_query("count(up{job=\"node\"})").unwrap(); + assert_eq!(cat, "ONLY_SPATIAL"); + assert_eq!(result.get_metric_name().unwrap(), "up"); + assert_eq!(result.get_aggregation_op().unwrap(), "count"); + let labels = &result.tokens["metric"].metric.as_ref().unwrap().labels; + assert_eq!(labels.get("job").unwrap(), "node"); + } + + #[test] + fn combined_sum_of_rate() { + let tester = PatternTester::new(); + let (cat, result) = tester + .classify_query("sum(rate(http_requests_total{job=\"api\"}[5m]))") + .unwrap(); + assert_eq!(cat, "ONE_TEMPORAL_ONE_SPATIAL"); + assert_eq!(result.get_metric_name().unwrap(), "http_requests_total"); + assert_eq!(result.get_function_name().unwrap(), "rate"); + assert_eq!(result.get_aggregation_op().unwrap(), "sum"); + assert_eq!(result.get_range_duration().unwrap(), Duration::minutes(5)); + } + + #[test] + fn combined_avg_of_quantile_over_time() { + let tester = PatternTester::new(); + let (cat, result) = tester + .classify_query("avg(quantile_over_time(0.99, response_time_seconds[15m]))") + .unwrap(); + assert_eq!(cat, "ONE_TEMPORAL_ONE_SPATIAL"); + assert_eq!(result.get_metric_name().unwrap(), "response_time_seconds"); + assert_eq!(result.get_function_name().unwrap(), "quantile_over_time"); + assert_eq!(result.get_aggregation_op().unwrap(), "avg"); + assert_eq!(result.get_range_duration().unwrap(), Duration::minutes(15)); + } + + #[test] + fn combined_sum_of_avg_over_time() { + let tester = PatternTester::new(); + let (cat, result) = tester + .classify_query("sum by (job) (avg_over_time(memory_bytes{env=\"prod\"}[1h]))") + .unwrap(); + assert_eq!(cat, "ONE_TEMPORAL_ONE_SPATIAL"); + assert_eq!(result.get_metric_name().unwrap(), "memory_bytes"); + assert_eq!(result.get_function_name().unwrap(), "avg_over_time"); + assert_eq!(result.get_aggregation_op().unwrap(), "sum"); + assert_eq!(result.get_range_duration().unwrap(), Duration::hours(1)); + let labels = &result.tokens["metric"].metric.as_ref().unwrap().labels; + assert_eq!(labels.get("env").unwrap(), "prod"); + } + + // ------------------------------------------------------------------ + // Tests from rust_pattern_matching binary + // ------------------------------------------------------------------ + + #[test] + fn spatial_of_temporal_sum_of_sum_over_time() { + let tester = PatternTester::new(); + let (cat, result) = tester + .classify_query("sum by (instance, job) (sum_over_time(fake_metric_total[1m]))") + .unwrap(); + assert_eq!(cat, "ONE_TEMPORAL_ONE_SPATIAL"); + assert_eq!(result.get_metric_name().unwrap(), "fake_metric_total"); + assert_eq!(result.get_function_name().unwrap(), "sum_over_time"); + assert_eq!(result.get_aggregation_op().unwrap(), "sum"); + assert_eq!(result.get_range_duration().unwrap(), Duration::minutes(1)); + } + + #[test] + fn spatial_of_temporal_sum_of_count_over_time() { + let tester = PatternTester::new(); + let (cat, result) = tester + .classify_query("sum by (instance, job) (count_over_time(fake_metric_total[1m]))") + .unwrap(); + assert_eq!(cat, "ONE_TEMPORAL_ONE_SPATIAL"); + assert_eq!(result.get_metric_name().unwrap(), "fake_metric_total"); + assert_eq!(result.get_function_name().unwrap(), "count_over_time"); + assert_eq!(result.get_aggregation_op().unwrap(), "sum"); + } + + // ------------------------------------------------------------------ + // Tests from compare_patterns binary (pattern construction) + // ------------------------------------------------------------------ + + #[test] + fn pattern_builds_temporal_rate_increase() { + let ast = PatternTester::build_rate_pattern(); + assert!(ast.is_some()); + assert_eq!(ast.unwrap()["type"], "Call"); + } + + #[test] + fn pattern_builds_temporal_quantile_over_time() { + let ast = PatternTester::build_quantile_over_time_pattern(); + assert!(ast.is_some()); + assert_eq!(ast.unwrap()["type"], "Call"); + } + + #[test] + fn pattern_builds_spatial_aggregation() { + let ast = PatternTester::build_sum_pattern(); + assert!(ast.is_some()); + assert_eq!(ast.unwrap()["type"], "AggregateExpr"); + } + + #[test] + fn pattern_builds_metric() { + let ast = PatternTester::build_metric_pattern(); + assert!(ast.is_some()); + assert_eq!(ast.unwrap()["type"], "VectorSelector"); + } + + #[test] + fn pattern_builds_combined_temporal() { + let ast = PatternTester::build_one_temporal_one_spatial_pattern(); + assert!(ast.is_some()); + assert_eq!(ast.unwrap()["type"], "AggregateExpr"); + } + + #[test] + fn pattern_builds_combined_quantile() { + let ast = PatternTester::build_combined_quantile_pattern(); + assert!(ast.is_some()); + assert_eq!(ast.unwrap()["type"], "AggregateExpr"); + } + + #[test] + fn bare_metric_classified_as_only_vector() { + let tester = PatternTester::new(); + let (cat, result) = tester.classify_query("http_requests_total").unwrap(); + assert_eq!(cat, "ONLY_VECTOR"); + assert_eq!(result.get_metric_name().unwrap(), "http_requests_total"); + } +} diff --git a/asap-common/tests/rust_pattern_matching/Cargo.toml b/asap-common/tests/rust_pattern_matching/Cargo.toml deleted file mode 100644 index a90e2fe..0000000 --- a/asap-common/tests/rust_pattern_matching/Cargo.toml +++ /dev/null @@ -1,18 +0,0 @@ -[package] -name = "promql_pattern_matching_tests" -version.workspace = true -edition.workspace = true - -[[bin]] -name = "test_runner" -path = "src/main.rs" - -[dependencies] -promql-parser.workspace = true -serde_json.workspace = true -serde.workspace = true -tokio.workspace = true -chrono.workspace = true -tracing.workspace = true -tracing-subscriber.workspace = true -promql_utilities.workspace = true diff --git a/asap-common/tests/rust_pattern_matching/src/main.rs b/asap-common/tests/rust_pattern_matching/src/main.rs deleted file mode 100644 index d3ae437..0000000 --- a/asap-common/tests/rust_pattern_matching/src/main.rs +++ /dev/null @@ -1,150 +0,0 @@ -use promql_utilities::ast_matching::{PromQLPattern, PromQLPatternBuilder}; -use promql_utilities::query_logics::enums::QueryPatternType; -use serde_json::Value; -use std::collections::HashMap; - -// Helper functions (these would be closures or separate methods) -fn temporal_pattern( - pattern_type: &str, - blocks: &HashMap>>, -) -> PromQLPattern { - PromQLPattern::new(blocks[pattern_type].clone()) -} - -fn spatial_pattern( - pattern_type: &str, - blocks: &HashMap>>, -) -> PromQLPattern { - PromQLPattern::new(blocks[pattern_type].clone()) -} - -fn spatial_of_temporal_pattern(temporal_block: &Option>) -> PromQLPattern { - let pattern = PromQLPatternBuilder::aggregation( - vec!["sum", "count", "avg", "quantile", "min", "max"], - temporal_block.clone(), - None, - None, - None, - Some("aggregation"), - ); - PromQLPattern::new(pattern) -} - -fn main() { - let mut temporal_pattern_blocks = HashMap::new(); - temporal_pattern_blocks.insert( - "quantile".to_string(), - PromQLPatternBuilder::function( - vec!["quantile_over_time"], - vec![ - PromQLPatternBuilder::number(None, Some("quantile_param")), - PromQLPatternBuilder::matrix_selector( - PromQLPatternBuilder::metric(None, None, None, Some("metric")), - None, - Some("range_vector"), - ), - ], - Some("function"), - Some("function_args"), - ), - ); - - temporal_pattern_blocks.insert( - "generic".to_string(), - PromQLPatternBuilder::function( - vec![ - "sum_over_time", - "count_over_time", - "avg_over_time", - "min_over_time", - "max_over_time", - "increase", - "rate", - ], - vec![PromQLPatternBuilder::matrix_selector( - PromQLPatternBuilder::metric(None, None, None, Some("metric")), - None, - Some("range_vector"), - )], - Some("function"), - Some("function_args"), - ), - ); - - // Create spatial pattern blocks - let mut spatial_pattern_blocks = HashMap::new(); - spatial_pattern_blocks.insert( - "generic".to_string(), - PromQLPatternBuilder::aggregation( - vec!["sum", "count", "avg", "quantile", "min", "max"], - PromQLPatternBuilder::metric(None, None, None, Some("metric")), - None, - None, - None, - Some("aggregation"), - ), - ); - - // Create controller patterns - let mut controller_patterns = HashMap::new(); - controller_patterns.insert( - QueryPatternType::OnlyTemporal, - vec![ - temporal_pattern("quantile", &temporal_pattern_blocks), - temporal_pattern("generic", &temporal_pattern_blocks), - ], - ); - controller_patterns.insert( - QueryPatternType::OnlySpatial, - vec![spatial_pattern("generic", &spatial_pattern_blocks)], - ); - controller_patterns.insert( - QueryPatternType::OneTemporalOneSpatial, - vec![ - spatial_of_temporal_pattern(&temporal_pattern_blocks["quantile"]), - spatial_of_temporal_pattern(&temporal_pattern_blocks["generic"]), - ], - ); - - let queries = vec![ - // "sum_over_time(fake_metric_total[1m])", - // "count_over_time(fake_metric_total[1m])", - // "quantile_over_time(0.95, fake_metric_total[1m])", - // "sum by (instance, job) (fake_metric_total)", - // "count without (instance) (fake_metric_total)", - // "quantile by (instance) (0.95, fake_metric_total)", - // "sum by (instance, job) (rate(fake_metric_total[1m]))", - "sum by (instance, job) (sum_over_time(fake_metric_total[1m]))", - "sum by (instance, job) (count_over_time(fake_metric_total[1m]))", - ]; - - for query in queries { - let ast = match promql_parser::parser::parse(query) { - Ok(parsed) => parsed, - Err(e) => { - eprintln!("Failed to parse query '{}': {}", query, e); - continue; - } - }; - - let mut found_match = None; - for (pattern_type, patterns) in &controller_patterns { - for pattern in patterns { - // println!( - // "Trying pattern type: {:?} for query: {}", - // pattern_type, query - // ); - let match_result = pattern.matches(&ast); - if match_result.matches { - println!("Query: {}; Pattern: {:?}", query, pattern_type); - println!("Match result: {:?}", match_result); - found_match = Some((*pattern_type, match_result)); - break; - } - } - if found_match.is_some() { - break; - } - } - } -}