Skip to content
This repository

Add tests= filter for eunit suites #282

Closed
wants to merge 1 commit into from

4 participants

Roberto Ostinelli Richard Carlsson Fred Hebert Tuncer Ayaz
Roberto Ostinelli

Following implementation discussion in issue #250.

To summarize, consider the following setup:

myapp_mymod.erl

-module(myapp_mymod).
-export([myfunc/0]).
-include_lib("eunit/include/eunit.hrl").
myfunc() -> ok.
myprivate_test() -> ?assert(true).

myapp_mymod_tests.erl

-module(myapp_mymod_tests).
-compile([export_all]).
-include_lib("eunit/include/eunit.hrl").
myfunc_test() -> ?assertMatch(ok, myapp_mymod:myfunc()).

rebar eunit suites=myapp_mymod will run:

  • myapp_mymod:myprivate_test/0
  • myapp_mymod_tests:myfunc_test/0

rebar eunit suites=myapp_mymod tests=my will run the first match:

  • myapp_mymod:myprivate_test/0

rebar eunit suites=myapp_mymod tests=myfun will run the first match (available in _tests module):

  • myapp_mymod_tests:myfunc_test/0
Roberto Ostinelli

@tuncer, @dizzyd and @richcarl, this should be what you guys had in mind.

Richard Carlsson

It's not apparent from the example if this works for generator functions as well (or a mix of simple tests and generators), but I assume that it does. If the example is going into the docs you might want to fix that.

Roberto Ostinelli

@richcarl indeed, eunit:test([eunit_test:function_wrapper(M, F)]) works for both. Since these are all tests, why do you believe we should differentiate in the example?

Also, I've just realized that:

rebar suites=foo_tests tests=bar

will not work because _tests modules are currently stripped out at the beginning of the parsing rebar does to avoid running these tests twice. However if you do:

rebar suites=foo tests=bar

then foo_tests:bar/0 will be run, but only if there isn't a foo:bar/0 which will otherwise be run instead.

The question is: do you think we should cover this case too, i.e. allowing the developer to specify that they can select a specific test in the _tests module too? If so, I can provide a patch and rebase it to this one. Not sure what happens to the cover cases though.

Fred Hebert
ferd commented August 02, 2012

I have to say that I frequently write functions that test other implementations with similar names in the main module, but they nearly never intersect, mostly due of needing the _test() or _test_() name.

If this pull request changes the behaviour so that functions that do not end in either of these two patterns can be run as tests, I'm not sure I would want to encourage it. It would be changing a long-established and stable eunit behaviour for something entirely new and non-obvious. Suddenly, tests that are defined in the module and runnable in a way are no longer runnable in another one as they are ignored.

Related to this, this is Eunit's standard structure in test descriptors for eunit:test/1-2 :

  • {module, Mod} runs all tests in Mod
  • {dir, Path} runs all the tests for the modules found in Path
  • {file, Path} runs all the tests found in a single compiled module
  • {generator, Fun} runs a single generator function as a test.
  • {application, AppName} runs all the tests for all the modules mentioned in AppName's .app file.

While the {generator, Fun} pattern would allow what this pull request does, I think (and @richcarl can confirm/deny) it was meant to run individual tests rather than change eunit's behaviour, more as a way to debug existing suite than to allow someone to work around how Eunit expects tests to be named.

It could make sense to restrict its use a bit, or at the very least to use documentation the follows the _test() or _test_() standards rather than foo.

Roberto Ostinelli

If this pull request changes the behaviour so that functions that do not end in either of these two patterns can be run as tests, I'm not sure I would want to encourage it.

It does not. It only considers exported functions ending in _test or _test_.

It could make sense to restrict its use a bit, or at the very least to use documentation the follows the _test() or _test_() standards rather than foo.

In the original pull request #250 you can see that the 'shortest matching logic' has been requested. This is documented here and here.

