Skip to content

Commit 4ba60cb

Browse files
ridwanabdillahiRidwan Abdilahi
andauthored
Add Natvis visualizations and tests for ArrayVec and SliceVec types. (#167)
* Add Natvis visualizations and tests for `ArrayVec` and `SliceVec`. * CI yaml cleanups. * Respond to PR comments, cleanup Natvis definitions. Co-authored-by: Ridwan Abdilahi <riabdila@microsoft.com>
1 parent a711c72 commit 4ba60cb

File tree

6 files changed

+261
-1
lines changed

6 files changed

+261
-1
lines changed

.github/workflows/rust.yml

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,21 @@ on:
66

77
jobs:
88
build_test:
9-
runs-on: ubuntu-latest
109
strategy:
1110
matrix:
11+
os: [ubuntu-latest]
1212
rust:
1313
- 1.34.0
1414
- 1.36.0
1515
- stable
1616
- beta
1717
- nightly
18+
include:
19+
- rust: nightly
20+
os: windows-latest
21+
22+
runs-on: ${{ matrix.os }}
23+
1824
steps:
1925
- uses: actions/checkout@v1
2026
- uses: actions-rs/toolchain@v1
@@ -45,3 +51,13 @@ jobs:
4551
with:
4652
command: test
4753
args: --all-features
54+
# The #[debugger_visualizer] attribute is currently gated behind an unstable feature flag.
55+
# In order to test the visualizers for the tinyvec crate, they have to be tested on a nightly build.
56+
- name: Test debugger_visualizer feature on Nightly
57+
if: |
58+
matrix.os == 'windows-latest' &&
59+
matrix.rust == 'nightly'
60+
uses: actions-rs/cargo@v1
61+
with:
62+
command: test
63+
args: --all-features --test debugger_visualizer -- --test-threads=1

Cargo.toml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@ rustc_1_57 = ["rustc_1_55"]
4747
# https://github.com/rust-lang/rust/issues/54279
4848
nightly_slice_partition_dedup = []
4949

50+
# allow use of nightly feature `debugger_visualizer`,
51+
# will become useless once that is stabilized:
52+
# https://github.com/rust-lang/rust/issues/95939
53+
debugger_visualizer = []
54+
5055
# EXPERIMENTAL: Not part of SemVer. It adds `core::fmt::Write` to `ArrayVec`
5156
# and `SliceVec`. It works on Stable Rust, but Vec normally supports the
5257
# `std::io::Write` trait instead of `core::fmt::Write`, so we're keeping it as
@@ -78,6 +83,8 @@ members = ["fuzz"]
7883
criterion = "0.3.0"
7984
serde_test = "1.0"
8085
smallvec = "1"
86+
debugger_test = "0.1"
87+
debugger_test_parser = "0.1"
8188

8289
[[test]]
8390
name = "tinyvec"
@@ -92,3 +99,9 @@ required-features = ["alloc"]
9299
name = "smallvec"
93100
harness = false
94101
required-features = ["alloc", "real_blackbox"]
102+
103+
[[test]]
104+
path = "tests/debugger_visualizer.rs"
105+
name = "debugger_visualizer"
106+
required-features = ["debugger_visualizer"]
107+
test = false

debug_metadata/README.md

Lines changed: 111 additions & 0 deletions
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 `tinyvec`
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 `tinyvec` 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 `tinyvec` 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 `tinyvec` crate via `debug_metadata/tinyvec.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/tinyvec.natvis

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
2+
<Type Name="tinyvec::arrayvec::ArrayVec&lt;array$&lt;*,*&gt;&gt;">
3+
<DisplayString>{{ len={len} }}</DisplayString>
4+
<Expand>
5+
<Item Name="[len]">len</Item>
6+
<Item Name="[capacity]">$T2</Item>
7+
<ArrayItems>
8+
<Size>len</Size>
9+
<ValuePointer>($T1*)data</ValuePointer>
10+
</ArrayItems>
11+
</Expand>
12+
</Type>
13+
14+
<Type Name="tinyvec::slicevec::SliceVec&lt;*&gt;">
15+
<DisplayString>{{ len={len} }}</DisplayString>
16+
<Expand>
17+
<Item Name="[len]">len</Item>
18+
<ArrayItems>
19+
<Size>len</Size>
20+
<ValuePointer>data.data_ptr</ValuePointer>
21+
</ArrayItems>
22+
</Expand>
23+
</Type>
24+
</AutoVisualizer>

src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@
44
feature = "nightly_slice_partition_dedup",
55
feature(slice_partition_dedup)
66
)]
7+
#![cfg_attr(
8+
feature = "debugger_visualizer",
9+
feature(debugger_visualizer),
10+
debugger_visualizer(natvis_file = "../debug_metadata/tinyvec.natvis")
11+
)]
712
#![cfg_attr(docs_rs, feature(doc_cfg))]
813
#![warn(clippy::missing_inline_in_public_items)]
914
#![warn(clippy::must_use_candidate)]

tests/debugger_visualizer.rs

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
use debugger_test::debugger_test;
2+
use tinyvec::*;
3+
4+
#[inline(never)]
5+
fn __break() {
6+
println!("breakpoint hit");
7+
}
8+
9+
#[debugger_test(
10+
debugger = "cdb",
11+
commands = r#"
12+
dx strings
13+
dx inline_tv
14+
dx inline_tv.__0
15+
g
16+
dx slice_vec
17+
g
18+
dx strings
19+
"#,
20+
expected_statements = r#"
21+
strings : { len=0x3 } [Type: tinyvec::arrayvec::ArrayVec<array$<str,7> >]
22+
[<Raw View>] [Type: tinyvec::arrayvec::ArrayVec<array$<str,7> >]
23+
[len] : 0x3 [Type: unsigned short]
24+
[capacity] : 7
25+
[0] : "a" [Type: str]
26+
[1] : "b" [Type: str]
27+
[2] : "c" [Type: str]
28+
29+
inline_tv : Inline [Type: enum2$<tinyvec::tinyvec::TinyVec<array$<i32,4> > >]
30+
[<Raw View>] [Type: enum2$<tinyvec::tinyvec::TinyVec<array$<i32,4> > >]
31+
[+0x004] __0 : { len=0x4 } [Type: tinyvec::arrayvec::ArrayVec<array$<i32,4> >]
32+
33+
inline_tv.__0 : { len=0x4 } [Type: tinyvec::arrayvec::ArrayVec<array$<i32,4> >]
34+
[<Raw View>] [Type: tinyvec::arrayvec::ArrayVec<array$<i32,4> >]
35+
[len] : 0x4 [Type: unsigned short]
36+
[capacity] : 4
37+
[0] : 1 [Type: i32]
38+
[1] : 2 [Type: i32]
39+
[2] : 3 [Type: i32]
40+
[3] : 4 [Type: i32]
41+
42+
slice_vec : { len=0x3 } [Type: tinyvec::slicevec::SliceVec<str>]
43+
[<Raw View>] [Type: tinyvec::slicevec::SliceVec<str>]
44+
[len] : 0x3 [Type: unsigned __int64]
45+
[0] : "a" [Type: str]
46+
[1] : "b" [Type: str]
47+
[2] : "d" [Type: str]
48+
49+
strings : { len=0x6 } [Type: tinyvec::arrayvec::ArrayVec<array$<str,7> >]
50+
[<Raw View>] [Type: tinyvec::arrayvec::ArrayVec<array$<str,7> >]
51+
[len] : 0x6 [Type: unsigned short]
52+
[capacity] : 7
53+
[0] : "a" [Type: str]
54+
[1] : "b" [Type: str]
55+
[2] : "d" [Type: str]
56+
[3] : "e" [Type: str]
57+
[4] : "f" [Type: str]
58+
[5] : "g" [Type: str]
59+
"#
60+
)]
61+
#[inline(never)]
62+
fn test_debugger_visualizer() {
63+
let mut strings = ArrayVec::<[&str; 7]>::default();
64+
strings.push("a");
65+
strings.push("b");
66+
strings.push("c");
67+
assert_eq!(["a", "b", "c"], &strings[..]);
68+
69+
let mut inline_tv = tiny_vec!([i32; 4] => 1, 2, 3);
70+
assert!(inline_tv.is_inline());
71+
72+
inline_tv.push(4);
73+
__break();
74+
75+
{
76+
let mut slice_vec = SliceVec::from(strings.as_mut_slice());
77+
assert_eq!(3, slice_vec.capacity());
78+
assert_eq!("c", slice_vec.remove(2));
79+
slice_vec.push("d");
80+
println!("{:?}", slice_vec);
81+
__break();
82+
83+
assert_eq!(["a", "b", "d"], &slice_vec[..]);
84+
}
85+
86+
strings.push("e");
87+
strings.push("f");
88+
strings.push("g");
89+
assert_eq!(["a", "b", "d", "e", "f", "g"], &strings[..]);
90+
__break();
91+
}

0 commit comments

Comments
 (0)