KallDrexx
KallDrexx

Reputation: 27803

Why does Eunit not require test functions to be exported?

I'm going through the EUnit chapter in Learn You Some Erlang and one thing I am noticing from all the code samples is the test functions are never declared in -export() clauses.

Why is EUnit able to pick these test functions up?

Upvotes: 3

Views: 404

Answers (2)

toraritte
toraritte

Reputation: 8273

Glad I found this question because it gives me a meaningful way to procrastinate and I was wondering how functions get created and exported dynamically.

Started by looking at the latest commit affecting EUnit in the Erlang/OTP Github repo, which is 4273cbd. (The only reason for this was to find a relatively stable anchor instead of git branches.)

0. Include EUnit's header file

According EUnit's User's Guide, the first step is to -include_lib("eunit/include/eunit.hrl"). in the tested module, so I assume this is where the magic happens.

1. otp/lib/eunit/include/eunit.hrl (lines 79 - 91)

%% 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.

1.1 What does -compile({parse_transform, eunit_autoexport}). mean?

From the Erlang Reference Manual's Module chapter (Pre-Defined Module Attributes):

-compile(Options).
Compiler options. Options is a single option or a list of options. This attribute is added to the option list when compiling the module. See the compile(3) manual page in Compiler.

On to compile(3):

{parse_transform,Module}
Causes the parse transformation function Module:parse_transform/2 to be applied to the parsed code before the code is checked for errors.

From the erl_id_trans module:

This module performs an identity parse transformation of Erlang code. It is included as an example for users who wants to write their own parse transformers. If option {parse_transform,Module} is passed to the compiler, a user-written function parse_transform/2 is called by the compiler before the code is checked for errors.

Basically, if module M includes the {parse_transform, Module} compile option, then all of M's functions and attributes can be iterated through using your implementation of Module:parse_transform/2. Its first argument is Forms, which is M's module declaration described in Erlang's abstract format (described in Erlang Run-Time System Application (ERTS) User's Guide.

2. otp/lib/eunit/src/eunit_autoexport.erl

This module only exports parse_transfrom/2 to satisfy {parse_transform, Module} compile option and its first order of business is to figure out what are the configured suffixes for test case functions and generators. If not set manually, using _test and _test_ respectively (via lib/eunit/src/eunit_internal.hrl).

It then scans all the functions and attributes of your module using eunit_autoexport:form/5, and builds a list of to be exported functions where the suffixes above match (plus the original functions. I may be wrong on this one...).

Finally, eunit_autoexport:rewrite/2 builds a module declaration from the original Forms (given to eunit_autoexport:parse_transform/2 as the first argument) and the list of functions to be exported (that was supplied by form/5 above). On line 82 it injects the test/0 function mentioned in the EUnit documentation.

Upvotes: 0

Greg
Greg

Reputation: 8340

From the documentation:

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)

Upvotes: 3

Related Questions