gh_workflow_parser/err_msg_parse/yocto_err/
util.rs

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