Import debugger_test_parser 0.1.3 upstream upstream/0.1.3
authorRoy7Kim <myoungwoon.kim@samsung.com>
Thu, 4 May 2023 06:38:59 +0000 (15:38 +0900)
committerRoy7Kim <myoungwoon.kim@samsung.com>
Thu, 4 May 2023 06:38:59 +0000 (15:38 +0900)
.cargo_vcs_info.json [new file with mode: 0644]
Cargo.toml [new file with mode: 0644]
Cargo.toml.orig [new file with mode: 0644]
README.md [new file with mode: 0644]
src/lib.rs [new file with mode: 0644]
tests/test.rs [new file with mode: 0644]

diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json
new file mode 100644 (file)
index 0000000..20db627
--- /dev/null
@@ -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 (file)
index 0000000..edbacfd
--- /dev/null
@@ -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 (file)
index 0000000..ec33a9d
--- /dev/null
@@ -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 (file)
index 0000000..408e842
--- /dev/null
+++ b/README.md
@@ -0,0 +1,27 @@
+# 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
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644 (file)
index 0000000..199c66f
--- /dev/null
@@ -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::<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)
+}
diff --git a/tests/test.rs b/tests/test.rs
new file mode 100644 (file)
index 0000000..674f01b
--- /dev/null
@@ -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]
+        [<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);
+}