Rollup merge of #128161 - EtomicBomb:just-compiletest, r=notriddle

nested aux-build in tests/rustdoc/ tests

* Fixes bug that prevented using nested aux-build in `tests/rustdoc/` tests. Before, `fn document` and the auxiliary builder disagreed about where to find the nested aux-build source file (`auxiliary/auxiliary/aux.rs` vs `auxiliary/aux.rs`), preventing them from building. Picked the latter in line with other builders in compiletest.
* Adds `//@ doc-flags` header, which forwards flags to rustdoc and not rustc.
* Adds `//@ unique-doc-out-dir` header, which sets the --out-dir for the rustdoc invocation to a unique directory: `<root out dir>/docs/<test name>/doc`
* Changes working directory of the rustdoc invocation to the root out directory (common among all aux-builds). Prior art: exec_compiled_test in runtest.rs
* Adds tests that use nested aux builds and new headers

These changes provide useful capabilities for writing rustdoc tests on their own. They are also needed to test the implementation for the [mergable-rustdoc-cross-crate-info](https://github.com/rust-lang/rfcs/pull/3662) RFC.

try-job: x86_64-msvc
This commit is contained in:
Matthias Krüger 2024-08-03 11:17:42 +02:00 committed by GitHub
commit 47a795bbd3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
31 changed files with 318 additions and 35 deletions

View File

@ -18,6 +18,7 @@
"check-test-line-numbers-match",
"compare-output-lines-by-subset",
"compile-flags",
"doc-flags",
"dont-check-compiler-stderr",
"dont-check-compiler-stdout",
"dont-check-failure-status",
@ -226,6 +227,7 @@
"should-ice",
"stderr-per-bitwidth",
"test-mir-pass",
"unique-doc-out-dir",
"unset-exec-env",
"unset-rustc-env",
// Used by the tidy check `unknown_revision`.

View File

@ -95,6 +95,8 @@ pub struct TestProps {
pub compile_flags: Vec<String>,
// Extra flags to pass when the compiled code is run (such as --bench)
pub run_flags: Vec<String>,
/// Extra flags to pass to rustdoc but not the compiler.
pub doc_flags: Vec<String>,
// If present, the name of a file that this test should match when
// pretty-printed
pub pp_exact: Option<PathBuf>,
@ -122,6 +124,9 @@ pub struct TestProps {
pub unset_exec_env: Vec<String>,
// Build documentation for all specified aux-builds as well
pub build_aux_docs: bool,
/// Build the documentation for each crate in a unique output directory.
/// Uses <root output directory>/docs/<test name>/doc
pub unique_doc_out_dir: bool,
// Flag to force a crate to be built with the host architecture
pub force_host: bool,
// Check stdout for error-pattern output as well as stderr
@ -220,8 +225,10 @@ mod directives {
pub const REGEX_ERROR_PATTERN: &'static str = "regex-error-pattern";
pub const COMPILE_FLAGS: &'static str = "compile-flags";
pub const RUN_FLAGS: &'static str = "run-flags";
pub const DOC_FLAGS: &'static str = "doc-flags";
pub const SHOULD_ICE: &'static str = "should-ice";
pub const BUILD_AUX_DOCS: &'static str = "build-aux-docs";
pub const UNIQUE_DOC_OUT_DIR: &'static str = "unique-doc-out-dir";
pub const FORCE_HOST: &'static str = "force-host";
pub const CHECK_STDOUT: &'static str = "check-stdout";
pub const CHECK_RUN_RESULTS: &'static str = "check-run-results";
@ -267,6 +274,7 @@ pub fn new() -> Self {
regex_error_patterns: vec![],
compile_flags: vec![],
run_flags: vec![],
doc_flags: vec![],
pp_exact: None,
aux_builds: vec![],
aux_bins: vec![],
@ -281,6 +289,7 @@ pub fn new() -> Self {
exec_env: vec![],
unset_exec_env: vec![],
build_aux_docs: false,
unique_doc_out_dir: false,
force_host: false,
check_stdout: false,
check_run_results: false,
@ -378,6 +387,8 @@ fn load_from(&mut self, testfile: &Path, test_revision: Option<&str>, config: &C
|r| r,
);
config.push_name_value_directive(ln, DOC_FLAGS, &mut self.doc_flags, |r| r);
fn split_flags(flags: &str) -> Vec<String> {
// Individual flags can be single-quoted to preserve spaces; see
// <https://github.com/rust-lang/rust/pull/115948/commits/957c5db6>.
@ -415,6 +426,8 @@ fn split_flags(flags: &str) -> Vec<String> {
config.set_name_directive(ln, SHOULD_ICE, &mut self.should_ice);
config.set_name_directive(ln, BUILD_AUX_DOCS, &mut self.build_aux_docs);
config.set_name_directive(ln, UNIQUE_DOC_OUT_DIR, &mut self.unique_doc_out_dir);
config.set_name_directive(ln, FORCE_HOST, &mut self.force_host);
config.set_name_directive(ln, CHECK_STDOUT, &mut self.check_stdout);
config.set_name_directive(ln, CHECK_RUN_RESULTS, &mut self.check_run_results);

View File

@ -1,5 +1,6 @@
// ignore-tidy-filelength
use std::borrow::Cow;
use std::collections::{HashMap, HashSet};
use std::ffi::{OsStr, OsString};
use std::fs::{self, create_dir_all, File, OpenOptions};
@ -723,7 +724,7 @@ fn typecheck_source(&self, src: String) -> ProcRes {
self.maybe_add_external_args(&mut rustc, &self.config.target_rustcflags);
rustc.args(&self.props.compile_flags);
self.compose_and_run_compiler(rustc, Some(src))
self.compose_and_run_compiler(rustc, Some(src), self.testpaths)
}
fn run_debuginfo_test(&self) {
@ -1579,13 +1580,15 @@ fn compile_test_general(
passes,
);
self.compose_and_run_compiler(rustc, None)
self.compose_and_run_compiler(rustc, None, self.testpaths)
}
fn document(&self, out_dir: &Path) -> ProcRes {
/// `root_out_dir` and `root_testpaths` refer to the parameters of the actual test being run.
/// Auxiliaries, no matter how deep, have the same root_out_dir and root_testpaths.
fn document(&self, root_out_dir: &Path, root_testpaths: &TestPaths) -> ProcRes {
if self.props.build_aux_docs {
for rel_ab in &self.props.aux_builds {
let aux_testpaths = self.compute_aux_test_paths(&self.testpaths, rel_ab);
let aux_testpaths = self.compute_aux_test_paths(root_testpaths, rel_ab);
let aux_props =
self.props.from_aux_file(&aux_testpaths.file, self.revision, self.config);
let aux_cx = TestCx {
@ -1596,7 +1599,9 @@ fn document(&self, out_dir: &Path) -> ProcRes {
};
// Create the directory for the stdout/stderr files.
create_dir_all(aux_cx.output_base_dir()).unwrap();
let auxres = aux_cx.document(out_dir);
// use root_testpaths here, because aux-builds should have the
// same --out-dir and auxiliary directory.
let auxres = aux_cx.document(&root_out_dir, root_testpaths);
if !auxres.status.success() {
return auxres;
}
@ -1606,21 +1611,40 @@ fn document(&self, out_dir: &Path) -> ProcRes {
let aux_dir = self.aux_output_dir_name();
let rustdoc_path = self.config.rustdoc_path.as_ref().expect("--rustdoc-path not passed");
let mut rustdoc = Command::new(rustdoc_path);
// actual --out-dir given to the auxiliary or test, as opposed to the root out dir for the entire
// test
let out_dir: Cow<'_, Path> = if self.props.unique_doc_out_dir {
let file_name = self.testpaths.file.file_stem().expect("file name should not be empty");
let out_dir = PathBuf::from_iter([
root_out_dir,
Path::new("docs"),
Path::new(file_name),
Path::new("doc"),
]);
create_dir_all(&out_dir).unwrap();
Cow::Owned(out_dir)
} else {
Cow::Borrowed(root_out_dir)
};
let mut rustdoc = Command::new(rustdoc_path);
let current_dir = output_base_dir(self.config, root_testpaths, self.safe_revision());
rustdoc.current_dir(current_dir);
rustdoc
.arg("-L")
.arg(self.config.run_lib_path.to_str().unwrap())
.arg("-L")
.arg(aux_dir)
.arg("-o")
.arg(out_dir)
.arg(out_dir.as_ref())
.arg("--deny")
.arg("warnings")
.arg(&self.testpaths.file)
.arg("-A")
.arg("internal_features")
.args(&self.props.compile_flags);
.args(&self.props.compile_flags)
.args(&self.props.doc_flags);
if self.config.mode == RustdocJson {
rustdoc.arg("--output-format").arg("json").arg("-Zunstable-options");
@ -1630,7 +1654,7 @@ fn document(&self, out_dir: &Path) -> ProcRes {
rustdoc.arg(format!("-Clinker={}", linker));
}
self.compose_and_run_compiler(rustdoc, None)
self.compose_and_run_compiler(rustdoc, None, root_testpaths)
}
fn exec_compiled_test(&self) -> ProcRes {
@ -1828,9 +1852,16 @@ fn build_all_auxiliary(&self, of: &TestPaths, aux_dir: &Path, rustc: &mut Comman
}
}
fn compose_and_run_compiler(&self, mut rustc: Command, input: Option<String>) -> ProcRes {
/// `root_testpaths` refers to the path of the original test.
/// the auxiliary and the test with an aux-build have the same `root_testpaths`.
fn compose_and_run_compiler(
&self,
mut rustc: Command,
input: Option<String>,
root_testpaths: &TestPaths,
) -> ProcRes {
let aux_dir = self.aux_output_dir();
self.build_all_auxiliary(&self.testpaths, &aux_dir, &mut rustc);
self.build_all_auxiliary(root_testpaths, &aux_dir, &mut rustc);
rustc.envs(self.props.rustc_env.clone());
self.props.unset_rustc_env.iter().fold(&mut rustc, Command::env_remove);
@ -2545,7 +2576,7 @@ fn compile_test_and_save_ir(&self) -> (ProcRes, PathBuf) {
Vec::new(),
);
let proc_res = self.compose_and_run_compiler(rustc, None);
let proc_res = self.compose_and_run_compiler(rustc, None, self.testpaths);
let output_path = self.get_filecheck_file("ll");
(proc_res, output_path)
}
@ -2581,7 +2612,7 @@ fn compile_test_and_save_assembly(&self) -> (ProcRes, PathBuf) {
Vec::new(),
);
let proc_res = self.compose_and_run_compiler(rustc, None);
let proc_res = self.compose_and_run_compiler(rustc, None, self.testpaths);
let output_path = self.get_filecheck_file("s");
(proc_res, output_path)
}
@ -2664,7 +2695,7 @@ fn run_rustdoc_test(&self) {
let out_dir = self.output_base_dir();
remove_and_create_dir_all(&out_dir);
let proc_res = self.document(&out_dir);
let proc_res = self.document(&out_dir, &self.testpaths);
if !proc_res.status.success() {
self.fatal_proc_rec("rustdoc failed!", &proc_res);
}
@ -2723,7 +2754,7 @@ fn compare_to_default_rustdoc(&mut self, out_dir: &Path) {
let aux_dir = new_rustdoc.aux_output_dir();
new_rustdoc.build_all_auxiliary(&new_rustdoc.testpaths, &aux_dir, &mut rustc);
let proc_res = new_rustdoc.document(&compare_dir);
let proc_res = new_rustdoc.document(&compare_dir, &new_rustdoc.testpaths);
if !proc_res.status.success() {
eprintln!("failed to run nightly rustdoc");
return;
@ -2847,7 +2878,7 @@ fn run_rustdoc_json_test(&self) {
let out_dir = self.output_base_dir();
remove_and_create_dir_all(&out_dir);
let proc_res = self.document(&out_dir);
let proc_res = self.document(&out_dir, &self.testpaths);
if !proc_res.status.success() {
self.fatal_proc_rec("rustdoc failed!", &proc_res);
}
@ -2923,31 +2954,24 @@ fn get_lines<P: AsRef<Path>>(
fn check_rustdoc_test_option(&self, res: ProcRes) {
let mut other_files = Vec::new();
let mut files: HashMap<String, Vec<usize>> = HashMap::new();
let cwd = env::current_dir().unwrap();
files.insert(
self.testpaths
.file
.strip_prefix(&cwd)
.unwrap_or(&self.testpaths.file)
.to_str()
.unwrap()
.replace('\\', "/"),
self.get_lines(&self.testpaths.file, Some(&mut other_files)),
);
let normalized = fs::canonicalize(&self.testpaths.file).expect("failed to canonicalize");
let normalized = normalized.to_str().unwrap().replace('\\', "/");
files.insert(normalized, self.get_lines(&self.testpaths.file, Some(&mut other_files)));
for other_file in other_files {
let mut path = self.testpaths.file.clone();
path.set_file_name(&format!("{}.rs", other_file));
files.insert(
path.strip_prefix(&cwd).unwrap_or(&path).to_str().unwrap().replace('\\', "/"),
self.get_lines(&path, None),
);
let path = fs::canonicalize(path).expect("failed to canonicalize");
let normalized = path.to_str().unwrap().replace('\\', "/");
files.insert(normalized, self.get_lines(&path, None));
}
let mut tested = 0;
for _ in res.stdout.split('\n').filter(|s| s.starts_with("test ")).inspect(|s| {
if let Some((left, right)) = s.split_once(" - ") {
let path = left.rsplit("test ").next().unwrap();
if let Some(ref mut v) = files.get_mut(&path.replace('\\', "/")) {
let path = fs::canonicalize(&path).expect("failed to canonicalize");
let path = path.to_str().unwrap().replace('\\', "/");
if let Some(ref mut v) = files.get_mut(&path) {
tested += 1;
let mut iter = right.split("(line ");
iter.next();
@ -3779,7 +3803,7 @@ fn run_js_doc_test(&self) {
if let Some(nodejs) = &self.config.nodejs {
let out_dir = self.output_base_dir();
self.document(&out_dir);
self.document(&out_dir, &self.testpaths);
let root = self.config.find_rust_src_root().unwrap();
let file_stem =
@ -4095,7 +4119,7 @@ fn run_ui_test(&self) {
rustc.arg(crate_name);
}
let res = self.compose_and_run_compiler(rustc, None);
let res = self.compose_and_run_compiler(rustc, None, self.testpaths);
if !res.status.success() {
self.fatal_proc_rec("failed to compile fixed code", &res);
}

View File

@ -191,7 +191,7 @@ fn run_doctests_for_coverage(
rustdoc_cmd.arg(&self.testpaths.file);
let proc_res = self.compose_and_run_compiler(rustdoc_cmd, None);
let proc_res = self.compose_and_run_compiler(rustdoc_cmd, None, self.testpaths);
if !proc_res.status.success() {
self.fatal_proc_rec("rustdoc --test failed!", &proc_res)
}

View File

@ -0,0 +1,2 @@
//@ build-aux-docs
pub struct Quebec;

View File

@ -0,0 +1,4 @@
//@ aux-build:q.rs
//@ build-aux-docs
extern crate q;
pub trait Tango {}

View File

@ -0,0 +1,16 @@
//@ aux-build:t.rs
//@ build-aux-docs
//@ has q/struct.Quebec.html
//@ has s/struct.Sierra.html
//@ has t/trait.Tango.html
//@ hasraw s/struct.Sierra.html 'Tango'
//@ hasraw trait.impl/t/trait.Tango.js 'struct.Sierra.html'
//@ hasraw search-index.js 'Tango'
//@ hasraw search-index.js 'Sierra'
//@ hasraw search-index.js 'Quebec'
// We document multiple crates into the same output directory, which
// merges the cross-crate information. Everything is available.
extern crate t;
pub struct Sierra;
impl t::Tango for Sierra {}

View File

@ -0,0 +1,5 @@
//@ build-aux-docs
//@ doc-flags:--enable-index-page
//@ doc-flags:-Zunstable-options
pub struct Quebec;

View File

@ -0,0 +1,7 @@
//@ aux-build:q.rs
//@ build-aux-docs
//@ doc-flags:--enable-index-page
//@ doc-flags:-Zunstable-options
extern crate q;
pub trait Tango {}

View File

@ -0,0 +1,24 @@
//@ aux-build:t.rs
//@ build-aux-docs
//@ doc-flags:--enable-index-page
//@ doc-flags:-Zunstable-options
//@ has index.html
//@ has index.html '//h1' 'List of all crates'
//@ has index.html '//ul[@class="all-items"]//a[@href="q/index.html"]' 'q'
//@ has index.html '//ul[@class="all-items"]//a[@href="s/index.html"]' 's'
//@ has index.html '//ul[@class="all-items"]//a[@href="t/index.html"]' 't'
//@ has q/struct.Quebec.html
//@ has s/struct.Sierra.html
//@ has t/trait.Tango.html
//@ hasraw s/struct.Sierra.html 'Tango'
//@ hasraw trait.impl/t/trait.Tango.js 'struct.Sierra.html'
//@ hasraw search-index.js 'Tango'
//@ hasraw search-index.js 'Sierra'
//@ hasraw search-index.js 'Quebec'
// We document multiple crates into the same output directory, which
// merges the cross-crate information. Everything is available.
extern crate t;
pub struct Sierra;
impl t::Tango for Sierra {}

View File

@ -0,0 +1,2 @@
//@ build-aux-docs
pub trait Foxtrot {}

View File

@ -0,0 +1,14 @@
//@ aux-build:f.rs
//@ build-aux-docs
//@ has e/enum.Echo.html
//@ has f/trait.Foxtrot.html
//@ hasraw e/enum.Echo.html 'Foxtrot'
//@ hasraw trait.impl/f/trait.Foxtrot.js 'enum.Echo.html'
//@ hasraw search-index.js 'Foxtrot'
//@ hasraw search-index.js 'Echo'
// document two crates in the same way that cargo does. do not provide
// --enable-index-page
extern crate f;
pub enum Echo {}
impl f::Foxtrot for Echo {}

View File

@ -0,0 +1,5 @@
//@ build-aux-docs
//@ doc-flags:--enable-index-page
//@ doc-flags:-Zunstable-options
pub trait Foxtrot {}

View File

@ -0,0 +1,21 @@
//@ aux-build:f.rs
//@ build-aux-docs
//@ doc-flags:--enable-index-page
//@ doc-flags:-Zunstable-options
//@ has index.html
//@ has index.html '//h1' 'List of all crates'
//@ has index.html '//ul[@class="all-items"]//a[@href="f/index.html"]' 'f'
//@ has index.html '//ul[@class="all-items"]//a[@href="e/index.html"]' 'e'
//@ has e/enum.Echo.html
//@ has f/trait.Foxtrot.html
//@ hasraw e/enum.Echo.html 'Foxtrot'
//@ hasraw trait.impl/f/trait.Foxtrot.js 'enum.Echo.html'
//@ hasraw search-index.js 'Foxtrot'
//@ hasraw search-index.js 'Echo'
// document two crates in the same way that cargo does, writing them both
// into the same output directory
extern crate f;
pub enum Echo {}
impl f::Foxtrot for Echo {}

View File

@ -0,0 +1,2 @@
//@ build-aux-docs
pub trait Foxtrot {}

View File

@ -0,0 +1,20 @@
//@ aux-build:f.rs
//@ build-aux-docs
//@ doc-flags:--enable-index-page
//@ doc-flags:-Zunstable-options
//@ has index.html
//@ has index.html '//h1' 'List of all crates'
//@ has index.html '//ul[@class="all-items"]//a[@href="f/index.html"]' 'f'
//@ has index.html '//ul[@class="all-items"]//a[@href="e/index.html"]' 'e'
//@ has e/enum.Echo.html
//@ has f/trait.Foxtrot.html
//@ hasraw e/enum.Echo.html 'Foxtrot'
//@ hasraw trait.impl/f/trait.Foxtrot.js 'enum.Echo.html'
//@ hasraw search-index.js 'Foxtrot'
//@ hasraw search-index.js 'Echo'
// only declare --enable-index-page to the last rustdoc invocation
extern crate f;
pub enum Echo {}
impl f::Foxtrot for Echo {}

View File

@ -0,0 +1,5 @@
//@ build-aux-docs
//@ doc-flags:--enable-index-page
//@ doc-flags:-Zunstable-options
pub struct Quebec;

View File

@ -0,0 +1,7 @@
//@ aux-build:s.rs
//@ build-aux-docs
//@ doc-flags:--enable-index-page
//@ doc-flags:-Zunstable-options
extern crate s;
pub type Romeo = s::Sierra;

View File

@ -0,0 +1,8 @@
//@ aux-build:t.rs
//@ build-aux-docs
//@ doc-flags:--enable-index-page
//@ doc-flags:-Zunstable-options
extern crate t;
pub struct Sierra;
impl t::Tango for Sierra {}

View File

@ -0,0 +1,7 @@
//@ aux-build:q.rs
//@ build-aux-docs
//@ doc-flags:--enable-index-page
//@ doc-flags:-Zunstable-options
extern crate q;
pub trait Tango {}

View File

@ -0,0 +1,30 @@
//@ aux-build:r.rs
//@ aux-build:q.rs
//@ aux-build:t.rs
//@ aux-build:s.rs
//@ build-aux-docs
//@ doc-flags:--enable-index-page
//@ doc-flags:-Zunstable-options
//@ has index.html '//h1' 'List of all crates'
//@ has index.html
//@ has index.html '//ul[@class="all-items"]//a[@href="i/index.html"]' 'i'
//@ has index.html '//ul[@class="all-items"]//a[@href="q/index.html"]' 'q'
//@ has index.html '//ul[@class="all-items"]//a[@href="r/index.html"]' 'r'
//@ has index.html '//ul[@class="all-items"]//a[@href="s/index.html"]' 's'
//@ has index.html '//ul[@class="all-items"]//a[@href="t/index.html"]' 't'
//@ has q/struct.Quebec.html
//@ has r/type.Romeo.html
//@ has s/struct.Sierra.html
//@ has t/trait.Tango.html
//@ hasraw s/struct.Sierra.html 'Tango'
//@ hasraw trait.impl/t/trait.Tango.js 'struct.Sierra.html'
//@ hasraw search-index.js 'Quebec'
//@ hasraw search-index.js 'Romeo'
//@ hasraw search-index.js 'Sierra'
//@ hasraw search-index.js 'Tango'
//@ has type.impl/s/struct.Sierra.js
//@ hasraw type.impl/s/struct.Sierra.js 'Tango'
//@ hasraw type.impl/s/struct.Sierra.js 'Romeo'
// document everything in the default mode

View File

@ -0,0 +1,12 @@
//@ build-aux-docs
//@ doc-flags:--enable-index-page
//@ doc-flags:-Zunstable-options
//@ has index.html
//@ has index.html '//h1' 'List of all crates'
//@ has index.html '//ul[@class="all-items"]//a[@href="q/index.html"]' 'q'
//@ has q/struct.Quebec.html
//@ hasraw search-index.js 'Quebec'
// there's nothing cross-crate going on here
pub struct Quebec;

View File

@ -0,0 +1,6 @@
//@ build-aux-docs
//@ has q/struct.Quebec.html
//@ hasraw search-index.js 'Quebec'
// there's nothing cross-crate going on here
pub struct Quebec;

View File

@ -0,0 +1,2 @@
//@ build-aux-docs
pub struct Quebec;

View File

@ -0,0 +1,4 @@
//@ aux-build:q.rs
//@ build-aux-docs
extern crate q;
pub trait Tango {}

View File

@ -0,0 +1,6 @@
//@ aux-build:t.rs
//@ build-aux-docs
// simple test to see if we support building transitive crates
extern crate t;
pub struct Sierra;
impl t::Tango for Sierra {}

View File

@ -0,0 +1,2 @@
//@ build-aux-docs
pub trait Foxtrot {}

View File

@ -0,0 +1,6 @@
//@ aux-build:f.rs
//@ build-aux-docs
// simple test to assert that we can do a two-level aux-build
extern crate f;
pub enum Echo {}
impl f::Foxtrot for Echo {}

View File

@ -0,0 +1,10 @@
//@ build-aux-docs
//@ doc-flags:--scrape-examples-output-path=examples
//@ doc-flags:--scrape-examples-target-crate=q
//@ doc-flags:-Zunstable-options
//@ has examples
// where will --scrape-examples-output-path resolve the path to be?
// should be the root output directory
pub struct Quebec;

View File

@ -0,0 +1,3 @@
//@ build-aux-docs
//@ unique-doc-out-dir
pub trait Foxtrot {}

View File

@ -0,0 +1,14 @@
//@ aux-build:f.rs
//@ build-aux-docs
//@ has e/enum.Echo.html
//@ !has f/trait.Foxtrot.html
//@ hasraw e/enum.Echo.html 'Foxtrot'
//@ hasraw trait.impl/f/trait.Foxtrot.js 'enum.Echo.html'
//@ !hasraw search-index.js 'Foxtrot'
//@ hasraw search-index.js 'Echo'
// test the fact that our test runner will document this crate somewhere
// else
extern crate f;
pub enum Echo {}
impl f::Foxtrot for Echo {}