tl;dr: tests=foo will only match test functions starting with foo (i.e. foo*_test and foo*_test_).

Related to this, this is Eunit's standard structure in test descriptors for eunit:test/1-2 :

Following the above mentioned discussion, FYI @richcarl has already added {test, M, F} to the descriptors, in his repo.

Moreover, the usage of eunit_test:function_wrapper(M, F) in the patch follows his recommendation.

Roberto Ostinelli

Finalized and updated patch.

To summarize:


rebar eunit suites=foo

will run all tests in foo and foo_tests.


rebar eunit suites=foo_tests

will run all tests in foo_tests.


rebar eunit suites=foo tests=bar

will look in this order, and run only the first existing test:

foo:bar_test
foo:bar_test_
foo_tests:bar_test
foo_tests:bar_test_

rebar eunit suites=foo tests=b

same as previous example, will look first into foo and then into foo_tests to run only the first existing test whose name starts with b.


rebar eunit suites=foo_tests tests=bar

will look in this order, and run only the first existing test:

foo_tests:bar_test
foo_tests:bar_test_

rebar eunit suites=foo_tests tests=bar_test_

will look in this order, and run only the first existing test:

foo_tests:bar_test_
Richard Carlsson

Seems good to me. The only thing you might want to consider is whether the user should be allowed to give a regexp or glob instead of just a prefix, making it possible to select a bunch of tests easily, e.g., tests=foo* or tests=(foo|bar)_blaha.*

Fred Hebert
ferd commented August 06, 2012

@ostinelli that seems good to me.

@richcarl That's an interesting idea. Would it create any command line issues if I have a function with a 'name-as*-atom-with-*-in-it_test'? It might be better to document the fuzzy matching as expected behaviour (or risk people not knowing about it) rather than starting to handle escaping in there, though that's nothing but a corner case.

Roberto Ostinelli

