Packages
Execute Python applications from Erlang using dirty NIFs
Retired package: Release invalid - Missing priv/erlang_loop.py
Current section
Files
Jump to
Current section
Files
test/py_context_SUITE.erl
%%% @doc Common Test suite for py context affinity.
-module(py_context_SUITE).
-include_lib("common_test/include/ct.hrl").
-export([
all/0,
init_per_suite/1,
end_per_suite/1,
init_per_testcase/2,
end_per_testcase/2
]).
-export([
bind_unbind_test/1,
bind_persists_state_test/1,
explicit_context_test/1,
with_context_implicit_test/1,
with_context_explicit_test/1,
automatic_cleanup_test/1,
multiple_contexts_isolated_test/1,
double_bind_idempotent_test/1,
unbind_without_bind_test/1,
context_call_test/1
]).
all() -> [
bind_unbind_test,
bind_persists_state_test,
explicit_context_test,
with_context_implicit_test,
with_context_explicit_test,
automatic_cleanup_test,
multiple_contexts_isolated_test,
double_bind_idempotent_test,
unbind_without_bind_test,
context_call_test
].
init_per_suite(Config) ->
{ok, _} = application:ensure_all_started(erlang_python),
Config.
end_per_suite(_Config) ->
ok = application:stop(erlang_python),
ok.
init_per_testcase(_TestCase, Config) ->
%% Ensure the test process is not bound at the start of each test
py:unbind(),
Config.
end_per_testcase(_TestCase, _Config) ->
%% Clean up any bindings after each test
py:unbind(),
ok.
%%% ============================================================================
%%% Test Cases
%%% ============================================================================
%% @doc Test basic bind/unbind functionality
bind_unbind_test(_Config) ->
false = py:is_bound(),
ok = py:bind(),
true = py:is_bound(),
ok = py:unbind(),
false = py:is_bound().
%% @doc Test that state persists across calls when bound
bind_persists_state_test(_Config) ->
ok = py:bind(),
ok = py:exec(<<"test_var = 42">>),
{ok, 42} = py:eval(<<"test_var">>),
ok = py:exec(<<"test_var += 1">>),
{ok, 43} = py:eval(<<"test_var">>),
ok = py:unbind().
%% @doc Test explicit context creation and usage
explicit_context_test(_Config) ->
{ok, Ctx} = py:bind(new),
ok = py:ctx_exec(Ctx, <<"ctx_var = 'hello'">>),
{ok, <<"hello">>} = py:ctx_eval(Ctx, <<"ctx_var">>),
ok = py:unbind(Ctx).
%% @doc Test with_context with implicit (arity-0) function
with_context_implicit_test(_Config) ->
Result = py:with_context(fun() ->
ok = py:exec(<<"x = 10">>),
ok = py:exec(<<"x *= 2">>),
py:eval(<<"x">>)
end),
{ok, 20} = Result,
false = py:is_bound().
%% @doc Test with_context with explicit (arity-1) function
with_context_explicit_test(_Config) ->
Result = py:with_context(fun(Ctx) ->
ok = py:ctx_exec(Ctx, <<"y = 5">>),
py:ctx_eval(Ctx, <<"y * 3">>)
end),
{ok, 15} = Result.
%% @doc Test automatic cleanup when bound process dies
automatic_cleanup_test(_Config) ->
Parent = self(),
Stats1 = py_pool:get_stats(),
Avail1 = maps:get(available_workers, Stats1),
Pid = spawn(fun() ->
ok = py:bind(),
Parent ! bound,
receive stop -> ok end
end),
receive bound -> ok end,
%% Worker should be checked out
Stats2 = py_pool:get_stats(),
Avail2 = maps:get(available_workers, Stats2),
true = Avail2 < Avail1,
%% Kill the process
exit(Pid, kill),
timer:sleep(50),
%% Worker should be returned to pool
Stats3 = py_pool:get_stats(),
Avail3 = maps:get(available_workers, Stats3),
Avail1 = Avail3.
%% @doc Test that multiple explicit contexts are isolated
multiple_contexts_isolated_test(_Config) ->
{ok, Ctx1} = py:bind(new),
{ok, Ctx2} = py:bind(new),
ok = py:ctx_exec(Ctx1, <<"shared_name = 1">>),
ok = py:ctx_exec(Ctx2, <<"shared_name = 2">>),
%% Each context should have its own value
{ok, 1} = py:ctx_eval(Ctx1, <<"shared_name">>),
{ok, 2} = py:ctx_eval(Ctx2, <<"shared_name">>),
ok = py:unbind(Ctx1),
ok = py:unbind(Ctx2).
%% @doc Test that double bind is idempotent
double_bind_idempotent_test(_Config) ->
ok = py:bind(),
ok = py:bind(), % Should be idempotent
true = py:is_bound(),
ok = py:unbind(),
false = py:is_bound().
%% @doc Test that unbind without bind is safe (idempotent)
unbind_without_bind_test(_Config) ->
false = py:is_bound(),
ok = py:unbind(), % Should be safe even without prior bind
false = py:is_bound().
%% @doc Test py:ctx_call with explicit context
context_call_test(_Config) ->
{ok, Ctx} = py:bind(new),
%% Import a module in this context
ok = py:ctx_exec(Ctx, <<"import json">>),
%% Use the imported module via ctx_call
{ok, <<"{\"a\": 1}">>} = py:ctx_call(Ctx, json, dumps, [#{a => 1}]),
ok = py:unbind(Ctx).