Permalink
Switch branches/tags
Nothing to show
Find file
Fetching contributors…
Cannot retrieve contributors at this time
282 lines (232 sloc) 8.95 KB
%% erlc -o ebin t/*.erl -W0
%% erl -pa ebin -sasl sasl_error_logger false -name emysql_eqc@`hostname`
%% eqc:quickcheck(emysql_eqc:prop_emysql_eqc()).
%% eqc_gen:sample(eqc_statem:commands(rpcore_user_eqc)).
-module(emysql_eqc).
-behaviour(eqc_statem).
-export([command/1, initial_state/0, next_state/3, precondition/2, postcondition/3]).
-compile(export_all).
-include_lib("eqc/include/eqc.hrl").
-include_lib("eqc/include/eqc_statem.hrl").
-include_lib("emysql/include/emysql.hrl").
-record(state, {pool_id, num_conns, tables, insert_statements}).
-record(column_def, {'Field', 'Type', 'Null', 'Key', 'Default', 'Extra'}).
-define(POOLID, test1).
-define(MAX_CONNECTIONS, 40).
initial_state() -> #state{pool_id=?POOLID, num_conns=1, tables=[], insert_statements=[]}.
command(State) ->
oneof(
[{call, ?MODULE, increment_pool_size, [State#state.pool_id, 1]}] ++
[{call, ?MODULE, decrement_pool_size, [State#state.pool_id, 1]}] ++
[{call, ?MODULE, show_tables, [State#state.pool_id]}] ++
[{call, ?MODULE, create_table, [State#state.pool_id, table_name(), ?SUCHTHAT(Y, list(column()), length(Y) > 0)]}] ++
[{call, ?MODULE, drop_table, [State#state.pool_id, oneof(State#state.tables)]} || length(State#state.tables) > 0] ++
[{call, ?MODULE, prepare_insert, [State#state.pool_id, oneof(State#state.tables)]} || length(State#state.tables) > 0] ++
[{call, ?MODULE, call_insert, [State#state.pool_id, ?LET({TN,CD}, oneof(State#state.tables), {TN, column_data(CD)})]} || length(State#state.tables) > 0] ++
[{call, ?MODULE, timeout, [State#state.pool_id]}] ++
[]
).
%% #############################################################################
%% #############################################################################
%% #############################################################################
precondition(S, {call, ?MODULE, increment_pool_size, [_PoolId, Num]}) ->
S#state.num_conns + Num < ?MAX_CONNECTIONS;
precondition(S, {call, ?MODULE, decrement_pool_size, [_PoolId, Num]}) ->
S#state.num_conns - Num >= 1;
precondition(_S, {call, ?MODULE, create_table, [_PoolId, _Name, Columns]}) ->
length(lists:usort([string:to_lower(K) || {K,_} <- Columns])) == length(Columns);
precondition(S, {call, ?MODULE, drop_table, [_PoolId, {Name, _}]}) ->
proplists:is_defined(Name, S#state.tables);
precondition(S, {call, ?MODULE, call_insert, [_PoolId, {Name, _}]}) ->
proplists:is_defined(Name, S#state.tables) andalso lists:member(Name, S#state.insert_statements);
precondition(_, _) -> true.
%% #############################################################################
%% #############################################################################
%% #############################################################################
next_state(S, _V, {call, ?MODULE, increment_pool_size, [_PoolId, Num]}) ->
S#state{num_conns = (S#state.num_conns + Num)};
next_state(S, _V, {call, ?MODULE, decrement_pool_size, [_PoolId, Num]}) ->
S#state{num_conns = (S#state.num_conns - Num)};
next_state(S, _V, {call, ?MODULE, create_table, [_PoolId, Name, Columns]}) ->
case proplists:is_defined(Name, S#state.tables) of
false ->
S#state{tables = [{Name, Columns} | S#state.tables]};
true ->
S
end;
next_state(S, _V, {call, ?MODULE, drop_table, [_PoolId, {Name, _}]}) ->
S#state{tables = proplists:delete(Name, S#state.tables)};
next_state(S, _V, {call, ?MODULE, prepare_insert, [_PoolId, {TableName, _}]}) ->
S#state{insert_statements = [TableName|S#state.insert_statements]};
next_state(S, _, _) -> S.
%% #############################################################################
%% #############################################################################
%% #############################################################################
postcondition(_S, {call, ?MODULE, increment_pool_size, [_PoolId, _Num]}, Result) ->
Result == ok;
postcondition(_S, {call, ?MODULE, decrement_pool_size, [_PoolId, _Num]}, Result) ->
Result == ok;
postcondition(_S, {call, ?MODULE, show_tables, [_PoolId]}, Result) ->
is_record(Result, result_packet);
postcondition(_S, {call, ?MODULE, create_table, [_PoolId, _Name, _Columns]}, Result) ->
case Result of
Err when is_record(Err, error_packet) ->
case Err:code() of
1050 -> %% table already exists
true;
_ ->
false
end;
_ ->
true == is_record(Result, ok_packet)
end;
postcondition(_S, {call, ?MODULE, drop_table, [_PoolId, {_Name, _}]}, Result) ->
case Result of
OK when is_record(OK, ok_packet) ->
true;
Err when is_record(Err, error_packet) ->
case Err:code() of
1051 -> %% unknown table
true;
_ ->
false
end
end;
postcondition(_S, {call, ?MODULE, prepare_insert, [_PoolId, {_Name, _}]}, Result) ->
case Result of
ok ->
true;
Err when is_record(Err, error_packet) ->
case Err:code() of
1146 ->
true;
_ ->
false
end
end;
postcondition(_S, {call, ?MODULE, call_insert, [_PoolId, {_Name, _}]}, Result) ->
is_record(Result, ok_packet) andalso 1 == Result:affected_rows();
postcondition(_S, {call, ?MODULE, timeout, [_PoolId]}, Result) ->
Result == {'EXIT', mysql_timeout};
postcondition(_, _, _) -> true.
%% #############################################################################
%% #############################################################################
%% #############################################################################
prop_emysql_eqc() ->
?FORALL(Cmds, commands(?MODULE),
begin
init(),
{H,S,Res} = run_commands(?MODULE,Cmds),
cleanup(),
?WHENFAIL(io:format("FAIL ~p\n~p\n~p\n",[H, S, Res]), Res==ok)
end).
increment_pool_size(PoolId, Num) ->
emysql:increment_pool_size(PoolId, Num).
decrement_pool_size(PoolId, Num) ->
emysql:decrement_pool_size(PoolId, Num).
column() ->
{column_name(), column_type()}.
column_name() ->
?SUCHTHAT(X, list(alpha()), length(X) > 0).
%?SUCHTHAT(X, [alpha()] ++ list(safe_char()), length(X) > 0 andalso hd(lists:reverse(X)) =/= 32).
column_data(Columns) ->
[data_for_type(Type) || {_Name, Type} <- Columns].
data_for_type("DECIMAL") -> real();
data_for_type("TINYINT") -> int();
data_for_type("LONG") -> int();
data_for_type("FLOAT") -> real();
data_for_type("DOUBLE") -> real();
data_for_type("TIMESTAMP") -> "null";
data_for_type("INT") -> int();
data_for_type("DATE") -> "null";
data_for_type("TIME") -> "null";
data_for_type("DATETIME") -> "null";
data_for_type("YEAR") -> choose(0, 3000);
data_for_type("VARCHAR(255)") -> list(char());
data_for_type("BIT") -> oneof([1,0,true,false]);
data_for_type("BLOB") -> list(char()).
table_name() ->
?SUCHTHAT(X, list(alpha()), length(X) > 0).
%?SUCHTHAT(X, [alpha()] ++ list(safe_char()), length(X) > 0 andalso hd(lists:reverse(X)) =/= 32).
alpha() ->
oneof([
%%choose(65,90),
choose(97,122)
]).
safe_char() ->
oneof([
choose(32,95),
choose(97,126)
]).
column_type() ->
elements([
"DECIMAL",
"TINYINT",
"LONG",
"FLOAT",
"DOUBLE",
"TIMESTAMP",
"INT",
"DATE",
"TIME",
"DATETIME",
"YEAR",
"VARCHAR(255)",
"BIT",
"BLOB"
]).
show_tables(PoolId) ->
(catch emysql:execute(PoolId, "SHOW TABLES")).
create_table(PoolId, TableName, Columns) ->
ColumnDefs = ["`" ++ CName ++ "` " ++ CType || {CName, CType} <- Columns],
Query = "CREATE TABLE IF NOT EXISTS `" ++ TableName ++ "` ( " ++ string:join(ColumnDefs, ", ") ++ ")",
(catch emysql:execute(PoolId, Query)).
drop_table(PoolId, {TableName, _}) ->
(catch emysql:execute(PoolId, "DROP TABLE `" ++ TableName ++ "`")).
prepare_insert(PoolId, {TableName, _}) ->
case (catch emysql:execute(PoolId, "DESC `" ++ TableName ++ "`")) of
Result when is_record(Result, result_packet) ->
ColDefs = Result:as_record(column_def, record_info(fields, column_def)),
Stmt = "INSERT INTO `" ++ TableName ++ "` ( " ++ string:join(["`" ++ binary_to_list(Col) ++ "`" || {_,Col,_,_,_,_,_} <- ColDefs], ", ") ++ " ) VALUES ( " ++ string:join(["?" || _ <- ColDefs], ", ") ++ " )",
(catch emysql:prepare(list_to_atom(TableName), Stmt));
Err ->
Err
end.
call_insert(PoolId, {TableName, Args}) ->
(catch emysql:execute(PoolId, list_to_atom(TableName), Args)).
timeout(PoolId) ->
(catch emysql:execute(PoolId, "SELECT SLEEP(8)", 10)).
init() ->
error_logger:tty(false),
ok = load_app(crypto),
ok = application:start(crypto),
ok = load_app(emysql),
ok = application:set_env(emysql, pools, [
{?POOLID, [
{size, 1},
{user, "test"},
{password, "test"},
{host, "localhost"},
{port, 3306},
{database, "testdatabase"},
{encoding, 'utf8'}
]}
]),
ok = application:start(emysql),
[emysql:execute(?POOLID, "DROP TABLE " ++ Table) || [Table] <- (emysql:execute(?POOLID, "SHOW TABLES")):rows()],
ok.
cleanup() ->
ok = application:stop(crypto),
ok = application:stop(emysql),
ok = application:unload(crypto),
ok = application:unload(emysql),
ok.
load_app(Name) ->
case application:load(Name) of
{error, {already_loaded, Name}} ->
application:stop(Name),
application:unload(Name),
application:load(Name);
ok ->
ok
end.