@richcarl I believe this is a good idea. However, this pull request has been going on for more than 2 months now (see #250), and I'd like to see it merged upstream, or abandoned.

What you are proposing can easily be implemented in a future feature, since it wouldn't break backwards compatibility with the functionality currently implemented in this patch.

I guess now it's up to @tuncer and the basho guys. ^^_

r.

Tuncer Ayaz

Thanks, merged.

Tuncer Ayaz tuncer closed this August 11, 2012
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Showing 1 unique commit by 1 author.

Aug 10, 2012
Roberto Ostinelli Add tests= filter for eunit suites 21c0df5
This page is out of date. Refresh to see the latest.
4  src/rebar.erl
@@ -297,7 +297,9 @@ generate-upgrade  previous_release=path  Build an upgrade package
297 297
 
298 298
 generate-appups   previous_release=path  Generate appup files
299 299
 
300  
-eunit       [suites=foo]             Run eunit [test/foo_tests.erl] tests
  300
+eunit       [suites=foo]             Run eunit tests [foo.erl and test/foo_tests.erl]
  301
+            [suites=foo] [tests=bar] Run specific eunit tests [first test name starting
  302
+                                     with 'bar' in foo.erl and test/foo_tests.erl]
301 303
 ct          [suites=] [case=]        Run common_test suites
302 304
 
303 305
 qc                                   Test QuickCheck properties
168  src/rebar_eunit.erl
@@ -43,7 +43,14 @@
43 43
 %% The following Global options are supported:
44 44
 %% <ul>
45 45
 %%   <li>verbose=1 - show extra output from the eunit test</li>
46  
-%%   <li>suites="foo,bar" - runs test/foo_tests.erl and test/bar_tests.erl</li>
  46
+%%   <li>
  47
+%%      suites="foo,bar" - runs tests in foo.erl, test/foo_tests.erl and
  48
+%%      tests in bar.erl, test/bar_tests.erl
  49
+%%   </li>
  50
+%%   <li>
  51
+%%      suites="foo,bar" tests="baz"- runs first test with name starting
  52
+%%      with 'baz' in foo.erl, test/foo_tests.erl and tests in bar.erl,
  53
+%%      test/bar_tests.erl
47 54
 %% </ul>
48 55
 %% Additionally, for projects that have separate folders for the core
49 56
 %% implementation, and for the unit tests, then the following
@@ -92,21 +99,7 @@ run_eunit(Config, CodePath, SrcErls) ->
92 99
     %% cover and eunit testing. Normally you can just tell cover
93 100
     %% and/or eunit to scan the directory for you, but eunit does a
94 101
     %% code:purge in conjunction with that scan and causes any cover
95  
-    %% compilation info to be lost.  Filter out "*_tests" modules so
96  
-    %% eunit won't doubly run them and so cover only calculates
97  
-    %% coverage on production code.  However, keep "*_tests" modules
98  
-    %% that are not automatically included by eunit.
99  
-    %%
100  
-    %% From 'Primitives' in the EUnit User's Guide
101  
-    %% http://www.erlang.org/doc/apps/eunit/chapter.html
102  
-    %% "In addition, EUnit will also look for another module whose
103  
-    %% name is ModuleName plus the suffix _tests, and if it exists,
104  
-    %% all the tests from that module will also be added. (If
105  
-    %% ModuleName already contains the suffix _tests, this is not
106  
-    %% done.) E.g., the specification {module, mymodule} will run all
107  
-    %% tests in the modules mymodule and mymodule_tests. Typically,
108  
-    %% the _tests module should only contain test cases that use the
109  
-    %% public interface of the main module (and no other code)."
  102
+    %% compilation info to be lost.
110 103
 
111 104
     AllBeamFiles = rebar_utils:beams(?EUNIT_DIR),
112 105
     {BeamFiles, TestBeamFiles} =
@@ -115,16 +108,20 @@ run_eunit(Config, CodePath, SrcErls) ->
115 108
     OtherBeamFiles = TestBeamFiles --
116 109
         [filename:rootname(N) ++ "_tests.beam" || N <- AllBeamFiles],
117 110
     ModuleBeamFiles = BeamFiles ++ OtherBeamFiles,
118  
-    Modules = [rebar_utils:beam_to_mod(?EUNIT_DIR, N) || N <- ModuleBeamFiles],
  111
+
  112
+    %% Get modules to be run in eunit
  113
+    AllModules = [rebar_utils:beam_to_mod(?EUNIT_DIR, N) || N <- AllBeamFiles],
  114
+    {SuitesProvided, FilteredModules} = filter_suites(Config, AllModules),
  115
+    Tests = get_tests(Config, SuitesProvided, ModuleBeamFiles, FilteredModules),
  116
+
119 117
     SrcModules = [rebar_utils:erl_to_mod(M) || M <- SrcErls],
120  
-    FilteredModules = filter_modules(Config, Modules),
121 118
 
122 119
     {ok, CoverLog} = cover_init(Config, ModuleBeamFiles),
123 120
 
124 121
     StatusBefore = status_before_eunit(),
125  
-    EunitResult = perform_eunit(Config, FilteredModules),
126  
-    perform_cover(Config, FilteredModules, SrcModules),
  122
+    EunitResult = perform_eunit(Config, Tests),
127 123
 
  124
+    perform_cover(Config, FilteredModules, SrcModules),
128 125
     cover_close(CoverLog),
129 126
 
130 127
     case proplists:get_value(reset_after_eunit, get_eunit_opts(Config),
@@ -164,17 +161,136 @@ setup_code_path() ->
164 161
     true = code:add_pathz(rebar_utils:ebin_dir()),
165 162
     CodePath.
166 163
 
167  
-filter_modules(Config, Modules) ->
  164
+filter_suites(Config, Modules) ->
168 165
     RawSuites = rebar_config:get_global(Config, suites, ""),
  166
+    SuitesProvided = RawSuites =/= "",
169 167
     Suites = [list_to_atom(Suite) || Suite <- string:tokens(RawSuites, ",")],
170  
-    filter_modules1(Modules, Suites).
  168
+    {SuitesProvided, filter_suites1(Modules, Suites)}.
171 169
 
172  
-filter_modules1(Modules, []) ->
  170
+filter_suites1(Modules, []) ->
173 171
     Modules;
174  
-filter_modules1(Modules, Suites) ->
  172
+filter_suites1(Modules, Suites) ->
175 173
     [M || M <- Modules, lists:member(M, Suites)].
176 174
 
177  
-perform_eunit(Config, FilteredModules) ->
  175
+get_tests(Config, SuitesProvided, ModuleBeamFiles, FilteredModules) ->
  176
+    case SuitesProvided of
  177
+        false ->
  178
+            %% No specific suites have been provided, use ModuleBeamFiles
  179
+            %% which filters out "*_tests" modules so eunit won't doubly run
  180
+            %% them and cover only calculates coverage on production code.
  181
+            %% However, keep "*_tests" modules that are not automatically
  182
+            %% included by eunit.
  183
+            %%
  184
+            %% From 'Primitives' in the EUnit User's Guide
  185
+            %% http://www.erlang.org/doc/apps/eunit/chapter.html
  186
+            %% "In addition, EUnit will also look for another module whose
  187
+            %% name is ModuleName plus the suffix _tests, and if it exists,
  188
+            %% all the tests from that module will also be added. (If
  189
+            %% ModuleName already contains the suffix _tests, this is not
  190
+            %% done.) E.g., the specification {module, mymodule} will run all
  191
+            %% tests in the modules mymodule and mymodule_tests. Typically,
  192
+            %% the _tests module should only contain test cases that use the
  193
+            %% public interface of the main module (and no other code)."
  194
+            [rebar_utils:beam_to_mod(?EUNIT_DIR, N) || N <- ModuleBeamFiles];
  195
+        true ->
  196
+            %% Specific suites have been provided, return the existing modules
  197
+            build_tests(Config, FilteredModules)
  198
+    end.
  199
+
  200
+build_tests(Config, SuitesModules) ->
  201
+    RawFunctions = rebar_utils:get_experimental_global(Config, tests, ""),
  202
+    Tests = [list_to_atom(F1) || F1 <- string:tokens(RawFunctions, ",")],
  203
+    case Tests of
  204
+        [] ->
  205
+            SuitesModules;
  206
+        Functions ->
  207
+            case build_tests1(SuitesModules, Functions, []) of
  208
+                [] ->
  209
+                    [];
  210
+                RawTests ->
  211
+                    ?CONSOLE("    Running test function(s):~n", []),
  212
+                    F = fun({M, F2}, Acc) ->
  213
+                                ?CONSOLE("      ~p:~p/0~n", [M, F2]),
  214
+                                [eunit_test:function_wrapper(M, F2)|Acc]
  215
+                        end,
  216
+                    lists:foldl(F, [], RawTests)
  217
+            end
  218
+    end.
  219
+
  220
+build_tests1([], _Functions, TestFunctions) ->
  221
+    TestFunctions;
  222
+
  223
+build_tests1([Module|TModules], Functions, TestFunctions) ->
  224
+    %% Get module exports
  225
+    ModuleStr = atom_to_list(Module),
  226
+    ModuleExports = get_beam_test_exports(ModuleStr),
  227
+    %% Get module _tests exports
  228
+    TestModuleStr = string:concat(ModuleStr, "_tests"),
  229
+    TestModuleExports = get_beam_test_exports(TestModuleStr),
  230
+    %% Build tests {M, F} list
  231
+    Tests = build_tests2(Functions, {Module, ModuleExports},
  232
+                         {list_to_atom(TestModuleStr), TestModuleExports}),
  233
+    build_tests1(TModules, Functions, lists:merge([TestFunctions, Tests])).
  234
+
  235
+build_tests2(Functions, {Mod, ModExports}, {TestMod, TestModExports}) ->
  236
+    %% Look for matching functions into ModExports
  237
+    ModExportsStr = [atom_to_list(E1) || E1 <- ModExports],
  238
+    TestModExportsStr = [atom_to_list(E2) || E2 <- TestModExports],
  239
+    get_matching_exports(Functions, {Mod, ModExportsStr},
  240
+                         {TestMod, TestModExportsStr}, []).
  241
+
  242
+get_matching_exports([], _, _, Matched) ->
  243
+    Matched;
  244
+get_matching_exports([Function|TFunctions], {Mod, ModExportsStr},
  245
+                     {TestMod, TestModExportsStr}, Matched) ->
  246
+
  247
+    FunctionStr = atom_to_list(Function),
  248
+    %% Get matching Function in module, otherwise look in _tests module
  249
+    NewMatch = case get_matching_export(FunctionStr, ModExportsStr) of
  250
+                   [] ->
  251
+                       {TestMod, get_matching_export(FunctionStr,
  252
+                                                     TestModExportsStr)};
  253
+                   MatchingExport ->
  254
+                       {Mod, MatchingExport}
  255
+               end,
  256
+    case NewMatch of
  257
+        {_, []} ->
  258
+            get_matching_exports(TFunctions, {Mod, ModExportsStr},
  259
+                                 {TestMod, TestModExportsStr}, Matched);
  260
+        _ ->
  261
+            get_matching_exports(TFunctions, {Mod, ModExportsStr},
  262
+                                 {TestMod, TestModExportsStr},
  263
+                                 [NewMatch|Matched])
  264
+    end.
  265
+
  266
+get_matching_export(_FunctionStr, []) ->
  267
+    [];
  268
+get_matching_export(FunctionStr, [ExportStr|TExportsStr]) ->
  269
+    case string:str(ExportStr, FunctionStr) of
  270
+        1 ->
  271
+            list_to_atom(ExportStr);
  272
+        _ ->
  273
+            get_matching_export(FunctionStr, TExportsStr)
  274
+    end.
  275
+
  276
+get_beam_test_exports(ModuleStr) ->
  277
+    FilePath = filename:join(eunit_dir(),
  278
+                             string:concat(ModuleStr, ".beam")),
  279
+    case filelib:is_regular(FilePath) of
  280
+        true ->
  281
+            {beam_file, _, Exports0, _, _, _} = beam_disasm:file(FilePath),
  282
+            Exports1 = [FunName || {FunName, FunArity, _} <-
  283
+                                       Exports0, FunArity =:= 0],
  284
+            F = fun(FName) ->
  285
+                        FNameStr = atom_to_list(FName),
  286
+                        re:run(FNameStr, "_test(_)?") =/= nomatch
  287
+                end,
  288
+            lists:filter(F, Exports1);
  289
+        _ ->
  290
+            []
  291
+    end.
  292
+
  293
+perform_eunit(Config, Tests) ->
178 294
     EunitOpts = get_eunit_opts(Config),
179 295
 
180 296
     %% Move down into ?EUNIT_DIR while we run tests so any generated files
@@ -182,7 +298,7 @@ perform_eunit(Config, FilteredModules) ->
182 298
     Cwd = rebar_utils:get_cwd(),
183 299
     ok = file:set_cwd(?EUNIT_DIR),
184 300
 
185  
-    EunitResult = (catch eunit:test(FilteredModules, EunitOpts)),
  301
+    EunitResult = (catch eunit:test(Tests, EunitOpts)),
186 302
 
187 303
     %% Return to original working dir
188 304
     ok = file:set_cwd(Cwd),
118  test/rebar_eunit_tests.erl
@@ -59,6 +59,104 @@ eunit_test_() ->
59 59
                ?_assert(string:str(RebarOut, "All 2 tests passed") =/= 0)}]
