--- /dev/null
+# debugger_test_parser\r
+\r
+This crate provides a way of parsing the output of a debugger and verifying any expected content has been found.\r
+\r
+To use, add this crate as dependency in your `Cargo.toml`.\r
+\r
+## Contributing\r
+\r
+This project welcomes contributions and suggestions. Most contributions require you to agree to a\r
+Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us\r
+the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com.\r
+\r
+When you submit a pull request, a CLA bot will automatically determine whether you need to provide\r
+a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions\r
+provided by the bot. You will only need to do this once across all repos using our CLA.\r
+\r
+This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).\r
+For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or\r
+contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.\r
+\r
+## Trademarks\r
+\r
+This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft \r
+trademarks or logos is subject to and must follow \r
+[Microsoft's Trademark & Brand Guidelines](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general).\r
+Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship.\r
+Any use of third-party trademarks or logos are subject to those third-party's policies.\r
--- /dev/null
+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::<Vec<&str>>();
+
+ // 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::<Vec<&str>>();
+
+ 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<OutputParsingStyle> {
+ 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)
+}
--- /dev/null
+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]
+ [<Raw View>] [Type: foo::Point]
+ [x] : 0 [Type: int]
+ [y] : 0 [Type: int]
+ dx point_b
+ point_b : (5, 8) [Type: foo::Point]
+ [<Raw View>] [Type: foo::Point]
+ [x] : 5 [Type: int]
+ [y] : 8 [Type: int]
+ dx line
+ line : ((0, 0), (5, 8)) [Type: foo::Line]
+ [<Raw View>] [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]
+ [<Raw View>] [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]
+ [<Raw View>] [Type: foo::Point]
+ [x] : 0 [Type: int]
+ [y] : 0 [Type: int]
+ dx point_b
+ point_b : (5, 8) [Type: foo::Point]
+ [<Raw View>] [Type: foo::Point]
+ [x] : 5 [Type: int]
+ [y] : 8 [Type: int]
+ dx line
+ line : ((0, 0), (5, 8)) [Type: foo::Line]
+ [<Raw View>] [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]
+ [<Raw View>] [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]
+ [<Raw View>] [Type: foo::Point]
+ [x] : 0 [Type: int]
+ [y] : 0 [Type: int]
+ dx point_b
+ point_b : (5, 8) [Type: foo::Point]
+ [<Raw View>] [Type: foo::Point]
+ [x] : 5 [Type: int]
+ [y] : 8 [Type: int]
+ dx line
+ line : ((0, 0), (5, 8)) [Type: foo::Line]
+ [<Raw View>] [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]
+ [<Raw View>] [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);
+}