Skip to content

Commit fb12248

Browse files
ridwanabdillahiRidwan Abdilahi
authored and
Ridwan Abdilahi
committed
Add Natvis definitions for url types and create tests to ensure visualizations do not become stale or broken.
Add a README for documenting how the debugger visualizers and how to embed/ test them. Update the github actions CI workflow to manually enable features, in addition to the default features, to allow for testing unstable features separately. Cleanup running tests with the serde feature enabled. Update documentation noting the debugger_visualizer crate feature is an unstable feature. Respond to PR comments. Fix unused variables warnings. Fix lint errors
1 parent a72f83d commit fb12248

File tree

7 files changed

+285
-6
lines changed

7 files changed

+285
-6
lines changed

.github/workflows/main.yml

+19-4
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,6 @@ jobs:
2525
rust: beta
2626
- os: macos-latest
2727
rust: nightly
28-
- os: windows-latest
29-
rust: nightly
3028

3129
runs-on: ${{ matrix.os }}
3230

@@ -41,10 +39,27 @@ jobs:
4139
with:
4240
command: build
4341
args: --all-targets
44-
- uses: actions-rs/cargo@v1
42+
# Run tests
43+
- name: Run tests
44+
uses: actions-rs/cargo@v1
45+
with:
46+
command: test
47+
# Run tests enabling the serde feature
48+
- name: Run url tests with the serde feature
49+
uses: actions-rs/cargo@v1
50+
with:
51+
command: test
52+
args: --manifest-path url/Cargo.toml --features "serde"
53+
# The #[debugger_visualizer] attribute is currently gated behind an unstable feature flag.
54+
# In order to test the visualizers for the url crate, they have to be tested on a nightly build.
55+
- name: Run debugger_visualizer tests
56+
if: |
57+
matrix.os == 'windows-latest' &&
58+
matrix.rust == 'nightly'
59+
uses: actions-rs/cargo@v1
4560
with:
4661
command: test
47-
args: --all-features
62+
args: --test debugger_visualizer --manifest-path url/Cargo.toml --features "serde,debugger_visualizer" -- --test-threads=1
4863

4964
WASM:
5065
runs-on: ubuntu-latest

debug_metadata/README.md