60 60
      end}.
61 61
 
  62
+eunit_with_suites_and_tests_test_() ->
  63
+    [{"Ensure EUnit runs selected suites",
  64
+      setup, fun() ->
  65
+                     setup_project_with_multiple_modules(),
  66
+                     rebar("-v eunit suites=myapp_mymod2")
  67
+             end,
  68
+      fun teardown/1,
  69
+      fun(RebarOut) ->
  70
+              [{"Selected suite tests in 'test' directory are found and run",
  71
+                ?_assert(string:str(RebarOut, "myapp_mymod2_tests:") =/= 0)},
  72
+
  73
+               {"Selected suite tests in 'src' directory are found and run",
  74
+                ?_assert(string:str(RebarOut, "myapp_mymod2:") =/= 0)},
  75
+
  76
+               {"Unselected suite tests in 'test' directory are not run",
  77
+                ?_assert(string:str(RebarOut, "myapp_mymod_tests:") =:= 0)},
  78
+
  79
+               {"Unselected suite tests in 'src' directory are not run",
  80
+                ?_assert(string:str(RebarOut, "myapp_mymod:") =:= 0)},
  81
+
  82
+               {"Selected suite tests are only run once",
  83
+                ?_assert(string:str(RebarOut, "All 4 tests passed") =/= 0)}]
  84
+      end},
  85
+     {"Ensure EUnit runs selected _tests suites",
  86
+      setup, fun() ->
  87
+                     setup_project_with_multiple_modules(),
  88
+                     rebar("-v eunit suites=myapp_mymod2_tests")
  89
+             end,
  90
+      fun teardown/1,
  91
+      fun(RebarOut) ->
  92
+              [{"Selected suite tests in 'test' directory are found and run",
  93
+                ?_assert(string:str(RebarOut, "myapp_mymod2_tests:") =/= 0)},
  94
+
  95
+               {"Selected suite tests in 'src' directory are not run",
  96
+                ?_assert(string:str(RebarOut, "myapp_mymod2:") =:= 0)},
  97
+
  98
+               {"Unselected suite tests in 'test' directory are not run",
  99
+                ?_assert(string:str(RebarOut, "myapp_mymod_tests:") =:= 0)},
  100
+
  101
+               {"Unselected suite tests in 'src' directory are not run",
  102
+                ?_assert(string:str(RebarOut, "myapp_mymod:") =:= 0)},
  103
+
  104
+               {"Selected suite tests are only run once",
  105
+                ?_assert(string:str(RebarOut, "All 2 tests passed") =/= 0)}]
  106
+      end},
  107
+     {"Ensure EUnit runs a specific test defined in a selected suite",
  108
+      setup, fun() ->
  109
+                     setup_project_with_multiple_modules(),
  110
+                     rebar("-v eunit suites=myapp_mymod2 tests=myprivate2")
  111
+             end,
  112
+      fun teardown/1,
  113
+      fun(RebarOut) ->
  114
+              [{"Selected suite tests are found and run",
  115
+                ?_assert(string:str(RebarOut,
  116
+                                    "myapp_mymod2:myprivate2_test/0") =/= 0)},
  117
+
  118
+               {"Selected suite tests is run once",
  119
+                ?_assert(string:str(RebarOut, "Test passed") =/= 0)}]
  120
+      end},
  121
+     {"Ensure EUnit runs specific tests defined in selected suites",
  122
+      setup, fun() ->
  123
+                     setup_project_with_multiple_modules(),
  124
+                     rebar("-v eunit suites=myapp_mymod,myapp_mymod2"
  125
+                           " tests=myprivate,myfunc2")
  126
+             end,
  127
+      fun teardown/1,
  128
+      fun(RebarOut) ->
  129
+              [{"Selected suite tests are found and run",
  130
+                [?_assert(string:str(RebarOut,
  131
+                                     "myapp_mymod:myprivate_test/0") =/= 0),
  132
+                 ?_assert(string:str(RebarOut,
  133
+                                     "myapp_mymod2:myprivate2_test/0") =/= 0),
  134
+                 ?_assert(
  135
+                    string:str(RebarOut,
  136
+                               "myapp_mymod2_tests:myfunc2_test/0") =/= 0)]},
  137
+
  138
+               {"Selected suite tests are run once",
  139
+                ?_assert(string:str(RebarOut, "All 3 tests passed") =/= 0)}]
  140
+      end},
  141
+     {"Ensure EUnit runs specific test in _tests suites",
  142
+      setup,
  143
+      fun() ->
  144
+              setup_project_with_multiple_modules(),
  145
+              rebar("-v eunit suites=myapp_mymod2_tests tests=common_name_test")
  146
+      end,
  147
+      fun teardown/1,
  148
+      fun(RebarOut) ->
  149
+              [{"Only selected suite tests are found and run",
  150
+                [?_assert(string:str(RebarOut,
  151
+                                     "myapp_mymod2:common_name_test/0") =:= 0),
  152
+                 ?_assert(string:str(RebarOut,
  153
+                                     "myapp_mymod2_tests:common_name_test/0")
  154
+                          =/= 0)]},
  155
+
  156
+               {"Selected suite tests is run once",
  157
+                ?_assert(string:str(RebarOut, "Test passed") =/= 0)}]
  158
+      end}].
  159
