Skip to content

Commit

Permalink
Add Natvis definitions for some of the core types in the http crate…
Browse files Browse the repository at this point in the history
…. Add tests and a testing framework to ensure the Natvis definitions do not become stale and/or broken.
  • Loading branch information
ridwanabdillahi committed Jul 27, 2022
1 parent 34a9d6b commit 270ad8c
Show file tree
Hide file tree
Showing 5 changed files with 266 additions and 2 deletions.
20 changes: 18 additions & 2 deletions .github/workflows/ci.yml
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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]
Expand Down
17 changes: 17 additions & 0 deletions Cargo.toml
Expand Up @@ -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"
Expand All @@ -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
106 changes: 106 additions & 0 deletions debug_metadata/http.natvis
@@ -0,0 +1,106 @@
<?xml version="1.0" encoding="utf-8"?>
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
<Type Name="http::byte_str::ByteStr">
<DisplayString>{bytes.ptr,[bytes.len]sb}</DisplayString>
</Type>

<Type Name="http::header::map::Bucket&lt;*&gt;">
<DisplayString>{{ key={key}, value={value} }}</DisplayString>
<Expand>
<Item Name="[hash]">hash</Item>
<Item Name="[key]">key</Item>
<Item Name="[value]">value</Item>
<Item Name="[links]">links</Item>
</Expand>
</Type>

<Type Name="http::header::map::HeaderMap&lt;*&gt;">
<Expand>
<ExpandedItem>entries</ExpandedItem>
<Item Name="[extra_values]">extra_values</Item>
</Expand>
</Type>

<Type Name="http::header::name::HeaderName">
<Intrinsic Name="inner_tag" Expression="inner.discriminant" />
<DisplayString Condition="inner_tag() == 0">{inner.variant0.__0,en}</DisplayString>
<DisplayString Condition="inner_tag() == 1">{inner.variant1.__0.__0.bytes.ptr,[inner.variant1.__0.__0.bytes.len]s8}</DisplayString>
</Type>

<Type Name="http::header::value::HeaderValue">
<DisplayString>{(char*)inner.ptr,[inner.len]}</DisplayString>
<Expand>
<Item Name="[is_sensitive]">is_sensitive</Item>
</Expand>
</Type>

<Type Name="http::method::Method">
<DisplayString>{__0}</DisplayString>
</Type>

<Type Name="http::request::Builder">
<Expand>
<ExpandedItem>inner</ExpandedItem>
</Expand>
</Type>

<Type Name="http::request::Parts">
<DisplayString>{{ method={method}, uri={uri} }}</DisplayString>
<Expand>
<Item Name="[method]">method</Item>
<Item Name="[uri]">uri</Item>
<Item Name="[version]">version</Item>
<Item Name="[headers]">headers</Item>
<Item Name="[extensions]">extensions</Item>
</Expand>
</Type>

<Type Name="http::response::Parts">
<DisplayString>{{ status={status} }}</DisplayString>
<Expand>
<Item Name="[status]">status</Item>
<Item Name="[version]">version</Item>
<Item Name="[headers]">headers</Item>
<Item Name="[extensions]">extensions</Item>
</Expand>
</Type>

<Type Name="http::status::StatusCode">
<DisplayString>{__0,d}</DisplayString>
</Type>

<Type Name="http::uri::authority::Authority">
<DisplayString Condition="data.bytes.len &gt; 0">{data.bytes.ptr,[data.bytes.len]sb}</DisplayString>
</Type>

<Type Name="http::uri::path::PathAndQuery">
<Intrinsic Name="path_length" Expression="data.bytes.len" />
<DisplayString Condition="path_length() == 0">/</DisplayString>
<DisplayString>{data.bytes.ptr,[data.bytes.len]sb}</DisplayString>
</Type>

<Type Name="http::uri::scheme::Scheme">
<Intrinsic Name="inner_tag" Expression="inner.discriminant" />
<DisplayString Condition="inner_tag() == 0">{inner.variant0,en}</DisplayString>
<DisplayString Condition="inner_tag() == 1">{inner.variant1.__0,en}</DisplayString>
<DisplayString Condition="inner_tag() == 2">{inner.variant2.__0,en}</DisplayString>
</Type>

<Type Name="http::uri::Uri">
<Intrinsic Name="scheme_tag" Expression="scheme.inner.discriminant" />
<Intrinsic Name="protocol_tag" Expression="scheme.inner.variant1.__0" />
<DisplayString Condition="scheme_tag() == 0">{authority}{path_and_query}</DisplayString>
<DisplayString Condition="scheme_tag() == 1 &amp;&amp; protocol_tag() == 0">http://{authority}{path_and_query}</DisplayString>
<DisplayString Condition="scheme_tag() == 1 &amp;&amp; protocol_tag() == 1">https://{authority}{path_and_query}</DisplayString>
<DisplayString>{scheme}://{authority}{path_and_query}</DisplayString>
<Expand>
<Item Name="[scheme]">scheme</Item>
<Item Name="[authority]">authority</Item>
<Item Name="[path_and_query]">path_and_query</Item>
</Expand>
</Type>

<Type Name="http::version::Version">
<DisplayString>{__0,en}</DisplayString>
</Type>
</AutoVisualizer>
6 changes: 6 additions & 0 deletions src/lib.rs
Expand Up @@ -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)]
Expand Down
119 changes: 119 additions & 0 deletions 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<str>]
[+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]
[<Raw View>] [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<http::header::value::HeaderValue>]
[extensions] [Type: http::extensions::Extensions]
request.head.uri : https://www.rust-lang.org/ [Type: http::uri::Uri]
[<Raw View>] [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<http::header::value::HeaderValue>]
[<Raw View>] [Type: http::header::map::HeaderMap<http::header::value::HeaderValue>]
[extra_values] : { len=0x0 } [Type: alloc::vec::Vec<http::header::map::ExtraValue<http::header::value::HeaderValue>,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<http::header::value::HeaderValue>]
[2] : { key=ContentLanguage, value="en_US" } [Type: http::header::map::Bucket<http::header::value::HeaderValue>]
response [Type: http::response::Response<str>]
[+0x000] head : { status=404 } [Type: http::response::Parts]
[+0x070] body : "HELLLLOOOOO WOOOOOORLLLLDDD!" [Type: str]
response.head : { status=404 } [Type: http::response::Parts]
[<Raw View>] [Type: http::response::Parts]
[status] : 404 [Type: http::status::StatusCode]
[version] : Http11 [Type: http::version::Version]
[headers] : { len=0x0 } [Type: http::header::map::HeaderMap<http::header::value::HeaderValue>]
[extensions] [Type: http::extensions::Extensions]
response.head.headers : { len=0x0 } [Type: http::header::map::HeaderMap<http::header::value::HeaderValue>]
[<Raw View>] [Type: http::header::map::HeaderMap<http::header::value::HeaderValue>]
[extra_values] : { len=0x0 } [Type: alloc::vec::Vec<http::header::map::ExtraValue<http::header::value::HeaderValue>,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]
[<Raw View>] [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::<Uri>()
.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<Response<&str>> {
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
}

0 comments on commit 270ad8c

Please sign in to comment.