Question

I have a common test suite that attempts to create an ets table for use in all suites and all test cases. It looks like so:

-module(an_example_SUITE).
-include_lib("common_test/include/ct.hrl").

-compile(export_all).

all() -> [ets_tests].

init_per_suite(Config) ->
    TabId = ets:new(conns, [set]),
    ets:insert(TabId, {foo, 2131}),
    [{table,TabId} | Config].

end_per_suite(Config) ->
    ets:delete(?config(table, Config)).

ets_tests(Config) ->
    TabId = ?config(table, Config),
    [{foo, 2131}] = ets:lookup(TabId, foo).

The ets_tests function failed with a badarg. Creating/destroying the ets table per testcase, which looks like so:

-module(an_example_SUITE).
-include_lib("common_test/include/ct.hrl").

-compile(export_all).

all() -> [ets_tests].

init_per_testcase(Config) ->
    TabId = ets:new(conns, [set]),
    ets:insert(TabId, {foo, 2131}),
    [{table,TabId} | Config].

end_per_testcase(Config) ->
    ets:delete(?config(table, Config)).

ets_tests(Config) ->
    TabId = ?config(table, Config),
    [{foo, 2131}] = ets:lookup(TabId, foo).

Running this, I find that it functions beautifully.

I'm confused by this behavior and unable to determine why this would happen, form the docs. Questions:

  • Why does this happen?
  • How can I have an ets table to share between a per suite and per testcase?
Was it helpful?

Solution

As was already mentioned in the answer by Pascal and as discussed in the User Guide only init_per_testcase and end_per_testcase run in the same process as the testcase. Since ETS tables are bound to a owner process your only way to have a ETS table persist during a whole suite or group is to give it away or define a heir process.

You can easily spawn a process in your init_per_suite or init_per_group functions, set it as heir for the ETS table and pass its pid along in the config.

To clean up all you need is to kill this process in your end_per_suite or end_per_group functions.

-module(an_example_SUITE).
-include_lib("common_test/include/ct.hrl").

-compile(export_all).

all() -> [ets_tests].

ets_owner() ->
    receive
        stop -> exit(normal);
        Any -> ets_owner()
    end.

init_per_suite(Config) ->
    Pid = spawn(fun ets_owner/0),
    TabId = ets:new(conns, [set, protected, {heir, Pid, []}]),
    ets:insert(TabId, {foo, 2131}),
    [{table,TabId},{table_owner, Pid} | Config].

end_per_suite(Config) ->
    ?config(table_owner, Config) ! stop.

ets_tests(Config) ->
    TabId = ?config(table, Config),
    [{foo, 2131}] = ets:lookup(TabId, foo).

You also need to make sure you can still access your table from the testcase process, by making it either protectedor public

OTHER TIPS

An ets table is attached to a process and destroyed as soon as the process ends, unless you use the the give_away function (which is not feasible I fear in this case)

As state in the common tets doc, each test case and the init_per_suite and end_per_suite are run in separate processes, so the ets table is destroyed as soon as you leave the init_per_suite function.

fron common_test doc

init_per_suite and end_per_suite will execute on dedicated Erlang processes, just like the test cases do. The result of these functions is however not included in the test run statistics of successful, failed and skipped cases.

from ets doc

The default owner is the process that created the table. Table ownership can be transferred at process termination by using the heir option or explicitly by calling give_away/3.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top