Skip to content

Commit f12ac0f

Browse files
authored
Test push functionality (#16)
* Add tests for async functionality * Add bloicking test to prometheus_client * Factor out shared logic * Factor out shared logic for prometheus crate tests
1 parent deb33f0 commit f12ac0f

File tree

5 files changed

+246
-4
lines changed

5 files changed

+246
-4
lines changed

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ prometheus-client = { version = "0.22", default-features = false, optional = tru
1818
reqwest = { version = "0.12", default-features = false, optional = true }
1919
log = { version = "0.4", default-features = false, optional = true }
2020

21+
[dev-dependencies]
22+
tokio = { version = "1.0", features = ["full"] }
23+
mockito = "1.4"
24+
2125
[features]
2226
default = ["non_blocking"]
2327
non_blocking = []

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ use url::Url;
104104
async fn main() -> Result<(), Box<dyn std::error::Error>> {
105105
let push_gateway: Url = Url::parse("<address to pushgateway>")?;
106106
let client = Client::new();
107-
let metrics_pusher = PrometheusClientMetricsPusher::from(client, &push_gateway)?;
107+
let metrics_pusher = PrometheusClientMetricsPusher::create(client, &push_gateway)?;
108108
let grouping: HashMap<&str, &str> = HashMap::from([("<label_name>", "<label_value>")]);
109109
let mut metrics = String::new();
110110
encode(&mut metrics, &registry)?;
@@ -139,7 +139,7 @@ use url::Url;
139139
fn main() -> Result<(), Box<dyn std::error::Error>> {
140140
let push_gateway: Url = Url::parse("<address to pushgateway>")?;
141141
let client = Client::new();
142-
let metrics_pusher = PrometheusClientMetricsPusherBlocking::from(client, &push_gateway)?;
142+
let metrics_pusher = PrometheusClientMetricsPusherBlocking::create(client, &push_gateway)?;
143143
let grouping: HashMap<&str, &str> = HashMap::from([("<label_name>", "<label_value>")]);
144144
let mut metrics = String::new();
145145
encode(&mut metrics, &registry)?;

src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@
104104
//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
105105
//! let push_gateway: Url = Url::parse("<address to pushgateway>")?;
106106
//! let client = Client::new();
107-
//! let metrics_pusher = PrometheusClientMetricsPusher::from(client, &push_gateway)?;
107+
//! let metrics_pusher = PrometheusClientMetricsPusher::create(client, &push_gateway)?;
108108
//! let grouping: HashMap<&str, &str> = HashMap::from([("<label_name>", "<label_value>")]);
109109
//! let mut metrics = String::new();
110110
//! encode(&mut metrics, &registry)?;
@@ -139,7 +139,7 @@
139139
//! fn main() -> Result<(), Box<dyn std::error::Error>> {
140140
//! let push_gateway: Url = Url::parse("<address to pushgateway>")?;
141141
//! let client = Client::new();
142-
//! let metrics_pusher = PrometheusClientMetricsPusherBlocking::from(client, &push_gateway)?;
142+
//! let metrics_pusher = PrometheusClientMetricsPusherBlocking::create(client, &push_gateway)?;
143143
//! let grouping: HashMap<&str, &str> = HashMap::from([("<label_name>", "<label_value>")]);
144144
//! let mut metrics = String::new();
145145
//! encode(&mut metrics, &registry)?;

src/prometheus_client_crate.rs

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,3 +106,134 @@ where
106106
)
107107
}
108108
}
109+
110+
#[cfg(test)]
111+
mod test {
112+
use std::collections::HashMap;
113+
114+
use mockito::Mock;
115+
use mockito::Server;
116+
use mockito::ServerGuard;
117+
use prometheus_client::encoding::text::encode;
118+
use prometheus_client::encoding::EncodeLabelSet;
119+
use prometheus_client::encoding::EncodeLabelValue;
120+
use prometheus_client::metrics::counter::Counter;
121+
use prometheus_client::metrics::family::Family;
122+
use prometheus_client::registry::Registry;
123+
use prometheus_client_crate::PrometheusClientMetricsPusher;
124+
use prometheus_client_crate::PrometheusClientMetricsPusherBlocking;
125+
use url::Url;
126+
127+
use crate::prometheus_client_crate;
128+
129+
#[derive(Clone, Debug, Hash, PartialEq, Eq, EncodeLabelValue)]
130+
enum Method {
131+
#[allow(clippy::upper_case_acronyms)]
132+
GET,
133+
}
134+
135+
#[derive(Clone, Debug, Hash, PartialEq, Eq, EncodeLabelSet)]
136+
struct Labels {
137+
// Use your own enum types to represent label values.
138+
method: Method,
139+
// Or just a plain string.
140+
path: String,
141+
}
142+
143+
fn create_metrics() -> String {
144+
// Given I have a counter metric
145+
let mut registry = <Registry>::default();
146+
let http_requests = Family::<Labels, Counter>::default();
147+
registry.register(
148+
"http_requests",
149+
"Number of HTTP requests received",
150+
http_requests.clone(),
151+
);
152+
http_requests
153+
.get_or_create(&Labels { method: Method::GET, path: "/metrics".to_string() })
154+
.inc();
155+
156+
let mut metrics = String::new();
157+
encode(&mut metrics, &registry).unwrap();
158+
159+
metrics
160+
}
161+
162+
fn create_push_gateway_mock(
163+
server: &mut ServerGuard,
164+
) -> (Mock, Url, &'static str, HashMap<&'static str, &'static str>) {
165+
let pushgateway_address = Url::parse(&server.url()).unwrap();
166+
let job = "prometheus_client_crate_job";
167+
let label_name = "kind";
168+
let label_value = "test";
169+
let path = format!("/metrics/job/{job}/{label_name}/{label_value}");
170+
171+
let grouping: HashMap<&str, &str> = HashMap::from([(label_name, label_value)]);
172+
173+
let expected = "# HELP http_requests Number of HTTP requests received.\n".to_owned()
174+
+ "# TYPE http_requests counter\n"
175+
+ "http_requests_total{method=\"GET\",path=\"/metrics\"} 1\n"
176+
+ "# EOF\n";
177+
178+
let pushgateway_mock = server
179+
.mock("PUT", &*path)
180+
.with_status(200)
181+
.match_header("content-type", "text/plain")
182+
.match_body(mockito::Matcher::from(&*expected))
183+
.create();
184+
185+
(pushgateway_mock, pushgateway_address, job, grouping)
186+
}
187+
188+
#[cfg(feature = "with_reqwest_blocking")]
189+
#[test]
190+
fn test_push_all_blocking_reqwest_prometheus_client_crate() {
191+
use reqwest::blocking::Client;
192+
// Given I have a counter metric
193+
let metrics = create_metrics();
194+
195+
// And a push gateway and a job
196+
let mut server = Server::new();
197+
let (pushgateway_mock, push_gateway_address, job, grouping) =
198+
create_push_gateway_mock(&mut server);
199+
200+
// And a blocking prometheus metrics pusher
201+
let metrics_pusher =
202+
PrometheusClientMetricsPusherBlocking::create(Client::new(), &push_gateway_address)
203+
.unwrap();
204+
205+
// When I push all metrics to the push gateway
206+
metrics_pusher
207+
.push_all(job, &grouping, metrics)
208+
.expect("Failed to push metrics");
209+
210+
// Then the metrics are received by the push_gateway
211+
pushgateway_mock.expect(1).assert();
212+
}
213+
214+
#[cfg(feature = "with_reqwest")]
215+
#[tokio::test]
216+
async fn test_push_all_non_blocking_reqwest_prometheus_client_crate() {
217+
use reqwest::Client;
218+
// Given I have a counter metric
219+
let metrics = create_metrics();
220+
221+
// And a push gateway and a job
222+
let mut server = Server::new_async().await;
223+
let (pushgateway_mock, push_gateway_address, job, grouping) =
224+
create_push_gateway_mock(&mut server);
225+
226+
// And a nonblocking prometheus metrics pusher
227+
let metrics_pusher =
228+
PrometheusClientMetricsPusher::create(Client::new(), &push_gateway_address).unwrap();
229+
230+
// When I push all metrics to the push gateway
231+
metrics_pusher
232+
.push_all(job, &grouping, metrics)
233+
.await
234+
.expect("Failed to push metrics");
235+
236+
// Then the metrics are received by the push_gateway
237+
pushgateway_mock.expect(1).assert();
238+
}
239+
}

src/prometheus_crate.rs

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,3 +146,110 @@ where
146146
)
147147
}
148148
}
149+
150+
#[cfg(test)]
151+
mod test {
152+
use std::collections::HashMap;
153+
154+
use mockito::Mock;
155+
use mockito::Server;
156+
use mockito::ServerGuard;
157+
use prometheus::labels;
158+
use prometheus::proto::MetricFamily;
159+
use prometheus::Counter;
160+
use prometheus::Encoder;
161+
use prometheus::Opts;
162+
use prometheus::ProtobufEncoder;
163+
use prometheus_crate::PrometheusMetricsPusher;
164+
use prometheus_crate::PrometheusMetricsPusherBlocking;
165+
use url::Url;
166+
167+
use crate::prometheus_crate;
168+
169+
fn create_metrics(name: &str) -> (Vec<u8>, Vec<MetricFamily>) {
170+
let counter_opts = Opts::new(name, "test counter help");
171+
let counter = Counter::with_opts(counter_opts).unwrap();
172+
prometheus::register(Box::new(counter.clone())).unwrap();
173+
counter.inc();
174+
175+
let encoder = ProtobufEncoder::new();
176+
let metric_families = prometheus::gather();
177+
let mut metrics = vec![];
178+
encoder.encode(&metric_families, &mut metrics).unwrap();
179+
180+
(metrics, metric_families)
181+
}
182+
183+
fn create_push_gateway_mock(
184+
server: &mut ServerGuard,
185+
metrics: Vec<u8>,
186+
) -> (Mock, Url, &str, HashMap<&str, &str>) {
187+
let push_gateway_address = Url::parse(&server.url()).unwrap();
188+
let job = "prometheus_crate_job";
189+
let label_name = "kind";
190+
let label_value = "test";
191+
let path = format!("/metrics/job/{job}/{label_name}/{label_value}");
192+
let grouping = labels! { label_name => label_value };
193+
194+
let pushgateway_mock = server
195+
.mock("PUT", &*path)
196+
.with_status(200)
197+
.match_header("content-type", ProtobufEncoder::new().format_type())
198+
.match_body(mockito::Matcher::from(metrics))
199+
.create();
200+
201+
(pushgateway_mock, push_gateway_address, job, grouping)
202+
}
203+
204+
#[cfg(feature = "with_reqwest_blocking")]
205+
#[test]
206+
fn test_push_all_blocking_reqwest_prometheus_crate() {
207+
// Given I have a counter metric
208+
let (metrics, metric_families) = create_metrics("test_counter");
209+
210+
// And a push gateway and a job
211+
let mut server = Server::new();
212+
let (pushgateway_mock, push_gateway_address, job, grouping) =
213+
create_push_gateway_mock(&mut server, metrics);
214+
215+
// And a blocking prometheus metrics pusher
216+
let metrics_pusher = PrometheusMetricsPusherBlocking::from(
217+
reqwest::blocking::Client::new(),
218+
&push_gateway_address,
219+
)
220+
.unwrap();
221+
222+
// When I push all metrics to the push gateway
223+
metrics_pusher
224+
.push_all(job, &grouping, metric_families)
225+
.expect("Failed to push metrics");
226+
227+
// Then the metrics are received by the push_gateway
228+
pushgateway_mock.expect(1).assert();
229+
}
230+
231+
#[cfg(feature = "with_reqwest")]
232+
#[tokio::test]
233+
async fn test_push_all_non_blocking_reqwest_prometheus_crate() {
234+
// Given I have a counter metric
235+
let (metrics, metric_families) = create_metrics("test_counter_async");
236+
237+
// And a push gateway and a job
238+
let mut server = Server::new_async().await;
239+
let (pushgateway_mock, push_gateway_address, job, grouping) =
240+
create_push_gateway_mock(&mut server, metrics);
241+
242+
// And a nonblocking prometheus metrics pusher
243+
let metrics_pusher =
244+
PrometheusMetricsPusher::from(reqwest::Client::new(), &push_gateway_address).unwrap();
245+
246+
// When I push all metrics to the push gateway
247+
metrics_pusher
248+
.push_all(job, &grouping, metric_families)
249+
.await
250+
.expect("Failed to push metrics");
251+
252+
// Then the metrics are received by the push_gateway
253+
pushgateway_mock.expect(1).assert();
254+
}
255+
}

0 commit comments

Comments
 (0)