• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

VolumeGraphics / havocompare / 52c5f976bf181254837288e9e59364957cf19c40

06 Oct 2023 07:25PM UTC coverage: 85.286% (+0.04%) from 85.246%
52c5f976bf181254837288e9e59364957cf19c40

push

github

ChrisRega
integ test for identity
serde default

25 of 25 new or added lines in 1 file covered. (100.0%)

2956 of 3466 relevant lines covered (85.29%)

2604.15 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.62
/src/json.rs
1
use crate::report::{DiffDetail, Difference};
2
use crate::Error;
3
use itertools::Itertools;
4
use regex::Regex;
5
use schemars_derive::JsonSchema;
6
use serde::{Deserialize, Serialize};
7
use std::path::Path;
8
use tracing::error;
9

10
#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone)]
2✔
11
/// configuration for the json compare module
12
pub struct JsonConfig {
13
    #[serde(default)]
14
    ignore_keys: Vec<String>,
15
}
16
impl JsonConfig {
17
    pub(crate) fn get_ignore_list(&self) -> Result<Vec<Regex>, regex::Error> {
7✔
18
        self.ignore_keys.iter().map(|v| Regex::new(v)).collect()
7✔
19
    }
7✔
20
}
21

22
pub(crate) fn compare_files<P: AsRef<Path>>(
4✔
23
    nominal: P,
4✔
24
    actual: P,
4✔
25
    config: &JsonConfig,
4✔
26
) -> Result<Difference, Error> {
4✔
27
    let mut diff = Difference::new_for_file(&nominal, &actual);
4✔
28
    let compared_file_name = nominal.as_ref().to_string_lossy().into_owned();
4✔
29

30
    let nominal = vg_errortools::fat_io_wrap_std(&nominal, &std::fs::read_to_string)?;
4✔
31
    let actual = vg_errortools::fat_io_wrap_std(&actual, &std::fs::read_to_string)?;
4✔
32
    let ignores = config.get_ignore_list()?;
4✔
33

34
    let json_diff = json_diff::process::compare_jsons(&nominal, &actual);
4✔
35
    let json_diff = match json_diff {
4✔
36
        Ok(diff) => diff,
4✔
37
        Err(e) => {
×
38
            let error_message =
×
39
                format!("JSON deserialization failed for {compared_file_name} (error: {e})");
×
40
            error!("{}", error_message);
×
41
            diff.push_detail(DiffDetail::Error(error_message));
×
42
            diff.error();
×
43
            return Ok(diff);
×
44
        }
45
    };
46
    let filtered_diff: Vec<_> = json_diff
4✔
47
        .all_diffs()
4✔
48
        .into_iter()
4✔
49
        .filter(|(_d, v)| !ignores.iter().any(|excl| excl.is_match(v.get_key())))
18✔
50
        .collect();
4✔
51

4✔
52
    if !filtered_diff.is_empty() {
4✔
53
        for (d_type, key) in filtered_diff.iter() {
16✔
54
            error!("{d_type}: {key}");
16✔
55
        }
56
        let left = filtered_diff
4✔
57
            .iter()
4✔
58
            .filter_map(|(k, v)| {
4✔
59
                if matches!(k, json_diff::enums::DiffType::LeftExtra) {
16✔
60
                    Some(v.to_string())
3✔
61
                } else {
62
                    None
13✔
63
                }
64
            })
16✔
65
            .join("\n");
4✔
66
        let right = filtered_diff
4✔
67
            .iter()
4✔
68
            .filter_map(|(k, v)| {
4✔
69
                if matches!(k, json_diff::enums::DiffType::RightExtra) {
16✔
70
                    Some(v.to_string())
4✔
71
                } else {
72
                    None
12✔
73
                }
74
            })
16✔
75
            .join("\n");
4✔
76
        let differences = filtered_diff
4✔
77
            .iter()
4✔
78
            .filter_map(|(k, v)| {
4✔
79
                if matches!(k, json_diff::enums::DiffType::Mismatch) {
16✔
80
                    Some(v.to_string())
9✔
81
                } else {
82
                    None
7✔
83
                }
84
            })
16✔
85
            .join("\n");
4✔
86
        let root_mismatch = filtered_diff
4✔
87
            .iter()
4✔
88
            .find(|(k, _v)| matches!(k, json_diff::enums::DiffType::RootMismatch))
16✔
89
            .map(|(_, v)| v.to_string());
4✔
90

4✔
91
        diff.push_detail(DiffDetail::Json {
4✔
92
            differences,
4✔
93
            left,
4✔
94
            right,
4✔
95
            root_mismatch,
4✔
96
        });
4✔
97

4✔
98
        diff.error();
4✔
99
    }
×
100

101
    Ok(diff)
4✔
102
}
4✔
103

