Why does an ets table survive ct:init_per_testcase but not init_per_suite?
Asked Answered
Z

2

6

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?
Zampino answered 26/3, 2013 at 7:13 Comment(2)
Fixed the working example (was just a copy of the nonworking one)Foison
Added a working example to my code, now runs ok for meFoison
F
3

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

Foison answered 26/3, 2013 at 10:21 Comment(1)
Indeed they run on the same process.To quote the erlang documentation " As init_per_testcase and end_per_testcase execute on the same Erlang process as the test case, printouts from these configuration functions are included in the test case log file. " erlang.org/doc/apps/common_test/write_test_chapter.html#id63060Epifaniaepifano
P
2

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.

Pompidou answered 26/3, 2013 at 8:26 Comment(1)
Well it is feasible ... see my answerFoison

© 2022 - 2024 — McMap. All rights reserved.