+111
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
## Debugger Visualizers
2+
3+
Many languages and debuggers enable developers to control how a type is
4+
displayed in a debugger. These are called "debugger visualizations" or "debugger
5+
views".
6+
7+
The Windows debuggers (WinDbg\CDB) support defining custom debugger visualizations using
8+
the `Natvis` framework. To use Natvis, developers write XML documents using the natvis
9+
schema that describe how debugger types should be displayed with the `.natvis` extension.
10+
(See: https://docs.microsoft.com/en-us/visualstudio/debugger/create-custom-views-of-native-objects?view=vs-2019)
11+
The Natvis files provide patterns which match type names a description of how to display
12+
those types.
13+
14+
The Natvis schema can be found either online (See: https://code.visualstudio.com/docs/cpp/natvis#_schema)
15+
or locally at `<VS Installation Folder>\Xml\Schemas\1033\natvis.xsd`.
16+
17+
The GNU debugger (GDB) supports defining custom debugger views using Pretty Printers.
18+
Pretty printers are written as python scripts that describe how a type should be displayed
19+
when loaded up in GDB/LLDB. (See: https://sourceware.org/gdb/onlinedocs/gdb/Pretty-Printing.html#Pretty-Printing)
20+
The pretty printers provide patterns, which match type names, and for matching
21+
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).
22+
23+
### Embedding Visualizers
24+
25+
Through the use of the currently unstable `#[debugger_visualizer]` attribute, the `url`
26+
crate can embed debugger visualizers into the crate metadata.
27+
28+
Currently the two types of visualizers supported are Natvis and Pretty printers.
29+
30+
For Natvis files, when linking an executable with a crate that includes Natvis files,
31+
the MSVC linker will embed the contents of all Natvis files into the generated `PDB`.
32+
33+
For pretty printers, the compiler will encode the contents of the pretty printer
34+
in the `.debug_gdb_scripts` section of the `ELF` generated.
35+
36+
### Testing Visualizers
37+
38+
The `url` crate supports testing debugger visualizers defined for this crate. The entry point for
39+
these tests are `tests/debugger_visualizer.rs`. These tests are defined using the `debugger_test` and
40+
`debugger_test_parser` crates. The `debugger_test` crate is a proc macro crate which defines a
41+
single proc macro attribute, `#[debugger_test]`. For more detailed information about this crate,
42+
see https://crates.io/crates/debugger_test. The CI pipeline for the `url` crate has been updated
43+
to run the debugger visualizer tests to ensure debugger visualizers do not become broken/stale.
44+
45+
The `#[debugger_test]` proc macro attribute may only be used on test functions and will run the
46+
function under the debugger specified by the `debugger` meta item.
47+
48+
This proc macro attribute has 3 required values:
49+
50+
1. The first required meta item, `debugger`, takes a string value which specifies the debugger to launch.
51+
2. The second required meta item, `commands`, takes a string of new line (`\n`) separated list of debugger
52+
commands to run.
53+
3. The third required meta item, `expected_statements`, takes a string of new line (`\n`) separated list of
54+
statements that must exist in the debugger output. Pattern matching through regular expressions is also
55+
supported by using the `pattern:` prefix for each expected statement.
56+
57+
#### Example:
58+
59+
```rust
60+
#[debugger_test(
61+
debugger = "cdb",
62+
commands = "command1\ncommand2\ncommand3",
63+
expected_statements = "statement1\nstatement2\nstatement3")]
64+
fn test() {
65+
66+
}
67+
```
68+
69+
Using a multiline string is also supported, with a single debugger command/expected statement per line:
70+
71+
```rust
72+
#[debugger_test(
73+
debugger = "cdb",
74+
commands = "
75+
command1
76+
command2
77+
command3",
78+
expected_statements = "
79+
statement1
80+
pattern:statement[0-9]+
81+
statement3")]
82+
fn test() {
83+
84+
}
85+
```
86+
87+
In the example above, the second expected statement uses pattern matching through a regular expression
88+
by using the `pattern:` prefix.
89+
90+
#### Testing Locally
91+
92+
Currently, only Natvis visualizations have been defined for the `url` crate via `debug_metadata/url.natvis`,
93+
which means the `tests/debugger_visualizer.rs` tests need to be run on Windows using the `*-pc-windows-msvc` targets.
94+
To run these tests locally, first ensure the debugging tools for Windows are installed or install them following
95+
the steps listed here, [Debugging Tools for Windows](https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/).
96+
Once the debugging tools have been installed, the tests can be run in the same manner as they are in the CI
97+
pipeline.
98+
99+
#### Note
100+
101+
When running the debugger visualizer tests, `tests/debugger_visualizer.rs`, they need to be run consecutively
102+
and not in parallel. This can be achieved by passing the flag `--test-threads=1` to rustc. This is due to
103+
how the debugger tests are run. Each test marked with the `#[debugger_test]` attribute launches a debugger
104+
and attaches it to the current test process. If tests are running in parallel, the test will try to attach
105+
a debugger to the current process which may already have a debugger attached causing the test to fail.
106+
107+
For example:
108+
109+
```
110+
cargo test --test debugger_visualizer --features debugger_visualizer -- --test-threads=1
111+
```

debug_metadata/url.natvis

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
3+
<Type Name="url::Url">
4+
<Intrinsic Name="ptr" Expression="serialization.vec.buf.ptr.pointer.pointer" />
5+
<DisplayString>{serialization}</DisplayString>
6+
<Expand>
7+
<Synthetic Name="[scheme]">
8+
<DisplayString>{(char*)(ptr()),[scheme_end]s8}</DisplayString>
9+
</Synthetic>
10+
<Synthetic Name="[username]" Condition="username_end > (scheme_end + 3)">
11+
<!-- Add 3 to the scheme end to account for the scheme separator which is '://' -->
12+
<DisplayString>{(char*)(ptr()+(scheme_end + 3)),[((username_end)-(scheme_end + 3))]s8}</DisplayString>
13+
</Synthetic>
14+
<Synthetic Name="[host]" Condition="host.discriminant != 0">
15+
<DisplayString>{(char*)(ptr()+host_start),[host_end-host_start]s8}</DisplayString>
16+
</Synthetic>
17+
<Synthetic Name="[port]" Condition="port.discriminant == 1">
18+
<DisplayString>{port.variant1.__0,d}</DisplayString>
19+
</Synthetic>
20+
<Synthetic Name="[path]">
21+
<DisplayString Condition="query_start.discriminant == 0 &amp;&amp; fragment_start.discriminant == 0">{(char*)(ptr()+path_start),[(serialization.vec.len-path_start)]s8}</DisplayString>
22+
<DisplayString Condition="query_start.discriminant == 1">{(char*)(ptr()+path_start),[(query_start.variant1.__0-path_start)]s8}</DisplayString>
23+
<DisplayString Condition="fragment_start.discriminant == 1">{(char*)(ptr()+path_start),[(fragment_start.variant1.__0-path_start)]s8}</DisplayString>
24+
</Synthetic>
25+
<Synthetic Name="[query]" Condition="query_start.discriminant == 1">
26+
<DisplayString Condition="fragment_start.discriminant == 0">{(char*)(ptr()+query_start.variant1.__0+1),[((serialization.vec.len)-(query_start.variant1.__0+1))]s8}</DisplayString>
27+
<DisplayString Condition="fragment_start.discriminant == 1">{(char*)(ptr()+query_start.variant1.__0+1),[((fragment_start.variant1.__0)-(query_start.variant1.__0+1))]s8}</DisplayString>
28+
</Synthetic>
29+
<Synthetic Name="[fragment]" Condition="fragment_start.discriminant == 1">
30+
<DisplayString>{(char*)(ptr()+fragment_start.variant1.__0+1),[(serialization.vec.len-fragment_start.variant1.__0-1)]s8}</DisplayString>
31+
</Synthetic>
32+
</Expand>
33+
</Type>
34+
</AutoVisualizer>

url/Cargo.toml

+12
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ appveyor = { repository = "Manishearth/rust-url" }
2323
[dev-dependencies]
2424
serde_json = "1.0"
2525
bencher = "0.1"
26+
# To test debugger visualizers defined for the url crate such as url.natvis
27+
debugger_test = "0.1"
28+
debugger_test_parser = "0.1"
2629

2730
[dependencies]
2831
form_urlencoded = { version = "1.0.0", path = "../form_urlencoded" }
@@ -32,8 +35,17 @@ serde = {version = "1.0", optional = true, features = ["derive"]}
3235

3336
[features]
3437
default = ["idna"]
38+
# UNSTABLE FEATURES (requires Rust nightly)
39+
# Enable to use the #[debugger_visualizer] attribute.
40+
debugger_visualizer = []
3541

3642
[[bench]]
3743
name = "parse_url"
3844
path = "benches/parse_url.rs"
3945
harness = false
46+
47+
[[test]]
48+
name = "debugger_visualizer"
49+
path = "tests/debugger_visualizer.rs"
50+
required-features = ["debugger_visualizer"]
51+
test = false

url/src/lib.rs

+5
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,11 @@ url = { version = "2", default-features = false }
131131
*/
132132

133133
#![doc(html_root_url = "https://docs.rs/url/2.2.2")]
134+
#![cfg_attr(
135+
feature = "debugger_visualizer",
136+
feature(debugger_visualizer),
137+
debugger_visualizer(natvis_file = "../../debug_metadata/url.natvis")
138+
)]
134139

