ci_manager/err_parse/yocto/
util.rs

1use crate::*;
2
3#[derive(
4    Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Copy, Display, EnumString, EnumIter,
5)]
6pub enum YoctoFailureKind {
7    /// The 6 standard tasks in Yocto <https://docs.yoctoproject.org/ref-manual/tasks.html>
8    #[strum(serialize = "do_build")]
9    DoBuild,
10    #[strum(serialize = "do_compile")]
11    DoCompile,
12    #[strum(serialize = "do_compile_ptest_base")]
13    DoCompilePtestBase,
14    #[strum(serialize = "do_configure")]
15    DoConfigure,
16    #[strum(serialize = "do_configure_ptest_base")]
17    DoConfigurePtestBase,
18    #[strum(serialize = "do_deploy")]
19    DoDeploy,
20    /// Other tasks
21    #[strum(serialize = "do_fetch")]
22    DoFetch,
23    #[strum(serialize = "do_rootfs")]
24    DoRootFs,
25    #[strum(serialize = "do_image")]
26    DoImage,
27    /// If it's a type of failure we're not familiar with or parsing fails, default to this
28    #[default]
29    #[strum(serialize = "misc")]
30    Misc,
31}
32
33impl YoctoFailureKind {
34    /// Takes in a yocto logfile filename such as `log.do_fetch.21616` and attempts to determine the type
35    /// of yocto task the the logfile is associated with.
36    ///
37    /// # Example
38    /// ```
39    /// # use ci_manager::err_parse::yocto::util::YoctoFailureKind;
40    /// let kind = YoctoFailureKind::parse_from_logfilename("log.do_fetch.21616").unwrap();
41    /// assert_eq!(kind, YoctoFailureKind::DoFetch);
42    ///
43    /// // Infallible if you're sure the filename is a yocto log but it might not be a known task
44    /// let kind = YoctoFailureKind::parse_from_logfilename("log.some_custom_task.21616").unwrap_or_default();
45    /// assert_eq!(kind, YoctoFailureKind::Misc);
46    /// ```
47    pub fn parse_from_logfilename(fname: &str) -> Result<Self> {
48        for variant in YoctoFailureKind::iter() {
49            let variant_as_str = variant.to_string();
50            if fname.contains(&variant_as_str) {
51                return Ok(variant);
52            }
53        }
54        bail!("Could not determine task from input: {fname}")
55    }
56}
57
58/// Find the `--- Error summary ---` section in the log and return the rest of the log.
59pub fn yocto_error_summary(log: &str) -> Result<String> {
60    const YOCTO_ERROR_SUMMARY_SIGNATURE: &str = "--- Error summary ---";
61    let error_summary = log
62        .split(YOCTO_ERROR_SUMMARY_SIGNATURE)
63        .collect::<Vec<&str>>()
64        .pop()
65        .context("No error summary found")?;
66    Ok(error_summary.trim().to_string())
67}
68
69/// Trim the trailing `error: Recipe` lines from the error summary
70/// This is to remove the noise of just recipe failures
71pub fn trim_trailing_just_recipes(log: &str) -> Result<String> {
72    let trimmed = log
73        .lines()
74        .rev()
75        .skip_while(|line| {
76            line.starts_with("error: Recipe ")
77                // Also skip the last line that looks like `##[error]Process completed with exit code 2.`
78                || line.starts_with("##[error]Process completed with exit code")
79        })
80        .collect::<Vec<&str>>()
81        .iter()
82        .rev()
83        .fold(String::with_capacity(log.len()), |acc, line| {
84            acc + line + "\n"
85        });
86    Ok(trimmed)
87}
88
89/// Find the kind of yocto failure in the string e.g. this would be `do_fetch`
90/// ERROR: Logfile of failure stored in: /app/yocto/build/tmp/work/x86_64-linux/sqlite3-native/3.43.2/temp/log.do_fetch.21616
91///
92/// # Example
93/// ```
94/// use ci_manager::err_parse::yocto::util::find_yocto_failure_log_str;
95/// let log = r#"ERROR: Some error message
96/// ERROR: Logfile of failure stored in: /app/yocto/build/tmp/work/x86_64-linux/sqlite3-native/3.43.2/temp/log.do_fetch.21616
97/// ERROR: Some other error message"#;
98///
99/// let failure_log_str = find_yocto_failure_log_str(log).unwrap();
100///
101/// assert_eq!(failure_log_str, "ERROR: Logfile of failure stored in: /app/yocto/build/tmp/work/x86_64-linux/sqlite3-native/3.43.2/temp/log.do_fetch.21616");
102/// ```
103///
104///
105pub fn find_yocto_failure_log_str(log: &str) -> Result<&str> {
106    let log_file_line = log
107        .lines()
108        .find(|line| line.contains("Logfile of failure stored in"))
109        .context("No log file line found")?;
110
111    Ok(log_file_line)
112}
113
114#[cfg(test)]
115mod tests {
116    use super::*;
117    use pretty_assertions::assert_eq;
118    use std::str::FromStr;
119
120    const ERROR_SUMMARY_TEST_STR: &str = r#"ERROR: sqlite3-native-3_3.43.2-r0 do_fetch: Bitbake Fetcher Error: MalformedUrl('${SOURCE_MIRROR_URL}')
121    ERROR: Logfile of failure stored in: /app/yocto/build/tmp/work/x86_64-linux/sqlite3-native/3.43.2/temp/log.do_fetch.21616
122    ERROR: Task (virtual:native:/app/yocto/build/../poky/meta/recipes-support/sqlite/sqlite3_3.43.2.bb:do_fetch) failed with exit code '1'
123
124    2024-02-11 00:09:04 - ERROR    - Command "/app/yocto/poky/bitbake/bin/bitbake -c build test-template-ci-xilinx-image package-index" failed with error 1"#;
125
126    #[test]
127    fn test_determine_yocto_error_kind() {
128        let task = "do_build";
129        assert_eq!(
130            YoctoFailureKind::from_str(task).unwrap(),
131            YoctoFailureKind::DoBuild
132        );
133    }
134
135    #[test]
136    fn test_yocto_error_from_error_message() {
137        // find the part of the string after
138        let log_file_line = ERROR_SUMMARY_TEST_STR
139            .lines()
140            .find(|line| line.contains("Logfile of failure stored in"))
141            .ok_or("No log file line found")
142            .unwrap();
143        dbg!(log_file_line);
144        // Get the path stored after `Logfile of failure stored in: `
145        let logfile_path = log_file_line
146            .split("Logfile of failure stored in: ")
147            .collect::<Vec<&str>>()
148            .pop()
149            .ok_or("No log file found");
150
151        let path = std::path::PathBuf::from(logfile_path.unwrap());
152        let fname = path.file_stem().unwrap().to_str().unwrap();
153
154        let yocto_failure = YoctoFailureKind::parse_from_logfilename(fname).unwrap();
155        assert_eq!(yocto_failure, YoctoFailureKind::DoFetch);
156    }
157
158    const TEST_NOT_TRIMMED_YOCTO_ERROR_SUMMARY: &str = r#"ERROR: sqlite3-native-3_3.43.2-r0 do_fetch: Bitbake Fetcher Error: MalformedUrl('${SOURCE_MIRROR_URL}')
159ERROR: Logfile of failure stored in: /app/yocto/build/tmp/work/x86_64-linux/sqlite3-native/3.43.2/temp/log.do_fetch.21665
160ERROR: Task (virtual:native:/app/yocto/build/../poky/meta/recipes-support/sqlite/sqlite3_3.43.2.bb:do_fetch) failed with exit code '1'
161
1622024-02-16 12:45:43 - ERROR    - Command "/app/yocto/poky/bitbake/bin/bitbake -c build test-template-ci-xilinx-image package-index" failed with error 1
163error: Recipe `in-container-build-ci-image` failed on line 31 with exit code 2
164error: Recipe `run-in-docker` failed with exit code 2
165error: Recipe `build-ci-image` failed with exit code 2"#;
166
167    const TEST_EXPECT_TRIMMED_YOCTO_ERROR_SUMMARY: &str = r#"ERROR: sqlite3-native-3_3.43.2-r0 do_fetch: Bitbake Fetcher Error: MalformedUrl('${SOURCE_MIRROR_URL}')
168ERROR: Logfile of failure stored in: /app/yocto/build/tmp/work/x86_64-linux/sqlite3-native/3.43.2/temp/log.do_fetch.21665
169ERROR: Task (virtual:native:/app/yocto/build/../poky/meta/recipes-support/sqlite/sqlite3_3.43.2.bb:do_fetch) failed with exit code '1'
170
1712024-02-16 12:45:43 - ERROR    - Command "/app/yocto/poky/bitbake/bin/bitbake -c build test-template-ci-xilinx-image package-index" failed with error 1
172"#;
173
174    #[test]
175    pub fn test_trim_yocto_error_summary() {
176        let trimmed = trim_trailing_just_recipes(TEST_NOT_TRIMMED_YOCTO_ERROR_SUMMARY).unwrap();
177        eprintln!("{trimmed}");
178        assert_eq!(trimmed, TEST_EXPECT_TRIMMED_YOCTO_ERROR_SUMMARY);
179    }
180}