From: Roy7Kim Date: Thu, 4 May 2023 06:38:59 +0000 (+0900) Subject: Import debugger_test_parser 0.1.3 X-Git-Tag: upstream/0.1.3 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=a1d6a9c4783b900a56770a4598a11c020e52f476;p=platform%2Fupstream%2Frust-debugger_test_parser.git Import debugger_test_parser 0.1.3 --- a1d6a9c4783b900a56770a4598a11c020e52f476 diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json new file mode 100644 index 0000000..20db627 --- /dev/null +++ b/.cargo_vcs_info.json @@ -0,0 +1,6 @@ +{ + "git": { + "sha1": "5a2c4884a9f345b294fcf1ef7eef26e04c648cb8" + }, + "path_in_vcs": "debugger_test_parser" +} \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..edbacfd --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,38 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2018" +name = "debugger_test_parser" +version = "0.1.3" +description = """ +Provides a library for parsing the output of a debugger and verifying the contents. +""" +homepage = "https://github.com/microsoft/rust_debugger_test/debugger_test_parser" +documentation = "https://docs.rs/debugger_test_parser" +readme = "README.md" +keywords = [ + "debugger", + "cdb", + "natvis", + "debugger_visualizer", +] +license = "MIT OR Apache-2.0" +repository = "https://github.com/microsoft/rust_debugger_test/debugger_test_parser" + +[dependencies.anyhow] +version = "1.0.58" + +[dependencies.log] +version = "0.4.17" + +[dependencies.regex] +version = "1.6.0" diff --git a/Cargo.toml.orig b/Cargo.toml.orig new file mode 100644 index 0000000..ec33a9d --- /dev/null +++ b/Cargo.toml.orig @@ -0,0 +1,18 @@ +[package] +name = "debugger_test_parser" +version = "0.1.3" +edition = "2018" +description = """ +Provides a library for parsing the output of a debugger and verifying the contents. +""" +documentation = "https://docs.rs/debugger_test_parser" +readme = "README.md" +homepage = "https://github.com/microsoft/rust_debugger_test/debugger_test_parser" +repository = "https://github.com/microsoft/rust_debugger_test/debugger_test_parser" +license = "MIT OR Apache-2.0" +keywords = ["debugger", "cdb", "natvis", "debugger_visualizer"] + +[dependencies] +anyhow = "1.0.58" +log = "0.4.17" +regex = "1.6.0" diff --git a/README.md b/README.md new file mode 100644 index 0000000..408e842 --- /dev/null +++ b/README.md @@ -0,0 +1,27 @@ +# debugger_test_parser + +This crate provides a way of parsing the output of a debugger and verifying any expected content has been found. + +To use, add this crate as dependency in your `Cargo.toml`. + +## Contributing + +This project welcomes contributions and suggestions. Most contributions require you to agree to a +Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us +the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com. + +When you submit a pull request, a CLA bot will automatically determine whether you need to provide +a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions +provided by the bot. You will only need to do this once across all repos using our CLA. + +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). +For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or +contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. + +## Trademarks + +This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft +trademarks or logos is subject to and must follow +[Microsoft's Trademark & Brand Guidelines](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general). +Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship. +Any use of third-party trademarks or logos are subject to those third-party's policies. diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..199c66f --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,110 @@ +use regex::Regex; + +enum OutputParsingStyle { + LiteralMatch(String), + PatternMatch(Regex), +} + +const PATTERN_PREFIX: &str = "pattern:"; + +/// Parse the output of a debugger and verify that the expected contents +/// are found. If content was expected in the debugger output that is not +/// found, stop and return an error. +pub fn parse(debugger_output: String, expected_contents: Vec<&str>) -> anyhow::Result<()> { + // If there are no check statements, return early. + if expected_contents.len() == 0 { + log::info!("No expected contents found."); + return anyhow::Ok(()); + } + + // Trim whitespace at the beginning and end of output lines. + let debugger_output_lines = debugger_output + .trim() + .lines() + .map(|line| line.trim()) + .collect::>(); + + // Trim whitespace at the beginning and end of expected contents. + let expected_contents = expected_contents + .iter() + .filter_map(|line| { + let str = line.trim(); + match str.is_empty() { + false => Some(str), + true => None, + } + }) + .map(|line| line.trim()) + .collect::>(); + + let mut index = 0; + + for expected in expected_contents { + let parsing_style = get_output_parsing_style(expected)?; + loop { + if index >= debugger_output_lines.len() { + let error_msg = format_error_message(&parsing_style); + anyhow::bail!( + "Unable to find expected content in the debugger output. {}", + error_msg + ); + } + + let debugger_output_line = debugger_output_lines[index]; + index += 1; + + // Search for the expected line or pattern within the current debugger output line. + match &parsing_style { + OutputParsingStyle::LiteralMatch(literal_str) => { + let str = literal_str.as_str(); + if debugger_output_line.contains(&str) { + log::info!( + "Expected content found: `{}` at line `{}`", + str, + debugger_output_line + ); + break; + } + } + OutputParsingStyle::PatternMatch(re) => { + if re.is_match(&debugger_output_line) { + log::info!("Expected pattern found: `{}`", debugger_output_line); + break; + } + } + } + } + } + + anyhow::Ok(()) +} + +fn format_error_message(parsing_style: &OutputParsingStyle) -> String { + match parsing_style { + OutputParsingStyle::LiteralMatch(literal_string) => { + format!("Missing line: `{}`", literal_string) + } + OutputParsingStyle::PatternMatch(pattern) => { + format!("Found 0 matches for pattern: `{}`", pattern.to_string()) + } + } +} + +/// Get the parsing style for the given expected statement. +fn get_output_parsing_style(expected_output: &str) -> anyhow::Result { + let parsing_style = if expected_output.starts_with(PATTERN_PREFIX) { + let re_pattern = expected_output + .strip_prefix(PATTERN_PREFIX) + .expect("string starts with `pattern:`"); + let re = match Regex::new(re_pattern) { + Ok(re) => re, + Err(error) => anyhow::bail!("Invalid regex pattern: {}\n{}", re_pattern, error), + }; + + OutputParsingStyle::PatternMatch(re) + } else { + OutputParsingStyle::LiteralMatch(String::from(expected_output)) + }; + + Ok(parsing_style) +} diff --git a/tests/test.rs b/tests/test.rs new file mode 100644 index 0000000..674f01b --- /dev/null +++ b/tests/test.rs @@ -0,0 +1,210 @@ +use debugger_test_parser::parse; + +/// Verify that a test failed with a specific error message. +fn verify_expected_failure(result: anyhow::Result<()>, expected_err_msg: &str) { + let error = result + .expect_err(format!("Expected error message missing: `{}`.", expected_err_msg).as_str()); + assert_eq!(expected_err_msg, format!("{}", error)); +} + +/// Test parsing empty debugger output. +#[test] +fn test_parse_empty_output() { + let output = String::from(""); + let expected_contents = Vec::with_capacity(0); + parse(output, expected_contents).expect("able to parse output."); +} + +/// Test parsing debugger output for a single command. +/// No expected content. +#[test] +fn test_parse_output_command() { + let output = String::from( + r#" + dv + var1 = 0 + var2 = { len = 3 } + var3 = { struct } + "#, + ); + + let expected_contents = Vec::with_capacity(0); + parse(output, expected_contents).expect("able to parse output."); +} + +/// Test parsing a single debugger output. +/// Verify expected content. +#[test] +fn test_verify_output_command() { + let output = String::from( + r#" + dv + var1 = 0 + var2 = { len = 3 } + var3 = { struct } + "#, + ); + + let expected_contents = vec!["var1 = 0"]; + parse(output, expected_contents).expect("able to parse output."); +} + +#[test] +fn test_trim_expected_contents() { + let output = String::from( + r#" + dv + var1 = 0 + var2 = { len = 3 } + var3 = { struct } + "#, + ); + + let expected_contents = vec![" var1 = 0"]; + parse(output, expected_contents).expect("able to parse output."); +} + +/// Test parsing debugger output for mutliple commands. +/// Verify expected content. +#[test] +fn test_verify_output_multiple_commands() { + let output = String::from( + r#" + .nvlist + a.exe (embedded NatVis "path\to\foo.natvis") + dx point_a + point_a : (0, 0) [Type: foo::Point] + [] [Type: foo::Point] + [x] : 0 [Type: int] + [y] : 0 [Type: int] + dx point_b + point_b : (5, 8) [Type: foo::Point] + [] [Type: foo::Point] + [x] : 5 [Type: int] + [y] : 8 [Type: int] + dx line + line : ((0, 0), (5, 8)) [Type: foo::Line] + [] [Type: foo::Line] + [a] : (0, 0) [Type: foo::Point] + [b] : (5, 8) [Type: foo::Point] + dx person + person : "Person A" is 10 years old. [Type: foo::Person] + [] [Type: foo::Person] + [name] : "Person A" [Type: alloc::string::String] + [age] : 10 [Type: int] + "#, + ); + + let expected_contents = vec![ + r#"pattern:a\.exe \(embedded NatVis ".*foo\.natvis"\)"#, + "point_a : (0, 0) [Type: foo::Point]", + "[x] : 0 [Type: int]", + "person : \"Person A\" is 10 years old. [Type: foo::Person]", + "[name] : \"Person A\" [Type: alloc::string::String]", + ]; + parse(output, expected_contents).expect("able to parse output."); +} + +/// Test expected content not found in debugger output due to incorrect ordering. +/// Parsing fails. +#[test] +fn test_err_expected_string_not_found() { + let output = String::from( + r#" + .nvlist + a.exe (embedded NatVis "path\to\foo.natvis") + dx point_a + point_a : (0, 0) [Type: foo::Point] + [] [Type: foo::Point] + [x] : 0 [Type: int] + [y] : 0 [Type: int] + dx point_b + point_b : (5, 8) [Type: foo::Point] + [] [Type: foo::Point] + [x] : 5 [Type: int] + [y] : 8 [Type: int] + dx line + line : ((0, 0), (5, 8)) [Type: foo::Line] + [] [Type: foo::Line] + [a] : (0, 0) [Type: foo::Point] + [b] : (5, 8) [Type: foo::Point] + dx person + person : "Person A" is 10 years old. [Type: foo::Person] + [] [Type: foo::Person] + [name] : "Person A" [Type: alloc::string::String] + [age] : 10 [Type: int] + "#, + ); + + let expected_contents = vec![ + "person : \"Person A\" is 10 years old. [Type: foo::Person]", + "point_a : (0, 0) [Type: foo::Point]", + ]; + + let expected_err_msg = "Unable to find expected content in the debugger output. Missing line: `point_a : (0, 0) [Type: foo::Point]`"; + verify_expected_failure(parse(output, expected_contents), expected_err_msg); +} + +/// Test expected pattern not found in debugger output due to incorrect ordering. +/// Parsing fails. +#[test] +fn test_err_expected_pattern_not_found() { + let output = String::from( + r#" + .nvlist + a.exe (embedded NatVis "path\to\foo.natvis") + dx point_a + point_a : (0, 0) [Type: foo::Point] + [] [Type: foo::Point] + [x] : 0 [Type: int] + [y] : 0 [Type: int] + dx point_b + point_b : (5, 8) [Type: foo::Point] + [] [Type: foo::Point] + [x] : 5 [Type: int] + [y] : 8 [Type: int] + dx line + line : ((0, 0), (5, 8)) [Type: foo::Line] + [] [Type: foo::Line] + [a] : (0, 0) [Type: foo::Point] + [b] : (5, 8) [Type: foo::Point] + dx person + person : "Person A" is 10 years old. [Type: foo::Person] + [] [Type: foo::Person] + [name] : "Person A" [Type: alloc::string::String] + [age] : 10 [Type: int] + "#, + ); + + let expected_contents = vec![ + "point_a : (0, 0) [Type: foo::Point]", + r#"pattern:a\.exe \(embedded NatVis ".*foo\.natvis"\)"#, + ]; + + let expected_err_msg = "Unable to find expected content in the debugger output. Found 0 matches for pattern: `a\\.exe \\(embedded NatVis \".*foo\\.natvis\"\\)`"; + verify_expected_failure(parse(output, expected_contents), expected_err_msg); +} + +/// Test expected pattern is not a valid regex. +/// Parsing fails. +#[test] +fn test_err_expected_pattern_not_valid() { + let output = String::from( + r#" + .nvlist + a.exe (embedded NatVis "path\to\foo.natvis") + dv + vec { len = 5 } + vec2 { len = 1 } + "#, + ); + + let expected_contents = vec![r#"pattern:vec2 { len = 1 }"#]; + + let expected_err_msg = r#"Invalid regex pattern: vec2 { len = 1 } +regex parse error: + vec2 { len = 1 } + ^ +error: repetition quantifier expects a valid decimal"#; + verify_expected_failure(parse(output, expected_contents), expected_err_msg); +}