blob: 67f1c989c30e7f4c8566b88a9a64e9dd4a83cc56 [file] [log] [blame]
<!DOCTYPE HTML>
<html lang="en" class="light sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>#[test] implementation - Rust Compiler Development Guide</title>
<!-- Custom HTML head -->
<meta name="description" content="A guide to developing the Rust compiler (rustc)">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff">
<link rel="icon" href="favicon.svg">
<link rel="shortcut icon" href="favicon.png">
<link rel="stylesheet" href="css/variables.css">
<link rel="stylesheet" href="css/general.css">
<link rel="stylesheet" href="css/chrome.css">
<link rel="stylesheet" href="css/print.css" media="print">
<!-- Fonts -->
<link rel="stylesheet" href="FontAwesome/css/font-awesome.css">
<link rel="stylesheet" href="fonts/fonts.css">
<!-- Highlight.js Stylesheets -->
<link rel="stylesheet" id="highlight-css" href="highlight.css">
<link rel="stylesheet" id="tomorrow-night-css" href="tomorrow-night.css">
<link rel="stylesheet" id="ayu-highlight-css" href="ayu-highlight.css">
<!-- Custom theme stylesheets -->
<!-- Provide site root and default themes to javascript -->
<script>
const path_to_root = "";
const default_light_theme = "light";
const default_dark_theme = "navy";
</script>
<!-- Start loading toc.js asap -->
<script src="toc.js"></script>
</head>
<body>
<div id="body-container">
<!-- Work around some values being stored in localStorage wrapped in quotes -->
<script>
try {
let theme = localStorage.getItem('mdbook-theme');
let sidebar = localStorage.getItem('mdbook-sidebar');
if (theme.startsWith('"') && theme.endsWith('"')) {
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
}
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
}
} catch (e) { }
</script>
<!-- Set the theme before any content is loaded, prevents flash -->
<script>
const default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? default_dark_theme : default_light_theme;
let theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
const html = document.documentElement;
html.classList.remove('light')
html.classList.add(theme);
html.classList.add("js");
</script>
<input type="checkbox" id="sidebar-toggle-anchor" class="hidden">
<!-- Hide / unhide sidebar before it is displayed -->
<script>
let sidebar = null;
const sidebar_toggle = document.getElementById("sidebar-toggle-anchor");
if (document.body.clientWidth >= 1080) {
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
sidebar = sidebar || 'visible';
} else {
sidebar = 'hidden';
}
sidebar_toggle.checked = sidebar === 'visible';
html.classList.remove('sidebar-visible');
html.classList.add("sidebar-" + sidebar);
</script>
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
<!-- populated by js -->
<mdbook-sidebar-scrollbox class="sidebar-scrollbox"></mdbook-sidebar-scrollbox>
<noscript>
<iframe class="sidebar-iframe-outer" src="toc.html"></iframe>
</noscript>
<div id="sidebar-resize-handle" class="sidebar-resize-handle">
<div class="sidebar-resize-indicator"></div>
</div>
</nav>
<div id="page-wrapper" class="page-wrapper">
<div class="page">
<div id="menu-bar-hover-placeholder"></div>
<div id="menu-bar" class="menu-bar sticky">
<div class="left-buttons">
<label id="sidebar-toggle" class="icon-button" for="sidebar-toggle-anchor" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
<i class="fa fa-bars"></i>
</label>
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
<i class="fa fa-paint-brush"></i>
</button>
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
<li role="none"><button role="menuitem" class="theme" id="default_theme">Auto</button></li>
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
</ul>
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
<i class="fa fa-search"></i>
</button>
</div>
<h1 class="menu-title">Rust Compiler Development Guide</h1>
<div class="right-buttons">
<a href="print.html" title="Print this book" aria-label="Print this book">
<i id="print-button" class="fa fa-print"></i>
</a>
<a href="https://github.com/rust-lang/rustc-dev-guide" title="Git repository" aria-label="Git repository">
<i id="git-repository-button" class="fa fa-github"></i>
</a>
<a href="https://github.com/rust-lang/rustc-dev-guide/edit/master/src/test-implementation.md" title="Suggest an edit" aria-label="Suggest an edit">
<i id="git-edit-button" class="fa fa-edit"></i>
</a>
</div>
</div>
<div id="search-wrapper" class="hidden">
<form id="searchbar-outer" class="searchbar-outer">
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
</form>
<div id="searchresults-outer" class="searchresults-outer hidden">
<div id="searchresults-header" class="searchresults-header"></div>
<ul id="searchresults">
</ul>
</div>
</div>
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
<script>
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
});
</script>
<div id="content" class="content">
<main>
<h1 id="the-test-attribute"><a class="header" href="#the-test-attribute">The <code>#[test]</code> attribute</a></h1>
<ul>
<li><a href="#step-1-re-exporting">Step 1: Re-Exporting</a></li>
<li><a href="#step-2-harness-generation">Step 2: Harness generation</a></li>
<li><a href="#step-3-test-object-generation">Step 3: Test object generation</a></li>
<li><a href="#inspecting-the-generated-code">Inspecting the generated code</a></li>
</ul>
<p>Many Rust programmers rely on a built-in attribute called <code>#[test]</code>. All
you have to do is mark a function and include some asserts like so:</p>
<pre><code class="language-rust ignore">#[test]
fn my_test() {
assert!(2+2 == 4);
}</code></pre>
<p>When this program is compiled using <code>rustc --test</code> or <code>cargo test</code>, it will
produce an executable that can run this, and any other test function. This
method of testing allows tests to live alongside code in an organic way. You
can even put tests inside private modules:</p>
<pre><code class="language-rust ignore">mod my_priv_mod {
fn my_priv_func() -&gt; bool {}
#[test]
fn test_priv_func() {
assert!(my_priv_func());
}
}</code></pre>
<p>Private items can thus be easily tested without worrying about how to expose
them to any sort of external testing apparatus. This is key to the
ergonomics of testing in Rust. Semantically, however, it's rather odd.
How does any sort of <code>main</code> function invoke these tests if they're not visible?
What exactly is <code>rustc --test</code> doing?</p>
<p><code>#[test]</code> is implemented as a syntactic transformation inside the compiler's
<a href="https://github.com/rust-lang/rust/tree/master/compiler/rustc_ast"><code>rustc_ast</code></a>. Essentially, it's a fancy <a href="./macro-expansion.html"><code>macro</code></a> that
rewrites the crate in 3 steps:</p>
<h2 id="step-1-re-exporting"><a class="header" href="#step-1-re-exporting">Step 1: Re-Exporting</a></h2>
<p>As mentioned earlier, tests can exist inside private modules, so we need a
way of exposing them to the main function, without breaking any existing
code. To that end, <a href="https://github.com/rust-lang/rust/tree/master/compiler/rustc_ast"><code>rustc_ast</code></a> will create local modules called
<code>__test_reexports</code> that recursively reexport tests. This expansion translates
the above example into:</p>
<pre><code class="language-rust ignore">mod my_priv_mod {
fn my_priv_func() -&gt; bool {}
pub fn test_priv_func() {
assert!(my_priv_func());
}
pub mod __test_reexports {
pub use super::test_priv_func;
}
}</code></pre>
<p>Now, our test can be accessed as
<code>my_priv_mod::__test_reexports::test_priv_func</code>. For deeper module
structures, <code>__test_reexports</code> will reexport modules that contain tests, so a
test at <code>a::b::my_test</code> becomes
<code>a::__test_reexports::b::__test_reexports::my_test</code>. While this process seems
pretty safe, what happens if there is an existing <code>__test_reexports</code> module?
The answer: nothing.</p>
<p>To explain, we need to understand how Rust's <a href="./ast-validation.html">Abstract Syntax Tree</a>
represents <a href="https://doc.rust-lang.org/nightly/nightly-rustc/rustc_span/symbol/struct.Ident.html">identifiers</a>. The name of every function, variable, module,
etc. is not stored as a string, but rather as an opaque <a href="https://doc.rust-lang.org/nightly/nightly-rustc/rustc_span/symbol/struct.Symbol.html">Symbol</a> which
is essentially an ID number for each identifier. The compiler keeps a separate
hashtable that allows us to recover the human-readable name of a Symbol when
necessary (such as when printing a syntax error). When the compiler generates
the <code>__test_reexports</code> module, it generates a new <a href="https://doc.rust-lang.org/nightly/nightly-rustc/rustc_span/symbol/struct.Symbol.html">Symbol</a> for the
identifier, so while the compiler-generated <code>__test_reexports</code> may share a name
with your hand-written one, it will not share a <a href="https://doc.rust-lang.org/nightly/nightly-rustc/rustc_span/symbol/struct.Symbol.html">Symbol</a>. This
technique prevents name collision during code generation and is the foundation
of Rust's <a href="./macro-expansion.html"><code>macro</code></a> hygiene.</p>
<h2 id="step-2-harness-generation"><a class="header" href="#step-2-harness-generation">Step 2: Harness generation</a></h2>
<p>Now that our tests are accessible from the root of our crate, we need to do
something with them using <a href="./ast-validation.html"><code>rustc_ast</code></a> generates a module like so:</p>
<pre><code class="language-rust ignore">#[main]
pub fn main() {
extern crate test;
test::test_main_static(&amp;[&amp;path::to::test1, /*...*/]);
}</code></pre>
<p>Here <code>path::to::test1</code> is a constant of type <a href="https://doc.rust-lang.org/test/struct.TestDescAndFn.html"><code>test::TestDescAndFn</code></a>.</p>
<p>While this transformation is simple, it gives us a lot of insight into how
tests are actually run. The tests are aggregated into an array and passed to
a test runner called <code>test_main_static</code>. We'll come back to exactly what
<a href="https://doc.rust-lang.org/test/struct.TestDescAndFn.html"><code>TestDescAndFn</code></a> is, but for now, the key takeaway is that there is a crate
called <a href="https://doc.rust-lang.org/test/index.html"><code>test</code></a> that is part of Rust core, that implements all of the
runtime for testing. <a href="https://doc.rust-lang.org/test/index.html"><code>test</code></a>'s interface is unstable, so the only stable way
to interact with it is through the <code>#[test]</code> macro.</p>
<h2 id="step-3-test-object-generation"><a class="header" href="#step-3-test-object-generation">Step 3: Test object generation</a></h2>
<p>If you've written tests in Rust before, you may be familiar with some of the
optional attributes available on test functions. For example, a test can be
annotated with <code>#[should_panic]</code> if we expect the test to cause a panic. It
looks something like this:</p>
<pre><code class="language-rust ignore">#[test]
#[should_panic]
fn foo() {
panic!("intentional");
}</code></pre>
<p>This means our tests are more than just simple functions, they have
configuration information as well. <code>test</code> encodes this configuration data into
a <code>struct</code> called <a href="https://doc.rust-lang.org/test/struct.TestDesc.html"><code>TestDesc</code></a>. For each test function in a crate,
<a href="https://github.com/rust-lang/rust/tree/master/compiler/rustc_ast"><code>rustc_ast</code></a> will parse its attributes and generate a <a href="https://doc.rust-lang.org/test/struct.TestDesc.html"><code>TestDesc</code></a>
instance. It then combines the <a href="https://doc.rust-lang.org/test/struct.TestDesc.html"><code>TestDesc</code></a> and test function into the
predictably named <a href="https://doc.rust-lang.org/test/struct.TestDescAndFn.html"><code>TestDescAndFn</code></a> <code>struct</code>, that <a href="https://doc.rust-lang.org/test/fn.test_main_static.html"><code>test_main_static</code></a>
operates on.
For a given test, the generated <a href="https://doc.rust-lang.org/test/struct.TestDescAndFn.html"><code>TestDescAndFn</code></a> instance looks like so:</p>
<pre><code class="language-rust ignore">self::test::TestDescAndFn{
desc: self::test::TestDesc{
name: self::test::StaticTestName("foo"),
ignore: false,
should_panic: self::test::ShouldPanic::Yes,
allow_fail: false,
},
testfn: self::test::StaticTestFn(||
self::test::assert_test_result(::crate::__test_reexports::foo())),
}</code></pre>
<p>Once we've constructed an array of these test objects, they're passed to the
test runner via the harness generated in Step 2.</p>
<h2 id="inspecting-the-generated-code"><a class="header" href="#inspecting-the-generated-code">Inspecting the generated code</a></h2>
<p>On <code>nightly</code> <code>rustc</code>, there's an unstable flag called <code>unpretty</code> that you can use
to print out the module source after <a href="./macro-expansion.html"><code>macro</code></a> expansion:</p>
<pre><code class="language-bash">$ rustc my_mod.rs -Z unpretty=hir
</code></pre>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="attributes.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="panic-implementation.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="attributes.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="panic-implementation.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</nav>
</div>
<script>
window.playground_copyable = true;
</script>
<script src="elasticlunr.min.js"></script>
<script src="mark.min.js"></script>
<script src="searcher.js"></script>
<script src="clipboard.min.js"></script>
<script src="highlight.js"></script>
<script src="book.js"></script>
<!-- Custom JS scripts -->
<script src="mermaid.min.js"></script>
<script src="mermaid-init.js"></script>
</div>
</body>
</html>