File: journal.rs

package info (click to toggle)
rust-libsystemd 0.5.0-1
  • links: PTS, VCS
  • area: main
  • in suites: sid, trixie
  • size: 384 kB
  • sloc: makefile: 2
file content (135 lines) | stat: -rw-r--r-- 4,060 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
use std::process::Command;

use libsystemd::logging::Priority;
use rand::distributions::Alphanumeric;
use rand::Rng;
use std::collections::HashMap;
use std::time::Duration;

fn random_name(prefix: &str) -> String {
    format!(
        "{}_{}",
        prefix,
        rand::thread_rng()
            .sample_iter(&Alphanumeric)
            .take(10)
            .map(char::from)
            .collect::<String>()
    )
}

/// Retry `f` 10 times 100ms apart.
///
/// When `f` returns an error wait 100ms and try it again, up to ten times.
/// If the last attempt failed return the error returned by that attempt.
///
/// If `f` returns Ok immediately return the result.
fn retry<T, E>(f: impl Fn() -> Result<T, E>) -> Result<T, E> {
    let attempts = 10;
    let interval = Duration::from_millis(100);
    for attempt in (0..attempts).rev() {
        match f() {
            Ok(result) => return Ok(result),
            Err(e) if attempt == 0 => return Err(e),
            Err(_) => std::thread::sleep(interval),
        }
    }
    unreachable!()
}

/// Read from journal with `journalctl`.
///
/// `test_name` is the randomized name of the test being run, and gets
/// added as `TEST_NAME` match to the `journalctl` call, to make sure to
/// only select journal entries originating from and relevant to the
/// current test.
fn read_from_journal(test_name: &str) -> Vec<HashMap<String, String>> {
    let stdout = String::from_utf8(
        Command::new("journalctl")
            .args(&["--user", "--output=json"])
            // Filter by the PID of the current test process
            .arg(format!("_PID={}", std::process::id()))
            .arg(format!("TEST_NAME={}", test_name))
            .output()
            .unwrap()
            .stdout,
    )
    .unwrap();

    stdout
        .lines()
        .map(|l| serde_json::from_str(l).unwrap())
        .collect()
}

/// Read exactly one line from journal for the given test name.
///
/// Try to read lines for `testname` from journal, and `retry()` if the wasn't
/// _exactly_ one matching line.
fn retry_read_one_line_from_journal(testname: &str) -> HashMap<String, String> {
    retry(|| {
        let mut messages = read_from_journal(testname);
        if messages.len() == 1 {
            Ok(messages.pop().unwrap())
        } else {
            Err(format!(
                "one messages expected, got {} messages",
                messages.len()
            ))
        }
    })
    .unwrap()
}

#[test]
fn simple_message() {
    let test_name = random_name("simple_message");
    libsystemd::logging::journal_send(
        Priority::Info,
        "Hello World",
        vec![
            ("TEST_NAME", test_name.as_str()),
            ("FOO", "another piece of data"),
        ]
        .into_iter(),
    )
    .unwrap();

    let message = retry_read_one_line_from_journal(&test_name);
    assert_eq!(message["MESSAGE"], "Hello World");
    assert_eq!(message["TEST_NAME"], test_name);
    assert_eq!(message["PRIORITY"], "6");
    assert_eq!(message["FOO"], "another piece of data");
}

#[test]
fn multiline_message() {
    let test_name = random_name("multiline_message");
    libsystemd::logging::journal_send(
        Priority::Info,
        "Hello\nMultiline\nWorld",
        vec![("TEST_NAME", test_name.as_str())].into_iter(),
    )
    .unwrap();

    let message = retry_read_one_line_from_journal(&test_name);
    assert_eq!(message["MESSAGE"], "Hello\nMultiline\nWorld");
    assert_eq!(message["TEST_NAME"], test_name);
    assert_eq!(message["PRIORITY"], "6");
}

#[test]
fn multiline_message_trailing_newline() {
    let test_name = random_name("multiline_message_trailing_newline");
    libsystemd::logging::journal_send(
        Priority::Info,
        "A trailing newline\n",
        vec![("TEST_NAME", test_name.as_str())].into_iter(),
    )
    .unwrap();

    let message = retry_read_one_line_from_journal(&test_name);
    assert_eq!(message["MESSAGE"], "A trailing newline\n");
    assert_eq!(message["TEST_NAME"], test_name);
    assert_eq!(message["PRIORITY"], "6");
}