135140
pub use form_urlencoded;
136141

url/src/parser.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ impl fmt::Display for SyntaxViolation {
143143
}
144144
}
145145

146-
#[derive(Copy, Clone, PartialEq)]
146+
#[derive(Copy, Clone, Eq, PartialEq)]
147147
pub enum SchemeType {
148148
File,
149149
SpecialNotFile,
@@ -1561,7 +1561,7 @@ pub fn is_windows_drive_letter(segment: &str) -> bool {
15611561
/// Whether path starts with a root slash
15621562
/// and a windows drive letter eg: "/c:" or "/a:/"
15631563
fn path_starts_with_windows_drive_letter(s: &str) -> bool {
1564-
if let Some(c) = s.as_bytes().get(0) {
1564+
if let Some(c) = s.as_bytes().first() {
15651565
matches!(c, b'/' | b'\\' | b'?' | b'#') && starts_with_windows_drive_letter(&s[1..])
15661566
} else {
15671567
false

url/tests/debugger_visualizer.rs

+102
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
use debugger_test::debugger_test;
2+
use url::Url;
3+
4+
#[inline(never)]
5+
fn __break() {}
6+
7+
#[debugger_test(
8+
debugger = "cdb",
9+
commands = "
10+
.nvlist
11+
12+
dx base_url
13+
14+
dx url_with_non_special_scheme
15+
16+
dx url_with_user_pass_port_query_fragments
17+
18+
dx url_blob
19+
20+
dx url_with_base
21+
22+
dx url_with_base_replaced
23+
24+
dx url_with_comma",
25+
expected_statements = r#"
26+
pattern:debugger_visualizer-.*\.exe \(embedded NatVis ".*-[0-9]+\.natvis"\)
27+
28+
base_url : "http://example.org/foo/bar" [Type: url::Url]
29+
[<Raw View>] [Type: url::Url]
30+
[scheme] : "http"
31+
[host] : "example.org"
32+
[path] : "/foo/bar"
33+
34+
url_with_non_special_scheme : "non-special://test/x" [Type: url::Url]
35+
[<Raw View>] [Type: url::Url]
36+
[scheme] : "non-special"
37+
[host] : "test"
38+
[path] : "/x"
39+
40+
url_with_user_pass_port_query_fragments : "http://user:pass@foo:21/bar;par?b#c" [Type: url::Url]
41+
[<Raw View>] [Type: url::Url]
42+
[scheme] : "http"
43+
[username] : "user"
44+
[host] : "foo"
45+
[port] : 21
46+
[path] : "/bar;par"
47+
[query] : "b"
48+
[fragment] : "c"
49+
50+
url_blob : "blob:https://example.com:443/" [Type: url::Url]
51+
[<Raw View>] [Type: url::Url]
52+
[scheme] : "blob"
53+
[path] : "https://example.com:443/"
54+
55+
url_with_base : "http://example.org/a%2fc" [Type: url::Url]
56+
[<Raw View>] [Type: url::Url]
57+
[scheme] : "http"
58+
[host] : "example.org"
59+
[path] : "/a%2fc"
60+
61+
url_with_base_replaced : "http://[::7f00:1]/" [Type: url::Url]
62+
[<Raw View>] [Type: url::Url]
63+
[scheme] : "http"
64+
[host] : "[::7f00:1]"
65+
[path] : "/"
66+
67+
url_with_comma : "data:text/html,test#test" [Type: url::Url]
68+
[<Raw View>] [Type: url::Url]
69+
[scheme] : "data"
70+
[path] : "text/html,test"
71+
[fragment] : "test"
72+
"#
73+
)]
74+
fn test_url_visualizer() {
75+
// Copied from https://github.com/web-platform-tests/wpt/blob/master/url/
76+
let base_url = Url::parse("http://example.org/foo/bar").unwrap();
77+
assert_eq!(base_url.as_str(), "http://example.org/foo/bar");
78+
79+
let url_with_non_special_scheme = Url::parse("non-special://:@test/x").unwrap();
80+
assert_eq!(url_with_non_special_scheme.as_str(), "non-special://test/x");
81+
82+
let url_with_user_pass_port_query_fragments =
83+
Url::parse("http://user:pass@foo:21/bar;par?b#c").unwrap();
84+
assert_eq!(
85+
url_with_user_pass_port_query_fragments.as_str(),
86+
"http://user:pass@foo:21/bar;par?b#c"
87+
);
88+
89+
let url_blob = Url::parse("blob:https://example.com:443/").unwrap();
90+
assert_eq!(url_blob.as_str(), "blob:https://example.com:443/");
91+
92+
let url_with_base = base_url.join("/a%2fc").unwrap();
93+
assert_eq!(url_with_base.as_str(), "http://example.org/a%2fc");
94+
95+
let url_with_base_replaced = base_url.join("http://[::127.0.0.1]").unwrap();
96+
assert_eq!(url_with_base_replaced.as_str(), "http://[::7f00:1]/");
97+
98+
let url_with_comma = base_url.join("data:text/html,test#test").unwrap();
99+
assert_eq!(url_with_comma.as_str(), "data:text/html,test#test");
100+
101+
__break();
102+
}

0 commit comments

Comments
 (0)