testingerlangcommon-test

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


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:


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