Skip to content

Commit b054f6e

Browse files
hyperpolymathclaude
andcommitted
test: add criterion benchmarks for CloudGuard hardening policy evaluation
Benchmarks cover hardening_policy lookup, audit_settings all-pass and all-fail paths, and audit throughput scaling with setting count. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 3a1cd66 commit b054f6e

2 files changed

Lines changed: 140 additions & 0 deletions

File tree

Cargo.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,11 @@ path = "src/lib.rs"
2525
[[bin]]
2626
name = "cloudguard"
2727
path = "src/main.rs"
28+
29+
[[bench]]
30+
name = "cloudguard_bench"
31+
harness = false
32+
33+
[dev-dependencies]
34+
criterion = { version = "0.5", features = ["html_reports"] }
35+
serde_json = "1"

benches/cloudguard_bench.rs

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
// SPDX-License-Identifier: PMPL-1.0-or-later
2+
// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell
3+
4+
//! CloudGuard CLI benchmarks — hardening policy evaluation and audit scanning.
5+
//!
6+
//! Measures the core hot paths exercised during domain auditing:
7+
//! - `hardening_policy()` — policy table lookup (called on every audit run)
8+
//! - `audit_settings()` — full compliance scan over a mock settings payload
9+
//! - Policy table iteration throughput at varying setting counts
10+
11+
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion};
12+
use cloudguard_cli::api::{audit_settings, hardening_policy, AuditFinding, CfSetting};
13+
14+
// ============================================================================
15+
// Helpers — construct representative Cloudflare setting payloads
16+
// ============================================================================
17+
18+
/// Build a mock settings list that matches the hardening policy exactly
19+
/// (all passing — represents a fully hardened domain).
20+
fn make_compliant_settings() -> Vec<CfSetting> {
21+
hardening_policy()
22+
.iter()
23+
.map(|&(id, expected, _severity)| CfSetting {
24+
id: id.to_string(),
25+
value: serde_json::Value::String(expected.to_string()),
26+
editable: true,
27+
modified_on: String::new(),
28+
})
29+
.collect()
30+
}
31+
32+
/// Build a mock settings list with every value set to a non-compliant value
33+
/// (all failing — represents an unhardened domain, worst case for finding allocation).
34+
fn make_noncompliant_settings() -> Vec<CfSetting> {
35+
hardening_policy()
36+
.iter()
37+
.map(|&(id, _expected, _severity)| CfSetting {
38+
id: id.to_string(),
39+
value: serde_json::Value::String("off".to_string()),
40+
editable: true,
41+
modified_on: String::new(),
42+
})
43+
.collect()
44+
}
45+
46+
/// Build a settings list with exactly `n` settings (subset of policy).
47+
fn make_settings_n(n: usize) -> Vec<CfSetting> {
48+
hardening_policy()
49+
.iter()
50+
.take(n)
51+
.map(|&(id, expected, _)| CfSetting {
52+
id: id.to_string(),
53+
value: serde_json::Value::String(expected.to_string()),
54+
editable: true,
55+
modified_on: String::new(),
56+
})
57+
.collect()
58+
}
59+
60+
// ============================================================================
61+
// Benchmarks
62+
// ============================================================================
63+
64+
/// Benchmark returning the static hardening policy table.
65+
///
66+
/// This is called every time the audit or harden command runs, so
67+
/// it should be essentially free — we verify that here.
68+
fn bench_hardening_policy(c: &mut Criterion) {
69+
c.bench_function("hardening_policy_lookup", |b| {
70+
b.iter(|| black_box(hardening_policy()))
71+
});
72+
}
73+
74+
/// Benchmark auditing a fully compliant domain (all settings pass).
75+
///
76+
/// Best case: no allocation for findings, pure iteration + comparison.
77+
fn bench_audit_all_pass(c: &mut Criterion) {
78+
let settings = make_compliant_settings();
79+
c.bench_function("audit_settings_all_pass", |b| {
80+
b.iter(|| black_box(audit_settings(black_box("example.com"), black_box(&settings))))
81+
});
82+
}
83+
84+
/// Benchmark auditing a fully non-compliant domain (all settings fail).
85+
///
86+
/// Worst case: every setting produces an `AuditFinding` allocation.
87+
fn bench_audit_all_fail(c: &mut Criterion) {
88+
let settings = make_noncompliant_settings();
89+
c.bench_function("audit_settings_all_fail", |b| {
90+
b.iter(|| black_box(audit_settings(black_box("example.com"), black_box(&settings))))
91+
});
92+
}
93+
94+
/// Benchmark audit throughput as the number of checked settings scales.
95+
///
96+
/// Shows how audit time grows relative to the number of settings checked
97+
/// (should be linear in the policy size).
98+
fn bench_audit_scaling(c: &mut Criterion) {
99+
let policy_size = hardening_policy().len();
100+
let mut group = c.benchmark_group("audit_settings_n");
101+
for n in [4, 8, 16, policy_size] {
102+
let settings = make_settings_n(n);
103+
group.bench_with_input(BenchmarkId::from_parameter(n), &n, |b, _| {
104+
b.iter(|| black_box(audit_settings(black_box("bench.example.com"), black_box(&settings))))
105+
});
106+
}
107+
group.finish();
108+
}
109+
110+
/// Benchmark iterating over the full hardening policy (policy size measurement).
111+
fn bench_policy_iteration(c: &mut Criterion) {
112+
c.bench_function("policy_iteration_full", |b| {
113+
b.iter(|| {
114+
let policy = hardening_policy();
115+
let mut count = 0usize;
116+
for &(id, _val, _sev) in policy {
117+
count += black_box(id).len();
118+
}
119+
black_box(count)
120+
})
121+
});
122+
}
123+
124+
criterion_group!(
125+
benches,
126+
bench_hardening_policy,
127+
bench_audit_all_pass,
128+
bench_audit_all_fail,
129+
bench_audit_scaling,
130+
bench_policy_iteration,
131+
);
132+
criterion_main!(benches);

0 commit comments

Comments
 (0)