munin_plugin/lib.rs
1//! munin-plugin - Simple writing of plugins for munin in Rust
2//!
3//! SPDX-License-Identifier: MIT AND Apache-2.0
4//! Copyright (C) 2022 Joerg Jaspert <joerg@ganneff.de>
5//!
6//! # About
7
8//! Simple way to write munin plugins. There are basically two types of plugins,
9//! - **Simple** or **standard** ones, those are called once every munin
10//! run and gather and output there data at that time. Usually every 5
11//! minutes.
12//! - **Streaming** ones, those daemonize themself and _continuously_
13//! gather data, usually caching it in a file, and when munin comes
14//! around after 5 minutes again, they output everything they gathered
15//! in the meantime.
16//!
17//! Those _streaming_ plugins are needed/useful, when graphs with
18//! resolutions down to the second, rather than the default 5 minutes,
19//! should be created.
20//!
21//! Both types of plugins have to follow all the usual rules for munin
22//! plugins, such as outputting their data to stdout and reacting to
23//! the `config` parameter to print their munin graph configuration.
24//!
25//! # Repositories / plugins using this code
26//! - [Simple munin plugin to graph load](https://github.com/Ganneff/munin-load)
27//! - [Munin CPU graph with 1second resolution](https://github.com/Ganneff/cpu1sec/)
28//! - [Munin Interface graph with 1second resolution](https://github.com/Ganneff/if1sec)
29//!
30//! # Usage
31
32//! This library tries to abstract as much of the details away, so you
33//! can concentrate on the actual task - defining how the graph should
34//! appear and gathering the data. For that, you need to implement the
35//! [MuninPlugin] trait and provide the two functions `config` and
36//! `acquire`, all the rest are provided with a (hopefully) useful
37//! default implementation.
38//!
39//! ## config()
40//! The _config_ function will be called whenever the plugin gets
41//! called with the config argument from munin. This happens on every
42//! munin run, which usually happens every 5 minutes. It is expected
43//! to print out a munin graph configuration and you can find details
44//! on possible values to print at [the Munin Plugin
45//! Guide](http://guide.munin-monitoring.org/en/latest/plugin/writing.html).
46//! For some basics you can also look into the examples throughout
47//! this lib.
48//!
49//! **Note**: Streaming plugins should take care of correctly setting
50//! munins `graph_data_size` and `update_rate` option. Those is the
51//! difference in their configuration compared to standard plugins!
52//!
53//! ## acquire()
54//!
55//! The _acquire_ function will be called whenever the plugin needs to
56//! gather data. For a _standard_ plugin that will be once every 5
57//! minutes. A _streaming_ plugin will call this function once every
58//! second.
59//!
60//! In both cases, _standard_ and _streaming_, you should do whatever
61//! is needed to gather the data and then write it to the provided
62//! handle, this library will take care of either handing it directly
63//! to munin on stdout (_standard_) or storing it in a cache file
64//! (_streaming_), to hand it out whenever munin comes around to fetch
65//! the data.
66//!
67//! The format to write the data in is the one munin expects,
68//! - _standard_: fieldname.value VALUE
69//! - _streaming_: fieldname.value EPOCH:VALUE
70//! where fieldname matches the config output, EPOCH is the
71//! unix epoch in seconds and VALUE is whatever value got
72//! calculated.
73//!
74//! # Example
75//! The following implements the **load** plugin from munin, graphing
76//! the load average of the system, using the 5-minute value. As
77//! implemented, it expects to be run by munin every 5 minutes,
78//! usually munin will first run it with the config parameter,
79//! followed by no parameter to fetch data. If munin-node supports it
80//! and the capability _dirtyconfig_ is set, config will also print
81//! out data (this library handles that for you).
82//!
83//! It is a shortened version of the plugin linked above (Simple munin
84//! plugin to graph load), with things like logging dropped.
85//!
86//! For more example code look into the actual [MuninPlugin] trait and
87//! its function definitions.
88//!
89//! ```rust
90//! use anyhow::Result;
91//! use munin_plugin::{Config, MuninPlugin};
92//! use procfs::LoadAverage;
93//! use std::io::{self, BufWriter, Write};
94//!
95//! // Our plugin struct
96//! #[derive(Debug)]
97//! struct LoadPlugin;
98//!
99//! // Implement the needed functions
100//! impl MuninPlugin for LoadPlugin {
101//! // Write out munin config. handle is setup as a bufwriter to stdout.
102//! fn config<W: Write>(&self, handle: &mut BufWriter<W>) -> Result<()> {
103//! writeln!(handle, "graph_title Load average")?;
104//! writeln!(handle, "graph_args --base 1000 -l 0")?;
105//! writeln!(handle, "graph_vlabel load")?;
106//! writeln!(handle, "graph_scale no")?;
107//! writeln!(handle, "graph_category system")?;
108//! writeln!(handle, "load.label load")?;
109//! writeln!(handle, "load.warning 10")?;
110//! writeln!(handle, "load.critical 120")?;
111//! writeln!(handle, "graph_info The load average of the machine describes how many processes are in the run-queue (scheduled to run immediately.")?;
112//! writeln!(handle, "load.info Average load for the five minutes.")?;
113//! Ok(())
114//! }
115//!
116//! // Calculate data (we want the 5-minute load average) and write it to the handle.
117//! fn acquire<W: Write>(&mut self, handle: &mut BufWriter<W>, _config: &Config, _epoch: u64) -> Result<()> {
118//! let load = (LoadAverage::new().unwrap().five * 100.0) as isize;
119//! writeln!(handle, "load.value {}", load)?;
120//! Ok(())
121//! }
122//! }
123//!
124//! // The actual program start point
125//! fn main() -> Result<()> {
126//! // Get our Plugin
127//! let mut load = LoadPlugin;
128//! // And let it do the work.
129//! load.simple_start(String::from("load"))?;
130//! Ok(())
131//! }
132//! ```
133//!
134//! # Logging
135//! This crate uses the default [log] crate to output log messages of
136//! level trace. If you want to see them, select a log framework you
137//! like and ensure its level will display trace messages. See
138//! that frameworks documentation on how to setup/include it.
139//!
140//! If you do not want/need log output, just do nothing.
141
142// Tell us if we forget to document things
143#![warn(missing_docs)]
144// We do not want to write unsafe code
145#![forbid(unsafe_code)]
146
147pub mod config;
148pub use crate::config::Config;
149
150use anyhow::{anyhow, Result};
151// daemonize
152use fs2::FileExt;
153use log::{trace, warn};
154// daemonize
155use daemonize::Daemonize;
156use spin_sleep::LoopHelper;
157use std::{
158 env,
159 io::{self, BufWriter, Write},
160 path::Path,
161};
162// daemonize
163use std::{
164 fs::{rename, OpenOptions},
165 process::{Command, Stdio},
166 thread,
167 time::{Duration, SystemTime, UNIX_EPOCH},
168};
169// daemonize
170use tempfile::NamedTempFile;
171
172/// Defines a Munin Plugin and the needed functions
173pub trait MuninPlugin {
174 /// Write out a munin config, read the [Developing
175 /// plugins](http://guide.munin-monitoring.org/en/latest/develop/plugins/index.html)
176 /// guide from munin for everything you can print out here.
177 ///
178 /// Note that munin expects this to appear on stdout, so the
179 /// plugin gives you a handle to write to, which is setup as a
180 /// [std::io::BufWriter] to stdout. The [std::io::BufWriter]
181 /// capacity defaults to 8192 bytes, but if you need more, its
182 /// size can be set using [Config::config_size]. An example where this
183 /// may be useful is a munin multigraph plugin that outputs config
184 /// for many graphs.
185 ///
186 /// # Example
187 /// ```rust
188 /// # pub use munin_plugin::*;
189 /// # use anyhow::{anyhow, Result};
190 /// # use std::{
191 /// # env,
192 /// # io::{self, BufWriter, Write},
193 /// # };
194 /// # struct LoadPlugin;
195 /// # impl MuninPlugin for LoadPlugin {
196 /// # fn acquire<W: Write>(&mut self, handle: &mut BufWriter<W>, config: &Config, epoch: u64) -> Result<()> { todo!() }
197 /// # fn fetch<W: Write>(&mut self, handle: &mut BufWriter<W>, config: &Config) -> Result<()> { todo!() }
198 /// fn config<W: Write>(&self, handle: &mut BufWriter<W>) -> Result<()> {
199 /// writeln!(handle, "graph_title Load average")?;
200 /// writeln!(handle, "graph_args --base 1000 -l 0")?;
201 /// writeln!(handle, "graph_vlabel load")?;
202 /// writeln!(handle, "graph_scale no")?;
203 /// writeln!(handle, "graph_category system")?;
204 /// writeln!(handle, "load.label load")?;
205 /// writeln!(handle, "load.warning 10")?;
206 /// writeln!(handle, "load.critical 120")?;
207 /// writeln!(handle, "graph_info The load average of the machine describes how many processes are in the run-queue (scheduled to run immediately.")?;
208 /// writeln!(handle, "load.info Average load for the five minutes.")?;
209 /// Ok(())
210 /// }
211 /// # }
212 /// ```
213 fn config<W: Write>(&self, handle: &mut BufWriter<W>) -> Result<()>;
214
215 /// Acquire data
216 ///
217 /// Acquire is called whenever data should be gathered. For a
218 /// _standard_ plugin this will be every 5 minutes, a _streaming_
219 /// plugin will call acquire once a second. Acquire is expected to
220 /// do whatever is neccessary to gather the data that the plugin
221 /// is supposed to gather. It should writeln!() it to the provided
222 /// handle, which - depending on the plugin type - will either be
223 /// connected to stdout or a cachefile. The data written out has
224 /// to be in munin compatible format:
225 /// - _standard_ plugin: fieldname.value VALUE
226 /// - _streaming_ plugin: fieldname.value EPOCH:VALUE
227 /// where fieldname matches the config output, EPOCH is the unix
228 /// epoch in seconds and VALUE is whatever value got calculated.
229 ///
230 /// # Example 1, _standard_ plugin
231 /// ```rust
232 /// # pub use munin_plugin::*;
233 /// # use procfs::LoadAverage;
234 /// # use anyhow::{anyhow, Result};
235 /// # use std::{
236 /// # env,
237 /// # fs::{rename, OpenOptions},
238 /// # io::{self, BufWriter, Write},
239 /// # path::{Path, PathBuf},
240 /// # time::{SystemTime, UNIX_EPOCH},
241 /// # };
242 /// # struct InterfacePlugin {
243 /// # interface: String,
244 /// # cache: PathBuf,
245 /// # if_txbytes: PathBuf,
246 /// # if_rxbytes: PathBuf,
247 /// # };
248 /// # impl MuninPlugin for InterfacePlugin {
249 /// # fn fetch<W: Write>(&mut self, handle: &mut BufWriter<W>, config: &Config) -> Result<()> { todo!() }
250 /// # fn config<W: Write>(&self, handle: &mut BufWriter<W>) -> Result<()> { todo!() }
251 /// fn acquire<W: Write>(&mut self, handle: &mut BufWriter<W>, config: &Config, epoch: u64) -> Result<()> {
252 /// let load = (LoadAverage::new().unwrap().five * 100.0) as isize;
253 /// writeln!(handle, "load.value {}", load)?;
254 /// Ok(())
255 /// }
256 /// # }
257 /// ```
258 ///
259 /// # Example 2, _streaming_ plugin
260 /// ```rust
261 /// # pub use munin_plugin::*;
262 /// # use anyhow::{anyhow, Result};
263 /// # use std::{
264 /// # env,
265 /// # fs::{rename, OpenOptions},
266 /// # io::{self, BufWriter, Write},
267 /// # path::{Path, PathBuf},
268 /// # time::{SystemTime, UNIX_EPOCH},
269 /// # };
270 /// # struct InterfacePlugin {
271 /// # interface: String,
272 /// # cache: PathBuf,
273 /// # if_txbytes: PathBuf,
274 /// # if_rxbytes: PathBuf,
275 /// # };
276 /// # impl MuninPlugin for InterfacePlugin {
277 /// # fn fetch<W: Write>(&mut self, handle: &mut BufWriter<W>, config: &Config) -> Result<()> { todo!() }
278 /// # fn config<W: Write>(&self, handle: &mut BufWriter<W>) -> Result<()> { todo!() }
279 /// fn acquire<W: Write>(&mut self, handle: &mut BufWriter<W>, config: &Config, epoch: u64) -> Result<()> {
280 /// // Read in the received and transferred bytes, store as u64
281 /// let rx: u64 = std::fs::read_to_string(&self.if_rxbytes)?.trim().parse()?;
282 /// let tx: u64 = std::fs::read_to_string(&self.if_txbytes)?.trim().parse()?;
283 ///
284 /// // And now write out values
285 /// writeln!(handle, "{0}_tx.value {1}:{2}", self.interface, epoch, tx)?;
286 /// writeln!(handle, "{0}_rx.value {1}:{2}", self.interface, epoch, rx)?;
287 ///
288 /// Ok(())
289 /// }
290 /// # }
291 /// ```
292 fn acquire<W: Write>(
293 &mut self,
294 handle: &mut BufWriter<W>,
295 config: &Config,
296 epoch: u64,
297 ) -> Result<()>;
298
299 /// Daemonize
300 ///
301 /// This function is called whenever the plugin gets run with the
302 /// acquire argument. That usually happens on fetch and acquire
303 /// gets run in the background. `daemon()` will lock its pidfile,
304 /// to show it is running, start a loop, run once a second,
305 /// calling [MuninPlugin::acquire].
306 #[cfg(not(tarpaulin_include))]
307 fn daemon(&mut self, config: &Config) -> Result<()> {
308 // Need to run as daemon/forked in backgreound, so prepare
309 let daemonize = Daemonize::new()
310 .pid_file(&config.pidfile)
311 .chown_pid_file(true)
312 .working_directory("/tmp");
313
314 daemonize.start()?;
315
316 // Repeat once per second
317 let mut loop_helper = LoopHelper::builder().build_with_target_rate(1);
318
319 // We run forever
320 loop {
321 // Let loop helper prepare
322 loop_helper.loop_start();
323
324 // Streaming plugins need the epoch, so provide it
325 let epoch = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs(); // without the nanosecond part
326
327 // Own scope, so file is closed before we sleep. Ensures
328 // we won't have a file open, that fetch just moved away
329 // to send out to munin.
330 {
331 // Open the munin cachefile to store our values, using
332 // a BufWriter to "collect" the writeln!() in acquire
333 // together
334 let mut handle = BufWriter::with_capacity(
335 config.fetch_size,
336 OpenOptions::new()
337 .create(true) // If not there, create
338 .write(true) // We want to write
339 .append(true) // We want to append
340 .open(&config.plugin_cache)?,
341 );
342
343 self.acquire(&mut handle, config, epoch)?;
344 }
345 // Sleep for the rest of the second
346 loop_helper.loop_sleep();
347 }
348 }
349
350 /// Fetch delivers actual data to munin. This is called whenever
351 /// the plugin is called without an argument. If the
352 /// [config::Config::dirtyconfig] setting is true (auto-detected from
353 /// environment set by munin), this will also be called right
354 /// after having called [MuninPlugin::config].
355 ///
356 /// The size of the BufWriter this function uses is configurable
357 /// from [Config::fetch_size].
358 ///
359 /// This function will adjust its behaviour based on the plugin
360 /// being a _standard_ or _streaming_ plugin. For _standard_ plugins
361 /// it will simply call acquire, so data is gathered and written
362 /// to the provided handle (and as such, to stdout where munin
363 /// expects it).
364 ///
365 /// For _streaming_ plugins it will create a temporary file beside
366 /// the [config::Config::plugin_cache], will rename the
367 /// [config::Config::plugin_cache] and then use [std::io::copy] to
368 /// "copy" the data to the provided handle.
369 ///
370 /// # Overriding this function
371 /// If you want to override this function, you should ensure that
372 /// (for _streaming_ plugins) you ensure that the cache file is
373 /// reset, whenever `fetch()` runs, or old data may be given to
374 /// munin needlessly. You also need to ensure to not accidently
375 /// deleting data when dealing with your cachefile. For example:
376 /// You read the whole cachefile, then output it to munin, then
377 /// delete it - and during the halfsecond this took, new data
378 /// appeared in the file, now lost.
379 fn fetch<W: Write>(&mut self, handle: &mut BufWriter<W>, config: &Config) -> Result<()> {
380 // Daemonize means plugin writes a cachefile, so lets output that
381 if config.daemonize {
382 // We need a temporary file
383 let fetchpath = NamedTempFile::new_in(&config.plugin_statedir)?;
384 // Rename the cache file, to ensure that acquire doesn't add data
385 // between us outputting data and deleting the file
386 rename(&config.plugin_cache, &fetchpath)?;
387 // Want to read the tempfile now
388 let mut fetchfile = std::fs::File::open(&fetchpath)?;
389 // And ask io::copy to just take it all and shove it into the handle
390 io::copy(&mut fetchfile, handle)?;
391 } else {
392 // Not daemonizing, plugin gathers data and wants to output it directly.
393 // So we just call acquire, which is expected to write its data to handle.
394 self.acquire(handle, config, 0)?;
395 }
396 Ok(())
397 }
398
399 /// Check whatever is neccessary to decide if the plugin can
400 /// auto-configure itself.
401 ///
402 /// For example a network load plugin may check if network
403 /// interfaces exists and then return true, something presenting
404 /// values of a daemon like apache or ntp may check if that is
405 /// installed - and possibly if fetching values is possible.
406 ///
407 /// If this function is not overwritten, it defaults to false.
408 fn check_autoconf(&self) -> bool {
409 false
410 }
411
412 /// Tell munin if the plugin supports autoconf.
413 ///
414 /// Munin expects a simple yes or no on stdout, so we just print
415 /// it, depending on the return value of
416 /// [MuninPlugin::check_autoconf]. The default of that is a plain
417 /// false. If it is possible for your plugin to detect, if it can
418 /// autoconfigure itself, then implement the logic in
419 /// [MuninPlugin::check_autoconf] and have it return true.
420 #[cfg(not(tarpaulin_include))]
421 fn autoconf(&self) {
422 if self.check_autoconf() {
423 println!("yes")
424 } else {
425 println!("no")
426 }
427 }
428
429 /// A simplified start, only need a name, for the rest, defaults are fine.
430 ///
431 /// This is just a tiny bit of "being lazy is good" and will
432 /// create the [MuninPlugin::config] with the given name, then
433 /// call the real start function. Only useful for plugins that do
434 /// not use daemonization or need other config changes to run
435 /// successfully..
436 #[cfg(not(tarpaulin_include))]
437 fn simple_start(&mut self, name: String) -> Result<bool> {
438 trace!("Simple Start, setting up config");
439 let config = Config::new(name);
440 trace!("Plugin: {:#?}", config);
441
442 self.start(config)?;
443 Ok(true)
444 }
445
446 /// The main plugin function, this will deal with parsing
447 /// commandline arguments and doing what is expected of the plugin
448 /// (present config, fetch values, whatever).
449 #[cfg(not(tarpaulin_include))]
450 fn start(&mut self, config: Config) -> Result<bool> {
451 trace!("Plugin start");
452 trace!("My plugin config: {config:#?}");
453
454 // Store arguments for (possible) later use
455 let args: Vec<String> = env::args().collect();
456
457 // Now go over the args and see what we are supposed to do
458 match args.len() {
459 // no arguments passed, print data
460 1 => {
461 trace!("No argument, assuming fetch");
462 if config.daemonize {
463 // For daemonization we need to check if a copy of us
464 // with the acquire arg already runs. We do this by
465 // trying to lock our pidfile. If that works, nothing
466 // is running, then we need to start us in the
467 // background.
468 let lockfile = !Path::exists(&config.pidfile) || {
469 let lockedfile = OpenOptions::new()
470 .create(true)
471 .write(true)
472 .open(&config.pidfile)?;
473 lockedfile.try_lock_exclusive().is_ok()
474 };
475 // If we could lock, it appears that acquire isn't running. Start it.
476 if lockfile {
477 trace!("Could lock the pidfile, will spawn acquire now");
478 Command::new(&args[0])
479 .arg("acquire")
480 .stdin(Stdio::null())
481 .stdout(Stdio::null())
482 .stderr(Stdio::null())
483 .spawn()?;
484 trace!("Spawned, sleep for 1s, then continue");
485 // Now we wait one second before going on, so the
486 // newly spawned process had a chance to generate us
487 // some data
488 thread::sleep(Duration::from_secs(1));
489 }
490 }
491 // Daemonized or not, fetch means handing out data, so lets do this.
492 trace!("Calling fetch");
493 // We want to write a possibly large amount to stdout, take and lock it
494 let stdout = io::stdout();
495 // Buffered writer, to gather multiple small writes together
496 let mut handle = BufWriter::with_capacity(config.fetch_size, stdout.lock());
497 // And give us data, please
498 self.fetch(&mut handle, &config)?;
499 trace!("Done");
500 // And flush the handle, so it can also deal with possible errors
501 handle.flush()?;
502
503 return Ok(true);
504 }
505 // Argument passed, check which one and act accordingly
506 2 => match args[1].as_str() {
507 "config" => {
508 // We want to write a possibly large amount to stdout, take and lock it
509 let stdout = io::stdout();
510 {
511 // Buffered writer, to gather multiple small writes together
512 let mut handle =
513 BufWriter::with_capacity(config.config_size, stdout.lock());
514 self.config(&mut handle)?;
515 // And flush the handle, so it can also deal with possible errors
516 handle.flush()?;
517 }
518 // If munin supports dirtyconfig, send the data now
519 if config.dirtyconfig {
520 trace!("Munin supports dirtyconfig, sending data now");
521 let mut handle = BufWriter::with_capacity(config.fetch_size, stdout.lock());
522 self.fetch(&mut handle, &config)?;
523 // And flush the handle, so it can also deal with possible errors
524 handle.flush()?;
525 }
526 return Ok(true);
527 }
528 "autoconf" => {
529 self.autoconf();
530 return Ok(true);
531 }
532 "acquire" => {
533 trace!("Called acquire to gather data, will run loop forever");
534 // Will only ever process anything after this line, if
535 // one process has our pidfile already locked, ie. if
536 // another acquire is running. (Or if we can not
537 // daemonize for another reason).
538 if let Err(e) = self.daemon(&config) {
539 return Err(anyhow!(
540 "Could not start plugin {} in daemon mode to gather data - already running? ({})",
541 config.plugin_name,
542 e
543 ));
544 };
545 }
546 &_ => trace!("Unsupported argument: {}", args[1]),
547 },
548 // Whatever else
549 _ => return Err(anyhow!("No argument given")),
550 }
551 Ok(true)
552 }
553}
554
555#[cfg(test)]
556mod tests {
557 use super::*;
558
559 // Our plugin struct
560 #[derive(Debug)]
561 struct TestPlugin;
562 impl MuninPlugin for TestPlugin {
563 fn config<W: Write>(&self, handle: &mut BufWriter<W>) -> Result<()> {
564 writeln!(handle, "This is a test plugin")?;
565 writeln!(handle, "There is no config")?;
566 Ok(())
567 }
568 fn acquire<W: Write>(
569 &mut self,
570 handle: &mut BufWriter<W>,
571 config: &Config,
572 epoch: u64,
573 ) -> Result<()> {
574 writeln!(handle, "This is a value for {}", config.plugin_name)?;
575 writeln!(handle, "And one more value with epoch {}", epoch)?;
576 Ok(())
577 }
578 }
579
580 #[test]
581 fn test_config() {
582 let test = TestPlugin;
583
584 // We want to check the output of config contains our test string
585 // above, so have it "write" it to a variable, then check if
586 // the variable contains what we want
587 let checktext = Vec::new();
588 let mut handle = BufWriter::new(checktext);
589 test.config(&mut handle).unwrap();
590 handle.flush().unwrap();
591
592 // And now check what got "written" into the variable
593 let (recovered_writer, _buffered_data) = handle.into_parts();
594 let output = String::from_utf8(recovered_writer).unwrap();
595 assert_eq!(
596 output,
597 String::from("This is a test plugin\nThere is no config\n")
598 );
599 }
600
601 #[test]
602 fn test_fetch_standard() {
603 let mut test = TestPlugin;
604
605 // We want to check the output of fetch contains our test string
606 // above, so have it "write" it to a variable, then check if
607 // the variable contains what we want
608 let checktext = Vec::new();
609 let mut handle = BufWriter::new(checktext);
610 test.fetch(&mut handle, &config::Config::new("test".to_string()))
611 .unwrap();
612 handle.flush().unwrap();
613
614 // And now check what got "written" into the variable
615 let (recovered_writer, _buffered_data) = handle.into_parts();
616 let output = String::from_utf8(recovered_writer).unwrap();
617 assert_eq!(
618 output,
619 String::from("This is a value for test\nAnd one more value with epoch 0\n")
620 );
621 }
622
623 #[test]
624 fn test_fetch_streaming() {
625 let mut config = Config::new(String::from("testplugin"));
626 config.daemonize = true;
627 config.fetch_size = 16384;
628
629 let mut test = TestPlugin {};
630
631 // We need a temporary file
632 let fetchpath = NamedTempFile::new_in(
633 config
634 .plugin_cache
635 .parent()
636 .expect("Could not find useful temp path"),
637 )
638 .unwrap();
639
640 {
641 // Setup a bufwriter as daemon() does.
642 let mut handle = BufWriter::with_capacity(
643 config.fetch_size,
644 OpenOptions::new()
645 .create(true) // If not there, create
646 .write(true) // We want to write
647 .append(true) // We want to append
648 .open(&fetchpath)
649 .unwrap(),
650 );
651
652 // And have acquire write to it
653 test.acquire(&mut handle, &config, 42).unwrap();
654 }
655
656 // And we want to access the tempfile and read from it
657 (_, config.plugin_cache) = fetchpath.keep().unwrap();
658 let checktext = Vec::new();
659 let mut handle = BufWriter::new(checktext);
660
661 test.fetch(&mut handle, &config).unwrap();
662 handle.flush().unwrap();
663 let (recovered_writer, _buffered_data) = handle.into_parts();
664 let output = String::from_utf8(recovered_writer).unwrap();
665
666 assert_eq!(
667 output,
668 String::from("This is a value for testplugin\nAnd one more value with epoch 42\n")
669 );
670 }
671
672 #[test]
673 // Kind of silly, its always false
674 fn test_check_autoconf() {
675 let test = TestPlugin;
676 assert!(!test.check_autoconf());
677 }
678}