+
62 160
 cover_test_() ->
63 161
     {"Ensure Cover runs with tests in a test dir and no defined suite",
64 162
      setup, fun() -> setup_cover_project(), rebar("-v eunit") end,
@@ -158,6 +256,21 @@ basic_setup_test_() ->
158 256
          "-include_lib(\"eunit/include/eunit.hrl\").\n",
159 257
          "myfunc_test() -> ?assertMatch(ok, myapp_mymod:myfunc()).\n"]).
160 258
 
  259
+-define(myapp_mymod2,
  260
+        ["-module(myapp_mymod2).\n",
  261
+         "-export([myfunc2/0]).\n",
  262
+         "-include_lib(\"eunit/include/eunit.hrl\").\n",
  263
+         "myfunc2() -> ok.\n",
  264
+         "myprivate2_test() -> ?assert(true).\n",
  265
+         "common_name_test() -> ?assert(true).\n"]).
  266
+
  267
+-define(myapp_mymod2_tests,
  268
+        ["-module(myapp_mymod2_tests).\n",
  269
+         "-compile([export_all]).\n",
  270
+         "-include_lib(\"eunit/include/eunit.hrl\").\n",
  271
+         "myfunc2_test() -> ?assertMatch(ok, myapp_mymod2:myfunc2()).\n",
  272
+         "common_name_test() -> ?assert(true).\n"]).
  273
+
161 274
 -define(mysuite,
162 275
         ["-module(mysuite).\n",
163 276
          "-export([all_test_/0]).\n",
@@ -186,6 +299,11 @@ setup_basic_project() ->
186 299
     ok = file:write_file("test/myapp_mymod_tests.erl", ?myapp_mymod_tests),
187 300
     ok = file:write_file("src/myapp_mymod.erl", ?myapp_mymod).
188 301
 
  302
+setup_project_with_multiple_modules() ->
  303
+    setup_basic_project(),
  304
+    ok = file:write_file("test/myapp_mymod2_tests.erl", ?myapp_mymod2_tests),
  305
+    ok = file:write_file("src/myapp_mymod2.erl", ?myapp_mymod2).
  306
+
189 307
 setup_cover_project() ->
190 308
     setup_basic_project(),
191 309
     ok = file:write_file("rebar.config", "{cover_enabled, true}.\n").
Commit_comment_tip

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.