From 84adefa331c4159d432d22840663c38f155cd4c1 Mon Sep 17 00:00:00 2001 From: Erlang/OTP Date: Fri, 20 Nov 2009 14:54:40 +0000 Subject: The R13B03 release. --- lib/eunit/AUTHORS | 2 + lib/eunit/COPYING | 504 +++++++++++++++++ lib/eunit/Makefile | 97 ++++ lib/eunit/doc/edoc-info | 3 + lib/eunit/doc/erlang.png | Bin 0 -> 2109 bytes lib/eunit/doc/eunit.html | 71 +++ lib/eunit/doc/eunit_surefire.html | 78 +++ lib/eunit/doc/html/.gitignore | 0 lib/eunit/doc/index.html | 17 + lib/eunit/doc/man3/.gitignore | 0 lib/eunit/doc/modules-frame.html | 13 + lib/eunit/doc/overview-summary.html | 1032 +++++++++++++++++++++++++++++++++++ lib/eunit/doc/overview.edoc | 1028 ++++++++++++++++++++++++++++++++++ lib/eunit/doc/packages-frame.html | 11 + lib/eunit/doc/pdf/.gitignore | 0 lib/eunit/doc/src/Makefile | 174 ++++++ lib/eunit/doc/src/book.xml | 51 ++ lib/eunit/doc/src/fascicules.xml | 18 + lib/eunit/doc/src/make.dep | 19 + lib/eunit/doc/src/notes.xml | 153 ++++++ lib/eunit/doc/src/part.xml | 38 ++ lib/eunit/doc/src/part_notes.xml | 39 ++ lib/eunit/doc/src/ref_man.xml | 40 ++ lib/eunit/doc/stylesheet.css | 55 ++ lib/eunit/ebin/.gitignore | 0 lib/eunit/examples/Makefile | 58 ++ lib/eunit/examples/fib.erl | 19 + lib/eunit/examples/tests.txt | 1 + lib/eunit/include/eunit.hrl | 340 ++++++++++++ lib/eunit/info | 2 + lib/eunit/src/Makefile | 113 ++++ lib/eunit/src/eunit.app.src | 21 + lib/eunit/src/eunit.appup.src | 1 + lib/eunit/src/eunit.erl | 250 +++++++++ lib/eunit/src/eunit_autoexport.erl | 104 ++++ lib/eunit/src/eunit_data.erl | 732 +++++++++++++++++++++++++ lib/eunit/src/eunit_internal.hrl | 48 ++ lib/eunit/src/eunit_lib.erl | 576 +++++++++++++++++++ lib/eunit/src/eunit_listener.erl | 178 ++++++ lib/eunit/src/eunit_proc.erl | 661 ++++++++++++++++++++++ lib/eunit/src/eunit_serial.erl | 186 +++++++ lib/eunit/src/eunit_server.erl | 341 ++++++++++++ lib/eunit/src/eunit_striptests.erl | 67 +++ lib/eunit/src/eunit_surefire.erl | 417 ++++++++++++++ lib/eunit/src/eunit_test.erl | 320 +++++++++++ lib/eunit/src/eunit_tests.erl | 42 ++ lib/eunit/src/eunit_tty.erl | 257 +++++++++ lib/eunit/test/Makefile | 82 +++ lib/eunit/test/eunit.cover | 3 + lib/eunit/test/eunit.dynspec | 6 + lib/eunit/test/eunit_SUITE.erl | 31 ++ lib/eunit/vsn.mk | 1 + 52 files changed, 8300 insertions(+) create mode 100644 lib/eunit/AUTHORS create mode 100644 lib/eunit/COPYING create mode 100644 lib/eunit/Makefile create mode 100644 lib/eunit/doc/edoc-info create mode 100644 lib/eunit/doc/erlang.png create mode 100644 lib/eunit/doc/eunit.html create mode 100644 lib/eunit/doc/eunit_surefire.html create mode 100644 lib/eunit/doc/html/.gitignore create mode 100644 lib/eunit/doc/index.html create mode 100644 lib/eunit/doc/man3/.gitignore create mode 100644 lib/eunit/doc/modules-frame.html create mode 100644 lib/eunit/doc/overview-summary.html create mode 100644 lib/eunit/doc/overview.edoc create mode 100644 lib/eunit/doc/packages-frame.html create mode 100644 lib/eunit/doc/pdf/.gitignore create mode 100644 lib/eunit/doc/src/Makefile create mode 100644 lib/eunit/doc/src/book.xml create mode 100644 lib/eunit/doc/src/fascicules.xml create mode 100644 lib/eunit/doc/src/make.dep create mode 100644 lib/eunit/doc/src/notes.xml create mode 100644 lib/eunit/doc/src/part.xml create mode 100644 lib/eunit/doc/src/part_notes.xml create mode 100644 lib/eunit/doc/src/ref_man.xml create mode 100644 lib/eunit/doc/stylesheet.css create mode 100644 lib/eunit/ebin/.gitignore create mode 100644 lib/eunit/examples/Makefile create mode 100644 lib/eunit/examples/fib.erl create mode 100644 lib/eunit/examples/tests.txt create mode 100644 lib/eunit/include/eunit.hrl create mode 100644 lib/eunit/info create mode 100644 lib/eunit/src/Makefile create mode 100644 lib/eunit/src/eunit.app.src create mode 100644 lib/eunit/src/eunit.appup.src create mode 100644 lib/eunit/src/eunit.erl create mode 100644 lib/eunit/src/eunit_autoexport.erl create mode 100644 lib/eunit/src/eunit_data.erl create mode 100644 lib/eunit/src/eunit_internal.hrl create mode 100644 lib/eunit/src/eunit_lib.erl create mode 100644 lib/eunit/src/eunit_listener.erl create mode 100644 lib/eunit/src/eunit_proc.erl create mode 100644 lib/eunit/src/eunit_serial.erl create mode 100644 lib/eunit/src/eunit_server.erl create mode 100644 lib/eunit/src/eunit_striptests.erl create mode 100644 lib/eunit/src/eunit_surefire.erl create mode 100644 lib/eunit/src/eunit_test.erl create mode 100644 lib/eunit/src/eunit_tests.erl create mode 100644 lib/eunit/src/eunit_tty.erl create mode 100644 lib/eunit/test/Makefile create mode 100644 lib/eunit/test/eunit.cover create mode 100644 lib/eunit/test/eunit.dynspec create mode 100644 lib/eunit/test/eunit_SUITE.erl create mode 100644 lib/eunit/vsn.mk (limited to 'lib/eunit') diff --git a/lib/eunit/AUTHORS b/lib/eunit/AUTHORS new file mode 100644 index 0000000000..b7c1426aff --- /dev/null +++ b/lib/eunit/AUTHORS @@ -0,0 +1,2 @@ +Richard Carlsson +Mickaël Rémond diff --git a/lib/eunit/COPYING b/lib/eunit/COPYING new file mode 100644 index 0000000000..223ede7de3 --- /dev/null +++ b/lib/eunit/COPYING @@ -0,0 +1,504 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/lib/eunit/Makefile b/lib/eunit/Makefile new file mode 100644 index 0000000000..ee69d5e8e0 --- /dev/null +++ b/lib/eunit/Makefile @@ -0,0 +1,97 @@ +# ``The contents of this file are subject to the Erlang Public License, +# Version 1.1, (the "License"); you may not use this file except in +# compliance with the License. You should have received a copy of the +# Erlang Public License along with this software. If not, it can be +# retrieved via the world wide web at http://www.erlang.org/. +# +# Software distributed under the License is distributed on an "AS IS" +# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +# the License for the specific language governing rights and limitations +# under the License. +# +# The Initial Developer of the Original Code is Ericsson Utvecklings AB. +# Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings +# AB. All Rights Reserved.'' +# +# $Id$ +# +include $(ERL_TOP)/make/target.mk +include $(ERL_TOP)/make/$(TARGET)/otp.mk + +# +# Macros +# + +SUB_DIRECTORIES = src examples doc/src + +include vsn.mk +VSN = $(EUNIT_VSN) + +DIR_NAME = eunit-$(VSN) + +ifndef APP_RELEASE_DIR + APP_RELEASE_DIR = /tmp +endif + +ifndef APP_TAR_FILE + APP_TAR_FILE = $(APP_RELEASE_DIR)/$(DIR_NAME).tgz +endif + +APP_DIR = $(APP_RELEASE_DIR)/$(DIR_NAME) + +APPNAME = eunit +DOC_OPTS = [{def,{version,"$(VSN)"}}] + +SPECIAL_TARGETS = + +# +# Default Subdir Targets +# +include $(ERL_TOP)/make/otp_subdir.mk + + +.PHONY: info version + +info: + @echo "APP_RELEASE_DIR: $(APP_RELEASE_DIR)" + @echo "APP_DIR: $(APP_DIR)" + @echo "APP_TAR_FILE: $(APP_TAR_FILE)" + +version: + @echo "$(VSN)" + +#docs: +# erl -noshell -run edoc_run application "'$(APPNAME)'" '"."' '$(DOC_OPTS)' -s init stop + +#edocs: docs + +# ---------------------------------------------------- +# Application (source) release targets +# ---------------------------------------------------- +app_release: tar + +app_dir: $(APP_DIR) + +$(APP_DIR): + cat TAR.exclude > TAR.exclude2; \ + echo "eunit/TAR.exclude2" >> TAR.exclude2; \ + (cd ..; find eunit -name 'findmerge.*' >> eunit/TAR.exclude2) + (cd ..; find eunit -name '*.contrib*' >> eunit/TAR.exclude2) + (cd ..; find eunit -name '*.keep*' >> eunit/TAR.exclude2) + (cd ..; find eunit -name '*~' >> eunit/TAR.exclude2) + (cd ..; find eunit -name 'erl_crash.dump' >> eunit/TAR.exclude2) + (cd ..; find eunit -name '*.log' >> eunit/TAR.exclude2) + (cd ..; find eunit -name 'core' >> eunit/TAR.exclude2) + (cd ..; find eunit -name '.cmake.state' >> eunit/TAR.exclude2) + mkdir $(APP_DIR); \ + (cd ..; tar cfX - eunit/TAR.exclude2 eunit) | \ + (cd $(APP_DIR); tar xf -); \ + mv $(APP_DIR)/eunit/* $(APP_DIR)/; \ + rmdir $(APP_DIR)/eunit + mkdir $(APP_DIR)/doc; \ + (cd doc; tar cf - man3 html) | (cd $(APP_DIR)/doc; tar xf -) + +tar: $(APP_TAR_FILE) + +$(APP_TAR_FILE): $(APP_DIR) + (cd $(APP_RELEASE_DIR); gtar zcf $(APP_TAR_FILE) $(DIR_NAME)) diff --git a/lib/eunit/doc/edoc-info b/lib/eunit/doc/edoc-info new file mode 100644 index 0000000000..1c04b2ed1a --- /dev/null +++ b/lib/eunit/doc/edoc-info @@ -0,0 +1,3 @@ +{application,eunit}. +{packages,[]}. +{modules,[eunit,eunit_surefire]}. diff --git a/lib/eunit/doc/erlang.png b/lib/eunit/doc/erlang.png new file mode 100644 index 0000000000..987a618e24 Binary files /dev/null and b/lib/eunit/doc/erlang.png differ diff --git a/lib/eunit/doc/eunit.html b/lib/eunit/doc/eunit.html new file mode 100644 index 0000000000..a181d12ce3 --- /dev/null +++ b/lib/eunit/doc/eunit.html @@ -0,0 +1,71 @@ + + + +Module eunit + + + + +
+ +

Module eunit

+This module is the main EUnit user interface. +

Copyright © 2004-2009 Mickaël Rémond, Richard Carlsson

+ +

Version: 2.1.1, Apr 22 2009 22:37:19

+

