From ed04b406b0e77b4fc798358af3d8a96614c73c82 Mon Sep 17 00:00:00 2001 From: ridwanabdillahi <91507758+ridwanabdillahi@users.noreply.github.com> Date: Fri, 18 Mar 2022 14:30:58 -0700 Subject: [PATCH 1/3] Add Natvis definitions for some of the core types in the `http` crate. Add tests and a testing framework to ensure the Natvis definitions do not become stale and/or broken. Add a README.md to describe how to define Natvis visualizations, embed them into the `http` crate, and test them to ensure they do not become stale and/or broken. --- .github/workflows/ci.yml | 20 +++++- Cargo.toml | 17 +++++ debug_metadata/README.md | 111 ++++++++++++++++++++++++++++++++ debug_metadata/http.natvis | 106 +++++++++++++++++++++++++++++++ src/lib.rs | 6 ++ tests/debugger_visualizer.rs | 119 +++++++++++++++++++++++++++++++++++ 6 files changed, 377 insertions(+), 2 deletions(-) create mode 100644 debug_metadata/README.md create mode 100644 debug_metadata/http.natvis create mode 100644 tests/debugger_visualizer.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9e07c21f..51491a9e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,10 +11,12 @@ env: jobs: test: - name: Test ${{ matrix.rust }} + name: Test ${{ matrix.os }}-${{ matrix.rust }} #needs: [style] strategy: matrix: + + os: [ubuntu-latest] rust: - stable - beta @@ -26,8 +28,11 @@ jobs: include: - rust: nightly benches: true + # Add testing for the nightly toolchain on windows to test debugger_visualizer support. + - rust: nightly + os: windows-latest - runs-on: ubuntu-latest + runs-on: ${{ matrix.os }} steps: - name: Checkout @@ -52,6 +57,17 @@ jobs: command: test args: --benches ${{ matrix.features }} + # The #[debugger_visualizer] attribute is currently gated behind an unstable feature flag. + # In order to test the visualizers for the http crate, they have to be tested on a nightly build. + - name: Run tests with debugger_visualizer feature + if: | + matrix.os == 'windows-latest' && + matrix.rust == 'nightly' + uses: actions-rs/cargo@v1 + with: + command: test + args: --test debugger_visualizer --features 'debugger_visualizer' -- --test-threads=1 + wasm: name: WASM #needs: [style] diff --git a/Cargo.toml b/Cargo.toml index 6fe4b13c..94af6681 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,6 +37,13 @@ serde = "1.0" serde_json = "1.0" doc-comment = "0.3" criterion = "0.3.2" +debugger_test = "0.1.0" +debugger_test_parser = "0.1.0" + +[features] +# UNSTABLE FEATURES (requires Rust nightly) +# Enable to use the #[debugger_visualizer] attribute. +debugger_visualizer = [] [[bench]] name = "header_map" @@ -62,3 +69,13 @@ path = "benches/method.rs" [[bench]] name = "uri" path = "benches/uri.rs" + +[[test]] +name = "debugger_visualizer" +path = "tests/debugger_visualizer.rs" +# Do not run these tests by default. These tests need to +# be run with the additional rustc flag `--test-threads=1` +# since each test causes a debugger to attach to the current +# test process. If multiple debuggers try to attach at the same +# time, the test will fail. +test = false diff --git a/debug_metadata/README.md b/debug_metadata/README.md new file mode 100644 index 00000000..447e2faf --- /dev/null +++ b/debug_metadata/README.md @@ -0,0 +1,111 @@ +## Debugger Visualizers + +Many languages and debuggers enable developers to control how a type is +displayed in a debugger. These are called "debugger visualizations" or "debugger +views". + +The Windows debuggers (WinDbg\CDB) support defining custom debugger visualizations using +the `Natvis` framework. To use Natvis, developers write XML documents using the natvis +schema that describe how debugger types should be displayed with the `.natvis` extension. +(See: https://docs.microsoft.com/en-us/visualstudio/debugger/create-custom-views-of-native-objects?view=vs-2019) +The Natvis files provide patterns which match type names a description of how to display +those types. + +The Natvis schema can be found either online (See: https://code.visualstudio.com/docs/cpp/natvis#_schema) +or locally at `\Xml\Schemas\1033\natvis.xsd`. + +The GNU debugger (GDB) supports defining custom debugger views using Pretty Printers. +Pretty printers are written as python scripts that describe how a type should be displayed +when loaded up in GDB/LLDB. (See: https://sourceware.org/gdb/onlinedocs/gdb/Pretty-Printing.html#Pretty-Printing) +The pretty printers provide patterns, which match type names, and for matching +types, descibe how to display those types. (For writing a pretty printer, see: https://sourceware.org/gdb/onlinedocs/gdb/Writing-a-Pretty_002dPrinter.html#Writing-a-Pretty_002dPrinter). + +### Embedding Visualizers + +Through the use of the currently unstable `#[debugger_visualizer]` attribute, the `http` +crate can embed debugger visualizers into the crate metadata. + +Currently the two types of visualizers supported are Natvis and Pretty printers. + +For Natvis files, when linking an executable with a crate that includes Natvis files, +the MSVC linker will embed the contents of all Natvis files into the generated `PDB`. + +For pretty printers, the compiler will encode the contents of the pretty printer +in the `.debug_gdb_scripts` section of the `ELF` generated. + +### Testing Visualizers + +The `http` crate supports testing debugger visualizers defined for this crate. The entry point for +these tests are `tests/debugger_visualizer.rs`. These tests are defined using the `debugger_test` and +`debugger_test_parser` crates. The `debugger_test` crate is a proc macro crate which defines a +single proc macro attribute, `#[debugger_test]`. For more detailed information about this crate, +see https://crates.io/crates/debugger_test. The CI pipeline for the `http` crate has been updated +to run the debugger visualizer tests to ensure debugger visualizers do not become broken/stale. + +The `#[debugger_test]` proc macro attribute may only be used on test functions and will run the +function under the debugger specified by the `debugger` meta item. + +This proc macro attribute has 3 required values: + +1. The first required meta item, `debugger`, takes a string value which specifies the debugger to launch. +2. The second required meta item, `commands`, takes a string of new line (`\n`) separated list of debugger +commands to run. +3. The third required meta item, `expected_statements`, takes a string of new line (`\n`) separated list of +statements that must exist in the debugger output. Pattern matching through regular expressions is also +supported by using the `pattern:` prefix for each expected statement. + +#### Example: + +```rust +#[debugger_test( + debugger = "cdb", + commands = "command1\ncommand2\ncommand3", + expected_statements = "statement1\nstatement2\nstatement3")] +fn test() { + +} +``` + +Using a multiline string is also supported, with a single debugger command/expected statement per line: + +```rust +#[debugger_test( + debugger = "cdb", + commands = " +command1 +command2 +command3", + expected_statements = " +statement1 +pattern:statement[0-9]+ +statement3")] +fn test() { + +} +``` + +In the example above, the second expected statement uses pattern matching through a regular expression +by using the `pattern:` prefix. + +#### Testing Locally + +Currently, only Natvis visualizations have been defined for the `http` crate via `debug_metadata/http.natvis`, +which means the `tests/debugger_visualizer.rs` tests need to be run on Windows using the `*-pc-windows-msvc` targets. +To run these tests locally, first ensure the debugging tools for Windows are installed or install them following +the steps listed here, [Debugging Tools for Windows](https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/). +Once the debugging tools have been installed, the tests can be run in the same manner as they are in the CI +pipeline. + +#### Note + +When running the debugger visualizer tests, `tests/debugger_visualizer.rs`, they need to be run consecutively +and not in parallel. This can be achieved by passing the flag `--test-threads=1` to rustc. This is due to +how the debugger tests are run. Each test marked with the `#[debugger_test]` attribute launches a debugger +and attaches it to the current test process. If tests are running in parallel, the test will try to attach +a debugger to the current process which may already have a debugger attached causing the test to fail. + +For example: + +``` +cargo test --test debugger_visualizer --features debugger_visualizer -- --test-threads=1 +``` diff --git a/debug_metadata/http.natvis b/debug_metadata/http.natvis new file mode 100644 index 00000000..341bd8b3 --- /dev/null +++ b/debug_metadata/http.natvis @@ -0,0 +1,106 @@ + + + + {bytes.ptr,[bytes.len]sb} + + + + {{ key={key}, value={value} }} + + hash + key + value + links + + + + + + entries + extra_values + + + + + + {inner.variant0.__0,en} + {inner.variant1.__0.__0.bytes.ptr,[inner.variant1.__0.__0.bytes.len]s8} + + + + {(char*)inner.ptr,[inner.len]} + + is_sensitive + + + + + {__0} + + + + + inner + + + + + {{ method={method}, uri={uri} }} + + method + uri + version + headers + extensions + + + + + {{ status={status} }} + + status + version + headers + extensions + + + + + {__0,d} + + + + {data.bytes.ptr,[data.bytes.len]sb} + + + + + / + {data.bytes.ptr,[data.bytes.len]sb} + + + + + {inner.variant0,en} + {inner.variant1.__0,en} + {inner.variant2.__0,en} + + + + + + {authority}{path_and_query} + http://{authority}{path_and_query} + https://{authority}{path_and_query} + {scheme}://{authority}{path_and_query} + + scheme + authority + path_and_query + + + + + {__0,en} + + diff --git a/src/lib.rs b/src/lib.rs index 42118296..61d6f416 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -158,6 +158,12 @@ //! assert_eq!(uri.query(), None); //! ``` +#![cfg_attr(feature = "debugger_visualizer", feature(debugger_visualizer))] +#![cfg_attr( + feature = "debugger_visualizer", + debugger_visualizer(natvis_file = "../debug_metadata/http.natvis") +)] + #![deny(warnings, missing_docs, missing_debug_implementations)] #[cfg(test)] diff --git a/tests/debugger_visualizer.rs b/tests/debugger_visualizer.rs new file mode 100644 index 00000000..9d44879a --- /dev/null +++ b/tests/debugger_visualizer.rs @@ -0,0 +1,119 @@ +use debugger_test::debugger_test; +use http::uri::Scheme; +use http::{Request, Response, StatusCode, Uri}; + +#[inline(never)] +fn __break() {} + +#[debugger_test( + debugger = "cdb", + commands = r#" +.nvlist + +dx request +dx request.head +dx request.head.uri +dx request.head.headers + +dx response +dx response.head +dx response.head.headers + +dx uri +"#, + expected_statements = r#" +pattern:.*\.exe \(embedded NatVis .*debugger_visualizer-0\.natvis + +request [Type: http::request::Request] + [+0x000] head : { method=Get, uri=https://www.rust-lang.org/ } [Type: http::request::Parts] + [+0x0e0] body : "HELLLLOOOOO WOOOOOORLLLLDDD!" [Type: str] + +request.head : { method=Get, uri=https://www.rust-lang.org/ } [Type: http::request::Parts] + [] [Type: http::request::Parts] + [method] : Get [Type: http::method::Method] + [uri] : https://www.rust-lang.org/ [Type: http::uri::Uri] + [version] : Http11 [Type: http::version::Version] + [headers] : { len=0x3 } [Type: http::header::map::HeaderMap] + [extensions] [Type: http::extensions::Extensions] + +request.head.uri : https://www.rust-lang.org/ [Type: http::uri::Uri] + [] [Type: http::uri::Uri] + [scheme] : Https [Type: http::uri::scheme::Scheme] + [authority] : www.rust-lang.org [Type: http::uri::authority::Authority] + [path_and_query] : / [Type: http::uri::path::PathAndQuery] + +request.head.headers : { len=0x3 } [Type: http::header::map::HeaderMap] + [] [Type: http::header::map::HeaderMap] + [extra_values] : { len=0x0 } [Type: alloc::vec::Vec,alloc::alloc::Global>] + [len] : 0x3 [Type: unsigned __int64] + [capacity] : 0x6 [Type: unsigned __int64] + [1] : { key=UserAgent, value="my-awesome-agent/1.0" } [Type: http::header::map::Bucket] + [2] : { key=ContentLanguage, value="en_US" } [Type: http::header::map::Bucket] + +response [Type: http::response::Response] + [+0x000] head : { status=404 } [Type: http::response::Parts] + [+0x070] body : "HELLLLOOOOO WOOOOOORLLLLDDD!" [Type: str] + +response.head : { status=404 } [Type: http::response::Parts] + [] [Type: http::response::Parts] + [status] : 404 [Type: http::status::StatusCode] + [version] : Http11 [Type: http::version::Version] + [headers] : { len=0x0 } [Type: http::header::map::HeaderMap] + [extensions] [Type: http::extensions::Extensions] + +response.head.headers : { len=0x0 } [Type: http::header::map::HeaderMap] + [] [Type: http::header::map::HeaderMap] + [extra_values] : { len=0x0 } [Type: alloc::vec::Vec,alloc::alloc::Global>] + [len] : 0x0 [Type: unsigned __int64] + [capacity] : 0x0 [Type: unsigned __int64] + +uri : https://www.rust-lang.org/index.html [Type: http::uri::Uri] + [] [Type: http::uri::Uri] + [scheme] : Https [Type: http::uri::scheme::Scheme] + [authority] : www.rust-lang.org [Type: http::uri::authority::Authority] + [path_and_query] : /index.html [Type: http::uri::path::PathAndQuery] +"# +)] +fn test_debugger_visualizer() { + let request = Request::builder() + .uri("https://www.rust-lang.org/") + .header(http::header::AGE, 0) + .header(http::header::USER_AGENT, "my-awesome-agent/1.0") + .header(http::header::CONTENT_LANGUAGE, "en_US") + .body("HELLLLOOOOO WOOOOOORLLLLDDD!") + .unwrap(); + + assert!(request.headers().contains_key(http::header::USER_AGENT)); + assert_eq!( + "www.rust-lang.org", + request.uri().authority().unwrap().host() + ); + + let response = send(request).expect("http response is success"); + assert!(!response.status().is_success()); + + let uri = "https://www.rust-lang.org/index.html" + .parse::() + .unwrap(); + assert_eq!(uri.scheme(), Some(&Scheme::HTTPS)); + assert_eq!(uri.host(), Some("www.rust-lang.org")); + assert_eq!(uri.path(), "/index.html"); + assert_eq!(uri.query(), None); + __break(); +} + +fn send(req: Request<&str>) -> http::Result> { + if req.uri() != "/awesome-url" { + let result = Response::builder() + .status(StatusCode::NOT_FOUND) + .body(req.body().clone()); + + return result; + } + + let body = req.body().clone(); + + let response = Response::builder().status(StatusCode::OK).body(body); + + response +} From c19f31687632add55c73442cf7226a0830cde7da Mon Sep 17 00:00:00 2001 From: ridwanabdillahi <91507758+ridwanabdillahi@users.noreply.github.com> Date: Fri, 12 Aug 2022 11:04:30 -0700 Subject: [PATCH 2/3] Minor cleanups to debugger_visualizer test expectations. --- tests/debugger_visualizer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/debugger_visualizer.rs b/tests/debugger_visualizer.rs index 9d44879a..a9b102f7 100644 --- a/tests/debugger_visualizer.rs +++ b/tests/debugger_visualizer.rs @@ -22,7 +22,7 @@ dx response.head.headers dx uri "#, expected_statements = r#" -pattern:.*\.exe \(embedded NatVis .*debugger_visualizer-0\.natvis +pattern:.*\.exe \(embedded NatVis .*-[0-9]+\.natvis request [Type: http::request::Request] [+0x000] head : { method=Get, uri=https://www.rust-lang.org/ } [Type: http::request::Parts] From fa0f8ea43f5d2f59b7c7ddf04ebe09db067c304e Mon Sep 17 00:00:00 2001 From: ridwanabdillahi <91507758+ridwanabdillahi@users.noreply.github.com> Date: Thu, 18 Aug 2022 11:56:08 -0700 Subject: [PATCH 3/3] Update Natvis defintions following latest changes to the debuginfo layouts for enums generated by the Rust compiler. --- debug_metadata/http.natvis | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/debug_metadata/http.natvis b/debug_metadata/http.natvis index 341bd8b3..dad93f92 100644 --- a/debug_metadata/http.natvis +++ b/debug_metadata/http.natvis @@ -22,9 +22,9 @@ - - {inner.variant0.__0,en} - {inner.variant1.__0.__0.bytes.ptr,[inner.variant1.__0.__0.bytes.len]s8} + + {inner.variant0.value.__0,en} + {inner.variant1.value.__0.__0.bytes.ptr,[inner.variant1.value.__0.__0.bytes.len]s8} @@ -80,15 +80,15 @@ - + {inner.variant0,en} - {inner.variant1.__0,en} - {inner.variant2.__0,en} + {inner.variant1.value.__0,en} + {inner.variant2.value.__0,en} - - + + {authority}{path_and_query} http://{authority}{path_and_query} https://{authority}{path_and_query}