Skip to content

Commit

Permalink
Add Natvis visualizations and tests for ArrayVec and SliceVec typ…
Browse files Browse the repository at this point in the history
…es. (#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>
  • Loading branch information
ridwanabdillahi and Ridwan Abdilahi committed Sep 7, 2022
1 parent a711c72 commit 4ba60cb
Show file tree
Hide file tree
Showing 6 changed files with 261 additions and 1 deletion.
18 changes: 17 additions & 1 deletion .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,21 @@ on:

jobs:
build_test:
runs-on: ubuntu-latest
strategy:
matrix:
os: [ubuntu-latest]
rust:
- 1.34.0
- 1.36.0
- stable
- beta
- nightly
include:
- rust: nightly
os: windows-latest

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

steps:
- uses: actions/checkout@v1
- uses: actions-rs/toolchain@v1
Expand Down Expand Up @@ -45,3 +51,13 @@ jobs:
with:
command: test
args: --all-features
# The #[debugger_visualizer] attribute is currently gated behind an unstable feature flag.
# In order to test the visualizers for the tinyvec crate, they have to be tested on a nightly build.
- name: Test debugger_visualizer feature on Nightly
if: |
matrix.os == 'windows-latest' &&
matrix.rust == 'nightly'
uses: actions-rs/cargo@v1
with:
command: test
args: --all-features --test debugger_visualizer -- --test-threads=1
13 changes: 13 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ rustc_1_57 = ["rustc_1_55"]
# https://github.com/rust-lang/rust/issues/54279
nightly_slice_partition_dedup = []

# allow use of nightly feature `debugger_visualizer`,
# will become useless once that is stabilized:
# https://github.com/rust-lang/rust/issues/95939
debugger_visualizer = []

# EXPERIMENTAL: Not part of SemVer. It adds `core::fmt::Write` to `ArrayVec`
# and `SliceVec`. It works on Stable Rust, but Vec normally supports the
# `std::io::Write` trait instead of `core::fmt::Write`, so we're keeping it as
Expand Down Expand Up @@ -78,6 +83,8 @@ members = ["fuzz"]
criterion = "0.3.0"
serde_test = "1.0"
smallvec = "1"
debugger_test = "0.1"
debugger_test_parser = "0.1"

[[test]]
name = "tinyvec"
Expand All @@ -92,3 +99,9 @@ required-features = ["alloc"]
name = "smallvec"
harness = false
required-features = ["alloc", "real_blackbox"]

[[test]]
path = "tests/debugger_visualizer.rs"
name = "debugger_visualizer"
required-features = ["debugger_visualizer"]
test = false
111 changes: 111 additions & 0 deletions debug_metadata/README.md
Original file line number Diff line number Diff line change
@@ -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 `<VS Installation Folder>\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 `tinyvec`
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 `tinyvec` 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 `tinyvec` 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 `tinyvec` crate via `debug_metadata/tinyvec.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
```
24 changes: 24 additions & 0 deletions debug_metadata/tinyvec.natvis
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
<Type Name="tinyvec::arrayvec::ArrayVec&lt;array$&lt;*,*&gt;&gt;">
<DisplayString>{{ len={len} }}</DisplayString>
<Expand>
<Item Name="[len]">len</Item>
<Item Name="[capacity]">$T2</Item>
<ArrayItems>
<Size>len</Size>
<ValuePointer>($T1*)data</ValuePointer>
</ArrayItems>
</Expand>
</Type>

<Type Name="tinyvec::slicevec::SliceVec&lt;*&gt;">
<DisplayString>{{ len={len} }}</DisplayString>
<Expand>
<Item Name="[len]">len</Item>
<ArrayItems>
<Size>len</Size>
<ValuePointer>data.data_ptr</ValuePointer>
</ArrayItems>
</Expand>
</Type>
</AutoVisualizer>
5 changes: 5 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@
feature = "nightly_slice_partition_dedup",
feature(slice_partition_dedup)
)]
#![cfg_attr(
feature = "debugger_visualizer",
feature(debugger_visualizer),
debugger_visualizer(natvis_file = "../debug_metadata/tinyvec.natvis")
)]
#![cfg_attr(docs_rs, feature(doc_cfg))]
#![warn(clippy::missing_inline_in_public_items)]
#![warn(clippy::must_use_candidate)]
Expand Down
91 changes: 91 additions & 0 deletions tests/debugger_visualizer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
use debugger_test::debugger_test;
use tinyvec::*;

#[inline(never)]
fn __break() {
println!("breakpoint hit");
}

#[debugger_test(
debugger = "cdb",
commands = r#"
dx strings
dx inline_tv
dx inline_tv.__0
g
dx slice_vec
g
dx strings
"#,
expected_statements = r#"
strings : { len=0x3 } [Type: tinyvec::arrayvec::ArrayVec<array$<str,7> >]
[<Raw View>] [Type: tinyvec::arrayvec::ArrayVec<array$<str,7> >]
[len] : 0x3 [Type: unsigned short]
[capacity] : 7
[0] : "a" [Type: str]
[1] : "b" [Type: str]
[2] : "c" [Type: str]
inline_tv : Inline [Type: enum2$<tinyvec::tinyvec::TinyVec<array$<i32,4> > >]
[<Raw View>] [Type: enum2$<tinyvec::tinyvec::TinyVec<array$<i32,4> > >]
[+0x004] __0 : { len=0x4 } [Type: tinyvec::arrayvec::ArrayVec<array$<i32,4> >]
inline_tv.__0 : { len=0x4 } [Type: tinyvec::arrayvec::ArrayVec<array$<i32,4> >]
[<Raw View>] [Type: tinyvec::arrayvec::ArrayVec<array$<i32,4> >]
[len] : 0x4 [Type: unsigned short]
[capacity] : 4
[0] : 1 [Type: i32]
[1] : 2 [Type: i32]
[2] : 3 [Type: i32]
[3] : 4 [Type: i32]
slice_vec : { len=0x3 } [Type: tinyvec::slicevec::SliceVec<str>]
[<Raw View>] [Type: tinyvec::slicevec::SliceVec<str>]
[len] : 0x3 [Type: unsigned __int64]
[0] : "a" [Type: str]
[1] : "b" [Type: str]
[2] : "d" [Type: str]
strings : { len=0x6 } [Type: tinyvec::arrayvec::ArrayVec<array$<str,7> >]
[<Raw View>] [Type: tinyvec::arrayvec::ArrayVec<array$<str,7> >]
[len] : 0x6 [Type: unsigned short]
[capacity] : 7
[0] : "a" [Type: str]
[1] : "b" [Type: str]
[2] : "d" [Type: str]
[3] : "e" [Type: str]
[4] : "f" [Type: str]
[5] : "g" [Type: str]
"#
)]
#[inline(never)]
fn test_debugger_visualizer() {
let mut strings = ArrayVec::<[&str; 7]>::default();
strings.push("a");
strings.push("b");
strings.push("c");
assert_eq!(["a", "b", "c"], &strings[..]);

let mut inline_tv = tiny_vec!([i32; 4] => 1, 2, 3);
assert!(inline_tv.is_inline());

inline_tv.push(4);
__break();

{
let mut slice_vec = SliceVec::from(strings.as_mut_slice());
assert_eq!(3, slice_vec.capacity());
assert_eq!("c", slice_vec.remove(2));
slice_vec.push("d");
println!("{:?}", slice_vec);
__break();

assert_eq!(["a", "b", "d"], &slice_vec[..]);
}

strings.push("e");
strings.push("f");
strings.push("g");
assert_eq!(["a", "b", "d", "e", "f", "g"], &strings[..]);
__break();
}

0 comments on commit 4ba60cb

Please sign in to comment.