Authors: Mickaël Rémond (mickael.remond@process-one.net) [web site: http://www.process-one.net/], Richard Carlsson (richardc@it.uu.se) [web site: http://user.it.uu.se/~richardc/].

+ +

Description

This module is the main EUnit user interface. +

Function Index

+ + + + +
start/0Starts the EUnit server.
stop/0Stops the EUnit server.
test/1Equivalent to test(Tests, []). +
test/2Runs a set of tests.
+ +

Function Details

+ +

start/0

+
+

start() -> any()

+

Starts the EUnit server. Normally, you don't need to call this + function; it is started automatically.

+ +

stop/0

+
+

stop() -> any()

+

Stops the EUnit server. Normally, you don't need to call this + function.

+ +

test/1

+
+

test(Tests) -> any()

+

Equivalent to test(Tests, []).

+ + +

test/2

+
+

test(Tests::term(), Options::[term()]) -> ok | {error, term()}

+

Runs a set of tests. The format of Tests is described in the + section EUnit test + representation of the overview.

+ + Example:
  eunit:test(fred)

runs all tests in the module fred + and also any tests in the module fred_tests, if that module exists.

+ + Options: +
+
verbose
+
Displays more details about the running tests.
+
+ + Options in the environment variable EUNIT are also included last in + the option list, i.e., have lower precedence than those in Options.

+

See also: test/1.

+
+ + +

Generated by EDoc, Apr 22 2009, 22:37:19.

+ + diff --git a/lib/eunit/doc/eunit_surefire.html b/lib/eunit/doc/eunit_surefire.html new file mode 100644 index 0000000000..f2ecbae572 --- /dev/null +++ b/lib/eunit/doc/eunit_surefire.html @@ -0,0 +1,78 @@ + + + +Module eunit_surefire + + + + +
+ +

Module eunit_surefire

+Surefire reports for EUnit (Format used by Maven and Atlassian +Bamboo for example to integrate test results). +

Copyright © 2009 Mickaël Rémond, Paul Guyot

+ +

Behaviours: eunit_listener.

+

Authors: Mickaël Rémond (mremond@process-one.net).

+

See also: eunit.

+ +

Description

Surefire reports for EUnit (Format used by Maven and Atlassian +Bamboo for example to integrate test results). Based on initial code +from Paul Guyot.

+ + Example: Generate XML result file in the current directory: +
     eunit:test([fib, eunit_examples],
+                [{report,{eunit_surefire,[{dir,"."}]}}]).
+

Function Index

+ + + + + + + +
handle_begin/3
handle_cancel/3
handle_end/3
init/1
start/0
start/1
terminate/2
+ +

Function Details

+ +

handle_begin/3

+
+

handle_begin(X1, Data, St) -> any()

+
+ +

handle_cancel/3

+
+

handle_cancel(X1, Data, St) -> any()

+
+ +

handle_end/3

+
+

handle_end(X1, Data, St) -> any()

+
+ +

init/1

+
+

init(Options) -> any()

+
+ +

start/0

+
+

start() -> any()

+
+ +

start/1

+
+

start(Options) -> any()

+
+ +

terminate/2

+
+

terminate(X1, St) -> any()

+
+
+ + +

Generated by EDoc, Apr 22 2009, 22:37:19.

+ + diff --git a/lib/eunit/doc/html/.gitignore b/lib/eunit/doc/html/.gitignore new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/eunit/doc/index.html b/lib/eunit/doc/index.html new file mode 100644 index 0000000000..9bd8e8cf6b --- /dev/null +++ b/lib/eunit/doc/index.html @@ -0,0 +1,17 @@ + + + +The eunit application + + + + + + +<h2>This page uses frames</h2> +<p>Your browser does not accept frames. +<br>You should go to the <a href="overview-summary.html">non-frame version</a> instead. +</p> + + + \ No newline at end of file diff --git a/lib/eunit/doc/man3/.gitignore b/lib/eunit/doc/man3/.gitignore new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/eunit/doc/modules-frame.html b/lib/eunit/doc/modules-frame.html new file mode 100644 index 0000000000..a484e99d4c --- /dev/null +++ b/lib/eunit/doc/modules-frame.html @@ -0,0 +1,13 @@ + + + +The eunit application + + + +

Modules

+ + +
eunit
eunit_surefire
+ + \ No newline at end of file diff --git a/lib/eunit/doc/overview-summary.html b/lib/eunit/doc/overview-summary.html new file mode 100644 index 0000000000..ea7beba8b3 --- /dev/null +++ b/lib/eunit/doc/overview-summary.html @@ -0,0 +1,1032 @@ + + + +EUnit - a Lightweight Unit Testing Framework for Erlang + + + + + +

EUnit - a Lightweight Unit Testing Framework for Erlang +

+

Copyright © 2004-2007 Mickaël Rémond, Richard Carlsson

+

Version: 2.1.1, Apr 22 2009 22:37:19 +

+

Authors: Richard Carlsson (richardc@it.uu.se) [web site: http://user.it.uu.se/~richardc/], Mickaël Rémond (mickael.remond@process-one.net) [web site: http://www.process-one.net/].

+

EUnit is a unit testing framework for Erlang. It is very powerful +and flexible, is easy to use, and has small syntactical overhead.

+ + + +

EUnit builds on ideas from the family of unit testing frameworks for +Object Oriented languages that originated with JUnit by Beck and Gamma +(and Beck's previous framework SUnit for Smalltalk). However, EUnit uses +techniques more adapted to functional and concurrent programming, and is +typically less verbose than its relatives.

+ +

Although EUnit uses many preprocessor macros, they have been designed to +be as nonintrusive as possible, and should not cause conflicts with +existing code. Adding EUnit tests to a module should thus not normally +require changing existing code. Furthermore, tests that only exercise +the exported functions of a module can always be placed in a completely +separate module, avoiding any conflicts entirely.

+ +

Unit testing

+ +

Unit Testing is testing of individual program "units" in relative +isolation. There is no particular size requirement: a unit can be a +function, a module, a process, or even a whole application, but the most +typical testing units are individual functions or modules. In order to +test a unit, you specify a set of individual tests, set up the smallest +necessary environment for being able to run those tests (often, you +don't need to do any setup at all), you run the tests and collect the +results, and finally you do any necessary cleanup so that the test can +be run again later. A Unit Testing Framework tries to help you in each +stage of this process, so that it is easy to write tests, easy to run +them, and easy to see which tests failed (so you can fix the bugs).

+ +

Advantages of unit testing

+ +
+
Reduces the risks of changing the program
+
Most programs will be modified during their lifetime: bugs will be + fixed, features will be added, optimizations may become necessary, or + the code will need to be refactored or cleaned up in other ways to + make it easier to work with. But every change to a working program is + a risk of introducing new bugs - or reintroducing bugs that had + previously been fixed. Having a set of unit tests that you can run + with very little effort makes it easy to know that the code still + works as it should (this use is called regression testing; + see Terminology). This goes a long way to reduce the + resistance to changing and refactoring code.
+
Helps guide and speed up the development process
+
By focusing on getting the code to pass the tests, the programmer + can become more productive, not overspecify or get lost in premature + optimizations, and create code that is correct from the very beginning + (so-called test-driven development; see Terminology).
+
Helps separate interface from implementation
+
When writing tests, the programmer may discover dependencies + (in order to get the tests to run) that ought not to be there, and + which need to be abstracted away to get a cleaner design. This helps + eliminate bad dependencies before they spread throughout the + code.
+
Makes component integration easier
+
By testing in a bottom-up fashion, beginning with the smallest + program units and creating a confidence in that they work as they + should, it becomes easier to test that a higher-level component, + consisting of several such units, also behaves according to + specification (known as integration testing; see Terminology).
+
Is self-documenting
+
The tests can be read as documentation, typically showing both + examples of correct and incorrect usage, along with the expected + consequences.
+
+ +

Terminology

+ +
+
Unit testing
+
Testing that a program unit behaves as it is supposed to do (in + itself), according to its specifications. Unit tests have an important + function as regression tests, when the program later is modified for + some reason, since they check that the program still behaves according + to specification.
+
Regression testing
+
Running a set of tests after making changes to a program, to check + that the program behaves as it did before the changes (except, of + course, for any intentional changes in behaviour). Unit tests are + important as regression tests, but regression testing can involve more + than just unit testing, and may also test behaviour that might not be + part of the normal specification (such as bug-for-bug-compatibility). +
+
Integration testing
+
Testing that a number of individually developed program units + (assumed to already have been separately unit tested) work together as + expected. Depending on the system being developed, integration testing + may be as simple as "just another level of unit testing", but might + also involve other kinds of tests (compare system testing). +
+
System testing
+
Testing that a complete system behaves according to its + specification. Specifically, system testing should not require knowing + any details about the implementation. It typically involves testing + many different aspects of the system behaviour apart from the basic + functionality, such as performance, usability, and reliability.
+
Test-driven development
+
A program development technique where you continuously write tests + before you implement the code that is supposed to pass those + tests. This can help you focus on solving the right problems, and not + make a more complicated implementation than necessary, by letting the + unit tests determine when a program is "done": if it fulfils its + specifications, there is no need to keep adding functionality.
+
Mock object
+
Sometimes, testing some unit A (e.g., a function) requires that + it collaborates somehow with some other unit B (perhaps being passed + as an argument, or by reference) - but B has not been implemented + yet. A "mock object" - an object which, for the purposes of testing + A, looks and behaves like a real B - might then be used instead. + (This is of course only useful if it would be significantly more work + to implement a real B than to create a mock object.)
+
Test case
+
A single, well-defined test, that somehow can be uniquely + identified. When executed, the test case either passes or + fails; the test report should identify exactly which test + cases failed.
+
Test suite
+
A collection of test cases, generally with a specific, common + target for testing, such as a single function, module, or subsystem. A + test suite may also be recursively composed by smaller test + suites.
+
+ +

Getting started

+ + +

Including the EUnit header file

+ +The simplest way to use EUnit in an Erlang module is to add the +following line at the beginning of the module (after the -module +declaration, but before any function definitions): +
   -include_lib("eunit/include/eunit.hrl").
+ +This will have the following effect: +
    +
  • Creates an exported function test() (unless testing is turned + off, and the module does not already contain a test() function), that + can be used to run all the unit tests defined in the module
  • +
  • Causes all functions whose names match ..._test() or ..._test_() + to be automatically exported from the module (unless testing is + turned off, or the EUNIT_NOAUTO macro is defined)
  • +
  • Makes all the preprocessor macros of EUnit available, to help + writing tests
  • +
+ +Note: For -include_lib(...) to work, the Erlang +module search path must contain a directory whose name ends in +eunit/ebin (pointing to the ebin subdirectory of the EUnit +installation directory). If EUnit is installed as lib/eunit under your +Erlang/OTP system directory, its ebin subdirectory will be +automatically added to the search path when Erlang starts. Otherwise, +you need to add the directory explicitly, by passing a -pa flag to the +erl or erlc command. For example, a Makefile could contain the +following action for compiling .erl files: +
   erlc -pa "path/to/eunit/ebin" $(ERL_COMPILE_FLAGS) -o$(EBIN) $<
+or if you want Eunit to always be available when you run Erlang +interactively, you can add a line like the following to your +$HOME/.erlang file: +
   code:add_path("/path/to/eunit/ebin").
+ +

Writing simple test functions

+ +

The EUnit framework makes it extremely easy to write unit tests in +Erlang. There are a few different ways of writing them, though, so we +start with the simplest:

+ +

A function with a name ending in ..._test() is recognized by EUnit as +a simple test function - it takes no arguments, and its execution either +succeeds (returning some arbitrary value that EUnit will throw away), or +fails by throwing an exception of some kind (or by not terminating, in +which case it will be aborted after a while).

+ +An example of a simple test function could be the following: +
   reverse_test() -> lists:reverse([1,2,3]).

+This just tests that the function lists:reverse(List) does not crash +when List is [1,2,3]. It is not a great test, but many people write +simple functions like this one to test the basic functionality of their +code, and those tests can be used directly by EUnit, without changes, +as long as their function names match.

+ +
Use exceptions to signal failure
+ +To write more interesting tests, we need to make them crash (throw an +exception) when they don't get the result they expect. A simple way of +doing this is to use pattern matching with =, as in the following +examples: +
   reverse_nil_test() -> [] = lists:reverse([]).
+   reverse_one_test() -> [1] = lists:reverse([1]).
+   reverse_two_test() -> [2,1] = lists:reverse([1,2]).

+If there was some bug in lists:reverse/1 that made it return something +other than [2,1] when it got [1,2] as input, then the last test +above would throw a badmatch error. The first two (we assume they do +not get a badmatch) would simply return [] and [1], respectively, +so both succeed. (Note that EUnit is not psychic: if you write a test +that returns a value, even if it is the wrong value, EUnit will consider +it a success. You must make sure that the test is written so that it +causes a crash if the result is not what it should be.)

+ +
Using assert macros
+ +If you want to use Boolean operators for your tests, the assert +macro comes in handy (see EUnit macros for details): +
   length_test() -> ?assert(length([1,2,3]) =:= 3).

+The ?assert(Expression) macro will evaluate Expression, and if that +does not evaluate to true, it will throw an exception; otherwise it +just returns ok. In the above example, the test will thus fail if the +call to length does not return 3.

+ +

Running EUnit

+ +

If you have added the declaration +-include_lib("eunit/include/eunit.hrl") to your module, as described +above, you only need to compile the module, and run the automatically +exported function test(). For example, if your module was named m, +then calling m:test() will run EUnit on all the tests defined in the +module. You do not need to write -export declarations for the test +functions. This is all done by magic.

+ +

You can also use the function eunit:test/1 to run arbitrary +tests, for example to try out some more advanced test descriptors (see +EUnit test representation). For example, running +eunit:test(m) does the same thing as the auto-generated function +m:test(), while eunit:test({inparallel, m}) runs the same test +cases but executes them all in parallel.

+ +
Putting tests in separate modules
+ +

If you want to separate your test code from your normal code (at least +for testing the exported functions), you can simply write the test +functions in a module named m_tests (note: not m_test), if your +module is named m. Then, whenever you ask EUnit to test the module +m, it will also look for the module m_tests and run those tests as +well. See ModuleName in the section Primitives for details.

+ +
EUnit captures standard output
+ +

If your test code writes to the standard output, you may be surprised to +see that the text does not appear on the console when the tests are +running. This is because EUnit captures all standard output from test +functions (this also includes setup and cleanup functions, but not +generator functions), so that it can be included in the test report if +errors occur. To bypass EUnit and print text directly to the console +while testing, you can write to the user output stream, as in +io:format(user, "~w", [Term]). The recommended way of doing this is to +use the EUnit Debugging macros, which make it much simpler.

+ +

Writing test generating functions

+ +

A drawback of simple test functions is that you must write a separate +function (with a separate name) for each test case. A more compact way +of writing tests (and much more flexible, as we shall see), is to write +functions that return tests, instead of being tests.

+ +

A function with a name ending in ..._test_() (note the final +underscore) is recognized by EUnit as a test generator +function. Test generators return a representation of a set +of tests to be executed by EUnit.

+ +
Representing a test as data
+ +The most basic representation of a test is a single fun-expression that +takes no arguments. For example, the following test generator: +
   basic_test_() ->
+       fun () -> ?assert(1 + 1 =:= 2) end.
+will have the same effect as the following simple test: +
   simple_test() ->
+       ?assert(1 + 1 =:= 2).

+(in fact, EUnit will handle all simple tests just like it handles +fun-expressions: it will put them in a list, and run them one by one).

+ +
Using macros to write tests
+ +To make tests more compact and readable, as well as automatically add +information about the line number in the source code where a test +occurred (and reduce the number of characters you have to type), you can +use the _test macro (note the initial underscore character), like +this: +
   basic_test_() ->
+       ?_test(?assert(1 + 1 =:= 2)).

+The _test macro takes any expression (the "body") as argument, and +places it within a fun-expression (along with some extra information). +The body can be any kind of test expression, just like the body of a +simple test function.

+ +
Underscore-prefixed macros create test objects
+ +But this example can be made even shorter! Most test macros, such as the +family of assert macros, have a corresponding form with an initial +underscore character, which automatically adds a ?_test(...) wrapper. +The above example can then simply be written: +
   basic_test_() ->
+       ?_assert(1 + 1 =:= 2).

+which has exactly the same meaning (note the _assert instead of +assert). You can think of the initial underscore as signalling +test object.

+ +

An example

+ +Sometimes, an example says more than a thousand words. The following +small Erlang module shows how EUnit can be used in practice. +
   -module(fib).
+   -export([fib/1]).
+   -include_lib("eunit/include/eunit.hrl").
+
+   fib(0) -> 1;
+   fib(1) -> 1;
+   fib(N) when N > 1 -> fib(N-1) + fib(N-2).
+
+   fib_test_() ->
+       [?_assert(fib(0) =:= 1),
+	?_assert(fib(1) =:= 1),
+	?_assert(fib(2) =:= 2),
+	?_assert(fib(3) =:= 3),
+	?_assert(fib(4) =:= 5),
+	?_assert(fib(5) =:= 8),
+	?_assertException(error, function_clause, fib(-1)),
+	?_assert(fib(31) =:= 2178309)
+       ].
+ +

(Author's note: When I first wrote this example, I happened to write a +* instead of + in the fib function. Of course, this showed up +immediately when I ran the tests.)

+ +

See EUnit test representation for a full list of all the ways +you can specify test sets in EUnit.

+ +

Disabling testing

+ +Testing can be turned off by defining the NOTEST macro when compiling, +for example as an option to erlc, as in: +
   erlc -DNOTEST my_module.erl
+or by adding a macro definition to the code, before the EUnit header +file is included: +
   -define(NOTEST, 1).

+(the value is not important, but should typically be 1 or true). +Note that unless the EUNIT_NOAUTO macro is defined, disabling testing +will also automatically strip all test functions from the code, except +for any that are explicitly declared as exported.

+ +For instance, to use EUnit in your application, but with testing turned +off by default, put the following lines in a header file: +
   -define(NOTEST, true).
+   -include_lib("eunit/include/eunit.hrl").
+and then make sure that every module of your application includes that +header file. This means that you have a only a single place to modify in +order to change the default setting for testing. To override the NOTEST +setting without modifying the code, you can define TEST in a compiler +option, like this: +
   erlc -DTEST my_module.erl
+ +

See Compilation control macros for details about these +macros.

+ +

Avoiding compile-time dependency on EUnit

+ +If you are distributing the source code for your application for other +people to compile and run, you probably want to ensure that the code +compiles even if EUnit is not available. Like the example in the +previous section, you can put the following lines in a common header +file: +
   -ifdef(TEST).
+   -include_lib("eunit/include/eunit.hrl").
+   -endif.

+and, of course, also make sure that you place all test code that uses +EUnit macros within -ifdef(TEST) or -ifdef(EUNIT) sections.

+ + +

EUnit macros

+ +

Although all the functionality of EUnit is avaliable even without the +use of preprocessor macros, the EUnit header file defines a number of +such macros in order to make it as easy as possible to write unit tests +as compactly as possible and without getting too many details in the +way.

+ +

Except where explicitly stated, using EUnit macros will never introduce +run-time dependencies on the EUnit library code, regardless of whether +your code is compiled with testing enabled or disabled.

+ + + +

Basic macros

+ +
+
_test(Expr)
+
Turns Expr into a "test object", by wrapping it in a +fun-expression and a source line number. Technically, this is the same +as {?LINE, fun () -> (Expr) end}. +
+
+ +

Compilation control macros

+ +
+
EUNIT
+
This macro is always defined to true whenever EUnit is enabled at +compile time. This is typically used to place testing code within +conditional compilation, as in: +
   -ifdef(EUNIT).
+       % test code here
+       ...
+   -endif.
+e.g., to ensure that the code can be compiled without including the +EUnit header file, when testing is disabled. See also the macros TEST +and NOTEST. +
+ +
EUNIT_NOAUTO
+
If this macro is defined, the automatic exporting or stripping of +test functions will be disabled. +
+ +
TEST
+

This macro is always defined (to true, unless previously defined +by the user to have another value) whenever EUnit is enabled at compile +time. This can be used to place testing code within conditional +compilation; see also the macros NOTEST and EUNIT.

+ +

For testing code that is strictly dependent on EUnit, it may be +preferable to use the EUNIT macro for this purpose, while for code +that uses more generic testing conventions, using the TEST macro may +be preferred.

+ +The TEST macro can also be used to override the NOTEST macro. If +TEST is defined before the EUnit header file is +included (even if NOTEST is also defined), then the code will be +compiled with EUnit enabled. +
+ +
NOTEST
+

This macro is always defined (to true, unless previously defined +by the user to have another value) whenever EUnit is disabled +at compile time. (Compare the TEST macro.)

+ +This macro can also be used for conditional compilation, but is more +typically used to disable testing: If NOTEST is defined +before the EUnit header file is included, and TEST +is not defined, then the code will be compiled with EUnit +disabled. See also Disabling testing. +
+ +
NOASSERT
+
If this macro is defined, the assert macros will have no effect, +when testing is also disabled. See Assert macros. When +testing is enabled, the assert macros are always enabled automatically +and cannot be disabled. +
+ +
ASSERT
+
If this macro is defined, it overrides the NOASSERT macro, forcing +the assert macros to always be enabled regardless of other settings. +
+ +
NODEBUG
+
If this macro is defined, the debugging macros will have no effect. +See Debugging macros. NODEBUG also implies NOASSERT, +unless testing is enabled. +
+ +
DEBUG
+
If this macro is defined, it overrides the NODEBUG macro, forcing +the debugging macros to be enabled. +
+
+ +

Utility macros

+ +

The following macros can make tests more compact and readable:

+ +
+
LET(Var,Arg,Expr)
+
Creates a local binding Var = Arg in Expr. (This is the same as +(fun(Var)->(Expr)end)(Arg).) Note that the binding is not exported +outside of Expr, and that within Expr, this binding of Var will +shadow any binding of Var in the surrounding scope. +
+
IF(Cond,TrueCase,FalseCase)
+
Evaluates TrueCase if Cond evaluates to true, or otherwise +evaluates FalseCase if Cond evaluates to false. (This is the same +as (case (Cond) of true->(TrueCase); false->(FalseCase) end).) Note +that it is an error if Cond does not yield a boolean value. +
+
+ +

Assert macros

+ +

(Note that these macros also have corresponding forms which start with +an "_" (underscore) character, as in ?_assert(BoolExpr), that create +a "test object" instead of performing the test immediately. This is +equivalent to writing ?_test(assert(BoolExpr)), etc.)

+ +

If the macro NOASSERT is defined before the EUnit header file is +included, these macros have no effect when testing is also disabled; see +Compilation control macros for details.

+ +
+
assert(BoolExpr)
+

Evaluates the expression BoolExpr, if testing is enabled. Unless +the result is true, an informative exception will be generated. If +there is no exception, the result of the macro expression is the atom +ok, and the value of BoolExpr is discarded. If testing is disabled, +the macro will not generate any code except the atom ok, and +BoolExpr will not be evaluated.

+ +Typical usage: +
   ?assert(f(X, Y) =:= [])
+ +The assert macro can be used anywhere in a program, not just in unit +tests, to check pre/postconditions and invariants. For example: +
   some_recursive_function(X, Y, Z) ->
+       ?assert(X + Y > Z),
+       ...
+
+
assertNot(BoolExpr)
+
Equivalent to assert(not (BoolExpr)). +
+
assertMatch(GuardedPattern, Expr)
+

Evaluates Expr and matches the result against GuardedPattern, if +testing is enabled. If the match fails, an informative exception will be +generated; see the assert macro for further details. GuardedPattern +can be anything that you can write on the left hand side of the -> +symbol in a case-clause, except that it cannot contain comma-separated +guard tests.

+ +

The main reason for using assertMatch also for simple matches, instead +of matching with =, is that it produces more detailed error messages.

+ +Examples: +
   ?assertMatch({found, {fred, _}}, lookup(bloggs, Table))
+
   ?assertMatch([X|_] when X > 0, binary_to_list(B))
+
+
assertEqual(Expect, Expr)
+

Evaluates the expressions Expect and Expr and compares the +results for equality, if testing is enabled. If the values are not +equal, an informative exception will be generated; see the assert +macro for further details.

+ +

assertEqual is more suitable than than assertMatch when the +left-hand side is a computed value rather than a simple pattern, and +gives more details than ?assert(Expect =:= Expr).

+ +Examples: +
   ?assertEqual("b" ++ "a", lists:reverse("ab"))
+
   ?assertEqual(foo(X), bar(Y))
+
+
assertException(ClassPattern, TermPattern, Expr)
+
assertError(TermPattern, Expr)
+
assertExit(TermPattern, Expr)
+
assertThrow(TermPattern, Expr)
+

Evaluates Expr, catching any exception and testing that it matches +the expected ClassPattern:TermPattern. If the match fails, or if no +exception is thrown by Expr, an informative exception will be +generated; see the assert macro for further details. The +assertError, assertExit, and assertThrow macros, are equivalent to +using assertException with a ClassPattern of error, exit, or +throw, respectively.

+ +Examples: +
   ?assertError(badarith, X/0)
+
   ?assertExit(normal, exit(normal))
+
   ?assertException(throw, {not_found,_}, throw({not_found,42}))
+
+
+ +

Macros for running external commands

+ +

Keep in mind that external commands are highly dependent on the +operating system. You can use the standard library function os:type() +in test generator functions, to produce different sets of tests +depending on the current operating system.

+ +

Note: these macros introduce a run-time dependency on the EUnit library +code, if compiled with testing enabled.

+ +
+
assertCmd(CommandString)
+

Runs CommandString as an external command, if testing is enabled. +Unless the returned status value is 0, an informative exception will be +generated. If there is no exception, the result of the macro expression +is the atom ok. If testing is disabled, the macro will not generate +any code except the atom ok, and the command will not be executed.

+ +Typical usage: +
   ?assertCmd("mkdir foo")
+
+
assertCmdStatus(N, CommandString)
+
Like the assertCmd(CommandString) macro, but generates an +exception unless the returned status value is N. +
+
assertCmdOutput(Text, CommandString)
+
Runs CommandString as an external command, if testing is enabled. +Unless the output produced by the command exactly matches the specified +string Text, an informative exception will be generated. (Note that +the output is normalized to use a single LF character as line break on +all platforms.) If there is no exception, the result of the macro +expression is the atom ok. If testing is disabled, the macro will not +generate any code except the atom ok, and the command will not be +executed. +
+
cmd(CommandString)
+

Runs CommandString as an external command. Unless the returned +status value is 0 (indicating success), an informative exception will be +generated; otherwise, the result of the macro expression is the output +produced by the command, as a flat string. The output is normalized to +use a single LF character as line break on all platforms.

+ +

This macro is useful in the setup and cleanup sections of fixtures, +e.g., for creating and deleting files or perform similar operating +system specific tasks, to make sure that the test system is informed of +any failures.

+ +A Unix-specific example: +
   {setup,
+    fun () -> ?cmd("mktemp") end,
+    fun (FileName) -> ?cmd("rm " ++ FileName) end,
+    ...}
+
+
+ +

Debugging macros

+ +

To help with debugging, EUnit defines several useful macros for printing +messages directly to the console (rather than to the standard output). +Furthermore, these macros all use the same basic format, which includes +the file and line number where they occur, making it possible in some +development environments (e.g., when running Erlang in an Emacs buffer) +to simply click on the message and jump directly to the corresponding +line in the code.

+ +

If the macro NODEBUG is defined before the EUnit header file is +included, these macros have no effect; see +Compilation control macros for details.

+ +
+
debugHere
+
Just prints a marker showing the current file and line number. Note +that this is an argument-less macro. The result is always ok.
+
debugMsg(Text)
+
Outputs the message Text (which can be a plain string, an IO-list, +or just an atom). The result is always ok.
+
debugFmt(FmtString, Args)
+
This formats the text like io:format(FmtString, Args) and outputs +it like debugMsg. The result is always ok.
+
debugVal(Expr)
+
Prints both the source code for Expr and its current value. E.g., +?debugVal(f(X)) might be displayed as "f(X) = 42". (Large terms are +shown truncated.) The result is always the value of Expr, so this +macro can be wrapped around any expression to display its value when +the code is compiled with debugging enabled.
+
debugTime(Text,Expr)
+
Prints Text and the wall clock time for evaluation of Expr. The +result is always the value of Expr, so this macro can be wrapped +around any expression to show its run time when the code is compiled +with debugging enabled. For example, List1 = ?debugTime("sorting", +lists:sort(List)) might show as "sorting: 0.015 s".
+ +
+ + +

EUnit test representation

+ +

The way EUnit represents tests and test sets as data is flexible, +powerful, and concise. This section describes the representation in +detail.

+ + + +

Simple test objects

+ +A simple test object is one of the following: +
    +
  • A nullary functional value (i.e., a fun that takes zero + arguments). Examples: +
       fun () -> ... end
    +
       fun some_function/0
    +
       fun some_module:some_function/0
    +
  • +
  • A pair of atoms {ModuleName, FunctionName}, referring to the + function ModuleName:FunctionName/0
  • +
  • A pair {LineNumber, SimpleTest}, where LineNumber is a + nonnegative integer and SimpleTest is another simple test + object. LineNumber should indicate the source line of the test. + Pairs like this are usually only created via ?_test(...) macros; + see Basic macros.
  • +

+In brief, a simple test object consists of a single function that takes +no arguments (possibly annotated with some additional metadata, i.e., a +line number). Evaluation of the function either succeeds, by +returning some value (which is ignored), or fails, by throwing +an exception.

+ +

Test sets and deep lists

+ +

A test set can be easily created by placing a sequence of test objects +in a list. If T_1, ..., T_N are individual test objects, then [T_1, +..., T_N] is a test set consisting of those objects (in that order).

+ +

Test sets can be joined in the same way: if S_1, ..., S_K are test +sets, then [S_1, ..., S_K] is also a test set, where the tests of +S_i are ordered before those of S_(i+1), for each subset S_i.

+ +

Thus, the main representation of test sets is deep lists, and +a simple test object can be viewed as a test set containing only a +single test; there is no difference between T and [T].

+ +

A module can also be used to represent a test set; see ModuleName +under Primitives below.

+ +

Titles

+ +

Any test or test set T can be annotated with a title, by wrapping it +in a pair {Title, T}, where Title is a string. For convenience, any +test which is normally represented using a tuple can simply be given a +title string as the first element, i.e., writing {"The Title", ...} +instead of adding an extra tuple wrapper as in {"The Title", {...}}.

+ + +

Primitives

+ +The following are primitives, which do not contain other test sets as +arguments: +
+
ModuleName::atom() +
+
A single atom represents a module name, and is equivalent to +{module, ModuleName}. This is often used as in the call +eunit:test(some_module). +
+
{module, ModuleName::atom()} +
+

This composes a test set from the exported test functions of the +named module, i.e., those functions with arity zero whose names end +with _test or _test_. Basically, the ..._test() functions become +simple tests, while the ..._test_() functions become generators.

+ +In addition, EUnit will also look for another module whose name is +ModuleName plus the suffix _tests, and if it exists, all the tests +from that module will also be added. (If ModuleName already contains +the suffix _tests, this is not done.) E.g., the specification +{module, mymodule} will run all tests in the modules mymodule and +mymodule_tests. Typically, the _tests module should only contain +test cases that use the public interface of the main module (and no +other code). +
+
{application, AppName::atom(), Info::list()} +
+
This is a normal Erlang/OTP application descriptor, as found in an + .app file. The resulting test set consists of the modules listed in + the modules entry in Info. +
+
{application, AppName::atom()} +
+
This creates a test set from all the modules belonging to the +specified application, by consulting the application's .app file +(see {file, FileName}), or if no such file exists, by testing all +object files in the application's ebin-directory (see {dir, +Path}); if that does not exist, the code:lib_dir(AppName) directory +is used. +
+
Path::string() +
+
A single string represents the path of a file or directory, and is +equivalent to {file, Path}, or {dir, Path}, respectively, depending +on what Path refers to in the file system. +
+
{file, FileName::string()} +
+

If FileName has a suffix that indicates an object file (.beam), +EUnit will try to reload the module from the specified file and test it. +Otherwise, the file is assumed to be a text file containing test +specifications, which will be read using the standard library function +file:path_consult/2.

+ +Unless the file name is absolute, the file is first searched for +relative to the current directory, and then using the normal search path +(code:get_path()). This means that the names of typical "app" files +can be used directly, without a path, e.g., "mnesia.app". +
+
{dir, Path::string()} +
+
This tests all object files in the specified directory, as if they +had been individually specified using {file, FileName}. +
+
{generator, GenFun::(() -> Tests)} +
+
The generator function GenFun is called to produce a test +set. +
+
{generator, ModuleName::atom(), FunctionName::atom()} +
+
The function ModuleName:FunctionName() is called to produce a test +set. +
+
{with, X::any(), [AbstractTestFun::((any()) -> any())]} +
+
Distributes the value X over the unary functions in the list, +turning them into nullary test functions. An AbstractTestFun is like +an ordinary test fun, but takes one argument instead of zero - it's +basically missing some information before it can be a proper test. In +practice, {with, X, [F_1, ..., F_N]} is equivalent to [fun () -> +F_1(X) end, ..., fun () -> F_N(X) end]. This is particularly useful if +your abstract test functions are already implemented as proper +functions: {with, FD, [fun filetest_a/1, fun filetest_b/1, fun +filetest_c/1]} is equivalent to [fun () -> filetest_a(FD) end, fun () +-> filetest_b(FD) end, fun () -> filetest_c(FD) end], but much more +compact. See also Fixtures, below. +
+
+ +

Control

+ +The following representations control how and where tests are executed: +
+
{spawn, Tests}
+
Runs the specified tests in a separate subprocess, while the current +test process waits for it to finish. This is useful for tests that need +a fresh, isolated process state. (Note that EUnit always starts at least +one such a subprocess automatically; tests are never executed by the +caller's own process.)
+
{spawn, Node::atom(), Tests}
+
Like {spawn, Tests}, but runs the specified tests on the given +Erlang node.
+
{timeout, Time::number(), Tests}
+
Runs the specified tests under the given timeout. Time is in +seconds; e.g., 60 means one minute and 0.1 means 1/10th of a second. If +the timeout is exceeded, the unfinished tests will be forced to +terminate. Note that if a timeout is set around a fixture, it includes +the time for setup and cleanup, and if the timeout is triggered, the +entire fixture is abruptly terminated (without running the +cleanup).
+
{inorder, Tests}
+
Runs the specified tests in strict order. Also see {inparallel, +Tests}. By default, tests are neither marked as inorder or +inparallel, but may be executed as the test framework chooses.
+
{inparallel, Tests}
+
Runs the specified tests in parallel (if possible). Also see +{inorder, Tests}.
+
{inparallel, N::integer(), Tests}
+
Like {inparallel, Tests}, but running no more than N subtests +simultaneously.
+
+ +

Fixtures

+ +

A "fixture" is some state that is necessary for a particular set of +tests to run. EUnit's support for fixtures makes it easy to set up such +state locally for a test set, and automatically tear it down again when +the test set is finished, regardless of the outcome (success, failures, +timeouts, etc.).

+ +

To make the descriptions simpler, we first list some definitions: +

+ + + + + + + + + + + + + + + + + + + +
Setup() -> (R::any())
SetupX(X::any()) -> (R::any())
Cleanup(R::any()) -> any()
CleanupX(X::any(), R::any()) -> any()
Instantiator((R::any()) -> Tests) | {with, [AbstractTestFun::((any()) -> any())]}
Wherelocal | spawn | {spawn, Node::atom()}
+
+(these are explained in more detail further below.)

+ +The following representations specify fixture handling for test sets: +
+
{setup, Setup, Tests | Instantiator}
+
{setup, Setup, Cleanup, Tests | Instantiator}
+
{setup, Where, Setup, Tests | Instantiator}
+
{setup, Where, Setup, Cleanup, Tests | Instantiator}
+
setup sets up a single fixture for running all of the specified +tests, with optional teardown afterwards. The arguments are described in +detail below. +
+
{node, Node::atom(), Tests | Instantiator}
+
{node, Node::atom(), Args::string(), Tests | Instantiator}
+
node is like setup, but with a built-in behaviour: it starts a +slave node for the duration of the tests. The atom Node should have +the format nodename@full.machine.name, and Args are the optional +arguments to the new node; see slave:start_link/3 for details. +
+
{foreach, Where, Setup, Cleanup, [Tests | Instantiator]}
+
{foreach, Setup, Cleanup, [Tests | Instantiator]}
+
{foreach, Where, Setup, [Tests | Instantiator]}
+
{foreach, Setup, [Tests | Instantiator]}
+
foreach is used to set up a fixture and optionally tear it down +afterwards, repeated for each single one of the specified test sets. +
+
{foreachx, Where, SetupX, CleanupX, + Pairs::[{X::any(), ((X::any(), R::any()) -> Tests)}]}
+
{foreachx, SetupX, CleanupX, Pairs}
+
{foreachx, Where, SetupX, Pairs}
+
{foreachx, SetupX, Pairs}
+
foreachx is like foreach, but uses a list of pairs, each +containing an extra argument X and an extended instantiator function. +
+
+ +

A Setup function is executed just before any of the specified tests +are run, and a Cleanup function is executed when no more of the +specified tests will be run, regardless of the reason. A Setup +function takes no argument, and returns some value which will be passed +as it is to the Cleanup function. A Cleanup function should do +whatever necessary and return some arbitrary value, such as the atom +ok. (SetupX and CleanupX functions are similar, but receive one +additional argument: some value X, which depends on the context.) When +no Cleanup function is specified, a dummy function is used which has +no effect.

+ +

An Instantiator function receives the same value as the Cleanup +function, i.e., the value returned by the Setup function. It should +then behave much like a generator (see Primitives), and +return a test set whose tests have been instantiated with the +given value. A special case is the syntax {with, [AbstractTestFun]} +which represents an instantiator function that distributes the value +over a list of unary functions; see Primitives: {with, X, +[...]} for more details.

+ +A Where term controls how the specified tests are executed. The +default is spawn, which means that the current process handles the +setup and teardown, while the tests are executed in a subprocess. +{spawn, Node} is like spawn, but runs the subprocess on the +specified node. local means that the current process will handle both +setup/teardown and running the tests - the drawback is that if a test +times out so that the process is killed, the cleanup will not be +performed; hence, avoid this for persistent fixtures such as file +operations. In general, 'local' should only be used when: +
    +
  • the setup/teardown needs to be executed by the process that will + run the tests;
  • +
  • no further teardown needs to be done if the process is killed + (i.e., no state outside the process was affected by the setup)
  • +
+ +

Lazy generators

+ +

Sometimes, it can be convenient not to produce the whole set of test +descriptions before the testing begins; for example, if you want to +generate a huge amount of tests that would take up too much space to +keep in memory all at once.

+ +

It is fairly easy to write a generator which, each time it is called, +either produces an empty list if it is done, or otherwise produces a +list containing a single test case plus a new generator which will +produce the rest of the tests. This demonstrates the basic pattern:

+ +
   lazy_test_() ->
+       lazy_gen(10000).
+
+   lazy_gen(N) ->
+       {generator,
+        fun () ->
+            if N > 0 ->
+                   [?_test(...)
+                    | lazy_gen(N-1)];
+               true ->
+                   []
+            end
+        end}.
+ +

When EUnit traverses the test representation in order to run the tests, +the new generator will not be called to produce the next test until the +previous test has been executed.

+ +Note that it is easiest to write this kind of recursive generator using +a help function, like the lazy_gen/1 function above. It can also be +written using a recursive fun, if you prefer to not clutter your +function namespace and are comfortable with writing that kind of code. + +
+ +

Generated by EDoc, Apr 22 2009, 22:37:19.

+ + diff --git a/lib/eunit/doc/overview.edoc b/lib/eunit/doc/overview.edoc new file mode 100644 index 0000000000..2583f0be25 --- /dev/null +++ b/lib/eunit/doc/overview.edoc @@ -0,0 +1,1028 @@ + + -*- html -*- + + EUnit overview page + +@title EUnit - a Lightweight Unit Testing Framework for Erlang + +@author Richard Carlsson + [http://user.it.uu.se/~richardc/] +@author Mickaël Rémond + [http://www.process-one.net/] +@copyright 2004-2007 Mickaël Rémond, Richard Carlsson +@version {@version}, {@date} {@time} + +@doc EUnit is a unit testing framework for Erlang. It is very powerful +and flexible, is easy to use, and has small syntactical overhead. + +
    +
  • {@section Unit testing}
  • +
  • {@section Terminology}
  • +
  • {@section Getting started}
  • +
  • {@section EUnit macros}
  • +
  • {@section EUnit test representation}
  • +
+ +EUnit builds on ideas from the family of unit testing frameworks for +Object Oriented languages that originated with JUnit by Beck and Gamma +(and Beck's previous framework SUnit for Smalltalk). However, EUnit uses +techniques more adapted to functional and concurrent programming, and is +typically less verbose than its relatives. + +Although EUnit uses many preprocessor macros, they have been designed to +be as nonintrusive as possible, and should not cause conflicts with +existing code. Adding EUnit tests to a module should thus not normally +require changing existing code. Furthermore, tests that only exercise +the exported functions of a module can always be placed in a completely +separate module, avoiding any conflicts entirely. + +== Unit testing == + +Unit Testing is testing of individual program "units" in relative +isolation. There is no particular size requirement: a unit can be a +function, a module, a process, or even a whole application, but the most +typical testing units are individual functions or modules. In order to +test a unit, you specify a set of individual tests, set up the smallest +necessary environment for being able to run those tests (often, you +don't need to do any setup at all), you run the tests and collect the +results, and finally you do any necessary cleanup so that the test can +be run again later. A Unit Testing Framework tries to help you in each +stage of this process, so that it is easy to write tests, easy to run +them, and easy to see which tests failed (so you can fix the bugs). + +=== Advantages of unit testing === + +
+
Reduces the risks of changing the program
+
Most programs will be modified during their lifetime: bugs will be + fixed, features will be added, optimizations may become necessary, or + the code will need to be refactored or cleaned up in other ways to + make it easier to work with. But every change to a working program is + a risk of introducing new bugs - or reintroducing bugs that had + previously been fixed. Having a set of unit tests that you can run + with very little effort makes it easy to know that the code still + works as it should (this use is called regression testing; + see {@section Terminology}). This goes a long way to reduce the + resistance to changing and refactoring code.
+
Helps guide and speed up the development process
+
By focusing on getting the code to pass the tests, the programmer + can become more productive, not overspecify or get lost in premature + optimizations, and create code that is correct from the very beginning + (so-called test-driven development; see {@section + Terminology}).
+
Helps separate interface from implementation
+
When writing tests, the programmer may discover dependencies + (in order to get the tests to run) that ought not to be there, and + which need to be abstracted away to get a cleaner design. This helps + eliminate bad dependencies before they spread throughout the + code.
+
Makes component integration easier
+
By testing in a bottom-up fashion, beginning with the smallest + program units and creating a confidence in that they work as they + should, it becomes easier to test that a higher-level component, + consisting of several such units, also behaves according to + specification (known as integration testing; see {@section + Terminology}).
+
Is self-documenting
+
The tests can be read as documentation, typically showing both + examples of correct and incorrect usage, along with the expected + consequences.
+
+ +== Terminology == + +
+
Unit testing
+
Testing that a program unit behaves as it is supposed to do (in + itself), according to its specifications. Unit tests have an important + function as regression tests, when the program later is modified for + some reason, since they check that the program still behaves according + to specification.
+
Regression testing
+
Running a set of tests after making changes to a program, to check + that the program behaves as it did before the changes (except, of + course, for any intentional changes in behaviour). Unit tests are + important as regression tests, but regression testing can involve more + than just unit testing, and may also test behaviour that might not be + part of the normal specification (such as bug-for-bug-compatibility). +
+
Integration testing
+
Testing that a number of individually developed program units + (assumed to already have been separately unit tested) work together as + expected. Depending on the system being developed, integration testing + may be as simple as "just another level of unit testing", but might + also involve other kinds of tests (compare system testing). +
+
System testing
+
Testing that a complete system behaves according to its + specification. Specifically, system testing should not require knowing + any details about the implementation. It typically involves testing + many different aspects of the system behaviour apart from the basic + functionality, such as performance, usability, and reliability.
+
Test-driven development
+
A program development technique where you continuously write tests + before you implement the code that is supposed to pass those + tests. This can help you focus on solving the right problems, and not + make a more complicated implementation than necessary, by letting the + unit tests determine when a program is "done": if it fulfils its + specifications, there is no need to keep adding functionality.
+
Mock object
+
Sometimes, testing some unit `A' (e.g., a function) requires that + it collaborates somehow with some other unit `B' (perhaps being passed + as an argument, or by reference) - but `B' has not been implemented + yet. A "mock object" - an object which, for the purposes of testing + `A', looks and behaves like a real `B' - might then be used instead. + (This is of course only useful if it would be significantly more work + to implement a real `B' than to create a mock object.)
+
Test case
+
A single, well-defined test, that somehow can be uniquely + identified. When executed, the test case either passes or + fails; the test report should identify exactly which test + cases failed.
+
Test suite
+
A collection of test cases, generally with a specific, common + target for testing, such as a single function, module, or subsystem. A + test suite may also be recursively composed by smaller test + suites.
+
+ +== Getting started == +
    +
  • {@section Including the EUnit header file}
  • +
  • {@section Writing simple test functions}
  • +
  • {@section Running EUnit}
  • +
  • {@section Writing test generating functions}
  • +
  • {@section An example}
  • +
  • {@section Disabling testing}
  • +
  • {@section Avoiding compile-time dependency on EUnit}
  • +
+ +=== Including the EUnit header file === + +The simplest way to use EUnit in an Erlang module is to add the +following line at the beginning of the module (after the `-module' +declaration, but before any function definitions): +```-include_lib("eunit/include/eunit.hrl").''' + +This will have the following effect: +
    +
  • Creates an exported function `test()' (unless testing is turned + off, and the module does not already contain a test() function), that + can be used to run all the unit tests defined in the module
  • +
  • Causes all functions whose names match `..._test()' or `..._test_()' + to be automatically exported from the module (unless testing is + turned off, or the `EUNIT_NOAUTO' macro is defined)
  • +
  • Makes all the preprocessor macros of EUnit available, to help + writing tests
  • +
+ +Note: For `-include_lib(...)' to work, the Erlang +module search path must contain a directory whose name ends in +`eunit/ebin' (pointing to the `ebin' subdirectory of the EUnit +installation directory). If EUnit is installed as `lib/eunit' under your +Erlang/OTP system directory, its `ebin' subdirectory will be +automatically added to the search path when Erlang starts. Otherwise, +you need to add the directory explicitly, by passing a `-pa' flag to the +`erl' or `erlc' command. For example, a Makefile could contain the +following action for compiling `.erl' files: +```erlc -pa "path/to/eunit/ebin" $(ERL_COMPILE_FLAGS) -o$(EBIN) $<''' +or if you want Eunit to always be available when you run Erlang +interactively, you can add a line like the following to your +`$HOME/.erlang' file: +```code:add_path("/path/to/eunit/ebin").''' + +=== Writing simple test functions === + +The EUnit framework makes it extremely easy to write unit tests in +Erlang. There are a few different ways of writing them, though, so we +start with the simplest: + +A function with a name ending in `..._test()' is recognized by EUnit as +a simple test function - it takes no arguments, and its execution either +succeeds (returning some arbitrary value that EUnit will throw away), or +fails by throwing an exception of some kind (or by not terminating, in +which case it will be aborted after a while). + +An example of a simple test function could be the following: +```reverse_test() -> lists:reverse([1,2,3]).''' +This just tests that the function `lists:reverse(List)' does not crash +when `List' is `[1,2,3]'. It is not a great test, but many people write +simple functions like this one to test the basic functionality of their +code, and those tests can be used directly by EUnit, without changes, +as long as their function names match. + +==== Use exceptions to signal failure ==== + +To write more interesting tests, we need to make them crash (throw an +exception) when they don't get the result they expect. A simple way of +doing this is to use pattern matching with `=', as in the following +examples: +```reverse_nil_test() -> [] = lists:reverse([]). + reverse_one_test() -> [1] = lists:reverse([1]). + reverse_two_test() -> [2,1] = lists:reverse([1,2]). +''' +If there was some bug in `lists:reverse/1' that made it return something +other than `[2,1]' when it got `[1,2]' as input, then the last test +above would throw a `badmatch' error. The first two (we assume they do +not get a `badmatch') would simply return `[]' and `[1]', respectively, +so both succeed. (Note that EUnit is not psychic: if you write a test +that returns a value, even if it is the wrong value, EUnit will consider +it a success. You must make sure that the test is written so that it +causes a crash if the result is not what it should be.) + +==== Using assert macros ==== + +If you want to use Boolean operators for your tests, the `assert' +macro comes in handy (see {@section EUnit macros} for details): +```length_test() -> ?assert(length([1,2,3]) =:= 3).''' +The `?assert(Expression)' macro will evaluate `Expression', and if that +does not evaluate to `true', it will throw an exception; otherwise it +just returns `ok'. In the above example, the test will thus fail if the +call to `length' does not return 3. + +=== Running EUnit === + +If you have added the declaration +`-include_lib("eunit/include/eunit.hrl")' to your module, as described +above, you only need to compile the module, and run the automatically +exported function `test()'. For example, if your module was named `m', +then calling `m:test()' will run EUnit on all the tests defined in the +module. You do not need to write `-export' declarations for the test +functions. This is all done by magic. + +You can also use the function {@link eunit:test/1} to run arbitrary +tests, for example to try out some more advanced test descriptors (see +{@section EUnit test representation}). For example, running +``eunit:test(m)'' does the same thing as the auto-generated function +``m:test()'', while ``eunit:test({inparallel, m})'' runs the same test +cases but executes them all in parallel. + +==== Putting tests in separate modules ==== + +If you want to separate your test code from your normal code (at least +for testing the exported functions), you can simply write the test +functions in a module named `m_tests' (note: not `m_test'), if your +module is named `m'. Then, whenever you ask EUnit to test the module +`m', it will also look for the module `m_tests' and run those tests as +well. See `ModuleName' in the section {@section Primitives} for details. + +==== EUnit captures standard output ==== + +If your test code writes to the standard output, you may be surprised to +see that the text does not appear on the console when the tests are +running. This is because EUnit captures all standard output from test +functions (this also includes setup and cleanup functions, but not +generator functions), so that it can be included in the test report if +errors occur. To bypass EUnit and print text directly to the console +while testing, you can write to the `user' output stream, as in +`io:format(user, "~w", [Term])'. The recommended way of doing this is to +use the EUnit {@section Debugging macros}, which make it much simpler. + +=== Writing test generating functions === + +A drawback of simple test functions is that you must write a separate +function (with a separate name) for each test case. A more compact way +of writing tests (and much more flexible, as we shall see), is to write +functions that return tests, instead of being tests. + +A function with a name ending in `..._test_()' (note the final +underscore) is recognized by EUnit as a test generator +function. Test generators return a representation of a set +of tests to be executed by EUnit. + +==== Representing a test as data ==== + +The most basic representation of a test is a single fun-expression that +takes no arguments. For example, the following test generator: +```basic_test_() -> + fun () -> ?assert(1 + 1 =:= 2) end.''' +will have the same effect as the following simple test: +```simple_test() -> + ?assert(1 + 1 =:= 2).''' +(in fact, EUnit will handle all simple tests just like it handles +fun-expressions: it will put them in a list, and run them one by one). + +==== Using macros to write tests ==== + +To make tests more compact and readable, as well as automatically add +information about the line number in the source code where a test +occurred (and reduce the number of characters you have to type), you can +use the `_test' macro (note the initial underscore character), like +this: +```basic_test_() -> + ?_test(?assert(1 + 1 =:= 2)).''' +The `_test' macro takes any expression (the "body") as argument, and +places it within a fun-expression (along with some extra information). +The body can be any kind of test expression, just like the body of a +simple test function. + +==== Underscore-prefixed macros create test objects ==== + +But this example can be made even shorter! Most test macros, such as the +family of `assert' macros, have a corresponding form with an initial +underscore character, which automatically adds a `?_test(...)' wrapper. +The above example can then simply be written: +```basic_test_() -> + ?_assert(1 + 1 =:= 2).''' +which has exactly the same meaning (note the `_assert' instead of +`assert'). You can think of the initial underscore as signalling +test object. + +=== An example === + +Sometimes, an example says more than a thousand words. The following +small Erlang module shows how EUnit can be used in practice. +```-module(fib). + -export([fib/1]). + -include_lib("eunit/include/eunit.hrl"). + + fib(0) -> 1; + fib(1) -> 1; + fib(N) when N > 1 -> fib(N-1) + fib(N-2). + + fib_test_() -> + [?_assert(fib(0) =:= 1), + ?_assert(fib(1) =:= 1), + ?_assert(fib(2) =:= 2), + ?_assert(fib(3) =:= 3), + ?_assert(fib(4) =:= 5), + ?_assert(fib(5) =:= 8), + ?_assertException(error, function_clause, fib(-1)), + ?_assert(fib(31) =:= 2178309) + ].''' + +(Author's note: When I first wrote this example, I happened to write a +`*' instead of `+' in the `fib' function. Of course, this showed up +immediately when I ran the tests.) + +See {@section EUnit test representation} for a full list of all the ways +you can specify test sets in EUnit. + +=== Disabling testing === + +Testing can be turned off by defining the `NOTEST' macro when compiling, +for example as an option to `erlc', as in: +```erlc -DNOTEST my_module.erl''' +or by adding a macro definition to the code, before the EUnit header +file is included: +```-define(NOTEST, 1).''' +(the value is not important, but should typically be 1 or `true'). +Note that unless the `EUNIT_NOAUTO' macro is defined, disabling testing +will also automatically strip all test functions from the code, except +for any that are explicitly declared as exported. + +For instance, to use EUnit in your application, but with testing turned +off by default, put the following lines in a header file: +```-define(NOTEST, true). + -include_lib("eunit/include/eunit.hrl").''' +and then make sure that every module of your application includes that +header file. This means that you have a only a single place to modify in +order to change the default setting for testing. To override the `NOTEST' +setting without modifying the code, you can define `TEST' in a compiler +option, like this: +```erlc -DTEST my_module.erl''' + +See {@section Compilation control macros} for details about these +macros. + +=== Avoiding compile-time dependency on EUnit === + +If you are distributing the source code for your application for other +people to compile and run, you probably want to ensure that the code +compiles even if EUnit is not available. Like the example in the +previous section, you can put the following lines in a common header +file: +```-ifdef(TEST). + -include_lib("eunit/include/eunit.hrl"). + -endif.''' +and, of course, also make sure that you place all test code that uses +EUnit macros within `-ifdef(TEST)' or `-ifdef(EUNIT)' sections. + + +== EUnit macros == + +Although all the functionality of EUnit is available even without the +use of preprocessor macros, the EUnit header file defines a number of +such macros in order to make it as easy as possible to write unit tests +as compactly as possible and without getting too many details in the +way. + +Except where explicitly stated, using EUnit macros will never introduce +run-time dependencies on the EUnit library code, regardless of whether +your code is compiled with testing enabled or disabled. + +
    +
  • {@section Basic macros}
  • +
  • {@section Compilation control macros}
  • +
  • {@section Utility macros}
  • +
  • {@section Assert macros}
  • +
  • {@section Macros for running external commands}
  • +
  • {@section Debugging macros}
  • +
+ +=== Basic macros === + +
+
`_test(Expr)'
+
Turns `Expr' into a "test object", by wrapping it in a +fun-expression and a source line number. Technically, this is the same +as `{?LINE, fun () -> (Expr) end}'. +
+
+ +=== Compilation control macros === + +
+
`EUNIT'
+
This macro is always defined to `true' whenever EUnit is enabled at +compile time. This is typically used to place testing code within +conditional compilation, as in: +```-ifdef(EUNIT). + % test code here + ... + -endif.''' +e.g., to ensure that the code can be compiled without including the +EUnit header file, when testing is disabled. See also the macros `TEST' +and `NOTEST'. +
+ +
`EUNIT_NOAUTO'
+
If this macro is defined, the automatic exporting or stripping of +test functions will be disabled. +
+ +
`TEST'
+
This macro is always defined (to `true', unless previously defined +by the user to have another value) whenever EUnit is enabled at compile +time. This can be used to place testing code within conditional +compilation; see also the macros `NOTEST' and `EUNIT'. + +For testing code that is strictly dependent on EUnit, it may be +preferable to use the `EUNIT' macro for this purpose, while for code +that uses more generic testing conventions, using the `TEST' macro may +be preferred. + +The `TEST' macro can also be used to override the `NOTEST' macro. If +`TEST' is defined before the EUnit header file is +included (even if `NOTEST' is also defined), then the code will be +compiled with EUnit enabled. +
+ +
`NOTEST'
+
This macro is always defined (to `true', unless previously defined +by the user to have another value) whenever EUnit is disabled +at compile time. (Compare the `TEST' macro.) + +This macro can also be used for conditional compilation, but is more +typically used to disable testing: If `NOTEST' is defined +before the EUnit header file is included, and `TEST' +is not defined, then the code will be compiled with EUnit +disabled. See also {@section Disabling testing}. +
+ +
`NOASSERT'
+
If this macro is defined, the assert macros will have no effect, +when testing is also disabled. See {@section Assert macros}. When +testing is enabled, the assert macros are always enabled automatically +and cannot be disabled. +
+ +
`ASSERT'
+
If this macro is defined, it overrides the NOASSERT macro, forcing +the assert macros to always be enabled regardless of other settings. +
+ +
`NODEBUG'
+
If this macro is defined, the debugging macros will have no effect. +See {@section Debugging macros}. `NODEBUG' also implies `NOASSERT', +unless testing is enabled. +
+ +
`DEBUG'
+
If this macro is defined, it overrides the NODEBUG macro, forcing +the debugging macros to be enabled. +
+
+ +=== Utility macros === + +The following macros can make tests more compact and readable: + +
+
`LET(Var,Arg,Expr)'
+
Creates a local binding `Var = Arg' in `Expr'. (This is the same as +`(fun(Var)->(Expr)end)(Arg)'.) Note that the binding is not exported +outside of `Expr', and that within `Expr', this binding of `Var' will +shadow any binding of `Var' in the surrounding scope. +
+
`IF(Cond,TrueCase,FalseCase)'
+
Evaluates `TrueCase' if `Cond' evaluates to `true', or otherwise +evaluates `FalseCase' if `Cond' evaluates to `false'. (This is the same +as `(case (Cond) of true->(TrueCase); false->(FalseCase) end)'.) Note +that it is an error if `Cond' does not yield a boolean value. +
+
+ +=== Assert macros === + +(Note that these macros also have corresponding forms which start with +an "`_'" (underscore) character, as in `?_assert(BoolExpr)', that create +a "test object" instead of performing the test immediately. This is +equivalent to writing `?_test(assert(BoolExpr))', etc.) + +If the macro `NOASSERT' is defined before the EUnit header file is +included, these macros have no effect when testing is also disabled; see +{@section Compilation control macros} for details. + +
+
`assert(BoolExpr)'
+
Evaluates the expression `BoolExpr', if testing is enabled. Unless +the result is `true', an informative exception will be generated. If +there is no exception, the result of the macro expression is the atom +`ok', and the value of `BoolExpr' is discarded. If testing is disabled, +the macro will not generate any code except the atom `ok', and +`BoolExpr' will not be evaluated. + +Typical usage: +```?assert(f(X, Y) =:= [])''' + +The `assert' macro can be used anywhere in a program, not just in unit +tests, to check pre/postconditions and invariants. For example: +```some_recursive_function(X, Y, Z) -> + ?assert(X + Y > Z), + ...''' +
+
`assertNot(BoolExpr)'
+
Equivalent to `assert(not (BoolExpr))'. +
+
`assertMatch(GuardedPattern, Expr)'
+
Evaluates `Expr' and matches the result against `GuardedPattern', if +testing is enabled. If the match fails, an informative exception will be +generated; see the `assert' macro for further details. `GuardedPattern' +can be anything that you can write on the left hand side of the `->' +symbol in a case-clause, except that it cannot contain comma-separated +guard tests. + +The main reason for using `assertMatch' also for simple matches, instead +of matching with `=', is that it produces more detailed error messages. + +Examples: +```?assertMatch({found, {fred, _}}, lookup(bloggs, Table))''' +```?assertMatch([X|_] when X > 0, binary_to_list(B))''' +
+
`assertEqual(Expect, Expr)'
+
Evaluates the expressions `Expect' and `Expr' and compares the +results for equality, if testing is enabled. If the values are not +equal, an informative exception will be generated; see the `assert' +macro for further details. + +`assertEqual' is more suitable than than `assertMatch' when the +left-hand side is a computed value rather than a simple pattern, and +gives more details than `?assert(Expect =:= Expr)'. + +Examples: +```?assertEqual("b" ++ "a", lists:reverse("ab"))''' +```?assertEqual(foo(X), bar(Y))''' +
+
`assertException(ClassPattern, TermPattern, Expr)'
+
`assertError(TermPattern, Expr)'
+
`assertExit(TermPattern, Expr)'
+
`assertThrow(TermPattern, Expr)'
+
Evaluates `Expr', catching any exception and testing that it matches +the expected `ClassPattern:TermPattern'. If the match fails, or if no +exception is thrown by `Expr', an informative exception will be +generated; see the `assert' macro for further details. The +`assertError', `assertExit', and `assertThrow' macros, are equivalent to +using `assertException' with a `ClassPattern' of `error', `exit', or +`throw', respectively. + +Examples: +```?assertError(badarith, X/0)''' +```?assertExit(normal, exit(normal))''' +```?assertException(throw, {not_found,_}, throw({not_found,42}))''' +
+
+ +=== Macros for running external commands === + +Keep in mind that external commands are highly dependent on the +operating system. You can use the standard library function `os:type()' +in test generator functions, to produce different sets of tests +depending on the current operating system. + +Note: these macros introduce a run-time dependency on the EUnit library +code, if compiled with testing enabled. + +
+
`assertCmd(CommandString)'
+
Runs `CommandString' as an external command, if testing is enabled. +Unless the returned status value is 0, an informative exception will be +generated. If there is no exception, the result of the macro expression +is the atom `ok'. If testing is disabled, the macro will not generate +any code except the atom `ok', and the command will not be executed. + +Typical usage: +```?assertCmd("mkdir foo")''' +
+
`assertCmdStatus(N, CommandString)'
+
Like the `assertCmd(CommandString)' macro, but generates an +exception unless the returned status value is `N'. +
+
`assertCmdOutput(Text, CommandString)'
+
Runs `CommandString' as an external command, if testing is enabled. +Unless the output produced by the command exactly matches the specified +string `Text', an informative exception will be generated. (Note that +the output is normalized to use a single LF character as line break on +all platforms.) If there is no exception, the result of the macro +expression is the atom `ok'. If testing is disabled, the macro will not +generate any code except the atom `ok', and the command will not be +executed. +
+
`cmd(CommandString)'
+
Runs `CommandString' as an external command. Unless the returned +status value is 0 (indicating success), an informative exception will be +generated; otherwise, the result of the macro expression is the output +produced by the command, as a flat string. The output is normalized to +use a single LF character as line break on all platforms. + +This macro is useful in the setup and cleanup sections of fixtures, +e.g., for creating and deleting files or perform similar operating +system specific tasks, to make sure that the test system is informed of +any failures. + +A Unix-specific example: +```{setup, + fun () -> ?cmd("mktemp") end, + fun (FileName) -> ?cmd("rm " ++ FileName) end, + ...}''' +
+
+ +=== Debugging macros === + +To help with debugging, EUnit defines several useful macros for printing +messages directly to the console (rather than to the standard output). +Furthermore, these macros all use the same basic format, which includes +the file and line number where they occur, making it possible in some +development environments (e.g., when running Erlang in an Emacs buffer) +to simply click on the message and jump directly to the corresponding +line in the code. + +If the macro `NODEBUG' is defined before the EUnit header file is +included, these macros have no effect; see +{@section Compilation control macros} for details. + +
+
`debugHere'
+
Just prints a marker showing the current file and line number. Note +that this is an argument-less macro. The result is always `ok'.
+
`debugMsg(Text)'
+
Outputs the message `Text' (which can be a plain string, an IO-list, +or just an atom). The result is always `ok'.
+
`debugFmt(FmtString, Args)'
+
This formats the text like `io:format(FmtString, Args)' and outputs +it like `debugMsg'. The result is always `ok'.
+
`debugVal(Expr)'
+
Prints both the source code for `Expr' and its current value. E.g., +`?debugVal(f(X))' might be displayed as "`f(X) = 42'". (Large terms are +shown truncated.) The result is always the value of `Expr', so this +macro can be wrapped around any expression to display its value when +the code is compiled with debugging enabled.
+
`debugTime(Text,Expr)'
+
Prints `Text' and the wall clock time for evaluation of `Expr'. The +result is always the value of `Expr', so this macro can be wrapped +around any expression to show its run time when the code is compiled +with debugging enabled. For example, `List1 = ?debugTime("sorting", +lists:sort(List))' might show as "`sorting: 0.015 s'".
+ +
+ + +== EUnit test representation == + +The way EUnit represents tests and test sets as data is flexible, +powerful, and concise. This section describes the representation in +detail. + +
    +
  • {@section Simple test objects}
  • +
  • {@section Test sets and deep lists}
  • +
  • {@section Titles}
  • +
  • {@section Primitives}
  • +
  • {@section Control}
  • +
  • {@section Fixtures}
  • +
  • {@section Lazy generators}
  • +
+ +=== Simple test objects === + +A simple test object is one of the following: +
    +
  • A nullary functional value (i.e., a fun that takes zero + arguments). Examples: +```fun () -> ... end''' +```fun some_function/0''' +```fun some_module:some_function/0''' +
  • +
  • A pair of atoms `{ModuleName, FunctionName}', referring to the + function `ModuleName:FunctionName/0'
  • +
  • A pair `{LineNumber, SimpleTest}', where `LineNumber' is a + nonnegative integer and `SimpleTest' is another simple test + object. `LineNumber' should indicate the source line of the test. + Pairs like this are usually only created via `?_test(...)' macros; + see {@section Basic macros}.
  • +
+In brief, a simple test object consists of a single function that takes +no arguments (possibly annotated with some additional metadata, i.e., a +line number). Evaluation of the function either succeeds, by +returning some value (which is ignored), or fails, by throwing +an exception. + +=== Test sets and deep lists === + +A test set can be easily created by placing a sequence of test objects +in a list. If `T_1', ..., `T_N' are individual test objects, then `[T_1, +..., T_N]' is a test set consisting of those objects (in that order). + +Test sets can be joined in the same way: if `S_1', ..., `S_K' are test +sets, then `[S_1, ..., S_K]' is also a test set, where the tests of +`S_i' are ordered before those of `S_(i+1)', for each subset `S_i'. + +Thus, the main representation of test sets is deep lists, and +a simple test object can be viewed as a test set containing only a +single test; there is no difference between `T' and `[T]'. + +A module can also be used to represent a test set; see `ModuleName' +under {@section Primitives} below. + +=== Titles === + +Any test or test set `T' can be annotated with a title, by wrapping it +in a pair `{Title, T}', where `Title' is a string. For convenience, any +test which is normally represented using a tuple can simply be given a +title string as the first element, i.e., writing `{"The Title", ...}' +instead of adding an extra tuple wrapper as in `{"The Title", {...}}'. + + +=== Primitives === + +The following are primitives, which do not contain other test sets as +arguments: +
+
`ModuleName::atom()' +
+
A single atom represents a module name, and is equivalent to +`{module, ModuleName}'. This is often used as in the call +`eunit:test(some_module)'. +
+
`{module, ModuleName::atom()}' +
+
This composes a test set from the exported test functions of the +named module, i.e., those functions with arity zero whose names end +with `_test' or `_test_'. Basically, the `..._test()' functions become +simple tests, while the `..._test_()' functions become generators. + +In addition, EUnit will also look for another module whose name is +`ModuleName' plus the suffix `_tests', and if it exists, all the tests +from that module will also be added. (If `ModuleName' already contains +the suffix `_tests', this is not done.) E.g., the specification +`{module, mymodule}' will run all tests in the modules `mymodule' and +`mymodule_tests'. Typically, the `_tests' module should only contain +test cases that use the public interface of the main module (and no +other code). +
+
`{application, AppName::atom(), Info::list()}' +
+
This is a normal Erlang/OTP application descriptor, as found in an + `.app' file. The resulting test set consists of the modules listed in + the `modules' entry in `Info'. +
+
`{application, AppName::atom()}' +
+
This creates a test set from all the modules belonging to the +specified application, by consulting the application's `.app' file +(see `{file, FileName}'), or if no such file exists, by testing all +object files in the application's ebin-directory (see `{dir, +Path}'); if that does not exist, the `code:lib_dir(AppName)' directory +is used. +
+
`Path::string()' +
+
A single string represents the path of a file or directory, and is +equivalent to `{file, Path}', or `{dir, Path}', respectively, depending +on what `Path' refers to in the file system. +
+
`{file, FileName::string()}' +
+
If `FileName' has a suffix that indicates an object file (`.beam'), +EUnit will try to reload the module from the specified file and test it. +Otherwise, the file is assumed to be a text file containing test +specifications, which will be read using the standard library function +`file:path_consult/2'. + +Unless the file name is absolute, the file is first searched for +relative to the current directory, and then using the normal search path +(`code:get_path()'). This means that the names of typical "app" files +can be used directly, without a path, e.g., `"mnesia.app"'. +
+
`{dir, Path::string()}' +
+
This tests all object files in the specified directory, as if they +had been individually specified using `{file, FileName}'. +
+
`{generator, GenFun::(() -> Tests)}' +
+
The generator function `GenFun' is called to produce a test +set. +
+
`{generator, ModuleName::atom(), FunctionName::atom()}' +
+
The function `ModuleName:FunctionName()' is called to produce a test +set. +
+
`{with, X::any(), [AbstractTestFun::((any()) -> any())]}' +
+
Distributes the value `X' over the unary functions in the list, +turning them into nullary test functions. An `AbstractTestFun' is like +an ordinary test fun, but takes one argument instead of zero - it's +basically missing some information before it can be a proper test. In +practice, `{with, X, [F_1, ..., F_N]}' is equivalent to `[fun () -> +F_1(X) end, ..., fun () -> F_N(X) end]'. This is particularly useful if +your abstract test functions are already implemented as proper +functions: `{with, FD, [fun filetest_a/1, fun filetest_b/1, fun +filetest_c/1]}' is equivalent to `[fun () -> filetest_a(FD) end, fun () +-> filetest_b(FD) end, fun () -> filetest_c(FD) end]', but much more +compact. See also {@section Fixtures}, below. +
+
+ +=== Control === + +The following representations control how and where tests are executed: +
+
`{spawn, Tests}'
+
Runs the specified tests in a separate subprocess, while the current +test process waits for it to finish. This is useful for tests that need +a fresh, isolated process state. (Note that EUnit always starts at least +one such a subprocess automatically; tests are never executed by the +caller's own process.)
+
`{spawn, Node::atom(), Tests}'
+
Like `{spawn, Tests}', but runs the specified tests on the given +Erlang node.
+
`{timeout, Time::number(), Tests}'
+
Runs the specified tests under the given timeout. Time is in +seconds; e.g., 60 means one minute and 0.1 means 1/10th of a second. If +the timeout is exceeded, the unfinished tests will be forced to +terminate. Note that if a timeout is set around a fixture, it includes +the time for setup and cleanup, and if the timeout is triggered, the +entire fixture is abruptly terminated (without running the +cleanup).
+
`{inorder, Tests}'
+
Runs the specified tests in strict order. Also see `{inparallel, +Tests}'. By default, tests are neither marked as `inorder' or +`inparallel', but may be executed as the test framework chooses.
+
`{inparallel, Tests}'
+
Runs the specified tests in parallel (if possible). Also see +`{inorder, Tests}'.
+
`{inparallel, N::integer(), Tests}'
+
Like `{inparallel, Tests}', but running no more than `N' subtests +simultaneously.
+
+ +=== Fixtures === + +A "fixture" is some state that is necessary for a particular set of +tests to run. EUnit's support for fixtures makes it easy to set up such +state locally for a test set, and automatically tear it down again when +the test set is finished, regardless of the outcome (success, failures, +timeouts, etc.). + +To make the descriptions simpler, we first list some definitions: +
+ + + + + + + + + + + + + + + + + + + +
`Setup'`() -> (R::any())'
`SetupX'`(X::any()) -> (R::any())'
`Cleanup'`(R::any()) -> any()'
`CleanupX'`(X::any(), R::any()) -> any()'
`Instantiator'`((R::any()) -> Tests) | {with, [AbstractTestFun::((any()) -> any())]}'
`Where'`local | spawn | {spawn, Node::atom()}'
+
+(these are explained in more detail further below.) + +The following representations specify fixture handling for test sets: +
+
`{setup, Setup, Tests | Instantiator}'
+
`{setup, Setup, Cleanup, Tests | Instantiator}'
+
`{setup, Where, Setup, Tests | Instantiator}'
+
`{setup, Where, Setup, Cleanup, Tests | Instantiator}'
+
`setup' sets up a single fixture for running all of the specified +tests, with optional teardown afterwards. The arguments are described in +detail below. +
+
`{node, Node::atom(), Tests | Instantiator}'
+
`{node, Node::atom(), Args::string(), Tests | Instantiator}'
+
`node' is like `setup', but with a built-in behaviour: it starts a +slave node for the duration of the tests. The atom `Node' should have +the format `nodename@full.machine.name', and `Args' are the optional +arguments to the new node; see `slave:start_link/3' for details. +
+
`{foreach, Where, Setup, Cleanup, [Tests | Instantiator]}'
+
`{foreach, Setup, Cleanup, [Tests | Instantiator]}'
+
`{foreach, Where, Setup, [Tests | Instantiator]}'
+
`{foreach, Setup, [Tests | Instantiator]}'
+
`foreach' is used to set up a fixture and optionally tear it down +afterwards, repeated for each single one of the specified test sets. +
+
`{foreachx, Where, SetupX, CleanupX, + Pairs::[{X::any(), ((X::any(), R::any()) -> Tests)}]}'
+
`{foreachx, SetupX, CleanupX, Pairs}'
+
`{foreachx, Where, SetupX, Pairs}'
+
`{foreachx, SetupX, Pairs}'
+
`foreachx' is like `foreach', but uses a list of pairs, each +containing an extra argument `X' and an extended instantiator function. +
+
+ +A `Setup' function is executed just before any of the specified tests +are run, and a `Cleanup' function is executed when no more of the +specified tests will be run, regardless of the reason. A `Setup' +function takes no argument, and returns some value which will be passed +as it is to the `Cleanup' function. A `Cleanup' function should do +whatever necessary and return some arbitrary value, such as the atom +`ok'. (`SetupX' and `CleanupX' functions are similar, but receive one +additional argument: some value `X', which depends on the context.) When +no `Cleanup' function is specified, a dummy function is used which has +no effect. + +An `Instantiator' function receives the same value as the `Cleanup' +function, i.e., the value returned by the `Setup' function. It should +then behave much like a generator (see {@section Primitives}), and +return a test set whose tests have been instantiated with the +given value. A special case is the syntax `{with, [AbstractTestFun]}' +which represents an instantiator function that distributes the value +over a list of unary functions; see {@section Primitives}: `{with, X, +[...]}' for more details. + +A `Where' term controls how the specified tests are executed. The +default is `spawn', which means that the current process handles the +setup and teardown, while the tests are executed in a subprocess. +`{spawn, Node}' is like `spawn', but runs the subprocess on the +specified node. `local' means that the current process will handle both +setup/teardown and running the tests - the drawback is that if a test +times out so that the process is killed, the cleanup will not be +performed; hence, avoid this for persistent fixtures such as file +operations. In general, 'local' should only be used when: +
    +
  • the setup/teardown needs to be executed by the process that will + run the tests;
  • +
  • no further teardown needs to be done if the process is killed + (i.e., no state outside the process was affected by the setup)
  • +
+ +=== Lazy generators === + +Sometimes, it can be convenient not to produce the whole set of test +descriptions before the testing begins; for example, if you want to +generate a huge amount of tests that would take up too much space to +keep in memory all at once. + +It is fairly easy to write a generator which, each time it is called, +either produces an empty list if it is done, or otherwise produces a +list containing a single test case plus a new generator which will +produce the rest of the tests. This demonstrates the basic pattern: + +```lazy_test_() -> + lazy_gen(10000). + + lazy_gen(N) -> + {generator, + fun () -> + if N > 0 -> + [?_test(...) + | lazy_gen(N-1)]; + true -> + [] + end + end}.''' + +When EUnit traverses the test representation in order to run the tests, +the new generator will not be called to produce the next test until the +previous test has been executed. + +Note that it is easiest to write this kind of recursive generator using +a help function, like the `lazy_gen/1' function above. It can also be +written using a recursive fun, if you prefer to not clutter your +function namespace and are comfortable with writing that kind of code. diff --git a/lib/eunit/doc/packages-frame.html b/lib/eunit/doc/packages-frame.html new file mode 100644 index 0000000000..52b45534f5 --- /dev/null +++ b/lib/eunit/doc/packages-frame.html @@ -0,0 +1,11 @@ + + + +The eunit application + + + +

Packages

+
+ + \ No newline at end of file diff --git a/lib/eunit/doc/pdf/.gitignore b/lib/eunit/doc/pdf/.gitignore new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/eunit/doc/src/Makefile b/lib/eunit/doc/src/Makefile new file mode 100644 index 0000000000..faf2f9a847 --- /dev/null +++ b/lib/eunit/doc/src/Makefile @@ -0,0 +1,174 @@ +# +# 2004-2007 +# Ericsson AB, All Rights Reserved +# +# +# The contents of this file are subject to the Erlang Public License, +# Version 1.1, (the "License"); you may not use this file except in +# compliance with the License. You should have received a copy of the +# Erlang Public License along with this software. If not, it can be +# retrieved online at http://www.erlang.org/. +# +# Software distributed under the License is distributed on an "AS IS" +# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +# the License for the specific language governing rights and limitations +# under the License. +# +# The Initial Developer of the Original Code is Ericsson AB. +# +# +include $(ERL_TOP)/make/target.mk +include $(ERL_TOP)/make/$(TARGET)/otp.mk + +# ---------------------------------------------------- +# Application version +# ---------------------------------------------------- +include ../../vsn.mk +VSN=$(EUNIT_VSN) +APPLICATION=eunit + +# ---------------------------------------------------- +# Release directory specification +# ---------------------------------------------------- +RELSYSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN) + +# ---------------------------------------------------- +# Help application directory specification +# ---------------------------------------------------- + +EDOC_DIR = $(ERL_TOP)/lib/edoc +SYNTAX_TOOLS_DIR = $(ERL_TOP)/lib/syntax_tools + +# ---------------------------------------------------- +# Target Specs +# ---------------------------------------------------- +EUNIT_DIR = $(ERL_TOP)/lib/eunit/src +EUNIT_INC_DIR = $(ERL_TOP)/lib/eunit/include + +EUNIT_MODULES = \ + eunit eunit_surefire + +XML_APPLICATION_FILES = ref_man.xml + +XML_REF3_FILES = $(EUNIT_MODULES:=.xml) + +XML_PART_FILES = \ + part.xml \ + part_notes.xml + +XML_CHAPTER_FILES = \ + chapter.xml + +XML_NOTES_FILES = \ + notes.xml + +HTML_EXAMPLE_FILES = + +HTML_STYLESHEET_FILES = \ + ../stylesheet.css + +BOOK_FILES = book.xml + +XML_FILES = \ + $(BOOK_FILES) $(XML_CHAPTER_FILES) $(XML_NOTES_FILES) \ + $(XML_PART_FILES) $(XML_REF3_FILES) $(XML_APPLICATION_FILES) + + +# ---------------------------------------------------- +INFO_FILE = ../../info + +HTML_FILES = $(XML_APPLICATION_FILES:%.xml=$(HTMLDIR)/%.html) \ + $(XML_PART_FILES:%.xml=$(HTMLDIR)/%.html) + + +EXTRA_FILES = \ + $(DEFAULT_HTML_FILES) \ + $(DEFAULT_GIF_FILES) \ + $(XML_REF3_FILES:%.xml=$(HTMLDIR)/%.html) \ + $(XML_REF6_FILES:%.xml=$(HTMLDIR)/%.html) \ + $(XML_CHAPTER_FILES:%.xml=$(HTMLDIR)/%.html)\ + $(XML_NOTES_FILES:%.xml=$(HTMLDIR)/%.html) + +MAN3_FILES = $(XML_REF3_FILES:%.xml=$(MAN3DIR)/%.3) +MAN6_FILES = $(XML_REF6_FILES:%_app.xml=$(MAN6DIR)/%.6) + +HTML_REF_MAN_FILE = $(HTMLDIR)/index.html + +TOP_PDF_FILE = $(PDFDIR)/$(APPLICATION)-$(VSN).pdf + + +# ---------------------------------------------------- +# FLAGS +# ---------------------------------------------------- +XML_FLAGS += +DVIPS_FLAGS += + +# ---------------------------------------------------- +# Targets +# ---------------------------------------------------- +$(HTMLDIR)/%.gif: %.gif + $(INSTALL_DATA) $< $@ + +docs: pdf html man + +$(TOP_PDF_FILE): $(XML_FILES) + +pdf: $(TOP_PDF_FILE) + +html: gifs $(HTML_REF_MAN_FILE) + + +man: $(MAN3_FILES) + +gifs: $(GIF_FILES:%=$(HTMLDIR)/%) + +$(XML_REF3_FILES): + docb_gen -def vsn $(EUNIT_VSN) -includes $(EUNIT_INC_DIR) $(EUNIT_DIR)/$(@:%.xml=%.erl) + +$(XML_CHAPTER_FILES): + docb_gen -chapter -def vsn $(EUNIT_VSN) ../overview.edoc + + +info: + @echo "XML_PART_FILES: $(XML_PART_FILES)" + @echo "XML_APPLICATION_FILES: $(XML_APPLICATION_FILES)" + @echo "EUNIT_XML_FILES: $(EUNIT_XML_FILES)" + @echo "EUNIT_MODULES: $(EUNIT_MODULES)" + @echo "HTML_FILES: $(HTML_FILES)" + @echo "HTMLDIR: $(HTMLDIR)" + @echo "DEFAULT_GIF_FILES: $(DEFAULT_GIF_FILES)" + @echo "DEFAULT_HTML_FILES: $(DEFAULT_HTML_FILES)" + @echo "EXTRA_FILES: $(EXTRA_FILES)" + +xml: $(XML_REF3_FILES) $(XML_CHAPTER_FILES) + +debug opt: + +clean clean_docs: + rm -rf $(HTMLDIR)/* + rm -f $(MAN3DIR)/* + rm -f $(XML_CHAPTER_FILES) *.html + rm -f $(TOP_PDF_FILE) $(TOP_PDF_FILE:%.pdf=%.fo) + rm -f errs core *~ + + +# ---------------------------------------------------- +# Release Target +# ---------------------------------------------------- +include $(ERL_TOP)/make/otp_release_targets.mk + +release_docs_spec: docs + $(INSTALL_DIR) $(RELSYSDIR)/doc/pdf + $(INSTALL_DATA) $(TOP_PDF_FILE) $(RELSYSDIR)/doc/pdf + $(INSTALL_DIR) $(RELSYSDIR)/doc/html + $(INSTALL_DATA) $(HTMLDIR)/* \ + $(RELSYSDIR)/doc/html + $(INSTALL_DATA) $(INFO_FILE) $(RELSYSDIR) + $(INSTALL_DIR) $(RELEASE_PATH)/man/man3 + $(INSTALL_DATA) $(MAN3DIR)/* $(RELEASE_PATH)/man/man3 + + +release_spec: + + + diff --git a/lib/eunit/doc/src/book.xml b/lib/eunit/doc/src/book.xml new file mode 100644 index 0000000000..4444b1dd7a --- /dev/null +++ b/lib/eunit/doc/src/book.xml @@ -0,0 +1,51 @@ + + + + +
+ + 2008 + 2008 + Ericsson AB, All Rights Reserved + + + The contents of this file are subject to the Erlang Public License, + Version 1.1, (the "License"); you may not use this file except in + compliance with the License. You should have received a copy of the + Erlang Public License along with this software. If not, it can be + retrieved online at http://www.erlang.org/. + + Software distributed under the License is distributed on an "AS IS" + basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + the License for the specific language governing rights and limitations + under the License. + + The Initial Developer of the Original Code is Ericsson AB. + + + EUnit + Richard Carlsson, Mickaël Rémond + + 2008-10-29 + 2.0 + book.xml +
+ + + EUnit Application + + + + + + + + + + + + + + +
+ diff --git a/lib/eunit/doc/src/fascicules.xml b/lib/eunit/doc/src/fascicules.xml new file mode 100644 index 0000000000..bbe1e6c5cc --- /dev/null +++ b/lib/eunit/doc/src/fascicules.xml @@ -0,0 +1,18 @@ + + + + + + User's Guide + + + Reference Manual + + + Release Notes + + + Off-Print + + + diff --git a/lib/eunit/doc/src/make.dep b/lib/eunit/doc/src/make.dep new file mode 100644 index 0000000000..d68f888403 --- /dev/null +++ b/lib/eunit/doc/src/make.dep @@ -0,0 +1,19 @@ +# ---------------------------------------------------- +# >>>> Do not edit this file <<<< +# This file was automaticly generated by +# /home/otp/bin/docdepend +# ---------------------------------------------------- + + +# ---------------------------------------------------- +# TeX files that the DVI file depend on +# ---------------------------------------------------- + +book.dvi: book.tex chapter.tex eunit.tex part.tex ref_man.tex + +# ---------------------------------------------------- +# Source inlined when transforming from source to LaTeX +# ---------------------------------------------------- + +book.tex: ref_man.xml + diff --git a/lib/eunit/doc/src/notes.xml b/lib/eunit/doc/src/notes.xml new file mode 100644 index 0000000000..ac86448fab --- /dev/null +++ b/lib/eunit/doc/src/notes.xml @@ -0,0 +1,153 @@ + + + + +
+ + 2008 + 2008 + Ericsson AB, All Rights Reserved + + + The contents of this file are subject to the Erlang Public License, + Version 1.1, (the "License"); you may not use this file except in + compliance with the License. You should have received a copy of the + Erlang Public License along with this software. If not, it can be + retrieved online at http://www.erlang.org/. + + Software distributed under the License is distributed on an "AS IS" + basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + the License for the specific language governing rights and limitations + under the License. + + The Initial Developer of the Original Code is Ericsson AB. + + + EUnit Release Notes + otp_appnotes + nil + nil + nil + notes.xml +
+

This document describes the changes made to the EUnit application.

+ +
Eunit 2.1.4 + +
Improvements and New Features + + +

+ The documentation is now built with open source tools + (xsltproc and fop) that exists on most platforms. One + visible change is that the frames are removed.

+

+ Own Id: OTP-8201

+
+
+
+ +
+ +
Eunit 2.1.3 + +
Improvements and New Features + + +

+ Miscellaneous updates.

+

+ Own Id: OTP-8190

+
+
+
+ +
+ +
Eunit 2.1.2 + +
Improvements and New Features + + +

+ Miscellanous updates.

+

+ Own Id: OTP-8038

+
+
+
+ +
+ +
Eunit 2.1.1 + +
Fixed Bugs and Malfunctions + + +

+ eunit was broken in R13B.

+

+ Own Id: OTP-8018

+
+
+
+ +
+ +
Eunit 2.1 + +
Improvements and New Features + + +

Mostly internal changes, in particular to the event + protocol; fixes problems with timeouts that could cause + eunit to hang, and makes it much easier to write new + reporting back-ends.

+

New "surefire" report backend for Maven and + Bamboo.

+

The test representation is no longer traversed twice + (the first pass was for enumeration only). This + eliminates some strange restrictions on how generators + can be written, but it also means that reports cannot be + quite as complete as before in the event of skipped + tests.

+

+ Own Id: OTP-7964

+
+
+
+ +
+
+ EUnit 2.0.1 + +
+ Improvements and New Features + + +

+ Corrected the documentation build. +

+
+
+
+
+ + +
+ EUnit 2.0 + +
+ Improvements and New Features + + +

+ This is the first version of EUnit (for unit testing of Erlang modules) by Richard Carlsson + released in OTP. +

+
+
+
+
+
+ diff --git a/lib/eunit/doc/src/part.xml b/lib/eunit/doc/src/part.xml new file mode 100644 index 0000000000..e31a8d1b78 --- /dev/null +++ b/lib/eunit/doc/src/part.xml @@ -0,0 +1,38 @@ + + + + +
+ + 2008 + 2008 + Ericsson AB, All Rights Reserved + + + The contents of this file are subject to the Erlang Public License, + Version 1.1, (the "License"); you may not use this file except in + compliance with the License. You should have received a copy of the + Erlang Public License along with this software. If not, it can be + retrieved online at http://www.erlang.org/. + + Software distributed under the License is distributed on an "AS IS" + basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + the License for the specific language governing rights and limitations + under the License. + + The Initial Developer of the Original Code is Ericsson AB. + + + EUnit User's Guide + + + + +
+ +

The EUnit application + contains modules with support for unit testing.

+
+ +
+ diff --git a/lib/eunit/doc/src/part_notes.xml b/lib/eunit/doc/src/part_notes.xml new file mode 100644 index 0000000000..28644f961b --- /dev/null +++ b/lib/eunit/doc/src/part_notes.xml @@ -0,0 +1,39 @@ + + + + +
+ + 2008 + 2008 + Ericsson AB, All Rights Reserved + + + The contents of this file are subject to the Erlang Public License, + Version 1.1, (the "License"); you may not use this file except in + compliance with the License. You should have received a copy of the + Erlang Public License along with this software. If not, it can be + retrieved online at http://www.erlang.org/. + + Software distributed under the License is distributed on an "AS IS" + basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + the License for the specific language governing rights and limitations + under the License. + + The Initial Developer of the Original Code is Ericsson AB. + + + EUnit Release Notes + + + 2008-10-29 + + +
+ +

The EUnit application + contains modules with support for unit testing

+
+ +
+ diff --git a/lib/eunit/doc/src/ref_man.xml b/lib/eunit/doc/src/ref_man.xml new file mode 100644 index 0000000000..02feef5e97 --- /dev/null +++ b/lib/eunit/doc/src/ref_man.xml @@ -0,0 +1,40 @@ + + + + +
+ + 2008 + 2008 + Ericsson AB, All Rights Reserved + + + The contents of this file are subject to the Erlang Public License, + Version 1.1, (the "License"); you may not use this file except in + compliance with the License. You should have received a copy of the + Erlang Public License along with this software. If not, it can be + retrieved online at http://www.erlang.org/. + + Software distributed under the License is distributed on an "AS IS" + basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + the License for the specific language governing rights and limitations + under the License. + + The Initial Developer of the Original Code is Ericsson AB. + + + EUnit Reference Manual + + + 2008-10-29 + 2.0 + ref_man.xml +
+ +

The EUnit application + contains modules with support for unit testing.

+
+ + +
+ diff --git a/lib/eunit/doc/stylesheet.css b/lib/eunit/doc/stylesheet.css new file mode 100644 index 0000000000..e426a90483 --- /dev/null +++ b/lib/eunit/doc/stylesheet.css @@ -0,0 +1,55 @@ +/* standard EDoc style sheet */ +body { + font-family: Verdana, Arial, Helvetica, sans-serif; + margin-left: .25in; + margin-right: .2in; + margin-top: 0.2in; + margin-bottom: 0.2in; + color: #000000; + background-color: #ffffff; +} +h1,h2 { + margin-left: -0.2in; +} +div.navbar { + background-color: #add8e6; + padding: 0.2em; +} +h2.indextitle { + padding: 0.4em; + background-color: #add8e6; +} +h3.function,h3.typedecl { + background-color: #add8e6; + padding-left: 1em; +} +div.spec { + margin-left: 2em; + background-color: #eeeeee; +} +a.module,a.package { + text-decoration:none +} +a.module:hover,a.package:hover { + background-color: #eeeeee; +} +ul.definitions { + list-style-type: none; +} +ul.index { + list-style-type: none; + background-color: #eeeeee; +} + +/* + * Minor style tweaks + */ +ul { + list-style-type: square; +} +table { + border-collapse: collapse; +} +td { + padding: 3 +} diff --git a/lib/eunit/ebin/.gitignore b/lib/eunit/ebin/.gitignore new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/eunit/examples/Makefile b/lib/eunit/examples/Makefile new file mode 100644 index 0000000000..d1b5bac224 --- /dev/null +++ b/lib/eunit/examples/Makefile @@ -0,0 +1,58 @@ +# ``The contents of this file are subject to the Erlang Public License, +# Version 1.1, (the "License"); you may not use this file except in +# compliance with the License. You should have received a copy of the +# Erlang Public License along with this software. If not, it can be +# retrieved via the world wide web at http://www.erlang.org/. +# +# Software distributed under the License is distributed on an "AS IS" +# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +# the License for the specific language governing rights and limitations +# under the License. +# +# The Initial Developer of the Original Code is Ericsson Utvecklings AB. +# Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings +# AB. All Rights Reserved.'' +# +# $Id$ +# +include $(ERL_TOP)/make/target.mk +include $(ERL_TOP)/make/$(TARGET)/otp.mk + +# ---------------------------------------------------- +# Application version +# ---------------------------------------------------- +include ../vsn.mk +VSN=$(EUNIT_VSN) + +# ---------------------------------------------------- +# Release Macros +# ---------------------------------------------------- +RELSYSDIR = $(RELEASE_PATH)/lib/eunit-$(VSN) + +# ---------------------------------------------------- +# Macros +# ---------------------------------------------------- + +EXAMPLE_FILES = fib.erl tests.txt + +# ---------------------------------------------------- +# Make Rules +# ---------------------------------------------------- +debug opt: + +clean: + +docs: + + +# ---------------------------------------------------- +# Release Targets +# ---------------------------------------------------- +include $(ERL_TOP)/make/otp_release_targets.mk + +release_spec: + $(INSTALL_DIR) $(RELSYSDIR)/examples + $(INSTALL_DATA) $(EXAMPLE_FILES) $(RELSYSDIR)/examples + +release_docs_spec: + diff --git a/lib/eunit/examples/fib.erl b/lib/eunit/examples/fib.erl new file mode 100644 index 0000000000..1df02cebfc --- /dev/null +++ b/lib/eunit/examples/fib.erl @@ -0,0 +1,19 @@ + +-module(fib). +-export([fib/1]). +-include_lib("eunit/include/eunit.hrl"). + +fib(0) -> 1; +fib(1) -> 1; +fib(N) when N > 1 -> fib(N-1) + fib(N-2). + +fib_test_() -> + [?_assert(fib(0) =:= 1), + ?_assert(fib(1) =:= 1), + ?_assert(fib(2) =:= 2), + ?_assert(fib(3) =:= 3), + ?_assert(fib(4) =:= 5), + ?_assert(fib(5) =:= 8), + ?_assertException(error, function_clause, fib(-1)), + ?_assert(fib(31) =:= 2178309) + ]. diff --git a/lib/eunit/examples/tests.txt b/lib/eunit/examples/tests.txt new file mode 100644 index 0000000000..f514972f1a --- /dev/null +++ b/lib/eunit/examples/tests.txt @@ -0,0 +1 @@ +[eunit_lib]. diff --git a/lib/eunit/include/eunit.hrl b/lib/eunit/include/eunit.hrl new file mode 100644 index 0000000000..82ba982f03 --- /dev/null +++ b/lib/eunit/include/eunit.hrl @@ -0,0 +1,340 @@ +%% This library is free software; you can redistribute it and/or modify +%% it under the terms of the GNU Lesser General Public License as +%% published by the Free Software Foundation; either version 2 of the +%% License, or (at your option) any later version. +%% +%% This library is distributed in the hope that it will be useful, but +%% WITHOUT ANY WARRANTY; without even the implied warranty of +%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%% Lesser General Public License for more details. +%% +%% You should have received a copy of the GNU Lesser General Public +%% License along with this library; if not, write to the Free Software +%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +%% USA +%% +%% $Id: eunit.hrl 337 2009-03-09 08:38:28Z rcarlsson $ +%% +%% Copyright (C) 2004-2006 Mickaël Rémond, Richard Carlsson + +%% Including this file turns on testing and defines TEST, unless NOTEST +%% is defined before the file is included. If both NOTEST and TEST are +%% already defined, then TEST takes precedence, and NOTEST will become +%% undefined. +%% +%% If NODEBUG is defined before this file is included, the debug macros +%% are disabled, unless DEBUG is also defined, in which case NODEBUG +%% will become undefined. NODEBUG also implies NOASSERT, unless testing +%% is enabled. +%% +%% If including this file causes TEST to be defined, then NOASSERT will +%% be undefined, even if it was previously defined and even if NODEBUG +%% is defined. If both ASSERT and NOASSERT are defined before the file +%% is included, then ASSERT takes precedence, and NOASSERT will become +%% undefined regardless of TEST. +%% +%% After including this file, EUNIT will be defined if and only if TEST +%% is defined. + +-ifndef(EUNIT_HRL). +-define(EUNIT_HRL, true). + +%% allow defining TEST to override NOTEST +-ifdef(TEST). +-undef(NOTEST). +-endif. + +%% allow defining DEBUG to override NODEBUG +-ifdef(DEBUG). +-undef(NODEBUG). +-endif. + +%% allow NODEBUG to imply NOASSERT, unless overridden below +-ifdef(NODEBUG). +-ifndef(NOASSERT). +-define(NOASSERT, true). +-endif. +-endif. + +%% note that the main switch used within this file is NOTEST; however, +%% both TEST and EUNIT may be used to check whether testing is enabled +-ifndef(NOTEST). +-undef(NOASSERT). % testing requires that assertions are enabled +-ifndef(TEST). +-define(TEST, true). +-endif. +-ifndef(EUNIT). +-define(EUNIT, true). +-endif. +-else. +-undef(EUNIT). +-endif. + +%% allow ASSERT to override NOASSERT (regardless of TEST/NOTEST) +-ifdef(ASSERT). +-undef(NOASSERT). +-endif. + +%% Parse transforms for automatic exporting/stripping of test functions. +%% (Note that although automatic stripping is convenient, it will make +%% the code dependent on this header file and the eunit_striptests +%% module for compilation, even when testing is switched off! Using +%% -ifdef(EUNIT) around all test code makes the program more portable.) + +-ifndef(EUNIT_NOAUTO). +-ifndef(NOTEST). +-compile({parse_transform, eunit_autoexport}). +-else. +-compile({parse_transform, eunit_striptests}). +-endif. +-endif. + +%% All macros should be available even if testing is turned off, and +%% should preferably not require EUnit to be present at runtime. +%% +%% We must use fun-call wrappers ((fun () -> ... end)()) to avoid +%% exporting local variables, and furthermore we only use variable names +%% prefixed with "__", that hopefully will not be bound outside the fun. + +%% A generic let-macro is particularly useful when writing test cases. +%% It is more compact than 'begin X = Y, Z end', and guarantees that +%% X gets a new, local binding. +%% (Note that lowercase 'let' is a reserved word.) +-ifndef(LET). +-define(LET(X,Y,Z), ((fun(X)->(Z)end)(Y))). +-endif. + +%% It is important that testing code is short and readable. +%% An if-then-else macro can make some code much more compact. +%% Compare: case f(X) of true->g(X); false->h(X) end +%% and: ?IF(f(X), g(Y), h(Z)) +-ifndef(IF). +-define(IF(B,T,F), (case (B) of true->(T); false->(F) end)). +-endif. + +%% This macro yields 'true' if the value of E matches the guarded +%% pattern G, otherwise 'false'. +-ifndef(MATCHES). +-define(MATCHES(G,E), (case (E) of G -> true; _ -> false end)). +-endif. + +%% This macro can be used at any time to check whether or not the code +%% is currently running directly under eunit. Note that it does not work +%% in secondary processes if they have been assigned a new group leader. +-ifndef(UNDER_EUNIT). +-define(UNDER_EUNIT, + (?MATCHES({current_function,{eunit_proc,_,_}}, + .erlang:process_info(.erlang:group_leader(), + current_function)))). +-endif. + +-ifdef(NOASSERT). +%% The plain assert macro should be defined to do nothing if this file +%% is included when debugging/testing is turned off. +-ifndef(assert). +-define(assert(BoolExpr),ok). +-endif. +-else. +%% The assert macro is written the way it is so as not to cause warnings +%% for clauses that cannot match, even if the expression is a constant. +-undef(assert). +-define(assert(BoolExpr), + ((fun () -> + case (BoolExpr) of + true -> ok; + __V -> .erlang:error({assertion_failed, + [{module, ?MODULE}, + {line, ?LINE}, + {expression, (??BoolExpr)}, + {expected, true}, + {value, case __V of false -> __V; + _ -> {not_a_boolean,__V} + end}]}) + end + end)())). +-endif. +-define(assertNot(BoolExpr), ?assert(not (BoolExpr))). + +-define(_test(Expr), {?LINE, fun () -> (Expr) end}). + +-define(_assert(BoolExpr), ?_test(?assert(BoolExpr))). + +-define(_assertNot(BoolExpr), ?_assert(not (BoolExpr))). + +%% This is mostly a convenience which gives more detailed reports. +%% Note: Guard is a guarded pattern, and can not be used for value. +-ifdef(NOASSERT). +-define(assertMatch(Guard,Expr),ok). +-else. +-define(assertMatch(Guard, Expr), + ((fun () -> + case (Expr) of + Guard -> ok; + __V -> .erlang:error({assertMatch_failed, + [{module, ?MODULE}, + {line, ?LINE}, + {expression, (??Expr)}, + {expected, (??Guard)}, + {value, __V}]}) + end + end)())). +-endif. +-define(_assertMatch(Guard, Expr), ?_test(?assertMatch(Guard, Expr))). + +%% This is a convenience macro which gives more detailed reports when +%% the expected LHS value is not a pattern, but a computed value +-ifdef(NOASSERT). +-define(assertEqual(Expect,Expr),ok). +-else. +-define(assertEqual(Expect, Expr), + ((fun (__X) -> + case (Expr) of + __X -> ok; + __V -> .erlang:error({assertEqual_failed, + [{module, ?MODULE}, + {line, ?LINE}, + {expression, (??Expr)}, + {expected, __X}, + {value, __V}]}) + end + end)(Expect))). +-endif. +-define(_assertEqual(Expect, Expr), ?_test(?assertEqual(Expect, Expr))). + +%% Note: Class and Term are patterns, and can not be used for value. +-ifdef(NOASSERT). +-define(assertException(Class, Term, Expr),ok). +-else. +-define(assertException(Class, Term, Expr), + ((fun () -> + try (Expr) of + __V -> .erlang:error({assertException_failed, + [{module, ?MODULE}, + {line, ?LINE}, + {expression, (??Expr)}, + {expected, + "{ "++(??Class)++" , "++(??Term) + ++" , [...] }"}, + {unexpected_success, __V}]}) + catch + Class:Term -> ok; + __C:__T -> + .erlang:error({assertException_failed, + [{module, ?MODULE}, + {line, ?LINE}, + {expression, (??Expr)}, + {expected, + "{ "++(??Class)++" , "++(??Term) + ++" , [...] }"}, + {unexpected_exception, + {__C, __T, + .erlang:get_stacktrace()}}]}) + end + end)())). +-endif. + +-define(assertError(Term, Expr), ?assertException(error, Term, Expr)). +-define(assertExit(Term, Expr), ?assertException(exit, Term, Expr)). +-define(assertThrow(Term, Expr), ?assertException(throw, Term, Expr)). + +-define(_assertException(Class, Term, Expr), + ?_test(?assertException(Class, Term, Expr))). +-define(_assertError(Term, Expr), ?_assertException(error, Term, Expr)). +-define(_assertExit(Term, Expr), ?_assertException(exit, Term, Expr)). +-define(_assertThrow(Term, Expr), ?_assertException(throw, Term, Expr)). + +%% Macros for running operating system commands. (Note that these +%% require EUnit to be present at runtime, or at least eunit_lib.) + +%% these can be used for simply running commands in a controlled way +-define(_cmd_(Cmd), (.eunit_lib:command(Cmd))). +-define(cmdStatus(N, Cmd), + ((fun () -> + case ?_cmd_(Cmd) of + {(N), __Out} -> __Out; + {__N, _} -> .erlang:error({command_failed, + [{module, ?MODULE}, + {line, ?LINE}, + {command, (Cmd)}, + {expected_status,(N)}, + {status,__N}]}) + end + end)())). +-define(_cmdStatus(N, Cmd), ?_test(?cmdStatus(N, Cmd))). +-define(cmd(Cmd), ?cmdStatus(0, Cmd)). +-define(_cmd(Cmd), ?_test(?cmd(Cmd))). + +%% these are only used for testing; they always return 'ok' on success, +%% and have no effect if debugging/testing is turned off +-ifdef(NOASSERT). +-define(assertCmdStatus(N, Cmd),ok). +-else. +-define(assertCmdStatus(N, Cmd), + ((fun () -> + case ?_cmd_(Cmd) of + {(N), _} -> ok; + {__N, _} -> .erlang:error({assertCmd_failed, + [{module, ?MODULE}, + {line, ?LINE}, + {command, (Cmd)}, + {expected_status,(N)}, + {status,__N}]}) + end + end)())). +-endif. +-define(assertCmd(Cmd), ?assertCmdStatus(0, Cmd)). + +-ifdef(NOASSERT). +-define(assertCmdOutput(T, Cmd),ok). +-else. +-define(assertCmdOutput(T, Cmd), + ((fun () -> + case ?_cmd_(Cmd) of + {_, (T)} -> ok; + {_, __T} -> .erlang:error({assertCmdOutput_failed, + [{module, ?MODULE}, + {line, ?LINE}, + {command,(Cmd)}, + {expected_output,(T)}, + {output,__T}]}) + end + end)())). +-endif. + +-define(_assertCmdStatus(N, Cmd), ?_test(?assertCmdStatus(N, Cmd))). +-define(_assertCmd(Cmd), ?_test(?assertCmd(Cmd))). +-define(_assertCmdOutput(T, Cmd), ?_test(?assertCmdOutput(T, Cmd))). + +%% Macros to simplify debugging (in particular, they work even when the +%% standard output is being redirected by EUnit while running tests) + +-ifdef(NODEBUG). +-define(debugMsg(S), ok). +-define(debugHere, ok). +-define(debugFmt(S, As), ok). +-define(debugVal(E), (E)). +-define(debugTime(S,E), (E)). +-else. +-define(debugMsg(S), + (begin + .io:fwrite(user, <<"~s:~w: ~s\n">>, [?FILE, ?LINE, S]), + ok + end)). +-define(debugHere, (?debugMsg("<-"))). +-define(debugFmt(S, As), (?debugMsg(.io_lib:format((S), (As))))). +-define(debugVal(E), + ((fun (__V) -> + ?debugFmt(<<"~s = ~P">>, [(??E), __V, 15]), + __V + end)(E))). +-define(debugTime(S,E), + ((fun () -> + {__T0, _} = statistics(wall_clock), + __V = (E), + {__T1, _} = statistics(wall_clock), + ?debugFmt(<<"~s: ~.3f s">>, [(S), (__T1-__T0)/1000]), + __V + end)())). +-endif. + +-endif. % EUNIT_HRL diff --git a/lib/eunit/info b/lib/eunit/info new file mode 100644 index 0000000000..138f4dc040 --- /dev/null +++ b/lib/eunit/info @@ -0,0 +1,2 @@ +group: tools +short: Support for unit testing. diff --git a/lib/eunit/src/Makefile b/lib/eunit/src/Makefile new file mode 100644 index 0000000000..4897c20ec1 --- /dev/null +++ b/lib/eunit/src/Makefile @@ -0,0 +1,113 @@ +# +# Copyright (C) 2008, Ericsson Telecommunications +# Authors: Richard Carlsson, Bertil Karlsson +# +include $(ERL_TOP)/make/target.mk +include $(ERL_TOP)/make/$(TARGET)/otp.mk + +# ---------------------------------------------------- +# Application version +# ---------------------------------------------------- +include ../vsn.mk +VSN=$(EUNIT_VSN) + +# ---------------------------------------------------- +# Release directory specification +# ---------------------------------------------------- +RELSYSDIR = $(RELEASE_PATH)/lib/eunit-$(VSN) + + +# +# Common Macros +# + +EBIN = ../ebin +INCLUDE=../include + +ERL_COMPILE_FLAGS += -pa $(EBIN) -I$(INCLUDE) +warn_unused_vars +nowarn_shadow_vars +warn_unused_import +warn_obsolete_guard + +SOURCES= \ + eunit_autoexport.erl \ + eunit_striptests.erl \ + eunit.erl \ + eunit_tests.erl \ + eunit_server.erl \ + eunit_proc.erl \ + eunit_serial.erl \ + eunit_test.erl \ + eunit_lib.erl \ + eunit_data.erl \ + eunit_tty.erl \ + eunit_surefire.erl \ + eunit_listener.erl + +INCLUDE_FILES = eunit.hrl + +OBJECTS=$(SOURCES:%.erl=$(EBIN)/%.$(EMULATOR)) $(APP_TARGET) $(APPUP_TARGET) + +INCLUDE_DELIVERABLES = $(INCLUDE_FILES:%=$(INCLUDE)/%) + +APP_FILE= eunit.app +APP_SRC= $(APP_FILE).src +APP_TARGET= $(EBIN)/$(APP_FILE) + +APPUP_FILE= eunit.appup +APPUP_SRC= $(APPUP_FILE).src +APPUP_TARGET= $(EBIN)/$(APPUP_FILE) + +# ---------------------------------------------------- +# Targets +# ---------------------------------------------------- + +debug opt: $(OBJECTS) + +docs: + +all: $(OBJECTS) + + +clean: + rm -f $(OBJECTS) + rm -f core *~ + +distclean: clean + +info: + @echo "MODULES: $(MODULES)" + @echo "EBIN: $(EBIN)" + @echo "EMULATOR: $(EMULATOR)" + @echo "APP_TARGET: $(APP_TARGET)" + @echo "TARGET_FILES: $(TARGET_FILES)" + @echo "DOC_TARGET_FILES: $(DOC_TARGET_FILES)" + @echo "DOCDIR/%html: $(DOCDIR)/%.html" + +realclean: clean + +$(EBIN)/%.$(EMULATOR):%.erl + erlc -W $(ERL_COMPILE_FLAGS) -o$(EBIN) $< + +# ---------------------------------------------------- +# Special Build Targets +# ---------------------------------------------------- + +$(APP_TARGET): $(APP_SRC) ../vsn.mk + sed -e 's;%VSN%;$(VSN);' $< > $@ + +$(APPUP_TARGET): $(APPUP_SRC) ../vsn.mk + sed -e 's;%VSN%;$(VSN);' $< > $@ + +# ---------------------------------------------------- +# Release Target +# ---------------------------------------------------- +include $(ERL_TOP)/make/otp_release_targets.mk + +release_spec: opt + $(INSTALL_DIR) $(RELSYSDIR)/ebin + $(INSTALL_DATA) $(OBJECTS) $(RELSYSDIR)/ebin + $(INSTALL_DIR) $(RELSYSDIR)/src + $(INSTALL_DATA) $(SOURCES) $(RELSYSDIR)/src + $(INSTALL_DIR) $(RELSYSDIR)/include + $(INSTALL_DATA) $(INCLUDE_DELIVERABLES) $(RELSYSDIR)/include + +release_docs_spec: + diff --git a/lib/eunit/src/eunit.app.src b/lib/eunit/src/eunit.app.src new file mode 100644 index 0000000000..4fd76588c3 --- /dev/null +++ b/lib/eunit/src/eunit.app.src @@ -0,0 +1,21 @@ +% This is an -*- erlang -*- file. + +{application, eunit, + [{description, "EUnit"}, + {vsn, "%VSN%"}, + {modules, [eunit, + eunit_autoexport, + eunit_striptests, + eunit_server, + eunit_proc, + eunit_serial, + eunit_test, + eunit_tests, + eunit_lib, + eunit_listener, + eunit_data, + eunit_tty, + eunit_surefire]}, + {registered,[]}, + {applications, [stdlib]}, + {env, []}]}. diff --git a/lib/eunit/src/eunit.appup.src b/lib/eunit/src/eunit.appup.src new file mode 100644 index 0000000000..54a63833e6 --- /dev/null +++ b/lib/eunit/src/eunit.appup.src @@ -0,0 +1 @@ +{"%VSN%",[],[]}. diff --git a/lib/eunit/src/eunit.erl b/lib/eunit/src/eunit.erl new file mode 100644 index 0000000000..59084a52fb --- /dev/null +++ b/lib/eunit/src/eunit.erl @@ -0,0 +1,250 @@ +%% This library is free software; you can redistribute it and/or modify +%% it under the terms of the GNU Lesser General Public License as +%% published by the Free Software Foundation; either version 2 of the +%% License, or (at your option) any later version. +%% +%% This library is distributed in the hope that it will be useful, but +%% WITHOUT ANY WARRANTY; without even the implied warranty of +%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%% Lesser General Public License for more details. +%% +%% You should have received a copy of the GNU Lesser General Public +%% License along with this library; if not, write to the Free Software +%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +%% USA +%% +%% $Id: eunit.erl 339 2009-04-05 14:10:47Z rcarlsson $ +%% +%% @copyright 2004-2009 Mickaël Rémond, Richard Carlsson +%% @author Mickaël Rémond +%% [http://www.process-one.net/] +%% @author Richard Carlsson +%% [http://user.it.uu.se/~richardc/] +%% @version {@version}, {@date} {@time} +%% @doc This module is the main EUnit user interface. + +-module(eunit). + +-include("eunit.hrl"). +-include("eunit_internal.hrl"). + +%% Official exports +-export([start/0, stop/0, test/1, test/2]). + +%% Experimental; may be removed or relocated +-export([start/1, stop/1, test/3, submit/1, submit/2, submit/3, watch/1, + watch/2, watch/3, watch_path/1, watch_path/2, watch_path/3, + watch_regexp/1, watch_regexp/2, watch_regexp/3, watch_app/1, + watch_app/2, watch_app/3]). + +%% EUnit entry points + +%% TODO: Command line interface similar to that of edoc? + +%% @doc Starts the EUnit server. Normally, you don't need to call this +%% function; it is started automatically. +start() -> + start(?SERVER). + +%% @private +%% @doc See {@link start/0}. +start(Server) -> + eunit_server:start(Server). + +%% @doc Stops the EUnit server. Normally, you don't need to call this +%% function. +stop() -> + stop(?SERVER). + +%% @private +%% @doc See {@link stop/0}. +stop(Server) -> + eunit_server:stop(Server). + +%% @private +watch(Target) -> + watch(Target, []). + +%% @private +watch(Target, Options) -> + watch(?SERVER, Target, Options). + +%% @private +watch(Server, Target, Options) -> + eunit_server:watch(Server, Target, Options). + +%% @private +watch_path(Target) -> + watch_path(Target, []). + +%% @private +watch_path(Target, Options) -> + watch_path(?SERVER, Target, Options). + +%% @private +watch_path(Server, Target, Options) -> + eunit_server:watch_path(Server, Target, Options). + +%% @private +watch_regexp(Target) -> + watch_regexp(Target, []). + +%% @private +watch_regexp(Target, Options) -> + watch_regexp(?SERVER, Target, Options). + +%% @private +watch_regexp(Server, Target, Options) -> + eunit_server:watch_regexp(Server, Target, Options). + +%% @private +watch_app(Name) -> + watch_app(Name, []). + +%% @private +watch_app(Name, Options) -> + watch_app(?SERVER, Name, Options). + +%% @private +watch_app(Server, Name, Options) -> + case code:lib_dir(Name) of + Path when is_list(Path) -> + watch_path(Server, filename:join(Path, "ebin"), Options); + _ -> + error + end. + +%% @equiv test(Tests, []) +test(Tests) -> + test(Tests, []). + +%% @spec test(Tests::term(), Options::[term()]) -> ok | {error, term()} +%% @doc Runs a set of tests. The format of `Tests' is described in the +%% section EUnit test +%% representation of the overview. +%% +%% Example: ```eunit:test(fred)''' runs all tests in the module `fred' +%% and also any tests in the module `fred_tests', if that module exists. +%% +%% Options: +%%
+%%
`verbose'
+%%
Displays more details about the running tests.
+%%
+%% +%% Options in the environment variable EUNIT are also included last in +%% the option list, i.e., have lower precedence than those in `Options'. +%% @see test/1 +test(Tests, Options) -> + test(?SERVER, Tests, all_options(Options)). + +%% @private +%% @doc See {@link test/2}. +test(Server, Tests, Options) -> + Listeners = [eunit_tty:start(Options) | listeners(Options)], + Serial = eunit_serial:start(Listeners), + case eunit_server:start_test(Server, Serial, Tests, Options) of + {ok, Reference} -> test_run(Reference, Listeners); + {error, R} -> {error, R} + end. + +test_run(Reference, Listeners) -> + receive + {start, Reference} -> + cast(Listeners, {start, Reference}) + end, + receive + {done, Reference} -> + cast(Listeners, {stop, Reference, self()}), + receive + {result, Reference, Result} -> + Result + end + end. + +cast([P | Ps], Msg) -> + P ! Msg, + cast(Ps, Msg); +cast([], _Msg) -> + ok. + +%% TODO: functions that run tests on a given node, not a given server +%% TODO: maybe some functions could check for a globally registered server? +%% TODO: some synchronous but completely quiet interface function + +%% @private +submit(T) -> + submit(T, []). + +%% @private +submit(T, Options) -> + submit(?SERVER, T, Options). + +%% @private +submit(Server, T, Options) -> + Dummy = spawn(fun devnull/0), + eunit_server:start_test(Server, Dummy, T, Options). + +listeners(Options) -> + Ps = start_listeners(proplists:get_all_values(report, Options)), + %% the event_log option is for debugging, to view the raw events + case proplists:get_value(event_log, Options) of + undefined -> + Ps; + X -> + LogFile = if is_list(X) -> X; + true -> "eunit-events.log" + end, + [spawn_link(fun () -> event_logger(LogFile) end) | Ps] + end. + +start_listeners([P | Ps]) when is_pid(P) ; is_atom(P) -> + [P | start_listeners(Ps)]; +start_listeners([{Mod, Opts} | Ps]) when is_atom(Mod) -> + [Mod:start(Opts) | start_listeners(Ps)]; +start_listeners([]) -> + []. + +%% TODO: make this report file errors +event_logger(LogFile) -> + case file:open(LogFile, [write]) of + {ok, FD} -> + receive + {start, Reference} -> + event_logger_loop(Reference, FD) + end; + Error -> + exit(Error) + end. + +event_logger_loop(Reference, FD) -> + receive + {status, _Id, _Info}=Msg -> + io:fwrite(FD, "~p.\n", [Msg]), + event_logger_loop(Reference, FD); + {stop, Reference, _ReplyTo} -> + %% no need to reply, just exit + file:close(FD), + exit(normal) + end. + +%% TODO: make a proper logger for asynchronous execution with submit/3 + +devnull() -> + receive _ -> devnull() end. + +%% including options from EUNIT environment variable + +all_options(Opts) -> + try os:getenv("EUNIT") of + false -> Opts; + S -> + {ok, Ts, _} = erl_scan:string(S), + {ok, V} = erl_parse:parse_term(Ts ++ [{dot,1}]), + if is_list(V) -> Opts ++ V; + true -> Opts ++ [V] + end + catch + _:_ -> Opts + end. diff --git a/lib/eunit/src/eunit_autoexport.erl b/lib/eunit/src/eunit_autoexport.erl new file mode 100644 index 0000000000..7b153c1194 --- /dev/null +++ b/lib/eunit/src/eunit_autoexport.erl @@ -0,0 +1,104 @@ +%% This library is free software; you can redistribute it and/or modify +%% it under the terms of the GNU Lesser General Public License as +%% published by the Free Software Foundation; either version 2 of the +%% License, or (at your option) any later version. +%% +%% This library is distributed in the hope that it will be useful, but +%% WITHOUT ANY WARRANTY; without even the implied warranty of +%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%% Lesser General Public License for more details. +%% +%% You should have received a copy of the GNU Lesser General Public +%% License along with this library; if not, write to the Free Software +%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +%% USA +%% +%% $Id: eunit_autoexport.erl 329 2009-03-01 11:23:32Z rcarlsson $ +%% +%% @author Richard Carlsson +%% @copyright 2006 Richard Carlsson +%% @private +%% @see eunit +%% @doc Parse transform for automatic exporting of test functions. + +-module(eunit_autoexport). + +-include("eunit_internal.hrl"). + +-export([parse_transform/2]). + + +parse_transform(Forms, Options) -> + TestSuffix = proplists:get_value(eunit_test_suffix, Options, + ?DEFAULT_TEST_SUFFIX), + GeneratorSuffix = proplists:get_value(eunit_generator_suffix, + Options, + ?DEFAULT_GENERATOR_SUFFIX), + ExportSuffix = proplists:get_value(eunit_export_suffix, Options, + ?DEFAULT_EXPORT_SUFFIX), + F = fun (Form, Set) -> + form(Form, Set, TestSuffix, GeneratorSuffix, + ExportSuffix) + end, + Exports = sets:to_list(lists:foldl(F, sets:new(), Forms)), + rewrite(Forms, Exports). + +form({function, _L, Name, 0, _Cs}, S, TestSuffix, GeneratorSuffix, + ExportSuffix) -> + N = atom_to_list(Name), + case lists:suffix(TestSuffix, N) of + true -> + sets:add_element({Name, 0}, S); + false -> + case lists:suffix(GeneratorSuffix, N) of + true -> + sets:add_element({Name, 0}, S); + false -> + case lists:suffix(ExportSuffix, N) of + true -> + sets:add_element({Name, 0}, S); + false -> + S + end + end + end; +form({function, _L, ?DEFAULT_MODULE_WRAPPER_NAME, 1, _Cs}, S, _, _, _) -> + sets:add_element({?DEFAULT_MODULE_WRAPPER_NAME,1}, S); +form(_, S, _, _, _) -> + S. + +rewrite([{attribute,_,module,{Name,_Ps}}=M | Fs], Exports) -> + module_decl(Name, M, Fs, Exports); +rewrite([{attribute,_,module,Name}=M | Fs], Exports) -> + module_decl(Name, M, Fs, Exports); +rewrite([F | Fs], Exports) -> + [F | rewrite(Fs, Exports)]; +rewrite([], _Exports) -> + []. %% fail-safe, in case there is no module declaration + +rewrite([{function,_,test,0,_}=F | Fs], As, Module, _Test) -> + rewrite(Fs, [F | As], Module, false); +rewrite([F | Fs], As, Module, Test) -> + rewrite(Fs, [F | As], Module, Test); +rewrite([], As, Module, Test) -> + {if Test -> + EUnit = {record_field,0,{atom,0,''},{atom,0,eunit}}, + [{function,0,test,0, + [{clause,0,[],[], + [{call,0,{remote,0,EUnit,{atom,0,test}}, + [{atom,0,Module}]}]}]} + | As]; + true -> + As + end, + Test}. + +module_decl(Name, M, Fs, Exports) -> + Module = if is_atom(Name) -> Name; + true -> list_to_atom(packages:concat(Name)) + end, + {Fs1, Test} = rewrite(Fs, [], Module, true), + Es = if Test -> [{test,0} | Exports]; + true -> Exports + end, + [M, {attribute,0,export,Es} | lists:reverse(Fs1)]. diff --git a/lib/eunit/src/eunit_data.erl b/lib/eunit/src/eunit_data.erl new file mode 100644 index 0000000000..0543b6c543 --- /dev/null +++ b/lib/eunit/src/eunit_data.erl @@ -0,0 +1,732 @@ +%% This library is free software; you can redistribute it and/or modify +%% it under the terms of the GNU Lesser General Public License as +%% published by the Free Software Foundation; either version 2 of the +%% License, or (at your option) any later version. +%% +%% This library is distributed in the hope that it will be useful, but +%% WITHOUT ANY WARRANTY; without even the implied warranty of +%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%% Lesser General Public License for more details. +%% +%% You should have received a copy of the GNU Lesser General Public +%% License along with this library; if not, write to the Free Software +%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +%% USA +%% +%% $Id$ +%% +%% @author Richard Carlsson +%% @copyright 2006 Richard Carlsson +%% @private +%% @see eunit +%% @doc Interpretation of symbolic test representation + +-module(eunit_data). + +-include("eunit.hrl"). +-include("eunit_internal.hrl"). + +-include_lib("kernel/include/file.hrl"). + +-export([iter_init/2, iter_next/1, iter_prev/1, iter_id/1, + enter_context/3, get_module_tests/1]). + +-import(lists, [foldr/3]). + +-define(TICKS_PER_SECOND, 1000). + +%% @type tests() = +%% SimpleTest +%% | [tests()] +%% | moduleName() +%% | {module, moduleName()} +%% | {application, appName()} +%% | {application, appName(), [term()]} +%% | fileName() +%% | {file, fileName()} +%% | {string(), tests()} +%% | {generator, () -> tests()} +%% | {generator, M::moduleName(), F::functionName()} +%% | {spawn, tests()} +%% | {spawn, Node::atom(), tests()} +%% | {timeout, T::number(), tests()} +%% | {inorder, tests()} +%% | {inparallel, tests()} +%% | {inparallel, N::integer(), tests()} +%% | {with, X::any(), [AbstractTestFunction]} +%% | {setup, Where::local | spawn | {spawn, Node::atom()}, +%% Setup::() -> (R::any()), +%% Cleanup::(R::any()) -> any(), +%% tests() | Instantiator +%% } +%% | {setup, Setup, Cleanup, tests() | Instantiator} +%% | {setup, Where, Setup, tests() | Instantiator} +%% | {setup, Setup, tests() | Instantiator} +%% | {foreach, Where::local | spawn | {spawn, Node::atom()}, +%% Setup::() -> (R::any()), +%% Cleanup::(R::any()) -> any(), +%% [tests() | Instantiator] +%% } +%% | {foreach, Setup, Cleanup, [tests() | Instantiator]} +%% | {foreach, Where, Setup, [tests() | Instantiator]} +%% | {foreach, Setup, [tests() | Instantiator]} +%% | {foreachx, Where::local | spawn | {spawn, Node::atom()}, +%% SetupX::(X::any()) -> (R::any()), +%% CleanupX::(X::any(), R::any()) -> any(), +%% Pairs::[{X::any(), +%% (X::any(), R::any()) -> tests()}] +%% } +%% | {foreachx, SetupX, CleanupX, Pairs} +%% | {foreachx, Where, SetupX, Pairs} +%% | {foreachx, SetupX, Pairs} +%% | {node, Node::atom(), tests() | Instantiator} +%% | {node, Node, Args::string(), tests() | Instantiator} +%% +%% SimpleTest = TestFunction | {Line::integer(), SimpleTest} +%% +%% TestFunction = () -> any() +%% | {M::moduleName(), F::functionName()}. +%% +%% AbstractTestFunction = (X::any()) -> any() +%% +%% Instantiator = (R::any()) -> tests() +%% | {with, [AbstractTestFunction]} +%% +%% Note that `{string(), ...}' is a short-hand for `{string(), {...}}' +%% if the tuple contains more than two elements. +%% +%% @type moduleName() = atom() +%% @type functionName() = atom() +%% @type arity() = integer() +%% @type appName() = atom() +%% @type fileName() = string() + +%% TODO: Can we mark up tests as known-failures? +%% TODO: Is it possible to handle known timout/setup failures? +%% TODO: Add diagnostic tests which never fail, but may cause warnings? + +%% --------------------------------------------------------------------- +%% Abstract test set iterator + +-record(iter, + {prev = [], + next = [], + tests = [], + pos = 0, + parent = []}). + +%% @spec (tests(), [integer()]) -> testIterator() +%% @type testIterator() + +iter_init(Tests, ParentID) -> + #iter{tests = Tests, parent = lists:reverse(ParentID)}. + +%% @spec (testIterator()) -> [integer()] + +iter_id(#iter{pos = N, parent = Ns}) -> + lists:reverse(Ns, [N]). + +%% @spec (testIterator()) -> none | {testItem(), testIterator()} + +iter_next(I = #iter{next = []}) -> + case next(I#iter.tests) of + {T, Tests} -> + {T, I#iter{prev = [T | I#iter.prev], + tests = Tests, + pos = I#iter.pos + 1}}; + none -> + none + end; +iter_next(I = #iter{next = [T | Ts]}) -> + {T, I#iter{next = Ts, + prev = [T | I#iter.prev], + pos = I#iter.pos + 1}}. + +%% @spec (testIterator()) -> none | {testItem(), testIterator()} + +iter_prev(#iter{prev = []}) -> + none; +iter_prev(#iter{prev = [T | Ts], next = Next, pos = Pos} = I) -> + {T, I#iter{prev = Ts, next = [T | Next], pos = Pos - 1}}. + + +%% --------------------------------------------------------------------- +%% Concrete test set representation iterator + +%% @spec (tests()) -> none | {testItem(), tests()} +%% @type testItem() = #test{} | #group{} +%% @throws {bad_test, term()} +%% | {generator_failed, exception()} +%% | {no_such_function, eunit_lib:mfa()} +%% | {module_not_found, moduleName()} +%% | {application_not_found, appName()} +%% | {file_read_error, {Reason::atom(), Message::string(), +%% fileName()}} + +next(Tests) -> + case eunit_lib:dlist_next(Tests) of + [T | Ts] -> + case parse(T) of + {data, T1} -> + next([T1 | Ts]); + T1 -> + {T1, Ts} + end; + [] -> + none + end. + +%% this returns either a #test{} or #group{} record, or {data, T} to +%% signal that T has been substituted for the given representation + +parse({foreach, S, Fs}) when is_function(S), is_list(Fs) -> + parse({foreach, S, fun ok/1, Fs}); +parse({foreach, S, C, Fs}) + when is_function(S), is_function(C), is_list(Fs) -> + parse({foreach, ?DEFAULT_SETUP_PROCESS, S, C, Fs}); +parse({foreach, P, S, Fs}) + when is_function(S), is_list(Fs) -> + parse({foreach, P, S, fun ok/1, Fs}); +parse({foreach, P, S, C, Fs} = T) + when is_function(S), is_function(C), is_list(Fs) -> + check_arity(S, 0, T), + check_arity(C, 1, T), + case Fs of + [F | Fs1] -> + {data, [{setup, P, S, C, F}, {foreach, P, S, C, Fs1}]}; + [] -> + {data, []} + end; +parse({foreachx, S1, Ps}) when is_function(S1), is_list(Ps) -> + parse({foreachx, S1, fun ok/2, Ps}); +parse({foreachx, S1, C1, Ps}) + when is_function(S1), is_function(C1), is_list(Ps) -> + parse({foreachx, ?DEFAULT_SETUP_PROCESS, S1, C1, Ps}); +parse({foreachx, P, S1, Ps}) + when is_function(S1), is_list(Ps) -> + parse({foreachx, P, S1, fun ok/2, Ps}); +parse({foreachx, P, S1, C1, Ps} = T) + when is_function(S1), is_function(C1), is_list(Ps) -> + check_arity(S1, 1, T), + check_arity(C1, 2, T), + case Ps of + [{X, F1} | Ps1] when is_function(F1) -> + check_arity(F1, 2, T), + S = fun () -> S1(X) end, + C = fun (R) -> C1(X, R) end, + F = fun (R) -> F1(X, R) end, + {data, [{setup, P, S, C, F}, {foreachx, P, S1, C1, Ps1}]}; + [_|_] -> + bad_test(T); + [] -> + {data, []} + end; +parse({generator, F} = T) when is_function(F) -> + check_arity(F, 0, T), + %% use run_testfun/1 to handle wrapper exceptions + case eunit_test:run_testfun(F) of + {ok, T1} -> + {data, T1}; + {error, {Class, Reason, Trace}} -> + throw({generator_failed, {Class, Reason, Trace}}) + end; +parse({generator, M, F}) when is_atom(M), is_atom(F) -> + parse({generator, eunit_test:function_wrapper(M, F)}); +parse({inorder, T}) -> + group(#group{tests = T, order = inorder}); +parse({inparallel, T}) -> + parse({inparallel, 0, T}); +parse({inparallel, N, T}) when is_integer(N), N >= 0 -> + group(#group{tests = T, order = {inparallel, N}}); +parse({timeout, N, T}) when is_number(N), N >= 0 -> + group(#group{tests = T, timeout = round(N * ?TICKS_PER_SECOND)}); +parse({spawn, T}) -> + group(#group{tests = T, spawn = local}); +parse({spawn, N, T}) when is_atom(N) -> + group(#group{tests = T, spawn = {remote, N}}); +parse({setup, S, I}) when is_function(S); is_list(S) -> + parse({setup, ?DEFAULT_SETUP_PROCESS, S, I}); +parse({setup, S, C, I}) when is_function(S), is_function(C) -> + parse({setup, ?DEFAULT_SETUP_PROCESS, S, C, I}); +parse({setup, P, S, I}) when is_function(S) -> + parse({setup, P, S, fun ok/1, I}); +parse({setup, P, L, I} = T) when is_list(L) -> + check_setup_list(L, T), + {S, C} = eunit_test:multi_setup(L), + parse({setup, P, S, C, I}); +parse({setup, P, S, C, I} = T) + when is_function(S), is_function(C), is_function(I) -> + check_arity(S, 0, T), + check_arity(C, 1, T), + case erlang:fun_info(I, arity) of + {arity, 0} -> + %% if I is nullary, it is a plain test + parse({setup, S, C, fun (_) -> I end}); + _ -> + %% otherwise, I must be an instantiator function + check_arity(I, 1, T), + case P of + local -> ok; + spawn -> ok; + {spawn, N} when is_atom(N) -> ok; + _ -> bad_test(T) + end, + group(#group{tests = I, + context = #context{setup = S, cleanup = C, + process = P}}) + end; +parse({setup, P, S, C, {with, As}}) when is_list(As) -> + parse({setup, P, S, C, fun (X) -> {with, X, As} end}); +parse({setup, P, S, C, T}) when is_function(S), is_function(C) -> + parse({setup, P, S, C, fun (_) -> T end}); +parse({node, N, T}) when is_atom(N) -> + parse({node, N, "", T}); +parse({node, N, A, T1}=T) when is_atom(N) -> + case eunit_lib:is_string(A) of + true -> + %% TODO: better stack traces for internal funs like these + parse({setup, + fun () -> + %% TODO: auto-start net_kernel if needed + StartedNet = false, +%% The following is commented out because of problems when running +%% eunit as part of the init sequence (from the command line): +%% StartedNet = +%% case whereis(net_kernel) of +%% undefined -> +%% M = list_to_atom(atom_to_list(N) +%% ++ "_master"), +%% case net_kernel:start([M]) of +%% {ok, _} -> +%% true; +%% {error, E} -> +%% throw({net_kernel_start, E}) +%% end; +%% _ -> false +%% end, +%% ?debugVal({started, StartedNet}), + {Name, Host} = eunit_lib:split_node(N), + {ok, Node} = slave:start_link(Host, Name, A), + {Node, StartedNet} + end, + fun ({Node, StopNet}) -> +%% ?debugVal({stop, StopNet}), + slave:stop(Node), + case StopNet of + true -> net_kernel:stop(); + false -> ok + end + end, + T1}); + false -> + bad_test(T) + end; +parse({module, M}) when is_atom(M) -> + {data, {"module '" ++ atom_to_list(M) ++ "'", get_module_tests(M)}}; +parse({application, A}) when is_atom(A) -> + try parse({file, atom_to_list(A)++".app"}) + catch + {file_read_error,{enoent,_,_}} -> + case code:lib_dir(A) of + Dir when is_list(Dir) -> + %% add "ebin" if it exists, like code_server does + BinDir = filename:join(Dir, "ebin"), + case file:read_file_info(BinDir) of + {ok, #file_info{type=directory}} -> + parse({dir, BinDir}); + _ -> + parse({dir, Dir}) + end; + _ -> + throw({application_not_found, A}) + end + end; +parse({application, A, Info}=T) when is_atom(A) -> + case proplists:get_value(modules, Info) of + Ms when is_list(Ms) -> + case [M || M <- Ms, not is_atom(M)] of + [] -> + {data, {"application '" ++ atom_to_list(A) ++ "'", Ms}}; + _ -> + bad_test(T) + end; + _ -> + bad_test(T) + end; +parse({file, F} = T) when is_list(F) -> + case eunit_lib:is_string(F) of + true -> + {data, {"file \"" ++ F ++ "\"", get_file_tests(F)}}; + false -> + bad_test(T) + end; +parse({dir, D}=T) when is_list(D) -> + case eunit_lib:is_string(D) of + true -> + {data, {"directory \"" ++ D ++ "\"", get_directory_modules(D)}}; + false -> + bad_test(T) + end; +parse({with, X, As}=T) when is_list(As) -> + case As of + [A | As1] -> + check_arity(A, 1, T), + {data, [{eunit_lib:fun_parent(A), fun () -> A(X) end}, + {with, X, As1}]}; + [] -> + {data, []} + end; +parse({S, T1} = T) when is_list(S) -> + case eunit_lib:is_string(S) of + true -> + group(#group{tests = T1, desc = list_to_binary(S)}); + false -> + bad_test(T) + end; +parse({S, T1}) when is_binary(S) -> + group(#group{tests = T1, desc = S}); +parse(T) when tuple_size(T) > 2, is_list(element(1, T)) -> + [S | Es] = tuple_to_list(T), + parse({S, list_to_tuple(Es)}); +parse(T) when tuple_size(T) > 2, is_binary(element(1, T)) -> + [S | Es] = tuple_to_list(T), + parse({S, list_to_tuple(Es)}); +parse(M) when is_atom(M) -> + parse({module, M}); +parse(T) when is_list(T) -> + case eunit_lib:is_string(T) of + true -> + try parse({dir, T}) + catch + {file_read_error,{R,_,_}} + when R =:= enotdir; R =:= enoent -> + parse({file, T}) + end; + false -> + bad_test(T) + end; +parse(T) -> + parse_simple(T). + +%% parse_simple always produces a #test{} record + +parse_simple({L, F}) when is_integer(L), L >= 0 -> + (parse_simple(F))#test{line = L}; +parse_simple({{M,N,A}=Loc, F}) when is_atom(M), is_atom(N), is_integer(A) -> + (parse_simple(F))#test{location = Loc}; +parse_simple(F) -> + parse_function(F). + +parse_function(F) when is_function(F) -> + check_arity(F, 0, F), + #test{f = F, location = eunit_lib:fun_parent(F)}; +parse_function({M,F}) when is_atom(M), is_atom(F) -> + #test{f = eunit_test:function_wrapper(M, F), location = {M, F, 0}}; +parse_function(F) -> + bad_test(F). + +check_arity(F, N, T) when is_function(F) -> + case erlang:fun_info(F, arity) of + {arity, N} -> + ok; + _ -> + bad_test(T) + end; +check_arity(_, _, T) -> + bad_test(T). + +check_setup_list([{Tag, S, C} | Es], T) + when is_atom(Tag), is_function(S), is_function(C) -> + check_arity(S, 0, T), + check_arity(C, 1, T), + check_setup_list(Es, T); +check_setup_list([{Tag, S} | Es], T) + when is_atom(Tag), is_function(S) -> + check_arity(S, 0, T), + check_setup_list(Es, T); +check_setup_list([], _T) -> + ok; +check_setup_list(_, T) -> + bad_test(T). + +bad_test(T) -> + throw({bad_test, T}). + +ok(_) -> ok. +ok(_, _) -> ok. + +%% This does some look-ahead and folds nested groups and tests where +%% possible. E.g., {String, Test} -> Test#test{desc = String}. + +group(#group{context = #context{}} = G) -> + %% leave as it is - the test body is an instantiator, which is not + %% suitable for lookahead (and anyway, properties of the setup + %% should not be merged with properties of its body, e.g. spawn) + G; +group(#group{tests = T0, desc = Desc, order = Order, context = Context, + spawn = Spawn, timeout = Timeout} = G) -> + {T1, Ts} = lookahead(T0), + {T2, _} = lookahead(Ts), + case T1 of + #test{desc = Desc1, timeout = Timeout1} + when T2 =:= none, Spawn =:= undefined, Context =:= undefined, + ((Desc =:= undefined) or (Desc1 =:= undefined)), + ((Timeout =:= undefined) or (Timeout1 =:= undefined)) -> + %% a single test within a non-spawn/setup group: put the + %% information directly on the test; drop the order + T1#test{desc = join_properties(Desc, Desc1), + timeout = join_properties(Timeout, Timeout1)}; + + #test{timeout = undefined} + when T2 =:= none, Timeout =/= undefined, Context =:= undefined -> + %% a single test without timeout, within a non-joinable + %% group with a timeout and no fixture: push the timeout to + %% the test + G#group{tests = {timeout, (Timeout div ?TICKS_PER_SECOND), T0}, + timeout = undefined}; + + #group{desc = Desc1, order = Order1, context = Context1, + spawn = Spawn1, timeout = Timeout1} + when T2 =:= none, + ((Desc =:= undefined) or (Desc1 =:= undefined)), + ((Order =:= undefined) or (Order1 =:= undefined)), + ((Context =:= undefined) or (Context1 =:= undefined)), + ((Spawn =:= undefined) or (Spawn1 =:= undefined)), + ((Timeout =:= undefined) or (Timeout1 =:= undefined)) -> + %% two nested groups with non-conflicting properties + group(T1#group{desc = join_properties(Desc, Desc1), + order = join_properties(Order, Order1), + context = join_properties(Context, Context1), + spawn = join_properties(Spawn, Spawn1), + timeout = join_properties(Timeout, Timeout1)}); + + #group{order = Order1, timeout = Timeout1} + when T2 =:= none -> + %% two nested groups that cannot be joined: try to push the + %% timeout and ordering properties to the inner group + push_order(Order, Order1, push_timeout(Timeout, Timeout1, G)); + + _ -> + %% leave the group as it is and discard the lookahead + G + end. + +lookahead(T) -> + case next(T) of + {T1, Ts} -> {T1, Ts}; + none -> {none, []} + end. + +join_properties(undefined, X) -> X; +join_properties(X, undefined) -> X. + +push_timeout(Timeout, undefined, G=#group{context=undefined}) + when Timeout =/= undefined -> + %% A timeout on a context (fixture) includes the setup/cleanup time + %% and must not be propagated into the body + G#group{tests = {timeout, (Timeout div ?TICKS_PER_SECOND), G#group.tests}, + timeout = undefined}; +push_timeout(_, _, G) -> + G. + +push_order(inorder, undefined, G) -> + G#group{tests = {inorder, G#group.tests}, order = undefined}; +push_order({inparallel, N}, undefined, G) -> + G#group{tests = {inparallel, N, G#group.tests}, order = undefined}; +push_order(_, _, G) -> + G. + +%% --------------------------------------------------------------------- +%% Extracting test funs from a module + +%% @throws {module_not_found, moduleName()} + +get_module_tests(M) -> + try M:module_info(exports) of + Es -> + Fs = get_module_tests_1(M, Es), + W = ?DEFAULT_MODULE_WRAPPER_NAME, + case lists:member({W,1}, Es) of + false -> Fs; + true -> {generator, fun () -> M:W(Fs) end} + end + catch + error:undef -> + throw({module_not_found, M}) + end. + +get_module_tests_1(M, Es) -> + Fs = testfuns(Es, M, ?DEFAULT_TEST_SUFFIX, + ?DEFAULT_GENERATOR_SUFFIX), + Name = atom_to_list(M), + case lists:suffix(?DEFAULT_TESTMODULE_SUFFIX, Name) of + false -> + Name1 = Name ++ ?DEFAULT_TESTMODULE_SUFFIX, + M1 = list_to_atom(Name1), + try get_module_tests(M1) of + Fs1 -> + Fs ++ [{"module '" ++ Name1 ++ "'", Fs1}] + catch + {module_not_found, M1} -> + Fs + end; + true -> + Fs + end. + +testfuns(Es, M, TestSuffix, GeneratorSuffix) -> + foldr(fun ({F, 0}, Fs) -> + N = atom_to_list(F), + case lists:suffix(TestSuffix, N) of + true -> + [{M,F} | Fs]; + false -> + case lists:suffix(GeneratorSuffix, N) of + true -> + [{generator, M, F} | Fs]; + false -> + Fs + end + end; + (_, Fs) -> + Fs + end, + [], + Es). + + +%% --------------------------------------------------------------------- +%% Getting a test set from a file + +%% @throws {file_read_error, {Reason::atom(), Message::string(), +%% fileName()}} + +get_file_tests(F) -> + case is_module_filename(F) of + true -> + %% look relative to current dir first + case file:read_file_info(F) of + {ok, #file_info{type=regular}} -> + objfile_test(F); + _ -> + %% (where_is_file/1 does not take a path argument) + case code:where_is_file(F) of + non_existing -> + %% this will produce a suitable error message + objfile_test(F); + Path -> + objfile_test(Path) + end + end; + false -> + eunit_lib:consult_file(F) + end. + +is_module_filename(F) -> + filename:extension(F) =:= code:objfile_extension(). + +objfile_test(File) -> + try + {module, M} = lists:keyfind(module, 1, beam_lib:info(File)), + {setup, + fun () -> + %% TODO: better error/stacktrace for this internal fun + code:purge(M), + {module,M} = code:load_abs(filename:rootname(File)), + ok + end, + {module, M}} + catch + _:_ -> + throw({file_read_error, + {undefined, "extracting module name failed", File}}) + end. + + +%% --------------------------------------------------------------------- +%% Getting a list of module names from object files in a directory + +%% @throws {file_read_error, {Reason::atom(), Message::string(), +%% fileName()}} + +%% TODO: handle packages (recursive search for files) + +get_directory_modules(D) -> + [objfile_test(filename:join(D, F)) + || F <- eunit_lib:list_dir(D), is_module_filename(F)]. + + + +%% --------------------------------------------------------------------- +%% Entering a setup-context, with guaranteed cleanup. + +%% @spec (Tests::#context{}, Instantiate, Callback) -> any() +%% Instantiate = (any()) -> tests() +%% Callback = (tests()) -> any() +%% @throws {context_error, Error, eunit_lib:exception()} +%% Error = setup_failed | instantiation_failed | cleanup_failed + +enter_context(#context{setup = S, cleanup = C, process = P}, I, F) -> + F1 = case P of + local -> F; + spawn -> fun (X) -> F({spawn, X}) end; + {spawn, N} -> fun (T) -> F({spawn, N, T}) end + end, + eunit_test:enter_context(S, C, I, F1). + + +-ifdef(TEST). +generator_exported_() -> + generator(). + +generator() -> + T = ?_test(ok), + [T, T, T]. + +echo_proc() -> + receive {P,X} -> P ! X, echo_proc() end. + +ping(P) -> + P ! {self(),ping}, receive ping -> ok end. + +data_test_() -> + Setup = fun () -> spawn(fun echo_proc/0) end, + Cleanup = fun (Pid) -> exit(Pid, kill) end, + Fail = ?_test(throw(eunit)), + T = ?_test(ok), + Tests = [T,T,T], + [?_assertMatch(ok, eunit:test(T)), + ?_assertMatch(error, eunit:test(Fail)), + ?_assertMatch(ok, eunit:test({generator, fun () -> Tests end})), + ?_assertMatch(ok, eunit:test({generator, fun generator/0})), + ?_assertMatch(ok, eunit:test({generator, ?MODULE, generator_exported_})), + ?_assertMatch(ok, eunit:test({inorder, Tests})), + ?_assertMatch(ok, eunit:test({inparallel, Tests})), + ?_assertMatch(ok, eunit:test({timeout, 10, Tests})), + ?_assertMatch(ok, eunit:test({spawn, Tests})), + ?_assertMatch(ok, eunit:test({setup, Setup, Cleanup, + fun (P) -> ?_test(ok = ping(P)) end})), + %%?_assertMatch(ok, eunit:test({node, test@localhost, Tests})), + ?_assertMatch(ok, eunit:test({module, eunit_lib})), + ?_assertMatch(ok, eunit:test(eunit_lib)), + ?_assertMatch(ok, eunit:test("examples/tests.txt")) + + %%?_test({foreach, Setup, [T, T, T]}) + ]. + +lazy_test_() -> + {spawn, [?_test(undefined = put(count, 0)), + lazy_gen(7), + ?_assertMatch(7, get(count))]}. + +lazy_gen(N) -> + {generator, + fun () -> + if N > 0 -> + [?_test(put(count,1+get(count))) + | lazy_gen(N-1)]; + true -> + [] + end + end}. +-endif. diff --git a/lib/eunit/src/eunit_internal.hrl b/lib/eunit/src/eunit_internal.hrl new file mode 100644 index 0000000000..8d0ac30bd7 --- /dev/null +++ b/lib/eunit/src/eunit_internal.hrl @@ -0,0 +1,48 @@ +%% ------------------------------------------------------------------- +%% File: eunit_internal.hrl +%% +%% $Id: eunit_internal.hrl 329 2009-03-01 11:23:32Z rcarlsson $ +%% +%% @author Richard Carlsson +%% @copyright 2006 Richard Carlsson +%% @doc + +-define(SERVER, eunit_server). +-define(DEFAULT_TEST_SUFFIX, "_test"). +-define(DEFAULT_GENERATOR_SUFFIX, "_test_"). +-define(DEFAULT_EXPORT_SUFFIX, "_exported_"). +-define(DEFAULT_TESTMODULE_SUFFIX, "_tests"). +-define(DEFAULT_GROUP_TIMEOUT, infinity). +-define(DEFAULT_TEST_TIMEOUT, 5000). +-define(DEFAULT_SETUP_PROCESS, spawn). +-define(DEFAULT_MODULE_WRAPPER_NAME, eunit_wrapper_). + +-ifdef(DEBUG). +-define(debugmsg(S),io:fwrite("\n* ~s: ~s\n", [?MODULE,S])). +-define(debugmsg1(S,As),io:fwrite("\n* ~s: " ++ S ++ "\n", [?MODULE] ++ As)). +-else. +-define(debugmsg(S),ok). +-define(debugmsg1(S,As),ok). +-endif. + + +%% --------------------------------------------------------------------- +%% Internal test data representation + +-record(test, {f = undefined, + desc = undefined, + timeout = undefined, + location = undefined, + line = 0 + }). + +-record(group, {desc = undefined, + order = undefined, % run in order or in parallel + timeout = undefined, + context = undefined, % setup-context record + spawn = undefined, % run group in new process + tests = undefined}). + +-record(context, {setup = undefined, + cleanup = undefined, + process = local}). % spawn new process for body diff --git a/lib/eunit/src/eunit_lib.erl b/lib/eunit/src/eunit_lib.erl new file mode 100644 index 0000000000..4751f1094a --- /dev/null +++ b/lib/eunit/src/eunit_lib.erl @@ -0,0 +1,576 @@ +%% This library is free software; you can redistribute it and/or modify +%% it under the terms of the GNU Lesser General Public License as +%% published by the Free Software Foundation; either version 2 of the +%% License, or (at your option) any later version. +%% +%% This library is distributed in the hope that it will be useful, but +%% WITHOUT ANY WARRANTY; without even the implied warranty of +%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%% Lesser General Public License for more details. +%% +%% You should have received a copy of the GNU Lesser General Public +%% License along with this library; if not, write to the Free Software +%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +%% USA +%% +%% $Id: eunit_lib.erl 339 2009-04-05 14:10:47Z rcarlsson $ +%% +%% @copyright 2004-2007 Mickaël Rémond, Richard Carlsson +%% @author Mickaël Rémond +%% [http://www.process-one.net/] +%% @author Richard Carlsson +%% [http://user.it.uu.se/~richardc/] +%% @private +%% @see eunit +%% @doc Utility functions for eunit + +-module(eunit_lib). + +-include("eunit.hrl"). +-include("eunit_internal.hrl"). + + +-export([dlist_next/1, uniq/1, fun_parent/1, is_string/1, command/1, + command/2, command/3, trie_new/0, trie_store/2, trie_match/2, + split_node/1, consult_file/1, list_dir/1, format_exit_term/1, + format_exception/1, format_error/1]). + + +%% Type definitions for describing exceptions +%% +%% @type exception() = {exceptionClass(), Reason::term(), stackTrace()} +%% +%% @type exceptionClass() = error | exit | throw +%% +%% @type stackTrace() = [{moduleName(), functionName(), +%% arity() | argList()}] +%% +%% @type moduleName() = atom() +%% @type functionName() = atom() +%% @type arity() = integer() +%% @type mfa() = {moduleName(), functionName(), arity()} +%% @type argList() = [term()] +%% @type fileName() = string() + + +%% --------------------------------------------------------------------- +%% Formatting of error descriptors + +format_exception({Class,Term,Trace}) + when is_atom(Class), is_list(Trace) -> + case is_stacktrace(Trace) of + true -> + io_lib:format("~w:~P\n~s", + [Class, Term, 20, format_stacktrace(Trace)]); + false -> + format_term(Term) + end; +format_exception(Term) -> + format_term(Term). + +format_term(Term) -> + io_lib:format("~P\n", [Term, 15]). + +format_exit_term(Term) -> + {Reason, Trace} = analyze_exit_term(Term), + io_lib:format("~P~s", [Reason, 15, Trace]). + +analyze_exit_term({Reason, [_|_]=Trace}=Term) -> + case is_stacktrace(Trace) of + true -> + {Reason, format_stacktrace(Trace)}; + false -> + {Term, ""} + end; +analyze_exit_term(Term) -> + {Term, ""}. + +is_stacktrace([]) -> + true; +is_stacktrace([{M,F,A}|Fs]) when is_atom(M), is_atom(F), is_integer(A) -> + is_stacktrace(Fs); +is_stacktrace([{M,F,As}|Fs]) when is_atom(M), is_atom(F), is_list(As) -> + is_stacktrace(Fs); +is_stacktrace(_) -> + false. + +format_stacktrace(Trace) -> + format_stacktrace(Trace, "in function", "in call from"). + +format_stacktrace([{M,F,A}|Fs], Pre, Pre1) when is_integer(A) -> + [io_lib:fwrite(" ~s ~w:~w/~w\n", [Pre, M, F, A]) + | format_stacktrace(Fs, Pre1, Pre1)]; +format_stacktrace([{M,F,As}|Fs], Pre, Pre1) when is_list(As) -> + A = length(As), + C = case is_op(M,F,A) of + true when A =:= 1 -> + [A1] = As, + io_lib:fwrite("~s ~s", [F,format_arg(A1)]); + true when A =:= 2 -> + [A1, A2] = As, + io_lib:fwrite("~s ~s ~s", + [format_arg(A1),F,format_arg(A2)]); + false -> + io_lib:fwrite("~w(~s)", [F,format_arglist(As)]) + end, + [io_lib:fwrite(" ~s ~w:~w/~w\n called as ~s\n", + [Pre,M,F,A,C]) + | format_stacktrace(Fs,Pre1,Pre1)]; +format_stacktrace([],_Pre,_Pre1) -> + "". + +format_arg(A) -> + io_lib:format("~P",[A,15]). + +format_arglist([A]) -> + format_arg(A); +format_arglist([A|As]) -> + [io_lib:format("~P,",[A,15]) | format_arglist(As)]; +format_arglist([]) -> + "". + +is_op(erlang, F, A) -> + erl_internal:arith_op(F, A) + orelse erl_internal:bool_op(F, A) + orelse erl_internal:comp_op(F, A) + orelse erl_internal:list_op(F, A) + orelse erl_internal:send_op(F, A); +is_op(_M, _F, _A) -> + false. + +format_error({bad_test, Term}) -> + error_msg("bad test descriptor", "~P", [Term, 15]); +format_error({generator_failed, Exception}) -> + error_msg("test generator failed", "~s", + [format_exception(Exception)]); +format_error({no_such_function, {M,F,A}}) + when is_atom(M), is_atom(F), is_integer(A) -> + error_msg(io_lib:format("no such function: ~w:~w/~w", [M,F,A]), + "", []); +format_error({module_not_found, M}) -> + error_msg("test module not found", "~p", [M]); +format_error({application_not_found, A}) when is_atom(A) -> + error_msg("application not found", "~w", [A]); +format_error({file_read_error, {_R, Msg, F}}) -> + error_msg("error reading file", "~s: ~s", [Msg, F]); +format_error({setup_failed, Exception}) -> + error_msg("context setup failed", "~s", + [format_exception(Exception)]); +format_error({cleanup_failed, Exception}) -> + error_msg("context cleanup failed", "~s", + [format_exception(Exception)]); +format_error({instantiation_failed, Exception}) -> + error_msg("instantiation of subtests failed", "~s", + [format_exception(Exception)]). + +error_msg(Title, Fmt, Args) -> + Msg = io_lib:format("::"++Fmt, Args), % gets indentation right + io_lib:fwrite("*** ~s ***\n~s\n\n", [Title, Msg]). + + +%% --------------------------------------------------------------------- +%% Deep list iterator; accepts improper lists/sublists, and also accepts +%% non-lists on the top level. Nonempty strings (not deep strings) are +%% recognized as separate elements, even on the top level. (It is not +%% recommended to include integers in the deep list, since a list of +%% integers is likely to be interpreted as a string.). The result is +%% always presented as a list (which may be improper), which is either +%% empty or otherwise has a non-list head element. + +dlist_next([X | Xs] = Xs0) when is_list(X) -> + case is_nonempty_string(X) of + true -> Xs0; + false -> dlist_next(X, Xs) + end; +dlist_next([_|_] = Xs) -> + case is_nonempty_string(Xs) of + true -> [Xs]; + false -> Xs + end; +dlist_next([]) -> + []; +dlist_next(X) -> + [X]. + +%% the first two clauses avoid pushing empty lists on the stack +dlist_next([X], Ys) when is_list(X) -> + case is_nonempty_string(X) of + true -> [X | Ys]; + false -> dlist_next(X, Ys) + end; +dlist_next([X], Ys) -> + [X | Ys]; +dlist_next([X | Xs], Ys) when is_list(X) -> + case is_nonempty_string(X) of + true -> [X | [Xs | Ys]]; + false -> dlist_next(X, [Xs | Ys]) + end; +dlist_next([X | Xs], Ys) -> + [X | [Xs | Ys]]; +dlist_next([], Xs) -> + dlist_next(Xs). + + +-ifdef(TEST). +dlist_test_() -> + {"deep list traversal", + [{"non-list term -> singleton list", + ?_test([any] = dlist_next(any))}, + {"empty list -> empty list", + ?_test([] = dlist_next([]))}, + {"singleton list -> singleton list", + ?_test([any] = dlist_next([any]))}, + {"taking the head of a flat list", + ?_test([a,b,c] = dlist_next([a,b,c]))}, + {"skipping an initial empty list", + ?_test([a,b,c] = dlist_next([[],a,b,c]))}, + {"skipping nested initial empty lists", + ?_test([a,b,c] = dlist_next([[[[]]],a,b,c]))}, + {"skipping a final empty list", + ?_test([] = dlist_next([[]]))}, + {"skipping nested final empty lists", + ?_test([] = dlist_next([[[[]]]]))}, + {"the first element is in a sublist", + ?_test([a,b,c] = dlist_next([[a],b,c]))}, + {"recognizing a naked string", + ?_test(["abc"] = dlist_next("abc"))}, + {"recognizing a wrapped string", + ?_test(["abc"] = dlist_next(["abc"]))}, + {"recognizing a leading string", + ?_test(["abc",a,b,c] = dlist_next(["abc",a,b,c]))}, + {"recognizing a nested string", + ?_test(["abc"] = dlist_next([["abc"]]))}, + {"recognizing a leading string in a sublist", + ?_test(["abc",a,b,c] = dlist_next([["abc"],a,b,c]))}, + {"traversing an empty list", + ?_test([] = dlist_flatten([]))}, + {"traversing a flat list", + ?_test([a,b,c] = dlist_flatten([a,b,c]))}, + {"traversing a deep list", + ?_test([a,b,c] = dlist_flatten([[],[a,[b,[]],c],[]]))}, + {"traversing a deep but empty list", + ?_test([] = dlist_flatten([[],[[[]]],[]]))} + ]}. + +%% test support +dlist_flatten(Xs) -> + case dlist_next(Xs) of + [X | Xs1] -> [X | dlist_flatten(Xs1)]; + [] -> [] + end. +-endif. + + +%% --------------------------------------------------------------------- +%% Check for proper Unicode-stringness. + +is_string([C | Cs]) when is_integer(C), C >= 0, C =< 16#10ffff -> + is_string(Cs); +is_string([_ | _]) -> + false; +is_string([]) -> + true; +is_string(_) -> + false. + +is_nonempty_string([]) -> false; +is_nonempty_string(Cs) -> is_string(Cs). + +-ifdef(TEST). +is_string_test_() -> + {"is_string", + [{"no non-lists", ?_assert(not is_string($A))}, + {"no non-integer lists", ?_assert(not is_string([true]))}, + {"empty string", ?_assert(is_string(""))}, + {"ascii string", ?_assert(is_string(lists:seq(0, 127)))}, + {"latin-1 string", ?_assert(is_string(lists:seq(0, 255)))}, + {"unicode string", + ?_assert(is_string([0, $A, 16#10fffe, 16#10ffff]))}, + {"not above unicode range", + ?_assert(not is_string([0, $A, 16#110000]))}, + {"no negative codepoints", ?_assert(not is_string([$A, -1, 0]))} + ]}. +-endif. + + +%% --------------------------------------------------------------------- +%% Splitting a full node name into basename and hostname, +%% using 'localhost' as the default hostname + +split_node(N) when is_atom(N) -> split_node(atom_to_list(N)); +split_node(Cs) -> split_node_1(Cs, []). + +split_node_1([$@ | Cs], As) -> split_node_2(As, Cs); +split_node_1([C | Cs], As) -> split_node_1(Cs, [C | As]); +split_node_1([], As) -> split_node_2(As, "localhost"). + +split_node_2(As, Cs) -> + {list_to_atom(lists:reverse(As)), list_to_atom(Cs)}. + +%% --------------------------------------------------------------------- +%% Get the name of the containing function for a fun. (This is encoded +%% in the name of the generated function that implements the fun.) +fun_parent(F) -> + {module, M} = erlang:fun_info(F, module), + {name, N} = erlang:fun_info(F, name), + case erlang:fun_info(F, type) of + {type, external} -> + {arity, A} = erlang:fun_info(F, arity), + {M, N, A}; + {type, local} -> + [$-|S] = atom_to_list(N), + C1 = string:chr(S, $/), + C2 = string:chr(S, $-), + {M, list_to_atom(string:sub_string(S, 1, C1 - 1)), + list_to_integer(string:sub_string(S, C1 + 1, C2 - 1))} + end. + +-ifdef(TEST). +fun_parent_test() -> + {?MODULE,fun_parent_test,0} = fun_parent(fun () -> ok end). +-endif. + +%% --------------------------------------------------------------------- +%% Ye olde uniq function + +uniq([X, X | Xs]) -> uniq([X | Xs]); +uniq([X | Xs]) -> [X | uniq(Xs)]; +uniq([]) -> []. + +-ifdef(TEST). +uniq_test_() -> + {"uniq", + [?_assertError(function_clause, uniq(ok)), + ?_assertError(function_clause, uniq([1|2])), + ?_test([] = uniq([])), + ?_test([1,2,3] = uniq([1,2,3])), + ?_test([1,2,3] = uniq([1,2,2,3])), + ?_test([1,2,3,2,1] = uniq([1,2,2,3,2,2,1])), + ?_test([1,2,3] = uniq([1,1,1,2,2,2,3,3,3])), + ?_test(["1","2","3"] = uniq(["1","1","2","2","3","3"])) + ]}. +-endif. + +%% --------------------------------------------------------------------- +%% Replacement for os:cmd + +%% TODO: Better cmd support, especially on Windows (not much tested) +%% TODO: Can we capture stderr separately somehow? + +command(Cmd) -> + command(Cmd, ""). + +command(Cmd, Dir) -> + command(Cmd, Dir, []). + +command(Cmd, Dir, Env) -> + CD = if Dir =:= "" -> []; + true -> [{cd, Dir}] + end, + SetEnv = if Env =:= [] -> []; + true -> [{env, Env}] + end, + Opt = CD ++ SetEnv ++ [stream, exit_status, use_stdio, + stderr_to_stdout, in, eof], + P = open_port({spawn, Cmd}, Opt), + get_data(P, []). + +get_data(P, D) -> + receive + {P, {data, D1}} -> + get_data(P, [D1|D]); + {P, eof} -> + port_close(P), + receive + {P, {exit_status, N}} -> + {N, normalize(lists:flatten(lists:reverse(D)))} + end + end. + +normalize([$\r, $\n | Cs]) -> + [$\n | normalize(Cs)]; +normalize([$\r | Cs]) -> + [$\n | normalize(Cs)]; +normalize([C | Cs]) -> + [C | normalize(Cs)]; +normalize([]) -> + []. + +-ifdef(TEST). + +cmd_test_() -> + ([?_test({0, "hello\n"} = ?_cmd_("echo hello"))] + ++ case os:type() of + {unix, _} -> + unix_cmd_tests(); + {win32, _} -> + win32_cmd_tests(); + _ -> + [] + end). + +unix_cmd_tests() -> + [{"command execution, status, and output", + [?_cmd("echo hello"), + ?_assertCmdStatus(0, "true"), + ?_assertCmdStatus(1, "false"), + ?_assertCmd("true"), + ?_assertCmdOutput("hello\n", "echo hello"), + ?_assertCmdOutput("hello", "echo -n hello") + ]}, + {"file setup and cleanup", + setup, + fun () -> ?cmd("mktemp tmp.XXXXXXXX") end, + fun (File) -> ?cmd("rm " ++ File) end, + fun (File) -> + [?_assertCmd("echo xyzzy >" ++ File), + ?_assertCmdOutput("xyzzy\n", "cat " ++ File)] + end} + ]. + +win32_cmd_tests() -> + [{"command execution, status, and output", + [?_cmd("echo hello"), + ?_assertCmdOutput("hello\n", "echo hello") + ]} + ]. + +-endif. % TEST + + +%% --------------------------------------------------------------------- +%% Wrapper around file:path_consult + +%% @throws {file_read_error, {Reason::atom(), Message::string(), +%% fileName()}} + +consult_file(File) -> + case file:path_consult(["."]++code:get_path(), File) of + {ok, Data, _Path} -> + Data; + {error, Reason} -> + Msg = file:format_error(Reason), + throw({file_read_error, {Reason, Msg, File}}) + end. + +%% --------------------------------------------------------------------- +%% Wrapper around file:list_dir + +%% @throws {file_read_error, {Reason::atom(), Message::string(), +%% fileName()}} + +list_dir(Dir) -> + case file:list_dir(Dir) of + {ok, Fs} -> + Fs; + {error, Reason} -> + Msg = file:format_error(Reason), + throw({file_read_error, {Reason, Msg, Dir}}) + end. + +%% --------------------------------------------------------------------- +%% A trie for remembering and checking least specific cancelled events +%% (an empty list `[]' simply represents a stored empty list, i.e., all +%% events will match, while an empty tree means that no events match). + +trie_new() -> + gb_trees:empty(). + +trie_store([_ | _], []) -> + []; +trie_store([E | Es], T) -> + case gb_trees:lookup(E, T) of + none -> + if Es =:= [] -> + gb_trees:insert(E, [], T); + true -> + gb_trees:insert(E, trie_store(Es, gb_trees:empty()), + T) + end; + {value, []} -> + T; %% prefix already stored + {value, T1} -> + gb_trees:update(E, trie_store(Es, T1), T) + end; +trie_store([], _T) -> + []. + +trie_match([_ | _], []) -> + prefix; +trie_match([E | Es], T) -> + case gb_trees:lookup(E, T) of + none -> + no; + {value, []} -> + if Es =:= [] -> exact; + true -> prefix + end; + {value, T1} -> + trie_match(Es, T1) + end; +trie_match([], []) -> + exact; +trie_match([], _T) -> + no. + +-ifdef(TEST). + +trie_test_() -> + [{"basic representation", + [?_assert(trie_new() =:= gb_trees:empty()), + ?_assert(trie_store([1], trie_new()) + =:= gb_trees:insert(1, [], gb_trees:empty())), + ?_assert(trie_store([1,2], trie_new()) + =:= gb_trees:insert(1, + gb_trees:insert(2, [], + gb_trees:empty()), + gb_trees:empty())), + ?_assert([] =:= trie_store([1], [])), + ?_assert([] =:= trie_store([], gb_trees:empty())) + ]}, + {"basic storing and matching", + [?_test(no = trie_match([], trie_new())), + ?_test(exact = trie_match([], trie_store([], trie_new()))), + ?_test(no = trie_match([], trie_store([1], trie_new()))), + ?_test(exact = trie_match([1], trie_store([1], trie_new()))), + ?_test(prefix = trie_match([1,2], trie_store([1], trie_new()))), + ?_test(no = trie_match([1], trie_store([1,2], trie_new()))), + ?_test(no = trie_match([1,3], trie_store([1,2], trie_new()))), + ?_test(exact = trie_match([1,2,3,4,5], + trie_store([1,2,3,4,5], trie_new()))), + ?_test(prefix = trie_match([1,2,3,4,5], + trie_store([1,2,3], trie_new()))), + ?_test(no = trie_match([1,2,2,4,5], + trie_store([1,2,3], trie_new()))) + ]}, + {"matching with partially overlapping patterns", + setup, + fun () -> + trie_store([1,3,2], trie_store([1,2,3], trie_new())) + end, + fun (T) -> + [?_test(no = trie_match([], T)), + ?_test(no = trie_match([1], T)), + ?_test(no = trie_match([1,2], T)), + ?_test(no = trie_match([1,3], T)), + ?_test(exact = trie_match([1,2,3], T)), + ?_test(exact = trie_match([1,3,2], T)), + ?_test(no = trie_match([1,2,2], T)), + ?_test(no = trie_match([1,3,3], T)), + ?_test(prefix = trie_match([1,2,3,4], T)), + ?_test(prefix = trie_match([1,3,2,1], T))] + end}, + {"matching with more general pattern overriding less general", + setup, + fun () -> trie_store([1], trie_store([1,2,3], trie_new())) end, + fun (_) -> ok end, + fun (T) -> + [?_test(no = trie_match([], T)), + ?_test(exact = trie_match([1], T)), + ?_test(prefix = trie_match([1,2], T)), + ?_test(prefix = trie_match([1,2,3], T)), + ?_test(prefix = trie_match([1,2,3,4], T))] + end} + ]. + +-endif. % TEST diff --git a/lib/eunit/src/eunit_listener.erl b/lib/eunit/src/eunit_listener.erl new file mode 100644 index 0000000000..20faecbf01 --- /dev/null +++ b/lib/eunit/src/eunit_listener.erl @@ -0,0 +1,178 @@ +%% This library is free software; you can redistribute it and/or modify +%% it under the terms of the GNU Lesser General Public License as +%% published by the Free Software Foundation; either version 2 of the +%% License, or (at your option) any later version. +%% +%% This library is distributed in the hope that it will be useful, but +%% WITHOUT ANY WARRANTY; without even the implied warranty of +%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%% Lesser General Public License for more details. +%% +%% You should have received a copy of the GNU Lesser General Public +%% License along with this library; if not, write to the Free Software +%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +%% USA +%% +%% $Id$ +%% +%% @author Richard Carlsson +%% @copyright 2009 Richard Carlsson +%% @private +%% @see eunit +%% @doc Generic listener process for eunit. + +-module(eunit_listener). + +-define(NODEBUG, true). +-include("eunit.hrl"). +-include("eunit_internal.hrl"). + +-export([start/1, start/2]). + +-export([behaviour_info/1]). + + +behaviour_info(callbacks) -> + [{init,1},{handle_begin,3},{handle_end,3},{handle_cancel,3}, + {terminate,2}]; +behaviour_info(_Other) -> + undefined. + + +-record(state, {callback, % callback module + pass = 0, + fail = 0, + skip = 0, + cancel = 0, + state % substate + }). + +start(Callback) -> + start(Callback, []). + +start(Callback, Options) -> + St = #state{callback = Callback}, + spawn_opt(fun () -> init(St, Options) end, + proplists:get_all_values(spawn, Options)). + +init(St0, Options) -> + St1 = call(init, [Options], St0), + St2 = expect([], undefined, St1), + Data = [{pass, St2#state.pass}, + {fail, St2#state.fail}, + {skip, St2#state.skip}, + {cancel, St2#state.cancel}], + call(terminate, [{ok, Data}, St2#state.state], St2), + exit(normal). + +expect(Id, ParentId, St) -> + case wait_for(Id, 'begin', ParentId) of + {done, Data} -> + {done, Data, St}; + {ok, Msg} -> + case Msg of + {group, Data} -> + group(Id, Data, St); + {test, Data} -> + St1 = handle_begin(test, Id, Data, St), + case wait_for(Id, 'end', ParentId) of + {cancel, Reason} -> + handle_cancel(test, Id, Data, Reason, St1); + {ok, Result} -> + handle_end(test, Id, Data, Result, St1) + end + end + end. + +%% collect group items in order until group is done +group(Id, Data, St) -> + St1 = handle_begin(group, Id, Data, St), + group_loop(0, Id, Data, St1). + +group_loop(N, Id, Data, St) -> + N1 = N + 1, + case expect(Id ++ [N1], Id, St) of + {done, {cancel, Reason}, St1} -> + handle_cancel(group, Id, Data, Reason, St1); + {done, Result, St1} -> + handle_end(group, Id, Data, Result, St1); + St1 -> + group_loop(N1, Id, Data, St1) + end. + +%% waiting for [..., M, N] begin +%% get: +%% [..., M, N] begin test -> expect [..., M, N] end (test begin) +%% [..., M, N] begin group -> expect [..., M, N, 1] end (group begin) +%% [..., M] end -> expect [..., M+1] begin (parent end) +%% cancel([..., M]) (parent cancel) +%% +%% waiting for [..., M, N] end +%% get: +%% [..., M, N] end -> expect [..., M, N+1] begin (seen end) +%% cancel([..., M, N]) (cancelled) + +wait_for(Id, Type, ParentId) -> + ?debugFmt("waiting for ~w ~w", [Id, Type]), + receive + {status, Id, {progress, Type, Data}} -> + ?debugFmt("got status ~w ~w", [Id, Data]), + {ok, Data}; + {status, ParentId, {progress, 'end', Data}} when Type =:= 'begin' -> + ?debugFmt("got parent end ~w ~w", [ParentId, Data]), + {done, Data}; + {status, Id, {cancel, Reason}} when Type =:= 'end' -> + ?debugFmt("got cancel ~w ~w", [Id, Reason]), + {cancel, Reason}; + {status, ParentId, {cancel, _Reason}} -> + ?debugFmt("got parent cancel ~w ~w", [ParentId, _Reason]), + {done, {cancel, _Reason}} + end. + +call(F, As, St) when is_atom(F) -> + try apply(St#state.callback, F, As) of + Substate -> St#state{state = Substate} + catch + Class:Term -> + Trace = erlang:get_stacktrace(), + if F =/= terminate -> + call(terminate, [{error, {Class, Term, Trace}}, + St#state.state], St); + true -> ok + end, + erlang:raise(Class, Term, Trace) + end. + +handle_begin(group, Id, Data0, St) -> + Data = [{id, Id} | Data0], + ?debugFmt("handle_begin group ~w ~w", [Id, Data0]), + call(handle_begin, [group, Data, St#state.state], St); +handle_begin(test, Id, Data0, St) -> + Data = [{id, Id} | Data0], + ?debugFmt("handle_begin test ~w ~w", [Id, Data0]), + call(handle_begin, [test, Data, St#state.state], St). + +handle_end(group, Id, Data0, {Count, Data1}, St) -> + Data = [{id, Id}, {size, Count} | Data0 ++ Data1], + ?debugFmt("handle_end group ~w ~w", [Id, {Count, Data1}]), + call(handle_end, [group, Data, St#state.state], St); +handle_end(test, Id, Data0, {Status, Data1}, St) -> + Data = [{id, Id}, {status, Status} | Data0 ++ Data1], + ?debugFmt("handle_end test ~w ~w", [Id, {Status, Data1}]), + St1 = case Status of + ok -> St#state{pass = St#state.pass + 1}; + {skipped,_} -> St#state{skip = St#state.skip + 1}; + {error,_} -> St#state{fail = St#state.fail + 1} + end, + call(handle_end, [test, Data, St#state.state], St1). + +handle_cancel(group, Id, Data0, Reason, St) -> + Data = [{id, Id}, {reason, Reason} | Data0], + ?debugFmt("handle_cancel group ~w ~w", [Id, Reason]), + call(handle_cancel, [group, Data, St#state.state], + St#state{cancel = St#state.cancel + 1}); +handle_cancel(test, Id, Data0, Reason, St) -> + Data = [{id, Id}, {reason, Reason} | Data0], + ?debugFmt("handle_cancel test ~w ~w", [Id, Reason]), + call(handle_cancel, [test, Data, St#state.state], + St#state{cancel = St#state.cancel + 1}). diff --git a/lib/eunit/src/eunit_proc.erl b/lib/eunit/src/eunit_proc.erl new file mode 100644 index 0000000000..e2d51d8bd5 --- /dev/null +++ b/lib/eunit/src/eunit_proc.erl @@ -0,0 +1,661 @@ +%% This library is free software; you can redistribute it and/or modify +%% it under the terms of the GNU Lesser General Public License as +%% published by the Free Software Foundation; either version 2 of the +%% License, or (at your option) any later version. +%% +%% This library is distributed in the hope that it will be useful, but +%% WITHOUT ANY WARRANTY; without even the implied warranty of +%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%% Lesser General Public License for more details. +%% +%% You should have received a copy of the GNU Lesser General Public +%% License along with this library; if not, write to the Free Software +%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +%% USA +%% +%% $Id$ +%% +%% @author Richard Carlsson +%% @copyright 2006 Richard Carlsson +%% @private +%% @see eunit +%% @doc Test runner process tree functions + +-module(eunit_proc). + +-include("eunit.hrl"). +-include("eunit_internal.hrl"). + +-export([start/4]). + +%% This must be exported; see new_group_leader/1 for details. +-export([group_leader_process/1]). + +-record(procstate, {ref, id, super, insulator, parent, order}). + + +%% Spawns test process and returns the process Pid; sends {done, +%% Reference, Pid} to caller when finished. See the function +%% wait_for_task/2 for details about the need for the reference. +%% +%% The `Super' process receives a stream of status messages; see +%% message_super/3 for details. + +start(Tests, Order, Super, Reference) + when is_pid(Super), is_reference(Reference) -> + St = #procstate{ref = Reference, + id = [], + super = Super, + order = Order}, + spawn_group(local, #group{tests = Tests}, St). + + +%% Status messages sent to the supervisor process. (A supervisor does +%% not have to act on these messages - it can e.g. just log them, or +%% even discard them.) Each status message has the following form: +%% +%% {status, Id, Info} +%% +%% where Id identifies the item that the message pertains to, and the +%% Info part can be one of: +%% +%% {progress, 'begin', {test | group, Data}} +%% indicates that the item has been entered, and what type it is; +%% Data is [{desc,binary()}, {source,Source}, {line,integer()}] for +%% a test, and [{desc,binary()}, {spawn,SpawnType}, +%% {order,OrderType}] for a group. +%% +%% {progress, 'end', {Status, Data}} +%% Status = 'ok' | {error, Exception} | {skipped, Cause} | integer() +%% Data = [{time,integer()}, {output,binary()}] +%% +%% where Time is measured in milliseconds and Output is the data +%% written to the standard output stream during the test; if +%% Status is {skipped, Cause}, then Cause is a term thrown from +%% eunit_test:run_testfun/1. For a group item, the Status field is +%% the number of immediate subitems of the group; this helps the +%% collation of results. Failure for groups is always signalled +%% through a cancel message, not through the Status field. +%% +%% {cancel, Descriptor} +%% where Descriptor can be: +%% timeout a timeout occurred +%% {blame, Id} forced to terminate because of item `Id' +%% {abort, Cause} the test or group failed to execute +%% {exit, Reason} the test process terminated unexpectedly +%% {startup, Reason} failed to start a remote test process +%% +%% where Cause is a term thrown from eunit_data:enter_context/4 or +%% from eunit_data:iter_next/2, and Reason is an exit term from a +%% crashed process +%% +%% Note that due to concurrent (and possibly distributed) execution, +%% there are *no* strict ordering guarantees on the status messages, +%% with one exception: a 'begin' message will always arrive before its +%% corresponding 'end' message. + +message_super(Id, Info, St) -> + St#procstate.super ! {status, Id, Info}. + + +%% @TODO implement synchronized mode for insulator/child execution + +%% Ideas for synchronized mode: +%% +%% * At each "program point", i.e., before entering a test, entering a +%% group, or leaving a group, the child will synchronize with the +%% insulator to make sure it is ok to proceed. +%% +%% * The insulator can receive controlling messages from higher up in +%% the hierarchy, telling it to pause, resume, single-step, repeat, etc. +%% +%% * Synchronization on entering/leaving groups is necessary in order to +%% get control over things such as subprocess creation/termination and +%% setup/cleanup, making it possible to, e.g., repeat all the tests +%% within a particular subprocess without terminating and restarting it, +%% or repeating tests without repeating the setup/cleanup. +%% +%% * Some tests that depend on state will not be possible to repeat, but +%% require a fresh context setup. There is nothing that can be done +%% about this, and the many tests that are repeatable should not be +%% punished because of it. The user must decide which level to restart. +%% +%% * Question: How propagate control messages down the hierarchy +%% (preferably only to the correct insulator process)? An insulator does +%% not currenctly know whether its child process has spawned subtasks. +%% (The "supervisor" process does not know the Pids of the controlling +%% insulator processes in the tree, and it probably should not be +%% responsible for this anyway.) + + +%% --------------------------------------------------------------------- +%% Process tree primitives + +%% A "task" consists of an insulator process and a child process which +%% handles the actual work. When the child terminates, the insulator +%% process sends {done, Reference, self()} to the process which started +%% the task (the "parent"). The child process is given a State record +%% which contains the process id:s of the parent, the insulator, and the +%% supervisor. + +%% @spec (Type, (#procstate{}) -> () -> term(), #procstate{}) -> pid() +%% Type = local | {remote, Node::atom()} + +start_task(Type, Fun, St0) -> + St = St0#procstate{parent = self()}, + %% (note: the link here is mainly to propagate signals *downwards*, + %% so that the insulator can detect if the process that started the + %% task dies before the task is done) + F = fun () -> insulator_process(Type, Fun, St) end, + case Type of + local -> + %% we assume (at least for now) that local spawns can never + %% fail in such a way that the process does not start, so a + %% new local insulator does not need to synchronize here + spawn_link(F); + {remote, Node} -> + Pid = spawn_link(Node, F), + %% See below for the need for the {ok, Reference, Pid} + %% message. + Reference = St#procstate.ref, + Monitor = erlang:monitor(process, Pid), + %% (the DOWN message is guaranteed to arrive after any + %% messages sent by the process itself) + receive + {ok, Reference, Pid} -> + Pid; + {'DOWN', Monitor, process, Pid, Reason} -> + %% send messages as if the insulator process was + %% started, but terminated on its own accord + Msg = {startup, Reason}, + message_super(St#procstate.id, {cancel, Msg}, St), + self() ! {done, Reference, Pid} + end, + erlang:demonitor(Monitor, [flush]), + Pid + end. + +%% Relatively simple, and hopefully failure-proof insulator process +%% (This is cleaner than temporarily setting up the caller to trap +%% signals, and does not affect the caller's mailbox or other state.) +%% +%% We assume that nobody does a 'kill' on an insulator process - if that +%% should happen, the test framework will hang since the insulator will +%% never send a reply; see below for more. +%% +%% Note that even if the insulator process itself never fails, it is +%% still possible that it does not start properly, if it is spawned +%% remotely (e.g., if the remote node is down). Therefore, remote +%% insulators must always immediately send an {ok, Reference, self()} +%% message to the parent as soon as it is spawned. + +%% @spec (Type, Fun::() -> term(), St::#procstate{}) -> ok +%% Type = local | {remote, Node::atom()} + +insulator_process(Type, Fun, St0) -> + process_flag(trap_exit, true), + Parent = St0#procstate.parent, + if Type =:= local -> ok; + true -> Parent ! {ok, St0#procstate.ref, self()} + end, + St = St0#procstate{insulator = self()}, + Child = spawn_link(fun () -> child_process(Fun(St), St) end), + insulator_wait(Child, Parent, [], St). + +%% Normally, child processes exit with the reason 'normal' even if the +%% executed tests failed (by throwing exceptions), since the tests are +%% executed within a try-block. Child processes can terminate abnormally +%% by the following reasons: +%% 1) an error in the processing of the test descriptors (a malformed +%% descriptor, failure in a setup, cleanup or initialization, a +%% missing module or function, or a failing generator function); +%% 2) an internal error in the test running framework itself; +%% 3) receiving a non-trapped error signal as a consequence of running +%% test code. +%% Those under point 1 are "expected errors", handled specially in the +%% protocol, while the other two are unexpected errors. (Since alt. 3 +%% implies that the test neither reported success nor failure, it can +%% never be considered "proper" behaviour of a test.) Abnormal +%% termination is reported to the supervisor process but otherwise does +%% not affect the insulator compared to normal termination. Child +%% processes can also be killed abruptly by their insulators, in case of +%% a timeout or if a parent process dies. +%% +%% The insulator is the group leader for the child process, and gets all +%% of its standard I/O. The output is buffered and associated with the +%% currently active test or group, and is sent along with the 'end' +%% progress message when the test or group has finished. + +insulator_wait(Child, Parent, Buf, St) -> + receive + {child, Child, Id, {'begin', Type, Data}} -> + message_super(Id, {progress, 'begin', {Type, Data}}, St), + insulator_wait(Child, Parent, [[] | Buf], St); + {child, Child, Id, {'end', Status, Time}} -> + Data = [{time, Time}, {output, buffer_to_binary(hd(Buf))}], + message_super(Id, {progress, 'end', {Status, Data}}, St), + insulator_wait(Child, Parent, tl(Buf), St); + {child, Child, Id, {skipped, Reason}} -> + %% this happens when a subgroup fails to enter the context + message_super(Id, {cancel, {abort, Reason}}, St), + insulator_wait(Child, Parent, Buf, St); + {child, Child, Id, {abort, Cause}} -> + %% this happens when the child code threw an internal + %% eunit_abort; the child process has already exited + exit_messages(Id, {abort, Cause}, St), + %% no need to wait for the {'EXIT',Child,_} message + terminate_insulator(St); + {io_request, Child, ReplyAs, Req} -> + %% we only collect output from the child process itself, not + %% from secondary processes, otherwise we get race problems; + %% however, each test runs its personal group leader that + %% funnels all output - see the run_test() function + Buf1 = io_request(Child, ReplyAs, Req, hd(Buf)), + insulator_wait(Child, Parent, [Buf1 | tl(Buf)], St); + {io_request, From, ReplyAs, Req} when is_pid(From) -> + %% (this shouldn't happen anymore, but we keep it safe) + %% just ensure the sender gets a reply; ignore the data + io_request(From, ReplyAs, Req, []), + insulator_wait(Child, Parent, Buf, St); + {timeout, Child, Id} -> + exit_messages(Id, timeout, St), + kill_task(Child, St); + {'EXIT', Child, normal} -> + terminate_insulator(St); + {'EXIT', Child, Reason} -> + exit_messages(St#procstate.id, {exit, Reason}, St), + terminate_insulator(St); + {'EXIT', Parent, _} -> + %% make sure child processes are cleaned up recursively + kill_task(Child, St) + end. + +kill_task(Child, St) -> + exit(Child, kill), + terminate_insulator(St). + +buffer_to_binary([B]) when is_binary(B) -> B; % avoid unnecessary copying +buffer_to_binary(Buf) -> list_to_binary(lists:reverse(Buf)). + +%% Unlinking before exit avoids polluting the parent process with exit +%% signals from the insulator. The child process is already dead here. + +terminate_insulator(St) -> + %% messaging/unlinking is ok even if the parent is already dead + Parent = St#procstate.parent, + Parent ! {done, St#procstate.ref, self()}, + unlink(Parent), + exit(normal). + +%% send cancel messages for the Id of the "causing" item, and also for +%% the Id of the insulator itself, if they are different +exit_messages(Id, Cause, St) -> + %% the message for the most specific Id is always sent first + message_super(Id, {cancel, Cause}, St), + case St#procstate.id of + Id -> ok; + Id1 -> message_super(Id1, {cancel, {blame, Id}}, St) + end. + +%% Child processes send all messages via the insulator to ensure proper +%% sequencing with timeouts and exit signals. + +message_insulator(Data, St) -> + St#procstate.insulator ! {child, self(), St#procstate.id, Data}. + +%% Timeout handling + +set_timeout(Time, St) -> + erlang:send_after(Time, St#procstate.insulator, + {timeout, self(), St#procstate.id}). + +clear_timeout(Ref) -> + erlang:cancel_timer(Ref). + +with_timeout(undefined, Default, F, St) -> + with_timeout(Default, F, St); +with_timeout(Time, _Default, F, St) -> + with_timeout(Time, F, St). + +with_timeout(infinity, F, _St) -> + %% don't start timers unnecessarily + {T0, _} = statistics(wall_clock), + Value = F(), + {T1, _} = statistics(wall_clock), + {Value, T1 - T0}; +with_timeout(Time, F, St) when is_integer(Time), Time > 16#FFFFffff -> + with_timeout(16#FFFFffff, F, St); +with_timeout(Time, F, St) when is_integer(Time), Time < 0 -> + with_timeout(0, F, St); +with_timeout(Time, F, St) when is_integer(Time) -> + Ref = set_timeout(Time, St), + {T0, _} = statistics(wall_clock), + try F() of + Value -> + %% we could also read the timer, but this is simpler + {T1, _} = statistics(wall_clock), + {Value, T1 - T0} + after + clear_timeout(Ref) + end. + +%% The normal behaviour of a child process is not to trap exit +%% signals. The testing framework is not dependent on this, however, so +%% the test code is allowed to enable signal trapping as it pleases. +%% Note that I/O is redirected to the insulator process. + +%% @spec (() -> term(), #procstate{}) -> ok + +child_process(Fun, St) -> + group_leader(St#procstate.insulator, self()), + try Fun() of + _ -> ok + catch + %% the only "normal" way for a child process to bail out (e.g, + %% when not being able to parse the test descriptor) is to throw + %% an {eunit_abort, Reason} exception; any other exception will + %% be reported as an unexpected termination of the test + {eunit_abort, Cause} -> + message_insulator({abort, Cause}, St), + exit(aborted) + end. + +-ifdef(TEST). +child_test_() -> + [{"test processes do not trap exit signals", + ?_assertMatch(false, process_flag(trap_exit, false))}]. +-endif. + +%% @throws abortException() +%% @type abortException() = {eunit_abort, Cause::term()} + +abort_task(Cause) -> + throw({eunit_abort, Cause}). + +%% Typically, the process that executes this code is not trapping +%% signals, but it might be - it is outside of our control, since test +%% code can enable or disable trapping at will. That we cannot rely on +%% process links here, is why the insulator process of a task must be +%% guaranteed to always send a reply before it terminates. +%% +%% The unique reference guarantees that we don't extract any message +%% from the mailbox unless it belongs to the test framework (and not to +%% the running tests) - it is not possible to use selective receive to +%% match only messages that are tagged with some pid out of a +%% dynamically varying set of pids. When the wait-loop terminates, no +%% such message should remain in the mailbox. + +wait_for_task(Pid, St) -> + wait_for_tasks(sets:from_list([Pid]), St). + +wait_for_tasks(PidSet, St) -> + case sets:size(PidSet) of + 0 -> + ok; + _ -> + %% (note that when we receive this message for some task, we + %% are guaranteed that the insulator process of the task has + %% already informed the supervisor about any anomalies) + Reference = St#procstate.ref, + receive + {done, Reference, Pid} -> + %% (if Pid is not in the set, del_element has no + %% effect, so this is always safe) + Rest = sets:del_element(Pid, PidSet), + wait_for_tasks(Rest, St) + end + end. + +%% --------------------------------------------------------------------- +%% Separate testing process + +%% TODO: Ability to stop after N failures. +%% TODO: Flow control, starting new job as soon as slot is available + +tests(T, St) -> + I = eunit_data:iter_init(T, St#procstate.id), + case St#procstate.order of + inorder -> tests_inorder(I, St); + inparallel -> tests_inparallel(I, 0, St); + {inparallel, N} when is_integer(N), N >= 0 -> + tests_inparallel(I, N, St) + end. + +set_id(I, St) -> + St#procstate{id = eunit_data:iter_id(I)}. + +tests_inorder(I, St) -> + tests_inorder(I, 0, St). + +tests_inorder(I, N, St) -> + case get_next_item(I) of + {T, I1} -> + handle_item(T, set_id(I1, St)), + tests_inorder(I1, N+1, St); + none -> + N % the return status of a group is the subtest count + end. + +tests_inparallel(I, K0, St) -> + tests_inparallel(I, 0, St, K0, K0, sets:new()). + +tests_inparallel(I, N, St, K, K0, Children) when K =< 0, K0 > 0 -> + wait_for_tasks(Children, St), + tests_inparallel(I, N, St, K0, K0, sets:new()); +tests_inparallel(I, N, St, K, K0, Children) -> + case get_next_item(I) of + {T, I1} -> + Child = spawn_item(T, set_id(I1, St)), + tests_inparallel(I1, N+1, St, K - 1, K0, + sets:add_element(Child, Children)); + none -> + wait_for_tasks(Children, St), + N % the return status of a group is the subtest count + end. + +%% this starts a new separate task for an inparallel-item (which might +%% be a group and in that case might cause yet another spawn in the +%% handle_group() function, but it might also be just a single test) +spawn_item(T, St0) -> + Fun = fun (St) -> + fun () -> handle_item(T, St) end + end, + %% inparallel-items are always spawned locally + start_task(local, Fun, St0). + +get_next_item(I) -> + try eunit_data:iter_next(I) + catch + Term -> abort_task(Term) + end. + +handle_item(T, St) -> + case T of + #test{} -> handle_test(T, St); + #group{} -> handle_group(T, St) + end. + +handle_test(T, St) -> + Data = [{desc, T#test.desc}, {source, T#test.location}, + {line, T#test.line}], + message_insulator({'begin', test, Data}, St), + + %% each test case runs under a fresh group leader process + G0 = group_leader(), + Runner = self(), + G1 = new_group_leader(Runner), + group_leader(G1, self()), + + %% run the actual test, handling timeouts and getting the total run + %% time of the test code (and nothing else) + {Status, Time} = with_timeout(T#test.timeout, ?DEFAULT_TEST_TIMEOUT, + fun () -> run_test(T) end, St), + + %% restore group leader, get the collected output, and re-emit it so + %% that it all seems to come from this process, and always comes + %% before the 'end' message for this test + group_leader(G0, self()), + Output = group_leader_sync(G1), + io:put_chars(Output), + + message_insulator({'end', Status, Time}, St), + ok. + +%% @spec (#test{}) -> ok | {error, eunit_lib:exception()} +%% | {skipped, eunit_test:wrapperError()} + +run_test(#test{f = F}) -> + try eunit_test:run_testfun(F) of + {ok, _Value} -> + %% just discard the return value + ok; + {error, Exception} -> + {error, Exception} + catch + throw:WrapperError -> {skipped, WrapperError} + end. + +set_group_order(#group{order = undefined}, St) -> + St; +set_group_order(#group{order = Order}, St) -> + St#procstate{order = Order}. + +handle_group(T, St0) -> + St = set_group_order(T, St0), + case T#group.spawn of + undefined -> + run_group(T, St); + Type -> + Child = spawn_group(Type, T, St), + wait_for_task(Child, St) + end. + +spawn_group(Type, T, St0) -> + Fun = fun (St) -> + fun () -> run_group(T, St) end + end, + start_task(Type, Fun, St0). + +run_group(T, St) -> + %% note that the setup/cleanup is outside the group timeout; if the + %% setup fails, we do not start any timers + Timeout = T#group.timeout, + Data = [{desc, T#group.desc}, {spawn, T#group.spawn}, + {order, T#group.order}], + message_insulator({'begin', group, Data}, St), + F = fun (G) -> enter_group(G, Timeout, St) end, + try with_context(T, F) of + {Status, Time} -> + message_insulator({'end', Status, Time}, St) + catch + %% a throw here can come from eunit_data:enter_context/4 or from + %% get_next_item/1; for context errors, report group as aborted, + %% but continue processing tests + {context_error, Why, Trace} -> + message_insulator({skipped, {Why, Trace}}, St) + end, + ok. + +enter_group(T, Timeout, St) -> + with_timeout(Timeout, ?DEFAULT_GROUP_TIMEOUT, + fun () -> tests(T, St) end, St). + +with_context(#group{context = undefined, tests = T}, F) -> + F(T); +with_context(#group{context = #context{} = C, tests = I}, F) -> + eunit_data:enter_context(C, I, F). + +%% Group leader process for test cases - collects I/O output requests. + +new_group_leader(Runner) -> + %% We must use spawn/3 here (with explicit module and function + %% name), because the 'current function' status of the group leader + %% is used by the UNDER_EUNIT macro (in eunit.hrl). If we spawn + %% using a fun, the current function will be 'erlang:apply/2' during + %% early process startup, which will fool the macro. + spawn_link(?MODULE, group_leader_process, [Runner]). + +group_leader_process(Runner) -> + group_leader_loop(Runner, infinity, []). + +group_leader_loop(Runner, Wait, Buf) -> + receive + {io_request, From, ReplyAs, Req} -> + P = process_flag(priority, normal), + %% run this part under normal priority always + Buf1 = io_request(From, ReplyAs, Req, Buf), + process_flag(priority, P), + group_leader_loop(Runner, Wait, Buf1); + stop -> + %% quitting time: make a minimal pause, go low on priority, + %% set receive-timeout to zero and schedule out again + receive after 2 -> ok end, + process_flag(priority, low), + group_leader_loop(Runner, 0, Buf); + _ -> + %% discard any other messages + group_leader_loop(Runner, Wait, Buf) + after Wait -> + %% no more messages and nothing to wait for; we ought to + %% have collected all immediately pending output now + process_flag(priority, normal), + Runner ! {self(), buffer_to_binary(Buf)} + end. + +group_leader_sync(G) -> + G ! stop, + receive + {G, Buf} -> Buf + end. + +%% Implementation of buffering I/O for group leader processes. (Note that +%% each batch of characters is just pushed on the buffer, so it needs to +%% be reversed when it is flushed.) + +io_request(From, ReplyAs, Req, Buf) -> + {Reply, Buf1} = io_request(Req, Buf), + io_reply(From, ReplyAs, Reply), + Buf1. + +io_reply(From, ReplyAs, Reply) -> + From ! {io_reply, ReplyAs, Reply}. + +io_request({put_chars, Chars}, Buf) -> + {ok, [Chars | Buf]}; +io_request({put_chars, M, F, As}, Buf) -> + try apply(M, F, As) of + Chars -> {ok, [Chars | Buf]} + catch + C:T -> {{error, {C,T,erlang:get_stacktrace()}}, Buf} + end; +io_request({put_chars, _Enc, Chars}, Buf) -> + io_request({put_chars, Chars}, Buf); +io_request({put_chars, _Enc, Mod, Func, Args}, Buf) -> + io_request({put_chars, Mod, Func, Args}, Buf); +io_request({get_chars, _Enc, _Prompt, _N}, Buf) -> + {eof, Buf}; +io_request({get_chars, _Prompt, _N}, Buf) -> + {eof, Buf}; +io_request({get_line, _Prompt}, Buf) -> + {eof, Buf}; +io_request({get_line, _Enc, _Prompt}, Buf) -> + {eof, Buf}; +io_request({get_until, _Prompt, _M, _F, _As}, Buf) -> + {eof, Buf}; +io_request({setopts, _Opts}, Buf) -> + {ok, Buf}; +io_request(getopts, Buf) -> + {error, {error, enotsup}, Buf}; +io_request({get_geometry,columns}, Buf) -> + {error, {error, enotsup}, Buf}; +io_request({get_geometry,rows}, Buf) -> + {error, {error, enotsup}, Buf}; +io_request({requests, Reqs}, Buf) -> + io_requests(Reqs, {ok, Buf}); +io_request(_, Buf) -> + {{error, request}, Buf}. + +io_requests([R | Rs], {ok, Buf}) -> + io_requests(Rs, io_request(R, Buf)); +io_requests(_, Result) -> + Result. diff --git a/lib/eunit/src/eunit_serial.erl b/lib/eunit/src/eunit_serial.erl new file mode 100644 index 0000000000..d9ccae86f9 --- /dev/null +++ b/lib/eunit/src/eunit_serial.erl @@ -0,0 +1,186 @@ +%% This library is free software; you can redistribute it and/or modify +%% it under the terms of the GNU Lesser General Public License as +%% published by the Free Software Foundation; either version 2 of the +%% License, or (at your option) any later version. +%% +%% This library is distributed in the hope that it will be useful, but +%% WITHOUT ANY WARRANTY; without even the implied warranty of +%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%% Lesser General Public License for more details. +%% +%% You should have received a copy of the GNU Lesser General Public +%% License along with this library; if not, write to the Free Software +%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +%% USA +%% +%% $Id$ +%% +%% @author Richard Carlsson +%% @copyright 2006 Richard Carlsson +%% @private +%% @see eunit +%% @doc Event serializing and multiplexing process, to be used as the +%% main "supervisor" process for en EUnit test runner. See eunit_proc +%% for details about the events that will be sent to the listeners +%% (provided to this process at startup). This process guarantees that +%% listeners will receive events in order, even if tests execute in +%% parallel. For every received 'begin' event, there will be exactly one +%% 'end' or 'cancel' event. For a cancelling event with identifier Id, +%% no further events will arrive whose identifiers have Id as prefix. + +-module(eunit_serial). + +-include("eunit.hrl"). +-include("eunit_internal.hrl"). + +-export([start/1]). + +%% Notes: +%% * Due to concurrency, there are no guarantees that we will receive +%% all status messages for the items within a group before we receive +%% the 'end' message of the group itself. +%% +%% * A cancelling event may arrive at any time, and may concern items we +%% are not yet expecting (if tests are executed in parallel), or may +%% concern not only the current item but possibly a group ancestor of +%% the current item (as in the case of a group timeout). +%% +%% * It is not possible to use selective receive to extract only those +%% cancelling messages that affect the current item and its parents; +%% basically, because we cannot have a dynamically computed prefix as a +%% pattern in a receive. Hence, we must extract each cancelling event as +%% it arrives and keep track of them separately. +%% +%% * Before we wait for a new item, we must check whether it (and thus +%% also all its subitems, if any) is already cancelled. +%% +%% * When a new cancelling event arrives, we must either store it for +%% future use, and/or cancel the current item and possibly one or more +%% of its parent groups. + +-record(state, {listeners :: set(), + cancelled = eunit_lib:trie_new() :: gb_tree(), + messages = dict:new() :: dict()}). + +start(Pids) -> + spawn(fun () -> serializer(Pids) end). + +serializer(Pids) -> + St = #state{listeners = sets:from_list(Pids), + cancelled = eunit_lib:trie_new(), + messages = dict:new()}, + expect([], undefined, 0, St), + exit(normal). + +%% collect beginning and end of an expected item; return {Done, NewSt} +%% where Done is true if there are no more items of this group +expect(Id, ParentId, GroupMinSize, St0) -> + case wait(Id, 'begin', ParentId, GroupMinSize, St0) of + {done, St1} -> + {true, St1}; + {cancel, prefix, _Msg, St1} -> + %% if a parent caused the cancel, signal done with group and + %% cast no cancel event (since the item might not exist) + {true, St1}; + {cancel, exact, Msg, St1} -> + cast_cancel(Id, Msg, St1), + {false, St1}; + {ok, Msg, St1} -> + %%?debugVal({got_begin, Id, Msg}), + cast(Msg, St1), + St2 = case Msg of + {status, _, {progress, 'begin', {group, _Info}}} -> + group(Id, 0, St1); + _ -> + St1 + end, + case wait(Id, 'end', ParentId, GroupMinSize, St2) of + {cancel, Why, Msg1, St3} -> + %% we know the item exists, so always cast a cancel + %% event, and signal done with the group if a parent + %% caused the cancel + cast_cancel(Id, Msg1, St3), + {(Why =:= prefix), St3}; + {ok, Msg1, St3} -> + %%?debugVal({got_end, Id, Msg1}), + cast(Msg1, St3), + {false, St3} + end + end. + +%% collect group items in order until group is done +group(ParentId, GroupMinSize, St) -> + N = GroupMinSize + 1, + case expect(ParentId ++ [N], ParentId, GroupMinSize, St) of + {false, St1} -> + group(ParentId, N, St1); + {true, St1} -> + St1 + end. + +cast_cancel(Id, undefined, St) -> + %% reasonable message for implicitly cancelled events + cast({status, Id, {cancel, undefined}}, St); +cast_cancel(_Id, Msg, St) -> + cast(Msg, St). + +cast(Msg, St) -> + sets:fold(fun (L, M) -> L ! M end, Msg, St#state.listeners), + ok. + +%% wait for a particular begin or end event, that might have arrived or +%% been cancelled already, or might become cancelled later, or might not +%% even exist (for the last+1 element of a group) +wait(Id, Type, ParentId, GroupMinSize, St) -> + %%?debugVal({wait, Id, Type}), + case check_cancelled(Id, St) of + no -> + case recall(Id, St) of + undefined -> + wait_1(Id, Type, ParentId, GroupMinSize, St); + Msg -> + {ok, Msg, forget(Id, St)} + end; + Why -> + %%?debugVal({cancelled, Why, Id, ParentId}), + {cancel, Why, recall(Id, St), forget(Id, St)} + end. + +%% the event has not yet arrived or been cancelled - wait for more info +wait_1(Id, Type, ParentId, GroupMinSize, St) -> + receive + {status, Id, {progress, Type, _}}=Msg -> + %%?debugVal({Type, ParentId, Id}), + {ok, Msg, St}; + {status, ParentId, {progress, 'end', {GroupMinSize, _}}}=Msg -> + %% the parent group ended (the final status of a group is + %% the count of its subitems), and we have seen all of its + %% subtests, so the currently expected event does not exist + %%?debugVal({end_group, ParentId, Id, GroupMinSize}), + {done, remember(ParentId, Msg, St)}; + {status, SomeId, {cancel, _Cause}}=Msg -> + %%?debugVal({got_cancel, SomeId, _Cause}), + St1 = set_cancelled(SomeId, Msg, St), + wait(Id, Type, ParentId, GroupMinSize, St1) + end. + +set_cancelled(Id, Msg, St0) -> + St = remember(Id, Msg, St0), + St#state{cancelled = eunit_lib:trie_store(Id, St0#state.cancelled)}. + +check_cancelled(Id, St) -> + %% returns 'no', 'exact', or 'prefix' + eunit_lib:trie_match(Id, St#state.cancelled). + +remember(Id, Msg, St) -> + St#state{messages = dict:store(Id, Msg, St#state.messages)}. + +forget(Id, St) -> + %% this is just to enable garbage collection of old messages + St#state{messages = dict:store(Id, undefined, St#state.messages)}. + +recall(Id, St) -> + case dict:find(Id, St#state.messages) of + {ok, Msg} -> Msg; + error -> undefined + end. diff --git a/lib/eunit/src/eunit_server.erl b/lib/eunit/src/eunit_server.erl new file mode 100644 index 0000000000..bf1bb9bcef --- /dev/null +++ b/lib/eunit/src/eunit_server.erl @@ -0,0 +1,341 @@ +%% This library is free software; you can redistribute it and/or modify +%% it under the terms of the GNU Lesser General Public License as +%% published by the Free Software Foundation; either version 2 of the +%% License, or (at your option) any later version. +%% +%% This library is distributed in the hope that it will be useful, but +%% WITHOUT ANY WARRANTY; without even the implied warranty of +%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%% Lesser General Public License for more details. +%% +%% You should have received a copy of the GNU Lesser General Public +%% License along with this library; if not, write to the Free Software +%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +%% USA +%% +%% $Id: eunit_server.erl 267 2008-10-19 18:48:03Z rcarlsson $ +%% +%% @author Richard Carlsson +%% @copyright 2006 Richard Carlsson +%% @private +%% @see eunit +%% @doc EUnit server process + +-module(eunit_server). + +-export([start/1, stop/1, start_test/4, watch/3, watch_path/3, + watch_regexp/3]). + +-export([main/1]). % private + +-include("eunit.hrl"). +-include("eunit_internal.hrl"). + + +-define(AUTO_TIMEOUT, 60000). %% auto test time limit + +%% TODO: pass options to server, such as default timeout? + +start(Server) when is_atom(Server) -> + ensure_started(Server). + +stop(Server) -> + command(Server, stop). + + +-record(job, {super, test, options}). + +%% The `Super' process will receive a stream of status messages; see +%% eunit_proc:status_message/3 for details. + +start_test(Server, Super, T, Options) -> + command(Server, {start, #job{super = Super, + test = T, + options = Options}}). + +watch(Server, Module, Opts) when is_atom(Module) -> + command(Server, {watch, {module, Module}, Opts}). + +watch_path(Server, Path, Opts) -> + command(Server, {watch, {path, filename:flatten(Path)}, Opts}). + +watch_regexp(Server, Regex, Opts) -> + case regexp:parse(Regex) of + {ok, R} -> + command(Server, {watch, {regexp, R}, Opts}); + {error, _}=Error -> + Error + end. + +%% This makes sure the server is started before sending the command, and +%% returns {ok, Result} if the server accepted the command or {error, +%% server_down} if the server process crashes. If the server does not +%% reply, this function will wait until the server is killed. + +command(Server, Cmd) -> + if is_atom(Server), Cmd /= stop -> ensure_started(Server); + true -> ok + end, + if is_pid(Server) -> command_1(Server, Cmd); + true -> + case whereis(Server) of + undefined -> {error, server_down}; + Pid -> command_1(Pid, Cmd) + end + end. + +command_1(Pid, Cmd) when is_pid(Pid) -> + Pid ! {command, self(), Cmd}, + command_wait(Pid, 1000, undefined). + +command_wait(Pid, Timeout, Monitor) -> + receive + {Pid, Result} -> Result; + {'DOWN', Monitor, process, Pid, _R} -> {error, server_down} + after Timeout -> + %% avoid creating a monitor unless some time has passed + command_wait(Pid, infinity, erlang:monitor(process, Pid)) + end. + +%% Starting the server + +ensure_started(Name) -> + ensure_started(Name, 5). + +ensure_started(Name, N) when N > 0 -> + case whereis(Name) of + undefined -> + Parent = self(), + Pid = spawn(fun () -> server_start(Name, Parent) end), + receive + {Pid, ok} -> + Pid; + {Pid, error} -> + receive after 200 -> ensure_started(Name, N - 1) end + end; + Pid -> + Pid + end; +ensure_started(_, _) -> + throw(no_server). + +server_start(undefined = Name, Parent) -> + %% anonymous server + server_start_1(Name, Parent); +server_start(Name, Parent) -> + try register(Name, self()) of + true -> server_start_1(Name, Parent) + catch + _:_ -> + Parent ! {self(), error}, + exit(error) + end. + +server_start_1(Name, Parent) -> + Parent ! {self(), ok}, + server_init(Name). + +-record(state, {name, + stopped, + jobs, + queue, + auto_test, + modules, + paths, + regexps}). + +server_init(Name) -> + server(#state{name = Name, + stopped = false, + jobs = dict:new(), + queue = queue:new(), + auto_test = queue:new(), + modules = sets:new(), + paths = sets:new(), + regexps = sets:new()}). + +server(St) -> + server_check_exit(St), + ?MODULE:main(St). + +%% @private +main(St) -> + receive + {done, auto_test, _Pid} -> + server(auto_test_done(St)); + {done, Reference, _Pid} -> + server(handle_done(Reference, St)); + {command, From, _Cmd} when St#state.stopped -> + From ! {self(), stopped}; + {command, From, Cmd} -> + server_command(From, Cmd, St); + {code_monitor, {loaded, M, _Time}} -> + case is_watched(M, St) of + true -> + server(new_auto_test(self(), M, St)); + false -> + server(St) + end + end. + +server_check_exit(St) -> + case dict:size(St#state.jobs) of + 0 when St#state.stopped -> exit(normal); + _ -> ok + end. + +server_command(From, {start, Job}, St) -> + Reference = make_ref(), + St1 = case proplists:get_bool(enqueue, Job#job.options) of + true -> + enqueue(Job, From, Reference, St); + false -> + start_job(Job, From, Reference, St) + end, + server_command_reply(From, {ok, Reference}), + server(St1); +server_command(From, stop, St) -> + %% unregister the server name and let remaining jobs finish + server_command_reply(From, {error, stopped}), + catch unregister(St#state.name), + server(St#state{stopped = true}); +server_command(From, {watch, Target, _Opts}, St) -> + %% the code watcher is only started on demand + %% FIXME: this is disabled for now in the OTP distribution + %%code_monitor:monitor(self()), + %% TODO: propagate options to testing stage + St1 = add_watch(Target, St), + server_command_reply(From, ok), + server(St1); +server_command(From, {forget, Target}, St) -> + St1 = delete_watch(Target, St), + server_command_reply(From, ok), + server(St1); +server_command(From, Cmd, St) -> + server_command_reply(From, {error, {unknown_command, Cmd}}), + server(St). + +server_command_reply(From, Result) -> + From ! {self(), Result}. + +enqueue(Job, From, Reference, St) -> + case dict:size(St#state.jobs) of + 0 -> + start_job(Job, From, Reference, St); + _ -> + St#state{queue = queue:in({Job, From, Reference}, + St#state.queue)} + end. + +dequeue(St) -> + case queue:out(St#state.queue) of + {empty, _} -> + St; + {{value, {Job, From, Reference}}, Queue} -> + start_job(Job, From, Reference, St#state{queue = Queue}) + end. + +start_job(Job, From, Reference, St) -> + From ! {start, Reference}, + %% The default is to run tests in order unless otherwise specified + Order = proplists:get_value(order, Job#job.options, inorder), + eunit_proc:start(Job#job.test, Order, Job#job.super, Reference), + St#state{jobs = dict:store(Reference, From, St#state.jobs)}. + +handle_done(Reference, St) -> + case dict:find(Reference, St#state.jobs) of + {ok, From} -> + From ! {done, Reference}, + dequeue(St#state{jobs = dict:erase(Reference, + St#state.jobs)}); + error -> + St + end. + +%% Adding and removing watched modules or paths + +add_watch({module, M}, St) -> + St#state{modules = sets:add_element(M, St#state.modules)}; +add_watch({path, P}, St) -> + St#state{paths = sets:add_element(P, St#state.paths)}; +add_watch({regexp, R}, St) -> + St#state{regexps = sets:add_element(R, St#state.regexps)}. + +delete_watch({module, M}, St) -> + St#state{modules = sets:del_element(M, St#state.modules)}; +delete_watch({path, P}, St) -> + St#state{paths = sets:del_element(P, St#state.paths)}; +delete_watch({regexp, R}, St) -> + St#state{regexps = sets:del_element(R, St#state.regexps)}. + +%% Checking if a module is being watched + +is_watched(M, St) when is_atom(M) -> + sets:is_element(M, St#state.modules) orelse + is_watched(code:which(M), St); +is_watched(Path, St) -> + sets:is_element(filename:dirname(Path), St#state.paths) orelse + match_any(sets:to_list(St#state.regexps), Path). + +match_any([R | Rs], Str) -> + case regexp:first_match(Str, R) of + {match, _, _} -> true; + _ -> match_any(Rs, Str) + end; +match_any([], _Str) -> false. + +%% Running automatic tests when a watched module is loaded. +%% Uses a queue in order to avoid overlapping output when several +%% watched modules are loaded simultaneously. (The currently running +%% automatic test is kept in the queue until it is done. An empty queue +%% means that no automatic test is running.) + +new_auto_test(Server, M, St) -> + case queue:is_empty(St#state.auto_test) of + true -> + start_auto_test(Server, M); + false -> + ok + end, + St#state{auto_test = queue:in({Server, M}, St#state.auto_test)}. + +auto_test_done(St) -> + %% remove finished test from queue before checking for more + {_, Queue} = queue:out(St#state.auto_test), + case queue:out(Queue) of + {{value, {Server, M}}, _} -> + %% this is just lookahead - the item is not removed + start_auto_test(Server, M); + {empty, _} -> + ok + end, + St#state{auto_test = Queue}. + +start_auto_test(Server, M) -> + spawn(fun () -> auto_super(Server, M) end). + +auto_super(Server, M) -> + process_flag(trap_exit, true), + %% Give the user a short delay before any output is produced + receive after 333 -> ok end, + %% Make sure output is sent to console on server node + group_leader(whereis(user), self()), + Pid = spawn_link(fun () -> auto_proc(Server, M) end), + receive + {'EXIT', Pid, _} -> + ok + after ?AUTO_TIMEOUT -> + exit(Pid, kill), + io:put_chars("\n== EUnit: automatic test was aborted ==\n"), + io:put_chars("\n> ") + end, + Server ! {done, auto_test, self()}. + +auto_proc(Server, M) -> + %% Make the output start on a new line instead of on the same line + %% as the current shell prompt. + io:fwrite("\n== EUnit: testing module ~w ==\n", [M]), + eunit:test(Server, M, [enqueue]), + %% Make sure to print a dummy prompt at the end of the output, most + %% of all so that the Emacs mode realizes that input is active. + io:put_chars("\n-> "). diff --git a/lib/eunit/src/eunit_striptests.erl b/lib/eunit/src/eunit_striptests.erl new file mode 100644 index 0000000000..606e44b286 --- /dev/null +++ b/lib/eunit/src/eunit_striptests.erl @@ -0,0 +1,67 @@ +%% This library is free software; you can redistribute it and/or modify +%% it under the terms of the GNU Lesser General Public License as +%% published by the Free Software Foundation; either version 2 of the +%% License, or (at your option) any later version. +%% +%% This library is distributed in the hope that it will be useful, but +%% WITHOUT ANY WARRANTY; without even the implied warranty of +%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%% Lesser General Public License for more details. +%% +%% You should have received a copy of the GNU Lesser General Public +%% License along with this library; if not, write to the Free Software +%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +%% USA +%% +%% $Id: eunit_striptests.erl 329 2009-03-01 11:23:32Z rcarlsson $ +%% +%% @author Richard Carlsson +%% @author Eric Merritt +%% @copyright 2006 Richard Carlsson, Eric Merritt +%% @private +%% @see eunit +%% @doc Parse transform for stripping EUnit test functions. + +-module(eunit_striptests). + +-include("eunit_internal.hrl"). + +-export([parse_transform/2]). + +parse_transform(Forms, Options) -> + TestSuffix = proplists:get_value(eunit_test_suffix, Options, + ?DEFAULT_TEST_SUFFIX), + GeneratorSuffix = proplists:get_value(eunit_generator_suffix, + Options, + ?DEFAULT_GENERATOR_SUFFIX), + ExportSuffix = proplists:get_value(eunit_export_suffix, Options, + ?DEFAULT_EXPORT_SUFFIX), + Exports = lists:foldl(fun ({attribute,_,export,Es}, S) -> + sets:union(sets:from_list(Es), S); + (_F, S) -> S + end, + sets:new(), Forms), + F = fun (Form, Acc) -> + form(Form, Acc, Exports, TestSuffix, GeneratorSuffix, + ExportSuffix) + end, + lists:reverse(lists:foldl(F, [], Forms)). + +form({function, _L, Name, 0, _Cs}=Form, Acc, Exports, TestSuffix, + GeneratorSuffix, ExportSuffix) -> + N = atom_to_list(Name), + case not sets:is_element({Name, 0}, Exports) + andalso (lists:suffix(TestSuffix, N) + orelse lists:suffix(GeneratorSuffix, N) + orelse lists:suffix(ExportSuffix, N)) + of + true -> + Acc; + false -> + [Form | Acc] + end; +form({function, _L, ?DEFAULT_MODULE_WRAPPER_NAME, 1, _Cs}, Acc, _, _, _, + _) -> + Acc; +form(Form, Acc, _, _, _, _) -> + [Form | Acc]. diff --git a/lib/eunit/src/eunit_surefire.erl b/lib/eunit/src/eunit_surefire.erl new file mode 100644 index 0000000000..aeda31d251 --- /dev/null +++ b/lib/eunit/src/eunit_surefire.erl @@ -0,0 +1,417 @@ +%% This library is free software; you can redistribute it and/or modify +%% it under the terms of the GNU Lesser General Public License as +%% published by the Free Software Foundation; either version 2 of the +%% License, or (at your option) any later version. +%% +%% This library is distributed in the hope that it will be useful, but +%% WITHOUT ANY WARRANTY; without even the implied warranty of +%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%% Lesser General Public License for more details. +%% +%% You should have received a copy of the GNU Lesser General Public +%% License along with this library; if not, write to the Free Software +%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +%% USA +%% +%% $Id: $ +%% +%% @author Mickaël Rémond +%% @copyright 2009 Mickaël Rémond, Paul Guyot +%% @see eunit +%% @doc Surefire reports for EUnit (Format used by Maven and Atlassian +%% Bamboo for example to integrate test results). Based on initial code +%% from Paul Guyot. +%% +%% Example: Generate XML result file in the current directory: +%% ```eunit:test([fib, eunit_examples], +%% [{report,{eunit_surefire,[{dir,"."}]}}]).''' + +-module(eunit_surefire). + +-behaviour(eunit_listener). + +-define(NODEBUG, true). +-include("eunit.hrl"). +-include("eunit_internal.hrl"). + +-export([start/0, start/1]). + +-export([init/1, handle_begin/3, handle_end/3, handle_cancel/3, + terminate/2]). + +%% ============================================================================ +%% MACROS +%% ============================================================================ +-define(XMLDIR, "."). +-define(INDENT, <<" ">>). +-define(NEWLINE, <<"\n">>). + +%% ============================================================================ +%% TYPES +%% ============================================================================ +-type(chars() :: [char() | any()]). % chars() + +%% ============================================================================ +%% RECORDS +%% ============================================================================ +-record(testcase, + { + name :: chars(), + description :: chars(), + result :: ok | {failed, tuple()} | {aborted, tuple()} | {skipped, tuple()}, + time :: integer(), + output :: binary() + }). +-record(testsuite, + { + name = <<>> :: binary(), + time = 0 :: integer(), + output = <<>> :: binary(), + succeeded = 0 :: integer(), + failed = 0 :: integer(), + aborted = 0 :: integer(), + skipped = 0 :: integer(), + testcases = [] :: [#testcase{}] + }). +-record(state, {verbose = false, + indent = 0, + xmldir = ".", + testsuite = #testsuite{} + }). + +start() -> + start([]). + +start(Options) -> + eunit_listener:start(?MODULE, Options). + +init(Options) -> + XMLDir = proplists:get_value(dir, Options, ?XMLDIR), + St = #state{verbose = proplists:get_bool(verbose, Options), + xmldir = XMLDir, + testsuite = #testsuite{}}, + receive + {start, _Reference} -> + St + end. + +terminate({ok, _Data}, St) -> + TestSuite = St#state.testsuite, + XmlDir = St#state.xmldir, + write_report(TestSuite, XmlDir), + ok; +terminate({error, Reason}, _St) -> + io:fwrite("Internal error: ~P.\n", [Reason, 25]), + sync_end(error). + +sync_end(Result) -> + receive + {stop, Reference, ReplyTo} -> + ReplyTo ! {result, Reference, Result}, + ok + end. + +handle_begin(group, Data, St) -> + NewId = proplists:get_value(id, Data), + case NewId of + [] -> + St; + [_GroupId] -> + Desc = proplists:get_value(desc, Data), + TestSuite = St#state.testsuite, + NewTestSuite = TestSuite#testsuite{name = Desc}, + St#state{testsuite=NewTestSuite}; + %% Surefire format is not hierarchic: Ignore subgroups: + _ -> + St + end; +handle_begin(test, _Data, St) -> + St. +handle_end(group, Data, St) -> + %% Retrieve existing test suite: + case proplists:get_value(id, Data) of + [] -> + St; + [_GroupId|_] -> + TestSuite = St#state.testsuite, + + %% Update TestSuite data: + Time = proplists:get_value(time, Data), + Output = proplists:get_value(output, Data), + NewTestSuite = TestSuite#testsuite{ time = Time, output = Output }, + St#state{testsuite=NewTestSuite} + end; +handle_end(test, Data, St) -> + %% Retrieve existing test suite: + TestSuite = St#state.testsuite, + + %% Create test case: + Name = format_name(proplists:get_value(source, Data), + proplists:get_value(line, Data)), + Desc = format_desc(proplists:get_value(desc, Data)), + Result = proplists:get_value(status, Data), + Time = proplists:get_value(time, Data), + Output = proplists:get_value(output, Data), + TestCase = #testcase{name = Name, description = Desc, + time = Time,output = Output}, + NewTestSuite = add_testcase_to_testsuite(Result, TestCase, TestSuite), + St#state{testsuite=NewTestSuite}. + +%% Cancel group does not give information on the individual cancelled test case +%% We ignore this event +handle_cancel(group, _Data, St) -> + St; +handle_cancel(test, Data, St) -> + %% Retrieve existing test suite: + TestSuite = St#state.testsuite, + + %% Create test case: + Name = format_name(proplists:get_value(source, Data), + proplists:get_value(line, Data)), + Desc = format_desc(proplists:get_value(desc, Data)), + Reason = proplists:get_value(reason, Data), + TestCase = #testcase{ + name = Name, description = Desc, + result = {skipped, Reason}, time = 0, + output = <<>>}, + NewTestSuite = TestSuite#testsuite{ + skipped = TestSuite#testsuite.skipped+1, + testcases=[TestCase|TestSuite#testsuite.testcases] }, + St#state{testsuite=NewTestSuite}. + +format_name({Module, Function, Arity}, Line) -> + lists:flatten([atom_to_list(Module), ":", atom_to_list(Function), "/", + integer_to_list(Arity), "_", integer_to_list(Line)]). +format_desc(undefined) -> + ""; +format_desc(Desc) when is_binary(Desc) -> + binary_to_list(Desc); +format_desc(Desc) when is_list(Desc) -> + Desc. + +%% Add testcase to testsuite depending on the result of the test. +add_testcase_to_testsuite(ok, TestCaseTmp, TestSuite) -> + TestCase = TestCaseTmp#testcase{ result = ok }, + TestSuite#testsuite{ + succeeded = TestSuite#testsuite.succeeded+1, + testcases=[TestCase|TestSuite#testsuite.testcases] }; +add_testcase_to_testsuite({error, Exception}, TestCaseTmp, TestSuite) -> + case Exception of + {error,{AssertionException,_},_} when + AssertionException == assertion_failed; + AssertionException == assertMatch_failed; + AssertionException == assertEqual_failed; + AssertionException == assertException_failed; + AssertionException == assertCmd_failed; + AssertionException == assertCmdOutput_failed + -> + TestCase = TestCaseTmp#testcase{ result = {failed, Exception} }, + TestSuite#testsuite{ + failed = TestSuite#testsuite.failed+1, + testcases = [TestCase|TestSuite#testsuite.testcases] }; + _ -> + TestCase = TestCaseTmp#testcase{ result = {aborted, Exception} }, + TestSuite#testsuite{ + aborted = TestSuite#testsuite.aborted+1, + testcases = [TestCase|TestSuite#testsuite.testcases] } + end. + +%% ---------------------------------------------------------------------------- +%% Write a report to the XML directory. +%% This function opens the report file, calls write_report_to/2 and closes the file. +%% ---------------------------------------------------------------------------- +write_report(#testsuite{name = Name} = TestSuite, XmlDir) -> + Filename = filename:join(XmlDir, lists:flatten(["TEST-", escape_suitename(Name)], ".xml")), + case file:open(Filename, [write, raw]) of + {ok, FileDescriptor} -> + try + write_report_to(TestSuite, FileDescriptor) + after + file:close(FileDescriptor) + end; + {error, _Reason} = Error -> throw(Error) + end. + +%% ---------------------------------------------------------------------------- +%% Actually write a report. +%% ---------------------------------------------------------------------------- +write_report_to(TestSuite, FileDescriptor) -> + write_header(FileDescriptor), + write_start_tag(TestSuite, FileDescriptor), + write_testcases(lists:reverse(TestSuite#testsuite.testcases), FileDescriptor), + write_end_tag(FileDescriptor). + +%% ---------------------------------------------------------------------------- +%% Write the XML header. +%% ---------------------------------------------------------------------------- +write_header(FileDescriptor) -> + file:write(FileDescriptor, [<<"">>, ?NEWLINE]). + +%% ---------------------------------------------------------------------------- +%% Write the testsuite start tag, with attributes describing the statistics +%% of the test suite. +%% ---------------------------------------------------------------------------- +write_start_tag( + #testsuite{ + name = Name, + time = Time, + succeeded = Succeeded, + failed = Failed, + skipped = Skipped, + aborted = Aborted}, + FileDescriptor) -> + Total = Succeeded + Failed + Skipped + Aborted, + StartTag = [ + <<">, integer_to_list(Total), + <<"\" failures=\"">>, integer_to_list(Failed), + <<"\" errors=\"">>, integer_to_list(Aborted), + <<"\" skipped=\"">>, integer_to_list(Skipped), + <<"\" time=\"">>, format_time(Time), + <<"\" name=\"">>, escape_attr(Name), + <<"\">">>, ?NEWLINE], + file:write(FileDescriptor, StartTag). + +%% ---------------------------------------------------------------------------- +%% Recursive function to write the test cases. +%% ---------------------------------------------------------------------------- +write_testcases([], _FileDescriptor) -> void; +write_testcases([TestCase| Tail], FileDescriptor) -> + write_testcase(TestCase, FileDescriptor), + write_testcases(Tail, FileDescriptor). + +%% ---------------------------------------------------------------------------- +%% Write the testsuite end tag. +%% ---------------------------------------------------------------------------- +write_end_tag(FileDescriptor) -> + file:write(FileDescriptor, [<<"">>, ?NEWLINE]). + +%% ---------------------------------------------------------------------------- +%% Write a test case, as a testcase tag. +%% If the test case was successful and if there was no output, we write an empty +%% tag. +%% ---------------------------------------------------------------------------- +write_testcase( + #testcase{ + name = Name, + description = Description, + result = Result, + time = Time, + output = Output}, + FileDescriptor) -> + DescriptionAttr = case Description of + <<>> -> []; + [] -> []; + _ -> [<<" description=\"">>, escape_attr(Description), <<"\"">>] + end, + StartTag = [ + ?INDENT, <<">, format_time(Time), + <<"\" name=\"">>, escape_attr(Name), <<"\"">>, + DescriptionAttr], + ContentAndEndTag = case {Result, Output} of + {ok, []} -> [<<"/>">>, ?NEWLINE]; + {ok, <<>>} -> [<<"/>">>, ?NEWLINE]; + _ -> [<<">">>, ?NEWLINE, format_testcase_result(Result), format_testcase_output(Output), ?INDENT, <<"">>, ?NEWLINE] + end, + file:write(FileDescriptor, [StartTag, ContentAndEndTag]). + +%% ---------------------------------------------------------------------------- +%% Format the result of the test. +%% Failed tests are represented with a failure tag. +%% Aborted tests are represented with an error tag. +%% Skipped tests are represented with a skipped tag. +%% ---------------------------------------------------------------------------- +format_testcase_result(ok) -> [<<>>]; +format_testcase_result({failed, {error, {Type, _}, _} = Exception}) when is_atom(Type) -> + [?INDENT, ?INDENT, <<">, escape_attr(atom_to_list(Type)), <<"\">">>, ?NEWLINE, + <<"::">>, escape_text(eunit_lib:format_exception(Exception)), + ?INDENT, ?INDENT, <<"">>, ?NEWLINE]; +format_testcase_result({failed, Term}) -> + [?INDENT, ?INDENT, <<"">>, ?NEWLINE, + escape_text(io_lib:write(Term)), + ?INDENT, ?INDENT, <<"">>, ?NEWLINE]; +format_testcase_result({aborted, {Class, _Term, _Trace} = Exception}) when is_atom(Class) -> + [?INDENT, ?INDENT, <<">, escape_attr(atom_to_list(Class)), <<"\">">>, ?NEWLINE, + <<"::">>, escape_text(eunit_lib:format_exception(Exception)), + ?INDENT, ?INDENT, <<"">>, ?NEWLINE]; +format_testcase_result({aborted, Term}) -> + [?INDENT, ?INDENT, <<"">>, ?NEWLINE, + escape_text(io_lib:write(Term)), + ?INDENT, ?INDENT, <<"">>, ?NEWLINE]; +format_testcase_result({skipped, {abort, Error}}) when is_tuple(Error) -> + [?INDENT, ?INDENT, <<">, escape_attr(atom_to_list(element(1, Error))), <<"\">">>, ?NEWLINE, + escape_text(eunit_lib:format_error(Error)), + ?INDENT, ?INDENT, <<"">>, ?NEWLINE]; +format_testcase_result({skipped, {Type, Term}}) when is_atom(Type) -> + [?INDENT, ?INDENT, <<">, escape_attr(atom_to_list(Type)), <<"\">">>, ?NEWLINE, + escape_text(io_lib:write(Term)), + ?INDENT, ?INDENT, <<"">>, ?NEWLINE]; +format_testcase_result({skipped, timeout}) -> + [?INDENT, ?INDENT, <<"">>, ?NEWLINE]; +format_testcase_result({skipped, Term}) -> + [?INDENT, ?INDENT, <<"">>, ?NEWLINE, + escape_text(io_lib:write(Term)), + ?INDENT, ?INDENT, <<"">>, ?NEWLINE]. + +%% ---------------------------------------------------------------------------- +%% Format the output of a test case in xml. +%% Empty output is simply the empty string. +%% Other output is inside a xml tag. +%% ---------------------------------------------------------------------------- +format_testcase_output([]) -> []; +format_testcase_output(Output) -> + [?INDENT, ?INDENT, <<"">>, escape_text(Output), ?NEWLINE, ?INDENT, ?INDENT, <<"">>, ?NEWLINE]. + +%% ---------------------------------------------------------------------------- +%% Return the time in the SECS.MILLISECS format. +%% ---------------------------------------------------------------------------- +format_time(Time) -> + format_time_s(lists:reverse(integer_to_list(Time))). +format_time_s([Digit]) -> ["0.00", Digit]; +format_time_s([Digit1, Digit2]) -> ["0.0", Digit2, Digit1]; +format_time_s([Digit1, Digit2, Digit3]) -> ["0.", Digit3, Digit2, Digit1]; +format_time_s([Digit1, Digit2, Digit3 | Tail]) -> [lists:reverse(Tail), $., Digit3, Digit2, Digit1]. + +%% ---------------------------------------------------------------------------- +%% Escape a suite's name to generate the filename. +%% Remark: we might overwrite another testsuite's file. +%% ---------------------------------------------------------------------------- +escape_suitename([Head | _T] = List) when is_list(Head) -> + escape_suitename(lists:flatten(List)); +escape_suitename(Binary) when is_binary(Binary) -> + escape_suitename(binary_to_list(Binary)); +escape_suitename("module '" ++ String) -> + escape_suitename(String); +escape_suitename(String) -> + escape_suitename(String, []). + +escape_suitename(Binary, Acc) when is_binary(Binary) -> escape_suitename(binary_to_list(Binary), Acc); +escape_suitename([], Acc) -> lists:reverse(Acc); +escape_suitename([$ | Tail], Acc) -> escape_suitename(Tail, [$_ | Acc]); +escape_suitename([$' | Tail], Acc) -> escape_suitename(Tail, Acc); +escape_suitename([$/ | Tail], Acc) -> escape_suitename(Tail, [$: | Acc]); +escape_suitename([$\\ | Tail], Acc) -> escape_suitename(Tail, [$: | Acc]); +escape_suitename([Char | Tail], Acc) when Char < $! -> escape_suitename(Tail, Acc); +escape_suitename([Char | Tail], Acc) when Char > $~ -> escape_suitename(Tail, Acc); +escape_suitename([Char | Tail], Acc) -> escape_suitename(Tail, [Char | Acc]). + +%% ---------------------------------------------------------------------------- +%% Escape text for XML text nodes. +%% Replace < with <, > with > and & with & +%% ---------------------------------------------------------------------------- +escape_text(Text) when is_binary(Text) -> escape_text(binary_to_list(Text)); +escape_text(Text) -> escape_xml(lists:flatten(Text), [], false). + + +%% ---------------------------------------------------------------------------- +%% Escape text for XML attribute nodes. +%% Replace < with <, > with > and & with & +%% ---------------------------------------------------------------------------- +escape_attr(Text) when is_binary(Text) -> escape_attr(binary_to_list(Text)); +escape_attr(Text) -> escape_xml(lists:flatten(Text), [], true). + +escape_xml([], Acc, _ForAttr) -> lists:reverse(Acc); +escape_xml([$< | Tail], Acc, ForAttr) -> escape_xml(Tail, [$;, $t, $l, $& | Acc], ForAttr); +escape_xml([$> | Tail], Acc, ForAttr) -> escape_xml(Tail, [$;, $t, $g, $& | Acc], ForAttr); +escape_xml([$& | Tail], Acc, ForAttr) -> escape_xml(Tail, [$;, $p, $m, $a, $& | Acc], ForAttr); +escape_xml([$" | Tail], Acc, true) -> escape_xml(Tail, [$;, $t, $o, $u, $q, $& | Acc], true); % " +escape_xml([Char | Tail], Acc, ForAttr) when is_integer(Char) -> escape_xml(Tail, [Char | Acc], ForAttr). diff --git a/lib/eunit/src/eunit_test.erl b/lib/eunit/src/eunit_test.erl new file mode 100644 index 0000000000..d322c4b420 --- /dev/null +++ b/lib/eunit/src/eunit_test.erl @@ -0,0 +1,320 @@ +%% This library is free software; you can redistribute it and/or modify +%% it under the terms of the GNU Lesser General Public License as +%% published by the Free Software Foundation; either version 2 of the +%% License, or (at your option) any later version. +%% +%% This library is distributed in the hope that it will be useful, but +%% WITHOUT ANY WARRANTY; without even the implied warranty of +%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%% Lesser General Public License for more details. +%% +%% You should have received a copy of the GNU Lesser General Public +%% License along with this library; if not, write to the Free Software +%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +%% USA +%% +%% $Id: eunit_test.erl 336 2009-03-06 14:43:21Z rcarlsson $ +%% +%% @author Richard Carlsson +%% @copyright 2006 Richard Carlsson +%% @private +%% @see eunit +%% @doc Test running functionality + +-module(eunit_test). + +-export([run_testfun/1, function_wrapper/2, enter_context/4, + multi_setup/1]). + + +-include("eunit.hrl"). +-include("eunit_internal.hrl"). + + +%% --------------------------------------------------------------------- +%% Getting a cleaned up stack trace. (We don't want it to include +%% eunit's own internal functions. This complicates self-testing +%% somewhat, but you can't have everything.) Note that we assume that +%% this particular module is the boundary between eunit and user code. + +get_stacktrace() -> + get_stacktrace([]). + +get_stacktrace(Ts) -> + eunit_lib:uniq(prune_trace(erlang:get_stacktrace(), Ts)). + +prune_trace([{eunit_data, _, _} | Rest], Tail) -> + prune_trace(Rest, Tail); +prune_trace([{?MODULE, _, _} | _Rest], Tail) -> + Tail; +prune_trace([T | Ts], Tail) -> + [T | prune_trace(Ts, Tail)]; +prune_trace([], Tail) -> + Tail. + + +%% --------------------------------------------------------------------- +%% Test runner + +%% @spec ((any()) -> any()) -> {ok, Value} | {error, eunit_lib:exception()} +%% @throws wrapperError() + +run_testfun(F) -> + try + F() + of Value -> + {ok, Value} + catch + {eunit_internal, Term} -> + %% Internally generated: re-throw Term (lose the trace) + throw(Term); + Class:Reason -> + {error, {Class, Reason, get_stacktrace()}} + end. + + +-ifdef(TEST). +macro_test_() -> + {"macro definitions", + [{?LINE, fun () -> + {?LINE, F} = ?_test(undefined), + {ok, undefined} = run_testfun(F) + end}, + ?_test(begin + {?LINE, F} = ?_assert(true), + {ok, ok} = run_testfun(F) + end), + ?_test(begin + {?LINE, F} = ?_assert(false), + {error,{error,{assertion_failed, + [{module,_}, + {line,_}, + {expression,_}, + {expected,true}, + {value,false}]}, + _}} + = run_testfun(F) + end), + ?_test(begin + {?LINE, F} = ?_assert([]), + {error,{error,{assertion_failed, + [{module,_}, + {line,_}, + {expression,_}, + {expected,true}, + {value,{not_a_boolean,[]}}]}, + _}} + = run_testfun(F) + end), + ?_test(begin + {?LINE, F} = ?_assertNot(false), + {ok, ok} = run_testfun(F) + end), + ?_test(begin + {?LINE, F} = ?_assertNot(true), + {error,{error,{assertion_failed, + [{module,_}, + {line,_}, + {expression,_}, + {expected,true}, + {value,false}]}, + _}} + = run_testfun(F) + end), + ?_test(begin + {?LINE, F} = ?_assertMatch(ok, ok), + {ok, ok} = run_testfun(F) + end), + ?_test(begin + {?LINE, F} = ?_assertMatch([_], []), + {error,{error,{assertMatch_failed, + [{module,_}, + {line,_}, + {expression,_}, + {expected,"[ _ ]"}, + {value,[]}]}, + _}} + = run_testfun(F) + end), + ?_test(begin + {?LINE, F} = ?_assertEqual(ok, ok), + {ok, ok} = run_testfun(F) + end), + ?_test(begin + {?LINE, F} = ?_assertEqual(3, 1+1), + {error,{error,{assertEqual_failed, + [{module,_}, + {line,_}, + {expression,_}, + {expected,3}, + {value,2}]}, + _}} + = run_testfun(F) + end), + ?_test(begin + {?LINE, F} = ?_assertException(error, badarith, + erlang:error(badarith)), + {ok, ok} = run_testfun(F) + end), + ?_test(begin + {?LINE, F} = ?_assertException(error, badarith, ok), + {error,{error,{assertException_failed, + [{module,_}, + {line,_}, + {expression,_}, + {expected,_}, + {unexpected_success,ok}]}, + _}} + = run_testfun(F) + end), + ?_test(begin + {?LINE, F} = ?_assertException(error, badarg, + erlang:error(badarith)), + {error,{error,{assertException_failed, + [{module,_}, + {line,_}, + {expression,_}, + {expected,_}, + {unexpected_exception, + {error,badarith,_}}]}, + _}} + = run_testfun(F) + end) + ]}. + +under_eunit_test() -> ?assert(?UNDER_EUNIT). +-endif. + + +%% --------------------------------------------------------------------- +%% Wrapper for simple "named function" tests ({M,F}), which provides +%% better error reporting when the function is missing at test time. +%% +%% Note that the wrapper fun is usually called by run_testfun/1, and the +%% special exceptions thrown here are expected to be handled there. +%% +%% @throws {eunit_internal, wrapperError()} +%% +%% @type wrapperError() = {no_such_function, mfa()} +%% | {module_not_found, moduleName()} + +function_wrapper(M, F) -> + fun () -> + try M:F() + catch + error:undef -> + %% Check if it was M:F/0 that was undefined + case erlang:module_loaded(M) of + false -> + fail({module_not_found, M}); + true -> + case erlang:function_exported(M, F, 0) of + false -> + fail({no_such_function, {M,F,0}}); + true -> + rethrow(error, undef, [{M,F,0}]) + end + end + end + end. + +rethrow(Class, Reason, Trace) -> + erlang:raise(Class, Reason, get_stacktrace(Trace)). + +fail(Term) -> + throw({eunit_internal, Term}). + + +-ifdef(TEST). +wrapper_test_() -> + {"error handling in function wrapper", + [?_assertException(throw, {module_not_found, eunit_nonexisting}, + run_testfun(function_wrapper(eunit_nonexisting,test))), + ?_assertException(throw, + {no_such_function, {?MODULE,nonexisting_test,0}}, + run_testfun(function_wrapper(?MODULE,nonexisting_test))), + ?_test({error, {error, undef, _T}} + = run_testfun(function_wrapper(?MODULE,wrapper_test_exported_))) + ]}. + +%% this must be exported (done automatically by the autoexport transform) +wrapper_test_exported_() -> + {ok, ?MODULE:nonexisting_function()}. +-endif. + + +%% --------------------------------------------------------------------- +%% Entering a setup-context, with guaranteed cleanup. + +%% @spec (Setup, Cleanup, Instantiate, Callback) -> any() +%% Setup = () -> any() +%% Cleanup = (any()) -> any() +%% Instantiate = (any()) -> tests() +%% Callback = (tests()) -> any() +%% @throws {context_error, Error, eunit_lib:exception()} +%% Error = setup_failed | instantiation_failed | cleanup_failed + +enter_context(Setup, Cleanup, Instantiate, Callback) -> + try Setup() of + R -> + try Instantiate(R) of + T -> + try Callback(T) %% call back to client code + after + %% Always run cleanup; client may be an idiot + try Cleanup(R) + catch + Class:Term -> + context_error(cleanup_failed, Class, Term) + end + end + catch + Class:Term -> + context_error(instantiation_failed, Class, Term) + end + catch + Class:Term -> + context_error(setup_failed, Class, Term) + end. + +context_error(Type, Class, Term) -> + throw({context_error, Type, {Class, Term, get_stacktrace()}}). + +%% This generates single setup/cleanup functions from a list of tuples +%% on the form {Tag, Setup, Cleanup}, where the setup function always +%% backs out correctly from partial completion. + +multi_setup(List) -> + {SetupAll, CleanupAll} = multi_setup(List, fun ok/1), + %% must reverse back and forth here in order to present the list in + %% "natural" order to the test instantiation function + {fun () -> lists:reverse(SetupAll([])) end, + fun (Rs) -> CleanupAll(lists:reverse(Rs)) end}. + +multi_setup([{Tag, S, C} | Es], CleanupPrev) -> + Cleanup = fun ([R | Rs]) -> + try C(R) of + _ -> CleanupPrev(Rs) + catch + Class:Term -> + throw({Tag, {Class, Term, get_stacktrace()}}) + end + end, + {SetupRest, CleanupAll} = multi_setup(Es, Cleanup), + {fun (Rs) -> + try S() of + R -> + SetupRest([R|Rs]) + catch + Class:Term -> + CleanupPrev(Rs), + throw({Tag, {Class, Term, get_stacktrace()}}) + end + end, + CleanupAll}; +multi_setup([{Tag, S} | Es], CleanupPrev) -> + multi_setup([{Tag, S, fun ok/1} | Es], CleanupPrev); +multi_setup([], CleanupAll) -> + {fun (Rs) -> Rs end, CleanupAll}. + +ok(_) -> ok. diff --git a/lib/eunit/src/eunit_tests.erl b/lib/eunit/src/eunit_tests.erl new file mode 100644 index 0000000000..37c0b4d6ae --- /dev/null +++ b/lib/eunit/src/eunit_tests.erl @@ -0,0 +1,42 @@ +%% This library is free software; you can redistribute it and/or modify +%% it under the terms of the GNU Lesser General Public License as +%% published by the Free Software Foundation; either version 2 of the +%% License, or (at your option) any later version. +%% +%% This library is distributed in the hope that it will be useful, but +%% WITHOUT ANY WARRANTY; without even the implied warranty of +%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%% Lesser General Public License for more details. +%% +%% You should have received a copy of the GNU Lesser General Public +%% License along with this library; if not, write to the Free Software +%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +%% USA +%% +%% $Id: eunit_tests.erl 238 2007-11-15 10:23:54Z mremond $ +%% +%% @author Richard Carlsson +%% @copyright 2007 Richard Carlsson +%% @private +%% @see eunit +%% @doc External tests for eunit.erl + +-module(eunit_tests). + +-include("eunit.hrl"). + +-ifdef(TEST). +%% Cause all the other modules to be tested as well as this one. +full_test_() -> + %%{application, eunit}. % this currently causes a loop + %% We use the below until loop detection is implemented + [eunit_autoexport, + eunit_striptests, + eunit_server, + eunit_proc, + eunit_serial, + eunit_test, + eunit_lib, + eunit_data, + eunit_tty]. +-endif. diff --git a/lib/eunit/src/eunit_tty.erl b/lib/eunit/src/eunit_tty.erl new file mode 100644 index 0000000000..5fe0140559 --- /dev/null +++ b/lib/eunit/src/eunit_tty.erl @@ -0,0 +1,257 @@ +%% This library is free software; you can redistribute it and/or modify +%% it under the terms of the GNU Lesser General Public License as +%% published by the Free Software Foundation; either version 2 of the +%% License, or (at your option) any later version. +%% +%% This library is distributed in the hope that it will be useful, but +%% WITHOUT ANY WARRANTY; without even the implied warranty of +%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%% Lesser General Public License for more details. +%% +%% You should have received a copy of the GNU Lesser General Public +%% License along with this library; if not, write to the Free Software +%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +%% USA +%% +%% $Id: eunit_tty.erl 330 2009-03-01 16:28:02Z rcarlsson $ +%% +%% @author Richard Carlsson +%% @copyright 2006-2009 Richard Carlsson +%% @private +%% @see eunit +%% @doc Text-based frontend for EUnit + +-module(eunit_tty). + +-behaviour(eunit_listener). + +-define(NODEBUG, true). +-include("eunit.hrl"). +-include("eunit_internal.hrl"). + +-export([start/0, start/1]). + +-export([init/1, handle_begin/3, handle_end/3, handle_cancel/3, + terminate/2]). + +-record(state, {verbose = false, + indent = 0 + }). + +start() -> + start([]). + +start(Options) -> + eunit_listener:start(?MODULE, Options). + +init(Options) -> + St = #state{verbose = proplists:get_bool(verbose, Options)}, + receive + {start, _Reference} -> + if St#state.verbose -> print_header(); + true -> ok + end, + St + end. + +terminate({ok, Data}, St) -> + Pass = proplists:get_value(pass, Data, 0), + Fail = proplists:get_value(fail, Data, 0), + Skip = proplists:get_value(skip, Data, 0), + Cancel = proplists:get_value(cancel, Data, 0), + if Fail =:= 0, Skip =:= 0, Cancel =:= 0 -> + if Pass =:= 0 -> + io:fwrite(" There were no tests to run.\n"); + true -> + if St#state.verbose -> print_bar(); + true -> ok + end, + if Pass =:= 1 -> + io:fwrite(" Test passed.\n"); + true -> + io:fwrite(" All ~w tests passed.\n", [Pass]) + end + end, + sync_end(ok); + true -> + print_bar(), + io:fwrite(" Failed: ~w. Skipped: ~w. Passed: ~w.\n", + [Fail, Skip, Pass]), + if Cancel =/= 0 -> + io:fwrite("One or more tests were cancelled.\n"); + true -> ok + end, + sync_end(error) + end; +terminate({error, Reason}, _St) -> + io:fwrite("Internal error: ~P.\n", [Reason, 25]), + sync_end(error). + +sync_end(Result) -> + receive + {stop, Reference, ReplyTo} -> + ReplyTo ! {result, Reference, Result}, + ok + end. + +print_header() -> + io:fwrite("======================== EUnit ========================\n"). + +print_bar() -> + io:fwrite("=======================================================\n"). + + +handle_begin(group, Data, St) -> + ?debugFmt("handle_begin group ~w", [Data]), + Desc = proplists:get_value(desc, Data), + if Desc =/= "", Desc =/= undefined, St#state.verbose -> + I = St#state.indent, + print_group_start(I, Desc), + St#state{indent = I + 1}; + true -> + St + end; +handle_begin(test, Data, St) -> + ?debugFmt("handle_begin test ~w", [Data]), + if St#state.verbose -> print_test_begin(St#state.indent, Data); + true -> ok + end, + St. + +handle_end(group, Data, St) -> + ?debugFmt("handle_end group ~w", [Data]), + Desc = proplists:get_value(desc, Data), + if Desc =/= "", Desc =/= undefined, St#state.verbose -> + Time = proplists:get_value(time, Data), + I = St#state.indent, + print_group_end(I, Time), + St#state{indent = I - 1}; + true -> + St + end; +handle_end(test, Data, St) -> + ?debugFmt("handle_end test ~w", [Data]), + case proplists:get_value(status, Data) of + ok -> + if St#state.verbose -> print_test_end(Data); + true -> ok + end, + St; + Status -> + if St#state.verbose -> ok; + true -> print_test_begin(St#state.indent, Data) + end, + print_test_error(Status, Data), + St + end. + +handle_cancel(group, Data, St) -> + ?debugFmt("handle_cancel group ~w", [Data]), + I = St#state.indent, + case proplists:get_value(reason, Data) of + undefined -> + %% "skipped" message is not interesting here + St#state{indent = I - 1}; + Reason -> + Desc = proplists:get_value(desc, Data), + if Desc =/= "", Desc =/= undefined, St#state.verbose -> + print_group_cancel(I, Reason); + true -> + print_group_start(I, Desc), + print_group_cancel(I, Reason) + end, + St#state{indent = I - 1} + end; +handle_cancel(test, Data, St) -> + ?debugFmt("handle_cancel test ~w", [Data]), + if St#state.verbose -> ok; + true -> print_test_begin(St#state.indent, Data) + end, + print_test_cancel(proplists:get_value(reason, Data)), + St. + + +indent(N) when is_integer(N), N >= 1 -> + io:put_chars(lists:duplicate(N * 2, $\s)); +indent(_N) -> + ok. + +print_group_start(I, Desc) -> + indent(I), + io:fwrite("~s\n", [Desc]). + +print_group_end(I, Time) -> + if Time > 0 -> + indent(I), + io:fwrite("[done in ~.3f s]\n", [Time/1000]); + true -> + ok + end. + +print_test_begin(I, Data) -> + Desc = proplists:get_value(desc, Data), + Line = proplists:get_value(line, Data, 0), + indent(I), + L = if Line =:= 0 -> ""; + true -> io_lib:fwrite("~w:", [Line]) + end, + D = if Desc =:= "" ; Desc =:= undefined -> ""; + true -> io_lib:fwrite(" (~s)", [Desc]) + end, + case proplists:get_value(source, Data) of + {Module, Name, _Arity} -> + io:fwrite("~s:~s ~s~s...", [Module, L, Name, D]); + _ -> + io:fwrite("~s~s...", [L, D]) + end. + +print_test_end(Data) -> + Time = proplists:get_value(time, Data, 0), + T = if Time > 0 -> io_lib:fwrite("[~.3f s] ", [Time/1000]); + true -> "" + end, + io:fwrite("~sok\n", [T]). + +print_test_error({error, Exception}, Data) -> + Output = proplists:get_value(output, Data), + io:fwrite("*failed*\n::~s", + [eunit_lib:format_exception(Exception)]), + case Output of + <<>> -> + io:put_chars("\n\n"); + <> -> + io:fwrite(" output:<<\"~s\">>...\n\n", [Text]); + _ -> + io:fwrite(" output:<<\"~s\">>\n\n", [Output]) + end; +print_test_error({skipped, Reason}, _) -> + io:fwrite("*did not run*\n::~s\n", [format_skipped(Reason)]). + +format_skipped({module_not_found, M}) -> + io_lib:format("missing module: ~w", [M]); +format_skipped({no_such_function, {M,F,A}}) -> + io_lib:format("no such function: ~w:~w/~w", [M,F,A]). + +print_test_cancel(Reason) -> + io:fwrite(format_cancel(Reason)). + +print_group_cancel(_I, {blame, _}) -> + ok; +print_group_cancel(I, Reason) -> + indent(I), + io:fwrite(format_cancel(Reason)). + +format_cancel(undefined) -> + "*skipped*\n"; +format_cancel(timeout) -> + "*timed out*\n"; +format_cancel({startup, Reason}) -> + io_lib:fwrite("*could not start test process*\n::~P\n\n", + [Reason, 15]); +format_cancel({blame, _SubId}) -> + "*cancelled because of subtask*\n"; +format_cancel({exit, Reason}) -> + io_lib:fwrite("*unexpected termination of test process*\n::~P\n\n", + [Reason, 15]); +format_cancel({abort, Reason}) -> + eunit_lib:format_error(Reason). diff --git a/lib/eunit/test/Makefile b/lib/eunit/test/Makefile new file mode 100644 index 0000000000..83fca0ade4 --- /dev/null +++ b/lib/eunit/test/Makefile @@ -0,0 +1,82 @@ +# +# %CopyrightBegin% +# +# Copyright Ericsson AB 2009. All Rights Reserved. +# +# The contents of this file are subject to the Erlang Public License, +# Version 1.1, (the "License"); you may not use this file except in +# compliance with the License. You should have received a copy of the +# Erlang Public License along with this software. If not, it can be +# retrieved online at http://www.erlang.org/. +# +# Software distributed under the License is distributed on an "AS IS" +# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +# the License for the specific language governing rights and limitations +# under the License. +# +# %CopyrightEnd% +# +include $(ERL_TOP)/make/target.mk +include $(ERL_TOP)/make/$(TARGET)/otp.mk + +MODULES = \ + eunit_SUITE + +ERL_FILES= $(MODULES:%=%.erl) + +TARGET_FILES= $(MODULES:%=$(EBIN)/%.$(EMULATOR)) +INSTALL_PROGS= $(TARGET_FILES) + +EMAKEFILE=Emakefile +COVERFILE=eunit.cover + +# ---------------------------------------------------- +# Release directory specification +# ---------------------------------------------------- +RELSYSDIR = $(RELEASE_PATH)/eunit_test + +# ---------------------------------------------------- +# FLAGS +# ---------------------------------------------------- +ERL_MAKE_FLAGS += -pa $(ERL_TOP)/lib/test_server/ebin +ERL_COMPILE_FLAGS += -I$(ERL_TOP)/lib/test_server/include + +EBIN = . + +# ---------------------------------------------------- +# Targets +# ---------------------------------------------------- +.PHONY: make_emakefile + +make_emakefile: + $(ERL_TOP)/make/make_emakefile $(ERL_COMPILE_FLAGS) -o$(EBIN) \ + $(MODULES) >> $(EMAKEFILE) + +tests debug opt: make_emakefile + erl $(ERL_MAKE_FLAGS) -make + +clean: + rm -f $(EMAKEFILE) + rm -f $(TARGET_FILES) + rm -f core + +docs: + +# ---------------------------------------------------- +# Special targets +# ---------------------------------------------------- + +# ---------------------------------------------------- +# Release Target +# ---------------------------------------------------- +include $(ERL_TOP)/make/otp_release_targets.mk + +release_spec: opt + +release_tests_spec: make_emakefile + $(INSTALL_DIR) $(RELSYSDIR) + $(INSTALL_DATA) eunit.dynspec $(EMAKEFILE) \ + $(COVERFILE) $(ERL_FILES) \ + $(RELSYSDIR) + +release_docs_spec: diff --git a/lib/eunit/test/eunit.cover b/lib/eunit/test/eunit.cover new file mode 100644 index 0000000000..d1eaf770b6 --- /dev/null +++ b/lib/eunit/test/eunit.cover @@ -0,0 +1,3 @@ +%% -*- erlang -*- +{exclude,[eunit_test]}. + diff --git a/lib/eunit/test/eunit.dynspec b/lib/eunit/test/eunit.dynspec new file mode 100644 index 0000000000..c1d345ac14 --- /dev/null +++ b/lib/eunit/test/eunit.dynspec @@ -0,0 +1,6 @@ +%% -*- erlang -*- +%% You can test this file using this command. +%% file:script("eunit.dynspec", [{'Os',"Unix"}]). + +[]. + diff --git a/lib/eunit/test/eunit_SUITE.erl b/lib/eunit/test/eunit_SUITE.erl new file mode 100644 index 0000000000..4ebcec6f5d --- /dev/null +++ b/lib/eunit/test/eunit_SUITE.erl @@ -0,0 +1,31 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +-module(eunit_SUITE). + +-export([all/1,eunit_test/1]). + +-include("test_server.hrl"). + +all(suite) -> + [eunit_test]. + +eunit_test(Config) when is_list(Config) -> + ok = file:set_cwd(code:lib_dir(eunit)), + ok = eunit:test(eunit). + diff --git a/lib/eunit/vsn.mk b/lib/eunit/vsn.mk new file mode 100644 index 0000000000..002703b1b3 --- /dev/null +++ b/lib/eunit/vsn.mk @@ -0,0 +1 @@ +EUNIT_VSN = 2.1.4 -- cgit v1.2.3