git_perf/
config.rs

1use anyhow::Result;
2use std::{
3    fs::File,
4    io::{Read, Write},
5};
6use toml_edit::{value, Document};
7
8use crate::git::git_interop::get_head_revision;
9
10pub fn write_config(conf: &str) -> Result<()> {
11    let mut f = File::create(".gitperfconfig")?;
12    f.write_all(conf.as_bytes())?;
13    Ok(())
14}
15
16pub fn read_config() -> Result<String> {
17    read_config_from_file(".gitperfconfig")
18}
19
20use std::path::Path;
21
22fn read_config_from_file<P: AsRef<Path>>(file: P) -> Result<String> {
23    let mut conf_str = String::new();
24    File::open(file)?.read_to_string(&mut conf_str)?;
25    Ok(conf_str)
26}
27
28pub fn determine_epoch_from_config(measurement: &str) -> Option<u32> {
29    // TODO(hoewelmk) configure path, use different working directory than repo root
30    // TODO(hoewelmk) proper error handling
31    let conf = read_config().ok()?;
32    determine_epoch(measurement, &conf)
33}
34
35fn determine_epoch(measurement: &str, conf_str: &str) -> Option<u32> {
36    let config = conf_str
37        .parse::<Document>()
38        .expect("Failed to parse config");
39
40    let get_epoch = |section: &str| {
41        let s = config
42            .get("measurement")?
43            .get(section)?
44            .get("epoch")?
45            .as_str()?;
46        u32::from_str_radix(s, 16).ok()
47    };
48
49    get_epoch(measurement).or_else(|| get_epoch("*"))
50}
51
52pub fn bump_epoch_in_conf(measurement: &str, conf_str: &mut String) -> Result<()> {
53    let mut conf = conf_str
54        .parse::<Document>()
55        .expect("failed to parse config");
56
57    let head_revision = get_head_revision()?;
58    // TODO(kaihowl) ensure that always non-inline tables are written in an empty config file
59    conf["measurement"][measurement]["epoch"] = value(&head_revision[0..8]);
60    *conf_str = conf.to_string();
61
62    Ok(())
63}
64
65pub fn bump_epoch(measurement: &str) -> Result<()> {
66    let mut conf_str = read_config().unwrap_or_default();
67    bump_epoch_in_conf(measurement, &mut conf_str)?;
68    write_config(&conf_str)?;
69    Ok(())
70}
71
72/// Returns the backoff max elapsed seconds from a config string, or 60 if not set.
73pub fn backoff_max_elapsed_seconds_from_str(conf: &str) -> u64 {
74    let doc = conf.parse::<Document>().ok();
75    doc.and_then(|doc| {
76        doc.get("backoff")
77            .and_then(|b| b.get("max_elapsed_seconds"))
78            .and_then(|v| v.as_integer())
79            .map(|v| v as u64)
80    })
81    .unwrap_or(60)
82}
83
84/// Returns the backoff max elapsed seconds from config, or 60 if not set.
85pub fn backoff_max_elapsed_seconds() -> u64 {
86    backoff_max_elapsed_seconds_from_str(read_config().unwrap_or_default().as_str())
87}
88
89#[cfg(test)]
90mod test {
91    use super::*;
92
93    #[test]
94    fn test_read_epochs() {
95        // TODO(hoewelmk) order unspecified in serialization...
96        let configfile = r#"[measurement."something"]
97#My comment
98epoch="34567898"
99
100[measurement."somethingelse"]
101epoch="a3dead"
102
103[measurement."*"]
104# General performance regression
105epoch="12344555"
106"#;
107
108        let epoch = determine_epoch("something", configfile);
109        assert_eq!(epoch, Some(0x34567898));
110
111        let epoch = determine_epoch("somethingelse", configfile);
112        assert_eq!(epoch, Some(0xa3dead));
113
114        let epoch = determine_epoch("unspecified", configfile);
115        assert_eq!(epoch, Some(0x12344555));
116    }
117
118    #[test]
119    fn test_bump_epochs() {
120        let configfile = r#"[measurement."something"]
121#My comment
122epoch = "34567898"
123"#;
124
125        let mut actual = String::from(configfile);
126        bump_epoch_in_conf("something", &mut actual).expect("Failed to bump epoch");
127
128        let expected = format!(
129            r#"[measurement."something"]
130#My comment
131epoch = "{}"
132"#,
133            &get_head_revision().expect("get_head_revision failed")[0..8],
134        );
135
136        assert_eq!(actual, expected);
137    }
138
139    #[test]
140    fn test_bump_new_epoch_and_read_it() {
141        let mut conf = String::new();
142        bump_epoch_in_conf("mymeasurement", &mut conf).expect("Failed to bump epoch");
143        let epoch = determine_epoch("mymeasurement", &conf);
144        assert!(epoch.is_some());
145    }
146
147    #[test]
148    fn test_parsing() {
149        let toml_str = r#"
150        measurement = { test2 = { epoch = "834ae670e2ecd5c87020fde23378b890832d6076" } }
151    "#;
152
153        let doc = toml_str.parse::<Document>().expect("sfdfdf");
154
155        let measurement = "test";
156
157        if let Some(e) = doc
158            .get("measurement")
159            .and_then(|m| m.get(measurement))
160            .and_then(|m| m.get("epoch"))
161        {
162            println!("YAY: {}", e);
163            panic!("stuff");
164        }
165    }
166
167    #[test]
168    fn test_backoff_max_elapsed_seconds() {
169        // Case 1: config string with explicit value
170        let configfile = "[backoff]\nmax_elapsed_seconds = 42\n";
171        assert_eq!(super::backoff_max_elapsed_seconds_from_str(configfile), 42);
172
173        // Case 2: config string missing value
174        let configfile = "";
175        assert_eq!(super::backoff_max_elapsed_seconds_from_str(configfile), 60);
176    }
177}