mirror of
https://github.com/tokio-rs/tokio.git
synced 2026-08-28 00:00:11 +02:00
Introduce tokio-trace (#827)
<!-- Thank you for your Pull Request. Please provide a description above and review the requirements below. Bug fixes and new features should include tests. Contributors guide: https://github.com/tokio-rs/tokio/blob/master/CONTRIBUTING.md --> ## Motivation In asynchronous systems like Tokio, interpreting traditional log messages can often be quite challenging. Since individual tasks are multiplexed on the same thread, associated events and log lines are intermixed making it difficult to trace the logic flow. Currently, none of the available logging frameworks or libraries in Rust offer the ability to trace logical paths through a futures-based program. There also are complementary goals that can be accomplished with such a system. For example, metrics / instrumentation can be tracked by observing emitted events, or trace data can be exported to a distributed tracing or event processing system. In addition, it can often be useful to generate this diagnostic data in a structured manner that can be consumed programmatically. While prior art for structured logging in Rust exists, it is not currently standardized, and is not "Tokio-friendly". ## Solution This branch adds a new library to the tokio project, `tokio-trace`. `tokio-trace` expands upon logging-style diagnostics by allowing libraries and applications to record structured events with additional information about *temporality* and *causality* --- unlike a log message, a span in `tokio-trace` has a beginning and end time, may be entered and exited by the flow of execution, and may exist within a nested tree of similar spans. In addition, `tokio-trace` spans are *structured*, with the ability to record typed data as well as textual messages. The `tokio-trace-core` crate contains the core primitives for this system, which are expected to remain stable, while `tokio-trace` crate provides a more "batteries-included" API. In particular, it provides macros which are a superset of the `log` crate's `error!`, `warn!`, `info!`, `debug!`, and `trace!` macros, allowing users to begin the process of adopting `tokio-trace` by performing a drop-in replacement. ## Notes Work on this project had previously been carried out in the [tokio-trace-prototype] repository. In addition to the `tokio-trace` and `tokio-trace-core` crates, the `tokio-trace-prototype` repo also contains prototypes or sketches of adapter, compatibility, and utility crates which provide useful functionality for `tokio-trace`, but these crates are not yet ready for a release. When this branch is merged, that repository will be archived, and the remaining unstable crates will be moved to a new `tokio-trace-nursery` repository. Remaining issues on the `tokio-trace-prototype` repo will be moved to the appropriate new repo. The crates added in this branch are not _identical_ to the current head of the `tokio-trace-prototype` repo, as I did some final clean-up and docs polish in this branch prior to merging this PR. [tokio-trace-prototype]: https://github.com/hawkw/tokio-trace-prototype Closes: #561 Signed-off-by: Eliza Weisman <[email protected]>
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
#[macro_use]
|
||||
extern crate tokio_trace;
|
||||
mod support;
|
||||
|
||||
use self::support::*;
|
||||
use tokio_trace::{dispatcher, Dispatch};
|
||||
|
||||
#[test]
|
||||
fn dispatcher_is_sticky() {
|
||||
// Test ensuring that entire trace trees are collected by the same
|
||||
// dispatcher, even across dispatcher context switches.
|
||||
let (subscriber1, handle1) = subscriber::mock()
|
||||
.enter(span::mock().named("foo"))
|
||||
.exit(span::mock().named("foo"))
|
||||
.enter(span::mock().named("foo"))
|
||||
.enter(span::mock().named("bar"))
|
||||
.exit(span::mock().named("bar"))
|
||||
.drop_span(span::mock().named("bar"))
|
||||
.exit(span::mock().named("foo"))
|
||||
.drop_span(span::mock().named("foo"))
|
||||
.done()
|
||||
.run_with_handle();
|
||||
let mut foo = dispatcher::with_default(Dispatch::new(subscriber1), || {
|
||||
let mut foo = span!("foo");
|
||||
foo.enter(|| {});
|
||||
foo
|
||||
});
|
||||
dispatcher::with_default(Dispatch::new(subscriber::mock().done().run()), move || {
|
||||
foo.enter(|| span!("bar").enter(|| {}))
|
||||
});
|
||||
|
||||
handle1.assert_finished();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dispatcher_isnt_too_sticky() {
|
||||
// Test ensuring that new trace trees are collected by the current
|
||||
// dispatcher.
|
||||
let (subscriber1, handle1) = subscriber::mock()
|
||||
.enter(span::mock().named("foo"))
|
||||
.exit(span::mock().named("foo"))
|
||||
.enter(span::mock().named("foo"))
|
||||
.enter(span::mock().named("bar"))
|
||||
.exit(span::mock().named("bar"))
|
||||
.drop_span(span::mock().named("bar"))
|
||||
.exit(span::mock().named("foo"))
|
||||
.drop_span(span::mock().named("foo"))
|
||||
.done()
|
||||
.run_with_handle();
|
||||
let (subscriber2, handle2) = subscriber::mock()
|
||||
.enter(span::mock().named("baz"))
|
||||
.enter(span::mock().named("quux"))
|
||||
.exit(span::mock().named("quux"))
|
||||
.drop_span(span::mock().named("quux"))
|
||||
.exit(span::mock().named("baz"))
|
||||
.drop_span(span::mock().named("baz"))
|
||||
.done()
|
||||
.run_with_handle();
|
||||
|
||||
let mut foo = dispatcher::with_default(Dispatch::new(subscriber1), || {
|
||||
let mut foo = span!("foo");
|
||||
foo.enter(|| {});
|
||||
foo
|
||||
});
|
||||
let mut baz = dispatcher::with_default(Dispatch::new(subscriber2), || span!("baz"));
|
||||
dispatcher::with_default(Dispatch::new(subscriber::mock().done().run()), move || {
|
||||
foo.enter(|| span!("bar").enter(|| {}));
|
||||
baz.enter(|| span!("quux").enter(|| {}))
|
||||
});
|
||||
|
||||
handle1.assert_finished();
|
||||
handle2.assert_finished();
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
#[macro_use]
|
||||
extern crate tokio_trace;
|
||||
mod support;
|
||||
|
||||
use self::support::*;
|
||||
|
||||
use tokio_trace::{
|
||||
field::{debug, display},
|
||||
subscriber::with_default,
|
||||
Level,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn event_without_message() {
|
||||
let (subscriber, handle) = subscriber::mock()
|
||||
.event(
|
||||
event::mock().with_fields(
|
||||
field::mock("answer")
|
||||
.with_value(&42)
|
||||
.and(
|
||||
field::mock("to_question")
|
||||
.with_value(&"life, the universe, and everything"),
|
||||
)
|
||||
.only(),
|
||||
),
|
||||
)
|
||||
.done()
|
||||
.run_with_handle();
|
||||
|
||||
with_default(subscriber, || {
|
||||
info!(
|
||||
answer = 42,
|
||||
to_question = "life, the universe, and everything"
|
||||
);
|
||||
});
|
||||
|
||||
handle.assert_finished();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn event_with_message() {
|
||||
let (subscriber, handle) = subscriber::mock()
|
||||
.event(event::mock().with_fields(field::mock("message").with_value(
|
||||
&tokio_trace::field::debug(format_args!(
|
||||
"hello from my event! yak shaved = {:?}",
|
||||
true
|
||||
)),
|
||||
)))
|
||||
.done()
|
||||
.run_with_handle();
|
||||
|
||||
with_default(subscriber, || {
|
||||
debug!("hello from my event! yak shaved = {:?}", true);
|
||||
});
|
||||
|
||||
handle.assert_finished();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn one_with_everything() {
|
||||
let (subscriber, handle) = subscriber::mock()
|
||||
.event(
|
||||
event::mock()
|
||||
.with_fields(
|
||||
field::mock("message")
|
||||
.with_value(&tokio_trace::field::debug(format_args!(
|
||||
"{:#x} make me one with{what:.>20}",
|
||||
4277009102u64,
|
||||
what = "everything"
|
||||
)))
|
||||
.and(field::mock("foo").with_value(&666))
|
||||
.and(field::mock("bar").with_value(&false))
|
||||
.only(),
|
||||
)
|
||||
.at_level(tokio_trace::Level::ERROR)
|
||||
.with_target("whatever"),
|
||||
)
|
||||
.done()
|
||||
.run_with_handle();
|
||||
|
||||
with_default(subscriber, || {
|
||||
event!(
|
||||
target: "whatever",
|
||||
tokio_trace::Level::ERROR,
|
||||
{ foo = 666, bar = false },
|
||||
"{:#x} make me one with{what:.>20}", 4277009102u64, what = "everything"
|
||||
);
|
||||
});
|
||||
|
||||
handle.assert_finished();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn moved_field() {
|
||||
let (subscriber, handle) = subscriber::mock()
|
||||
.event(
|
||||
event::mock().with_fields(
|
||||
field::mock("foo")
|
||||
.with_value(&display("hello from my event"))
|
||||
.only(),
|
||||
),
|
||||
)
|
||||
.done()
|
||||
.run_with_handle();
|
||||
with_default(subscriber, || {
|
||||
let from = "my event";
|
||||
event!(Level::INFO, foo = display(format!("hello from {}", from)))
|
||||
});
|
||||
|
||||
handle.assert_finished();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn borrowed_field() {
|
||||
let (subscriber, handle) = subscriber::mock()
|
||||
.event(
|
||||
event::mock().with_fields(
|
||||
field::mock("foo")
|
||||
.with_value(&display("hello from my event"))
|
||||
.only(),
|
||||
),
|
||||
)
|
||||
.done()
|
||||
.run_with_handle();
|
||||
with_default(subscriber, || {
|
||||
let from = "my event";
|
||||
let mut message = format!("hello from {}", from);
|
||||
event!(Level::INFO, foo = display(&message));
|
||||
message.push_str(", which happened!");
|
||||
});
|
||||
|
||||
handle.assert_finished();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn move_field_out_of_struct() {
|
||||
#[derive(Debug)]
|
||||
struct Position {
|
||||
x: f32,
|
||||
y: f32,
|
||||
}
|
||||
|
||||
let pos = Position {
|
||||
x: 3.234,
|
||||
y: -1.223,
|
||||
};
|
||||
let (subscriber, handle) = subscriber::mock()
|
||||
.event(
|
||||
event::mock().with_fields(
|
||||
field::mock("x")
|
||||
.with_value(&debug(3.234))
|
||||
.and(field::mock("y").with_value(&debug(-1.223)))
|
||||
.only(),
|
||||
),
|
||||
)
|
||||
.event(event::mock().with_fields(field::mock("position").with_value(&debug(&pos))))
|
||||
.done()
|
||||
.run_with_handle();
|
||||
|
||||
with_default(subscriber, || {
|
||||
let pos = Position {
|
||||
x: 3.234,
|
||||
y: -1.223,
|
||||
};
|
||||
debug!(x = debug(pos.x), y = debug(pos.y));
|
||||
debug!(target: "app_events", { position = debug(pos) }, "New position");
|
||||
});
|
||||
handle.assert_finished();
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
#[macro_use]
|
||||
extern crate tokio_trace;
|
||||
// Tests that macros work across various invocation syntax.
|
||||
//
|
||||
// These are quite repetitive, and _could_ be generated by a macro. However,
|
||||
// they're compile-time tests, so I want to get line numbers etc out of
|
||||
// failures, and producing them with a macro would muddy the waters a bit.
|
||||
|
||||
#[test]
|
||||
fn span() {
|
||||
span!(target: "foo_events", level: tokio_trace::Level::DEBUG, "foo", bar = 2, baz = 3);
|
||||
span!(target: "foo_events", level: tokio_trace::Level::DEBUG, "foo", bar = 2, baz = 4,);
|
||||
span!(target: "foo_events", level: tokio_trace::Level::DEBUG, "foo");
|
||||
span!(target: "foo_events", level: tokio_trace::Level::DEBUG, "bar",);
|
||||
span!(level: tokio_trace::Level::DEBUG, "foo", bar = 2, baz = 3);
|
||||
span!(level: tokio_trace::Level::DEBUG, "foo", bar = 2, baz = 4,);
|
||||
span!(level: tokio_trace::Level::DEBUG, "foo");
|
||||
span!(level: tokio_trace::Level::DEBUG, "bar",);
|
||||
span!("foo", bar = 2, baz = 3);
|
||||
span!("foo", bar = 2, baz = 4,);
|
||||
span!("foo");
|
||||
span!("bar",);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn event() {
|
||||
event!(tokio_trace::Level::DEBUG, foo = 3, bar = 2, baz = false);
|
||||
event!(tokio_trace::Level::DEBUG, foo = 3, bar = 3,);
|
||||
event!(tokio_trace::Level::DEBUG, "foo");
|
||||
event!(tokio_trace::Level::DEBUG, "foo: {}", 3);
|
||||
event!(tokio_trace::Level::DEBUG, { foo = 3, bar = 80 }, "baz");
|
||||
event!(tokio_trace::Level::DEBUG, { foo = 2, bar = 79 }, "baz {:?}", true);
|
||||
event!(tokio_trace::Level::DEBUG, { foo = 2, bar = 79 }, "baz {:?}, {quux}", true, quux = false);
|
||||
event!(tokio_trace::Level::DEBUG, { foo = 2, bar = 78, }, "baz");
|
||||
event!(target: "foo_events", tokio_trace::Level::DEBUG, foo = 3, bar = 2, baz = false);
|
||||
event!(target: "foo_events", tokio_trace::Level::DEBUG, foo = 3, bar = 3,);
|
||||
event!(target: "foo_events", tokio_trace::Level::DEBUG, "foo");
|
||||
event!(target: "foo_events", tokio_trace::Level::DEBUG, "foo: {}", 3);
|
||||
event!(target: "foo_events", tokio_trace::Level::DEBUG, { foo = 3, bar = 80 }, "baz");
|
||||
event!(target: "foo_events", tokio_trace::Level::DEBUG, { foo = 2, bar = 79 }, "baz {:?}", true);
|
||||
event!(target: "foo_events", tokio_trace::Level::DEBUG, { foo = 2, bar = 79 }, "baz {:?}, {quux}", true, quux = false);
|
||||
event!(target: "foo_events", tokio_trace::Level::DEBUG, { foo = 2, bar = 78, }, "baz");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trace() {
|
||||
trace!(foo = 3, bar = 2, baz = false);
|
||||
trace!(foo = 3, bar = 3,);
|
||||
trace!("foo");
|
||||
trace!("foo: {}", 3);
|
||||
trace!({ foo = 3, bar = 80 }, "baz");
|
||||
trace!({ foo = 2, bar = 79 }, "baz {:?}", true);
|
||||
trace!({ foo = 2, bar = 79 }, "baz {:?}, {quux}", true, quux = false);
|
||||
trace!({ foo = 2, bar = 78, }, "baz");
|
||||
trace!(target: "foo_events", foo = 3, bar = 2, baz = false);
|
||||
trace!(target: "foo_events", foo = 3, bar = 3,);
|
||||
trace!(target: "foo_events", "foo");
|
||||
trace!(target: "foo_events", "foo: {}", 3);
|
||||
trace!(target: "foo_events", { foo = 3, bar = 80 }, "baz");
|
||||
trace!(target: "foo_events", { foo = 2, bar = 79 }, "baz {:?}", true);
|
||||
trace!(target: "foo_events", { foo = 2, bar = 79 }, "baz {:?}, {quux}", true, quux = false);
|
||||
trace!(target: "foo_events", { foo = 2, bar = 78, }, "baz");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn debug() {
|
||||
debug!(foo = 3, bar = 2, baz = false);
|
||||
debug!(foo = 3, bar = 3,);
|
||||
debug!("foo");
|
||||
debug!("foo: {}", 3);
|
||||
debug!({ foo = 3, bar = 80 }, "baz");
|
||||
debug!({ foo = 2, bar = 79 }, "baz {:?}", true);
|
||||
debug!({ foo = 2, bar = 79 }, "baz {:?}, {quux}", true, quux = false);
|
||||
debug!({ foo = 2, bar = 78, }, "baz");
|
||||
debug!(target: "foo_events", foo = 3, bar = 2, baz = false);
|
||||
debug!(target: "foo_events", foo = 3, bar = 3,);
|
||||
debug!(target: "foo_events", "foo");
|
||||
debug!(target: "foo_events", "foo: {}", 3);
|
||||
debug!(target: "foo_events", { foo = 3, bar = 80 }, "baz");
|
||||
debug!(target: "foo_events", { foo = 2, bar = 79 }, "baz {:?}", true);
|
||||
debug!(target: "foo_events", { foo = 2, bar = 79 }, "baz {:?}, {quux}", true, quux = false);
|
||||
debug!(target: "foo_events", { foo = 2, bar = 79 }, "baz {:?}, {quux}", true, quux = false);
|
||||
debug!(target: "foo_events", { foo = 2, bar = 78, }, "baz");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn info() {
|
||||
info!(foo = 3, bar = 2, baz = false);
|
||||
info!(foo = 3, bar = 3,);
|
||||
info!("foo");
|
||||
info!("foo: {}", 3);
|
||||
info!({ foo = 3, bar = 80 }, "baz");
|
||||
info!({ foo = 2, bar = 79 }, "baz {:?}", true);
|
||||
info!({ foo = 2, bar = 79 }, "baz {:?}, {quux}", true, quux = false);
|
||||
info!({ foo = 2, bar = 78, }, "baz");
|
||||
info!(target: "foo_events", foo = 3, bar = 2, baz = false);
|
||||
info!(target: "foo_events", foo = 3, bar = 3,);
|
||||
info!(target: "foo_events", "foo");
|
||||
info!(target: "foo_events", "foo: {}", 3);
|
||||
info!(target: "foo_events", { foo = 3, bar = 80 }, "baz");
|
||||
info!(target: "foo_events", { foo = 2, bar = 79 }, "baz {:?}", true);
|
||||
info!(target: "foo_events", { foo = 2, bar = 79 }, "baz {:?}, {quux}", true, quux = false);
|
||||
info!(target: "foo_events", { foo = 2, bar = 79 }, "baz {:?}, {quux}", true, quux = false);
|
||||
info!(target: "foo_events", { foo = 2, bar = 78, }, "baz");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn warn() {
|
||||
warn!(foo = 3, bar = 2, baz = false);
|
||||
warn!(foo = 3, bar = 3,);
|
||||
warn!("foo");
|
||||
warn!("foo: {}", 3);
|
||||
warn!({ foo = 3, bar = 80 }, "baz");
|
||||
warn!({ foo = 2, bar = 79 }, "baz {:?}", true);
|
||||
warn!({ foo = 2, bar = 79 }, "baz {:?}, {quux}", true, quux = false);
|
||||
warn!({ foo = 2, bar = 78 }, "baz");
|
||||
warn!(target: "foo_events", foo = 3, bar = 2, baz = false);
|
||||
warn!(target: "foo_events", foo = 3, bar = 3,);
|
||||
warn!(target: "foo_events", "foo");
|
||||
warn!(target: "foo_events", "foo: {}", 3);
|
||||
warn!(target: "foo_events", { foo = 3, bar = 80 }, "baz");
|
||||
warn!(target: "foo_events", { foo = 2, bar = 79 }, "baz {:?}", true);
|
||||
warn!(target: "foo_events", { foo = 2, bar = 79 }, "baz {:?}, {quux}", true, quux = false);
|
||||
warn!(target: "foo_events", { foo = 2, bar = 78, }, "baz");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn error() {
|
||||
error!(foo = 3, bar = 2, baz = false);
|
||||
error!(foo = 3, bar = 3,);
|
||||
error!("foo");
|
||||
error!("foo: {}", 3);
|
||||
error!({ foo = 3, bar = 80 }, "baz");
|
||||
error!({ foo = 2, bar = 79 }, "baz {:?}", true);
|
||||
error!({ foo = 2, bar = 79 }, "baz {:?}, {quux}", true, quux = false);
|
||||
error!({ foo = 2, bar = 78, }, "baz");
|
||||
error!(target: "foo_events", foo = 3, bar = 2, baz = false);
|
||||
error!(target: "foo_events", foo = 3, bar = 3,);
|
||||
error!(target: "foo_events", "foo");
|
||||
error!(target: "foo_events", "foo: {}", 3);
|
||||
error!(target: "foo_events", { foo = 3, bar = 80 }, "baz");
|
||||
error!(target: "foo_events", { foo = 2, bar = 79 }, "baz {:?}", true);
|
||||
error!(target: "foo_events", { foo = 2, bar = 79 }, "baz {:?}, {quux}", true, quux = false);
|
||||
error!(target: "foo_events", { foo = 2, bar = 78, }, "baz");
|
||||
}
|
||||
@@ -0,0 +1,478 @@
|
||||
#[macro_use]
|
||||
extern crate tokio_trace;
|
||||
mod support;
|
||||
|
||||
use self::support::*;
|
||||
use std::thread;
|
||||
use tokio_trace::{
|
||||
dispatcher,
|
||||
field::{debug, display},
|
||||
subscriber::with_default,
|
||||
Dispatch, Level, Span,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn closed_handle_cannot_be_entered() {
|
||||
let subscriber = subscriber::mock()
|
||||
.enter(span::mock().named("foo"))
|
||||
.drop_span(span::mock().named("bar"))
|
||||
.enter(span::mock().named("bar"))
|
||||
.exit(span::mock().named("bar"))
|
||||
.drop_span(span::mock().named("bar"))
|
||||
.exit(span::mock().named("foo"))
|
||||
.run();
|
||||
|
||||
dispatcher::with_default(Dispatch::new(subscriber), || {
|
||||
span!("foo").enter(|| {
|
||||
let bar = span!("bar");
|
||||
let mut another_bar = bar.clone();
|
||||
drop(bar);
|
||||
|
||||
another_bar.enter(|| {});
|
||||
|
||||
another_bar.close();
|
||||
// After we close `another_bar`, it should close and not be
|
||||
// re-entered.
|
||||
another_bar.enter(|| {});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn handles_to_the_same_span_are_equal() {
|
||||
// Create a mock subscriber that will return `true` on calls to
|
||||
// `Subscriber::enabled`, so that the spans will be constructed. We
|
||||
// won't enter any spans in this test, so the subscriber won't actually
|
||||
// expect to see any spans.
|
||||
dispatcher::with_default(Dispatch::new(subscriber::mock().run()), || {
|
||||
let foo1 = span!("foo");
|
||||
let foo2 = foo1.clone();
|
||||
// Two handles that point to the same span are equal.
|
||||
assert_eq!(foo1, foo2);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn handles_to_different_spans_are_not_equal() {
|
||||
dispatcher::with_default(Dispatch::new(subscriber::mock().run()), || {
|
||||
// Even though these spans have the same name and fields, they will have
|
||||
// differing metadata, since they were created on different lines.
|
||||
let foo1 = span!("foo", bar = 1u64, baz = false);
|
||||
let foo2 = span!("foo", bar = 1u64, baz = false);
|
||||
|
||||
assert_ne!(foo1, foo2);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn handles_to_different_spans_with_the_same_metadata_are_not_equal() {
|
||||
// Every time time this function is called, it will return a _new
|
||||
// instance_ of a span with the same metadata, name, and fields.
|
||||
fn make_span() -> Span<'static> {
|
||||
span!("foo", bar = 1u64, baz = false)
|
||||
}
|
||||
|
||||
dispatcher::with_default(Dispatch::new(subscriber::mock().run()), || {
|
||||
let foo1 = make_span();
|
||||
let foo2 = make_span();
|
||||
|
||||
assert_ne!(foo1, foo2);
|
||||
// assert_ne!(foo1.data(), foo2.data());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spans_always_go_to_the_subscriber_that_tagged_them() {
|
||||
let subscriber1 = subscriber::mock()
|
||||
.enter(span::mock().named("foo"))
|
||||
.exit(span::mock().named("foo"))
|
||||
.enter(span::mock().named("foo"))
|
||||
.exit(span::mock().named("foo"))
|
||||
.drop_span(span::mock().named("foo"))
|
||||
.done();
|
||||
let subscriber1 = Dispatch::new(subscriber1.run());
|
||||
let subscriber2 = Dispatch::new(subscriber::mock().run());
|
||||
|
||||
let mut foo = dispatcher::with_default(subscriber1, || {
|
||||
let mut foo = span!("foo");
|
||||
foo.enter(|| {});
|
||||
foo
|
||||
});
|
||||
// Even though we enter subscriber 2's context, the subscriber that
|
||||
// tagged the span should see the enter/exit.
|
||||
dispatcher::with_default(subscriber2, move || foo.enter(|| {}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spans_always_go_to_the_subscriber_that_tagged_them_even_across_threads() {
|
||||
let subscriber1 = subscriber::mock()
|
||||
.enter(span::mock().named("foo"))
|
||||
.exit(span::mock().named("foo"))
|
||||
.enter(span::mock().named("foo"))
|
||||
.exit(span::mock().named("foo"))
|
||||
.drop_span(span::mock().named("foo"))
|
||||
.done();
|
||||
let subscriber1 = Dispatch::new(subscriber1.run());
|
||||
let mut foo = dispatcher::with_default(subscriber1, || {
|
||||
let mut foo = span!("foo");
|
||||
foo.enter(|| {});
|
||||
foo
|
||||
});
|
||||
|
||||
// Even though we enter subscriber 2's context, the subscriber that
|
||||
// tagged the span should see the enter/exit.
|
||||
thread::spawn(move || {
|
||||
dispatcher::with_default(Dispatch::new(subscriber::mock().run()), || {
|
||||
foo.enter(|| {});
|
||||
})
|
||||
})
|
||||
.join()
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dropping_a_span_calls_drop_span() {
|
||||
let (subscriber, handle) = subscriber::mock()
|
||||
.enter(span::mock().named("foo"))
|
||||
.exit(span::mock().named("foo"))
|
||||
.drop_span(span::mock().named("foo"))
|
||||
.done()
|
||||
.run_with_handle();
|
||||
dispatcher::with_default(Dispatch::new(subscriber), || {
|
||||
let mut span = span!("foo");
|
||||
span.enter(|| {});
|
||||
drop(span);
|
||||
});
|
||||
|
||||
handle.assert_finished();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn span_closes_after_event() {
|
||||
let (subscriber, handle) = subscriber::mock()
|
||||
.enter(span::mock().named("foo"))
|
||||
.event(event::mock())
|
||||
.exit(span::mock().named("foo"))
|
||||
.drop_span(span::mock().named("foo"))
|
||||
.done()
|
||||
.run_with_handle();
|
||||
dispatcher::with_default(Dispatch::new(subscriber), || {
|
||||
span!("foo").enter(|| {
|
||||
event!(Level::DEBUG, {}, "my event!");
|
||||
});
|
||||
});
|
||||
|
||||
handle.assert_finished();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_span_after_event() {
|
||||
let (subscriber, handle) = subscriber::mock()
|
||||
.enter(span::mock().named("foo"))
|
||||
.event(event::mock())
|
||||
.exit(span::mock().named("foo"))
|
||||
.drop_span(span::mock().named("foo"))
|
||||
.enter(span::mock().named("bar"))
|
||||
.exit(span::mock().named("bar"))
|
||||
.drop_span(span::mock().named("bar"))
|
||||
.done()
|
||||
.run_with_handle();
|
||||
dispatcher::with_default(Dispatch::new(subscriber), || {
|
||||
span!("foo").enter(|| {
|
||||
event!(Level::DEBUG, {}, "my event!");
|
||||
});
|
||||
span!("bar").enter(|| {});
|
||||
});
|
||||
|
||||
handle.assert_finished();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn event_outside_of_span() {
|
||||
let (subscriber, handle) = subscriber::mock()
|
||||
.event(event::mock())
|
||||
.enter(span::mock().named("foo"))
|
||||
.exit(span::mock().named("foo"))
|
||||
.drop_span(span::mock().named("foo"))
|
||||
.done()
|
||||
.run_with_handle();
|
||||
dispatcher::with_default(Dispatch::new(subscriber), || {
|
||||
debug!("my event!");
|
||||
span!("foo").enter(|| {});
|
||||
});
|
||||
|
||||
handle.assert_finished();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cloning_a_span_calls_clone_span() {
|
||||
let (subscriber, handle) = subscriber::mock()
|
||||
.clone_span(span::mock().named("foo"))
|
||||
.run_with_handle();
|
||||
dispatcher::with_default(Dispatch::new(subscriber), || {
|
||||
let span = span!("foo");
|
||||
let _span2 = span.clone();
|
||||
});
|
||||
|
||||
handle.assert_finished();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn drop_span_when_exiting_dispatchers_context() {
|
||||
let (subscriber, handle) = subscriber::mock()
|
||||
.clone_span(span::mock().named("foo"))
|
||||
.drop_span(span::mock().named("foo"))
|
||||
.drop_span(span::mock().named("foo"))
|
||||
.run_with_handle();
|
||||
dispatcher::with_default(Dispatch::new(subscriber), || {
|
||||
let span = span!("foo");
|
||||
let _span2 = span.clone();
|
||||
drop(span);
|
||||
});
|
||||
|
||||
handle.assert_finished();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clone_and_drop_span_always_go_to_the_subscriber_that_tagged_the_span() {
|
||||
let (subscriber1, handle1) = subscriber::mock()
|
||||
.enter(span::mock().named("foo"))
|
||||
.exit(span::mock().named("foo"))
|
||||
.clone_span(span::mock().named("foo"))
|
||||
.enter(span::mock().named("foo"))
|
||||
.exit(span::mock().named("foo"))
|
||||
.drop_span(span::mock().named("foo"))
|
||||
.drop_span(span::mock().named("foo"))
|
||||
.run_with_handle();
|
||||
let subscriber1 = Dispatch::new(subscriber1);
|
||||
let subscriber2 = Dispatch::new(subscriber::mock().done().run());
|
||||
|
||||
let mut foo = dispatcher::with_default(subscriber1, || {
|
||||
let mut foo = span!("foo");
|
||||
foo.enter(|| {});
|
||||
foo
|
||||
});
|
||||
// Even though we enter subscriber 2's context, the subscriber that
|
||||
// tagged the span should see the enter/exit.
|
||||
dispatcher::with_default(subscriber2, move || {
|
||||
let foo2 = foo.clone();
|
||||
foo.enter(|| {});
|
||||
drop(foo);
|
||||
drop(foo2);
|
||||
});
|
||||
|
||||
handle1.assert_finished();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn span_closes_when_exited() {
|
||||
let (subscriber, handle) = subscriber::mock()
|
||||
.enter(span::mock().named("foo"))
|
||||
.exit(span::mock().named("foo"))
|
||||
.drop_span(span::mock().named("foo"))
|
||||
.done()
|
||||
.run_with_handle();
|
||||
dispatcher::with_default(Dispatch::new(subscriber), || {
|
||||
let mut foo = span!("foo");
|
||||
assert!(!foo.is_closed());
|
||||
|
||||
foo.enter(|| {});
|
||||
assert!(!foo.is_closed());
|
||||
|
||||
foo.close();
|
||||
assert!(foo.is_closed());
|
||||
|
||||
// Now that `foo` has closed, entering it should do nothing.
|
||||
foo.enter(|| {});
|
||||
assert!(foo.is_closed());
|
||||
});
|
||||
|
||||
handle.assert_finished();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn entering_a_closed_span_again_is_a_no_op() {
|
||||
let (subscriber, handle) = subscriber::mock()
|
||||
.drop_span(span::mock().named("foo"))
|
||||
.done()
|
||||
.run_with_handle();
|
||||
dispatcher::with_default(Dispatch::new(subscriber), || {
|
||||
let mut foo = span!("foo");
|
||||
|
||||
foo.close();
|
||||
foo.enter(|| {
|
||||
// This should do nothing.
|
||||
});
|
||||
assert!(foo.is_closed());
|
||||
});
|
||||
|
||||
handle.assert_finished();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn moved_field() {
|
||||
let (subscriber, handle) = subscriber::mock()
|
||||
.new_span(
|
||||
span::mock().named("foo").with_field(
|
||||
field::mock("bar")
|
||||
.with_value(&display("hello from my span"))
|
||||
.only(),
|
||||
),
|
||||
)
|
||||
.enter(span::mock().named("foo"))
|
||||
.exit(span::mock().named("foo"))
|
||||
.drop_span(span::mock().named("foo"))
|
||||
.done()
|
||||
.run_with_handle();
|
||||
dispatcher::with_default(Dispatch::new(subscriber), || {
|
||||
let from = "my span";
|
||||
let mut span = span!("foo", bar = display(format!("hello from {}", from)));
|
||||
span.enter(|| {});
|
||||
});
|
||||
|
||||
handle.assert_finished();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn borrowed_field() {
|
||||
let (subscriber, handle) = subscriber::mock()
|
||||
.new_span(
|
||||
span::mock().named("foo").with_field(
|
||||
field::mock("bar")
|
||||
.with_value(&display("hello from my span"))
|
||||
.only(),
|
||||
),
|
||||
)
|
||||
.enter(span::mock().named("foo"))
|
||||
.exit(span::mock().named("foo"))
|
||||
.drop_span(span::mock().named("foo"))
|
||||
.done()
|
||||
.run_with_handle();
|
||||
|
||||
dispatcher::with_default(Dispatch::new(subscriber), || {
|
||||
let from = "my span";
|
||||
let mut message = format!("hello from {}", from);
|
||||
let mut span = span!("foo", bar = display(&message));
|
||||
span.enter(|| {
|
||||
message.insert_str(10, " inside");
|
||||
});
|
||||
});
|
||||
|
||||
handle.assert_finished();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn move_field_out_of_struct() {
|
||||
#[derive(Debug)]
|
||||
struct Position {
|
||||
x: f32,
|
||||
y: f32,
|
||||
}
|
||||
|
||||
let pos = Position {
|
||||
x: 3.234,
|
||||
y: -1.223,
|
||||
};
|
||||
let (subscriber, handle) = subscriber::mock()
|
||||
.new_span(
|
||||
span::mock().named("foo").with_field(
|
||||
field::mock("x")
|
||||
.with_value(&debug(3.234))
|
||||
.and(field::mock("y").with_value(&debug(-1.223)))
|
||||
.only(),
|
||||
),
|
||||
)
|
||||
.new_span(
|
||||
span::mock()
|
||||
.named("bar")
|
||||
.with_field(field::mock("position").with_value(&debug(&pos)).only()),
|
||||
)
|
||||
.run_with_handle();
|
||||
|
||||
dispatcher::with_default(Dispatch::new(subscriber), || {
|
||||
let pos = Position {
|
||||
x: 3.234,
|
||||
y: -1.223,
|
||||
};
|
||||
let mut foo = span!("foo", x = debug(pos.x), y = debug(pos.y));
|
||||
let mut bar = span!("bar", position = debug(pos));
|
||||
foo.enter(|| {});
|
||||
bar.enter(|| {});
|
||||
});
|
||||
|
||||
handle.assert_finished();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_field_after_new_span() {
|
||||
let (subscriber, handle) = subscriber::mock()
|
||||
.new_span(
|
||||
span::mock()
|
||||
.named("foo")
|
||||
.with_field(field::mock("bar").with_value(&5).only()),
|
||||
)
|
||||
.record(
|
||||
span::mock().named("foo"),
|
||||
field::mock("baz").with_value(&true).only(),
|
||||
)
|
||||
.enter(span::mock().named("foo"))
|
||||
.exit(span::mock().named("foo"))
|
||||
.drop_span(span::mock().named("foo"))
|
||||
.done()
|
||||
.run_with_handle();
|
||||
|
||||
dispatcher::with_default(Dispatch::new(subscriber), || {
|
||||
let mut span = span!("foo", bar = 5, baz);
|
||||
span.record("baz", &true);
|
||||
span.enter(|| {})
|
||||
});
|
||||
|
||||
handle.assert_finished();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_fields_only_after_new_span() {
|
||||
let (subscriber, handle) = subscriber::mock()
|
||||
.new_span(span::mock().named("foo"))
|
||||
.record(
|
||||
span::mock().named("foo"),
|
||||
field::mock("bar").with_value(&5).only(),
|
||||
)
|
||||
.record(
|
||||
span::mock().named("foo"),
|
||||
field::mock("baz").with_value(&true).only(),
|
||||
)
|
||||
.enter(span::mock().named("foo"))
|
||||
.exit(span::mock().named("foo"))
|
||||
.drop_span(span::mock().named("foo"))
|
||||
.done()
|
||||
.run_with_handle();
|
||||
|
||||
dispatcher::with_default(Dispatch::new(subscriber), || {
|
||||
let mut span = span!("foo", bar, baz);
|
||||
span.record("bar", &5);
|
||||
span.record("baz", &true);
|
||||
span.enter(|| {})
|
||||
});
|
||||
|
||||
handle.assert_finished();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_span_with_target_and_log_level() {
|
||||
let (subscriber, handle) = subscriber::mock()
|
||||
.new_span(
|
||||
span::mock()
|
||||
.named("foo")
|
||||
.with_target("app_span")
|
||||
.at_level(tokio_trace::Level::DEBUG),
|
||||
)
|
||||
.done()
|
||||
.run_with_handle();
|
||||
|
||||
with_default(subscriber, || {
|
||||
span!(target: "app_span", level: tokio_trace::Level::DEBUG, "foo");
|
||||
});
|
||||
|
||||
handle.assert_finished();
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
#[macro_use]
|
||||
extern crate tokio_trace;
|
||||
mod support;
|
||||
|
||||
use self::support::*;
|
||||
use tokio_trace::{dispatcher, Dispatch};
|
||||
|
||||
use std::sync::{
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
Arc,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn filters_are_not_reevaluated_for_the_same_span() {
|
||||
// Asserts that the `span!` macro caches the result of calling
|
||||
// `Subscriber::enabled` for each span.
|
||||
let alice_count = Arc::new(AtomicUsize::new(0));
|
||||
let bob_count = Arc::new(AtomicUsize::new(0));
|
||||
let alice_count2 = alice_count.clone();
|
||||
let bob_count2 = bob_count.clone();
|
||||
|
||||
let (subscriber, handle) = subscriber::mock()
|
||||
.with_filter(move |meta| match meta.name {
|
||||
"alice" => {
|
||||
alice_count2.fetch_add(1, Ordering::Relaxed);
|
||||
false
|
||||
}
|
||||
"bob" => {
|
||||
bob_count2.fetch_add(1, Ordering::Relaxed);
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
})
|
||||
.run_with_handle();
|
||||
|
||||
dispatcher::with_default(Dispatch::new(subscriber), move || {
|
||||
// Enter "alice" and then "bob". The dispatcher expects to see "bob" but
|
||||
// not "alice."
|
||||
let mut alice = span!("alice");
|
||||
let mut bob = alice.enter(|| {
|
||||
let mut bob = span!("bob");
|
||||
bob.enter(|| ());
|
||||
bob
|
||||
});
|
||||
|
||||
// The filter should have seen each span a single time.
|
||||
assert_eq!(alice_count.load(Ordering::Relaxed), 1);
|
||||
assert_eq!(bob_count.load(Ordering::Relaxed), 1);
|
||||
|
||||
alice.enter(|| bob.enter(|| {}));
|
||||
|
||||
// The subscriber should see "bob" again, but the filter should not have
|
||||
// been called.
|
||||
assert_eq!(alice_count.load(Ordering::Relaxed), 1);
|
||||
assert_eq!(bob_count.load(Ordering::Relaxed), 1);
|
||||
|
||||
bob.enter(|| {});
|
||||
assert_eq!(alice_count.load(Ordering::Relaxed), 1);
|
||||
assert_eq!(bob_count.load(Ordering::Relaxed), 1);
|
||||
});
|
||||
handle.assert_finished();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filters_are_reevaluated_for_different_call_sites() {
|
||||
// Asserts that the `span!` macro caches the result of calling
|
||||
// `Subscriber::enabled` for each span.
|
||||
let charlie_count = Arc::new(AtomicUsize::new(0));
|
||||
let dave_count = Arc::new(AtomicUsize::new(0));
|
||||
let charlie_count2 = charlie_count.clone();
|
||||
let dave_count2 = dave_count.clone();
|
||||
|
||||
let subscriber = subscriber::mock()
|
||||
.with_filter(move |meta| {
|
||||
println!("Filter: {:?}", meta.name);
|
||||
match meta.name {
|
||||
"charlie" => {
|
||||
charlie_count2.fetch_add(1, Ordering::Relaxed);
|
||||
false
|
||||
}
|
||||
"dave" => {
|
||||
dave_count2.fetch_add(1, Ordering::Relaxed);
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
})
|
||||
.run();
|
||||
|
||||
dispatcher::with_default(Dispatch::new(subscriber), move || {
|
||||
// Enter "charlie" and then "dave". The dispatcher expects to see "dave" but
|
||||
// not "charlie."
|
||||
let mut charlie = span!("charlie");
|
||||
let mut dave = charlie.enter(|| {
|
||||
let mut dave = span!("dave");
|
||||
dave.enter(|| {});
|
||||
dave
|
||||
});
|
||||
|
||||
// The filter should have seen each span a single time.
|
||||
assert_eq!(charlie_count.load(Ordering::Relaxed), 1);
|
||||
assert_eq!(dave_count.load(Ordering::Relaxed), 1);
|
||||
|
||||
charlie.enter(|| dave.enter(|| {}));
|
||||
|
||||
// The subscriber should see "dave" again, but the filter should not have
|
||||
// been called.
|
||||
assert_eq!(charlie_count.load(Ordering::Relaxed), 1);
|
||||
assert_eq!(dave_count.load(Ordering::Relaxed), 1);
|
||||
|
||||
// A different span with the same name has a different call site, so it
|
||||
// should cause the filter to be reapplied.
|
||||
let mut charlie2 = span!("charlie");
|
||||
charlie.enter(|| {});
|
||||
assert_eq!(charlie_count.load(Ordering::Relaxed), 2);
|
||||
assert_eq!(dave_count.load(Ordering::Relaxed), 1);
|
||||
|
||||
// But, the filter should not be re-evaluated for the new "charlie" span
|
||||
// when it is re-entered.
|
||||
charlie2.enter(|| span!("dave").enter(|| {}));
|
||||
assert_eq!(charlie_count.load(Ordering::Relaxed), 2);
|
||||
assert_eq!(dave_count.load(Ordering::Relaxed), 2);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filter_caching_is_lexically_scoped() {
|
||||
pub fn my_great_function() -> bool {
|
||||
span!("emily").enter(|| true)
|
||||
}
|
||||
|
||||
pub fn my_other_function() -> bool {
|
||||
span!("frank").enter(|| true)
|
||||
}
|
||||
|
||||
let count = Arc::new(AtomicUsize::new(0));
|
||||
let count2 = count.clone();
|
||||
|
||||
let subscriber = subscriber::mock()
|
||||
.with_filter(move |meta| match meta.name {
|
||||
"emily" | "frank" => {
|
||||
count2.fetch_add(1, Ordering::Relaxed);
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
})
|
||||
.run();
|
||||
|
||||
dispatcher::with_default(Dispatch::new(subscriber), || {
|
||||
// Call the function once. The filter should be re-evaluated.
|
||||
assert!(my_great_function());
|
||||
assert_eq!(count.load(Ordering::Relaxed), 1);
|
||||
|
||||
// Call the function again. The cached result should be used.
|
||||
assert!(my_great_function());
|
||||
assert_eq!(count.load(Ordering::Relaxed), 1);
|
||||
|
||||
assert!(my_other_function());
|
||||
assert_eq!(count.load(Ordering::Relaxed), 2);
|
||||
|
||||
assert!(my_great_function());
|
||||
assert_eq!(count.load(Ordering::Relaxed), 2);
|
||||
|
||||
assert!(my_other_function());
|
||||
assert_eq!(count.load(Ordering::Relaxed), 2);
|
||||
|
||||
assert!(my_great_function());
|
||||
assert_eq!(count.load(Ordering::Relaxed), 2);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
#![allow(missing_docs)]
|
||||
use super::{field, metadata};
|
||||
|
||||
use std::fmt;
|
||||
|
||||
/// A mock event.
|
||||
///
|
||||
/// This is intended for use with the mock subscriber API in the
|
||||
/// `subscriber` module.
|
||||
#[derive(Debug, Default, Eq, PartialEq)]
|
||||
pub struct MockEvent {
|
||||
pub fields: Option<field::Expect>,
|
||||
metadata: metadata::Expect,
|
||||
}
|
||||
|
||||
pub fn mock() -> MockEvent {
|
||||
MockEvent {
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl MockEvent {
|
||||
pub fn named<I>(self, name: I) -> Self
|
||||
where
|
||||
I: Into<String>,
|
||||
{
|
||||
Self {
|
||||
metadata: metadata::Expect {
|
||||
name: Some(name.into()),
|
||||
..self.metadata
|
||||
},
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_fields<I>(self, fields: I) -> Self
|
||||
where
|
||||
I: Into<field::Expect>,
|
||||
{
|
||||
Self {
|
||||
fields: Some(fields.into()),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn at_level(self, level: tokio_trace::Level) -> Self {
|
||||
Self {
|
||||
metadata: metadata::Expect {
|
||||
level: Some(level),
|
||||
..self.metadata
|
||||
},
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_target<I>(self, target: I) -> Self
|
||||
where
|
||||
I: Into<String>,
|
||||
{
|
||||
Self {
|
||||
metadata: metadata::Expect {
|
||||
target: Some(target.into()),
|
||||
..self.metadata
|
||||
},
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub(in support) fn check(self, event: &tokio_trace::Event) {
|
||||
let meta = event.metadata();
|
||||
let name = meta.name();
|
||||
self.metadata.check(meta, format_args!("event {}", name));
|
||||
if let Some(mut expected_fields) = self.fields {
|
||||
let mut checker = expected_fields.checker(format!("{}", name));
|
||||
event.record(&mut checker);
|
||||
checker.finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for MockEvent {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "an event")?;
|
||||
if let Some(ref name) = self.metadata.name {
|
||||
write!(f, " named {:?}", name)?;
|
||||
}
|
||||
if let Some(ref fields) = self.fields {
|
||||
write!(f, " with {}", fields)?
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
use tokio_trace::{
|
||||
callsite::Callsite,
|
||||
field::{self, Field, Record, Value},
|
||||
};
|
||||
|
||||
use std::{collections::HashMap, fmt};
|
||||
|
||||
#[derive(Default, Debug, Eq, PartialEq)]
|
||||
pub struct Expect {
|
||||
fields: HashMap<String, MockValue>,
|
||||
only: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MockField {
|
||||
name: String,
|
||||
value: MockValue,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub enum MockValue {
|
||||
I64(i64),
|
||||
U64(u64),
|
||||
Bool(bool),
|
||||
Str(String),
|
||||
Debug(String),
|
||||
Any,
|
||||
}
|
||||
|
||||
pub fn mock<K>(name: K) -> MockField
|
||||
where
|
||||
String: From<K>,
|
||||
{
|
||||
MockField {
|
||||
name: name.into(),
|
||||
value: MockValue::Any,
|
||||
}
|
||||
}
|
||||
|
||||
impl MockField {
|
||||
/// Expect a field with the given name and value.
|
||||
pub fn with_value(self, value: &Value) -> Self {
|
||||
Self {
|
||||
value: MockValue::from(value),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn and(self, other: MockField) -> Expect {
|
||||
Expect {
|
||||
fields: HashMap::new(),
|
||||
only: false,
|
||||
}
|
||||
.and(self)
|
||||
.and(other)
|
||||
}
|
||||
|
||||
pub fn only(self) -> Expect {
|
||||
Expect {
|
||||
fields: HashMap::new(),
|
||||
only: true,
|
||||
}
|
||||
.and(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<Expect> for MockField {
|
||||
fn into(self) -> Expect {
|
||||
Expect {
|
||||
fields: HashMap::new(),
|
||||
only: false,
|
||||
}
|
||||
.and(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Expect {
|
||||
pub fn and(mut self, field: MockField) -> Self {
|
||||
self.fields.insert(field.name, field.value);
|
||||
self
|
||||
}
|
||||
|
||||
/// Indicates that no fields other than those specified should be expected.
|
||||
pub fn only(self) -> Self {
|
||||
Self { only: true, ..self }
|
||||
}
|
||||
|
||||
fn compare_or_panic(&mut self, name: &str, value: &Value, ctx: &str) {
|
||||
let value = value.into();
|
||||
match self.fields.remove(name) {
|
||||
Some(MockValue::Any) => {}
|
||||
Some(expected) => assert!(
|
||||
expected == value,
|
||||
"\nexpected `{}` to contain:\n\t`{}{}`\nbut got:\n\t`{}{}`",
|
||||
ctx,
|
||||
name,
|
||||
expected,
|
||||
name,
|
||||
value
|
||||
),
|
||||
None if self.only => panic!(
|
||||
"\nexpected `{}` to contain only:\n\t`{}`\nbut got:\n\t`{}{}`",
|
||||
ctx, self, name, value
|
||||
),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn checker<'a>(&'a mut self, ctx: String) -> CheckRecorder<'a> {
|
||||
CheckRecorder { expect: self, ctx }
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.fields.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for MockValue {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
MockValue::I64(v) => write!(f, ": i64 = {:?}", v),
|
||||
MockValue::U64(v) => write!(f, ": u64 = {:?}", v),
|
||||
MockValue::Bool(v) => write!(f, ": bool = {:?}", v),
|
||||
MockValue::Str(v) => write!(f, ": &str = {:?}", v),
|
||||
MockValue::Debug(v) => write!(f, ": &fmt::Debug = {:?}", v),
|
||||
MockValue::Any => write!(f, ": _ = _"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CheckRecorder<'a> {
|
||||
expect: &'a mut Expect,
|
||||
ctx: String,
|
||||
}
|
||||
|
||||
impl<'a> Record for CheckRecorder<'a> {
|
||||
fn record_i64(&mut self, field: &Field, value: i64) {
|
||||
self.expect
|
||||
.compare_or_panic(field.name(), &value, &self.ctx[..])
|
||||
}
|
||||
|
||||
fn record_u64(&mut self, field: &Field, value: u64) {
|
||||
self.expect
|
||||
.compare_or_panic(field.name(), &value, &self.ctx[..])
|
||||
}
|
||||
|
||||
fn record_bool(&mut self, field: &Field, value: bool) {
|
||||
self.expect
|
||||
.compare_or_panic(field.name(), &value, &self.ctx[..])
|
||||
}
|
||||
|
||||
fn record_str(&mut self, field: &Field, value: &str) {
|
||||
self.expect
|
||||
.compare_or_panic(field.name(), &value, &self.ctx[..])
|
||||
}
|
||||
|
||||
fn record_debug(&mut self, field: &Field, value: &fmt::Debug) {
|
||||
self.expect
|
||||
.compare_or_panic(field.name(), &field::debug(value), &self.ctx)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> CheckRecorder<'a> {
|
||||
pub fn finish(self) {
|
||||
assert!(
|
||||
self.expect.fields.is_empty(),
|
||||
"{}missing {}",
|
||||
self.expect,
|
||||
self.ctx
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Value> for MockValue {
|
||||
fn from(value: &'a Value) -> Self {
|
||||
struct MockValueBuilder {
|
||||
value: Option<MockValue>,
|
||||
}
|
||||
|
||||
impl Record for MockValueBuilder {
|
||||
fn record_i64(&mut self, _: &Field, value: i64) {
|
||||
self.value = Some(MockValue::I64(value));
|
||||
}
|
||||
|
||||
fn record_u64(&mut self, _: &Field, value: u64) {
|
||||
self.value = Some(MockValue::U64(value));
|
||||
}
|
||||
|
||||
fn record_bool(&mut self, _: &Field, value: bool) {
|
||||
self.value = Some(MockValue::Bool(value));
|
||||
}
|
||||
|
||||
fn record_str(&mut self, _: &Field, value: &str) {
|
||||
self.value = Some(MockValue::Str(value.to_owned()));
|
||||
}
|
||||
|
||||
fn record_debug(&mut self, _: &Field, value: &fmt::Debug) {
|
||||
self.value = Some(MockValue::Debug(format!("{:?}", value)));
|
||||
}
|
||||
}
|
||||
|
||||
let fake_field = callsite!(name: "fake", fields: fake_field)
|
||||
.metadata()
|
||||
.fields()
|
||||
.field("fake_field")
|
||||
.unwrap();
|
||||
let mut builder = MockValueBuilder { value: None };
|
||||
value.record(&fake_field, &mut builder);
|
||||
builder
|
||||
.value
|
||||
.expect("finish called before a value was recorded")
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Expect {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "fields ")?;
|
||||
let entries = self
|
||||
.fields
|
||||
.iter()
|
||||
.map(|(k, v)| (field::display(k), field::display(v)));
|
||||
f.debug_map().entries(entries).finish()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
use std::fmt;
|
||||
use tokio_trace::Metadata;
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Default)]
|
||||
pub struct Expect {
|
||||
pub name: Option<String>,
|
||||
pub level: Option<tokio_trace::Level>,
|
||||
pub target: Option<String>,
|
||||
}
|
||||
|
||||
impl Expect {
|
||||
pub(in support) fn check(&self, actual: &Metadata, ctx: fmt::Arguments) {
|
||||
if let Some(ref expected_name) = self.name {
|
||||
let name = actual.name();
|
||||
assert!(
|
||||
expected_name == name,
|
||||
"expected {} to be named `{}`, but got one named `{}`",
|
||||
ctx,
|
||||
expected_name,
|
||||
name
|
||||
)
|
||||
}
|
||||
|
||||
if let Some(ref expected_level) = self.level {
|
||||
let level = actual.level();
|
||||
assert!(
|
||||
expected_level == level,
|
||||
"expected {} to be at level `{:?}`, but it was at level `{:?}` instead",
|
||||
ctx,
|
||||
expected_level,
|
||||
level,
|
||||
)
|
||||
}
|
||||
|
||||
if let Some(ref expected_target) = self.target {
|
||||
let target = actual.target();
|
||||
assert!(
|
||||
expected_target == &target,
|
||||
"expected {} to have target `{}`, but it had target `{}` instead",
|
||||
ctx,
|
||||
expected_target,
|
||||
target,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Expect {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
if let Some(ref name) = self.name {
|
||||
write!(f, "named `{}`", name)?;
|
||||
}
|
||||
|
||||
if let Some(ref level) = self.level {
|
||||
write!(f, " at the `{:?}` level", level)?;
|
||||
}
|
||||
|
||||
if let Some(ref target) = self.target {
|
||||
write!(f, " with target `{}`", target)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
#![allow(dead_code)]
|
||||
pub mod event;
|
||||
pub mod field;
|
||||
mod metadata;
|
||||
pub mod span;
|
||||
pub mod subscriber;
|
||||
@@ -0,0 +1,109 @@
|
||||
#![allow(missing_docs)]
|
||||
use super::{field, metadata};
|
||||
use std::fmt;
|
||||
|
||||
/// A mock span.
|
||||
///
|
||||
/// This is intended for use with the mock subscriber API in the
|
||||
/// `subscriber` module.
|
||||
#[derive(Debug, Default, Eq, PartialEq)]
|
||||
pub struct MockSpan {
|
||||
pub(in support) metadata: metadata::Expect,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Eq, PartialEq)]
|
||||
pub struct NewSpan {
|
||||
pub(in support) span: MockSpan,
|
||||
pub(in support) fields: field::Expect,
|
||||
}
|
||||
|
||||
pub fn mock() -> MockSpan {
|
||||
MockSpan {
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl MockSpan {
|
||||
pub fn named<I>(self, name: I) -> Self
|
||||
where
|
||||
I: Into<String>,
|
||||
{
|
||||
Self {
|
||||
metadata: metadata::Expect {
|
||||
name: Some(name.into()),
|
||||
..self.metadata
|
||||
},
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn at_level(self, level: tokio_trace::Level) -> Self {
|
||||
Self {
|
||||
metadata: metadata::Expect {
|
||||
level: Some(level),
|
||||
..self.metadata
|
||||
},
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_target<I>(self, target: I) -> Self
|
||||
where
|
||||
I: Into<String>,
|
||||
{
|
||||
Self {
|
||||
metadata: metadata::Expect {
|
||||
target: Some(target.into()),
|
||||
..self.metadata
|
||||
},
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn name(&self) -> Option<&str> {
|
||||
self.metadata.name.as_ref().map(String::as_ref)
|
||||
}
|
||||
|
||||
pub fn with_field<I>(self, fields: I) -> NewSpan
|
||||
where
|
||||
I: Into<field::Expect>,
|
||||
{
|
||||
NewSpan {
|
||||
span: self,
|
||||
fields: fields.into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(in support) fn check_metadata(&self, actual: &tokio_trace::Metadata) {
|
||||
self.metadata.check(actual, format_args!("span {}", self))
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for MockSpan {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
if self.metadata.name.is_some() {
|
||||
write!(f, "a span{}", self.metadata)
|
||||
} else {
|
||||
write!(f, "any span{}", self.metadata)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<NewSpan> for MockSpan {
|
||||
fn into(self) -> NewSpan {
|
||||
NewSpan {
|
||||
span: self,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for NewSpan {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "a new span{}", self.span.metadata)?;
|
||||
if !self.fields.is_empty() {
|
||||
write!(f, " with {}", self.fields)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,337 @@
|
||||
#![allow(missing_docs)]
|
||||
use super::{
|
||||
event::MockEvent,
|
||||
field as mock_field,
|
||||
span::{MockSpan, NewSpan},
|
||||
};
|
||||
use std::{
|
||||
collections::{HashMap, VecDeque},
|
||||
fmt,
|
||||
sync::{
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
Arc, Mutex,
|
||||
},
|
||||
};
|
||||
use tokio_trace::{field, Event, Id, Metadata, Subscriber};
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
enum Expect {
|
||||
Event(MockEvent),
|
||||
Enter(MockSpan),
|
||||
Exit(MockSpan),
|
||||
CloneSpan(MockSpan),
|
||||
DropSpan(MockSpan),
|
||||
Record(MockSpan, mock_field::Expect),
|
||||
NewSpan(NewSpan),
|
||||
Nothing,
|
||||
}
|
||||
|
||||
struct SpanState {
|
||||
name: &'static str,
|
||||
refs: usize,
|
||||
}
|
||||
|
||||
struct Running<F: Fn(&Metadata) -> bool> {
|
||||
spans: Mutex<HashMap<Id, SpanState>>,
|
||||
expected: Arc<Mutex<VecDeque<Expect>>>,
|
||||
ids: AtomicUsize,
|
||||
filter: F,
|
||||
}
|
||||
|
||||
pub struct MockSubscriber<F: Fn(&Metadata) -> bool> {
|
||||
expected: VecDeque<Expect>,
|
||||
filter: F,
|
||||
}
|
||||
|
||||
pub struct MockHandle(Arc<Mutex<VecDeque<Expect>>>);
|
||||
|
||||
pub fn mock() -> MockSubscriber<fn(&Metadata) -> bool> {
|
||||
MockSubscriber {
|
||||
expected: VecDeque::new(),
|
||||
filter: (|_: &Metadata| true) as for<'r, 's> fn(&'r Metadata<'s>) -> _,
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Fn(&Metadata) -> bool> MockSubscriber<F> {
|
||||
pub fn enter(mut self, span: MockSpan) -> Self {
|
||||
self.expected.push_back(Expect::Enter(span));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn event(mut self, event: MockEvent) -> Self {
|
||||
self.expected.push_back(Expect::Event(event));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn exit(mut self, span: MockSpan) -> Self {
|
||||
self.expected.push_back(Expect::Exit(span));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn clone_span(mut self, span: MockSpan) -> Self {
|
||||
self.expected.push_back(Expect::CloneSpan(span));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn drop_span(mut self, span: MockSpan) -> Self {
|
||||
self.expected.push_back(Expect::DropSpan(span));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn done(mut self) -> Self {
|
||||
self.expected.push_back(Expect::Nothing);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn record<I>(mut self, span: MockSpan, fields: I) -> Self
|
||||
where
|
||||
I: Into<mock_field::Expect>,
|
||||
{
|
||||
self.expected.push_back(Expect::Record(span, fields.into()));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn new_span<I>(mut self, new_span: I) -> Self
|
||||
where
|
||||
I: Into<NewSpan>,
|
||||
{
|
||||
self.expected.push_back(Expect::NewSpan(new_span.into()));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_filter<G>(self, filter: G) -> MockSubscriber<G>
|
||||
where
|
||||
G: Fn(&Metadata) -> bool,
|
||||
{
|
||||
MockSubscriber {
|
||||
filter,
|
||||
expected: self.expected,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(self) -> impl Subscriber {
|
||||
let (subscriber, _) = self.run_with_handle();
|
||||
subscriber
|
||||
}
|
||||
|
||||
pub fn run_with_handle(self) -> (impl Subscriber, MockHandle) {
|
||||
let expected = Arc::new(Mutex::new(self.expected));
|
||||
let handle = MockHandle(expected.clone());
|
||||
let subscriber = Running {
|
||||
spans: Mutex::new(HashMap::new()),
|
||||
expected,
|
||||
ids: AtomicUsize::new(0),
|
||||
filter: self.filter,
|
||||
};
|
||||
(subscriber, handle)
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Fn(&Metadata) -> bool> Subscriber for Running<F> {
|
||||
fn enabled(&self, meta: &Metadata) -> bool {
|
||||
(self.filter)(meta)
|
||||
}
|
||||
|
||||
fn record(&self, id: &Id, values: &field::ValueSet) {
|
||||
let spans = self.spans.lock().unwrap();
|
||||
let mut expected = self.expected.lock().unwrap();
|
||||
let span = spans
|
||||
.get(id)
|
||||
.unwrap_or_else(|| panic!("no span for ID {:?}", id));
|
||||
println!("record: {}; id={:?}; values={:?};", span.name, id, values);
|
||||
let was_expected = if let Some(Expect::Record(_, _)) = expected.front() {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
};
|
||||
if was_expected {
|
||||
if let Expect::Record(expected_span, mut expected_values) =
|
||||
expected.pop_front().unwrap()
|
||||
{
|
||||
if let Some(name) = expected_span.name() {
|
||||
assert_eq!(name, span.name);
|
||||
}
|
||||
let mut checker = expected_values.checker(format!("span {}: ", span.name));
|
||||
values.record(&mut checker);
|
||||
checker.finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn event(&self, event: &Event) {
|
||||
let name = event.metadata().name();
|
||||
println!("event: {};", name);
|
||||
match self.expected.lock().unwrap().pop_front() {
|
||||
None => {}
|
||||
Some(Expect::Event(expected)) => expected.check(event),
|
||||
Some(ex) => ex.bad(format_args!("observed event {:?}", event)),
|
||||
}
|
||||
}
|
||||
|
||||
fn record_follows_from(&self, _span: &Id, _follows: &Id) {
|
||||
// TODO: it should be possible to expect spans to follow from other spans
|
||||
}
|
||||
|
||||
fn new_span(&self, meta: &Metadata, values: &field::ValueSet) -> Id {
|
||||
let id = self.ids.fetch_add(1, Ordering::SeqCst);
|
||||
let id = Id::from_u64(id as u64);
|
||||
println!(
|
||||
"new_span: name={:?}; target={:?}; id={:?};",
|
||||
meta.name(),
|
||||
meta.target(),
|
||||
id
|
||||
);
|
||||
let mut expected = self.expected.lock().unwrap();
|
||||
let was_expected = match expected.front() {
|
||||
Some(Expect::NewSpan(_)) => true,
|
||||
_ => false,
|
||||
};
|
||||
if was_expected {
|
||||
if let Expect::NewSpan(mut expected) = expected.pop_front().unwrap() {
|
||||
let name = meta.name();
|
||||
expected
|
||||
.span
|
||||
.metadata
|
||||
.check(meta, format_args!("span `{}`", name));
|
||||
let mut checker = expected.fields.checker(format!("{}", name));
|
||||
values.record(&mut checker);
|
||||
checker.finish();
|
||||
}
|
||||
}
|
||||
self.spans.lock().unwrap().insert(
|
||||
id.clone(),
|
||||
SpanState {
|
||||
name: meta.name(),
|
||||
refs: 1,
|
||||
},
|
||||
);
|
||||
id
|
||||
}
|
||||
|
||||
fn enter(&self, id: &Id) {
|
||||
let spans = self.spans.lock().unwrap();
|
||||
if let Some(span) = spans.get(id) {
|
||||
println!("enter: {}; id={:?};", span.name, id);
|
||||
match self.expected.lock().unwrap().pop_front() {
|
||||
None => {}
|
||||
Some(Expect::Enter(ref expected_span)) => {
|
||||
if let Some(name) = expected_span.name() {
|
||||
assert_eq!(name, span.name);
|
||||
}
|
||||
}
|
||||
Some(ex) => ex.bad(format_args!("entered span {:?}", span.name)),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn exit(&self, id: &Id) {
|
||||
let spans = self.spans.lock().unwrap();
|
||||
let span = spans
|
||||
.get(id)
|
||||
.unwrap_or_else(|| panic!("no span for ID {:?}", id));
|
||||
println!("exit: {}; id={:?};", span.name, id);
|
||||
match self.expected.lock().unwrap().pop_front() {
|
||||
None => {}
|
||||
Some(Expect::Exit(ref expected_span)) => {
|
||||
if let Some(name) = expected_span.name() {
|
||||
assert_eq!(name, span.name);
|
||||
}
|
||||
}
|
||||
Some(ex) => ex.bad(format_args!("exited span {:?}", span.name)),
|
||||
};
|
||||
}
|
||||
|
||||
fn clone_span(&self, id: &Id) -> Id {
|
||||
let name = self.spans.lock().unwrap().get_mut(id).map(|span| {
|
||||
let name = span.name;
|
||||
println!("clone_span: {}; id={:?}; refs={:?};", name, id, span.refs);
|
||||
span.refs += 1;
|
||||
name
|
||||
});
|
||||
if name.is_none() {
|
||||
println!("clone_span: id={:?};", id);
|
||||
}
|
||||
let mut expected = self.expected.lock().unwrap();
|
||||
let was_expected = if let Some(Expect::CloneSpan(ref span)) = expected.front() {
|
||||
assert_eq!(name, span.name());
|
||||
true
|
||||
} else {
|
||||
false
|
||||
};
|
||||
if was_expected {
|
||||
expected.pop_front();
|
||||
}
|
||||
id.clone()
|
||||
}
|
||||
|
||||
fn drop_span(&self, id: Id) {
|
||||
let mut is_event = false;
|
||||
let name = if let Ok(mut spans) = self.spans.try_lock() {
|
||||
spans.get_mut(&id).map(|span| {
|
||||
let name = span.name;
|
||||
if name.contains("event") {
|
||||
is_event = true;
|
||||
}
|
||||
println!("drop_span: {}; id={:?}; refs={:?};", name, id, span.refs);
|
||||
span.refs -= 1;
|
||||
name
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if name.is_none() {
|
||||
println!("drop_span: id={:?}", id);
|
||||
}
|
||||
if let Ok(mut expected) = self.expected.try_lock() {
|
||||
let was_expected = match expected.front() {
|
||||
Some(Expect::DropSpan(ref span)) => {
|
||||
// Don't assert if this function was called while panicking,
|
||||
// as failing the assertion can cause a double panic.
|
||||
if !::std::thread::panicking() {
|
||||
assert_eq!(name, span.name());
|
||||
}
|
||||
true
|
||||
}
|
||||
Some(Expect::Event(_)) => {
|
||||
if !::std::thread::panicking() {
|
||||
assert!(is_event);
|
||||
}
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
if was_expected {
|
||||
expected.pop_front();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MockHandle {
|
||||
pub fn assert_finished(&self) {
|
||||
if let Ok(ref expected) = self.0.lock() {
|
||||
assert!(
|
||||
!expected.iter().any(|thing| thing != &Expect::Nothing),
|
||||
"more notifications expected: {:?}",
|
||||
**expected
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Expect {
|
||||
fn bad<'a>(&self, what: fmt::Arguments<'a>) {
|
||||
match self {
|
||||
Expect::Event(e) => panic!("expected event {}, but {} instead", e, what,),
|
||||
Expect::Enter(e) => panic!("expected to enter {} but {} instead", e, what,),
|
||||
Expect::Exit(e) => panic!("expected to exit {} but {} instead", e, what,),
|
||||
Expect::CloneSpan(e) => panic!("expected to clone {} but {} instead", e, what,),
|
||||
Expect::DropSpan(e) => panic!("expected to drop {} but {} instead", e, what,),
|
||||
Expect::Record(e, fields) => {
|
||||
panic!("expected {} to record {} but {} instead", e, fields, what,)
|
||||
}
|
||||
Expect::NewSpan(e) => panic!("expected {} but {} instead", e, what),
|
||||
Expect::Nothing => panic!("expected nothing else to happen, but {} instead", what,),
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user