diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9e07c21f..fdbca225 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 regex 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/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 +}