104
#[cfg(test)]
105
mod test {
106
    use super::*;
107

108
    fn trim_split(list: &str) -> Vec<&str> {
2✔
109
        list.split("\n").map(|e| e.trim()).collect()
5✔
110
    }
2✔
111
    #[test]
1✔
112
    fn identity_is_empty() {
1✔
113
        let cfg = JsonConfig {
1✔
114
            ignore_keys: vec![],
1✔
115
        };
1✔
116
        let result = compare_files(
1✔
117
            "tests/integ/data/json/expected/guy.json",
1✔
118
            "tests/integ/data/json/expected/guy.json",
1✔
119
            &cfg,
1✔
120
        )
1✔
121
        .unwrap();
1✔
122
        if let DiffDetail::Json {
123
            differences,
1✔
124
            left,
1✔
125
            right,
1✔
126
            root_mismatch,
1✔
127
        } = result.detail.first().unwrap()
1✔
128
        {
129
            assert!(differences.is_empty());
1✔
130
            assert!(left.is_empty());
1✔
131
            assert!(right.is_empty());
1✔
132
            assert!(root_mismatch.is_none());
1✔
133
        } else {
134
            panic!("wrong diffdetail");
×
135
        }
136
    }
1✔
137
    #[test]
1✔
138
    fn no_filter() {
1✔
139
        let cfg = JsonConfig {
1✔
140
            ignore_keys: vec![],
1✔
141
        };
1✔
142
        let result = compare_files(
1✔
143
            "tests/integ/data/json/expected/guy.json",
1✔
144
            "tests/integ/data/json/actual/guy.json",
1✔
145
            &cfg,
1✔
146
        )
1✔
147
        .unwrap();
1✔
148
        if let DiffDetail::Json {
149
            differences,
1✔
150
            left,
1✔
151
            right,
1✔
152
            root_mismatch,
1✔
153
        } = result.detail.first().unwrap()
1✔
154
        {
155
            let differences = trim_split(differences);
1✔
156
            assert!(differences.contains(&"car -> [ \"RX7\" :: \"Panda Trueno\" ]"));
1✔
157
            assert!(differences.contains(&"age -> [ 21 :: 18 ]"));
1✔
158
            assert!(differences.contains(&"name -> [ \"Keisuke\" :: \"Takumi\" ]"));
1✔
159
            assert_eq!(differences.len(), 3);
1✔
160

161
            assert_eq!(left.as_str(), " brothers");
1✔
162
            assert!(right.is_empty());
1✔
163
            assert!(root_mismatch.is_none());
1✔
164
        } else {
165
            panic!("wrong diffdetail");
×
166
        }
167
    }
1✔
168

169
    #[test]
1✔
170
    fn filter_works() {
1✔
171
        let cfg = JsonConfig {
1✔
172
            ignore_keys: vec!["name".to_string(), "brother(s?)".to_string()],
1✔
173
        };
1✔
174
        let result = compare_files(
1✔
175
            "tests/integ/data/json/expected/guy.json",
1✔
176
            "tests/integ/data/json/actual/guy.json",
1✔
177
            &cfg,
1✔
178
        )
1✔
179
        .unwrap();
1✔
180
        if let DiffDetail::Json {
181
            differences,
1✔
182
            left,
1✔
183
            right,
1✔
184
            root_mismatch,
1✔
185
        } = result.detail.first().unwrap()
1✔
186
        {
187
            let differences = trim_split(differences);
1✔
188
            assert!(differences.contains(&"car -> [ \"RX7\" :: \"Panda Trueno\" ]"));
1✔
189
            assert!(differences.contains(&"age -> [ 21 :: 18 ]"));
1✔
190
            assert_eq!(differences.len(), 2);
1✔
191
            assert!(right.is_empty());
1✔
192
            assert!(left.is_empty());
1✔
193
            assert!(root_mismatch.is_none());
1✔
194
        } else {
195
            panic!("wrong diffdetail");
×
196
        }
197
    }
1✔
198
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc