Expected Behavior:
Clicking "Run doctest" in vscode should execute one test from doctest snippets.
Terminal output SHOULD say ("1 passed;" or "1 failed;"), and "1 filtered out;".
Actual Behavior:
Clicking "Run doctest" in vscode executes 0 tests, and shows that 2 were filtered out.
Terminal output:
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 2 filtered out; finished in 0.00s
Source code:
My misbehaving crate: https://github.com/darrow-olykos/rusty-gists/blob/main/crates/my_cache/src/lib.rs
Behaving crate (where this is not an issue): https://github.com/darrow-olykos/rusty-gists/blob/main/crates/math/src/lib.rs
My machine:
macOS 12
rustc 1.57.0
rust analyzer v0.3.954
What I have done to try to narrow down the scope of the problem:
- Running the "same" command in the terminal demonstrates expected behavior. The terminal output shows
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 1 filtered out; finished in 0.40s
when I runcargo test --doc --package my_cache -- "Cacher<T>::new" --nocapture
, which is exactly what the terminal says is ran when I click on "Run Doctest". - Clicking "Run Doctest" in another crate I have, (called "math") in the same repo, demonstrates expected behavior.
Looking at the differences between my misbehaving crate and my working crate:
A. This misbehaving crate has it's doctest is inside of an impl
where-as that other crate's doctest is at the root level of the file.
B. This misbehaving crate's doctest is for a generic struct that accepts a closure
type.
C. Executing cargo test
from crates/my_cache
demonstrates expected behavior, with the following terminal output:
// ... some output omitted
Doc-tests my_cache
running 2 tests
test src/lib.rs - Cacher<T>::new (line 26) ... ok
test src/lib.rs - Cacher<T>::value (line 42) ... ok
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.44s
Again, here is the source code:
Misbehaving crate: https://github.com/darrow-olykos/rusty-gists/blob/main/crates/my_cache/src/lib.rs
Behaving crate: https://github.com/darrow-olykos/rusty-gists/blob/main/crates/math/src/lib.rs
Maybe? notable details:
- I have modeled my git repo after the structure of https://github.com/rust-analyzer/rust-analyzer, such that I'm using cargo
workspaces
andcrates/*
are members. I have a rootcargo.toml
which can specify local dependencies with something likemy_cache = {path = "crates/my_cache}
, however I cannot think of a reason why this would be a contributing factor because I've proven that mymath
crate can be in this structure without vscode getting confused and filtering out doctests in that crate unexpected.
My suspicions?:
- Something is happening that is causing the doctest to be filtered out when it should not be filtered out.
- Maybe the command that claims to be executing when I click
Run Doctest
isn't the ACTUAL command getting executed. - Maybe the (bug?) has something to do with the closure type. I forget where I read this, but I vaguely recall Rust closure types are "unnamed" in a way that makes referencing them strange. Unfortunately I cannot find the resource that I was reviewing that walked through this in detail. (It might have been a resource that was covering the Rust compiler and how the Rust compiler manifests data types in memory, but I do not recall details (maybe someone reading this will know what I'm referring to here)).
- Maybe the command that claims to be executing when I click
Here is the misbehaving crate's source code copied into this answer for the sake of longevity in case I make changes to my github repo:
// Credit to: https://doc.rust-lang.org/book/ch13-01-closures.html
// (I've modified their example to use a HashMap instead of a single value)
use std::collections::HashMap;
/// cacher for calculation with two u64's as input and u64 as output
/// can be generalized more
pub struct Cacher<T>
where
T: FnMut(u64, u64) -> u64,
{
calculation: T,
values: HashMap<String, u64>,
}
impl<T> Cacher<T>
where
T: FnMut(u64, u64) -> u64,
{
/// Returns a Cacher<T> which can cache results of calculations for the provided closure.
///
/// # Arguments
///
/// `T` - Closure that computes produces a value. Value is cached based on args. Cached value is returend on subsequent calls if args are the same.
///
/// # Examples
/// ```rust
/// use my_cache::Cacher;
/// let mut cacher = Cacher::new(|x,y|x+y);
/// ```
pub fn new(calculation: T) -> Self {
let values = HashMap::new();
Cacher {
calculation,
values,
}
}
/// Returns value of calculation `T`. Cached value is returned if unique `n`, `k` pair provided, otherwise calcuation runs and then value is cached.
///
/// # Examples
///
/// ```rust
/// use std::rc::Rc;
/// use std::cell::{RefCell, RefMut};
///
/// use my_cache::Cacher;
///
/// let mut count = Rc::new(RefCell::new(0));
/// let add = |x, y| {
/// let mut count_mut_ref = count.borrow_mut();
/// *count_mut_ref += 1; x + y
/// };
/// let mut cacher = Cacher::new(add);
///
/// assert_eq!(*count.borrow(), 0);
/// assert_eq!(cacher.value(2, 3), 5); // new calculation, count += 1
/// assert_eq!(*count.borrow(), 1);
/// assert_eq!(cacher.value(2, 3), 5); // repeat, use cache
/// assert_eq!(*count.borrow(), 1);
/// assert_eq!(cacher.value(2, 4), 6); // new calculation, count += 1
/// assert_eq!(*count.borrow(), 2);
/// ```
pub fn value(&mut self, n: u64, k: u64) -> u64 {
let key = n.to_string() + &k.to_string();
let cached_result = self.values.get(&key);
if let Some(value) = cached_result {
*value
} else {
let v = (self.calculation)(n, k);
self.values.insert(key, v);
v
}
}
}