{"id":596,"date":"2017-01-20T13:31:01","date_gmt":"2017-01-20T13:31:01","guid":{"rendered":"http:\/\/dlang.org\/blog\/?p=596"},"modified":"2021-10-08T11:08:53","modified_gmt":"2021-10-08T11:08:53","slug":"testing-in-the-d-standard-library","status":"publish","type":"post","link":"https:\/\/dlang.org\/blog\/2017\/01\/20\/testing-in-the-d-standard-library\/","title":{"rendered":"Testing In The D Standard Library"},"content":{"rendered":"<p><em>Jack Stouffer is a member of the Phobos team and a contributor to dlang.org. You can check out more of his writing on <a href=\"https:\/\/jackstouffer.com\/blog\/index.html\">his blog<\/a>.<\/em><\/p>\n<hr \/>\n<p><img loading=\"lazy\" class=\"alignleft size-full wp-image-181\" src=\"http:\/\/dlang.org\/blog\/wp-content\/uploads\/2016\/08\/d6.png\" alt=\"\" width=\"200\" height=\"200\" \/>In the D standard library, colloquially named <a href=\"https:\/\/github.com\/dlang\/phobos\">Phobos<\/a>, we take a multi-pronged approach to testing and code review. Currently, there are five different services any addition has to go through:<\/p>\n<ol>\n<li>The whole complier chain of tests: DMD&#8217;s and DRuntime&#8217;s test suite, and Phobos&#8217;s unit tests<\/li>\n<li>A documentation builder<\/li>\n<li>Coverage analysis<\/li>\n<li>A style checker<\/li>\n<li>And a community project builder\/test runner<\/li>\n<\/ol>\n<p>Using these, we&#8217;re able to automatically catch the vast majority of common problems that we see popping up in PRs. And we make regressions much less likely using the full test suite and examining coverage reports.<\/p>\n<p>Hopefully this will provide some insight into how a project like a standard library can use testing in order to increase stability. Also, it can spark some ideas on how to improve your own testing and review process.<\/p>\n<h2>Unit Tests<\/h2>\n<p>In D, unit tests are an integrated part of the language rather than a library<br \/>\nfeature:<\/p>\n<pre class=\"prettyprint lang-d\">size_t sum(int[] a)\r\n{\r\n    size_t result;\r\n\r\n    foreach (e; a)\r\n    {\r\n        result += e;\r\n    }\r\n\r\n    return result;\r\n}\r\n\r\nunittest\r\n{\r\n    assert(sum([1, 2, 3]) == 6);\r\n    assert(sum([0, 50, 100]) == 150);\r\n}\r\n\r\nvoid main() {}<\/pre>\n<p>Save this as <code>test.d<\/code> and run <strong>dmd -unittest -run test.d<\/strong>. Before your <code>main<\/code> function is run, all of the <code>unittest<\/code> blocks will be executed. If any of the asserts fail, execution is terminated and an error is printed to <code>stderr<\/code>.<\/p>\n<p>The effect of putting unit tests in the language has been enormous. One of the main ones we&#8217;ve seen is tests no longer have the &#8220;out of sight, out of mind&#8221; problem. Comprehensive tests in D projects are the rule and not the exception. Phobos dogfoods inline <code>unittest<\/code> blocks and uses them as its complete test suite. There are no other tests for Phobos than the inline tests, meaning for a reviewer to check their changes, they can just run <strong>dmd -main -unittest -run std\/algorithm\/searching.d<\/strong> (this is just for quick and dirty tests; full tests are done via <strong>make<\/strong>).<\/p>\n<p>Every PR onto Phobos runs the inline unit tests, DMD&#8217;s tests, and the DRuntime tests on the following platforms:<\/p>\n<ul>\n<li>Windows 32 and 64 bit<\/li>\n<li>MacOS 32 and 64 bit<\/li>\n<li>Linux 32 and 64 bit<\/li>\n<li>FreeBSD 32 and 64 bit<\/li>\n<\/ul>\n<p>This is done by <a href=\"https:\/\/github.com\/braddr\">Brad Roberts<\/a>&#8216;s <a href=\"https:\/\/auto-tester.puremagic.com\/pulls.ghtml?projectid=1\">auto-tester<\/a>. As a quick aside, work is currently progressing to make bring D to <a href=\"https:\/\/forum.dlang.org\/thread\/m2io5g7uvj.fsf@comcast.net\">iOS<\/a> and <a href=\"https:\/\/forum.dlang.org\/thread\/ovkhtsdzlfzqrqneolyv@forum.dlang.org\">Android<\/a>.<\/p>\n<h3>Idiot Proof<\/h3>\n<p>In order to avoid pulling untested PRs, we have three mechanisms in place. First, only PRs which have at least one Github review by someone with pull rights can be merged.<\/p>\n<p>Second, we don&#8217;t use the normal button for merging PRs. Instead, once a reviewer is satisfied with the code, we tell the auto-tester to merge the PR if and only if all tests have passed on all platforms.<\/p>\n<p>Third, every single change to any of the official repositories has to go through the PR review process. This includes changes made by the BDFL Walter Bright and the Language Architect Andrei Alexandrescu. We have even turned off pushing directly to the master branch in Github to make sure that nothing gets around this.<\/p>\n<h3>Unit Tests and Examples<\/h3>\n<p>Unit tests in D solve the perennial problem of out of date docs by using the unit test code itself as the example code in the documentation. This way, documentation examples are part of the test suite rather than just some comment which <em>will<\/em> go out of date.<\/p>\n<p>With this format, if the unit test goes out of date, then the test suite fails. When the tests are updated, the docs change automatically.<\/p>\n<p>Here&#8217;s an example:<\/p>\n<pre class=\"prettyprint lang-d\">\/**\r\n * Sums an array of `int`s.\r\n * \r\n * Params:\r\n *      a = the array to sum\r\n * Returns:\r\n *      The sum of the array.\r\n *\/\r\nsize_t sum(int[] a)\r\n{\r\n    size_t result;\r\n\r\n    foreach (e; a)\r\n    {\r\n        result += e;\r\n    }\r\n\r\n    return result;\r\n}\r\n\r\n\/\/\/\r\nunittest\r\n{\r\n    assert(sum([1, 2, 3]) == 6);\r\n    assert(sum([0, 50, 100]) == 150);\r\n}\r\n\r\n\/\/ only tests with a doc string above them get included in the\r\n\/\/ docs\r\nunittest\r\n{\r\n    assert(sum([100, 100, 100]) == 300);\r\n}\r\n\r\nvoid main() {}<\/pre>\n<p>Run <strong>dmd -D test.d<\/strong> and it generates the following un-styled HTML:<\/p>\n<p><img loading=\"lazy\" class=\"aligncenter size-medium wp-image-598\" src=\"http:\/\/dlang.org\/blog\/wp-content\/uploads\/2017\/01\/Screen-Shot-2017-01-12-at-12.06.27-PM-290x300.png\" alt=\"\" width=\"290\" height=\"300\" srcset=\"https:\/\/dlang.org\/blog\/wp-content\/uploads\/2017\/01\/Screen-Shot-2017-01-12-at-12.06.27-PM-290x300.png 290w, https:\/\/dlang.org\/blog\/wp-content\/uploads\/2017\/01\/Screen-Shot-2017-01-12-at-12.06.27-PM-768x794.png 768w, https:\/\/dlang.org\/blog\/wp-content\/uploads\/2017\/01\/Screen-Shot-2017-01-12-at-12.06.27-PM.png 876w\" sizes=\"(max-width: 290px) 100vw, 290px\" \/><\/p>\n<p>Phobos uses this to great effect. The vast majority of examples in the <a href=\"https:\/\/dlang.org\/phobos\/index.html\">Phobos documentation<\/a> are from <code>unittest<\/code> blocks. For example, <a href=\"https:\/\/dlang.org\/phobos\/std_algorithm_searching.html#.find.3\">here<\/a> is the documentation for <code>std.algorithm.fin<\/code>d and <a href=\"https:\/\/github.com\/dlang\/phobos\/blob\/48704296eff8e8f9133563bf1de2ed73ab07a32c\/std\/algorithm\/searching.d#L1786\">here<\/a> is the unit test that generates that example.<\/p>\n<p>This is not a catch all approach. Wholesale example programs, which are very useful when introducing a complex module or function, still have to be in comments.<\/p>\n<h3>Protecting Against Old Bugs<\/h3>\n<p>Despite our best efforts, bugs do find their way into released code. When they do, we require the person who&#8217;s patching the code to add in an extra unit test underneath the buggy function in order to protect against future regressions.<\/p>\n<h2>Docs<\/h2>\n<p>For Phobos, the documentation pages which were changed are generated on a test server for every PR. Developed by <a href=\"https:\/\/github.com\/CyberShadow\">Vladimir Panteleev<\/a>, the <a href=\"http:\/\/dtest.thecybershadow.net\/\">DAutoTest<\/a> allows reviewers to compare the old page and the new page from one location.<\/p>\n<p>For example, <a href=\"https:\/\/github.com\/dlang\/phobos\/pull\/4987\">this PR<\/a> changed the docs for two <code>struct<\/code>s and their member functions. <a href=\"http:\/\/dtest.thecybershadow.net\/results\/816c7f4ea65d76a2b8c995fe9075afa6024f751a\/a17204a3a666df395e47965515121a24d234ed38\/\">This page<\/a> on DAutoTest shows a summary of the changed pages with links to view the final result.<\/p>\n<h2>Coverage<\/h2>\n<p>Perfectly measuring the effectiveness of a test suite is impossible, but we can get a good rough approximation with test coverage. For those unaware, coverage is a ratio which represents the number of lines of code that were executed during a test suite vs. lines that weren&#8217;t executed.<\/p>\n<p>DMD has built-in coverage analysis to work in tandem with the built-in unit tests. Instead of <strong>dmd -unittest -run main.d<\/strong>, do <strong>dmd -unittest -cov -run main.d<\/strong> and a file will be generated showing a report of how many times each line of code was executed with a final coverage ratio at the end.<\/p>\n<p>We generate this report for each PR. Also, we use codecov in order to get details on how well the new code is covered, as well as how coverage for the whole project has changed. If coverage for the patch is lower than 80%, then codecov marks the PR as failed.<\/p>\n<p>At the time of writing, of the 77,601 lines of code (not counting docs or whitespace) in Phobos, 68,549 were covered during testing and 9,052 lines were not. This gives Phobos a test coverage of <a href=\"https:\/\/codecov.io\/gh\/dlang\/phobos\">88.3%<\/a>, which is increasing all of the time. This is all achieved with the built in <code>unittest<\/code> blocks.<\/p>\n<h2>Project Tester<\/h2>\n<p>Because test coverage doesn&#8217;t necessarily &#8220;cover&#8221; all real world use cases and combinations of features, D uses a <a href=\"https:\/\/jenkins.io\/index.html\">Jenkins<\/a> server to download, build, and run the tests for <a href=\"https:\/\/ci.dawg.eu\/job\/projects\/\">a select number of popular D projects<\/a> with the master branches of Phobos, DRuntime, and DMD. If any of the tests fail, the reviewers are notified.<\/p>\n<h2>Style And Anti-Pattern Checker<\/h2>\n<p>Having a code style set from on high stops a lot of pointless Internet flame wars (tabs vs spaces anyone?) dead in their tracks. D has had such a <a href=\"https:\/\/dlang.org\/dstyle.html\">style guide<\/a> for a couple of years now, but its enforcement in official code repos was spotty at best, and was mostly limited to brace style.<\/p>\n<p>Now, we use CircleCI in order to run a series of bash scripts and the fantastically helpful <a href=\"https:\/\/github.com\/Hackerpilot\/Dscanner\">dscanner<\/a> which automatically checks for <a href=\"https:\/\/github.com\/Hackerpilot\/Dscanner#implemented-checks\">all sorts of things<\/a> you shouldn&#8217;t be doing in your code. For example, CircleCI will give an error if it finds:<\/p>\n<ul>\n<li>bad brace style<\/li>\n<li>trailing whitespace<\/li>\n<li>using whole module imports inside of functions<\/li>\n<li>redundant parenthesis<\/li>\n<\/ul>\n<p>And so on.<\/p>\n<p>The automation of the style checker and coverage reports was done by <a href=\"https:\/\/github.com\/wilzbach\">Sebastian Wilzbach<\/a>. dscanner was written by <a href=\"https:\/\/github.com\/Hackerpilot\">Brian Schott<\/a>.<\/p>\n<h2>Closing Thoughts<\/h2>\n<p>We&#8217;re still working to improve somethings. Currently, Sebastian is writing a script to <a href=\"https:\/\/github.com\/dlang\/phobos\/pull\/4998\">automatically check<\/a> the documentation of every function for at least one example. Plus, the D Style Guide can be expanded to end arguments over the formatting of template constraints and other contested topics.<\/p>\n<p>Practically speaking, other than getting the coverage of Phobos up to &gt;= 95%, there&#8217;s not too much more to do. Phobos is one of the most throughly tested projects I&#8217;ve ever worked on, and it shows. Just recently, Phobos hit under <a href=\"https:\/\/issues.dlang.org\/buglist.cgi?component=phobos&amp;limit=0&amp;list_id=213089&amp;order=bug_id&amp;product=D&amp;query_format=advanced&amp;resolution=---\">1000 open bugs<\/a>, and that&#8217;s including enhancement requests.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Jack Stouffer is a member of the Phobos team and a contributor to dlang.org. You can check out more of his writing on his blog. In the D standard library, colloquially named Phobos, we take a multi-pronged approach to testing and code review. Currently, there are five different services any addition has to go through: [&hellip;]<\/p>\n","protected":false},"author":13,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":[],"categories":[9,10,20],"tags":[],"_links":{"self":[{"href":"https:\/\/dlang.org\/blog\/wp-json\/wp\/v2\/posts\/596"}],"collection":[{"href":"https:\/\/dlang.org\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/dlang.org\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/dlang.org\/blog\/wp-json\/wp\/v2\/users\/13"}],"replies":[{"embeddable":true,"href":"https:\/\/dlang.org\/blog\/wp-json\/wp\/v2\/comments?post=596"}],"version-history":[{"count":5,"href":"https:\/\/dlang.org\/blog\/wp-json\/wp\/v2\/posts\/596\/revisions"}],"predecessor-version":[{"id":1574,"href":"https:\/\/dlang.org\/blog\/wp-json\/wp\/v2\/posts\/596\/revisions\/1574"}],"wp:attachment":[{"href":"https:\/\/dlang.org\/blog\/wp-json\/wp\/v2\/media?parent=596"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/dlang.org\/blog\/wp-json\/wp\/v2\/categories?post=596"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/dlang.org\/blog\/wp-json\/wp\/v2\/tags?post=596"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}