Skip to content

Commit 16751b7

Browse files
added more tests
1 parent f58e144 commit 16751b7

6 files changed

Lines changed: 437 additions & 2 deletions

File tree

‎crates/e2e/tests/tier1_scan.rs‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,14 @@ fn regex_pred(col_id: usize, pattern: &str) -> Predicate {
5151
})
5252
}
5353

54+
fn notregex_pred(col_id: usize, pattern: &str) -> Predicate {
55+
Predicate(L3Expr::Compare {
56+
left: Box::new(L3Expr::Column(col_id)),
57+
op: CompareOp::NotRegex,
58+
right: Box::new(L3Expr::Literal(L3Scalar::Utf8(pattern.into()))),
59+
})
60+
}
61+
5462
// #1 — bare metric name, no matchers
5563
#[test]
5664
fn q01_bare_scan() {
@@ -102,6 +110,20 @@ fn q04_regex_predicate() {
102110
assert_eq!(lower(r#"http_requests_total{job=~"api.*"}"#), expected);
103111
}
104112

113+
// negative regex matcher; RHS is the pattern string, op is NotRegex
114+
// schema: [ts(0), value(1), job(2)]
115+
#[test]
116+
fn q_notregex_predicate() {
117+
let expected = QueryExpr::Scan {
118+
source: Source::TimeSeries {
119+
metric: "http_requests_total".into(),
120+
},
121+
predicates: vec![notregex_pred(2, "internal.*")],
122+
schema: metric_schema(&["job"]),
123+
};
124+
assert_eq!(lower(r#"http_requests_total{job!~"internal.*"}"#), expected);
125+
}
126+
105127
// multiple matchers — two predicates, canonicalized alphabetically by label name
106128
// labels sorted: job(2) < status(3) → schema: [ts, value, job, status]
107129
// predicates in same alphabetical order: job first, then status

‎crates/e2e/tests/tier2_agg.rs‎

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,77 @@ fn q08_avg_by_datacenter() {
9090
);
9191
}
9292

93+
// multiple group keys — sum by (job, status); alphabetical: job(2), status(3)
94+
#[test]
95+
fn q_sum_by_job_and_status() {
96+
assert_eq!(
97+
lower("sum by (job, status) (http_requests_total)"),
98+
agg(
99+
vec![2, 3],
100+
AggIntent::Sum { col: None },
101+
scan("http_requests_total", &["job", "status"])
102+
),
103+
);
104+
}
105+
106+
// min — cross-series minimum
107+
#[test]
108+
fn q_min_no_group() {
109+
assert_eq!(
110+
lower("min(http_requests_total)"),
111+
agg(
112+
vec![],
113+
AggIntent::Min { col: None },
114+
scan("http_requests_total", &[])
115+
),
116+
);
117+
}
118+
119+
// max — cross-series maximum, grouped by job
120+
#[test]
121+
fn q_max_by_job() {
122+
assert_eq!(
123+
lower("max by (job) (http_requests_total)"),
124+
agg(
125+
vec![2],
126+
AggIntent::Max { col: None },
127+
scan("http_requests_total", &["job"])
128+
),
129+
);
130+
}
131+
132+
// stddev — cross-series standard deviation; PromQL stddev uses population=false in the lowering
133+
#[test]
134+
fn q_stddev_no_group() {
135+
assert_eq!(
136+
lower("stddev(http_requests_total)"),
137+
agg(
138+
vec![],
139+
AggIntent::StdDev {
140+
col: None,
141+
population: false
142+
},
143+
scan("http_requests_total", &[])
144+
),
145+
);
146+
}
147+
148+
// stdvar — cross-series variance
149+
#[test]
150+
fn q_stdvar_no_group() {
151+
assert_eq!(
152+
lower("stdvar(http_requests_total)"),
153+
agg(
154+
vec![],
155+
AggIntent::Variance {
156+
col: None,
157+
population: false
158+
},
159+
scan("http_requests_total", &[])
160+
),
161+
);
162+
}
163+
93164
// #10 — cross-series quantile; no TimeRange node (no range window)
94165
#[test]
95166
fn q10_quantile_cross_series() {

‎crates/e2e/tests/tier3_range.rs‎

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,65 @@ fn q16_sum_over_time() {
8383
);
8484
}
8585

86+
// avg_over_time: per-series mean over the window
87+
#[test]
88+
fn q_avg_over_time() {
89+
assert_eq!(
90+
lower("avg_over_time(http_requests_total[5m])"),
91+
range_agg(300, AggIntent::Avg { col: None }, "http_requests_total"),
92+
);
93+
}
94+
95+
// min_over_time: per-series minimum over the window
96+
#[test]
97+
fn q_min_over_time() {
98+
assert_eq!(
99+
lower("min_over_time(http_requests_total[5m])"),
100+
range_agg(300, AggIntent::Min { col: None }, "http_requests_total"),
101+
);
102+
}
103+
104+
// max_over_time: per-series maximum over the window
105+
#[test]
106+
fn q_max_over_time() {
107+
assert_eq!(
108+
lower("max_over_time(http_requests_total[5m])"),
109+
range_agg(300, AggIntent::Max { col: None }, "http_requests_total"),
110+
);
111+
}
112+
113+
// stddev_over_time: per-series standard deviation over the window; population=false per lowering
114+
#[test]
115+
fn q_stddev_over_time() {
116+
assert_eq!(
117+
lower("stddev_over_time(http_requests_total[5m])"),
118+
range_agg(
119+
300,
120+
AggIntent::StdDev {
121+
col: None,
122+
population: false
123+
},
124+
"http_requests_total"
125+
),
126+
);
127+
}
128+
129+
// stdvar_over_time: per-series variance over the window
130+
#[test]
131+
fn q_stdvar_over_time() {
132+
assert_eq!(
133+
lower("stdvar_over_time(http_requests_total[5m])"),
134+
range_agg(
135+
300,
136+
AggIntent::Variance {
137+
col: None,
138+
population: false
139+
},
140+
"http_requests_total"
141+
),
142+
);
143+
}
144+
86145
// #17 — quantile_over_time: per-series quantile over the window
87146
#[test]
88147
fn q17_quantile_over_time() {

‎crates/e2e/tests/tier4_binary.rs‎

Lines changed: 115 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
use std::time::Duration;
99

1010
use asap_control_core::intent_algebra::{
11-
AggIntent, ArithOp, BinaryOpKind, QueryExpr, Source, VectorMatch, VectorMatchKind,
11+
AggIntent, ArithOp, BinaryOpKind, CompareOp, GroupSide, QueryExpr, Source, VectorGrouping,
12+
VectorMatch, VectorMatchKind,
1213
};
1314
use asap_control_core::types::AccuracyTarget;
1415
use asap_control_lower::lower_promql;
@@ -97,6 +98,119 @@ fn q20_div_two_rates() {
9798
);
9899
}
99100

101+
// comparison ops — filter semantics; each op between two instant vectors
102+
#[test]
103+
fn q_gt_comparison() {
104+
assert_eq!(
105+
lower("http_requests_total > http_errors_total"),
106+
QueryExpr::BinaryOp {
107+
op: BinaryOpKind::Compare(CompareOp::Gt),
108+
lhs: Box::new(scan("http_requests_total", &[])),
109+
rhs: Box::new(scan("http_errors_total", &[])),
110+
vector_match: None,
111+
}
112+
);
113+
}
114+
115+
#[test]
116+
fn q_lt_comparison() {
117+
assert_eq!(
118+
lower("http_requests_total < http_errors_total"),
119+
QueryExpr::BinaryOp {
120+
op: BinaryOpKind::Compare(CompareOp::Lt),
121+
lhs: Box::new(scan("http_requests_total", &[])),
122+
rhs: Box::new(scan("http_errors_total", &[])),
123+
vector_match: None,
124+
}
125+
);
126+
}
127+
128+
#[test]
129+
fn q_ge_comparison() {
130+
assert_eq!(
131+
lower("http_requests_total >= http_errors_total"),
132+
QueryExpr::BinaryOp {
133+
op: BinaryOpKind::Compare(CompareOp::Ge),
134+
lhs: Box::new(scan("http_requests_total", &[])),
135+
rhs: Box::new(scan("http_errors_total", &[])),
136+
vector_match: None,
137+
}
138+
);
139+
}
140+
141+
#[test]
142+
fn q_le_comparison() {
143+
assert_eq!(
144+
lower("http_requests_total <= http_errors_total"),
145+
QueryExpr::BinaryOp {
146+
op: BinaryOpKind::Compare(CompareOp::Le),
147+
lhs: Box::new(scan("http_requests_total", &[])),
148+
rhs: Box::new(scan("http_errors_total", &[])),
149+
vector_match: None,
150+
}
151+
);
152+
}
153+
154+
// ignoring(job) — match on all labels except job; labels are strings, not column ids
155+
#[test]
156+
fn q_add_with_ignoring() {
157+
assert_eq!(
158+
lower("http_requests_total + ignoring(job) http_errors_total"),
159+
QueryExpr::BinaryOp {
160+
op: BinaryOpKind::Arith(ArithOp::Add),
161+
lhs: Box::new(scan("http_requests_total", &[])),
162+
rhs: Box::new(scan("http_errors_total", &[])),
163+
vector_match: Some(VectorMatch {
164+
kind: VectorMatchKind::Ignoring,
165+
labels: vec!["job".into()],
166+
grouping: None,
167+
}),
168+
}
169+
);
170+
}
171+
172+
// group_left — many-to-one: left side has higher cardinality
173+
#[test]
174+
fn q_mul_group_left() {
175+
assert_eq!(
176+
lower("http_requests_total * on(job) group_left() node_info"),
177+
QueryExpr::BinaryOp {
178+
op: BinaryOpKind::Arith(ArithOp::Mul),
179+
lhs: Box::new(scan("http_requests_total", &[])),
180+
rhs: Box::new(scan("node_info", &[])),
181+
vector_match: Some(VectorMatch {
182+
kind: VectorMatchKind::On,
183+
labels: vec!["job".into()],
184+
grouping: Some(VectorGrouping {
185+
side: GroupSide::Left,
186+
labels: vec![],
187+
}),
188+
}),
189+
}
190+
);
191+
}
192+
193+
// group_right — one-to-many: right side has higher cardinality
194+
#[test]
195+
fn q_mul_group_right() {
196+
assert_eq!(
197+
lower("node_info * on(job) group_right() http_requests_total"),
198+
QueryExpr::BinaryOp {
199+
op: BinaryOpKind::Arith(ArithOp::Mul),
200+
lhs: Box::new(scan("node_info", &[])),
201+
rhs: Box::new(scan("http_requests_total", &[])),
202+
vector_match: Some(VectorMatch {
203+
kind: VectorMatchKind::On,
204+
labels: vec!["job".into()],
205+
grouping: Some(VectorGrouping {
206+
side: GroupSide::Right,
207+
labels: vec![],
208+
}),
209+
}),
210+
}
211+
);
212+
}
213+
100214
// #21 — divide two sum-by-job aggregates over different metrics
101215
// each side: Aggregate{Sum, by=[2]} over Scan([ts, value, job])
102216
#[test]

‎crates/e2e/tests/tier5_nested.rs‎

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
use std::time::Duration;
1414

1515
use asap_control_core::intent_algebra::{
16-
AggIntent, CompareOp, L3Expr, L3Scalar, Predicate, QueryExpr, Source,
16+
AggIntent, ArithOp, BinaryOpKind, CompareOp, L3Expr, L3Scalar, Predicate, QueryExpr, Source,
1717
};
1818
use asap_control_core::types::AccuracyTarget;
1919
use asap_control_lower::lower_promql;
@@ -82,6 +82,71 @@ fn q23_sum_by_job_over_filtered_scan() {
8282
);
8383
}
8484

85+
// #25 — binary op over two complex subtrees
86+
// LHS: sum by (job) over rate over filtered scan
87+
// schema [ts, value, job, status]; outer by=[2] (job)
88+
// RHS: sum by (job) over rate over bare scan
89+
// schema [ts, value, job]; outer by=[2] (job)
90+
#[test]
91+
fn q25_div_over_complex_subtrees() {
92+
let lhs_scan = QueryExpr::Scan {
93+
source: Source::TimeSeries {
94+
metric: "http_requests_total".into(),
95+
},
96+
predicates: vec![Predicate(L3Expr::Compare {
97+
left: Box::new(L3Expr::Column(3)),
98+
op: CompareOp::Eq,
99+
right: Box::new(L3Expr::Literal(L3Scalar::Utf8("200".into()))),
100+
})],
101+
schema: metric_schema(&["job", "status"]),
102+
};
103+
let lhs = agg(
104+
vec![2],
105+
AggIntent::Sum { col: None },
106+
agg(
107+
vec![],
108+
AggIntent::Rate,
109+
QueryExpr::TimeRange {
110+
range: Duration::from_secs(300),
111+
child: Box::new(lhs_scan),
112+
},
113+
),
114+
);
115+
116+
let rhs_scan = QueryExpr::Scan {
117+
source: Source::TimeSeries {
118+
metric: "http_errors_total".into(),
119+
},
120+
predicates: vec![],
121+
schema: metric_schema(&["job"]),
122+
};
123+
let rhs = agg(
124+
vec![2],
125+
AggIntent::Sum { col: None },
126+
agg(
127+
vec![],
128+
AggIntent::Rate,
129+
QueryExpr::TimeRange {
130+
range: Duration::from_secs(300),
131+
child: Box::new(rhs_scan),
132+
},
133+
),
134+
);
135+
136+
let expected = QueryExpr::BinaryOp {
137+
op: BinaryOpKind::Arith(ArithOp::Div),
138+
lhs: Box::new(lhs),
139+
rhs: Box::new(rhs),
140+
vector_match: None,
141+
};
142+
assert_eq!(
143+
lower(
144+
r#"sum by (job) (rate(http_requests_total{status="200"}[5m])) / sum by (job) (rate(http_errors_total[5m]))"#
145+
),
146+
expected
147+
);
148+
}
149+
85150
// #24 — sum by job over rate over a filtered scan
86151
// same schema [ts, value, job, status]; rate is label-preserving,
87152
// so outer sum by job still finds job at col 2

0 commit comments

Comments
 (0)