diff options
84 files changed, 24439 insertions, 846 deletions
diff --git a/erts/doc/src/erlang.xml b/erts/doc/src/erlang.xml index 6f30c7fb21..77a628e82b 100644 --- a/erts/doc/src/erlang.xml +++ b/erts/doc/src/erlang.xml @@ -2209,7 +2209,7 @@ os_prompt%</pre> </desc> </func> <func> - <name>erlang:max(Term1, Term2) -> Maximum</name> + <name>max(Term1, Term2) -> Maximum</name> <fsummary>Return the largest of two term</fsummary> <type> <v>Term1 = Term2 = Maximum = term()</v> @@ -2458,7 +2458,7 @@ os_prompt%</pre> </desc> </func> <func> - <name>erlang:min(Term1, Term2) -> Minimum</name> + <name>min(Term1, Term2) -> Minimum</name> <fsummary>Return the smallest of two term</fsummary> <type> <v>Term1 = Term2 = Minimum = term()</v> @@ -2644,6 +2644,37 @@ os_prompt%</pre> </desc> </func> <func> + <name>erlang:nif_error(Reason)</name> + <fsummary>Stop execution with a given reason</fsummary> + <type> + <v>Reason = term()</v> + </type> + <desc> + <p>Works exactly like + <seealso marker="#error/1">erlang:error/1</seealso>, + but Dialyzer thinks that this BIF will return an arbitrary term. + When used in a stub function for a NIF to generate an + exception when the NIF library is not loaded, Dialyzer + will not generate false warnings.</p> + </desc> + </func> + <func> + <name>erlang:nif_error(Reason, Args)</name> + <fsummary>Stop execution with a given reason</fsummary> + <type> + <v>Reason = term()</v> + <v>Args = [term()]</v> + </type> + <desc> + <p>Works exactly like + <seealso marker="#error/2">erlang:error/2</seealso>, + but Dialyzer thinks that this BIF will return an arbitrary term. + When used in a stub function for a NIF to generate an + exception when the NIF library is not loaded, Dialyzer + will not generate false warnings.</p> + </desc> + </func> + <func> <name>node() -> Node</name> <fsummary>Name of the local node</fsummary> <type> @@ -3200,7 +3231,7 @@ os_prompt%</pre> </desc> </func> <func> - <name>erlang:port_command(Port, Data, OptionList) -> true|false</name> + <name>port_command(Port, Data, OptionList) -> true|false</name> <fsummary>Send data to a port</fsummary> <type> <v>Port = port() | atom()</v> @@ -3236,10 +3267,6 @@ os_prompt%</pre> <note> <p>More options may be added in the future.</p> </note> - <note> - <p><c>erlang:port_command/3</c> is currently not auto imported, but - it is planned to be auto imported in OTP R14.</p> - </note> <p>Failures:</p> <taglist> <tag><c>badarg</c></tag> diff --git a/erts/emulator/beam/bif.c b/erts/emulator/beam/bif.c index 10cc2e9003..506bf383ca 100644 --- a/erts/emulator/beam/bif.c +++ b/erts/emulator/beam/bif.c @@ -1132,6 +1132,34 @@ BIF_RETTYPE error_2(Process* p, Eterm value, Eterm args) } /**********************************************************************/ +/* + * This is like exactly like error/1. The only difference is + * that Dialyzer thinks that it it will return an arbitrary term. + * It is useful in stub functions for NIFs. + */ + +BIF_RETTYPE nif_error_1(Process* p, Eterm term) +{ + p->fvalue = term; + BIF_ERROR(p, EXC_ERROR); +} + +/**********************************************************************/ +/* + * This is like exactly like error/2. The only difference is + * that Dialyzer thinks that it it will return an arbitrary term. + * It is useful in stub functions for NIFs. + */ + +BIF_RETTYPE nif_error_2(Process* p, Eterm value, Eterm args) +{ + Eterm* hp = HAlloc(p, 3); + + p->fvalue = TUPLE2(hp, value, args); + BIF_ERROR(p, EXC_ERROR_2); +} + +/**********************************************************************/ /* this is like throw/1 except that we set freason to EXC_EXIT */ BIF_RETTYPE exit_1(BIF_ALIST_1) diff --git a/erts/emulator/beam/bif.tab b/erts/emulator/beam/bif.tab index 38e2dd77d3..0674aae77f 100644 --- a/erts/emulator/beam/bif.tab +++ b/erts/emulator/beam/bif.tab @@ -791,6 +791,9 @@ bif binary:encode_unsigned/2 bif binary:decode_unsigned/1 bif binary:decode_unsigned/2 +bif erlang:nif_error/1 +bif erlang:nif_error/2 + # # Obsolete # diff --git a/erts/emulator/test/bif_SUITE.erl b/erts/emulator/test/bif_SUITE.erl index 15c09d9158..b4ef0e6d5a 100644 --- a/erts/emulator/test/bif_SUITE.erl +++ b/erts/emulator/test/bif_SUITE.erl @@ -22,12 +22,13 @@ -include("test_server.hrl"). -export([all/1,init_per_testcase/2,fin_per_testcase/2, + types/1, t_list_to_existing_atom/1,os_env/1,otp_7526/1, binary_to_atom/1,binary_to_existing_atom/1, atom_to_binary/1,min_max/1]). all(suite) -> - [t_list_to_existing_atom,os_env,otp_7526, + [types,t_list_to_existing_atom,os_env,otp_7526, atom_to_binary,binary_to_atom,binary_to_existing_atom, min_max]. @@ -39,6 +40,73 @@ fin_per_testcase(_Func, Config) -> Dog=?config(watchdog, Config), ?t:timetrap_cancel(Dog). +types(Config) when is_list(Config) -> + c:l(erl_bif_types), + case erlang:function_exported(erl_bif_types, module_info, 0) of + false -> + %% Fail cleanly. + ?line ?t:fail("erl_bif_types not compiled"); + true -> + types_1() + end. + +types_1() -> + ?line List0 = erlang:system_info(snifs), + + %% Ignore missing type information for hipe BIFs. + ?line List = [MFA || {M,_,_}=MFA <- List0, M =/= hipe_bifs], + + case [MFA || MFA <- List, not known_types(MFA)] of + [] -> + types_2(List); + BadTypes -> + io:put_chars("No type information:\n"), + io:format("~p\n", [lists:sort(BadTypes)]), + ?line ?t:fail({length(BadTypes),bifs_without_types}) + end. + +types_2(List) -> + BadArity = [MFA || {M,F,A}=MFA <- List, + begin + Types = erl_bif_types:arg_types(M, F, A), + length(Types) =/= A + end], + case BadArity of + [] -> + types_3(List); + [_|_] -> + io:put_chars("Bifs with bad arity\n"), + io:format("~p\n", [BadArity]), + ?line ?t:fail({length(BadArity),bad_arity}) + end. + +types_3(List) -> + BadSmokeTest = [MFA || {M,F,A}=MFA <- List, + begin + try erl_bif_types:type(M, F, A) of + Type -> + %% Test that type is returned. + not erl_types:is_erl_type(Type) + catch + Class:Error -> + io:format("~p: ~p ~p\n", + [MFA,Class,Error]), + true + end + end], + case BadSmokeTest of + [] -> + ok; + [_|_] -> + io:put_chars("Bifs with failing calls to erlang_bif_types:type/3 " + "(or with bogus return values):\n"), + io:format("~p\n", [BadSmokeTest]), + ?line ?t:fail({length(BadSmokeTest),bad_smoke_test}) + end. + +known_types({M,F,A}) -> + erl_bif_types:is_known(M, F, A). + t_list_to_existing_atom(Config) when is_list(Config) -> ?line all = list_to_existing_atom("all"), ?line ?MODULE = list_to_existing_atom(?MODULE_STRING), diff --git a/erts/etc/win32/cygwin_tools/vc/rc.sh b/erts/etc/win32/cygwin_tools/vc/rc.sh index 5f5e84f89e..054c672e64 100755 --- a/erts/etc/win32/cygwin_tools/vc/rc.sh +++ b/erts/etc/win32/cygwin_tools/vc/rc.sh @@ -2,20 +2,20 @@ # set -x # # %CopyrightBegin% -# -# Copyright Ericsson AB 2002-2009. All Rights Reserved. -# +# +# Copyright Ericsson AB 2002-2010. All Rights Reserved. +# # The contents of this file are subject to the Erlang Public License, # Version 1.1, (the "License"); you may not use this file except in # compliance with the License. You should have received a copy of the # Erlang Public License along with this software. If not, it can be # retrieved online at http://www.erlang.org/. -# +# # Software distributed under the License is distributed on an "AS IS" # basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See # the License for the specific language governing rights and limitations # under the License. -# +# # %CopyrightEnd% # # Save the command line for debug outputs diff --git a/lib/crypto/c_src/crypto.c b/lib/crypto/c_src/crypto.c index bb639054a6..68079f06c7 100644 --- a/lib/crypto/c_src/crypto.c +++ b/lib/crypto/c_src/crypto.c @@ -198,7 +198,7 @@ static ErlNifFunc nif_funcs[] = { {"rand_bytes", 3, rand_bytes_3}, {"rand_uniform_nif", 2, rand_uniform_nif}, {"mod_exp_nif", 3, mod_exp_nif}, - {"dss_verify", 3, dss_verify}, + {"dss_verify", 4, dss_verify}, {"rsa_verify", 4, rsa_verify}, {"aes_cbc_crypt", 4, aes_cbc_crypt}, {"exor", 2, exor}, @@ -207,7 +207,7 @@ static ErlNifFunc nif_funcs[] = { {"rc4_encrypt_with_state", 2, rc4_encrypt_with_state}, {"rc2_40_cbc_crypt", 4, rc2_40_cbc_crypt}, {"rsa_sign_nif", 3, rsa_sign_nif}, - {"dss_sign_nif", 2, dss_sign_nif}, + {"dss_sign_nif", 3, dss_sign_nif}, {"rsa_public_crypt", 4, rsa_public_crypt}, {"rsa_private_crypt", 4, rsa_private_crypt}, {"dh_generate_parameters_nif", 2, dh_generate_parameters_nif}, @@ -255,6 +255,7 @@ static ERL_NIF_TERM atom_unable_to_check_generator; static ERL_NIF_TERM atom_not_suitable_generator; static ERL_NIF_TERM atom_check_failed; static ERL_NIF_TERM atom_unknown; +static ERL_NIF_TERM atom_none; static int is_ok_load_info(ErlNifEnv* env, ERL_NIF_TERM load_info) @@ -322,6 +323,7 @@ static int load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) atom_not_suitable_generator = enif_make_atom(env,"not_suitable_generator"); atom_check_failed = enif_make_atom(env,"check_failed"); atom_unknown = enif_make_atom(env,"unknown"); + atom_none = enif_make_atom(env,"none"); *priv_data = NULL; library_refc++; @@ -766,7 +768,7 @@ static int inspect_mpint(ErlNifEnv* env, ERL_NIF_TERM term, ErlNifBinary* bin) } static ERL_NIF_TERM dss_verify(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{/* (Data,Signature,Key=[P, Q, G, Y]) */ +{/* (DigestType,Data,Signature,Key=[P, Q, G, Y]) */ ErlNifBinary data_bin, sign_bin; BIGNUM *dsa_p, *dsa_q, *dsa_g, *dsa_y; unsigned char hmacbuf[SHA_DIGEST_LENGTH]; @@ -774,9 +776,8 @@ static ERL_NIF_TERM dss_verify(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv DSA *dsa; int i; - if (!inspect_mpint(env,argv[0],&data_bin) - || !inspect_mpint(env,argv[1],&sign_bin) - || !enif_get_list_cell(env, argv[2], &head, &tail) + if (!inspect_mpint(env, argv[2], &sign_bin) + || !enif_get_list_cell(env, argv[3], &head, &tail) || !get_bn_from_mpint(env, head, &dsa_p) || !enif_get_list_cell(env, tail, &head, &tail) || !get_bn_from_mpint(env, head, &dsa_q) @@ -785,10 +786,18 @@ static ERL_NIF_TERM dss_verify(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv || !enif_get_list_cell(env, tail, &head, &tail) || !get_bn_from_mpint(env, head, &dsa_y) || !enif_is_empty_list(env,tail)) { - return enif_make_badarg(env); } - SHA1(data_bin.data+4, data_bin.size-4, hmacbuf); + if (argv[0] == atom_sha && inspect_mpint(env, argv[1], &data_bin)) { + SHA1(data_bin.data+4, data_bin.size-4, hmacbuf); + } + else if (argv[0] == atom_none && enif_inspect_binary(env, argv[1], &data_bin) + && data_bin.size == SHA_DIGEST_LENGTH) { + memcpy(hmacbuf, data_bin.data, SHA_DIGEST_LENGTH); + } + else { + return enif_make_badarg(env); + } dsa = DSA_new(); dsa->p = dsa_p; @@ -1023,7 +1032,7 @@ static ERL_NIF_TERM rsa_sign_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM ar } static ERL_NIF_TERM dss_sign_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{/* (Data,Key=[P,Q,G,PrivKey]) */ +{/* (DigesType, Data, Key=[P,Q,G,PrivKey]) */ ErlNifBinary data_bin, ret_bin; ERL_NIF_TERM head, tail; unsigned char hmacbuf[SHA_DIGEST_LENGTH]; @@ -1032,8 +1041,7 @@ static ERL_NIF_TERM dss_sign_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM ar int i; dsa->pub_key = NULL; - if (!inspect_mpint(env, argv[0], &data_bin) - || !enif_get_list_cell(env, argv[1], &head, &tail) + if (!enif_get_list_cell(env, argv[2], &head, &tail) || !get_bn_from_mpint(env, head, &dsa->p) || !enif_get_list_cell(env, tail, &head, &tail) || !get_bn_from_mpint(env, head, &dsa->q) @@ -1042,13 +1050,21 @@ static ERL_NIF_TERM dss_sign_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM ar || !enif_get_list_cell(env, tail, &head, &tail) || !get_bn_from_mpint(env, head, &dsa->priv_key) || !enif_is_empty_list(env,tail)) { - + goto badarg; + } + if (argv[0] == atom_sha && inspect_mpint(env, argv[1], &data_bin)) { + SHA1(data_bin.data+4, data_bin.size-4, hmacbuf); + } + else if (argv[0] == atom_none && enif_inspect_binary(env,argv[1],&data_bin) + && data_bin.size == SHA_DIGEST_LENGTH) { + memcpy(hmacbuf, data_bin.data, SHA_DIGEST_LENGTH); + } + else { + badarg: DSA_free(dsa); return enif_make_badarg(env); } - SHA1(data_bin.data+4, data_bin.size-4, hmacbuf); - enif_alloc_binary(DSA_size(dsa), &ret_bin); i = DSA_sign(NID_sha1, hmacbuf, SHA_DIGEST_LENGTH, ret_bin.data, &dsa_s_len, dsa); diff --git a/lib/crypto/doc/src/crypto.xml b/lib/crypto/doc/src/crypto.xml index 256eab3e3c..e1431cfd81 100644 --- a/lib/crypto/doc/src/crypto.xml +++ b/lib/crypto/doc/src/crypto.xml @@ -755,39 +755,44 @@ Mpint() = <![CDATA[<<ByteLen:32/integer-big, Bytes:ByteLen/binary>>]]> <func> <name>dss_sign(Data, Key) -> Signature</name> + <name>dss_sign(DigestType, Data, Key) -> Signature</name> <fsummary>Sign the data using dsa with given private key.</fsummary> <type> - <v>Digest = Mpint</v> + <v>DigestType = sha | none (default is sha)</v> + <v>Data = Mpint | ShaDigest</v> <v>Key = [P, Q, G, X]</v> <v>P, Q, G, X = Mpint</v> <d> Where <c>P</c>, <c>Q</c> and <c>G</c> are the dss parameters and <c>X</c> is the private key.</d> - <v>Mpint = binary()</v> + <v>ShaDigest = binary() with length 20 bytes</v> <v>Signature = binary()</v> </type> <desc> - <p>Calculates the sha digest of the <c>Data</c> - and creates a DSS signature with the private key <c>Key</c> - of the digest.</p> + <p>Creates a DSS signature with the private key <c>Key</c> of a digest. + If <c>DigestType</c> is 'sha', the digest is calculated as SHA1 of <c>Data</c>. + If <c>DigestType</c> is 'none', <c>Data</c> is the precalculated SHA1 digest.</p> </desc> </func> <func> <name>dss_verify(Data, Signature, Key) -> Verified</name> + <name>dss_verify(DigestType, Data, Signature, Key) -> Verified</name> <fsummary>Verify the data and signature using dsa with given public key.</fsummary> <type> <v>Verified = boolean()</v> - <v>Digest, Signature = Mpint</v> + <v>DigestType = sha | none</v> + <v>Data = Mpint | ShaDigest</v> + <v>Signature = Mpint</v> <v>Key = [P, Q, G, Y]</v> <v>P, Q, G, Y = Mpint</v> <d> Where <c>P</c>, <c>Q</c> and <c>G</c> are the dss parameters and <c>Y</c> is the public key.</d> - <v>Mpint = binary()</v> + <v>ShaDigest = binary() with length 20 bytes</v> </type> <desc> - <p>Calculates the sha digest of the <c>Data</c> and verifies that the - digest matches the DSS signature using the public key <c>Key</c>. - </p> + <p>Verifies that a digest matches the DSS signature using the public key <c>Key</c>. + If <c>DigestType</c> is 'sha', the digest is calculated as SHA1 of <c>Data</c>. + If <c>DigestType</c> is 'none', <c>Data</c> is the precalculated SHA1 digest.</p> </desc> </func> diff --git a/lib/crypto/src/crypto.app.src b/lib/crypto/src/crypto.app.src index a24760a781..5548b6a1b5 100644 --- a/lib/crypto/src/crypto.app.src +++ b/lib/crypto/src/crypto.app.src @@ -1,23 +1,23 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 1999-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 1999-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% {application, crypto, - [{description, "CRYPTO version 1"}, + [{description, "CRYPTO version 2"}, {vsn, "%VSN%"}, {modules, [crypto, crypto_app, diff --git a/lib/crypto/src/crypto.erl b/lib/crypto/src/crypto.erl index 5b1ce96caf..39512d27e1 100644 --- a/lib/crypto/src/crypto.erl +++ b/lib/crypto/src/crypto.erl @@ -40,8 +40,8 @@ -export([exor/2]). -export([rc4_encrypt/2, rc4_set_key/1, rc4_encrypt_with_state/2]). -export([rc2_40_cbc_encrypt/3, rc2_40_cbc_decrypt/3]). --export([dss_verify/3, rsa_verify/3, rsa_verify/4]). --export([dss_sign/2, rsa_sign/2, rsa_sign/3]). +-export([dss_verify/3, dss_verify/4, rsa_verify/3, rsa_verify/4]). +-export([dss_sign/2, dss_sign/3, rsa_sign/2, rsa_sign/3]). -export([rsa_public_encrypt/3, rsa_private_decrypt/3]). -export([rsa_private_encrypt/3, rsa_public_decrypt/3]). -export([dh_generate_key/1, dh_generate_key/2, dh_compute_key/3]). @@ -82,6 +82,10 @@ aes_cbc_256_encrypt, aes_cbc_256_decrypt, info_lib]). +-type rsa_digest_type() :: 'md5' | 'sha'. +-type dss_digest_type() :: 'none' | 'sha'. +-type crypto_integer() :: binary() | integer(). + -define(nif_stub,nif_stub_error(?LINE)). -on_load(on_load/0). @@ -118,7 +122,7 @@ on_load() -> nif_stub_error(Line) -> - erlang:error({nif_not_loaded,module,?MODULE,line,Line}). + erlang:nif_error({nif_not_loaded,module,?MODULE,line,Line}). start() -> application:start(crypto). @@ -146,6 +150,12 @@ version() -> ?CRYPTO_VSN. %% %% MD5 %% + +-spec md5(iodata()) -> binary(). +-spec md5_init() -> binary(). +-spec md5_update(binary(), iodata()) -> binary(). +-spec md5_final(binary()) -> binary(). + md5(_Data) -> ?nif_stub. md5_init() -> ?nif_stub. md5_update(_Context, _Data) -> ?nif_stub. @@ -154,6 +164,11 @@ md5_final(_Context) -> ?nif_stub. %% %% MD4 %% +-spec md4(iodata()) -> binary(). +-spec md4_init() -> binary(). +-spec md4_update(binary(), iodata()) -> binary(). +-spec md4_final(binary()) -> binary(). + md4(_Data) -> ?nif_stub. md4_init() -> ?nif_stub. md4_update(_Context, _Data) -> ?nif_stub. @@ -162,6 +177,11 @@ md4_final(_Context) -> ?nif_stub. %% %% SHA %% +-spec sha(iodata()) -> binary(). +-spec sha_init() -> binary(). +-spec sha_update(binary(), iodata()) -> binary(). +-spec sha_final(binary()) -> binary(). + sha(_Data) -> ?nif_stub. sha_init() -> ?nif_stub. sha_update(_Context, _Data) -> ?nif_stub. @@ -175,6 +195,9 @@ sha_final(_Context) -> ?nif_stub. %% %% MD5_MAC %% +-spec md5_mac(iodata(), iodata()) -> binary. +-spec md5_mac_96(iodata(), iodata()) -> binary. + md5_mac(Key, Data) -> md5_mac_n(Key,Data,16). @@ -186,6 +209,9 @@ md5_mac_n(_Key,_Data,_MacSz) -> ?nif_stub. %% %% SHA_MAC %% +-spec sha_mac(iodata(), iodata()) -> binary. +-spec sha_mac_96(iodata(), iodata()) -> binary. + sha_mac(Key, Data) -> sha_mac_n(Key,Data,20). @@ -201,6 +227,9 @@ sha_mac_n(_Key,_Data,_MacSz) -> ?nif_stub. %% %% DES - in cipher block chaining mode (CBC) %% +-spec des_cbc_encrypt(iodata(), binary(), iodata()) -> binary(). +-spec des_cbc_decrypt(iodata(), binary(), iodata()) -> binary(). + des_cbc_encrypt(Key, IVec, Data) -> des_cbc_crypt(Key, IVec, Data, true). @@ -215,6 +244,8 @@ des_cbc_crypt(_Key, _IVec, _Data, _IsEncrypt) -> ?nif_stub. %% Returns the IVec to be used in the next iteration of %% des_cbc_[encrypt|decrypt]. %% +-spec des_cbc_ivec(iodata()) -> binary(). + des_cbc_ivec(Data) when is_binary(Data) -> {_, IVec} = split_binary(Data, size(Data) - 8), IVec; @@ -224,6 +255,9 @@ des_cbc_ivec(Data) when is_list(Data) -> %% %% DES - in electronic codebook mode (ECB) %% +-spec des_ecb_encrypt(iodata(), iodata()) -> binary(). +-spec des_ecb_decrypt(iodata(), iodata()) -> binary(). + des_ecb_encrypt(Key, Data) -> des_ecb_crypt(Key, Data, true). des_ecb_decrypt(Key, Data) -> @@ -233,6 +267,11 @@ des_ecb_crypt(_Key, _Data, _IsEncrypt) -> ?nif_stub. %% %% DES3 - in cipher block chaining mode (CBC) %% +-spec des3_cbc_encrypt(iodata(), iodata(), iodata(), binary(), iodata()) -> + binary(). +-spec des3_cbc_decrypt(iodata(), iodata(), iodata(), binary(), iodata()) -> + binary(). + des3_cbc_encrypt(Key1, Key2, Key3, IVec, Data) -> des_ede3_cbc_encrypt(Key1, Key2, Key3, IVec, Data). des_ede3_cbc_encrypt(Key1, Key2, Key3, IVec, Data) -> @@ -248,6 +287,14 @@ des_ede3_cbc_crypt(_Key1, _Key2, _Key3, _IVec, _Data, _IsEncrypt) -> ?nif_stub. %% %% Blowfish %% +-spec blowfish_ecb_encrypt(iodata(), iodata()) -> binary(). +-spec blowfish_ecb_decrypt(iodata(), iodata()) -> binary(). +-spec blowfish_cbc_encrypt(iodata(), binary(), iodata()) -> binary(). +-spec blowfish_cbc_decrypt(iodata(), binary(), iodata()) -> binary(). +-spec blowfish_cfb64_encrypt(iodata(), binary(), iodata()) -> binary(). +-spec blowfish_cfb64_decrypt(iodata(), binary(), iodata()) -> binary(). +-spec blowfish_ofb64_encrypt(iodata(), binary(), iodata()) -> binary(). + blowfish_ecb_encrypt(Key, Data) -> bf_ecb_crypt(Key,Data, true). @@ -277,6 +324,9 @@ blowfish_ofb64_encrypt(_Key, _IVec, _Data) -> ?nif_stub. %% %% AES in cipher feedback mode (CFB) %% +-spec aes_cfb_128_encrypt(iodata(), binary(), iodata()) -> binary(). +-spec aes_cfb_128_decrypt(iodata(), binary(), iodata()) -> binary(). + aes_cfb_128_encrypt(Key, IVec, Data) -> aes_cfb_128_crypt(Key, IVec, Data, true). @@ -289,6 +339,10 @@ aes_cfb_128_crypt(_Key, _IVec, _Data, _IsEncrypt) -> ?nif_stub. %% %% RAND - pseudo random numbers using RN_ functions in crypto lib %% +-spec rand_bytes(non_neg_integer()) -> binary(). +-spec rand_uniform(crypto_integer(), crypto_integer()) -> + crypto_integer(). + rand_bytes(_Bytes) -> ?nif_stub. rand_bytes(_Bytes, _Topmask, _Bottommask) -> ?nif_stub. @@ -331,9 +385,16 @@ mod_exp_nif(_Base,_Exp,_Mod) -> ?nif_stub. %% %% DSS, RSA - verify %% +-spec dss_verify(binary(), binary(), [binary()]) -> boolean(). +-spec dss_verify(dss_digest_type(), binary(), binary(), [binary()]) -> boolean(). +-spec rsa_verify(binary(), binary(), [binary()]) -> boolean(). +-spec rsa_verify(rsa_digest_type(), binary(), binary(), [binary()]) -> + boolean(). %% Key = [P,Q,G,Y] P,Q,G=DSSParams Y=PublicKey -dss_verify(_Data,_Signature,_Key) -> ?nif_stub. +dss_verify(Data,Signature,Key) -> + dss_verify(sha, Data, Signature, Key). +dss_verify(_Type,_Data,_Signature,_Key) -> ?nif_stub. % Key = [E,N] E=PublicExponent N=PublicModulus rsa_verify(Data,Signature,Key) -> @@ -345,13 +406,20 @@ rsa_verify(_Type,_Data,_Signature,_Key) -> ?nif_stub. %% DSS, RSA - sign %% %% Key = [P,Q,G,X] P,Q,G=DSSParams X=PrivateKey -dss_sign(Data, Key) -> - case dss_sign_nif(Data,Key) of +-spec dss_sign(binary(), [binary()]) -> binary(). +-spec dss_sign(dss_digest_type(), binary(), [binary()]) -> binary(). +-spec rsa_sign(binary(), [binary()]) -> binary(). +-spec rsa_sign(rsa_digest_type(), binary(), [binary()]) -> binary(). + +dss_sign(Data,Key) -> + dss_sign(sha,Data,Key). +dss_sign(Type, Data, Key) -> + case dss_sign_nif(Type,Data,Key) of error -> erlang:error(badkey, [Data, Key]); Sign -> Sign end. -dss_sign_nif(_Data,_Key) -> ?nif_stub. +dss_sign_nif(_Type,_Data,_Key) -> ?nif_stub. %% Key = [E,N,D] E=PublicExponent N=PublicModulus D=PrivateExponent rsa_sign(Data,Key) -> @@ -368,6 +436,16 @@ rsa_sign_nif(_Type,_Data,_Key) -> ?nif_stub. %% %% rsa_public_encrypt %% rsa_private_decrypt +-type rsa_padding() :: 'rsa_pkcs1_padding' | 'rsa_pkcs1_oaep_padding' | 'rsa_no_padding'. + +-spec rsa_public_encrypt(binary(), [binary()], rsa_padding()) -> + binary(). +-spec rsa_public_decrypt(binary(), [binary()], rsa_padding()) -> + binary(). +-spec rsa_private_encrypt(binary(), [binary()], rsa_padding()) -> + binary(). +-spec rsa_private_decrypt(binary(), [binary()], rsa_padding()) -> + binary(). %% Binary, Key = [E,N] rsa_public_encrypt(BinMesg, Key, Padding) -> @@ -409,6 +487,14 @@ rsa_public_decrypt(BinMesg, Key, Padding) -> %% %% AES - with 128 or 256 bit key in cipher block chaining mode (CBC) %% +-spec aes_cbc_128_encrypt(iodata(), binary(), iodata()) -> + binary(). +-spec aes_cbc_128_decrypt(iodata(), binary(), iodata()) -> + binary(). +-spec aes_cbc_256_encrypt(iodata(), binary(), iodata()) -> + binary(). +-spec aes_cbc_256_decrypt(iodata(), binary(), iodata()) -> + binary(). aes_cbc_128_encrypt(Key, IVec, Data) -> aes_cbc_crypt(Key, IVec, Data, true). @@ -443,11 +529,15 @@ aes_cbc_ivec(Data) when is_list(Data) -> %% NB doesn't check that they are the same size, just concatenates %% them and sends them to the driver %% +-spec exor(iodata(), iodata()) -> binary(). + exor(_A, _B) -> ?nif_stub. %% %% RC4 - symmetric stream cipher %% +-spec rc4_encrypt(iodata(), iodata()) -> binary(). + rc4_encrypt(_Key, _Data) -> ?nif_stub. rc4_set_key(_Key) -> ?nif_stub. rc4_encrypt_with_state(_State, _Data) -> ?nif_stub. @@ -490,6 +580,10 @@ dh_check([_Prime,_Gen]) -> ?nif_stub. %% DHParameters = [P (Prime)= mpint(), G(Generator) = mpint()] %% PrivKey = mpint() +-spec dh_generate_key([binary()]) -> {binary(),binary()}. +-spec dh_generate_key(binary()|undefined, [binary()]) -> + {binary(),binary()}. + dh_generate_key(DHParameters) -> dh_generate_key(undefined, DHParameters). dh_generate_key(PrivateKey, DHParameters) -> @@ -502,6 +596,8 @@ dh_generate_key_nif(_PrivateKey, _DHParameters) -> ?nif_stub. %% DHParameters = [P (Prime)= mpint(), G(Generator) = mpint()] %% MyPrivKey, OthersPublicKey = mpint() +-spec dh_compute_key(binary(), binary(), [binary()]) -> binary(). + dh_compute_key(OthersPublicKey, MyPrivateKey, DHParameters) -> case dh_compute_key_nif(OthersPublicKey,MyPrivateKey,DHParameters) of error -> erlang:error(computation_failed, [OthersPublicKey,MyPrivateKey,DHParameters]); diff --git a/lib/crypto/test/crypto_SUITE.erl b/lib/crypto/test/crypto_SUITE.erl index 08d7a0ce99..576949d38d 100644 --- a/lib/crypto/test/crypto_SUITE.erl +++ b/lib/crypto/test/crypto_SUITE.erl @@ -770,18 +770,18 @@ dsa_verify_test(Config) when is_list(Config) -> crypto:mpint(Key) ], - ?line m(crypto:dss_verify(sized_binary(Msg), sized_binary(SigBlob), + ?line m(my_dss_verify(sized_binary(Msg), sized_binary(SigBlob), ValidKey), true), BadMsg = one_bit_wrong(Msg), - ?line m(crypto:dss_verify(sized_binary(BadMsg), sized_binary(SigBlob), + ?line m(my_dss_verify(sized_binary(BadMsg), sized_binary(SigBlob), ValidKey), false), BadSig = one_bit_wrong(SigBlob), - ?line m(crypto:dss_verify(sized_binary(Msg), sized_binary(BadSig), + ?line m(my_dss_verify(sized_binary(Msg), sized_binary(BadSig), ValidKey), false), SizeErr = size(SigBlob) - 13, - BadArg = (catch crypto:dss_verify(sized_binary(Msg), <<SizeErr:32, SigBlob/binary>>, + BadArg = (catch my_dss_verify(sized_binary(Msg), <<SizeErr:32, SigBlob/binary>>, ValidKey)), ?line m(element(1,element(2,BadArg)), badarg), @@ -791,9 +791,12 @@ dsa_verify_test(Config) when is_list(Config) -> crypto:mpint(Key+17) ], - ?line m(crypto:dss_verify(sized_binary(Msg), sized_binary(SigBlob), + ?line m(my_dss_verify(sized_binary(Msg), sized_binary(SigBlob), InValidKey), false). + +one_bit_wrong(List) when is_list(List) -> + lists:map(fun(Bin) -> one_bit_wrong(Bin) end, List); one_bit_wrong(Bin) -> Half = size(Bin) div 2, <<First:Half/binary, Byte:8, Last/binary>> = Bin, @@ -843,15 +846,15 @@ dsa_sign_test(Config) when is_list(Config) -> ParamG = 18320614775012672475365915366944922415598782131828709277168615511695849821411624805195787607930033958243224786899641459701930253094446221381818858674389863050420226114787005820357372837321561754462061849169568607689530279303056075793886577588606958623645901271866346406773590024901668622321064384483571751669, Params = [crypto:mpint(ParamP), crypto:mpint(ParamQ), crypto:mpint(ParamG)], - ?line Sig1 = crypto:dss_sign(sized_binary(Msg), Params ++ [crypto:mpint(PrivKey)]), + ?line Sig1 = my_dss_sign(sized_binary(Msg), Params ++ [crypto:mpint(PrivKey)]), - ?line m(crypto:dss_verify(sized_binary(Msg), sized_binary(Sig1), + ?line m(my_dss_verify(sized_binary(Msg), Sig1, Params ++ [crypto:mpint(PubKey)]), true), - ?line m(crypto:dss_verify(sized_binary(one_bit_wrong(Msg)), sized_binary(Sig1), + ?line m(my_dss_verify(sized_binary(one_bit_wrong(Msg)), Sig1, Params ++ [crypto:mpint(PubKey)]), false), - ?line m(crypto:dss_verify(sized_binary(Msg), sized_binary(one_bit_wrong(Sig1)), + ?line m(my_dss_verify(sized_binary(Msg), one_bit_wrong(Sig1), Params ++ [crypto:mpint(PubKey)]), false), %%?line Bad = crypto:dss_sign(sized_binary(Msg), [Params, crypto:mpint(PubKey)]), @@ -1132,3 +1135,24 @@ zero_bin(N) when is_integer(N) -> <<0:N8/integer>>; zero_bin(B) when is_binary(B) -> zero_bin(size(B)). + +my_dss_verify(Data,[Sign|Tail],Key) -> + Res = my_dss_verify(Data,sized_binary(Sign),Key), + case Tail of + [] -> Res; + _ -> ?line Res = my_dss_verify(Data,Tail,Key) + end; +my_dss_verify(Data,Sign,Key) -> + ?line Res = crypto:dss_verify(Data, Sign, Key), + ?line Res = crypto:dss_verify(sha, Data, Sign, Key), + ?line <<_:32,Raw/binary>> = Data, + ?line Res = crypto:dss_verify(none, crypto:sha(Raw), Sign, Key), + Res. + +my_dss_sign(Data,Key) -> + ?line S1 = crypto:dss_sign(Data, Key), + ?line S2 = crypto:dss_sign(sha, Data, Key), + ?line <<_:32,Raw/binary>> = Data, + ?line S3 = crypto:dss_sign(none, crypto:sha(Raw), Key), + [S1,S2,S3]. + diff --git a/lib/dialyzer/RELEASE_NOTES b/lib/dialyzer/RELEASE_NOTES index b668142327..62b0c92f97 100644 --- a/lib/dialyzer/RELEASE_NOTES +++ b/lib/dialyzer/RELEASE_NOTES @@ -3,6 +3,19 @@ (in reversed chronological order) ============================================================================== +Version 2.3.0 (in Erlang/OTP R14) +--------------------------------- + - Dialyzer properly supports the new attribute -export_type and checks + that remote types only refer to exported types. A warning is produced + if some files/applications refer to types defined in modules which are + neither in the PLT nor in the analyzed applications. + - Support for detecting data races involving whereis/1 and unregister/1. + - More precise identification of the reason(s) why a record construction + violates the types declared for its fields. + - Fixed bug in the handling of the 'or' guard. + - Better handling of the erlang:element/2 BIF. + - Complete handling of Erlang BIFs. + Version 2.2.0 (in Erlang/OTP R13B04) ------------------------------------ - Much better support for opaque types (thanks to Manouk Manoukian). diff --git a/lib/dialyzer/src/dialyzer.erl b/lib/dialyzer/src/dialyzer.erl index 3b7b68e8c4..d8fd073ca6 100644 --- a/lib/dialyzer/src/dialyzer.erl +++ b/lib/dialyzer/src/dialyzer.erl @@ -33,8 +33,8 @@ %% NOTE: Only functions exported by this module are available to %% other applications. %%-------------------------------------------------------------------- --export([plain_cl/0, - run/1, +-export([plain_cl/0, + run/1, gui/0, gui/1, plt_info/1, @@ -55,7 +55,7 @@ plain_cl() -> case dialyzer_cl_parse:start() of - {check_init, Opts} -> + {check_init, Opts} -> cl_halt(cl_check_init(Opts), Opts); {plt_info, Opts} -> cl_halt(cl_print_plt_info(Opts), Opts); @@ -72,7 +72,7 @@ plain_cl() -> false -> gui_halt(internal_gui(Type, Opts), Opts) end; - {cl, Opts} -> + {cl, Opts} -> case Opts#options.check_plt of true -> case cl_check_init(Opts#options{get_warnings = false}) of @@ -82,7 +82,7 @@ plain_cl() -> false -> cl_halt(cl(Opts), Opts) end; - {error, Msg} -> + {error, Msg} -> cl_error(Msg) end. @@ -146,7 +146,7 @@ cl(Opts) -> -spec run(dial_options()) -> [dial_warning()]. run(Opts) -> - try dialyzer_options:build([{report_mode, quiet}, + try dialyzer_options:build([{report_mode, quiet}, {erlang_mode, true}|Opts]) of {error, Msg} -> throw({dialyzer_error, Msg}); @@ -161,7 +161,7 @@ run(Opts) -> throw({dialyzer_error, ErrorMsg1}) end catch - throw:{dialyzer_error, ErrorMsg} -> + throw:{dialyzer_error, ErrorMsg} -> erlang:error({dialyzer_error, lists:flatten(ErrorMsg)}) end. @@ -226,7 +226,7 @@ plt_info(Plt) -> %%----------- doit(F) -> - try + try {ok, F()} catch throw:{dialyzer_error, Msg} -> @@ -241,9 +241,9 @@ gui_halt(R, Opts) -> -spec cl_halt({'ok',dial_ret()} | {'error',string()}, #options{}) -> no_return(). -cl_halt({ok, R = ?RET_NOTHING_SUSPICIOUS}, #options{report_mode = quiet}) -> +cl_halt({ok, R = ?RET_NOTHING_SUSPICIOUS}, #options{report_mode = quiet}) -> halt(R); -cl_halt({ok, R = ?RET_DISCREPANCIES}, #options{report_mode = quiet}) -> +cl_halt({ok, R = ?RET_DISCREPANCIES}, #options{report_mode = quiet}) -> halt(R); cl_halt({ok, R = ?RET_NOTHING_SUSPICIOUS}, #options{}) -> io:put_chars("done (passed successfully)\n"), @@ -267,7 +267,7 @@ cl_check_log(Output) -> -spec format_warning(dial_warning()) -> string(). -format_warning({_Tag, {File, Line}, Msg}) when is_list(File), +format_warning({_Tag, {File, Line}, Msg}) when is_list(File), is_integer(Line) -> BaseName = filename:basename(File), String = lists:flatten(message_to_string(Msg)), @@ -290,7 +290,7 @@ message_to_string({app_call, [M, F, Args, Culprit, ExpectedType, FoundType]}) -> message_to_string({bin_construction, [Culprit, Size, Seg, Type]}) -> io_lib:format("Binary construction will fail since the ~s field ~s in" " segment ~s has type ~s\n", [Culprit, Size, Seg, Type]); -message_to_string({call, [M, F, Args, ArgNs, FailReason, +message_to_string({call, [M, F, Args, ArgNs, FailReason, SigArgs, SigRet, Contract]}) -> io_lib:format("The call ~w:~w~s ", [M, F, Args]) ++ call_or_apply_to_string(ArgNs, FailReason, SigArgs, SigRet, Contract); @@ -329,9 +329,9 @@ message_to_string({no_return, [Type|Name]}) -> only_normal -> NameString ++ "has no local return\n"; both -> NameString ++ "has no local return\n" end; -message_to_string({record_constr, [Types, Name]}) -> +message_to_string({record_constr, [RecConstr, FieldDiffs]}) -> io_lib:format("Record construction ~s violates the" - " declared type for #~w{}\n", [Types, Name]); + " declared type of field ~s\n", [RecConstr, FieldDiffs]); message_to_string({record_constr, [Name, Field, Type]}) -> io_lib:format("Record construction violates the declared type for #~w{}" " since ~s cannot be of type ~s\n", [Name, Field, Type]); @@ -358,7 +358,7 @@ message_to_string({contract_diff, [M, F, _A, Contract, Sig]}) -> [M, F, Contract, M, F, Sig]); message_to_string({contract_subtype, [M, F, _A, Contract, Sig]}) -> io_lib:format("Type specification ~w:~w~s" - " is a subtype of the success typing: ~w:~w~s\n", + " is a subtype of the success typing: ~w:~w~s\n", [M, F, Contract, M, F, Sig]); message_to_string({contract_supertype, [M, F, _A, Contract, Sig]}) -> io_lib:format("Type specification ~w:~w~s" @@ -427,7 +427,7 @@ message_to_string({spec_missing, [B, F, A]}) -> %% Auxiliary functions below %%----------------------------------------------------------------------------- -call_or_apply_to_string(ArgNs, FailReason, SigArgs, SigRet, +call_or_apply_to_string(ArgNs, FailReason, SigArgs, SigRet, {IsOverloaded, Contract}) -> PositionString = form_position_string(ArgNs), case FailReason of @@ -442,7 +442,7 @@ call_or_apply_to_string(ArgNs, FailReason, SigArgs, SigRet, " from the success typing arguments: ~s\n", [PositionString, SigArgs]) end; - only_contract -> + only_contract -> case (ArgNs =:= []) orelse IsOverloaded of true -> %% We do not know which arguments caused the failure @@ -494,7 +494,7 @@ form_position_string(ArgNs) -> case ArgNs of [] -> ""; [N1] -> ordinal(N1); - [_,_|_] -> + [_,_|_] -> [Last|Prevs] = lists:reverse(ArgNs), ", " ++ Head = lists:flatten([io_lib:format(", ~s",[ordinal(N)]) || N <- lists:reverse(Prevs)]), diff --git a/lib/dialyzer/src/dialyzer_analysis_callgraph.erl b/lib/dialyzer/src/dialyzer_analysis_callgraph.erl index e3dd690470..3438cc8c7e 100644 --- a/lib/dialyzer/src/dialyzer_analysis_callgraph.erl +++ b/lib/dialyzer/src/dialyzer_analysis_callgraph.erl @@ -398,7 +398,7 @@ store_core(Mod, Core, NoWarn, Callgraph, CServer) -> store_code_and_build_callgraph(Mod, LabeledCore, Callgraph, CServer3, NoWarn). abs_get_nowarn(Abs, M) -> - [{M, F, A} + [{M, F, A} || {attribute, _, compile, {nowarn_unused_function, {F, A}}} <- Abs]. get_exported_types_from_core(Core) -> @@ -421,7 +421,7 @@ label_core(Core, CServer) -> NextLabel = dialyzer_codeserver:get_next_core_label(CServer), CoreTree = cerl:from_records(Core), {LabeledTree, NewNextLabel} = cerl_trees:label(CoreTree, NextLabel), - {cerl:to_records(LabeledTree), + {cerl:to_records(LabeledTree), dialyzer_codeserver:set_next_core_label(NewNextLabel, CServer)}. store_code_and_build_callgraph(Mod, Core, Callgraph, CServer, NoWarn) -> @@ -489,7 +489,8 @@ rcv_and_send_ext_types(Parent) -> Self = self(), Self ! {Self, done}, ExtTypes = rcv_ext_types(Self, []), - Parent ! {Self, ext_types, ExtTypes}. + Parent ! {Self, ext_types, ExtTypes}, + ok. rcv_ext_types(Self, ExtTypes) -> receive @@ -515,7 +516,7 @@ filter_warnings(LegalWarnings, Warnings) -> send_analysis_done(Parent, Plt, DocPlt) -> Parent ! {self(), done, Plt, DocPlt}, ok. - + send_ext_calls(Parent, ExtCalls) -> Parent ! {self(), ext_calls, ExtCalls}, ok. @@ -539,7 +540,7 @@ send_mod_deps(Parent, ModuleDeps) -> Parent ! {self(), mod_deps, ModuleDeps}, ok. -format_bad_calls([{{_, _, _}, {_, module_info, A}}|Left], CodeServer, Acc) +format_bad_calls([{{_, _, _}, {_, module_info, A}}|Left], CodeServer, Acc) when A =:= 0; A =:= 1 -> format_bad_calls(Left, CodeServer, Acc); format_bad_calls([{FromMFA, {M, F, A} = To}|Left], CodeServer, Acc) -> @@ -552,7 +553,7 @@ format_bad_calls([], _CodeServer, Acc) -> Acc. find_call_file_and_line(Tree, MFA) -> - Fun = + Fun = fun(SubTree, Acc) -> case cerl:is_c_call(SubTree) of true -> diff --git a/lib/dialyzer/src/dialyzer_behaviours.erl b/lib/dialyzer/src/dialyzer_behaviours.erl index 3fae816cfe..47ce9ba6eb 100644 --- a/lib/dialyzer/src/dialyzer_behaviours.erl +++ b/lib/dialyzer/src/dialyzer_behaviours.erl @@ -34,19 +34,25 @@ translate_behaviour_api_call/5, translatable_behaviours/1, translate_callgraph/3]). +-export_type([behaviour/0, behaviour_api_dict/0]). + %%-------------------------------------------------------------------- -include("dialyzer.hrl"). %%-------------------------------------------------------------------- +-type behaviour() :: atom(). + -record(state, {plt :: dialyzer_plt:plt(), codeserver :: dialyzer_codeserver:codeserver(), - filename :: string(), - behlines :: [{atom(), number()}]}). + filename :: file:filename(), + behlines :: [{behaviour(), non_neg_integer()}]}). + +%%-------------------------------------------------------------------- -spec get_behaviours([module()], dialyzer_codeserver:codeserver()) -> - {[atom()], [atom()]}. + {[behaviour()], [behaviour()]}. get_behaviours(Modules, Codeserver) -> get_behaviours(Modules, Codeserver, [], []). @@ -59,29 +65,37 @@ check_callbacks(Module, Attrs, Plt, Codeserver) -> {Behaviours, BehLines} = get_behaviours(Attrs), case Behaviours of [] -> []; - _ -> {_Var,Code} = - dialyzer_codeserver:lookup_mfa_code({Module,module_info,0}, - Codeserver), - File = get_file(cerl:get_ann(Code)), - State = #state{plt = Plt, codeserver = Codeserver, filename = File, - behlines = BehLines}, - Warnings = get_warnings(Module, Behaviours, State), - [add_tag_file_line(Module, W, State) || W <- Warnings] + _ -> + MFA = {Module,module_info,0}, + {_Var,Code} = dialyzer_codeserver:lookup_mfa_code(MFA, Codeserver), + File = get_file(cerl:get_ann(Code)), + State = #state{plt = Plt, codeserver = Codeserver, filename = File, + behlines = BehLines}, + Warnings = get_warnings(Module, Behaviours, State), + [add_tag_file_line(Module, W, State) || W <- Warnings] end. --spec translatable_behaviours(cerl:c_module()) -> [{atom(),[_]}]. +-spec translatable_behaviours(cerl:c_module()) -> behaviour_api_dict(). translatable_behaviours(Tree) -> Attrs = cerl:module_attrs(Tree), {Behaviours, _BehLines} = get_behaviours(Attrs), [{B, Calls} || B <- Behaviours, (Calls = behaviour_api_calls(B)) =/= []]. --spec get_behaviour_apis([atom()]) -> [mfa()]. +-spec get_behaviour_apis([behaviour()]) -> [mfa()]. get_behaviour_apis(Behaviours) -> get_behaviour_apis(Behaviours, []). --spec translate_behaviour_api_call(_, _, _, _, _) -> _. +-spec translate_behaviour_api_call(dialyzer_races:mfa_or_funlbl(), + [erl_types:erl_type()], + [dialyzer_races:core_vars()], + module(), + behaviour_api_dict()) -> + {dialyzer_races:mfa_or_funlbl(), + [erl_types:erl_type()], + [dialyzer_races:core_vars()]} + | 'plain_call'. translate_behaviour_api_call(_Fun, _ArgTypes, _Args, _Module, []) -> plain_call; @@ -101,8 +115,9 @@ translate_behaviour_api_call({Module, Fun, Arity}, ArgTypes, Args, translate_behaviour_api_call(_Fun, _ArgTypes, _Args, _Module, _BehApiInfo) -> plain_call. --spec translate_callgraph([{atom(), _}], atom(), dialyzer_callgraph:callgraph()) - -> dialyzer_callgraph:callgraph(). +-spec translate_callgraph(behaviour_api_dict(), atom(), + dialyzer_callgraph:callgraph()) -> + dialyzer_callgraph:callgraph(). translate_callgraph([{Behaviour,_}|Behaviours], Module, Callgraph) -> UsedCalls = [Call || {_From, {M, _F, _A}} = Call <- @@ -263,7 +278,7 @@ get_line([]) -> -1. get_file([{file, File}|_]) -> File; get_file([_|Tail]) -> get_file(Tail). -%%------------------------------------------------------------------------------ +%%----------------------------------------------------------------------------- get_behaviours([], _Codeserver, KnownAcc, UnknownAcc) -> {KnownAcc, UnknownAcc}; @@ -292,7 +307,7 @@ call_behaviours([Behaviour|Rest], KnownAcc, UnknownAcc) -> _:_ -> call_behaviours(Rest, KnownAcc, [Behaviour | UnknownAcc]) end. -%------------------------------------------------------------------------------- +%------------------------------------------------------------------------------ get_behaviour_apis([], Acc) -> Acc; @@ -301,14 +316,22 @@ get_behaviour_apis([Behaviour | Rest], Acc) -> {{Fun, Arity}, _} <- behaviour_api_calls(Behaviour)], get_behaviour_apis(Rest, MFAs ++ Acc). -%------------------------------------------------------------------------------- +%------------------------------------------------------------------------------ nth_or_0(0, _List, Zero) -> Zero; nth_or_0(N, List, _Zero) -> lists:nth(N, List). -%------------------------------------------------------------------------------- +%------------------------------------------------------------------------------ + +-type behaviour_api_dict()::[{behaviour(), behaviour_api_info()}]. +-type behaviour_api_info()::[{original_fun(), replacement_fun()}]. +-type original_fun()::{atom(), arity()}. +-type replacement_fun()::{atom(), arity(), arg_list()}. +-type arg_list()::[byte()]. + +-spec behaviour_api_calls(behaviour()) -> behaviour_api_info(). behaviour_api_calls(gen_server) -> [{{start_link, 3}, {init, 1, [2]}}, diff --git a/lib/dialyzer/src/dialyzer_cl.erl b/lib/dialyzer/src/dialyzer_cl.erl index 1d02c4f0dc..57f0d6e736 100644 --- a/lib/dialyzer/src/dialyzer_cl.erl +++ b/lib/dialyzer/src/dialyzer_cl.erl @@ -48,7 +48,7 @@ report_mode = normal :: rep_mode(), return_status= ?RET_NOTHING_SUSPICIOUS :: dial_ret(), stored_warnings = [] :: [dial_warning()], - unknown_behaviours = [] :: [atom()] + unknown_behaviours = [] :: [dialyzer_behaviours:behaviour()] }). %%-------------------------------------------------------------------- @@ -577,7 +577,7 @@ format_log_cache(LogCache) -> store_warnings(#cl_state{stored_warnings = StoredWarnings} = St, Warnings) -> St#cl_state{stored_warnings = StoredWarnings ++ Warnings}. --spec store_unknown_behaviours(#cl_state{}, [_]) -> #cl_state{}. +-spec store_unknown_behaviours(#cl_state{}, [dialyzer_behaviours:behaviour()]) -> #cl_state{}. store_unknown_behaviours(#cl_state{unknown_behaviours = Behs} = St, Beh) -> St#cl_state{unknown_behaviours = Beh ++ Behs}. diff --git a/lib/dialyzer/src/dialyzer_codeserver.erl b/lib/dialyzer/src/dialyzer_codeserver.erl index 3cf090712c..b2097f7e53 100644 --- a/lib/dialyzer/src/dialyzer_codeserver.erl +++ b/lib/dialyzer/src/dialyzer_codeserver.erl @@ -21,7 +21,7 @@ %%%------------------------------------------------------------------- %%% File : dialyzer_codeserver.erl %%% Author : Tobias Lindahl <[email protected]> -%%% Description : +%%% Description : %%% %%% Created : 4 Apr 2005 by Tobias Lindahl <[email protected]> %%%------------------------------------------------------------------- @@ -33,7 +33,7 @@ finalize_records/2, get_contracts/1, get_exported_types/1, - get_exports/1, + get_exports/1, get_records/1, get_next_core_label/1, get_temp_contracts/1, @@ -86,7 +86,7 @@ new() -> delete(#codeserver{table_pid = TablePid}) -> table__delete(TablePid). --spec insert(module(), cerl:c_module(), codeserver()) -> codeserver(). +-spec insert(atom(), cerl:c_module(), codeserver()) -> codeserver(). insert(Mod, ModCode, CS) -> NewTablePid = table__insert(CS#codeserver.table_pid, Mod, ModCode), @@ -129,7 +129,7 @@ get_exports(#codeserver{exports = Exports}) -> finalize_exported_types(Set, CS) -> CS#codeserver{exported_types = Set, temp_exported_types = sets:new()}. --spec lookup_mod_code(module(), codeserver()) -> cerl:c_module(). +-spec lookup_mod_code(atom(), codeserver()) -> cerl:c_module(). lookup_mod_code(Mod, CS) when is_atom(Mod) -> table__lookup(CS#codeserver.table_pid, Mod). @@ -149,7 +149,7 @@ get_next_core_label(#codeserver{next_core_label = NCL}) -> set_next_core_label(NCL, CS) -> CS#codeserver{next_core_label = NCL}. --spec store_records(module(), dict(), codeserver()) -> codeserver(). +-spec store_records(atom(), dict(), codeserver()) -> codeserver(). store_records(Mod, Dict, #codeserver{records = RecDict} = CS) when is_atom(Mod) -> @@ -158,7 +158,7 @@ store_records(Mod, Dict, #codeserver{records = RecDict} = CS) false -> CS#codeserver{records = dict:store(Mod, Dict, RecDict)} end. --spec lookup_mod_records(module(), codeserver()) -> dict(). +-spec lookup_mod_records(atom(), codeserver()) -> dict(). lookup_mod_records(Mod, #codeserver{records = RecDict}) when is_atom(Mod) -> @@ -167,12 +167,12 @@ lookup_mod_records(Mod, #codeserver{records = RecDict}) {ok, Dict} -> Dict end. --spec get_records(codeserver()) -> dict(). +-spec get_records(codeserver()) -> dict(). get_records(#codeserver{records = RecDict}) -> RecDict. --spec store_temp_records(module(), dict(), codeserver()) -> codeserver(). +-spec store_temp_records(atom(), dict(), codeserver()) -> codeserver(). store_temp_records(Mod, Dict, #codeserver{temp_records = TempRecDict} = CS) when is_atom(Mod) -> @@ -181,7 +181,7 @@ store_temp_records(Mod, Dict, #codeserver{temp_records = TempRecDict} = CS) false -> CS#codeserver{temp_records = dict:store(Mod, Dict, TempRecDict)} end. --spec get_temp_records(codeserver()) -> dict(). +-spec get_temp_records(codeserver()) -> dict(). get_temp_records(#codeserver{temp_records = TempRecDict}) -> TempRecDict. @@ -191,12 +191,12 @@ get_temp_records(#codeserver{temp_records = TempRecDict}) -> set_temp_records(Dict, CS) -> CS#codeserver{temp_records = Dict}. --spec finalize_records(dict(), codeserver()) -> codeserver(). +-spec finalize_records(dict(), codeserver()) -> codeserver(). finalize_records(Dict, CS) -> CS#codeserver{records = Dict, temp_records = dict:new()}. --spec store_contracts(module(), dict(), codeserver()) -> codeserver(). +-spec store_contracts(atom(), dict(), codeserver()) -> codeserver(). store_contracts(Mod, Dict, #codeserver{contracts = C} = CS) when is_atom(Mod) -> case dict:size(Dict) =:= 0 of @@ -204,7 +204,7 @@ store_contracts(Mod, Dict, #codeserver{contracts = C} = CS) when is_atom(Mod) -> false -> CS#codeserver{contracts = dict:store(Mod, Dict, C)} end. --spec lookup_mod_contracts(module(), codeserver()) -> dict(). +-spec lookup_mod_contracts(atom(), codeserver()) -> dict(). lookup_mod_contracts(Mod, #codeserver{contracts = ContDict}) when is_atom(Mod) -> @@ -213,7 +213,7 @@ lookup_mod_contracts(Mod, #codeserver{contracts = ContDict}) {ok, Dict} -> Dict end. --spec lookup_mfa_contract(mfa(), codeserver()) -> +-spec lookup_mfa_contract(mfa(), codeserver()) -> 'error' | {'ok', dialyzer_contracts:file_contract()}. lookup_mfa_contract({M,_F,_A} = MFA, #codeserver{contracts = ContDict}) -> @@ -222,12 +222,12 @@ lookup_mfa_contract({M,_F,_A} = MFA, #codeserver{contracts = ContDict}) -> {ok, Dict} -> dict:find(MFA, Dict) end. --spec get_contracts(codeserver()) -> dict(). +-spec get_contracts(codeserver()) -> dict(). get_contracts(#codeserver{contracts = ContDict}) -> ContDict. --spec store_temp_contracts(module(), dict(), codeserver()) -> codeserver(). +-spec store_temp_contracts(atom(), dict(), codeserver()) -> codeserver(). store_temp_contracts(Mod, Dict, #codeserver{temp_contracts = C} = CS) when is_atom(Mod) -> @@ -291,7 +291,7 @@ table__loop(Cached, Map) -> Pid ! {self(), Mod, Ans}, table__loop({Mod, Ans}, Map); {insert, List} -> - NewMap = lists:foldl(fun({Key, Val}, AccMap) -> + NewMap = lists:foldl(fun({Key, Val}, AccMap) -> dict:store(Key, Val, AccMap) end, Map, List), table__loop(Cached, NewMap) diff --git a/lib/dialyzer/src/dialyzer_contracts.erl b/lib/dialyzer/src/dialyzer_contracts.erl index 2bedf99e42..bf80c6f470 100644 --- a/lib/dialyzer/src/dialyzer_contracts.erl +++ b/lib/dialyzer/src/dialyzer_contracts.erl @@ -21,7 +21,7 @@ -module(dialyzer_contracts). -export([check_contract/2, - check_contracts/3, + check_contracts/3, contracts_without_fun/3, contract_to_string/1, get_invalid_contract_warnings/3, @@ -106,13 +106,13 @@ contract_to_string(#contract{forms = Forms}) -> contract_to_string_1([{Contract, []}]) -> strip_fun(erl_types:t_form_to_string(Contract)); contract_to_string_1([{Contract, []}|Rest]) -> - strip_fun(erl_types:t_form_to_string(Contract)) ++ "\n ; " + strip_fun(erl_types:t_form_to_string(Contract)) ++ "\n ; " ++ contract_to_string_1(Rest); contract_to_string_1([{Contract, Constraints}]) -> - strip_fun(erl_types:t_form_to_string(Contract)) ++ " when " + strip_fun(erl_types:t_form_to_string(Contract)) ++ " when " ++ constraints_to_string(Constraints); contract_to_string_1([{Contract, Constraints}|Rest]) -> - strip_fun(erl_types:t_form_to_string(Contract)) ++ " when " + strip_fun(erl_types:t_form_to_string(Contract)) ++ " when " ++ constraints_to_string(Constraints) ++ ";" ++ contract_to_string_1(Rest). @@ -130,7 +130,7 @@ constraints_to_string([{type, _, constraint, [{atom, _, What}, Types]}]) -> sequence([erl_types:t_form_to_string(T) || T <- Types], ",") ++ ")"; constraints_to_string([{type, _, constraint, [{atom, _, What}, Types]}|Rest]) -> atom_to_list(What) ++ "(" ++ - sequence([erl_types:t_form_to_string(T) || T <- Types], ",") + sequence([erl_types:t_form_to_string(T) || T <- Types], ",") ++ "), " ++ constraints_to_string(Rest). sequence([], _Delimiter) -> ""; @@ -156,21 +156,21 @@ process_contract_remote_types(CodeServer) -> end, NewContractDict = dict:map(ModuleFun, TmpContractDict), dialyzer_codeserver:finalize_contracts(NewContractDict, CodeServer). - + -spec check_contracts([{mfa(), file_contract()}], dialyzer_callgraph:callgraph(), dict()) -> plt_contracts(). check_contracts(Contracts, Callgraph, FunTypes) -> FoldFun = - fun(Label, Type, NewContracts) -> + fun(Label, Type, NewContracts) -> {ok, {M,F,A} = MFA} = dialyzer_callgraph:lookup_name(Label, Callgraph), case orddict:find(MFA, Contracts) of - {ok, {_FileLine, Contract}} -> + {ok, {_FileLine, Contract}} -> case check_contract(Contract, Type) of ok -> case erl_bif_types:is_known(M, F, A) of true -> - %% Disregard the contracts since + %% Disregard the contracts since %% this is a known function. NewContracts; false -> @@ -187,8 +187,8 @@ check_contracts(Contracts, Callgraph, FunTypes) -> -spec check_contract(#contract{}, erl_types:erl_type()) -> 'ok' | {'error', term()}. check_contract(#contract{contracts = Contracts}, SuccType) -> - try - Contracts1 = [{Contract, insert_constraints(Constraints, dict:new())} + try + Contracts1 = [{Contract, insert_constraints(Constraints, dict:new())} || {Contract, Constraints} <- Contracts], Contracts2 = [erl_types:t_subst(Contract, Dict) || {Contract, Dict} <- Contracts1], @@ -197,7 +197,7 @@ check_contract(#contract{contracts = Contracts}, SuccType) -> error -> {error, {overlapping_contract, []}}; ok -> - InfList = [erl_types:t_inf(Contract, SuccType, opaque) + InfList = [erl_types:t_inf(Contract, SuccType, opaque) || Contract <- Contracts2], case check_contract_inf_list(InfList, SuccType) of {error, _} = Invalid -> Invalid; @@ -229,7 +229,7 @@ check_contract_inf_list([FunType|Left], SuccType) -> STRange = erl_types:t_fun_range(SuccType), case erl_types:t_is_none_or_unit(STRange) of true -> ok; - false -> + false -> Range = erl_types:t_fun_range(FunType), case erl_types:t_is_none(erl_types:t_inf(STRange, Range, opaque)) of true -> check_contract_inf_list(Left, SuccType); @@ -261,9 +261,9 @@ check_extraneous_1(Contract, SuccType) -> process_contracts(OverContracts, Args) -> process_contracts(OverContracts, Args, erl_types:t_none()). - + process_contracts([OverContract|Left], Args, AccRange) -> - NewAccRange = + NewAccRange = case process_contract(OverContract, Args) of error -> AccRange; {ok, Range} -> erl_types:t_sup(AccRange, Range) @@ -276,12 +276,12 @@ process_contracts([], _Args, AccRange) -> process_contract({Contract, Constraints}, CallTypes0) -> CallTypesFun = erl_types:t_fun(CallTypes0, erl_types:t_any()), - ContArgsFun = erl_types:t_fun(erl_types:t_fun_args(Contract), + ContArgsFun = erl_types:t_fun(erl_types:t_fun_args(Contract), erl_types:t_any()), ?debug("Instance: Contract: ~s\n Arguments: ~s\n", - [erl_types:t_to_string(ContArgsFun), + [erl_types:t_to_string(ContArgsFun), erl_types:t_to_string(CallTypesFun)]), - case solve_constraints(ContArgsFun, CallTypesFun, Constraints) of + case solve_constraints(ContArgsFun, CallTypesFun, Constraints) of {ok, VarDict} -> {ok, erl_types:t_subst(erl_types:t_fun_range(Contract), VarDict)}; error -> error @@ -291,7 +291,7 @@ solve_constraints(Contract, Call, Constraints) -> %% First make sure the call follows the constraints CDict = insert_constraints(Constraints, dict:new()), Contract1 = erl_types:t_subst(Contract, CDict), - %% Just a safe over-approximation. + %% Just a safe over-approximation. %% TODO: Find the types for type variables properly ContrArgs = erl_types:t_fun_args(Contract1), CallArgs = erl_types:t_fun_args(Call), @@ -312,7 +312,7 @@ solve_constraints(Contract, Call, Constraints) -> -spec contracts_without_fun(dict(), [_], dialyzer_callgraph:callgraph()) -> [dial_warning()]. contracts_without_fun(Contracts, AllFuns0, Callgraph) -> - AllFuns1 = [{dialyzer_callgraph:lookup_name(Label, Callgraph), Arity} + AllFuns1 = [{dialyzer_callgraph:lookup_name(Label, Callgraph), Arity} || {Label, Arity} <- AllFuns0], AllFuns2 = [{M, F, A} || {{ok, {M, F, _}}, A} <- AllFuns1], AllContractMFAs = dict:fetch_keys(Contracts), @@ -354,9 +354,9 @@ contract_from_form(Forms, RecDict) -> {CFuns, Forms1} = contract_from_form(Forms, RecDict, [], []), #tmp_contract{contract_funs = CFuns, forms = Forms1}. -contract_from_form([{type, _, 'fun', [_, _]} = Form | Left], RecDict, +contract_from_form([{type, _, 'fun', [_, _]} = Form | Left], RecDict, TypeAcc, FormAcc) -> - TypeFun = + TypeFun = fun(ExpTypes, AllRecords) -> Type = erl_types:t_from_form(Form, RecDict), NewType = erl_types:t_solve_remote(Type, ExpTypes, AllRecords), @@ -365,10 +365,10 @@ contract_from_form([{type, _, 'fun', [_, _]} = Form | Left], RecDict, NewTypeAcc = [TypeFun | TypeAcc], NewFormAcc = [{Form, []} | FormAcc], contract_from_form(Left, RecDict, NewTypeAcc, NewFormAcc); -contract_from_form([{type, _L1, bounded_fun, +contract_from_form([{type, _L1, bounded_fun, [{type, _L2, 'fun', [_, _]} = Form, Constr]}| Left], RecDict, TypeAcc, FormAcc) -> - TypeFun = + TypeFun = fun(ExpTypes, AllRecords) -> Constr1 = [constraint_from_form(C, RecDict, ExpTypes, AllRecords) || C <- Constr], @@ -376,14 +376,14 @@ contract_from_form([{type, _L1, bounded_fun, Type = erl_types:t_from_form(Form, RecDict, VarDict), NewType = erl_types:t_solve_remote(Type, ExpTypes, AllRecords), {NewType, Constr1} - end, + end, NewTypeAcc = [TypeFun | TypeAcc], NewFormAcc = [{Form, Constr} | FormAcc], contract_from_form(Left, RecDict, NewTypeAcc, NewFormAcc); -contract_from_form([], _RecDict, TypeAcc, FormAcc) -> +contract_from_form([], _RecDict, TypeAcc, FormAcc) -> {lists:reverse(TypeAcc), lists:reverse(FormAcc)}. -constraint_from_form({type, _, constraint, [{atom, _, is_subtype}, +constraint_from_form({type, _, constraint, [{atom, _, is_subtype}, [Type1, Type2]]}, RecDict, ExpTypes, AllRecords) -> T1 = erl_types:t_from_form(Type1, RecDict), @@ -396,7 +396,7 @@ constraint_from_form({type, _, constraint, [{atom,_,Name}, List]}, _RecDict, N = length(List), throw({error, io_lib:format("Unsupported type guard ~w/~w\n", [Name, N])}). -%% Gets the most general domain of a list of domains of all +%% Gets the most general domain of a list of domains of all %% the overloaded contracts general_domain(List) -> @@ -425,7 +425,7 @@ get_invalid_contract_warnings_modules([Mod|Mods], CodeServer, Plt, Acc) -> get_invalid_contract_warnings_modules([], _CodeServer, _Plt, Acc) -> Acc. -get_invalid_contract_warnings_funs([{MFA, {FileLine, Contract}}|Left], +get_invalid_contract_warnings_funs([{MFA, {FileLine, Contract}}|Left], Plt, RecDict, Acc) -> case dialyzer_plt:lookup(Plt, MFA) of none -> @@ -453,15 +453,15 @@ get_invalid_contract_warnings_funs([{MFA, {FileLine, Contract}}|Left], BifRet = erl_bif_types:type(M, F, A), BifSig = erl_types:t_fun(BifArgs, BifRet), case check_contract(Contract, BifSig) of - {error, _} -> + {error, _} -> [invalid_contract_warning(MFA, FileLine, BifSig, RecDict) |Acc]; ok -> - picky_contract_check(CSig, BifSig, MFA, FileLine, + picky_contract_check(CSig, BifSig, MFA, FileLine, Contract, RecDict, Acc) end; false -> - picky_contract_check(CSig, Sig, MFA, FileLine, Contract, + picky_contract_check(CSig, Sig, MFA, FileLine, Contract, RecDict, Acc) end end, @@ -485,12 +485,12 @@ picky_contract_check(CSig0, Sig0, MFA, FileLine, Contract, RecDict, Acc) -> Sig = erl_types:t_abstract_records(Sig0, RecDict), case erl_types:t_is_equal(CSig, Sig) of true -> Acc; - false -> + false -> case (erl_types:t_is_none(erl_types:t_fun_range(Sig)) andalso erl_types:t_is_unit(erl_types:t_fun_range(CSig))) of true -> Acc; false -> - case extra_contract_warning(MFA, FileLine, Contract, + case extra_contract_warning(MFA, FileLine, Contract, CSig, Sig, RecDict) of no_warning -> Acc; {warning, Warning} -> [Warning|Acc] @@ -509,16 +509,16 @@ extra_contract_warning({M, F, A}, FileLine, Contract, CSig, Sig, RecDict) -> ContractString = contract_to_string(Contract), {Tag, Msg} = case erl_types:t_is_subtype(CSig, Sig) of - true -> - {?WARN_CONTRACT_SUBTYPE, + true -> + {?WARN_CONTRACT_SUBTYPE, {contract_subtype, [M, F, A, ContractString, SigString]}}; false -> case erl_types:t_is_subtype(Sig, CSig) of true -> - {?WARN_CONTRACT_SUPERTYPE, + {?WARN_CONTRACT_SUPERTYPE, {contract_supertype, [M, F, A, ContractString, SigString]}}; false -> - {?WARN_CONTRACT_NOT_EQUAL, + {?WARN_CONTRACT_NOT_EQUAL, {contract_diff, [M, F, A, ContractString, SigString]}} end end, diff --git a/lib/dialyzer/src/dialyzer_dataflow.erl b/lib/dialyzer/src/dialyzer_dataflow.erl index a3c7114ee1..b80c7efc1a 100644 --- a/lib/dialyzer/src/dialyzer_dataflow.erl +++ b/lib/dialyzer/src/dialyzer_dataflow.erl @@ -21,7 +21,7 @@ %%%------------------------------------------------------------------- %%% File : dialyzer_dataflow.erl %%% Author : Tobias Lindahl <[email protected]> -%%% Description : +%%% Description : %%% %%% Created : 19 Apr 2005 by Tobias Lindahl <[email protected]> %%%------------------------------------------------------------------- @@ -30,6 +30,7 @@ -export([get_fun_types/4, get_warnings/5, format_args/3]). +%% Data structure interfaces. -export([state__add_warning/2, state__cleanup/1, state__get_callgraph/1, state__get_races/1, state__get_records/1, state__put_callgraph/2, @@ -42,7 +43,7 @@ -include("dialyzer.hrl"). --import(erl_types, +-import(erl_types, [any_none/1, t_any/0, t_atom/0, t_atom/1, t_atom_vals/1, t_binary/0, t_boolean/0, t_bitstr/0, t_bitstr/2, t_bitstr_concat/1, t_bitstr_match/2, @@ -90,14 +91,15 @@ fun_tab :: dict(), plt :: dialyzer_plt:plt(), opaques :: [erl_types:erl_type()], - races :: dialyzer_races:races(), - records :: dict(), + races = dialyzer_races:new() :: dialyzer_races:races(), + records = dict:new() :: dict(), tree_map :: dict(), warning_mode = false :: boolean(), warnings = [] :: [dial_warning()], work :: {[_], [_], set()}, module :: module(), - behaviour_api_info = [] :: [{atom(),[_]}]}). + behaviour_api_dict = [] :: + dialyzer_behaviours:behaviour_api_dict()}). %% Exported Types @@ -165,20 +167,20 @@ get_top_level_signatures(Code, Records) -> error -> Arity = cerl:fname_arity(V), Type = t_fun(lists:duplicate(Arity, - t_none()), + t_none()), t_none()), dict:store(Label, Type, Acc); {ok, _} -> Acc end end, FunTypes, cerl:module_defs(Tree)), dialyzer_callgraph:delete(Callgraph), - Sigs = [{{cerl:fname_id(V), cerl:fname_arity(V)}, - dict:fetch(get_label(F), FunTypes1)} + Sigs = [{{cerl:fname_id(V), cerl:fname_arity(V)}, + dict:fetch(get_label(F), FunTypes1)} || {V, F} <- cerl:module_defs(Tree)], ordsets:from_list(Sigs). get_def_plt() -> - try + try dialyzer_plt:from_file(dialyzer_plt:get_default_plt()) catch throw:{dialyzer_error, _} -> dialyzer_plt:new() @@ -204,7 +206,7 @@ annotate_module(Code, Plt) -> annotate(Tree, State) -> case cerl:subtrees(Tree) of [] -> set_type(Tree, State); - List -> + List -> NewSubTrees = [[annotate(Subtree, State) || Subtree <- Group] || Group <- List], NewTree = cerl:update_tree(Tree, NewSubTrees), @@ -216,9 +218,9 @@ set_type(Tree, State) -> 'fun' -> Type = state__fun_type(Tree, State), case t_is_any(Type) of - true -> + true -> cerl:set_ann(Tree, delete_ann(typesig, cerl:get_ann(Tree))); - false -> + false -> cerl:set_ann(Tree, append_ann(typesig, Type, cerl:get_ann(Tree))) end; apply -> @@ -226,10 +228,10 @@ set_type(Tree, State) -> unknown -> Tree; ReturnType -> case t_is_any(ReturnType) of - true -> + true -> cerl:set_ann(Tree, delete_ann(type, cerl:get_ann(Tree))); - false -> - cerl:set_ann(Tree, append_ann(type, ReturnType, + false -> + cerl:set_ann(Tree, append_ann(type, ReturnType, cerl:get_ann(Tree))) end end; @@ -238,7 +240,7 @@ set_type(Tree, State) -> end. append_ann(Tag, Val, [X | Xs]) -> - if tuple_size(X) >= 1, element(1, X) =:= Tag -> + if tuple_size(X) >= 1, element(1, X) =:= Tag -> append_ann(Tag, Val, Xs); true -> [X | append_ann(Tag, Val, Xs)] @@ -247,7 +249,7 @@ append_ann(Tag, Val, []) -> [{Tag, Val}]. delete_ann(Tag, [X | Xs]) -> - if tuple_size(X) >= 1, element(1, X) =:= Tag -> + if tuple_size(X) >= 1, element(1, X) =:= Tag -> delete_ann(Tag, Xs); true -> [X | delete_ann(Tag, Xs)] @@ -316,21 +318,21 @@ analyze_loop(#state{callgraph = Callgraph, races = Races} = State) -> {Fun, NewState} -> ArgTypes = state__get_args(Fun, NewState), case any_none(ArgTypes) of - true -> - ?debug("Not handling1 ~w: ~s\n", - [state__lookup_name(get_label(Fun), State), + true -> + ?debug("Not handling1 ~w: ~s\n", + [state__lookup_name(get_label(Fun), State), t_to_string(t_product(ArgTypes))]), analyze_loop(NewState); - false -> + false -> case state__fun_env(Fun, NewState) of - none -> - ?debug("Not handling2 ~w: ~s\n", - [state__lookup_name(get_label(Fun), State), + none -> + ?debug("Not handling2 ~w: ~s\n", + [state__lookup_name(get_label(Fun), State), t_to_string(t_product(ArgTypes))]), analyze_loop(NewState); Map -> - ?debug("Handling fun ~p: ~s\n", - [state__lookup_name(get_label(Fun), State), + ?debug("Handling fun ~p: ~s\n", + [state__lookup_name(get_label(Fun), State), t_to_string(state__fun_type(Fun, NewState))]), NewState1 = state__mark_fun_as_handled(NewState, Fun), Vars = cerl:fun_vars(Fun), @@ -341,19 +343,19 @@ analyze_loop(#state{callgraph = Callgraph, races = Races} = State) -> RaceAnalysis = dialyzer_races:get_race_analysis(Races), NewState3 = case RaceDetection andalso RaceAnalysis of - true -> + true -> NewState2 = state__renew_curr_fun( state__lookup_name(FunLabel, NewState1), FunLabel, NewState1), state__renew_race_list([], 0, NewState2); false -> NewState1 end, - {NewState4, _Map2, BodyType} = + {NewState4, _Map2, BodyType} = traverse(Body, Map1, NewState3), - ?debug("Done analyzing: ~w:~s\n", + ?debug("Done analyzing: ~w:~s\n", [state__lookup_name(get_label(Fun), State), t_to_string(t_fun(ArgTypes, BodyType))]), - NewState5 = + NewState5 = case RaceDetection andalso RaceAnalysis of true -> Races1 = NewState4#state.races, @@ -384,7 +386,7 @@ traverse(Tree, Map, State) -> %% This only happens when checking for illegal record patterns %% so the handling is a bit rudimentary. traverse(cerl:alias_pat(Tree), Map, State); - apply -> + apply -> handle_apply(Tree, Map, State); binary -> Segs = cerl:binary_segments(Tree), @@ -418,7 +420,7 @@ traverse(Tree, Map, State) -> %% By not including the variables in scope we can assure that we %% will get the current function type when using the variables. FoldFun = fun({Var, Fun}, {AccState, AccMap}) -> - {NewAccState, NewAccMap0, FunType} = + {NewAccState, NewAccMap0, FunType} = traverse(Fun, AccMap, AccState), NewAccMap = enter_type(Var, FunType, NewAccMap0), {NewAccState, NewAccMap} @@ -430,7 +432,7 @@ traverse(Tree, Map, State) -> case cerl:unfold_literal(Tree) of Tree -> Type = literal_type(Tree), - NewType = + NewType = case erl_types:t_opaque_match_atom(Type, State#state.opaques) of [Opaque] -> Opaque; _ -> Type @@ -448,8 +450,8 @@ traverse(Tree, Map, State) -> bs_init_writable -> t_from_term(<<>>); Other -> erlang:error({'Unsupported primop', Other}) end, - {State, Map, Type}; - 'receive' -> + {State, Map, Type}; + 'receive' -> handle_receive(Tree, Map, State); seq -> Arg = cerl:seq_arg(Tree), @@ -459,13 +461,13 @@ traverse(Tree, Map, State) -> true -> SMA; false -> - State2 = + State2 = case (t_is_any(ArgType) orelse t_is_simple(ArgType) orelse is_call_to_send(Arg)) of true -> % do not warn in these cases State1; false -> - state__add_warning(State1, ?WARN_UNMATCHED_RETURN, Arg, + state__add_warning(State1, ?WARN_UNMATCHED_RETURN, Arg, {unmatched_return, [format_type(ArgType, State1)]}) end, @@ -483,12 +485,12 @@ traverse(Tree, Map, State) -> var -> ?debug("Looking up unknown variable: ~p\n", [Tree]), case state__lookup_type_for_rec_var(Tree, State) of - error -> + error -> LType = lookup_type(Tree, Map), Opaques = State#state.opaques, case t_opaque_match_record(LType, Opaques) of [Opaque] -> {State, Map, Opaque}; - _ -> + _ -> case t_opaque_match_atom(LType, Opaques) of [Opaque] -> {State, Map, Opaque}; _ -> {State, Map, LType} @@ -508,7 +510,7 @@ traverse_list([Tree|Tail], Map, State, Acc) -> traverse_list(Tail, Map1, State1, [Type|Acc]); traverse_list([], Map, State, Acc) -> {State, Map, lists:reverse(Acc)}. - + %%________________________________________ %% %% Special instructions @@ -520,7 +522,7 @@ handle_apply(Tree, Map, State) -> {State1, Map1, ArgTypes} = traverse_list(Args, Map, State), {State2, Map2, OpType} = traverse(Op, Map1, State1), case any_none(ArgTypes) of - true -> + true -> {State2, Map2, t_none()}; false -> {CallSitesKnown, FunList} = @@ -535,7 +537,7 @@ handle_apply(Tree, Map, State) -> OpType1 = t_inf(OpType, t_fun(Arity, t_any())), case t_is_none(OpType1) of true -> - Msg = {fun_app_no_fun, + Msg = {fun_app_no_fun, [format_cerl(Op), format_type(OpType, State2), Arity]}, State3 = state__add_warning(State2, ?WARN_FAILING_CALL, Tree, Msg), @@ -543,7 +545,7 @@ handle_apply(Tree, Map, State) -> false -> NewArgs = t_inf_lists(ArgTypes, t_fun_args(OpType1)), case any_none(NewArgs) of - true -> + true -> Msg = {fun_app_args, [format_args(Args, ArgTypes, State), format_type(OpType, State)]}, @@ -556,7 +558,7 @@ handle_apply(Tree, Map, State) -> end end; true -> - FunInfoList = [{local, state__fun_info(Fun, State)} + FunInfoList = [{local, state__fun_info(Fun, State)} || Fun <- FunList], handle_apply_or_call(FunInfoList, Args, ArgTypes, Map2, Tree, State1) end @@ -564,7 +566,7 @@ handle_apply(Tree, Map, State) -> handle_apply_or_call(FunInfoList, Args, ArgTypes, Map, Tree, State) -> None = t_none(), - handle_apply_or_call(FunInfoList, Args, ArgTypes, Map, Tree, State, + handle_apply_or_call(FunInfoList, Args, ArgTypes, Map, Tree, State, [None || _ <- ArgTypes], None). handle_apply_or_call([{local, external}|Left], Args, ArgTypes, Map, Tree, State, @@ -579,7 +581,7 @@ handle_apply_or_call([{TypeOfApply, {Fun, Sig, Contr, LocalRet}}|Left], Any = t_any(), AnyArgs = [Any || _ <- Args], GenSig = {AnyArgs, fun(_) -> t_any() end}, - {CArgs, CRange} = + {CArgs, CRange} = case Contr of {value, #contract{args = As} = C} -> {As, fun(FunArgs) -> @@ -629,9 +631,9 @@ handle_apply_or_call([{TypeOfApply, {Fun, Sig, Contr, LocalRet}}|Left], end end, ArgModeMask = [case lists:member(Arg, Opaques) of - true -> opaque; - false -> structured - end || Arg <- ArgTypes], + true -> opaque; + false -> structured + end || Arg <- ArgTypes], NewArgsSig = t_inf_lists_masked(SigArgs, ArgTypes, ArgModeMask), NewArgsContract = t_inf_lists_masked(CArgs, ArgTypes, ArgModeMask), NewArgsBif = t_inf_lists_masked(BifArgs, ArgTypes, ArgModeMask), @@ -639,7 +641,7 @@ handle_apply_or_call([{TypeOfApply, {Fun, Sig, Contr, LocalRet}}|Left], NewArgTypes = t_inf_lists_masked(NewArgTypes0, NewArgsBif, ArgModeMask), BifRet = BifRange(NewArgTypes), {TmpArgTypes, TmpArgsContract} = - case (TypeOfApply == remote) andalso (not IsBIF) of + case (TypeOfApply =:= remote) andalso (not IsBIF) of true -> List1 = lists:zip(CArgs, NewArgTypes), List2 = lists:zip(CArgs, NewArgsContract), @@ -650,16 +652,17 @@ handle_apply_or_call([{TypeOfApply, {Fun, Sig, Contr, LocalRet}}|Left], false -> {NewArgTypes, NewArgsContract} end, ContrRet = CRange(TmpArgTypes), - RetMode = case t_contains_opaque(ContrRet) orelse t_contains_opaque(BifRet) of - true -> opaque; - false -> structured - end, + RetMode = + case t_contains_opaque(ContrRet) orelse t_contains_opaque(BifRet) of + true -> opaque; + false -> structured + end, RetWithoutLocal = t_inf(t_inf(ContrRet, BifRet, RetMode), SigRange, RetMode), ?debug("--------------------------------------------------------\n", []), ?debug("Fun: ~p\n", [Fun]), ?debug("Args: ~s\n", [erl_types:t_to_string(t_product(ArgTypes))]), ?debug("NewArgsSig: ~s\n", [erl_types:t_to_string(t_product(NewArgsSig))]), - ?debug("NewArgsContract: ~s\n", + ?debug("NewArgsContract: ~s\n", [erl_types:t_to_string(t_product(NewArgsContract))]), ?debug("NewArgsBif: ~s\n", [erl_types:t_to_string(t_product(NewArgsBif))]), ?debug("NewArgTypes: ~s\n", [erl_types:t_to_string(t_product(NewArgTypes))]), @@ -679,11 +682,11 @@ handle_apply_or_call([{TypeOfApply, {Fun, Sig, Contr, LocalRet}}|Left], %% respective callback module's function. Module = State#state.module, - BehApiInfo = State#state.behaviour_api_info, + BehApiDict = State#state.behaviour_api_dict, {RealFun, RealArgTypes, RealArgs} = case dialyzer_behaviours:translate_behaviour_api_call(Fun, ArgTypes, Args, Module, - BehApiInfo) of + BehApiDict) of plain_call -> {Fun, ArgTypes, Args}; BehaviourAPI -> BehaviourAPI end, @@ -700,10 +703,10 @@ handle_apply_or_call([{TypeOfApply, {Fun, Sig, Contr, LocalRet}}|Left], FailedSig = any_none(NewArgsSig), FailedContract = any_none([CRange(TmpArgsContract)|NewArgsContract]), FailedBif = any_none([BifRange(NewArgsBif)|NewArgsBif]), - InfSig = t_inf(t_fun(SigArgs, SigRange), + InfSig = t_inf(t_fun(SigArgs, SigRange), t_fun(BifArgs, BifRange(BifArgs))), FailReason = apply_fail_reason(FailedSig, FailedBif, FailedContract), - Msg = get_apply_fail_msg(Fun, Args, ArgTypes, NewArgTypes, InfSig, + Msg = get_apply_fail_msg(Fun, Args, ArgTypes, NewArgTypes, InfSig, Contr, CArgs, State1, FailReason), WarnType = case Msg of {call, _} -> ?WARN_FAILING_CALL; @@ -727,15 +730,15 @@ handle_apply_or_call([{TypeOfApply, {Fun, Sig, Contr, LocalRet}}|Left], remote -> add_bif_warnings(Fun, NewArgTypes, Tree, State2) end, - NewAccArgTypes = + NewAccArgTypes = case FailedConj of true -> AccArgTypes; false -> [t_sup(X, Y) || {X, Y} <- lists:zip(NewArgTypes, AccArgTypes)] end, NewAccRet = t_sup(AccRet, t_inf(RetWithoutLocal, LocalRet, opaque)), - handle_apply_or_call(Left, Args, ArgTypes, Map, Tree, + handle_apply_or_call(Left, Args, ArgTypes, Map, Tree, State3, NewAccArgTypes, NewAccRet); -handle_apply_or_call([], Args, _ArgTypes, Map, _Tree, State, +handle_apply_or_call([], Args, _ArgTypes, Map, _Tree, State, AccArgTypes, AccRet) -> NewMap = enter_type_lists(Args, AccArgTypes, Map), {State, NewMap, AccRet}. @@ -747,13 +750,13 @@ apply_fail_reason(FailedSig, FailedBif, FailedContract) -> true -> both end. -get_apply_fail_msg(Fun, Args, ArgTypes, NewArgTypes, +get_apply_fail_msg(Fun, Args, ArgTypes, NewArgTypes, Sig, Contract, ContrArgs, State, FailReason) -> ArgStrings = format_args(Args, ArgTypes, State), ContractInfo = case Contract of {value, #contract{} = C} -> - {dialyzer_contracts:is_overloaded(C), + {dialyzer_contracts:is_overloaded(C), dialyzer_contracts:contract_to_string(C)}; none -> {false, none} end, @@ -767,7 +770,7 @@ get_apply_fail_msg(Fun, Args, ArgTypes, NewArgTypes, {M, F, _A} -> case is_opaque_type_test_problem(Fun, NewArgTypes, State) of true -> - [Opaque] = NewArgTypes, + [Opaque] = NewArgTypes, {opaque_type_test, [atom_to_list(F), erl_types:t_to_string(Opaque)]}; false -> SigArgs = t_fun_args(Sig), @@ -791,7 +794,7 @@ get_apply_fail_msg(Fun, Args, ArgTypes, NewArgTypes, {call_without_opaque, [M, F, ArgStrings, ExpectedTriples]}; false -> %% there is a structured term clash in some argument {call, [M, F, ArgStrings, - ArgNs, FailReason, + ArgNs, FailReason, format_sig_args(Sig, State), format_type(t_fun_range(Sig), State), ContractInfo]} @@ -799,8 +802,8 @@ get_apply_fail_msg(Fun, Args, ArgTypes, NewArgTypes, end end; Label when is_integer(Label) -> - {apply, [ArgStrings, - ArgNs, FailReason, + {apply, [ArgStrings, + ArgNs, FailReason, format_sig_args(Sig, State), format_type(t_fun_range(Sig), State), ContractInfo]} @@ -828,7 +831,7 @@ is_opaque_type_test_problem(Fun, ArgTypes, State) -> FN =:= is_number; FN =:= is_pid; FN =:= is_port; FN =:= is_reference; FN =:= is_tuple -> [Type] = ArgTypes, - erl_types:t_is_opaque(Type) andalso + erl_types:t_is_opaque(Type) andalso not lists:member(Type, State#state.opaques); _ -> false end. @@ -1045,7 +1048,7 @@ handle_cons(Tree, Map, State) -> Tl = cerl:cons_tl(Tree), {State1, Map1, HdType} = traverse(Hd, Map, State), {State2, Map2, TlType} = traverse(Tl, Map1, State1), - State3 = + State3 = case t_is_none(t_inf(TlType, t_list())) of true -> Msg = {improper_list_constr, [format_type(TlType, State2)]}, @@ -1090,7 +1093,7 @@ handle_let(Tree, Map, #state{callgraph = Callgraph, races = Races} = State) -> case cerl:is_literal(Mod) andalso cerl:concrete(Mod) =:= ets andalso cerl:is_literal(Name) andalso - cerl:concrete(Name) =:= new of + cerl:concrete(Name) =:= new of true -> NewTable = dialyzer_races:get_new_table(State1#state.races), renew_public_tables(Vars, NewTable, @@ -1114,7 +1117,7 @@ handle_module(Tree, Map, State) -> %% By not including the variables in scope we can assure that we %% will get the current function type when using the variables. Defs = cerl:module_defs(Tree), - PartFun = fun({_Var, Fun}) -> + PartFun = fun({_Var, Fun}) -> state__is_escaping(get_label(Fun), State) end, {Defs1, Defs2} = lists:partition(PartFun, Defs), @@ -1145,12 +1148,12 @@ handle_receive(Tree, Map, RaceListSize + 1, State); false -> State end, - {MapList, State2, ReceiveType} = + {MapList, State2, ReceiveType} = handle_clauses(Clauses, ?no_arg, t_any(), t_any(), State1, [], Map, [], []), Map1 = join_maps(MapList, Map), {State3, Map2, TimeoutType} = traverse(Timeout, Map1, State2), - case (t_is_atom(TimeoutType) andalso + case (t_is_atom(TimeoutType) andalso (t_atom_vals(TimeoutType) =:= ['infinity'])) of true -> {State3, Map2, ReceiveType}; @@ -1170,17 +1173,17 @@ handle_try(Tree, Map, State) -> Vars = cerl:try_vars(Tree), Body = cerl:try_body(Tree), Handler = cerl:try_handler(Tree), - {State1, Map1, ArgType} = traverse(Arg, Map, State), + {State1, Map1, ArgType} = traverse(Arg, Map, State), Map2 = mark_as_fresh(Vars, Map1), {SuccState, SuccMap, SuccType} = case bind_pat_vars(Vars, t_to_tlist(ArgType), [], Map2, State1) of {error, _, _, _, _} -> {State1, map__new(), t_none()}; {SuccMap1, VarTypes} -> - %% Try to bind the argument. Will only succeed if + %% Try to bind the argument. Will only succeed if %% it is a simple structured term. SuccMap2 = - case bind_pat_vars_reverse([Arg], [t_product(VarTypes)], [], + case bind_pat_vars_reverse([Arg], [t_product(VarTypes)], [], SuccMap1, State1) of {error, _, _, _, _} -> SuccMap1; {SM, _} -> SM @@ -1214,10 +1217,10 @@ handle_tuple(Tree, Map, State) -> RecFields = t_tuple_args(RecStruct), case bind_pat_vars(Elements, RecFields, [], Map1, State1) of {error, _, ErrorPat, ErrorType, _} -> - Msg = {record_constr, + Msg = {record_constr, [TagVal, format_patterns(ErrorPat), format_type(ErrorType, State1)]}, - State2 = state__add_warning(State1, ?WARN_MATCHING, + State2 = state__add_warning(State1, ?WARN_MATCHING, Tree, Msg), {State2, Map1, t_none()}; {Map2, _ETypes} -> @@ -1226,26 +1229,24 @@ handle_tuple(Tree, Map, State) -> _ -> case state__lookup_record(TagVal, length(Left), State1) of error -> {State1, Map1, TupleType}; - {ok, Prototype} -> - %% io:format("In handle_tuple:\n Prototype = ~p\n", [Prototype]), - InfTupleType = t_inf(Prototype, TupleType), - %% io:format(" TupleType = ~p,\n Inf = ~p\n", [TupleType, InfTupleType]), + {ok, RecType} -> + InfTupleType = t_inf(RecType, TupleType), case t_is_none(InfTupleType) of true -> - Msg = {record_constr, - [format_type(TupleType, State1), TagVal]}, - State2 = state__add_warning(State1, ?WARN_MATCHING, + RecC = format_type(TupleType, State1), + FieldDiffs = format_field_diffs(TupleType, State1), + Msg = {record_constr, [RecC, FieldDiffs]}, + State2 = state__add_warning(State1, ?WARN_MATCHING, Tree, Msg), {State2, Map1, t_none()}; false -> - case bind_pat_vars(Elements, t_tuple_args(Prototype), + case bind_pat_vars(Elements, t_tuple_args(RecType), [], Map1, State1) of {error, bind, ErrorPat, ErrorType, _} -> - %% io:format("error\n", []), - Msg = {record_constr, + Msg = {record_constr, [TagVal, format_patterns(ErrorPat), format_type(ErrorType, State1)]}, - State2 = state__add_warning(State1, ?WARN_MATCHING, + State2 = state__add_warning(State1, ?WARN_MATCHING, Tree, Msg), {State2, Map1, t_none()}; {Map2, ETypes} -> @@ -1307,7 +1308,7 @@ handle_clauses([C|Left], Arg, ArgType, OrigArgType, handle_clauses([], _Arg, _ArgType, _OrigArgType, #state{callgraph = Callgraph, races = Races} = State, CaseTypes, _MapIn, Acc, ClauseAcc) -> - State1 = + State1 = case dialyzer_callgraph:get_race_detection(Callgraph) andalso dialyzer_races:get_race_analysis(Races) of true -> @@ -1315,7 +1316,7 @@ handle_clauses([], _Arg, _ArgType, _OrigArgType, [dialyzer_races:end_case_new(ClauseAcc)| dialyzer_races:get_race_list(Races)], dialyzer_races:get_race_list_size(Races) + 1, State); - false -> State + false -> State end, {lists:reverse(Acc), State1, t_sup(CaseTypes)}. @@ -1326,7 +1327,7 @@ do_clause(C, Arg, ArgType0, OrigArgType, Map, Body = cerl:clause_body(C), RaceDetection = dialyzer_callgraph:get_race_detection(Callgraph), RaceAnalysis = dialyzer_races:get_race_analysis(Races), - State1 = + State1 = case RaceDetection andalso RaceAnalysis of true -> state__renew_fun_args(Pats, State); @@ -1341,7 +1342,7 @@ do_clause(C, Arg, ArgType0, OrigArgType, Map, true -> {error, bind, Pats, ArgType0, ArgType0}; false -> - ArgTypes = + ArgTypes = case t_is_any(ArgType0) of true -> [ArgType0 || _ <- Pats]; false -> t_to_tlist(ArgType0) @@ -1350,7 +1351,7 @@ do_clause(C, Arg, ArgType0, OrigArgType, Map, end, case BindRes of {error, BindOrOpaque, NewPats, Type, OpaqueTerm} -> - ?debug("Failed binding pattern: ~s\nto ~s\n", + ?debug("Failed binding pattern: ~s\nto ~s\n", [cerl_prettypr:format(C), format_type(ArgType0, State1)]), case state__warning_mode(State1) of false -> @@ -1361,7 +1362,7 @@ do_clause(C, Arg, ArgType0, OrigArgType, Map, bind -> format_patterns(Pats); opaque -> format_patterns(NewPats) end, - {Msg, Force} = + {Msg, Force} = case t_is_none(ArgType0) of true -> PatTypes = [PatString, format_type(OrigArgType, State1)], @@ -1377,13 +1378,13 @@ do_clause(C, Arg, ArgType0, OrigArgType, Map, {_, _} -> {{pattern_match_cov, PatTypes}, false} end; false -> - %% Try to find out if this is a default clause in a list + %% Try to find out if this is a default clause in a list %% comprehension and supress this. A real Hack(tm) Force0 = case is_compiler_generated(cerl:get_ann(C)) of true -> case Pats of - [Pat] -> + [Pat] -> case cerl:is_c_cons(Pat) of true -> not (cerl:is_c_var(cerl:cons_hd(Pat)) andalso @@ -1400,9 +1401,9 @@ do_clause(C, Arg, ArgType0, OrigArgType, Map, end, PatTypes = case BindOrOpaque of bind -> [PatString, format_type(ArgType0, State1)]; - opaque -> [PatString, format_type(Type, State1), + opaque -> [PatString, format_type(Type, State1), format_type(OpaqueTerm, State1)] - end, + end, FailedMsg = case BindOrOpaque of bind -> {pattern_match, PatTypes}; opaque -> {opaque_match, PatTypes} @@ -1422,9 +1423,9 @@ do_clause(C, Arg, ArgType0, OrigArgType, Map, case Arg =:= ?no_arg of true -> Map2; false -> - %% Try to bind the argument. Will only succeed if + %% Try to bind the argument. Will only succeed if %% it is a simple structured term. - case bind_pat_vars_reverse([Arg], [t_product(PatTypes)], + case bind_pat_vars_reverse([Arg], [t_product(PatTypes)], [], Map2, State1) of {error, _, _, _, _} -> Map2; {NewMap, _} -> NewMap @@ -1438,11 +1439,11 @@ do_clause(C, Arg, ArgType0, OrigArgType, Map, t_subtract(t_product(t_to_tlist(ArgType0)), GenType) end, case bind_guard(Guard, Map3, State1) of - {error, Reason} -> - ?debug("Failed guard: ~s\n", + {error, Reason} -> + ?debug("Failed guard: ~s\n", [cerl_prettypr:format(C, [{hook, cerl_typean:pp_hook()}])]), PatString = format_patterns(Pats), - DefaultMsg = + DefaultMsg = case Pats =:= [] of true -> {guard_fail, []}; false -> @@ -1472,7 +1473,7 @@ do_clause(C, Arg, ArgType0, OrigArgType, Map, bind_subst(Arg, Pats, Map) -> case cerl:type(Arg) of - values -> + values -> bind_subst_list(cerl:values_es(Arg), Pats, Map); var -> [Pat] = Pats, @@ -1501,16 +1502,16 @@ bind_subst_list([], [], Map) -> %% bind_pat_vars(Pats, Types, Acc, Map, State) -> - try + try bind_pat_vars(Pats, Types, Acc, Map, State, false) - catch + catch throw:Error -> Error % Error = {error, bind | opaque, ErrorPats, ErrorType} end. bind_pat_vars_reverse(Pats, Types, Acc, Map, State) -> - try + try bind_pat_vars(Pats, Types, Acc, Map, State, true) - catch + catch throw:Error -> Error % Error = {error, bind | opaque, ErrorPats, ErrorType} end. @@ -1522,7 +1523,7 @@ bind_pat_vars([Pat|PatLeft], [Type|TypeLeft], Acc, Map, State, Rev) -> AliasPat = cerl:alias_pat(Pat), Var = cerl:alias_var(Pat), Map1 = enter_subst(Var, AliasPat, Map), - {Map2, [PatType]} = bind_pat_vars([AliasPat], [Type], [], + {Map2, [PatType]} = bind_pat_vars([AliasPat], [Type], [], Map1, State, Rev), {enter_type(Var, PatType, Map2), PatType}; binary -> @@ -1543,18 +1544,18 @@ bind_pat_vars([Pat|PatLeft], [Type|TypeLeft], Acc, Map, State, Rev) -> cons -> Cons = t_inf(Type, t_cons()), case t_is_none(Cons) of - true -> + true -> bind_opaque_pats(t_cons(), Type, Pat, Map, State, Rev); false -> - {Map1, [HdType, TlType]} = + {Map1, [HdType, TlType]} = bind_pat_vars([cerl:cons_hd(Pat), cerl:cons_tl(Pat)], - [t_cons_hd(Cons), t_cons_tl(Cons)], + [t_cons_hd(Cons), t_cons_tl(Cons)], [], Map, State, Rev), {Map1, t_cons(HdType, TlType)} end; literal -> Literal = literal_type(Pat), - LiteralOrOpaque = + LiteralOrOpaque = case t_opaque_match_atom(Literal, State#state.opaques) of [Opaque] -> Opaque; _ -> Literal @@ -1566,7 +1567,7 @@ bind_pat_vars([Pat|PatLeft], [Type|TypeLeft], Acc, Map, State, Rev) -> end; tuple -> Es = cerl:tuple_es(Pat), - Prototype = + Prototype = case Es of [] -> t_tuple([]); [Tag|Left] -> @@ -1587,10 +1588,10 @@ bind_pat_vars([Pat|PatLeft], [Type|TypeLeft], Acc, Map, State, Rev) -> false -> SubTuples = t_tuple_subtypes(Tuple), %% Need to call the top function to get the try-catch wrapper - Results = + Results = case Rev of true -> - [bind_pat_vars_reverse(Es, t_tuple_args(SubTuple), [], + [bind_pat_vars_reverse(Es, t_tuple_args(SubTuple), [], Map, State) || SubTuple <- SubTuples]; false -> @@ -1634,12 +1635,12 @@ bind_pat_vars([Pat|PatLeft], [Type|TypeLeft], Acc, Map, State, Rev) -> end, %% Must do inf when binding args to pats. Vars in pats are fresh. VarType2 = t_inf(VarType1, Type), - VarType3 = + VarType3 = case Opaques =/= [] of true -> case t_opaque_match_record(VarType2, Opaques) of [OpaqueRec] -> OpaqueRec; - _ -> + _ -> case t_opaque_match_atom(VarType2, Opaques) of [OpaqueAtom] -> OpaqueAtom; _ -> VarType2 @@ -1650,9 +1651,9 @@ bind_pat_vars([Pat|PatLeft], [Type|TypeLeft], Acc, Map, State, Rev) -> case t_is_none(VarType3) of true -> case t_find_opaque_mismatch(VarType1, Type) of - {ok, T1, T2} -> + {ok, T1, T2} -> bind_error([Pat], T1, T2, opaque); - error -> + error -> bind_error([Pat], Type, t_none(), bind) end; false -> @@ -1754,10 +1755,10 @@ bind_guard(Guard, Map, State) -> end. bind_guard(Guard, Map, Env, Eval, State) -> - ?debug("Handling ~w guard: ~s\n", + ?debug("Handling ~w guard: ~s\n", [Eval, cerl_prettypr:format(Guard, [{noann, true}])]), case cerl:type(Guard) of - binary -> + binary -> {Map, t_binary()}; 'case' -> Arg = cerl:case_arg(Guard), @@ -1795,10 +1796,10 @@ bind_guard(Guard, Map, Env, Eval, State) -> var -> ?debug("Looking for var(~w)...", [cerl_trees:get_label(Guard)]), case dict:find(get_label(Guard), Env) of - error -> + error -> ?debug("Did not find it\n", []), Type = lookup_type(Guard, Map), - Constr = + Constr = case Eval of pos -> t_atom(true); neg -> t_atom(false); @@ -1806,7 +1807,7 @@ bind_guard(Guard, Map, Env, Eval, State) -> end, Inf = t_inf(Constr, Type), {enter_type(Guard, Inf, Map), Inf}; - {ok, Tree} -> + {ok, Tree} -> ?debug("Found it\n", []), {Map1, Type} = bind_guard(Tree, Map, Env, Eval, State), {enter_type(Guard, Type, Map1), Type} @@ -1829,7 +1830,7 @@ handle_guard_call(Guard, Map, Env, Eval, State) -> handle_guard_type_test(Guard, F, Map, Env, Eval, State); {erlang, is_function, 2} -> handle_guard_is_function(Guard, Map, Env, Eval, State); - MFA when (MFA =:= {erlang, internal_is_record, 3}) or + MFA when (MFA =:= {erlang, internal_is_record, 3}) or (MFA =:= {erlang, is_record, 3}) -> handle_guard_is_record(Guard, Map, Env, Eval, State); {erlang, '=:=', 2} -> @@ -1842,7 +1843,7 @@ handle_guard_call(Guard, Map, Env, Eval, State) -> handle_guard_or(Guard, Map, Env, Eval, State); {erlang, 'not', 1} -> handle_guard_not(Guard, Map, Env, Eval, State); - {erlang, Comp, 2} when Comp =:= '<'; Comp =:= '=<'; + {erlang, Comp, 2} when Comp =:= '<'; Comp =:= '=<'; Comp =:= '>'; Comp =:= '>=' -> handle_guard_comp(Guard, Comp, Map, Env, Eval, State); _ -> @@ -1877,7 +1878,7 @@ handle_guard_gen_fun({M, F, A}, Guard, Map, Env, Eval, State) -> List -> List end, Map2 = enter_type_lists(Args, t_inf_lists(BifArgs, As0, Mode), Map1), - Ret = + Ret = case Eval of pos -> t_inf(t_atom(true), BifRet); neg -> t_inf(t_atom(false), BifRet); @@ -1894,14 +1895,14 @@ handle_guard_gen_fun({M, F, A}, Guard, Map, Env, Eval, State) -> end. handle_guard_type_test(Guard, F, Map, Env, Eval, State) -> - [Arg] = cerl:call_args(Guard), + [Arg] = cerl:call_args(Guard), {Map1, ArgType} = bind_guard(Arg, Map, Env, dont_know, State), case bind_type_test(Eval, F, ArgType, State) of - error -> + error -> ?debug("Type test: ~w failed\n", [F]), signal_guard_fail(Guard, [ArgType], State); - {ok, NewArgType, Ret} -> - ?debug("Type test: ~w succeeded, NewType: ~s, Ret: ~s\n", + {ok, NewArgType, Ret} -> + ?debug("Type test: ~w succeeded, NewType: ~s, Ret: ~s\n", [F, t_to_string(NewArgType), t_to_string(Ret)]), {enter_type(Arg, NewArgType, Map1), Ret} end. @@ -1932,13 +1933,13 @@ bind_type_test(Eval, TypeTest, ArgType, State) -> end; neg -> case Mode of - opaque -> + opaque -> Struct = erl_types:t_opaque_structure(ArgType), case t_is_none(t_subtract(Struct, Type)) of true -> error; false -> {ok, ArgType, t_atom(false)} end; - structured -> + structured -> Sub = t_subtract(ArgType, Type), case t_is_none(Sub) of true -> error; @@ -1976,7 +1977,7 @@ handle_guard_comp(Guard, Comp, Map, Env, Eval, State) -> error -> signal_guard_fail(Guard, ArgTypes, State); {ok, NewMap} -> {NewMap, t_atom(true)} end; - {_, _} -> + {_, _} -> handle_guard_gen_fun({erlang, Comp, 2}, Guard, Map, Env, Eval, State) end. @@ -2023,7 +2024,7 @@ handle_guard_is_function(Guard, Map, Env, Eval, State) -> end, FunType = t_inf(FunType0, FunTypeConstr), case t_is_none(FunType) of - true -> + true -> case Eval of pos -> signal_guard_fail(Guard, ArgTypes0, State); neg -> {Map1, t_atom(false)}; @@ -2059,16 +2060,16 @@ handle_guard_is_record(Guard, Map, Env, Eval, State) -> end, Type = t_inf(NewTupleType, RecType, Mode), case t_is_none(Type) of - true -> + true -> case Eval of - pos -> signal_guard_fail(Guard, - [RecType, t_from_term(Tag), + pos -> signal_guard_fail(Guard, + [RecType, t_from_term(Tag), t_from_term(Arity)], State); neg -> {Map1, t_atom(false)}; dont_know -> {Map1, t_atom(false)} end; - false -> + false -> case Eval of pos -> {enter_type(Rec, Type, Map1), t_atom(true)}; neg -> {Map1, t_atom(false)}; @@ -2081,17 +2082,17 @@ handle_guard_eq(Guard, Map, Env, Eval, State) -> case {cerl:type(Arg1), cerl:type(Arg2)} of {literal, literal} -> case cerl:concrete(Arg1) =:= cerl:concrete(Arg2) of - true -> - if + true -> + if Eval =:= pos -> {Map, t_atom(true)}; Eval =:= neg -> throw({fail, none}); Eval =:= dont_know -> {Map, t_atom(true)} end; false -> - if + if Eval =:= neg -> {Map, t_atom(false)}; Eval =:= dont_know -> {Map, t_atom(false)}; - Eval =:= pos -> + Eval =:= pos -> ArgTypes = [t_from_term(cerl:concrete(Arg1)), t_from_term(cerl:concrete(Arg2))], signal_guard_fail(Guard, ArgTypes, State) @@ -2146,7 +2147,7 @@ handle_guard_eqeq(Guard, Map, Env, Eval, State) -> false -> if Eval =:= neg -> {Map, t_atom(false)}; Eval =:= dont_know -> {Map, t_atom(false)}; - Eval =:= pos -> + Eval =:= pos -> ArgTypes = [t_from_term(cerl:concrete(Arg1)), t_from_term(cerl:concrete(Arg2))], signal_guard_fail(Guard, ArgTypes, State) @@ -2163,7 +2164,7 @@ handle_guard_eqeq(Guard, Map, Env, Eval, State) -> bind_eqeq_guard(Guard, Arg1, Arg2, Map, Env, Eval, State) -> {Map1, Type1} = bind_guard(Arg1, Map, Env, dont_know, State), {Map2, Type2} = bind_guard(Arg2, Map1, Env, dont_know, State), - ?debug("Types are:~s =:= ~s\n", [t_to_string(Type1), + ?debug("Types are:~s =:= ~s\n", [t_to_string(Type1), t_to_string(Type2)]), Inf = t_inf(Type1, Type2), case t_is_none(Inf) of @@ -2204,15 +2205,15 @@ bind_eqeq_guard_lit_other(Guard, Arg1, Arg2, Map, Env, State) -> {_, Type} = MT = bind_guard(Arg2, Map, Env, pos, State), case t_is_atom(true, Type) of true -> MT; - false -> + false -> {_, Type0} = bind_guard(Arg2, Map, Env, dont_know, State), signal_guard_fail(Guard, [Type0, t_atom(true)], State) end; - false -> + false -> {Map1, Type} = bind_guard(Arg2, Map, Env, neg, State), case t_is_atom(false, Type) of true -> {Map1, t_atom(true)}; - false -> + false -> {_, Type0} = bind_guard(Arg2, Map, Env, dont_know, State), signal_guard_fail(Guard, [Type0, t_atom(true)], State) end; @@ -2244,11 +2245,11 @@ handle_guard_and(Guard, Map, Env, Eval, State) -> end end; neg -> - {Map1, Type1} = + {Map1, Type1} = try bind_guard(Arg1, Map, Env, neg, State) catch throw:{fail, _} -> bind_guard(Arg2, Map, Env, pos, State) end, - {Map2, Type2} = + {Map2, Type2} = try bind_guard(Arg1, Map, Env, neg, State) catch throw:{fail, _} -> bind_guard(Arg2, Map, Env, pos, State) end, @@ -2274,18 +2275,18 @@ handle_guard_or(Guard, Map, Env, Eval, State) -> [Arg1, Arg2] = cerl:call_args(Guard), case Eval of pos -> - {Map1, Bool1} = + {Map1, Bool1} = try bind_guard(Arg1, Map, Env, pos, State) - catch + catch throw:{fail,_} -> bind_guard(Arg1, Map, Env, dont_know, State) end, - {Map2, Bool2} = + {Map2, Bool2} = try bind_guard(Arg2, Map, Env, pos, State) - catch + catch throw:{fail,_} -> bind_guard(Arg2, Map, Env, dont_know, State) end, case ((t_is_atom(true, Bool1) andalso t_is_boolean(Bool2)) - orelse + orelse (t_is_atom(true, Bool2) andalso t_is_boolean(Bool1))) of true -> {join_maps([Map1, Map2], Map), t_atom(true)}; false -> throw({fail, none}) @@ -2313,19 +2314,19 @@ handle_guard_or(Guard, Map, Env, Eval, State) -> handle_guard_not(Guard, Map, Env, Eval, State) -> [Arg] = cerl:call_args(Guard), case Eval of - neg -> + neg -> {Map1, Type} = bind_guard(Arg, Map, Env, pos, State), case t_is_atom(true, Type) of true -> {Map1, t_atom(false)}; false -> throw({fail, none}) end; - pos -> + pos -> {Map1, Type} = bind_guard(Arg, Map, Env, neg, State), case t_is_atom(false, Type) of true -> {Map1, t_atom(true)}; false -> throw({fail, none}) end; - dont_know -> + dont_know -> {Map1, Type} = bind_guard(Arg, Map, Env, dont_know, State), Bool = t_inf(Type, t_boolean()), case t_is_none(Bool) of @@ -2357,10 +2358,10 @@ signal_guard_fail(Guard, ArgTypes, State) -> MFA = {cerl:atom_val(cerl:call_module(Guard)), F, length(Args)}, Msg = case is_infix_op(MFA) of - true -> + true -> [ArgType1, ArgType2] = ArgTypes, [Arg1, Arg2] = Args, - {guard_fail, [format_args_1([Arg1], [ArgType1], State), + {guard_fail, [format_args_1([Arg1], [ArgType1], State), atom_to_list(F), format_args_1([Arg2], [ArgType2], State)]}; false -> @@ -2383,7 +2384,7 @@ is_infix_op({M, F, A}) when is_atom(M), is_atom(F), no_return(). signal_guard_fatal_fail(Guard, ArgTypes, State) -> - Args = cerl:call_args(Guard), + Args = cerl:call_args(Guard), F = cerl:atom_val(cerl:call_name(Guard)), Msg = mk_guard_msg(F, Args, ArgTypes, State), throw({fatal_fail, {Guard, Msg}}). @@ -2394,11 +2395,11 @@ mk_guard_msg(F, Args, ArgTypes, State) -> true -> {opaque_guard, FArgs}; false -> {guard_fail, FArgs} end. - + bind_guard_case_clauses(Arg, Clauses, Map, Env, Eval, State) -> Clauses1 = filter_fail_clauses(Clauses), {GenMap, GenArgType} = bind_guard(Arg, Map, Env, dont_know, State), - bind_guard_case_clauses(GenArgType, GenMap, Arg, Clauses1, Map, Env, Eval, + bind_guard_case_clauses(GenArgType, GenMap, Arg, Clauses1, Map, Env, Eval, t_none(), [], State). filter_fail_clauses([Clause|Left]) -> @@ -2415,7 +2416,7 @@ filter_fail_clauses([Clause|Left]) -> filter_fail_clauses([]) -> []. -bind_guard_case_clauses(GenArgType, GenMap, ArgExpr, [Clause|Left], +bind_guard_case_clauses(GenArgType, GenMap, ArgExpr, [Clause|Left], Map, Env, Eval, AccType, AccMaps, State) -> Pats = cerl:clause_pats(Clause), {NewMap0, ArgType} = @@ -2429,7 +2430,7 @@ bind_guard_case_clauses(GenArgType, GenMap, ArgExpr, [Clause|Left], false -> bind_guard(ArgExpr, Map, Env, neg, State); _ -> {GenMap, GenArgType} end - catch + catch throw:{fail, _} -> {none, GenArgType} end; false -> @@ -2459,7 +2460,7 @@ bind_guard_case_clauses(GenArgType, GenMap, ArgExpr, [Clause|Left], NewGenArgType = t_subtract(GenArgType, GenPatType), case (NewMap1 =:= none) orelse t_is_none(GenArgType) of true -> - bind_guard_case_clauses(NewGenArgType, GenMap, ArgExpr, Left, Map, Env, + bind_guard_case_clauses(NewGenArgType, GenMap, ArgExpr, Left, Map, Env, Eval, AccType, AccMaps, State); false -> {NewAccType, NewAccMaps} = @@ -2469,15 +2470,15 @@ bind_guard_case_clauses(GenArgType, GenMap, ArgExpr, [Clause|Left], true -> throw({fail, none}); false -> ok end, - {NewMap3, CType} = bind_guard(cerl:clause_body(Clause), NewMap2, + {NewMap3, CType} = bind_guard(cerl:clause_body(Clause), NewMap2, Env, Eval, State), case Eval of - pos -> + pos -> case t_is_atom(true, CType) of true -> ok; false -> throw({fail, none}) end; - neg -> + neg -> case t_is_atom(false, CType) of true -> ok; false -> throw({fail, none}) @@ -2489,10 +2490,10 @@ bind_guard_case_clauses(GenArgType, GenMap, ArgExpr, [Clause|Left], catch throw:{fail, _What} -> {AccType, AccMaps} end, - bind_guard_case_clauses(NewGenArgType, GenMap, ArgExpr, Left, Map, Env, + bind_guard_case_clauses(NewGenArgType, GenMap, ArgExpr, Left, Map, Env, Eval, NewAccType, NewAccMaps, State) end; -bind_guard_case_clauses(_GenArgType, _GenMap, _ArgExpr, [], Map, _Env, _Eval, +bind_guard_case_clauses(_GenArgType, _GenMap, _ArgExpr, [], Map, _Env, _Eval, AccType, AccMaps, _State) -> case t_is_none(AccType) of true -> throw({fail, none}); @@ -2578,7 +2579,7 @@ enter_type(Key, Val, {Map, Subst} = MS) -> enter_subst(Key, Val, {Map, Subst} = MS) -> KeyLabel = get_label(Key), case cerl:is_literal(Val) of - true -> + true -> NewMap = dict:store(KeyLabel, literal_type(Val), Map), {NewMap, Subst}; false -> @@ -2600,13 +2601,13 @@ enter_subst(Key, Val, {Map, Subst} = MS) -> end end. -lookup_type(Key, {Map, Subst}) -> +lookup_type(Key, {Map, Subst}) -> lookup(Key, Map, Subst, t_none()). lookup(Key, Map, Subst, AnyNone) -> case cerl:is_literal(Key) of true -> literal_type(Key); - false -> + false -> Label = get_label(Key), case dict:find(Label, Subst) of {ok, NewKey} -> lookup(NewKey, Map, Subst, AnyNone); @@ -2671,7 +2672,7 @@ get_label(T) -> t_is_simple(ArgType) -> t_is_atom(ArgType) orelse t_is_number(ArgType) orelse t_is_port(ArgType) - orelse t_is_pid(ArgType) orelse t_is_reference(ArgType) + orelse t_is_pid(ArgType) orelse t_is_reference(ArgType) orelse t_is_nil(ArgType). %% t_is_structured(ArgType) -> @@ -2689,8 +2690,8 @@ is_call_to_send(Tree) -> Mod = cerl:call_module(Tree), Name = cerl:call_name(Tree), Arity = cerl:call_arity(Tree), - cerl:is_c_atom(Mod) - andalso cerl:is_c_atom(Name) + cerl:is_c_atom(Mod) + andalso cerl:is_c_atom(Name) andalso (cerl:atom_val(Name) =:= '!') andalso (cerl:atom_val(Mod) =:= erlang) andalso (Arity =:= 2) @@ -2716,7 +2717,7 @@ filter_match_fail([Clause] = Cls) -> filter_match_fail([H|T]) -> [H|filter_match_fail(T)]; filter_match_fail([]) -> - %% This can actually happen, for example in + %% This can actually happen, for example in %% receive after 1 -> ok end []. @@ -2733,9 +2734,11 @@ determine_mode(Type, Opaques) -> %%% =========================================================================== state__new(Callgraph, Tree, Plt, Module, Records, BehaviourTranslations) -> + Opaques = erl_types:module_builtin_opaques(Module) ++ + erl_types:t_opaque_from_records(Records), TreeMap = build_tree_map(Tree), Funs = dict:fetch_keys(TreeMap), - FunTab = init_fun_tab(Funs, dict:new(), TreeMap, Callgraph, Plt), + FunTab = init_fun_tab(Funs, dict:new(), TreeMap, Callgraph, Plt, Opaques), Work = init_work([get_label(Tree)]), Env = dict:store(top, map__new(), dict:new()), Opaques = erl_types:module_builtin_opaques(Module) ++ @@ -2743,12 +2746,12 @@ state__new(Callgraph, Tree, Plt, Module, Records, BehaviourTranslations) -> #state{callgraph = Callgraph, envs = Env, fun_tab = FunTab, opaques = Opaques, plt = Plt, races = dialyzer_races:new(), records = Records, warning_mode = false, warnings = [], work = Work, tree_map = TreeMap, - module = Module, behaviour_api_info = BehaviourTranslations}. + module = Module, behaviour_api_dict = BehaviourTranslations}. state__mark_fun_as_handled(#state{fun_tab = FunTab} = State, Fun0) -> Fun = get_label(Fun0), case dict:find(Fun, FunTab) of - {ok, {not_handled, Entry}} -> + {ok, {not_handled, Entry}} -> State#state{fun_tab = dict:store(Fun, Entry, FunTab)}; {ok, {_, _}} -> State @@ -2802,7 +2805,7 @@ state__add_warning(State, Tag, Tree, Msg) -> state__add_warning(#state{warning_mode = false} = State, _, _, _, _) -> State; -state__add_warning(#state{warnings = Warnings, warning_mode = true} = State, +state__add_warning(#state{warnings = Warnings, warning_mode = true} = State, Tag, Tree, Msg, Force) -> Ann = cerl:get_ann(Tree), case Force of @@ -2850,7 +2853,7 @@ state__get_warnings(#state{tree_map = TreeMap, fun_tab = FunTab, {Name, Contract} = case dialyzer_callgraph:lookup_name(FunLbl, Callgraph) of error -> {[], none}; - {ok, {_M, F, A} = MFA} -> + {ok, {_M, F, A} = MFA} -> {[F, A], dialyzer_plt:lookup_contract(Plt, MFA)} end, case t_is_none(Ret) of @@ -2868,19 +2871,19 @@ state__get_warnings(#state{tree_map = TreeMap, fun_tab = FunTab, case classify_returns(Fun) of no_match -> Msg = {no_return, [no_match|Name]}, - state__add_warning(AccState, ?WARN_RETURN_NO_RETURN, + state__add_warning(AccState, ?WARN_RETURN_NO_RETURN, Fun, Msg); only_explicit -> Msg = {no_return, [only_explicit|Name]}, - state__add_warning(AccState, ?WARN_RETURN_ONLY_EXIT, + state__add_warning(AccState, ?WARN_RETURN_ONLY_EXIT, Fun, Msg); only_normal -> Msg = {no_return, [only_normal|Name]}, - state__add_warning(AccState, ?WARN_RETURN_NO_RETURN, + state__add_warning(AccState, ?WARN_RETURN_NO_RETURN, Fun, Msg); both -> Msg = {no_return, [both|Name]}, - state__add_warning(AccState, ?WARN_RETURN_NO_RETURN, + state__add_warning(AccState, ?WARN_RETURN_NO_RETURN, Fun, Msg) end; false -> @@ -2918,10 +2921,10 @@ state__lookup_name(Fun, #state{callgraph = Callgraph}) -> state__lookup_record(Tag, Arity, #state{records = Records}) -> case erl_types:lookup_record(Tag, Arity, Records) of - {ok, Fields} -> + {ok, Fields} -> {ok, t_tuple([t_atom(Tag)| [FieldType || {_FieldName, FieldType} <- Fields]])}; - error -> + error -> error end. @@ -2944,15 +2947,15 @@ build_tree_map(Tree) -> end, cerl_trees:fold(Fun, dict:new(), Tree). -init_fun_tab([top|Left], Dict, TreeMap, Callgraph, Plt) -> +init_fun_tab([top|Left], Dict, TreeMap, Callgraph, Plt, Opaques) -> NewDict = dict:store(top, {not_handled, {[], t_none()}}, Dict), - init_fun_tab(Left, NewDict, TreeMap, Callgraph, Plt); -init_fun_tab([Fun|Left], Dict, TreeMap, Callgraph, Plt) -> + init_fun_tab(Left, NewDict, TreeMap, Callgraph, Plt, Opaques); +init_fun_tab([Fun|Left], Dict, TreeMap, Callgraph, Plt, Opaques) -> Arity = cerl:fun_arity(dict:fetch(Fun, TreeMap)), FunEntry = case dialyzer_callgraph:is_escaping(Fun, Callgraph) of true -> - Args = lists:duplicate(Arity, t_any()), + Args = lists:duplicate(Arity, t_any()), case lookup_fun_sig(Fun, Callgraph, Plt) of none -> {Args, t_unit()}; {value, {RetType, _}} -> @@ -2964,8 +2967,8 @@ init_fun_tab([Fun|Left], Dict, TreeMap, Callgraph, Plt) -> false -> {lists:duplicate(Arity, t_none()), t_unit()} end, NewDict = dict:store(Fun, {not_handled, FunEntry}, Dict), - init_fun_tab(Left, NewDict, TreeMap, Callgraph, Plt); -init_fun_tab([], Dict, _TreeMap, _Callgraph, _Plt) -> + init_fun_tab(Left, NewDict, TreeMap, Callgraph, Plt, Opaques); +init_fun_tab([], Dict, _TreeMap, _Callgraph, _Plt, _Opaques) -> Dict. state__update_fun_env(Tree, Map, #state{envs = Envs} = State) -> @@ -2992,7 +2995,7 @@ state__all_fun_types(#state{fun_tab = FunTab}) -> dict:map(fun(_Fun, {Args, Ret}) -> t_fun(Args, Ret)end, Tab1). state__fun_type(Fun, #state{fun_tab = FunTab}) -> - Label = + Label = if is_integer(Fun) -> Fun; true -> get_label(Fun) end, @@ -3003,10 +3006,10 @@ state__fun_type(Fun, #state{fun_tab = FunTab}) -> t_fun(A, R) end. -state__update_fun_entry(Tree, ArgTypes, Out0, +state__update_fun_entry(Tree, ArgTypes, Out0, #state{fun_tab=FunTab, callgraph=CG, plt=Plt} = State)-> Fun = get_label(Tree), - Out1 = + Out1 = if Fun =:= top -> Out0; true -> case lookup_fun_sig(Fun, CG, Plt) of @@ -3018,15 +3021,15 @@ state__update_fun_entry(Tree, ArgTypes, Out0, case dict:find(Fun, FunTab) of {ok, {ArgTypes, OldOut}} -> case t_is_equal(OldOut, Out) of - true -> - ?debug("Fixpoint for ~w: ~s\n", - [state__lookup_name(Fun, State), + true -> + ?debug("Fixpoint for ~w: ~s\n", + [state__lookup_name(Fun, State), t_to_string(t_fun(ArgTypes, Out))]), State; false -> NewEntry = {ArgTypes, Out}, - ?debug("New Entry for ~w: ~s\n", - [state__lookup_name(Fun, State), + ?debug("New Entry for ~w: ~s\n", + [state__lookup_name(Fun, State), t_to_string(t_fun(ArgTypes, Out))]), NewFunTab = dict:store(Fun, NewEntry, FunTab), State1 = State#state{fun_tab = NewFunTab}, @@ -3035,8 +3038,8 @@ state__update_fun_entry(Tree, ArgTypes, Out0, {ok, {NewArgTypes, _OldOut}} -> %% Can only happen in self-recursive functions. Only update the out type. NewEntry = {NewArgTypes, Out}, - ?debug("New Entry for ~w: ~s\n", - [state__lookup_name(Fun, State), + ?debug("New Entry for ~w: ~s\n", + [state__lookup_name(Fun, State), t_to_string(t_fun(NewArgTypes, Out))]), NewFunTab = dict:store(Fun, NewEntry, FunTab), State1 = State#state{fun_tab = NewFunTab}, @@ -3055,9 +3058,9 @@ state__add_work_from_fun(Tree, #state{callgraph = Callgraph, MFAList -> LabelList = [dialyzer_callgraph:lookup_label(MFA, Callgraph) || MFA <- MFAList], - %% Must filter the result for results in this module. + %% Must filter the result for results in this module. FilteredList = [L || {ok, L} <- LabelList, dict:is_key(L, TreeMap)], - ?debug("~w: Will try to add:~w\n", + ?debug("~w: Will try to add:~w\n", [state__lookup_name(get_label(Tree), State), MFAList]), lists:foldl(fun(L, AccState) -> state__add_work(L, AccState) @@ -3088,15 +3091,15 @@ state__fun_info(external, #state{}) -> external; state__fun_info({_, _, _} = MFA, #state{plt = PLT}) -> {MFA, - dialyzer_plt:lookup(PLT, MFA), + dialyzer_plt:lookup(PLT, MFA), dialyzer_plt:lookup_contract(PLT, MFA), t_any()}; state__fun_info(Fun, #state{callgraph = CG, fun_tab = FunTab, plt = PLT}) -> {Sig, Contract} = case dialyzer_callgraph:lookup_name(Fun, CG) of - error -> + error -> {dialyzer_plt:lookup(PLT, Fun), none}; - {ok, MFA} -> + {ok, MFA} -> {dialyzer_plt:lookup(PLT, MFA), dialyzer_plt:lookup_contract(PLT, MFA)} end, LocalRet = @@ -3124,18 +3127,18 @@ state__find_apply_return(Tree, #state{callgraph = Callgraph} = State) -> forward_args(Fun, ArgTypes, #state{work = Work, fun_tab = FunTab} = State) -> {OldArgTypes, OldOut, Fixpoint} = case dict:find(Fun, FunTab) of - {ok, {not_handled, {OldArgTypes0, OldOut0}}} -> + {ok, {not_handled, {OldArgTypes0, OldOut0}}} -> {OldArgTypes0, OldOut0, false}; {ok, {OldArgTypes0, OldOut0}} -> - {OldArgTypes0, OldOut0, + {OldArgTypes0, OldOut0, t_is_subtype(t_product(ArgTypes), t_product(OldArgTypes0))} end, case Fixpoint of true -> State; - false -> + false -> NewArgTypes = [t_sup(X, Y) || {X, Y} <- lists:zip(ArgTypes, OldArgTypes)], NewWork = add_work(Fun, Work), - ?debug("~w: forwarding args ~s\n", + ?debug("~w: forwarding args ~s\n", [state__lookup_name(Fun, State), t_to_string(t_product(NewArgTypes))]), NewFunTab = dict:store(Fun, {NewArgTypes, OldOut}, FunTab), @@ -3250,7 +3253,7 @@ get_file([_|Tail]) -> get_file(Tail). is_compiler_generated(Ann) -> lists:member(compiler_generated, Ann) orelse (get_line(Ann) < 1). --spec format_args([term()], [erl_types:erl_type()], state()) -> +-spec format_args([cerl:cerl()], [erl_types:erl_type()], state()) -> nonempty_string(). format_args([], [], _State) -> @@ -3258,9 +3261,6 @@ format_args([], [], _State) -> format_args(ArgList, TypeList, State) -> "(" ++ format_args_1(ArgList, TypeList, State) ++ ")". --spec format_args_1([term(),...], [erl_types:erl_type(),...], state()) -> - string(). - format_args_1([Arg], [Type], State) -> format_arg(Arg) ++ format_type(Type, State); format_args_1([Arg|Args], [Type|Types], State) -> @@ -3293,6 +3293,11 @@ format_arg(Arg) -> format_type(Type, #state{records = R}) -> t_to_string(Type, R). +-spec format_field_diffs(erl_types:erl_type(), state()) -> string(). + +format_field_diffs(RecConstruction, #state{records = R}) -> + erl_types:record_field_diffs_to_string(RecConstruction, R). + -spec format_sig_args(erl_types:erl_type(), state()) -> string(). format_sig_args(Type, #state{records = R}) -> @@ -3300,12 +3305,12 @@ format_sig_args(Type, #state{records = R}) -> case SigArgs of [] -> "()"; [SArg|SArgs] -> - lists:flatten("(" ++ t_to_string(SArg, R) + lists:flatten("(" ++ t_to_string(SArg, R) ++ ["," ++ t_to_string(T, R) || T <- SArgs] ++ ")") end. format_cerl(Tree) -> - cerl_prettypr:format(cerl:set_ann(Tree, []), + cerl_prettypr:format(cerl:set_ann(Tree, []), [{hook, dialyzer_utils:pp_hook()}, {noann, true}, {paper, 100000}, %% These guys strip @@ -3368,7 +3373,7 @@ find_terminals(Tree) -> true -> M = cerl:concrete(M0), F = cerl:concrete(F0), - case (erl_bif_types:is_known(M, F, A) + case (erl_bif_types:is_known(M, F, A) andalso t_is_none(erl_bif_types:type(M, F, A))) of true -> {true, false}; false -> {false, true} @@ -3383,12 +3388,12 @@ find_terminals(Tree) -> letrec -> find_terminals(cerl:letrec_body(Tree)); literal -> {false, true}; primop -> {false, false}; %% match_fail, etc. are not explicit exits. - 'receive' -> + 'receive' -> Timeout = cerl:receive_timeout(Tree), Clauses = cerl:receive_clauses(Tree), case (cerl:is_literal(Timeout) andalso (cerl:concrete(Timeout) =:= infinity)) of - true -> + true -> if Clauses =:= [] -> {false, true}; %% A never ending receive. true -> find_terminals_list(Clauses) end; @@ -3456,11 +3461,11 @@ find_rec_warnings_tuple(Tree, State) -> TagVal = cerl:atom_val(Tag), case state__lookup_record(TagVal, length(Left), State) of error -> State; - {ok, Prototype} -> + {ok, Prototype} -> InfTupleType = t_inf(Prototype, TupleType), case t_is_none(InfTupleType) of true -> - Msg = {record_matching, + Msg = {record_matching, [format_patterns([Tree]), TagVal]}, state__add_warning(State, ?WARN_MATCHING, Tree, Msg); false -> @@ -3478,7 +3483,7 @@ find_rec_warnings_tuple(Tree, State) -> %%---------------------------------------------------------------------------- -ifdef(DEBUG_PP). -debug_pp(Tree, true) -> +debug_pp(Tree, true) -> io:put_chars(cerl_prettypr:format(Tree, [{hook, cerl_typean:pp_hook()}])), io:nl(), ok; diff --git a/lib/dialyzer/src/dialyzer_options.erl b/lib/dialyzer/src/dialyzer_options.erl index da0e1f9aaf..010625b7bd 100644 --- a/lib/dialyzer/src/dialyzer_options.erl +++ b/lib/dialyzer/src/dialyzer_options.erl @@ -184,7 +184,7 @@ build_options([], Options) -> assert_filenames(Term, [FileName|Left]) when length(FileName) >= 0 -> case filelib:is_file(FileName) orelse filelib:is_dir(FileName) of true -> ok; - false -> bad_option("No such file or directory", FileName) + false -> bad_option("No such file, directory or application", FileName) end, assert_filenames(Term, Left); assert_filenames(_Term, []) -> diff --git a/lib/dialyzer/src/dialyzer_plt.erl b/lib/dialyzer/src/dialyzer_plt.erl index c10375eea2..268ec4a5f0 100644 --- a/lib/dialyzer/src/dialyzer_plt.erl +++ b/lib/dialyzer/src/dialyzer_plt.erl @@ -21,7 +21,7 @@ %%%------------------------------------------------------------------- %%% File : dialyzer_plt.erl %%% Author : Tobias Lindahl <[email protected]> -%%% Description : Interface to display information in the persistent +%%% Description : Interface to display information in the persistent %%% lookup tables. %%% %%% Created : 23 Jul 2004 by Tobias Lindahl <[email protected]> @@ -101,7 +101,7 @@ new() -> #plt{}. --spec delete_module(plt(), module()) -> plt(). +-spec delete_module(plt(), atom()) -> plt(). delete_module(#plt{info = Info, types = Types, contracts = Contracts, exported_types = ExpTypes}, Mod) -> @@ -136,7 +136,7 @@ delete_contract_list(#plt{contracts = Contracts} = PLT, List) -> PLT#plt{contracts = table_delete_list(Contracts, List)}. %% -spec insert(plt(), mfa() | integer(), {_, _}) -> plt(). -%% +%% %% insert(#plt{info = Info} = PLT, Id, Types) -> %% PLT#plt{info = table_insert(Info, Id, Types)}. @@ -177,12 +177,12 @@ get_exported_types(#plt{exported_types = ExpTypes}) -> -type mfa_types() :: {mfa(), erl_types:erl_type(), [erl_types:erl_type()]}. --spec lookup_module(plt(), module()) -> 'none' | {'value', [mfa_types()]}. +-spec lookup_module(plt(), atom()) -> 'none' | {'value', [mfa_types()]}. lookup_module(#plt{info = Info}, M) when is_atom(M) -> table_lookup_module(Info, M). --spec contains_module(plt(), module()) -> boolean(). +-spec contains_module(plt(), atom()) -> boolean(). contains_module(#plt{info = Info, contracts = Cs}, M) when is_atom(M) -> table_contains_module(Info, M) orelse table_contains_module(Cs, M). @@ -190,7 +190,7 @@ contains_module(#plt{info = Info, contracts = Cs}, M) when is_atom(M) -> -spec contains_mfa(plt(), mfa()) -> boolean(). contains_mfa(#plt{info = Info, contracts = Contracts}, MFA) -> - (table_lookup(Info, MFA) =/= none) + (table_lookup(Info, MFA) =/= none) orelse (table_lookup(Contracts, MFA) =/= none). -spec get_default_plt() -> file:filename(). @@ -221,10 +221,10 @@ from_file(FileName, ReturnInfo) -> case get_record_from_file(FileName) of {ok, Rec} -> case check_version(Rec) of - error -> + error -> Msg = io_lib:format("Old PLT file ~s\n", [FileName]), error(Msg); - ok -> + ok -> Plt = #plt{info = Rec#file_plt.info, types = Rec#file_plt.types, contracts = Rec#file_plt.contracts, @@ -238,12 +238,12 @@ from_file(FileName, ReturnInfo) -> end end; {error, Reason} -> - error(io_lib:format("Could not read PLT file ~s: ~p\n", + error(io_lib:format("Could not read PLT file ~s: ~p\n", [FileName, Reason])) end. -type inc_file_err_rsn() :: 'no_such_file' | 'read_error'. --spec included_files(file:filename()) -> {'ok', [file:filename()]} +-spec included_files(file:filename()) -> {'ok', [file:filename()]} | {'error', inc_file_err_rsn()}. included_files(FileName) -> @@ -268,12 +268,12 @@ get_record_from_file(FileName) -> try binary_to_term(Bin) of #file_plt{} = FilePLT -> {ok, FilePLT}; _ -> {error, not_valid} - catch + catch _:_ -> {error, not_valid} end; {error, enoent} -> {error, no_such_file}; - {error, _} -> + {error, _} -> {error, read_error} end. @@ -295,9 +295,9 @@ to_file(FileName, #plt{info = Info, types = Types, contracts = Contracts, exported_types = ExpTypes}, ModDeps, {MD5, OldModDeps}) -> - NewModDeps = dict:merge(fun(_Key, OldVal, NewVal) -> + NewModDeps = dict:merge(fun(_Key, OldVal, NewVal) -> ordsets:union(OldVal, NewVal) - end, + end, OldModDeps, ModDeps), ImplMd5 = compute_implementation_md5(), Record = #file_plt{version = ?VSN, @@ -312,7 +312,7 @@ to_file(FileName, case file:write_file(FileName, Bin) of ok -> ok; {error, Reason} -> - Msg = io_lib:format("Could not write PLT file ~s: ~w\n", + Msg = io_lib:format("Could not write PLT file ~s: ~w\n", [FileName, Reason]), throw({dialyzer_error, Msg}) end. @@ -320,8 +320,8 @@ to_file(FileName, -type md5_diff() :: [{'differ', atom()} | {'removed', atom()}]. -type check_error() :: 'not_valid' | 'no_such_file' | 'read_error' | {'no_file_to_remove', file:filename()}. - --spec check_plt(file:filename(), [file:filename()], [file:filename()]) -> + +-spec check_plt(file:filename(), [file:filename()], [file:filename()]) -> 'ok' | {'error', check_error()} | {'differ', [file_md5()], md5_diff(), mod_deps()} @@ -331,7 +331,7 @@ check_plt(FileName, RemoveFiles, AddFiles) -> case get_record_from_file(FileName) of {ok, #file_plt{file_md5_list = Md5, mod_deps = ModDeps} = Rec} -> case check_version(Rec) of - ok -> + ok -> case compute_new_md5(Md5, RemoveFiles, AddFiles) of ok -> ok; {differ, NewMd5, DiffMd5} -> {differ, NewMd5, DiffMd5, ModDeps}; @@ -388,7 +388,7 @@ compute_md5_from_files(Files) -> compute_md5_from_file(File) -> case filelib:is_regular(File) of - false -> + false -> Msg = io_lib:format("Not a regular file: ~s\n", [File]), throw({dialyzer_error, Msg}); true -> @@ -419,7 +419,7 @@ init_md5_list_1([{File, _Md5}|Md5Left], [{remove, File}|DiffLeft], Acc) -> init_md5_list_1(Md5Left, DiffLeft, Acc); init_md5_list_1([{File, _Md5} = Entry|Md5Left], [{add, File}|DiffLeft], Acc) -> init_md5_list_1(Md5Left, DiffLeft, [Entry|Acc]); -init_md5_list_1([{File1, _Md5} = Entry|Md5Left] = Md5List, +init_md5_list_1([{File1, _Md5} = Entry|Md5Left] = Md5List, [{Tag, File2}|DiffLeft] = DiffList, Acc) -> case File1 < File2 of true -> init_md5_list_1(Md5Left, DiffList, [Entry|Acc]); @@ -450,7 +450,7 @@ get_specs(#plt{info = Info}) -> beam_file_to_module(Filename) -> list_to_atom(filename:basename(Filename, ".beam")). --spec get_specs(plt(), module(), atom(), arity_patt()) -> 'none' | string(). +-spec get_specs(plt(), atom(), atom(), arity_patt()) -> 'none' | string(). get_specs(#plt{info = Info}, M, F, A) when is_atom(M), is_atom(F) -> MFA = {M, F, A}, @@ -460,7 +460,7 @@ get_specs(#plt{info = Info}, M, F, A) when is_atom(M), is_atom(F) -> end. create_specs([{{M, F, _A}, {Ret, Args}}|Left], M) -> - [io_lib:format("-spec ~w(~s) -> ~s\n", + [io_lib:format("-spec ~w(~s) -> ~s\n", [F, expand_args(Args), erl_types:t_to_string(Ret)]) | create_specs(Left, M)]; create_specs(List = [{{M, _F, _A}, {_Ret, _Args}}| _], _M) -> @@ -516,7 +516,7 @@ table_insert_list(Plt, [{Key, Val}|Left]) -> table_insert_list(Plt, []) -> Plt. -table_insert(Plt, Key, {_Ret, _Arg} = Obj) -> +table_insert(Plt, Key, {_Ret, _Arg} = Obj) -> dict:store(Key, Obj, Plt); table_insert(Plt, Key, #contract{} = C) -> dict:store(Key, C, Plt). @@ -592,7 +592,7 @@ pp_non_returning() -> [M, F, dialyzer_utils:format_sig(Type)]) end, lists:sort(None)). --spec pp_mod(module()) -> 'ok'. +-spec pp_mod(atom()) -> 'ok'. pp_mod(Mod) when is_atom(Mod) -> PltFile = get_default_plt(), diff --git a/lib/dialyzer/src/dialyzer_races.erl b/lib/dialyzer/src/dialyzer_races.erl index fb16e6a75f..ec8d613b96 100644 --- a/lib/dialyzer/src/dialyzer_races.erl +++ b/lib/dialyzer/src/dialyzer_races.erl @@ -21,7 +21,7 @@ %%%---------------------------------------------------------------------- %%% File : dialyzer_races.erl %%% Author : Maria Christakis <[email protected]> -%%% Description : Utility functions for race condition detection +%%% Description : Utility functions for race condition detection %%% %%% Created : 21 Nov 2008 by Maria Christakis <[email protected]> %%%---------------------------------------------------------------------- @@ -39,7 +39,7 @@ let_tag_new/2, new/0, put_curr_fun/3, put_fun_args/2, put_race_analysis/2, put_race_list/3]). --export_type([races/0]). +-export_type([races/0, mfa_or_funlbl/0, core_vars/0]). -include("dialyzer.hrl"). @@ -82,7 +82,7 @@ -type call() :: 'whereis' | 'register' | 'unregister' | 'ets_new' | 'ets_lookup' | 'ets_insert' | 'mnesia_dirty_read1' | 'mnesia_dirty_read2' | 'mnesia_dirty_write1' - | 'mnesia_dirty_write2' | 'function_call'. + | 'mnesia_dirty_write2' | 'function_call'. -type race_tag() :: 'whereis_register' | 'whereis_unregister' | 'ets_lookup_insert' | 'mnesia_dirty_read_write'. @@ -159,7 +159,7 @@ %%% =========================================================================== -spec store_race_call(mfa_or_funlbl(), [erl_types:erl_type()], [core_vars()], - file_line(), dialyzer_dataflow:state()) -> + file_line(), dialyzer_dataflow:state()) -> dialyzer_dataflow:state(). store_race_call(Fun, ArgTypes, Args, FileLine, State) -> @@ -168,7 +168,7 @@ store_race_call(Fun, ArgTypes, Args, FileLine, State) -> CurrFunLabel = Races#races.curr_fun_label, RaceTags = Races#races.race_tags, CleanState = dialyzer_dataflow:state__records_only(State), - {NewRaceList, NewRaceListSize, NewRaceTags, NewTable} = + {NewRaceList, NewRaceListSize, NewRaceTags, NewTable} = case CurrFun of {_Module, module_info, A} when A =:= 0 orelse A =:= 1 -> {[], 0, RaceTags, no_t}; @@ -424,7 +424,7 @@ fixup_race_forward_pullout(CurrFun, CurrFunLabel, Calls, Code, RaceList, Races = dialyzer_dataflow:state__get_races(State), {RetCurrFun, RetCurrFunLabel, RetCalls, RetCode, RetRaceList, RetRaceVarMap, RetFunDefVars, RetFunCallVars, - RetFunArgTypes, RetNestingLevel} = + RetFunArgTypes, RetNestingLevel} = fixup_race_forward_helper(NewCurrFun, NewCurrFunLabel, Fun, Int, NewCalls, NewCalls, [#curr_fun{status = out, mfa = NewCurrFun, @@ -578,7 +578,7 @@ fixup_race_forward(CurrFun, CurrFunLabel, Calls, Code, RaceList, RaceTag -> PublicTables = dialyzer_callgraph:get_public_tables(Callgraph), NamedTables = dialyzer_callgraph:get_named_tables(Callgraph), - WarnVarArgs1 = + WarnVarArgs1 = var_type_analysis(FunDefVars, FunArgTypes, WarnVarArgs, RaceWarnTag, RaceVarMap, dialyzer_dataflow:state__records_only(State)), @@ -600,7 +600,7 @@ fixup_race_forward(CurrFun, CurrFunLabel, Calls, Code, RaceList, [#warn_call{call_name = ets_insert, args = WarnVarArgs, var_map = RaceVarMap}], [Tab, Names, _, _] = WarnVarArgs, - case IsPublic orelse + case IsPublic orelse compare_var_list(Tab, PublicTables, RaceVarMap) orelse length(Names -- NamedTables) < length(Names) of @@ -638,7 +638,7 @@ fixup_race_forward(CurrFun, CurrFunLabel, Calls, Code, RaceList, #curr_fun{mfa = CurrFun2, label = CurrFunLabel2, var_map = RaceVarMap2, def_vars = FunDefVars2, call_vars = FunCallVars2, arg_types = FunArgTypes2}, - Code2, NestingLevel2} = + Code2, NestingLevel2} = remove_clause(NewRL, #curr_fun{mfa = CurrFun, label = CurrFunLabel, var_map = RaceVarMap1, @@ -650,7 +650,7 @@ fixup_race_forward(CurrFun, CurrFunLabel, Calls, Code, RaceList, RaceVarMap2, FunDefVars2, FunCallVars2, FunArgTypes2, NestingLevel2, false}; false -> - {CurrFun, CurrFunLabel, Tail, NewRL, RaceVarMap1, + {CurrFun, CurrFunLabel, Tail, NewRL, RaceVarMap1, FunDefVars, FunCallVars, FunArgTypes, NewNL, false} end; #end_clause{arg = Arg, pats = Pats, guard = Guard} -> @@ -895,7 +895,7 @@ do_clause(RaceList, WarnVarArgs, RaceWarnTag, RaceVarMap, CurrLevel, PublicTables, NamedTables) -> {DepList, IsPublic, Continue} = get_deplist_paths(fixup_case_path(RaceList, 0), WarnVarArgs, - RaceWarnTag, RaceVarMap, CurrLevel, + RaceWarnTag, RaceVarMap, CurrLevel, PublicTables, NamedTables), {fixup_case_rest_paths(RaceList, 0), DepList, IsPublic, Continue}. @@ -965,7 +965,7 @@ fixup_race_forward_helper(CurrFun, CurrFunLabel, Fun, FunLabel, #curr_fun{mfa = NewCurrFun, label = NewCurrFunLabel, var_map = NewRaceVarMap, def_vars = NewFunDefVars, call_vars = NewFunCallVars, arg_types = NewFunArgTypes}, - NewCode, NewNestingLevel} = + NewCode, NewNestingLevel} = remove_clause(RaceList, #curr_fun{mfa = CurrFun, label = CurrFunLabel, var_map = RaceVarMap, def_vars = FunDefVars, call_vars = FunCallVars, @@ -997,7 +997,7 @@ fixup_race_forward_helper(CurrFun, CurrFunLabel, Fun, FunLabel, arg_types = NewFunTypes}], [#curr_fun{status = in, mfa = Fun, label = FunLabel, var_map = NewRaceVarMap, - def_vars = Args, call_vars = NewFunArgs, + def_vars = Args, call_vars = NewFunArgs, arg_types = NewFunTypes}| lists:reverse(StateRaceList)] ++ RetC, NewRaceVarMap), @@ -1062,7 +1062,7 @@ fixup_race_backward(CurrFun, Calls, CallsToAnalyze, Parents, Height) -> case Height =:= 0 of true -> Parents; false -> - case Calls of + case Calls of [] -> case is_integer(CurrFun) orelse lists:member(CurrFun, Parents) of true -> Parents; @@ -1221,7 +1221,7 @@ are_bound_vars(Vars1, Vars2, RaceVarMap) -> callgraph__renew_tables(Table, Callgraph) -> case Table of {named, NameLabel, Names} -> - PTablesToAdd = + PTablesToAdd = case NameLabel of ?no_label -> []; _Other -> [NameLabel] @@ -1440,7 +1440,7 @@ lists_key_members_lists_helper(Elem, List, N) when is_integer(Elem) -> end; lists_key_members_lists_helper(_Elem, _List, _N) -> [0]. - + lists_key_replace(N, List, NewMember) -> {Before, [_|After]} = lists:split(N - 1, List), Before ++ [NewMember|After]. @@ -1490,7 +1490,7 @@ refine_race_helper(RaceCall, VarArgs, WarnVarArgs, RaceWarnTag, DependencyList, false -> DependencyList end. -remove_clause(RaceList, CurrTuple, Code, NestingLevel) -> +remove_clause(RaceList, CurrTuple, Code, NestingLevel) -> NewRaceList = fixup_case_rest_paths(RaceList, 0), {NewCurrTuple, NewCode} = cleanup_clause_code(CurrTuple, Code, 0, NestingLevel), @@ -1623,7 +1623,7 @@ compare_ets_insert(OldWarnVarArgs, NewWarnVarArgs, RaceVarMap) -> end end, case Bool of - true -> + true -> case any_args(Old4) of true -> case compare_list_vars(Old3, ets_list_args(New3), [], RaceVarMap) of @@ -1692,7 +1692,6 @@ compare_types(VarArgs, WarnVarArgs, RaceWarnTag, RaceVarMap) -> false -> compare_var_list(VA1, WVA1, RaceVarMap) orelse compare_argtypes(VA2, WVA2) - end end; ?WARN_WHEREIS_UNREGISTER -> @@ -1717,12 +1716,12 @@ compare_types(VarArgs, WarnVarArgs, RaceWarnTag, RaceVarMap) -> false -> case any_args(WVA2) of true -> compare_var_list(VA1, WVA1, RaceVarMap); - false -> + false -> compare_var_list(VA1, WVA1, RaceVarMap) orelse compare_argtypes(VA2, WVA2) end end, - Bool andalso + Bool andalso (case any_args(VA4) of true -> compare_var_list(VA3, WVA3, RaceVarMap); @@ -2159,7 +2158,7 @@ race_var_map_guard_helper1(Arg, Pats, RaceVarMap, Op) -> _Else -> {RaceVarMap, false} end; false -> {RaceVarMap, false} - end; + end; _Other -> {RaceVarMap, false} end; _Other -> {RaceVarMap, false} @@ -2242,7 +2241,7 @@ var_analysis(FunDefArgs, FunCallArgs, WarnVarArgs, RaceWarnTag) -> [WVA1, WVA2|T] = WarnVarArgs, ArgNos = lists_key_members_lists(WVA1, FunDefArgs), [[lists_get(N, FunCallArgs) || N <- ArgNos], WVA2|T] - end. + end. var_type_analysis(FunDefArgs, FunCallTypes, WarnVarArgs, RaceWarnTag, RaceVarMap, CleanState) -> @@ -2287,7 +2286,7 @@ var_type_analysis(FunDefArgs, FunCallTypes, WarnVarArgs, RaceWarnTag, ets_tuple_argtypes1(lists:nth(N2 + 1, FunVarArgs), [], [], 0), []), FirstVarArg ++ [Vars2, NewWVA4] - + end; ?WARN_MNESIA_DIRTY_READ_WRITE -> [WVA1, WVA2|T] = WarnVarArgs, @@ -2331,7 +2330,7 @@ get_race_warn(Fun, Args, ArgTypes, DepList, State) -> -spec get_race_warnings(races(), dialyzer_dataflow:state()) -> {races(), dialyzer_dataflow:state()}. - + get_race_warnings(#races{race_warnings = RaceWarnings}, State) -> get_race_warnings_helper(RaceWarnings, State). @@ -2431,12 +2430,12 @@ end_clause_new(Arg, Pats, Guard) -> #end_clause{arg = Arg, pats = Pats, guard = Guard}. -spec get_curr_fun(races()) -> mfa_or_funlbl(). - + get_curr_fun(#races{curr_fun = CurrFun}) -> CurrFun. -spec get_curr_fun_args(races()) -> core_args(). - + get_curr_fun_args(#races{curr_fun_args = CurrFunArgs}) -> CurrFunArgs. @@ -2446,17 +2445,17 @@ get_new_table(#races{new_table = Table}) -> Table. -spec get_race_analysis(races()) -> boolean(). - + get_race_analysis(#races{race_analysis = RaceAnalysis}) -> RaceAnalysis. -spec get_race_list(races()) -> code(). - + get_race_list(#races{race_list = RaceList}) -> RaceList. -spec get_race_list_size(races()) -> non_neg_integer(). - + get_race_list_size(#races{race_list_size = RaceListSize}) -> RaceListSize. @@ -2484,10 +2483,10 @@ put_fun_args(Args, #races{curr_fun_args = CurrFunArgs} = Races) -> empty -> Races#races{curr_fun_args = Args}; _Other -> Races end. - + -spec put_race_analysis(boolean(), races()) -> races(). - + put_race_analysis(Analysis, Races) -> Races#races{race_analysis = Analysis}. diff --git a/lib/dialyzer/src/dialyzer_succ_typings.erl b/lib/dialyzer/src/dialyzer_succ_typings.erl index 1ff4783852..8bfc66fc39 100644 --- a/lib/dialyzer/src/dialyzer_succ_typings.erl +++ b/lib/dialyzer/src/dialyzer_succ_typings.erl @@ -435,7 +435,7 @@ format_scc(SCC) -> %% %% ============================================================================ --spec doit(module() | string()) -> 'ok'. +-spec doit(atom() | file:filename()) -> 'ok'. doit(Module) -> {ok, AbstrCode} = dialyzer_utils:get_abstract_code_from_src(Module), diff --git a/lib/dialyzer/src/dialyzer_typesig.erl b/lib/dialyzer/src/dialyzer_typesig.erl index 35b283a00a..3effb1c2e6 100644 --- a/lib/dialyzer/src/dialyzer_typesig.erl +++ b/lib/dialyzer/src/dialyzer_typesig.erl @@ -21,7 +21,7 @@ %%%------------------------------------------------------------------- %%% File : dialyzer_typesig.erl %%% Author : Tobias Lindahl <[email protected]> -%%% Description : +%%% Description : %%% %%% Created : 25 Apr 2005 by Tobias Lindahl <[email protected]> %%%------------------------------------------------------------------- @@ -31,12 +31,12 @@ -export([analyze_scc/5]). -export([get_safe_underapprox/2]). --import(erl_types, +-import(erl_types, [t_any/0, t_atom/0, t_atom_vals/1, t_binary/0, t_bitstr/0, t_bitstr/2, t_bitstr_concat/1, t_boolean/0, t_collect_vars/1, t_cons/2, t_cons_hd/1, t_cons_tl/1, t_float/0, t_from_range/2, t_from_term/1, - t_fun/0, t_fun/2, t_fun_args/1, t_fun_range/1, + t_fun/0, t_fun/2, t_fun_args/1, t_fun_range/1, t_has_var/1, t_inf/2, t_inf/3, t_integer/0, t_is_any/1, t_is_atom/1, t_is_atom/2, t_is_cons/1, t_is_equal/2, @@ -44,7 +44,7 @@ t_is_integer/1, t_non_neg_integer/0, t_is_list/1, t_is_nil/1, t_is_none/1, t_is_number/1, - t_is_subtype/2, t_limit/2, t_list/0, t_list/1, + t_is_subtype/2, t_limit/2, t_list/0, t_list/1, t_list_elements/1, t_nonempty_list/1, t_maybe_improper_list/0, t_module/0, t_number/0, t_number_vals/1, t_opaque_match_record/2, t_opaque_matching_structure/2, @@ -101,7 +101,7 @@ name_map = dict:new() :: dict(), next_label :: label(), non_self_recs = [] :: [label()], - plt :: dialyzer_plt:plt(), + plt :: dialyzer_plt:plt(), prop_types = dict:new() :: dict(), records = dict:new() :: dict(), opaques = [] :: [erl_types:erl_type()], @@ -140,8 +140,8 @@ %% where Def = {Var, Fun} as in the Core Erlang module definitions. %% Records = dict(RecName, {Arity, [{FieldName, FieldType}]}) %% NextLabel - An integer that is higher than any label in the code. -%% CallGraph - A callgraph as produced by dialyzer_callgraph.erl -%% Note: The callgraph must have been built with all the +%% CallGraph - A callgraph as produced by dialyzer_callgraph.erl +%% Note: The callgraph must have been built with all the %% code that the SCC is a part of. %% PLT - A dialyzer PLT. This PLT should contain available information %% about functions that can be called by this SCC. @@ -150,7 +150,7 @@ %%----------------------------------------------------------------------------- -spec analyze_scc(typesig_scc(), label(), - dialyzer_callgraph:callgraph(), + dialyzer_callgraph:callgraph(), dialyzer_plt:plt(), dict()) -> dict(). analyze_scc(SCC, NextLabel, CallGraph, Plt, PropTypes) -> @@ -202,7 +202,7 @@ traverse(Tree, DefinedVars, State) -> {State1, OpType} = traverse(Op, DefinedVars, State0), {State2, FunType} = state__get_fun_prototype(OpType, Arity, State1), State3 = state__store_conj(FunType, eq, OpType, State2), - State4 = state__store_conj(mk_var(Tree), sub, t_fun_range(FunType), + State4 = state__store_conj(mk_var(Tree), sub, t_fun_range(FunType), State3), State5 = state__store_conj_lists(ArgTypes, sub, t_fun_args(FunType), State4), @@ -216,7 +216,7 @@ traverse(Tree, DefinedVars, State) -> end end; binary -> - {State1, SegTypes} = traverse_list(cerl:binary_segments(Tree), + {State1, SegTypes} = traverse_list(cerl:binary_segments(Tree), DefinedVars, State), Type = mk_fun_var(fun(Map) -> TmpSegTypes = lookup_type_list(SegTypes, Map), @@ -227,7 +227,7 @@ traverse(Tree, DefinedVars, State) -> Size = cerl:bitstr_size(Tree), UnitVal = cerl:int_val(cerl:bitstr_unit(Tree)), Val = cerl:bitstr_val(Tree), - {State1, [SizeType, ValType]} = + {State1, [SizeType, ValType]} = traverse_list([Size, Val], DefinedVars, State), {State2, TypeConstr} = case cerl:bitstr_bitsize(Tree) of @@ -250,7 +250,7 @@ traverse(Tree, DefinedVars, State) -> case state__is_in_match(State1) of true -> Flags = cerl:concrete(cerl:bitstr_flags(Tree)), - mk_fun_var(bitstr_val_constr(SizeType, UnitVal, Flags), + mk_fun_var(bitstr_val_constr(SizeType, UnitVal, Flags), [SizeType]); false -> t_integer() end; @@ -282,7 +282,7 @@ traverse(Tree, DefinedVars, State) -> false -> ConsVar = mk_var(Tree), ConsType = mk_fun_var(fun(Map) -> - t_cons(lookup_type(HdVar, Map), + t_cons(lookup_type(HdVar, Map), lookup_type(TlVar, Map)) end, [HdVar, TlVar]), HdType = mk_fun_var(fun(Map) -> @@ -299,8 +299,8 @@ traverse(Tree, DefinedVars, State) -> true -> t_cons_tl(Cons) end end, [ConsVar]), - State2 = state__store_conj_lists([HdVar, TlVar, ConsVar], sub, - [HdType, TlType, ConsType], + State2 = state__store_conj_lists([HdVar, TlVar, ConsVar], sub, + [HdType, TlType, ConsType], State1), {State2, ConsVar} end; @@ -314,14 +314,14 @@ traverse(Tree, DefinedVars, State) -> error -> t_fun(length(Vars), t_none()); {ok, Dom} -> t_fun(Dom, t_none()) end, - State2 = + State2 = try State1 = case state__add_prop_constrs(Tree, State0) of not_called -> State0; PropState -> PropState end, {BodyState, BodyVar} = traverse(Body, DefinedVars1, State1), - state__store_conj(mk_var(Tree), eq, + state__store_conj(mk_var(Tree), eq, t_fun(mk_var_list(Vars), BodyVar), BodyState) catch throw:error -> @@ -340,7 +340,7 @@ traverse(Tree, DefinedVars, State) -> Arg = cerl:let_arg(Tree), Body = cerl:let_body(Tree), {State1, ArgVars} = traverse(Arg, DefinedVars, State), - State2 = state__store_conj(t_product(mk_var_list(Vars)), eq, + State2 = state__store_conj(t_product(mk_var_list(Vars)), eq, ArgVars, State1), DefinedVars1 = add_def_list(Vars, DefinedVars), traverse(Body, DefinedVars1, State2); @@ -353,12 +353,12 @@ traverse(Tree, DefinedVars, State) -> DefinedVars1 = add_def_list(Vars, DefinedVars), {State2, _} = traverse_list(Funs, DefinedVars1, State1), traverse(Body, DefinedVars1, State2); - literal -> + literal -> %% This is needed for finding records case cerl:unfold_literal(Tree) of - Tree -> + Tree -> Type = t_from_term(cerl:concrete(Tree)), - NewType = + NewType = case erl_types:t_opaque_match_atom(Type, State#state.opaques) of [Opaque] -> Opaque; _ -> Type @@ -370,7 +370,7 @@ traverse(Tree, DefinedVars, State) -> Defs = cerl:module_defs(Tree), Funs = [Fun || {_Var, Fun} <- Defs], Vars = [Var || {Var, _Fun} <- Defs], - DefinedVars1 = add_def_list(Vars, DefinedVars), + DefinedVars1 = add_def_list(Vars, DefinedVars), State1 = state__store_funs(Vars, Funs, State), FoldFun = fun(Fun, AccState) -> {S, _} = traverse(Fun, DefinedVars1, @@ -388,7 +388,7 @@ traverse(Tree, DefinedVars, State) -> 'receive' -> Clauses = filter_match_fail(cerl:receive_clauses(Tree)), Timeout = cerl:receive_timeout(Tree), - case (cerl:is_c_atom(Timeout) andalso + case (cerl:is_c_atom(Timeout) andalso (cerl:atom_val(Timeout) =:= infinity)) of true -> handle_clauses(Clauses, mk_var(Tree), [], DefinedVars, State); @@ -421,7 +421,7 @@ traverse(Tree, DefinedVars, State) -> case t_has_var(Var) of true -> {AccState1, NewVar} = state__mk_var(AccState), - {NewVar, + {NewVar, state__store_conj(Var, eq, NewVar, AccState1)}; false -> {Var, AccState} @@ -431,7 +431,7 @@ traverse(Tree, DefinedVars, State) -> {TmpState, t_tuple(NewEvars)} end, case Elements of - [Tag|Fields] -> + [Tag|Fields] -> case cerl:is_c_atom(Tag) of true -> %% Check if an opaque term is constructed. @@ -534,7 +534,7 @@ handle_try(Tree, DefinedVars, State) -> Handler = cerl:try_handler(Tree), State1 = state__new_constraint_context(State), {ArgBodyState, BodyVar} = - try + try {State2, ArgVar} = traverse(Arg, DefinedVars, State1), DefinedVars1 = add_def_list(Vars, DefinedVars), {State3, BodyVar1} = traverse(Body, DefinedVars1, State2), @@ -542,17 +542,17 @@ handle_try(Tree, DefinedVars, State) -> State3), {State4, BodyVar1} catch - throw:error -> + throw:error -> {State1, t_none()} end, State6 = state__new_constraint_context(ArgBodyState), {HandlerState, HandlerVar} = try - DefinedVars2 = add_def_list([X || X <- EVars, cerl:is_c_var(X)], + DefinedVars2 = add_def_list([X || X <- EVars, cerl:is_c_var(X)], DefinedVars), traverse(Handler, DefinedVars2, State6) catch - throw:error -> + throw:error -> {State6, t_none()} end, ArgBodyCs = state__cs(ArgBodyState), @@ -561,7 +561,7 @@ handle_try(Tree, DefinedVars, State) -> OldCs = state__cs(State), case state__is_in_guard(State) of true -> - Conj1 = mk_conj_constraint_list([ArgBodyCs, + Conj1 = mk_conj_constraint_list([ArgBodyCs, mk_constraint(BodyVar, eq, TreeVar)]), Disj = mk_disj_constraint_list([Conj1, mk_constraint(HandlerVar, eq, TreeVar)]), @@ -573,10 +573,10 @@ handle_try(Tree, DefinedVars, State) -> {NewCs, ReturnVar} = case {t_is_none(BodyVar), t_is_none(HandlerVar)} of {false, false} -> - Conj1 = + Conj1 = mk_conj_constraint_list([ArgBodyCs, mk_constraint(TreeVar, eq, BodyVar)]), - Conj2 = + Conj2 = mk_conj_constraint_list([HandlerCs, mk_constraint(TreeVar, eq, HandlerVar)]), Disj = mk_disj_constraint_list([Conj1, Conj2]), @@ -603,7 +603,7 @@ handle_try(Tree, DefinedVars, State) -> %% Call %% -handle_call(Call, DefinedVars, State) -> +handle_call(Call, DefinedVars, State) -> Args = cerl:call_args(Call), Mod = cerl:call_module(Call), Fun = cerl:call_name(Call), @@ -618,7 +618,7 @@ handle_call(Call, DefinedVars, State) -> case state__lookup_rec_var_in_scope(MFA, State) of error -> case get_bif_constr(MFA, Dst, ArgVars, State1) of - none -> + none -> {get_plt_constr(MFA, Dst, ArgVars, State1), Dst}; C -> {state__store_conj(C, State1), Dst} @@ -647,7 +647,7 @@ get_plt_constr(MFA, Dst, ArgVars, State) -> case PltRes of none -> State; {value, {PltRetType, PltArgTypes}} -> - state__store_conj_lists([Dst|ArgVars], sub, + state__store_conj_lists([Dst|ArgVars], sub, [PltRetType|PltArgTypes], State) end; {value, #contract{args = GenArgs} = C} -> @@ -655,7 +655,7 @@ get_plt_constr(MFA, Dst, ArgVars, State) -> case PltRes of none -> {mk_fun_var(fun(Map) -> - ArgTypes = lookup_type_list(ArgVars, Map), + ArgTypes = lookup_type_list(ArgVars, Map), dialyzer_contracts:get_contract_return(C, ArgTypes) end, ArgVars), GenArgs}; {value, {PltRetType, PltArgTypes}} -> @@ -692,7 +692,7 @@ filter_match_fail([Clause] = Cls) -> filter_match_fail([H|T]) -> [H|filter_match_fail(T)]; filter_match_fail([]) -> - %% This can actually happen, for example in + %% This can actually happen, for example in %% receive after 1 -> ok end []. @@ -714,16 +714,16 @@ handle_clauses(Clauses, TopVar, Arg, Action, DefinedVars, State) -> if length(Clauses) > ?MAX_NOF_CLAUSES -> overflow; true -> [] end, - {State1, CList} = handle_clauses_1(Clauses, TopVar, Arg, DefinedVars, + {State1, CList} = handle_clauses_1(Clauses, TopVar, Arg, DefinedVars, State, SubtrTypeList, []), {NewCs, NewState} = case Action of - none -> + none -> if CList =:= [] -> throw(error); true -> {CList, State1} end; - _ -> - try + _ -> + try {State2, ActionVar} = traverse(Action, DefinedVars, State1), TmpC = mk_constraint(TopVar, eq, ActionVar), ActionCs = mk_conj_constraint_list([state__cs(State2),TmpC]), @@ -740,7 +740,7 @@ handle_clauses(Clauses, TopVar, Arg, Action, DefinedVars, State) -> FinalState = state__new_constraint_context(NewState), {state__store_conj_list([OldCs, NewCList], FinalState), TopVar}. -handle_clauses_1([Clause|Tail], TopVar, Arg, DefinedVars, +handle_clauses_1([Clause|Tail], TopVar, Arg, DefinedVars, State, SubtrTypes, Acc) -> State0 = state__new_constraint_context(State), Pats = cerl:clause_pats(Clause), @@ -749,22 +749,22 @@ handle_clauses_1([Clause|Tail], TopVar, Arg, DefinedVars, NewSubtrTypes = case SubtrTypes =:= overflow of true -> overflow; - false -> + false -> ordsets:add_element(get_safe_underapprox(Pats, Guard), SubtrTypes) end, - try + try DefinedVars1 = add_def_from_tree_list(Pats, DefinedVars), State1 = state__set_in_match(State0, true), {State2, PatVars} = traverse_list(Pats, DefinedVars1, State1), State3 = case Arg =:= [] of true -> State2; - false -> + false -> S = state__store_conj(Arg, eq, t_product(PatVars), State2), case SubtrTypes =:= overflow of true -> S; false -> - SubtrPatVar = mk_fun_var(fun(Map) -> + SubtrPatVar = mk_fun_var(fun(Map) -> TmpType = lookup_type(Arg, Map), t_subtract_list(TmpType, SubtrTypes) end, [Arg]), @@ -772,15 +772,15 @@ handle_clauses_1([Clause|Tail], TopVar, Arg, DefinedVars, end end, State4 = handle_guard(Guard, DefinedVars1, State3), - {State5, BodyVar} = traverse(Body, DefinedVars1, + {State5, BodyVar} = traverse(Body, DefinedVars1, state__set_in_match(State4, false)), State6 = state__store_conj(TopVar, eq, BodyVar, State5), Cs = state__cs(State6), - handle_clauses_1(Tail, TopVar, Arg, DefinedVars, State6, + handle_clauses_1(Tail, TopVar, Arg, DefinedVars, State6, NewSubtrTypes, [Cs|Acc]) catch - throw:error -> - handle_clauses_1(Tail, TopVar, Arg, DefinedVars, + throw:error -> + handle_clauses_1(Tail, TopVar, Arg, DefinedVars, State, NewSubtrTypes, Acc) end; handle_clauses_1([], _TopVar, _Arg, _DefinedVars, State, _SubtrType, Acc) -> @@ -792,7 +792,7 @@ get_safe_underapprox(Pats, Guard) -> try Map1 = cerl_trees:fold(fun(X, Acc) -> case cerl:is_c_var(X) of - true -> + true -> dict:store(cerl_trees:get_label(X), t_any(), Acc); false -> Acc @@ -804,8 +804,8 @@ get_safe_underapprox(Pats, Guard) -> false -> case cerl:is_c_var(Guard) of false -> Map2; - true -> - dict:store(cerl_trees:get_label(Guard), + true -> + dict:store(cerl_trees:get_label(Guard), t_from_term(true), Map2) end end, @@ -819,8 +819,8 @@ get_underapprox_from_guard(Tree, Map) -> True = t_from_term(true), case cerl:type(Tree) of call -> - case {cerl:concrete(cerl:call_module(Tree)), - cerl:concrete(cerl:call_name(Tree)), + case {cerl:concrete(cerl:call_module(Tree)), + cerl:concrete(cerl:call_name(Tree)), length(cerl:call_args(Tree))} of {erlang, is_function, 2} -> [Fun, Arity] = cerl:call_args(Tree), @@ -856,15 +856,15 @@ get_underapprox_from_guard(Tree, Map) -> {erlang, '==', 2} -> throw(dont_know); {erlang, 'and', 2} -> [Arg1, Arg2] = cerl:call_args(Tree), - case ((cerl:is_c_var(Arg1) orelse cerl:is_literal(Arg1)) + case ((cerl:is_c_var(Arg1) orelse cerl:is_literal(Arg1)) andalso (cerl:is_c_var(Arg2) orelse cerl:is_literal(Arg2))) of true -> {Arg1Type, _} = get_underapprox_from_guard(Arg1, Map), {Arg2Type, _} = get_underapprox_from_guard(Arg2, Map), - case (t_is_equal(True, Arg1Type) andalso + case (t_is_equal(True, Arg1Type) andalso t_is_equal(True, Arg2Type)) of - true -> {True, Map}; + true -> {True, Map}; false -> throw(dont_know) end; false -> @@ -876,7 +876,7 @@ get_underapprox_from_guard(Tree, Map) -> end end; var -> - Type = + Type = case dict:find(cerl_trees:get_label(Tree), Map) of error -> throw(dont_know); {ok, T} -> T @@ -931,7 +931,7 @@ bitstr_constr(SizeType, UnitVal) -> MinSize = erl_types:number_min(TmpSizeType), t_bitstr(UnitVal, MinSize * UnitVal) end; - false -> + false -> t_bitstr(UnitVal, 0) end end. @@ -975,9 +975,9 @@ get_safe_underapprox_1([Pat|Left], Acc, Map) -> end; binary -> %% TODO: Can maybe do something here - throw(dont_know); + throw(dont_know); cons -> - {[Hd, Tl], Map1} = + {[Hd, Tl], Map1} = get_safe_underapprox_1([cerl:cons_hd(Pat), cerl:cons_tl(Pat)], [], Map), case t_is_any(Tl) of true -> get_safe_underapprox_1(Left, [t_nonempty_list(Hd)|Acc], Map1); @@ -1020,7 +1020,7 @@ get_safe_underapprox_1([], Acc, Map) -> %% Guards %% -handle_guard(Guard, DefinedVars, State) -> +handle_guard(Guard, DefinedVars, State) -> True = t_from_term(true), State1 = state__set_in_guard(State, true), State2 = state__new_constraint_context(State1), @@ -1039,7 +1039,7 @@ handle_guard(Guard, DefinedVars, State) -> %% %%============================================================================= -get_bif_constr({erlang, Op, 2}, Dst, Args = [Arg1, Arg2], _State) +get_bif_constr({erlang, Op, 2}, Dst, Args = [Arg1, Arg2], _State) when Op =:= '+'; Op =:= '-'; Op =:= '*' -> ReturnType = mk_fun_var(fun(Map) -> TmpArgTypes = lookup_type_list(Args, Map), @@ -1047,14 +1047,14 @@ get_bif_constr({erlang, Op, 2}, Dst, Args = [Arg1, Arg2], _State) end, Args), ArgFun = fun(A, Pos) -> - F = + F = fun(Map) -> DstType = lookup_type(Dst, Map), AType = lookup_type(A, Map), case t_is_integer(DstType) of true -> case t_is_integer(AType) of - true -> + true -> eval_inv_arith(Op, Pos, DstType, AType); false -> %% This must be temporary. @@ -1062,7 +1062,7 @@ get_bif_constr({erlang, Op, 2}, Dst, Args = [Arg1, Arg2], _State) end; false -> case t_is_float(DstType) of - true -> + true -> case t_is_integer(AType) of true -> t_float(); false -> t_number() @@ -1079,9 +1079,9 @@ get_bif_constr({erlang, Op, 2}, Dst, Args = [Arg1, Arg2], _State) mk_conj_constraint_list([mk_constraint(Dst, sub, ReturnType), mk_constraint(Arg1, sub, Arg1FunVar), mk_constraint(Arg2, sub, Arg2FunVar)]); -get_bif_constr({erlang, Op, 2}, Dst, [Arg1, Arg2] = Args, _State) +get_bif_constr({erlang, Op, 2}, Dst, [Arg1, Arg2] = Args, _State) when Op =:= '<'; Op =:= '=<'; Op =:= '>'; Op =:= '>=' -> - ArgFun = + ArgFun = fun(LocalArg1, LocalArg2, LocalOp) -> fun(Map) -> DstType = lookup_type(Dst, Map), @@ -1098,19 +1098,19 @@ get_bif_constr({erlang, Op, 2}, Dst, [Arg1, Arg2] = Args, _State) Max2 = erl_types:number_max(Arg2Type), Min2 = erl_types:number_min(Arg2Type), case LocalOp of - '=<' -> + '=<' -> if IsTrue -> t_from_range(Min1, Max2); IsFalse -> t_from_range(range_inc(Min2), Max1) end; - '<' -> + '<' -> if IsTrue -> t_from_range(Min1, range_dec(Max2)); IsFalse -> t_from_range(Min2, Max1) end; - '>=' -> + '>=' -> if IsTrue -> t_from_range(Min2, Max1); IsFalse -> t_from_range(Min1, range_dec(Max2)) end; - '>' -> + '>' -> if IsTrue -> t_from_range(range_inc(Min2), Max1); IsFalse -> t_from_range(Min1, Max2) end @@ -1131,7 +1131,7 @@ get_bif_constr({erlang, Op, 2}, Dst, [Arg1, Arg2] = Args, _State) DstArgs = [Dst, Arg1, Arg2], Arg1Var = mk_fun_var(Arg1Fun, DstArgs), Arg2Var = mk_fun_var(Arg2Fun, DstArgs), - DstVar = mk_fun_var(fun(Map) -> + DstVar = mk_fun_var(fun(Map) -> TmpArgTypes = lookup_type_list(Args, Map), erl_bif_types:type(erlang, Op, 2, TmpArgTypes) end, Args), @@ -1143,9 +1143,9 @@ get_bif_constr({erlang, '++', 2}, Dst, [Hd, Tl] = Args, _State) -> DstType = lookup_type(Dst, Map), case t_is_cons(DstType) of true -> t_list(t_cons_hd(DstType)); - false -> + false -> case t_is_list(DstType) of - true -> + true -> case t_is_nil(DstType) of true -> DstType; false -> t_list(t_list_elements(DstType)) @@ -1160,7 +1160,7 @@ get_bif_constr({erlang, '++', 2}, Dst, [Hd, Tl] = Args, _State) -> true -> t_sup(t_cons_tl(DstType), DstType); false -> case t_is_list(DstType) of - true -> + true -> case t_is_nil(DstType) of true -> DstType; false -> t_list(t_list_elements(DstType)) @@ -1170,10 +1170,10 @@ get_bif_constr({erlang, '++', 2}, Dst, [Hd, Tl] = Args, _State) -> end end, DstL = [Dst], - HdVar = mk_fun_var(HdFun, DstL), + HdVar = mk_fun_var(HdFun, DstL), TlVar = mk_fun_var(TlFun, DstL), ArgTypes = erl_bif_types:arg_types(erlang, '++', 2), - ReturnType = mk_fun_var(fun(Map) -> + ReturnType = mk_fun_var(fun(Map) -> TmpArgTypes = lookup_type_list(Args, Map), erl_bif_types:type(erlang, '++', 2, TmpArgTypes) end, Args), @@ -1198,7 +1198,7 @@ get_bif_constr({erlang, is_function, 2}, Dst, [Fun, Arity], _State) -> ArgFun = fun(Map) -> DstType = lookup_type(Dst, Map), case t_is_atom(true, DstType) of - true -> + true -> ArityType = lookup_type(Arity, Map), case t_number_vals(ArityType) of unknown -> t_fun(); @@ -1231,7 +1231,7 @@ get_bif_constr({erlang, is_record, 2}, Dst, [Var, Tag] = Args, _State) -> end end, ArgV = mk_fun_var(ArgFun, [Dst]), - DstFun = fun(Map) -> + DstFun = fun(Map) -> TmpArgTypes = lookup_type_list(Args, Map), erl_bif_types:type(erlang, is_record, 2, TmpArgTypes) end, @@ -1241,7 +1241,7 @@ get_bif_constr({erlang, is_record, 2}, Dst, [Var, Tag] = Args, _State) -> mk_constraint(Var, sub, ArgV)]); get_bif_constr({erlang, is_record, 3}, Dst, [Var, Tag, Arity] = Args, State) -> %% TODO: Revise this to make it precise for Tag and Arity. - ArgFun = + ArgFun = fun(Map) -> case t_is_atom(true, lookup_type(Dst, Map)) of true -> @@ -1257,14 +1257,14 @@ get_bif_constr({erlang, is_record, 3}, Dst, [Var, Tag, Arity] = Args, State) -> GenRecord = t_tuple([TagType|AnyElems]), case t_atom_vals(TagType) of [TagVal] -> - case state__lookup_record(State, TagVal, + case state__lookup_record(State, TagVal, ArityVal - 1) of {ok, Type} -> AllOpaques = State#state.opaques, case t_opaque_match_record(Type, AllOpaques) of [Opaque] -> Opaque; _ -> Type - end; + end; error -> GenRecord end; _ -> GenRecord @@ -1279,9 +1279,9 @@ get_bif_constr({erlang, is_record, 3}, Dst, [Var, Tag, Arity] = Args, State) -> end end, ArgV = mk_fun_var(ArgFun, [Tag, Arity, Dst]), - DstFun = fun(Map) -> + DstFun = fun(Map) -> [TmpVar, TmpTag, TmpArity] = TmpArgTypes = lookup_type_list(Args, Map), - TmpArgTypes2 = + TmpArgTypes2 = case lists:member(TmpVar, State#state.opaques) of true -> case t_is_integer(TmpArity) of @@ -1293,7 +1293,7 @@ get_bif_constr({erlang, is_record, 3}, Dst, [Var, Tag, Arity] = Args, State) -> case t_atom_vals(TmpTag) of [TmpTagVal] -> case state__lookup_record(State, TmpTagVal, TmpArityVal - 1) of - {ok, TmpType} -> + {ok, TmpType} -> case t_is_none(t_inf(TmpType, TmpVar, opaque)) of true -> TmpArgTypes; false -> [TmpType, TmpTag, TmpArity] @@ -1312,7 +1312,7 @@ get_bif_constr({erlang, is_record, 3}, Dst, [Var, Tag, Arity] = Args, State) -> end, erl_bif_types:type(erlang, is_record, 3, TmpArgTypes2) end, - DstV = mk_fun_var(DstFun, Args), + DstV = mk_fun_var(DstFun, Args), mk_conj_constraint_list([mk_constraint(Dst, sub, DstV), mk_constraint(Arity, sub, t_integer()), mk_constraint(Tag, sub, t_atom()), @@ -1334,7 +1334,7 @@ get_bif_constr({erlang, 'and', 2}, Dst, [Arg1, Arg2] = Args, _State) -> true -> False; false -> t_boolean() end; - false -> + false -> t_boolean() end end @@ -1349,7 +1349,7 @@ get_bif_constr({erlang, 'and', 2}, Dst, [Arg1, Arg2] = Args, _State) -> case t_is_atom(false, Arg2Type) of true -> False; false -> - case (t_is_atom(true, Arg1Type) + case (t_is_atom(true, Arg1Type) andalso t_is_atom(true, Arg2Type)) of true -> True; false -> t_boolean() @@ -1378,7 +1378,7 @@ get_bif_constr({erlang, 'or', 2}, Dst, [Arg1, Arg2] = Args, _State) -> true -> True; false -> t_boolean() end; - false -> + false -> t_boolean() end end @@ -1393,7 +1393,7 @@ get_bif_constr({erlang, 'or', 2}, Dst, [Arg1, Arg2] = Args, _State) -> case t_is_atom(true, Arg2Type) of true -> True; false -> - case (t_is_atom(false, Arg1Type) + case (t_is_atom(false, Arg1Type) andalso t_is_atom(false, Arg2Type)) of true -> False; false -> t_boolean() @@ -1414,7 +1414,7 @@ get_bif_constr({erlang, 'or', 2}, Dst, [Arg1, Arg2] = Args, _State) -> get_bif_constr({erlang, 'not', 1}, Dst, [Arg] = Args, _State) -> True = t_from_term(true), False = t_from_term(false), - Fun = fun(Var) -> + Fun = fun(Var) -> fun(Map) -> Type = lookup_type(Var, Map), case t_is_atom(true, Type) of @@ -1439,7 +1439,7 @@ get_bif_constr({erlang, '=:=', 2}, Dst, [Arg1, Arg2] = Args, _State) -> OtherVarType = lookup_type(OtherVar, Map), case t_is_atom(true, DstType) of true -> OtherVarType; - false -> + false -> case t_is_atom(false, DstType) of true -> case is_singleton_type(OtherVarType) of @@ -1492,7 +1492,7 @@ get_bif_constr({erlang, '==', 2}, Dst, [Arg1, Arg2] = Args, _State) -> true -> case t_is_number(VarType) of true -> t_number(); - false -> + false -> case t_is_atom(VarType) of true -> VarType; false -> t_any() @@ -1511,7 +1511,8 @@ get_bif_constr({erlang, '==', 2}, Dst, [Arg1, Arg2] = Args, _State) -> mk_conj_constraint_list([mk_constraint(Dst, sub, DstV), mk_constraint(Arg1, sub, ArgV1), mk_constraint(Arg2, sub, ArgV2)]); -get_bif_constr({erlang, element, 2} = _BIF, Dst, Args, State) -> +get_bif_constr({erlang, element, 2} = _BIF, Dst, Args, + #state{cs = Constrs} = State) -> GenType = erl_bif_types:type(erlang, element, 2), case t_is_none(GenType) of true -> ?debug("Bif: ~w failed\n", [_BIF]), throw(error); @@ -1525,9 +1526,14 @@ get_bif_constr({erlang, element, 2} = _BIF, Dst, Args, State) -> erl_bif_types:type(erlang, element, 2, ATs2) end, ReturnType = mk_fun_var(Fun, Args), - ArgTypes = erl_bif_types:arg_types(erlang, element, 2), + ArgTypes = erl_bif_types:arg_types(erlang, element, 2), Cs = mk_constraints(Args, sub, ArgTypes), - mk_conj_constraint_list([mk_constraint(Dst, sub, ReturnType)|Cs]) + NewCs = + case find_element(Args, Constrs) of + 'unknown' -> Cs; + Elem -> [mk_constraint(Dst, eq, Elem)|Cs] + end, + mk_conj_constraint_list([mk_constraint(Dst, sub, ReturnType)|NewCs]) end; get_bif_constr({M, F, A} = _BIF, Dst, Args, State) -> GenType = erl_bif_types:type(M, F, A), @@ -1541,7 +1547,7 @@ get_bif_constr({M, F, A} = _BIF, Dst, Args, State) -> false -> T end end, - ReturnType = mk_fun_var(fun(Map) -> + ReturnType = mk_fun_var(fun(Map) -> TmpArgTypes0 = lookup_type_list(Args, Map), TmpArgTypes = [UnopaqueFun(T) || T<- TmpArgTypes0], erl_bif_types:type(M, F, A, TmpArgTypes) @@ -1561,12 +1567,12 @@ get_bif_constr({M, F, A} = _BIF, Dst, Args, State) -> end end. -eval_inv_arith('+', _Pos, Dst, Arg) -> +eval_inv_arith('+', _Pos, Dst, Arg) -> erl_bif_types:type(erlang, '-', 2, [Dst, Arg]); -eval_inv_arith('*', _Pos, Dst, Arg) -> +eval_inv_arith('*', _Pos, Dst, Arg) -> case t_number_vals(Arg) of [0] -> t_integer(); - _ -> + _ -> TmpRet = erl_bif_types:type(erlang, 'div', 2, [Dst, Arg]), Zero = t_from_term(0), %% If 0 is not part of the result, it cannot be part of the argument. @@ -1575,9 +1581,9 @@ eval_inv_arith('*', _Pos, Dst, Arg) -> true -> TmpRet end end; -eval_inv_arith('-', 1, Dst, Arg) -> +eval_inv_arith('-', 1, Dst, Arg) -> erl_bif_types:type(erlang, '-', 2, [Arg, Dst]); -eval_inv_arith('-', 2, Dst, Arg) -> +eval_inv_arith('-', 2, Dst, Arg) -> erl_bif_types:type(erlang, '+', 2, [Arg, Dst]). range_inc(neg_inf) -> neg_inf; @@ -1614,7 +1620,7 @@ get_bif_test_constr(Dst, Arg, Type, State) -> end; false -> t_from_term(false) end; - false -> + false -> case t_is_subtype(ArgType, Type) of true -> t_from_term(true); false -> t_boolean() @@ -1632,11 +1638,11 @@ get_bif_test_constr(Dst, Arg, Type, State) -> %%============================================================================= solve([Fun], State) -> - ?debug("============ Analyzing Fun: ~w ===========\n", + ?debug("============ Analyzing Fun: ~w ===========\n", [debug_lookup_name(Fun)]), solve_fun(Fun, dict:new(), State); solve([_|_] = SCC, State) -> - ?debug("============ Analyzing SCC: ~w ===========\n", + ?debug("============ Analyzing SCC: ~w ===========\n", [[debug_lookup_name(F) || F <- SCC]]), solve_scc(SCC, dict:new(), State, false). @@ -1655,7 +1661,7 @@ solve_fun(Fun, FunMap, State) -> solve_scc(SCC, Map, State, TryingUnit) -> State1 = state__mark_as_non_self_rec(SCC, State), - Vars0 = [{Fun, state__get_rec_var(Fun, State)} || Fun <- SCC], + Vars0 = [{Fun, state__get_rec_var(Fun, State)} || Fun <- SCC], Vars = [Var || {_, {ok, Var}} <- Vars0], Funs = [Fun || {Fun, {ok, _}} <- Vars0], Types = unsafe_lookup_type_list(Funs, Map), @@ -1682,7 +1688,7 @@ solve_scc(SCC, Map, State, TryingUnit) -> false -> Map2 end; - false -> + false -> ?debug("SCC ~w did not reach fixpoint\n", [SCC]), solve_scc(SCC, Map2, State, TryingUnit) end. @@ -1704,29 +1710,29 @@ scc_fold_fun(F, FunMap, State) -> format_type(NewType)]), NewFunMap. -solve_ref_or_list(#constraint_ref{id = Id, deps = Deps}, +solve_ref_or_list(#constraint_ref{id = Id, deps = Deps}, Map, MapDict, State) -> - {OldLocalMap, Check} = + {OldLocalMap, Check} = case dict:find(Id, MapDict) of error -> {dict:new(), false}; {ok, M} -> {M, true} - end, + end, ?debug("Checking ref to fun: ~w\n", [debug_lookup_name(Id)]), CheckDeps = ordsets:del_element(t_var_name(Id), Deps), case Check andalso maps_are_equal(OldLocalMap, Map, CheckDeps) of - true -> + true -> ?debug("Equal\n", []), {ok, MapDict, Map}; false -> ?debug("Not equal. Solving\n", []), Cs = state__get_cs(Id, State), - Res = + Res = case state__is_self_rec(Id, State) of true -> solve_self_recursive(Cs, Map, MapDict, Id, t_none(), State); false -> solve_ref_or_list(Cs, Map, MapDict, State) end, case Res of - {error, NewMapDict} -> + {error, NewMapDict} -> ?debug("Error solving for function ~p\n", [debug_lookup_name(Id)]), Arity = state__fun_arity(Id, State), FunType = @@ -1755,17 +1761,17 @@ solve_ref_or_list(#constraint_ref{id = Id, deps = Deps}, end; solve_ref_or_list(#constraint_list{type=Type, list = Cs, deps = Deps, id = Id}, Map, MapDict, State) -> - {OldLocalMap, Check} = + {OldLocalMap, Check} = case dict:find(Id, MapDict) of error -> {dict:new(), false}; {ok, M} -> {M, true} end, ?debug("Checking ref to list: ~w\n", [Id]), case Check andalso maps_are_equal(OldLocalMap, Map, Deps) of - true -> + true -> ?debug("~w equal ~w\n", [Type, Id]), {ok, MapDict, Map}; - false -> + false -> ?debug("~w not equal: ~w. Solving\n", [Type, Id]), solve_clist(Cs, Type, Id, Deps, MapDict, Map, State) end. @@ -1793,7 +1799,7 @@ solve_self_recursive(Cs, Map, MapDict, Id, RecType0, State) -> [[{X, format_type(Y)} || {X, Y} <- dict:to_list(NewMap)]]), NewRecType = unsafe_lookup_type(Id, NewMap), case t_is_equal(NewRecType, RecType0) of - true -> + true -> {ok, NewMapDict, enter_type(RecVar, NewRecType, NewMap)}; false -> solve_self_recursive(Cs, Map, MapDict, Id, NewRecType, State) @@ -1801,7 +1807,7 @@ solve_self_recursive(Cs, Map, MapDict, Id, RecType0, State) -> end. solve_clist(Cs, conj, Id, Deps, MapDict, Map, State) -> - case solve_cs(Cs, Map, MapDict, State) of + case solve_cs(Cs, Map, MapDict, State) of {error, _} = Error -> Error; {ok, NewMapDict, NewMap} = Ret -> case Cs of @@ -1821,12 +1827,12 @@ solve_clist(Cs, disj, Id, _Deps, MapDict, Map, State) -> {ok, NewDict, NewMap} -> {{ok, NewMap}, NewDict}; {error, _NewDict} = Error -> Error end - end, + end, {Maps, NewMapDict} = lists:mapfoldl(Fun, MapDict, Cs), case [X || {ok, X} <- Maps] of [] -> {error, NewMapDict}; - MapList -> - NewMap = join_maps(MapList), + MapList -> + NewMap = join_maps(MapList), {ok, dict:store(Id, NewMap, NewMapDict), NewMap} end. @@ -1844,13 +1850,13 @@ solve_cs([#constraint{} = C|Tail], Map, MapDict, State) -> case solve_one_c(C, Map, State#state.opaques) of error -> ?debug("+++++++++++\nFailed: ~s :: ~s ~w ~s :: ~s\n+++++++++++\n", - [format_type(C#constraint.lhs), + [format_type(C#constraint.lhs), format_type(lookup_type(C#constraint.lhs, Map)), C#constraint.op, - format_type(C#constraint.rhs), + format_type(C#constraint.rhs), format_type(lookup_type(C#constraint.rhs, Map))]), {error, MapDict}; - {ok, NewMap} -> + {ok, NewMap} -> solve_cs(Tail, NewMap, MapDict, State) end; solve_cs([], Map, MapDict, _State) -> @@ -1863,7 +1869,7 @@ solve_one_c(#constraint{lhs = Lhs, rhs = Rhs, op = Op}, Map, Opaques) -> ?debug("Solving: ~s :: ~s ~w ~s :: ~s\n\tInf: ~s\n", [format_type(Lhs), format_type(LhsType), Op, format_type(Rhs), format_type(RhsType), format_type(Inf)]), - case t_is_none(Inf) of + case t_is_none(Inf) of true -> error; false -> case Op of @@ -1887,8 +1893,8 @@ solve_subtype(Type, Inf, Map, Opaques) -> try t_unify(Type, Inf, Opaques) of {_, List} -> {ok, enter_type_list(List, Map)} catch - throw:{mismatch, _T1, _T2} -> - ?debug("Mismatch between ~s and ~s\n", + throw:{mismatch, _T1, _T2} -> + ?debug("Mismatch between ~s and ~s\n", [format_type(_T1), format_type(_T2)]), error end. @@ -1936,9 +1942,9 @@ maps_are_equal_1(Map1, Map2, [H|Tail]) -> T2 = lookup_type(H, Map2), case t_is_equal(T1, T2) of true -> maps_are_equal_1(Map1, Map2, Tail); - false -> + false -> ?debug("~w: ~s =/= ~s\n", [H, format_type(T1), format_type(T2)]), - false + false end; maps_are_equal_1(_Map1, _Map2, []) -> true. @@ -1953,7 +1959,7 @@ prune_keys(Map1, Map2, Deps) -> true -> Keys1 = dict:fetch_keys(Map1), case length(Keys1) > NofDeps of - true -> + true -> Set1 = lists:sort(Keys1), Set2 = lists:sort(dict:fetch_keys(Map2)), ordsets:intersection(ordsets:union(Set1, Set2), Deps); @@ -2035,7 +2041,7 @@ lookup_type(Key, Map) -> mk_var(Var) -> case cerl:is_literal(Var) of true -> Var; - false -> + false -> case cerl:is_c_values(Var) of true -> t_product(mk_var_no_lit_list(cerl:values_es(Var))); false -> t_var(cerl_trees:get_label(Var)) @@ -2076,10 +2082,10 @@ state__set_opaques(#state{records = RecDict} = State, {M, _F, _A}) -> state__lookup_record(#state{records = Records}, Tag, Arity) -> case erl_types:lookup_record(Tag, Arity, Records) of - {ok, Fields} -> + {ok, Fields} -> {ok, t_tuple([t_from_term(Tag)| [FieldType || {_FieldName, FieldType} <- Fields]])}; - error -> + error -> error end. @@ -2098,12 +2104,12 @@ state__is_in_guard(#state{in_guard = Bool}) -> state__get_fun_prototype(Op, Arity, State) -> case t_is_fun(Op) of true -> {State, Op}; - false -> + false -> {State1, [Ret|Args]} = state__mk_vars(Arity+1, State), Fun = t_fun(Args, Ret), {State1, Fun} end. - + state__lookup_rec_var_in_scope(MFA, #state{name_map = NameMap}) -> dict:find(MFA, NameMap). @@ -2115,11 +2121,11 @@ state__store_fun_arity(Tree, #state{fun_arities = Map} = State) -> state__fun_arity(Id, #state{fun_arities = Map}) -> dict:fetch(Id, Map). -state__lookup_undef_var(Tree, #state{callgraph = CG, plt = Plt}) -> +state__lookup_undef_var(Tree, #state{callgraph = CG, plt = Plt}) -> Label = cerl_trees:get_label(Tree), case dialyzer_callgraph:lookup_rec_var(Label, CG) of error -> error; - {ok, MFA} -> + {ok, MFA} -> case dialyzer_plt:lookup(Plt, MFA) of none -> error; {value, {RetType, ArgTypes}} -> {ok, t_fun(ArgTypes, RetType)} @@ -2179,7 +2185,7 @@ state__add_prop_constrs(Tree, #state{prop_types = PropTypes} = State) -> case erl_types:any_none(ArgTypes) of true -> not_called; false -> - ?debug("Adding propagated constr: ~s for function ~w\n", + ?debug("Adding propagated constr: ~s for function ~w\n", [format_type(FunType), debug_lookup_name(mk_var(Tree))]), FunVar = mk_var(Tree), state__store_conj(FunVar, sub, FunType, State) @@ -2225,7 +2231,7 @@ state__store_conj_lists_1([], _Op, [], State) -> state__mk_var(#state{next_label = NL} = State) -> {State#state{next_label = NL+1}, t_var(NL)}. - + state__mk_vars(N, #state{next_label = NL} = State) -> NewLabel = NL + N, Vars = [t_var(X) || X <- lists:seq(NL, NewLabel-1)], @@ -2235,7 +2241,7 @@ state__store_constrs(Id, Cs, #state{cmap = Dict} = State) -> NewDict = dict:store(Id, Cs, Dict), State#state{cmap = NewDict}. -state__get_cs(Var, #state{cmap = Dict}) -> +state__get_cs(Var, #state{cmap = Dict}) -> dict:fetch(Var, Dict). %% The functions here will not be treated as self recursive. @@ -2286,7 +2292,7 @@ mk_constraint(Lhs, Op, Rhs) -> %% This constraint is constant. Solve it immediately. case solve_one_c(C, dict:new(), []) of error -> throw(error); - _ -> + _ -> %% This is always true, keep it anyway for logistic reasons C end; @@ -2335,7 +2341,7 @@ mk_constraint_1(Lhs, eq, Rhs) when Lhs < Rhs -> mk_constraint_1(Lhs, eq, Rhs) -> #constraint{lhs = Rhs, op = eq, rhs = Lhs}; mk_constraint_1(Lhs, Op, Rhs) -> - #constraint{lhs = Lhs, op = Op, rhs = Rhs}. + #constraint{lhs = Lhs, op = Op, rhs = Rhs}. mk_constraints([Lhs|LhsTail], Op, [Rhs|RhsTail]) -> [mk_constraint(Lhs, Op, Rhs)|mk_constraints(LhsTail, Op, RhsTail)]; @@ -2350,7 +2356,7 @@ mk_constraint_list(Type, List) -> List2 = ordsets:filter(fun(X) -> get_deps(X) =/= [] end, List1), Deps = calculate_deps(List2), case Deps =:= [] of - true -> #constraint_list{type = conj, + true -> #constraint_list{type = conj, list = [mk_constraint(t_any(), eq, t_any())], deps = []}; false -> #constraint_list{type = Type, list = List2, deps = Deps} @@ -2372,11 +2378,11 @@ update_constraint_list(CL, List) -> %% We expand guard constraints into dijunctive normal form to gain %% precision in simple guards. However, because of the exponential %% growth of this expansion in the presens of disjunctions we can even -%% get into trouble while expanding. +%% get into trouble while expanding. %% %% To limit this we only expand when the number of disjunctions are %% below a certain limit. This limit is currently set based on the -%% behaviour of boolean 'or'. +%% behaviour of boolean 'or'. %% %% V1 = V2 or V3 %% @@ -2395,7 +2401,7 @@ update_constraint_list(CL, List) -> -define(DISJ_NORM_FORM_LIMIT, 28). mk_disj_norm_form(#constraint_list{} = CL) -> - try + try List1 = expand_to_conjunctions(CL), mk_disj_constraint_list(List1) catch @@ -2409,7 +2415,7 @@ expand_to_conjunctions(#constraint_list{type = conj, list = List}) -> true -> [mk_conj_constraint_list(List1)]; false -> case List2 of - [JustOneList] -> + [JustOneList] -> [mk_conj_constraint_list([L|List1]) || L <- JustOneList]; _ -> combine_conj_lists(List2, List1) @@ -2422,7 +2428,7 @@ expand_to_conjunctions(#constraint_list{type = disj, list = List}) -> List1 = [C || C <- List, is_simple_constraint(C)], %% Just an assert. [] = [C || #constraint{} = C <- List1], - Expanded = lists:flatten([expand_to_conjunctions(C) + Expanded = lists:flatten([expand_to_conjunctions(C) || #constraint_list{} = C <- List]), ReturnList = Expanded ++ List1, if length(ReturnList) > ?DISJ_NORM_FORM_LIMIT -> throw(too_many_disj); @@ -2467,7 +2473,7 @@ wrap_simple_constr(#constraint_list{} = C) -> C; wrap_simple_constr(#constraint_ref{} = C) -> C. enumerate_constraints(State) -> - Cs = [mk_constraint_ref(Id, get_deps(state__get_cs(Id, State))) + Cs = [mk_constraint_ref(Id, get_deps(state__get_cs(Id, State))) || Id <- state__scc(State)], {_, _, NewState} = enumerate_constraints(Cs, 0, [], State), NewState. @@ -2475,9 +2481,9 @@ enumerate_constraints(State) -> enumerate_constraints([#constraint_ref{id = Id} = C|Tail], N, Acc, State) -> Cs = state__get_cs(Id, State), {[NewCs], NewN, NewState1} = enumerate_constraints([Cs], N, [], State), - NewState2 = state__store_constrs(Id, NewCs, NewState1), + NewState2 = state__store_constrs(Id, NewCs, NewState1), enumerate_constraints(Tail, NewN+1, [C|Acc], NewState2); -enumerate_constraints([#constraint_list{type = conj, list = List} = C|Tail], +enumerate_constraints([#constraint_list{type = conj, list = List} = C|Tail], N, Acc, State) -> %% Separate the flat constraints from the deep ones to make a %% separate fixpoint interation over the flat ones for speed. @@ -2496,7 +2502,7 @@ enumerate_constraints([#constraint_list{type = conj, list = List} = C|Tail], end, NewAcc = [C#constraint_list{list = NewList, id = {list, N3}}|Acc], enumerate_constraints(Tail, N3+1, NewAcc, State2); -enumerate_constraints([#constraint_list{list = List, type = disj} = C|Tail], +enumerate_constraints([#constraint_list{list = List, type = disj} = C|Tail], N, Acc, State) -> {NewList, NewN, NewState} = enumerate_constraints(List, N, [], State), NewAcc = [C#constraint_list{list = NewList, id = {list, NewN}}|Acc], @@ -2515,7 +2521,7 @@ group_constraints_in_components(Cs, N) -> case find_dep_components(DepList, []) of [_] -> {Cs, N}; [_|_] = Components -> - ConstrComp = [[C || #constraint{deps = D} = C <- Cs, + ConstrComp = [[C || #constraint{deps = D} = C <- Cs, ordsets:is_subset(D, Comp)] || Comp <- Components], lists:mapfoldl(fun(CComp, TmpN) -> @@ -2545,7 +2551,7 @@ find_dep_components([], AccSet, Ungrouped) -> %% Put the fun ref constraints last in any conjunction since we need %% to separate the environment from the interior of the function. order_fun_constraints(State) -> - Cs = [mk_constraint_ref(Id, get_deps(state__get_cs(Id, State))) + Cs = [mk_constraint_ref(Id, get_deps(state__get_cs(Id, State))) || Id <- state__scc(State)], order_fun_constraints(Cs, State). @@ -2565,8 +2571,8 @@ order_fun_constraints([#constraint_list{list = List, type = Type} = C|Tail], case Type of conj -> order_fun_constraints(List, [], [], State); disj -> - FoldFun = fun(X, AccState) -> - {[NewX], NewAccState} = + FoldFun = fun(X, AccState) -> + {[NewX], NewAccState} = order_fun_constraints([X], [], [], AccState), {NewX, NewAccState} end, @@ -2588,7 +2594,7 @@ order_fun_constraints([], Funs, Acc, State) -> is_singleton_non_number_type(Type) -> case t_is_number(Type) of - true -> false; + true -> false; false -> is_singleton_type(Type) end. @@ -2613,6 +2619,41 @@ is_singleton_type(Type) -> end end. +find_element(Args, Cs) -> + [Pos, Tuple] = Args, + case erl_types:t_is_number(Pos) of + true -> + case erl_types:t_number_vals(Pos) of + 'unknown' -> 'unknown'; + [I] -> + case find_constraint(Tuple, Cs) of + 'unknown' -> 'unknown'; + #constraint{lhs = ExTuple} -> + case erl_types:t_is_tuple(ExTuple) of + true -> + Elems = erl_types:t_tuple_args(ExTuple), + Elem = lists:nth(I, Elems), + case erl_types:t_is_var(Elem) of + true -> Elem; + false -> 'unknown' + end; + false -> 'unknown' + end + end; + _ -> 'unknown' + end; + false -> 'unknown' + end. + +find_constraint(_Tuple, []) -> + 'unknown'; +find_constraint(Tuple, [#constraint{op = 'eq', rhs = Tuple} = C|_]) -> + C; +find_constraint(Tuple, [#constraint_list{list = List}|Cs]) -> + find_constraint(Tuple, List ++ Cs); +find_constraint(Tuple, [_|Cs]) -> + find_constraint(Tuple, Cs). + %% ============================================================================ %% %% Pretty printer and debug facilities. @@ -2638,7 +2679,7 @@ format_type(Type) -> -ifdef(DEBUG_NAME_MAP). debug_make_name_map(Vars, Funs) -> Map = get(dialyzer_typesig_map), - NewMap = + NewMap = if Map =:= undefined -> debug_make_name_map(Vars, Funs, dict:new()); true -> debug_make_name_map(Vars, Funs, Map) end, @@ -2676,15 +2717,15 @@ pp_constraints(Cs, State) -> io:nl(), Res. -pp_constraints([List|Tail], Separator, Level, MaxDepth, +pp_constraints([List|Tail], Separator, Level, MaxDepth, State) when is_list(List) -> pp_constraints(List++Tail, Separator, Level, MaxDepth, State); -pp_constraints([#constraint_ref{id = Id}|Left], Separator, +pp_constraints([#constraint_ref{id = Id}|Left], Separator, Level, MaxDepth, State) -> Cs = state__get_cs(Id, State), io:format("%Ref ~w%", [t_var_name(Id)]), pp_constraints([Cs|Left], Separator, Level, MaxDepth, State); -pp_constraints([#constraint{lhs = Lhs, op = Op, rhs = Rhs}], _Separator, +pp_constraints([#constraint{lhs = Lhs, op = Op, rhs = Rhs}], _Separator, Level, MaxDepth, _State) -> io:format("~s ~w ~s", [format_type(Lhs), Op, format_type(Rhs)]), erlang:max(Level, MaxDepth); @@ -2721,7 +2762,7 @@ pp_constrs_scc(_SCC, _State) -> constraints_to_dot_scc(SCC, State) -> io:format("SCC: ~p\n", [SCC]), - Name = lists:flatten([io_lib:format("'~w'", [debug_lookup_name(Fun)]) + Name = lists:flatten([io_lib:format("'~w'", [debug_lookup_name(Fun)]) || Fun <- SCC]), Cs = [state__get_cs(Fun, State) || Fun <- SCC], constraints_to_dot(Cs, Name, State). @@ -2737,22 +2778,22 @@ constraints_to_dot(Cs0, Name, State) -> constraints_to_nodes([{Name, #constraint_list{type = Type, list = List, id=Id}} |Left], N, Level, Graph, Opts, State) -> - N1 = N + length(List), + N1 = N + length(List), NewList = lists:zip(lists:seq(N, N1 - 1), List), Names = [SubName || {SubName, _C} <- NewList], Edges = [{Name, SubName} || SubName <- Names], - ThisNode = [{Name, Opt} || Opt <- [{label, + ThisNode = [{Name, Opt} || Opt <- [{label, lists:flatten(io_lib:format("~w", [Id]))}, {shape, get_shape(Type)}, {level, Level}]], - {NewGraph, NewOpts, N2} = constraints_to_nodes(NewList, N1, Level+1, - [Edges|Graph], + {NewGraph, NewOpts, N2} = constraints_to_nodes(NewList, N1, Level+1, + [Edges|Graph], [ThisNode|Opts], State), constraints_to_nodes(Left, N2, Level, NewGraph, NewOpts, State); constraints_to_nodes([{Name, #constraint{lhs = Lhs, op = Op, rhs = Rhs}}|Left], N, Level, Graph, Opts, State) -> - Label = lists:flatten(io_lib:format("~s ~w ~s", - [format_type(Lhs), Op, + Label = lists:flatten(io_lib:format("~s ~w ~s", + [format_type(Lhs), Op, format_type(Rhs)])), ThisNode = [{Name, Opt} || Opt <- [{label, Label}, {level, Level}]], NewOpts = [ThisNode|Opts], @@ -2761,20 +2802,20 @@ constraints_to_nodes([{Name, #constraint_ref{id = Id0}}|Left], N, Level, Graph, Opts, State) -> Id = debug_lookup_name(Id0), CList = state__get_cs(Id0, State), - ThisNode = [{Name, Opt} || Opt <- [{label, + ThisNode = [{Name, Opt} || Opt <- [{label, lists:flatten(io_lib:format("~w", [Id]))}, {shape, ellipse}, - {level, Level}]], - NewList = [{N, CList}], - {NewGraph, NewOpts, N1} = constraints_to_nodes(NewList, N + 1, Level + 1, + {level, Level}]], + NewList = [{N, CList}], + {NewGraph, NewOpts, N1} = constraints_to_nodes(NewList, N + 1, Level + 1, [{Name, N}|Graph], [ThisNode|Opts], State), constraints_to_nodes(Left, N1, Level, NewGraph, NewOpts, State); constraints_to_nodes([], N, _Level, Graph, Opts, _State) -> {lists:flatten(Graph), lists:flatten(Opts), N}. - + get_shape(conj) -> box; -get_shape(disj) -> diamond. +get_shape(disj) -> diamond. -else. constraints_to_dot_scc(_SCC, _State) -> diff --git a/lib/dialyzer/src/dialyzer_utils.erl b/lib/dialyzer/src/dialyzer_utils.erl index 338027c5ab..a9da229061 100644 --- a/lib/dialyzer/src/dialyzer_utils.erl +++ b/lib/dialyzer/src/dialyzer_utils.erl @@ -21,7 +21,7 @@ %%%------------------------------------------------------------------- %%% File : dialyzer_utils.erl %%% Author : Tobias Lindahl <[email protected]> -%%% Description : +%%% Description : %%% %%% Created : 5 Dec 2006 by Tobias Lindahl <[email protected]> %%%------------------------------------------------------------------- @@ -74,12 +74,11 @@ print_types1([{record, _Name} = Key|T], RecDict) -> -define(debug(D_), ok). -endif. -%% -%% Types that need to be imported from somewhere else -%% +%% ---------------------------------------------------------------------------- --type abstract_code() :: [tuple()]. %% XXX: refine --type comp_options() :: [atom()]. %% XXX: a restricted set of options is used +-type abstract_code() :: [tuple()]. %% XXX: import from somewhere +-type comp_options() :: [compile:option()]. +-type mod_or_fname() :: atom() | file:filename(). %% ============================================================================ %% @@ -87,13 +86,13 @@ print_types1([{record, _Name} = Key|T], RecDict) -> %% %% ============================================================================ --spec get_abstract_code_from_src(atom() | file:filename()) -> +-spec get_abstract_code_from_src(mod_or_fname()) -> {'ok', abstract_code()} | {'error', [string()]}. get_abstract_code_from_src(File) -> get_abstract_code_from_src(File, src_compiler_opts()). --spec get_abstract_code_from_src(atom() | file:filename(), comp_options()) -> +-spec get_abstract_code_from_src(mod_or_fname(), comp_options()) -> {'ok', abstract_code()} | {'error', [string()]}. get_abstract_code_from_src(File, Opts) -> @@ -176,7 +175,7 @@ get_record_and_type_info(AbstractCode) -> get_record_and_type_info(AbstractCode, Module, RecDict) -> get_record_and_type_info(AbstractCode, Module, [], RecDict). -get_record_and_type_info([{attribute, _, record, {Name, Fields0}}|Left], +get_record_and_type_info([{attribute, _, record, {Name, Fields0}}|Left], Module, Records, RecDict) -> {ok, Fields} = get_record_fields(Fields0, RecDict), Arity = length(Fields), @@ -189,7 +188,7 @@ get_record_and_type_info([{attribute, _, type, {{record, Name}, Fields0, []}} Arity = length(Fields), NewRecDict = dict:store({record, Name}, [{Arity, Fields}], RecDict), get_record_and_type_info(Left, Module, Records, NewRecDict); -get_record_and_type_info([{attribute, _, Attr, {Name, TypeForm}}|Left], +get_record_and_type_info([{attribute, _, Attr, {Name, TypeForm}}|Left], Module, Records, RecDict) when Attr =:= 'type'; Attr =:= 'opaque' -> try @@ -198,7 +197,7 @@ get_record_and_type_info([{attribute, _, Attr, {Name, TypeForm}}|Left], catch throw:{error, _} = Error -> Error end; -get_record_and_type_info([{attribute, _, Attr, {Name, TypeForm, Args}}|Left], +get_record_and_type_info([{attribute, _, Attr, {Name, TypeForm, Args}}|Left], Module, Records, RecDict) when Attr =:= 'type'; Attr =:= 'opaque' -> try @@ -220,7 +219,7 @@ get_record_and_type_info([], _Module, Records, RecDict) -> end. add_new_type(TypeOrOpaque, Name, TypeForm, ArgForms, Module, RecDict) -> - case erl_types:type_is_defined(TypeOrOpaque, Name, RecDict) of + case erl_types:type_is_defined(TypeOrOpaque, Name, RecDict) of true -> throw({error, io_lib:format("Type already defined: ~w\n", [Name])}); false -> @@ -238,7 +237,7 @@ add_new_type(TypeOrOpaque, Name, TypeForm, ArgForms, Module, RecDict) -> get_record_fields(Fields, RecDict) -> get_record_fields(Fields, RecDict, []). -get_record_fields([{typed_record_field, OrdRecField, TypeForm}|Left], +get_record_fields([{typed_record_field, OrdRecField, TypeForm}|Left], RecDict, Acc) -> Name = case OrdRecField of @@ -313,19 +312,19 @@ merge_records(NewRecords, OldRecords) -> %% %% ============================================================================ --spec get_spec_info(module(), abstract_code(), dict()) -> +-spec get_spec_info(atom(), abstract_code(), dict()) -> {'ok', dict()} | {'error', string()}. get_spec_info(ModName, AbstractCode, RecordsDict) -> get_spec_info(AbstractCode, dict:new(), RecordsDict, ModName, "nofile"). -%% TypeSpec is a list of conditional contracts for a function. +%% TypeSpec is a list of conditional contracts for a function. %% Each contract is of the form {[Argument], Range, [Constraint]} where %% - Argument and Range are in erl_types:erl_type() format and %% - Constraint is of the form {subtype, T1, T2} where T1 and T2 %% are erl_types:erl_type() -get_spec_info([{attribute, Ln, spec, {Id, TypeSpec}}|Left], +get_spec_info([{attribute, Ln, spec, {Id, TypeSpec}}|Left], SpecDict, RecordsDict, ModName, File) when is_list(TypeSpec) -> MFA = case Id of {_, _, _} = T -> T; @@ -340,7 +339,7 @@ get_spec_info([{attribute, Ln, spec, {Id, TypeSpec}}|Left], {ok, {{OtherFile, L},_C}} -> {Mod, Fun, Arity} = MFA, Msg = io_lib:format(" Contract for function ~w:~w/~w " - "already defined in ~s:~w\n", + "already defined in ~s:~w\n", [Mod, Fun, Arity, OtherFile, L]), throw({error, Msg}) catch @@ -376,7 +375,7 @@ sets_filter([Mod|Mods], ExpTypes) -> %% %% ============================================================================ --spec src_compiler_opts() -> comp_options(). +-spec src_compiler_opts() -> [compile:option(),...]. src_compiler_opts() -> [no_copt, to_core, binary, return_errors, @@ -401,7 +400,7 @@ cleanup_parse_transforms([]) -> -spec format_errors([{module(), string()}]) -> [string()]. format_errors([{Mod, Errors}|Left]) -> - FormatedError = + FormatedError = [io_lib:format("~s:~w: ~s\n", [Mod, Line, M:format_error(Desc)]) || {Line, M, Desc} <- Errors], [lists:flatten(FormatedError) | format_errors(Left)]; @@ -476,7 +475,7 @@ pp_size(Size, Ctxt, Cont) -> end. pp_opts(Type, Flags) -> - FinalFlags = + FinalFlags = case cerl:atom_val(Type) of binary -> []; float -> keep_endian(cerl:concrete(Flags)); diff --git a/lib/dialyzer/vsn.mk b/lib/dialyzer/vsn.mk index e3e3f6d668..f2daf86def 100644 --- a/lib/dialyzer/vsn.mk +++ b/lib/dialyzer/vsn.mk @@ -1 +1 @@ -DIALYZER_VSN = 2.2.0 +DIALYZER_VSN = 2.3.0 diff --git a/lib/hipe/cerl/cerl_closurean.erl b/lib/hipe/cerl/cerl_closurean.erl index 12771668ac..021acd5b35 100644 --- a/lib/hipe/cerl/cerl_closurean.erl +++ b/lib/hipe/cerl/cerl_closurean.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2003-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 2003-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% %% ===================================================================== @@ -808,7 +808,7 @@ take_work({Queue0, Set0}) -> is_escape_op(match_fail, 1) -> false; is_escape_op(F, A) when is_atom(F), is_integer(A) -> true. --spec is_escape_op(module(), atom(), arity()) -> boolean(). +-spec is_escape_op(atom(), atom(), arity()) -> boolean(). is_escape_op(erlang, error, 1) -> false; is_escape_op(erlang, error, 2) -> false; @@ -825,7 +825,7 @@ is_escape_op(M, F, A) when is_atom(M), is_atom(F), is_integer(A) -> true. is_literal_op(match_fail, 1) -> true; is_literal_op(F, A) when is_atom(F), is_integer(A) -> false. --spec is_literal_op(module(), atom(), arity()) -> boolean(). +-spec is_literal_op(atom(), atom(), arity()) -> boolean(). is_literal_op(erlang, '+', 2) -> true; is_literal_op(erlang, '-', 2) -> true; diff --git a/lib/hipe/cerl/cerl_messagean.erl b/lib/hipe/cerl/cerl_messagean.erl index 0753376e7d..6dd93adaa3 100644 --- a/lib/hipe/cerl/cerl_messagean.erl +++ b/lib/hipe/cerl/cerl_messagean.erl @@ -1,19 +1,19 @@ %% ===================================================================== %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2004-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 2004-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% %% Message analysis of Core Erlang programs. @@ -1043,7 +1043,7 @@ get_deps(L, Dep) -> %% is_escape_op(_F, _A) -> []. --spec is_escape_op(module(), atom(), arity()) -> [arity()]. +-spec is_escape_op(atom(), atom(), arity()) -> [arity()]. is_escape_op(erlang, '!', 2) -> [2]; is_escape_op(erlang, send, 2) -> [2]; @@ -1064,7 +1064,7 @@ is_escape_op(_M, _F, _A) -> []. is_imm_op(match_fail, 1) -> true; is_imm_op(_, _) -> false. --spec is_imm_op(module(), atom(), arity()) -> boolean(). +-spec is_imm_op(atom(), atom(), arity()) -> boolean(). is_imm_op(erlang, self, 0) -> true; is_imm_op(erlang, '=:=', 2) -> true; @@ -1102,4 +1102,4 @@ is_imm_op(erlang, throw, 1) -> true; is_imm_op(erlang, exit, 1) -> true; is_imm_op(erlang, error, 1) -> true; is_imm_op(erlang, error, 2) -> true; -is_imm_op(_, _, _) -> false. +is_imm_op(_M, _F, _A) -> false. diff --git a/lib/hipe/cerl/erl_bif_types.erl b/lib/hipe/cerl/erl_bif_types.erl index be3073c0e6..838f9429f0 100644 --- a/lib/hipe/cerl/erl_bif_types.erl +++ b/lib/hipe/cerl/erl_bif_types.erl @@ -143,6 +143,51 @@ type(M, F, A) -> -spec type(atom(), atom(), arity(), [erl_types:erl_type()]) -> erl_types:erl_type(). +%%-- binary ------------------------------------------------------------------- +type(binary, at, 2, Xs) -> + strict(arg_types(binary, at, 2), Xs, fun(_) -> t_integer() end); +type(binary, bin_to_list, Arity, Xs) when 1 =< Arity, Arity =< 3 -> + strict(arg_types(binary, bin_to_list, Arity), Xs, + fun(_) -> t_list(t_integer()) end); +type(binary, compile_pattern, 1, Xs) -> + strict(arg_types(binary, compile_pattern, 1), Xs, + fun(_) -> t_tuple([t_atom(bm),t_binary()]) end); +type(binary, copy, Arity, Xs) when Arity =:= 1; Arity =:= 2 -> + strict(arg_types(binary, copy, Arity), Xs, + fun(_) -> t_binary() end); +type(binary, decode_unsigned, Arity, Xs) when Arity =:= 1; Arity =:= 2 -> + strict(arg_types(binary, decode_unsigned, Arity), Xs, + fun(_) -> t_non_neg_integer() end); +type(binary, encode_unsigned, Arity, Xs) when Arity =:= 1; Arity =:= 2 -> + strict(arg_types(binary, encode_unsigned, Arity), Xs, + fun(_) -> t_binary() end); +type(binary, first, 1, Xs) -> + strict(arg_types(binary, first, 1), Xs, fun(_) -> t_non_neg_integer() end); +type(binary, last, 1, Xs) -> + strict(arg_types(binary, last, 1), Xs, fun(_) -> t_non_neg_integer() end); +type(binary, list_to_bin, 1, Xs) -> + type(erlang, list_to_binary, 1, Xs); +type(binary, longest_common_prefix, 1, Xs) -> + strict(arg_types(binary, longest_common_prefix, 1), Xs, + fun(_) -> t_integer() end); +type(binary, longest_common_suffix, 1, Xs) -> + strict(arg_types(binary, longest_common_suffix, 1), Xs, + fun(_) -> t_integer() end); +type(binary, match, Arity, Xs) when Arity =:= 2; Arity =:= 3 -> + strict(arg_types(binary, match, Arity), Xs, + fun(_) -> + t_sup(t_atom('nomatch'), t_binary_canonical_part()) + end); +type(binary, matches, Arity, Xs) when Arity =:= 2; Arity =:= 3 -> + strict(arg_types(binary, matches, Arity), Xs, + fun(_) -> t_list(t_binary_canonical_part()) end); +type(binary, part, 2, Xs) -> + type(erlang, binary_part, 2, Xs); +type(binary, part, 3, Xs) -> + type(erlang, binary_part, 3, Xs); +type(binary, referenced_byte_size, 1, Xs) -> + strict(arg_types(binary, referenced_byte_size, 1), Xs, + fun(_) -> t_non_neg_integer() end); %%-- code --------------------------------------------------------------------- type(code, add_path, 1, Xs) -> strict(arg_types(code, add_path, 1), Xs, @@ -665,6 +710,14 @@ type(erlang, 'bnot', 1, Xs) -> %% strict(arg_types(erlang, 'bnot', 1), Xs, fun (_) -> t_integer() end); type(erlang, abs, 1, Xs) -> strict(arg_types(erlang, abs, 1), Xs, fun ([X]) -> X end); +type(erlang, adler32, 1, Xs) -> + strict(arg_types(erlang, adler32, 1), Xs, fun (_) -> t_adler32() end); +type(erlang, adler32, 2, Xs) -> + strict(arg_types(erlang, adler32, 2), Xs, fun (_) -> t_adler32() end); +type(erlang, adler32_combine, 3, Xs) -> + strict(arg_types(erlang, adler32_combine, 3), Xs, + fun (_) -> t_adler32() end); +type(erlang, append, 2, Xs) -> type(erlang, '++', 2, Xs); % alias type(erlang, append_element, 2, Xs) -> strict(arg_types(erlang, append_element, 2), Xs, fun (_) -> t_tuple() end); type(erlang, apply, 2, Xs) -> @@ -683,6 +736,10 @@ type(erlang, atom_to_binary, 2, Xs) -> strict(arg_types(erlang, atom_to_binary, 2), Xs, fun (_) -> t_binary() end); type(erlang, atom_to_list, 1, Xs) -> strict(arg_types(erlang, atom_to_list, 1), Xs, fun (_) -> t_string() end); +type(erlang, binary_part, 2, Xs) -> + strict(arg_types(erlang, binary_part, 2), Xs, fun (_) -> t_binary() end); +type(erlang, binary_part, 3, Xs) -> + strict(arg_types(erlang, binary_part, 3), Xs, fun (_) -> t_binary() end); type(erlang, binary_to_atom, 2, Xs) -> strict(arg_types(erlang, binary_to_atom, 2), Xs, fun (_) -> t_atom() end); type(erlang, binary_to_existing_atom, 2, Xs) -> @@ -726,11 +783,11 @@ type(erlang, check_process_code, 2, Xs) -> type(erlang, concat_binary, 1, Xs) -> strict(arg_types(erlang, concat_binary, 1), Xs, fun (_) -> t_binary() end); type(erlang, crc32, 1, Xs) -> - strict(arg_types(erlang, crc32, 1), Xs, fun (_) -> t_integer() end); + strict(arg_types(erlang, crc32, 1), Xs, fun (_) -> t_crc32() end); type(erlang, crc32, 2, Xs) -> - strict(arg_types(erlang, crc32, 2), Xs, fun (_) -> t_integer() end); + strict(arg_types(erlang, crc32, 2), Xs, fun (_) -> t_crc32() end); type(erlang, crc32_combine, 3, Xs) -> - strict(arg_types(erlang, crc32_combine, 3), Xs, fun (_) -> t_integer() end); + strict(arg_types(erlang, crc32_combine, 3), Xs, fun (_) -> t_crc32() end); type(erlang, date, 0, _) -> t_date(); type(erlang, decode_packet, 3, Xs) -> @@ -752,6 +809,10 @@ type(erlang, demonitor, 2, Xs) -> type(erlang, disconnect_node, 1, Xs) -> strict(arg_types(erlang, disconnect_node, 1), Xs, fun (_) -> t_boolean() end); type(erlang, display, 1, _) -> t_atom('true'); +type(erlang, display_string, 1, Xs) -> + strict(arg_types(erlang, display_string, 1), Xs, fun(_) -> t_atom('true') end); +type(erlang, display_nl, 0, _) -> + t_atom('true'); type(erlang, dist_exit, 3, Xs) -> strict(arg_types(erlang, dist_exit, 3), Xs, fun (_) -> t_atom('true') end); type(erlang, element, 2, Xs) -> @@ -802,6 +863,8 @@ type(erlang, fun_to_list, 1, Xs) -> type(erlang, garbage_collect, 0, _) -> t_atom('true'); type(erlang, garbage_collect, 1, Xs) -> strict(arg_types(erlang, garbage_collect, 1), Xs, fun (_) -> t_boolean() end); +type(erlang, garbage_collect_message_area, 0, _) -> + t_boolean(); type(erlang, get, 0, _) -> t_list(t_tuple(2)); type(erlang, get, 1, _) -> t_any(); % | t_atom('undefined') type(erlang, get_cookie, 0, _) -> t_atom(); % | t_atom('nocookie') @@ -1155,6 +1218,10 @@ type(erlang, monitor_node, 2, Xs) -> type(erlang, monitor_node, 3, Xs) -> strict(arg_types(erlang, monitor_node, 3), Xs, fun (_) -> t_atom('true') end); +type(erlang, nif_error, 1, _) -> + t_any(); +type(erlang, nif_error, 2, Xs) -> + strict(arg_types(erlang, nif_error, 2), Xs, fun (_) -> t_any() end); type(erlang, node, 0, _) -> t_node(); type(erlang, node, 1, Xs) -> strict(arg_types(erlang, node, 1), Xs, fun (_) -> t_node() end); @@ -1173,8 +1240,8 @@ type(erlang, phash2, 2, Xs) -> strict(arg_types(erlang, phash2, 2), Xs, fun (_) -> t_non_neg_integer() end); type(erlang, pid_to_list, 1, Xs) -> strict(arg_types(erlang, pid_to_list, 1), Xs, fun (_) -> t_string() end); -type(erlang, port_call, 3, Xs) -> - strict(arg_types(erlang, port_call, 3), Xs, fun (_) -> t_any() end); +type(erlang, port_call, Arity, Xs) when Arity =:= 2; Arity =:= 3 -> + strict(arg_types(erlang, port_call, Arity), Xs, fun (_) -> t_any() end); type(erlang, port_close, 1, Xs) -> strict(arg_types(erlang, port_close, 1), Xs, fun (_) -> t_atom('true') end); @@ -1503,6 +1570,7 @@ type(erlang, statistics, 1, Xs) -> T_statistics_1 end end); +type(erlang, subtract, 2, Xs) -> type(erlang, '--', 2, Xs); % alias type(erlang, suspend_process, 1, Xs) -> strict(arg_types(erlang, suspend_process, 1), Xs, fun (_) -> t_atom('true') end); @@ -1595,7 +1663,7 @@ type(erlang, system_info, 1, Xs) -> t_sup([t_atom('false'), t_list(t_tuple([t_atom(), t_any()]))]); ['endian'] -> - t_sup([t_atom('big'), t_atom('little')]); + t_endian(); ['fullsweep_after'] -> t_tuple([t_atom('fullsweep_after'), t_non_neg_integer()]); ['garbage_collection'] -> @@ -1607,9 +1675,8 @@ type(erlang, system_info, 1, Xs) -> ['heap_type'] -> t_sup([t_atom('private'), t_atom('hybrid')]); ['hipe_architecture'] -> - t_sup([t_atom('amd64'), t_atom('arm'), - t_atom('powerpc'), t_atom('undefined'), - t_atom('ultrasparc'), t_atom('x86')]); + t_atoms(['amd64', 'arm', 'powerpc', 'ppc64', + 'undefined', 'ultrasparc', 'x86']); ['info'] -> t_binary(); ['internal_cpu_topology'] -> %% Undocumented internal feature @@ -1785,11 +1852,21 @@ type(erts_debug, disassemble, 1, Xs) -> fun (_) -> t_sup([t_atom('false'), t_atom('undef'), t_tuple([t_integer(), t_binary(), t_mfa()])]) end); +type(erts_debug, display, 1, _) -> + t_string(); type(erts_debug, dist_ext_to_term, 2, Xs) -> strict(arg_types(erts_debug, dist_ext_to_term, 2), Xs, fun (_) -> t_any() end); +type(erts_debug, dump_monitors, 1, Xs) -> + strict(arg_types(erts_debug, dump_monitors, 1), Xs, + fun(_) -> t_atom('true') end); +type(erts_debug, dump_links, 1, Xs) -> + strict(arg_types(erts_debug, dump_links, 1), Xs, + fun(_) -> t_atom('true') end); type(erts_debug, flat_size, 1, Xs) -> strict(arg_types(erts_debug, flat_size, 1), Xs, fun (_) -> t_integer() end); +type(erts_debug, get_internal_state, 1, _) -> + t_any(); type(erts_debug, lock_counters, 1, Xs) -> strict(arg_types(erts_debug, lock_counters, 1), Xs, fun ([Arg]) -> @@ -1810,6 +1887,8 @@ type(erts_debug, lock_counters, 1, Xs) -> end); type(erts_debug, same, 2, Xs) -> strict(arg_types(erts_debug, same, 2), Xs, fun (_) -> t_boolean() end); +type(erts_debug, set_internal_state, 2, _) -> + t_any(); %%-- ets ---------------------------------------------------------------------- type(ets, all, 0, _) -> t_list(t_tab()); @@ -2099,7 +2178,7 @@ type(hipe_bifs, set_native_address, 3, Xs) -> strict(arg_types(hipe_bifs, set_native_address, 3), Xs, fun (_) -> t_nil() end); type(hipe_bifs, system_crc, 1, Xs) -> - strict(arg_types(hipe_bifs, system_crc, 1), Xs, fun (_) -> t_integer() end); + strict(arg_types(hipe_bifs, system_crc, 1), Xs, fun (_) -> t_crc32() end); type(hipe_bifs, term_to_word, 1, Xs) -> strict(arg_types(hipe_bifs, term_to_word, 1), Xs, fun (_) -> t_integer() end); @@ -3194,6 +3273,53 @@ arith(Op, X1, X2) -> -spec arg_types(atom(), atom(), arity()) -> [erl_types:erl_type()] | 'unknown'. +%%------- binary -------------------------------------------------------------- +arg_types(binary, at, 2) -> + [t_binary(), t_non_neg_integer()]; +arg_types(binary, bin_to_list, 1) -> + [t_binary()]; +arg_types(binary, bin_to_list, 2) -> + [t_binary(), t_binary_part()]; +arg_types(binary, bin_to_list, 3) -> + [t_binary(), t_integer(), t_non_neg_integer()]; +arg_types(binary, compile_pattern, 1) -> + [t_sup(t_binary(), t_list(t_binary()))]; +arg_types(binary, copy, 1) -> + [t_binary()]; +arg_types(binary, copy, 2) -> + [t_binary(), t_non_neg_integer()]; +arg_types(binary, decode_unsigned, 1) -> + [t_binary()]; +arg_types(binary, decode_unsigned, 2) -> + [t_binary(), t_endian()]; +arg_types(binary, encode_unsigned, 1) -> + [t_non_neg_integer()]; +arg_types(binary, encode_unsigned, 2) -> + [t_non_neg_integer(), t_endian()]; +arg_types(binary, first, 1) -> + [t_binary()]; +arg_types(binary, last, 1) -> + [t_binary()]; +arg_types(binary, list_to_bin, 1) -> + arg_types(erlang, list_to_binary, 1); +arg_types(binary, longest_common_prefix, 1) -> + [t_list(t_binary())]; +arg_types(binary, longest_common_suffix, 1) -> + [t_list(t_binary())]; +arg_types(binary, match, 2) -> + [t_binary(), t_binary_pattern()]; +arg_types(binary, match, 3) -> + [t_binary(), t_binary_pattern(), t_binary_options()]; +arg_types(binary, matches, 2) -> + [t_binary(), t_binary_pattern()]; +arg_types(binary, matches, 3) -> + [t_binary(), t_binary_pattern(), t_binary_options()]; +arg_types(binary, part, 2) -> + arg_types(erlang, binary_part, 2); +arg_types(binary, part, 3) -> + arg_types(erlang, binary_part, 3); +arg_types(binary, referenced_byte_size, 1) -> + [t_binary()]; %%------- code ---------------------------------------------------------------- arg_types(code, add_path, 1) -> [t_string()]; @@ -3375,6 +3501,14 @@ arg_types(erlang, 'bnot', 1) -> [t_integer()]; arg_types(erlang, abs, 1) -> [t_number()]; +arg_types(erlang, adler32, 1) -> + [t_iodata()]; +arg_types(erlang, adler32, 2) -> + [t_adler32(), t_iodata()]; +arg_types(erlang, adler32_combine, 3) -> + [t_adler32(), t_adler32(), t_non_neg_integer()]; +arg_types(erlang, append, 2) -> + arg_types(erlang, '++', 2); arg_types(erlang, append_element, 2) -> [t_tuple(), t_any()]; arg_types(erlang, apply, 2) -> @@ -3388,6 +3522,10 @@ arg_types(erlang, atom_to_binary, 2) -> [t_atom(), t_encoding_a2b()]; arg_types(erlang, atom_to_list, 1) -> [t_atom()]; +arg_types(erlang, binary_part, 2) -> + [t_binary(), t_tuple([t_integer(),t_integer()])]; +arg_types(erlang, binary_part, 3) -> + [t_binary(), t_integer(), t_integer()]; arg_types(erlang, binary_to_atom, 2) -> [t_binary(), t_encoding_a2b()]; arg_types(erlang, binary_to_existing_atom, 2) -> @@ -3423,9 +3561,9 @@ arg_types(erlang, concat_binary, 1) -> arg_types(erlang, crc32, 1) -> [t_iodata()]; arg_types(erlang, crc32, 2) -> - [t_integer(), t_iodata()]; + [t_crc32(), t_iodata()]; arg_types(erlang, crc32_combine, 3) -> - [t_integer(), t_integer(), t_integer()]; + [t_crc32(), t_crc32(), t_non_neg_integer()]; arg_types(erlang, date, 0) -> []; arg_types(erlang, decode_packet, 3) -> @@ -3440,6 +3578,10 @@ arg_types(erlang, disconnect_node, 1) -> [t_node()]; arg_types(erlang, display, 1) -> [t_any()]; +arg_types(erlang, display_nl, 0) -> + []; +arg_types(erlang, display_string, 1) -> + [t_string()]; arg_types(erlang, dist_exit, 3) -> [t_pid(), t_dist_exit(), t_sup(t_pid(), t_port())]; arg_types(erlang, element, 2) -> @@ -3476,6 +3618,8 @@ arg_types(erlang, garbage_collect, 0) -> []; arg_types(erlang, garbage_collect, 1) -> [t_pid()]; +arg_types(erlang, garbage_collect_message_area, 0) -> + []; arg_types(erlang, get, 0) -> []; arg_types(erlang, get, 1) -> @@ -3628,6 +3772,10 @@ arg_types(erlang, monitor_node, 2) -> [t_node(), t_boolean()]; arg_types(erlang, monitor_node, 3) -> [t_node(), t_boolean(), t_list(t_atom('allow_passive_connect'))]; +arg_types(erlang, nif_error, 1) -> + [t_any()]; +arg_types(erlang, nif_error, 2) -> + [t_any(), t_list()]; arg_types(erlang, node, 0) -> []; arg_types(erlang, node, 1) -> @@ -3668,6 +3816,8 @@ arg_types(erlang, phash2, 2) -> [t_any(), t_pos_integer()]; arg_types(erlang, pid_to_list, 1) -> [t_pid()]; +arg_types(erlang, port_call, 2) -> + [t_sup(t_port(), t_atom()), t_any()]; arg_types(erlang, port_call, 3) -> [t_sup(t_port(), t_atom()), t_integer(), t_any()]; arg_types(erlang, port_close, 1) -> @@ -3795,6 +3945,8 @@ arg_types(erlang, statistics, 1) -> t_atom('run_queue'), t_atom('runtime'), t_atom('wall_clock')])]; +arg_types(erlang, subtract, 2) -> + arg_types(erlang, '--', 2); arg_types(erlang, suspend_process, 1) -> [t_pid()]; arg_types(erlang, suspend_process, 2) -> @@ -3917,10 +4069,18 @@ arg_types(erts_debug, breakpoint, 2) -> [t_tuple([t_atom(), t_atom(), t_sup(t_integer(), t_atom('_'))]), t_boolean()]; arg_types(erts_debug, disassemble, 1) -> [t_sup(t_mfa(), t_integer())]; +arg_types(erts_debug, display, 1) -> + [t_any()]; arg_types(erts_debug, dist_ext_to_term, 2) -> [t_tuple(), t_binary()]; +arg_types(erts_debug, dump_monitors, 1) -> + [t_sup([t_pid(),t_atom()])]; +arg_types(erts_debug, dump_links, 1) -> + [t_sup([t_pid(),t_atom(),t_port()])]; arg_types(erts_debug, flat_size, 1) -> [t_any()]; +arg_types(erts_debug, get_internal_state, 1) -> + [t_any()]; arg_types(erts_debug, lock_counters, 1) -> [t_sup([t_atom(enabled), t_atom(info), @@ -3929,6 +4089,8 @@ arg_types(erts_debug, lock_counters, 1) -> t_tuple([t_atom(process_locks), t_boolean()])])]; arg_types(erts_debug, same, 2) -> [t_any(), t_any()]; +arg_types(erts_debug, set_internal_state, 2) -> + [t_any(), t_any()]; %%------- ets ----------------------------------------------------------------- arg_types(ets, all, 0) -> []; @@ -4109,7 +4271,7 @@ arg_types(hipe_bifs, call_count_off, 1) -> arg_types(hipe_bifs, call_count_on, 1) -> [t_mfa()]; arg_types(hipe_bifs, check_crc, 1) -> - [t_integer()]; + [t_crc32()]; arg_types(hipe_bifs, enter_code, 2) -> [t_binary(), t_sup(t_nil(), t_tuple())]; arg_types(hipe_bifs, enter_sdesc, 1) -> @@ -4153,7 +4315,7 @@ arg_types(hipe_bifs, set_funinfo_native_address, 3) -> arg_types(hipe_bifs, set_native_address, 3) -> [t_mfa(), t_integer(), t_boolean()]; arg_types(hipe_bifs, system_crc, 1) -> - [t_integer()]; + [t_crc32()]; arg_types(hipe_bifs, term_to_word, 1) -> [t_any()]; arg_types(hipe_bifs, update_code_size, 3) -> @@ -4467,6 +4629,30 @@ t_httppacket() -> t_sup([t_HttpRequest(), t_HttpResponse(), t_HttpHeader(), t_atom('http_eoh'), t_HttpError()]). +t_endian() -> + t_sup([t_atom('big'), t_atom('little')]). + +%% ===================================================================== +%% Types for the binary module +%% ===================================================================== + +t_binary_part() -> + t_tuple([t_non_neg_integer(),t_integer()]). + +t_binary_canonical_part() -> + t_tuple([t_non_neg_integer(),t_non_neg_integer()]). + +t_binary_pattern() -> + t_sup([t_binary(), + t_list(t_binary()), + t_binary_compiled_pattern()]). + +t_binary_compiled_pattern() -> + t_tuple([t_atom('cp'),t_binary()]). + +t_binary_options() -> + t_list(t_tuple([t_atom('scope'),t_binary_part()])). + %% ===================================================================== %% HTTP types documented in R12B-4 %% ===================================================================== @@ -4549,6 +4735,12 @@ t_code_loaded_fname_or_status() -> %% These are used for the built-in functions of 'erlang' %% ===================================================================== +t_adler32() -> + t_non_neg_integer(). + +t_crc32() -> + t_non_neg_integer(). + t_decode_packet_option() -> t_sup([t_tuple([t_atom('packet_size'), t_non_neg_integer()]), t_tuple([t_atom('line_length'), t_non_neg_integer()])]). diff --git a/lib/hipe/cerl/erl_types.erl b/lib/hipe/cerl/erl_types.erl index 758914ff9e..9a40be6d14 100644 --- a/lib/hipe/cerl/erl_types.erl +++ b/lib/hipe/cerl/erl_types.erl @@ -205,8 +205,10 @@ t_var_name/1, %% t_assign_variables_to_subtype/2, type_is_defined/3, + record_field_diffs_to_string/2, subst_all_vars_to_any/1, - lift_list_to_pos_empty/1 + lift_list_to_pos_empty/1, + is_erl_type/1 ]). %%-define(DO_ERL_TYPES_TEST, true). @@ -302,7 +304,7 @@ %% Auxiliary types and convenient macros %% --type parse_form() :: {atom(), _, _} | {atom(), _, _, _}. %% XXX: Temporarily +-type parse_form() :: {atom(), _, _} | {atom(), _, _, _} | {'op', _, _, _, _}. %% XXX: Temporarily -type rng_elem() :: 'pos_inf' | 'neg_inf' | integer(). -record(int_set, {set :: [integer()]}). @@ -652,7 +654,7 @@ module_builtin_opaques(Module) -> %% Remote types: these types are used for preprocessing; %% they should never reach the analysis stage. --spec t_remote(module(), atom(), [_]) -> erl_type(). +-spec t_remote(atom(), atom(), [erl_type()]) -> erl_type(). t_remote(Mod, Name, Args) -> ?remote(set_singleton(#remote{mod = Mod, name = Name, args = Args})). @@ -757,9 +759,8 @@ t_solve_remote_type(#remote{mod = RemMod, name = Name, args = Args} = RemType, throw({error, Msg}) end; false -> - Msg = io_lib:format("Unable to find exported type ~w:~w/~w\n", - [RemMod, Name, ArgsLen]), - throw({error, Msg}) + self() ! {self(), ext_types, {RemMod, Name, ArgsLen}}, + {t_any(), []} end end. @@ -1607,7 +1608,7 @@ t_set() -> t_tid() -> t_opaque(ets, tid, [], t_integer()). --spec all_opaque_builtins() -> [erl_type()]. +-spec all_opaque_builtins() -> [erl_type(),...]. all_opaque_builtins() -> [t_array(), t_dict(), t_digraph(), t_gb_set(), @@ -3311,28 +3312,44 @@ record_to_string(Tag, [_|Fields], FieldNames, RecDict) -> FieldStrings = record_fields_to_string(Fields, FieldNames, RecDict, []), "#" ++ atom_to_list(Tag) ++ "{" ++ sequence(FieldStrings, [], ",") ++ "}". -record_fields_to_string([Field|Left1], [{FieldName, DeclaredType}|Left2], - RecDict, Acc) -> - PrintType = - case t_is_equal(Field, DeclaredType) of - true -> false; +record_fields_to_string([F|Fs], [{FName, _DefType}|FDefs], RecDict, Acc) -> + NewAcc = + case t_is_any(F) orelse t_is_atom('undefined', F) of + true -> Acc; false -> - case t_is_any(DeclaredType) andalso t_is_atom(undefined, Field) of - true -> false; - false -> - TmpType = t_subtract(DeclaredType, t_atom(undefined)), - not t_is_equal(Field, TmpType) - end + StrFV = atom_to_list(FName) ++ "::" ++ t_to_string(F, RecDict), + %% ActualDefType = t_subtract(DefType, t_atom('undefined')), + %% Str = case t_is_any(ActualDefType) of + %% true -> StrFV; + %% false -> StrFV ++ "::" ++ t_to_string(ActualDefType, RecDict) + %% end, + [StrFV|Acc] end, - case PrintType of - false -> record_fields_to_string(Left1, Left2, RecDict, Acc); - true -> - String = atom_to_list(FieldName) ++ "::" ++ t_to_string(Field, RecDict), - record_fields_to_string(Left1, Left2, RecDict, [String|Acc]) - end; + record_fields_to_string(Fs, FDefs, RecDict, NewAcc); record_fields_to_string([], [], _RecDict, Acc) -> lists:reverse(Acc). +-spec record_field_diffs_to_string(erl_type(), dict()) -> string(). + +record_field_diffs_to_string(?tuple([_|Fs], Arity, Tag), RecDict) -> + [TagAtom] = t_atom_vals(Tag), + {ok, FieldNames} = lookup_record(TagAtom, Arity-1, RecDict), + %% io:format("RecCElems = ~p\nRecTypes = ~p\n", [Fs, FieldNames]), + FieldDiffs = field_diffs(Fs, FieldNames, RecDict, []), + sequence(FieldDiffs, [], " and "). + +field_diffs([F|Fs], [{FName, DefType}|FDefs], RecDict, Acc) -> + NewAcc = + case t_is_subtype(F, DefType) of + true -> Acc; + false -> + Str = atom_to_list(FName) ++ "::" ++ t_to_string(DefType, RecDict), + [Str|Acc] + end, + field_diffs(Fs, FDefs, RecDict, NewAcc); +field_diffs([], [], _, Acc) -> + lists:reverse(Acc). + comma_sequence(Types, RecDict) -> List = [case T =:= ?any of true -> "_"; @@ -3399,6 +3416,18 @@ t_from_form({atom, _L, Atom}, _TypeNames, _RecDict, _VarDict) -> {t_atom(Atom), []}; t_from_form({integer, _L, Int}, _TypeNames, _RecDict, _VarDict) -> {t_integer(Int), []}; +t_from_form({op, _L, _Op, _Arg} = Op, _TypeNames, _RecDict, _VarDict) -> + case erl_eval:partial_eval(Op) of + {integer, _, Val} -> + {t_integer(Val), []}; + _ -> throw({error, io_lib:format("Unable evaluate type ~w\n", [Op])}) + end; +t_from_form({op, _L, _Op, _Arg1, _Arg2} = Op, _TypeNames, _RecDict, _VarDict) -> + case erl_eval:partial_eval(Op) of + {integer, _, Val} -> + {t_integer(Val), []}; + _ -> throw({error, io_lib:format("Unable evaluate type ~w\n", [Op])}) + end; t_from_form({type, _L, any, []}, _TypeNames, _RecDict, _VarDict) -> {t_any(), []}; t_from_form({type, _L, arity, []}, _TypeNames, _RecDict, _VarDict) -> @@ -3409,9 +3438,15 @@ t_from_form({type, _L, atom, []}, _TypeNames, _RecDict, _VarDict) -> {t_atom(), []}; t_from_form({type, _L, binary, []}, _TypeNames, _RecDict, _VarDict) -> {t_binary(), []}; -t_from_form({type, _L, binary, [{integer, _, Base}, {integer, _, Unit}]}, +t_from_form({type, _L, binary, [Base, Unit]} = Type, _TypeNames, _RecDict, _VarDict) -> - {t_bitstr(Unit, Base), []}; + case {erl_eval:partial_eval(Base), erl_eval:partial_eval(Unit)} of + {{integer, _, BaseVal}, + {integer, _, UnitVal}} + when BaseVal >= 0, UnitVal >= 0 -> + {t_bitstr(UnitVal, BaseVal), []}; + _ -> throw({error, io_lib:format("Unable evaluate type ~w\n", [Type])}) + end; t_from_form({type, _L, bitstring, []}, _TypeNames, _RecDict, _VarDict) -> {t_bitstr(), []}; t_from_form({type, _L, bool, []}, _TypeNames, _RecDict, _VarDict) -> @@ -3515,9 +3550,14 @@ t_from_form({type, _L, product, Elements}, TypeNames, RecDict, VarDict) -> {t_product(L), R}; t_from_form({type, _L, queue, []}, _TypeNames, _RecDict, _VarDict) -> {t_queue(), []}; -t_from_form({type, _L, range, [{integer, _, From}, {integer, _, To}]}, +t_from_form({type, _L, range, [From, To]} = Type, _TypeNames, _RecDict, _VarDict) -> - {t_from_range(From, To), []}; + case {erl_eval:partial_eval(From), erl_eval:partial_eval(To)} of + {{integer, _, FromVal}, + {integer, _, ToVal}} -> + {t_from_range(FromVal, ToVal), []}; + _ -> throw({error, io_lib:format("Unable evaluate type ~w\n", [Type])}) + end; t_from_form({type, _L, record, [Name|Fields]}, TypeNames, RecDict, VarDict) -> record_from_form(Name, Fields, TypeNames, RecDict, VarDict); t_from_form({type, _L, reference, []}, _TypeNames, _RecDict, _VarDict) -> @@ -3692,6 +3732,16 @@ t_form_to_string({var, _L, Name}) -> atom_to_list(Name); t_form_to_string({atom, _L, Atom}) -> io_lib:write_string(atom_to_list(Atom), $'); % To quote or not to quote... ' t_form_to_string({integer, _L, Int}) -> integer_to_list(Int); +t_form_to_string({op, _L, _Op, _Arg} = Op) -> + case erl_eval:partial_eval(Op) of + {integer, _, _} = Int -> t_form_to_string(Int); + _ -> io_lib:format("Bad formed type ~w",[Op]) + end; +t_form_to_string({op, _L, _Op, _Arg1, _Arg2} = Op) -> + case erl_eval:partial_eval(Op) of + {integer, _, _} = Int -> t_form_to_string(Int); + _ -> io_lib:format("Bad formed type ~w",[Op]) + end; t_form_to_string({ann_type, _L, [Var, Type]}) -> t_form_to_string(Var) ++ "::" ++ t_form_to_string(Type); t_form_to_string({paren_type, _L, [Type]}) -> @@ -3718,8 +3768,12 @@ t_form_to_string({type, _L, nonempty_list, [Type]}) -> t_form_to_string({type, _L, nonempty_string, []}) -> "nonempty_string()"; t_form_to_string({type, _L, product, Elements}) -> "<" ++ sequence(t_form_to_string_list(Elements), ",") ++ ">"; -t_form_to_string({type, _L, range, [{integer, _, From}, {integer, _, To}]}) -> - io_lib:format("~w..~w", [From, To]); +t_form_to_string({type, _L, range, [From, To]} = Type) -> + case {erl_eval:partial_eval(From), erl_eval:partial_eval(To)} of + {{integer, _, FromVal}, {integer, _, ToVal}} -> + io_lib:format("~w..~w", [FromVal, ToVal]); + _ -> io_lib:format("Bad formed type ~w",[Type]) + end; t_form_to_string({type, _L, record, [{atom, _, Name}]}) -> io_lib:format("#~w{}", [Name]); t_form_to_string({type, _L, record, [{atom, _, Name}|Fields]}) -> @@ -3738,13 +3792,17 @@ t_form_to_string({type, _L, Name, []} = T) -> try t_to_string(t_from_form(T)) catch throw:{error, _} -> atom_to_list(Name) ++ "()" end; -t_form_to_string({type, _L, binary, [{integer, _, X}, {integer, _, Y}]}) -> - case Y of - 0 -> - case X of - 0 -> "<<>>"; - _ -> io_lib:format("<<_:~w>>", [X]) - end +t_form_to_string({type, _L, binary, [X,Y]} = Type) -> + case {erl_eval:partial_eval(X), erl_eval:partial_eval(Y)} of + {{integer, _, XVal}, {integer, _, YVal}} -> + case YVal of + 0 -> + case XVal of + 0 -> "<<>>"; + _ -> io_lib:format("<<_:~w>>", [XVal]) + end + end; + _ -> io_lib:format("Bad formed type ~w",[Type]) end; t_form_to_string({type, _L, Name, List}) -> io_lib:format("~w(~s)", [Name, sequence(t_form_to_string_list(List), ",")]). @@ -3776,6 +3834,8 @@ any_none_or_unit([?unit|_]) -> true; any_none_or_unit([_|Left]) -> any_none_or_unit(Left); any_none_or_unit([]) -> false. +-spec is_erl_type(any()) -> boolean(). + is_erl_type(?any) -> true; is_erl_type(?none) -> true; is_erl_type(?unit) -> true; diff --git a/lib/inets/test/httpd_SUITE.erl b/lib/inets/test/httpd_SUITE.erl index 3c9b5e41a7..3255cbec06 100644 --- a/lib/inets/test/httpd_SUITE.erl +++ b/lib/inets/test/httpd_SUITE.erl @@ -1775,7 +1775,16 @@ essl_time_test(Config) when is_list(Config) -> ssl_time_test(Tag, Config) when is_list(Config) -> %% <CONDITIONAL-SKIP> - Skippable = [win32], + FreeBSDVersionVerify = + fun() -> + case os:version() of + {7, 1, _} -> % We only have one such machine, so... + true; + _ -> + false + end + end, + Skippable = [win32, {unix, [{freebsd, FreeBSDVersionVerify}]}], Condition = fun() -> ?OS_BASED_SKIP(Skippable) end, ?NON_PC_TC_MAYBE_SKIP(Config, Condition), %% </CONDITIONAL-SKIP> @@ -2271,12 +2280,22 @@ ssl_restart_disturbing_block(Tag, Config) -> fun() -> case os:type() of {unix, linux} -> - HW = string:strip(os:cmd("uname -m"), right, $\n), - case HW of + case ?OSCMD("uname -m") of "ppc" -> - case inet:gethostname() of - {ok, "peach"} -> - true; + case file:read_file_info("/etc/fedora-release") of + {ok, _} -> + case ?OSCMD("awk '{print $2}' /etc/fedora-release") of + "release" -> + %% Fedora 7 and later + case ?OSCMD("awk '{print $3}' /etc/fedora-release") of + "7" -> + true; + _ -> + false + end; + _ -> + false + end; _ -> false end; diff --git a/lib/inets/test/inets_test_lib.erl b/lib/inets/test/inets_test_lib.erl index 707b8c026a..86fc2d1a32 100644 --- a/lib/inets/test/inets_test_lib.erl +++ b/lib/inets/test/inets_test_lib.erl @@ -31,10 +31,16 @@ -export([info/4, log/4, debug/4, print/4]). -export([check_body/1]). -export([millis/0, millis_diff/2, hours/1, minutes/1, seconds/1, sleep/1]). +-export([oscmd/1]). -export([non_pc_tc_maybe_skip/4, os_based_skip/1]). -export([flush/0]). -export([start_node/1, stop_node/1]). +%% -- Misc os command and stuff + +oscmd(Cmd) -> + string:strip(os:cmd(Cmd), right, $\n). + %% -- Misc node operation wrapper functions -- start_node(Name) -> diff --git a/lib/inets/test/inets_test_lib.hrl b/lib/inets/test/inets_test_lib.hrl index 12a43fa136..0cdb04139c 100644 --- a/lib/inets/test/inets_test_lib.hrl +++ b/lib/inets/test/inets_test_lib.hrl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2001-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 2001-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% %% @@ -46,6 +46,11 @@ -endif. +%% - OS Command and stuff + +-define(OSCMD(Cmd), inets_test_lib:oscmd(Cmd)). + + %% - Test case macros - -define(EXPANDABLE(I, C, F), inets_test_lib:expandable(I, C, F)). diff --git a/lib/kernel/src/net_kernel.erl b/lib/kernel/src/net_kernel.erl index 0e17c059e5..0e5cc8c2c6 100644 --- a/lib/kernel/src/net_kernel.erl +++ b/lib/kernel/src/net_kernel.erl @@ -1086,11 +1086,11 @@ do_spawn(SpawnFuncArgs, SpawnOpts, State) -> spawn_func(link,{From,Tag},M,F,A,Gleader) -> link(From), - async_gen_server_reply({From,Tag},self()), %% ahhh + gen_server:reply({From,Tag},self()), %% ahhh group_leader(Gleader,self()), apply(M,F,A); spawn_func(_,{From,Tag},M,F,A,Gleader) -> - async_gen_server_reply({From,Tag},self()), %% ahhh + gen_server:reply({From,Tag},self()), %% ahhh group_leader(Gleader,self()), apply(M,F,A). @@ -1527,10 +1527,12 @@ async_gen_server_reply(From, Msg) -> {Pid, Tag} = From, M = {Tag, Msg}, case catch erlang:send(Pid, M, [nosuspend, noconnect]) of - true -> - M; - false -> - spawn(fun() -> gen_server:reply(From, Msg) end); - EXIT -> + ok -> + ok; + nosuspend -> + spawn(fun() -> catch erlang:send(Pid, M, [noconnect]) end); + noconnect -> + ok; % The gen module takes care of this case. + {'EXIT', _}=EXIT -> EXIT end. diff --git a/lib/mnesia/doc/src/notes.xml b/lib/mnesia/doc/src/notes.xml index 66242398d9..b0bead0ba0 100644 --- a/lib/mnesia/doc/src/notes.xml +++ b/lib/mnesia/doc/src/notes.xml @@ -37,6 +37,22 @@ bugfixes for every release of Mnesia. Each release of Mnesia thus constitutes one section in this document. The title of each section is the version number of Mnesia.</p> + + <section><title>Mnesia 4.4.14</title> + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Added mnesia:subscribe(activity) contributed by Bernard + Duggan.</p> + <p> + Own Id: OTP-8519</p> + </item> + </list> + </section> + + </section> <section><title>Mnesia 4.4.13</title> diff --git a/lib/mnesia/src/mnesia.appup.src b/lib/mnesia/src/mnesia.appup.src index b3b9297db2..47c9bf9979 100644 --- a/lib/mnesia/src/mnesia.appup.src +++ b/lib/mnesia/src/mnesia.appup.src @@ -1,69 +1,7 @@ %% -*- erlang -*- {"%VSN%", - [ - {"4.4.12", - [ - {update, mnesia, soft, soft_purge, soft_purge, []}, - {update, mnesia_loader, soft, soft_purge, soft_purge, []}, - {update, mnesia_monitor, soft, soft_purge, soft_purge, []}, - {update, mnesia_tm, soft, soft_purge, soft_purge, []} - ] - }, - {"4.4.11", - [ - {update, mnesia, soft, soft_purge, soft_purge, []}, - {update, mnesia_loader, soft, soft_purge, soft_purge, []}, - {update, mnesia_monitor, soft, soft_purge, soft_purge, []}, - {update, mnesia_tm, soft, soft_purge, soft_purge, []}, - {update, mnesia_locker, soft, soft_purge, soft_purge, []}, - {update, mnesia_controller, soft, soft_purge, soft_purge, []} - ] - }, - {"4.4.10", - [ - {update, mnesia, soft, soft_purge, soft_purge, []}, - {update, mnesia_loader, soft, soft_purge, soft_purge, []}, - {update, mnesia_monitor, soft, soft_purge, soft_purge, []}, - {update, mnesia_tm, soft, soft_purge, soft_purge, []}, - {update, mnesia_locker, soft, soft_purge, soft_purge, []}, - {update, mnesia_controller, soft, soft_purge, soft_purge, []} - ] - }, - {"4.4.9", [{restart_application, mnesia}]}, - {"4.4.8", [{restart_application, mnesia}]}, - {"4.4.7", [{restart_application, mnesia}]} + [ ], [ - {"4.4.12", - [ - {update, mnesia, soft, soft_purge, soft_purge, []}, - {update, mnesia_loader, soft, soft_purge, soft_purge, []}, - {update, mnesia_monitor, soft, soft_purge, soft_purge, []}, - {update, mnesia_tm, soft, soft_purge, soft_purge, []} - ] - }, - {"4.4.11", - [ - {update, mnesia, soft, soft_purge, soft_purge, []}, - {update, mnesia_loader, soft, soft_purge, soft_purge, []}, - {update, mnesia_monitor, soft, soft_purge, soft_purge, []}, - {update, mnesia_tm, soft, soft_purge, soft_purge, []}, - {update, mnesia_locker, soft, soft_purge, soft_purge, []}, - {update, mnesia_controller, soft, soft_purge, soft_purge, []} - ] - }, - {"4.4.10", - [ - {update, mnesia, soft, soft_purge, soft_purge, []}, - {update, mnesia_loader, soft, soft_purge, soft_purge, []}, - {update, mnesia_monitor, soft, soft_purge, soft_purge, []}, - {update, mnesia_tm, soft, soft_purge, soft_purge, []}, - {update, mnesia_locker, soft, soft_purge, soft_purge, []}, - {update, mnesia_controller, soft, soft_purge, soft_purge, []} - ] - }, - {"4.4.9", [{restart_application, mnesia}]}, - {"4.4.8", [{restart_application, mnesia}]}, - {"4.4.7", [{restart_application, mnesia}]} ] }. diff --git a/lib/mnesia/test/Makefile b/lib/mnesia/test/Makefile new file mode 100644 index 0000000000..a4f32e3f78 --- /dev/null +++ b/lib/mnesia/test/Makefile @@ -0,0 +1,118 @@ +# +# %CopyrightBegin% +# +# Copyright Ericsson AB 1996-2009. All Rights Reserved. +# +# The contents of this file are subject to the Erlang Public License, +# Version 1.1, (the "License"); you may not use this file except in +# compliance with the License. You should have received a copy of the +# Erlang Public License along with this software. If not, it can be +# retrieved online at http://www.erlang.org/. +# +# Software distributed under the License is distributed on an "AS IS" +# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +# the License for the specific language governing rights and limitations +# under the License. +# +# %CopyrightEnd% +# +include $(ERL_TOP)/make/target.mk +include $(ERL_TOP)/make/$(TARGET)/otp.mk + +# ---------------------------------------------------- +# Target Specs +# ---------------------------------------------------- + +MODULES= \ + mt \ + mnesia_SUITE \ + mnesia_test_lib \ + mnesia_install_test \ + mnesia_registry_test \ + mnesia_config_test \ + mnesia_frag_test \ + mnesia_inconsistent_database_test \ + mnesia_config_backup \ + mnesia_config_event \ + mnesia_examples_test \ + mnesia_nice_coverage_test \ + mnesia_evil_coverage_test \ + mnesia_evil_backup \ + mnesia_trans_access_test \ + mnesia_dirty_access_test \ + mnesia_atomicity_test \ + mnesia_consistency_test \ + mnesia_isolation_test \ + mnesia_durability_test \ + mnesia_recovery_test \ + mnesia_qlc_test \ + mnesia_schema_recovery_test \ + mnesia_measure_test \ + mnesia_cost \ + mnesia_dbn_meters + +MnesiaExamplesDir := ../examples + +ExampleModules = \ + company \ + company_o \ + bup \ + mnesia_meter \ + mnesia_tpcb +ExamplesHrl = \ + company.hrl \ + company_o.hrl + +ERL_FILES= $(MODULES:%=%.erl) $(ExampleModules:%=$(MnesiaExamplesDir)/%.erl) + +HRL_FILES= mnesia_test_lib.hrl $(ExamplesHrl:%=$(MnesiaExamplesDir)/%) + +TARGET_FILES= \ + $(MODULES:%=$(EBIN)/%.$(EMULATOR)) $(ExampleModules:%=$(EBIN)/%.$(EMULATOR)) + +INSTALL_PROGS= $(TARGET_FILES) + +# ---------------------------------------------------- +# Release directory specification +# ---------------------------------------------------- +RELSYSDIR = $(RELEASE_PATH)/mnesia_test + +# ---------------------------------------------------- +# FLAGS +# ---------------------------------------------------- +#ERL_COMPILE_FLAGS += + +EBIN = . + +# ---------------------------------------------------- +# Targets +# ---------------------------------------------------- + +tests debug opt: $(TARGET_FILES) + +$(EBIN)/%.beam: $(MnesiaExamplesDir)/%.erl + $(ERLC) -bbeam $(ERL_COMPILE_FLAGS) -o$(EBIN) $< + +clean: + rm -f $(TARGET_FILES) + rm -f core + +docs: + +# ---------------------------------------------------- +# Release Target +# ---------------------------------------------------- +include $(ERL_TOP)/make/otp_release_targets.mk + +release_spec: opt + +release_tests_spec: opt + $(INSTALL_DIR) $(RELSYSDIR) + $(INSTALL_DATA) mnesia.spec mnesia.spec.vxworks $(ERL_FILES) $(HRL_FILES) $(RELSYSDIR) + $(INSTALL_PROGRAM) mt $(INSTALL_PROGS) $(RELSYSDIR) +# chmod -f -R u+w $(RELSYSDIR) +# @tar cf - *_SUITE_data | (cd $(RELSYSDIR); tar xf -) + +release_docs_spec: + + diff --git a/lib/mnesia/test/README b/lib/mnesia/test/README new file mode 100644 index 0000000000..e0ced7399d --- /dev/null +++ b/lib/mnesia/test/README @@ -0,0 +1,107 @@ +This directory contains the test suite of Mnesia. +Compile it with "erl -make". + +Test cases are identified with a {Mod, Fun} tuple that maps +to a function Mod:Fun(Config), where the test case hopefully +is implemented. The test suite is organized in a hierarchy +with {mnesia_SUITE, all} as the top. + +The module called mt, implements various convenience functions +to ease up the execution of test cases. It does also provide +aliases for some test cases. For example the atom Mod is an +alias for {Mod, all}, the atom all for {mnesia_SUITE, all}, +evil for mnesia_evil_coverage_test etc. + + mt:struct(TestCase) + + Displays the test case structure from TestCase + and downwards the hierarchy. E.g. mt:struct(all) + will display the entire test suite. + + mt:t(TestCase), mt:t(TestCase, Config) + + Runs a single test case or a hierarchy of test cases. + mt:t(silly) is be a good starter, but you may also + try mt:t(all) directly if you feel lucky. + + The identity of the last run test case and the outcome of + it is stored on file. mt:t() will re-run the last test case. + + The Config argument contains various configuration + parameters for the test cases, such as which nodes that + are available for running the test suite. The default + settings should be enough for the most. Use mt:read_config() + to get the current default setting and change it with + mt:write_config(Config). + + mt:doc(TestCase) + + Generates html documentation for the test suite. + +In order to be able to run the test suite, the Erlang node must +be started with the distribution enabled and the code path must +be set to the mnesia/ebin, mnesia/examples, and mnesia/test +directories. E.g. the following would do: + + erl -sname a -pa $top/examples -pa $top/src -pa $top/ebin + +where $top is the path to the Mnesia installation. Many test +cases needs 2 or 3 nodes. The node names may explicitly be +stated as test suite configuration parameters, but by default +the extra node names are generated. In this example the names +will be: a, a1 and a2. It is enough to start the first node +manually, the extra nodes will automatically be started if +neccessary. + +The attached UNIX shell script mt, does not work on all +platforms, but it may be used as a source for inspiration. It +starts three Erlang nodes in one xterm's each. The main xterm +(a@localhost) logs all output in the Erlang shell to a +file. The file is piped thru grep to easily find successful +test cases (i.e. test cases that encountered an error). + +During development we want to be able to run the test cases +in the debugger. This demands a little bit of preparations: + + - Start the neccessary number of nodes (normally 3). + This may either be done by running the mt script or + by starting the main node and then invoke mt:start_nodes() + to start the extra nodes with slave. + + - Ensure that the nodes are connected. The easiest way to do + this is by invoking mt:ping(). + + - Load all files that needs to be interpreted. This is typically + all Mnesia files plus the test case. By invoking mnesia:ni() + and mnesia:ni([TestModule]) the neccessary modules will be + loaded on all CONNECTED nodes. + +The test case execution is supervised in order to ensure that no test +case exceeds its maximum time limit, which by default is 5 minutes. +When the limit is reached, the running test case gets aborted and the +test server runs the next test case in line. This behaviour is useful +when running the entire test suite during the night, but it is really +annoying during debugging. + + Use the "erl -mnesia_test_timeout" flag to disable the test case + time limit mechanism. + +Some mechanisms in Mnesia are almost impossible to test with a +white box technique. In order to be able to write predictable +test cases which tests the same thing every time it is run, +Mnesia has been instrumented with debug functions. These may be +controlled from a test program. For example to verify that the +commit protocols work it is essential that it is possible to +ensure that we are able to kill Mnesia in the most critical +situations. Normally Mnesia is compiled with the debug +functions disabled and this means that test cases which +requires this functionality will be skipped. The mnesia:ni(), +mentioned above, functions ensures that the interpreted code is +instrumented with Mnesia's debug functionality. The mnesia:nc() +functions compiles Mnesia with the debug setting enabled. + +Happy bug hunting! + + Hakan Mattsson <[email protected]> + + diff --git a/lib/mnesia/test/mnesia.spec b/lib/mnesia/test/mnesia.spec new file mode 100644 index 0000000000..596f8b917d --- /dev/null +++ b/lib/mnesia/test/mnesia.spec @@ -0,0 +1,23 @@ +{topcase, {dir, "../mnesia_test"}}. +{require_nodenames, 2}. +{skip, {mnesia_measure_test, ram_meter, "Takes to long time"}}. +{skip, {mnesia_measure_test, disc_meter, "Takes to long time"}}. +{skip, {mnesia_measure_test, disc_only_meter, "Takes to long time"}}. +{skip, {mnesia_measure_test, cost, "Takes to long time"}}. +{skip, {mnesia_measure_test, dbn_meters, "Takes to long time"}}. +{skip, {mnesia_measure_test, tpcb, "Takes to long time"}}. +{skip, {mnesia_measure_test, prediction, "Not yet implemented"}}. +{skip, {mnesia_measure_test, consumption, "Not yet implemented"}}. +{skip, {mnesia_measure_test, scalability, "Not yet implemented"}}. +{skip, {mnesia_measure_test, tpcb, "Takes too much time and memory"}}. +{skip, {mnesia_measure_test, measure_all_api_functions, "Not yet implemented"}}. +{skip, {mnesia_measure_test, mnemosyne_vs_mnesia_kernel, "Not yet implemented"}}. +{skip, {mnesia_examples_test, company, "Not yet implemented"}}. +{skip, {mnesia_config_test, ignore_fallback_at_startup, "Not yet implemented"}}. +{skip, {mnesia_evil_backup, local_backup_checkpoint, "Not yet implemented"}}. +{skip, {mnesia_config_test, max_wait_for_decision, "Not yet implemented"}}. +{skip, {mnesia_recovery_test, after_full_disc_partition, "Not yet implemented"}}. +{skip, {mnesia_recovery_test, system_upgrade, "Not yet implemented"}}. +{skip, {mnesia_consistency_test, consistency_after_change_table_copy_type, "Not yet implemented"}}. +{skip, {mnesia_consistency_test, consistency_after_transform_table, "Not yet implemented"}}. +{skip, {mnesia_consistency_test, consistency_after_rename_of_node, "Not yet implemented"}}. diff --git a/lib/mnesia/test/mnesia.spec.vxworks b/lib/mnesia/test/mnesia.spec.vxworks new file mode 100644 index 0000000000..11c01ea3fe --- /dev/null +++ b/lib/mnesia/test/mnesia.spec.vxworks @@ -0,0 +1,362 @@ +{topcase, {dir, "../mnesia_test"}}. +{require_nodenames, 3}. +{diskless, true}. +{skip, {mnesia_measure_test, all, "Too heavy"}}. +%{mnesia_install_test, silly_durability} 'IMPL' +%{mnesia_install_test, silly_move} 'IMPL' +{skip, {mnesia_install_test, silly_upgrade, "Uses disk"}}. +%{mnesia_install_test, conflict} 'IMPL' +%{mnesia_install_test, dist} 'IMPL' +{skip, {mnesia_examples_test, all, "Uses disk"}}. +{skip, {mnesia_nice_coverage_test, all, "Uses disk"}}. + +%{mnesia_evil_coverage_test, system_info} 'IMPL' +%{mnesia_evil_coverage_test, table_info} 'IMPL' +%{mnesia_evil_coverage_test, error_description} 'IMPL' +{skip, {mnesia_evil_coverage_test, db_node_lifecycle, "Uses disk"}}. +{skip, {mnesia_evil_coverage_test, local_content, "Uses disk"}}. +%{mnesia_evil_coverage_test, start_and_stop} 'IMPL' +%{mnesia_evil_coverage_test, transaction} 'IMPL' +{skip, {mnesia_evil_coverage_test, checkpoint, "Uses disk"}}. +{skip, {mnesia_evil_backup, backup, "Uses disk"}}. +{skip, {mnesia_evil_backup, global_backup_checkpoint, "Uses disk"}}. +{skip, {mnesia_evil_backup, incremental_backup_checkpoint, "Uses disk"}}. +{skip, {mnesia_evil_backup, local_backup_checkpoint, "Uses disk"}}. +{skip, {mnesia_evil_backup, selective_backup_checkpoint, "Uses disk"}}. +{skip, {mnesia_evil_backup, restore_errors, "Uses disk"}}. +{skip, {mnesia_evil_backup, restore_clear, "Uses disk"}}. +{skip, {mnesia_evil_backup, restore_keep, "Uses disk"}}. +{skip, {mnesia_evil_backup, restore_recreate, "Uses disk"}}. +{skip, {mnesia_evil_backup, traverse_backup, "Uses disk"}}. +{skip, {mnesia_evil_backup, install_fallback, "Uses disk"}}. +{skip, {mnesia_evil_backup, uninstall_fallback, "Uses disk"}}. +{skip, {mnesia_evil_backup, local_fallback, "Uses disk"}}. +%{mnesia_evil_coverage_test, table_lifecycle} 'IMPL' +{skip, {mnesia_evil_coverage_test, replica_management, "Uses disk"}}. +%{mnesia_evil_coverage_test, change_table_access_mode} 'IMPL' +%{mnesia_evil_coverage_test, change_table_load_order} 'IMPL' +{skip, {mnesia_evil_coverage_test, set_master_nodes, "Uses disk"}}. +{skip, {mnesia_evil_coverage_test, offline_set_master_nodes, "Uses disk"}}. +{skip, {mnesia_evil_coverage_test, replica_location, "Uses disk"}}. +%{mnesia_evil_coverage_test, add_table_index_ram} 'IMPL' +{skip, {mnesia_trans_access_test, add_table_index_disc, "Uses disc"}}. +{skip, {mnesia_trans_access_test, add_table_index_disc_only, "Uses disc"}}. +%{mnesia_evil_coverage_test, create_live_table_index_ram} 'IMPL' +{skip, {mnesia_trans_access_test, create_live_table_index_disc, "Uses disc"}}. +{skip, {mnesia_trans_access_test, create_live_table_index_disc_only, "Uses disc"}}. +%{mnesia_evil_coverage_test, del_table_index_ram} 'IMPL' +{skip, {mnesia_trans_access_test, del_table_index_disc, "Uses disc"}}. +{skip, {mnesia_trans_access_test, del_table_index_disc_only, "Uses disc"}}. +{skip, {mnesia_trans_access_test, idx_schema_changes_ram, "Uses disk"}}. +{skip, {mnesia_trans_access_test, idx_schema_changes_disc, "Uses disc"}}. +{skip, {mnesia_trans_access_test, idx_schema_changes_disc_only, "Uses disc"}}. +%{mnesia_dirty_access_test, dirty_write_ram} 'IMPL' + +{skip, {mnesia_dirty_access_test, dirty_write_disc, "Uses disc"}}. +{skip, {mnesia_dirty_access_test, dirty_write_disc_only, "Uses disc"}}. +%{mnesia_dirty_access_test, dirty_read_ram} 'IMPL' +{skip, {mnesia_dirty_access_test, dirty_read_disc, "Uses disc"}}. +{skip, {mnesia_dirty_access_test, dirty_read_disc_only, "Uses disc"}}. +%{mnesia_dirty_access_test, dirty_update_counter_ram} 'IMPL' +{skip, {mnesia_dirty_access_test, dirty_update_counter_disc, "Uses disc"}}. +{skip, {mnesia_dirty_access_test, dirty_update_counter_disc_only, "Uses disc"}}. +%{mnesia_dirty_access_test, dirty_delete_ram} 'IMPL' +{skip, {mnesia_dirty_access_test, dirty_delete_disc, "Uses disc"}}. +{skip, {mnesia_dirty_access_test, dirty_delete_disc_only, "Uses disc"}}. +%{mnesia_dirty_access_test, dirty_delete_object_ram} 'IMPL' +{skip, {mnesia_dirty_access_test, dirty_delete_object_disc, "Uses disc"}}. +{skip, {mnesia_dirty_access_test, dirty_delete_object_disc_only, "Uses disc"}}. +%{mnesia_dirty_access_test, dirty_match_object_ram} 'IMPL' +{skip, {mnesia_dirty_access_test, dirty_match_object_disc, "Uses disc"}}. +{skip, {mnesia_dirty_access_test, dirty_match_object_disc_only, "Uses disc"}}. +%{mnesia_dirty_access_test, dirty_index_match_object_ram} 'IMPL' +{skip, {mnesia_dirty_access_test, dirty_index_match_object_disc, "Uses disc"}}. +{skip, {mnesia_dirty_access_test, dirty_index_match_object_disc_only, "Uses disc"}}. +%{mnesia_dirty_access_test, dirty_index_read_ram} 'IMPL' +{skip, {mnesia_dirty_access_test, dirty_index_read_disc, "Uses disc"}}. +{skip, {mnesia_dirty_access_test, dirty_index_read_disc_only, "Uses disc"}}. +%{mnesia_dirty_access_test, dirty_index_update_set_ram} 'IMPL' +{skip, {mnesia_dirty_access_test, dirty_index_update_set_disc, "Uses disc"}}. +{skip, {mnesia_dirty_access_test, dirty_index_update_set_disc_only, "Uses disc"}}. +%{mnesia_dirty_access_test, dirty_index_update_bag_ram} 'IMPL' +{skip, {mnesia_dirty_access_test, dirty_index_update_bag_disc, "Uses disc"}}. +{skip, {mnesia_dirty_access_test, dirty_index_update_bag_disc_only, "Uses disc"}}. +%{mnesia_dirty_access_test, dirty_iter_ram} 'IMPL' +{skip, {mnesia_dirty_access_test, dirty_iter_disc, "Uses disc"}}. +{skip, {mnesia_dirty_access_test, dirty_iter_disc_only, "Uses disc"}}. +{skip, {mnesia_dirty_access_test, admin_tests, "Uses disk"}}. + +%{mnesia_trans_access_test, write} 'IMPL' +%{mnesia_trans_access_test, read} 'IMPL' +%{mnesia_trans_access_test, wread} 'IMPL' +%{mnesia_trans_access_test, delete} 'IMPL' +%{mnesia_trans_access_test, delete_object} 'IMPL' +%{mnesia_trans_access_test, match_object} 'IMPL' +%{mnesia_trans_access_test, all_keys} 'IMPL' +%{mnesia_trans_access_test, index_match_object} 'IMPL' +%{mnesia_trans_access_test, index_read} 'IMPL' +%{mnesia_trans_access_test, index_update_set} 'IMPL' +%{mnesia_trans_access_test, index_update_bag} 'IMPL' +{skip, {mnesia_evil_coverage_test, dump_tables, "Uses disk"}}. +{skip, {mnesia_evil_coverage_test, dump_log, "Uses disk"}}. +%{mnesia_evil_coverage_test, wait_for_tables} 'IMPL' +{skip, {mnesia_evil_coverage_test, force_load_table, "Uses disk"}}. +%{mnesia_evil_coverage_test, user_properties} 'IMPL' +%{mnesia_evil_coverage_test, record_name_dirty_access_ram} 'IMPL' +{skip, {mnesia_evil_coverage_test, record_name_dirty_access_disc, "Uses disc"}}. +{skip, {mnesia_evil_coverage_test, record_name_dirty_access_disc_only, "Uses disc"}}. +%{mnesia_evil_coverage_test, snmp_open_table} 'IMPL' +%{mnesia_evil_coverage_test, snmp_close_table} 'IMPL' +%{mnesia_evil_coverage_test, snmp_get_next_index} 'IMPL' +%{mnesia_evil_coverage_test, snmp_get_row} 'IMPL' +%{mnesia_evil_coverage_test, snmp_get_mnesia_key} 'IMPL' +%{mnesia_evil_coverage_test, snmp_update_counter} 'IMPL' +%{mnesia_evil_coverage_test, info} 'IMPL' +%{mnesia_evil_coverage_test, schema_0} 'IMPL' +%{mnesia_evil_coverage_test, schema_1} 'IMPL' +%{mnesia_evil_coverage_test, view_0} 'IMPL' +{skip, {mnesia_evil_coverage_test, view_1, "Uses disk"}}. +{skip, {mnesia_evil_coverage_test, view_2, "Uses disk"}}. +%{mnesia_evil_coverage_test, lkill} 'IMPL' +%{mnesia_evil_coverage_test, kill} 'IMPL' + +%{mnesia_config_test, access_module} 'IMPL' +%{mnesia_config_test, auto_repair} 'IMPL' +{skip, {mnesia_config_test, backup_module, "Uses disk"}}. +{skip, {mnesia_config_test, dynamic_connect, "Uses disk"}}. +%{mnesia_config_test, debug} 'IMPL' +%{mnesia_config_test, dir} 'IMPL' +{skip, {mnesia_config_test, dump_log_load_regulation, "Uses disk"}}. +{skip, {mnesia_config_test, dump_log_time_threshold, "Uses disk"}}. +{skip, {mnesia_config_test, dump_log_write_threshold, "Uses disk"}}. +{skip, {mnesia_config_test, dump_log_update_in_place, "Uses disk"}}. +{skip, {mnesia_config_test, embedded_mnemosyne, "Uses Mnemosyne"}}. +%{mnesia_config_test, event_module} 'IMPL' +{skip, {mnesia_config_test, ignore_fallback_at_startup, "Not Yet impl"}}. +%{mnesia_config_test, inconsistent_database} 'IMPL' +{skip, {mnesia_config_test, max_wait_for_decision, "Not Yet impl"}}. +{skip, {mnesia_config_test, start_one_disc_full_then_one_disc_less, "Uses disc"}}. +{skip, {mnesia_config_test, start_first_one_disc_less_then_one_disc_full, "Uses disc"}}. +%%{skip, {mnesia_config_test, start_first_one_disc_less_then_two_more_disc_less, "Uses disc"}}. +{skip, {mnesia_config_test, schema_location_and_extra_db_nodes_combinations, "Uses disk"}}. +{skip, {mnesia_config_test, table_load_to_disc_less_nodes, "Uses disc"}}. +{skip, {mnesia_config_test, schema_merge, "Uses Disc"}}. +%{mnesia_config_test, unknown_config} 'IMPL' +%{mnesia_registry_test, good_dump} 'IMPL' +%{mnesia_registry_test, bad_dump} 'IMPL' + +%{mnesia_atomicity_test, explicit_abort_in_middle_of_trans} 'IMPL' +%{mnesia_atomicity_test, runtime_error_in_middle_of_trans} 'IMPL' +%{mnesia_atomicity_test, kill_self_in_middle_of_trans} 'IMPL' +%{mnesia_atomicity_test, throw_in_middle_of_trans} 'IMPL' +%{mnesia_atomicity_test, mnesia_down_during_infinite_trans} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_sw_rt} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_sw_wt} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_wr_r} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_sw_sw} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_sw_w} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_sw_wr} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_wr_wt} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_wr_sw} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_wr_w} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_r_sw} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_r_w} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_r_wt} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_rt_sw} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_rt_w} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_rt_wt} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_wt_r} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_wt_w} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_wt_rt} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_wt_wt} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_wt_wr} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_wt_sw} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_w_wr} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_w_sw} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_w_r} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_w_w} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_w_rt} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_w_wt} 'IMPL' +%{mnesia_atomicity_test, restart_r_one} 'IMPL' +%{mnesia_atomicity_test, restart_w_one} 'IMPL' +%{mnesia_atomicity_test, restart_rt_one} 'IMPL' +%{mnesia_atomicity_test, restart_wt_one} 'IMPL' +%{mnesia_atomicity_test, restart_wr_one} 'IMPL' +%{mnesia_atomicity_test, restart_sw_one} 'IMPL' +%{mnesia_atomicity_test, restart_r_two} 'IMPL' +%{mnesia_atomicity_test, restart_w_two} 'IMPL' +%{mnesia_atomicity_test, restart_rt_two} 'IMPL' +%{mnesia_atomicity_test, restart_wt_two} 'IMPL' +%{mnesia_atomicity_test, restart_wr_two} 'IMPL' +%{mnesia_atomicity_test, restart_sw_two} 'IMPL' + +%{mnesia_isolation_test, no_conflict} 'IMPL' +%{mnesia_isolation_test, simple_queue_conflict} 'IMPL' +%{mnesia_isolation_test, advanced_queue_conflict} 'IMPL' +%{mnesia_isolation_test, simple_deadlock_conflict} 'IMPL' +%{mnesia_isolation_test, advanced_deadlock_conflict} 'IMPL' +%{mnesia_isolation_test, lock_burst} 'IMPL' +%{mnesia_isolation_test, basic_sticky_functionality} 'IMPL' +%{mnesia_isolation_test, create_table} 'IMPL' +%{mnesia_isolation_test, delete_table} 'IMPL' +%{mnesia_isolation_test, move_table_copy} 'IMPL' +%{mnesia_isolation_test, add_table_index} 'IMPL' +%{mnesia_isolation_test, del_table_index} 'IMPL' +%{mnesia_isolation_test, transform_table} 'IMPL' +%{mnesia_isolation_test, snmp_open_table} 'IMPL' +%{mnesia_isolation_test, snmp_close_table} 'IMPL' +{skip, {mnesia_isolation_test, change_table_copy_type, "Uses disk"}}. +%{mnesia_isolation_test, change_table_access} 'IMPL' +%{mnesia_isolation_test, add_table_copy} 'IMPL' +%{mnesia_isolation_test, del_table_copy} 'IMPL' +{skip, {mnesia_isolation_test, dump_tables, "Uses disk"}}. +{skip, {mnesia_isolation_test, extra_admin_tests, "Uses disk"}}. +%{mnesia_isolation_test, del_table_copy_1} 'IMPL' +%{mnesia_isolation_test, del_table_copy_2} 'IMPL' +%{mnesia_isolation_test, del_table_copy_3} 'IMPL' +%{mnesia_isolation_test, add_table_copy_1} 'IMPL' +%{mnesia_isolation_test, add_table_copy_2} 'IMPL' +%{mnesia_isolation_test, add_table_copy_3} 'IMPL' +%{mnesia_isolation_test, add_table_copy_4} 'IMPL' +%{mnesia_isolation_test, move_table_copy_1} 'IMPL' +%{mnesia_isolation_test, move_table_copy_2} 'IMPL' +%{mnesia_isolation_test, move_table_copy_3} 'IMPL' +%{mnesia_isolation_test, move_table_copy_4} 'IMPL' +%{mnesia_isolation_test, dirty_updates_visible_direct} 'IMPL' +%{mnesia_isolation_test, dirty_reads_regardless_of_trans} 'IMPL' +%{mnesia_isolation_test, trans_update_invisibible_outside_trans} 'IMPL' +%{mnesia_isolation_test, trans_update_visible_inside_trans} 'IMPL' +%{mnesia_isolation_test, write_shadows} 'IMPL' +%{mnesia_isolation_test, delete_shadows} 'IMPL' +%{mnesia_isolation_test, write_delete_shadows_bag} 'IMPL' + +{skip, {mnesia_durability_test, all, "Uses disk "}}. +%{mnesia_durability_test, load_local_contents_directly} 'IMPL' +%{mnesia_durability_test, load_directly_when_all_are_ram_copiesA} 'IMPL' +%{mnesia_durability_test, load_directly_when_all_are_ram_copiesB} 'IMPL' +%{skip, {mnesia_durability_test, late_load_when_all_are_ram_copies_on_ram_nodes1, "Uses disk schema"}}. +%{skip, {mnesia_durability_test, late_load_when_all_are_ram_copies_on_ram_nodes2, "Uses disk schema"}}. +%{skip, {mnesia_durability_test, load_when_last_replica_becomes_available, "Uses disk"}}. +%{skip, {mnesia_durability_test, load_when_we_have_down_from_all_other_replica_nodes, "Uses disk"}}. +%{skip, {mnesia_durability_test, late_load_transforms_into_disc_load, "Uses disc"}}. +%{mnesia_durability_test, late_load_leads_to_hanging} 'IMPL' +%{mnesia_durability_test, force_load_when_nobody_intents_to_load} 'IMPL' +%{mnesia_durability_test, force_load_when_someone_has_decided_to_load} 'IMPL' +%{mnesia_durability_test, force_load_when_someone_else_already_has_loaded} 'IMPL' +%{mnesia_durability_test, force_load_when_we_has_loaded} 'IMPL' +%{mnesia_durability_test, force_load_on_a_non_local_table} 'IMPL' +%{mnesia_durability_test, force_load_when_the_table_does_not_exist} 'IMPL' +%{mnesia_durability_test, master_nodes} 'IMPL' +%{mnesia_durability_test, master_on_non_local_tables} 'IMPL' +%{mnesia_durability_test, remote_force_load_with_local_master_node} 'IMPL' +%{mnesia_durability_test, dump_ram_copies} 'IMPL' +%{skip, {mnesia_durability_test, dump_disc_copies, "Uses disc"}}. +%{skip, {mnesia_durability_test, dump_disc_only, "Uses disc"}}. +%{skip, {mnesia_durability_test, durability_of_disc_copies, "Uses disc"}}. +%{skip, {mnesia_durability_test, durability_of_disc_only_copies, "Uses disc"}}. + +{skip, {mnesia_recovery_test, mnesia_down, "Uses Disk"}}. +%{mnesia_recovery_test, no_master_2} 'IMPL' +%{mnesia_recovery_test, no_master_3} 'IMPL' +%{mnesia_recovery_test, one_master_2} 'IMPL' +%{mnesia_recovery_test, one_master_3} 'IMPL' +%{mnesia_recovery_test, two_master_2} 'IMPL' +%{mnesia_recovery_test, two_master_3} 'IMPL' +%{mnesia_recovery_test, all_master_2} 'IMPL' +%{mnesia_recovery_test, all_master_3} 'IMPL' +{skip, {mnesia_recovery_test, mnesia_down_during_startup_disk_ram, "Uses disk"}}. +%{mnesia_recovery_test, mnesia_down_during_startup_init_ram} 'IMPL' +{skip, {mnesia_recovery_test, mnesia_down_during_startup_init_disc, "Uses disc"}}. +{skip, {mnesia_recovery_test, mnesia_down_during_startup_init_disc_only, "Uses disc"}}. +%{mnesia_recovery_test, mnesia_down_during_startup_tm_ram} 'IMPL' +{skip, {mnesia_recovery_test, mnesia_down_during_startup_tm_disc, "Uses disc"}}. +{skip, {mnesia_recovery_test, mnesia_down_during_startup_tm_disc_only, "Uses disc"}}. +%{mnesia_recovery_test, explicit_stop_during_snmp} 'IMPL' + +{skip, {mnesia_recovery_test, schema_trans, "Uses Disk, needs disk log"}}. +{skip, {mnesia_recovery_test, async_dirty, "Uses disc"}}. +{skip, {mnesia_recovery_test, sync_dirty, "Uses disc"}}. +{skip, {mnesia_recovery_test, sym_trans, "Uses disc"}}. +{skip, {mnesia_recovery_test, asym_trans, "Uses disc"}}. + +{skip, {mnesia_recovery_test, after_full_disc_partition, "Not Yet impl"}}. +{skip, {mnesia_recovery_test, after_corrupt_files, "Uses disk"}}. + +%{mnesia_evil_coverage_test, subscriptions} 'IMPL' +%{mnesia_evil_coverage_test, nested_trans_both_ok} 'IMPL' +%{mnesia_evil_coverage_test, nested_trans_child_dies} 'IMPL' +%{mnesia_evil_coverage_test, nested_trans_parent_dies} 'IMPL' +%{mnesia_evil_coverage_test, nested_trans_both_dies} 'IMPL' +%{mnesia_evil_coverage_test, mix_of_trans_sync_dirty} 'IMPL' +%{mnesia_evil_coverage_test, mix_of_trans_async_dirty} 'IMPL' +%{mnesia_evil_coverage_test, mix_of_trans_ets} 'IMPL' + +{skip, {mnesia_recovery_test, disc_less, "Uses disc (on the other nodes)"}}. +{skip, {mnesia_recovery_test, system_upgrade, "Not Yet impl"}}. +%{mnesia_consistency_test, consistency_after_restart_1_ram} 'IMPL' +{skip, {mnesia_consistency_test, consistency_after_restart_1_disc, "Uses disc"}}. +{skip, {mnesia_consistency_test, consistency_after_restart_1_disc_only, "Uses disc"}}. +%{mnesia_consistency_test, consistency_after_restart_2_ram} 'IMPL' +{skip, {mnesia_consistency_test, consistency_after_restart_2_disc, "Uses disc"}}. +{skip, {mnesia_consistency_test, consistency_after_restart_2_disc_only, "Uses disc"}}. +{skip, {mnesia_consistency_test, consistency_after_dump_tables_1_ram, "Uses disk"}}. +{skip, {mnesia_consistency_test, consistency_after_dump_tables_2_ram, "Uses disk"}}. +%{mnesia_consistency_test, consistency_after_add_replica_2_ram} 'IMPL' +{skip, {mnesia_consistency_test, consistency_after_add_replica_2_disc, "Uses disc"}}. +{skip, {mnesia_consistency_test, consistency_after_add_replica_2_disc_only, "Uses disc"}}. +%{mnesia_consistency_test, consistency_after_add_replica_3_ram} 'IMPL' +{skip, {mnesia_consistency_test, consistency_after_add_replica_3_disc, "Uses disc"}}. +{skip, {mnesia_consistency_test, consistency_after_add_replica_3_disc_only, "Uses disc"}}. +%{mnesia_consistency_test, consistency_after_del_replica_2_ram} 'IMPL' +{skip, {mnesia_consistency_test, consistency_after_del_replica_2_disc, "Uses disc"}}. +{skip, {mnesia_consistency_test, consistency_after_del_replica_2_disc_only, "Uses disc"}}. +%{mnesia_consistency_test, consistency_after_del_replica_3_ram} 'IMPL' +{skip, {mnesia_consistency_test, consistency_after_del_replica_3_disc, "Uses disc"}}. +{skip, {mnesia_consistency_test, consistency_after_del_replica_3_disc_only, "Uses disc"}}. +%{mnesia_consistency_test, consistency_after_move_replica_2_ram} 'IMPL' +{skip, {mnesia_consistency_test, consistency_after_move_replica_2_disc, "Uses disc"}}. +{skip, {mnesia_consistency_test, consistency_after_move_replica_2_disc_only, "Uses disc"}}. +%{mnesia_consistency_test, consistency_after_move_replica_3_ram} 'IMPL' +{skip, {mnesia_consistency_test, consistency_after_move_replica_3_disc, "Uses disc"}}. +{skip, {mnesia_consistency_test, consistency_after_move_replica_3_disc_only, "Uses disc"}}. +{skip, {mnesia_consistency_test, consistency_after_transform_table, "Not yet implemented"}}. +{skip, {mnesia_consistency_test, consistency_after_change_table_copy_type, "Not yet implemented"}}. +{skip, {mnesia_consistency_test, consistency_after_fallback_2_ram, "Uses disk"}}. +{skip, {mnesia_consistency_test, consistency_after_fallback_2_disc, "Uses disc"}}. +{skip, {mnesia_consistency_test, consistency_after_fallback_2_disc_only, "Uses disc"}}. +{skip, {mnesia_consistency_test, consistency_after_fallback_3_ram, "Uses disk"}}. +{skip, {mnesia_consistency_test, consistency_after_fallback_3_disc, "Uses disc"}}. +{skip, {mnesia_consistency_test, consistency_after_fallback_3_disc_only, "Uses disc"}}. +{skip, {mnesia_consistency_test, consistency_after_restore_clear_ram, "Uses disk"}}. +{skip, {mnesia_consistency_test, consistency_after_restore_clear_disc, "Uses disc"}}. +{skip, {mnesia_consistency_test, consistency_after_restore_clear_disc_only, "Uses disc"}}. +{skip, {mnesia_consistency_test, consistency_after_restore_recreate_ram, "Uses disk"}}. +{skip, {mnesia_consistency_test, consistency_after_restore_recreate_disc, "Uses disc"}}. +{skip, {mnesia_consistency_test, consistency_after_restore_recreate_disc_only, "Uses disc"}}. +{skip, {mnesia_consistency_test, consistency_after_rename_of_node, "Not yet implemented"}}. +{skip, {mnesia_consistency_test, updates_during_checkpoint_activation, "Uses disk"}}. +%{skip, {mnesia_consistency_test, updates_during_checkpoint_activation_2_disc, "Uses disc"}}. +%{skip, {mnesia_consistency_test, updates_during_checkpoint_activation_2_disc_only, "Uses disc"}}. +%%{mnesia_consistency_test, updates_during_checkpoint_activation_3_ram} 'IMPL' +%{skip, {mnesia_consistency_test, updates_during_checkpoint_activation_3_disc, "Uses disc"}}. +%{skip, {mnesia_consistency_test, updates_during_checkpoint_activation_3_disc_only, "Uses disc"}}. +{skip, {mnesia_consistency_test, updates_during_checkpoint_iteration, "Uses disk"}}. +%{skip, {mnesia_consistency_test, updates_during_checkpoint_iteration_2_disc, "Uses disc"}}. +%{skip, {mnesia_consistency_test, updates_during_checkpoint_iteration_2_disc_only, "Uses disc"}}. +{skip, {mnesia_consistency_test, load_table_with_activated_checkpoint_ram, "Uses disk"}}. +{skip, {mnesia_consistency_test, load_table_with_activated_checkpoint_disc, "Uses disc"}}. +{skip, {mnesia_consistency_test, load_table_with_activated_checkpoint_disc_only, "Uses disc"}}. +{skip, {mnesia_consistency_test, add_table_copy_to_table_with_activated_checkpoint_ram, "Uses disk"}}. +{skip, {mnesia_consistency_test, add_table_copy_to_table_with_activated_checkpoint_disc, "Uses disc"}}. +{skip, {mnesia_consistency_test, add_table_copy_to_table_with_activated_checkpoint_disc_only, "Uses disc"}}. +{skip, {mnesia_consistency_test, inst_fallback_process_dies, "Uses disk"}}. +{skip, {mnesia_consistency_test, fatal_when_inconsistency, "Uses disk"}}. +{skip, {mnesia_consistency_test, after_delete, "Uses disk"}}. +{skip, {mnesia_consistency_test, mnesia_down_during_backup_causes_switch, "Uses disk"}}. +{skip, {mnesia_consistency_test, mnesia_down_during_backup_causes_abort, "Uses disk"}}. +%{mnesia_consistency_test, cause_switch_after} 'IMPL' +%{mnesia_consistency_test, cause_abort_before} 'IMPL' +%{mnesia_consistency_test, cause_abort_after} 'IMPL' +%{mnesia_consistency_test, change_schema_before} 'IMPL' +%{mnesia_consistency_test, change_schema_after} 'IMPL' + diff --git a/lib/mnesia/test/mnesia_SUITE.erl b/lib/mnesia/test/mnesia_SUITE.erl new file mode 100644 index 0000000000..b28deaf330 --- /dev/null +++ b/lib/mnesia/test/mnesia_SUITE.erl @@ -0,0 +1,203 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1997-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(mnesia_SUITE). +-author('[email protected]'). +-compile([export_all]). +-include("mnesia_test_lib.hrl"). + +init_per_testcase(Func, Conf) -> + mnesia_test_lib:init_per_testcase(Func, Conf). + +fin_per_testcase(Func, Conf) -> + mnesia_test_lib:fin_per_testcase(Func, Conf). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +all(doc) -> + ["Verify that Mnesia really is a distributed real-time DBMS", + "This is the test suite of the Mnesia DBMS. The test suite", + "covers many aspects of usage and is indended to be developed", + "incrementally. The test suite is divided into a hierarchy of test", + "suites where the leafs actually implements the test cases.", + "The intention of each test case and sub test suite can be", + "read in comments where they are implemented or in worst cases", + "from their long mnemonic names. ", + "", + "The most simple test case of them all is called 'silly'", + "and is useful to run now and then, e.g. when some new fatal", + "bug has been introduced. It may be run even if Mnesia is in", + "such a bad shape that the test machinery cannot be used.", + "NB! Invoke the function directly with mnesia_SUITE:silly()", + "and do not involve the normal test machinery."]; +all(suite) -> + [ + light, + medium, + heavy, + clean_up_suite + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +silly() -> + mnesia_install_test:silly(). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +light(doc) -> + ["The 'light' test suite runs a selected set of test suites and is", + "intended to be the smallest test suite that is meaningful", + "to run. It starts with an installation test (which in essence is the", + "'silly' test case) and then it covers all functions in the API in", + "various depths. All configuration parameters and examples are also", + "covered."]; +light(suite) -> + [ + install, + nice, + evil, + {mnesia_frag_test, light}, + qlc, + registry, + config, + examples + ]. + +install(suite) -> + [{mnesia_install_test, all}]. + +nice(suite) -> + [{mnesia_nice_coverage_test, all}]. + +evil(suite) -> + [{mnesia_evil_coverage_test, all}]. + +qlc(suite) -> + [{mnesia_qlc_test, all}]. + +registry(suite) -> + [{mnesia_registry_test, all}]. + +config(suite) -> + [{mnesia_config_test, all}]. + +examples(suite) -> + [{mnesia_examples_test, all}]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +medium(doc) -> + ["The 'medium' test suite verfies the ACID (atomicity, consistency", + "isolation and durability) properties and various recovery scenarios", + "These tests may take quite while to run."]; +medium(suite) -> + [ + install, + atomicity, + isolation, + durability, + recovery, + consistency, + {mnesia_frag_test, medium} + ]. + +atomicity(suite) -> + [{mnesia_atomicity_test, all}]. + +isolation(suite) -> + [{mnesia_isolation_test, all}]. + +durability(suite) -> + [{mnesia_durability_test, all}]. + +recovery(suite) -> + [{mnesia_recovery_test, all}]. + +consistency(suite) -> + [{mnesia_consistency_test, all}]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +heavy(doc) -> + ["The 'heavy' test suite runs some resource consuming tests and", + "benchmarks"]; +heavy(suite) -> + [measure]. + +measure(suite) -> + [{mnesia_measure_test, all}]. + +prediction(suite) -> + [{mnesia_measure_test, prediction}]. + +fairness(suite) -> + [{mnesia_measure_test, fairness}]. + +benchmarks(suite) -> + [{mnesia_measure_test, benchmarks}]. + +consumption(suite) -> + [{mnesia_measure_test, consumption}]. + +scalability(suite) -> + [{mnesia_measure_test, scalability}]. + + +clean_up_suite(doc) -> ["Not a test case only kills mnesia and nodes, that where" + "started during the tests"]; +clean_up_suite(suite) -> + []; +clean_up_suite(Config) when is_list(Config)-> + mnesia:kill(), + Slaves = mnesia_test_lib:lookup_config(nodenames, Config), + Nodes = lists:delete(node(), Slaves), + rpc:multicall(Nodes, erlang, halt, []), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +otp_r4b(doc) -> + ["This test suite is an extract of the grand Mnesia suite", + "it contains OTP R4B specific test cases"]; +otp_r4b(suite) -> + [ + {mnesia_config_test, access_module}, + {mnesia_config_test, dump_log_load_regulation}, + {mnesia_config_test, embedded_mnemosyne}, + {mnesia_config_test, ignore_fallback_at_startup}, + {mnesia_config_test, max_wait_for_decision}, + {mnesia_consistency_test, consistency_after_restore}, + {mnesia_evil_backup, restore}, + {mnesia_evil_coverage_test, offline_set_master_nodes}, + {mnesia_evil_coverage_test, record_name}, + {mnesia_evil_coverage_test, user_properties}, + {mnesia_registry_test, all}, + otp_2363 + ]. + +otp_2363(doc) -> + ["Index on disc only tables"]; +otp_2363(suite) -> + [ + {mnesia_dirty_access_test, dirty_index_match_object_disc_only}, + {mnesia_dirty_access_test,dirty_index_read_disc_only}, + {mnesia_dirty_access_test,dirty_index_update_bag_disc_only}, + {mnesia_dirty_access_test,dirty_index_update_set_disc_only}, + {mnesia_evil_coverage_test, create_live_table_index_disc_only} + ]. + + + diff --git a/lib/mnesia/test/mnesia_atomicity_test.erl b/lib/mnesia/test/mnesia_atomicity_test.erl new file mode 100644 index 0000000000..645c203a91 --- /dev/null +++ b/lib/mnesia/test/mnesia_atomicity_test.erl @@ -0,0 +1,839 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1997-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(mnesia_atomicity_test). +-author('[email protected]'). +-author('[email protected]'). +-compile([export_all]). +-include("mnesia_test_lib.hrl"). + +init_per_testcase(Func, Conf) -> + mnesia_test_lib:init_per_testcase(Func, Conf). + +fin_per_testcase(Func, Conf) -> + mnesia_test_lib:fin_per_testcase(Func, Conf). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +all(doc) -> + ["Verify atomicity of transactions", + "Verify that transactions are atomic, i.e. either all operations", + "in a transaction will be performed or none of them. It must be", + "assured that no partitially completed operations leaves any", + "effects in the database."]; +all(suite) -> + [ + explicit_abort_in_middle_of_trans, + runtime_error_in_middle_of_trans, + kill_self_in_middle_of_trans, + throw_in_middle_of_trans, + mnesia_down_in_middle_of_trans + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +explicit_abort_in_middle_of_trans(suite) -> []; +explicit_abort_in_middle_of_trans(Config) when is_list(Config) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = explicit_abort_in_middle_of_trans, + + Rec1A = {Tab, 1, a}, + Rec1B = {Tab, 1, b}, + + ?match({atomic, ok}, mnesia:create_table([{name, Tab}, + {ram_copies, [Node1]}])), + %% Start a transaction on one node + {success, [A]} = ?start_activities([Node1]), + + %% store an object in the Tab - first tranaction + ?start_transactions([A]), + A ! fun() -> + mnesia:write(Rec1A) % returns ok when successful + end, + ?match_receive({A, ok}), + A ! end_trans, + ?match_receive({A, {atomic, end_trans}}), + + %% second transaction: store some new objects and abort before the + %% transaction is finished -> the new changes should be invisable + ?start_transactions([A]), + A ! fun() -> + mnesia:write(Rec1B), + exit(abort_by_purpose) %does that stop the process A ??? + end, + ?match_receive({A, {aborted, abort_by_purpose}}), + + + %?match_receive({A, {'EXIT', Pid, normal}}), % A died and sends EXIT + + + %% Start a second transactionprocess, after the first failed + {success, [B]} = ?start_activities([Node1]), + + %% check, whether the interupted transaction had no influence on the db + ?start_transactions([B]), + B ! fun() -> + ?match([Rec1A], mnesia:read({Tab, 1})), + ok + end, + ?match_receive({B, ok}), + B ! end_trans, + ?match_receive({B, {atomic, end_trans}}), + + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +runtime_error_in_middle_of_trans(suite) -> []; +runtime_error_in_middle_of_trans(Config) when is_list(Config) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = runtime_error_in_middle_of_trans, + + Rec1A = {Tab, 1, a}, + Rec1B = {Tab, 1, b}, + Rec1C = {Tab, 1, c}, + + ?match({atomic, ok}, mnesia:create_table([{name, Tab}, + {ram_copies, [Node1]}])), + %% Start a transaction on one node + {success, [A]} = ?start_activities([Node1]), + + %% store an object in the Tab - first tranaction + ?start_transactions([A]), + A ! fun() -> + mnesia:write(Rec1A) % returns ok when successful + end, + ?match_receive({A, ok}), + A ! end_trans, + ?match_receive({A, {atomic, end_trans}}), + + %% second transaction: store some new objects and abort before the + %% transaction is finished -> the new changes should be invisable + ?start_transactions([A]), + A ! fun() -> + mnesia:write(Rec1B), + erlang:error(foo), % that should provoke a runtime error + mnesia:write(Rec1C) + end, + ?match_receive({A, {aborted, _Reason}}), + + %?match_receive({A, {'EXIT', Msg1}), % A died and sends EXIT + + + %% Start a second transactionprocess, after the first failed + {success, [B]} = ?start_activities([Node1]), + + %% check, whether the interupted transaction had no influence on the db + ?start_transactions([B]), + B ! fun() -> + ?match([Rec1A], mnesia:read({Tab, 1})), + ok + end, + ?match_receive({B, ok}), + B ! end_trans, + ?match_receive({B, {atomic, end_trans}}), + + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +kill_self_in_middle_of_trans(suite) -> []; +kill_self_in_middle_of_trans(Config) when is_list(Config) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = kill_self_in_middle_of_trans, + + Rec1A = {Tab, 1, a}, + Rec1B = {Tab, 1, b}, + Rec1C = {Tab, 1, c}, + + ?match({atomic, ok}, mnesia:create_table([{name, Tab}, + {ram_copies, [Node1]}])), + %% Start a transaction on one node + {success, [A]} = ?start_activities([Node1]), + + %% store an object in the Tab - first tranaction + ?start_transactions([A]), + A ! fun() -> + mnesia:write(Rec1A) % returns ok when successful + end, + ?match_receive({A, ok}), + A ! end_trans, + ?match_receive({A, {atomic, end_trans}}), + + %% second transaction: store some new objects and abort before the + %% transaction is finished -> the new changes should be invisable + ?start_transactions([A]), + A ! fun() -> + mnesia:write(Rec1B), + exit(self(), kill), % that should kill the process himself + % - poor guy ! + mnesia:write(Rec1C) + end, + %% + %% exit(.., kill) : the transaction can't trap this error - thus no + %% proper result can be send by the test server + + % ?match_receive({A, {aborted, Reason}}), + + ?match_receive({'EXIT', _Pid, killed}), % A is killed and sends EXIT + + %% Start a second transactionprocess, after the first failed + {success, [B]} = ?start_activities([Node1]), + + %% check, whether the interupted transaction had no influence on the db + ?start_transactions([B]), + B ! fun() -> + ?match([Rec1A], mnesia:read({Tab, 1})), + ok + end, + ?match_receive({B, ok}), + B ! end_trans, + ?match_receive({B, {atomic, end_trans}}), + + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +throw_in_middle_of_trans(suite) -> []; +throw_in_middle_of_trans(Config) when is_list(Config) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = throw_in_middle_of_trans, + + Rec1A = {Tab, 1, a}, + Rec1B = {Tab, 1, b}, + Rec1C = {Tab, 1, c}, + + ?match({atomic, ok}, mnesia:create_table([{name, Tab}, + {ram_copies, [Node1]}])), + %% Start a transaction on one node + {success, [A]} = ?start_activities([Node1]), + + %% store an object in the Tab - first tranaction + ?start_transactions([A]), + A ! fun() -> + mnesia:write(Rec1A) % returns ok when successful + end, + ?match_receive({A, ok}), + A ! end_trans, + ?match_receive({A, {atomic, end_trans}}), + + %% second transaction: store some new objects and abort before the + %% transaction is finished -> the new changes should be invisable + ?start_transactions([A]), + A ! fun() -> + mnesia:write(Rec1B), + throw(exit_transactian_by_a_throw), + mnesia:write(Rec1C) + end, + ?match_receive({A, {aborted, {throw, exit_transactian_by_a_throw}}}), + % A ! end_trans, % is A still alive ? + % ?match_receive({A, {atomic, end_trans}}), % {'EXIT', Pid, normal} + + %?match_receive({A, {'EXIT', Pid, normal}}), % A died and sends EXIT + + %% Start a second transactionprocess, after the first failed + {success, [B]} = ?start_activities([Node1]), + + %% check, whether the interupted transaction had no influence on the db + ?start_transactions([B]), + B ! fun() -> + ?match([Rec1A], mnesia:read({Tab, 1})), + ok + end, + ?match_receive({B, ok}), + B ! end_trans, + ?match_receive({B, {atomic, end_trans}}), + + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +mnesia_down_in_middle_of_trans(suite) -> + [ + mnesia_down_during_infinite_trans, + lock_waiter, + restart_check + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +mnesia_down_during_infinite_trans(suite) -> []; +mnesia_down_during_infinite_trans(Config) when is_list(Config) -> + [Node1, Node2] = ?acquire_nodes(2, Config), + Tab = mnesia_down_during_infinite_trans, + + ?match({atomic, ok}, + mnesia:create_table([{name, Tab}, {ram_copies, [Node1, Node2]}])), + %% Start a transaction on one node + {success, [A2, A1]} = ?start_activities([Node2, Node1]), + %% Start order of the transactions are important + %% We also needs to sync the tid counter + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write({Tab, 1, test_ok}) end)), + mnesia_test_lib:start_sync_transactions([A2, A1]), + + %% Obtain a write lock and wait forever + RecA = {Tab, 1, test_not_ok}, + A1 ! fun() -> mnesia:write(RecA) end, + ?match_receive({A1, ok}), + + A1 ! fun() -> process_flag(trap_exit, true), timer:sleep(infinity) end, + ?match_receive(timeout), + + %% Try to get read lock, but gets queued + A2 ! fun() -> mnesia:read({Tab, 1}) end, + ?match_receive(timeout), + + %% Kill Mnesia on other node + mnesia_test_lib:kill_mnesia([Node1]), + + %% Second transaction gets the read lock + ?match_receive({A2, [{Tab, 1, test_ok}]}), + exit(A1, kill), % Needed since we trap exit + + ?verify_mnesia([Node2], [Node1]). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +lock_waiter(doc) -> + ["The purpose of this test case is to test the following situation:", + "process B locks an object, process A accesses that object as", + "well, but A has to wait for the lock to be released. Then", + "mnesia of B goes down. Question: will A get the lock ?", + "important: the transaction of A is the oldest one !!! (= a little tricky)", + "", + "several different access operations shall be tested", + "rt = read_lock_table, wt = write_lock_table, r = read,", + "sw = s_write, w = write, wr = wread"]; +lock_waiter(suite) -> + [ + lock_waiter_sw_r, + lock_waiter_sw_rt, + lock_waiter_sw_wt, + lock_waiter_wr_r, + lock_waiter_srw_r, + lock_waiter_sw_sw, + lock_waiter_sw_w, + lock_waiter_sw_wr, + lock_waiter_sw_srw, + lock_waiter_wr_wt, + lock_waiter_srw_wt, + lock_waiter_wr_sw, + lock_waiter_srw_sw, + lock_waiter_wr_w, + lock_waiter_srw_w, + lock_waiter_r_sw, + lock_waiter_r_w, + lock_waiter_r_wt, + lock_waiter_rt_sw, + lock_waiter_rt_w, + lock_waiter_rt_wt, + lock_waiter_wr_wr, + lock_waiter_srw_srw, + lock_waiter_wt_r, + lock_waiter_wt_w, + lock_waiter_wt_rt, + lock_waiter_wt_wt, + lock_waiter_wt_wr, + lock_waiter_wt_srw, + lock_waiter_wt_sw, + lock_waiter_w_wr, + lock_waiter_w_srw, + lock_waiter_w_sw, + lock_waiter_w_r, + lock_waiter_w_w, + lock_waiter_w_rt, + lock_waiter_w_wt + ]. + +lock_waiter_sw_r(suite) -> []; +lock_waiter_sw_r(Config) when is_list(Config) -> + start_lock_waiter(sw, r, Config). + +lock_waiter_sw_rt(suite) -> []; +lock_waiter_sw_rt(Config) when is_list(Config) -> + start_lock_waiter(sw, rt, Config). + +lock_waiter_sw_wt(suite) -> []; +lock_waiter_sw_wt(Config) when is_list(Config) -> + start_lock_waiter(sw, wt,Config). + +lock_waiter_wr_r(suite) -> []; +lock_waiter_wr_r(Config) when is_list(Config) -> + start_lock_waiter(wr, r, Config). + +lock_waiter_srw_r(suite) -> []; +lock_waiter_srw_r(Config) when is_list(Config) -> + start_lock_waiter(srw, r, Config). + +lock_waiter_sw_sw(suite) -> []; +lock_waiter_sw_sw(Config) when is_list(Config) -> + start_lock_waiter(sw, sw,Config). + +lock_waiter_srw_srw(suite) -> []; +lock_waiter_srw_srw(Config) when is_list(Config) -> + start_lock_waiter(srw, srw,Config). + +lock_waiter_wr_wr(suite) -> []; +lock_waiter_wr_wr(Config) when is_list(Config) -> + start_lock_waiter(wr, wr,Config). + +lock_waiter_sw_w(suite) -> []; +lock_waiter_sw_w(Config) when is_list(Config) -> + start_lock_waiter(sw, w,Config). + +lock_waiter_sw_wr(suite) -> []; +lock_waiter_sw_wr(Config) when is_list(Config) -> + start_lock_waiter(sw, wr,Config). + +lock_waiter_sw_srw(suite) -> []; +lock_waiter_sw_srw(Config) when is_list(Config) -> + start_lock_waiter(sw, srw,Config). + +lock_waiter_wr_wt(suite) -> []; +lock_waiter_wr_wt(Config) when is_list(Config) -> + start_lock_waiter(wr, wt,Config). + +lock_waiter_srw_wt(suite) -> []; +lock_waiter_srw_wt(Config) when is_list(Config) -> + start_lock_waiter(srw, wt,Config). + +lock_waiter_wr_sw(suite) -> []; +lock_waiter_wr_sw(Config) when is_list(Config) -> + start_lock_waiter(wr, sw,Config). + +lock_waiter_srw_sw(suite) -> []; +lock_waiter_srw_sw(Config) when is_list(Config) -> + start_lock_waiter(srw, sw,Config). + +lock_waiter_wr_w(suite) -> []; +lock_waiter_wr_w(Config) when is_list(Config) -> + start_lock_waiter(wr, w,Config). + +lock_waiter_srw_w(suite) -> []; +lock_waiter_srw_w(Config) when is_list(Config) -> + start_lock_waiter(srw, w,Config). + +lock_waiter_r_sw(suite) -> []; +lock_waiter_r_sw(Config) when is_list(Config) -> + start_lock_waiter(r, sw,Config). + +lock_waiter_r_w(suite) -> []; +lock_waiter_r_w(Config) when is_list(Config) -> + start_lock_waiter(r, w,Config). + +lock_waiter_r_wt(suite) -> []; +lock_waiter_r_wt(Config) when is_list(Config) -> + start_lock_waiter(r, wt,Config). + +lock_waiter_rt_sw(suite) -> []; +lock_waiter_rt_sw(Config) when is_list(Config) -> + start_lock_waiter(rt, sw,Config). + +lock_waiter_rt_w(suite) -> []; +lock_waiter_rt_w(Config) when is_list(Config) -> + start_lock_waiter(rt, w,Config). + +lock_waiter_rt_wt(suite) -> []; +lock_waiter_rt_wt(Config) when is_list(Config) -> + start_lock_waiter(rt, wt,Config). + +lock_waiter_wt_r(suite) -> []; +lock_waiter_wt_r(Config) when is_list(Config) -> + start_lock_waiter(wt, r,Config). + +lock_waiter_wt_w(suite) -> []; +lock_waiter_wt_w(Config) when is_list(Config) -> + start_lock_waiter(wt, w,Config). + +lock_waiter_wt_rt(suite) -> []; +lock_waiter_wt_rt(Config) when is_list(Config) -> + start_lock_waiter(wt, rt,Config). + +lock_waiter_wt_wt(suite) -> []; +lock_waiter_wt_wt(Config) when is_list(Config) -> + start_lock_waiter(wt, wt,Config). + +lock_waiter_wt_wr(suite) -> []; +lock_waiter_wt_wr(Config) when is_list(Config) -> + start_lock_waiter(wt, wr,Config). + +lock_waiter_wt_srw(suite) -> []; +lock_waiter_wt_srw(Config) when is_list(Config) -> + start_lock_waiter(wt, srw,Config). + +lock_waiter_wt_sw(suite) -> []; +lock_waiter_wt_sw(Config) when is_list(Config) -> + start_lock_waiter(wt, sw,Config). + +lock_waiter_w_wr(suite) -> []; +lock_waiter_w_wr(Config) when is_list(Config) -> + start_lock_waiter(w, wr, Config). + +lock_waiter_w_srw(suite) -> []; +lock_waiter_w_srw(Config) when is_list(Config) -> + start_lock_waiter(w, srw, Config). + +lock_waiter_w_sw(suite) -> []; +lock_waiter_w_sw(Config) when is_list(Config) -> + start_lock_waiter(w, sw, Config). + +lock_waiter_w_r(suite) -> []; +lock_waiter_w_r(Config) when is_list(Config) -> + start_lock_waiter(w, r, Config). + +lock_waiter_w_w(suite) -> []; +lock_waiter_w_w(Config) when is_list(Config) -> + start_lock_waiter(w, w, Config). + +lock_waiter_w_rt(suite) -> []; +lock_waiter_w_rt(Config) when is_list(Config) -> + start_lock_waiter(w, rt, Config). + +lock_waiter_w_wt(suite) -> []; +lock_waiter_w_wt(Config) when is_list(Config) -> + start_lock_waiter(w, wt, Config). + +start_lock_waiter(BlockOpA, BlockOpB, Config) -> + [N1, N2] = Nodes = ?acquire_nodes(2, Config), + + TabName = mk_tab_name(lock_waiter_), + ?match({atomic, ok}, mnesia:create_table(TabName, + [{ram_copies, [N1, N2]}])), + + %% initialize the table with object {1, c} - when there + %% is a read transaction, the read will find that value + ?match({atomic, ok}, mnesia:sync_transaction(fun() -> mnesia:write({TabName, 1, c}) end)), + rpc:call(N2, ?MODULE, sync_tid_release, []), + + Tester = self(), + Fun_A =fun() -> + NewCounter = incr_restart_counter(), + if + NewCounter == 1 -> + Tester ! go_ahead_test, + receive go_ahead -> ok end; + true -> ok + end, + lock_waiter_fun(BlockOpA, TabName, a), + NewCounter + end, + + %% it's not possible to just spawn the transaction, because + %% the result shall be evaluated + A = spawn_link(N1, ?MODULE, perform_restarted_transaction, [Fun_A]), + + ?match(ok, receive go_ahead_test -> ok after 10000 -> timeout end), + + mnesia_test_lib:sync_trans_tid_serial([N1, N2]), + + Fun_B = fun() -> + lock_waiter_fun(BlockOpB, TabName, b), + A ! go_ahead, + wait(infinity) + end, + + B = spawn_link(N2, mnesia, transaction, [Fun_B, 100]), + + io:format("waiting for A (~p on ~p) to be in the queue ~n", [A, [N1, N2]]), + wait_for_a(A, [N1, N2]), + + io:format("Queus ~p~n", + [[{N,rpc:call(N, mnesia, system_info, [lock_queue])} || N <- Nodes]]), + + KillNode = node(B), + io:format("A was in the queue, time to kill Mnesia on B's node (~p on ~p)~n", + [B, KillNode]), + + mnesia_test_lib:kill_mnesia([KillNode]), % kill mnesia of fun B + + %% Read Ops does not need to be restarted + ExpectedCounter = + if + BlockOpA == sw, BlockOpB == w -> 1; + BlockOpA == sw, BlockOpB == wt -> 1; + BlockOpA == sw, BlockOpB == wr -> 1; + BlockOpA == srw, BlockOpB == w -> 1; + BlockOpA == srw, BlockOpB == wt -> 1; + BlockOpA == srw, BlockOpB == wr -> 1; + BlockOpA == r, BlockOpB /= sw -> 1; + BlockOpA == rt, BlockOpB /= sw -> 1; + true -> 2 + end, + ?match_multi_receive([{'EXIT', A, {atomic, ExpectedCounter}}, + {'EXIT', B, killed}]), + + %% the expected result depends on the transaction of + %% fun A - when that doesn't change the object in the + %% table (e.g. it is a read) then the predefined + %% value {Tabname, 1, c} is expected to be the result here + ExpectedResult = + case BlockOpA of + w -> {TabName, 1, a}; + sw ->{TabName, 1, a}; + _all_other -> {TabName, 1, c} + end, + + ?match({atomic, [ExpectedResult]}, + mnesia:transaction(fun() -> mnesia:read({TabName, 1}) end, 100)), + ?verify_mnesia([N1], [N2]). + +mk_tab_name(Prefix) -> + {Mega, Sec, Micro} = erlang:now(), + list_to_atom(lists:concat([Prefix , Mega, '_', Sec, '_', Micro])). + +lock_waiter_fun(Op, TabName, Val) -> + case Op of + rt -> mnesia:read_lock_table(TabName); + wt -> mnesia:write_lock_table(TabName); + r -> mnesia:read({TabName, 1}); + w -> mnesia:write({TabName, 1, Val}); + wr -> mnesia:wread({TabName, 1}); + srw -> mnesia:read(TabName, 1, sticky_write); + sw -> mnesia:s_write({TabName, 1, Val}) + end. + +wait_for_a(Pid, Nodes) -> + wait_for_a(Pid, Nodes, 5). + +wait_for_a(_P, _N, 0) -> + ?error("Timeout while waiting for lock on a~n", []); + +wait_for_a(Pid, Nodes, Count) -> + %% io:format("WAIT_FOR_A ~p ON ~w ~n", [Pid, Nodes]), + List = [rpc:call(N, mnesia, system_info, [lock_queue]) || N <- Nodes], + Q = lists:append(List), + check_q(Pid, Q, Nodes, Count). + +check_q(Pid, [{{_Oid,_Tid}, _Op, Pid, _WFT} | _Tail], _N, _Count) -> + ok; +check_q(Pid, [{_Oid, _Op, Pid, _Tid, _WFT} | _Tail], _N, _Count) -> + ok; +check_q(Pid, [_ | Tail], N, Count) -> + check_q(Pid, Tail, N, Count); +check_q(Pid, [], N, Count) -> + timer:sleep(500), + wait_for_a(Pid, N, Count - 1). + +perform_restarted_transaction (Fun_Trans) -> + %% the result of the transaction shall be: + %% - undefined (if the transaction was never executed) + %% - Times ( number of times that the transaction has been executed) + + Result = mnesia:transaction(Fun_Trans, 100), + exit(Result). + +%% Returns new val +incr_restart_counter() -> + NewCount = + case get(count_restart_of_transaction) of + undefined -> 1; + OldCount -> OldCount + 1 + end, + put(count_restart_of_transaction, NewCount), + NewCount. + +wait(Mseconds) -> + receive + after Mseconds -> ok + end. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +restart_check (doc) -> + [ + "test case:'A' performs a transaction on a table which", + "is only replicated on node B. During that transaction", + "mnesia on node B is killed. The transaction of A should", + "be stopped, since there is no further replica", + "rt = read_lock_table, wt = write_lock_table, r = read,", + "sw = s_write, w = write, wr = wread,"]; +restart_check(suite) -> + [ + restart_r_one, + restart_w_one, + restart_rt_one, + restart_wt_one, + restart_wr_one, + restart_sw_one, + restart_r_two, + restart_w_two, + restart_rt_two, + restart_wt_two, + restart_wr_two, + restart_sw_two + ]. + +restart_r_one(suite) -> []; +restart_r_one(Config) when is_list(Config) -> + start_restart_check(r, one, Config). + +restart_w_one(suite) -> []; +restart_w_one(Config) when is_list(Config) -> + start_restart_check(w, one, Config). + +restart_rt_one(suite) -> []; +restart_rt_one(Config) when is_list(Config) -> + start_restart_check(rt, one, Config). + +restart_wt_one(suite) -> []; +restart_wt_one(Config) when is_list(Config) -> + start_restart_check(wt, one, Config). + +restart_wr_one(suite) -> []; +restart_wr_one(Config) when is_list(Config) -> + start_restart_check(wr, one, Config). + +restart_sw_one(suite) -> []; +restart_sw_one(Config) when is_list(Config) -> + start_restart_check(sw, one, Config). + +restart_r_two(suite) -> []; +restart_r_two(Config) when is_list(Config) -> + start_restart_check(r, two, Config). + +restart_w_two(suite) -> []; +restart_w_two(Config) when is_list(Config) -> + start_restart_check(w, two, Config). + +restart_rt_two(suite) -> []; +restart_rt_two(Config) when is_list(Config) -> + start_restart_check(rt, two, Config). + +restart_wt_two(suite) -> []; +restart_wt_two(Config) when is_list(Config) -> + start_restart_check(wt, two, Config). + +restart_wr_two(suite) -> []; +restart_wr_two(Config) when is_list(Config) -> + start_restart_check(wr, two, Config). + +restart_sw_two(suite) -> []; +restart_sw_two(Config) when is_list(Config) -> + start_restart_check(sw, two, Config). + +start_restart_check(RestartOp, ReplicaNeed, Config) -> + [N1, N2, N3] = Nodes = ?acquire_nodes(3, Config), + + {TabName, _TabNodes} = create_restart_table(ReplicaNeed, Nodes), + + %% initialize the table with object {1, c} - when there + %% is a read transaction, the read will find that value + ?match({atomic, ok}, mnesia:sync_transaction(fun() -> mnesia:write({TabName, 1, c}) end)), + + %% Really sync tid_release + rpc:multicall([N2,N3], ?MODULE, sync_tid_release, []), + Coord = self(), + + Fun_A = fun() -> + NewCounter = incr_restart_counter(), + case NewCounter of + 1 -> + mnesia:write({TabName, 1, d}), + %% send a message to the test proc + Coord ! {self(),fun_a_is_blocked}, + receive go_ahead -> ok end; + _ -> + %% the fun will NOT be blocked here + restart_fun_A(RestartOp, TabName) + end, + NewCounter + end, + + A = spawn_link(N1, ?MODULE, perform_restarted_transaction, [Fun_A]), + ?match_receive({A,fun_a_is_blocked}), + + %% mnesia shall be killed at that node, where A is reading + %% the information from + kill_where_to_read(TabName, N1, [N2, N3]), + + %% wait some time to let mnesia go down and spread those news around + %% fun A shall be able to finish its job before being restarted + wait(500), + A ! go_ahead, + + %% the sticky write doesnt work on remote nodes !!! + ExpectedMsg = + case RestartOp of + sw when ReplicaNeed == two -> + {'EXIT',A,{aborted, {not_local, TabName}}}; + _all_other -> + case ReplicaNeed of + one -> + {'EXIT',A,{aborted, {no_exists, TabName}}}; + two -> + {'EXIT',A,{atomic, 2}} + end + end, + + ?match_receive(ExpectedMsg), + + %% now mnesia has to be started again on the node KillNode + %% because the next test suite will need it + ?match([], mnesia_test_lib:start_mnesia(Nodes, [TabName])), + + + %% the expected result depends on the transaction of + %% fun A - when that doesnt change the object in the + %% table (e.g. it is a read) then the predefined + %% value {Tabname, 1, c} is expected to be the result here + + ExpectedResult = + case ReplicaNeed of + one -> + []; + two -> + case RestartOp of + w -> [{TabName, 1, a}]; + _ ->[ {TabName, 1, c}] + end + end, + + ?match({atomic, ExpectedResult}, + mnesia:transaction(fun() -> mnesia:read({TabName, 1}) end,100)), + ?verify_mnesia(Nodes, []). + +create_restart_table(ReplicaNeed, [_N1, N2, N3]) -> + TabNodes = + case ReplicaNeed of + one -> [N2]; + two -> [N2, N3] + end, + TabName = mk_tab_name(restart_check_), + ?match({atomic, ok}, mnesia:create_table(TabName, [{ram_copies, TabNodes}])), + {TabName, TabNodes}. + +restart_fun_A(Op, TabName) -> + case Op of + rt -> mnesia:read_lock_table(TabName); + wt -> mnesia:write_lock_table(TabName); + r -> mnesia:read( {TabName, 1}); + w -> mnesia:write({TabName, 1, a}); + wr -> mnesia:wread({TabName, 1}); + sw -> mnesia:s_write({TabName, 1, a}) + end. + +kill_where_to_read(TabName, N1, Nodes) -> + Read = rpc:call(N1,mnesia,table_info, [TabName, where_to_read]), + case lists:member(Read, Nodes) of + true -> + mnesia_test_lib:kill_mnesia([Read]); + false -> + ?error("Fault while killing Mnesia: ~p~n", [Read]), + mnesia_test_lib:kill_mnesia(Nodes) + end. + +sync_tid_release() -> + sys:get_status(whereis(mnesia_tm)), + sys:get_status(whereis(mnesia_locker)), + ok. + diff --git a/lib/mnesia/test/mnesia_config_backup.erl b/lib/mnesia/test/mnesia_config_backup.erl new file mode 100644 index 0000000000..a33ec6ac5c --- /dev/null +++ b/lib/mnesia/test/mnesia_config_backup.erl @@ -0,0 +1,105 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1997-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(mnesia_config_backup). +-author('[email protected]'). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% +%% This module is used for testing the backup module config parameter. +%% +%% This module is an impostor for the mnesia_backup module. +%% +%% +%% Original doc below: +%% +%% This module contains one implementation of callback functions +%% used by Mnesia at backup and restore. The user may however +%% write an own module the same interface as mnesia_backup and +%% configure Mnesia so the alternate module performs the actual +%% accesses to the backup media. This means that the user may put +%% the backup on medias that Mnesia does not know about, possibly +%% on hosts where Erlang is not running. +%% +%% The OpaqueData argument is never interpreted by other parts of +%% Mnesia. It is the property of this module. Alternate implementations +%% of this module may have different interpretations of OpaqueData. +%% The OpaqueData argument given to open_write/1 and open_read/1 +%% are forwarded directly from the user. +%% +%% All functions must return {ok, NewOpaqueData} or {error, Reason}. +%% +%% The NewOpaqueData arguments returned by backup callback functions will +%% be given as input when the next backup callback function is invoked. +%% If any return value does not match {ok, _} the backup will be aborted. +%% +%% The NewOpaqueData arguments returned by restore callback functions will +%% be given as input when the next restore callback function is invoked +%% If any return value does not match {ok, _} the restore will be aborted. +%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-export([ + open_write/1, write/2, commit_write/1, abort_write/1, + open_read/1, read/1, close_read/1 + ]). + +-record(backup, {name, mode, items}). + +open_write(Name) -> + file:delete(Name), + {ok, #backup{name = Name, mode = write, items = []}}. + +write(Opaque, Item) when Opaque#backup.mode == write -> + %% Build the list in reverse order + {ok, Opaque#backup{items = [Item | Opaque#backup.items]}}. + +commit_write(Opaque) when Opaque#backup.mode == write -> + Bin = term_to_binary(Opaque#backup.items), + case file:write_file(Opaque#backup.name, Bin) of + ok -> + {ok, Opaque#backup{mode = closed, items = []}}; + {error, Reason} -> + {error, {commit_write, Reason}} + end. + +abort_write(Opaque) -> + {ok, Opaque#backup{mode = closed, items = []}}. + +open_read(Name) -> + case file:read_file(Name) of + {ok, Bin} -> + ReverseList = binary_to_term(Bin), + List = lists:reverse(ReverseList), + {ok, #backup{name = Name, mode = read, items = List}}; + {error, Reason} -> + {error, {open_read, Reason}} + end. + +read(Opaque) when Opaque#backup.mode == read -> + case Opaque#backup.items of + [Head | Tail] -> + {ok, Opaque#backup{items = Tail}, Head}; + [] -> + {ok, Opaque#backup{mode = closed}, []} + end. + +close_read(Opaque) -> + {ok, Opaque#backup{mode = closed, items = []}}. diff --git a/lib/mnesia/test/mnesia_config_event.erl b/lib/mnesia/test/mnesia_config_event.erl new file mode 100644 index 0000000000..6c1dea7ed5 --- /dev/null +++ b/lib/mnesia/test/mnesia_config_event.erl @@ -0,0 +1,74 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1997-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(mnesia_config_event). +-author('[email protected]'). + +-behaviour(gen_event). + +%% +%% This module was stolen from Mnesia +%% + + +%% gen_event callback interface +-export([init/1, handle_event/2, handle_call/2, handle_info/2, + terminate/2, code_change/3]). + + +init(_Args) -> + {ok, []}. + +handle_event(Msg, State) -> + handle_any_event(Msg, State). + +handle_info(Msg, State) -> + handle_any_event(Msg, State). + + +handle_call(Msg, State) -> + handle_any_event(Msg, State). + + +%% The main... + +handle_any_event({get_log, Pid}, State) -> + Pid ! {log, State}, + {ok, State}; +handle_any_event(Msg, State) -> + io:format("Got event: ~p~n", [Msg]), + {ok, [Msg | State]}. + +%%----------------------------------------------------------------- +%% terminate(Reason, State) -> +%% AnyVal +%%----------------------------------------------------------------- + +terminate(_Reason, _State) -> + ok. + +%%---------------------------------------------------------------------- +%% Func: code_change/3 +%% Purpose: Upgrade process when its code is to be changed +%% Returns: {ok, NewState} +%%---------------------------------------------------------------------- +code_change(_OldVsn, _State, _Extra) -> + exit(not_supported). + diff --git a/lib/mnesia/test/mnesia_config_test.erl b/lib/mnesia/test/mnesia_config_test.erl new file mode 100644 index 0000000000..7b62c63a62 --- /dev/null +++ b/lib/mnesia/test/mnesia_config_test.erl @@ -0,0 +1,1466 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1997-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(mnesia_config_test). +-author('[email protected]'). + +-include("mnesia_test_lib.hrl"). + +-record(test_table,{i,a1,a2,a3}). +-record(test_table2,{i, b}). + +-export([ + all/1, + access_module/1, + auto_repair/1, + backup_module/1, + debug/1, + dir/1, + dump_log_load_regulation/1, + dump_log_thresholds/1, + dump_log_update_in_place/1, + embedded_mnemosyne/1, + event_module/1, + ignore_fallback_at_startup/1, + inconsistent_database/1, + max_wait_for_decision/1, + send_compressed/1, + + app_test/1, + schema_config/1, + schema_merge/1, + unknown_config/1, + + dump_log_time_threshold/1, + dump_log_write_threshold/1, + + start_one_disc_full_then_one_disc_less/1, + start_first_one_disc_less_then_one_disc_full/1, + start_first_one_disc_less_then_two_more_disc_less/1, + schema_location_and_extra_db_nodes_combinations/1, + table_load_to_disc_less_nodes/1, + dynamic_connect/1, + dynamic_basic/1, + dynamic_ext/1, + dynamic_bad/1, + + init_per_testcase/2, + fin_per_testcase/2, + c_nodes/0 + ]). + +-export([check_logs/1]). + +-define(init(N, Config), + mnesia_test_lib:prepare_test_case([{init_test_case, [mnesia]}, + delete_schema, + {reload_appls, [mnesia]}], + N, Config, ?FILE, ?LINE)). +-define(acquire(N, Config), + mnesia_test_lib:prepare_test_case([{init_test_case, [mnesia]}, + delete_schema, + {reload_appls, [mnesia]}, + create_schema, + {start_appls, [mnesia]}], + N, Config, ?FILE, ?LINE)). +-define(acquire_schema(N, Config), + mnesia_test_lib:prepare_test_case([{init_test_case, [mnesia]}, + delete_schema, + {reload_appls, [mnesia]}, + create_schema], + N, Config, ?FILE, ?LINE)). +-define(cleanup(N, Config), + mnesia_test_lib:prepare_test_case([{reload_appls, [mnesia]}], + N, Config, ?FILE, ?LINE)). +-define(trans(Fun), + ?match({atomic, ok}, mnesia:transaction(Fun))). + +init_per_testcase(Func, Conf) -> + mnesia_test_lib:init_per_testcase(Func, Conf). + +fin_per_testcase(Func, Conf) -> + mnesia_test_lib:fin_per_testcase(Func, Conf). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + +all(doc) -> + [ + "Test all configuration parameters", + "Perform an exhaustive test of all the various parameters that", + "may be used to configure the Mnesia application.", + "", + "Hint: Check out the unofficial function mnesia:start/1.", + " But be careful to cleanup all configuration parameters", + " afterwards since the rest of the test suite may rely on", + " these default configurations. Perhaps it is best to run", + " these tests in a separate node which is dropped afterwards.", + "Are really all configuration parameters covered?"]; + +all(suite) -> + [ + access_module, + auto_repair, + backup_module, + debug, + dir, + dump_log_load_regulation, + dump_log_thresholds, + dump_log_update_in_place, + embedded_mnemosyne, + event_module, + ignore_fallback_at_startup, + inconsistent_database, + max_wait_for_decision, + send_compressed, + + app_test, + schema_config, + unknown_config + ]. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +access_module(doc) -> + ["Replace the activity access module with another module and ", + "use it to read and write to some alternate table storage"]; +access_module(suite) -> []; +access_module(Config) when is_list(Config) -> + Nodes = ?acquire_schema(1, Config), + ?match(ok, mnesia:start([{access_module, mnesia_frag}])), + + ?match(mnesia_frag, mnesia:system_info(access_module)), + + access_tab(ram_copies, Nodes), + case mnesia_test_lib:diskless(Config) of + true -> skip; + false -> + access_tab(disc_copies, Nodes) + , access_tab(disc_only_copies, Nodes) + end, + + ?verify_mnesia(Nodes, []), + ?cleanup(1, Config). + +access_tab(Storage, Nodes) -> + Tab = list_to_atom(lists:concat([access_tab_, Storage])), + RecName = some_access, + Attr = val, + TabDef = [{Storage, Nodes}, + {type, bag}, + {index, [Attr]}, + {record_name, RecName}], + ?match({atomic,ok}, mnesia:create_table(Tab, TabDef)), + + Activity = fun(Kind) -> + A = [Kind, Tab, RecName, Attr, Nodes], + io:format("kind: ~w, storage: ~w~n", [Kind, Storage]), + mnesia:activity(Kind, fun do_access/5, A) + end, + ModActivity = fun(Kind, M) -> + io:format("kind: ~w, storage: ~w. module: ~w~n", + [Kind, Storage, M]), + A = [Kind, Tab, RecName, Attr, Nodes], + mnesia:activity(Kind, fun do_access/5, A, M) + end, + ?match(ok, Activity(transaction)), + ?match(ok, Activity({transaction, 47})), + ?match(ok, ModActivity(transaction, mnesia)), + ?match(ok, ModActivity(transaction, mnesia_frag)), + + ?match(ok, Activity(async_dirty)), + ?match(ok, Activity(sync_dirty)), + case Storage of + ram_copies -> + ?match(ok, Activity(ets)); + _ -> + ignore + end. + +do_access(Kind, Tab, RecName, Attr, Nodes) -> + Tens = lists:sort([{RecName, 1, 10}, {RecName, 3, 10}]), + {OptNodes, OptTens} = + case Kind of + transaction -> {Nodes, Tens}; + {transaction, _} -> {Nodes, Tens}; + async_dirty -> {[], Tens}; + sync_dirty -> {[], Tens}; + ets -> {[], []} + end, + ?match(RecName, mnesia:table_info(Tab, record_name)), + + ?match(ok, mnesia:write(Tab, {RecName, 1, 10}, write)), + ?match(ok, mnesia:write(Tab, {RecName, 2, 20}, sticky_write)), + ?match(ok, mnesia:write(Tab, {RecName, 2, 21}, sticky_write)), + ?match(ok, mnesia:write(Tab, {RecName, 2, 22}, write)), + ?match(ok, mnesia:write(Tab, {RecName, 3, 10}, write)), + + Twos = [{RecName, 2, 20}, {RecName, 2, 21}, {RecName, 2, 22}], + ?match(Twos, lists:sort(mnesia:read(Tab, 2, read))), + + ?match(ok, mnesia:delete_object(Tab, {RecName, 2, 21}, sticky_write)), + + TenPat = {RecName, '_', 10}, + ?match(Tens, lists:sort(mnesia:match_object(Tab, TenPat, read))), + ?match(OptTens, lists:sort(mnesia:index_match_object(Tab, TenPat, Attr, read) )), + ?match(OptTens, lists:sort(mnesia:index_read(Tab, 10, Attr))), + Keys = [1, 2, 3], + ?match(Keys, lists:sort(mnesia:all_keys(Tab))), + + First = mnesia:first(Tab), + Mid = mnesia:next(Tab, First), + Last = mnesia:next(Tab, Mid), + ?match('$end_of_table', mnesia:next(Tab, Last)), + ?match(Keys, lists:sort([First,Mid,Last])), + + %% For set and bag these last, prev works as first and next + First2 = mnesia:last(Tab), + Mid2 = mnesia:prev(Tab, First2), + Last2 = mnesia:prev(Tab, Mid2), + ?match('$end_of_table', mnesia:prev(Tab, Last2)), + ?match(Keys, lists:sort([First2,Mid2,Last2])), + + ?match([ok, ok, ok], [mnesia:delete(Tab, K, write) || K <- Keys]), + W = wild_pattern, + ?match([], mnesia:match_object(Tab, mnesia:table_info(Tab, W), read)), + ?log("Safe fixed ~p~n", [catch ets:info(Tab, safe_fixed)]), + ?log("Fixed ~p ~n", [catch ets:info(Tab, fixed)]), + + ?match(OptNodes, mnesia:lock({global, some_lock_item, Nodes}, write)), + ?match(OptNodes, mnesia:lock({global, some_lock_item, Nodes}, read)), + ?match(OptNodes, mnesia:lock({table, Tab}, read)), + ?match(OptNodes, mnesia:lock({table, Tab}, write)), + + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +auto_repair(doc) -> + ["Try the auto_repair mechanism on the various disk_logs and dets files.", + "", + "The case tests both normal values of the parameter, and also", + "one crazy value.", + "The test of the real auto_repair functionality is made in the", + "dets suite" + ]; +auto_repair(suite) -> []; +auto_repair(Config) when is_list(Config) -> + ?init(1, Config), + ?match(ok, mnesia:start()), % Check default true + ?match(true, mnesia:system_info(auto_repair)), + ?match(stopped, mnesia:stop()), + ?match(ok, mnesia:start([{auto_repair, true}])), + ?match(true, mnesia:system_info(auto_repair)), + ?match(stopped, mnesia:stop()), + ?match(ok, mnesia:start([{auto_repair, false}])), + ?match(false, mnesia:system_info(auto_repair)), + ?match(stopped, mnesia:stop()), + ?match({error, {bad_type, auto_repair, your_mama}}, + mnesia:start([{auto_repair, your_mama}])), + ?match(stopped, mnesia:stop()), + ?cleanup(1, Config), + ok. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +backup_module(doc) -> + ["Replace the backup module with another module and use it to", + "read and write to an alternate backup media, e.g stored in", + "the internal state of a simple process."]; +backup_module(suite) -> []; +backup_module(Config) when is_list(Config) -> + Nodes = ?acquire_schema(1, Config), + ?match(ok, mnesia:start([{backup_module, mnesia_config_backup}])), + ?match({atomic,ok}, + mnesia:create_table(test_table, + [{disc_copies, Nodes}, + {attributes, + record_info(fields,test_table)}])), + + ?match({atomic,ok}, + mnesia:create_table(test_table2, + [{disc_copies, Nodes}, + {attributes, + record_info(fields,test_table2)}])), + %% Write in test table + ?trans(fun() -> mnesia:write(#test_table{i=1}) end), + ?trans(fun() -> mnesia:write(#test_table{i=2}) end), + + %% Write in test table 2 + ?trans(fun() -> mnesia:write(#test_table2{i=3}) end), + ?trans(fun() -> mnesia:write(#test_table2{i=4}) end), + mnesia_test_lib:sync_tables(Nodes, [test_table, test_table2]), + + File = whow, + %% Now make a backup + ?match(ok, mnesia:backup(File)), + + ?match(ok, mnesia:install_fallback(File)), + + %% Now add things + ?trans(fun() -> mnesia:write(#test_table{i=2.5}) end), + ?trans(fun() -> mnesia:write(#test_table2{i=3.5}) end), + + mnesia_test_lib:kill_mnesia(Nodes), + receive after 2000 -> ok end, + ?match([], mnesia_test_lib:start_mnesia(Nodes, [test_table, test_table2])), + + %% Now check newly started tables + ?match({atomic, [1,2]}, + mnesia:transaction(fun() -> lists:sort(mnesia:all_keys(test_table)) end)), + ?match({atomic, [3,4]}, + mnesia:transaction(fun() -> lists:sort(mnesia:all_keys(test_table2)) end)), + + file:delete(File), + ?verify_mnesia(Nodes, []), + ?cleanup(1, Config), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +debug(doc) -> + ["Try out the four debug levels and ensure that the", + "expected events are generated."]; +debug(suite) -> []; +debug(Config) when is_list(Config) -> + Nodes = ?init(1, Config), + case application:get_env(mnesia,debug) of + undefined -> + ?match(none, mnesia:system_info(debug)); + {ok, false} -> + ?match(none, mnesia:system_info(debug)); + {ok, true} -> + ?match(debug, mnesia:system_info(debug)); + {ok, Env} -> + ?match(Env, mnesia:system_info(debug)) + end, + + ?match(ok, mnesia:start([{debug, verbose}])), + ?match(verbose, mnesia:system_info(debug)), + mnesia_test_lib:kill_mnesia(Nodes), + receive after 2000 -> ok end, + + ?match(ok, mnesia:start([{debug, debug}])), + ?match(debug, mnesia:system_info(debug)), + mnesia_test_lib:kill_mnesia(Nodes), + receive after 2000 -> ok end, + + ?match(ok, mnesia:start([{debug, trace}])), + ?match(trace, mnesia:system_info(debug)), + mnesia_test_lib:kill_mnesia(Nodes), + receive after 2000 -> ok end, + + ?match(ok, mnesia:start([{debug, true}])), + ?match(debug, mnesia:system_info(debug)), + mnesia_test_lib:kill_mnesia(Nodes), + receive after 2000 -> ok end, + + ?match(ok, mnesia:start([{debug, false}])), + ?match(none, mnesia:system_info(debug)), + + ?verify_mnesia(Nodes, []), + ?cleanup(1, Config), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +dir(doc) -> + ["Try to use alternate Mnesia directories"]; +dir(suite) -> []; +dir(Config) when is_list(Config) -> + Nodes = ?init(1, Config), + + ?match(ok, mnesia:start([{dir, tuff}])), + Dir = filename:join([element(2, file:get_cwd()), "tuff"]), + ?match(Dir, mnesia:system_info(directory)), + mnesia_test_lib:kill_mnesia(Nodes), + + ?cleanup(1, Config), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +dump_log_update_in_place(doc) -> + ["Change the update in place policy for the transaction log dumper."]; +dump_log_update_in_place(suite) -> []; +dump_log_update_in_place(Config) when is_list(Config) -> + Nodes = ?acquire(1, Config), + ?match(true, mnesia:system_info(dump_log_update_in_place)), + ?match({atomic,ok}, + mnesia:create_table(test_table, + [{disc_copies, Nodes}, + {attributes, + record_info(fields,test_table)}])), + + mnesia_test_lib:kill_mnesia(Nodes), + receive after 2000 -> ok end, + + ?match(ok, mnesia:start([{dump_log_update_in_place, false}])), + ?match(false, mnesia:system_info(dump_log_update_in_place)), + + mnesia_test_lib:sync_tables(Nodes, [schema, test_table]), + + %% Now provoke some log dumps + + L = lists:map( + fun(Num) -> + %% Write something on one end ... + mnesia:transaction( + fun() -> + mnesia:write(#test_table{i=Num}) end + ) end, + lists:seq(1, 110)), + + L2 = lists:duplicate(110, {atomic, ok}), + + %% If this fails then some of the 110 writes above failed + ?match(true, L==L2), + if L==L2 -> ok; + true -> + ?verbose("***** List1 len: ~p, List2 len: ~p~n", + [length(L), length(L2)]), + ?verbose("L: ~p~nL2:~p~n", [L, L2]) + end, + + %% If we still can write, then Mnesia is probably alive + ?trans(fun() -> mnesia:write(#test_table{i=115}) end), + + ?verify_mnesia(Nodes, []), + ?cleanup(1, Config), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +dump_log_thresholds(doc) -> + ["Elaborate with various values of the dump log thresholds and how", + "they affects each others. Both the dump_log_time_threshold and the", + "dump_log_write_threshold must be covered. Do also check that both", + "kinds of overload events are generated as expected.", + "", + "Logs are checked by first doing whatever has to be done to trigger ", + "a dump, and then stopping Mnesia and then look in the ", + "data files and see that the correct amount of transactions ", + "have been done."]; +dump_log_thresholds(suite) -> + [ + dump_log_time_threshold, + dump_log_write_threshold + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +dump_log_write_threshold(doc)-> + ["This test case must be rewritten.", + "Dump logs are tested by doing transactions, then killing Mnesia and ", + "then examining the table data files and see if they are correct.", + "The test_table is used as a counter, test_table. is stepped once ", + "for each transaction."]; +dump_log_write_threshold(suite)->[]; +dump_log_write_threshold(Config) when is_list(Config) -> + [N1] = ?acquire_schema(1, Config), + + Threshold = 3, + ?match(ok,mnesia:start([{dump_log_write_threshold, Threshold}])), + + ?match({atomic,ok}, + mnesia:create_table(test_table, + [{disc_copies, [N1]}, + {attributes, + record_info(fields,test_table)}])), + ?match(dumped, mnesia:dump_log()), + + ?match(ok, do_trans(2)), % Shall not have dumped + check_logs(0), + + ?match(ok, do_trans(Threshold - 2)), % Trigger a dump + receive after 1000 -> ok end, + check_logs(Threshold), + + + ?match(ok, do_trans(Threshold - 1)), + ?match(dumped, mnesia:dump_log()), %% This should trigger ets2dcd dump + check_logs(0), %% and leave no dcl file + + ?match(stopped, mnesia:stop()), + + %% Check bad threshold value + ?match({error,{bad_type,dump_log_write_threshold,0}}, + mnesia:start([{dump_log_write_threshold,0}])), + + ?verify_mnesia([], [N1]), + ?cleanup(1, Config), + ok. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +dump_log_time_threshold(doc)-> + ["See doc on above."]; +dump_log_time_threshold(suite)->[]; +dump_log_time_threshold(Config) when is_list(Config) -> + Nodes = ?acquire_schema(1, Config), + Time = 4000, + + %% Check bad threshold value + ?match({error,{bad_type,dump_log_time_threshold,0}}, + mnesia:start([{dump_log_time_threshold,0}])), + + + ?match(ok,mnesia:start([{dump_log_write_threshold,100}, + {dump_log_time_threshold, Time}])), + + ?match({atomic,ok},mnesia:create_table(test_table, + [{disc_copies, Nodes}, + {attributes, + record_info(fields, + test_table)}])), + + %% Check that nothing is dumped when within time threshold + ?match(ok, do_trans(1)), + check_logs(0), + + ?match(Time, mnesia:system_info(dump_log_time_threshold)), + + %% Check that things get dumped when time threshold exceeded + ?match(ok, do_trans(5)), + receive after Time+2000 -> ok end, + check_logs(6), + + ?verify_mnesia([node()], []), + ?cleanup(1, Config), + ok. + +%%%%%%%% +%% +%% Help functions for dump log + +%% Do a transaction N times +do_trans(0) -> ok; +do_trans(N) -> + Fun = fun() -> + XX=incr(), + mnesia:write(#test_table{i=XX}) + end, + {atomic, ok} = mnesia:transaction(Fun), + do_trans(N-1). + +%% An increasing number +incr() -> + case get(bloody_counter) of + undefined -> put(bloody_counter, 2), 1; + Num -> put(bloody_counter, Num+1) + end. + +%% +%% Check that the correct number of transactions have been recorded. +%%-record(test_table,{i,a1,a2,a3}). +check_logs(N) -> + File = mnesia_lib:tab2dcl(test_table), + Args = [{file, File}, {name, testing}, {repair, true}, {mode, read_only}], + + if N == 0 -> + ?match(false, mnesia_lib:exists(File)); + true -> + ?match(true, mnesia_lib:exists(File)), + ?match({ok, _Log}, disk_log:open(Args)), + + {Cont, Terms} = disk_log:chunk(testing, start), + ?match(eof, disk_log:chunk(testing, Cont)), + %%?verbose("N: ~p, L: ~p~n", [N, L]), + disk_log:close(testing), + + %% Correct number of records in file + ?match({N, N}, {N, length(Terms) -1 }) %% Ignore Header + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +dump_log_load_regulation(doc) -> + ["Test the load regulation of the dumper"]; +dump_log_load_regulation(suite) -> + []; +dump_log_load_regulation(Config) when is_list(Config) -> + Nodes = ?acquire_nodes(1, Config), + Param = dump_log_load_regulation, + + %% Normal + NoReg = false, + ?match(NoReg, mnesia:system_info(Param)), + ?match([], mnesia_test_lib:stop_mnesia(Nodes)), + + %% Bad + Bad = arne_anka, + ?match({error, {bad_type, Param, Bad}}, + mnesia:start([{Param, Bad}])), + + %% Regulation activated + Reg = true, + ?match(ok,mnesia:start([{Param, Reg}])), + ?match(Reg, mnesia:system_info(Param)), + + Args = + [{db_nodes, Nodes}, + {driver_nodes, Nodes}, + {replica_nodes, Nodes}, + {n_drivers_per_node, 5}, + {n_branches, length(Nodes) * 10}, + {n_accounts_per_branch, 5}, + {replica_type, disc_copies}, + {stop_after, timer:seconds(30)}, + {report_interval, timer:seconds(10)}, + {use_running_mnesia, true}, + {reuse_history_id, true}], + + ?match({ok, _}, mnesia_tpcb:start(Args)), + + ?verify_mnesia(Nodes, []), + ?cleanup(1, Config), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +embedded_mnemosyne(doc) -> + ["Start Mnemosyne as an embedded part of Mnesia", + "on some of the nodes"]; +embedded_mnemosyne(suite) -> + []; +embedded_mnemosyne(Config) when is_list(Config) -> + Nodes = ?acquire_nodes(1, Config), + Param = embedded_mnemosyne, + + %% Normal + NoMnem = false, + ?match(NoMnem, mnesia:system_info(Param)), + ?match(undefined, whereis(mnemosyne_catalog)), + ?match([], mnesia_test_lib:stop_mnesia(Nodes)), + + %% Bad + Bad = arne_anka, + ?match({error, {bad_type, Param, Bad}}, + mnesia:start([{Param, Bad}])), + + case code:priv_dir(mnemosyne) of + {error, _} -> %% No mnemosyne on later systems + ok; + _ -> + %% Mnemosyne as embedded application + Mnem = true, + ?match(undefined, whereis(mnemosyne_catalog)), + ?match(ok,mnesia:start([{Param, Mnem}])), + ?match(Mnem, mnesia:system_info(Param)), + ?match(Pid when is_pid(Pid), whereis(mnemosyne_catalog)), + ?match([], mnesia_test_lib:stop_mnesia(Nodes)), + ?match(undefined, whereis(mnemosyne_catalog)) + end, + ?verify_mnesia([], Nodes), + ?cleanup(1, Config), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +ignore_fallback_at_startup(doc) -> + ["Start Mnesia without rollback of the database to the fallback. ", + "Once Mnesia has been (re)started the installed fallback should", + "be handled as a normal active fallback.", + "Install a customized event module which disables the termination", + "of Mnesia when mnesia_down occurrs with an active fallback."]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +max_wait_for_decision(doc) -> + ["Provoke Mnesia to make a forced decision of the outome", + "of a heavy weight transaction."]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +send_compressed(doc) -> []; +send_compressed(suite) -> []; +send_compressed(Config) -> + [N1,N2] = Nodes = ?acquire_nodes(2, Config), + ?match({atomic,ok}, mnesia:create_table(t0, [{ram_copies,[N1,N2]}])), + ?match({atomic,ok}, mnesia:create_table(t1, [{disc_copies,[N1,N2]}])), + ?match({atomic,ok}, mnesia:create_table(t2, [{disc_only_copies,[N1,N2]}])), + + Max = 1000, + Create = fun(Tab) -> [mnesia:write({Tab, N, {N, "FILLER-123490878345asdasd"}}) + || N <- lists:seq(1, Max)], + ok + end, + + ?match([], mnesia_test_lib:kill_mnesia([N2])), + + ?match([], mnesia_test_lib:kill_mnesia([N1])), + ?match(ok, mnesia:start([{send_compressed, 9}])), + ?match(ok, mnesia:wait_for_tables([t0,t1,t2], 5000)), + + ?match({atomic, ok}, mnesia:transaction(Create, [t0])), + ?match({atomic, ok}, mnesia:transaction(Create, [t1])), + ?match({atomic, ok}, mnesia:transaction(Create, [t2])), + + ?match([], mnesia_test_lib:start_mnesia([N2], [t0,t1,t2])), + + Verify = fun(Tab) -> + [ [{Tab,N,{N,_}}] = mnesia:read(Tab, N) || N <- lists:seq(1, Max)], + ok + end, + ?match({atomic, ok}, rpc:call(N1, mnesia, transaction, [Verify, [t0]])), + ?match({atomic, ok}, rpc:call(N1, mnesia, transaction, [Verify, [t1]])), + ?match({atomic, ok}, rpc:call(N1, mnesia, transaction, [Verify, [t2]])), + + ?match({atomic, ok}, rpc:call(N2, mnesia, transaction, [Verify, [t0]])), + ?match({atomic, ok}, rpc:call(N2, mnesia, transaction, [Verify, [t1]])), + ?match({atomic, ok}, rpc:call(N2, mnesia, transaction, [Verify, [t2]])), + + ?verify_mnesia(Nodes, []), + ?cleanup(1, Config), + ok. + +app_test(doc) -> []; +app_test(suite) -> []; +app_test(_Config) -> + ?match(ok,test_server:app_test(mnesia)), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +event_module(doc) -> + ["Replace the event module with another module and use it as", + "receiver of the various system and table events. Provoke", + "coverage of all kinds of events."]; +event_module(suite) -> []; +event_module(Config) when is_list(Config) -> + Filter = fun({mnesia_system_event,{mnesia_info, _, _}}) -> false; + (_) -> true + end, + + [_N1, N2]=Nodes=?acquire_schema(2, Config), + + Def = case mnesia_test_lib:diskless(Config) of + true -> [{event_module, mnesia_config_event}, + {extra_db_nodes, Nodes}]; + false -> + [{event_module, mnesia_config_event}] + end, + + ?match({[ok, ok], []}, rpc:multicall(Nodes, mnesia, start, [Def])), + receive after 1000 -> ok end, + mnesia_event ! {get_log, self()}, + DebugLog1 = receive + {log, L1} -> L1 + after 10000 -> [timeout] + end, + ?match([{mnesia_system_event,{mnesia_up,N2}}], + lists:filter(Filter, DebugLog1)), + mnesia_test_lib:kill_mnesia([N2]), + receive after 2000 -> ok end, + + ?match({[ok], []}, rpc:multicall([N2], mnesia, start, [])), + + receive after 1000 -> ok end, + mnesia_event ! {get_log, self()}, + DebugLog = receive + {log, L} -> L + after 10000 -> [timeout] + end, + ?match([{mnesia_system_event,{mnesia_up,N2}}, + {mnesia_system_event,{mnesia_down,N2}}, + {mnesia_system_event,{mnesia_up, N2}}], + lists:filter(Filter, DebugLog)), + ?verify_mnesia(Nodes, []), + ?cleanup(1, Config), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +schema_config(doc) -> + ["Try many configurations with various schema_location's with and", + "without explicit extra_db_nodes. Do also provoke various schema merge", + "situations. Most of the other test suites focusses on tests where the", + "schema is residing on disc. Now it is time to perform an exhaustive", + "elaboration with various disc less configurations."]; +schema_config(suite) -> + [ + start_one_disc_full_then_one_disc_less, + start_first_one_disc_less_then_one_disc_full, + start_first_one_disc_less_then_two_more_disc_less, + schema_location_and_extra_db_nodes_combinations, + table_load_to_disc_less_nodes, + schema_merge, + dynamic_connect + ]. +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +start_one_disc_full_then_one_disc_less(doc)-> + ["Start a disk node and then a disk less one. Distribute some", + "tables between them."]; +start_one_disc_full_then_one_disc_less(suite) -> []; +start_one_disc_full_then_one_disc_less(Config) when is_list(Config) -> + [N1, N2] = ?init(2, Config), + ?match(ok, mnesia:create_schema([N1])), + ?match([], mnesia_test_lib:start_mnesia([N1])), + + ?match({atomic, ok}, mnesia:add_table_copy(schema, N2, ram_copies)), + + ?match(ok, rpc:call(N2, mnesia, start, [[{schema_location, ram}, + {extra_db_nodes, [N1]}]])), + mnesia_test_lib:sync_tables([N1, N2], [schema]), + + %% Now create some tables + ?match({atomic,ok}, + mnesia:create_table(test_table, + [{ram_copies, [N1, N2]}, + {attributes, + record_info(fields,test_table)}])), + + ?match({atomic,ok}, + rpc:call( + N2, mnesia,create_table, [test_table2, + [{ram_copies, [N1, N2]}, + {attributes, + record_info(fields,test_table2)}]])), + + %% Write something on one end ... + Rec = #test_table{i=55}, + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write(Rec) end)), + + %% ... and read it in the other + ?match({atomic, [Rec]}, + rpc:call(N2, mnesia, transaction, + [fun() -> mnesia:read({test_table, 55}) end])), + + + %% Then do the same but start at the other end + Rec2 = #test_table2{i=155}, + ?match({atomic, ok}, + rpc:call(N2, mnesia, transaction, + [fun() -> + mnesia:write(Rec2) end + ])), + + ?match({atomic, [Rec2]}, + mnesia:transaction(fun() -> mnesia:read({test_table2, 155}) end)), + + ?verify_mnesia([N1, N2], []), + ?cleanup(2, Config), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +start_first_one_disc_less_then_one_disc_full(doc)-> + ["no_doc"]; +start_first_one_disc_less_then_one_disc_full(suite) -> []; +start_first_one_disc_less_then_one_disc_full(Config) when is_list(Config) -> + [N1, N2] = Nodes = ?init(2, Config), + ?match(ok, mnesia:create_schema([N1])), + ?match([], mnesia_test_lib:start_mnesia([N1])), + + ?match({atomic, ok}, mnesia:add_table_copy(schema, N2, ram_copies)), + + ?match(ok, rpc:call(N2, mnesia, start, [[{schema_location, ram}, + {extra_db_nodes, Nodes}]])), + + mnesia_test_lib:sync_tables([N1, N2], [schema]), + + mnesia_test_lib:kill_mnesia(Nodes), + receive after 2000 -> ok end, + ?match([], mnesia_test_lib:start_mnesia(Nodes)), + + mnesia_test_lib:sync_tables([N1, N2], [schema]), + + %% Now create some tables + ?match({atomic,ok}, + rpc:call( + N1, mnesia,create_table, [test_table, + [%%{disc_copies, [node()]}, + {ram_copies, [N1, N2]}, + {attributes, + record_info(fields,test_table)}]])), + mnesia_test_lib:sync_tables([N1, N2], [test_table]), + + ?match({atomic,ok}, + rpc:call( + N2, mnesia,create_table, [test_table2, + [%%{disc_copies, [node()]}, + {ram_copies, [N1, N2]}, + {attributes, + record_info(fields,test_table2)}]])), + + mnesia_test_lib:sync_tables([N1, N2], [test_table, test_table2]), + + %% Assure tables loaded + ?match({[ok, ok], []}, + rpc:multicall([N1, N2], mnesia, wait_for_tables, + [[schema, test_table, test_table2], 10000])), + + %% Write something on one end ... + Rec = #test_table{i=55}, + ?match({atomic, ok}, + rpc:call(N1, mnesia, transaction, + [fun() -> mnesia:write(Rec) end])), + + %% ... and read it in the other + ?match({atomic, [Rec]}, + rpc:call(N2, mnesia, transaction, + [fun() -> mnesia:read({test_table, 55}) end])), + + %% Then do the same but start at the other end + Rec2 = #test_table2{i=155}, + ?match({atomic, ok}, + rpc:call(N2, mnesia, transaction, + [fun() -> + mnesia:write(Rec2) end + ])), + + ?match({atomic, [Rec2]}, + rpc:call(N1, mnesia, transaction, + [fun() -> mnesia:read({test_table2, 155}) end])), + + ?verify_mnesia(Nodes, []), + ?cleanup(1, Config), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +start_first_one_disc_less_then_two_more_disc_less(doc)-> + ["no doc"]; +start_first_one_disc_less_then_two_more_disc_less(suite) -> []; +start_first_one_disc_less_then_two_more_disc_less(Config) when is_list(Config) -> + Nodes = [N1, N2, N3] = ?init(3, Config), + + ?match(ok, rpc:call(N1, mnesia, start, [[{schema_location, ram}]])), + + %% Really should use test_lib:mnesia_start for these ones but ... + ?match({atomic, ok}, + rpc:call(N1, mnesia,add_table_copy, [schema, N2, ram_copies])), + ?match({atomic, ok}, + rpc:call(N1, mnesia,add_table_copy, [schema, N3, ram_copies])), + + ?match(ok, rpc:call(N2, mnesia, start, [[{schema_location, ram}, + {extra_db_nodes, [N1]}]])), + ?match(ok, rpc:call(N3, mnesia, start, [[{schema_location, ram}, + {extra_db_nodes, [N1, N2]}]])), + + %% Now create some tables + ?match({atomic,ok}, + rpc:call( + N1, mnesia,create_table, [test_table, + [%%{disc_copies, [node()]}, + {ram_copies, [N1, N2, N3]}, + {attributes, + record_info(fields,test_table)}]])), + + %% Assure tables loaded + ?match({[ok, ok, ok], []}, + rpc:multicall([N1, N2, N3], mnesia, wait_for_tables, + [[test_table], 1000])), + + %% Write something on one end ... + ?match({atomic, ok}, + rpc:call(N1, mnesia, transaction, + [fun() -> mnesia:write(#test_table{i=44}) end])), + + %% Force synchronicity + ?match({atomic, ok}, + rpc:call(N1, mnesia, transaction, + [fun() -> mnesia:write_lock_table(test_table) end])), + + %% ... and read it in the others + ?match({[{atomic, [{test_table, 44, _, _, _}]}, + {atomic, [{test_table, 44, _, _, _}]}], []}, + rpc:multicall([N2, N3], mnesia, transaction, + [fun() -> mnesia:read({test_table, 44}) end])), + + %% Then do the other way around + ?match({atomic, ok}, + rpc:call(N3, mnesia, transaction, + [fun() -> mnesia:write(#test_table{i=33}) end])), + %% Force synchronicity + ?match({atomic, ok}, + rpc:call(N3, mnesia, transaction, + [fun() -> mnesia:write_lock_table(test_table) end])), + + ?match({[{atomic, [{test_table, 44, _, _, _}]}, + {atomic, [{test_table, 44, _, _, _}]}], []}, + rpc:multicall([N1, N2], mnesia, transaction, + [fun() -> mnesia:read({test_table, 44}) end])), + + mnesia_test_lib:reload_appls([mnesia], Nodes), + ok. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +schema_location_and_extra_db_nodes_combinations(doc)-> + ["Test schema loaction and extra_db_nodes combinations."]; +schema_location_and_extra_db_nodes_combinations(suite) -> []; +schema_location_and_extra_db_nodes_combinations(Config) when is_list(Config) -> + [N1, N2] = Nodes = ?init(2, Config), + ?match(ok, mnesia:create_schema([N1])), + ?match([], mnesia_test_lib:start_mnesia([N1])), + + %% Really should use test_lib:mnesia_start for these ones but ... + ?match({atomic, ok}, + rpc:call(N1, mnesia,add_table_copy, [schema, N2, ram_copies])), + + ?match(ok, rpc:call(N2, mnesia, start, [[{schema_location, ram}, + {extra_db_nodes, [N1]}]])), + + %% Assure tables loaded + ?match({[ok, ok], []}, + rpc:multicall([N1, N2], mnesia, wait_for_tables, + [[schema], 10000])), + + ?verify_mnesia(Nodes, []), + ?cleanup(2, Config), + ok. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +table_load_to_disc_less_nodes(doc)-> + ["Load tables to disc less nodes"]; +table_load_to_disc_less_nodes(suite) -> []; +table_load_to_disc_less_nodes(Config) when is_list(Config) -> + [N1, N2] = ?init(2, Config), + + ?match(ok, rpc:call(N1, mnesia, start, [[{schema_location, ram}]])), + + %% Really should use test_lib:mnesia_start for these ones but ... + ?match({atomic, ok}, + rpc:call(N1, mnesia,add_table_copy, [schema, N2, ram_copies])), + + ?match(ok, rpc:call(N2, mnesia, start, [[{schema_location, ram}, + {extra_db_nodes, [N1]}]])), + + %% Now create some tables + ?match({atomic,ok}, + rpc:call( + N1, mnesia,create_table, [test_table, + [%%{disc_copies, [node()]}, + {ram_copies, [N1, N2]}, + {attributes, + record_info(fields,test_table)}]])), + + %% Assure tables loaded + ?match({[ok, ok], []}, + rpc:multicall([N1, N2], mnesia, wait_for_tables, + [[test_table], 1000])), + + %% Write something on one end ... + ?match({atomic, ok}, + rpc:call(N1, mnesia, transaction, + [fun() -> mnesia:write(#test_table{i=44}) end])), + + %% Force synchronicity + ?match({atomic, ok}, + rpc:call(N1, mnesia, transaction, + [fun() -> mnesia:write_lock_table(test_table) end])), + + %% ... and read it in the others + ?match({atomic, [{test_table, 44, _, _, _}]}, + rpc:call(N2, mnesia, transaction, + [fun() -> mnesia:read({test_table, 44}) end])), + + ?cleanup(2, Config), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +schema_merge(doc) -> + ["Provoke various schema merge situations.", + "Perform various schema updates while some nodes are down,", + "stop the started nodes, start the stopped nodes and perform", + "schema updates. Now we have a situation were some of the table", + "definitions have been changed on two or more nodes independently", + "of each other and when Mnesia on the nodes tries to connect", + "to each other at restart the schema will be merged.", + "Do also try to provoke schema merge situations were the", + "schema cannot be merged."]; + +schema_merge(suite) -> []; + +schema_merge(Config) when is_list(Config) -> + [N1, N2]=Nodes=?acquire(2,Config), + + mnesia_test_lib:kill_mnesia([N2]), + receive after 1000 -> ok end, + + Storage = mnesia_test_lib:storage_type(disc_copies, Config), + ?match({atomic,ok}, + rpc:call( + N1, mnesia,create_table, + [test_table, + [{Storage, [N1]}, + {attributes, + record_info(fields,test_table)}]])), + + ?match({atomic, ok}, + rpc:call(N1, mnesia, transaction, + [fun() -> mnesia:write(#test_table{i=44}) end])), + + mnesia_test_lib:kill_mnesia([N1]), + receive after 2000 -> ok end, + %% Can't use std start because it waits for schema + ?match(ok, rpc:call(N2, mnesia, start, [])), + + ?match({atomic,ok}, + rpc:call( + N2, mnesia,create_table, + [test_table2, + [{Storage, [N2]}, + {attributes, + record_info(fields,test_table2)}]])), + + receive after 5000 -> ok end, + + ?match({atomic, ok}, + rpc:call(N2, mnesia, transaction, + [fun() -> mnesia:write(#test_table2{i=33}) end])), + + %% Can't use std start because it waits for schema + ?match(ok, rpc:call(N1, mnesia, start, [])), + + %% Assure tables loaded + ?match({[ok, ok], []}, + rpc:multicall([N1, N2], mnesia, wait_for_tables, + [[schema, test_table, test_table2], 10000])), + + %% ... and read it in the others + ?match({[{atomic, [{test_table, 44, _, _, _}]}, + {atomic, [{test_table, 44, _, _, _}]}], []}, + rpc:multicall([N1, N2], mnesia, transaction, + [fun() -> mnesia:read({test_table, 44}) end])), + + ?match({[{atomic, [{test_table2, 33, _}]}, + {atomic, [{test_table2, 33, _}]}], []}, + rpc:multicall([N1, N2], mnesia, transaction, + [fun() -> mnesia:read({test_table2, 33}) end])), + + ?verify_mnesia(Nodes, []), + ?cleanup(2, Config), + ok. + + +-define(connect(Nodes), mnesia:change_config(extra_db_nodes, Nodes)). +-define(rpc_connect(From, Nodes), + rpc:call(From, mnesia, change_config, [extra_db_nodes, Nodes])). + + +sort({ok, NS}) -> + {ok, lists:sort(NS)}; +sort(Ns) when is_tuple(Ns) -> + Ns; +sort(NS) when is_list(NS) -> + lists:sort(NS). + + +dynamic_connect(doc) -> + ["Test the new functionality where we start mnesia first and then " + "connect to the other mnesia nodes"]; +dynamic_connect(suite) -> + [ + dynamic_basic, + dynamic_ext, + dynamic_bad + ]. + + +dynamic_basic(suite) -> []; +dynamic_basic(Config) when is_list(Config) -> + Nodes = [N1, N2, N3] = ?acquire_nodes(3, Config), + SNs = lists:sort(Nodes), + + ?match({atomic, ok}, mnesia:create_table(tab1, [{ram_copies, Nodes--[N1]}, {disc_copies, [N1]}])), + ?match({atomic, ok}, mnesia:create_table(tab2, [{disc_copies, Nodes}])), + + ?match({ok, SNs}, sort(?rpc_connect(N1, Nodes))), %% What shall happen? + ?match({ok, []}, sort(?rpc_connect(N1, [nonode@nothosted]))), %% What shall happen? + + ?match([], mnesia_test_lib:kill_mnesia([N2])), + ?match(ok, mnesia:delete_schema([N2])), + + ?match(ok, mnesia:dirty_write({tab1, 1, 1})), + ?match(ok, mnesia:dirty_write({tab2, 1, 1})), + + ?match(ok, rpc:call(N2, mnesia, start, [[{extra_db_nodes, [N1]}]])), + ?match(ok, rpc:call(N2, mnesia, wait_for_tables, [[tab1,tab2],5000])), + io:format("Here ~p ~n",[?LINE]), + check_storage(N2, N1, [N3]), + ?match(SNs, sort(rpc:call(N1, mnesia, system_info, [running_db_nodes]))), + ?match(SNs, sort(rpc:call(N2, mnesia, system_info, [running_db_nodes]))), + + ?match([], mnesia_test_lib:kill_mnesia([N3])), + ?match(ok, mnesia:delete_schema([N3])), + + io:format("T1 ~p ~n",[rpc:call(N3,?MODULE,c_nodes,[])]), + ?match(ok, rpc:call(N3, mnesia, start, [])), + io:format("T2 ~p ~n",[rpc:call(N3,?MODULE,c_nodes,[])]), + timer:sleep(2000), + io:format("T3 ~p ~n",[rpc:call(N3,?MODULE,c_nodes,[])]), + ?match({ok, [N1]}, sort(?rpc_connect(N3, [N1]))), + io:format("T4 ~p ~n",[rpc:call(N3,?MODULE,c_nodes,[])]), + ?match(ok, rpc:call(N3, mnesia, wait_for_tables, [[tab1,tab2],5000])), + io:format("Here ~p ~n",[?LINE]), + check_storage(N3, N1, [N2]), + ?match(SNs, sort(rpc:call(N1, mnesia, system_info, [running_db_nodes]))), + ?match(SNs, sort(rpc:call(N2, mnesia, system_info, [running_db_nodes]))), + + ?match([], mnesia_test_lib:kill_mnesia([N3])), + ?match(ok, mnesia:delete_schema([N3])), + + ?match(ok, rpc:call(N3, mnesia, start, [])), + ?match({ok, [N3]}, sort(?rpc_connect(N1, [N3]))), + ?match(ok, rpc:call(N3, mnesia, wait_for_tables, [[tab1,tab2],5000])), + io:format("Here ~p ~n",[?LINE]), + check_storage(N3, N1, [N2]), + ?match(SNs, sort(rpc:call(N1, mnesia, system_info, [running_db_nodes]))), + ?match(SNs, sort(rpc:call(N2, mnesia, system_info, [running_db_nodes]))), + + mnesia_test_lib:kill_mnesia([N2]), + ?match(ok, mnesia:delete_schema([N2])), + ?match({atomic, ok}, mnesia:del_table_copy(schema, N2)), + + % Ok, we have now removed references to node N2 from the other nodes + % mnesia should come up now. + ?match({atomic, ok}, mnesia:add_table_copy(tab1, N2, ram_copies)), + + ?match(ok, rpc:call(N2, mnesia, start, [])), + ?match({ok, _}, sort(?rpc_connect(N2, [N3]))), + + ?match(SNs, sort(rpc:call(N1, mnesia, system_info, [running_db_nodes]))), + ?match(SNs, sort(rpc:call(N2, mnesia, system_info, [running_db_nodes]))), + ?match(SNs, sort(rpc:call(N3, mnesia, system_info, [running_db_nodes]))), + + ?match(ok, rpc:call(N2, mnesia, wait_for_tables, [[tab1], 1000])), + ?match([{tab1, 1, 1}], rpc:call(N2, mnesia, dirty_read, [tab1, 1])), + + mnesia_test_lib:kill_mnesia([N2]), + + %%% SYNC!!! + timer:sleep(1000), + + ?match([N3,N1], sort(rpc:call(N1, mnesia, system_info, [running_db_nodes]))), + ?match([N3,N1], sort(rpc:call(N3, mnesia, system_info, [running_db_nodes]))), + + ?match(ok, rpc:call(N2, mnesia, start, [])), + ?match({ok, _}, sort(?rpc_connect(N3, [N2]))), + + ?match(SNs, sort(rpc:call(N1, mnesia, system_info, [running_db_nodes]))), + ?match(SNs, sort(rpc:call(N2, mnesia, system_info, [running_db_nodes]))), + ?match(SNs, sort(rpc:call(N3, mnesia, system_info, [running_db_nodes]))), + + ?match(ok, rpc:call(N2, mnesia, wait_for_tables, [[tab1], 1000])), + ?match([{tab1, 1, 1}], rpc:call(N2, mnesia, dirty_read, [tab1, 1])), + + ?verify_mnesia(Nodes, []), +%% ?cleanup(3, Config). + ok. + +c_nodes() -> + {mnesia_lib:val({current, db_nodes}),mnesia_lib:val(recover_nodes)}. + + +dynamic_ext(suite) -> []; +dynamic_ext(Config) when is_list(Config) -> + Ns = [N1,N2] = ?acquire_nodes(2, Config), + SNs = lists:sort([N1,N2]), + + ?match({atomic, ok}, mnesia:create_table(tab0, [{disc_copies, [N1,N2]}])), + ?match({atomic, ok}, mnesia:create_table(tab1, [{ram_copies, [N2]}])), + ?match({atomic, ok}, mnesia:create_table(tab2, [{disc_copies, [N2]}])), + ?match({atomic, ok}, mnesia:create_table(tab3, [{disc_only_copies, [N2]}])), + + mnesia_test_lib:kill_mnesia([N2]), + ?match(ok, mnesia:delete_schema([N2])), + ?match(ok, rpc:call(N2, mnesia, start, [[{extra_db_nodes, [N1]}]])), + + ?match(SNs, sort(rpc:call(N1, mnesia, system_info, [running_db_nodes]))), + ?match(SNs, sort(rpc:call(N2, mnesia, system_info, [running_db_nodes]))), + + ?match(ok, rpc:call(N2, mnesia, wait_for_tables, [[tab0,tab1,tab2,tab3], 2000])), + + Check = fun({Tab,Storage}) -> + ?match(Storage, rpc:call(N2, mnesia, table_info, [Tab, storage_type])), + ?match([{N2,Storage}], + lists:sort(rpc:call(N2, mnesia, table_info, [Tab, where_to_commit]))) + end, + [Check(Test) || Test <- [{tab1, ram_copies},{tab2, disc_copies},{tab3, disc_only_copies}]], + + T = now(), + ?match(ok, mnesia:dirty_write({tab0, 42, T})), + ?match(ok, mnesia:dirty_write({tab1, 42, T})), + ?match(ok, mnesia:dirty_write({tab2, 42, T})), + ?match(ok, mnesia:dirty_write({tab3, 42, T})), + + ?match(stopped, rpc:call(N2, mnesia, stop, [])), + ?match(ok, rpc:call(N2, mnesia, start, [])), + ?match(SNs, sort(rpc:call(N2, mnesia, system_info, [running_db_nodes]))), + ?match(ok, mnesia:wait_for_tables([tab0,tab1,tab2,tab3], 10000)), + ?match(ok, rpc:call(N2, mnesia, wait_for_tables, [[tab1,tab2,tab3], 100])), + ?match([], mnesia:dirty_read({tab1, 41})), + ?match([{tab2,42,T}], mnesia:dirty_read({tab2, 42})), + ?match([{tab3,42,T}], mnesia:dirty_read({tab3, 42})), + + mnesia_test_lib:kill_mnesia([N2]), + ?match(ok, mnesia:delete_schema([N2])), + + ?match(stopped, rpc:call(N1, mnesia, stop, [])), + + ?match(ok, rpc:call(N2, mnesia, start, [[{extra_db_nodes,[N1,N2]}]])), + ?match({timeout,[tab0]}, rpc:call(N2, mnesia, wait_for_tables, [[tab0], 500])), + + ?match(ok, rpc:call(N1, mnesia, start, [[{extra_db_nodes, [N1,N2]}]])), + ?match(ok, rpc:call(N1, mnesia, wait_for_tables, [[tab0], 1500])), + ?match(ok, rpc:call(N2, mnesia, wait_for_tables, [[tab0], 1500])), + ?match([{tab0,42,T}], mnesia:dirty_read({tab0, 42})), + ?match([{tab0,42,T}], rpc:call(N2, mnesia,dirty_read,[{tab0,42}])), + + ?match(stopped, rpc:call(N1, mnesia, stop, [])), + mnesia_test_lib:kill_mnesia([N2]), + ?match(ok, mnesia:delete_schema([N2])), + ?match(ok, rpc:call(N1, mnesia, start, [[{extra_db_nodes, [N1,N2]}]])), + ?match({timeout,[tab0]}, rpc:call(N1, mnesia, wait_for_tables, [[tab0], 500])), + + ?match(ok, rpc:call(N2, mnesia, start, [[{extra_db_nodes,[N1,N2]}]])), + ?match(ok, rpc:call(N1, mnesia, wait_for_tables, [[tab0], 1500])), + ?match(ok, rpc:call(N2, mnesia, wait_for_tables, [[tab0], 1500])), + ?match([{tab0,42,T}], mnesia:dirty_read({tab0, 42})), + ?match([{tab0,42,T}], rpc:call(N2,mnesia,dirty_read,[{tab0,42}])), + + ?verify_mnesia(Ns, []), + ok. + +check_storage(Me, Orig, Other) -> + io:format("Nodes ~p ~p ~p~n",[Me,Orig,Other]), + rpc:multicall(Other, sys, status, [mnesia_locker]), + rpc:call(Me, sys, status, [mnesia_locker]), + rpc:call(Orig, sys, status, [mnesia_locker]), + rpc:multicall(Other, sys, status, [mnesia_controller]), + rpc:call(Me, sys, status, [mnesia_controller]), + rpc:call(Orig, sys, status, [mnesia_controller]), + %% Verify disc_copies + W2C = lists:sort([{Node,disc_copies} || Node <- [Me,Orig|Other]]), + W2W = lists:sort([Me,Orig|Other]), + ?match(disc_copies, rpc:call(Orig, mnesia, table_info, [schema, storage_type])), + ?match(disc_copies, rpc:call(Me, mnesia, table_info, [schema, storage_type])), + ?match(W2C, lists:sort(rpc:call(Orig, mnesia, table_info, [schema, where_to_commit]))), + ?match(W2C, lists:sort(rpc:call(Me, mnesia, table_info, [schema, where_to_commit]))), + + ?match(disc_copies, rpc:call(Orig, mnesia, table_info, [tab2, storage_type])), + ?match(disc_copies, rpc:call(Me, mnesia, table_info, [tab2, storage_type])), + ?match(W2W, lists:sort(rpc:call(Me, mnesia, table_info, [tab2, where_to_write]))), + ?match(Me, rpc:call(Me, mnesia, table_info, [tab2, where_to_read])), + + ?match(W2C, lists:sort(rpc:call(Orig, mnesia, table_info, [tab2, where_to_commit]))), + ?match(W2C, lists:sort(rpc:call(Me, mnesia, table_info, [tab2, where_to_commit]))), + + ?match([{tab1,1,1}], mnesia:dirty_read(tab1,1)), + ?match([{tab2,1,1}], mnesia:dirty_read(tab2,1)), + ?match([{tab1,1,1}], rpc:call(Me, mnesia, dirty_read, [tab1,1])), + ?match([{tab2,1,1}], rpc:call(Me, mnesia, dirty_read, [tab2,1])), + + ?match(true, rpc:call(Me, mnesia_monitor, use_dir, [])), + ?match(disc_copies, rpc:call(Me, mnesia_lib, val, [{schema, storage_type}])), + + mnesia_test_lib:kill_mnesia([Orig]), + mnesia_test_lib:kill_mnesia(Other), + T = now(), + ?match(ok, rpc:call(Me, mnesia, dirty_write, [{tab2, 42, T}])), + ?match(stopped, rpc:call(Me, mnesia, stop, [])), + ?match(ok, rpc:call(Me, mnesia, start, [])), + ?match([], mnesia_test_lib:start_mnesia([Orig|Other], [tab1,tab2])), + ?match([{tab2,42,T}], rpc:call(Me, mnesia, dirty_read, [{tab2, 42}])), + ?match([{tab2,42,T}], rpc:call(Orig, mnesia, dirty_read, [{tab2, 42}])), + + ?match([{tab1,1,1}], mnesia:dirty_read(tab1,1)), + ?match([{tab2,1,1}], mnesia:dirty_read(tab2,1)), + ?match([{tab1,1,1}], rpc:call(Me, mnesia, dirty_read, [tab1,1])), + ?match([{tab2,1,1}], rpc:call(Me, mnesia, dirty_read, [tab2,1])), + ok. + + +dynamic_bad(suite) -> []; +dynamic_bad(Config) when is_list(Config) -> + Ns = [N1, N2, N3] = ?acquire_nodes(3, Config), + SNs = lists:sort([N2,N3]), + + ?match({atomic, ok}, mnesia:change_table_copy_type(schema, N2, ram_copies)), + ?match({atomic, ok}, mnesia:change_table_copy_type(schema, N3, ram_copies)), + ?match({atomic, ok}, mnesia:create_table(tab1, [{ram_copies, Ns -- [N1]}, + {disc_copies, [N1]}])), + ?match(ok, mnesia:dirty_write({tab1, 1, 1})), + + mnesia_test_lib:kill_mnesia(Ns), + ?match({[ok, ok], []}, rpc:multicall(Ns -- [N1], mnesia, start, [])), + ?match({ok, [N2]}, ?rpc_connect(N3, [N2])), + ?match(SNs, sort(rpc:call(N2, mnesia, system_info, [running_db_nodes]))), + ?match(SNs, sort(rpc:call(N3, mnesia, system_info, [running_db_nodes]))), + ?match({badrpc, {'EXIT', {aborted, {no_exists, _, _}}}}, + rpc:call(N2, mnesia, table_info, [tab1, where_to_read])), + + ?match(ok, mnesia:start()), + ?match(ok, rpc:call(N2, mnesia, wait_for_tables, [[tab1], 1000])), + ?match(N2, rpc:call(N2, mnesia, table_info, [tab1, where_to_read])), + ?match([{tab1, 1, 1}], rpc:call(N2, mnesia, dirty_read, [tab1, 1])), + + mnesia_test_lib:kill_mnesia(Ns), + ?match({[ok, ok], []}, rpc:multicall(Ns -- [N1], mnesia, start, [])), + ?match({ok, [N2]}, ?rpc_connect(N3, [N2])), + % Make a merge conflict + ?match({atomic, ok}, rpc:call(N3, mnesia, create_table, [tab1, []])), + + io:format("We expect a mnesia crash here~n", []), + ?match({error,{_, _}}, mnesia:start()), + + ?verify_mnesia(Ns -- [N1], []), + ok. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +unknown_config(doc) -> + ["Try some unknown configuration parameters and see that expected", + "things happens."]; +unknown_config(suite)-> []; +unknown_config(Config) when is_list(Config) -> + ?init(1, Config), + %% NOTE: case 1 & 2 below do not respond the same + ?match({error, Res} when element(1, Res) == bad_type, + mnesia:start([{undefined_config,[]}])), + %% Below does not work, but the "correct" behaviour would be to have + %% case 1 above to behave as the one below. + + %% in mnesia-1.3 {error,{bad_type,{[],undefined_config}}} + ?match({error, Res} when element(1, Res) == bad_type, + mnesia:start([{[],undefined_config}])), + ?cleanup(1, Config), + ok. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +inconsistent_database(doc) -> + ["Replace the event module with another module and use it as", + "receiver of the various system and table events. Provoke", + "coverage of all kinds of events."]; +inconsistent_database(suite) -> []; +inconsistent_database(Config) when is_list(Config) -> + Nodes = mnesia_test_lib:prepare_test_case([{init_test_case, [mnesia]}], + 2, Config, ?FILE, ?LINE), + KillAfter = length(Nodes) * timer:minutes(5), + ?acquire_schema(2, Config ++ [{tc_timeout, KillAfter}]), + + Ok = [ok || _N <- Nodes], + StartArgs = [{event_module, mnesia_inconsistent_database_test}], + ?match({Ok, []}, rpc:multicall(Nodes, mnesia, start, [StartArgs])), + ?match([], mnesia_test_lib:kill_mnesia(Nodes)), + + ?match(ok, mnesia_meter:go(ram_copies, Nodes)), + + mnesia_test_lib:reload_appls([mnesia], Nodes), + ok. + diff --git a/lib/mnesia/test/mnesia_consistency_test.erl b/lib/mnesia/test/mnesia_consistency_test.erl new file mode 100644 index 0000000000..ffe8ab7ac3 --- /dev/null +++ b/lib/mnesia/test/mnesia_consistency_test.erl @@ -0,0 +1,1612 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1997-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(mnesia_consistency_test). +-author('[email protected]'). +-compile([export_all]). + +-include("mnesia_test_lib.hrl"). + +init_per_testcase(Func, Conf) -> + mnesia_test_lib:init_per_testcase(Func, Conf). + +fin_per_testcase(Func, Conf) -> + mnesia_test_lib:fin_per_testcase(Func, Conf). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +all(doc) -> + ["Verify transaction consistency", + "Consistency is the property of the application that requires any", + "execution of the transaction to take the database from one", + "consistent state to another. Verify that the database is", + "consistent at any point in time.", + "Verify for various configurations.", + " Verify for both set and bag"]; +all(suite) -> + [ + consistency_after_restart, + consistency_after_dump_tables, + consistency_after_add_replica, + consistency_after_del_replica, + consistency_after_move_replica, + consistency_after_transform_table, + consistency_after_change_table_copy_type, + consistency_after_fallback, + consistency_after_restore, + consistency_after_rename_of_node, + checkpoint_retainer_consistency, + backup_consistency + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% stolen from mnesia_tpcb.erl: + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% Account record, total size must be at least 100 bytes + +-define(ACCOUNT_FILLER, + {123456789012345678901234567890123456789012345678901234567890, + 123456789012345678901234567890123456789012345678901234567890, + 123456789012345678901234567890123456789012345678901234}). + +-record(account, + { + id = 0, %% Unique account id + branch_id = 0, %% Branch where the account is held + balance = 0, %% Account balance + filler = ?ACCOUNT_FILLER %% Gap filler to ensure size >= 100 bytes + }). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% Branch record, total size must be at least 100 bytes + +-define(BRANCH_FILLER, + {123456789012345678901234567890123456789012345678901234567890, + 123456789012345678901234567890123456789012345678901234567890, + 123456789012345678901234567890123456789012345678901234567890}). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% Teller record, total size must be at least 100 bytes + +-define(TELLER_FILLER, + {123456789012345678901234567890123456789012345678901234567890, + 123456789012345678901234567890123456789012345678901234567890, + 1234567890123456789012345678901234567890123456789012345678}). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% History record, total size must be at least 50 bytes + +-define(HISTORY_FILLER, 1234567890). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-record(tab_config, + { + db_nodes = [node()], + replica_nodes = [node()], + replica_type = ram_copies, + use_running_mnesia = false, + n_branches = 1, + n_tellers_per_branch = 10, %% Must be 10 + n_accounts_per_branch = 100000, %% Must be 100000 + branch_filler = ?BRANCH_FILLER, + account_filler = ?ACCOUNT_FILLER, + teller_filler = ?TELLER_FILLER + }). +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% stolen from mnesia_tpcb.erl: + +list2rec(List, Fields, DefaultTuple) -> + [Name|Defaults] = tuple_to_list(DefaultTuple), + List2 = list2rec(List, Fields, Defaults, []), + list_to_tuple([Name] ++ List2). + +list2rec(_List, [], [], Acc) -> + Acc; +list2rec(List, [F|Fields], [D|Defaults], Acc) -> + {Val, List2} = + case lists:keysearch(F, 1, List) of + false -> + {D, List}; + {value, {F, NewVal}} -> + {NewVal, lists:keydelete(F, 1, List)} + end, + list2rec(List2, Fields, Defaults, Acc ++ [Val]). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +tpcb_config(ReplicaType, _NodeConfig, Nodes, NoDriverNodes) -> + [{n_branches, 10}, + {n_drivers_per_node, 10}, + {replica_nodes, Nodes}, + {driver_nodes, Nodes -- NoDriverNodes}, + {use_running_mnesia, true}, + {report_interval, infinity}, + {n_accounts_per_branch, 100}, + {replica_type, ReplicaType}, + {reuse_history_id, true}]. + +%% Stolen from mnesia_tpcb:dist +tpcb_config_dist(ReplicaType, _NodeConfig, Nodes, _Config) -> + [{db_nodes, Nodes}, + {driver_nodes, Nodes}, + {replica_nodes, Nodes}, + {n_drivers_per_node, 10}, + {n_branches, 1}, + {use_running_mnesia, true}, + {n_accounts_per_branch, 10}, + {replica_type, ReplicaType}, + {stop_after, timer:minutes(15)}, + {report_interval, timer:seconds(10)}, + {reuse_history_id, true}]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% stolen from mnesia_recovery_test.erl: + +receive_messages([]) -> []; +receive_messages(ListOfMsgs) -> + receive + {Pid, Msg} -> + case lists:member(Msg, ListOfMsgs) of + false -> + ?warning("I (~p) have received unexpected msg~n ~p ~n", + [self(),{Pid, Msg}]), + receive_messages(ListOfMsgs); + true -> + ?verbose("I (~p) got msg ~p from ~p ~n", [self(),Msg, Pid]), + [{Pid, Msg} | receive_messages(ListOfMsgs -- [Msg])] + end; + Else -> ?warning("Recevied unexpected Msg~n ~p ~n", [Else]) + after timer:minutes(3) -> + ?error("Timeout in receive msgs while waiting for ~p~n", + [ListOfMsgs]) + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +consistency_after_restart(suite) -> + [ + consistency_after_restart_1_ram, + consistency_after_restart_1_disc, + consistency_after_restart_1_disc_only, + consistency_after_restart_2_ram, + consistency_after_restart_2_disc, + consistency_after_restart_2_disc_only + ]. + +consistency_after_restart_1_ram(suite) -> []; +consistency_after_restart_1_ram(Config) when is_list(Config) -> + consistency_after_restart(ram_copies, 2, Config). + +consistency_after_restart_1_disc(suite) -> []; +consistency_after_restart_1_disc(Config) when is_list(Config) -> + consistency_after_restart(disc_copies, 2, Config). + +consistency_after_restart_1_disc_only(suite) -> []; +consistency_after_restart_1_disc_only(Config) when is_list(Config) -> + consistency_after_restart(disc_only_copies, 2, Config). + +consistency_after_restart_2_ram(suite) -> []; +consistency_after_restart_2_ram(Config) when is_list(Config) -> + consistency_after_restart(ram_copies, 3, Config). + +consistency_after_restart_2_disc(suite) -> []; +consistency_after_restart_2_disc(Config) when is_list(Config) -> + consistency_after_restart(disc_copies, 3, Config). + +consistency_after_restart_2_disc_only(suite) -> []; +consistency_after_restart_2_disc_only(Config) when is_list(Config) -> + consistency_after_restart(disc_only_copies, 3, Config). + +consistency_after_restart(ReplicaType, NodeConfig, Config) -> + [Node1 | _] = Nodes = ?acquire_nodes(NodeConfig, Config), + {success, [A]} = ?start_activities([Node1]), + ?log("consistency_after_restart with ~p on ~p~n", + [ReplicaType, Nodes]), + TpcbConfig = tpcb_config(ReplicaType, NodeConfig, Nodes, [Node1]), + mnesia_tpcb:init(TpcbConfig), + A ! fun () -> mnesia_tpcb:run(TpcbConfig) end, + timer:sleep(timer:seconds(10)), + mnesia_test_lib:kill_mnesia([Node1]), + %% Start and wait for tables to be loaded on all nodes + timer:sleep(timer:seconds(3)), + ?match([], mnesia_test_lib:start_mnesia(Nodes,[account,branch,teller, history])), + mnesia_tpcb:stop(), + ?match(ok, mnesia_tpcb:verify_tabs()), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +consistency_after_dump_tables(suite) -> + [ + consistency_after_dump_tables_1_ram, + consistency_after_dump_tables_2_ram + ]. + +consistency_after_dump_tables_1_ram(suite) -> []; +consistency_after_dump_tables_1_ram(Config) when is_list(Config) -> + consistency_after_dump_tables(ram_copies, 1, Config). + +consistency_after_dump_tables_2_ram(suite) -> []; +consistency_after_dump_tables_2_ram(Config) when is_list(Config) -> + consistency_after_dump_tables(ram_copies, 2, Config). + +consistency_after_dump_tables(ReplicaType, NodeConfig, Config) -> + [Node1 | _] = Nodes = ?acquire_nodes(NodeConfig, Config), + {success, [A]} = ?start_activities([Node1]), + ?log("consistency_after_dump_tables with ~p on ~p~n", + [ReplicaType, Nodes]), + TpcbConfig = tpcb_config(ReplicaType, NodeConfig, Nodes, []), + mnesia_tpcb:init(TpcbConfig), + A ! fun() -> mnesia_tpcb:run(TpcbConfig) end, + timer:sleep(timer:seconds(10)), + ?match({atomic, ok}, rpc:call(Node1, mnesia, dump_tables, + [[branch, teller, account, history]])), + mnesia_tpcb:stop(), + ?match(ok, mnesia_tpcb:verify_tabs()), + + mnesia_test_lib:kill_mnesia(Nodes), + timer:sleep(timer:seconds(1)), + ?match([], mnesia_test_lib:start_mnesia(Nodes,[account, branch, + teller, history])), + mnesia_tpcb:stop(), + ?match(ok, mnesia_tpcb:verify_tabs()), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +consistency_after_add_replica(suite) -> + [ + consistency_after_add_replica_2_ram, + consistency_after_add_replica_2_disc, + consistency_after_add_replica_2_disc_only, + consistency_after_add_replica_3_ram, + consistency_after_add_replica_3_disc, + consistency_after_add_replica_3_disc_only + ]. + +consistency_after_add_replica_2_ram(suite) -> []; +consistency_after_add_replica_2_ram(Config) when is_list(Config) -> + consistency_after_add_replica(ram_copies, 2, Config). + +consistency_after_add_replica_2_disc(suite) -> []; +consistency_after_add_replica_2_disc(Config) when is_list(Config) -> + consistency_after_add_replica(disc_copies, 2, Config). + +consistency_after_add_replica_2_disc_only(suite) -> []; +consistency_after_add_replica_2_disc_only(Config) when is_list(Config) -> + consistency_after_add_replica(disc_only_copies, 2, Config). + +consistency_after_add_replica_3_ram(suite) -> []; +consistency_after_add_replica_3_ram(Config) when is_list(Config) -> + consistency_after_add_replica(ram_copies, 3, Config). + +consistency_after_add_replica_3_disc(suite) -> []; +consistency_after_add_replica_3_disc(Config) when is_list(Config) -> + consistency_after_add_replica(disc_copies, 3, Config). + +consistency_after_add_replica_3_disc_only(suite) -> []; +consistency_after_add_replica_3_disc_only(Config) when is_list(Config) -> + consistency_after_add_replica(disc_only_copies, 3, Config). + +consistency_after_add_replica(ReplicaType, NodeConfig, Config) -> + Nodes0 = ?acquire_nodes(NodeConfig, Config), + AddNode = lists:last(Nodes0), + Nodes = Nodes0 -- [AddNode], + Node1 = hd(Nodes), + {success, [A]} = ?start_activities([Node1]), + ?log("consistency_after_add_replica with ~p on ~p~n", + [ReplicaType, Nodes0]), + TpcbConfig = tpcb_config(ReplicaType, NodeConfig, Nodes, []), + mnesia_tpcb:init(TpcbConfig), + A ! fun () -> mnesia_tpcb:run(TpcbConfig) end, + timer:sleep(timer:seconds(10)), + ?match({atomic, ok}, mnesia:add_table_copy(account, AddNode, ReplicaType)), + mnesia_tpcb:stop(), + ?match(ok, mnesia_tpcb:verify_tabs()), + ?verify_mnesia(Nodes0, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +consistency_after_del_replica(suite) -> + [ + consistency_after_del_replica_2_ram, + consistency_after_del_replica_2_disc, + consistency_after_del_replica_2_disc_only, + consistency_after_del_replica_3_ram, + consistency_after_del_replica_3_disc, + consistency_after_del_replica_3_disc_only + ]. + +consistency_after_del_replica_2_ram(suite) -> []; +consistency_after_del_replica_2_ram(Config) when is_list(Config) -> + consistency_after_del_replica(ram_copies, 2, Config). + +consistency_after_del_replica_2_disc(suite) -> []; +consistency_after_del_replica_2_disc(Config) when is_list(Config) -> + consistency_after_del_replica(disc_copies, 2, Config). + +consistency_after_del_replica_2_disc_only(suite) -> []; +consistency_after_del_replica_2_disc_only(Config) when is_list(Config) -> + consistency_after_del_replica(disc_only_copies, 2, Config). + +consistency_after_del_replica_3_ram(suite) -> []; +consistency_after_del_replica_3_ram(Config) when is_list(Config) -> + consistency_after_del_replica(ram_copies, 3, Config). + +consistency_after_del_replica_3_disc(suite) -> []; +consistency_after_del_replica_3_disc(Config) when is_list(Config) -> + consistency_after_del_replica(disc_copies, 3, Config). + +consistency_after_del_replica_3_disc_only(suite) -> []; +consistency_after_del_replica_3_disc_only(Config) when is_list(Config) -> + consistency_after_del_replica(disc_only_copies, 3, Config). + +consistency_after_del_replica(ReplicaType, NodeConfig, Config) -> + Nodes = ?acquire_nodes(NodeConfig, Config), + Node1 = hd(Nodes), + Node2 = lists:last(Nodes), + {success, [A]} = ?start_activities([Node1]), + ?log("consistency_after_del_replica with ~p on ~p~n", + [ReplicaType, Nodes]), + TpcbConfig = tpcb_config(ReplicaType, NodeConfig, Nodes, []), + mnesia_tpcb:init(TpcbConfig), + A ! fun () -> mnesia_tpcb:run(TpcbConfig) end, + timer:sleep(timer:seconds(10)), + ?match({atomic, ok}, mnesia:del_table_copy(account, Node2)), + mnesia_tpcb:stop(), + ?match(ok, mnesia_tpcb:verify_tabs()), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +consistency_after_move_replica(suite) -> + [ + consistency_after_move_replica_2_ram, + consistency_after_move_replica_2_disc, + consistency_after_move_replica_2_disc_only, + consistency_after_move_replica_3_ram, + consistency_after_move_replica_3_disc, + consistency_after_move_replica_3_disc_only + ]. + +consistency_after_move_replica_2_ram(suite) -> []; +consistency_after_move_replica_2_ram(Config) when is_list(Config) -> + consistency_after_move_replica(ram_copies, 2, Config). + +consistency_after_move_replica_2_disc(suite) -> []; +consistency_after_move_replica_2_disc(Config) when is_list(Config) -> + consistency_after_move_replica(disc_copies, 2, Config). + +consistency_after_move_replica_2_disc_only(suite) -> []; +consistency_after_move_replica_2_disc_only(Config) when is_list(Config) -> + consistency_after_move_replica(disc_only_copies, 2, Config). + +consistency_after_move_replica_3_ram(suite) -> []; +consistency_after_move_replica_3_ram(Config) when is_list(Config) -> + consistency_after_move_replica(ram_copies, 3, Config). + +consistency_after_move_replica_3_disc(suite) -> []; +consistency_after_move_replica_3_disc(Config) when is_list(Config) -> + consistency_after_move_replica(disc_copies, 3, Config). + +consistency_after_move_replica_3_disc_only(suite) -> []; +consistency_after_move_replica_3_disc_only(Config) when is_list(Config) -> + consistency_after_move_replica(disc_only_copies, 3, Config). + +consistency_after_move_replica(ReplicaType, NodeConfig, Config) -> + Nodes = ?acquire_nodes(NodeConfig, Config ++ [{tc_timeout, timer:minutes(10)}]), + Node1 = hd(Nodes), + Node2 = lists:last(Nodes), + {success, [A]} = ?start_activities([Node1]), + ?log("consistency_after_move_replica with ~p on ~p~n", + [ReplicaType, Nodes]), + TpcbConfig = tpcb_config(ReplicaType, NodeConfig, Nodes -- [Node2], []), + mnesia_tpcb:init(TpcbConfig), + A ! fun () -> mnesia_tpcb:run(TpcbConfig) end, + timer:sleep(timer:seconds(10)), + ?match({atomic, ok}, mnesia:move_table_copy(account, Node1, Node2)), + ?log("First move completed from node ~p to ~p ~n", [Node1, Node2]), + ?match({atomic, ok}, mnesia:move_table_copy(account, Node2, Node1)), + mnesia_tpcb:stop(), + ?match(ok, mnesia_tpcb:verify_tabs()), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +consistency_after_transform_table(doc) -> + ["Check that the database is consistent after transform_table.", + " While applications are updating the involved tables. "]; + +consistency_after_transform_table(suite) -> + [ + consistency_after_transform_table_ram, + consistency_after_transform_table_disc, + consistency_after_transform_table_disc_only + ]. + + +consistency_after_transform_table_ram(suite) -> []; +consistency_after_transform_table_ram(Config) when is_list(Config) -> + consistency_after_transform_table(ram_copies, Config). + +consistency_after_transform_table_disc(suite) -> []; +consistency_after_transform_table_disc(Config) when is_list(Config) -> + consistency_after_transform_table(disc_copies, Config). + +consistency_after_transform_table_disc_only(suite) -> []; +consistency_after_transform_table_disc_only(Config) when is_list(Config) -> + consistency_after_transform_table(disc_only_copies, Config). + +consistency_after_transform_table(Type, Config) -> + Nodes = [N1, N2,_N3] = ?acquire_nodes(3, Config), + + ?match({atomic, ok}, mnesia:create_table(tab1, [{index, [3]}, {Type, [N1]}])), + ?match({atomic, ok}, mnesia:create_table(tab2, [{index, [3]}, {Type, [N1,N2]}])), + ?match({atomic, ok}, mnesia:create_table(tab3, [{index, [3]}, {Type, Nodes}])), + ?match({atomic, ok}, mnesia:create_table(empty, [{index, [3]},{Type, Nodes}])), + + Tabs = lists:sort([tab1, tab2, tab3, empty]), + + [[mnesia:dirty_write({Tab, N, N}) || N <- lists:seq(1,10)] || + Tab <- Tabs -- [empty, tab4]], + mnesia:dump_log(), + + Ok = lists:duplicate(4, {atomic, ok}), + ?match(Ok, [mnesia:transform_table(Tab, fun({T, N, N}) -> {T, N, N, ok} end, + [k,a,n]) || Tab <- Tabs]), + [?match([k,a,n], mnesia:table_info(Tab, attributes)) || Tab <- Tabs], + + Filter = fun(Tab) -> mnesia:foldl(fun(A, Acc) when size(A) == 3 -> [A|Acc]; + (A, Acc) when size(A) == 4 -> Acc + end, [], Tab) + end, + + ?match([[],[],[],[]], [element(2,mnesia:transaction(Filter, [Tab])) || Tab <- Tabs]), + + mnesia_test_lib:kill_mnesia(Nodes), + mnesia_test_lib:start_mnesia(Nodes, Tabs), + + ?match([Tabs, Tabs, Tabs], + [lists:sort(rpc:call(Node, mnesia,system_info, [tables]) -- [schema]) || Node <- Nodes]), + + ?match([[],[],[],[]], [element(2,mnesia:transaction(Filter, [Tab])) || Tab <- Tabs]), + [?match([k,a,n], mnesia:table_info(Tab, attributes)) || Tab <- Tabs], + + ?verify_mnesia(Nodes, []). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +consistency_after_change_table_copy_type(doc) -> + ["Check that the database is consistent after change of copy type.", + " While applications are updating the involved tables. "]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +consistency_after_fallback(doc) -> + ["Check that installed fallbacks are consistent. Check this by starting ", + "some nodes, run tpcb on them, take a backup at any time, install it ", + "as a fallback, kill all nodes, start mnesia again and check for ", + "any inconsistencies"]; +consistency_after_fallback(suite) -> + [ + consistency_after_fallback_2_ram, + consistency_after_fallback_2_disc, + consistency_after_fallback_2_disc_only, + consistency_after_fallback_3_ram, + consistency_after_fallback_3_disc + , consistency_after_fallback_3_disc_only + ]. + +consistency_after_fallback_2_ram(suite) -> []; +consistency_after_fallback_2_ram(Config) when is_list(Config) -> + consistency_after_fallback(ram_copies, 2, Config). + +consistency_after_fallback_2_disc(suite) -> []; +consistency_after_fallback_2_disc(Config) when is_list(Config) -> + consistency_after_fallback(disc_copies, 2, Config). + +consistency_after_fallback_2_disc_only(suite) -> []; +consistency_after_fallback_2_disc_only(Config) when is_list(Config) -> + consistency_after_fallback(disc_only_copies, 2, Config). + +consistency_after_fallback_3_ram(suite) -> []; +consistency_after_fallback_3_ram(Config) when is_list(Config) -> + consistency_after_fallback(ram_copies, 3, Config). + +consistency_after_fallback_3_disc(suite) -> []; +consistency_after_fallback_3_disc(Config) when is_list(Config) -> + consistency_after_fallback(disc_copies, 3, Config). + +consistency_after_fallback_3_disc_only(suite) -> []; +consistency_after_fallback_3_disc_only(Config) when is_list(Config) -> + consistency_after_fallback(disc_only_copies, 3, Config). + +consistency_after_fallback(ReplicaType, NodeConfig, Config) -> + %%?verbose("Starting consistency_after_fallback2 at ~p~n", [self()]), + Delay = 5, + Nodes = ?acquire_nodes(NodeConfig, [{tc_timeout, timer:minutes(10)} | Config]), + Node1 = hd(Nodes), + %%?verbose("Mnesia info: ~p~n", [mnesia:info()]), + + {success, [A]} = ?start_activities([Node1]), + ?log("consistency_after_fallback with ~p on ~p~n", + [ReplicaType, Nodes]), + TpcbConfig = tpcb_config(ReplicaType, NodeConfig, Nodes, []), + mnesia_tpcb:init(TpcbConfig), + A ! fun () -> mnesia_tpcb:run(TpcbConfig) end, + timer:sleep(timer:seconds(Delay)), + + %% Make a backup + ?verbose("Doing backup~n", []), + ?match(ok, mnesia:backup(consistency_after_fallback2)), + + %% Install the backup as a fallback + ?verbose("Doing fallback~n", []), + ?match(ok, mnesia:install_fallback(consistency_after_fallback2)), + timer:sleep(timer:seconds(Delay)), + + %% Stop tpcb + ?verbose("Stopping TPC-B~n", []), + mnesia_tpcb:stop(), + ?match(ok, mnesia_tpcb:verify_tabs()), + + %% Stop and then start mnesia and check table consistency + %%?verbose("Restarting Mnesia~n", []), + mnesia_test_lib:kill_mnesia(Nodes), + mnesia_test_lib:start_mnesia(Nodes,[account,branch,teller,history]), + + ?match(ok, mnesia_tpcb:verify_tabs()), + if + ReplicaType == ram_copies -> + %% Test that change_table_copy work i.e. no account.dcd file exists. + ?match({atomic, ok}, mnesia:change_table_copy_type(account, node(), disc_copies)); + true -> + ignore + end, + file:delete(consistency_after_fallback2), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +consistency_after_restore(doc) -> + ["Verify consistency after restore operations."]; + +consistency_after_restore(suite) -> + [ + consistency_after_restore_clear_ram, + consistency_after_restore_clear_disc, + consistency_after_restore_clear_disc_only, + consistency_after_restore_recreate_ram, + consistency_after_restore_recreate_disc, + consistency_after_restore_recreate_disc_only + ]. + +consistency_after_restore_clear_ram(suite) -> []; +consistency_after_restore_clear_ram(Config) when is_list(Config) -> + consistency_after_restore(ram_copies, clear_tables, Config). + +consistency_after_restore_clear_disc(suite) -> []; +consistency_after_restore_clear_disc(Config) when is_list(Config) -> + consistency_after_restore(disc_copies, clear_tables, Config). + +consistency_after_restore_clear_disc_only(suite) -> []; +consistency_after_restore_clear_disc_only(Config) when is_list(Config) -> + consistency_after_restore(disc_only_copies, clear_tables, Config). + +consistency_after_restore_recreate_ram(suite) -> []; +consistency_after_restore_recreate_ram(Config) when is_list(Config) -> + consistency_after_restore(ram_copies, recreate_tables, Config). + +consistency_after_restore_recreate_disc(suite) -> []; +consistency_after_restore_recreate_disc(Config) when is_list(Config) -> + consistency_after_restore(disc_copies, recreate_tables, Config). + +consistency_after_restore_recreate_disc_only(suite) -> []; +consistency_after_restore_recreate_disc_only(Config) when is_list(Config) -> + consistency_after_restore(disc_only_copies, recreate_tables, Config). + +consistency_after_restore(ReplicaType, Op, Config) -> + Delay = 1, + Nodes = ?acquire_nodes(3, [{tc_timeout, timer:minutes(10)} | Config]), + [Node1, Node2, _Node3] = Nodes, + File = "cons_backup_restore", + + ?log("consistency_after_restore with ~p on ~p~n", + [ReplicaType, Nodes]), + Tabs = [carA, carB, carC, carD], + + ?match({atomic, ok}, mnesia:create_table(carA, [{ReplicaType, Nodes}])), + ?match({atomic, ok}, mnesia:create_table(carB, [{ReplicaType, Nodes -- [Node1]}])), + ?match({atomic, ok}, mnesia:create_table(carC, [{ReplicaType, Nodes -- [Node2]}])), + ?match({atomic, ok}, mnesia:create_table(carD, [{ReplicaType, [Node2]}])), + + NList = lists:seq(0, 20), + [lists:foreach(fun(E) -> ok = mnesia:dirty_write({Tab, E, 1}) end, NList) || + Tab <- Tabs], + + {ok, Name, _} = mnesia:activate_checkpoint([{max, [schema | Tabs]}, + {ram_overrides_dump, true}]), + ?verbose("Doing backup~n", []), + ?match(ok, mnesia:backup_checkpoint(Name, File)), + ?match(ok, mnesia:deactivate_checkpoint(Name)), + + [lists:foreach(fun(E) -> ok = mnesia:dirty_write({Tab, E, 2}) end, NList) || + Tab <- Tabs], + + Pids1 = [{'EXIT', spawn_link(?MODULE, change_tab, [self(), carA, Op]), ok} || _ <- lists:seq(1, 5)], + Pids2 = [{'EXIT', spawn_link(?MODULE, change_tab, [self(), carB, Op]), ok} || _ <- lists:seq(1, 5)], + Pids3 = [{'EXIT', spawn_link(?MODULE, change_tab, [self(), carC, Op]), ok} || _ <- lists:seq(1, 5)], + Pids4 = [{'EXIT', spawn_link(?MODULE, change_tab, [self(), carD, Op]), ok} || _ <- lists:seq(1, 5)], + + AllPids = Pids1 ++ Pids2 ++ Pids3 ++ Pids4, + + Restore = fun(F, Args) -> + case mnesia:restore(F, Args) of + {atomic, List} -> lists:sort(List); + Else -> Else + end + end, + + timer:sleep(timer:seconds(Delay)), %% Let changers grab locks + ?verbose("Doing restore~n", []), + ?match(Tabs, Restore(File, [{default_op, Op}])), + + timer:sleep(timer:seconds(Delay)), %% Let em die + + ?match_multi_receive(AllPids), + + case ?match(ok, restore_verify_tabs(Tabs)) of + {success, ok} -> + file:delete(File); + _ -> + {T, M, S} = time(), + File2 = ?flat_format("consistency_error~w~w~w.BUP", [T, M, S]), + file:rename(File, File2) + end, + ?verify_mnesia(Nodes, []). + +change_tab(Father, Tab, Test) -> + Key = random:uniform(20), + Update = fun() -> + case mnesia:read({Tab, Key}) of + [{Tab, Key, 1}] -> + quit; + [{Tab, Key, _N}] -> + mnesia:write({Tab, Key, 3}) + end + end, + case mnesia:transaction(Update) of + {atomic, quit} -> + exit(ok); + {aborted, {no_exists, Tab}} when Test == recreate_tables ->%% I'll allow this + change_tab(Father, Tab, Test); + {atomic, ok} -> + change_tab(Father, Tab, Test) + end. + +restore_verify_tabs([Tab | R]) -> + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:foldl(fun({_, _, 1}, ok) -> + ok; + (Else, Acc) -> + [Else|Acc] + end, ok, Tab) + end)), + restore_verify_tabs(R); +restore_verify_tabs([]) -> + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +consistency_after_rename_of_node(doc) -> + ["Skipped because it is an unimportant case."]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +checkpoint_retainer_consistency(doc) -> + ["Verify that the contents of a checkpoint retainer has the expected", + "contents in various situations."]; +checkpoint_retainer_consistency(suite) -> + [ + updates_during_checkpoint_activation, + updates_during_checkpoint_iteration, + load_table_with_activated_checkpoint, + add_table_copy_to_table_with_activated_checkpoint + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +updates_during_checkpoint_activation(doc) -> + ["Perform updates while the checkpoint getting activated", + "and verify that all checkpoint retainers associated with", + "different replicas of the same table really has the same", + "contents."]; +updates_during_checkpoint_activation(suite) -> + [ + updates_during_checkpoint_activation_2_ram, + updates_during_checkpoint_activation_2_disc, + updates_during_checkpoint_activation_2_disc_only, + updates_during_checkpoint_activation_3_ram, + updates_during_checkpoint_activation_3_disc + , updates_during_checkpoint_activation_3_disc_only + ]. + +updates_during_checkpoint_activation_2_ram(suite) -> []; +updates_during_checkpoint_activation_2_ram(Config) when is_list(Config) -> + updates_during_checkpoint_activation(ram_copies, 2, Config). + +updates_during_checkpoint_activation_2_disc(suite) -> []; +updates_during_checkpoint_activation_2_disc(Config) when is_list(Config) -> + updates_during_checkpoint_activation(disc_copies, 2, Config). + +updates_during_checkpoint_activation_2_disc_only(suite) -> []; +updates_during_checkpoint_activation_2_disc_only(Config) when is_list(Config) -> + updates_during_checkpoint_activation(disc_only_copies, 2, Config). + +updates_during_checkpoint_activation_3_ram(suite) -> []; +updates_during_checkpoint_activation_3_ram(Config) when is_list(Config) -> + updates_during_checkpoint_activation(ram_copies, 3, Config). + +updates_during_checkpoint_activation_3_disc(suite) -> []; +updates_during_checkpoint_activation_3_disc(Config) when is_list(Config) -> + updates_during_checkpoint_activation(disc_copies, 3, Config). + +updates_during_checkpoint_activation_3_disc_only(suite) -> []; +updates_during_checkpoint_activation_3_disc_only(Config) when is_list(Config) -> + updates_during_checkpoint_activation(disc_only_copies, 3, Config). + +updates_during_checkpoint_activation(ReplicaType,NodeConfig,Config) -> + %%?verbose("updates_during_checkpoint_activation2 at ~p~n", [self()]), + Delay = 5, + Nodes = ?acquire_nodes(NodeConfig, Config), + Node1 = hd(Nodes), + %%?verbose("Mnesia info: ~p~n", [mnesia:info()]), + + {success, [A]} = ?start_activities([Node1]), + ?log("consistency_after_fallback with ~p on ~p~n", + [ReplicaType, Nodes]), + TpcbConfig = tpcb_config_dist(ReplicaType, NodeConfig, Nodes, Config), + %%TpcbConfig = tpcb_config(ReplicaType, NodeConfig, Nodes), + mnesia_tpcb:init(TpcbConfig), + A ! fun () -> mnesia_tpcb:run(TpcbConfig) end, + timer:sleep(timer:seconds(Delay)), + + {ok, CPName, _NodeList} = + mnesia:activate_checkpoint([{max, mnesia:system_info(tables)}]), + timer:sleep(timer:seconds(Delay)), + + %% Stop tpcb + ?verbose("Stopping TPC-B~n", []), + mnesia_tpcb:stop(), + ?match(ok, mnesia_tpcb:verify_tabs()), + + ?match(ok, mnesia:backup_checkpoint(CPName, + updates_during_checkpoint_activation2)), + timer:sleep(timer:seconds(Delay)), + + ?match(ok, mnesia:install_fallback(updates_during_checkpoint_activation2)), + + %% Stop and then start mnesia and check table consistency + %%?verbose("Restarting Mnesia~n", []), + mnesia_test_lib:kill_mnesia(Nodes), + file:delete(updates_during_checkpoint_activation2), + mnesia_test_lib:start_mnesia(Nodes,[account,branch,teller, history]), + + ?match(ok, mnesia_tpcb:verify_tabs()), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +updates_during_checkpoint_iteration(doc) -> + ["Perform updates while someone is iterating over a checkpoint", + "and verify that the iterator really finds the expected data", + "regardless of ongoing upates."]; + +updates_during_checkpoint_iteration(suite) -> + [ + updates_during_checkpoint_iteration_2_ram, + updates_during_checkpoint_iteration_2_disc + , updates_during_checkpoint_iteration_2_disc_only + ]. + +updates_during_checkpoint_iteration_2_ram(suite) -> []; +updates_during_checkpoint_iteration_2_ram(Config) when is_list(Config) -> + updates_during_checkpoint_iteration(ram_copies, 2, Config). + +updates_during_checkpoint_iteration_2_disc(suite) -> []; +updates_during_checkpoint_iteration_2_disc(Config) when is_list(Config) -> + updates_during_checkpoint_iteration(disc_copies, 2, Config). + +updates_during_checkpoint_iteration_2_disc_only(suite) -> []; +updates_during_checkpoint_iteration_2_disc_only(Config) when is_list(Config) -> + updates_during_checkpoint_iteration(disc_only_copies, 2, Config). + +updates_during_checkpoint_iteration(ReplicaType,NodeConfig,Config) -> + %?verbose("updates_during_checkpoint_iteration2 at ~p~n", [self()]), + Delay = 5, + Nodes = ?acquire_nodes(NodeConfig, Config), + Node1 = hd(Nodes), + %?verbose("Mnesia info: ~p~n", [mnesia:info()]), + File = updates_during_checkpoint_iteration2, + {success, [A]} = ?start_activities([Node1]), + ?log("updates_during_checkpoint_iteration with ~p on ~p~n", + [ReplicaType, Nodes]), + TpcbConfig = tpcb_config_dist(ReplicaType, NodeConfig, Nodes, Config), + %%TpcbConfig = tpcb_config(ReplicaType, NodeConfig, Nodes), + TpcbConfigRec = list2rec(TpcbConfig, + record_info(fields,tab_config), + #tab_config{}), + mnesia_tpcb:init(TpcbConfig), + ?match(ok, mnesia_tpcb:verify_tabs()), + + {ok, CPName, _NodeList} = + mnesia:activate_checkpoint([{max, mnesia:system_info(tables)}, + {ram_overrides_dump,true}]), + A ! fun () -> mnesia:backup_checkpoint(CPName, File) end, + + do_changes_during_backup(TpcbConfigRec), + + ?match_receive({A,ok}), + + timer:sleep(timer:seconds(Delay)), + ?match(ok, mnesia:install_fallback(File)), + timer:sleep(timer:seconds(Delay)), + + ?match({error,{"Bad balance",_,_}}, mnesia_tpcb:verify_tabs()), + + mnesia_test_lib:kill_mnesia(Nodes), + mnesia_test_lib:start_mnesia(Nodes,[account,branch,teller, history]), + + ?match(ok, mnesia_tpcb:verify_tabs()), + + ?match(ok, file:delete(File)), + ?verify_mnesia(Nodes, []). + +do_changes_during_backup(TpcbConfig) -> + loop_branches(TpcbConfig#tab_config.n_branches, + TpcbConfig#tab_config.n_accounts_per_branch). + +loop_branches(N_br,N_acc) when N_br >= 1 -> + loop_accounts(N_br,N_acc), + loop_branches(N_br-1,N_acc); +loop_branches(_,_) -> done. + +loop_accounts(N_br, N_acc) when N_acc >= 1 -> + A = #account{id=N_acc, branch_id=N_br, balance = 4711}, + ok = mnesia:dirty_write(A), + loop_accounts(N_br, N_acc-1); + +loop_accounts(_,_) -> done. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +load_table_with_activated_checkpoint(doc) -> + ["Load a table with a checkpoint attached to it and verify that the", + "newly loaded replica also gets a checkpoint retainer attached to it", + "and that it is consistent with the original retainer."]; + +load_table_with_activated_checkpoint(suite) -> + [ + load_table_with_activated_checkpoint_ram, + load_table_with_activated_checkpoint_disc, + load_table_with_activated_checkpoint_disc_only + ]. + +load_table_with_activated_checkpoint_ram(suite) -> []; +load_table_with_activated_checkpoint_ram(Config) when is_list(Config) -> + load_table_with_activated_checkpoint(ram_copies, Config). + +load_table_with_activated_checkpoint_disc(suite) -> []; +load_table_with_activated_checkpoint_disc(Config) when is_list(Config) -> + load_table_with_activated_checkpoint(disc_copies, Config). + +load_table_with_activated_checkpoint_disc_only(suite) -> []; +load_table_with_activated_checkpoint_disc_only(Config) when is_list(Config) -> + load_table_with_activated_checkpoint(disc_only_copies, Config). + +load_table_with_activated_checkpoint(Type, Config) -> + Nodes = ?acquire_nodes(2, Config), + Node1 = hd(Nodes), + Tab = load_test, + Def = [{attributes, [key, value]}, + {Type, Nodes}], %% ??? important that RAM ??? + + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + ?match(ok, mnesia:dirty_write({Tab, 1, 4711})), + ?match(ok, mnesia:dirty_write({Tab, 2, 42})), + ?match(ok, mnesia:dirty_write({Tab, 3, 256})), + + timer:sleep(timer:seconds(1)), + + {ok, CPName, _NodeList} = + mnesia:activate_checkpoint([{max, mnesia:system_info(tables)}, + {ram_overrides_dump,true}]), + + mnesia_test_lib:stop_mnesia([Node1]), + mnesia_test_lib:start_mnesia([Node1],[Tab]), + %%--- check, whether the checkpiont is attached to both replicas + {success, [A,B]} = ?start_activities(Nodes), + + A ! fun () -> + mnesia:table_info(Tab,checkpoints) + end, + ?match_receive({A,[CPName]}), + + B ! fun () -> + mnesia:table_info(Tab,checkpoints) + end, + ?match_receive({B,[CPName]}), + + %%--- check, whether both retainers are consistent + ?match(ok, mnesia:dirty_write({Tab, 1, 815})), + A ! fun () -> + mnesia:backup_checkpoint(CPName, load_table_a) + end, + ?match_receive({A,ok}), + B ! fun () -> + mnesia:backup_checkpoint(CPName, load_table_b) + end, + ?match_receive({B,ok}), + + Mod = mnesia_backup, %% Assume local files + List_a = view(load_table_a, Mod), + List_b = view(load_table_b, Mod), + + ?match(List_a, List_b), + + ?match(ok,file:delete(load_table_a)), + ?match(ok,file:delete(load_table_b)), + ?verify_mnesia(Nodes, []). + +view(Source, Mod) -> + View = fun(Item, Acc) -> + ?verbose("tab - item : ~p ~n",[Item]), + case Item of + {schema, Tab, Cs} -> %% Remove cookie information + NewCs = lists:keyreplace(cookie, 1, Cs, + {cookie, skip_cookie}), + Item2 = {schema, Tab, NewCs}, + {[Item], [Item2|Acc]}; + _ -> + {[Item], [Item|Acc]} + end + end, + {ok,TabList} = + mnesia:traverse_backup(Source, Mod, dummy, read_only, View, []), + lists:sort(TabList). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +add_table_copy_to_table_with_activated_checkpoint(doc) -> + ["Add a replica to a table with a checkpoint attached to it", + "and verify that the new replica also gets a checkpoint", + "retainer attached to it and that it is consistent with the", + "original retainer."]; + +add_table_copy_to_table_with_activated_checkpoint(suite) -> + [ + add_table_copy_to_table_with_activated_checkpoint_ram, + add_table_copy_to_table_with_activated_checkpoint_disc, + add_table_copy_to_table_with_activated_checkpoint_disc_only + ]. + +add_table_copy_to_table_with_activated_checkpoint_ram(suite) -> []; +add_table_copy_to_table_with_activated_checkpoint_ram(Config) when is_list(Config) -> + add_table_copy_to_table_with_activated_checkpoint(ram_copies, Config). + +add_table_copy_to_table_with_activated_checkpoint_disc(suite) -> []; +add_table_copy_to_table_with_activated_checkpoint_disc(Config) when is_list(Config) -> + add_table_copy_to_table_with_activated_checkpoint(disc_copies, Config). + +add_table_copy_to_table_with_activated_checkpoint_disc_only(suite) -> []; +add_table_copy_to_table_with_activated_checkpoint_disc_only(Config) when is_list(Config) -> + add_table_copy_to_table_with_activated_checkpoint(disc_only_copies, Config). + +add_table_copy_to_table_with_activated_checkpoint(Type,Config) -> + Nodes = ?acquire_nodes(2, Config), + %?verbose("NODES = ~p ~n",[Nodes]), + [Node1,Node2] = Nodes, + + Tab = add_test, + Def = [{attributes, [key, value]}, + {Type, [Node1]}], %% ??? important that RAM ??? + + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + ?match(ok, mnesia:dirty_write({Tab, 1, 4711})), + ?match(ok, mnesia:dirty_write({Tab, 2, 42})), + ?match(ok, mnesia:dirty_write({Tab, 3, 256})), + + {ok, CPName, _NodeList} = + mnesia:activate_checkpoint([{max, mnesia:system_info(tables)}, + {ram_overrides_dump,true}]), + + ?match({atomic,ok},mnesia:add_table_copy(Tab,Node2,ram_copies)), + + %%--- check, whether the checkpiont is attached to both replicas + {success, [A,B]} = ?start_activities(Nodes), + + A ! fun () -> + mnesia:table_info(Tab,checkpoints) + end, + ?match_receive({A,[CPName]}), + + B ! fun () -> + mnesia:table_info(Tab,checkpoints) + end, + ?match_receive({B,[CPName]}), + + %%--- check, whether both retainers are consistent + + ?match(ok, mnesia:dirty_write({Tab, 1, 815})), + ?match(ok, mnesia:dirty_write({Tab, 2, 815})), + + A ! fun () -> + mnesia:backup_checkpoint(CPName, add_table_a) + end, + ?match_receive({A,ok}), + B ! fun () -> + mnesia:backup_checkpoint(CPName, add_table_b) + end, + ?match_receive({B,ok}), + + Mod = mnesia_backup, %% Assume local files + + List_a = view(add_table_a, Mod), + List_b = view(add_table_b, Mod), + + ?match(List_a, List_b), + + ?match(ok,file:delete(add_table_a)), + ?match(ok, file:delete(add_table_b)), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +backup_consistency(suite) -> + [ + interupted_install_fallback, + interupted_uninstall_fallback, + mnesia_down_during_backup_causes_switch, + mnesia_down_during_backup_causes_abort, + schema_transactions_during_backup + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +interupted_install_fallback(doc) -> + ["Verify that a interrupted install_fallback really", + "is performed on all nodes or none"]; + +interupted_install_fallback(suite) -> + [ + inst_fallback_process_dies, + fatal_when_inconsistency + ]. + +inst_fallback_process_dies(suite) -> + []; +inst_fallback_process_dies(Config) when is_list(Config) -> + ?is_debug_compiled, + + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + {success, [A,_B,_C]} = ?start_activities(Nodes), + + TestPid = self(), + DebugId = {mnesia_bup, fallback_receiver_loop, pre_swap}, + DebugFun = + fun(PrevContext, _EvalContext) -> + ?verbose("fallback_receiver_loop - pre_swap pid ~p #~p~n", + [self(),PrevContext]), + TestPid ! {self(),fallback_preswap}, + case receive_messages([fallback_continue]) of + [{TestPid,fallback_continue}] -> + ?deactivate_debug_fun(DebugId), + PrevContext+1 + end + end, + ?activate_debug_fun(DebugId, DebugFun, 1), + + Tab = install_table, + Def = [{attributes, [key, value]}, {disc_copies, Nodes}], + + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + + ?match(ok, mnesia:dirty_write({Tab, 1, 4711})), + ?match(ok, mnesia:dirty_write({Tab, 2, 42})), + ?match(ok, mnesia:dirty_write({Tab, 3, 256})), + + {ok, CPName, _NodeList} = + mnesia:activate_checkpoint([{max, mnesia:system_info(tables)}, + {ram_overrides_dump,true}]), + + ?match(ok, mnesia:backup_checkpoint(CPName, install_backup)), + + A ! fun() -> mnesia:install_fallback(install_backup) end, + [{AnsPid,fallback_preswap}] = receive_messages([fallback_preswap]), + exit(A, kill), + AnsPid ! {self(), fallback_continue}, + ?match_receive({'EXIT', A, killed}), + timer:sleep(2000), %% Wait till fallback is installed everywhere + + mnesia_test_lib:kill_mnesia(Nodes), + ?verbose("~n---->Mnesia is stopped everywhere<-----~n", []), + ?match([], mnesia_test_lib:start_mnesia(Nodes,[Tab])), + + check_data(Nodes, Tab), + ?match(ok, file:delete(install_backup)), + ?verify_mnesia(Nodes, []). + +check_data([N1 | R], Tab) -> + ?match([{Tab, 1, 4711}], rpc:call(N1, mnesia, dirty_read, [{Tab, 1}])), + ?match([{Tab, 2, 42}], rpc:call(N1, mnesia, dirty_read, [{Tab, 2}])), + ?match([{Tab, 3, 256}], rpc:call(N1, mnesia, dirty_read, [{Tab, 3}])), + check_data(R, Tab); +check_data([], _Tab) -> + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +fatal_when_inconsistency(suite) -> + []; +fatal_when_inconsistency(Config) when is_list(Config) -> + ?is_debug_compiled, + + [Node1, Node2, Node3] = Nodes = + ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + {success, [A,_B,_C]} = ?start_activities(Nodes), + + TestPid = self(), + DebugId = {mnesia_bup, fallback_receiver_loop, pre_swap}, + DebugFun = + fun(PrevContext, _EvalContext) -> + ?verbose("fallback_receiver_loop - pre_swap pid ~p #~p~n", + [self(),PrevContext]), + TestPid ! {self(),fallback_preswap}, + case receive_messages([fallback_continue]) of + [{TestPid,fallback_continue}] -> + ?deactivate_debug_fun(DebugId), + PrevContext+1 + end + end, + ?activate_debug_fun(DebugId, DebugFun, 1), + + Tab = install_table, + Def = [{attributes, [key, value]}, {disc_copies, Nodes}], + + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + + ?match(ok, mnesia:dirty_write({Tab, 1, 4711})), + ?match(ok, mnesia:dirty_write({Tab, 2, 42})), + ?match(ok, mnesia:dirty_write({Tab, 3, 256})), + + {ok, CPName, _NodeList} = + mnesia:activate_checkpoint([{max, mnesia:system_info(tables)}, + {ram_overrides_dump,true}]), + + ?match(ok, mnesia:backup_checkpoint(CPName, install_backup)), + ?match(ok, mnesia:dirty_write({Tab, 2, 42424242})), + + A ! fun() -> + mnesia:install_fallback(install_backup) + end, + + [{AnsPid,fallback_preswap}] = receive_messages([fallback_preswap]), + exit(AnsPid, kill), %% Kill install-fallback on local node will + AnsPid ! {self(), fallback_continue}, + ?deactivate_debug_fun(DebugId), + + ?match_receive({A,{error,{"Cannot install fallback", + {'EXIT',AnsPid,killed}}}}), + mnesia_test_lib:kill_mnesia(Nodes), + ?verbose("EXPECTING FATAL from 2 nodes WITH CORE DUMP~n", []), + + ?match([], mnesia_test_lib:start_mnesia([Node1],[])), + is_running(Node1, yes), + ?match([{Node2, mnesia, _}], mnesia_test_lib:start_mnesia([Node2],[])), + is_running(Node2, no), + ?match([{Node3, mnesia, _}], mnesia_test_lib:start_mnesia([Node3],[])), + is_running(Node3, no), + mnesia_test_lib:kill_mnesia(Nodes), + + ?match(ok, mnesia:install_fallback(install_backup)), + mnesia_test_lib:start_mnesia(Nodes,[Tab]), + + check_data(Nodes, Tab), + + ?match(ok,file:delete(install_backup)), + ?verify_mnesia(Nodes, []). + +is_running(Node, Shouldbe) -> + timer:sleep(1000), + Running = rpc:call(Node, mnesia, system_info, [is_running]), + case Running of + Shouldbe -> ok; + _ -> is_running(Node, Shouldbe) + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +interupted_uninstall_fallback(doc) -> + ["Verify that a interrupted uninstall_fallback really", + "is performed on all nodes or none"]; +interupted_uninstall_fallback(suite) -> + [ + after_delete + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +after_delete(doc) -> + ["interrupt the uninstall after deletion of ", + "fallback files - there shall be no fallback"]; +after_delete(suite) -> []; +after_delete(Config) when is_list(Config) -> + do_uninstall(Config, post_delete). + +%%%%%%%%%%%%%%%%%%%%%%%%% + +do_uninstall(Config,DebugPoint) -> + ?is_debug_compiled, + + Nodes = ?acquire_nodes(3, Config), + %%?verbose("NODES = ~p ~n",[Nodes]), + + {success, [P1,P2,P3]} = ?start_activities(Nodes), + + NP1 = node(P1), + NP2 = node(P2), + + {A,B,C} = case node() of + NP1 -> + %%?verbose("first case ~n"), + {P3,P2,P1}; + NP2 -> + %%?verbose("second case ~n"), + {P3, P1, P2}; + _ -> + { P1, P2, P3} + end, + + Node1 = node(A), + Node2 = node(B), + Node3 = node(C), + + ?verbose(" A pid:~p node:~p ~n",[A,Node1]), + ?verbose(" B pid:~p node:~p ~n",[B,Node2]), + ?verbose(" C pid:~p node:~p ~n",[C,Node3]), + + + TestPid = self(), + %%?verbose("TestPid : ~p~n",[TestPid]), + DebugId = {mnesia_bup, uninstall_fallback2, DebugPoint}, + DebugFun = fun(PrevContext, _EvalContext) -> + ?verbose("uninstall_fallback pid ~p #~p~n" + ,[self(),PrevContext]), + TestPid ! {self(),uninstall_predelete}, + case receive_messages([uninstall_continue]) of + [{TestPid,uninstall_continue}] -> + ?deactivate_debug_fun(DebugId), + %%?verbose("uninstall_fallback continues~n"), + PrevContext+1 + end + end, + ?remote_activate_debug_fun(Node1,DebugId, DebugFun, 1), + + Tab = install_table, + Def = [{attributes, [key, value]}, + {ram_copies, Nodes}], %% necessary to test different types ??? + + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + + ?match(ok, mnesia:dirty_write({Tab, 1, 4711})), + ?match(ok, mnesia:dirty_write({Tab, 2, 42})), + ?match(ok, mnesia:dirty_write({Tab, 3, 256})), + + {ok, CPName, _NodeList} = + mnesia:activate_checkpoint([{max, mnesia:system_info(tables)}, + {ram_overrides_dump,true}]), + + ?match(ok, mnesia:backup_checkpoint(CPName,install_backup)), + timer:sleep(timer:seconds(1)), + + A ! fun () -> + mnesia:install_fallback(install_backup) + end, + ?match_receive({A,ok}), + + A ! fun () -> + mnesia:uninstall_fallback() + end, + %% + %% catch the debug entry in mnesia and kill one Mnesia node + %% + + + [{AnsPid,uninstall_predelete}] = receive_messages([uninstall_predelete]), + + ?verbose("AnsPid : ~p~n",[AnsPid]), + + mnesia_test_lib:kill_mnesia([Node2]), + timer:sleep(timer:seconds(1)), + + AnsPid ! {self(),uninstall_continue}, + + ?match_receive({A,ok}), + + mnesia_test_lib:kill_mnesia(Nodes) , + mnesia_test_lib:start_mnesia(Nodes,[Tab]), + + A ! fun () -> + R1 = mnesia:dirty_read({Tab,1}), + R2 = mnesia:dirty_read({Tab,2}), + R3 = mnesia:dirty_read({Tab,3}), + {R1,R2,R3} + end, + ?match_receive({ A, {[],[],[]} }), + + B ! fun () -> + R1 = mnesia:dirty_read({Tab,1}), + R2 = mnesia:dirty_read({Tab,2}), + R3 = mnesia:dirty_read({Tab,3}), + {R1,R2,R3} + end, + ?match_receive({ B, {[],[],[]} }), + + C ! fun () -> + R1 = mnesia:dirty_read({Tab,1}), + R2 = mnesia:dirty_read({Tab,2}), + R3 = mnesia:dirty_read({Tab,3}), + {R1,R2,R3} + end, + ?match_receive({ C, {[],[],[]} }), + + ?match(ok,file:delete(install_backup)), + ?verify_mnesia(Nodes, []). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +mnesia_down_during_backup_causes_switch(doc) -> + ["Verify that an ongoing backup is not disturbed", + "even if the node hosting the replica that currently", + "is being backup'ed is stopped. The backup utility", + "is expected to switch over to another replica and", + "fulfill the backup."]; +mnesia_down_during_backup_causes_switch(suite) -> + [ + cause_switch_before, + cause_switch_after + ]. + +%%%%%%%%%%%%%%% + +cause_switch_before(doc) -> + ["interrupt the backup before iterating the retainer"]; +cause_switch_before(suite) -> []; +cause_switch_before(Config) when is_list(Config) -> + do_something_during_backup(cause_switch,pre,Config). + +%%%%%%%%%%%%%%% + +cause_switch_after(doc) -> + ["interrupt the backup after iterating the retainer"]; +cause_switch_after(suite) -> []; +cause_switch_after(Config) when is_list(Config) -> + do_something_during_backup(cause_switch,post,Config). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +mnesia_down_during_backup_causes_abort(doc) -> + ["Verify that an ongoing backup is aborted nicely", + "without leaving any backup file if the last replica", + "of a table becomes unavailable due to a node down", + "or some crash."]; +mnesia_down_during_backup_causes_abort(suite) -> + [ + cause_abort_before, + cause_abort_after + ]. + +%%%%%%%%%%%%%%%%%% + +cause_abort_before(doc) -> + ["interrupt the backup before iterating the retainer"]; + +cause_abort_before(suite) -> []; +cause_abort_before(Config) when is_list(Config) -> + do_something_during_backup(cause_abort,pre,Config). + +%%%%%%%%%%%%%%%%%% + +cause_abort_after(doc) -> + ["interrupt the backup after iterating the retainer"]; + +cause_abort_after(suite) -> []; +cause_abort_after(Config) when is_list(Config) -> + do_something_during_backup(cause_abort,post,Config). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +schema_transactions_during_backup(doc) -> + ["Verify that an schema transactions does not", + "affect an ongoing backup."]; +schema_transactions_during_backup(suite) -> + [ + change_schema_before, + change_schema_after + ]. + +%%%%%%%%%%%%% + +change_schema_before(doc) -> + ["interrupt the backup before iterating the retainer"]; +change_schema_before(suite) -> []; +change_schema_before(Config) when is_list(Config) -> + do_something_during_backup(change_schema,pre,Config). + +%%%%%%%%%%%%%%%% + +change_schema_after(doc) -> + ["interrupt the backup after iterating the retainer"]; +change_schema_after(suite) -> []; +change_schema_after(Config) when is_list(Config) -> + do_something_during_backup(change_schema,post,Config). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +do_something_during_backup(Action,DebugPoint,Config) -> + ?is_debug_compiled, + + Nodes = ?acquire_nodes(3, Config), + + {success, [A,B,C]} = ?start_activities(Nodes), + + Node1 = node(A), + Node2 = node(B), + Node3 = node(C), + + TestPid = self(), + %%?verbose("TestPid : ~p~n",[TestPid]), + + Tab = interrupt_table, + Bak = interrupt_backup, + Def = [{attributes, [key, value]}, + {ram_copies, [Node2,Node3]}], + %% necessary to test different types ??? + + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + + + + DebugId = {mnesia_log, tab_copier, DebugPoint}, + DebugFun = fun(PrevContext, EvalContext) -> + ?verbose("interrupt backup pid ~p #~p ~n context ~p ~n" + ,[self(),PrevContext,EvalContext]), + TestPid ! {self(),interrupt_backup_pre}, + global:set_lock({{lock_for_backup, Tab}, self()}, + Nodes, + infinity), + + %%?verbose("interrupt backup - continues ~n"), + ?deactivate_debug_fun(DebugId), + PrevContext+1 + end, + ?remote_activate_debug_fun(Node1,DebugId, DebugFun, 1), + + ?match(ok, mnesia:dirty_write({Tab, 1, 4711})), + ?match(ok, mnesia:dirty_write({Tab, 2, 42})), + ?match(ok, mnesia:dirty_write({Tab, 3, 256})), + + {ok, CPName, _NodeList} = + mnesia:activate_checkpoint([{max, mnesia:system_info(tables)}, + {ram_overrides_dump,true}]), + + A ! fun () -> + %%?verbose("node: ~p pid: ~p ~n",[node(),self()]), + mnesia:table_info(Tab,where_to_read) + end, + + ReadNode_a = receive { A, ReadNode_a_tmp } -> ReadNode_a_tmp end, + ?verbose("ReadNode ~p ~n",[ReadNode_a]), + + global:set_lock({{lock_for_backup, Tab}, self()}, Nodes, infinity), + + A ! fun () -> %% A shall perform the backup, so the test proc is + %% able to do further actions in between + mnesia:backup_checkpoint(CPName, Bak) + end, + + %% catch the debug function of mnesia, stop the backup process + %% kill the node ReadNode_a and continue the backup process + %% As there is a second replica of the table, the backup shall continue + + case receive_messages([interrupt_backup_pre]) of + [{_AnsPid,interrupt_backup_pre}] -> ok + end, + + case Action of + cause_switch -> + mnesia_test_lib:kill_mnesia([ReadNode_a]), + timer:sleep(timer:seconds(1)); + cause_abort -> + mnesia_test_lib:kill_mnesia([Node2,Node3]), + timer:sleep(timer:seconds(1)); + change_schema -> + Tab2 = second_interrupt_table, + Def2 = [{attributes, [key, value]}, + {ram_copies, Nodes}], + + ?match({atomic, ok}, mnesia:create_table(Tab2, Def2)) + end, + + %% AnsPid ! {self(),interrupt_backup_continue}, + global:del_lock({{lock_for_backup, Tab}, self()}, Nodes), + + case Action of + cause_abort -> + + %% answer of A when finishing the backup + ?match_receive({A,{error, _}}), + + ?match({error,{"Cannot install fallback",_}}, + mnesia:install_fallback(Bak)); + _ -> %% cause_switch, change_schema + + ?match_receive({A,ok}), %% answer of A when finishing the backup + + %% send a fun to that node where mnesia is still running + WritePid = case ReadNode_a of + Node2 -> C; %% node(C) == Node3 + Node3 -> B + end, + WritePid ! fun () -> + ?match(ok, mnesia:dirty_write({Tab, 1, 815})), + ?match(ok, mnesia:dirty_write({Tab, 2, 816})), + ok + end, + ?match_receive({ WritePid, ok }), + ?match(ok, mnesia:install_fallback(Bak)) + end, + + %% Stop and then start mnesia and check table consistency + %%?verbose("Restarting Mnesia~n", []), + mnesia_test_lib:kill_mnesia(Nodes), + mnesia_test_lib:start_mnesia(Nodes,[Tab]), + + case Action of + cause_switch -> + %% the backup should exist + cross_check_tables([A,B,C],Tab,{[{Tab,1,4711}], + [{Tab,2,42}], + [{Tab,3,256}] }), + ?match(ok,file:delete(Bak)); + cause_abort -> + %% the backup should NOT exist + cross_check_tables([A,B,C],Tab,{[],[],[]}), + %% file does not exist + ?match({error, _},file:delete(Bak)); + change_schema -> + %% the backup should exist + cross_check_tables([A,B,C],Tab,{[{Tab,1,4711}], + [{Tab,2,42}], + [{Tab,3,256}] }), + ?match(ok,file:delete(Bak)) + end, + ?verify_mnesia(Nodes, []). + +%% check the contents of the table +cross_check_tables([],_tab,_elements) -> ok; +cross_check_tables([Pid|Rest],Tab,{Val1,Val2,Val3}) -> + Pid ! fun () -> + R1 = mnesia:dirty_read({Tab,1}), + R2 = mnesia:dirty_read({Tab,2}), + R3 = mnesia:dirty_read({Tab,3}), + {R1,R2,R3} + end, + ?match_receive({ Pid, {Val1, Val2, Val3 } }), + cross_check_tables(Rest,Tab,{Val1,Val2,Val3} ). diff --git a/lib/mnesia/test/mnesia_cost.erl b/lib/mnesia/test/mnesia_cost.erl new file mode 100644 index 0000000000..54cb2b3064 --- /dev/null +++ b/lib/mnesia/test/mnesia_cost.erl @@ -0,0 +1,222 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(mnesia_cost). +-compile(export_all). + +%% This code exercises the mnesia system and produces a bunch +%% of measurements on what various things cost + +-define(TIMES, 1000). %% set to at least 1000 when running for real !! + +%% This is the record we perform all ops on in this test + +-record(item, {a = 1234, + b = foobar, + c = "1.2.3.4", + d = {'Lennart', 'Hyland'}, + e = true + }). + +go() -> + go([node() | nodes()]). + +go(Nodes) when hd(Nodes) == node() -> + {ok, Out} = file:open("MNESIA_COST", write), + put(out, Out), + + rpc:multicall(Nodes, mnesia, lkill, []), + ok = mnesia:delete_schema(Nodes), + ok = mnesia:create_schema(Nodes), + rpc:multicall(Nodes, mnesia, start, []), + TabDef = [{attributes, record_info(fields, item)}], + {atomic, ok} = mnesia:create_table(item, TabDef), + + round("single ram copy", "no index"), + {atomic, ok} = mnesia:add_table_index(item, #item.e), + round("single ram copy", "One index"), + + {atomic, ok} = mnesia:add_table_index(item, #item.c), + round("single ram copy", "Two indexes"), + + {atomic, ok} = mnesia:del_table_index(item, #item.e), + {atomic, ok} = mnesia:del_table_index(item, #item.c), + + {atomic, ok} = mnesia:change_table_copy_type(item, node(), disc_copies), + round("single disc copy", "no index"), + + {atomic, ok} = mnesia:change_table_copy_type(item, node(), ram_copies), + + case length(Nodes) of + Len when Len < 2 -> + format("<WARNING> replication skipped. Too few nodes.", []); + _Len -> + N2 = lists:nth(2, Nodes), + {atomic, ok} = mnesia:add_table_copy(item, N2, ram_copies), + round("2 replicated ram copy", "no index") + end, + file:close(Out), + erase(out), + ok. + +round(Replication, Index) -> + run(Replication, Index, [write], + fun() -> mnesia:write(#item{}) end), + + + run(Replication, Index, [read], + fun() -> mnesia:read({item, 1234}) end), + + run(Replication, Index, [read, write], + fun() -> mnesia:read({item, 1234}), + mnesia:write(#item{}) end), + + run(Replication, Index, [wread, write], + fun() -> mnesia:wread({item, 1234}), + mnesia:write(#item{}) end), + + + run(Replication, Index, [match, write, write, write], + fun() -> mnesia:match_object({item, 1, '_', '_', '_', true}), + mnesia:write(#item{a =1}), + mnesia:write(#item{a =2}), + mnesia:write(#item{a =3}) end). + + +format(F, As) -> + io:format(get(out), F, As). + +run(What, OtherInfo, Ops, F) -> + run(t, What, OtherInfo, Ops, F). + +run(How, What, OtherInfo, Ops, F) -> + T1 = erlang:now(), + statistics(runtime), + do_times(How, ?TIMES, F), + {_, RunTime} = statistics(runtime), + T2 = erlang:now(), + RealTime = subtr(T1, T2), + report(How, What, OtherInfo, Ops, RunTime, RealTime). + +report(t, What, OtherInfo, Ops, RunTime, RealTime) -> + format("~s, ~s, transaction call ", [What, OtherInfo]), + format("Ops is ", []), + lists:foreach(fun(Op) -> format("~w-", [Op]) end, Ops), + + format("~n ~w/~w Millisecs/Trans ~w/~w MilliSecs/Operation ~n~n", + [RunTime/?TIMES, + RealTime/?TIMES, + RunTime/(?TIMES*length(Ops)), + RealTime/(?TIMES*length(Ops))]); + +report(dirty, What, OtherInfo, Ops, RunTime, RealTime) -> + format("~s, ~s, dirty calls ", [What, OtherInfo]), + format("Ops is ", []), + lists:foreach(fun(Op) -> format("~w-", [Op]) end, Ops), + + format("~n ~w/~w Millisecs/Bunch ~w/~w MilliSecs/Operation ~n~n", + [RunTime/?TIMES, + RealTime/?TIMES, + RunTime/(?TIMES*length(Ops)), + RealTime/(?TIMES*length(Ops))]). + + +subtr(Before, After) -> + E =(element(1,After)*1000000000000 + +element(2,After)*1000000+element(3,After)) - + (element(1,Before)*1000000000000 + +element(2,Before)*1000000+element(3,Before)), + E div 1000. + +do_times(t, I, F) -> + do_trans_times(I, F); +do_times(dirty, I, F) -> + do_dirty(I, F). + +do_trans_times(I, F) when I /= 0 -> + {atomic, _} = mnesia:transaction(F), + do_trans_times(I-1, F); +do_trans_times(_,_) -> ok. + +do_dirty(I, F) when I /= 0 -> + F(), + do_dirty(I-1, F); +do_dirty(_,_) -> ok. + + + +table_load([N1,N2| _ ] = Ns) -> + Nodes = [N1,N2], + rpc:multicall(Ns, mnesia, lkill, []), + ok = mnesia:delete_schema(Ns), + ok = mnesia:create_schema(Nodes), + rpc:multicall(Nodes, mnesia, start, []), + TabDef = [{disc_copies,[N1]},{ram_copies,[N2]}, + {attributes,record_info(fields,item)},{record_name,item}], + Tabs = [list_to_atom("tab" ++ integer_to_list(I)) || I <- lists:seq(1,400)], + + [mnesia:create_table(Tab,TabDef) || Tab <- Tabs], + +%% InitTab = fun(Tab) -> +%% mnesia:write_lock_table(Tab), +%% InitRec = fun(Key) -> mnesia:write(Tab,#item{a=Key},write) end, +%% lists:foreach(InitRec, lists:seq(1,100)) +%% end, +%% +%% {Time,{atomic,ok}} = timer:tc(mnesia,transaction, [fun() ->lists:foreach(InitTab, Tabs) end]), + mnesia:dump_log(), +%% io:format("Init took ~p msec ~n", [Time/1000]), + rpc:call(N2, mnesia, stop, []), timer:sleep(1000), + mnesia:stop(), timer:sleep(500), + %% Warmup + ok = mnesia:start([{no_table_loaders, 1}]), + timer:tc(mnesia, wait_for_tables, [Tabs, infinity]), + mnesia:dump_log(), + rpc:call(N2, mnesia, dump_log, []), + io:format("Initialized ~n",[]), + + mnesia:stop(), timer:sleep(1000), + ok = mnesia:start([{no_table_loaders, 1}]), + {T1, ok} = timer:tc(mnesia, wait_for_tables, [Tabs, infinity]), + io:format("Loading from disc with 1 loader ~p msec~n",[T1/1000]), + mnesia:stop(), timer:sleep(1000), + ok = mnesia:start([{no_table_loaders, 4}]), + {T2, ok} = timer:tc(mnesia, wait_for_tables, [Tabs, infinity]), + io:format("Loading from disc with 4 loader ~p msec~n",[T2/1000]), + + %% Warmup + rpc:call(N2, ?MODULE, remote_load, [Tabs,4]), + io:format("Initialized ~n",[]), + + + T3 = rpc:call(N2, ?MODULE, remote_load, [Tabs,1]), + io:format("Loading from net with 1 loader ~p msec~n",[T3/1000]), + + T4 = rpc:call(N2, ?MODULE, remote_load, [Tabs,4]), + io:format("Loading from net with 4 loader ~p msec~n",[T4/1000]), + + ok. + +remote_load(Tabs,Loaders) -> + ok = mnesia:start([{no_table_loaders, Loaders}]), +%% io:format("~p ~n", [mnesia_controller:get_info(500)]), + {Time, ok} = timer:tc(mnesia, wait_for_tables, [Tabs, infinity]), + timer:sleep(1000), mnesia:stop(), timer:sleep(1000), + Time. diff --git a/lib/mnesia/test/mnesia_dbn_meters.erl b/lib/mnesia/test/mnesia_dbn_meters.erl new file mode 100644 index 0000000000..feaf90ee75 --- /dev/null +++ b/lib/mnesia/test/mnesia_dbn_meters.erl @@ -0,0 +1,242 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% + +-module(mnesia_dbn_meters). +-export([ + start/0, + local_start/0, + distr_start/1, + start/3 + ]). + +-record(simple,{key,val=0}). +-define(key,1). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Configuration and start + +start() -> + local_start(), + distr_start(nodes()). + +local_start() -> + start(one_ram_only,[node()],some_meters()), + start(one_disc_only,[node()],some_meters()). + +distr_start([]) -> + local_only; +distr_start(OtherNodes) when is_list(OtherNodes) -> + start(ram_and_ram,[node()|OtherNodes],some_meters()), + start(disc_and_disc,[node()|OtherNodes],some_meters()). + +start(Config,Nodes,Meters) -> + Attrs = record_info(fields,simple), + Schema = [{name,simple},{type,set},{attributes,Attrs}] ++ config(Config,Nodes), + L = '====================', + io:format("~n~p dbn_meters: ~p ~p~nSchema = ~p.~n~n",[L,Config,L,Schema]), + ok = mnesia:delete_schema(Nodes), + ok = mnesia:create_schema(Nodes), + rpc:multicall(Nodes, mnesia, start, []), + {atomic,_} = mnesia:create_table(Schema), + lists:foreach(fun report_meter/1,Meters), + {atomic, ok} = mnesia:delete_table(simple), + rpc:multicall(Nodes, mnesia, stop, []), + ok. + +config(one_ram_only,[Single|_]) -> + [{ram_copies,[Single]}]; +config(ram_and_ram,[Master|[Slave|_]]) -> + [{ram_copies,[Master,Slave]}]; +config(one_disc_only,[Single|_]) -> + [{disc_copies,[Single]}]; +config(disc_and_disc,[Master|[Slave|_]]) -> + [{disc_copies,[Master,Slave]}]; +config(Config,Nodes) -> + io:format("<ERROR> Config ~p not supported or too few nodes ~p given~n",[Config,Nodes]). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% The various DBN meters +some_meters() -> + [create, + open_safe_read, + open_dirty_read, + get_int, + open_update, + put_int, + put_int_and_copy, + dirty_put_int_and_copy, + start_trans, + commit_one_update, + delete, + dirty_delete + ]. + +report_meter(Meter) -> + Times = 100, + Micros = repeat_meter(Meter,{atomic,{0,ignore}},Times) div Times, + io:format("\t~-30w ~-10w micro seconds (mean of ~p repetitions)~n",[Meter,Micros,Times]). + +repeat_meter(_Meter,{atomic,{Micros,_Result}},0) -> + Micros; +repeat_meter(Meter,{atomic,{Micros,_Result}},Times) when Times > 0 -> + repeat_meter(Meter,catch meter(Meter),Times-1) + Micros; +repeat_meter(Meter,{aborted,Reason},Times) when Times > 0 -> + io:format("<ERROR>\t~-20w\t,aborted, because ~p~n",[Meter,Reason]), + 0; +repeat_meter(Meter,{'EXIT',Reason},Times) when Times > 0 -> + io:format("<ERROR>\t~-20w\tcrashed, because ~p~n",[Meter,Reason]), + 0. + +meter(create) -> + Key = 1, + mnesia:transaction(fun() -> mnesia:delete({simple,Key}) end), + Fun = fun() -> + BeforeT = erlang:now(), + R = mnesia:write(#simple{key=Key}), + AfterT = erlang:now(), + elapsed_time(BeforeT,AfterT,R) + end, + mnesia:transaction(Fun); + +meter(open_safe_read) -> + Key = 2, + mnesia:transaction(fun() -> mnesia:write(#simple{key=Key}) end), + Fun = fun() -> + BeforeT = erlang:now(), + R = mnesia:read({simple,Key}), + AfterT = erlang:now(), + elapsed_time(BeforeT,AfterT,R) + end, + mnesia:transaction(Fun); + +meter(open_dirty_read) -> + Key = 21, + mnesia:transaction(fun() -> mnesia:write(#simple{key=Key}) end), + Fun = fun() -> + BeforeT = erlang:now(), + R = mnesia:dirty_read({simple,Key}), + AfterT = erlang:now(), + elapsed_time(BeforeT,AfterT,R) + end, + mnesia:transaction(Fun); + +meter(get_int) -> + Key = 3, + mnesia:transaction(fun() -> mnesia:write(#simple{key=Key}) end), + Fun = fun() -> + [Simple] = mnesia:read({simple,Key}), + BeforeT = erlang:now(), + Int = Simple#simple.val, + AfterT = erlang:now(), + elapsed_time(BeforeT,AfterT,Int) + end, + mnesia:transaction(Fun); + +meter(open_update) -> + Key = 3, + mnesia:transaction(fun() -> mnesia:write(#simple{key=Key}) end), + Fun = fun() -> + BeforeT = erlang:now(), + R = mnesia:wread({simple,Key}), + AfterT = erlang:now(), + elapsed_time(BeforeT,AfterT,R) + end, + mnesia:transaction(Fun); + +meter(put_int) -> + Key = 4, + mnesia:transaction(fun() -> mnesia:write(#simple{key=Key}) end), + Fun = fun() -> + [Simple] = mnesia:wread({simple,Key}), + BeforeT = erlang:now(), + R = Simple#simple{val=7}, + AfterT = erlang:now(), + elapsed_time(BeforeT,AfterT,R) + end, + mnesia:transaction(Fun); + +meter(put_int_and_copy) -> + Key = 5, + mnesia:transaction(fun() -> mnesia:write(#simple{key=Key}) end), + Fun = fun() -> + [Simple] = mnesia:wread({simple,Key}), + BeforeT = erlang:now(), + Simple2 = Simple#simple{val=17}, + R = mnesia:write(Simple2), + AfterT = erlang:now(), + elapsed_time(BeforeT,AfterT,R) + end, + mnesia:transaction(Fun); + +meter(dirty_put_int_and_copy) -> + Key = 55, + mnesia:dirty_write(#simple{key=Key}), + [Simple] = mnesia:dirty_read({simple,Key}), + BeforeT = erlang:now(), + Simple2 = Simple#simple{val=17}, + R = mnesia:dirty_write(Simple2), + AfterT = erlang:now(), + {atomic,elapsed_time(BeforeT,AfterT,R)}; + +meter(start_trans) -> + BeforeT = erlang:now(), + {atomic,AfterT} = mnesia:transaction(fun() -> erlang:now() end), + {atomic,elapsed_time(BeforeT,AfterT,ok)}; + +meter(commit_one_update) -> + Key = 6, + mnesia:transaction(fun() -> mnesia:write(#simple{key=Key}) end), + Fun = fun() -> + [Simple] = mnesia:wread({simple,Key}), + Simple2 = Simple#simple{val=27}, + _R = mnesia:write(Simple2), + erlang:now() + end, + {atomic,BeforeT} = mnesia:transaction(Fun), + AfterT = erlang:now(), + {atomic,elapsed_time(BeforeT,AfterT,ok)}; + +meter(delete) -> + Key = 7, + mnesia:transaction(fun() -> mnesia:write(#simple{key=Key}) end), + Fun = fun() -> + BeforeT = erlang:now(), + R = mnesia:delete({simple,Key}), + AfterT = erlang:now(), + elapsed_time(BeforeT,AfterT,R) + end, + mnesia:transaction(Fun); + +meter(dirty_delete) -> + Key = 75, + mnesia:dirty_write(#simple{key=Key}), + BeforeT = erlang:now(), + R = mnesia:dirty_delete({simple,Key}), + AfterT = erlang:now(), + {atomic, elapsed_time(BeforeT,AfterT,R)}. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Calculate the elapsed time +elapsed_time(BeforeT,AfterT,Result) -> + {(element(1,AfterT)*1000000000000 + +element(2,AfterT)*1000000+element(3,AfterT)) - + (element(1,BeforeT)*1000000000000 + +element(2,BeforeT)*1000000+element(3,BeforeT)),Result}. diff --git a/lib/mnesia/test/mnesia_dirty_access_test.erl b/lib/mnesia/test/mnesia_dirty_access_test.erl new file mode 100644 index 0000000000..5f9f2a9733 --- /dev/null +++ b/lib/mnesia/test/mnesia_dirty_access_test.erl @@ -0,0 +1,927 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(mnesia_dirty_access_test). +-author('[email protected]'). +-compile([export_all]). +-include("mnesia_test_lib.hrl"). + +init_per_testcase(Func, Conf) -> + mnesia_test_lib:init_per_testcase(Func, Conf). + +fin_per_testcase(Func, Conf) -> + mnesia_test_lib:fin_per_testcase(Func, Conf). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +all(doc) -> + ["Evil dirty access, regardless of transaction scope.", + "Invoke all functions in the API and try to cover all legal uses", + "cases as well the illegal dito. This is a complement to the", + "other more explicit test cases."]; +all(suite) -> + [ + dirty_write, + dirty_read, + dirty_update_counter, + dirty_delete, + dirty_delete_object, + dirty_match_object, + dirty_index, + dirty_iter, + admin_tests + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Write records dirty + +dirty_write(suite) -> + [ + dirty_write_ram, + dirty_write_disc, + dirty_write_disc_only + ]. + +dirty_write_ram(suite) -> []; +dirty_write_ram(Config) when is_list(Config) -> + dirty_write(Config, ram_copies). + +dirty_write_disc(suite) -> []; +dirty_write_disc(Config) when is_list(Config) -> + dirty_write(Config, disc_copies). + +dirty_write_disc_only(suite) -> []; +dirty_write_disc_only(Config) when is_list(Config) -> + dirty_write(Config, disc_only_copies). + +dirty_write(Config, Storage) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = dirty_write, + Def = [{attributes, [k, v]}, {Storage, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + + ?match({'EXIT', _}, mnesia:dirty_write([])), + ?match({'EXIT', _}, mnesia:dirty_write({Tab, 2})), + ?match({'EXIT', _}, mnesia:dirty_write({foo, 2})), + ?match(ok, mnesia:dirty_write({Tab, 1, 2})), + + ?match({atomic, ok}, mnesia:transaction(fun() -> + mnesia:dirty_write({Tab, 1, 2}) end)), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Read records dirty + +dirty_read(suite) -> + [ + dirty_read_ram, + dirty_read_disc, + dirty_read_disc_only + ]. + +dirty_read_ram(suite) -> []; +dirty_read_ram(Config) when is_list(Config) -> + dirty_read(Config, ram_copies). + +dirty_read_disc(suite) -> []; +dirty_read_disc(Config) when is_list(Config) -> + dirty_read(Config, disc_copies). + +dirty_read_disc_only(suite) -> []; +dirty_read_disc_only(Config) when is_list(Config) -> + dirty_read(Config, disc_only_copies). + +dirty_read(Config, Storage) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = dirty_read, + Def = [{type, bag}, {attributes, [k, v]}, {Storage, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + + ?match({'EXIT', _}, mnesia:dirty_read([])), + ?match({'EXIT', _}, mnesia:dirty_read({Tab})), + ?match({'EXIT', _}, mnesia:dirty_read({Tab, 1, 2})), + ?match([], mnesia:dirty_read({Tab, 1})), + ?match(ok, mnesia:dirty_write({Tab, 1, 2})), + ?match([{Tab, 1, 2}], mnesia:dirty_read({Tab, 1})), + ?match(ok, mnesia:dirty_write({Tab, 1, 3})), + ?match([{Tab, 1, 2}, {Tab, 1, 3}], mnesia:dirty_read({Tab, 1})), + + ?match({atomic, [{Tab, 1, 2}, {Tab, 1, 3}]}, + mnesia:transaction(fun() -> mnesia:dirty_read({Tab, 1}) end)), + + ?match(false, mnesia:async_dirty(fun() -> mnesia:is_transaction() end)), + ?match(false, mnesia:sync_dirty(fun() -> mnesia:is_transaction() end)), + ?match(false, mnesia:ets(fun() -> mnesia:is_transaction() end)), + ?match(false, mnesia:activity(async_dirty, fun() -> mnesia:is_transaction() end)), + ?match(false, mnesia:activity(sync_dirty, fun() -> mnesia:is_transaction() end)), + ?match(false, mnesia:activity(ets, fun() -> mnesia:is_transaction() end)), + + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Update counter record dirty + +dirty_update_counter(suite) -> + [ + dirty_update_counter_ram, + dirty_update_counter_disc, + dirty_update_counter_disc_only + ]. + +dirty_update_counter_ram(suite) -> []; +dirty_update_counter_ram(Config) when is_list(Config) -> + dirty_update_counter(Config, ram_copies). + +dirty_update_counter_disc(suite) -> []; +dirty_update_counter_disc(Config) when is_list(Config) -> + dirty_update_counter(Config, disc_copies). + +dirty_update_counter_disc_only(suite) -> []; +dirty_update_counter_disc_only(Config) when is_list(Config) -> + dirty_update_counter(Config, disc_only_copies). + +dirty_update_counter(Config, Storage) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = dirty_update_counter, + Def = [{attributes, [k, v]}, {Storage, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + ?match(ok, mnesia:dirty_write({Tab, 1, 2})), + + ?match({'EXIT', _}, mnesia:dirty_update_counter({Tab, 1}, [])), + ?match({'EXIT', _}, mnesia:dirty_update_counter({Tab}, 3)), + ?match({'EXIT', _}, mnesia:dirty_update_counter({foo, 1}, 3)), + ?match(5, mnesia:dirty_update_counter({Tab, 1}, 3)), + ?match([{Tab, 1, 5}], mnesia:dirty_read({Tab, 1})), + + ?match({atomic, 8}, mnesia:transaction(fun() -> + mnesia:dirty_update_counter({Tab, 1}, 3) end)), + + ?match(1, mnesia:dirty_update_counter({Tab, foo}, 1)), + ?match([{Tab, foo,1}], mnesia:dirty_read({Tab,foo})), + + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Delete record dirty + +dirty_delete(suite) -> + [ + dirty_delete_ram, + dirty_delete_disc, + dirty_delete_disc_only + ]. + +dirty_delete_ram(suite) -> []; +dirty_delete_ram(Config) when is_list(Config) -> + dirty_delete(Config, ram_copies). + +dirty_delete_disc(suite) -> []; +dirty_delete_disc(Config) when is_list(Config) -> + dirty_delete(Config, disc_copies). + +dirty_delete_disc_only(suite) -> []; +dirty_delete_disc_only(Config) when is_list(Config) -> + dirty_delete(Config, disc_only_copies). + +dirty_delete(Config, Storage) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = dirty_delete, + Def = [{type, bag}, {attributes, [k, v]}, {Storage, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + + ?match({'EXIT', _}, mnesia:dirty_delete([])), + ?match({'EXIT', _}, mnesia:dirty_delete({Tab})), + ?match({'EXIT', _}, mnesia:dirty_delete({Tab, 1, 2})), + ?match(ok, mnesia:dirty_delete({Tab, 1})), + ?match(ok, mnesia:dirty_write({Tab, 1, 2})), + ?match(ok, mnesia:dirty_delete({Tab, 1})), + ?match(ok, mnesia:dirty_write({Tab, 1, 2})), + ?match(ok, mnesia:dirty_write({Tab, 1, 2})), + ?match(ok, mnesia:dirty_delete({Tab, 1})), + + ?match(ok, mnesia:dirty_write({Tab, 1, 2})), + ?match({atomic, ok}, mnesia:transaction(fun() -> + mnesia:dirty_delete({Tab, 1}) end)), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Delete matching record dirty + +dirty_delete_object(suite) -> + [ + dirty_delete_object_ram, + dirty_delete_object_disc, + dirty_delete_object_disc_only + ]. + +dirty_delete_object_ram(suite) -> []; +dirty_delete_object_ram(Config) when is_list(Config) -> + dirty_delete_object(Config, ram_copies). + +dirty_delete_object_disc(suite) -> []; +dirty_delete_object_disc(Config) when is_list(Config) -> + dirty_delete_object(Config, disc_copies). + +dirty_delete_object_disc_only(suite) -> []; +dirty_delete_object_disc_only(Config) when is_list(Config) -> + dirty_delete_object(Config, disc_only_copies). + +dirty_delete_object(Config, Storage) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = dirty_delete_object, + Def = [{type, bag}, {attributes, [k, v]}, {Storage, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + + OneRec = {Tab, 1, 2}, + ?match({'EXIT', _}, mnesia:dirty_delete_object([])), + ?match({'EXIT', _}, mnesia:dirty_delete_object({Tab})), + ?match({'EXIT', _}, mnesia:dirty_delete_object({Tab, 1})), + ?match(ok, mnesia:dirty_delete_object(OneRec)), + ?match(ok, mnesia:dirty_write(OneRec)), + ?match(ok, mnesia:dirty_delete_object(OneRec)), + ?match(ok, mnesia:dirty_write(OneRec)), + ?match(ok, mnesia:dirty_write(OneRec)), + ?match(ok, mnesia:dirty_delete_object(OneRec)), + + ?match(ok, mnesia:dirty_write(OneRec)), + ?match({atomic, ok}, mnesia:transaction(fun() -> + mnesia:dirty_delete_object(OneRec) end)), + + ?match({'EXIT', {aborted, {bad_type, Tab, _}}}, mnesia:dirty_delete_object(Tab, {Tab, {['_']}, 21})), + ?match({'EXIT', {aborted, {bad_type, Tab, _}}}, mnesia:dirty_delete_object(Tab, {Tab, {['$5']}, 21})), + + ?verify_mnesia(Nodes, []). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Read matching records dirty + +dirty_match_object(suite) -> + [ + dirty_match_object_ram, + dirty_match_object_disc, + dirty_match_object_disc_only + ]. + +dirty_match_object_ram(suite) -> []; +dirty_match_object_ram(Config) when is_list(Config) -> + dirty_match_object(Config, ram_copies). + +dirty_match_object_disc(suite) -> []; +dirty_match_object_disc(Config) when is_list(Config) -> + dirty_match_object(Config, disc_copies). + +dirty_match_object_disc_only(suite) -> []; +dirty_match_object_disc_only(Config) when is_list(Config) -> + dirty_match_object(Config, disc_only_copies). + +dirty_match_object(Config, Storage) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = dirty_match, + Def = [{attributes, [k, v]}, {Storage, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + + OneRec = {Tab, 1, 2}, + OnePat = {Tab, '$1', 2}, + ?match([], mnesia:dirty_match_object(OnePat)), + ?match(ok, mnesia:dirty_write(OneRec)), + ?match([OneRec], mnesia:dirty_match_object(OnePat)), + ?match({atomic, [OneRec]}, mnesia:transaction(fun() -> + mnesia:dirty_match_object(OnePat) end)), + + ?match({'EXIT', _}, mnesia:dirty_match_object({foo, '$1', 2})), + ?match({'EXIT', _}, mnesia:dirty_match_object({[], '$1', 2})), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +dirty_index(suite) -> + [ + dirty_index_match_object, + dirty_index_read, + dirty_index_update + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Dirty read matching records by using an index + +dirty_index_match_object(suite) -> + [ + dirty_index_match_object_ram, + dirty_index_match_object_disc, + dirty_index_match_object_disc_only + ]. + +dirty_index_match_object_ram(suite) -> []; +dirty_index_match_object_ram(Config) when is_list(Config) -> + dirty_index_match_object(Config, ram_copies). + +dirty_index_match_object_disc(suite) -> []; +dirty_index_match_object_disc(Config) when is_list(Config) -> + dirty_index_match_object(Config, disc_copies). + +dirty_index_match_object_disc_only(suite) -> []; +dirty_index_match_object_disc_only(Config) when is_list(Config) -> + dirty_index_match_object(Config, disc_only_copies). + +dirty_index_match_object(Config, Storage) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = dirty_index_match_object, + ValPos = 3, + BadValPos = ValPos + 1, + Def = [{attributes, [k, v]}, {Storage, [Node1]}, {index, [ValPos]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + + ?match([], mnesia:dirty_index_match_object({Tab, '$1', 2}, ValPos)), + OneRec = {Tab, 1, 2}, + ?match(ok, mnesia:dirty_write(OneRec)), + + ?match([OneRec], mnesia:dirty_index_match_object({Tab, '$1', 2}, ValPos)), + ?match({'EXIT', _}, mnesia:dirty_index_match_object({Tab, '$1', 2}, BadValPos)), + ?match({'EXIT', _}, mnesia:dirty_index_match_object({foo, '$1', 2}, ValPos)), + ?match({'EXIT', _}, mnesia:dirty_index_match_object({[], '$1', 2}, ValPos)), + ?match({atomic, [OneRec]}, mnesia:transaction(fun() -> + mnesia:dirty_index_match_object({Tab, '$1', 2}, ValPos) end)), + + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Read records by using an index + +dirty_index_read(suite) -> + [ + dirty_index_read_ram, + dirty_index_read_disc, + dirty_index_read_disc_only + ]. + +dirty_index_read_ram(suite) -> []; +dirty_index_read_ram(Config) when is_list(Config) -> + dirty_index_read(Config, ram_copies). + +dirty_index_read_disc(suite) -> []; +dirty_index_read_disc(Config) when is_list(Config) -> + dirty_index_read(Config, disc_copies). + +dirty_index_read_disc_only(suite) -> []; +dirty_index_read_disc_only(Config) when is_list(Config) -> + dirty_index_read(Config, disc_only_copies). + +dirty_index_read(Config, Storage) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = dirty_index_read, + ValPos = 3, + BadValPos = ValPos + 1, + Def = [{type, set}, + {attributes, [k, v]}, + {Storage, [Node1]}, + {index, [ValPos]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + + OneRec = {Tab, 1, 2}, + ?match([], mnesia:dirty_index_read(Tab, 2, ValPos)), + ?match(ok, mnesia:dirty_write(OneRec)), + ?match([OneRec], mnesia:dirty_index_read(Tab, 2, ValPos)), + ?match({atomic, [OneRec]}, + mnesia:transaction(fun() -> mnesia:dirty_index_read(Tab, 2, ValPos) end)), + ?match(42, mnesia:dirty_update_counter({Tab, 1}, 40)), + ?match([{Tab,1,42}], mnesia:dirty_read({Tab, 1})), + ?match([], mnesia:dirty_index_read(Tab, 2, ValPos)), + ?match([{Tab, 1, 42}], mnesia:dirty_index_read(Tab, 42, ValPos)), + + ?match({'EXIT', _}, mnesia:dirty_index_read(Tab, 2, BadValPos)), + ?match({'EXIT', _}, mnesia:dirty_index_read(foo, 2, ValPos)), + ?match({'EXIT', _}, mnesia:dirty_index_read([], 2, ValPos)), + + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +dirty_index_update(suite) -> + [ + dirty_index_update_set_ram, + dirty_index_update_set_disc, + dirty_index_update_set_disc_only, + dirty_index_update_bag_ram, + dirty_index_update_bag_disc, + dirty_index_update_bag_disc_only + ]; +dirty_index_update(doc) -> + ["See Ticket OTP-2083, verifies that a table with a index is " + "update in the correct way i.e. the index finds the correct " + "records after a update"]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +dirty_index_update_set_ram(suite) -> []; +dirty_index_update_set_ram(Config) when is_list(Config) -> + dirty_index_update_set(Config, ram_copies). + +dirty_index_update_set_disc(suite) -> []; +dirty_index_update_set_disc(Config) when is_list(Config) -> + dirty_index_update_set(Config, disc_copies). + +dirty_index_update_set_disc_only(suite) -> []; +dirty_index_update_set_disc_only(Config) when is_list(Config) -> + dirty_index_update_set(Config, disc_only_copies). + +dirty_index_update_set(Config, Storage) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = index_test, + ValPos = v1, + ValPos2 = v3, + Def = [{attributes, [k, v1, v2, v3]}, + {Storage, [Node1]}, + {index, [ValPos]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + + Pat1 = {Tab, '$1', 2, '$2', '$3'}, + Pat2 = {Tab, '$1', '$2', '$3', '$4'}, + + Rec1 = {Tab, 1, 2, 3, 4}, + Rec2 = {Tab, 2, 2, 13, 14}, + Rec3 = {Tab, 1, 12, 13, 14}, + Rec4 = {Tab, 4, 2, 13, 14}, + + ?match([], mnesia:dirty_index_read(Tab, 2, ValPos)), + ?match(ok, mnesia:dirty_write(Rec1)), + ?match([Rec1], mnesia:dirty_index_read(Tab, 2, ValPos)), + + ?match(ok, mnesia:dirty_write(Rec2)), + R1 = mnesia:dirty_index_read(Tab, 2, ValPos), + ?match([Rec1, Rec2], lists:sort(R1)), + + ?match(ok, mnesia:dirty_write(Rec3)), + R2 = mnesia:dirty_index_read(Tab, 2, ValPos), + ?match([Rec2], lists:sort(R2)), + ?match([Rec2], mnesia:dirty_index_match_object(Pat1, ValPos)), + + {atomic, R3} = mnesia:transaction(fun() -> mnesia:match_object(Pat2) end), + ?match([Rec3, Rec2], lists:sort(R3)), + + ?match(ok, mnesia:dirty_write(Rec4)), + R4 = mnesia:dirty_index_read(Tab, 2, ValPos), + ?match([Rec2, Rec4], lists:sort(R4)), + + ?match(ok, mnesia:dirty_delete({Tab, 4})), + ?match([Rec2], mnesia:dirty_index_read(Tab, 2, ValPos)), + + ?match({atomic, ok}, mnesia:del_table_index(Tab, ValPos)), + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write(Rec4) end)), + ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)), + ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos2)), + + R5 = mnesia:dirty_match_object(Pat2), + ?match([Rec3, Rec2, Rec4], lists:sort(R5)), + + R6 = mnesia:dirty_index_read(Tab, 2, ValPos), + ?match([Rec2, Rec4], lists:sort(R6)), + ?match([], mnesia:dirty_index_read(Tab, 4, ValPos2)), + R7 = mnesia:dirty_index_read(Tab, 14, ValPos2), + ?match([Rec3, Rec2, Rec4], lists:sort(R7)), + + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write(Rec1) end)), + R8 = mnesia:dirty_index_read(Tab, 2, ValPos), + ?match([Rec1, Rec2, Rec4], lists:sort(R8)), + ?match([Rec1], mnesia:dirty_index_read(Tab, 4, ValPos2)), + R9 = mnesia:dirty_index_read(Tab, 14, ValPos2), + ?match([Rec2, Rec4], lists:sort(R9)), + + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:delete_object(Rec2) end)), + R10 = mnesia:dirty_index_read(Tab, 2, ValPos), + ?match([Rec1, Rec4], lists:sort(R10)), + ?match([Rec1], mnesia:dirty_index_read(Tab, 4, ValPos2)), + ?match([Rec4], mnesia:dirty_index_read(Tab, 14, ValPos2)), + + ?match(ok, mnesia:dirty_delete({Tab, 4})), + R11 = mnesia:dirty_index_read(Tab, 2, ValPos), + ?match([Rec1], lists:sort(R11)), + ?match([Rec1], mnesia:dirty_index_read(Tab, 4, ValPos2)), + ?match([], mnesia:dirty_index_read(Tab, 14, ValPos2)), + + ?verify_mnesia(Nodes, []). + +dirty_index_update_bag_ram(suite) -> []; +dirty_index_update_bag_ram(Config)when is_list(Config) -> + dirty_index_update_bag(Config, ram_copies). + +dirty_index_update_bag_disc(suite) -> []; +dirty_index_update_bag_disc(Config)when is_list(Config) -> + dirty_index_update_bag(Config, disc_copies). + +dirty_index_update_bag_disc_only(suite) -> []; +dirty_index_update_bag_disc_only(Config)when is_list(Config) -> + dirty_index_update_bag(Config, disc_only_copies). + +dirty_index_update_bag(Config, Storage) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = index_test, + ValPos = v1, + ValPos2 = v3, + Def = [{type, bag}, + {attributes, [k, v1, v2, v3]}, + {Storage, [Node1]}, + {index, [ValPos]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + + Pat1 = {Tab, '$1', 2, '$2', '$3'}, + Pat2 = {Tab, '$1', '$2', '$3', '$4'}, + + Rec1 = {Tab, 1, 2, 3, 4}, + Rec2 = {Tab, 2, 2, 13, 14}, + Rec3 = {Tab, 1, 12, 13, 14}, + Rec4 = {Tab, 4, 2, 13, 4}, + Rec5 = {Tab, 1, 2, 234, 14}, + + %% Simple Index + ?match([], mnesia:dirty_index_read(Tab, 2, ValPos)), + ?match(ok, mnesia:dirty_write(Rec1)), + ?match([Rec1], mnesia:dirty_index_read(Tab, 2, ValPos)), + + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write(Rec2) end)), + R1 = mnesia:dirty_index_read(Tab, 2, ValPos), + ?match([Rec1, Rec2], lists:sort(R1)), + + ?match(ok, mnesia:dirty_write(Rec3)), + R2 = mnesia:dirty_index_read(Tab, 2, ValPos), + ?match([Rec1, Rec2], lists:sort(R2)), + + R3 = mnesia:dirty_index_match_object(Pat1, ValPos), + ?match([Rec1, Rec2], lists:sort(R3)), + + R4 = mnesia:dirty_match_object(Pat2), + ?match([Rec1, Rec3, Rec2], lists:sort(R4)), + + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write(Rec4) end)), + R5 = mnesia:dirty_index_read(Tab, 2, ValPos), + ?match([Rec1, Rec2, Rec4], lists:sort(R5)), + + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:delete({Tab, 4}) end)), + R6 = mnesia:dirty_index_read(Tab, 2, ValPos), + ?match([Rec1, Rec2], lists:sort(R6)), + + ?match(ok, mnesia:dirty_delete_object(Rec1)), + ?match([Rec2], mnesia:dirty_index_read(Tab, 2, ValPos)), + R7 = mnesia:dirty_match_object(Pat2), + ?match([Rec3, Rec2], lists:sort(R7)), + + %% Two indexies + ?match({atomic, ok}, mnesia:del_table_index(Tab, ValPos)), + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write(Rec1) end)), + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write(Rec4) end)), + ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)), + ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos2)), + + R8 = mnesia:dirty_index_read(Tab, 2, ValPos), + ?match([Rec1, Rec2, Rec4], lists:sort(R8)), + + R9 = mnesia:dirty_index_read(Tab, 4, ValPos2), + ?match([Rec1, Rec4], lists:sort(R9)), + R10 = mnesia:dirty_index_read(Tab, 14, ValPos2), + ?match([Rec3, Rec2], lists:sort(R10)), + + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write(Rec5) end)), + R11 = mnesia:dirty_index_read(Tab, 2, ValPos), + ?match([Rec1, Rec5, Rec2, Rec4], lists:sort(R11)), + R12 = mnesia:dirty_index_read(Tab, 4, ValPos2), + ?match([Rec1, Rec4], lists:sort(R12)), + R13 = mnesia:dirty_index_read(Tab, 14, ValPos2), + ?match([Rec5, Rec3, Rec2], lists:sort(R13)), + + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:delete_object(Rec1) end)), + R14 = mnesia:dirty_index_read(Tab, 2, ValPos), + ?match([Rec5, Rec2, Rec4], lists:sort(R14)), + ?match([Rec4], mnesia:dirty_index_read(Tab, 4, ValPos2)), + R15 = mnesia:dirty_index_read(Tab, 14, ValPos2), + ?match([Rec5, Rec3, Rec2], lists:sort(R15)), + + ?match(ok, mnesia:dirty_delete_object(Rec5)), + R16 = mnesia:dirty_index_read(Tab, 2, ValPos), + ?match([Rec2, Rec4], lists:sort(R16)), + ?match([Rec4], mnesia:dirty_index_read(Tab, 4, ValPos2)), + R17 = mnesia:dirty_index_read(Tab, 14, ValPos2), + ?match([Rec3, Rec2], lists:sort(R17)), + + ?match(ok, mnesia:dirty_write(Rec1)), + ?match(ok, mnesia:dirty_delete({Tab, 1})), + R18 = mnesia:dirty_index_read(Tab, 2, ValPos), + ?match([Rec2, Rec4], lists:sort(R18)), + ?match([Rec4], mnesia:dirty_index_read(Tab, 4, ValPos2)), + R19 = mnesia:dirty_index_read(Tab, 14, ValPos2), + ?match([Rec2], lists:sort(R19)), + + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Dirty iteration +%% dirty_slot, dirty_first, dirty_next + +dirty_iter(suite) -> + [ + dirty_iter_ram, + dirty_iter_disc, + dirty_iter_disc_only + ]. + +dirty_iter_ram(suite) -> []; +dirty_iter_ram(Config) when is_list(Config) -> + dirty_iter(Config, ram_copies). + +dirty_iter_disc(suite) -> []; +dirty_iter_disc(Config) when is_list(Config) -> + dirty_iter(Config, disc_copies). + +dirty_iter_disc_only(suite) -> []; +dirty_iter_disc_only(Config) when is_list(Config) -> + dirty_iter(Config, disc_only_copies). + +dirty_iter(Config, Storage) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = dirty_iter, + Def = [{type, bag}, {attributes, [k, v]}, {Storage, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + + ?match([], all_slots(Tab)), + ?match([], all_nexts(Tab)), + + Keys = lists:seq(1, 5), + Records = [{Tab, A, B} || A <- Keys, B <- lists:seq(1, 2)], + lists:foreach(fun(Rec) -> ?match(ok, mnesia:dirty_write(Rec)) end, Records), + + SortedRecords = lists:sort(Records), + ?match(SortedRecords, lists:sort(all_slots(Tab))), + ?match(Keys, lists:sort(all_nexts(Tab))), + + ?match({'EXIT', _}, mnesia:dirty_first(foo)), + ?match({'EXIT', _}, mnesia:dirty_next(foo, foo)), + ?match({'EXIT', _}, mnesia:dirty_slot(foo, 0)), + ?match({'EXIT', _}, mnesia:dirty_slot(foo, [])), + ?match({atomic, Keys}, + mnesia:transaction(fun() -> lists:sort(all_nexts(Tab)) end)), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Returns a list of all keys in table +all_slots(Tab) -> + all_slots(Tab, [], 0). + +all_slots(_Tab, '$end_of_table', _) -> + []; +all_slots(Tab, PrevRecords, PrevSlot) -> + Records = mnesia:dirty_slot(Tab, PrevSlot), + PrevRecords ++ all_slots(Tab, Records, PrevSlot + 1). + +%% Returns a list of all keys in table + +all_nexts(Tab) -> + FirstKey = mnesia:dirty_first(Tab), + all_nexts(Tab, FirstKey). + +all_nexts(_Tab, '$end_of_table') -> + []; +all_nexts(Tab, PrevKey) -> + Key = mnesia:dirty_next(Tab, PrevKey), + [PrevKey] ++ all_nexts(Tab, Key). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +admin_tests(doc) -> + ["Verifies that dirty operations work during schema operations"]; + +admin_tests(suite) -> + [del_table_copy_1, + del_table_copy_2, + del_table_copy_3, + add_table_copy_1, + add_table_copy_2, + add_table_copy_3, + add_table_copy_4, + move_table_copy_1, + move_table_copy_2, + move_table_copy_3, + move_table_copy_4]. + +update_trans(Tab, Key, Acc) -> + Update = + fun() -> + Res = (catch mnesia:read({Tab, Key})), + case Res of + [{Tab, Key, Extra, Acc}] -> + mnesia:write({Tab,Key,Extra, Acc+1}); + Val -> + {read, Val, {acc, Acc}} + end + end, + receive + {Pid, quit} -> Pid ! {self(), Acc} + after + 3 -> + case catch mnesia:sync_dirty(Update) of + ok -> + update_trans(Tab, Key, Acc+1); + Else -> + ?error("Dirty Operation failed on ~p (update no ~p) with ~p~n" + "Info w2read ~p w2write ~p w2commit ~p storage ~p ~n", + [node(), + Acc, + Else, + mnesia:table_info(Tab, where_to_read), + mnesia:table_info(Tab, where_to_write), + mnesia:table_info(Tab, where_to_commit), + mnesia:table_info(Tab, storage_type)]) + end + end. + +del_table_copy_1(suite) -> []; +del_table_copy_1(Config) when is_list(Config) -> + [_Node1, Node2, _Node3] = Nodes = ?acquire_nodes(3, Config), + del_table(Node2, Node2, Nodes). %Called on same Node as deleted +del_table_copy_2(suite) -> []; +del_table_copy_2(Config) when is_list(Config) -> + [Node1, Node2, _Node3] = Nodes = ?acquire_nodes(3, Config), + del_table(Node1, Node2, Nodes). %Called from other Node +del_table_copy_3(suite) -> []; +del_table_copy_3(Config) when is_list(Config) -> + [_Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + del_table(Node3, Node2, Nodes). %Called from Node w.o. table + +del_table(CallFrom, DelNode, [Node1, Node2, Node3]) -> + Tab = schema_ops, + Def = [{disc_only_copies, [Node1]}, {ram_copies, [Node2]}, + {attributes, [key, attr1, attr2]}], + ?log("Test case removing table from ~w, with ~w~n", [DelNode, Def]), + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + insert(Tab, 1000), + + Pid1 = spawn_link(Node1, ?MODULE, update_trans, [Tab, 1, 0]), + Pid2 = spawn_link(Node2, ?MODULE, update_trans, [Tab, 2, 0]), + Pid3 = spawn_link(Node3, ?MODULE, update_trans, [Tab, 3, 0]), + + + dbg:tracer(process, {fun(Msg,_) -> tracer(Msg) end, void}), + %% dbg:n(Node2), + %% dbg:n(Node3), + %% dbg:tp('_', []), + %% dbg:tpl(dets, [timestamp]), + dbg:p(Pid1, [m,c,timestamp]), + + ?match({atomic, ok}, + rpc:call(CallFrom, mnesia, del_table_copy, [Tab, DelNode])), + + Pid1 ! {self(), quit}, R1 = + receive {Pid1, Res1} -> Res1 + after + 5000 -> io:format("~p~n",[process_info(Pid1)]),error + end, + Pid2 ! {self(), quit}, R2 = + receive {Pid2, Res2} -> Res2 + after + 5000 -> error + end, + Pid3 ! {self(), quit}, R3 = + receive {Pid3, Res3} -> Res3 + after + 5000 -> error + end, + verify_oids(Tab, Node1, Node2, Node3, R1, R2, R3), + ?verify_mnesia([Node1, Node2, Node3], []). + +tracer({trace_ts, _, send, Msg, Pid, {_,S,Ms}}) -> + io:format("~p:~p ~p >> ~w ~n",[S,Ms,Pid,Msg]); +tracer({trace_ts, _, 'receive', Msg, {_,S,Ms}}) -> + io:format("~p:~p << ~w ~n",[S,Ms,Msg]); + + +tracer(Msg) -> + io:format("UMsg ~p ~n",[Msg]), + ok. + + + +add_table_copy_1(suite) -> []; +add_table_copy_1(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + Def = [{ram_copies, [Node1, Node2]}, + {attributes, [key, attr1, attr2]}], + add_table(Node1, Node3, Nodes, Def). +%% Not so much diff from 1 but I got a feeling of a bug +%% should behave exactly the same but just checking the internal ordering +add_table_copy_2(suite) -> []; +add_table_copy_2(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + Def = [{ram_copies, [Node1, Node2]}, + {attributes, [key, attr1, attr2]}], + add_table(Node2, Node3, Nodes, Def). +add_table_copy_3(suite) -> []; +add_table_copy_3(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + Def = [{ram_copies, [Node1, Node2]}, + {attributes, [key, attr1, attr2]}], + add_table(Node3, Node3, Nodes, Def). +add_table_copy_4(suite) -> []; +add_table_copy_4(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + Def = [{disc_only_copies, [Node1]}, + {attributes, [key, attr1, attr2]}], + add_table(Node2, Node3, Nodes, Def). + +add_table(CallFrom, AddNode, [Node1, Node2, Node3], Def) -> + ?log("Test case adding table at ~w, with ~w~n", [AddNode, Def]), + Tab = schema_ops, + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + insert(Tab, 1002), + + Pid1 = spawn_link(Node1, ?MODULE, update_trans, [Tab, 1, 0]), + Pid2 = spawn_link(Node2, ?MODULE, update_trans, [Tab, 2, 0]), + Pid3 = spawn_link(Node3, ?MODULE, update_trans, [Tab, 3, 0]), + + ?match({atomic, ok}, rpc:call(CallFrom, mnesia, add_table_copy, + [Tab, AddNode, ram_copies])), + Pid1 ! {self(), quit}, R1 = receive {Pid1, Res1} -> Res1 after 5000 -> error end, + Pid2 ! {self(), quit}, R2 = receive {Pid2, Res2} -> Res2 after 5000 -> error end, + Pid3 ! {self(), quit}, R3 = receive {Pid3, Res3} -> Res3 after 5000 -> error end, + verify_oids(Tab, Node1, Node2, Node3, R1, R2, R3), + ?verify_mnesia([Node1, Node2, Node3], []). + +move_table_copy_1(suite) -> []; +move_table_copy_1(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + Def = [{ram_copies, [Node1, Node2]}, + {attributes, [key, attr1, attr2]}], + move_table(Node1, Node1, Node3, Nodes, Def). +move_table_copy_2(suite) -> []; +move_table_copy_2(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + Def = [{ram_copies, [Node1, Node2]}, + {attributes, [key, attr1, attr2]}], + move_table(Node2, Node1, Node3, Nodes, Def). +move_table_copy_3(suite) -> []; +move_table_copy_3(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + Def = [{ram_copies, [Node1, Node2]}, + {attributes, [key, attr1, attr2]}], + move_table(Node3, Node1, Node3, Nodes, Def). +move_table_copy_4(suite) -> []; +move_table_copy_4(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + Def = [{ram_copies, [Node1]}, + {attributes, [key, attr1, attr2]}], + move_table(Node2, Node1, Node3, Nodes, Def). + +move_table(CallFrom, FromNode, ToNode, [Node1, Node2, Node3], Def) -> + ?log("Test case move table from ~w to ~w, with ~w~n", [FromNode, ToNode, Def]), + Tab = schema_ops, + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + insert(Tab, 1002), + + Pid1 = spawn_link(Node1, ?MODULE, update_trans, [Tab, 1, 0]), + Pid2 = spawn_link(Node2, ?MODULE, update_trans, [Tab, 2, 0]), + Pid3 = spawn_link(Node3, ?MODULE, update_trans, [Tab, 3, 0]), + + ?match({atomic, ok}, rpc:call(CallFrom, mnesia, move_table_copy, + [Tab, FromNode, ToNode])), + Pid1 ! {self(), quit}, + R1 = receive {Pid1, Res1} -> Res1 after 5000 -> ?error("timeout pid1~n", []) end, + Pid2 ! {self(), quit}, + R2 = receive {Pid2, Res2} -> Res2 after 5000 -> ?error("timeout pid2~n", []) end, + Pid3 ! {self(), quit}, + R3 = receive {Pid3, Res3} -> Res3 after 5000 -> ?error("timeout pid3~n", []) end, + verify_oids(Tab, Node1, Node2, Node3, R1, R2, R3), + ?verify_mnesia([Node1, Node2, Node3], []). + +% Verify consistency between different nodes +% Due to limitations in the current dirty_ops this can wrong from time to time! +verify_oids(Tab, N1, N2, N3, R1, R2, R3) -> + io:format("DEBUG 1=>~p 2=>~p 3=>~p~n", [R1,R2,R3]), + ?match([{_, _, _, R1}], rpc:call(N1, mnesia, dirty_read, [{Tab, 1}])), + ?match([{_, _, _, R1}], rpc:call(N2, mnesia, dirty_read, [{Tab, 1}])), + ?match([{_, _, _, R1}], rpc:call(N3, mnesia, dirty_read, [{Tab, 1}])), + ?match([{_, _, _, R2}], rpc:call(N1, mnesia, dirty_read, [{Tab, 2}])), + ?match([{_, _, _, R2}], rpc:call(N2, mnesia, dirty_read, [{Tab, 2}])), + ?match([{_, _, _, R2}], rpc:call(N3, mnesia, dirty_read, [{Tab, 2}])), + ?match([{_, _, _, R3}], rpc:call(N1, mnesia, dirty_read, [{Tab, 3}])), + ?match([{_, _, _, R3}], rpc:call(N2, mnesia, dirty_read, [{Tab, 3}])), + ?match([{_, _, _, R3}], rpc:call(N3, mnesia, dirty_read, [{Tab, 3}])). + +insert(_Tab, 0) -> ok; +insert(Tab, N) when N > 0 -> + ok = mnesia:sync_dirty(fun() -> false = mnesia:is_transaction(), mnesia:write({Tab, N, N, 0}) end), + insert(Tab, N-1). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/lib/mnesia/test/mnesia_durability_test.erl b/lib/mnesia/test/mnesia_durability_test.erl new file mode 100644 index 0000000000..b917b0ca40 --- /dev/null +++ b/lib/mnesia/test/mnesia_durability_test.erl @@ -0,0 +1,1470 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1997-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(mnesia_durability_test). +-author('[email protected]'). +-compile([export_all]). +-include("mnesia_test_lib.hrl"). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +init_per_testcase(Func, Conf) -> + mnesia_test_lib:init_per_testcase(Func, Conf). + +fin_per_testcase(Func, Conf) -> + mnesia_test_lib:fin_per_testcase(Func, Conf). + +-record(test_rec,{key,val}). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +all(doc) -> + ["Verify durability", + "Verify that the effects of committed transactions are durable.", + "The content of the tables tables must be restored at startup."]; +all(suite) -> + [ + load_tables, + durability_of_dump_tables, + durability_of_disc_copies, + durability_of_disc_only_copies + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +load_tables(doc) -> + ["Try to provoke all kinds of table load scenarios."]; +load_tables(suite) -> + [ + load_latest_data, + load_local_contents_directly, + load_directly_when_all_are_ram_copiesA, + load_directly_when_all_are_ram_copiesB, + late_load_when_all_are_ram_copies_on_ram_nodes, + load_when_last_replica_becomes_available, + load_when_we_have_down_from_all_other_replica_nodes, + late_load_transforms_into_disc_load, + late_load_leads_to_hanging, + force_load_when_nobody_intents_to_load, + force_load_when_someone_has_decided_to_load, + force_load_when_someone_else_already_has_loaded, + force_load_when_we_has_loaded, + force_load_on_a_non_local_table, + force_load_when_the_table_does_not_exist, + load_tables_with_master_tables + ]. +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +load_latest_data(doc) -> + ["Base functionality, verify that the latest data is loaded"]; +load_latest_data(suite) -> []; +load_latest_data(Config) when is_list(Config) -> + [N1,N2,N3] = Nodes = ?acquire_nodes(3, Config), + %%Create a replicated local table + ?match({atomic,ok}, mnesia:create_table(t0, [{disc_copies,[N1,N2]}])), + ?match({atomic,ok}, mnesia:create_table(t1, [{disc_copies,[N1,N2]}])), + ?match({atomic,ok}, mnesia:create_table(t2, [{disc_copies,[N1,N2]}])), + ?match({atomic,ok}, mnesia:create_table(t3, [{disc_copies,[N1,N2]}])), + ?match({atomic,ok}, mnesia:create_table(t4, [{disc_copies,[N1,N2]}])), + ?match({atomic,ok}, mnesia:create_table(t5, [{disc_copies,[N1,N2]}])), + Rec1 = {t1, test, ok}, + Rec2 = {t1, test, 2}, + + ?match([], mnesia_test_lib:kill_mnesia([N1])), + ?match(ok, rpc:call(N2, mnesia, dirty_write, [Rec2])), + ?match([], mnesia_test_lib:kill_mnesia([N2])), + ?match([], mnesia_test_lib:kill_mnesia([N3])), + + ?match([], mnesia_test_lib:start_mnesia([N1], [])), + %% Should wait for N2 + ?match({timeout, [t1]}, rpc:call(N1, mnesia, wait_for_tables, [[t1], 3000])), + ?match([], mnesia_test_lib:start_mnesia([N3], [])), + ?match({timeout, [t1]}, rpc:call(N1, mnesia, wait_for_tables, [[t1], 3000])), + + + ?match([], mnesia_test_lib:start_mnesia([N2], [])), + ?match(ok, rpc:call(N2, mnesia, wait_for_tables, [[t1], 3000])), + ?match(ok, rpc:call(N1, mnesia, wait_for_tables, [[t1], 3000])), + %% We should find the record + ?match([Rec2], rpc:call(N1, mnesia, dirty_read, [t1, test])), + ?match([Rec2], rpc:call(N2, mnesia, dirty_read, [t1, test])), + + %% ok, lets switch order + ?match(ok, mnesia:dirty_delete_object(Rec1)), + ?match(ok, mnesia:dirty_delete_object(Rec2)), + %% redo + + ?match([], mnesia_test_lib:kill_mnesia([N2])), + ?match(ok, mnesia:dirty_write(Rec1)), + ?match([], mnesia_test_lib:kill_mnesia([N1])), + ?match([], mnesia_test_lib:kill_mnesia([N3])), + + ?match([], mnesia_test_lib:start_mnesia([N2], [])), + %% Should wait for N1 + ?match({timeout, [t1]}, rpc:call(N2, mnesia, wait_for_tables, [[t1], 2000])), + ?match([], mnesia_test_lib:start_mnesia([N3], [])), + ?match({timeout, [t1]}, rpc:call(N2, mnesia, wait_for_tables, [[t1], 2000])), + ?match([], mnesia_test_lib:start_mnesia([N1], [])), + ?match(ok, rpc:call(N2, mnesia, wait_for_tables, [[t1], 1000])), + ?match(ok, rpc:call(N1, mnesia, wait_for_tables, [[t1], 1000])), + %% We should find the record + ?match([Rec1], rpc:call(N1, mnesia, dirty_read, [t1, test])), + ?match([Rec1], rpc:call(N2, mnesia, dirty_read, [t1, test])), + + ?verify_mnesia(Nodes, []). + + +load_local_contents_directly(doc) -> + ["Local contents shall always be loaded. Check this by having a local ", + "table on two nodes N1, N2, stopping N1 before N2, an then verifying ", + "that N1 can start without N2 being started."]; +load_local_contents_directly(suite) -> []; +load_local_contents_directly(Config) when is_list(Config) -> + [N1, N2] = Nodes = ?acquire_nodes(2, Config), + %%Create a replicated local table + ?match({atomic,ok}, + mnesia:create_table(test_rec, + [{local_content,true}, + {disc_copies,Nodes}, + {attributes,record_info(fields,test_rec)}] + ) ), + %%Verify that it has local contents. + ?match( true, mnesia:table_info(test_rec,local_content) ), + %%Helper Funs + Write_one = fun(Value) -> mnesia:write(#test_rec{key=1,val=Value}) end, + Read_one = fun(Key) -> mnesia:read( {test_rec, Key}) end, + %%Write a value one N1 that we may test against later + ?match({atomic,ok}, + rpc:call( N1, mnesia, transaction, [Write_one,[11]] ) ), + %%Stop Mnesia on N1 + %?match([], mnesia_test_lib:stop_mnesia([N1])), + ?match([], mnesia_test_lib:kill_mnesia([N1])), + + %%Write a value on N2, same key but a different value + ?match({atomic,ok}, + rpc:call( N2, mnesia, transaction, [Write_one,[22]] ) ), + %%Stop Mnesia on N2 + %?match([], mnesia_test_lib:stop_mnesia([N2])), + ?match([], mnesia_test_lib:kill_mnesia([N2])), + + %%Restart Mnesia on N1 verify that we can read from it without + %%starting Mnesia on N2. + ?match(ok, rpc:call(N1, mnesia, start, [])), + ?match(ok, rpc:call(N1, mnesia, wait_for_tables, [[test_rec], 30000])), + %%Read back the value + ?match( {atomic,[#test_rec{key=1,val=11}]}, + rpc:call(N1, mnesia, transaction, [Read_one,[1]] ) ), + %%Restart Mnesia on N2 and verify the contents there. + ?match(ok, rpc:call(N2, mnesia, start, [])), + ?match(ok, rpc:call(N2, mnesia, wait_for_tables, [[test_rec], 30000])), + ?match( {atomic,[#test_rec{key=1,val=22}]}, + rpc:call(N2, mnesia, transaction, [Read_one,[1]] ) ), + %%Check that the start of Mnesai on N2 did not affect the contents on N1 + ?match( {atomic,[#test_rec{key=1,val=11}]}, + rpc:call(N1, mnesia, transaction, [Read_one,[1]] ) ), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +load_directly_when_all_are_ram_copiesA(doc) -> + ["Tables that are RAM copies only shall also be loaded directly. ", + "1. N1 and N2 has RAM copies of a table, stop N1 before N2. ", + "2. When N1 starts he shall have access to the table ", + " without having to start N2" ]; +load_directly_when_all_are_ram_copiesA(suite) -> []; +load_directly_when_all_are_ram_copiesA(Config) when is_list(Config) -> + [N1, N2] = Nodes = ?acquire_nodes(2, Config), + + ?match({atomic,ok}, + mnesia:create_table(test_rec, + [{ram_copies,Nodes}, + {attributes,record_info(fields,test_rec)}] + ) ), + ?match( Nodes, mnesia:table_info(test_rec,ram_copies) ), + ?match( [], mnesia:table_info(test_rec,disc_copies) ), + ?match( [], mnesia:table_info(test_rec,disc_only_copies) ), + Write_one = fun(Value) -> mnesia:write(#test_rec{key=2,val=Value}) end, + Read_one = fun() -> mnesia:read({test_rec,2}) end, + %%Write a value one N1 that we may test against later + ?match({atomic,ok}, + rpc:call( N1, mnesia, transaction, [Write_one,[11]] ) ), + %%Stop Mnesia on N1 + ?match([], mnesia_test_lib:kill_mnesia([N1])), + %%Write a value and check result (on N2; not possible on N1 + %%since Mnesia is stopped there). + ?match({atomic,ok}, rpc:call(N2,mnesia,transaction,[Write_one,[22]]) ), + ?match({atomic,[#test_rec{key=2,val=22}]}, + rpc:call(N2,mnesia,transaction,[Read_one]) ), + %%Stop Mnesia on N2 + ?match([], mnesia_test_lib:kill_mnesia([N2])), + %%Restart Mnesia on N1 verify that we can access test_rec from + %%N1 without starting Mnesia on N2. + ?match(ok, rpc:call(N1, mnesia, start, [])), + ?match(ok, rpc:call(N1, mnesia, wait_for_tables, [[test_rec], 30000])), + ?match({atomic,[]}, rpc:call(N1,mnesia,transaction,[Read_one])), + ?match({atomic,ok}, rpc:call(N1,mnesia,transaction,[Write_one,[33]])), + ?match({atomic,[#test_rec{key=2,val=33}]}, + rpc:call(N1,mnesia,transaction,[Read_one])), + %%Restart Mnesia on N2 and verify the contents there. + ?match([], mnesia_test_lib:start_mnesia([N2], [test_rec])), + ?match( {atomic,[#test_rec{key=2,val=33}]}, + rpc:call(N2, mnesia, transaction, [Read_one] ) ), + %%Check that the start of Mnesai on N2 did not affect the contents on N1 + ?match( {atomic,[#test_rec{key=2,val=33}]}, + rpc:call(N1, mnesia, transaction, [Read_one] ) ), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +load_directly_when_all_are_ram_copiesB(doc) -> + ["Tables that are RAM copies only shall be loaded from a replicat ", + "when possible. ", + "1. N1 and N2 has RAM copies of a table, stop N1 before N2.", + "2. Now start N2 first and then N1, N1 shall then load the table ", + " from N2."]; +load_directly_when_all_are_ram_copiesB(suite) -> []; +load_directly_when_all_are_ram_copiesB(Config) when is_list(Config) -> + [N1, N2] = Nodes = ?acquire_nodes(2, Config), + ?match({atomic,ok}, + mnesia:create_table(test_rec, + [{ram_copies,Nodes}, + {attributes,record_info(fields,test_rec)}] + ) ), + ?match( Nodes, mnesia:table_info(test_rec,ram_copies) ), + ?match( [], mnesia:table_info(test_rec,disc_copies) ), + ?match( [], mnesia:table_info(test_rec,disc_only_copies) ), + Write_one = fun(Value) -> mnesia:write(#test_rec{key=3,val=Value}) end, + Read_one = fun() -> mnesia:read( {test_rec, 3}) end, + %%Write a value one N1 that we may test against later + ?match({atomic,ok}, + rpc:call( N1, mnesia, transaction, [Write_one,[11]] ) ), + ?match({atomic,[#test_rec{key=3,val=11}]}, + rpc:call(N2,mnesia,transaction,[Read_one]) ), + %%Stop Mnesia on N1 + ?match([], mnesia_test_lib:kill_mnesia([N1])), + %%Write a value and check result (on N2; not possible on N1 + %%since Mnesia is stopped there). + ?match({atomic,ok}, rpc:call(N2,mnesia,transaction,[Write_one,[22]]) ), + ?match({atomic,[#test_rec{key=3,val=22}]}, + rpc:call(N2,mnesia,transaction,[Read_one]) ), + %%Stop Mnesia on N2 + ?match([], mnesia_test_lib:kill_mnesia([N2])), + %%Restart Mnesia on N2 verify that we can access test_rec from + %%N2 without starting Mnesia on N1. + ?match(ok, rpc:call(N2, mnesia, start, [])), + ?match(ok, rpc:call(N2, mnesia, wait_for_tables, [[test_rec], 30000])), + ?match({atomic,[]}, rpc:call(N2,mnesia,transaction,[Read_one])), + ?match({atomic,ok}, rpc:call(N2,mnesia,transaction,[Write_one,[33]])), + ?match({atomic,[#test_rec{key=3,val=33}]}, + rpc:call(N2,mnesia,transaction,[Read_one])), + %%Restart Mnesia on N1 and verify the contents there. + ?match([], mnesia_test_lib:start_mnesia([N1], [test_rec])), + ?match( {atomic,[#test_rec{key=3,val=33}]}, + rpc:call(N1,mnesia,transaction,[Read_one])), + %%Check that the start of Mnesai on N1 did not affect the contents on N2 + ?match( {atomic,[#test_rec{key=3,val=33}]}, + rpc:call(N2,mnesia,transaction,[Read_one])), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +late_load_when_all_are_ram_copies_on_ram_nodes(doc) -> + ["Load of ram_copies tables when all replicas resides on disc less nodes"]; +late_load_when_all_are_ram_copies_on_ram_nodes(suite) -> + [ + late_load_when_all_are_ram_copies_on_ram_nodes1, + late_load_when_all_are_ram_copies_on_ram_nodes2 + ]. + +late_load_when_all_are_ram_copies_on_ram_nodes1(suite) -> []; +late_load_when_all_are_ram_copies_on_ram_nodes1(Config) when is_list(Config) -> + [N1, N2] = mnesia_test_lib:prepare_test_case([{init_test_case, [mnesia]}, + delete_schema, + {reload_appls, [mnesia]}], + 2, Config, ?FILE, ?LINE), + Res = late_load_when_all_are_ram_copies_on_ram_nodes(N1, [N2], Config), + mnesia_test_lib:prepare_test_case([{reload_appls, [mnesia]}], + 2, Config, ?FILE, ?LINE), + Res. + +late_load_when_all_are_ram_copies_on_ram_nodes2(suite) -> []; +late_load_when_all_are_ram_copies_on_ram_nodes2(Config) when is_list(Config) -> + [N1, N2, N3] = mnesia_test_lib:prepare_test_case([{init_test_case, [mnesia]}, + delete_schema, + {reload_appls, [mnesia]}], + 3, Config, ?FILE, ?LINE), + Res = late_load_when_all_are_ram_copies_on_ram_nodes(N1, [N2, N3], Config), + mnesia_test_lib:prepare_test_case([{reload_appls, [mnesia]}], + 3, Config, ?FILE, ?LINE), + Res. + +late_load_when_all_are_ram_copies_on_ram_nodes(DiscNode, RamNs, _Config) + when DiscNode == node() -> + ?match(ok, mnesia:create_schema([DiscNode])), + ?match(ok, mnesia:start()), + Nodes = [DiscNode | RamNs], + Extra = [{extra_db_nodes, Nodes}], + Ok = [ok || _ <- RamNs], + ?match({Ok, []}, rpc:multicall(RamNs, mnesia, start, [Extra])), + ?match([], wait_until_running(Nodes)), + + LastRam = lists:last(RamNs), + %% ?match({atomic, ok}, + %% mnesia:add_table_copy(schema, LastRam, ram_copies)), + Def = [{ram_copies, RamNs}, {attributes, record_info(fields, test_rec)}], + ?match({atomic,ok}, mnesia:create_table(test_rec, Def)), + ?verify_mnesia(Nodes, []), + ?match([], mnesia_test_lib:stop_mnesia(RamNs)), + ?match(stopped, mnesia:stop()), + ?match(ok, mnesia:start()), + + Rec1 = #test_rec{key=3, val=33}, + Rec2 = #test_rec{key=4, val=44}, + + FirstRam = hd(RamNs), + ?match(ok, rpc:call(FirstRam, mnesia, start, [Extra])), + ?match(ok, rpc:call(FirstRam, mnesia, wait_for_tables, + [[test_rec], 30000])), + ?match(ok, rpc:call(FirstRam, mnesia, dirty_write,[Rec1])), + ?match(ok, mnesia:wait_for_tables([test_rec], 30000)), + mnesia:dirty_write(Rec2), + + if + FirstRam /= LastRam -> + ?match(ok, rpc:call(LastRam, mnesia, start, [Extra])), + ?match(ok, rpc:call(LastRam, mnesia, wait_for_tables, + [[test_rec], 30000])); + true -> + ignore + end, + ?match([Rec1], rpc:call(LastRam, mnesia, dirty_read, [{test_rec, 3}])), + ?match([Rec2], rpc:call(LastRam, mnesia, dirty_read, [{test_rec, 4}])), + ?verify_mnesia(Nodes, []). + +wait_until_running(Nodes) -> + wait_until_running(Nodes, 30). + +wait_until_running(Nodes, Times) when Times > 0-> + Alive = mnesia:system_info(running_db_nodes), + case Nodes -- Alive of + [] -> + []; + Remaining -> + timer:sleep(timer:seconds(1)), + wait_until_running(Remaining, Times - 1) + end; +wait_until_running(Nodes, _) -> + Nodes. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +load_when_last_replica_becomes_available(doc) -> + ["Check that when all Mnesia nodes die at the same instant, then the ", + "replicated table shall be accessible when the last node is started ", + "again.", + "Checked by cheating. Start Mnesia on N1, N2, N3. Have a table ", + "replicated on disc on all three nodes, fill in some transactions, ", + "install a fallback. Restart mnesia on all nodes" + "This is the cheat and it simulates that all nodes died at the same ", + "time. Check that the table is only accessible after the last node ", + "has come up."]; +load_when_last_replica_becomes_available(suite) -> []; +load_when_last_replica_becomes_available(Config) when is_list(Config) -> + [N1, N2, N3] = Nodes = ?acquire_nodes(3, Config), + ?match({atomic,ok}, + mnesia:create_table(test_rec, + [{disc_copies,Nodes}, + {attributes,record_info(fields,test_rec)}] + ) ), + ?match( [], mnesia:table_info(test_rec,ram_copies) ), + ?match( Nodes, mnesia:table_info(test_rec,disc_copies) ), + ?match( [], mnesia:table_info(test_rec,disc_only_copies) ), + Write_one = fun(Key,Val)->mnesia:write(#test_rec{key=Key,val=Val}) end, + Read_one = fun(Key) ->mnesia:read( {test_rec, Key}) end, + %%Write one value from each node. + ?match({atomic,ok},rpc:call(N1,mnesia,transaction,[Write_one,[1,11]])), + ?match({atomic,ok},rpc:call(N2,mnesia,transaction,[Write_one,[2,22]])), + ?match({atomic,ok},rpc:call(N3,mnesia,transaction,[Write_one,[3,33]])), + %%Check the values + ?match({atomic,[#test_rec{key=1,val=11}]}, + rpc:call(N2,mnesia,transaction,[Read_one,[1]]) ), + ?match({atomic,[#test_rec{key=2,val=22}]}, + rpc:call(N3,mnesia,transaction,[Read_one,[2]]) ), + ?match({atomic,[#test_rec{key=3,val=33}]}, + rpc:call(N1,mnesia,transaction,[Read_one,[3]]) ), + + ?match(ok, mnesia:backup("test_last_replica")), + ?match(ok, mnesia:install_fallback("test_last_replica")), + file:delete("test_last_replica"), + %%Stop Mnesia on all three nodes + ?match([], mnesia_test_lib:kill_mnesia(Nodes)), + + %%Start Mnesia on one node, make sure that test_rec is not available + ?match(ok, rpc:call(N2, mnesia, start, [])), + ?match({timeout,[test_rec]}, + rpc:call(N2, mnesia, wait_for_tables, [[test_rec], 10000])), + ?match(ok, rpc:call(N1, mnesia, start, [])), + ?match({timeout,[test_rec]}, + rpc:call(N1, mnesia, wait_for_tables, [[test_rec], 10000])), + %%Start the third node + ?match(ok, rpc:call(N3, mnesia, start, [])), + %%Make sure that the table is loaded everywhere + ?match(ok, rpc:call(N3, mnesia, wait_for_tables, [[test_rec], 30000])), + ?match(ok, rpc:call(N2, mnesia, wait_for_tables, [[test_rec], 30000])), + ?match(ok, rpc:call(N1, mnesia, wait_for_tables, [[test_rec], 30000])), + + %%Check the values + ?match({atomic,[#test_rec{key=1,val=11}]}, + rpc:call(N2,mnesia,transaction,[Read_one,[1]]) ), + ?match({atomic,[#test_rec{key=2,val=22}]}, + rpc:call(N3,mnesia,transaction,[Read_one,[2]]) ), + ?match({atomic,[#test_rec{key=3,val=33}]}, + rpc:call(N1,mnesia,transaction,[Read_one,[3]]) ), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +load_when_we_have_down_from_all_other_replica_nodes(doc) -> + ["The table can be loaded if this node was the last one surviving. ", + "Check this by having N1, N2, N3 and a table replicated on all those ", + "nodes. Then kill them in the N1, N2, N3 order. Then start N3 and ", + "verify that the table is available with correct contents."]; +load_when_we_have_down_from_all_other_replica_nodes(suite) -> []; +load_when_we_have_down_from_all_other_replica_nodes(Config) when is_list(Config) -> + [N1, N2, N3] = Nodes = ?acquire_nodes(3, Config), + ?match({atomic,ok}, + mnesia:create_table(test_rec, + [{disc_copies,Nodes}, + {attributes,record_info(fields,test_rec)}] + ) ), + ?match( [], mnesia:table_info(test_rec,ram_copies) ), + ?match( Nodes, mnesia:table_info(test_rec,disc_copies) ), + ?match( [], mnesia:table_info(test_rec,disc_only_copies) ), + Write_one = fun(Key,Val)->mnesia:write(#test_rec{key=Key,val=Val}) end, + Read_one = fun(Key) ->mnesia:read( {test_rec, Key}) end, + %%Write one value from each node. + ?match({atomic,ok},rpc:call(N1,mnesia,transaction,[Write_one,[1,111]])), + ?match({atomic,ok},rpc:call(N2,mnesia,transaction,[Write_one,[2,222]])), + ?match({atomic,ok},rpc:call(N3,mnesia,transaction,[Write_one,[3,333]])), + %%Check the values + ?match({atomic,[#test_rec{key=1,val=111}]}, + rpc:call(N2,mnesia,transaction,[Read_one,[1]]) ), + ?match({atomic,[#test_rec{key=2,val=222}]}, + rpc:call(N3,mnesia,transaction,[Read_one,[2]]) ), + ?match({atomic,[#test_rec{key=3,val=333}]}, + rpc:call(N1,mnesia,transaction,[Read_one,[3]]) ), + %%Stop Mnesia on all three nodes + ?match([], mnesia_test_lib:kill_mnesia([N1])), + ?match({atomic,ok},rpc:call(N2,mnesia,transaction,[Write_one,[22,22]])), + ?match([], mnesia_test_lib:kill_mnesia([N2])), + ?match({atomic,ok},rpc:call(N3,mnesia,transaction,[Write_one,[33,33]])), + ?match([], mnesia_test_lib:kill_mnesia([N3])), + ?verbose("Mnesia stoped on all three nodes.~n",[]), + + %%Start Mnesia on N3; wait for 'test_rec' table to load + ?match(ok, rpc:call(N3, mnesia, start, [])), + ?match(ok, rpc:call(N3, mnesia, wait_for_tables, [[test_rec], 30000])), + + %%Check the values + ?match({atomic,[#test_rec{key=1,val=111}]}, + rpc:call(N3,mnesia,transaction,[Read_one,[1]]) ), + ?match({atomic,[#test_rec{key=2,val=222}]}, + rpc:call(N3,mnesia,transaction,[Read_one,[2]]) ), + ?match({atomic,[#test_rec{key=3,val=333}]}, + rpc:call(N3,mnesia,transaction,[Read_one,[3]]) ), + ?match({atomic,[#test_rec{key=22,val=22}]}, + rpc:call(N3,mnesia,transaction,[Read_one,[22]]) ), + ?match({atomic,[#test_rec{key=33,val=33}]}, + rpc:call(N3,mnesia,transaction,[Read_one,[33]]) ), + ?verify_mnesia([N3], [N1, N2]). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +late_load_transforms_into_disc_load(doc) -> + ["Difficult case that needs instrumentation of Mnesia.", + "A table is force loaded, and Mnesia decides to load it from another ", + "Mnesia node because it is avaliable there. The other Mnesia node then ", + "dies in mid copy which shall make the first Mnesia node to really ", + "force load from disc.", + "Check this by starting N1 and N2 and replicating a table between ", + "them. Then kill N1 before N2. The idea is to start N2 first, then ", + "N1 and then do a force load on N1. This force load will load from ", + "N2 BUT N2 must be killed after the decision to load from it has ", + "been made. tricky."]; + +late_load_transforms_into_disc_load(suite) -> []; +late_load_transforms_into_disc_load(Config) when is_list(Config) -> + ?is_debug_compiled, + + [Node1, Node2] = Nodes = ?acquire_nodes(2, Config), + + {success, [A, B]} = ?start_activities(Nodes), + + ?match(Node1, node(A)), + ?match(Node2, node(B)), + + Tab = late_load_table, + Def = [{attributes, [key, value]}, + {disc_copies, Nodes}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + ?match(ok, mnesia:dirty_write({Tab, 111, 4711})), + ?match(ok, mnesia:dirty_write({Tab, 222, 42})), + + TestPid = self(), + DebugId = {mnesia_loader, do_get_network_copy}, + DebugFun = fun(PrevContext, EvalContext) -> + ?verbose("interrupt late load, pid ~p #~p ~n context ~p ~n", + [self(),PrevContext,EvalContext]), + + mnesia_test_lib:kill_mnesia([Node2]), + TestPid ! {self(),debug_fun_was_called}, + + ?verbose("interrupt late_log - continues ~n",[]), + ?deactivate_debug_fun(DebugId), + PrevContext+1 + end, + ?remote_activate_debug_fun(Node1,DebugId, DebugFun, 1), + + %% kill mnesia on node1 + mnesia_test_lib:kill_mnesia([Node1]), + %% wait a while, so that mnesia is really down + timer:sleep(timer:seconds(1)), + + ?match(ok, rpc:call(Node2, mnesia, dirty_write, [{Tab, 222, 815}])), + + %% start Mnesia on node1 + ?match(ok,mnesia:start()), + ?match(yes, mnesia:force_load_table(Tab)), + ?match(ok, mnesia:wait_for_tables([Tab],timer:seconds(30))), + + receive_messages([debug_fun_was_called]), + + check_tables([A],[{Tab,111},{Tab,222}],[[{Tab,111,4711}],[{Tab,222,42}]]), + ?verify_mnesia([Node1], [Node2]). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +late_load_leads_to_hanging(doc) -> + ["Difficult case that needs instrumentation of Mnesia.", + "A table is loaded, and Mnesia decides to load it from another ", + "Mnesia node because it has the latest copy there. ", + "The other Mnesia node then ", + "dies in mid copy which shall make the first Mnesia node not to ", + "force load from disc but to wait for the other node to come up again", + "Check this by starting N1 and N2 and replicating a table between ", + "them. Then kill N1 before N2. The idea is to start N2 first, then ", + "N1. This load will load from ", + "N2 BUT N2 must be killed after the decision to load from it has ", + "been made. tricky."]; + +late_load_leads_to_hanging(suite) -> []; +late_load_leads_to_hanging(Config) when is_list(Config) -> + ?is_debug_compiled, + + [Node1, Node2] = Nodes = ?acquire_nodes(2, Config), + + Tab = late_load_table, + Def = [{attributes, [key, value]}, + {disc_copies, Nodes}], + + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + ?match(ok, mnesia:dirty_write({Tab, 111, 4711})), + ?match(ok, mnesia:dirty_write({Tab, 222, 42})), + + DebugId = {mnesia_loader, do_get_network_copy}, + DebugFun = fun(PrevContext, EvalContext) -> + ?verbose("interrupt late load, pid ~p #~p ~n context ~p ~n", + [self(), PrevContext, EvalContext]), + mnesia_test_lib:kill_mnesia([Node2]), + ?verbose("interrupt late load - continues ~n",[]), + ?deactivate_debug_fun(DebugId), + PrevContext+1 + end, + + ?remote_activate_debug_fun(Node1,DebugId, DebugFun, 1), + mnesia_test_lib:kill_mnesia([Node1]), + %% wait a while, so that mnesia is really down + timer:sleep(timer:seconds(1)), + + ?match(ok, rpc:call(Node2, mnesia, dirty_write, [{Tab, 333, 666}])), + + %% start Mnesia on node1 + ?match(ok, mnesia:start()), + + ?match({timeout, [Tab]}, mnesia:wait_for_tables([Tab], timer:seconds(2))), + + ?match({'EXIT', {aborted, _}}, mnesia:dirty_read({Tab, 222})), + %% mnesia on node1 is waiting for node2 coming up + + ?match(ok, rpc:call(Node2, mnesia, start, [])), + ?match(ok, mnesia:wait_for_tables([Tab], timer:seconds(30))), + ?match([{Tab, 333, 666}], mnesia:dirty_read({Tab, 333})), + ?verify_mnesia([Node2, Node1], []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +force_load_when_nobody_intents_to_load(doc) -> + ["Normal force load. Start N1 N2, kill in N1, N2 order. Start N1 do ", + "force load. Did it work?"]; +force_load_when_nobody_intents_to_load(suite) -> []; +force_load_when_nobody_intents_to_load(Config) when is_list(Config) -> + [N1, N2] = Nodes = ?acquire_nodes(2, Config), + Table = test_rec, + Trec1a = #test_rec{key=1,val=111}, + Trec1b = #test_rec{key=1,val=333}, + Trec2a = #test_rec{key=2,val=222}, + Trec3a = #test_rec{key=3,val=333}, + Trec3b = #test_rec{key=3,val=666}, + + ?match({atomic,ok}, rpc:call(N1, mnesia,create_table, + [Table, + [{disc_copies,Nodes}, + {attributes,record_info(fields,test_rec)} + ] ] ) ), + ?match( [], mnesia:table_info(Table,ram_copies) ), + ?match( Nodes, mnesia:table_info(Table,disc_copies) ), + ?match( [], mnesia:table_info(Table,disc_only_copies) ), + Write_one = fun(Rec) -> mnesia:write(Rec) end, + Read_one = fun(Key) -> mnesia:read({Table, Key}) end, + %%Write one value + ?match({atomic,ok},rpc:call(N1,mnesia,transaction,[Write_one,[Trec1a]])), + %%Check it + ?match({atomic,[Trec1a]},rpc:call(N2,mnesia,transaction,[Read_one,[1]]) ), + %%Shut down mnesia on N1 + ?match([], mnesia_test_lib:stop_mnesia([N1])), + %%Write and check value while N1 is down + ?match({atomic,ok},rpc:call(N2,mnesia,transaction,[Write_one,[Trec1b]])), + ?match({atomic,ok},rpc:call(N2,mnesia,transaction,[Write_one,[Trec2a]])), + ?match({atomic,ok},rpc:call(N2,mnesia,transaction,[Write_one,[Trec3a]])), + ?match({aborted,{node_not_running,N1}}, + rpc:call(N1,mnesia,transaction,[Read_one,[2]]) ), + ?match({atomic,[Trec1b]},rpc:call(N2,mnesia,transaction,[Read_one,[1]]) ), + ?match({atomic,[Trec2a]},rpc:call(N2,mnesia,transaction,[Read_one,[2]]) ), + ?match({atomic,[Trec3a]},rpc:call(N2,mnesia,transaction,[Read_one,[3]]) ), + %%Shut down Mnesia on N2 + ?match([], mnesia_test_lib:stop_mnesia([N2])), + + %%Restart Mnesia on N1 + ?match(ok, rpc:call(N1, mnesia, start, [])), + %%Check that table is not available (waiting for N2) + ?match({timeout,[Table]}, + rpc:call(N1, mnesia, wait_for_tables, [[Table], 3000])), + + %%Force load on N1 + ?match(yes,rpc:call(N1,mnesia,force_load_table,[Table])), + %%Check values + ?match({atomic,[Trec1a]},rpc:call(N1,mnesia,transaction,[Read_one,[1]]) ), + ?match({atomic,[]}, rpc:call(N1,mnesia,transaction,[Read_one,[2]]) ), + ?match({atomic,[]}, rpc:call(N1,mnesia,transaction,[Read_one,[3]]) ), + %%Write a value for key=3 + ?match({atomic,ok},rpc:call(N1,mnesia,transaction,[Write_one,[Trec3b]])), + + %%Restart N2 and check values + ?match(ok, rpc:call(N2, mnesia, start, [])), + ?match(ok, rpc:call(N2, mnesia, wait_for_tables, [[Table], 30000])), + + ?match({atomic,[Trec1a]},rpc:call(N1,mnesia,transaction,[Read_one,[1]]) ), + ?match({atomic,[Trec1a]},rpc:call(N2,mnesia,transaction,[Read_one,[1]]) ), + + ?match({atomic,[]},rpc:call(N1,mnesia,transaction,[Read_one,[2]]) ), + ?match({atomic,[]},rpc:call(N2,mnesia,transaction,[Read_one,[2]]) ), + + ?match({atomic,[Trec3b]},rpc:call(N1,mnesia,transaction,[Read_one,[3]]) ), + ?match({atomic,[Trec3b]},rpc:call(N2,mnesia,transaction,[Read_one,[3]]) ), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +force_load_when_someone_has_decided_to_load(doc) -> + ["Difficult case that needs instrumentation of Mnesia.", + "Start N1 and N2, replicate table, kill in N1, N2 order. Start N2 ", + "and start N1 before N2 has really loaded the table but after N2 has ", + "decided to load it."]; + +force_load_when_someone_has_decided_to_load(suite) -> []; +force_load_when_someone_has_decided_to_load(Config) when is_list(Config) -> + ?is_debug_compiled, + + [Node1, Node2] = Nodes = ?acquire_nodes(2, Config), + {success, [A, B]} = ?start_activities(Nodes), + ?match(Node1, node(A)), %% Just to check :) + ?match(Node2, node(B)), + + Tab = late_load_table, + Def = [{attributes, [key, value]}, {disc_copies, Nodes}], + + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + ?match(ok, mnesia:dirty_write({Tab, 111, 4711})), + ?match(ok, mnesia:dirty_write({Tab, 222, 42})), + + Self = self(), + DebugId = {mnesia_controller, late_disc_load}, + DebugFun = fun(PrevContext, EvalContext) -> + ?verbose("interrupt late disc load, + pid ~p #~p ~n context ~p ~n", + [self(),PrevContext,EvalContext]), + Self ! {self(), fun_in_postion}, + wait_for_signal(), + ?verbose("interrupt late disc load - continues ~n",[]), + ?deactivate_debug_fun(DebugId), + PrevContext+1 + end, + + %% kill mnesia on node1 + mnesia_test_lib:kill_mnesia([Node1]), + %% wait a while, so that mnesia is really down + timer:sleep(timer:seconds(1)), + + ?match(ok, rpc:call(Node2, mnesia, dirty_write, [{Tab, 222, 815}])), + %% kill mnesia on node2 + mnesia_test_lib:kill_mnesia([Node2]), + %% wait a while, so that mnesia is really down + timer:sleep(timer:seconds(1)), + + ?remote_activate_debug_fun(Node2,DebugId, DebugFun, 1), + + B ! fun() -> mnesia:start() end, + [{Mnesia_Pid, fun_in_postion}] = receive_messages([fun_in_postion]), + + %% start Mnesia on node1 + A ! fun() -> mnesia:start() end, + ?match_receive(timeout), +% Got some problem with this testcase when we modified mnesia init +% These test cases are very implementation dependent! +% A ! fun() -> mnesia:wait_for_tables([Tab], 3000) end, +% ?match_receive({A, {timeout, [Tab]}}), + A ! fun() -> mnesia:force_load_table(Tab) end, + ?match_receive(timeout), + + Mnesia_Pid ! continue, + ?match_receive({B, ok}), + ?match_receive({A, ok}), + ?match_receive({A, yes}), + + B ! fun() -> mnesia:wait_for_tables([Tab], 10000) end, + ?match_receive({B, ok}), + ?match(ok, mnesia:wait_for_tables([Tab], timer:seconds(30))), + ?match([{Tab, 222, 815}], mnesia:dirty_read({Tab, 222})), + ?verify_mnesia(Nodes, []). + +wait_for_signal() -> + receive + continue -> ok + %% Don't eat any other mnesia internal msg's + after + timer:minutes(2) -> ?error("Timedout in wait_for_signal~n", []) + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +force_load_when_someone_else_already_has_loaded(doc) -> + ["Normal case. Do a force load when somebody else has loaded the table. ", + "Start N1, N2, kill in N1, N2 order. Start N2 load the table, start N1 ", + "force load. Did it work? (i.e: did N1 load the table from N2 as that", + "one is the latest version and it is available on N2)"]; + +force_load_when_someone_else_already_has_loaded(suite) -> []; +force_load_when_someone_else_already_has_loaded(Config) when is_list(Config) -> + [N1, N2] = Nodes = ?acquire_nodes(2, Config), + Table = test_rec, + Trec1 = #test_rec{key=1,val=111}, + Trec2 = #test_rec{key=1,val=222}, + + ?match({atomic,ok}, rpc:call(N1, mnesia,create_table, + [Table, + [{disc_copies,Nodes}, + {attributes,record_info(fields,test_rec)} + ] ] ) ), + ?match( [], mnesia:table_info(Table,ram_copies) ), + ?match( Nodes, mnesia:table_info(Table,disc_copies) ), + ?match( [], mnesia:table_info(Table,disc_only_copies) ), + Write_one = fun(Rec) -> mnesia:write(Rec) end, + Read_one = fun(Key) -> mnesia:read({Table, Key}) end, + %%Write one value + ?match({atomic,ok},rpc:call(N1,mnesia,transaction,[Write_one,[Trec1]])), + %%Check it + ?match({atomic,[Trec1]},rpc:call(N2,mnesia,transaction,[Read_one,[1]]) ), + %%Shut down mnesia + ?match([], mnesia_test_lib:stop_mnesia([N1])), + timer:sleep(500), + ?match([], mnesia_test_lib:stop_mnesia([N2])), + %%Restart Mnesia on N2;wait for tables to load + ?match(ok, rpc:call(N2, mnesia, start, [])), + ?match(ok, rpc:call(N2, mnesia, wait_for_tables, [[test_rec], 30000])), + %%Write one value + ?match({atomic,ok},rpc:call(N2,mnesia,transaction,[Write_one,[Trec2]])), + %%Start on N1; force load + ?match(ok, rpc:call(N1, mnesia, start, [])), + %%Force load from file + ?match(yes, rpc:call(N1,mnesia,force_load_table,[Table])), + %%Check the value + ?match({atomic,[Trec2]},rpc:call(N1,mnesia,transaction,[Read_one,[1]]) ), + %% === there must be a Trec2 here !!!! + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +force_load_when_we_has_loaded(doc) -> + ["Force load a table we already have loaded"]; +force_load_when_we_has_loaded(suite) -> []; +force_load_when_we_has_loaded(Config) when is_list(Config) -> + [N1] = Nodes = ?acquire_nodes(1, Config), + Table = test_rec, + Trec1 = #test_rec{key=1,val=111}, + Trec2 = #test_rec{key=1,val=222}, + + ?match({atomic,ok}, rpc:call(N1, mnesia,create_table, + [Table, + [{disc_copies,Nodes}, + {attributes,record_info(fields,test_rec)} + ] ] ) ), + ?match( [], mnesia:table_info(Table,ram_copies) ), + ?match( Nodes, mnesia:table_info(Table,disc_copies) ), + ?match( [], mnesia:table_info(Table,disc_only_copies) ), + Write_one = fun(Rec) -> mnesia:write(Rec) end, + Read_one = fun(Key) -> mnesia:read({Table, Key}) end, + %%Write one value + ?match({atomic,ok},rpc:call(N1,mnesia,transaction,[Write_one,[Trec1]])), + %%Check it + ?match({atomic,[Trec1]},rpc:call(N1,mnesia,transaction,[Read_one,[1]]) ), + %%Shut down mnesia + ?match([], mnesia_test_lib:stop_mnesia(Nodes)), + %%Restart Mnesia;wait for tables to load + ?match([], mnesia_test_lib:start_mnesia(Nodes, [Table])), + %%Write one value + ?match({atomic,ok},rpc:call(N1,mnesia,transaction,[Write_one,[Trec2]])), + %%Force load from file + ?match(yes, rpc:call(N1,mnesia,force_load_table,[Table])), + %%Check the value + ?match({atomic,[Trec2]},rpc:call(N1,mnesia,transaction,[Read_one,[1]]) ), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +force_load_on_a_non_local_table(doc) -> + ["This is NOT allowed, the test case is a negative test", + "Force load on a table that isn't replicated on this node."]; +force_load_on_a_non_local_table(suite) -> []; +force_load_on_a_non_local_table(Config) when is_list(Config) -> + [N1, N2, N3] = Nodes = ?acquire_nodes( 3, Config), + TableNodes = lists:sublist(Nodes,2), + Table = test_rec, + Trec1 = #test_rec{key=1,val=11}, + + ?match({atomic,ok}, rpc:call(N1, mnesia,create_table, + [Table, + [{disc_copies,TableNodes}, + {attributes,record_info(fields,test_rec)} + ] ] ) ), + ?match( [], mnesia:table_info(Table,ram_copies) ), + ?match( TableNodes, mnesia:table_info(Table,disc_copies) ), + ?match( [], mnesia:table_info(Table,disc_only_copies) ), + Write_one = fun(Rec) -> mnesia:write(Rec) end, + Read_one = fun(Key) -> mnesia:read({Table, Key}) end, + %%Write one value + ?match({atomic,ok},rpc:call(N1,mnesia,transaction,[Write_one,[Trec1]])), + %%Check it from the other nodes + ?match({atomic,[Trec1]},rpc:call(N2,mnesia,transaction,[Read_one,[1]]) ), + ?match({atomic,[Trec1]},rpc:call(N3,mnesia,transaction,[Read_one,[1]]) ), + + %%Make sure that Table is non-local + ?match_inverse(N3, rpc:call(N3,mnesia,table_info,[Table,where_to_read])), + %%Try to force load it + ?match(yes, rpc:call(N3,mnesia,force_load_table,[Table])), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +force_load_when_the_table_does_not_exist(doc) -> + ["This is NOT allowed, the test case is a negative test", + "Force load on a table that doesn't exist."]; +force_load_when_the_table_does_not_exist(suite) -> []; +force_load_when_the_table_does_not_exist(Config) when is_list(Config) -> + Nodes = ?acquire_nodes( 2, Config), + + %%Dummy table + ?match({atomic,ok}, + mnesia:create_table(test_rec, + [{disc_copies,Nodes}, + {attributes,record_info(fields,test_rec)}] + ) ), + ?match( [], mnesia:table_info(test_rec,ram_copies) ), + ?match( Nodes, mnesia:table_info(test_rec,disc_copies) ), + ?match( [], mnesia:table_info(test_rec,disc_only_copies) ), + Tab = dummy, + %%Make sure that Tab is an unknown table + ?match( false, lists:member(Tab,mnesia:system_info(tables)) ), + ?match( {error, {no_exists, Tab}}, mnesia:force_load_table(Tab) ), + ?verify_mnesia(Nodes, []). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +load_tables_with_master_tables(doc) -> + ["Verifies the semantics of different master nodes settings", + "The semantics should be:", + "1. Mnesia downs, Normally decides from where mnesia should load tables", + "2. Master tables (overrides mnesia downs) ", + "3. Force load (overrides Master tables) ", + "--- 1st from active master nodes", + "--- 2nd from active nodes", + "--- 3rd get local copy (if ram create new one)" + ]; + +load_tables_with_master_tables(suite) -> + [master_nodes, + starting_master_nodes, + master_on_non_local_tables, + remote_force_load_with_local_master_node]. + + +-define(SDwrite(Tup), fun() -> mnesia:write(Tup) end). + +master_nodes(suite) -> []; +master_nodes(Config) when is_list(Config) -> + [A, B, C] = Nodes = ?acquire_nodes(3, Config), + Tab = test_table_master_nodes, + ?match({atomic,ok}, mnesia:create_table(Tab, [{disc_copies, Nodes}])), + + %% Test one: Master A and the table should be loaded from A + + ?match(ok, rpc:call(A, mnesia, set_master_nodes, [Tab, [A]])), + ?match({atomic, ok}, mnesia:sync_transaction(?SDwrite({Tab, 1, init}))), + + mnesia_test_lib:stop_mnesia([A]), + ?match({atomic, ok}, rpc:call(B, mnesia, sync_transaction, [?SDwrite({Tab, 1, updated})])), + ?match(ok, rpc:call(A, mnesia, start, [])), + ?match(ok, rpc:call(A, mnesia, wait_for_tables, [[Tab], 3000])), + + ?match([{Tab, 1, init}], rpc:call(A, mnesia, dirty_read, [{Tab, 1}])), + ?match([{Tab, 1, updated}], rpc:call(B, mnesia, dirty_read, [{Tab, 1}])), + ?match([{Tab, 1, updated}], rpc:call(C, mnesia, dirty_read, [{Tab, 1}])), + + %% Test 2: Master [A,B] and B is Up the table should be loaded from B + + ?match(ok, rpc:call(A, mnesia, set_master_nodes, [Tab, [A, B]])), + ?match({atomic, ok}, mnesia:sync_transaction(?SDwrite({Tab, 1, init}))), + + mnesia_test_lib:stop_mnesia([A]), + ?match({atomic, ok}, rpc:call(B, mnesia, sync_transaction, [?SDwrite({Tab, 1, updated})])), + ?match(ok, rpc:call(A, mnesia, start, [])), + ?match(ok, rpc:call(A, mnesia, wait_for_tables, [[Tab], 3000])), + + ?match([{Tab, 1, updated}], rpc:call(A, mnesia, dirty_read, [{Tab, 1}])), + ?match([{Tab, 1, updated}], rpc:call(B, mnesia, dirty_read, [{Tab, 1}])), + ?match([{Tab, 1, updated}], rpc:call(C, mnesia, dirty_read, [{Tab, 1}])), + + %% Test 3: Master [A,B] and B is down the table should be loaded from A + + ?match(ok, rpc:call(A, mnesia, set_master_nodes, [Tab, [A, B]])), + ?match({atomic, ok}, mnesia:sync_transaction(?SDwrite({Tab, 1, init}))), + + mnesia_test_lib:stop_mnesia([A]), + ?match({atomic, ok}, rpc:call(B, mnesia, sync_transaction, [?SDwrite({Tab, 1, updated})])), + mnesia_test_lib:stop_mnesia([B]), + ?match(ok, rpc:call(A, mnesia, start, [])), + ?match(ok, rpc:call(A, mnesia, wait_for_tables, [[Tab], 3000])), + + ?match(ok, rpc:call(B, mnesia, start, [])), + ?match(ok, rpc:call(B, mnesia, wait_for_tables, [[Tab], 3000])), + + ?match([{Tab, 1, init}], rpc:call(A, mnesia, dirty_read, [{Tab, 1}])), + ?match([{Tab, 1, _Unknown}], rpc:call(B, mnesia, dirty_read, [{Tab, 1}])), + ?match([{Tab, 1, updated}], rpc:call(C, mnesia, dirty_read, [{Tab, 1}])), + + %% Test 4: Master [B] and B is Up the table should be loaded from B + + ?match(ok, rpc:call(A, mnesia, set_master_nodes, [Tab, [B]])), + ?match({atomic, ok}, mnesia:sync_transaction(?SDwrite({Tab, 1, init}))), + + mnesia_test_lib:stop_mnesia([A]), + ?match({atomic, ok}, rpc:call(B, mnesia, sync_transaction, [?SDwrite({Tab, 1, updated})])), + ?match(ok, rpc:call(A, mnesia, start, [])), + ?match(ok, rpc:call(A, mnesia, wait_for_tables, [[Tab], 3000])), + + ?match([{Tab, 1, updated}], rpc:call(A, mnesia, dirty_read, [{Tab, 1}])), + ?match([{Tab, 1, updated}], rpc:call(B, mnesia, dirty_read, [{Tab, 1}])), + ?match([{Tab, 1, updated}], rpc:call(C, mnesia, dirty_read, [{Tab, 1}])), + + %% Test 5: Master [B] and B is down the table should not be loaded + + ?match(ok, rpc:call(A, mnesia, set_master_nodes, [Tab, [B]])), + ?match({atomic, ok}, mnesia:sync_transaction(?SDwrite({Tab, 1, init}))), + + mnesia_test_lib:stop_mnesia([A]), + ?match({atomic, ok}, rpc:call(B, mnesia, sync_transaction, [?SDwrite({Tab, 1, updated})])), + mnesia_test_lib:stop_mnesia([B]), + ?match({atomic, ok}, rpc:call(C, mnesia, sync_transaction, [?SDwrite({Tab, 1, update_2})])), + ?match(ok, rpc:call(A, mnesia, start, [])), + ?match({timeout, [Tab]}, rpc:call(A, mnesia, wait_for_tables, [[Tab], 2000])), + + %% Test 6: Force load on table that couldn't be loaded due to master + %% table setttings, loads other active replicas i.e. from C + + ?match(yes, rpc:call(A, mnesia, force_load_table, [Tab])), + ?match(ok, rpc:call(A, mnesia, wait_for_tables, [[Tab], 3000])), + + ?match(ok, rpc:call(B, mnesia, start, [])), + ?match(ok, rpc:call(B, mnesia, wait_for_tables, [[Tab], 3000])), + + ?match([{Tab, 1, update_2}], rpc:call(A, mnesia, dirty_read, [{Tab, 1}])), + ?match([{Tab, 1, update_2}], rpc:call(B, mnesia, dirty_read, [{Tab, 1}])), + ?match([{Tab, 1, update_2}], rpc:call(C, mnesia, dirty_read, [{Tab, 1}])), + + %% Test 7: Master [B] and B is down the table should not be loaded, + %% force_load when there are no active replicas availible + %% should generate a load of a local table + + ?match(ok, rpc:call(A, mnesia, set_master_nodes, [Tab, [B]])), + ?match({atomic, ok}, mnesia:sync_transaction(?SDwrite({Tab, 1, init}))), + + mnesia_test_lib:stop_mnesia([A]), + ?match({atomic, ok}, rpc:call(B, mnesia, sync_transaction, [?SDwrite({Tab, 1, updated})])), + mnesia_test_lib:stop_mnesia([B, C]), + ?match(ok, rpc:call(A, mnesia, start, [])), + ?match({timeout, [Tab]}, rpc:call(A, mnesia, wait_for_tables, [[Tab], 2000])), + + ?match(yes, rpc:call(A, mnesia, force_load_table, [Tab])), + ?match([{Tab, 1, init}], rpc:call(A, mnesia, dirty_read, [{Tab, 1}])), + + ?verify_mnesia([A], [B,C]). + +starting_master_nodes(suite) -> []; +starting_master_nodes(doc) -> + ["Complementory to TEST 5 and 6 above, if the master node (B) starts" + " and loads the table it should be loaded on the waiting node (A) "]; +starting_master_nodes(Config) when is_list(Config) -> + [A, B, C] = Nodes = ?acquire_nodes(3, Config), + Tab = starting_master_nodes, + ?match({atomic,ok}, mnesia:create_table(Tab, [{disc_copies, Nodes}])), + %% Start by checking TEST 5 above. + + ?match(ok, rpc:call(A, mnesia, set_master_nodes, [Tab, [B]])), + ?match({atomic, ok}, mnesia:sync_transaction(?SDwrite({Tab, 1, init}))), + mnesia_test_lib:stop_mnesia([A]), + ?match({atomic, ok}, rpc:call(B, mnesia, sync_transaction, [?SDwrite({Tab, 1, updated})])), + mnesia_test_lib:stop_mnesia([B]), + ?match({atomic, ok}, rpc:call(C, mnesia, sync_transaction, [?SDwrite({Tab, 1, update_2})])), + + ?match(ok, rpc:call(A, mnesia, start, [])), + ?match({timeout, [Tab]}, rpc:call(A, mnesia, wait_for_tables, [[Tab], 2000])), + %% Start the B node and the table should be loaded on A! + ?match(ok, rpc:call(B, mnesia, start, [])), + ?match(ok, rpc:call(B, mnesia, wait_for_tables, [[Tab], 3000])), + ?match(ok, rpc:call(A, mnesia, wait_for_tables, [[Tab], 3000])), + + ?verify_mnesia([A,B,C], []). + + +master_on_non_local_tables(suite) -> []; +master_on_non_local_tables(Config) when is_list(Config) -> + [A, B, C] = Nodes = ?acquire_nodes(3, Config), + Tab = test_table_non_local, + ?match({atomic,ok}, mnesia:create_table(Tab, [{disc_copies, [B, C]}])), + + ?match(ok, rpc:call(A, mnesia, set_master_nodes, [Tab, [B]])), + ?match({atomic, ok}, mnesia:sync_transaction(?SDwrite({Tab, 1, init}))), + + %% Test 1: Test that table info are updated when master node comes up + + mnesia_test_lib:stop_mnesia([A, B]), + ?match({atomic, ok}, rpc:call(C, mnesia, sync_transaction, [?SDwrite({Tab, 1, updated})])), + ?match(ok, rpc:call(A, mnesia, start, [])), + + ?match({timeout, [Tab]}, rpc:call(A, mnesia, wait_for_tables, [[Tab], 2000])), + ErrorRead = {badrpc,{'EXIT', {aborted,{no_exists,[test_table_non_local,1]}}}}, + ErrorWrite = {badrpc,{'EXIT', {aborted,{no_exists,test_table_non_local}}}}, + ?match(ErrorRead, rpc:call(A, mnesia, dirty_read, [{Tab, 1}])), + ?match(ErrorWrite, rpc:call(A, mnesia, dirty_write, [{Tab, 1, updated_twice}])), + + ?match(ok, rpc:call(B, mnesia, start, [])), + ?match(ok, rpc:call(A, mnesia, wait_for_tables, [[Tab], 2000])), + + ?match([{Tab, 1, updated}], rpc:call(A, mnesia, dirty_read, [{Tab, 1}])), + ?match(B, rpc:call(A, mnesia, table_info, [Tab, where_to_read])), + ?match({atomic, ok}, rpc:call(A, mnesia, sync_transaction, [?SDwrite({Tab, 1, init})])), + + %% Test 2: Test that table info are updated after force_load + + mnesia_test_lib:stop_mnesia([A, B]), + ?match({atomic, ok}, rpc:call(C, mnesia, sync_transaction, [?SDwrite({Tab, 1, updated})])), + ?match(ok, rpc:call(A, mnesia, start, [])), + + ?match({timeout, [Tab]}, rpc:call(A, mnesia, wait_for_tables, [[Tab], 2000])), + ?match(yes, rpc:call(A, mnesia, force_load_table, [Tab])), + ?match(C, rpc:call(A, mnesia, table_info, [Tab, where_to_read])), + + ?match([{Tab, 1, updated}], rpc:call(A, mnesia, dirty_read, [{Tab, 1}])), + ?match({atomic, ok}, rpc:call(A, mnesia, sync_transaction, [?SDwrite({Tab, 1, updated_twice})])), + + ?match(ok, rpc:call(B, mnesia, start, [])), + ?match(ok, rpc:call(B, mnesia, wait_for_tables, [[Tab], 10000])), + + ?match([{Tab, 1, updated_twice}], rpc:call(A, mnesia, dirty_read, [{Tab, 1}])), + ?match([{Tab, 1, updated_twice}], rpc:call(B, mnesia, dirty_read, [{Tab, 1}])), + ?match([{Tab, 1, updated_twice}], rpc:call(C, mnesia, dirty_read, [{Tab, 1}])), + + ?verify_mnesia(Nodes, []). + +remote_force_load_with_local_master_node(doc) -> + ["Force load a table on a remote node while the ", + "local node is down. Start the local node and ", + "verfify that the tables is loaded from disc locally " + "if the local node has itself as master node and ", + "the remote node has both the local and remote node ", + "as master nodes"]; +remote_force_load_with_local_master_node(suite) -> []; +remote_force_load_with_local_master_node(Config) when is_list(Config) -> + [A, B] = Nodes = ?acquire_nodes(2, Config), + + Tab = remote_force_load_with_local_master_node, + ?match({atomic,ok}, mnesia:create_table(Tab, [{disc_copies, Nodes}])), + ?match(ok, rpc:call(A, mnesia, set_master_nodes, [Tab, [A, B]])), + ?match(ok, rpc:call(B, mnesia, set_master_nodes, [Tab, [B]])), + + W = fun(Who) -> mnesia:write({Tab, who, Who}) end, + ?match({atomic, ok}, rpc:call(A,mnesia, sync_transaction, [W, [a]])), + ?match(stopped, rpc:call(A, mnesia, stop, [])), + ?match({atomic, ok}, rpc:call(B, mnesia, sync_transaction, [W, [b]])), + ?match(stopped, rpc:call(B, mnesia, stop, [])), + + ?match(ok, rpc:call(A, mnesia, start, [])), + ?match(ok, rpc:call(A, mnesia, wait_for_tables, [[Tab], 3000])), + ?match([{Tab, who, a}], rpc:call(A, mnesia, dirty_read, [{Tab, who}])), + + ?match(ok, rpc:call(B, mnesia, start, [])), + ?match(ok, rpc:call(B, mnesia, wait_for_tables, [[Tab], 3000])), + ?match([{Tab, who, b}], rpc:call(B, mnesia, dirty_read, [{Tab, who}])), + + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +durability_of_dump_tables(doc) -> + [ "Verify that all tables contain the correct data when Mnesia", + "is restarted and tables are loaded from disc to recover", + " their previous contents. " ]; +durability_of_dump_tables(suite) -> [dump_ram_copies, + dump_disc_copies, + dump_disc_only]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +dump_ram_copies(doc) -> + ["Check that ram_copies tables are loaded with the" + "contents that had been dumped before Mnesia", + "was restarted. " ]; +dump_ram_copies(suite) -> []; +dump_ram_copies(Config) when is_list(Config) -> + Nodes = ?acquire_nodes(3, Config), + {success, [P1,P2,P3]} = ?start_activities(Nodes), + + NP1 = node(P1), + NP2 = node(P2), + + {A,B,C} = case node() of + NP1 -> + %?verbose("first case ~n"), + {P3,P2,P1}; + NP2 -> + %?verbose("second case ~n"), + {P3,P1,P2}; + _ -> + {P1,P2,P3} + end, + + Node1 = node(A), + Node2 = node(B), + Node3 = node(C), + + ?verbose(" A pid:~p node:~p ~n",[A,Node1]), + ?verbose(" B pid:~p node:~p ~n",[B,Node2]), + ?verbose(" C pid:~p node:~p ~n",[C,Node3]), + + + %% ram copies table on 2 nodes + + Tab = dump_table, + Def = [{attributes, [key, value]}, + {ram_copies, [Node1,Node2]}], + + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + + ?match(ok, mnesia:dirty_write({Tab, 1, 4711})), + ?match(ok, mnesia:dirty_write({Tab, 2, 42})), + ?match(ok, mnesia:dirty_write({Tab, 3, 256})), + + %% dump the table + + ?match( {atomic,ok}, mnesia:dump_tables([Tab])), + + %% perform updates (they shall be lost after kill Mnesia ) + + ?match(ok, mnesia:dirty_write({Tab, 1, 815})), + ?match(ok, mnesia:dirty_write({Tab, 2, 915})), + + %% add another replica on node3 + mnesia:add_table_copy(Tab,Node3,ram_copies), + + %% all 3 replicas shall have the new contents + cross_check_tables([A,B,C],Tab, + {[{Tab,1,815}],[{Tab,2,915}],[{Tab,3,256}]}), + + %% kill mnesia on node 3 + mnesia_test_lib:kill_mnesia([Node3]), + + %% wait a while, so that mnesia is really down + timer:sleep(timer:seconds(2)), + + mnesia_test_lib:kill_mnesia([Node1,Node2]), %% kill them as well + timer:sleep(timer:seconds(2)), + + %% start Mnesia only on node 3 + ?verbose("starting mnesia on Node3~n",[]), + + %% test_lib:mnesia_start doesnt work, because it waits + %% for the schema on all nodes ... ??? + ?match(ok,rpc:call(Node3,mnesia,start,[]) ), + ?match(ok,rpc:call(Node3,mnesia,wait_for_tables, + [[Tab],timer:seconds(30)] ) ), + + %% node3 shall have the conents of the dump + cross_check_tables([C],Tab,{[{Tab,1,4711}],[{Tab,2,42}],[{Tab,3,256}]}), + + %% start Mnesia on the other 2 nodes, too + mnesia_test_lib:start_mnesia([Node1,Node2],[Tab]), + + cross_check_tables([A,B,C],Tab, + {[{Tab,1,4711}],[{Tab,2,42}],[{Tab,3,256}]}), + ?verify_mnesia(Nodes, []). + +%% check the contents of the table + +cross_check_tables([],_tab,_elements) -> ok; +cross_check_tables([Pid|Rest],Tab,{Val1,Val2,Val3}) -> + Pid ! fun () -> + R1 = mnesia:dirty_read({Tab,1}), + R2 = mnesia:dirty_read({Tab,2}), + R3 = mnesia:dirty_read({Tab,3}), + {R1,R2,R3} + end, + ?match_receive({ Pid, {Val1, Val2, Val3 } }), + cross_check_tables(Rest,Tab,{Val1,Val2,Val3} ). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Should be in evil test suite !!! + +dump_disc_copies(doc) -> + ["Check that it is not possible to dump disc_copies tables"]; +dump_disc_copies(suite) -> []; +dump_disc_copies(Config) when is_list(Config) -> + do_dump_copies(Config, disc_copies). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Should be in evil test suite !!! +dump_disc_only(doc) -> + ["Check that it is not possible to dump disc_only_copies tables"]; +dump_disc_only(suite) -> []; +dump_disc_only(Config) when is_list(Config) -> + do_dump_copies(Config,disc_only_copies). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +do_dump_copies(Config,Copies) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + + Tab = dump_copies, + Def = [{attributes, [key, value]}, + {Copies, [Node1]}], + + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + + ?match(ok, mnesia:dirty_write({Tab, 1, 4711})), + ?match(ok, mnesia:dirty_write({Tab, 2, 42})), + ?match(ok, mnesia:dirty_write({Tab, 3, 256})), + + %% dump the table + ?match( {aborted, {"Only allowed on ram_copies",Tab,[Node1]}}, + mnesia:dump_tables([Tab])), + + ?match(ok, mnesia:dirty_write({Tab, 1, 815})), + ?match(ok, mnesia:dirty_write({Tab, 2, 915})), + + %% kill mnesia on node1 + mnesia_test_lib:kill_mnesia([Node1]), + + %% wait a while, so that mnesia is really down + timer:sleep(timer:seconds(1)), + + mnesia_test_lib:start_mnesia([Node1],[Tab]), + + ?match([{Tab, 1, 815}], mnesia:dirty_read({Tab,1}) ), + ?match([{Tab, 2, 915}], mnesia:dirty_read({Tab,2}) ), + ?match([{Tab, 3, 256}], mnesia:dirty_read({Tab,3}) ), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +durability_of_disc_copies(doc) -> + ["Perform all possible kinds of updates on tables and check" + "whether no data is lost after a restart of Mnesia.", + "This test is done for disc_copies"]; + +durability_of_disc_copies(suite) -> []; +durability_of_disc_copies(Config) when is_list(Config) -> + do_disc_durability(Config,disc_copies). +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +durability_of_disc_only_copies(doc) -> + ["Perform all possible kinds of updates on tables and check" + "whether no data is lost after a restart of Mnesia.", + "This test is done for disc_only_copies"]; +durability_of_disc_only_copies(suite) -> []; +durability_of_disc_only_copies(Config) when is_list(Config) -> + do_disc_durability(Config,disc_only_copies). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +do_disc_durability(Config,CopyType) -> + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(1)}]), + {success, [A,B,C]} = ?start_activities(Nodes), + + Tab_set = disc_durability_set, + Def_set = [{attributes, [key, value]}, + {CopyType, Nodes}], + + Tab_bag = disc_durability_bag, + Def_bag = [{attributes, [key, value]}, + {type, bag}, + {CopyType, Nodes}], + + ?match({atomic, ok}, mnesia:create_table(Tab_set, Def_set)), + ?match({atomic, ok}, mnesia:create_table(Tab_bag, Def_bag)), + + %% do updates + ?match({atomic, ok}, + mnesia:transaction(fun()-> + mnesia:write({Tab_set, 11, 1111}), + mnesia:write({Tab_set, 22, 2222}), + mnesia:write({Tab_set, 33, 3333}), + mnesia:write({Tab_set, 55, 5555}) + end)), + mnesia:dirty_write({Tab_set, 44, 4444}), + + ?match({atomic, ok}, + mnesia:transaction(fun()-> + mnesia:write({Tab_bag, 11, a_1111}), + mnesia:write({Tab_bag, 11, b_1111}), + mnesia:write({Tab_bag, 22, a_2222}), + mnesia:write({Tab_bag, 22, b_2222}), + mnesia:write({Tab_bag, 33, a_3333}), + mnesia:write({Tab_bag, 33, b_3333}) + end)), + ?match({atomic, ok}, + mnesia:transaction(fun()-> mnesia:delete({Tab_set, 22}) end)), + ?match(ok, mnesia:dirty_delete({Tab_set, 33})), + ?match(5558, mnesia:dirty_update_counter({Tab_set, 55}, 3)), + ?match({atomic, ok}, + mnesia:transaction(fun()-> + mnesia:delete_object({Tab_bag, 22, b_2222}) + end)), + ?match(ok, mnesia:dirty_delete_object({Tab_bag, 33, b_3333})), + ?match(10, mnesia:dirty_update_counter({Tab_set, counter}, 10)), + ?match({atomic, ok}, % Also syncs update_counter + mnesia:sync_transaction(fun() -> mnesia:write({Tab_set,66,6666}) end)), + + Updated = {[[{Tab_set,counter,10}], + [{Tab_set,counter,10}], + [{Tab_set,counter,10}]],[]}, + ?match(Updated, rpc:multicall(Nodes, mnesia, dirty_read, [Tab_set,counter])), + + %% kill mnesia on all nodes, start it again and check the data + mnesia_test_lib:kill_mnesia(Nodes), + mnesia_test_lib:start_mnesia(Nodes,[Tab_set,Tab_bag]), + + ?log("Flushed ~p ~n", [mnesia_test_lib:flush()]), %% Debugging strange msgs.. + ?log("Processes ~p ~p ~p~n", [A,B,C]), + check_tables([A,B,C], + [{Tab_set,11}, {Tab_set,22},{Tab_set,33}, + {Tab_set,44},{Tab_set,55}, {Tab_set,66}, + {Tab_bag,11}, {Tab_bag,22},{Tab_bag,33}, + {Tab_set, counter}], + [[{Tab_set, 11, 1111}], [], [], [{Tab_set, 44, 4444}], + [{Tab_set, 55, 5558}], [{Tab_set, 66, 6666}], + lists:sort([{Tab_bag, 11, a_1111},{Tab_bag, 11, b_1111}]), + [{Tab_bag, 22, a_2222}], [{Tab_bag, 33, a_3333}], + [{Tab_set, counter, 10}]]), + + timer:sleep(1000), %% Debugging strange msgs.. + ?log("Flushed ~p ~n", [mnesia_test_lib:flush()]), + ?verify_mnesia(Nodes, []). + +%% check the contents of the table +%% +%% all the processes in the PidList shall find all +%% table entries in ValList + +check_tables([],_vallist,_resultList) -> ok; +check_tables([Pid|Rest],ValList,ResultList) -> + Pid ! fun () -> + check_values(ValList) + end, + ?match_receive({ Pid, ResultList }), + check_tables(Rest,ValList,ResultList). + +check_values([]) -> []; +check_values([{Tab,Key}|Rest]) -> + Ret = lists:sort(mnesia:dirty_read({Tab,Key})), + [Ret|check_values(Rest)]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% stolen from mnesia_recovery_test.erl: + +receive_messages([]) -> []; +receive_messages(ListOfMsgs) -> + receive + timeout -> + case lists:member(timeout, ListOfMsgs) of + false -> + ?warning("I (~p) have received unexpected msg~n ~p ~n", + [self(),timeout]), + receive_messages(ListOfMsgs); + true -> + ?verbose("I (~p) got msg ~p ~n", [self(),timeout]), + [ timeout | receive_messages(ListOfMsgs -- [timeout])] + end; + + {Pid, Msg} -> + case lists:member(Msg, ListOfMsgs) of + false -> + ?warning("I (~p) have received unexpected msg~n ~p ~n", + [self(),{Pid, Msg}]), + receive_messages(ListOfMsgs); + true -> + ?verbose("I (~p) got msg ~p from ~p ~n", [self(),Msg, Pid]), + [{Pid, Msg} | receive_messages(ListOfMsgs -- [Msg])] + end; + + Else -> ?warning("Recevied unexpected Msg~n ~p ~n", [Else]) + after timer:seconds(40) -> + ?error("Timeout in receive msgs while waiting for ~p~n", + [ListOfMsgs]) + end. + diff --git a/lib/mnesia/test/mnesia_evil_backup.erl b/lib/mnesia/test/mnesia_evil_backup.erl new file mode 100644 index 0000000000..bbbebeb02c --- /dev/null +++ b/lib/mnesia/test/mnesia_evil_backup.erl @@ -0,0 +1,750 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1998-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +%%%---------------------------------------------------------------------- +%%% File : mnesia_evil_backup.erl +%%% Author : Dan Gudmundsson <dgud@legolas> +%%% Purpose : Evil backup tests +%%% Created : 3 Jun 1998 by Dan Gudmundsson <[email protected]> +%%%---------------------------------------------------------------------- + +-module(mnesia_evil_backup). +-author('[email protected]'). +-compile(export_all). +-include("mnesia_test_lib.hrl"). + +%%-export([Function/Arity, ...]). + +init_per_testcase(Func, Conf) -> + mnesia_test_lib:init_per_testcase(Func, Conf). + +fin_per_testcase(Func, Conf) -> + mnesia_test_lib:fin_per_testcase(Func, Conf). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +all(doc) -> + ["Checking all the functionality regarding ", + "to the backup and different ", + "kinds of restore and fallback interface"]; + +all(suite) -> + [ + backup, + bad_backup, + global_backup_checkpoint, + restore_tables, + traverse_backup, + selective_backup_checkpoint, + incremental_backup_checkpoint, +%% local_backup_checkpoint, + install_fallback, + uninstall_fallback, + local_fallback, + sops_with_checkpoint + ]. + +backup(doc) -> ["Checking the interface to the function backup", + "We don't check that the backups can be used here", + "That is checked in install_fallback and in restore"]; +backup(suite) -> []; +backup(Config) when is_list(Config) -> + [Node1, Node2] = _Nodes = ?acquire_nodes(2, Config), + Tab = backup_tab, + Def = [{disc_copies, [Node1]}, {ram_copies, [Node2]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + ?match(ok, mnesia:dirty_write({Tab, 1, test_ok})), + File = "backup_test.BUP", + ?match(ok, mnesia:backup(File)), + + File2 = "backup_test2.BUP", + Tab2 = backup_tab2, + Def2 = [{disc_only_copies, [Node2]}], + ?match({atomic, ok}, mnesia:create_table(Tab2, Def2)), + ?match(ok, mnesia:backup(File2, mnesia_backup)), + + File3 = "backup_test3.BUP", + mnesia_test_lib:kill_mnesia([Node2]), + ?match({error, _}, mnesia:backup(File3, mnesia_backup)), + + ?match(ok, file:delete(File)), + ?match(ok, file:delete(File2)), + ?match({error, _}, file:delete(File3)), + ?verify_mnesia([Node1], [Node2]). + + +bad_backup(suite) -> []; +bad_backup(Config) when is_list(Config) -> + [Node1] = ?acquire_nodes(1, Config), + Tab = backup_tab, + Def = [{disc_copies, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + ?match(ok, mnesia:dirty_write({Tab, 1, test_ok})), + File = "backup_test.BUP", + ?match(ok, mnesia:backup(File)), + file:write_file(File, "trash", [append]), + ?match(ok, mnesia:dirty_write({Tab, 1, test_bad})), + ?match({atomic,[Tab]}, mnesia:restore(File, [{clear_tables, [Tab]}])), + ?match([{Tab,1,test_ok}], mnesia:dirty_read(Tab, 1)), + + ?match(ok, file:delete(File)), + ?verify_mnesia([Node1], []). + + + +global_backup_checkpoint(doc) -> + ["Checking the interface to the function backup_checkpoint", + "We don't check that the backups can be used here", + "That is checked in install_fallback and in restore"]; +global_backup_checkpoint(suite) -> []; +global_backup_checkpoint(Config) when is_list(Config) -> + [Node1, Node2] = Nodes = ?acquire_nodes(2, Config), + Tab = backup_cp, + Def = [{disc_copies, [Node1]}, {ram_copies, [Node2]}], + File = "backup_checkpoint.BUP", + File2 = "backup_checkpoint2.BUP", + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + ?match(ok, mnesia:dirty_write({Tab, 1, test_ok})), + ?match({error, _}, mnesia:backup_checkpoint(cp_name, File)), + Spec = [{name, cp_name}, {max, mnesia:system_info(tables)}], + ?match({ok, _Name, _Ns}, mnesia:activate_checkpoint(Spec)), + ?match(ok, mnesia:backup_checkpoint(cp_name, File)), + ?match({error, _}, mnesia:backup_checkpoint(cp_name_nonexist, File)), + ?match(ok, mnesia:backup_checkpoint(cp_name, File2, mnesia_backup)), + ?match({error, _}, file:delete(File)), + ?match(ok, file:delete(File2)), + ?verify_mnesia(Nodes, []). + +restore_tables(doc) -> + ["Tests the interface of restore"]; + +restore_tables(suite) -> + [ + restore_errors, + restore_clear, + restore_keep, + restore_recreate, + restore_clear_ram + ]. + +restore_errors(suite) -> []; +restore_errors(Config) when is_list(Config) -> + [_Node] = ?acquire_nodes(1, Config), + ?match({aborted, enoent}, mnesia:restore(notAfile, [])), + ?match({aborted, {badarg, _}}, mnesia:restore(notAfile, not_a_list)), + ?match({aborted, {badarg, _}}, mnesia:restore(notAfile, [test_badarg])), + ?match({aborted, {badarg, _}}, mnesia:restore(notAfile, [{test_badarg, xxx}])), + ?match({aborted, {badarg, _}}, mnesia:restore(notAfile, [{skip_tables, xxx}])), + ?match({aborted, {badarg, _}}, mnesia:restore(notAfile, [{recreate_tables, [schema]}])), + ?match({aborted, {badarg, _}}, mnesia:restore(notAfile, [{default_op, asdklasd}])), + ok. + +restore_clear(suite) -> []; +restore_clear(Config) when is_list(Config) -> + restore(Config, clear_tables). + +restore_keep(suite) -> []; +restore_keep(Config) when is_list(Config) -> + restore(Config, keep_tables). + +restore_recreate(suite) -> []; +restore_recreate(Config) when is_list(Config) -> + restore(Config, recreate_tables). + +check_tab(Records, Line) -> + Verify = fun({Table, Key, Val}) -> + case catch mnesia:dirty_read({Table, Key}) of + [{Table, Key, Val}] -> ok; + Else -> + mnesia_test_lib:error("Not matching on Node ~p ~n" + " Expected ~p~n Actual ~p~n", + [node(), {Table, Key, Val}, Else], + ?MODULE, Line), + exit(error) + end; + (Recs) -> + [{Tab, Key, _}, _] = Recs, + SRecs = lists:sort(Recs), + R_Recs = lists:sort(catch mnesia:dirty_read({Tab, Key})), + case R_Recs of + SRecs -> ok; + Else -> + mnesia_test_lib:error("Not matching on Node ~p ~n" + " Expected ~p~n Actual ~p~n", + [node(), SRecs, Else], + ?MODULE, Line), + exit(error) + end + end, + lists:foreach(Verify, Records). + +restore(Config, Op) -> + [Node1, Node2, _Node3] = Nodes = ?acquire_nodes(3, Config), + + Tab1 = ram_snmp, + Def1 = [{snmp, [{key, integer}]}, {ram_copies, [Node1]}], + Tab2 = disc_index, + Def2 = [{index, [val]}, {disc_copies, [Node1, Node2]}], + Tab3 = dionly_bag, + Def3 = [{type, bag}, {disc_only_copies, Nodes}], + ?match({atomic, ok}, mnesia:create_table(Tab1, Def1)), + ?match({atomic, ok}, mnesia:create_table(Tab2, Def2)), + ?match({atomic, ok}, mnesia:create_table(Tab3, Def3)), + + File1 = "restore1.BUP", + File2 = "restore2.BUP", + + Restore = fun(O, A) -> + case mnesia:restore(O, A) of + {atomic, Tabs} when is_list(Tabs) -> {atomic, lists:sort(Tabs)}; + Other -> Other + end + end, + Tabs = lists:sort([Tab1, Tab2, Tab3]), + + [mnesia:dirty_write({Tab1, N, N+42}) || N <- lists:seq(1, 10)], + [mnesia:dirty_write({Tab2, N, N+43}) || N <- lists:seq(1, 10)], + [mnesia:dirty_write({Tab3, N, N+44}) || N <- lists:seq(1, 10)], + + Res1 = [{Tab1, N, N+42} || N <- lists:seq(1, 10)], + Res2 = [{Tab2, N, N+43} || N <- lists:seq(1, 10)], + Res3 = [{Tab3, N, N+44} || N <- lists:seq(1, 10)], + + {ok, Name, _} = mnesia:activate_checkpoint([{min, Tabs}, {ram_overrides_dump, true}]), + file:delete(File1), + + %% Test standard Restore on one table on one node + ?match(ok, mnesia:backup_checkpoint(Name, File1)), + ?match(ok, mnesia:deactivate_checkpoint(Name)), + ?match(ok, mnesia:backup(File2)), + [mnesia:dirty_write({Tab1, N, N+1}) || N <- lists:seq(1, 11)], + [mnesia:dirty_write({Tab2, N, N+1}) || N <- lists:seq(1, 11)], + [mnesia:dirty_write({Tab3, N, N+1}) || N <- lists:seq(1, 11)], + _Res11 = [{Tab1, N, N+1} || N <- lists:seq(1, 11)], + Res21 = [{Tab2, N, N+1} || N <- lists:seq(1, 11)], + Res31 = [[{Tab3, N, N+1}, {Tab3, N, N+44}] || N <- lists:seq(1, 10)], + + ?match({atomic, [Tab1]}, Restore(File1, [{Op, [Tab1]}, + {skip_tables, Tabs -- [Tab1]}])), + case Op of + keep_tables -> + ?match([{Tab1, 11, 12}], mnesia:dirty_read({Tab1, 11})); + clear_tables -> + ?match([], mnesia:dirty_read({Tab1, 11})); + recreate_tables -> + ?match([], mnesia:dirty_read({Tab1, 11})) + end, + [rpc:call(Node, ?MODULE, check_tab, [Res1, ?LINE]) || Node <- Nodes], + [rpc:call(Node, ?MODULE, check_tab, [Res21, ?LINE]) || Node <- Nodes], + [rpc:call(Node, ?MODULE, check_tab, [Res31, ?LINE]) || Node <- Nodes], + + %% Restore all tables on it's nodes + mnesia_schema:clear_table(Tab1), + mnesia_schema:clear_table(Tab2), + mnesia_schema:clear_table(Tab3), + [mnesia:dirty_write({Tab1, N, N+1}) || N <- lists:seq(1, 11)], + [mnesia:dirty_write({Tab2, N, N+1}) || N <- lists:seq(1, 11)], + [mnesia:dirty_write({Tab3, N, N+1}) || N <- lists:seq(1, 11)], + + ?match({atomic, ok}, mnesia:del_table_copy(Tab2, Node1)), + + ?match({ok, Node1}, mnesia:subscribe({table, Tab1})), + + ?match({atomic, Tabs}, Restore(File1, [{default_op, Op}, + {module, mnesia_backup}])), + case Op of + clear_tables -> + ?match_receive({mnesia_table_event, {delete, {schema, Tab1}, _}}), + ?match_receive({mnesia_table_event, {write, {schema, Tab1, _}, _}}), + check_subscr(Tab1), + [rpc:call(Node, ?MODULE, check_tab, [Res1, ?LINE]) || Node <- Nodes], + [rpc:call(Node, ?MODULE, check_tab, [Res2, ?LINE]) || Node <- Nodes], + [rpc:call(Node, ?MODULE, check_tab, [Res3, ?LINE]) || Node <- Nodes], + ?match([], mnesia:dirty_read({Tab1, 11})), + ?match([], mnesia:dirty_read({Tab2, 11})), + ?match([], mnesia:dirty_read({Tab3, 11})), + %% Check Index + ?match([{Tab2, 10, 53}], mnesia:dirty_index_read(Tab2, 53, val)), + ?match([], mnesia:dirty_index_read(Tab2, 11, val)), + %% Check Snmp + ?match({ok, [1]}, mnesia:snmp_get_next_index(Tab1,[])), + ?match({ok, {Tab1, 1, 43}}, mnesia:snmp_get_row(Tab1, [1])), + ?match(undefined, mnesia:snmp_get_row(Tab1, [11])), + %% Check schema info + ?match([Node2], mnesia:table_info(Tab2, where_to_write)); + keep_tables -> + check_subscr(Tab1), + [rpc:call(Node, ?MODULE, check_tab, [Res1, ?LINE]) || Node <- Nodes], + [rpc:call(Node, ?MODULE, check_tab, [Res2, ?LINE]) || Node <- Nodes], + [rpc:call(Node, ?MODULE, check_tab, [Res31, ?LINE]) || Node <- Nodes], + ?match([{Tab1, 11, 12}], mnesia:dirty_read({Tab1, 11})), + ?match([{Tab2, 11, 12}], mnesia:dirty_read({Tab2, 11})), + ?match([{Tab3, 11, 12}], mnesia:dirty_read({Tab3, 11})), + ?match([{Tab2, 10, 53}], mnesia:dirty_index_read(Tab2, 53, val)), + %% Check Index + ?match([], mnesia:dirty_index_read(Tab2, 11, val)), + ?match({ok, [1]}, mnesia:snmp_get_next_index(Tab1,[])), + %% Check Snmp + ?match({ok, {Tab1, 1, 43}}, mnesia:snmp_get_row(Tab1, [1])), + ?match({ok, {Tab1, 11, 12}}, mnesia:snmp_get_row(Tab1, [11])), + %% Check schema info + ?match([Node2], mnesia:table_info(Tab2, where_to_write)); + recreate_tables -> + check_subscr(Tab1, 0), + [rpc:call(Node, ?MODULE, check_tab, [Res1, ?LINE]) || Node <- Nodes], + [rpc:call(Node, ?MODULE, check_tab, [Res2, ?LINE]) || Node <- Nodes], + [rpc:call(Node, ?MODULE, check_tab, [Res3, ?LINE]) || Node <- Nodes], + ?match([], mnesia:dirty_read({Tab1, 11})), + ?match([], mnesia:dirty_read({Tab2, 11})), + ?match([], mnesia:dirty_read({Tab3, 11})), + %% Check Index + ?match([{Tab2, 10, 53}], mnesia:dirty_index_read(Tab2, 53, val)), + ?match([], mnesia:dirty_index_read(Tab2, 11, val)), + %% Check Snmp + ?match({ok, [1]}, mnesia:snmp_get_next_index(Tab1,[])), + ?match({ok, {Tab1, 1, 43}}, mnesia:snmp_get_row(Tab1, [1])), + ?match(undefined, mnesia:snmp_get_row(Tab1, [11])), + %% Check schema info + Ns = lists:sort([Node1, Node2]), + ?match(Ns, lists:sort(mnesia:table_info(Tab2, where_to_write))) + end, + ?match(ok, file:delete(File1)), + ?match(ok, file:delete(File2)), + ?verify_mnesia(Nodes, []). + + +check_subscr(Tab) -> + check_subscr(Tab, 10). + +check_subscr(_Tab, 0) -> + receive + Msg -> + ?error("Too many msgs ~p~n", [Msg]) + after 500 -> + ok + end; +check_subscr(Tab, N) -> + V = N +42, + receive + {mnesia_table_event, {write, {Tab, N, V}, _}} -> + check_subscr(Tab, N-1) + after 500 -> + ?error("Missing ~p~n", [{Tab, N, V}]) + end. + +restore_clear_ram(suite) -> []; +restore_clear_ram(Config) when is_list(Config) -> + Nodes = ?acquire_nodes(3, [{diskless, true}|Config]), + + ?match({atomic, ok}, mnesia:create_table(a, [{ram_copies, Nodes}])), + + Write = fun(What) -> + mnesia:write({a,1,What}), + mnesia:write({a,2,What}), + mnesia:write({a,3,What}) + end, + Bup = "restore_clear_ram.BUP", + + ?match({atomic, ok}, mnesia:transaction(Write, [initial])), + ?match({ok, _, _}, mnesia:activate_checkpoint([{name,test}, + {min, [schema, a]}, + {ram_overrides_dump, true}])), + ?match(ok, mnesia:backup_checkpoint(test, Bup)), + + ?match({atomic, ok}, mnesia:transaction(Write, [data])), + ?match({atomic, [a]}, mnesia:restore(Bup, [{clear_tables,[a]},{default_op,skip_tables}])), + + restore_clear_ram_loop(100, Nodes, Bup), + + ok. + +restore_clear_ram_loop(N, Nodes = [N1,N2,N3], Bup) when N > 0 -> + ?match([], mnesia_test_lib:stop_mnesia(Nodes)), + ?match({_, []}, rpc:multicall([N1,N2], mnesia, start, [[{extra_db_nodes, Nodes}]])), + Key = rpc:async_call(N3, mnesia, start, [[{extra_db_nodes, Nodes}]]), + ?match({atomic, ok}, mnesia:create_table(a, [{ram_copies, Nodes}])), + ?match({atomic, [a]}, mnesia:restore(Bup, [{clear_tables,[a]},{default_op,skip_tables}])), + ?match(ok, rpc:yield(Key)), + ?match(ok, rpc:call(N3, mnesia, wait_for_tables, [[a], 3000])), + case rpc:multicall(Nodes, mnesia, table_info, [a,size]) of + {[3,3,3], []} -> + restore_clear_ram_loop(N-1, Nodes, Bup); + Error -> + ?match(3, Error) + end; +restore_clear_ram_loop(_,_,_) -> + ok. + +traverse_backup(doc) -> + ["Testing the traverse_backup interface, the resulting file is not tested though", + "See install_fallback for result using the output file from traverse_backup", + "A side effect is that the backup file contents are tested"]; +traverse_backup(suite) -> []; +traverse_backup(Config) when is_list(Config) -> + [Node1, Node2] = Nodes = ?acquire_nodes(2, Config), + Tab = backup_tab, + Def = [{disc_copies, [Node1]}, {ram_copies, [Node2]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + ?match(ok, mnesia:dirty_write({Tab, 1, test_nok})), + ?match(ok, mnesia:dirty_write({Tab, 2, test_nok})), + ?match(ok, mnesia:dirty_write({Tab, 3, test_nok})), + ?match(ok, mnesia:dirty_write({Tab, 4, test_nok})), + ?match(ok, mnesia:dirty_write({Tab, 5, test_nok})), + File = "_treverse_backup.BUP", + File2 = "traverse_backup2.BUP", + File3 = "traverse_backup3.BUP", + ?match(ok, mnesia:backup(File)), + + Fun = fun({backup_tab, N, _}, Acc) -> {[{backup_tab, N, test_ok}], Acc+1}; + (Other, Acc) -> {[Other], Acc} + end, + + ?match({ok, 5}, mnesia:traverse_backup(File, read_only, Fun, 0)), + ?match(ok, file:delete(read_only)), + + ?match({ok, 5}, mnesia:traverse_backup(File, mnesia_backup, + dummy, read_only, Fun, 0)), + + ?match({ok, 5}, mnesia:traverse_backup(File, File2, Fun, 0)), + ?match({ok, 5}, mnesia:traverse_backup(File2, mnesia_backup, + File3, mnesia_backup, Fun, 0)), + + BadFun = fun({bad_tab, _N, asd}, Acc) -> {{error, error}, Acc} end, + ?match({error, _}, mnesia:traverse_backup(File, read_only, BadFun, 0)), + ?match({error, _}, file:delete(read_only)), + ?match(ok, file:delete(File)), + ?match(ok, file:delete(File2)), + ?match(ok, file:delete(File3)), + ?verify_mnesia(Nodes, []). + + +install_fallback(doc) -> + ["This tests the install_fallback intf.", + "It also verifies that the output from backup_checkpoint and traverse_backup", + "is valid"]; +install_fallback(suite) -> []; +install_fallback(Config) when is_list(Config) -> + [Node1, Node2] = Nodes = ?acquire_nodes(2, Config), + Tab = fallbacks_test, + Def = [{disc_copies, [Node1]}, {ram_copies, [Node2]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + ?match(ok, mnesia:dirty_write({Tab, 1, test_nok})), + ?match(ok, mnesia:dirty_write({Tab, 2, test_nok})), + ?match(ok, mnesia:dirty_write({Tab, 3, test_nok})), + ?match(ok, mnesia:dirty_write({Tab, 4, test_nok})), + ?match(ok, mnesia:dirty_write({Tab, 5, test_nok})), + + Tab2 = fallbacks_test2, + Def2 = [{disc_copies, [node()]}], + ?match({atomic, ok}, mnesia:create_table(Tab2, Def2)), + Tab3 = fallbacks_test3, + ?match({atomic, ok}, mnesia:create_table(Tab3, Def2)), + Fun2 = fun(Key) -> + Rec = {Tab2, Key, test_ok}, + mnesia:dirty_write(Rec), + [Rec] + end, + TabSize3 = 1000, + OldRecs2 = [Fun2(K) || K <- lists:seq(1, TabSize3)], + + Spec =[{name, cp_name}, {max, mnesia:system_info(tables)}], + ?match({ok, _Name, Nodes}, mnesia:activate_checkpoint(Spec)), + ?match(ok, mnesia:dirty_write({Tab, 6, test_nok})), + [mnesia:dirty_write({Tab2, K, test_nok}) || K <- lists:seq(1, TabSize3 + 10)], + File = "install_fallback.BUP", + File2 = "install_fallback2.BUP", + File3 = "install_fallback3.BUP", + ?match(ok, mnesia:backup_checkpoint(cp_name, File)), + + Fun = fun({T, N, _}, Acc) when T == Tab -> + case N rem 2 of + 0 -> + io:format("write ~p -> ~p~n", [N, T]), + {[{T, N, test_ok}], Acc + 1}; + 1 -> + io:format("write ~p -> ~p~n", [N, Tab3]), + {[{Tab3, N, test_ok}], Acc + 1} + end; + ({T, N}, Acc) when T == Tab -> + case N rem 2 of + 0 -> + io:format("delete ~p -> ~p~n", [N, T]), + {[{T, N}], Acc + 1}; + 1 -> + io:format("delete ~p -> ~p~n", [N, Tab3]), + {[{Tab3, N}], Acc + 1} + end; + (Other, Acc) -> + {[Other], Acc} + end, + ?match({ok, _}, mnesia:traverse_backup(File, File2, Fun, 0)), + ?match(ok, mnesia:install_fallback(File2)), + + mnesia_test_lib:kill_mnesia([Node1, Node2]), + timer:sleep(timer:seconds(1)), % Let it die! + + ?match([], mnesia_test_lib:start_mnesia([Node1, Node2], [Tab, Tab2, Tab3])), + + % Verify + ?match([], mnesia:dirty_read({Tab, 1})), + ?match([{Tab3, 1, test_ok}], mnesia:dirty_read({Tab3, 1})), + ?match([{Tab, 2, test_ok}], mnesia:dirty_read({Tab, 2})), + ?match([], mnesia:dirty_read({Tab3, 2})), + ?match([], mnesia:dirty_read({Tab, 3})), + ?match([{Tab3, 3, test_ok}], mnesia:dirty_read({Tab3, 3})), + ?match([{Tab, 4, test_ok}], mnesia:dirty_read({Tab, 4})), + ?match([], mnesia:dirty_read({Tab3, 4})), + ?match([], mnesia:dirty_read({Tab, 5})), + ?match([{Tab3, 5, test_ok}], mnesia:dirty_read({Tab3, 5})), + ?match([], mnesia:dirty_read({Tab, 6})), + ?match([], mnesia:dirty_read({Tab3, 6})), + ?match([], [mnesia:dirty_read({Tab2, K}) || K <- lists:seq(1, TabSize3)] -- OldRecs2), + ?match(TabSize3, mnesia:table_info(Tab2, size)), + + % Check the interface + file:delete(File3), + ?match({error, _}, mnesia:install_fallback(File3)), + ?match({error, _}, mnesia:install_fallback(File2, mnesia_badmod)), + ?match(ok, mnesia:install_fallback(File2, mnesia_backup)), + ?match(ok, file:delete(File)), + ?match(ok, file:delete(File2)), + ?match({error, _}, file:delete(File3)), + ?verify_mnesia(Nodes, []). + +uninstall_fallback(suite) -> []; +uninstall_fallback(Config) when is_list(Config) -> + [Node1, Node2] = Nodes = ?acquire_nodes(2, Config), + Tab = uinst_fallbacks_test, + File = "uinst_fallback.BUP", + File2 = "uinst_fallback2.BUP", + Def = [{disc_copies, [Node1]}, {ram_copies, [Node2]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + ?match(ok, mnesia:dirty_write({Tab, 1, test_ok})), + ?match(ok, mnesia:backup(File)), + Fun = fun({T, N, _}, Acc) when T == Tab -> + {[{T, N, test_nok}], Acc+1}; + (Other, Acc) -> {[Other], Acc} + end, + ?match({ok, _}, mnesia:traverse_backup(File, File2, Fun, 0)), + ?match({error, enoent}, mnesia:uninstall_fallback()), + ?match(ok, mnesia:install_fallback(File2)), + ?match(ok, file:delete(File)), + ?match(ok, file:delete(File2)), + ?match(ok, mnesia:uninstall_fallback()), + + mnesia_test_lib:kill_mnesia([Node1, Node2]), + timer:sleep(timer:seconds(1)), % Let it die! + ?match([], mnesia_test_lib:start_mnesia([Node1, Node2], [Tab])), + ?match([{Tab, 1, test_ok}], mnesia:dirty_read({Tab, 1})), + ?verify_mnesia(Nodes, []). + +local_fallback(suite) -> []; +local_fallback(Config) when is_list(Config) -> + [Node1, Node2] = Nodes = ?acquire_nodes(2, Config), + Tab = local_fallback, + File = "local_fallback.BUP", + Def = [{disc_copies, Nodes}], + Key = foo, + Pre = {Tab, Key, pre}, + Post = {Tab, Key, post}, + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + ?match(ok, mnesia:dirty_write(Pre)), + ?match(ok, mnesia:backup(File)), + ?match(ok, mnesia:dirty_write(Post)), + Local = [{scope, local}], + ?match({error, enoent}, mnesia:uninstall_fallback(Local)), + ?match(ok, mnesia:install_fallback(File, Local)), + ?match(true, mnesia:system_info(fallback_activated)), + ?match(ok, mnesia:uninstall_fallback(Local)), + ?match(false, mnesia:system_info(fallback_activated)), + ?match(ok, mnesia:install_fallback(File, Local)), + ?match(true, mnesia:system_info(fallback_activated)), + + ?match(false, rpc:call(Node2, mnesia, system_info , [fallback_activated])), + ?match(ok, rpc:call(Node2, mnesia, install_fallback , [File, Local])), + ?match([Post], mnesia:dirty_read({Tab, Key})), + ?match([Post], rpc:call(Node2, mnesia, dirty_read, [{Tab, Key}])), + + ?match([], mnesia_test_lib:kill_mnesia(Nodes)), + ?match([], mnesia_test_lib:start_mnesia(Nodes, [Tab])), + ?match([Pre], mnesia:dirty_read({Tab, Key})), + ?match([Pre], rpc:call(Node2, mnesia, dirty_read, [{Tab, Key}])), + Dir = rpc:call(Node2, mnesia, system_info , [directory]), + + ?match(ok, mnesia:dirty_write(Post)), + ?match([Post], mnesia:dirty_read({Tab, Key})), + ?match([], mnesia_test_lib:kill_mnesia([Node2])), + ?match(ok, mnesia:install_fallback(File, Local ++ [{mnesia_dir, Dir}])), + ?match([], mnesia_test_lib:kill_mnesia([Node1])), + + ?match([], mnesia_test_lib:start_mnesia([Node2], [])), + ?match(yes, rpc:call(Node2, mnesia, force_load_table, [Tab])), + ?match([], mnesia_test_lib:start_mnesia(Nodes, [Tab])), + ?match([Pre], mnesia:dirty_read({Tab, Key})), + + ?match(ok, file:delete(File)), + ?verify_mnesia(Nodes, []). + +selective_backup_checkpoint(doc) -> + ["Perform a selective backup of a checkpoint"]; +selective_backup_checkpoint(suite) -> []; +selective_backup_checkpoint(Config) when is_list(Config) -> + [Node1, Node2] = Nodes = ?acquire_nodes(2, Config), + Tab = sel_backup, + OmitTab = sel_backup_omit, + CpName = sel_cp, + Def = [{disc_copies, [Node1, Node2]}], + File = "selective_backup_checkpoint.BUP", + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + ?match({atomic, ok}, mnesia:create_table(OmitTab, Def)), + ?match(ok, mnesia:dirty_write({Tab, 1, test_ok})), + ?match(ok, mnesia:dirty_write({OmitTab, 1, test_ok})), + CpSpec = [{name, CpName}, {max, mnesia:system_info(tables)}], + ?match({ok, CpName, _Ns}, mnesia:activate_checkpoint(CpSpec)), + + BupSpec = [{tables, [Tab]}], + ?match(ok, mnesia:backup_checkpoint(CpName, File, BupSpec)), + + ?match([schema, sel_backup], bup_tables(File, mnesia_backup)), + ?match(ok, file:delete(File)), + + BupSpec2 = [{tables, [Tab, OmitTab]}], + ?match(ok, mnesia:backup_checkpoint(CpName, File, BupSpec2)), + + ?match([schema, sel_backup, sel_backup_omit], + bup_tables(File, mnesia_backup)), + ?match(ok, file:delete(File)), + ?verify_mnesia(Nodes, []). + +bup_tables(File, Mod) -> + Fun = fun(Rec, Tabs) -> + Tab = element(1, Rec), + Tabs2 = [Tab | lists:delete(Tab, Tabs)], + {[Rec], Tabs2} + end, + case mnesia:traverse_backup(File, Mod, dummy, read_only, Fun, []) of + {ok, Tabs} -> + lists:sort(Tabs); + {error, Reason} -> + exit(Reason) + end. + +incremental_backup_checkpoint(doc) -> + ["Perform a incremental backup of a checkpoint"]; +incremental_backup_checkpoint(suite) -> []; +incremental_backup_checkpoint(Config) when is_list(Config) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = incr_backup, + Def = [{disc_copies, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + OldRecs = [{Tab, K, -K} || K <- lists:seq(1, 5)], + ?match([ok|_], [mnesia:dirty_write(R) || R <- OldRecs]), + OldCpName = old_cp, + OldCpSpec = [{name, OldCpName}, {min, [Tab]}], + ?match({ok, OldCpName, _Ns}, mnesia:activate_checkpoint(OldCpSpec)), + + BupSpec = [{tables, [Tab]}], + OldFile = "old_full_backup.BUP", + ?match(ok, mnesia:backup_checkpoint(OldCpName, OldFile, BupSpec)), + ?match(OldRecs, bup_records(OldFile, mnesia_backup)), + ?match(ok, mnesia:dirty_delete({Tab, 1})), + ?match(ok, mnesia:dirty_write({Tab, 2, 2})), + ?match(ok, mnesia:dirty_write({Tab, 3, -3})), + + NewCpName = new_cp, + NewCpSpec = [{name, NewCpName}, {min, [Tab]}], + ?match({ok, NewCpName, _Ns}, mnesia:activate_checkpoint(NewCpSpec)), + ?match(ok, mnesia:dirty_write({Tab, 4, 4})), + + NewFile = "new_full_backup.BUP", + ?match(ok, mnesia:backup_checkpoint(NewCpName, NewFile, BupSpec)), + NewRecs = [{Tab, 2, 2}, {Tab, 3, -3}, + {Tab, 4, 4}, {Tab, 4}, {Tab, 4, -4}, {Tab, 5, -5}], + ?match(NewRecs, bup_records(NewFile, mnesia_backup)), + + DiffFile = "diff_backup.BUP", + DiffBupSpec = [{tables, [Tab]}, {incremental, OldCpName}], + ?match(ok, mnesia:backup_checkpoint(NewCpName, DiffFile, DiffBupSpec)), + DiffRecs = [{Tab, 1}, {Tab, 2}, {Tab, 2, 2}, {Tab, 3}, {Tab, 3, -3}, + {Tab, 4}, {Tab, 4, 4}, {Tab, 4}, {Tab, 4, -4}], + ?match(DiffRecs, bup_records(DiffFile, mnesia_backup)), + + ?match(ok, mnesia:deactivate_checkpoint(OldCpName)), + ?match(ok, mnesia:deactivate_checkpoint(NewCpName)), + ?match(ok, file:delete(OldFile)), + ?match(ok, file:delete(NewFile)), + ?match(ok, file:delete(DiffFile)), + + ?verify_mnesia(Nodes, []). + +bup_records(File, Mod) -> + Fun = fun(Rec, Recs) when element(1, Rec) == schema -> + {[Rec], Recs}; + (Rec, Recs) -> + {[Rec], [Rec | Recs]} + end, + case mnesia:traverse_backup(File, Mod, dummy, read_only, Fun, []) of + {ok, Recs} -> + lists:keysort(1, lists:keysort(2, lists:reverse(Recs))); + {error, Reason} -> + exit(Reason) + end. + +sops_with_checkpoint(doc) -> + ["Test schema operations during a checkpoint"]; +sops_with_checkpoint(suite) -> []; +sops_with_checkpoint(Config) when is_list(Config) -> + Ns = ?acquire_nodes(2, Config), + + ?match({ok, cp1, Ns}, mnesia:activate_checkpoint([{name, cp1},{max,mnesia:system_info(tables)}])), + Tab = tab, + ?match({atomic, ok}, mnesia:create_table(Tab, [{disc_copies,Ns}])), + OldRecs = [{Tab, K, -K} || K <- lists:seq(1, 5)], + [mnesia:dirty_write(R) || R <- OldRecs], + + ?match({ok, cp2, Ns}, mnesia:activate_checkpoint([{name, cp2},{max,mnesia:system_info(tables)}])), + File1 = "cp1_delete_me.BUP", + ?match(ok, mnesia:dirty_write({Tab,6,-6})), + ?match(ok, mnesia:backup_checkpoint(cp1, File1)), + ?match(ok, mnesia:dirty_write({Tab,7,-7})), + File2 = "cp2_delete_me.BUP", + ?match(ok, mnesia:backup_checkpoint(cp2, File2)), + + ?match(ok, mnesia:deactivate_checkpoint(cp1)), + ?match(ok, mnesia:backup_checkpoint(cp2, File1)), + ?match(ok, mnesia:dirty_write({Tab,8,-8})), + + ?match({atomic,ok}, mnesia:delete_table(Tab)), + ?match({error,_}, mnesia:backup_checkpoint(cp2, File2)), + ?match({'EXIT',_}, mnesia:dirty_write({Tab,9,-9})), + + ?match({atomic,_}, mnesia:restore(File1, [{default_op, recreate_tables}])), + Test = fun(N) when N > 5 -> ?error("To many records in backup ~p ~n", [N]); + (N) -> case mnesia:dirty_read(Tab,N) of + [{Tab,N,B}] when -B =:= N -> ok; + Other -> ?error("Not matching ~p ~p~n", [N,Other]) + end + end, + [Test(N) || N <- mnesia:dirty_all_keys(Tab)], + ?match({aborted,enoent}, mnesia:restore(File2, [{default_op, recreate_tables}])), + + file:delete(File1), file:delete(File2), + + ?verify_mnesia(Ns, []). diff --git a/lib/mnesia/test/mnesia_evil_coverage_test.erl b/lib/mnesia/test/mnesia_evil_coverage_test.erl new file mode 100644 index 0000000000..4fbf1b4003 --- /dev/null +++ b/lib/mnesia/test/mnesia_evil_coverage_test.erl @@ -0,0 +1,2401 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(mnesia_evil_coverage_test). +-author('[email protected]'). +-include("mnesia_test_lib.hrl"). + +-compile([export_all]). + +-define(cleanup(N, Config), + mnesia_test_lib:prepare_test_case([{reload_appls, [mnesia]}], + N, Config, ?FILE, ?LINE)). +init_per_testcase(Func, Conf) -> + mnesia_test_lib:init_per_testcase(Func, Conf). + +fin_per_testcase(Func, Conf) -> + mnesia_test_lib:fin_per_testcase(Func, Conf). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +all(doc) -> + ["Evil usage of the API.", + "Invoke all functions in the API and try to cover all legal uses", + "cases as well the illegal dito. This is a complement to the", + "other more explicit test cases."]; +all(suite) -> + [ + system_info, + table_info, + error_description, + db_node_lifecycle, + evil_delete_db_node, + start_and_stop, + checkpoint, + table_lifecycle, + add_copy_conflict, + add_copy_when_going_down, + replica_management, + schema_availability, + local_content, + table_access_modifications, + replica_location, + table_sync, + user_properties, + unsupp_user_props, + record_name, + snmp_access, + subscriptions, + iteration, + debug_support, + sorted_ets, + {mnesia_dirty_access_test, all}, + {mnesia_trans_access_test, all}, + {mnesia_evil_backup, all} + ]. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Get meta info about Mnesia + +system_info(suite) -> []; +system_info(Config) when is_list(Config) -> + Nodes = ?acquire_nodes(all, Config), + Ns = ?sort(Nodes), + ?match(yes, mnesia:system_info(is_running)), + ?match(Ns, ?sort(mnesia:system_info(db_nodes))), + ?match(Ns, ?sort(mnesia:system_info(running_db_nodes))), + ?match(A when is_atom(A), mnesia:system_info(debug)), + ?match(L when is_list(L), mnesia:system_info(directory)), + ?match(L when is_list(L), mnesia:system_info(log_version)), + ?match({_, _}, mnesia:system_info(schema_version)), + ?match(L when is_list(L), mnesia:system_info(tables)), + ?match(L when is_list(L), mnesia:system_info(local_tables)), + ?match(L when is_list(L), mnesia:system_info(held_locks)), + ?match(L when is_list(L), mnesia:system_info(lock_queue)), + ?match(L when is_list(L), mnesia:system_info(transactions)), + ?match(I when is_integer(I), mnesia:system_info(transaction_failures)), + ?match(I when is_integer(I), mnesia:system_info(transaction_commits)), + ?match(I when is_integer(I), mnesia:system_info(transaction_restarts)), + ?match(L when is_list(L), mnesia:system_info(checkpoints)), + ?match(A when is_atom(A), mnesia:system_info(backup_module)), + ?match(true, mnesia:system_info(auto_repair)), + ?match({_, _}, mnesia:system_info(dump_log_interval)), + ?match(A when is_atom(A), mnesia:system_info(dump_log_update_in_place)), + ?match(I when is_integer(I), mnesia:system_info(transaction_log_writes)), + ?match(I when is_integer(I), mnesia:system_info(send_compressed)), + ?match(L when is_list(L), mnesia:system_info(all)), + ?match({'EXIT', {aborted, Reason }} when element(1, Reason) == badarg + , mnesia:system_info(ali_baba)), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Get meta info about table + +table_info(suite) -> []; +table_info(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + + Tab = table_info, + Type = bag, + ValPos = 3, + Attrs = [k, v], + Arity = length(Attrs) +1, + + Schema = + case mnesia_test_lib:diskless(Config) of + true -> [{type, Type}, {attributes, Attrs}, {index, [ValPos]}, + {ram_copies, Nodes}]; + false -> + [{type, Type}, {attributes, Attrs}, {index, [ValPos]}, + {disc_only_copies, [Node1]}, {ram_copies, [Node2]}, + {disc_copies, [Node3]}] + end, + ?match({atomic, ok}, mnesia:create_table(Tab, Schema)), + + Size = 10, + Keys = lists:seq(1, Size), + Records = [{Tab, A, 7} || A <- Keys], + lists:foreach(fun(Rec) -> ?match(ok, mnesia:dirty_write(Rec)) end, Records), + ?match(Mem when is_integer(Mem), mnesia:table_info(Tab, memory)), + ?match(Size, mnesia:table_info(Tab, size)), + ?match(Type, mnesia:table_info(Tab, type)), + + case mnesia_test_lib:diskless(Config) of + true -> + ?match(Nodes, mnesia:table_info(Tab, ram_copies)); + false -> + ?match([Node3], mnesia:table_info(Tab, mnesia_test_lib:storage_type(disc_copies, Config))), + ?match([Node2], mnesia:table_info(Tab, ram_copies)), + ?match([Node1], mnesia:table_info(Tab, mnesia_test_lib:storage_type(disc_only_copies, Config))) + end, + Read = [Node1, Node2, Node3], + ?match(true, lists:member(mnesia:table_info(Tab, where_to_read), Read)), + Write = ?sort([Node1, Node2, Node3]), + ?match(Write, ?sort(mnesia:table_info(Tab, where_to_write))), + ?match([ValPos], mnesia:table_info(Tab, index)), + ?match(Arity, mnesia:table_info(Tab, arity)), + ?match(Attrs, mnesia:table_info(Tab, attributes)), + ?match({Tab, '_', '_'}, mnesia:table_info(Tab, wild_pattern)), + ?match({atomic, Attrs}, mnesia:transaction(fun() -> + mnesia:table_info(Tab, attributes) end)), + + ?match(L when is_list(L), mnesia:table_info(Tab, all)), + + %% Table info when table not loaded + ?match({atomic, ok}, + mnesia:create_table(tab_info, Schema)), + ?match(stopped, mnesia:stop()), + ?match(stopped, rpc:call(Node2, mnesia, stop, [])), + ?match(ok, mnesia:start()), + ?match(ok, mnesia:wait_for_tables([tab_info], 5000)), + ?match(0, mnesia:table_info(tab_info, size)), + ?verify_mnesia([Node1, Node3], [Node2]). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Check the error descriptions + +error_description(suite) -> []; +error_description(Config) when is_list(Config) -> + ?acquire_nodes(1, Config), + Errors = [nested_transaction, badarg, no_transaction, combine_error, + bad_index, already_exists, index_exists, no_exists, system_limit, + mnesia_down, not_a_db_node, bad_type, node_not_running, + truncated_binary_file, active, illegal + ], + ?match(X when is_atom(X), mnesia:error_description({error, bad_error_msg})), + ?match(X when is_tuple(X), mnesia:error_description({'EXIT', pid, bad})), + %% This is real error msg + ?match(X when is_tuple(X), mnesia:error_description( + {error, + {"Cannot prepare checkpoint (bad reply)", + {{877,957351,758147},a@legolas}, + {error,{node_not_running,a1@legolas}}}})), + check_errors(error, Errors), + check_errors(aborted, Errors), + check_errors('EXIT', Errors). + +check_errors(_Err, []) -> ok; +check_errors(Err, [Desc|R]) -> + ?match(X when is_list(X), mnesia:error_description({Err, Desc})), + check_errors(Err, R). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Add and drop db nodes + +db_node_lifecycle(suite) -> []; +db_node_lifecycle(Config) when is_list(Config) -> + [Node1, Node2, Node3] = AllNodes = ?acquire_nodes(3, Config), + Tab = db_node_lifecycle, + + Who = fun(T) -> + L1 = mnesia:table_info(T, ram_copies), + L2 = mnesia:table_info(T, disc_copies), + L3 = mnesia:table_info(T, disc_only_copies), + L1 ++ L2 ++ L3 + end, + + SNs = ?sort(AllNodes), + + Schema = [{name, Tab}, {ram_copies, [Node1, Node2]}], + ?match({atomic, ok}, mnesia:create_table(Schema)), + + ?match([], mnesia_test_lib:stop_mnesia(AllNodes)), + ?match(ok, mnesia:delete_schema(AllNodes)), + ?match({error, _}, mnesia:create_schema(foo)), + ?match({error, _}, mnesia:create_schema([foo])), + ?match({error, _}, mnesia:create_schema([foo@bar])), + ?match(ok, mnesia:start()), + ?match(false, mnesia:system_info(use_dir)), + ?match({atomic, ok}, mnesia:create_table(Tab, [])), + ?match({aborted, {has_no_disc, Node1}}, mnesia:dump_tables([Tab])), + ?match({aborted, {has_no_disc, Node1}}, mnesia:change_table_copy_type(Tab, node(), disc_copies)), + ?match({aborted, {has_no_disc, Node1}}, mnesia:change_table_copy_type(Tab, node(), disc_only_copies)), + + ?match(stopped, mnesia:stop()), + + ?match(ok, mnesia:create_schema(AllNodes)), + ?match([], mnesia_test_lib:start_mnesia(AllNodes)), + + ?match([SNs, SNs, SNs], + lists:map({lists, sort}, + element(1, rpc:multicall(AllNodes, mnesia, table_info, + [schema, disc_copies])))), + + ?match({aborted, {already_exists, schema, Node2, _}}, + mnesia:change_table_copy_type(schema, Node2, disc_copies)), + ?match({atomic, ok}, + mnesia:change_table_copy_type(schema, Node2, ram_copies)), + ?match({aborted, {already_exists, schema, Node2, _}}, + mnesia:change_table_copy_type(schema, Node2, ram_copies)), + + ?match({atomic, ok}, + mnesia:change_table_copy_type(schema, Node2, disc_copies)), + + ?match([SNs, SNs, SNs], + lists:map({lists, sort}, + element(1, rpc:multicall(AllNodes, mnesia, table_info, + [schema, disc_copies])))), + + %% Delete the DB + + Tab2 = disk_tab, + Tab3 = not_local, + Tab4 = local, + Tab5 = remote, + + Tabs = [Schema, + [{name, Tab2}, {disc_copies, AllNodes}], + [{name, Tab3}, {ram_copies, [Node2, Node3]}], + [{name, Tab4}, {disc_only_copies, [Node1]}], + [{name, Tab5}, {disc_only_copies, [Node2]}]], + + [?match({atomic, ok}, mnesia:create_table(T)) || T <- Tabs ], + + ?match({aborted, {active, _, Node2}}, + mnesia:del_table_copy(schema, Node2)), + + ?match([], mnesia_test_lib:stop_mnesia([Node1])), + ?match({aborted, {node_not_running, Node1}}, + mnesia:del_table_copy(schema, Node2)), + + ?match([], mnesia_test_lib:start_mnesia([Node1],[Tab2,Tab4])), + ?match([], mnesia_test_lib:stop_mnesia([Node2])), + ?match({atomic, ok}, + mnesia:del_table_copy(schema, Node2)), + + %% Check + RemNodes = AllNodes -- [Node2], + + ?match(RemNodes, mnesia:system_info(db_nodes)), + ?match([Node1], Who(Tab)), + ?match(RemNodes, Who(Tab2)), + ?match([Node3], Who(Tab3)), + ?match([Node1], Who(Tab4)), + ?match({'EXIT', {aborted, {no_exists, _, _}}}, Who(Tab5)), + + ?match({atomic, ok}, + mnesia:change_table_copy_type(Tab2, Node3, ram_copies)), + + ?match({atomic, ok}, + mnesia:change_table_copy_type(schema, Node3, ram_copies)), + + ?match([], mnesia_test_lib:stop_mnesia([Node3])), + ?match({atomic, ok}, + mnesia:del_table_copy(schema, Node3)), + ?match([Node1], mnesia:system_info(db_nodes)), + ?match([Node1], Who(Tab)), + ?match([Node1], Who(Tab2)), + ?match({'EXIT', {aborted, {no_exists, _, _}}}, Who(Tab3)), + ?match([Node1], Who(Tab4)), + ?match({'EXIT', {aborted, {no_exists, _, _}}}, Who(Tab5)), + + ?verify_mnesia([Node1], []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Drop a db node when several disk resident nodes are down + +evil_delete_db_node(suite) -> []; +evil_delete_db_node(Config) when is_list(Config) -> + [Node1, Node2, Node3] = AllNodes = ?acquire_nodes(3, Config), + Tab = evil_delete_db_node, + + ?match({atomic, ok}, mnesia:create_table(Tab, [{disc_copies, AllNodes}])), + + ?match([], mnesia_test_lib:stop_mnesia([Node2, Node3])), + + ?match({atomic, ok}, mnesia:del_table_copy(schema, Node2)), + + RemNodes = AllNodes -- [Node2], + + ?match(RemNodes, mnesia:system_info(db_nodes)), + ?match(RemNodes, mnesia:table_info(Tab, disc_copies)), + + ?verify_mnesia([Node1], []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Start and stop the system + +start_and_stop(suite) -> []; +start_and_stop(Config) when is_list(Config) -> + [Node1 | _] = Nodes = ?acquire_nodes(all, Config), + + ?match(stopped, rpc:call(Node1, mnesia, stop, [])), + ?match(stopped, rpc:call(Node1, mnesia, stop, [])), + ?match(ok, rpc:call(Node1, mnesia, start, [])), + ?match(ok, rpc:call(Node1, mnesia, start, [])), + ?match(stopped, rpc:call(Node1, mnesia, stop, [])), + ?verify_mnesia(Nodes -- [Node1], [Node1]), + ?match([], mnesia_test_lib:start_mnesia(Nodes)), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Checkpoints and backup management + +checkpoint(suite) -> []; +checkpoint(Config) when is_list(Config) -> + checkpoint(2, Config), + checkpoint(3, Config). + +checkpoint(NodeConfig, Config) -> + [Node1 | _] = TabNodes = ?acquire_nodes(NodeConfig, Config), + CreateTab = fun(Type, N, Ns) -> + Tab0 = lists:concat(["local_checkpoint_", Type, N]), + Tab = list_to_atom(Tab0), + catch mnesia:delete_table(Tab), + ?match({atomic, ok}, + mnesia:create_table(Tab, [{Type, Ns}])), + Tab + end, + CreateTabs = fun(Type, Acc) -> + [CreateTab(Type, 1, [hd(TabNodes)]), + CreateTab(Type, 2, TabNodes), + CreateTab(Type, 3, [lists:last(TabNodes)])] ++ + Acc + end, + Types = [ram_copies, disc_copies, disc_only_copies], + Tabs = lists:foldl(CreateTabs, [], Types), + Recs = ?sort([{T, N, N} || T <- Tabs, N <- lists:seq(1, 10)]), + lists:foreach(fun(R) -> ?match(ok, mnesia:dirty_write(R)) end, Recs), + + CpName = a_checkpoint_name, + MinArgs = [{name, CpName}, {min, Tabs}, {allow_remote, false}], + ?match({error, _}, rpc:call(Node1, mnesia, activate_checkpoint, [MinArgs])), + + MaxArgs = [{name, CpName}, {max, Tabs}, {allow_remote, true}], + ?match({ok, CpName, L} when is_list(L), + rpc:call(Node1, mnesia, activate_checkpoint, [MaxArgs])), + ?match(ok, rpc:call(Node1, mnesia, deactivate_checkpoint, [CpName])), + + Args = [{name, CpName}, {min, Tabs}, {allow_remote, true}], + ?match({ok, CpName, L} when is_list(L), + rpc:call(Node1, mnesia, activate_checkpoint, [Args])), + Recs2 = ?sort([{T, K, 0} || {T, K, _} <- Recs]), + lists:foreach(fun(R) -> ?match(ok, mnesia:dirty_write(R)) end, Recs2), + ?match(ok, rpc:call(Node1, mnesia, deactivate_checkpoint, [CpName])), + + ?match({error, Reason1 } when element(1, Reason1) == no_exists, + mnesia:deactivate_checkpoint(CpName)), + ?match({error, Reason2 } when element(1, Reason2) == badarg, + mnesia:activate_checkpoint(foo)), + ?match({error, Reason3 } when element(1, Reason3) == badarg, + mnesia:activate_checkpoint([{foo, foo}])), + ?match({error, Reason4 } when element(1, Reason4) == badarg, + mnesia:activate_checkpoint([{max, foo}])), + ?match({error, Reason5 } when element(1, Reason5) == badarg, + mnesia:activate_checkpoint([{min, foo}])), + ?match({error, _}, mnesia:activate_checkpoint([{min, [foo@bar]}])), + ?match({error, Reason6 } when element(1, Reason6) == badarg, + mnesia:activate_checkpoint([{allow_remote, foo}])), + + Fun = fun(Tab) -> ?match({atomic, ok}, mnesia:delete_table(Tab)) end, + lists:foreach(Fun, Tabs), + ?verify_mnesia(TabNodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Create and delete tables + +%% Get meta info about table + +-define(vrl, mnesia_test_lib:verify_replica_location). + +replica_location(suite) -> []; +replica_location(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + Tab = replica_location, + + %% Create three replicas + Schema = [{name, Tab}, {disc_only_copies, [Node1]}, + {ram_copies, [Node2]}, {disc_copies, [Node3]}], + ?match({atomic, ok}, mnesia:create_table(Schema)), + ?match([], ?vrl(Tab, [Node1], [Node2], [Node3], Nodes)), + + %% Delete one replica + ?match({atomic, ok}, mnesia:del_table_copy(Tab, Node2)), + ?match([], ?vrl(Tab, [Node1], [], [Node3], Nodes)), + + %% Move one replica + ?match({atomic, ok}, mnesia:move_table_copy(Tab, Node1, Node2)), + ?match([], ?vrl(Tab, [Node2], [], [Node3], Nodes)), + + %% Change replica type + ?match({atomic, ok}, mnesia:change_table_copy_type(Tab, Node2, ram_copies)), + ?match([], ?vrl(Tab, [], [Node2], [Node3], Nodes)), + + ?verify_mnesia(Nodes, []). + +table_lifecycle(suite) -> []; +table_lifecycle(Config) when is_list(Config) -> + [Node1, Node2] = Nodes = ?acquire_nodes(2, Config), + + ?match({atomic, ok}, mnesia:create_table([{type, bag}, + {ram_copies, [Node1]}, + {attributes, [rajtan, tajtan]}, + {name, order_of_args}])), + ?match([], mnesia:dirty_read({order_of_args, 4711})), + ?match({atomic, ok}, mnesia:create_table([{name, already_exists}, + {ram_copies, [Node1]}])), + ?match({aborted, Reason23 } when element(1, Reason23) ==already_exists, + mnesia:create_table([{name, already_exists}, + {ram_copies, [Node1]}])), + ?match({aborted, Reason21 } when element(1, Reason21) == bad_type, + mnesia:create_table([{name, bad_node}, {ram_copies, ["foo"]}])), + ?match({aborted, Reason2} when element(1, Reason2) == bad_type, + mnesia:create_table([{name, zero_arity}, {attributes, []}])), + ?match({aborted, Reason3} when element(1, Reason3) == badarg, + mnesia:create_table([])), + ?match({aborted, Reason4} when element(1, Reason4) == badarg, + mnesia:create_table(atom)), + ?match({aborted, Reason5} when element(1, Reason5) == badarg, + mnesia:create_table({cstruct, table_name_as_atom})), + ?match({aborted, Reason6 } when element(1, Reason6) == bad_type, + mnesia:create_table([{name, no_host}, {ram_copies, foo}])), + ?match({aborted, Reason7 } when element(1, Reason7) == bad_type, + mnesia:create_table([{name, no_host}, {disc_only_copies, foo}])), + ?match({aborted, Reason8} when element(1, Reason8) == bad_type, + mnesia:create_table([{name, no_host}, {disc_copies, foo}])), + + CreateFun = + fun() -> ?match({aborted, nested_transaction}, + mnesia:create_table([{name, nested_trans}])), ok + end, + ?match({atomic, ok}, mnesia:transaction(CreateFun)), + ?match({atomic, ok}, mnesia:create_table([{name, remote_tab}, + {ram_copies, [Node2]}])), + + ?match({atomic, ok}, mnesia:create_table([{name, a_brand_new_tab}, + {ram_copies, [Node1]}])), + ?match([], mnesia:dirty_read({a_brand_new_tab, 4711})), + ?match({atomic, ok}, mnesia:delete_table(a_brand_new_tab)), + ?match({'EXIT', {aborted, Reason31}} when element(1, Reason31) == no_exists, + mnesia:dirty_read({a_brand_new_tab, 4711})), + ?match({aborted, Reason41} when element(1, Reason41) == no_exists, + mnesia:delete_table(a_brand_new_tab)), + ?match({aborted, Reason9} when element(1, Reason9) == badarg, + mnesia:create_table([])), + + ?match({atomic, ok}, mnesia:create_table([{name, nested_del_trans}, + {ram_copies, [Node1]}])), + + DeleteFun = fun() -> ?match({aborted, nested_transaction}, + mnesia:delete_table(nested_del_trans)), ok end, + ?match({atomic, ok}, mnesia:transaction(DeleteFun)), + + ?match({aborted, Reason10} when element(1, Reason10) == bad_type, + mnesia:create_table([{name, create_with_index}, {index, 2}])), + ?match({aborted, Reason32} when element(1, Reason32) == bad_type, + mnesia:create_table([{name, create_with_index}, {index, [-1]}])), + ?match({aborted, Reason33} when element(1, Reason33) == bad_type, + mnesia:create_table([{name, create_with_index}, {index, [0]}])), + ?match({aborted, Reason34} when element(1, Reason34) == bad_type, + mnesia:create_table([{name, create_with_index}, {index, [1]}])), + ?match({aborted, Reason35} when element(1, Reason35) == bad_type, + mnesia:create_table([{name, create_with_index}, {index, [2]}])), + ?match({atomic, ok}, + mnesia:create_table([{name, create_with_index}, {index, [3]}, + {ram_copies, [Node1]}])), + ets:new(ets_table, [named_table]), + + ?match({aborted, _}, mnesia:create_table(ets_table, [{ram_copies, Nodes}])), + + ?verify_mnesia(Nodes, []). + +add_copy_conflict(suite) -> []; +add_copy_conflict(doc) -> + ["Verify that OTP-5065 doesn't happen again, whitebox testing"]; +add_copy_conflict(Config) when is_list(Config) -> + Nodes = [Node1, Node2] = + ?acquire_nodes(2, Config ++ [{tc_timeout, timer:minutes(2)}]), + + ?match({atomic, ok}, mnesia:create_table(a, [{ram_copies, Nodes}])), + ?match({atomic, ok}, mnesia:create_table(b, [{ram_copies, Nodes}])), + ?match({atomic, ok}, mnesia:create_table(test, [{ram_copies, [Node2]}])), + mnesia:stop(), + ?match(ok,mnesia:start([{no_table_loaders, 1}])), + + verify_ll_queue(10), + + Self = self(), + Test = fun() -> + Res = mnesia:add_table_copy(test, Node1, ram_copies), + Self ! {test, Res} + end, + %% Create conflict with loader queue. + spawn_link(Test), + ?match_receive(timeout), + %% Conflict ok + mnesia_controller:unblock_controller(), + + ?match_receive({test, {atomic,ok}}), + + ?verify_mnesia(Nodes, []), + ?cleanup(1, Config). + +verify_ll_queue(0) -> + ?error("Couldn't find anything in queue~n",[]); +verify_ll_queue(N) -> + ?match(granted,mnesia_controller:block_controller()), + case mnesia_controller:get_info(1000) of + {info,{state,_,true,[],_Loader,[],[],[],_,_,_,_,_,_}} -> + %% Very slow SMP machines havn't loaded it yet.. + mnesia_controller:unblock_controller(), + timer:sleep(10), + verify_ll_queue(N-1); + {info,{state,_,true,[],Loader,LL,[],[],_,_,_,_,_,_}} -> + io:format("~p~n", [{Loader,LL}]), + ?match([_], LL); %% Verify that something is in the loader queue + Else -> + ?error("No match ~p maybe the internal format has changed~n",[Else]) + end. + +add_copy_when_going_down(suite) -> []; +add_copy_when_going_down(doc) -> + ["Tests abort when node we load from goes down"]; +add_copy_when_going_down(Config) -> + [Node1, Node2] = + ?acquire_nodes(2, Config ++ [{tc_timeout, timer:minutes(2)}]), + ?match({atomic, ok}, mnesia:create_table(a, [{ram_copies, [Node1]}])), + %% Grab a write lock + WriteAndWait = fun() -> + mnesia:write({a,1,1}), + receive continue -> ok + end + end, + _Lock = spawn(fun() -> mnesia:transaction(WriteAndWait) end), + Tester = self(), + spawn_link(fun() -> Res = rpc:call(Node2,mnesia, add_table_copy, + [a, Node2, ram_copies]), + Tester ! {test, Res} + end), + %% We have a lock here we should get a timeout + ?match_receive(timeout), + mnesia_test_lib:kill_mnesia([Node1]), + ?match_receive({test,{aborted,_}}), + ?verify_mnesia([Node2], []). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Add, drop and move replicas, change storage types +%% Change table layout (only arity change supported) + +-record(replica_management, {k, v}). +-record(new_replica_management, {k, v, extra}). + +-define(SS(R), lists:sort(element(1,R))). + +replica_management(doc) -> + "Add, drop and move replicas, change storage types."; +replica_management(suite) -> + []; +replica_management(Config) when is_list(Config) -> + %% add_table_copy/3, del_table_copy/2, move_table_copy/3, + %% change_table_copy_type/3, transform_table/3 + + Nodes = [Node1, Node2, Node3] = ?acquire_nodes(3, Config), + + Tab = replica_management, + Attrs = record_info(fields, replica_management), + + %% + %% Add, delete and change replicas + %% + ?match({atomic, ok}, + mnesia:create_table([{name, Tab}, {attributes, Attrs}, + {ram_copies, [Node1, Node3]}])), + [?match(ok, mnesia:dirty_write({Tab, K, K + 2})) || K <-lists:seq(1, 10)], + ?match([], ?vrl(Tab, [], [Node1, Node3], [], Nodes)), + %% R - - + ?match({atomic, ok}, mnesia:dump_tables([Tab])), + ?match({aborted, Reason50 } when element(1, Reason50) == combine_error, + mnesia:add_table_copy(Tab, Node2, disc_copies)), + ?match({aborted, Reason51 } when element(1, Reason51) == combine_error, + mnesia:change_table_copy_type(Tab, Node1, disc_copies)), + ?match({atomic, ok}, mnesia:clear_table(Tab)), + SyncedCheck = fun() -> + mnesia:lock({record,Tab,0}, write), + ?match([0,0,0], ?SS(rpc:multicall(Nodes, mnesia, table_info, [Tab, size]))) + end, + mnesia:transaction(SyncedCheck), + + ?match({[0,0,0], []}, rpc:multicall(Nodes, mnesia, table_info, [Tab, size])), + ?match({atomic, ok}, mnesia:del_table_copy(Tab, Node1)), + ?match({atomic, ok}, mnesia:del_table_copy(Tab, Node3)), + ?match([], ?vrl(Tab, [], [], [], Nodes)), + %% - - - + ?match({aborted,Reason52} when element(1, Reason52) == no_exists, + mnesia:add_table_copy(Tab, Node3, ram_copies)), + + ?match({atomic, ok}, mnesia:create_table([{name, Tab}, + {attributes, Attrs}, + {disc_copies, [Node1]}])), + ?match([], ?vrl(Tab, [], [], [Node1], Nodes)), + %% D - - + [?match(ok, mnesia:dirty_write({Tab, K, K + 2})) || K <-lists:seq(1, 10)], + + ?match({aborted, Reason53} when element(1, Reason53) == badarg, + mnesia:add_table_copy(Tab, Node2, bad_storage_type)), + ?match({atomic, ok}, mnesia:add_table_copy(Tab, Node2, disc_only_copies)), + ?match([], ?vrl(Tab, [Node2], [], [Node1], Nodes)), + ?match([0,10,10], ?SS(rpc:multicall(Nodes, mnesia, table_info, [Tab, size]))), + %% D DO - + ?match({atomic, ok}, mnesia:add_table_copy(Tab, Node3, ram_copies)), + ?match([], ?vrl(Tab, [Node2], [Node3], [Node1], Nodes)), + ?match([10,10,10], ?SS(rpc:multicall(Nodes, mnesia, table_info, [Tab, size]))), + %% D DO R + ?match({atomic, ok}, + mnesia:change_table_copy_type(Tab, Node1, disc_only_copies)), + ?match([], ?vrl(Tab, [Node1, Node2], [Node3], [], Nodes)), + ?match([10,10,10], ?SS(rpc:multicall(Nodes, mnesia, table_info, [Tab, size]))), + %% DO DO R + ?match({aborted, Reason54} when element(1, Reason54) == already_exists, + mnesia:add_table_copy(Tab, Node3, ram_copies)), + ?match({atomic, ok}, mnesia:del_table_copy(Tab, Node1)), + ?match([], ?vrl(Tab, [Node2], [Node3], [], Nodes)), + %% - DO R + ?match({aborted, _}, mnesia:del_table_copy(Tab, Node1)), + ?match(Tab, ets:new(Tab, [named_table])), + ?match({aborted, _}, mnesia:add_table_copy(Tab, Node1, disc_copies)), + ?match(true, ets:delete(Tab)), + ?match({atomic, ok}, mnesia:add_table_copy(Tab, Node1, disc_copies)), + ?match([], ?vrl(Tab, [Node2], [Node3], [Node1], Nodes)), + ?match([10,10,10], ?SS(rpc:multicall(Nodes, mnesia, table_info, [Tab, size]))), + %% D DO R + ?match({atomic, ok},mnesia:change_table_copy_type(Tab, Node3, disc_only_copies)), + ?match([], ?vrl(Tab, [Node2, Node3], [], [Node1], Nodes)), + ?match([10,10,10], ?SS(rpc:multicall(Nodes, mnesia, table_info, [Tab, size]))), + + %% D DO D0 + ?match({atomic, ok}, mnesia:change_table_copy_type(Tab, Node3, ram_copies)), + ?match([], ?vrl(Tab, [Node2], [Node3], [Node1], Nodes)), + ?match([10,10,10], ?SS(rpc:multicall(Nodes, mnesia, table_info, [Tab, size]))), + %% D DO R + ?match({atomic, ok}, + mnesia:change_table_copy_type(Tab, Node2, disc_copies)), + ?match([], ?vrl(Tab, [], [Node3], [Node1,Node2], Nodes)), + ?match([10,10,10], ?SS(rpc:multicall(Nodes, mnesia, table_info, [Tab, size]))), + + %% D D R + ?match({atomic, ok}, mnesia:change_table_copy_type(Tab, Node1, disc_only_copies)), + ?match([], ?vrl(Tab, [Node1], [Node3], [Node2], Nodes)), + ?match([10,10,10], ?SS(rpc:multicall(Nodes, mnesia, table_info, [Tab, size]))), + + %% DO D R + ?match(Tab, ets:new(Tab, [named_table])), + ?match({aborted, _}, mnesia:change_table_copy_type(Tab, Node1, ram_copies)), + ?match(true, ets:delete(Tab)), + ?match({atomic, ok}, mnesia:change_table_copy_type(Tab, Node1, ram_copies)), + ?match([], ?vrl(Tab, [], [Node3,Node1], [Node2], Nodes)), + ?match([10,10,10], ?SS(rpc:multicall(Nodes, mnesia, table_info, [Tab, size]))), + %% R D R + ?match({atomic, ok}, mnesia:change_table_copy_type(Tab, Node1, disc_copies)), + ?match([], ?vrl(Tab, [], [Node3], [Node2,Node1], Nodes)), + ?match([10,10,10], ?SS(rpc:multicall(Nodes, mnesia, table_info, [Tab, size]))), + + %% D D R + ?match({atomic, ok}, mnesia:change_table_copy_type(Tab, Node2, disc_only_copies)), + ?match([], ?vrl(Tab, [Node2], [Node3], [Node1], Nodes)), + ?match([10,10,10], ?SS(rpc:multicall(Nodes, mnesia, table_info, [Tab, size]))), + + %% D DO R + ?match({atomic, ok}, mnesia:change_table_copy_type(Tab, Node3, disc_only_copies)), + ?match([], ?vrl(Tab, [Node2, Node3], [], [Node1], Nodes)), + ?match([10,10,10], ?SS(rpc:multicall(Nodes, mnesia, table_info, [Tab, size]))), + %% D DO DO + %% test clear + ?match({atomic, ok}, mnesia:clear_table(Tab)), + mnesia:transaction(SyncedCheck), + + %% rewrite for rest of testcase + [?match(ok, mnesia:dirty_write({Tab, K, K + 2})) || K <-lists:seq(1, 10)], + + %% D DO DO + ?match({atomic, ok}, mnesia:del_table_copy(Tab, Node2)), + ?match([], ?vrl(Tab, [Node3], [], [Node1], Nodes)), + %% D - DO + ?match({aborted, Reason55} when element(1, Reason55) == already_exists, + mnesia:change_table_copy_type(Tab, Node1, disc_copies)), + + %% + %% Move replica + %% + ?match({atomic, ok}, mnesia:move_table_copy(Tab, Node1, Node2)), + ?match([], ?vrl(Tab, [Node3], [], [Node2], Nodes)), + ?match([0,10,10], ?SS(rpc:multicall(Nodes, mnesia, table_info, [Tab, size]))), + %% - D DO + ?match({aborted, _}, mnesia:move_table_copy(Tab, Node1, Node2)), + ?match([], mnesia_test_lib:stop_mnesia([Node3])), + ?match({atomic,ok}, mnesia:transaction(fun() -> mnesia:write({Tab, 43, sync_me}) end)), + ?match([], ?vrl(Tab, [Node3], [], [Node2],Nodes -- [Node3])), + %% - D DO + ?match({aborted,Reason56} when element(1, Reason56) == not_active, + mnesia:move_table_copy(Tab, Node3, Node1)), + ?match([], ?vrl(Tab, [Node3], [], [Node2],Nodes -- [Node3])), + %% DO D - + ?match([], mnesia_test_lib:start_mnesia([Node3])), + ?match([], ?vrl(Tab, [Node3], [], [Node2], Nodes)), + %% DO D - + + %% + %% Transformer + %% + + NewAttrs = record_info(fields, new_replica_management), + Transformer = + fun(Rec) when is_record(Rec, replica_management) -> + #new_replica_management{k = Rec#replica_management.k, + v = Rec#replica_management.v, + extra = Rec#replica_management.k * 2} + end, + ?match({atomic, ok}, mnesia:transform_table(Tab, fun(R) -> R end, Attrs)), + ?match({atomic, ok}, mnesia:transform_table(Tab, Transformer, NewAttrs, new_replica_management)), + + ERlist = [#new_replica_management{k = K, v = K+2, extra = K*2} || K <- lists:seq(1, 10)], + ARlist = [hd(mnesia:dirty_read(Tab, K)) || K <- lists:seq(1, 10)], + + ?match(ERlist, ARlist), + + ?match({aborted, Reason56} when element(1, Reason56) == bad_type, + mnesia:transform_table(Tab, Transformer, 0)), + ?match({aborted, Reason57} when element(1, Reason57) == bad_type, + mnesia:transform_table(Tab, Transformer, -1)), + ?match({aborted, Reason58} when element(1, Reason58) == bad_type, + mnesia:transform_table(Tab, Transformer, [])), + ?match({aborted, Reason59} when element(1, Reason59) == bad_type, + mnesia:transform_table(Tab, no_fun, NewAttrs)), + ?match({aborted, Reason59} when element(1, Reason59) == bad_type, + mnesia:transform_table(Tab, fun(X) -> X end, NewAttrs, {tuple})), + + %% OTP-3878 + ?match({atomic, ok}, mnesia:transform_table(Tab, ignore, + NewAttrs ++ [dummy])), + ?match({atomic, ok}, mnesia:transform_table(Tab, ignore, + NewAttrs ++ [dummy], last_rec)), + + ARlist = [hd(mnesia:dirty_read(Tab, K)) || K <- lists:seq(1, 10)], + ?match({'EXIT',{aborted,{bad_type,_}}}, + mnesia:dirty_write(Tab, #new_replica_management{})), + ?match(ok, mnesia:dirty_write(Tab, {last_rec, k, v, e, dummy})), + + ?verify_mnesia(Nodes, []). + +schema_availability(doc) -> + ["Test that schema succeeds (or fails) as intended when some db nodes are down."]; +schema_availability(suite) -> + []; +schema_availability(Config) when is_list(Config) -> + [N1, _N2, N3] = Nodes = ?acquire_nodes(3, Config), + Tab = schema_availability, + Storage = mnesia_test_lib:storage_type(ram_copies, Config), + Def1 = [{Storage, [N1, N3]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def1)), + + N = 10, + ?match(ok, mnesia:sync_dirty(fun() -> [mnesia:write({Tab, K, K + 2}) || K <- lists:seq(1, N)], ok end)), + ?match({[N,0,N], []}, rpc:multicall(Nodes, mnesia, table_info, [Tab, size])), + ?match([], mnesia_test_lib:kill_mnesia([N3])), + ?match({[N,0,0], []}, rpc:multicall(Nodes, mnesia, table_info, [Tab, size])), + + ?match([], mnesia_test_lib:start_mnesia([N3], [Tab])), + ?match({[N,0,N], []}, rpc:multicall(Nodes, mnesia, table_info, [Tab, size])), + ?match([], mnesia_test_lib:kill_mnesia([N3])), + + ?match({atomic, ok}, mnesia:clear_table(Tab)), + ?match({[0,0,0], []}, rpc:multicall(Nodes, mnesia, table_info, [Tab, size])), + + ?match([], mnesia_test_lib:start_mnesia([N3], [Tab])), + ?match({[0,0,0], []}, rpc:multicall(Nodes, mnesia, table_info, [Tab, size])), + + ?verify_mnesia(Nodes, []). + +-define(badrpc(Tab), {badrpc, {'EXIT', {aborted,{no_exists,Tab}}}}). + +local_content(doc) -> + ["Test local_content functionality, we want to see that correct" + " properties gets propageted correctly between nodes"]; +local_content(suite) -> []; +local_content(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + Tab1 = local1, + Def1 = [{local_content, true}, {ram_copies, Nodes}], + Tab2 = local2, + Def2 = [{local_content, true}, {disc_copies, [Node1]}], + Tab3 = local3, + Def3 = [{local_content, true}, {disc_only_copies, [Node1]}], + Tab4 = local4, + Def4 = [{local_content, true}, {ram_copies, [Node1]}], + + ?match({atomic, ok}, mnesia:create_table(Tab1, Def1)), + ?match({atomic, ok}, mnesia:create_table(Tab2, Def2)), + ?match({atomic, ok}, mnesia:create_table(Tab3, Def3)), + ?match({atomic, ok}, mnesia:create_table(Tab4, Def4)), + + ?match(ok, rpc:call(Node1, mnesia, dirty_write, [{Tab1, 1, Node1}])), + ?match(ok, rpc:call(Node2, mnesia, dirty_write, [{Tab1, 1, Node2}])), + ?match(ok, rpc:call(Node3, mnesia, dirty_write, [{Tab1, 1, Node3}])), + ?match(ok, rpc:call(Node1, mnesia, dirty_write, [{Tab2, 1, Node1}])), + ?match(ok, rpc:call(Node1, mnesia, dirty_write, [{Tab3, 1, Node1}])), + ?match(ok, rpc:call(Node1, mnesia, dirty_write, [{Tab4, 1, Node1}])), + + ?match(?badrpc(Tab2), rpc:call(Node2, mnesia, dirty_write, [{Tab2, 1, Node2}])), + ?match(?badrpc(Tab3), rpc:call(Node2, mnesia, dirty_write, [{Tab3, 1, Node2}])), + ?match(?badrpc(Tab4), rpc:call(Node2, mnesia, dirty_write, [{Tab4, 1, Node2}])), + + ?match({atomic, ok}, rpc:call(Node1, mnesia, add_table_copy, [Tab2, Node2, ram_copies])), + ?match({atomic, ok}, rpc:call(Node2, mnesia, add_table_copy, [Tab3, Node2, disc_copies])), + ?match({atomic, ok}, rpc:call(Node3, mnesia, add_table_copy, [Tab4, Node2, disc_only_copies])), + ?match([], rpc:call(Node2, mnesia, dirty_read, [{Tab2, 1}])), + ?match([], rpc:call(Node2, mnesia, dirty_read, [{Tab3, 1}])), + ?match([], rpc:call(Node2, mnesia, dirty_read, [{Tab4, 1}])), + + ?match(ok, rpc:call(Node2, mnesia, dirty_write, [{Tab2, 1, Node2}])), + ?match(ok, rpc:call(Node2, mnesia, dirty_write, [{Tab3, 1, Node2}])), + ?match(ok, rpc:call(Node2, mnesia, dirty_write, [{Tab4, 1, Node2}])), + + ?match([{Tab1, 1, Node1}], rpc:call(Node1, mnesia, dirty_read, [{Tab1, 1}])), + ?match([{Tab2, 1, Node1}], rpc:call(Node1, mnesia, dirty_read, [{Tab2, 1}])), + ?match([{Tab3, 1, Node1}], rpc:call(Node1, mnesia, dirty_read, [{Tab3, 1}])), + ?match([{Tab4, 1, Node1}], rpc:call(Node1, mnesia, dirty_read, [{Tab4, 1}])), + + ?match([{Tab1, 1, Node2}], rpc:call(Node2, mnesia, dirty_read, [{Tab1, 1}])), + ?match([{Tab2, 1, Node2}], rpc:call(Node2, mnesia, dirty_read, [{Tab2, 1}])), + ?match([{Tab3, 1, Node2}], rpc:call(Node2, mnesia, dirty_read, [{Tab3, 1}])), + ?match([{Tab4, 1, Node2}], rpc:call(Node2, mnesia, dirty_read, [{Tab4, 1}])), + + ?match([{Tab1, 1, Node3}], rpc:call(Node3, mnesia, dirty_read, [{Tab1, 1}])), + ?match(?badrpc([_Tab2, 1]), rpc:call(Node3, mnesia, dirty_read, [{Tab2, 1}])), + ?match(?badrpc([_Tab3, 1]), rpc:call(Node3, mnesia, dirty_read, [{Tab3, 1}])), + ?match(?badrpc([_Tab4, 1]), rpc:call(Node3, mnesia, dirty_read, [{Tab4, 1}])), + + ?match({atomic, ok}, + mnesia:change_table_copy_type(schema, Node3, ram_copies)), + ?match([], mnesia_test_lib:stop_mnesia([Node3])), + + %% Added for OTP-44306 + ?match(ok, rpc:call(Node3, mnesia, start, [])), + ?match({ok, _}, mnesia:change_config(extra_db_nodes, [Node3])), + + mnesia_test_lib:sync_tables([Node3], [Tab1]), + + ?match([], rpc:call(Node3, mnesia, dirty_read, [{Tab1, 1}])), + + ?match({atomic, ok}, rpc:call(Node1, mnesia, clear_table, [Tab1])), + + SyncedCheck = fun(Tab) -> + mnesia:lock({record,Tab,0}, write), + {OK, []} = rpc:multicall(Nodes, mnesia, table_info, [Tab, size]), + OK + end, + ?match({atomic, [0,1,0]}, mnesia:transaction(SyncedCheck, [Tab1])), + ?match({atomic, ok}, rpc:call(Node2, mnesia, clear_table, [Tab2])), + ?match({atomic, [1,0,0]}, mnesia:transaction(SyncedCheck, [Tab2])), + ?match({atomic, ok}, rpc:call(Node2, mnesia, clear_table, [Tab3])), + ?match({atomic, [1,0,0]}, mnesia:transaction(SyncedCheck, [Tab3])), + + ?verify_mnesia(Nodes, []). + +table_access_modifications(suite) -> + [ + change_table_access_mode, + change_table_load_order, + set_master_nodes, + offline_set_master_nodes + ]. + +change_table_access_mode(suite) -> []; +change_table_access_mode(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + Tab = test_access_mode_tab, + + Def = case mnesia_test_lib:diskless(Config) of + true -> [{name, Tab}, {ram_copies, Nodes}]; + false -> [{name, Tab}, {ram_copies, [Node1]}, + {disc_copies, [Node2]}, + {disc_only_copies, [Node3]}] + end, + ?match({atomic, ok}, mnesia:create_table(Def)), + + Write = fun(What) -> mnesia:write({Tab, 1, What}) end, + Read = fun() -> mnesia:read({Tab, 1}) end, + + ?match({atomic, ok}, mnesia:transaction(Write, [test_ok])), + %% test read_only + ?match({atomic, ok}, mnesia:change_table_access_mode(Tab, read_only)), + ?match({aborted, _}, mnesia:transaction(Write, [nok])), + ?match({'EXIT', {aborted, _}}, mnesia:dirty_write({Tab, 1, [nok]})), + ?match({aborted, _}, rpc:call(Node2, mnesia, transaction, [Write, [nok]])), + ?match({aborted, _}, rpc:call(Node3, mnesia, transaction, [Write, [nok]])), + ?match({atomic, [{Tab, 1, test_ok}]}, mnesia:transaction(Read)), + %% test read_write + ?match({atomic, ok}, mnesia:change_table_access_mode(Tab, read_write)), + ?match({atomic, ok}, mnesia:transaction(Write, [test_ok1])), + ?match({atomic, [{Tab, 1, test_ok1}]}, mnesia:transaction(Read)), + ?match({atomic, ok}, rpc:call(Node2, mnesia, transaction, [Write, [test_ok2]])), + ?match({atomic, [{Tab, 1, test_ok2}]}, mnesia:transaction(Read)), + ?match({atomic, ok}, rpc:call(Node3, mnesia, transaction, [Write, [test_ok3]])), + ?match({atomic, [{Tab, 1, test_ok3}]}, mnesia:transaction(Read)), + + ?match({atomic, ok}, mnesia:delete_table(Tab)), + + Def4 = [{name, Tab}, {access_mode, read_only_bad}], + ?match({aborted, {bad_type, _, _}}, mnesia:create_table(Def4)), + + Def2 = [{name, Tab}, {access_mode, read_only}], + ?match({atomic, ok}, mnesia:create_table(Def2)), + ?match({aborted, _}, mnesia:transaction(Write, [nok])), + + ?match({atomic, ok}, mnesia:change_table_access_mode(Tab, read_write)), + ?match({atomic, ok}, mnesia:delete_table(Tab)), + + Def3 = [{name, Tab}, {mnesia_test_lib:storage_type(disc_copies, Config), + [Node1, Node2]}, + {access_mode, read_write}], + ?match({atomic, ok}, mnesia:create_table(Def3)), + ?match({atomic, ok}, mnesia:transaction(Write, [ok])), + ?match({atomic, ok}, mnesia:change_table_access_mode(Tab, read_only)), + ?match({aborted, _}, mnesia:del_table_copy(Tab, Node2)), + ?match({aborted, _}, mnesia:del_table_copy(Tab, Node1)), + ?match({aborted, _}, mnesia:delete_table(Tab)), + ?match({atomic, ok}, mnesia:change_table_access_mode(Tab, read_write)), + + ?match({aborted, {bad_type, _, _}}, + mnesia:change_table_access_mode(Tab, strange_atom)), + ?match({atomic, ok}, mnesia:delete_table(Tab)), + + ?match({aborted, {no_exists, _}}, + mnesia:change_table_access_mode(err_tab, read_only)), + ?match({aborted, {no_exists, _}}, + mnesia:change_table_access_mode([Tab], read_only)), + ?verify_mnesia(Nodes, []). + +change_table_load_order(suite) -> []; +change_table_load_order(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + Tab1 = load_order_tab1, + Tab2 = load_order_tab2, + Tab3 = load_order_tab3, + + Def = case mnesia_test_lib:diskless(Config) of + true -> [{ram_copies, Nodes}]; + false -> + [{ram_copies, [Node1]}, + {disc_copies, [Node2]}, + {disc_only_copies, [Node3]}] + end, + ?match({atomic, ok}, mnesia:create_table(Tab1, Def)), + ?match({atomic, ok}, mnesia:create_table(Tab2, Def)), + ?match({atomic, ok}, mnesia:create_table(Tab3, Def)), + + ?match({aborted, _}, mnesia:change_table_load_order(Tab1, should_be_integer)), + ?match({aborted, _}, mnesia:change_table_load_order(err_tab, 5)), + ?match({aborted, _}, mnesia:change_table_load_order([err_tab], 5)), + ?match({atomic, ok}, mnesia:change_table_load_order(Tab1, 5)), + ?match({atomic, ok}, mnesia:change_table_load_order(Tab2, 4)), + ?match({atomic, ok}, mnesia:change_table_load_order(Tab3, 73)), + + ?match({aborted, _}, mnesia:change_table_load_order(schema, -32)), + + ?verify_mnesia(Nodes, []). + +set_master_nodes(suite) -> []; +set_master_nodes(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + Tab1 = master_node_tab1, + Tab2 = master_node_tab2, + Tab3 = master_node_tab3, + Def1 = [{ram_copies, [Node1, Node2]}], + Def2 = [{disc_copies, [Node2, Node3]}], + Def3 = [{disc_only_copies, [Node3, Node1]}], + ?match({atomic, ok}, mnesia:create_table(Tab1, Def1)), + ?match({atomic, ok}, mnesia:create_table(Tab2, Def2)), + ?match({atomic, ok}, mnesia:create_table(Tab3, Def3)), + + ?match({error, _}, mnesia:set_master_nodes(schema, ['[email protected]'])), + ?match({error, _}, mnesia:set_master_nodes(badtab, [Node2, Node3])), + ?match({error, _}, mnesia:set_master_nodes(Tab1, [Node3])), + ?match([], mnesia:table_info(Tab1, master_nodes)), + + ?match(ok, mnesia:set_master_nodes(schema, [Node3, Node1])), + ?match([Node3, Node1], mnesia:table_info(schema, master_nodes)), + ?match(ok, mnesia:set_master_nodes(Tab1, [Node2])), + ?match([Node2], mnesia:table_info(Tab1, master_nodes)), + ?match(ok, mnesia:set_master_nodes(Tab1, [Node2, Node1])), + ?match([Node2, Node1], mnesia:table_info(Tab1, master_nodes)), + ?match(ok, mnesia:set_master_nodes(Tab2, [Node2])), % Should set where_to_read to Node2! + ?match([Node2], mnesia:table_info(Tab2, master_nodes)), + ?match(ok, mnesia:set_master_nodes(Tab3, [Node3])), + ?match([Node3], mnesia:table_info(Tab3, master_nodes)), + ?match(ok, mnesia:set_master_nodes(Tab3, [])), + ?match([], mnesia:table_info(Tab3, master_nodes)), + + ?match(ok, mnesia:set_master_nodes([Node1])), + ?match([Node1], mnesia:table_info(schema, master_nodes)), + ?match([Node1], mnesia:table_info(Tab1, master_nodes)), + ?match([], mnesia:table_info(Tab2, master_nodes)), + ?match([Node1], mnesia:table_info(Tab3, master_nodes)), + + ?match(ok, mnesia:set_master_nodes([Node1, Node2])), + ?match([Node1, Node2], mnesia:table_info(schema, master_nodes)), + ?match([Node1, Node2], mnesia:table_info(Tab1, master_nodes)), + ?match([Node2], mnesia:table_info(Tab2, master_nodes)), + ?match([Node1], mnesia:table_info(Tab3, master_nodes)), + + ?verify_mnesia(Nodes, []). + +offline_set_master_nodes(suite) -> []; +offline_set_master_nodes(Config) when is_list(Config) -> + [Node] = Nodes = ?acquire_nodes(1, Config), + Tab1 = offline_master_node_tab1, + Tab2 = offline_master_node_tab2, + Tab3 = offline_master_node_tab3, + Tabs = ?sort([Tab1, Tab2, Tab3]), + Def1 = [{ram_copies, [Node]}], + Def2 = [{disc_copies, [Node]}], + Def3 = [{disc_only_copies, [Node]}], + ?match({atomic, ok}, mnesia:create_table(Tab1, Def1)), + ?match({atomic, ok}, mnesia:create_table(Tab2, Def2)), + ?match({atomic, ok}, mnesia:create_table(Tab3, Def3)), + ?match([], mnesia:system_info(master_node_tables)), + ?match([], mnesia_test_lib:stop_mnesia([Node])), + + ?match(ok, mnesia:set_master_nodes(Tab1, [Node])), + ?match(ok, mnesia:set_master_nodes(Tab2, [Node])), + ?match(ok, mnesia:set_master_nodes(Tab3, [Node])), + ?match([], mnesia_test_lib:start_mnesia([Node])), + ?match(Tabs, ?sort(mnesia:system_info(master_node_tables))), + + ?match([], mnesia_test_lib:stop_mnesia([Node])), + ?match(ok, mnesia:set_master_nodes(Tab1, [])), + ?match(ok, mnesia:set_master_nodes(Tab2, [])), + ?match(ok, mnesia:set_master_nodes(Tab3, [])), + ?match([], mnesia_test_lib:start_mnesia([Node])), + ?match([], mnesia:system_info(master_node_tables)), + + ?match([], mnesia_test_lib:stop_mnesia([Node])), + ?match(ok, mnesia:set_master_nodes([Node])), + ?match([], mnesia_test_lib:start_mnesia([Node])), + AllTabs = ?sort([schema | Tabs]), + ?match(AllTabs, ?sort(mnesia:system_info(master_node_tables))), + + ?match([], mnesia_test_lib:stop_mnesia([Node])), + ?match(ok, mnesia:set_master_nodes([])), + ?match([], mnesia_test_lib:start_mnesia([Node])), + ?match([], mnesia:system_info(master_node_tables)), + + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Syncronize table with log or disc +%% +table_sync(suite) -> + [ + dump_tables, + dump_log, + wait_for_tables, + force_load_table + ]. + +%% Dump ram tables on disc +dump_tables(suite) -> []; +dump_tables(Config) when is_list(Config) -> + [Node1, Node2] = Nodes = ?acquire_nodes(2, Config), + Tab = dump_tables, + Schema = [{name, Tab}, {attributes, [k, v]}, {ram_copies, [Node2]}], + ?match({atomic, ok}, mnesia:create_table(Schema)), + + %% Dump 10 records + Size = 10, + Keys = lists:seq(1, Size), + Records = [{Tab, A, 7} || A <- Keys], + lists:foreach(fun(Rec) -> ?match(ok, mnesia:dirty_write(Rec)) end, Records), + + AllKeys = fun() -> ?sort(mnesia:all_keys(Tab)) end, + + ?match({atomic, Keys}, mnesia:transaction(AllKeys)), + ?match({atomic, ok}, mnesia:dump_tables([Tab])), + + %% Delete one record + ?match(ok, mnesia:dirty_delete({Tab, 5})), + Keys2 = lists:delete(5, Keys), + + ?match({atomic, Keys2}, mnesia:transaction(AllKeys)), + + %% Check that all 10 is restored after a stop + ?match([], mnesia_test_lib:stop_mnesia([Node1, Node2])), + ?match([], mnesia_test_lib:start_mnesia([Node1, Node2])), + ?match(ok, mnesia:wait_for_tables([Tab], infinity)), + + ?match({atomic, Keys}, mnesia:transaction(AllKeys)), + + ?match({aborted,Reason} when element(1, Reason) == no_exists, + mnesia:dump_tables([foo])), + ?verify_mnesia(Nodes, []). + +dump_log(suite) -> []; +dump_log(Config) when is_list(Config) -> + [Node1, Node2] = Nodes = ?acquire_nodes(2, Config), + Tab = dump_log, + Schema = [{name, Tab}, {attributes, [k, v]}, {ram_copies, [Node1, Node2]}], + ?match({atomic, ok}, mnesia:create_table(Schema)), + Tab1 = dump_log1, + Schema1 = [{name, Tab1}, {attributes, [k, v]}, {disc_copies, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Schema1)), + Tab2 = dump_log2, + Schema2 = [{name, Tab2}, {attributes, [k, v]}, {disc_only_copies, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Schema2)), + + ?match(ok, mnesia:dirty_write({Tab, 1, ok})), + ?match(ok, mnesia:dirty_write({Tab1, 1, ok})), + ?match(ok, mnesia:dirty_write({Tab2, 1, ok})), + + ?match(dumped, mnesia:dump_log()), + ?match(dumped, rpc:call(Node2, mnesia, dump_log, [])), + + ?match({atomic, ok}, mnesia:change_table_copy_type(schema, Node2, ram_copies)), + ?match(dumped, rpc:call(Node2, mnesia, dump_log, [])), + + Self = self(), + spawn(fun() -> dump_log(100, Self) end), + spawn(fun() -> dump_log(100, Self) end), + + ?match(ok, receive finished -> ok after 3000 -> timeout end), + ?match(ok, receive finished -> ok after 3000 -> timeout end), + + ?verify_mnesia(Nodes, []). + +dump_log(N, Tester) when N > 0 -> + mnesia:dump_log(), + dump_log(N-1, Tester); +dump_log(_, Tester) -> + Tester ! finished. + + +wait_for_tables(doc) -> + ["Intf. test of wait_for_tables, see also force_load_table"]; +wait_for_tables(suite) -> []; +wait_for_tables(Config) when is_list(Config) -> + [Node1, Node2] = Nodes = ?acquire_nodes(2, Config), + Tab = wf_tab, + Schema = [{name, Tab}, {ram_copies, [Node1, Node2]}], + ?match({atomic, ok}, mnesia:create_table(Schema)), + ?match(ok, mnesia:wait_for_tables([wf_tab], infinity)), + ?match(ok, mnesia:wait_for_tables([], timer:seconds(5))), + ?match({timeout, [bad_tab]}, mnesia:wait_for_tables([bad_tab], timer:seconds(5))), + ?match(ok, mnesia:wait_for_tables([wf_tab], 0)), + ?match({error,_}, mnesia:wait_for_tables([wf_tab], -1)), + ?verify_mnesia(Nodes, []). + +force_load_table(suite) -> []; +force_load_table(Config) when is_list(Config) -> + [Node1, Node2] = ?acquire_nodes(2, Config), + Tab = wf_tab, + + Schema = [{name, Tab}, {disc_copies, [Node1, Node2]}], + ?match({atomic, ok}, mnesia:create_table(Schema)), + ?match(ok, mnesia:dirty_write({Tab, 1, test_ok})), + mnesia_test_lib:kill_mnesia([Node1]), + ?match(ok, rpc:call(Node2, mnesia, dirty_write, [{Tab, 1, test_nok}])), + mnesia_test_lib:kill_mnesia([Node2]), + %% timer:sleep(timer:seconds(5)), + ?match(ok, mnesia:start()), + ?match({timeout, [Tab]}, mnesia:wait_for_tables([Tab], 5)), + ?match({'EXIT', _}, mnesia:dirty_read({Tab, 1})), + ?match(yes, mnesia:force_load_table(Tab)), + ?match([{Tab, 1, test_ok}], mnesia:dirty_read({Tab, 1})), + + ?match({error, _}, mnesia:force_load_table(error_tab)), + ?verify_mnesia([Node1], [Node2]). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +user_properties(doc) -> + ["Test of reading, writing and deletion of user properties", + "Plus initialization of user properties when a table is created", + "Do also test mnesia:table_info(Tab, user_properties)"]; +user_properties(suite) -> []; +user_properties(Config) when is_list(Config) -> + [Node] = Nodes = ?acquire_nodes(1, Config), + Tab1 = user_properties_1, + Tab2 = user_properties_2, + Tab3 = user_properties_3, + Def1 = [{ram_copies, [Node]}, {user_properties, []}], + Def2 = [{mnesia_test_lib:storage_type(disc_copies, Config), [Node]}], + Def3 = [{mnesia_test_lib:storage_type(disc_only_copies, Config), [Node]}, + {user_properties, []}], + + PropKey = my_prop, + Prop = {PropKey, some, elements}, + Prop2 = {PropKey, some, other, elements}, + YourProp= {your_prop}, + ?match({atomic, ok}, mnesia:create_table(Tab1, Def1)), + ?match({atomic, ok}, mnesia:create_table(Tab2, Def2)), + ?match({atomic, ok}, mnesia:create_table(Tab3, Def3)), + + ?match([], mnesia:table_info(Tab1, user_properties)), + ?match([], mnesia:table_info(Tab2, user_properties)), + ?match([], mnesia:table_info(Tab3, user_properties)), + + ?match({'EXIT', {no_exists, {Tab1, user_property, PropKey}}}, + mnesia:read_table_property(Tab1, PropKey)), + ?match({'EXIT', {no_exists, {Tab2, user_property, PropKey}}}, + mnesia:read_table_property(Tab2, PropKey)), + ?match({'EXIT', {no_exists, {Tab3, user_property, PropKey}}}, + mnesia:read_table_property(Tab3, PropKey)), + + ?match({atomic, ok}, mnesia:write_table_property(Tab1, Prop)), + ?match({atomic, ok}, mnesia:write_table_property(Tab2, Prop)), + ?match({atomic, ok}, mnesia:write_table_property(Tab3, Prop)), + ?match({atomic, ok}, mnesia:write_table_property(Tab1, YourProp)), + ?match({atomic, ok}, mnesia:write_table_property(Tab2, YourProp)), + ?match({atomic, ok}, mnesia:write_table_property(Tab3, YourProp)), + + ?match(Prop, mnesia:read_table_property(Tab1, PropKey)), + ?match(Prop, mnesia:read_table_property(Tab2, PropKey)), + ?match(Prop, mnesia:read_table_property(Tab3, PropKey)), + + ?match({atomic, ok}, mnesia:write_table_property(Tab1, Prop2)), + ?match({atomic, ok}, mnesia:write_table_property(Tab2, Prop2)), + ?match({atomic, ok}, mnesia:write_table_property(Tab3, Prop2)), + ?match(Prop2, mnesia:read_table_property(Tab1, PropKey)), + ?match(Prop2, mnesia:read_table_property(Tab2, PropKey)), + ?match(Prop2, mnesia:read_table_property(Tab3, PropKey)), + + ?match({atomic, ok}, mnesia:delete_table_property(Tab1, PropKey)), + ?match({atomic, ok}, mnesia:delete_table_property(Tab2, PropKey)), + ?match({atomic, ok}, mnesia:delete_table_property(Tab3, PropKey)), + + ?match([YourProp], mnesia:table_info(Tab1, user_properties)), + ?match([YourProp], mnesia:table_info(Tab2, user_properties)), + ?match([YourProp], mnesia:table_info(Tab3, user_properties)), + + Tab4 = user_properties_4, + ?match({atomic, ok}, + mnesia:create_table(Tab4, [{user_properties, [Prop]}])), + + ?match([Prop], mnesia:table_info(Tab4, user_properties)), + + %% Some error cases + + ?match({aborted, {bad_type, Tab1, {}}}, + mnesia:write_table_property(Tab1, {})), + ?match({aborted, {bad_type, Tab1, ali}}, + mnesia:write_table_property(Tab1, ali)), + + Tab5 = user_properties_5, + ?match({aborted, {bad_type, Tab5, {user_properties, {}}}}, + mnesia:create_table(Tab5, [{user_properties, {}}])), + ?match({aborted, {bad_type, Tab5, {user_properties, ali}}}, + mnesia:create_table(Tab5, [{user_properties, ali}])), + ?match({aborted, {bad_type, Tab5, {user_properties, [{}]}}}, + mnesia:create_table(Tab5, [{user_properties, [{}]}])), + ?match({aborted, {bad_type, Tab5, {user_properties, [ali]}}}, + mnesia:create_table(Tab5, [{user_properties, [ali]}])), + + ?verify_mnesia(Nodes, []). + + +unsupp_user_props(doc) -> + ["Simple test of adding user props in a schema_transaction"]; +unsupp_user_props(suite) -> []; +unsupp_user_props(Config) when is_list(Config) -> + [Node1] = ?acquire_nodes(1, Config), + Tab1 = silly1, + Tab2 = silly2, + Storage = mnesia_test_lib:storage_type(ram_copies, Config), + + ?match({atomic, ok}, rpc:call(Node1, mnesia, + create_table, [Tab1, [{Storage, [Node1]}]])), + ?match({atomic, ok}, rpc:call(Node1, mnesia, + create_table, [Tab2, [{Storage, [Node1]}]])), + + F1 = fun() -> + mnesia_schema:do_write_table_property( + silly1, {prop, propval1}), + mnesia_schema:do_write_table_property( + silly2, {prop, propval2}), % same key as above + mnesia_schema:do_write_table_property( + schema, {prop, propval3}) % same key as above + end, + ?match({atomic, ok}, rpc:call(Node1, mnesia_schema, + schema_transaction, [F1])), + + ?match([{prop,propval1}], rpc:call(Node1, mnesia, + table_info, [silly1, user_properties])), + ?match([{prop,propval2}], rpc:call(Node1, mnesia, + table_info, [silly2, user_properties])), + ?match([{prop,propval3}], rpc:call(Node1, mnesia, + table_info, [schema, user_properties])), + + F2 = fun() -> + mnesia_schema:do_write_table_property( + silly1, {prop, propval1a}), + mnesia_schema:do_write_table_property( + silly1, {prop, propval1b}) % same key as above + end, + ?match({atomic, ok}, rpc:call(Node1, mnesia_schema, + schema_transaction, [F2])), + + ?match([{prop,propval1b}], rpc:call(Node1, mnesia, + table_info, + [silly1, user_properties])), + ?verify_mnesia([Node1], []). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +snmp_access(doc) -> + ["Make Mnesia table accessible via SNMP"]; + +snmp_access(suite) -> + [ + snmp_open_table, + snmp_close_table, + snmp_get_next_index, + snmp_get_row, + snmp_get_mnesia_key, + snmp_update_counter, + snmp_order + ]. + +snmp_open_table(suite) -> []; +snmp_open_table(Config) when is_list(Config) -> + [Node1, Node2] = Nodes = ?acquire_nodes(2, Config), + Tab1 = local_snmp_table, + + Storage = mnesia_test_lib:storage_type(disc_copies, Config), + Def1 = + case mnesia_test_lib:diskless(Config) of + true -> [{ram_copies, Nodes}]; + false -> + [{disc_copies, [Node1]}, {ram_copies, [Node2]}] + end, + + Tab2 = ext_snmp_table, + Def2 = [{Storage, [Node2]}], + ErrTab = non_existing_tab, + ?match({atomic, ok}, mnesia:create_table(Tab1, Def1)), + ?match({atomic, ok}, mnesia:create_table(Tab2, Def2)), + ?match({atomic, ok}, mnesia:snmp_open_table(Tab1, [{key, integer}])), + ?match({atomic, ok}, mnesia:snmp_open_table(Tab2, [{key, integer}])), + ?match({aborted, _}, mnesia:snmp_open_table(ErrTab, [{key, integer}])), + ?verify_mnesia(Nodes, []). + +snmp_close_table(suite) -> []; +snmp_close_table(Config) when is_list(Config) -> + [Node1, Node2] = Nodes = ?acquire_nodes(2, Config), + Tab1 = local_snmp_table, + Storage = mnesia_test_lib:storage_type(disc_copies, Config), + Def1 = + case mnesia_test_lib:diskless(Config) of + true -> [{ram_copies, Nodes}]; + false -> + [{disc_copies, [Node1]}, {ram_copies, [Node2]}] + end, + + Tab2 = ext_snmp_table, + Def2 = [{snmp, [{key, integer}]}, {Storage, [Node2]}], + ErrTab = non_existing_tab, + ?match({atomic, ok}, mnesia:create_table(Tab1, Def1)), + ?match({atomic, ok}, mnesia:create_table(Tab2, Def2)), + ?match({atomic, ok}, mnesia:create_table(no_snmp_tab, [])), + add_some_records(Tab1, Tab2, 3), + ?match({atomic, ok}, mnesia:snmp_open_table(Tab1, [{key, integer}])), + add_some_records(Tab1, Tab2, 5), + ?match({atomic, ok}, mnesia:snmp_close_table(Tab1)), + + Transform = fun(Tab, Key) -> + [{T,K,V}] = mnesia:read(Tab, Key, write), + mnesia:delete(T,K, write), + mnesia:write({T, {K,K}, V, 43+V}) + end, + + ?match({atomic, ok}, mnesia:transform_table(Tab1, ignore, [key,val,new])), + ?match({atomic, ok}, + mnesia:transaction(fun() -> + mnesia:write_lock_table(Tab1), + Keys = mnesia:select(Tab1, [{{'_','$1','_'}, [], + ['$1']}]), + [Transform(Tab1, Key) || Key <- Keys], + ok + end)), + ?match([{Tab1, {1, 1}, 1, 44}], mnesia:dirty_read(Tab1, {1, 1})), + ?match({atomic, ok}, mnesia:snmp_open_table(Tab1, [{key,{integer,integer}}])), + + ?match({atomic, ok}, mnesia:snmp_close_table(Tab2)), + ?match({atomic, ok}, mnesia:transform_table(Tab2, ignore, [key,val,new])), + ?match({atomic, ok}, + mnesia:transaction(fun() -> + mnesia:write_lock_table(Tab2), + Keys = mnesia:select(Tab2, [{{'_','$1','_'}, [], + ['$1']}]), + [Transform(Tab2, Key) || Key <- Keys], + ok + end)), + + ?match({atomic, ok}, mnesia:snmp_open_table(Tab2, [{key,{integer,integer}}])), + + %% Should be aborted ???? + ?match({atomic, ok}, mnesia:snmp_close_table(no_snmp_tab)), + ?match({aborted, _}, mnesia:snmp_close_table(ErrTab)), + ?verify_mnesia(Nodes, []). + +snmp_get_next_index(suite) -> []; +snmp_get_next_index(Config) when is_list(Config) -> + [Node1, Node2] = Nodes = ?acquire_nodes(2, Config), + Tab1 = local_snmp_table, + Storage = mnesia_test_lib:storage_type(disc_copies, Config), + Def1 = + case mnesia_test_lib:diskless(Config) of + true -> [{ram_copies, Nodes}]; + false -> + [{disc_copies, [Node1]}, {ram_copies, [Node2]}] + end, + + Tab2 = ext_snmp_table, + Def2 = [{Storage, [Node2]}], + ?match({atomic, ok}, mnesia:create_table(Tab1, Def1)), + ?match({atomic, ok}, mnesia:create_table(Tab2, Def2)), + ?match({atomic, ok}, mnesia:snmp_open_table(Tab1, [{key, integer}])), + ?match({atomic, ok}, mnesia:snmp_open_table(Tab2, [{key, integer}])), + add_some_records(Tab1, Tab2, 1), + Test = + fun() -> + %% Test local tables + {success, Res11} = ?match({ok, _}, mnesia:snmp_get_next_index(Tab1,[])), + {ok, Index11} = Res11, + {success, _Res12} = + ?match(endOfTable, mnesia:snmp_get_next_index(Tab1, Index11)), + ?match({'EXIT',_}, mnesia:snmp_get_next_index(Tab1, endOfTable)), + + %% Test external table + {success, Res21} = + ?match({ok, _}, mnesia:snmp_get_next_index(Tab2, [])), + {ok, Index21} = Res21, + {success, _Res22} = + ?match(endOfTable, mnesia:snmp_get_next_index(Tab2, Index21)), + + {ok, Row} = mnesia:snmp_get_row(Tab1, Index11), + ?match(ok, mnesia:dirty_delete(Tab1, hd(Index11))), + + ?match(endOfTable, mnesia:snmp_get_next_index(Tab1,[])), + + ok = mnesia:dirty_write(Row), %% Reset to coming tests + + %% Test of non existing table + %%?match(endOfTable, mnesia:snmp_get_next_index(ErrTab, [])), + ok + end, + ?match(ok, Test()), + ?match({atomic,ok}, mnesia:transaction(Test)), + ?match(ok, mnesia:sync_dirty(Test)), + ?match(ok, mnesia:activity(transaction,Test,mnesia)), + + %%io:format("**********Before ~p~n", [mnesia_lib:val({Tab1,snmp})]), + %%io:format(" ~p ~n", [ets:tab2list(mnesia_lib:val({local_snmp_table,{index,snmp}}))]), + ?match([], mnesia_test_lib:stop_mnesia(Nodes)), + ?match([], mnesia_test_lib:start_mnesia(Nodes, [Tab1, Tab2])), + %%io:format("**********After ~p~n", [mnesia_lib:val({Tab1,snmp})]), + %%io:format(" ~p ~n", [ets:tab2list(mnesia_lib:val({local_snmp_table,{index,snmp}}))]), + + ?match(ok, Test()), + ?match({atomic,ok}, mnesia:transaction(Test)), + ?match(ok, mnesia:sync_dirty(Test)), + ?match(ok, mnesia:activity(transaction,Test,mnesia)), + + ?verify_mnesia(Nodes, []). + +add_some_records(Tab1, Tab2, N) -> + Recs1 = [{Tab1, I, I} || I <- lists:reverse(lists:seq(1, N))], + Recs2 = [{Tab2, I, I} || I <- lists:reverse(lists:seq(20, 20+N-1))], + lists:foreach(fun(R) -> mnesia:dirty_write(R) end, Recs1), + Fun = fun(R) -> mnesia:write(R) end, + Trans = fun() -> lists:foreach(Fun, Recs2) end, + {atomic, ok} = mnesia:transaction(Trans), + %% Sync things, so everything gets everywhere! + {atomic, ok} = mnesia:sync_transaction(fun() -> mnesia:write(lists:last(Recs1)) end), + {atomic, ok} = mnesia:sync_transaction(fun() -> mnesia:write(lists:last(Recs2)) end), + ?sort(Recs1 ++ Recs2). + +snmp_get_row(suite) -> []; +snmp_get_row(Config) when is_list(Config) -> + [Node1, Node2] = Nodes = ?acquire_nodes(2, Config), + Tab1 = local_snmp_table, + Storage = mnesia_test_lib:storage_type(disc_copies, Config), + Def1 = + case mnesia_test_lib:diskless(Config) of + true -> [{ram_copies, Nodes}]; + false -> + [{disc_copies, [Node1]}, {ram_copies, [Node2]}] + end, + Tab2 = ext_snmp_table, + Def2 = [{Storage, [Node2]}], + Tab3 = snmp_table, + Def3 = [{Storage, [Node1]}, + {attributes, [key, data1, data2]}], + + Setup = fun() -> + ?match({atomic, ok}, mnesia:create_table(Tab1, Def1)), + ?match({atomic, ok}, mnesia:create_table(Tab2, Def2)), + ?match({atomic, ok}, mnesia:create_table(Tab3, Def3)), + ?match({atomic, ok}, mnesia:snmp_open_table(Tab1, [{key, integer}])), + ?match({atomic, ok}, mnesia:snmp_open_table(Tab2, [{key, integer}])), + ?match({atomic, ok}, mnesia:snmp_open_table( + Tab3, [{key, {fix_string,integer}}])), + add_some_records(Tab1, Tab2, 1) + end, + Clear = fun() -> + ?match({atomic, ok}, mnesia:delete_table(Tab1)), + ?match({atomic, ok}, mnesia:delete_table(Tab2)), + ?match({atomic, ok}, mnesia:delete_table(Tab3)) + end, + Test = + fun() -> + %% Test local tables + {success, Res11} = + ?match({ok, [1]}, mnesia:snmp_get_next_index(Tab1,[])), + {ok, Index11} = Res11, + ?match({ok, {Tab1,1,1}}, mnesia:snmp_get_row(Tab1, Index11)), + ?match(endOfTable, mnesia:snmp_get_next_index(Tab1, Index11)), + ?match({'EXIT',_}, mnesia:snmp_get_row(Tab1, endOfTable)), + ?match(undefined, mnesia:snmp_get_row(Tab1, [73])), + + Add = fun() -> mnesia:write({Tab3, {"f_string", 3}, data1, data2}) end, + ?match({atomic, ok}, mnesia:transaction(Add)), + {success, {ok, Index31}} = ?match({ok, RowIndex31} when is_list(RowIndex31), + mnesia:snmp_get_next_index(Tab3,[])), + ?match({ok, Row31} when is_tuple(Row31), + mnesia:snmp_get_row(Tab3, Index31)), + ?match(endOfTable, mnesia:snmp_get_next_index(Tab3, Index31)), + Del = fun() -> mnesia:delete({Tab3,{"f_string",3}}) end, + ?match({atomic, ok}, mnesia:transaction(Del)), + ?match(undefined, mnesia:snmp_get_row(Tab3, "f_string" ++ [3])), + ?match(undefined, mnesia:snmp_get_row(Tab3, "f_string" ++ [73])), + + %% Test external table + {success, Res21} = ?match({ok,[20]}, mnesia:snmp_get_next_index(Tab2, [])), + {ok, Index21} = Res21, + ?match({ok, Row2} when is_tuple(Row2), mnesia:snmp_get_row(Tab2, Index21)), + ?match(endOfTable, mnesia:snmp_get_next_index(Tab2, Index21)), + %% Test of non existing table + %% ?match(endOfTable, mnesia:snmp_get_next_index(ErrTab, [])), + ok + end, + Setup(), + ?match(ok, Test()), + Clear(), Setup(), + ?match({atomic,ok}, mnesia:transaction(Test)), + Clear(), Setup(), + ?match(ok, mnesia:sync_dirty(Test)), + Clear(), Setup(), + ?match(ok, mnesia:activity(transaction,Test,mnesia)), + + Clear(), Setup(), + ?match([], mnesia_test_lib:stop_mnesia(Nodes)), + ?match([], mnesia_test_lib:start_mnesia(Nodes, [Tab1, Tab2])), + ?match(ok, Test()), + Clear(), Setup(), + ?match([], mnesia_test_lib:stop_mnesia(Nodes)), + ?match([], mnesia_test_lib:start_mnesia(Nodes, [Tab1, Tab2])), + ?match({atomic,ok}, mnesia:transaction(Test)), + + ?verify_mnesia(Nodes, []). + +snmp_get_mnesia_key(suite) -> []; +snmp_get_mnesia_key(Config) when is_list(Config) -> + [Node1, Node2] = Nodes = ?acquire_nodes(2, Config), + Tab1 = local_snmp_table, + Storage = mnesia_test_lib:storage_type(disc_copies, Config), + Def1 = + case mnesia_test_lib:diskless(Config) of + true -> [{ram_copies, Nodes}]; + false -> + [{disc_copies, [Node1]}, {ram_copies, [Node2]}] + end, + + Tab2 = ext_snmp_table, + Def2 = [{Storage, [Node2]}], + + Tab3 = fix_string, + Setup = fun() -> + ?match({atomic, ok}, mnesia:create_table(Tab1, Def1)), + ?match({atomic, ok}, mnesia:create_table(Tab2, Def2)), + ?match({atomic, ok}, mnesia:create_table(Tab3, Def1)), + ?match({atomic, ok}, mnesia:snmp_open_table(Tab1, [{key, integer}])), + ?match({atomic, ok}, mnesia:snmp_open_table(Tab2, [{key, integer}])), + ?match({atomic, ok}, mnesia:snmp_open_table(Tab3, [{key, {fix_string,integer}}])), + + add_some_records(Tab1, Tab2, 1) + end, + Clear = fun() -> + ?match({atomic, ok}, mnesia:delete_table(Tab1)), + ?match({atomic, ok}, mnesia:delete_table(Tab2)), + ?match({atomic, ok}, mnesia:delete_table(Tab3)) + end, + Test = + fun() -> + %% Test local tables + {success, Res11} = + ?match({ok, [1]}, mnesia:snmp_get_next_index(Tab1,[])), + {ok, Index11} = Res11, + ?match({ok, 1}, mnesia:snmp_get_mnesia_key(Tab1, Index11)), + %% Test external tables + {success, Res21} = + ?match({ok, [20]}, mnesia:snmp_get_next_index(Tab2, [])), + {ok, Index21} = Res21, + ?match({ok, 20}, mnesia:snmp_get_mnesia_key(Tab2, Index21)), + ?match(undefined, mnesia:snmp_get_mnesia_key(Tab2, [97])), + ?match({'EXIT', _}, mnesia:snmp_get_mnesia_key(Tab2, 97)), + + ?match({atomic,ok}, mnesia:transaction(fun() -> mnesia:delete({Tab1,1}) end)), + ?match(undefined, mnesia:snmp_get_mnesia_key(Tab1, Index11)), + + ?match({atomic,ok},mnesia:transaction(fun() -> mnesia:write({Tab1,73,7}) end)), + ?match({ok, 73}, mnesia:snmp_get_mnesia_key(Tab1, [73])), + ?match({atomic,ok}, mnesia:transaction(fun() -> mnesia:delete({Tab1,73}) end)), + ?match(undefined, mnesia:snmp_get_mnesia_key(Tab1, [73])), + + ?match({atomic,ok},mnesia:transaction(fun() -> mnesia:write({Tab3,{"S",5},7}) end)), + ?match({ok,{"S",5}}, mnesia:snmp_get_mnesia_key(Tab3, [$S,5])), + ?match({atomic,ok},mnesia:transaction(fun() -> mnesia:delete({Tab3,{"S",5}}) end)), + ?match(undefined, mnesia:snmp_get_mnesia_key(Tab3, [$S,5])), + + ok + end, + Setup(), + ?match(ok, Test()), + Clear(), Setup(), + ?match({atomic,ok}, mnesia:transaction(Test)), + Clear(), Setup(), + ?match(ok, mnesia:sync_dirty(Test)), + Clear(), Setup(), + ?match(ok, mnesia:activity(transaction,Test,mnesia)), + ?verify_mnesia(Nodes, []). + +snmp_update_counter(doc) -> + ["Verify that counters may be updated for tables with SNMP property"]; +snmp_update_counter(suite) -> []; +snmp_update_counter(Config) when is_list(Config) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = snmp_update_counter, + Def = [{attributes, [key, value]}, + {snmp, [{key, integer}]}, + {ram_copies, [Node1]} + ], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + Oid = {Tab, 1}, + ?match([], mnesia:dirty_read(Oid)), + ?match(ok, mnesia:dirty_write({Tab, 1, 1})), + ?match([{Tab, _Key, 1}], mnesia:dirty_read(Oid)), + ?match(3, mnesia:dirty_update_counter(Oid, 2)), + ?match([{Tab, _Key, 3}], mnesia:dirty_read(Oid)), + ?verify_mnesia(Nodes, []). + +snmp_order(doc) -> + ["Verify that sort order is correct in transactions and dirty variants"]; +snmp_order(suite) -> []; +snmp_order(Config) when is_list(Config) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = snmp_order, + Def = [{attributes, [key, value]}, + {snmp, [{key, {integer, integer, integer}}]}, + {ram_copies, [Node1]} + ], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + Oid = {Tab, 1}, + ?match([], mnesia:dirty_read(Oid)), + ?match({'EXIT', {aborted, _}}, mnesia:dirty_write({Tab, 1, 1})), + [mnesia:dirty_write({Tab, {A,B,2}, default}) || + A <- lists:seq(1, 9, 2), + B <- lists:seq(2, 8, 2)], + + Test1 = fun() -> + Keys0 = get_keys(Tab, []), + ?match(Keys0, lists:sort(Keys0)), + ?match([[1,2,2]|_], Keys0), + Keys1 = get_keys(Tab, [5]), + ?match(Keys1, lists:sort(Keys1)), + ?match([[5,2,2]|_], Keys1), + Keys2 = get_keys(Tab, [7, 4]), + ?match(Keys2, lists:sort(Keys2)), + ?match([[7,4,2]|_], Keys2), + ok + end, + ?match(ok, Test1()), + ?match({atomic, ok},mnesia:transaction(Test1)), + ?match(ok,mnesia:sync_dirty(Test1)), + + Test2 = fun() -> + mnesia:write(Tab, {Tab,{0,0,2},updated}, write), + mnesia:write(Tab, {Tab,{5,3,2},updated}, write), + mnesia:write(Tab, {Tab,{10,10,2},updated}, write), + Keys0 = get_keys(Tab, []), + ?match([[0,0,2],[1,2,2]|_], Keys0), + ?match(Keys0, lists:sort(Keys0)), + + Keys1 = get_keys(Tab, [5]), + ?match([[5,2,2],[5,3,2]|_], Keys1), + ?match(Keys1, lists:sort(Keys1)), + + Keys2 = get_keys(Tab, [7,4]), + ?match([[7,4,2]|_], Keys2), + ?match(Keys2, lists:sort(Keys2)), + ?match([10,10,2], lists:last(Keys0)), + ?match([10,10,2], lists:last(Keys1)), + ?match([10,10,2], lists:last(Keys2)), + + ?match([[10,10,2]], get_keys(Tab, [10])), + ok + end, + + ?match({atomic, ok},mnesia:transaction(Test2)), + + ?verify_mnesia(Nodes, []). + +get_keys(Tab, Key) -> + case mnesia:snmp_get_next_index(Tab, Key) of + endOfTable -> []; + {ok, Next} -> + [Next|get_keys(Tab, Next)] + end. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-record(tab, {i, e1, e2}). % Simple test table + +subscriptions(doc) -> + ["Test the event subscription mechanism"]; +subscriptions(suite) -> + [subscribe_standard, + subscribe_extended]. + +subscribe_extended(doc) -> + ["Test the extended set of events, test with and without checkpoints. "]; +subscribe_extended(suite) -> + []; +subscribe_extended(Config) when is_list(Config) -> + [N1, N2]=Nodes=?acquire_nodes(2, Config), + Tab1 = etab, + Storage = mnesia_test_lib:storage_type(ram_copies, Config), + Def1 = [{Storage, [N1, N2]}, {attributes, record_info(fields, tab)}], + ?match({atomic, ok}, mnesia:create_table(Tab1, Def1)), + + Tab2 = bag, + Def2 = [{Storage, [N1, N2]}, + {type, bag}, + {record_name, Tab1}, + {attributes, record_info(fields, tab)}], + ?match({atomic, ok}, mnesia:create_table(Tab2, Def2)), + + ?match({ok, N1}, mnesia:subscribe({table, Tab1, detailed})), + ?match({ok, N1}, mnesia:subscribe({table, Tab2, detailed})), + + ?match({error, {already_exists, _}}, mnesia:subscribe({table, Tab1, simple})), + ?match({error, {badarg, {table, Tab1, bad}}}, mnesia:subscribe({table, Tab1, bad})), + + ?match({ok, N1}, mnesia:subscribe(activity)), + test_ext_sub(Tab1, Tab2), + + ?match({ok, N1}, mnesia:unsubscribe(activity)), + ?match({ok, N1}, mnesia:subscribe({table, Tab1, detailed})), + ?match({atomic, ok}, mnesia:clear_table(Tab1)), + ?match({mnesia_table_event, {delete, schema, {schema, Tab1}, [{schema, Tab1, _}],_}}, recv_event()), + ?match({mnesia_table_event, {write, schema, {schema, Tab1, _}, [], _}}, recv_event()), + + ?match({atomic, ok}, mnesia_schema:clear_table(Tab2)), + ?match({mnesia_table_event, {delete, schema, {schema, Tab2}, [{schema, Tab2, _}],_}}, + recv_event()), + ?match({mnesia_table_event, {write, schema, {schema, Tab2, _}, [], _}}, recv_event()), + + ?match({ok, N1}, mnesia:unsubscribe({table, Tab2, detailed})), + {ok, _, _} = mnesia:activate_checkpoint([{name, testing}, + {ram_overrides_dump, true}, + {max, [Tab1, Tab2]}]), + ?match({ok, N1}, mnesia:subscribe({table, Tab2, detailed})), + ?match({ok, N1}, mnesia:subscribe(activity)), + test_ext_sub(Tab1, Tab2), + + ?verify_mnesia(Nodes, []). + +test_ext_sub(Tab1, Tab2) -> + %% The basics + Rec1 = {Tab1, 1, 0, 0}, + Rec2 = {Tab1, 1, 1, 0}, + Rec3 = {Tab1, 2, 1, 0}, + Rec4 = {Tab1, 2, 2, 0}, + + Write = fun(Tab, Rec) -> + mnesia:transaction(fun() -> mnesia:write(Tab, Rec, write) + end) + end, + Delete = fun(Tab, Rec) -> + mnesia:transaction(fun() -> mnesia:delete(Tab, Rec, write) + end) + end, + DelObj = fun(Tab, Rec) -> + mnesia:transaction(fun() -> mnesia:delete_object(Tab, Rec, write) + end) + end, + + S = self(), + D = {dirty, self()}, + %% SET + ?match(ok, mnesia:dirty_write(Rec1)), + ?match({mnesia_table_event, {write, Tab1, Rec1, [], D}}, recv_event()), + ?match(ok, mnesia:dirty_write(Rec3)), + ?match({mnesia_table_event, {write, Tab1, Rec3, [], D}}, recv_event()), + ?match(ok, mnesia:dirty_write(Rec1)), + ?match({mnesia_table_event, {write, Tab1, Rec1, [Rec1], D}}, recv_event()), + ?match({atomic, ok}, Write(Tab1, Rec2)), + ?match({mnesia_table_event, {write, Tab1, Rec2, [Rec1], {tid,_,S}}}, recv_event()), + ?match({mnesia_activity_event, {complete, {tid,_,S}}}, recv_event()), + ?match(ok, mnesia:dirty_delete({Tab1, 2})), + ?match({mnesia_table_event, {delete, Tab1, {Tab1, 2}, [Rec3], D}}, recv_event()), + ?match({atomic, ok}, DelObj(Tab1, Rec2)), + ?match({mnesia_table_event, {delete, Tab1, Rec2, [Rec2], {tid,_,S}}}, recv_event()), + ?match({mnesia_activity_event, {complete, {tid,_,S}}}, recv_event()), + + ?match({atomic, ok}, Delete(Tab1, 1)), + ?match({mnesia_table_event, {delete, Tab1, {Tab1, 1}, [], {tid,_,S}}}, recv_event()), + ?match({mnesia_activity_event, {complete, {tid,_,S}}}, recv_event()), + + ?match({ok, _N1}, mnesia:unsubscribe({table, Tab1, detailed})), + + %% BAG + + ?match({atomic, ok}, Write(Tab2, Rec1)), + ?match({mnesia_table_event, {write, Tab2, Rec1, [], {tid,_,S}}}, recv_event()), + ?match({mnesia_activity_event, {complete, {tid,_,S}}}, recv_event()), + ?match({atomic, ok}, Write(Tab2, Rec4)), + ?match({mnesia_table_event, {write, Tab2, Rec4, [], {tid,_,S}}}, recv_event()), + ?match({mnesia_activity_event, {complete, {tid,_,S}}}, recv_event()), + ?match({atomic, ok}, Write(Tab2, Rec3)), + ?match({mnesia_table_event, {write, Tab2, Rec3, [Rec4], {tid,_,S}}}, recv_event()), + ?match({mnesia_activity_event, {complete, {tid,_,S}}}, recv_event()), + ?match({atomic, ok}, Write(Tab2, Rec2)), + ?match({mnesia_table_event, {write, Tab2, Rec2, [Rec1], {tid,_,S}}}, recv_event()), + ?match({mnesia_activity_event, {complete, {tid,_,S}}}, recv_event()), + ?match({atomic, ok}, Write(Tab2, Rec1)), + ?match({mnesia_table_event, {write, Tab2, Rec1, [Rec1, Rec2], {tid,_,S}}}, recv_event()), + ?match({mnesia_activity_event, {complete, {tid,_,S}}}, recv_event()), + ?match({atomic, ok}, DelObj(Tab2, Rec2)), + ?match({mnesia_table_event, {delete, Tab2, Rec2, [Rec2], {tid,_,S}}}, recv_event()), + ?match({mnesia_activity_event, {complete, {tid,_,S}}}, recv_event()), + ?match({atomic, ok}, Delete(Tab2, 1)), + ?match({mnesia_table_event, {delete, Tab2, {Tab2, 1}, [Rec1], {tid,_,S}}}, recv_event()), + ?match({mnesia_activity_event, {complete, {tid,_,S}}}, recv_event()), + ?match({atomic, ok}, Delete(Tab2, 2)), + ?match({mnesia_table_event, {delete, Tab2, {Tab2, 2}, [Rec4, Rec3], {tid,_,S}}}, recv_event()), + ?match({mnesia_activity_event, {complete, {tid,_,S}}}, recv_event()), + ok. + + +subscribe_standard(doc) -> + ["Tests system events and the orignal table events"]; +subscribe_standard(suite) -> []; +subscribe_standard(Config) when is_list(Config)-> + [N1, N2]=?acquire_nodes(2, Config), + Tab = tab, + + Storage = mnesia_test_lib:storage_type(disc_copies, Config), + Def = [{Storage, [N1, N2]}, {attributes, record_info(fields, tab)}], + + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + + %% Check system events + ?match({ok, N1}, mnesia:subscribe(system)), + ?match({ok, N1}, mnesia:subscribe(activity)), + + ?match([], mnesia_test_lib:kill_mnesia([N2])), + ?match({mnesia_system_event, {mnesia_down, N2}}, recv_event()), + ?match(timeout, recv_event()), + + ?match([], mnesia_test_lib:start_mnesia([N2], [Tab])), + ?match({mnesia_activity_event, _}, recv_event()), + ?match({mnesia_system_event,{mnesia_up, N2}}, recv_event()), + + ?match(true, lists:member(self(), mnesia:system_info(subscribers))), + ?match([], mnesia_test_lib:kill_mnesia([N1])), + timer:sleep(500), + mnesia_test_lib:flush(), + ?match([], mnesia_test_lib:start_mnesia([N1], [Tab])), + ?match(timeout, recv_event()), + + ?match({ok, N1}, mnesia:subscribe(system)), + ?match({error, {already_exists, system}}, mnesia:subscribe(system)), + ?match(stopped, mnesia:stop()), + ?match({mnesia_system_event, {mnesia_down, N1}}, recv_event()), + ?match({error, {node_not_running, N1}}, mnesia:subscribe(system)), + ?match([], mnesia_test_lib:start_mnesia([N1, N2], [Tab])), + + %% Check table events + ?match({ok, N1}, mnesia:subscribe(activity)), + Old_Level = mnesia:set_debug_level(trace), + ?match({ok, N1}, mnesia:subscribe({table,Tab})), + + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write(#tab{i=155}) end)), + Self = self(), + ?match({mnesia_table_event, {write, _, _}}, recv_event()), + ?match({mnesia_activity_event, {complete, {tid, _, Self}}}, recv_event()), + + ?match({ok, N1}, mnesia:unsubscribe({table,Tab})), + ?match({ok, N1}, mnesia:unsubscribe(activity)), + + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write(#tab{i=255}) end)), + + ?match(timeout, recv_event()), + mnesia:set_debug_level(Old_Level), + + %% Check deletion of replica + + ?match({ok, N1}, mnesia:subscribe({table,Tab})), + ?match({ok, N1}, mnesia:subscribe(activity)), + ?match(ok, mnesia:dirty_write(#tab{i=355})), + ?match({mnesia_table_event, {write, _, _}}, recv_event()), + ?match({atomic, ok}, mnesia:del_table_copy(Tab, N1)), + ?match({mnesia_activity_event, _}, recv_event()), + ?match(ok, mnesia:dirty_write(#tab{i=455})), + ?match(timeout, recv_event()), + + ?match({atomic, ok}, mnesia:move_table_copy(Tab, N2, N1)), + ?match({mnesia_activity_event, _}, recv_event()), + ?match({ok, N1}, mnesia:subscribe({table,Tab})), + ?match(ok, mnesia:dirty_write(#tab{i=555})), + ?match({mnesia_table_event, {write, _, _}}, recv_event()), + ?match({atomic, ok}, mnesia:move_table_copy(Tab, N1, N2)), + ?match({mnesia_activity_event, _}, recv_event()), + ?match(ok, mnesia:dirty_write(#tab{i=655})), + ?match(timeout, recv_event()), + + ?match({atomic, ok}, mnesia:add_table_copy(Tab, N1, ram_copies)), + ?match({mnesia_activity_event, _}, recv_event()), + ?match({ok, N1}, mnesia:subscribe({table,Tab})), + ?match({error, {already_exists, {table,Tab, simple}}}, + mnesia:subscribe({table,Tab})), + ?match(ok, mnesia:dirty_write(#tab{i=755})), + ?match({mnesia_table_event, {write, _, _}}, recv_event()), + + ?match({atomic, ok}, mnesia:delete_table(Tab)), + ?match({mnesia_activity_event, _}, recv_event()), + ?match(timeout, recv_event()), + + mnesia_test_lib:kill_mnesia([N1]), + + ?verify_mnesia([N2], [N1]). + +recv_event() -> + receive + Event -> Event + after 1000 -> + timeout + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +iteration(doc) -> + ["Verify that the iteration functions works as expected"]; +iteration(suite) -> + [foldl]. + + +foldl(suite) -> + []; +foldl(doc) -> + [""]; +foldl(Config) when is_list(Config) -> + Nodes = [_N1, N2] = ?acquire_nodes(2, Config), + Tab1 = fold_local, + Tab2 = fold_remote, + Tab3 = fold_ordered, + ?match({atomic, ok}, mnesia:create_table(Tab1, [{ram_copies, Nodes}])), + ?match({atomic, ok}, mnesia:create_table(Tab2, [{ram_copies, [N2]}, {type, bag}])), + ?match({atomic, ok}, mnesia:create_table(Tab3, [{ram_copies, Nodes}, + {type, ordered_set}])), + + Tab1Els = [{Tab1, N, N} || N <- lists:seq(1, 10)], + Tab2Els = ?sort([{Tab2, 1, 2} | [{Tab2, N, N} || N <- lists:seq(1, 10)]]), + Tab3Els = [{Tab3, N, N} || N <- lists:seq(1, 10)], + + [mnesia:sync_transaction(fun() -> mnesia:write(E) end) || E <- Tab1Els], + [mnesia:sync_transaction(fun() -> mnesia:write(E) end) || E <- Tab2Els], + [mnesia:sync_transaction(fun() -> mnesia:write(E) end) || E <- Tab3Els], + + Fold = fun(Tab) -> + lists:reverse(mnesia:foldl(fun(E, A) -> [E | A] end, [], Tab)) + end, + Fold2 = fun(Tab, Lock) -> + lists:reverse(mnesia:foldl(fun(E, A) -> [E | A] end, [], Tab, Lock)) + end, + Exit = fun(Tab) -> + lists:reverse(mnesia:foldl(fun(_E, _A) -> exit(testing) end, [], Tab)) + end, + %% Errors + ?match({aborted, _}, mnesia:transaction(Fold, [error])), + ?match({aborted, _}, mnesia:transaction(fun(Tab) -> mnesia:foldl(badfun,[],Tab) end, + [Tab1])), + ?match({aborted, testing}, mnesia:transaction(Exit, [Tab1])), + ?match({aborted, _}, mnesia:transaction(Fold2, [Tab1, read_lock])), + + %% Success + ?match({atomic, Tab1Els}, sort_res(mnesia:transaction(Fold, [Tab1]))), + ?match({atomic, Tab2Els}, sort_res(mnesia:transaction(Fold, [Tab2]))), + ?match({atomic, Tab3Els}, mnesia:transaction(Fold, [Tab3])), + + ?match({atomic, Tab1Els}, sort_res(mnesia:transaction(Fold2, [Tab1, read]))), + ?match({atomic, Tab1Els}, sort_res(mnesia:transaction(Fold2, [Tab1, write]))), + + ?match(Tab1Els, sort_res(mnesia:sync_dirty(Fold, [Tab1]))), + ?match(Tab2Els, sort_res(mnesia:async_dirty(Fold, [Tab2]))), + + ?verify_mnesia(Nodes, []). + +sort_res({atomic, List}) -> + {atomic, ?sort(List)}; +sort_res(Else) when is_list(Else) -> + ?sort(Else); +sort_res(Else) -> + Else. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +debug_support(doc) -> + ["Check that the debug support has not decayed."]; +debug_support(suite) -> + [ + info, + schema_0, + schema_1, + view_0, + view_1, + view_2, + lkill, + kill + ]. + +info(suite) -> []; +info(Config) when is_list(Config) -> + Nodes = ?acquire_nodes(1, Config), + ?match(ok, mnesia:info()), + ?verify_mnesia(Nodes, []). + +schema_0(suite) -> []; +schema_0(Config) when is_list(Config) -> + Nodes = ?acquire_nodes(1, Config), + ?match(ok, mnesia:schema()), + ?verify_mnesia(Nodes, []). + +schema_1(suite) -> []; +schema_1(Config) when is_list(Config) -> + Nodes = ?acquire_nodes(1, Config), + Tab = schema_1, + ?match({atomic, ok}, mnesia:create_table(Tab, [])), + ?match(ok, mnesia:schema(Tab)), + ?verify_mnesia(Nodes, []). + +view_0(suite) -> []; +view_0(Config) when is_list(Config) -> + Nodes = ?acquire_nodes(1, Config), + ?match(ok, mnesia_lib:view()), + ?verify_mnesia(Nodes, []). + +view_1(suite) -> []; +view_1(Config) when is_list(Config) -> + Nodes = ?acquire_nodes(1, Config), + BinCore = mnesia_lib:mkcore({crashinfo, "Just testing..."}), + CoreFile = lists:concat(["MnesiaCore.", node(), ".view_1.", ?MODULE]), + ?match(ok, file:write_file(CoreFile, BinCore)), + ?match(ok, mnesia_lib:view(CoreFile)), + ?match(ok, file:delete(CoreFile)), + + ?match(stopped, mnesia:stop()), + Dir = mnesia:system_info(directory), + ?match(eof, mnesia_lib:view(filename:join(Dir, "LATEST.LOG"))), + ?match(ok, mnesia_lib:view(filename:join(Dir, "schema.DAT"))), + ?verify_mnesia([], Nodes). + +view_2(suite) -> []; +view_2(Config) when is_list(Config) -> + Nodes = ?acquire_nodes(1, Config), + BinCore = mnesia_lib:mkcore({crashinfo, "More testing..."}), + File = lists:concat([?MODULE, "view_2.", node()]), + ?match(ok, file:write_file(File, BinCore)), + ?match(ok, mnesia_lib:view(File, core)), + ?match(ok, file:delete(File)), + + ?match(stopped, mnesia:stop()), + Dir = mnesia:system_info(directory), + ?match(ok, file:rename(filename:join(Dir, "LATEST.LOG"), File)), + ?match(eof, mnesia_lib:view(File, log)), + ?match(ok, file:delete(File)), + + ?match(ok, file:rename(filename:join(Dir, "schema.DAT"), File)), + ?match(ok, mnesia_lib:view(File, dat)), + ?match(ok, file:delete(File)), + ?verify_mnesia([], Nodes). + +lkill(suite) -> []; +lkill(Config) when is_list(Config) -> + [Node1, Node2] = ?acquire_nodes(2, Config), + + ?match(yes, rpc:call(Node1, mnesia, system_info, [is_running])), + ?match(yes, rpc:call(Node2, mnesia, system_info, [is_running])), + ?match(ok, rpc:call(Node2, mnesia, lkill, [])), + ?match(yes, rpc:call(Node1, mnesia, system_info, [is_running])), + ?match(no, rpc:call(Node2, mnesia, system_info, [is_running])), + ?verify_mnesia([Node1], [Node2]). + +kill(suite) -> []; +kill(Config) when is_list(Config) -> + [Node1, Node2] = ?acquire_nodes(2, Config), + + ?match(yes, rpc:call(Node1, mnesia, system_info, [is_running])), + ?match(yes, rpc:call(Node2, mnesia, system_info, [is_running])), + ?match({_, []}, rpc:call(Node2, mnesia, kill, [])), + ?match(no, rpc:call(Node1, mnesia, system_info, [is_running])), + ?match(no, rpc:call(Node2, mnesia, system_info, [is_running])), + ?verify_mnesia([], [Node1, Node2]). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +record_name(doc) -> + ["Verify that record names may be differ from the name of ", + "the hosting table. Check at least access, restore, " + "registry, subscriptions and traveres_backup"]; +record_name(suite) -> + [ + record_name_dirty_access + ]. + +record_name_dirty_access(suite) -> + [ + record_name_dirty_access_ram, + record_name_dirty_access_disc, + record_name_dirty_access_disc_only + ]. + +record_name_dirty_access_ram(suite) -> + []; +record_name_dirty_access_ram(Config) when is_list(Config) -> + record_name_dirty_access(ram_copies, Config). + +record_name_dirty_access_disc(suite) -> + []; +record_name_dirty_access_disc(Config) when is_list(Config) -> + record_name_dirty_access(disc_copies, Config). + +record_name_dirty_access_disc_only(suite) -> + []; +record_name_dirty_access_disc_only(Config) when is_list(Config) -> + record_name_dirty_access(disc_only_copies, Config). + +record_name_dirty_access(Storage, Config) -> + [Node1, _Node2] = Nodes = ?acquire_nodes(2, Config), + + List = lists:concat([record_name_dirty_access_, Storage]), + Tab = list_to_atom(List), + RecName = some_record, + Attr = val, + TabDef = [{type, bag}, + {record_name, RecName}, + {index, [Attr]}, + {Storage, Nodes}], + ?match({atomic, ok}, mnesia:create_table(Tab, TabDef)), + + ?match(RecName, mnesia:table_info(Tab, record_name)), + + ?match(ok, mnesia:dirty_write(Tab, {RecName, 2, 20})), + ?match(ok, mnesia:dirty_write(Tab, {RecName, 2, 21})), + ?match(ok, mnesia:dirty_write(Tab, {RecName, 2, 22})), + + %% Backup test + BupFile = List ++ ".BUP", + CpName = cpname, + CpArgs = [{name, CpName}, {min, [Tab]}, {ram_overrides_dump, true}], + ?match({ok, CpName, _}, mnesia:activate_checkpoint(CpArgs)), + ?match(ok, mnesia:backup_checkpoint(CpName, BupFile)), + ?match(ok, mnesia:deactivate_checkpoint(CpName)), + + ?match(ok, mnesia:dirty_write(Tab, {RecName, 1, 10})), + ?match({ok, Node1}, mnesia:subscribe({table, Tab})), + ?match(ok, mnesia:dirty_write(Tab, {RecName, 3, 10})), + + Twos =?sort( [{RecName, 2, 20}, {RecName, 2, 21}, {RecName, 2, 22}]), + ?match(Twos, ?sort(mnesia:dirty_read(Tab, 2))), + + ?match(ok, mnesia:dirty_delete_object(Tab, {RecName, 2, 21})), + + Tens = ?sort([{RecName, 1, 10}, {RecName, 3, 10}]), + TenPat = {RecName, '_', 10}, + ?match(Tens, ?sort(mnesia:dirty_match_object(Tab, TenPat))), + ?match(Tens, ?sort(mnesia:dirty_select(Tab, [{TenPat, [], ['$_']}]))), + + %% Subscription test + E = mnesia_table_event, + ?match_receive({E, {write, {Tab, 3, 10}, _}}), + ?match_receive({E, {delete_object, {Tab, 2, 21}, _}}), + ?match({ok, Node1}, mnesia:unsubscribe({table, Tab})), + + ?match([], mnesia_test_lib:stop_mnesia([Node1])), + ?match([], mnesia_test_lib:start_mnesia(Nodes, [Tab])), + + ?match(Tens, ?sort(mnesia:dirty_index_match_object(Tab, TenPat, Attr) )), + ?match(Tens, ?sort(mnesia:dirty_index_read(Tab, 10, Attr))), + + ?match([1, 2, 3], ?sort(mnesia:dirty_all_keys(Tab))), + + ?match({ok, Node1}, mnesia:subscribe({table, Tab})), + ?match(ok, mnesia:dirty_delete(Tab, 2)), + ?match([], mnesia:dirty_read(Tab, 2)), + + ?match_receive({E, {delete, {Tab, 2}, _}}), + ?match([], mnesia_test_lib:flush()), + ?match({ok, Node1}, mnesia:unsubscribe({table, Tab})), + + %% Restore test + ?match({atomic, [Tab]}, mnesia:restore(BupFile, [{recreate_tables, [Tab]}])), + ?match(RecName, mnesia:table_info(Tab, record_name)), + + ?match(Twos, ?sort(mnesia:dirty_match_object(Tab, mnesia:table_info(Tab, wild_pattern)))), + ?match(Twos, ?sort(mnesia:dirty_select(Tab, + [{mnesia:table_info(Tab, wild_pattern), + [],['$_']}]))), + + %% Traverse backup test + + Fun = fun(Rec, {Good, Bad}) -> + ?verbose("BUP: ~p~n", [Rec]), + case Rec of + {T, K, V} when T == Tab -> + Good2 = Good ++ [{RecName, K, V}], + {[Rec], {?sort(Good2), Bad}}; + {T, K} when T == Tab -> + Good2 = [G || G <- Good, element(2, G) /= K], + {[Rec], {?sort(Good2), Bad}}; + _ when element(1, Rec) == schema -> + {[Rec], {Good, Bad}}; + _ -> + Bad2 = Bad ++ [Rec], + {[Rec], {Good, ?sort(Bad2)}} + end + end, + + ?match({ok, {Twos, []}}, mnesia:traverse_backup(BupFile, mnesia_backup, + dummy, read_only, + Fun, {[], []})), + ?match(ok, file:delete(BupFile)), + + %% Update counter test + + CounterTab = list_to_atom(lists:concat([Tab, "_counter"])), + CounterTabDef = [{record_name, some_counter}], + C = my_counter, + ?match({atomic, ok}, mnesia:create_table(CounterTab, CounterTabDef)), + ?match(some_counter, mnesia:table_info(CounterTab, record_name)), + ?match(0, mnesia:dirty_update_counter(CounterTab, gurka, -10)), + ?match(10, mnesia:dirty_update_counter(CounterTab, C, 10)), + ?match(11, mnesia:dirty_update_counter(CounterTab, C, 1)), + ?match(4711, mnesia:dirty_update_counter(CounterTab, C, 4700)), + ?match([{some_counter, C, 4711}], mnesia:dirty_read(CounterTab, C)), + ?match(0, mnesia:dirty_update_counter(CounterTab, C, -4747)), + + %% Registry tests + + RegTab = list_to_atom(lists:concat([Tab, "_registry"])), + RegTabDef = [{record_name, some_reg}], + ?match(ok, mnesia_registry:create_table(RegTab, RegTabDef)), + ?match(some_reg, mnesia:table_info(RegTab, record_name)), + {success, RegRecs} = + ?match([_ | _], mnesia_registry_test:dump_registry(node(), RegTab)), + + R = ?sort(RegRecs), + ?match(R, ?sort(mnesia_registry_test:restore_registry(node(), RegTab))), + + ?verify_mnesia(Nodes, []). + +sorted_ets(suite) -> + []; +sorted_ets(Config) when is_list(Config) -> + [N1, N2, N3] = All = ?acquire_nodes(3, Config), + + Tab = sorted_tab, + Def = case mnesia_test_lib:diskless(Config) of + true -> [{name, Tab}, {type, ordered_set}, {ram_copies, All}]; + false -> [{name, Tab}, {type, ordered_set}, + {ram_copies, [N1]}, + {disc_copies,[N2, N3]}] + end, + + ?match({atomic, ok}, mnesia:create_table(Def)), + ?match({aborted, _}, mnesia:create_table(fel, [{disc_only_copies, N1}])), + + ?match([ok | _], + [mnesia:dirty_write({Tab, {dirty, N}, N}) || N <- lists:seq(1, 10)]), + ?match({atomic, _}, + mnesia:sync_transaction(fun() -> + [mnesia:write({Tab, {trans, N}, N}) || + N <- lists:seq(1, 10)] + end)), + + List = mnesia:dirty_match_object({Tab, '_', '_'}), + ?match(List, ?sort(List)), + ?match(List, rpc:call(N2, mnesia, dirty_match_object, [{Tab, '_', '_'}])), + ?match(List, rpc:call(N3, mnesia, dirty_match_object, [{Tab, '_', '_'}])), + + mnesia_test_lib:stop_mnesia(All), + mnesia_test_lib:start_mnesia(All, [sorted_tab]), + + List = mnesia:dirty_match_object({Tab, '_', '_'}), + ?match(List, ?sort(List)), + ?match(List, rpc:call(N2, mnesia, dirty_match_object, [{Tab, '_', '_'}])), + ?match(List, rpc:call(N3, mnesia, dirty_match_object, [{Tab, '_', '_'}])), + + ?match(List, rpc:call(N3, mnesia, dirty_select, [Tab, [{{Tab, '_', '_'},[],['$_']}]])), + + TransMatch = fun() -> + mnesia:write({Tab, {trans, 0}, 0}), + mnesia:write({Tab, {trans, 11}, 11}), + mnesia:match_object({Tab, '_', '_'}) + end, + TransSelect = fun() -> + mnesia:write({Tab, {trans, 0}, 0}), + mnesia:write({Tab, {trans, 11}, 11}), + mnesia:select(Tab, [{{Tab, '_', '_'},[],['$_']}]) + end, + + TList = mnesia:transaction(TransMatch), + STList = ?sort(TList), + ?match(STList, TList), + ?match(STList, rpc:call(N2, mnesia, transaction, [TransMatch])), + ?match(STList, rpc:call(N3, mnesia, transaction, [TransMatch])), + + TSel = mnesia:transaction(TransSelect), + ?match(STList, TSel), + ?match(STList, rpc:call(N2, mnesia, transaction, [TransSelect])), + ?match(STList, rpc:call(N3, mnesia, transaction, [TransSelect])), + + ?match({atomic, ok}, mnesia:create_table(rec, [{type, ordered_set}])), + [ok = mnesia:dirty_write(R) || R <- [{rec,1,1}, {rec,2,1}]], + ?match({atomic, ok}, mnesia:add_table_index(rec, 3)), + TestIt = fun() -> + ok = mnesia:write({rec,1,1}), + mnesia:index_read(rec, 1, 3) + end, + ?match({atomic, [{rec,1,1}, {rec,2,1}]}, mnesia:transaction(TestIt)). + + diff --git a/lib/mnesia/test/mnesia_examples_test.erl b/lib/mnesia/test/mnesia_examples_test.erl new file mode 100644 index 0000000000..d1b1409c9d --- /dev/null +++ b/lib/mnesia/test/mnesia_examples_test.erl @@ -0,0 +1,160 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1997-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(mnesia_examples_test). +-author('[email protected]'). +-compile([export_all]). +-include("mnesia_test_lib.hrl"). + +init_per_testcase(Func, Conf) -> + mnesia_test_lib:init_per_testcase(Func, Conf). + +fin_per_testcase(Func, Conf) -> + mnesia_test_lib:fin_per_testcase(Func, Conf). + +-define(init(N, Config), + mnesia_test_lib:prepare_test_case([{init_test_case, [mnesia]}, + delete_schema], + N, Config, ?FILE, ?LINE)). + +opt_net_load(ExampleMod) -> + opt_net_load([node() | nodes()], ExampleMod, ok). + +opt_net_load([Node | Nodes], ExampleMod, Res) -> + case rpc:call(Node, ?MODULE, opt_load, [ExampleMod]) of + {module, ExampleMod} -> + opt_net_load(Nodes, ExampleMod, Res); + {error, Reason} -> + Error = {opt_net_load, ExampleMod, Node, Reason}, + opt_net_load(Nodes, ExampleMod, {error, Error}); + {badrpc, Reason} -> + Error = {opt_net_load, ExampleMod, Node, Reason}, + opt_net_load(Nodes, ExampleMod, {error, Error}) + end; +opt_net_load([], _ExampleMod, Res) -> + Res. + +opt_load(Mod) -> + case code:is_loaded(Mod) of + {file, _} -> + {module, Mod}; + false -> + Abs = filename:join([code:lib_dir(mnesia), examples, Mod]), + code:load_abs(Abs) + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +all(doc) -> + ["Run all examples mentioned in the documentation", + "Are really all examples covered?"]; +all(suite) -> + [ + bup, + company, + meter, + tpcb + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +bup(doc) -> ["Run the backup examples in bup.erl"]; +bup(suite) -> []; +bup(Config) when is_list(Config) -> + Nodes = ?init(3, Config), + opt_net_load(bup), + ?match(ok, bup:test(Nodes)). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +company(doc) -> + ["Run the company examples in company.erl and company_o.erl"]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +tpcb(doc) -> + ["Run the sample configurations of the stress tests in mnesia_tpcb.erl"]; +tpcb(suite) -> + [ + replica_test, + sticky_replica_test, + dist_test, + conflict_test, + frag_test, + frag2_test, + remote_test, + remote_frag2_test + ]. + +replica_test(suite) -> []; +replica_test(Config) when is_list(Config) -> + ?init(3, Config), + opt_net_load(mnesia_tpcb), + ?match({ok, _}, mnesia_tpcb:start(mnesia_tpcb:config(replica_test, ram_copies))). + +sticky_replica_test(suite) -> []; +sticky_replica_test(Config) when is_list(Config) -> + ?init(3, Config), + opt_net_load(mnesia_tpcb), + ?match({ok, _}, mnesia_tpcb:start(mnesia_tpcb:config(sticky_replica_test, ram_copies))). + +dist_test(suite) -> []; +dist_test(Config) when is_list(Config) -> + ?init(3, [{tc_timeout, timer:minutes(10)} | Config]), + opt_net_load(mnesia_tpcb), + ?match({ok, _}, mnesia_tpcb:start(mnesia_tpcb:config(dist_test, ram_copies))). + +conflict_test(suite) -> []; +conflict_test(Config) when is_list(Config) -> + ?init(3, Config), + opt_net_load(mnesia_tpcb), + ?match({ok, _}, mnesia_tpcb:start(mnesia_tpcb:config(conflict_test, ram_copies))). + +frag_test(suite) -> []; +frag_test(Config) when is_list(Config) -> + ?init(3, Config), + opt_net_load(mnesia_tpcb), + ?match({ok, _}, mnesia_tpcb:start(mnesia_tpcb:config(frag_test, ram_copies))). + +frag2_test(suite) -> []; +frag2_test(Config) when is_list(Config) -> + ?init(3, Config), + opt_net_load(mnesia_tpcb), + ?match({ok, _}, mnesia_tpcb:start(mnesia_tpcb:config(frag2_test, ram_copies))). + +remote_test(suite) -> []; +remote_test(Config) when is_list(Config) -> + ?init(3, Config), + opt_net_load(mnesia_tpcb), + ?match({ok, _}, mnesia_tpcb:start(mnesia_tpcb:config(remote_test, ram_copies))). + +remote_frag2_test(suite) -> []; +remote_frag2_test(Config) when is_list(Config) -> + ?init(3, Config), + opt_net_load(mnesia_tpcb), + ?match({ok, _}, mnesia_tpcb:start(mnesia_tpcb:config(remote_frag2_test, ram_copies))). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +meter(doc) -> + ["Run the meter example in mnesia_meter.erl"]; +meter(suite) -> + []; +meter(Config) when is_list(Config) -> + [N | _] = ?init(3, Config), + opt_net_load(mnesia_meter), + ?match(ok, mnesia_meter:go(ram_copies, [N])). + + diff --git a/lib/mnesia/test/mnesia_frag_test.erl b/lib/mnesia/test/mnesia_frag_test.erl new file mode 100644 index 0000000000..4add340254 --- /dev/null +++ b/lib/mnesia/test/mnesia_frag_test.erl @@ -0,0 +1,875 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1999-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(mnesia_frag_test). +-author('[email protected]'). +-include("mnesia_test_lib.hrl"). + +-compile([export_all]). + +init_per_testcase(Func, Conf) -> + mnesia_test_lib:init_per_testcase(Func, Conf). + +fin_per_testcase(Func, Conf) -> + mnesia_test_lib:fin_per_testcase(Func, Conf). + +-define(match_dist(ExpectedRes, Expr), + case ?match(ExpectedRes, Expr) of + + mnesia_test_lib:error(Format, Args,?FILE,?LINE)). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +all(doc) -> + ["Verify the functionality of fragmented tables"]; +all(suite) -> + [ + light, + medium + ]. + +light(suite) -> + [ + nice, + evil + ]. + +medium(suite) -> + [ + consistency + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +nice(suite) -> + [ + nice_single, + nice_multi, + nice_access, + iter_access + ]. + +nice_single(suite) -> []; +nice_single(Config) when is_list(Config) -> + [Node1, Node2] = Nodes = ?acquire_nodes(2, Config), + + %% Create a table with 2 fragments and 12 records + Tab = nice_frag, + Props = [{n_fragments, 2}, {node_pool, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Tab, [{frag_properties, Props}])), + Records = [{Tab, N, -N} || N <- lists:seq(1, 12)], + [frag_write(Tab, R) || R <- Records], + ?match([{Node1, 2}], frag_dist(Tab)), + ?match([8, 4], frag_rec_dist(Tab)), + + %% Adding a new node to pool should not affect distribution + ?match({atomic, ok}, mnesia:change_table_frag(Tab, {add_node, Node2})), + Dist = frag_dist(Tab), + ?match([{Node2, 0}, {Node1, 2}], Dist), + ?match([8, 4], frag_rec_dist(Tab)), + + %% Add new fragment hopefully on the new node + ?match({atomic, ok}, mnesia:change_table_frag(Tab, {add_frag, Dist})), + Dist2 = frag_dist(Tab), + ?match([{Node2, 1}, {Node1, 2}], Dist2), + ?match([3, 4, 5], frag_rec_dist(Tab)), + + %% Add new fragment hopefully on the new node + ?match({atomic, ok}, mnesia:change_table_frag(Tab, {add_frag, Dist2})), + Dist3 = frag_dist(Tab), + ?match([{Node1, 2}, {Node2, 2}], Dist3), + ?match([3, 2, 5, 2], frag_rec_dist(Tab)), + + %% Add new fragment hopefully on the new node + ?match({atomic, ok}, mnesia:change_table_frag(Tab, {add_frag, Dist3})), + Dist4 = frag_dist(Tab), + ?match([{Node2, 2}, {Node1, 3}], Dist4), + ?match([_, _, _, _, _], frag_rec_dist(Tab)), + + %% Dropping a node in pool should not affect distribution + ?match({atomic, ok}, mnesia:change_table_frag(Tab, {del_node, Node1})), + ?match([{Node2, 2}, {Node1, 3}], frag_dist(Tab)), + ?match([_, _, _, _, _], frag_rec_dist(Tab)), + + %% Dropping a fragment + ?match({atomic, ok}, mnesia:change_table_frag(Tab, del_frag)), + Dist5 = frag_dist(Tab), + ?match([{Node2, 2}, {Node1, 2}], Dist5), + ?match([3, 2, 5, 2], frag_rec_dist(Tab)), + + %% Add new fragment hopefully on the new node + ?match({atomic, ok}, mnesia:change_table_frag(Tab, {add_frag, Dist5})), + Dist6 = frag_dist(Tab), + ?match([{Node2, 3}, {Node1, 2}], Dist6), + ?match([_, _, _, _, _], frag_rec_dist(Tab)), + + %% Dropping all fragments but one + ?match({atomic, ok}, mnesia:change_table_frag(Tab, del_frag)), + ?match([3, 2, 5, 2], frag_rec_dist(Tab)), + ?match({atomic, ok}, mnesia:change_table_frag(Tab, del_frag)), + ?match([3, 4, 5], frag_rec_dist(Tab)), + ?match({atomic, ok}, mnesia:change_table_frag(Tab, del_frag)), + ?match([8, 4], frag_rec_dist(Tab)), + ?match({atomic, ok}, mnesia:change_table_frag(Tab, del_frag)), + ?match([{Node2, 0}, {Node1, 1}], frag_dist(Tab)), + ?match([12], frag_rec_dist(Tab)), + + %% Defragmenting the table clears frag_properties + ?match(Len when Len > 0, + length(mnesia:table_info(Tab, frag_properties))), + ?match({atomic, ok}, mnesia:change_table_frag(Tab, deactivate)), + ?match(0, length(mnesia:table_info(Tab, frag_properties))), + + %% Making the table fragmented again + Props2 = [{n_fragments, 1}], + ?match({atomic, ok}, mnesia:change_table_frag(Tab, {activate, Props2})), + ?match({atomic, ok}, mnesia:change_table_frag(Tab, {add_frag, frag_dist(Tab)})), + Dist7 = frag_dist(Tab), + ?match([{Node1, 1}, {Node2, 1}], Dist7), + ?match([8, 4], frag_rec_dist(Tab)), + + %% Deleting the fragmented table + ?match({atomic, ok}, mnesia:delete_table(Tab)), + ?match(false, lists:member(Tab, mnesia:system_info(tables))), + + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +nice_multi(doc) -> + ["Extending the nice case with one more node, ", + "one more replica and a foreign key"]; +nice_multi(suite) -> []; +nice_multi(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + + %% Create a table with 2 fragments and 8 records + Tab = frag_master, + Name = frag_rec, + Type = case mnesia_test_lib:diskless(Config) of + true -> n_ram_copies; + false -> n_disc_copies + end, + Props = [{n_fragments, 2}, + {Type, 2}, + {node_pool, [Node2, Node1]}], + Def = [{frag_properties, Props}, + {attributes, [id, data]}, + {record_name, Name}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + [frag_write(Tab, {Name, Id, -Id}) || Id <- lists:seq(1, 8)], + ?match([6, 2], frag_rec_dist(Tab)), + ?match([{Node2, 2}, {Node1, 2}], frag_dist(Tab)), + + %% And connect another table to it, via a foreign key + TabF = frag_slave, + PropsF = [{foreign_key, {Tab, foreign_id}}], + DefF = [{frag_properties, PropsF}, + {attributes, [id, foreign_id]}], + + ?match({atomic, ok}, mnesia:create_table(TabF, DefF)), + [frag_write(TabF, {TabF, {Id}, Id}) || Id <- lists:seq(1, 16)], + ?match([10, 6], frag_rec_dist(TabF)), + ?match([{Node2, 2}, {Node1, 2}], frag_dist(TabF)), + + %% Adding a new node to pool should not affect distribution + ?match({atomic, ok}, mnesia:change_table_frag(Tab, {add_node, Node3})), + Dist = frag_dist(Tab), + ?match([{Node3, 0}, {Node2, 2}, {Node1, 2}], Dist), + ?match([6, 2], frag_rec_dist(Tab)), + DistF = frag_dist(TabF), + ?match([{Node3, 0}, {Node2, 2}, {Node1, 2}], DistF), + ?match([10, 6], frag_rec_dist(TabF)), + + %% Add new fragment hopefully on the new node + ?match({atomic, ok}, mnesia:change_table_frag(Tab, {add_frag, Dist})), + Dist2 = frag_dist(Tab), + ?match([{Node3, 1},{Node1, 2},{Node2,3}], Dist2), + ?match([_, _, _], frag_rec_dist(Tab)), + DistF2 = frag_dist(TabF), + ?match([{Node3, 1},{Node1, 2},{Node2,3}], DistF2), + ?match([_, _, _], frag_rec_dist(TabF)), + + %% Add new fragment hopefully on the new node + ?match({atomic, ok}, mnesia:change_table_frag(Tab, {add_frag, Dist2})), + Dist3 = frag_dist(Tab), + ?match([{Node3, 2},{Node2,3},{Node1, 3}], Dist3), + ?match([3, 0, 3, 2], frag_rec_dist(Tab)), + DistF3 = frag_dist(TabF), + ?match([{Node3, 2},{Node2,3},{Node1, 3}], DistF3), + ?match([3, 3, 7, 3], frag_rec_dist(TabF)), + + %% Add new fragment hopefully on the new node + ?match({atomic, ok}, mnesia:change_table_frag(Tab, {add_frag, Dist3})), + Dist4 = frag_dist(Tab), + ?match([{Node1, 3}, {Node3, 3},{Node2, 4}], Dist4), + ?match([_, _, _, _, _], frag_rec_dist(Tab)), + DistF4 = frag_dist(TabF), + ?match([{Node1, 3}, {Node3, 3},{Node2, 4}], DistF4), + ?match([_, _, _, _, _], frag_rec_dist(TabF)), + + %% Dropping a node in pool should not affect distribution + ?match({atomic, ok}, mnesia:change_table_frag(Tab, {del_node, Node1})), + ?match([{Node3, 3},{Node2, 4}, {Node1, 3}], frag_dist(Tab)), + ?match([_, _, _, _, _], frag_rec_dist(Tab)), + ?match([{Node3, 3},{Node2, 4}, {Node1, 3}], frag_dist(TabF)), + ?match([_, _, _, _, _], frag_rec_dist(TabF)), + + %% Dropping a fragment + ?match({atomic, ok}, mnesia:change_table_frag(Tab, del_frag)), + Dist5 = frag_dist(Tab), + ?match([{Node3, 2},{Node2,3},{Node1, 3}], Dist5), + ?match([3, 0, 3, 2], frag_rec_dist(Tab)), + DistF5 = frag_dist(Tab), + ?match([{Node3, 2},{Node2,3},{Node1, 3}], DistF5), + ?match([3, 3, 7, 3], frag_rec_dist(TabF)), + + %% Add new fragment hopefully on the new node + ?match({atomic, ok}, mnesia:change_table_frag(Tab, {add_frag, Dist5})), + Dist6 = frag_dist(Tab), + ?match([{Node3, 3},{Node2, 4},{Node1, 3}], Dist6), + ?match([_, _, _, _, _], frag_rec_dist(Tab)), + DistF6 = frag_dist(TabF), + ?match([{Node3, 3},{Node2, 4},{Node1, 3}], DistF6), + ?match([_, _, _, _, _], frag_rec_dist(TabF)), + + %% Dropping all fragments but one + ?match({atomic, ok}, mnesia:change_table_frag(Tab, del_frag)), + ?match([3, 0, 3, 2], frag_rec_dist(Tab)), + ?match([3, 3, 7, 3], frag_rec_dist(TabF)), + ?match({atomic, ok}, mnesia:change_table_frag(Tab, del_frag)), + ?match([_, _, _], frag_rec_dist(Tab)), + ?match([_, _, _], frag_rec_dist(TabF)), + ?match({atomic, ok}, mnesia:change_table_frag(Tab, del_frag)), + ?match([6, 2], frag_rec_dist(Tab)), + ?match([10, 6], frag_rec_dist(TabF)), + ?match({atomic, ok}, mnesia:change_table_frag(Tab, del_frag)), + ?match([{Node3, 0}, {Node2, 1}, {Node1, 1}], frag_dist(Tab)), + ?match([8], frag_rec_dist(Tab)), + ?match([{Node3, 0}, {Node2, 1}, {Node1, 1}], frag_dist(TabF)), + ?match([16], frag_rec_dist(TabF)), + + %% Defragmenting the tables clears frag_properties + ?match(Len when Len > 0, + length(mnesia:table_info(TabF, frag_properties))), + ?match({atomic, ok}, mnesia:change_table_frag(TabF, deactivate)), + ?match(0, length(mnesia:table_info(TabF, frag_properties))), + ?match(Len when Len > 0, + length(mnesia:table_info(Tab, frag_properties))), + ?match({atomic, ok}, mnesia:change_table_frag(Tab, deactivate)), + ?match(0, length(mnesia:table_info(Tab, frag_properties))), + + %% Making the tables fragmented again + Props2 = [{n_fragments, 1}, {node_pool, [Node1, Node2]}], + ?match({atomic, ok}, mnesia:change_table_frag(Tab, {activate, Props2})), + ?match({atomic, ok}, mnesia:delete_table(TabF)), + ?match({atomic, ok}, mnesia:create_table(TabF, DefF)), + [frag_write(TabF, {TabF, {Id}, Id}) || Id <- lists:seq(1, 16)], + ?match({atomic, ok}, mnesia:change_table_frag(Tab, {add_frag, frag_dist(Tab)})), + ?match([{Node1, 2}, {Node2, 2}], frag_dist(Tab)), + ?match([6, 2], frag_rec_dist(Tab)), + ?match([{Node1, 2}, {Node2, 2}], frag_dist(TabF)), + ?match([10, 6], frag_rec_dist(TabF)), + + %% Deleting the fragmented tables + ?match({atomic, ok}, mnesia:delete_table(TabF)), + ?match(false, lists:member(TabF, mnesia:system_info(tables))), + ?match({atomic, ok}, mnesia:delete_table(Tab)), + ?match(false, lists:member(Tab, mnesia:system_info(tables))), + + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +nice_access(doc) -> + ["Cover entire callback interface"]; +nice_access(suite) -> []; +nice_access(Config) when is_list(Config) -> + Nodes = ?acquire_nodes(3, Config), + + Tab = frag_access, + Pool = lists:sort(Nodes), + Props = [{n_fragments, 20}, + {n_ram_copies, 2}, + {node_pool, Pool}], + Def = [{frag_properties, Props}, + {type, ordered_set}, + {index, [val]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + [frag_write(Tab, {Tab, Id, Id}) || Id <- lists:seq(1, 400)], + + %% And connect another table to it, via a foreign key + TabF = frag_access_slave, + PropsF = [{foreign_key, {Tab, val}}], + DefF = [{frag_properties, PropsF}, + {index, [val]}], + ?match({atomic, ok}, mnesia:create_table(TabF, DefF)), + [frag_write(TabF, {TabF, Id, Id}) || Id <- lists:seq(1, 400)], + + ?match(done, mnesia:activity(transaction, fun do_access/3, [Tab, Tab, Pool], mnesia_frag)), + ?match(done, mnesia:activity(transaction, fun do_access/3, [TabF, Tab, Pool], mnesia_frag)), + + ?verify_mnesia(Nodes, []). + +do_access(Tab, Master, Pool) -> + ?match(20, mnesia:table_info(Tab, n_fragments)), + ?match(Pool, mnesia:table_info(Tab, node_pool)), + ?match(2, mnesia:table_info(Tab, n_ram_copies)), + ?match(0, mnesia:table_info(Tab, n_disc_copies)), + ?match(0, mnesia:table_info(Tab, n_disc_only_copies)), + ?match(20, length(mnesia:table_info(Tab, frag_names))), + ?match(20, length(mnesia:table_info(Tab, frag_size))), + ?match(20, length(mnesia:table_info(Tab, frag_memory))), + PoolSize = length(Pool), + ?match(PoolSize, length(mnesia:table_info(Tab, frag_dist))), + ?match(400, mnesia:table_info(Tab, size)), + ?match(I when is_integer(I), mnesia:table_info(Tab, memory)), + ?match(Tab, mnesia:table_info(Tab, base_table)), + + Foreign = + if + Master == Tab -> + ?match(undefined, mnesia:table_info(Tab, foreign_key)), + ?match([_], mnesia:table_info(Tab, foreigners)), + ?match({'EXIT', {aborted, {combine_error, Tab, frag_properties, {foreign_key, undefined}}}}, + mnesia:read({Tab, 5}, 5, read)), + fun({T, _K}) -> T end; + true -> + ?match({Master, 3}, mnesia:table_info(Tab, foreign_key)), + ?match([], mnesia:table_info(Tab, foreigners)), + fun({T, K}) -> {T, K} end + end, + + Attr = val, + ?match(400, mnesia:table_info(Tab, size)), + Count = fun(_, N) -> N + 1 end, + ?match(400, mnesia:foldl(Count, 0, Tab)), + ?match(400, mnesia:foldr(Count, 0, Tab)), + ?match(ok, mnesia:write({Tab, [-1], 1})), + ?match(401, length(mnesia:match_object(Tab, {Tab, '_', '_'}, read))), + ?match(401, length(mnesia:select(Tab, [{{Tab, '_', '$1'}, [], ['$1']}], read))), + + First = mnesia:select(Tab, [{{Tab, '_', '$1'}, [], ['$1']}], 10, read), + TestCont = fun('$end_of_table', Total, _This) -> + Total; + ({Res,Cont1}, Total, This) -> + Cont = mnesia:select(Cont1), + This(Cont, length(Res) + Total, This) + end, + ?match(401, TestCont(First, 0, TestCont)), + + %% OTP + [_, Frag2|_] = frag_names(Tab), + Frag2key = mnesia:dirty_first(Frag2), + ?match({[Frag2key],_},mnesia:select(Tab,[{{Tab,Frag2key,'$1'},[],['$1']}],100,read)), + + ?match([{Tab, [-1], 1}], mnesia:read(Foreign({Tab, 1}), [-1], read)), + ?match(401, mnesia:foldl(Count, 0, Tab)), + ?match(401, mnesia:foldr(Count, 0, Tab)), + ?match(ok, mnesia:delete(Foreign({Tab, 2}), 2, write)), + ?match([], mnesia:read(Foreign({Tab, 2}), 2, read)), + ?match([{Tab, 3, 3}], mnesia:read(Foreign({Tab, 3}), 3, read)), + ?match(400, mnesia:foldl(Count, 0, Tab)), + ?match(400, mnesia:foldr(Count, 0, Tab)), + ?match(ok, mnesia:delete_object({Tab, 3, 3})), + ?match([], mnesia:read(Foreign({Tab, 3}), 3, read)), + One = lists:sort([{Tab, 1, 1}, {Tab, [-1], 1}]), + Pat = {Tab, '$1', 1}, + ?match(One, lists:sort(mnesia:match_object(Tab, Pat, read))), + ?match([1,[-1]], lists:sort(mnesia:select(Tab, [{Pat, [], ['$1']}], read))), + ?match([[[-1]]], lists:sort(mnesia:select(Tab, [{Pat, [{is_list, '$1'}], [['$1']]}], read))), + ?match([[1, 100]], lists:sort(mnesia:select(Tab, [{Pat, [{is_integer, '$1'}], [['$1',100]]}], read))), + ?match([1,[-1]], lists:sort(mnesia:select(Tab, [{Pat, [{is_list, '$1'}], ['$1']},{Pat, [{is_integer, '$1'}], ['$1']}], read))), + ?match(One, lists:sort(mnesia:index_match_object(Tab, Pat, Attr, read) )), + ?match(One, lists:sort(mnesia:index_read(Tab, 1, Attr))), + Keys = mnesia:all_keys(Tab), + ?match([-1], lists:max(Keys)), %% OTP-3779 + ?match(399, length(Keys)), + ?match(399, mnesia:foldl(Count, 0, Tab)), + ?match(399, mnesia:foldr(Count, 0, Tab)), + + ?match(Pool, lists:sort(mnesia:lock({table, Tab}, write))), + + done. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +iter_access(doc) -> + ["Cover table iteration via callback interface"]; +iter_access(suite) -> []; +iter_access(Config) when is_list(Config) -> + Nodes = ?acquire_nodes(3, Config), + + Tab = frag_access, + Pool = lists:sort(Nodes), + Props = [{n_fragments, 20}, + {n_ram_copies, 2}, + {node_pool, Pool}], + Def = [{frag_properties, Props}, + {type, ordered_set}, + {index, [val]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + [frag_write(Tab, {Tab, Id, Id}) || Id <- lists:seq(1, 400)], + + FragNames = frag_names(Tab), + RawRead = + fun(Frag) -> + Node = mnesia:table_info(Frag, where_to_read), + {Frag, rpc:call(Node, ets, tab2list, [Frag])} + end, + + ?match(done, mnesia:activity(transaction, fun nice_iter_access/3, [Tab, FragNames, RawRead], mnesia_frag)), + + FragNames = frag_names(Tab), + [First, Second | _] = FragNames, + [Last, LastButOne | _] = lists:reverse(FragNames), + + ?match({atomic, ok}, mnesia:clear_table(First)), + ?match({atomic, ok}, mnesia:clear_table(Second)), + ?match({atomic, ok}, mnesia:clear_table(lists:nth(8, FragNames))), + ?match({atomic, ok}, mnesia:clear_table(lists:nth(9, FragNames))), + ?match({atomic, ok}, mnesia:clear_table(lists:nth(10, FragNames))), + ?match({atomic, ok}, mnesia:clear_table(lists:nth(11, FragNames))), + ?match({atomic, ok}, mnesia:clear_table(LastButOne)), + ?match({atomic, ok}, mnesia:clear_table(Last)), + + ?match(done, mnesia:activity(transaction, fun evil_iter_access/3, [Tab, FragNames, RawRead], mnesia_frag)), + Size = fun(Table) -> mnesia:table_info(Table, size) end, + ?match(true, 0 < mnesia:activity(transaction, Size, [Tab], mnesia_frag)), + ?match({atomic, ok}, mnesia:activity(ets, fun() -> mnesia:clear_table(Tab) end, mnesia_frag)), + ?match(0, mnesia:activity(transaction, Size, [Tab], mnesia_frag)), + + ?verify_mnesia(Nodes, []). + +nice_iter_access(Tab, FragNames, RawRead) -> + RawData = ?ignore(lists:map(RawRead, FragNames)), + Keys = [K || {_, Recs} <- RawData, {_, K, _} <- Recs], + ExpectedFirst = hd(Keys), + ?match(ExpectedFirst, mnesia:first(Tab)), + ExpectedLast = lists:last(Keys), + ?match(ExpectedLast, mnesia:last(Tab)), + + ExpectedAllPrev = ['$end_of_table' | lists:reverse(tl(lists:reverse(Keys)))], + ?match(ExpectedAllPrev, lists:map(fun(K) -> mnesia:prev(Tab, K) end, Keys)), + + ExpectedAllNext = tl(Keys) ++ ['$end_of_table'], + ?match(ExpectedAllNext, lists:map(fun(K) -> mnesia:next(Tab, K) end, Keys)), + + done. + +evil_iter_access(Tab, FragNames, RawRead) -> + RawData = ?ignore(lists:map(RawRead, FragNames)), + Keys = [K || {_, Recs} <- RawData, {_, K, _} <- Recs], + ExpectedFirst = hd(Keys), + ?match(ExpectedFirst, mnesia:first(Tab)), + ExpectedLast = lists:last(Keys), + ?match(ExpectedLast, mnesia:last(Tab)), + + ExpectedAllPrev = ['$end_of_table' | lists:reverse(tl(lists:reverse(Keys)))], + ?match(ExpectedAllPrev, lists:map(fun(K) -> mnesia:prev(Tab, K) end, Keys)), + + ExpectedAllNext = tl(Keys) ++ ['$end_of_table'], + ?match(ExpectedAllNext, lists:map(fun(K) -> mnesia:next(Tab, K) end, Keys)), + + done. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +consistency(doc) -> + ["Add and delete fragments during TPC-B"]; +consistency(suite) -> []; +consistency(Config) when is_list(Config) -> + ?skip("Not yet implemented (NYI).~n", []), + Nodes = ?acquire_nodes(2, Config), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +evil(doc) -> + ["Evil coverage of fragmentation API."]; +evil(suite) -> + [ + evil_create, + evil_delete, + evil_change, + evil_combine, + evil_loop, + evil_delete_db_node + ]. + +evil_create(suite) -> []; +evil_create(Config) when is_list(Config) -> + [Node1, _Node2] = Nodes = ?acquire_nodes(2, Config), + + Create = fun(T, D, P) -> mnesia:create_table(T, [{frag_properties, P}| D]) end, + + Tab = evil_create, + %% Props in general + ?match({aborted, {badarg, Tab, {frag_properties, no_list}}}, + Create(Tab, [], no_list)), + ?match({aborted, {badarg,Tab , [no_tuple]}}, + Create(Tab, [], [no_tuple])), + ?match({aborted,{badarg, Tab, bad_key}}, + Create(Tab, [], [{bad_key, 7}])), + + %% n_fragments + ?match({aborted,{badarg, Tab, [{n_fragments}]}}, + Create(Tab, [], [{n_fragments}])), + ?match({aborted,{badarg, Tab, [{n_fragments, 1, 1}]}}, + Create(Tab, [], [{n_fragments, 1, 1}])), + ?match({aborted, {bad_type,Tab, {n_fragments, a}}}, + Create(Tab, [], [{n_fragments, a}])), + ?match({aborted, {bad_type, Tab, {n_fragments, 0}}}, + Create(Tab, [], [{n_fragments, 0}])), + + %% *_copies + ?match({aborted, {bad_type, Tab, {n_ram_copies, -1}}}, + Create(Tab, [], [{n_ram_copies, -1}, {n_fragments, 1}])), + ?match({aborted, {bad_type, Tab, {n_disc_copies, -1}}}, + Create(Tab, [], [{n_disc_copies, -1}, {n_fragments, 1}])), + ?match({aborted, {bad_type, Tab, {n_disc_only_copies, -1}}}, + Create(Tab, [], [{n_disc_only_copies, -1}, {n_fragments, 1}])), + + %% node_pool + ?match({aborted, {bad_type, Tab, {node_pool, 0}}}, + Create(Tab, [], [{node_pool, 0}])), + ?match({aborted, {combine_error, Tab, "Too few nodes in node_pool"}}, + Create(Tab, [], [{n_ram_copies, 2}, {node_pool, [Node1]}])), + + %% foreign_key + ?match({aborted, {bad_type, Tab, {foreign_key, bad_key}}}, + Create(Tab, [], [{foreign_key, bad_key}])), + ?match({aborted,{bad_type, Tab, {foreign_key, {bad_key}}}}, + Create(Tab, [], [{foreign_key, {bad_key}}])), + ?match({aborted, {no_exists, {bad_tab, frag_properties}}}, + Create(Tab, [], [{foreign_key, {bad_tab, val}}])), + ?match({aborted, {combine_error, Tab, {Tab, val}}}, + Create(Tab, [], [{foreign_key, {Tab, val}}])), + ?match({atomic, ok}, + Create(Tab, [], [{n_fragments, 1}])), + + ?match({aborted, {already_exists, Tab}}, + Create(Tab, [], [{n_fragments, 1}])), + + Tab2 = evil_create2, + ?match({aborted, {bad_type, no_attr}}, + Create(Tab2, [], [{foreign_key, {Tab, no_attr}}])), + ?match({aborted, {combine_error, Tab2, _, _, _}}, + Create(Tab2, [], [{foreign_key, {Tab, val}}, + {node_pool, [Node1]}])), + ?match({aborted, {combine_error, Tab2, _, _, _}}, + Create(Tab2, [], [{foreign_key, {Tab, val}}, + {n_fragments, 2}])), + ?match({atomic, ok}, + Create(Tab2, [{attributes, [a, b, c]}], [{foreign_key, {Tab, c}}])), + Tab3 = evil_create3, + ?match({aborted, {combine_error, Tab3, _, _, _}}, + Create(Tab3, [{attributes, [a, b]}], [{foreign_key, {Tab2, b}}])), + ?match({atomic, ok}, + Create(Tab3, [{attributes, [a, b]}], [{foreign_key, {Tab, b}}])), + + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +evil_delete(suite) -> []; +evil_delete(Config) when is_list(Config) -> + ?skip("Not yet implemented (NYI).~n", []), + Nodes = ?acquire_nodes(2, Config), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +evil_change(suite) -> []; +evil_change(Config) when is_list(Config) -> + [N1,N2,_N3] = Nodes = ?acquire_nodes(3, Config), + Create = fun(T, D, P) -> mnesia:create_table(T, [{frag_properties, P}| D]) end, + Props1 = [{n_fragments, 2}, {node_pool, [N1]}], + Tab1 = evil_change_ram, + ?match({atomic, ok}, Create(Tab1, [], Props1)), + + ?match({atomic,ok}, mnesia:change_table_frag(Tab1, {add_frag, Nodes})), + Dist10 = frag_dist(Tab1), + ?match([{N1,3}], Dist10), + ?match({atomic, ok}, mnesia:change_table_frag(Tab1, {add_node, N2})), + Dist11 = frag_dist(Tab1), + ?match([{N2,0},{N1,3}], Dist11), + mnesia_test_lib:kill_mnesia([N2]), + ?match({aborted,_}, mnesia:change_table_frag(Tab1, {add_frag, [N2,N1]})), + ?verbose("~p~n",[frag_dist(Tab1)]), + mnesia_test_lib:start_mnesia([N2]), + + Tab2 = evil_change_disc, + ?match({atomic,ok}, Create(Tab2,[],[{n_disc_copies,1},{n_fragments,1},{node_pool,[N1,N2]}])), + ?verbose("~p~n", [frag_dist(Tab2)]), + ?match({atomic,ok}, mnesia:change_table_frag(Tab2, {add_frag, [N1,N2]})), + _Dist20 = frag_dist(Tab2), + mnesia_test_lib:kill_mnesia([N2]), + ?match({atomic,ok}, mnesia:change_table_frag(Tab2, {add_frag, [N1,N2]})), + ?match({aborted,_}, mnesia:change_table_frag(Tab2, {add_frag, [N2,N1]})), + + mnesia_test_lib:start_mnesia([N2]), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +evil_combine(doc) -> ["Bug in mnesia_4.1.5. and earlier"]; +evil_combine(suite) -> []; +evil_combine(Config) when is_list(Config) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + ?match({atomic, ok},mnesia:create_table(tab1, [{disc_copies, [Node1]}, + {frag_properties, [{n_fragments, 2}, + {node_pool, [Node1]}, + {n_disc_copies, 1}]}])), + ?match({atomic, ok},mnesia:create_table(tab2, [{disc_copies, [Node1]}])), + mnesia:wait_for_tables([tab1, tab2], infinity), + + Add2 = fun() -> + mnesia:transaction(fun() -> + mnesia:write({tab2,1,1}) + end) + end, + Fun = fun() -> + Add2(), + mnesia:write({tab1,9,10}) + end, + ?match(ok, mnesia:activity({transaction, 1}, Fun, [], mnesia_frag)), + + Read = fun(T, K) -> + mnesia:read(T, K, read) + end, + + ?match([{tab1,9,10}],mnesia:activity(async_dirty, Read, [tab1, 9], mnesia_frag)), + ?match([{tab2,1,1}],mnesia:activity(async_dirty, Read, [tab2, 1], mnesia_frag)), + + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +evil_loop(doc) -> ["Test select/[14]"]; +evil_loop(suite) -> []; +evil_loop(Config) when is_list(Config) -> + [Node1,_Node2] = ?acquire_nodes(2, Config), + Tab1 = ss_oset, + Tab2 = ss_set, + Tab3 = ss_bag, + Tabs = [Tab1, Tab2, Tab3], + RecName = ss, + ?match({atomic, ok}, mnesia:create_table([{name, Tab1}, + {ram_copies, [Node1]}, + {record_name, RecName}, + {type, ordered_set}])), + ?match({atomic, ok}, mnesia:create_table([{name, Tab2}, + {record_name, RecName}, + {ram_copies, [Node1]}, + {type, set}])), + ?match({atomic, ok}, mnesia:create_table([{name, Tab3}, + {record_name, RecName}, + {ram_copies, [Node1]}, + {type, bag}])), + Keys = [-3, -2] ++ lists:seq(1, 5, 2) ++ lists:seq(6, 10), + Recs = [{RecName, K, K} || K <- Keys], + [mnesia:dirty_write(Tab1, R) || R <- Recs], + [mnesia:dirty_write(Tab2, R) || R <- Recs], + [mnesia:dirty_write(Tab3, R) || R <- Recs], + + Activate = + fun(Tab) -> + ?match({atomic, ok}, mnesia:change_table_frag(Tab, {activate, []})), + Dist = frag_dist(Tab), + ?match({atomic, ok}, mnesia:change_table_frag(Tab, {add_frag, Dist})) + end, + + Activate(Tab1), + Activate(Tab2), + Activate(Tab3), + + Match = fun(Tab) -> mnesia:match_object(Tab, {'_', '_', '_'}, write) end, + Select = fun(Tab) -> mnesia:select(Tab, [{'_', [], ['$_']}]) end, + Trans = fun(Fun, Args) -> mnesia:activity(transaction, Fun, Args, mnesia_frag) end, + LoopHelp = fun('$end_of_table',_) -> + []; + ({Res,Cont},Fun) -> + Sel = mnesia:select(Cont), + Res ++ Fun(Sel, Fun) + end, + SelLoop = fun(Table) -> + Sel = mnesia:select(Table, [{'_', [], ['$_']}], 1, read), + LoopHelp(Sel, LoopHelp) + end, + + R1 = {RecName, 2, 2}, + R2 = {RecName, 4, 4}, + R3 = {RecName, 2, 3}, + R4 = {RecName, 3, 1}, + R5 = {RecName, 104, 104}, + W1 = fun(Tab,Search) -> + mnesia:write(Tab,R1,write), + mnesia:write(Tab,R2,write), + Search(Tab) + end, + S1 = lists:sort([R1, R2| Recs]), + ?match(S1, sort_res(Trans(W1, [Tab1, Select]))), + ?match(S1, sort_res(Trans(W1, [Tab1, Match]))), + ?match(S1, sort_res(Trans(W1, [Tab1, SelLoop]))), + ?match(S1, sort_res(Trans(W1, [Tab2, Select]))), + ?match(S1, sort_res(Trans(W1, [Tab2, SelLoop]))), + ?match(S1, sort_res(Trans(W1, [Tab2, Match]))), + ?match(S1, sort_res(Trans(W1, [Tab3, Select]))), + ?match(S1, sort_res(Trans(W1, [Tab3, SelLoop]))), + ?match(S1, sort_res(Trans(W1, [Tab3, Match]))), + [mnesia:dirty_delete_object(Frag, R) || R <- [R1, R2], + Tab <- Tabs, + Frag <- frag_names(Tab)], + + W2 = fun(Tab, Search) -> + mnesia:write(Tab, R3, write), + mnesia:write(Tab, R1, write), + Search(Tab) + end, + S2 = lists:sort([R1 | Recs]), + S2Bag = lists:sort([R1, R3 | Recs]), + io:format("S2 = ~p\n", [S2]), + ?match(S2, sort_res(Trans(W2, [Tab1, Select]))), + ?match(S2, sort_res(Trans(W2, [Tab1, SelLoop]))), + ?match(S2, sort_res(Trans(W2, [Tab1, Match]))), + ?match(S2, sort_res(Trans(W2, [Tab2, Select]))), + ?match(S2, sort_res(Trans(W2, [Tab2, SelLoop]))), + ?match(S2, sort_res(Trans(W2, [Tab2, Match]))), + io:format("S2Bag = ~p\n", [S2Bag]), + ?match(S2Bag, sort_res(Trans(W2, [Tab3, Select]))), + ?match(S2Bag, sort_res(Trans(W2, [Tab3, SelLoop]))), + ?match(S2Bag, sort_res(Trans(W2, [Tab3, Match]))), + + W3 = fun(Tab,Search) -> + mnesia:write(Tab, R4, write), + mnesia:delete(Tab, element(2, R1), write), + Search(Tab) + end, + S3Bag = lists:sort([R4 | lists:delete(R1, Recs)]), + S3 = lists:delete({RecName, 3, 3}, S3Bag), + ?match(S3, sort_res(Trans(W3, [Tab1, Select]))), + ?match(S3, sort_res(Trans(W3, [Tab1, SelLoop]))), + ?match(S3, sort_res(Trans(W3, [Tab1, Match]))), + ?match(S3, sort_res(Trans(W3, [Tab2, SelLoop]))), + ?match(S3, sort_res(Trans(W3, [Tab2, Select]))), + ?match(S3, sort_res(Trans(W3, [Tab2, Match]))), + ?match(S3Bag, sort_res(Trans(W3, [Tab3, Select]))), + ?match(S3Bag, sort_res(Trans(W3, [Tab3, SelLoop]))), + ?match(S3Bag, sort_res(Trans(W3, [Tab3, Match]))), + + W4 = fun(Tab,Search) -> + mnesia:delete(Tab, -1, write), + mnesia:delete(Tab, 4 , write), + mnesia:delete(Tab, 17, write), + mnesia:delete_object(Tab, {RecName, -1, x}, write), + mnesia:delete_object(Tab, {RecName, 4, x}, write), + mnesia:delete_object(Tab, {RecName, 42, x}, write), + mnesia:delete_object(Tab, R2, write), + mnesia:write(Tab, R5, write), + Search(Tab) + end, + S4Bag = lists:sort([R5 | S3Bag]), + S4 = lists:sort([R5 | S3]), + ?match(S4, sort_res(Trans(W4, [Tab1, Select]))), + ?match(S4, sort_res(Trans(W4, [Tab1, SelLoop]))), + ?match(S4, sort_res(Trans(W4, [Tab1, Match]))), + ?match(S4, sort_res(Trans(W4, [Tab2, Select]))), + ?match(S4, sort_res(Trans(W4, [Tab2, SelLoop]))), + ?match(S4, sort_res(Trans(W4, [Tab2, Match]))), + ?match(S4Bag, sort_res(Trans(W4, [Tab3, Select]))), + ?match(S4Bag, sort_res(Trans(W4, [Tab3, SelLoop]))), + ?match(S4Bag, sort_res(Trans(W4, [Tab3, Match]))), + [mnesia:dirty_delete_object(Tab, R) || R <- [{RecName, 3, 3}, R5], Tab <- Tabs], + + %% hmmm anything more?? + + ?verify_mnesia([Node1], []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +evil_delete_db_node(doc) -> + ["Delete db_node with a repicated table with foreign key"]; +evil_delete_db_node(suite) -> []; +evil_delete_db_node(Config) when is_list(Config) -> + Nodes = lists:sort(?acquire_nodes(2, Config)), + Local = node(), + Remote = hd(Nodes -- [Local]), + + Type = case mnesia_test_lib:diskless(Config) of + true -> n_ram_copies; + false -> n_disc_copies + end, + Tab = frag_master, + ?match({atomic, ok}, mnesia:create_table(Tab, [{frag_properties, [{Type, 2}, {node_pool, Nodes}]}])), + ExtraTab = frag_foreigner, + ?match({atomic, ok}, mnesia:create_table(ExtraTab, [{frag_properties, [{foreign_key, {Tab, key}}, {node_pool, Nodes}]}])), + + GetPool = fun(T) -> + case lists:keysearch(node_pool, 1, mnesia:table_info (T, frag_properties)) of + {value, {node_pool, N}} -> lists:sort(N); + false -> [] + end + end, + ?match(Nodes, GetPool(Tab)), + ?match(Nodes, GetPool(ExtraTab)), + + + ?match(stopped, rpc:call(Remote, mnesia, stop, [])), + ?match({atomic, ok}, mnesia:del_table_copy(schema, Remote)), + + ?match([Local], GetPool(Tab)), + ?match([Local], GetPool(ExtraTab)), + + ?verify_mnesia([Local], []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Misc convenient helpers + +frag_write(Tab, Rec) -> + Fun = fun() -> mnesia:write(Tab, Rec, write) end, + mnesia:activity(sync_dirty, Fun, mnesia_frag). + +frag_dist(Tab) -> + Fun = fun() -> mnesia:table_info(Tab, frag_dist) end, + mnesia:activity(sync_dirty, Fun, mnesia_frag). + +frag_names(Tab) -> + Fun = fun() -> mnesia:table_info(Tab, frag_names) end, + mnesia:activity(sync_dirty, Fun, mnesia_frag). + +frag_rec_dist(Tab) -> + Fun = fun() -> mnesia:table_info(Tab, frag_size) end, + [Size || {_, Size} <- mnesia:activity(sync_dirty, Fun, mnesia_frag)]. + +table_size(Tab) -> + Node = mnesia:table_info(Tab, where_to_read), + rpc:call(Node, mnesia, table_info, [Tab, size]). + +sort_res(List) when is_list(List) -> + lists:sort(List); +sort_res(Else) -> + Else. + +rev_res(List) when is_list(List) -> + lists:reverse(List); +rev_res(Else) -> + Else. diff --git a/lib/mnesia/test/mnesia_inconsistent_database_test.erl b/lib/mnesia/test/mnesia_inconsistent_database_test.erl new file mode 100644 index 0000000000..b19cd8e01b --- /dev/null +++ b/lib/mnesia/test/mnesia_inconsistent_database_test.erl @@ -0,0 +1,74 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1998-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(mnesia_inconsistent_database_test). +-author('[email protected]'). + +-behaviour(gen_event). + +%%-compile([export_all]). +-include("mnesia_test_lib.hrl"). + +%% gen_event callback interface +-export([init/1, handle_event/2, handle_call/2, handle_info/2, + terminate/2, code_change/3]). + + +init(_Args) -> + ?verbose("~p installed as event_module~n", [?MODULE]), + {ok, []}. + +handle_event(Msg, State) -> + handle_any_event(Msg, State). + +handle_info(Msg, State) -> + handle_any_event(Msg, State). + + +handle_call(Msg, State) -> + handle_any_event(Msg, State). + + +%% The main... + +handle_any_event({mnesia_system_event, Event}, State) + when element(1, Event) == inconsistent_database -> + ?error("Got event: ~p~n", [Event]), + {ok, State}; +handle_any_event(Msg, State) -> + ?verbose("Got event: ~p~n", [Msg]), + {ok, State}. + +%%----------------------------------------------------------------- +%% terminate(Reason, State) -> +%% AnyVal +%%----------------------------------------------------------------- + +terminate(_Reason, _State) -> + ok. + +%%---------------------------------------------------------------------- +%% Func: code_change/3 +%% Purpose: Upgrade process when its code is to be changed +%% Returns: {ok, NewState} +%%---------------------------------------------------------------------- +code_change(_OldVsn, _State, _Extra) -> + exit(not_supported). + diff --git a/lib/mnesia/test/mnesia_install_test.erl b/lib/mnesia/test/mnesia_install_test.erl new file mode 100644 index 0000000000..42a2a19f37 --- /dev/null +++ b/lib/mnesia/test/mnesia_install_test.erl @@ -0,0 +1,342 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(mnesia_install_test). +-author('[email protected]'). + +-compile([export_all]). +-include("mnesia_test_lib.hrl"). + +init_per_testcase(Func, Conf) -> + mnesia_test_lib:init_per_testcase(Func, Conf). + +fin_per_testcase(Func, Conf) -> + mnesia_test_lib:fin_per_testcase(Func, Conf). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +all(doc) -> + ["Run some small but demanding test cases in order to verify", + "that the basic functionality in Mnesia still works.", + "", + "Try some very simple things to begin with and increase the", + "difficulty stepwise. This test suite should be run before", + "all the others if you expect to find bugs.", + "", + "The function mnesia_install_test:silly() does not use the whole", + "infra structure of the test suite. Invoke it on a single node to", + "begin with. If that works, proceed with pong = net_adm:ping(SomeOtherNode)", + "and rerun silly() in order to perform some distributed tests."]; +all(suite) -> + [ + silly_durability, + silly_move, + silly_upgrade + %,stress + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Stepwise of more and more advanced features +silly() -> + Nodes = [node()] ++ nodes(), + mnesia_test_lib:kill_mnesia(Nodes), + Config = [{nodes, Nodes}], + mnesia_test_lib:eval_test_case(?MODULE, silly2, Config). + +silly2(Config) when is_list(Config) -> + [Node1 | _] = Nodes = ?acquire_nodes(3, Config), + mnesia_test_lib:kill_mnesia(Nodes), + ?ignore([mnesia:delete_schema([N]) || N <- Nodes]), + ?match(ok, mnesia:create_schema([Node1])), + ?match(ok, rpc:call(Node1, mnesia, start, [])), + ?match(ok, rpc:call(Node1, mnesia, wait_for_tables, + [[schema], infinity])), + Res = silly_durability(Config), + StressFun = fun(F) -> apply(?MODULE, F, [Config]) end, + R = + case length(Nodes) of + L when L > 1 -> + Node2 = lists:nth(2, Nodes), + AddDb = [schema, Node2, ram_copies], + ?match({atomic, ok}, + rpc:call(Node1, mnesia, add_table_copy, AddDb)), + Args = [[{extra_db_nodes, [Node1]}]], + ?match(ok, rpc:call(Node2, mnesia, start, Args)), + ChangeDb = [schema, Node2, disc_copies], + ?match({atomic, ok}, + rpc:call(Node1, mnesia, change_table_copy_type, + ChangeDb)), + ?match([], mnesia_test_lib:sync_tables([Node1, Node2], + [schema])), + MoveRes = silly_move(Config), + UpgradeRes = silly_upgrade(Config), + StressRes = [StressFun(F) || F <- stress(suite)], + ?verify_mnesia([Node2], []), + [Res, MoveRes, UpgradeRes] ++ StressRes; + _ -> + StressRes = [StressFun(F) || F <- stress(suite)], + ?warning("Too few nodes. Perform net_adm:ping(OtherNode) " + "and rerun!!!~n", []), + [Res | StressRes] + end, + ?verify_mnesia([Node1], []), + R. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +silly_durability(doc) -> + ["Simple test of durability"]; +silly_durability(suite) -> []; +silly_durability(Config) when is_list(Config) -> + [Node1] = ?acquire_nodes(1, Config), + Tab = silly, + Storage = mnesia_test_lib:storage_type(disc_copies, Config), + + ?match({atomic, ok}, rpc:call(Node1, mnesia, + create_table, [Tab, [{Storage, [Node1]}]])), + + Read = fun() -> mnesia:read({Tab, a}) end, + Write = fun() -> mnesia:write({Tab, a, b}) end, + + ?match({atomic, []}, + rpc:call(Node1, mnesia, transaction, [Read])), + ?match({atomic, ok}, + rpc:call(Node1, mnesia, transaction, [Write])), + ?match({atomic, [{Tab, a, b}]}, + rpc:call(Node1, mnesia, transaction, [Read])), + + ?match(stopped, rpc:call(Node1, mnesia, stop, [])), + ?match(ok, rpc:call(Node1, mnesia, start, [])), + case mnesia_test_lib:diskless(Config) of + true -> + skip; + false -> + ?match(ok, rpc:call(Node1, mnesia, wait_for_tables, [[Tab], infinity])), + ?match({atomic, [{Tab, a, b}]}, + rpc:call(Node1, mnesia, transaction, [Read])) + end, + ?verify_mnesia([Node1], []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +silly_move(doc) -> + ["Simple test of movement of a replica from one node to another"]; +silly_move(suite) -> []; +silly_move(Config) when is_list(Config) -> + [Node1, Node2] = ?acquire_nodes(2, Config), + Tab = silly_move, + ?match({atomic, ok}, + rpc:call(Node1, mnesia, + create_table, [Tab, [{ram_copies, [Node2]}]])), + ?match([], mnesia_test_lib:sync_tables([Node1, Node2], [Tab])), + + Read = fun() -> mnesia:read({Tab, a}) end, + Write = fun() -> mnesia:write({Tab, a, b}) end, + + ?match({atomic, []}, + rpc:call(Node1, mnesia, transaction, [Read])), + ?match({atomic, ok}, + rpc:call(Node1, mnesia, transaction, [Write])), + ?match({atomic, [{Tab, a, b}]}, + rpc:call(Node1, mnesia, transaction, [Read])), + + case mnesia_test_lib:diskless(Config) of + true -> skip; + false -> + ?match({atomic, ok}, + rpc:call(Node1, mnesia, + change_table_copy_type, [Tab, Node2, disc_only_copies])), + ?match([], mnesia_test_lib:sync_tables([Node1, Node2], [Tab])) + end, + ?match({atomic, [{Tab, a, b}]}, rpc:call(Node1, mnesia, transaction, [Read])), + + ?match({atomic, ok}, + rpc:call(Node1, mnesia, + move_table_copy, [Tab, Node2, Node1])), + ?match([], mnesia_test_lib:sync_tables([Node1, Node2], [Tab])), + ?match({atomic, [{Tab, a, b}]}, + rpc:call(Node1, mnesia, transaction, [Read])), + ?verify_mnesia([Node1], []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +silly_upgrade(doc) -> + ["Simple test of a schema upgrade and restore from backup"]; +silly_upgrade(suite) -> []; +silly_upgrade(Config) when is_list(Config) -> + [Node1, Node2] = Nodes = ?acquire_nodes(2, Config), + Name = silly_upgrade, + Tab1 = silly_upgrade1, + Tab2 = silly_upgrade2, + Bup = "silly_upgrade.BUP", + Bup2 = "silly_upgrade_part.BUP", + ?match({atomic, ok}, mnesia:create_table(Tab1, [{ram_copies, Nodes}])), + ?match({atomic, ok}, mnesia:create_table(Tab2, [{disc_only_copies, Nodes}])), + + CpState = add_some_records(Tab1, Tab2, []), + ?match(match, verify_state(Tab1, Tab2, CpState)), + file:delete(Bup), + ?match(ok, mnesia:backup(Bup)), + Args = [{name, Name}, {ram_overrides_dump, true}, + {min, [Tab1, schema]}, {max, [Tab2]}], + ?match({ok, Name, _}, mnesia:activate_checkpoint(Args)), + + IgnoreState = add_more_records(Tab1, Tab2, CpState), + ?match(match, verify_state(Tab1, Tab2, IgnoreState)), + ?match({mismatch, _, _}, verify_state(Tab1, Tab2, CpState)), + ?match({atomic, ok}, mnesia:del_table_copy(Tab2, Node1)), + file:delete(Bup2), + ?match(ok, mnesia:backup_checkpoint(Name, Bup2)), + + UpgradeState = transform_some_records(Tab1, Tab2, IgnoreState), + ?match({mismatch, _, _}, verify_state(Tab1, Tab2, CpState)), + ?match({mismatch, _, _}, verify_state(Tab1, Tab2, IgnoreState)), + ?match(match, verify_state(Tab1, Tab2, UpgradeState)), + + ?match(ok, mnesia:deactivate_checkpoint(Name)), + ?match(match, verify_state(Tab1, Tab2, UpgradeState)), + + ?match(ok, mnesia:install_fallback(Bup2)), + file:delete(Bup2), + %% Will generate intentional crash, fatal error + ?match([], mnesia_test_lib:stop_mnesia([Node2])), + wait_till_dead([Node1, Node2]), + ?match([], mnesia_test_lib:start_mnesia([Node1, Node2], [Tab1, Tab2])), + ?match(match, verify_state(Tab1, Tab2, CpState)), + + ?match(ok, mnesia:install_fallback(Bup)), + file:delete(Bup), + %% Will generate intentional crash, fatal error + ?match([], mnesia_test_lib:stop_mnesia([Node1, Node2])), + wait_till_dead([Node1, Node2]), + ?match([], mnesia_test_lib:start_mnesia([Node1, Node2], [Tab1, Tab2])), + CpState2 = [X || X <- CpState, element(1, X) /= Tab1], + ?match(match, verify_state(Tab1, Tab2, CpState2)), + ?verify_mnesia(Nodes, []). + +wait_till_dead([]) -> ok; +wait_till_dead([N|Ns]) -> + Apps = rpc:call(N, application, which_applications, []), + case lists:keymember(mnesia, 1, Apps) of + true -> + timer:sleep(10), + wait_till_dead([N|Ns]); + false -> + wait_till_dead(Ns) + end. + +add_some_records(Tab1, Tab2, Old) -> + Recs1 = [{Tab1, I, I} || I <- lists:seq(1, 30)], + Recs2 = [{Tab2, I, I} || I <- lists:seq(20, 40)], + lists:foreach(fun(R) -> mnesia:dirty_write(R) end, Recs1), + Fun = fun(R) -> mnesia:write(R) end, + Trans = fun() -> lists:foreach(Fun, Recs2) end, + ?match({atomic, _}, mnesia:transaction(Trans)), + lists:sort(Old ++ Recs1 ++ Recs2). + +add_more_records(Tab1, Tab2, Old) -> + Change1 = [{T, K, V+100} || {T, K, V} <- Old, K==23], + Change2 = [{T, K, V+100} || {T, K, V} <- Old, K==24], + Del = [{T, K} || {T, K, _V} <- Old, K>=25], + New = [{Tab1, 50, 50}, {Tab2, 50, 50}], + lists:foreach(fun(R) -> mnesia:dirty_write(R) end, Change1), + lists:foreach(fun(R) -> mnesia:dirty_delete(R) end, Del), + Fun = fun(R) -> mnesia:write(R) end, + Trans = fun() -> lists:foreach(Fun, Change2 ++ New) end, + ?match({atomic, ok}, mnesia:transaction(Trans)), + Recs = [{T, K, V} || {T, K, V} <- Old, K<23] ++ Change1 ++ Change2 ++ New, + lists:sort(Recs). + + +verify_state(Tab1, Tab2, Exp) -> + Fun = fun() -> + Act1 = [mnesia:read({Tab1, K}) || K <- mnesia:all_keys(Tab1)], + Act2 = [mnesia:read({Tab2, K}) || K <- mnesia:all_keys(Tab2)], + Act = lists:append(Act1) ++ lists:append(Act2), + {ok, Act -- Exp, Exp -- Act} + end, + case mnesia:transaction(Fun) of + {atomic, {ok, [], []}} -> match; + {atomic, {ok, More, Less}} -> {mismatch, More, Less}; + {aborted, Reason} -> {error, Reason} + end. + +transform_some_records(Tab1, _Tab2, Old) -> + Fun = fun(Rec) -> + list_to_tuple(tuple_to_list(Rec) ++ [4711]) + end, + ?match({atomic, ok}, + mnesia:transform_table(Tab1, Fun, [key, val, extra])), + Filter = fun(Rec) when element(1, Rec) == Tab1 -> {true, Fun(Rec)}; + (_) -> true + end, + lists:sort(lists:zf(Filter, Old)). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +stress(doc) -> + ["Stress the system a little"]; +stress(suite) -> + [ + conflict, + dist + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +dist(doc) -> + ["Avoid lock conflicts in order to maximize thruput", + "Ten drivers per node, tables replicated to all nodes, lots of branches"]; +dist(suite) -> []; +dist(Config) when is_list(Config) -> + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, 10 * 60000}]), + Storage = mnesia_test_lib:storage_type(disc_copies, Config), + ?match({ok, _}, mnesia_tpcb:start(dist_args(Nodes, Storage))). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +conflict(doc) -> + ["Provoke a lot of lock conflicts.", + "Ten drivers per node, tables replicated to all nodes, single branch"]; +conflict(suite) -> []; +conflict(Config) when is_list(Config) -> + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, 10 * 60000}]), + Storage = mnesia_test_lib:storage_type(disc_copies, Config), + ?match({ok, _}, mnesia_tpcb:start(conflict_args(Nodes, Storage))). + +conflict_args(Nodes, ReplicaType) -> + [{db_nodes, Nodes}, + {driver_nodes, Nodes}, + {replica_nodes, Nodes}, + {n_drivers_per_node, 10}, + {n_branches, 1}, + {n_accounts_per_branch, 10}, + {replica_type, ReplicaType}, + {stop_after, timer:minutes(5)}, + {report_interval, timer:seconds(10)}, + {use_running_mnesia, true}, + {reuse_history_id, true}]. + +dist_args(Nodes, ReplicaType) -> + [{db_nodes, Nodes}, + {driver_nodes, Nodes}, + {replica_nodes, Nodes}, + {n_drivers_per_node, 10}, + {n_branches, length(Nodes) * 100}, + {n_accounts_per_branch, 10}, + {replica_type, ReplicaType}, + {stop_after, timer:minutes(5)}, + {report_interval, timer:seconds(10)}, + {use_running_mnesia, true}, + {reuse_history_id, true}]. + diff --git a/lib/mnesia/test/mnesia_isolation_test.erl b/lib/mnesia/test/mnesia_isolation_test.erl new file mode 100644 index 0000000000..4fc6e8fe58 --- /dev/null +++ b/lib/mnesia/test/mnesia_isolation_test.erl @@ -0,0 +1,2419 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1997-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(mnesia_isolation_test). +-author('[email protected]'). + +-compile([export_all]). +-include("mnesia_test_lib.hrl"). + +init_per_testcase(Func, Conf) -> + mnesia_test_lib:init_per_testcase(Func, Conf). + +fin_per_testcase(Func, Conf) -> + mnesia_test_lib:fin_per_testcase(Func, Conf). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +all(doc) -> + ["Verify the isolation property.", + "Operations of concurrent transactions must yield results which", + "are indistinguishable from the results which would be obtained by", + "forcing each transaction to be serially executed to completion in", + "some order. This means that repeated reads of the same records", + "within any committed transaction must have returned identical", + "data when run concurrently with any mix of arbitary transactions.", + "Updates in one transaction must not be visible in any other", + "transaction before the transaction has been committed."]; +all(suite) -> + [ + locking, + visibility + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +locking(doc) -> + ["Verify locking semantics for various configurations", + " NoLock = lock_funs(no_lock, any_granularity)", + " SharedLock = lock_funs(shared_lock, any_granularity)", + " ExclusiveLock = lock_funs(exclusive_lock, any_granularity)", + " AnyLock = lock_funs(any_lock, any_granularity)"]; +locking(suite) -> + [no_conflict, + simple_queue_conflict, + advanced_queue_conflict, + simple_deadlock_conflict, + advanced_deadlock_conflict, + lock_burst, + sticky_locks, + unbound_locking, + admin_conflict, +%% removed_resources, + nasty + ]. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +no_conflict(suite) -> []; +no_conflict(Config) when is_list(Config) -> + [Node1] = ?acquire_nodes(1, Config), + Tab = no_conflict, + create_conflict_table(Tab, [Node1]), + Fun = fun(OtherOid, Lock1, Lock2) -> + %% Start two transactions + {success, [B, A]} = ?start_activities([Node1, Node1]), + ?start_transactions([B, A]), + + A ! fun() -> Lock1(one_oid(Tab)), ok end, + ?match_receive({A, ok}), + B ! fun() -> Lock2(OtherOid), ok end, + ?match_receive({B, ok}), + A ! fun() -> mnesia:abort(ok) end, + ?match_receive({A, {aborted, ok}}), + B ! fun() -> mnesia:abort(ok) end, + ?match_receive({B, {aborted, ok}}) + end, + NoLocks = lock_funs(no_lock, any_granularity), + SharedLocks = lock_funs(shared_lock, any_granularity), + AnyLocks = lock_funs(any_lock, any_granularity), + OneOneFun = fun(Lock1, Lock2) -> Fun(one_oid(Tab), Lock1, Lock2) end, + fun_loop(OneOneFun, NoLocks, AnyLocks), + fun_loop(OneOneFun, AnyLocks, NoLocks), + fun_loop(OneOneFun, SharedLocks, SharedLocks), + + %% Lock different objects + OneOtherFun = fun(Lock1, Lock2) -> Fun(other_oid(Tab), Lock1, Lock2) end, + OneSharedLocks = lock_funs(shared_lock, one), + OneExclusiveLocks = lock_funs(exclusive_lock, one), + fun_loop(OneOtherFun, OneSharedLocks, OneExclusiveLocks), + fun_loop(OneOtherFun, OneExclusiveLocks, OneSharedLocks), + fun_loop(OneOtherFun, OneExclusiveLocks, OneExclusiveLocks), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +simple_queue_conflict(suite) -> []; +simple_queue_conflict(Config) when is_list(Config) -> + [Node1] = ?acquire_nodes(1, Config), + Tab = simple_queue_conflict, + create_conflict_table(Tab, [Node1]), + Fun = fun(OneLock, OtherLock) -> + %% Start two transactions + {success, [B, A]} = ?start_activities([Node1, Node1]), + ?start_transactions([B, A]), + + A ! fun() -> OneLock(one_oid(Tab)), ok end, + ?match_receive({A, ok}), + B ! fun() -> OtherLock(one_oid(Tab)), ok end, + wait_for_lock(B, [Node1], 20), % Max 10 sec + A ! end_trans, + ?match_multi_receive([{A, {atomic, end_trans}}, {B, ok}]), + B ! fun() -> mnesia:abort(ok) end, + ?match_receive({B, {aborted, ok}}) + end, + OneSharedLocks = lock_funs(shared_lock, one), + AllSharedLocks = lock_funs(shared_lock, all), + OneExclusiveLocks = lock_funs(exclusive_lock, one), + AllExclusiveLocks = lock_funs(exclusive_lock, all), + fun_loop(Fun, OneExclusiveLocks, OneExclusiveLocks), + fun_loop(Fun, AllExclusiveLocks, AllExclusiveLocks), + fun_loop(Fun, OneExclusiveLocks, AllExclusiveLocks), + fun_loop(Fun, AllExclusiveLocks, OneExclusiveLocks), + fun_loop(Fun, OneSharedLocks, AllExclusiveLocks), + fun_loop(Fun, AllSharedLocks, OneExclusiveLocks), + ok. + +wait_for_lock(Pid, _Nodes, 0) -> + Queue = mnesia:system_info(lock_queue), + ?error("Timeout while waiting for lock on Pid ~p in queue ~p~n", [Pid, Queue]); +wait_for_lock(Pid, Nodes, N) -> + rpc:multicall(Nodes, sys, get_status, [mnesia_locker]), + List = [rpc:call(Node, mnesia, system_info, [lock_queue]) || Node <- Nodes], + Q = lists:append(List), + check_q(Pid, Q, Nodes, N). + +check_q(Pid, [{_Oid, _Op, Pid, _Tid, _WFT} | _Tail], _N, _Count) -> ok; +check_q(Pid, [_ | Tail], N, Count) -> check_q(Pid, Tail, N, Count); +check_q(Pid, [], N, Count) -> + timer:sleep(500), + wait_for_lock(Pid, N, Count - 1). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +advanced_queue_conflict(suite) -> []; +advanced_queue_conflict(Config) when is_list(Config) -> + [Node1] = ?acquire_nodes(1, Config), + Tab = advanced_queue_conflict, + create_conflict_table(Tab, [Node1]), + OneRec = {Tab, 3, 3}, + OneOid = {Tab, 3}, + OtherRec = {Tab, 4, 4}, + OtherOid = {Tab, 4}, + + %% Start four transactions + {success, [D, C, B, A]} = ?start_activities(lists:duplicate(4, Node1)), + ?start_transactions([D, C, B, A]), + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + ?match([], mnesia:system_info(held_locks)), + ?match([], mnesia:system_info(lock_queue)), + + %% Acquire some locks + A ! fun() -> mnesia:write(OneRec) end, + ?match_receive({A, ok}), + A ! fun() -> mnesia:read(OneOid) end, + ?match_receive({A, [OneRec]}), + + B ! fun() -> mnesia:write(OtherRec) end, + ?match_receive({B, ok}), + B ! fun() -> mnesia:read(OneOid) end, + ?match_receive(timeout), + + C ! fun() -> mnesia:read(OtherOid) end, + ?match_receive(timeout), + D ! fun() -> mnesia:wread(OtherOid) end, + ?match_receive(timeout), + + %% and release them in a certain order + A ! end_trans, + ?match_multi_receive([{A, {atomic, end_trans}}, {B, [OneRec]}]), + B ! end_trans, + ?match_multi_receive([{B, {atomic, end_trans}}, {C, [OtherRec]}]), + C ! end_trans, + ?match_multi_receive([{C, {atomic, end_trans}}, {D, [OtherRec]}]), + D ! end_trans, + ?match_receive({D, {atomic, end_trans}}), + + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + ?match([], mnesia:system_info(held_locks)), + ?match([], mnesia:system_info(lock_queue)), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +simple_deadlock_conflict(suite) -> []; +simple_deadlock_conflict(Config) when is_list(Config) -> + [Node1] = ?acquire_nodes(1, Config), + Tab = simple_deadlock_conflict, + create_conflict_table(Tab, [Node1]), + Rec = {Tab, 4, 4}, + Oid = {Tab, 4}, + + %% Start two transactions + {success, [B, A]} = ?start_activities(lists:duplicate(2, Node1)), + mnesia_test_lib:start_transactions([B, A], 0), % A is newest + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + ?match([], mnesia:system_info(held_locks)), + ?match([], mnesia:system_info(lock_queue)), + + B ! fun() -> mnesia:write(Rec) end, + ?match_receive({B, ok}), + A ! fun() -> mnesia:read(Oid) end, + ?match_receive({A, {aborted, nomore}}), + B ! end_trans, + ?match_receive({B, {atomic, end_trans}}), + + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + ?match([], mnesia:system_info(held_locks)), + ?match([], mnesia:system_info(lock_queue)), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +advanced_deadlock_conflict(suite) -> []; +advanced_deadlock_conflict(Config) when is_list(Config) -> + [Node1, Node2] = ?acquire_nodes(2, Config), + Tab = advanced_deadlock_conflict, + create_conflict_table(Tab, [Node2]), + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + Rec = {Tab, 4, 4}, + Oid = {Tab, 4}, + + %% Start two transactions + {success, [B, A]} = ?start_activities([Node1, Node2]), + mnesia_test_lib:start_sync_transactions([B, A], 0), % A is newest + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + ?match([], mnesia:system_info(held_locks)), + ?match([], mnesia:system_info(lock_queue)), + + B ! fun() -> mnesia:write(Rec) end, + ?match_receive({B, ok}), + A ! fun() -> mnesia:read(Oid) end, + ?match_receive({A, {aborted, nomore}}), + B ! end_trans, + ?match_receive({B, {atomic, end_trans}}), + + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + ?match([], mnesia:system_info(held_locks)), + ?match([], mnesia:system_info(lock_queue)), + ok. + +one_oid(Tab) -> {Tab, 1}. +other_oid(Tab) -> {Tab, 2}. + +create_conflict_table(Tab, Nodes) -> + ?match({atomic, ok}, mnesia:create_table([{name, Tab}, + {ram_copies, Nodes}, + {attributes, [key, val]}, + {index, [val]} + ])), + ?match([], mnesia_test_lib:sync_tables(Nodes, [Tab])), + init_conflict_table(Tab). + +init_conflict_table(Tab) -> + Recs = mnesia:dirty_match_object({Tab, '_', '_'}), + lists:foreach(fun(R) -> mnesia:dirty_delete_object(R) end, Recs), + Keys = [one_oid(Tab), other_oid(Tab)], + [mnesia:dirty_write({T, K, K}) || {T, K} <- Keys]. + +%% Apply Fun for each X and Y +fun_loop(Fun, Xs, Ys) -> + lists:foreach(fun(X) -> lists:foreach(fun(Y) -> do_fun(Fun, X, Y) end, Ys) end, Xs). + +do_fun(Fun, X, Y) -> + Pid = spawn_link(?MODULE, do_fun, [self(), Fun, X, Y]), + receive + {done_fun, Pid} -> done_fun + end. + +do_fun(Monitor, Fun, X, Y) -> + ?log("{do_fun ~p~n", [[Fun, X, Y]]), + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + ?match([], mnesia:system_info(held_locks)), + ?match([], mnesia:system_info(lock_queue)), + Fun(X, Y), + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + ?match([], mnesia:system_info(held_locks)), + ?match([], mnesia:system_info(lock_queue)), + unlink(Monitor), + Monitor ! {done_fun, self()}, + exit(done_fun). + +%% Returns a list of fun's +lock_funs(no_lock, one) -> + [ + fun(Oid) -> mnesia:dirty_read(Oid) end, + fun({Tab, Key}) -> mnesia:dirty_write({Tab, Key, Key}) end, + fun({Tab, Key}) -> mnesia:dirty_write({Tab, Key, Key}), + mnesia:dirty_update_counter({Tab, Key}, 0) end, + fun(Oid) -> mnesia:dirty_delete(Oid) end, + fun({Tab, Key}) -> mnesia:dirty_delete_object({Tab, Key, Key}) end, + fun({Tab, Key}) -> mnesia:dirty_match_object({Tab, Key, Key}) end, + fun({Tab, Key}) -> mnesia:dirty_index_match_object({Tab, Key, Key}, val) end, + fun({Tab, Key}) -> mnesia:dirty_index_read(Tab, Key, val) end, + fun({Tab, Key}) -> mnesia:dirty_index_match_object({Tab, '_', Key}, val) end + ]; +lock_funs(no_lock, all) -> + [ + fun({Tab, _}) -> mnesia:dirty_match_object({Tab, '_', '_'}) end, + fun({Tab, _}) -> slot_iter(Tab) end, + fun({Tab, _}) -> key_iter(Tab) end + ]; +lock_funs(shared_lock, one) -> + + [ + fun(Oid) -> mnesia:read(Oid) end, + fun({Tab, Key}) -> + init_conflict_table(Tab), + mnesia:dirty_delete(other_oid(Tab)), + mnesia:match_object({Tab, Key, Key}) end + ]; +lock_funs(shared_lock, all) -> + [ + fun({Tab, _}) -> mnesia:read_lock_table(Tab) end, + fun({Tab, Key}) -> mnesia:match_object({Tab, '_', Key}) end, + fun({Tab, _}) -> mnesia:match_object({Tab, '_', '_'}) end, + fun({Tab, _}) -> mnesia:all_keys(Tab) end, + fun({Tab, Key}) -> mnesia:index_match_object({Tab, '_', Key}, val) end, + fun({Tab, Key}) -> mnesia:index_read(Tab, Key, val) end + ]; +lock_funs(exclusive_lock, one) -> + [ + fun(Oid) -> mnesia:wread(Oid) end, + fun({Tab, Key}) -> mnesia:write({Tab, Key, Key}) end, + fun(Oid) -> mnesia:delete(Oid) end, + fun({Tab, Key}) -> mnesia:delete_object({Tab, Key, Key}) end, + fun({Tab, Key}) -> mnesia:s_write({Tab, Key, Key}) end, + fun(Oid) -> mnesia:s_delete(Oid) end, + fun({Tab, Key}) -> mnesia:s_delete_object({Tab, Key, Key}) end + ]; +lock_funs(exclusive_lock, all) -> + [ + fun({Tab, _}) -> mnesia:write_lock_table(Tab) end + ]; +lock_funs(Compatibility, any_granularity) -> + lists:append([lock_funs(Compatibility, Granularity) || + Granularity <- [one, all]]); +lock_funs(any_lock, Granularity) -> + lists:append([lock_funs(Compatibility, Granularity) || + Compatibility <- [no_lock, shared_lock, exclusive_lock]]). + +slot_iter(Tab) -> + slot_iter(Tab, mnesia:dirty_slot(Tab, 0), 1). +slot_iter(_Tab, '$end_of_table', _) -> + []; +slot_iter(Tab, Recs, Slot) -> + Recs ++ slot_iter(Tab, mnesia:dirty_slot(Tab, Slot), Slot+1). + +key_iter(Tab) -> + key_iter(Tab, mnesia:dirty_first(Tab)). +key_iter(_Tab, '$end_of_table') -> + []; +key_iter(Tab, Key) -> + [Key | key_iter(Tab, mnesia:dirty_next(Tab, Key))]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +lock_burst(suite) -> []; +lock_burst(Config) when is_list(Config) -> + [Node1] = ?acquire_nodes(1, Config), + Tab = burst, + ?match({atomic, ok}, mnesia:create_table(Tab, + [{attributes, [a, b]}, + {ram_copies, [Node1]}])), + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + ?match([], mnesia:system_info(held_locks)), + ?match([], mnesia:system_info(lock_queue)), + ?match(ok, burst_em(Tab, 1000)), + ?match([{burst,1,1000}], mnesia:dirty_read(Tab,1)), + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + ?match([], mnesia:system_info(held_locks)), + ?match([], mnesia:system_info(lock_queue)), + ok. + +burst_em(Tab, N) -> + spawn_link(?MODULE, burst_counter, [self(), Tab, N]), + receive + burst_counter_done -> ok + end. + +burst_counter(Monitor, Tab, N) when N > 0 -> + ?match(ok, burst_gen(Tab, N, self())), + Monitor ! burst_receiver(N). + +burst_receiver(0) -> + burst_counter_done; +burst_receiver(N) -> + receive + burst_incr_done -> + burst_receiver(N-1) + end. + +burst_gen(_, 0, _) -> + ok; +burst_gen(Tab, N, Father) when is_integer(N), N > 0 -> + spawn_link(?MODULE, burst_incr, [Tab, Father]), + burst_gen(Tab, N-1, Father). + +burst_incr(Tab, Father) -> + Fun = fun() -> + Val = + case mnesia:read({Tab, 1}) of + [{Tab, 1, V}] -> V; + [] -> 0 + end, + mnesia:write({Tab, 1, Val+1}) + end, + ?match({atomic, ok}, mnesia:transaction(Fun)), + Father ! burst_incr_done. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +sticky_locks(doc) -> + ["Simple Tests of sticky locks"]; + +sticky_locks(suite) -> + [ + basic_sticky_functionality + %% Needs to be expandand a little bit further + ]. + +basic_sticky_functionality(suite) -> []; +basic_sticky_functionality(Config) when is_list(Config) -> + [N1, N2] = Nodes = ?acquire_nodes(2, Config), + Tab = basic_table, + Storage = mnesia_test_lib:storage_type(disc_copies, Config), + ?match({atomic, ok}, mnesia:create_table(Tab, [{Storage, Nodes}])), + ?match({atomic, ok}, mnesia:create_table(sync, [{ram_copies, Nodes}])), + Trans1 = fun() -> + ?match(ok, mnesia:s_write({Tab, 1, 2})), + ?match([{Tab, 1, 2}], mnesia:read({Tab, 1})), + ?match(timeout, receive M -> M after 500 -> timeout end), + ?match(ok, mnesia:s_write({Tab, 2, 2})), + ?match(ok, mnesia:write({Tab, 42, 4711})) + end, + Trans2 = fun() -> + ?match([{Tab, 1, 2}], mnesia:read({Tab, 1})), + ?match(timeout, receive M -> M after 500 -> timeout end), + ?match(ok, mnesia:write({Tab, 1, 4711})), + ?match(ok, mnesia:s_write({Tab, 2, 4})), + ?match(ok, mnesia:delete({Tab, 42})) + end, + rpc:call(N1, mnesia, transaction, [Trans1]), + ?match([{Tab,N1}], rpc:call(N1, ?MODULE, get_sticky, [])), + ?match([{Tab,N1}], rpc:call(N2, ?MODULE, get_sticky, [])), + + rpc:call(N2, mnesia, transaction, [Trans2]), + ?match([], rpc:call(N1, ?MODULE, get_sticky, [])), + ?match([], rpc:call(N2, ?MODULE, get_sticky, [])), + + Slock = fun() -> mnesia:read({sync,sync}),get_sticky() end, + ?match({atomic, [{Tab,1, 4711}]}, mnesia:transaction(fun() -> mnesia:read({Tab, 1}) end)), + ?match({atomic, [{Tab,2, 4}]}, mnesia:transaction(fun() -> mnesia:read({Tab, 2}) end)), + ?match({atomic, [{Tab,N1}]}, rpc:call(N1, mnesia, transaction, + [fun() -> mnesia:s_write({Tab, 1, 3}),Slock() end])), + ?match([{Tab,N1}], rpc:call(N2, ?MODULE, get_sticky, [])), + + ?match({atomic,[]}, rpc:call(N2, mnesia, transaction, + [fun() -> mnesia:s_write({Tab, 1, 4}),Slock() end])), + + ?match([], rpc:call(N1, ?MODULE, get_sticky, [])), + ?match([], rpc:call(N2, ?MODULE, get_sticky, [])), + + ?match({atomic,[{Tab,N2}]}, rpc:call(N2, mnesia, transaction, + [fun() -> mnesia:s_write({Tab, 1, 4}),Slock() end])), + + ?match({atomic,[]}, rpc:call(N1, mnesia, transaction, + [fun() -> mnesia:s_write({Tab, 1, 5}),Slock() end])), + ?match({atomic,[{Tab,N1}]}, rpc:call(N1, mnesia, transaction, + [fun() -> mnesia:s_write({Tab, 1, 5}),Slock() end])), + ?match({atomic,[]}, rpc:call(N2, mnesia, transaction, + [fun() -> mnesia:s_write({Tab, 1, 6}),Slock() end])), + ?match({atomic,[{Tab,N2}]}, rpc:call(N2, mnesia, transaction, + [fun() -> mnesia:s_write({Tab, 1, 7}),Slock() end])), + + ?match([{Tab,N2}], get_sticky()), + ?match({atomic, [{Tab,1, 7}]}, mnesia:transaction(fun() -> mnesia:read({Tab, 1}) end)), + ?match([{Tab,N2}], get_sticky()), + ?match({atomic, [{Tab,2, 4}]}, mnesia:transaction(fun() -> mnesia:read({Tab, 2}) end)), + ?match([{Tab,N2}], get_sticky()), + ?match({atomic,[{Tab,N2}]}, rpc:call(N2, mnesia, transaction, + [fun() -> mnesia:s_write({Tab, 1, 6}),Slock() end])), + ?match([{Tab,N2}], get_sticky()), + ?match({atomic, [{Tab,1, 6}]}, mnesia:transaction(fun() -> mnesia:read({Tab, 1}) end)), + ?match([{Tab,N2}], get_sticky()), + ?match({atomic, [{Tab,2, 4}]}, mnesia:transaction(fun() -> mnesia:read({Tab, 2}) end)), + ?match([{Tab,N2}], get_sticky()), + ?verify_mnesia(Nodes, []). + +get_sticky() -> + mnesia_locker ! {get_table, self(), mnesia_sticky_locks}, + receive {mnesia_sticky_locks, Locks} -> Locks end. + +get_held() -> + mnesia_locker ! {get_table, self(), mnesia_sticky_locks}, + receive {mnesia_sticky_locks, Locks} -> Locks end. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +unbound_locking(suite) -> + [unbound1, unbound2]; + +unbound_locking(doc) -> + ["Check that mnesia handles unbound key variables, GPRS bug." + "Ticket id: OTP-3342"]. + +unbound1(suite) -> []; +unbound1(Config) when is_list(Config) -> + [Node1] = ?acquire_nodes(1, Config), + + ?match({atomic, ok}, mnesia:create_table(ul, [])), + + Tester = self(), + Write = fun() -> + mnesia:write({ul, {key, {17,42}}, val}), + ?log("~p Got write lock waiting...~n", [self()]), + Tester ! continue, + receive + continue -> + ok + end, + ?log("..continuing~n", []), + ok + end, + + {success, [A]} = ?start_activities([Node1]), + ?start_transactions([A]), + A ! Write, + + receive continue -> ok end, + + Match = fun() -> + case catch mnesia:match_object({ul, {key, {'_', '$0'}}, '_'}) of + {'EXIT', What} -> %% Cyclic first time + ?log("Cyclic Restarting~n", []), + A ! continue, + A ! end_trans, + exit(What); + Res -> + ?log("Got match log ~p...~n", [Res]), + Res + end + end, + ?match({atomic, [{ul,{key,{17,42}},val}]}, mnesia:transaction(Match)), + + ?match_receive({A, ok}), + ?match_receive({A, {atomic, end_trans}}), + ok. + +unbound2(suite) -> []; +unbound2(Config) when is_list(Config) -> + [Node1] = ?acquire_nodes(1, Config), + + ?match({atomic, ok}, mnesia:create_table(ul, [])), + + {success, [B, A]} = ?start_activities([Node1, Node1]), + + Me = self(), + + Write = fun() -> + mnesia:write({ul, {key, {17,42}}, val}), + ?log("~p Got write lock waiting... Tid ~p ~n", + [self(), get(mnesia_activity_state)]), + Me ! ok_lock, + receive + continue -> + ok + end, + ?log("..continuing~n", []), + ok + end, + + Match = fun() -> + receive + continueB -> + ?log("~p, moving on TID ~p~n", + [self(), get(mnesia_activity_state)]), + Me ! {self(), continuing} + end, + case catch mnesia:match_object({ul, {key, {'_', '$0'}}, + '_'}) of + {'EXIT', What} -> %% Cyclic first time + ?log("Cyclic Restarting ~p ~n", [What]), + {should_not_happen,What}; + Res -> + ?log("Got match log ~p...~n", [Res]), + Res + end + end, + + B ! fun() -> mnesia:transaction(Match) end, + timer:sleep(100), %% Let B be started first.. + A ! fun() -> mnesia:transaction(Write) end, + + receive ok_lock -> ok end, + + B ! continueB, + ?match_receive({B, continuing}), + + %% B should now be in lock queue. + A ! continue, + ?match_receive({A, {atomic, ok}}), + ?match_receive({B, {atomic, [{ul,{key,{17,42}},val}]}}), + ok. + +receiver() -> + receive + {_Pid, begin_trans} -> + receiver(); + Else -> + Else + after + 10000 -> + timeout + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +admin_conflict(doc) -> + ["Provoke lock conflicts with schema transactions and checkpoints."]; +admin_conflict(suite) -> + [ + create_table, + delete_table, + move_table_copy, + add_table_index, + del_table_index, + transform_table, + snmp_open_table, + snmp_close_table, + change_table_copy_type, + change_table_access, + add_table_copy, + del_table_copy, + dump_tables, + extra_admin_tests + ]. + +create_table(suite) -> []; +create_table(Config) when is_list(Config) -> + [ThisNode, Node2] = ?acquire_nodes(2, Config), + Tab = c_t_tab, + Def = [{ram_copies, [ThisNode]}, {attributes, [key, attr1, attr2]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + insert(Tab, 50), + {success, [A]} = ?start_activities([ThisNode]), + mnesia_test_lib:start_sync_transactions([A], 0), + + A ! fun() -> mnesia:write({Tab, 1, 1, updated}) end, + ?match_receive({A, ok}), %% A is executed + + DiskMaybe = mnesia_test_lib:storage_type(disc_copies, Config), + + Pid = spawn_link(?MODULE, op, [self(), mnesia, create_table, + [test_tab1, [{DiskMaybe, [ThisNode]}]]]), + ?match_multi_receive([{Pid, {atomic, ok}}, + {'EXIT', Pid, normal}]), %% No Locks! op should be exec. + + Pid2 = spawn_link(?MODULE, op, [self(), mnesia, create_table, + [test_tab2, [{ram_copies, [Node2]}]]]), + + ?match_multi_receive([{Pid2, {atomic, ok}}, + {'EXIT', Pid2, normal}]), %% No Locks! op should be exec. + + A ! end_trans, + ?match_receive({A,{atomic,end_trans}}), + + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + ?match([], mnesia:system_info(held_locks)), + ?match([], mnesia:system_info(lock_queue)), + ok. + +delete_table(suite) -> []; +delete_table(Config) when is_list(Config) -> + [ThisNode, Node2] = ?acquire_nodes(2, Config), + Tab = d_t_tab, + Def = [{ram_copies, [ThisNode, Node2]}, {attributes, [key, attr1, attr2]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + insert(Tab, 50), + {success, [A]} = ?start_activities([ThisNode]), + mnesia_test_lib:start_sync_transactions([A], 0), + + A ! fun() -> mnesia:read({Tab, 1}) end, + ?match_receive({A, [{Tab, 1, 1, 0}]}), %% A is executed + + Pid = spawn_link(?MODULE, op, [self(), mnesia, delete_table, + [Tab]]), + + ?match_receive(timeout), %% op waits for locks occupied by A + + A ! end_trans, %% Kill A, locks should be released + ?match_receive({A,{atomic,end_trans}}), + + receive + Msg -> ?match({Pid, {atomic, ok}}, Msg) + after + timer:seconds(20) -> ?error("Operation timed out", []) + end, + + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + ?match([], mnesia:system_info(held_locks)), + ?match([], mnesia:system_info(lock_queue)), + ok. + +move_table_copy(suite) -> []; +move_table_copy(Config) when is_list(Config) -> + [ThisNode, Node2] = ?acquire_nodes(2, Config), + Tab = m_t_c_tab, + Def = [{ram_copies, [ThisNode]}, {attributes, [key, attr1, attr2]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + insert(Tab, 50), + {success, [A]} = ?start_activities([ThisNode]), + mnesia_test_lib:start_sync_transactions([A], 0), + + A ! fun() -> mnesia:write({Tab, 1, 2, 3}) end, + ?match_receive({A, ok}), %% A is executed + + Pid = spawn_link(?MODULE, op, [self(), mnesia, move_table_copy, + [Tab, ThisNode, Node2]]), + + ?match_receive(timeout), %% op waits for locks occupied by A + + A ! end_trans, %% Kill A, locks should be released + ?match_receive({A,{atomic,end_trans}}), + + receive + Msg -> ?match({Pid, {atomic, ok}}, Msg) + after + timer:seconds(20) -> ?error("Operation timed out", []) + end, + + timer:sleep(500), %% Don't know how to sync this !!! + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + sys:get_status(whereis(mnesia_tm)), % Explicit sync, release locks is async + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + ?match([], mnesia:system_info(held_locks)), + ?match([], mnesia:system_info(lock_queue)), + ok. + +add_table_index(suite) -> []; +add_table_index(Config) when is_list(Config) -> + [ThisNode, _Node2] = ?acquire_nodes(2, Config ++ [{tc_timeout, 60000}]), + Tab = a_t_i_tab, + Def = [{ram_copies, [ThisNode]}, {attributes, [key, attr1, attr2]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + insert(Tab, 50), + {success, [A]} = ?start_activities([ThisNode]), + mnesia_test_lib:start_sync_transactions([A], 0), + + A ! fun() -> mnesia:write({Tab, 1, 1, updated}) end, + ?match_receive({A, ok}), %% A is executed + + Pid = spawn_link(?MODULE, op, [self(), mnesia, + add_table_index, [Tab, attr1]]), + + ?match_receive(timeout), %% op waits for locks occupied by A + + A ! end_trans, %% Kill A, locks should be released + ?match_receive({A,{atomic,end_trans}}), + + receive + Msg -> ?match({Pid, {atomic, ok}}, Msg) + after + timer:seconds(20) -> ?error("Operation timed out", []) + end, + + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + ?match([], mnesia:system_info(held_locks)), + ?match([], mnesia:system_info(lock_queue)), + ok. + +del_table_index(suite) -> []; +del_table_index(Config) when is_list(Config) -> + [ThisNode, _Node2] = ?acquire_nodes(2, Config), + Tab = d_t_i_tab, + Def = [{ram_copies, [ThisNode]}, {attributes, [key, attr1, attr2]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + insert(Tab, 50), + ?match({atomic, ok}, mnesia:add_table_index(Tab, attr1)), + + {success, [A]} = ?start_activities([ThisNode]), + mnesia_test_lib:start_sync_transactions([A], 0), + + A ! fun() -> mnesia:write({Tab, 51, 51, attr2}) end, + ?match_receive({A, ok}), %% A is executed + + Pid = spawn_link(?MODULE, op, [self(), mnesia, del_table_index, + [Tab, attr1]]), + + ?match_receive(timeout), %% op waits for locks occupied by A + + A ! end_trans, %% Kill A, locks should be released + ?match_receive({A,{atomic,end_trans}}), + %% Locks released! op should be exec. + receive + Msg -> ?match({Pid, {atomic, ok}}, Msg) + after + timer:seconds(20) -> ?error("Operation timed out", []) + end, + + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + ?match([], mnesia:system_info(held_locks)), + ?match([], mnesia:system_info(lock_queue)), + ok. + +transform_table(suite) -> []; +transform_table(Config) when is_list(Config) -> + [ThisNode, Node2] = ?acquire_nodes(2, Config), + Tab = t_t_tab, + Def = [{ram_copies, [ThisNode, Node2]}, {attributes, [key, attr1, attr2]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + insert(Tab, 50), + {success, [A]} = ?start_activities([ThisNode]), + mnesia_test_lib:start_sync_transactions([A], 0), + + A ! fun() -> mnesia:read({Tab, 1}) end, + ?match_receive({A, [{Tab, 1, 1, 0}]}), %% A is executed + + Transform = fun({Table, Key, Attr1, Attr2}) -> % Need todo a transform + {Table, Key, {Attr1, Attr2}} end, + Pid = spawn_link(?MODULE, op, [self(), mnesia, transform_table, + [Tab, Transform, [key, attr1]]]), + ?match_receive(timeout), %% op waits for locks occupied by A + + A ! end_trans, %% Kill A, locks should be released + ?match_receive({A,{atomic,end_trans}}), + + receive + Msg -> ?match({Pid, {atomic, ok}}, Msg) + after + timer:seconds(20) -> ?error("Operation timed out", []) + end, + + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + ?match([], mnesia:system_info(held_locks)), + ?match([], mnesia:system_info(lock_queue)), + ok. + +snmp_open_table(suite) -> []; +snmp_open_table(Config) when is_list(Config) -> + [ThisNode, _Node2] = ?acquire_nodes(2, Config), + Tab = s_o_t_tab, + Def = [{ram_copies, [ThisNode]}, {attributes, [key, attr1, attr2]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + insert(Tab, 50), + {success, [A]} = ?start_activities([ThisNode]), + mnesia_test_lib:start_sync_transactions([A], 0), + + A ! fun() -> mnesia:write({Tab, 1, 1, 100}) end, + ?match_receive({A, ok}), %% A is executed + + Pid = spawn_link(?MODULE, op, [self(), mnesia, snmp_open_table, + [Tab, [{key, integer}]]]), + + ?match_receive(timeout), %% op waits for locks occupied by A + + A ! end_trans, %% Kill A, locks should be released + ?match_receive({A,{atomic,end_trans}}), + + %% Locks released! op should be exec. Can take a while (thats the timeout) + receive + Msg -> ?match({Pid, {atomic, ok}}, Msg) + after + timer:seconds(20) -> ?error("Operation timed out", []) + end, + + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + ?match([], mnesia:system_info(held_locks)), + ?match([], mnesia:system_info(lock_queue)), + ok. + +snmp_close_table(suite) -> []; +snmp_close_table(Config) when is_list(Config) -> + [ThisNode, _Node2] = ?acquire_nodes(2, Config), + Tab = s_c_t_tab, + Def = [{ram_copies, [ThisNode]}, {attributes, [key, attr1, attr2]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + ?match({atomic, ok}, mnesia:snmp_open_table(Tab, [{key, integer}])), + insert(Tab, 50), + {success, [A]} = ?start_activities([ThisNode]), + mnesia_test_lib:start_sync_transactions([A], 0), + + A ! fun() -> mnesia:write({Tab, 1, 1, 100}) end, + ?match_receive({A, ok}), %% A is executed + + Pid = spawn_link(?MODULE, op, [self(), mnesia, snmp_close_table, [Tab]]), + ?match_receive(timeout), %% op waits for locks occupied by A + + A ! end_trans, %% Kill A, locks should be released + ?match_receive({A,{atomic,end_trans}}), + + %% Locks released! op should be exec. Can take a while (thats the timeout) + receive + Msg -> ?match({Pid, {atomic, ok}}, Msg) + after + timer:seconds(20) -> ?error("Operation timed out", []) + end, + + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + ?match([], mnesia:system_info(held_locks)), + ?match([], mnesia:system_info(lock_queue)), + ok. + +change_table_copy_type(suite) -> []; +change_table_copy_type(Config) when is_list(Config) -> + [ThisNode, _Node2] = ?acquire_nodes(2, Config), + Tab = c_t_c_t_tab, + Def = [{ram_copies, [ThisNode]}, {attributes, [key, attr1, attr2]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + insert(Tab, 50), + {success, [A]} = ?start_activities([ThisNode]), + mnesia_test_lib:start_sync_transactions([A], 0), + A ! fun() -> mnesia:write({Tab, 1, 1, updated}) end, + ?match_receive({A, ok}), %% A is executed + + Pid = spawn_link(?MODULE, op, [self(), mnesia, change_table_copy_type, + [Tab, ThisNode, disc_copies]]), + + ?match_receive(timeout), %% op waits for locks occupied by A + + A ! end_trans, %% Kill A, locks should be released + ?match_receive({A,{atomic,end_trans}}), + + receive + Msg -> ?match({Pid, {atomic, ok}}, Msg) + after + timer:seconds(20) -> ?error("Operation timed out", []) + end, + + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + ?match([], mnesia:system_info(held_locks)), + ?match([], mnesia:system_info(lock_queue)), + ok. + +change_table_access(suite) -> []; +change_table_access(Config) when is_list(Config) -> + [ThisNode, _Node2] = ?acquire_nodes(2, Config), + Tab = c_t_a_tab, + Def = [{ram_copies, [ThisNode]}, {attributes, [key, attr1, attr2]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + insert(Tab, 50), + {success, [A]} = ?start_activities([ThisNode]), + mnesia_test_lib:start_sync_transactions([A], 0), + + A ! fun() -> mnesia:write({Tab, 1, 1, updated}) end, + ?match_receive({A, ok}), %% A is executed + + Pid = spawn_link(?MODULE, op, [self(), mnesia, change_table_access_mode, + [Tab, read_only]]), + + + ?match_receive(timeout), %% op waits for locks occupied by A + + A ! end_trans, %% Kill A, locks should be released + ?match_receive({A,{atomic,end_trans}}), + + receive + Msg -> ?match({Pid, {atomic, ok}}, Msg) + after + timer:seconds(20) -> ?error("Operation timed out", []) + end, + + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + ?match([], mnesia:system_info(held_locks)), + ?match([], mnesia:system_info(lock_queue)), + ok. + +add_table_copy(suite) -> []; +add_table_copy(Config) when is_list(Config) -> + [ThisNode, Node2] = ?acquire_nodes(2, Config), + Tab = a_t_c_tab, + Def = [{ram_copies, [ThisNode]}, {attributes, [key, attr1, attr2]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + insert(Tab, 50), + {success, [A]} = ?start_activities([ThisNode]), + mnesia_test_lib:start_sync_transactions([A], 0), + + A ! fun() -> mnesia:write({Tab, 1, 1, updated}) end, + ?match_receive({A, ok}), %% A is executed + + Pid = spawn_link(?MODULE, op, [self(), mnesia, add_table_copy, + [Tab, Node2, ram_copies]]), + + ?match_receive(timeout), %% op waits for locks occupied by A + + A ! end_trans, %% Kill A, locks should be released + ?match_receive({A,{atomic,end_trans}}), + + receive + Msg -> ?match({Pid, {atomic, ok}}, Msg) + after + timer:seconds(20) -> ?error("Operation timed out", []) + end, + + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + ?match([], mnesia:system_info(held_locks)), + ?match([], mnesia:system_info(lock_queue)), + ok. + +del_table_copy(suite) -> []; +del_table_copy(Config) when is_list(Config) -> + [ThisNode, Node2] = ?acquire_nodes(2, Config), + Tab = d_t_c_tab, + Def = [{ram_copies, [ThisNode, Node2]}, {attributes, [key, attr1, attr2]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + insert(Tab, 50), + {success, [A]} = ?start_activities([ThisNode]), + mnesia_test_lib:start_sync_transactions([A], 0), + A ! fun() -> mnesia:write({Tab, 1, 2, 5}) end, + ?match_receive({A, ok}), %% A is executed + + Pid = spawn_link(?MODULE, op, [self(), mnesia, del_table_copy, + [Tab, ThisNode]]), + + ?match_receive(timeout), %% op waits for locks occupied by A + A ! end_trans, %% Kill A, locks should be released + ?match_receive({A, {atomic,end_trans}}), + + ?match_receive({Pid, {atomic, ok}}), + ?match_receive({'EXIT', Pid, normal}), + + timer:sleep(500), %% Don't know how to sync this !!! + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + sys:get_status(whereis(mnesia_tm)), % Explicit sync, release locks is async + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + ?match([], mnesia:system_info(held_locks)), + ?match([], mnesia:system_info(lock_queue)), + ok. + +dump_tables(suite) -> []; +dump_tables(Config) when is_list(Config) -> + [ThisNode, Node2] = ?acquire_nodes(2, Config), + Tab = dump_t_tab, + Def = [{ram_copies, [ThisNode, Node2]}, {attributes, [key, attr1, attr2]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + insert(Tab, 50), + {success, [A]} = ?start_activities([ThisNode]), + mnesia_test_lib:start_sync_transactions([A], 0), + A ! fun() -> mnesia:write({Tab, 1, 1, updated}) end, + ?match_receive({A, ok}), %% A is executed + + Pid = spawn_link(?MODULE, op, [self(), mnesia, dump_tables, + [[Tab]]]), + + ?match_receive(timeout), %% op waits for locks occupied by A + + A ! end_trans, %% Kill A, locks should be released + ?match_receive({A,{atomic,end_trans}}), + + receive + Msg -> ?match({Pid, {atomic, ok}}, Msg) + after + timer:seconds(20) -> ?error("Operation timed out", []) + end, + + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + ?match([], mnesia:system_info(held_locks)), + ?match([], mnesia:system_info(lock_queue)), + ok. + +op(Father, Mod, Fun, Args) -> + Res = apply(Mod, Fun, Args), + Father ! {self(), Res}. + +insert(_Tab, 0) -> ok; +insert(Tab, N) when N > 0 -> + ok = mnesia:sync_dirty(fun() -> mnesia:write({Tab, N, N, 0}) end), + insert(Tab, N-1). + +extra_admin_tests(suite) -> + [del_table_copy_1, + del_table_copy_2, + del_table_copy_3, + add_table_copy_1, + add_table_copy_2, + add_table_copy_3, + add_table_copy_4, + move_table_copy_1, + move_table_copy_2, + move_table_copy_3, + move_table_copy_4]. + +update_own(Tab, Key, Acc) -> + Update = + fun() -> + Res = mnesia:read({Tab, Key}), + case Res of + [{Tab, Key, Extra, Acc}] -> + mnesia:write({Tab,Key,Extra, Acc+1}); + Val -> + {read, Val, {acc, Acc}} + end + end, + receive + {Pid, quit} -> Pid ! {self(), Acc} + after + 0 -> + case mnesia:transaction(Update) of + {atomic, ok} -> + update_own(Tab, Key, Acc+1); + Else -> + ?error("Trans failed on ~p with ~p~n" + "Info w2read ~p w2write ~p w2commit ~p storage ~p ~n", + [node(), + Else, + mnesia:table_info(Tab, where_to_read), + mnesia:table_info(Tab, where_to_write), + mnesia:table_info(Tab, where_to_commit), + mnesia:table_info(Tab, storage_type)]) + end + end. + +update_shared(Tab, Me, Acc) -> + Update = + fun() -> + W2R = mnesia:table_info(Tab, where_to_read), + Res = mnesia:read({Tab, 0}), + case Res of + [{Tab, Key, Extra, Val}] when element(Me, Extra) == Acc -> + Extra1 = setelement(Me, Extra, Acc+1), + Term = {Tab, Key, Extra1, Val+1}, + ok = mnesia:write(Term), +% ?log("At ~p: ~p w2r ~p w2w ~p ~n", +% [node(), Term, +% mnesia:table_info(Tab, where_to_read), + W2W = mnesia:table_info(Tab, where_to_write), + W2C = mnesia:table_info(Tab, where_to_commit), +%% mnesia:table_info(Tab, storage_type) +% ]), + {_Mod, Tid, Ts} = get(mnesia_activity_state), + io:format("~p ~p~n", [Tid, ets:tab2list(element(2,Ts))]), + {ok,Term,{W2R,W2W,W2C}}; + Val -> + Info = [{acc, Acc}, {me, Me}, + {tid, element(2, mnesia:get_activity_id())}, + {locks, mnesia:system_info(held_locks)}], + {read, Val, Info} + end + end, + receive + {Pid, quit} -> Pid ! {self(), Acc} + after + 0 -> + case mnesia:transaction(Update) of + {atomic, {ok,Term,W2}} -> + io:format("~p:~p:(~p,~p) ~w@~w~n", [erlang:now(),node(),Me,Acc,Term,W2]), + update_shared(Tab, Me, Acc+1); + Else -> + ?error("Trans failed on ~p with ~p~n" + "Info w2read ~p w2write ~p w2commit ~p storage ~p ~n", + [node(), + Else, + mnesia:table_info(Tab, where_to_read), + mnesia:table_info(Tab, where_to_write), + mnesia:table_info(Tab, where_to_commit), + mnesia:table_info(Tab, storage_type) + ]) + end + end. + +init_admin(Def, N1, N2, N3) -> + Tab = schema_ops, + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + insert(Tab, 1002), + + Pid1 = spawn_link(N1, ?MODULE, update_own, [Tab, 1, 0]), + Pid2 = spawn_link(N2, ?MODULE, update_own, [Tab, 2, 0]), + Pid3 = spawn_link(N3, ?MODULE, update_own, [Tab, 3, 0]), + + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write({Tab, 0, {0,0,0}, 0}) end)), + + Pid4 = spawn_link(N1, ?MODULE, update_shared, [Tab, 1, 0]), + Pid5 = spawn_link(N2, ?MODULE, update_shared, [Tab, 2, 0]), + Pid6 = spawn_link(N3, ?MODULE, update_shared, [Tab, 3, 0]), + + {Pid1, Pid2, Pid3, Pid4, Pid5, Pid6}. + +verify_results({P1, P2, P3, P4, P5, P6}) -> + Tab = schema_ops, N1 = node(P1), N2 = node(P2), N3 = node(P3), + + try + P1 ! {self(), quit}, + R1 = receive {P1, Res1} -> Res1 after 9000 -> throw({timeout,P1}) end, + P2 ! {self(), quit}, + R2 = receive {P2, Res2} -> Res2 after 9000 -> throw({timeout,P2}) end, + P3 ! {self(), quit}, + R3 = receive {P3, Res3} -> Res3 after 9000 -> throw({timeout,P3}) end, + + P4 ! {self(), quit}, + R4 = receive {P4, Res4} -> Res4 after 9000 -> throw({timeout,P4}) end, + P5 ! {self(), quit}, + R5 = receive {P5, Res5} -> Res5 after 9000 -> throw({timeout,P5}) end, + P6 ! {self(), quit}, + R6 = receive {P6, Res6} -> Res6 after 9000 -> throw({timeout,P6}) end, + + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write_lock_table(Tab) end)), + ?log("Should be ~p~n", [R1]), + ?match([{_, _, _, R1}], rpc:call(N1, mnesia, dirty_read, [{Tab, 1}])), + ?match([{_, _, _, R1}], rpc:call(N2, mnesia, dirty_read, [{Tab, 1}])), + ?match([{_, _, _, R1}], rpc:call(N3, mnesia, dirty_read, [{Tab, 1}])), + ?log("Should be ~p~n", [R2]), + ?match([{_, _, _, R2}], rpc:call(N1, mnesia, dirty_read, [{Tab, 2}])), + ?match([{_, _, _, R2}], rpc:call(N2, mnesia, dirty_read, [{Tab, 2}])), + ?match([{_, _, _, R2}], rpc:call(N3, mnesia, dirty_read, [{Tab, 2}])), + ?log("Should be ~p~n", [R3]), + ?match([{_, _, _, R3}], rpc:call(N1, mnesia, dirty_read, [{Tab, 3}])), + ?match([{_, _, _, R3}], rpc:call(N2, mnesia, dirty_read, [{Tab, 3}])), + ?match([{_, _, _, R3}], rpc:call(N3, mnesia, dirty_read, [{Tab, 3}])), + + Res = R4+R5+R6, + ?log("Should be {~p+~p+~p}= ~p~n", [R4, R5, R6, Res]), + ?match([{_, _, {R4,R5,R6}, Res}], rpc:call(N1, mnesia, dirty_read, [{Tab, 0}])), + ?match([{_, _, {R4,R5,R6}, Res}], rpc:call(N2, mnesia, dirty_read, [{Tab, 0}])), + ?match([{_, _, {R4,R5,R6}, Res}], rpc:call(N3, mnesia, dirty_read, [{Tab, 0}])) + catch throw:{timeout, Pid} -> + mnesia_lib:dist_coredump(), + ?error("Timeout ~p ~n", [Pid]) + end. + + +get_info(Tab) -> + Info = mnesia:table_info(Tab, all), + mnesia_lib:verbose("~p~n", [Info]). + +del_table_copy_1(suite) -> []; +del_table_copy_1(Config) when is_list(Config) -> + [_Node1, Node2, _Node3] = Nodes = ?acquire_nodes(3, Config), + del_table(Node2, Node2, Nodes). %Called on same Node as deleted +del_table_copy_2(suite) -> []; +del_table_copy_2(Config) when is_list(Config) -> + [Node1, Node2, _Node3] = Nodes = ?acquire_nodes(3, Config), + del_table(Node1, Node2, Nodes). %Called from other Node +del_table_copy_3(suite) -> []; +del_table_copy_3(Config) when is_list(Config) -> + [_Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + del_table(Node3, Node2, Nodes). %Called from Node w.o. table + +%%% The actual test +del_table(CallFrom, DelNode, [Node1, Node2, Node3]) -> + Def = [{ram_copies, [Node1]}, {disc_copies, [Node2]}, + {attributes, [key, attr1, attr2]}], + Tab = schema_ops, + Pids = init_admin(Def, Node1, Node2, Node3), + + ?log("Call from ~p delete table from ~p ~n", [CallFrom, DelNode]), + rpc:multicall([Node1, Node2, Node3], ?MODULE, get_info, [Tab]), + + ?match({atomic, ok}, + rpc:call(CallFrom, mnesia, del_table_copy, [Tab, DelNode])), + + verify_results(Pids), + rpc:multicall([Node1, Node2, Node3], ?MODULE, get_info, [Tab]), + ?verify_mnesia([Node1, Node2, Node3], []). + +add_table_copy_1(suite) -> []; +add_table_copy_1(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + Def = [{disc_only_copies, [Node1, Node2]}, + {attributes, [key, attr1, attr2]}], + add_table(Node1, Node3, Nodes, Def). +add_table_copy_2(suite) -> []; +add_table_copy_2(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + Def = [{disc_only_copies, [Node1, Node2]}, + {attributes, [key, attr1, attr2]}], + add_table(Node2, Node3, Nodes, Def). +add_table_copy_3(suite) -> []; +add_table_copy_3(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + Def = [{disc_only_copies, [Node1, Node2]}, + {attributes, [key, attr1, attr2]}], + add_table(Node3, Node3, Nodes, Def). +add_table_copy_4(suite) -> []; +add_table_copy_4(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + Def = [{disc_only_copies, [Node1]}, + {attributes, [key, attr1, attr2]}], + add_table(Node2, Node3, Nodes, Def). +%%% The actual test +add_table(CallFrom, AddNode, [Node1, Node2, Node3], Def) -> + Pids = init_admin(Def, Node1, Node2, Node3), + Tab = schema_ops, + ?log("Call from ~p add table to ~p ~n", [CallFrom, AddNode]), + rpc:multicall([Node1, Node2, Node3], ?MODULE, get_info, [Tab]), + ?match({atomic, ok}, rpc:call(CallFrom, mnesia, add_table_copy, + [Tab, AddNode, ram_copies])), + verify_results(Pids), + rpc:multicall([Node1, Node2, Node3], ?MODULE, get_info, [Tab]), + ?verify_mnesia([Node1, Node2, Node3], []). +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +move_table_copy_1(suite) -> []; +move_table_copy_1(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + Def = [{disc_copies, [Node1, Node2]}, + {attributes, [key, attr1, attr2]}], + move_table(Node1, Node1, Node3, Nodes, Def). +move_table_copy_2(suite) -> []; +move_table_copy_2(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + Def = [{disc_copies, [Node1, Node2]}, + {attributes, [key, attr1, attr2]}], + move_table(Node2, Node1, Node3, Nodes, Def). +move_table_copy_3(suite) -> []; +move_table_copy_3(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + Def = [{disc_copies, [Node1, Node2]}, + {attributes, [key, attr1, attr2]}], + move_table(Node3, Node1, Node3, Nodes, Def). +move_table_copy_4(suite) -> []; +move_table_copy_4(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + Def = [{disc_copies, [Node1]}, + {attributes, [key, attr1, attr2]}], + move_table(Node2, Node1, Node3, Nodes, Def). +%%% The actual test +move_table(CallFrom, FromNode, ToNode, [Node1, Node2, Node3], Def) -> + Pids = init_admin(Def, Node1, Node2, Node3), + Tab = schema_ops, + ?log("Call from ~p move table from ~p to ~p ~n", [CallFrom, FromNode, ToNode]), + rpc:multicall([Node1, Node2, Node3], ?MODULE, get_info, [Tab]), + ?match({atomic, ok}, rpc:call(CallFrom, mnesia, move_table_copy, + [Tab, FromNode, ToNode])), + verify_results(Pids), + rpc:multicall([Node1, Node2, Node3], ?MODULE, get_info, [Tab]), + ?verify_mnesia([Node1, Node2, Node3], []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +visibility(doc) -> + ["Verify the visibility semantics for various configurations"]; +visibility(suite) -> + [ + dirty_updates_visible_direct, + dirty_reads_regardless_of_trans, + trans_update_invisibible_outside_trans, + trans_update_visible_inside_trans, + write_shadows, + delete_shadows, +%% delete_shadows2, + write_delete_shadows_bag, + write_delete_shadows_bag2, + iteration, + shadow_search, + snmp_shadows + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +dirty_updates_visible_direct(doc) -> + ["One process can immediately see dirty updates of another"]; +dirty_updates_visible_direct(suite) -> []; +dirty_updates_visible_direct(Config) when is_list(Config) -> + dirty_visibility(outside_trans, Config). + +dirty_reads_regardless_of_trans(doc) -> + ["Dirty reads are not affected by transaction context"]; +dirty_reads_regardless_of_trans(suite) -> []; +dirty_reads_regardless_of_trans(Config) when is_list(Config) -> + dirty_visibility(inside_trans, Config). + +dirty_visibility(Mode, Config) -> + [Node1] = ?acquire_nodes(1, Config), + Tab = list_to_atom(lists:concat([dirty_visibility, '_', Mode])), + + ?match({atomic, ok}, mnesia:create_table([{name, Tab}, {ram_copies, [Node1]}])), + ValPos = 3, + + ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)), + + %% Start two processes + {success, [A]} = ?start_activities([Node1]), + + case Mode of + inside_trans -> + ?start_transactions([A]), + A ! fun() -> + mnesia:write({Tab, a, 11}), + mnesia:write({Tab, b, 22}), + mnesia:write({Tab, c, 1}), + mnesia:write({Tab, d, 2}), + mnesia:write({Tab, e, 3}), + lists:sort(mnesia:all_keys(Tab)) + end, + ?match_receive({A, [a, b, c, d, e]}); + outside_trans -> + ignore + end, + + RecA = {Tab, a, 1}, + PatA = {Tab, '$1', 1}, + RecB = {Tab, b, 3}, + PatB = {Tab, '$1', 3}, + RecB2 = {Tab, b, 2}, + PatB2 = {Tab, '$1', 2}, + ?match([], mnesia:dirty_read({Tab, a})), + ?match([], mnesia:dirty_read({Tab, b})), + ?match([], mnesia:dirty_match_object(PatA)), + ?match([], mnesia:dirty_match_object(PatB)), + ?match([], mnesia:dirty_match_object(PatB2)), + ?match([], mnesia:dirty_index_read(Tab, 1, ValPos)), + ?match([], mnesia:dirty_index_read(Tab, 3, ValPos)), + ?match([], mnesia:dirty_index_match_object(PatA, ValPos)), + ?match([], mnesia:dirty_index_match_object(PatB, ValPos)), + ?match([], mnesia:dirty_index_match_object(PatB2, ValPos)), + ?match('$end_of_table', mnesia:dirty_first(Tab)), + + %% dirty_write + A ! fun() -> mnesia:dirty_write(RecA) end, + ?match_receive({A, ok}), + ?match([RecA], mnesia:dirty_read({Tab, a})), + ?match([RecA], mnesia:dirty_match_object(PatA)), + ?match(a, mnesia:dirty_first(Tab)), + ?match([RecA], mnesia:dirty_index_read(Tab, 1, ValPos)), + ?match([RecA], mnesia:dirty_index_match_object(PatA, ValPos)), + ?match('$end_of_table', mnesia:dirty_next(Tab, a)), + + %% dirty_create + A ! fun() -> mnesia:dirty_write(RecB) end, + ?match_receive({A, ok}), + ?match([RecB], mnesia:dirty_read({Tab, b})), + ?match([RecB], mnesia:dirty_match_object(PatB)), + ?match([RecB], mnesia:dirty_index_read(Tab, 3, ValPos)), + ?match([RecB], mnesia:dirty_index_match_object(PatB, ValPos)), + ?match('$end_of_table', + mnesia:dirty_next(Tab, mnesia:dirty_next(Tab, mnesia:dirty_first(Tab)))), + + %% dirty_update_counter + A ! fun() -> mnesia:dirty_update_counter({Tab, b}, -1) end, + ?match_receive({A, _}), + ?match([RecB2], mnesia:dirty_read({Tab, b})), + ?match([], mnesia:dirty_match_object(PatB)), + ?match([RecB2], mnesia:dirty_match_object(PatB2)), + ?match([RecB2], mnesia:dirty_index_read(Tab, 2, ValPos)), + ?match([], mnesia:dirty_index_match_object(PatB, ValPos)), + ?match([RecB2], mnesia:dirty_index_match_object(PatB2, ValPos)), + ?match('$end_of_table', + mnesia:dirty_next(Tab, mnesia:dirty_next(Tab, mnesia:dirty_first(Tab)))), + + %% dirty_delete + A ! fun() -> mnesia:dirty_delete({Tab, b}) end, + ?match_receive({A, ok}), + ?match([], mnesia:dirty_read({Tab, b})), + ?match([], mnesia:dirty_match_object(PatB2)), + ?match([], mnesia:dirty_index_read(Tab, 3, ValPos)), + ?match([], mnesia:dirty_index_match_object(PatB2, ValPos)), + ?match(a, mnesia:dirty_first(Tab)), + ?match('$end_of_table', mnesia:dirty_next(Tab, a)), + + %% dirty_delete_object + ?match([RecA], mnesia:dirty_match_object(PatA)), + A ! fun() -> mnesia:dirty_delete_object(RecA) end, + ?match_receive({A, ok}), + ?match([], mnesia:dirty_read({Tab, a})), + ?match([], mnesia:dirty_match_object(PatA)), + ?match([], mnesia:dirty_index_read(Tab, 1, ValPos)), + ?match([], mnesia:dirty_index_match_object(PatA, ValPos)), + ?match('$end_of_table', mnesia:dirty_first(Tab)), + + case Mode of + inside_trans -> + A ! end_trans, + ?match_receive({A, {atomic, end_trans}}); + outside_trans -> + ignore + end, + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +trans_update_invisibible_outside_trans(doc) -> + ["Updates in a transaction are invisible outside the transaction"]; +trans_update_invisibible_outside_trans(suite) -> []; +trans_update_invisibible_outside_trans(Config) when is_list(Config) -> + [Node1] = ?acquire_nodes(1, Config), + Tab = trans_update_invisibible_outside_trans, + + ?match({atomic, ok}, mnesia:create_table([{name, Tab}, + {ram_copies, [Node1]}])), + ValPos = 3, + RecA = {Tab, a, 1}, + PatA = {Tab, '$1', 1}, + RecB = {Tab, b, 3}, + PatB = {Tab, '$1', 3}, + ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)), + + Verify = + fun() -> + ?match([], mnesia:dirty_read({Tab, a})), + ?match([], mnesia:dirty_read({Tab, b})), + ?match([], mnesia:dirty_match_object(PatA)), + ?match([], mnesia:dirty_match_object(PatB)), + ?match([], mnesia:dirty_index_read(Tab, 1, ValPos)), + ?match([], mnesia:dirty_index_read(Tab, 3, ValPos)), + ?match([], mnesia:dirty_index_match_object(PatA, ValPos)), + ?match([], mnesia:dirty_index_match_object(PatB, ValPos)), + ?match('$end_of_table', mnesia:dirty_first(Tab)) + end, + + Fun = fun() -> + ?match(ok, mnesia:write(RecA)), + Verify(), + + ?match(ok, mnesia:write(RecB)), + Verify(), + + ?match(ok, mnesia:delete({Tab, b})), + Verify(), + + ?match([RecA], mnesia:match_object(PatA)), + Verify(), + + ?match(ok, mnesia:delete_object(RecA)), + Verify(), + ok + end, + ?match({atomic, ok}, mnesia:transaction(Fun)), + Verify(), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +trans_update_visible_inside_trans(doc) -> + ["Updates in a transaction are visible in the same transaction"]; +trans_update_visible_inside_trans(suite) -> []; +trans_update_visible_inside_trans(Config) when is_list(Config) -> + [Node1] = ?acquire_nodes(1, Config), + Tab = trans_update_visible_inside_trans, + + ?match({atomic, ok}, mnesia:create_table([{name, Tab}, + {ram_copies, [Node1]}])), + ValPos = 3, + RecA = {Tab, a, 1}, + PatA = {Tab, '$1', 1}, + RecB = {Tab, b, 3}, + PatB = {Tab, '$1', 3}, + ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)), + + Fun = fun() -> + %% write + ?match(ok, mnesia:write(RecA)), + ?match([RecA], mnesia:read({Tab, a})), + ?match([RecA], mnesia:wread({Tab, a})), + ?match([RecA], mnesia:match_object(PatA)), + ?match([a], mnesia:all_keys(Tab)), + ?match([RecA], mnesia:index_match_object(PatA, ValPos)), + ?match([RecA], mnesia:index_read(Tab, 1, ValPos)), + + %% create + ?match(ok, mnesia:write(RecB)), + ?match([RecB], mnesia:read({Tab, b})), + ?match([RecB], mnesia:wread({Tab, b})), + ?match([RecB], mnesia:match_object(PatB)), + ?match([RecB], mnesia:index_match_object(PatB, ValPos)), + ?match([RecB], mnesia:index_read(Tab, 3, ValPos)), + + %% delete + ?match(ok, mnesia:delete({Tab, b})), + ?match([], mnesia:read({Tab, b})), + ?match([], mnesia:wread({Tab, b})), + ?match([], mnesia:match_object(PatB)), + ?match([a], mnesia:all_keys(Tab)), + ?match([], mnesia:index_match_object(PatB, ValPos)), + ?match([], mnesia:index_read(Tab, 2, ValPos)), + ?match([], mnesia:index_read(Tab, 3, ValPos)), + + %% delete_object + ?match(ok, mnesia:delete_object(RecA)), + ?match([], mnesia:read({Tab, a})), + ?match([], mnesia:wread({Tab, a})), + ?match([], mnesia:match_object(PatA)), + ?match([], mnesia:all_keys(Tab)), + ?match([], mnesia:index_match_object(PatA, ValPos)), + ?match([], mnesia:index_read(Tab, 2, ValPos)), + ?match([], mnesia:index_read(Tab, 3, ValPos)), + ok + end, + ?match({atomic, ok}, mnesia:transaction(Fun)), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +write_shadows(doc) -> + ["Tests whether the shadow shows the correct object when", + "writing to the table"]; +write_shadows(suite) -> []; +write_shadows(Config) when is_list(Config) -> + [Node1] = ?acquire_nodes(1, Config), + Tab = write_shadows, + + ?match({atomic, ok}, mnesia:create_table([{name, Tab}, + {ram_copies, [Node1]}, + {type, set}])), + ValPos = 3, + RecA1 = {Tab, a, 1}, + PatA1 = {Tab, '$1', 1}, + RecA2 = {Tab, a, 2}, + PatA2 = {Tab, '$1', 2}, + + + ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)), + + Fun1 = fun() -> + ?match(ok, mnesia:write(RecA1)), + ok + end, + + ?match({atomic, ok}, mnesia:transaction(Fun1)), + + Fun2 = fun() -> + %% write shadow old write - is the confirmed value visable + %% in the shadow ? + ?match([RecA1], mnesia:read({Tab, a})), + ?match([RecA1], mnesia:wread({Tab, a})), + ?match([RecA1], mnesia:match_object(PatA1)), + ?match([a], mnesia:all_keys(Tab)), + ?match([RecA1], mnesia:index_match_object(PatA1, ValPos)), + ?match([RecA1], mnesia:index_read(Tab, 1, ValPos)), + + %% write shadow new write - is a new value visable instead + %% of the old value ? + ?match(ok, mnesia:write(RecA2)), + + ?match([RecA2], mnesia:read({Tab, a})), + ?match([RecA2], mnesia:wread({Tab, a})), + ?match([RecA2], mnesia:match_object(PatA2)), %% delete shadow old but not new write - is the new value visable + + ?match([a], mnesia:all_keys(Tab)), + ?match([RecA2], mnesia:index_match_object(PatA2, ValPos)), + ?match([RecA2], mnesia:index_read(Tab, 2, ValPos)), + ok + + end, + ?match({atomic, ok}, mnesia:transaction(Fun2)), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +delete_shadows(doc) -> + ["Test whether the shadow shows the correct object when deleting objects"]; +delete_shadows(suite) -> []; +delete_shadows(Config) when is_list(Config) -> + [Node1] = ?acquire_nodes(1, Config), + Tab = delete_shadows, + + ?match({atomic, ok}, mnesia:create_table([{name, Tab}, + {ram_copies, [Node1]}, + {type, set}])), + ValPos = 3, + OidA = {Tab, a}, + RecA1 = {Tab, a, 1}, + PatA1 = {Tab, '$1', 1}, + RecA2 = {Tab, a, 2}, + PatA2 = {Tab, '$1', 2}, + + + ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)), + + Fun1 = fun() -> + ?match(ok, mnesia:write(RecA1)), + ok + end, + + ?match({atomic, ok}, mnesia:transaction(Fun1)), + + Fun2 = fun() -> + + + %% delete shadow old write - is the confirmed value invisible + %% when deleted in the transaction ? + ?match(ok, mnesia:delete(OidA)), + + ?match([], mnesia:read({Tab, a})), + ?match([], mnesia:wread({Tab, a})), + ?match([], mnesia:match_object(PatA1)), + ?match([], mnesia:all_keys(Tab)), + ?match([], mnesia:index_match_object(PatA1, ValPos)), + ?match([], mnesia:index_read(Tab, 1, ValPos)), + + %% delete shadow old but not new write - is the new value visable + %% when the old one was deleted ? + ?match(ok, mnesia:write(RecA2)), + + ?match([RecA2], mnesia:read({Tab, a})), + ?match([RecA2], mnesia:wread({Tab, a})), + ?match([RecA2], mnesia:match_object(PatA2)), + ?match([a], mnesia:all_keys(Tab)), + ?match([RecA2], mnesia:index_match_object(PatA2, ValPos)), + ?match([RecA2], mnesia:index_read(Tab, 2, ValPos)), + + %% delete shadow old and new write - is the new value invisable + %% when deleted ? + ?match(ok, mnesia:delete(OidA)), + + ?match([], mnesia:read({Tab, a})), + ?match([], mnesia:wread({Tab, a})), + ?match([], mnesia:match_object(PatA2)), + ?match([], mnesia:all_keys(Tab)), + ?match([], mnesia:index_match_object(PatA2, ValPos)), + ?match([], mnesia:index_read(Tab, 2, ValPos)), + ok + + end, + ?match({atomic, ok}, mnesia:transaction(Fun2)), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +write_delete_shadows_bag(doc) -> + ["Test the visibility of written and deleted objects in an bag type table"]; +write_delete_shadows_bag(suite) -> []; +write_delete_shadows_bag(Config) when is_list(Config) -> + [Node1] = ?acquire_nodes(1, Config), + Tab = write_delete_shadows_bag, + + ?match({atomic, ok}, mnesia:create_table([{name, Tab}, + {ram_copies, [Node1]}, + {type, bag}])), + ValPos = 3, + OidA = {Tab, a}, + + RecA1 = {Tab, a, 1}, + PatA1 = {Tab, '$1', 1}, + + RecA2 = {Tab, a, 2}, + PatA2 = {Tab, '$1', 2}, + + RecA3 = {Tab, a, 3}, + PatA3 = {Tab, '$1', 3}, + + PatA = {Tab, a, '_'}, + + + ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)), + + Fun1 = fun() -> + ?match(ok, mnesia:write(RecA1)), + ?match(ok, mnesia:write(RecA2)), + ok + end, + + ?match({atomic, ok}, mnesia:transaction(Fun1)), + + Fun2 = fun() -> + %% delete shadow old write - is the confirmed value invisible + %% when deleted in the transaction ? + ?match(ok, mnesia:delete_object(RecA1)), + + ?match([RecA2], mnesia:read({Tab, a})), + ?match([RecA2], mnesia:wread({Tab, a})), + ?match([RecA2], mnesia:match_object(PatA2)), + ?match([a], mnesia:all_keys(Tab)), + ?match([RecA2], mnesia:index_match_object(PatA2, ValPos)), + ?match([RecA2], mnesia:index_read(Tab, 2, ValPos)), + + ?match(ok, mnesia:delete(OidA)), + + ?match([], mnesia:read({Tab, a})), + ?match([], mnesia:wread({Tab, a})), + ?match([], mnesia:match_object(PatA1)), + ?match([], mnesia:all_keys(Tab)), + ?match([], mnesia:index_match_object(PatA1, ValPos)), + ?match([], mnesia:index_read(Tab, 1, ValPos)), + + %% delete shadow old but not new write - are both new value visable + %% when the old one was deleted ? + ?match(ok, mnesia:write(RecA2)), + ?match(ok, mnesia:write(RecA3)), + + + ?match([RecA2, RecA3], lists:sort(mnesia:read({Tab, a}))), + ?match([RecA2, RecA3], lists:sort(mnesia:wread({Tab, a}))), + ?match([RecA2], mnesia:match_object(PatA2)), + ?match([a], mnesia:all_keys(Tab)), + ?match([RecA2, RecA3], lists:sort(mnesia:match_object(PatA))), + ?match([RecA2], mnesia:index_match_object(PatA2, ValPos)), + ?match([RecA3], mnesia:index_match_object(PatA3, ValPos)), + ?match([RecA2], mnesia:index_read(Tab, 2, ValPos)), + + %% delete shadow old and new write - is the new value invisable + %% when deleted ? + ?match(ok, mnesia:delete(OidA)), + + ?match([], mnesia:read({Tab, a})), + ?match([], mnesia:wread({Tab, a})), + ?match([], mnesia:match_object(PatA2)), + ?match([], mnesia:all_keys(Tab)), + ?match([], mnesia:index_match_object(PatA2, ValPos)), + ?match([], mnesia:index_read(Tab, 2, ValPos)), + ok + end, + ?match({atomic, ok}, mnesia:transaction(Fun2)), + ok. + +write_delete_shadows_bag2(doc) -> + ["Test the visibility of written and deleted objects in an bag type table " + "and verifies the results"]; +write_delete_shadows_bag2(suite) -> []; +write_delete_shadows_bag2(Config) when is_list(Config) -> + + [Node1] = ?acquire_nodes(1, Config), + Tab = w_d_s_b, + + ?match({atomic, ok}, mnesia:create_table([{name, Tab}, + {ram_copies, [Node1]}, + {type, bag}])), + Del = fun() -> + R1 = mnesia:read({Tab, 1}), + mnesia:delete({Tab, 1}), + R2 = mnesia:read({Tab, 1}), + mnesia:write({Tab, 1, 1}), + mnesia:write({Tab, 1, 2}), + R3 = mnesia:read({Tab, 1}), + {R1, R2, R3} + end, + DelObj = fun() -> + R1 = mnesia:read({Tab, 2}), + mnesia:delete_object({Tab, 2, 2}), + R2 = mnesia:read({Tab, 2}), + mnesia:write({Tab, 2, 1}), + mnesia:write({Tab, 2, 2}), + R3 = mnesia:read({Tab, 2}), + {R1, R2, R3} + end, + Both1 = [{Tab, 1, 1}, {Tab, 1, 2}], + Both2 = [{Tab, 2, 1}, {Tab, 2, 2}], + ?match({atomic, {[], [], Both1}}, mnesia:transaction(Del)), + ?match({atomic, {Both1, [], Both1}}, mnesia:transaction(Del)), + ?match({atomic, Both1}, mnesia:transaction(fun() -> mnesia:read({Tab, 1}) end)), + ?match({atomic, {[], [], Both2}}, mnesia:transaction(DelObj)), + ?match({atomic, {Both2, [{Tab, 2, 1}], Both2}}, mnesia:transaction(DelObj)), + ?match({atomic, Both2}, mnesia:transaction(fun() -> mnesia:read({Tab, 2}) end)), + ?verify_mnesia([Node1], []). + +shadow_search(doc) -> + ["Verifies that ordered_set tables are ordered, and the order is kept" + "even when table is shadowed by transaction updates"]; +shadow_search(suite) -> []; +shadow_search(Config) when is_list(Config) -> + [Node1] = ?acquire_nodes(1, Config), + Tab1 = ss_oset, + Tab2 = ss_set, + Tab3 = ss_bag, + Tabs = [Tab1,Tab2,Tab3], + RecName = ss, + ?match({atomic, ok}, mnesia:create_table([{name, Tab1}, + {ram_copies, [Node1]}, + {record_name, RecName}, + {type, ordered_set}])), + ?match({atomic, ok}, mnesia:create_table([{name, Tab2}, + {record_name, RecName}, + {ram_copies, [Node1]}, + {type, set}])), + ?match({atomic, ok}, mnesia:create_table([{name, Tab3}, + {record_name, RecName}, + {ram_copies, [Node1]}, + {type, bag}])), + Recs = [{RecName, K, K} || K <- [1,3,5]], + [mnesia:dirty_write(Tab1, R) || R <- Recs], + [mnesia:dirty_write(Tab2, R) || R <- Recs], + [mnesia:dirty_write(Tab3, R) || R <- Recs], + + Match = fun(Tab) -> mnesia:match_object(Tab, {'_','_','_'}, write) end, + Select = fun(Tab) -> mnesia:select(Tab, [{'_', [], ['$_']}]) end, +% Trans = fun(Fun,Args) -> mnesia:transaction(Fun,Args) end, + LoopHelp = fun('$end_of_table',_) -> []; + ({Res,Cont},Fun) -> + Sel = mnesia:select(Cont), + Res ++ Fun(Sel, Fun) + end, + SelLoop = fun(Table) -> + Sel = mnesia:select(Table, [{'_', [], ['$_']}], 1, read), + LoopHelp(Sel,LoopHelp) + end, + + R1 = {RecName, 2, 2}, R2 = {RecName, 4, 4}, + R3 = {RecName, 2, 3}, R4 = {RecName, 3, 1}, + R5 = {RecName, 104, 104}, + W1 = fun(Tab,Search) -> mnesia:write(Tab,R1,write), + mnesia:write(Tab,R2,write), + Search(Tab) + end, + S1 = lists:sort([R1,R2|Recs]), + ?match({atomic,S1}, mnesia:transaction(W1, [Tab1,Select])), + ?match({atomic,S1}, mnesia:transaction(W1, [Tab1,Match])), + ?match({atomic,S1}, mnesia:transaction(W1, [Tab1,SelLoop])), + ?match({atomic,S1}, sort_res(mnesia:transaction(W1, [Tab2,Select]))), + ?match({atomic,S1}, sort_res(mnesia:transaction(W1, [Tab2,SelLoop]))), + ?match({atomic,S1}, sort_res(mnesia:transaction(W1, [Tab2,Match]))), + ?match({atomic,S1}, sort_res(mnesia:transaction(W1, [Tab3,Select]))), + ?match({atomic,S1}, sort_res(mnesia:transaction(W1, [Tab3,SelLoop]))), + ?match({atomic,S1}, sort_res(mnesia:transaction(W1, [Tab3,Match]))), + [mnesia:dirty_delete_object(Tab,R) || R <- [R1,R2], Tab <- Tabs], + + W2 = fun(Tab,Search) -> + mnesia:write(Tab,R3,write), + mnesia:write(Tab,R1,write), + Search(Tab) + end, + S2 = lists:sort([R1|Recs]), + S2Bag = lists:sort([R1,R3|Recs]), + ?match({atomic,S2}, mnesia:transaction(W2, [Tab1,Select])), + ?match({atomic,S2}, mnesia:transaction(W2, [Tab1,SelLoop])), + ?match({atomic,S2}, mnesia:transaction(W2, [Tab1,Match])), + ?match({atomic,S2}, sort_res(mnesia:transaction(W2, [Tab2,Select]))), + ?match({atomic,S2}, sort_res(mnesia:transaction(W2, [Tab2,SelLoop]))), + ?match({atomic,S2}, sort_res(mnesia:transaction(W2, [Tab2,Match]))), + ?match({atomic,S2Bag}, sort_res(mnesia:transaction(W2, [Tab3,Select]))), + ?match({atomic,S2Bag}, sort_res(mnesia:transaction(W2, [Tab3,SelLoop]))), + ?match({atomic,S2Bag}, sort_res(mnesia:transaction(W2, [Tab3,Match]))), +%% [mnesia:dirty_delete_object(Tab,R) || R <- [R1,R3], Tab <- Tabs], + + W3 = fun(Tab,Search) -> + mnesia:write(Tab,R4,write), + mnesia:delete(Tab,element(2,R1),write), + Search(Tab) + end, + S3Bag = lists:sort([R4|lists:delete(R1,Recs)]), + S3 = lists:delete({RecName,3,3},S3Bag), + ?match({atomic,S3}, mnesia:transaction(W3, [Tab1,Select])), + ?match({atomic,S3}, mnesia:transaction(W3, [Tab1,SelLoop])), + ?match({atomic,S3}, mnesia:transaction(W3, [Tab1,Match])), + ?match({atomic,S3}, sort_res(mnesia:transaction(W3, [Tab2,SelLoop]))), + ?match({atomic,S3}, sort_res(mnesia:transaction(W3, [Tab2,Select]))), + ?match({atomic,S3}, sort_res(mnesia:transaction(W3, [Tab2,Match]))), + ?match({atomic,S3Bag}, sort_res(mnesia:transaction(W3, [Tab3,Select]))), + ?match({atomic,S3Bag}, sort_res(mnesia:transaction(W3, [Tab3,SelLoop]))), + ?match({atomic,S3Bag}, sort_res(mnesia:transaction(W3, [Tab3,Match]))), + + W4 = fun(Tab,Search) -> + mnesia:delete(Tab,-1,write), + mnesia:delete(Tab,4 ,write), + mnesia:delete(Tab,17,write), + mnesia:delete_object(Tab,{RecName, -1, x},write), + mnesia:delete_object(Tab,{RecName, 4, x},write), + mnesia:delete_object(Tab,{RecName, 42, x},write), + mnesia:delete_object(Tab,R2,write), + mnesia:write(Tab, R5, write), + Search(Tab) + end, + S4Bag = lists:sort([R5|S3Bag]), + S4 = lists:sort([R5|S3]), + ?match({atomic,S4}, mnesia:transaction(W4, [Tab1,Select])), + ?match({atomic,S4}, mnesia:transaction(W4, [Tab1,SelLoop])), + ?match({atomic,S4}, mnesia:transaction(W4, [Tab1,Match])), + ?match({atomic,S4}, sort_res(mnesia:transaction(W4, [Tab2,Select]))), + ?match({atomic,S4}, sort_res(mnesia:transaction(W4, [Tab2,SelLoop]))), + ?match({atomic,S4}, sort_res(mnesia:transaction(W4, [Tab2,Match]))), + ?match({atomic,S4Bag}, sort_res(mnesia:transaction(W4, [Tab3,Select]))), + ?match({atomic,S4Bag}, sort_res(mnesia:transaction(W4, [Tab3,SelLoop]))), + ?match({atomic,S4Bag}, sort_res(mnesia:transaction(W4, [Tab3,Match]))), + [mnesia:dirty_delete_object(Tab,R) || R <- [{RecName,3,3},R5], Tab <- Tabs], + + %% hmmm anything more?? + + ?verify_mnesia([Node1], []). + +removed_resources(suite) -> + [rr_kill_copy]; +removed_resources(doc) -> + ["Verify that the locking behave when resources are removed"]. + +rr_kill_copy(suite) -> []; +rr_kill_copy(Config) when is_list(Config) -> + Ns = ?acquire_nodes(3,Config ++ [{tc_timeout, 60000}]), + DeleteMe = fun(_Tab,Where2read) -> + ?match([], mnesia_test_lib:kill_mnesia([Where2read])) + end, + Del = removed_resources(Ns, DeleteMe), + ?verify_mnesia(Ns -- [Del], []). + +removed_resources([_N1,N2,N3], DeleteRes) -> + Tab = del_res, + ?match({atomic, ok}, mnesia:create_table(Tab,[{ram_copies, [N2,N3]}])), + + Init = fun() -> [mnesia:write({Tab,Key,Key}) || Key <- lists:seq(0,99)] end, + ?match([], [Bad || Bad <- mnesia:sync_dirty(Init), Bad /= ok]), + + Where2Read = mnesia:table_info(Tab, where_to_read), + [Keep] = [N2,N3] -- [Where2Read], + Tester = self(), + + Conflict = fun() -> + %% Read a value.. + [{Tab,1,Val}] = mnesia:read({Tab,1}), + case get(restart) of + undefined -> + Tester ! {pid_1, self()}, + %% Wait for sync, the read value have been + %% updated and this function should be restarted. + receive {Tester,sync} -> ok end, + put(restart, restarted); + restarted -> + ok + end, + mnesia:write({Tab,1,Val+10}) + end, + Lucky = fun() -> + [{Tab,1,Val}] = mnesia:read({Tab,1}), + mnesia:write({Tab,1,Val+100}) + end, + + CPid = spawn_link(fun() -> Tester ! {self(), mnesia:transaction(Conflict)} end), + + %% sync first transaction + receive {pid_1, CPid} -> synced end, + + DeleteRes(Tab, Where2Read), + + ?match(Keep, mnesia:table_info(Tab, where_to_read)), + + %% Run the other/Lucky transaction, this should work since + %% it won't grab a lock on the conflicting transactions Where2Read node. + + LPid = spawn_link(Keep, fun() -> Tester ! {self(),mnesia:transaction(Lucky)} end), + ?match_receive({LPid,{atomic,ok}}), + + %% Continue Transaction no 1 + CPid ! {self(), sync}, + + ?match(ok, receive {CPid,{atomic,ok}} -> ok after 2000 -> process_info(self()) end), + + ?match({atomic,[{del_res,1,111}]}, mnesia:transaction(fun() -> mnesia:read({Tab,1}) end)), + Where2Read. + +nasty(suite) -> []; + +nasty(doc) -> + ["Tries to fullfill a rather nasty locking scenario, where we have had " + "bugs, the testcase tries a combination of locks in locker queue"]; + +%% This testcase no longer works as it was intended to show errors when +%% tablelocks was allowed to be placed in the queue though locks existed +%% in the queue with less Tid's. This is no longer allowed and the testcase +%% has been update. + +nasty(Config) -> + ?acquire_nodes(1, Config), + Tab = nasty, + ?match({atomic, ok}, mnesia:create_table(Tab, [])), + Coord = self(), + Write = fun(Key) -> + mnesia:write({Tab, Key, write}), + Coord ! {write, Key, self(), mnesia:get_activity_id()}, + receive + continue -> + ok + end, + Coord ! {done, {write, Key}, self()} + end, + + Update = fun(Key) -> + Coord ! {update, Key, self(), mnesia:get_activity_id()}, + receive + continue -> + ok + end, + mnesia:read({Tab, Key}), + mnesia:write({Tab, Key, update}), + receive + continue -> + ok + end, + + Coord ! {done, {update, Key}, self()} + end, + + TabLock = fun() -> + Coord ! {tablock, Tab, self(), mnesia:get_activity_id()}, + receive + continue -> + ok + end, + mnesia:lock({table, Tab}, write), + Coord ! {done, {tablock, Tab}, self()} + end, + + Up = spawn_link(mnesia, transaction, [Update, [0]]), + ?match_receive({update, 0, Up, _Tid}), + TL = spawn_link(mnesia, transaction, [TabLock]), + ?match_receive({tablock, Tab, _Tl, _Tid}), + W0 = spawn_link(mnesia, transaction, [Write, [0]]), + ?match_receive({write, 0, W0, _Tid}), + W1 = spawn_link(mnesia, transaction, [Write, [1]]), + ?match_receive({write, 1, W1, _Tid}), + + %% Nothing should be in msg queue! + ?match(timeout, receive A -> A after 1000 -> timeout end), + Up ! continue, %% Should be queued + ?match(timeout, receive A -> A after 1000 -> timeout end), + TL ! continue, %% Should be restarted +% ?match({tablock, _, _, _}, receive A -> A after 1000 -> timeout end), + ?match(timeout, receive A -> A after 1000 -> timeout end), + + LQ1 = mnesia_locker:get_lock_queue(), + ?match({2, _}, {length(LQ1), LQ1}), + W0 ! continue, % Up should be in queue + ?match_receive({done, {write, 0}, W0}), + ?match_receive({'EXIT', W0, normal}), + + TL ! continue, % Should stay in queue W1 + ?match(timeout, receive A -> A after 1000 -> timeout end), + Up ! continue, % Should stay in queue (TL got higher tid) + ?match(timeout, receive A -> A after 1000 -> timeout end), + + LQ2 = mnesia_locker:get_lock_queue(), + ?match({2, _}, {length(LQ2), LQ2}), + + W1 ! continue, + ?match_receive({done, {write, 1}, W1}), + get_exit(W1), + get_exit(TL), + ?match_receive({done, {tablock,Tab}, TL}), + get_exit(Up), + ?match_receive({done, {update, 0}, Up}), + + ok. + +get_exit(Pid) -> + receive + {'EXIT', Pid, normal} -> + ok + after 10000 -> + ?error("Timeout EXIT ~p~n", [Pid]) + end. + +iteration(doc) -> + ["Verify that the updates before/during iteration are visable " + "and that the order is preserved for ordered_set tables"]; +iteration(suite) -> + [foldl,first_next]. + +foldl(doc) -> + [""]; +foldl(suite) -> + []; +foldl(Config) when is_list(Config) -> + Nodes = [_,N2] = ?acquire_nodes(2, Config), + Tab1 = foldl_local, + Tab2 = foldl_remote, + Tab3 = foldl_ordered, + Tab11 = foldr_local, + Tab21 = foldr_remote, + Tab31 = foldr_ordered, + ?match({atomic, ok}, mnesia:create_table(Tab1, [{ram_copies, Nodes}])), + ?match({atomic, ok}, mnesia:create_table(Tab2, [{ram_copies, [N2]}, {type, bag}])), + ?match({atomic, ok}, mnesia:create_table(Tab3, [{ram_copies, Nodes}, + {type, ordered_set}])), + ?match({atomic, ok}, mnesia:create_table(Tab11, [{ram_copies, Nodes}])), + ?match({atomic, ok}, mnesia:create_table(Tab21, [{ram_copies, [N2]}, {type, bag}])), + ?match({atomic, ok}, mnesia:create_table(Tab31, [{ram_copies, Nodes}, + {type, ordered_set}])), + + + Tab1Els = [{Tab1, N, N} || N <- lists:seq(1, 10)], + Tab2Els = [{Tab2, 1, 2} | [{Tab2, N, N} || N <- lists:seq(1, 10)]], + Tab3Els = [{Tab3, N, N} || N <- lists:seq(1, 10)], + Tab11Els = [{Tab11, N, N} || N <- lists:seq(1, 10)], + Tab21Els = [{Tab21, 1, 2} | [{Tab21, N, N} || N <- lists:seq(1, 10)]], + Tab31Els = [{Tab31, N, N} || N <- lists:seq(1, 10)], + + [mnesia:sync_dirty(fun() -> mnesia:write(E) end) || E <- Tab1Els], + [mnesia:sync_dirty(fun() -> mnesia:write(E) end) || E <- Tab2Els], + [mnesia:sync_dirty(fun() -> mnesia:write(E) end) || E <- Tab3Els], + [mnesia:sync_dirty(fun() -> mnesia:write(E) end) || E <- Tab11Els], + [mnesia:sync_dirty(fun() -> mnesia:write(E) end) || E <- Tab21Els], + [mnesia:sync_dirty(fun() -> mnesia:write(E) end) || E <- Tab31Els], + + Get = fun(E, A) -> [E | A] end, + + %% Before + AddB = fun(Tab, Func) -> + mnesia:write({Tab, 0, 0}), + mnesia:write({Tab, 1, 0}), + mnesia:write({Tab, 11, 0}), + mnesia:Func(Get, [], Tab) + end, + AddT1 = [{Tab1, 0, 0}, {Tab1, 1, 0}] ++ tl(Tab1Els) ++ [{Tab1, 11, 0}], + AddT2 = lists:sort([{Tab2, 0, 0}, {Tab2, 1, 0}] ++ Tab2Els ++ [{Tab2, 11, 0}]), + AddT3 = [{Tab3, 0, 0}, {Tab3, 1, 0}] ++ tl(Tab3Els) ++ [{Tab3, 11, 0}], + AddT11 = [{Tab11, 0, 0}, {Tab11, 1, 0}] ++ tl(Tab11Els) ++ [{Tab11, 11, 0}], + AddT21 = lists:sort([{Tab21, 0, 0}, {Tab21, 1, 0}] ++ Tab21Els ++ [{Tab21, 11, 0}]), + AddT31 = [{Tab31, 0, 0}, {Tab31, 1, 0}] ++ tl(Tab31Els) ++ [{Tab31, 11, 0}], + + ?match({atomic, AddT1}, sort_res(mnesia:transaction(AddB, [Tab1, foldl]))), + ?match({atomic, AddT2}, sort_res(mnesia:transaction(AddB, [Tab2, foldl]))), + ?match({atomic, AddT3}, rev_res(mnesia:transaction(AddB, [Tab3, foldl]))), + ?match({atomic, AddT11}, sort_res(mnesia:transaction(AddB, [Tab11, foldr]))), + ?match({atomic, AddT21}, sort_res(mnesia:transaction(AddB, [Tab21, foldr]))), + ?match({atomic, AddT31}, mnesia:transaction(AddB, [Tab31, foldr])), + + ?match({atomic, ok}, mnesia:create_table(copy, [{ram_copies, [N2]}, + {record_name, Tab1}])), + CopyRec = fun(NewRec, Acc) -> + %% OTP-5495 + W = fun() -> mnesia:write(copy, NewRec, write), [NewRec| Acc] end, + {atomic,Res} = sort_res(mnesia:transaction(W)), + Res + end, + Copy = fun() -> + AddT1 = mnesia:foldl(CopyRec, [], Tab1), + AddT1 = sort_res(mnesia:foldl(Get, [], copy)) + end, + ?match({atomic, AddT1}, sort_res(mnesia:transaction(Copy))), + + Del = fun(E, A) -> mnesia:delete_object(E), [E|A] end, + DelD = fun(Tab) -> + mnesia:write({Tab, 12, 12}), + mnesia:delete({Tab, 0}), + mnesia:foldr(Del, [], Tab), + mnesia:foldl(Get, [], Tab) + end, + ?match({atomic, []}, sort_res(mnesia:transaction(DelD, [Tab1]))), + ?match({atomic, []}, sort_res(mnesia:transaction(DelD, [Tab2]))), + ?match({atomic, []}, rev_res(mnesia:transaction(DelD, [Tab3]))), + + ListWrite = fun(Tab) -> %% OTP-3893 + mnesia:write({Tab, [12], 12}), + mnesia:foldr(Get, [], Tab) + end, + ?match({atomic, [{Tab1, [12], 12}]}, sort_res(mnesia:transaction(ListWrite, [Tab1]))), + ?match({atomic, [{Tab2, [12], 12}]}, sort_res(mnesia:transaction(ListWrite, [Tab2]))), + ?match({atomic, [{Tab3, [12], 12}]}, rev_res(mnesia:transaction(ListWrite, [Tab3]))), + + ?verify_mnesia(Nodes, []). + +sort_res({atomic, List}) when is_list(List) -> + {atomic, lists:sort(List)}; +sort_res(Else) when is_list(Else) -> + lists:sort(Else); +sort_res(Else) -> + Else. + +rev_res({atomic, List}) -> + {atomic, lists:reverse(List)}; +rev_res(Else) -> + Else. + + +first_next(doc) -> [""]; +first_next(suite) -> []; +first_next(Config) when is_list(Config) -> + Nodes = [_,N2] = ?acquire_nodes(2, Config), + Tab1 = local, + Tab2 = remote, + Tab3 = ordered, + Tab4 = bag, + Tabs = [Tab1,Tab2,Tab3,Tab4], + + ?match({atomic, ok}, mnesia:create_table(Tab1, [{ram_copies, Nodes}])), + ?match({atomic, ok}, mnesia:create_table(Tab2, [{ram_copies, [N2]}])), + ?match({atomic, ok}, mnesia:create_table(Tab3, [{ram_copies, Nodes}, + {type, ordered_set}])), + ?match({atomic, ok}, mnesia:create_table(Tab4, [{ram_copies, Nodes}, + {type, bag}])), + + %% Some Helpers + Trans = fun(Fun) -> mnesia:transaction(Fun) end, + Continue = fun(first) -> next; + (last) -> prev + end, + LoopHelp = fun('$end_of_table',_,_,_Fun) -> []; + (Key,Tab,Op,Fun) -> + Next = mnesia:Op(Tab,Key), + [Next |Fun(Next,Tab,Op,Fun)] + end, + Loop = fun(Tab,Start) -> + First = mnesia:Start(Tab), + Res = [First|LoopHelp(First,Tab,Continue(Start),LoopHelp)], + case mnesia:table_info(Tab, type) of + ordered_set when Start == first -> Res; + ordered_set -> + {L1,L2} = lists:split(length(Res)-1,Res), + lists:reverse(L1) ++ L2; + _ -> lists:sort(Res) + end + end, + + %% Verify empty tables + [?match({atomic, ['$end_of_table']}, + Trans(fun() -> Loop(Tab,first) end)) + || Tab <- Tabs], + [?match({atomic, ['$end_of_table']}, + Trans(fun() -> Loop(Tab,last) end)) + || Tab <- Tabs], + %% Verify that trans write is visible inside trans + [?match({atomic, [0,10,'$end_of_table']}, + Trans(fun() -> + mnesia:write({Tab,0,0}), + mnesia:write({Tab,10,10}), + Loop(Tab,first) end)) + || Tab <- Tabs], + [?match({atomic, ['$end_of_table']}, + Trans(fun() -> + mnesia:delete({Tab,0}), + mnesia:delete({Tab,10}), + Loop(Tab,first) end)) + || Tab <- Tabs], + + [?match({atomic, [0,10,'$end_of_table']}, + Trans(fun() -> + mnesia:write({Tab,0,0}), + mnesia:write({Tab,10,10}), + Loop(Tab,last) end)) + || Tab <- Tabs], + [?match({atomic, ['$end_of_table']}, + Trans(fun() -> + mnesia:delete({Tab,0}), + mnesia:delete({Tab,10}), + Loop(Tab,last) end)) + || Tab <- Tabs], + + Tab1Els = [{Tab1, N, N} || N <- lists:seq(1, 5)], + Tab2Els = [{Tab2, N, N} || N <- lists:seq(1, 5)], + Tab3Els = [{Tab3, N, N} || N <- lists:seq(1, 5)], + Tab4Els = [{Tab4, 1, 2} | [{Tab4, N, N} || N <- lists:seq(1, 5)]], + + [mnesia:sync_dirty(fun() -> mnesia:write(E) end) || E <- Tab1Els], + [mnesia:sync_dirty(fun() -> mnesia:write(E) end) || E <- Tab2Els], + [mnesia:sync_dirty(fun() -> mnesia:write(E) end) || E <- Tab3Els], + [mnesia:sync_dirty(fun() -> mnesia:write(E) end) || E <- Tab4Els], + Keys = lists:sort(mnesia:dirty_all_keys(Tab1)), + R1 = Keys++ ['$end_of_table'], + [?match({atomic, R1}, Trans(fun() -> Loop(Tab,first) end)) + || Tab <- Tabs], + + [?match({atomic, R1}, Trans(fun() -> Loop(Tab,last) end)) + || Tab <- Tabs], + R2 = R1 -- [3], + + [?match({atomic, R2}, Trans(fun() -> mnesia:delete({Tab,3}),Loop(Tab,first) end)) + || Tab <- Tabs], + [?match({atomic, R1}, Trans(fun() -> mnesia:write({Tab,3,3}),Loop(Tab,first) end)) + || Tab <- Tabs], + [?match({atomic, R2}, Trans(fun() -> mnesia:delete({Tab,3}),Loop(Tab,last) end)) + || Tab <- Tabs], + [?match({atomic, R1}, Trans(fun() -> mnesia:write({Tab,3,3}),Loop(Tab,last) end)) + || Tab <- Tabs], + [?match({atomic, R1}, Trans(fun() -> mnesia:write({Tab,4,19}),Loop(Tab,first) end)) + || Tab <- Tabs], + [?match({atomic, R1}, Trans(fun() -> mnesia:write({Tab,4,4}),Loop(Tab,last) end)) + || Tab <- Tabs], + + ?verify_mnesia(Nodes, []). + + +snmp_shadows(doc) -> [""]; +snmp_shadows(suite) -> []; +snmp_shadows(Config) when is_list(Config) -> + Nodes = ?acquire_nodes(1, Config), + Tab = snmp_shadows, + io:format("With fixstring~n", []), + ?match({atomic, ok}, mnesia:create_table(Tab,[{snmp,[{key,{fix_string,integer}}]}])), + snmp_shadows_test(Tab), + ?match({atomic, ok}, mnesia:delete_table(Tab)), + io:format("Without fixstring~n", []), + ?match({atomic, ok}, mnesia:create_table(Tab,[{snmp,[{key,{string,integer}}]}])), + snmp_shadows_test(Tab), + ?verify_mnesia(Nodes, []). + +snmp_shadows_test(Tab) -> + [mnesia:dirty_write({Tab, {"string", N}, {N, init}}) || N <- lists:seq(2,8,2)], + + CheckOrder = fun(A={_,_,{_,_,State}}, Prev) -> + ?match({true, A, Prev}, {Prev < A, A, Prev}), + {State,A} + end, + R1 = mnesia:sync_dirty(fun() -> loop_snmp(Tab, []) end), + lists:mapfoldl(CheckOrder, {[],foo,foo}, R1), + R2 = mnesia:transaction(fun() -> loop_snmp(Tab, []) end), + ?match({atomic, R1}, R2), + + Shadow = fun() -> + ok = mnesia:write({Tab, {"string",1}, {1,update}}), + ok = mnesia:write({Tab, {"string",4}, {4,update}}), + ok = mnesia:write({Tab, {"string",6}, {6,update}}), + ok = mnesia:delete({Tab, {"string",6}}), + ok = mnesia:write({Tab, {"string",9}, {9,update}}), + ok = mnesia:write({Tab, {"string",3}, {3,update}}), + ok = mnesia:write({Tab, {"string",5}, {5,update}}), + [Row5] = mnesia:read({Tab, {"string",5}}), + ok = mnesia:delete_object(Row5), + loop_snmp(Tab, []) + end, + R3 = mnesia:sync_dirty(Shadow), + {L3,_} = lists:mapfoldl(CheckOrder, {[],foo,foo}, R3), + ?match([{1,update},{2,init},{3,update},{4,update},{8,init},{9,update}], L3), + ?match({atomic, ok}, mnesia:clear_table(Tab)), + + [mnesia:dirty_write({Tab, {"string", N}, {N, init}}) || N <- lists:seq(2,8,2)], + {atomic, R3} = mnesia:transaction(Shadow), + {L4,_} = lists:mapfoldl(CheckOrder, {[],foo,foo}, R3), + ?match([{1,update},{2,init},{3,update},{4,update},{8,init},{9,update}], L4), + ok. + +loop_snmp(Tab,Prev) -> + case mnesia:snmp_get_next_index(Tab,Prev) of + {ok, SKey} -> + {{ok,Row},_} = {mnesia:snmp_get_row(Tab, SKey),{?LINE,Prev,SKey}}, + {{ok,MKey},_} = {mnesia:snmp_get_mnesia_key(Tab,SKey),{?LINE,Prev,SKey}}, + ?match({[Row],Row,SKey,MKey}, {mnesia:read({Tab,MKey}),Row,SKey,MKey}), + [{SKey, MKey, Row} | loop_snmp(Tab, SKey)]; + endOfTable -> + [] + end. diff --git a/lib/mnesia/test/mnesia_measure_test.erl b/lib/mnesia/test/mnesia_measure_test.erl new file mode 100644 index 0000000000..fbf804dbec --- /dev/null +++ b/lib/mnesia/test/mnesia_measure_test.erl @@ -0,0 +1,203 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(mnesia_measure_test). +-author('[email protected]'). +-compile([export_all]). + +-include("mnesia_test_lib.hrl"). + +init_per_testcase(Func, Conf) -> + mnesia_test_lib:init_per_testcase(Func, Conf). + +fin_per_testcase(Func, Conf) -> + mnesia_test_lib:fin_per_testcase(Func, Conf). + +-define(init(N, Config), + mnesia_test_lib:prepare_test_case([{init_test_case, [mnesia]}, + delete_schema], + N, Config, ?FILE, ?LINE)). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +all(doc) -> + ["Measure various aspects of Mnesia", + "Verify that Mnesia has predictable response times,", + "that the transaction system has fair algoritms,", + "resource consumption, scalabilitym system limits etc.", + "Perform some benchmarks."]; +all(suite) -> + [ + prediction, + consumption, + scalability, + benchmarks + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +prediction(doc) -> + ["The system must have predictable response times.", + "The maintenance of the system should not impact on the", + "availability. Make sure that the response times does not vary too", + "much from the undisturbed normal usage.", + "Verify that deadlocks never occurs."]; +prediction(suite) -> + [ + reader_disturbed_by_node_down, + writer_disturbed_by_node_down, + reader_disturbed_by_node_up, + writer_disturbed_by_node_up, + reader_disturbed_by_schema_ops, + writer_disturbed_by_schema_ops, + reader_disturbed_by_checkpoint, + writer_disturbed_by_checkpoint, + reader_disturbed_by_dump_log, + writer_disturbed_by_dump_log, + reader_disturbed_by_backup, + writer_disturbed_by_backup, + reader_disturbed_by_restore, + writer_disturbed_by_restore, + fairness + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +fairness(doc) -> + ["Verify that the transaction system behaves fair, even under intense", + "stress. Combine different access patterns (transaction profiles)", + "in order to verify that concurrent applications gets a fair share", + "of the database resource. Verify that starvation never may occur."]; +fairness(suite) -> + [ + reader_competing_with_reader, + reader_competing_with_writer, + writer_competing_with_reader, + writer_competing_with_writer + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +consumption(doc) -> + ["Measure the resource consumption and publish the outcome. Make", + "sure that resources are released after failures."]; +consumption(suite) -> + [ + measure_resource_consumption, + determine_resource_leakage + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +scalability(doc) -> + ["Try out where the system limits are. We must at least meet the", + "documented system limits.", + "Redo the performance meters for various configurations and load,", + "especially near system limits."]; +scalability(suite) -> + [ + determine_system_limits, + performance_at_min_config, + performance_at_max_config, + performance_at_full_load, + resource_consumption_at_min_config, + resource_consumption_at_max_config, + resource_consumption_at_full_load + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +benchmarks(doc) -> + ["Measure typical database operations and publish them. Try to", + "verify that new releases of Mnesia always outperforms old", + "releases, or at least that the meters does not get worse."]; +benchmarks(suite) -> + [ + meter, + cost, + dbn_meters, + measure_all_api_functions, + tpcb, + mnemosyne_vs_mnesia_kernel + ]. + +dbn_meters(suite) -> []; +dbn_meters(Config) when is_list(Config) -> + _Nodes = ?init(3, Config), + ?match(ok, mnesia_dbn_meters:start()), + ok. + +tpcb(suite) -> + [ + ram_tpcb, + disc_tpcb, + disc_only_tpcb + ]. + +tpcb(ReplicaType, Config) -> + HarakiriDelay = {tc_timeout, timer:minutes(20)}, + Nodes = ?acquire_nodes(2, Config ++ [HarakiriDelay]), + Args = [{n_branches, 2}, + {n_drivers_per_node, 1}, + {replica_nodes, Nodes}, + {driver_nodes, [hd(Nodes)]}, + {use_running_mnesia, true}, + {use_sticky_locks, true}, + {replica_type, ReplicaType}], + ?match({ok, _}, mnesia_tpcb:start(Args)), + ?verify_mnesia(Nodes, []). + +ram_tpcb(suite) -> []; +ram_tpcb(Config) when is_list(Config) -> + tpcb(ram_copies, Config). + +disc_tpcb(suite) -> []; +disc_tpcb(Config) when is_list(Config) -> + tpcb(disc_copies, Config). + +disc_only_tpcb(suite) -> []; +disc_only_tpcb(Config) when is_list(Config) -> + tpcb(disc_only_copies, Config). + +meter(suite) -> + [ + ram_meter, + disc_meter, + disc_only_meter + ]. + +ram_meter(suite) -> []; +ram_meter(Config) when is_list(Config) -> + HarakiriDelay = [{tc_timeout, timer:minutes(20)}], + Nodes = ?init(3, Config ++ HarakiriDelay), + ?match(ok, mnesia_meter:go(ram_copies, Nodes)). + +disc_meter(suite) -> []; +disc_meter(Config) when is_list(Config) -> + HarakiriDelay = [{tc_timeout, timer:minutes(20)}], + Nodes = ?init(3, Config ++ HarakiriDelay), + ?match(ok, mnesia_meter:go(disc_copies, Nodes)). + +disc_only_meter(suite) -> []; +disc_only_meter(Config) when is_list(Config) -> + HarakiriDelay = [{tc_timeout, timer:minutes(20)}], + Nodes = ?init(3, Config ++ HarakiriDelay), + ?match(ok, mnesia_meter:go(disc_only_copies, Nodes)). + +cost(suite) -> []; +cost(Config) when is_list(Config) -> + Nodes = ?init(3, Config), + ?match(ok, mnesia_cost:go(Nodes)), + file:delete("MNESIA_COST"). diff --git a/lib/mnesia/test/mnesia_meter.erl b/lib/mnesia/test/mnesia_meter.erl new file mode 100644 index 0000000000..68094c4431 --- /dev/null +++ b/lib/mnesia/test/mnesia_meter.erl @@ -0,0 +1,465 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1997-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +%% Getting started: +%% +%% 1 Start one or more distributed Erlang nodes +%% 2a Connect the nodes, e.g. with net_adm:ping/1 +%% 3a Run mnesia_meter:go() +%% 3b Run mnesia_meter:go(ReplicaType) +%% 3c Run mnesia_meter:go(ReplicaType, Nodes) + +-module(mnesia_meter). +-author('[email protected]'). +-export([ + go/0, + go/1, + go/2, + repeat_meter/2 + ]). + +-record(person, {name, %% atomic, unique key + data, %% compound structure + married_to, %% name of partner or undefined + children}). %% list of children + +-record(meter, {desc, init, meter, micros}). + +-record(result, {desc, list}). + +-define(TIMES, 1000). + +go() -> + go(ram_copies). + +go(ReplicaType) -> + go(ReplicaType, [node() | nodes()]). + +go(ReplicaType, Nodes) -> + {ok, FunOverhead} = tc(fun(_) -> {atomic, ok} end, ?TIMES), + Size = size(term_to_binary(#person{})), + io:format("A fun apply costs ~p micro seconds. Record size is ~p bytes.~n", + [FunOverhead, Size]), + Res = go(ReplicaType, Nodes, [], FunOverhead, []), + NewRes = rearrange(Res, []), + DescHeader = lists:flatten(io_lib:format("~w on ~w", [ReplicaType, Nodes])), + ItemHeader = lists:seq(1, length(Nodes)), + Header = #result{desc = DescHeader, list = ItemHeader}, + SepList = ['--------' || _ <- Nodes], + Separator = #result{desc = "", list = SepList}, + display([Separator, Header, Separator | NewRes] ++ [Separator]). + +go(_ReplicaType, [], _Config, _FunOverhead, Acc) -> + Acc; +go(ReplicaType, [H | T], OldNodes, FunOverhead, Acc) -> + Nodes = [H | OldNodes], + Config = [{ReplicaType, Nodes}], + Res = run(Nodes, Config, FunOverhead), + go(ReplicaType, T, Nodes, FunOverhead, [{ReplicaType, Nodes, Res} | Acc]). + +rearrange([{_ReplicaType, _Nodes, Meters} | Tail], Acc) -> + Acc2 = [add_meter(M, Acc) || M <- Meters], + rearrange(Tail, Acc2); +rearrange([], Acc) -> + Acc. + +add_meter(M, Acc) -> + case lists:keysearch(M#meter.desc, #result.desc, Acc) of + {value, R} -> + R#result{list = [M#meter.micros | R#result.list]}; + false -> + #result{desc = M#meter.desc, list = [M#meter.micros]} + end. + +display(Res) -> + MaxDesc = lists:max([length(R#result.desc) || R <- Res]), + Format = lists:concat(["! ~-", MaxDesc, "s"]), + display(Res, Format, MaxDesc). + +display([R | Res], Format, MaxDesc) -> + case R#result.desc of + "" -> + io:format(Format, [lists:duplicate(MaxDesc, "-")]); + Desc -> + io:format(Format, [Desc]) + end, + display_items(R#result.list, R#result.desc), + io:format(" !~n", []), + display(Res, Format, MaxDesc); +display([], _Format, _MaxDesc) -> + ok. + +display_items([_Item | Items], "") -> + io:format(" ! ~s", [lists:duplicate(10, $-)]), + display_items(Items, ""); +display_items([Micros | Items], Desc) -> + io:format(" ! ~10w", [Micros]), + display_items(Items, Desc); +display_items([], _Desc) -> + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +meters() -> + [#meter{desc = "transaction update two records with read and write", + init = fun write_records/2, + meter = fun update_records/1}, + #meter{desc = "transaction update two records with wread and write", + init = fun write_records/2, + meter = fun w_update_records/1}, + #meter{desc = "transaction update two records with read and s_write", + init = fun s_write_records/2, + meter = fun s_update_records/1}, + #meter{desc = "sync_dirty update two records with read and write", + init = fun sync_dirty_write_records/2, + meter = fun sync_dirty_update_records/1}, + #meter{desc = "async_dirty update two records with read and write", + init = fun async_dirty_write_records/2, + meter = fun async_dirty_update_records/1}, + #meter{desc = "plain fun update two records with dirty_read and dirty_write", + init = fun dirty_write_records/2, + meter = fun dirty_update_records/1}, + #meter{desc = "ets update two records with read and write (local only)", + init = fun ets_opt_write_records/2, + meter = fun ets_update_records/1}, + #meter{desc = "plain fun update two records with ets:lookup and ets:insert (local only)", + init = fun bif_opt_write_records/2, + meter = fun bif_update_records/1}, + #meter{desc = "plain fun update two records with dets:lookup and dets:insert (local only)", + init = fun dets_opt_write_records/2, + meter = fun dets_update_records/1}, + + #meter{desc = "transaction write two records with write", + init = fun write_records/2, + meter = fun(X) -> write_records(X, 0-X) end}, + #meter{desc = "transaction write two records with s_write", + init = fun s_write_records/2, + meter = fun(X) -> s_write_records(X, 0-X) end}, + #meter{desc = "sync_dirty write two records with write", + init = fun sync_dirty_write_records/2, + meter = fun(X) -> sync_dirty_write_records(X, 0-X) end}, + #meter{desc = "async_dirty write two records with write", + init = fun async_dirty_write_records/2, + meter = fun(X) -> async_dirty_write_records(X, 0-X) end}, + #meter{desc = "plain fun write two records with dirty_write", + init = fun dirty_write_records/2, + meter = fun(X) -> dirty_write_records(X, 0-X) end}, + #meter{desc = "ets write two records with write (local only)", + init = fun ets_opt_write_records/2, + meter = fun(X) -> ets_write_records(X, 0-X) end}, + #meter{desc = "plain fun write two records with ets:insert (local only)", + init = fun bif_opt_write_records/2, + meter = fun(X) -> bif_write_records(X, 0-X) end}, + #meter{desc = "plain fun write two records with dets:insert (local only)", + init = fun dets_opt_write_records/2, + meter = fun(X) -> dets_write_records(X, 0-X) end}, + + #meter{desc = "transaction read two records with read", + init = fun write_records/2, + meter = fun(X) -> read_records(X, 0-X) end}, + #meter{desc = "sync_dirty read two records with read", + init = fun sync_dirty_write_records/2, + meter = fun(X) -> sync_dirty_read_records(X, 0-X) end}, + #meter{desc = "async_dirty read two records with read", + init = fun async_dirty_write_records/2, + meter = fun(X) -> async_dirty_read_records(X, 0-X) end}, + #meter{desc = "plain fun read two records with dirty_read", + init = fun dirty_write_records/2, + meter = fun(X) -> dirty_read_records(X, 0-X) end}, + #meter{desc = "ets read two records with read", + init = fun ets_opt_write_records/2, + meter = fun(X) -> ets_read_records(X, 0-X) end}, + #meter{desc = "plain fun read two records with ets:lookup", + init = fun bif_opt_write_records/2, + meter = fun(X) -> bif_read_records(X, 0-X) end}, + #meter{desc = "plain fun read two records with dets:lookup", + init = fun dets_opt_write_records/2, + meter = fun(X) -> dets_read_records(X, 0-X) end} + ]. + +update_fun(Name) -> + fun() -> + case mnesia:read({person, Name}) of + [] -> + mnesia:abort(no_such_person); + [Pers] -> + [Partner] = mnesia:read({person, Pers#person.married_to}), + mnesia:write(Pers#person{married_to = undefined}), + mnesia:write(Partner#person{married_to = undefined}) + end + end. + +update_records(Name) -> + mnesia:transaction(update_fun(Name)). + +sync_dirty_update_records(Name) -> + {atomic, mnesia:sync_dirty(update_fun(Name))}. + +async_dirty_update_records(Name) -> + {atomic, mnesia:async_dirty(update_fun(Name))}. + +ets_update_records(Name) -> + {atomic, mnesia:ets(update_fun(Name))}. + +w_update_records(Name) -> + F = fun() -> + case mnesia:wread({person, Name}) of + [] -> + mnesia:abort(no_such_person); + [Pers] -> + [Partner] = mnesia:wread({person, Pers#person.married_to}), + mnesia:write(Pers#person{married_to = undefined}), + mnesia:write(Partner#person{married_to = undefined}) + end + end, + mnesia:transaction(F). + +s_update_records(Name) -> + F = fun() -> + case mnesia:read({person, Name}) of + [] -> + mnesia:abort(no_such_person); + [Pers] -> + [Partner] = mnesia:read({person, Pers#person.married_to}), + mnesia:s_write(Pers#person{married_to = undefined}), + mnesia:s_write(Partner#person{married_to = undefined}) + end + end, + mnesia:transaction(F). + +dirty_update_records(Name) -> + case mnesia:dirty_read({person, Name}) of + [] -> + mnesia:abort(no_such_person); + [Pers] -> + [Partner] = mnesia:dirty_read({person, Pers#person.married_to}), + mnesia:dirty_write(Pers#person{married_to = undefined}), + mnesia:dirty_write(Partner#person{married_to = undefined}) + end, + {atomic, ok}. + +bif_update_records(Name) -> + case ets:lookup(person, Name) of + [] -> + mnesia:abort(no_such_person); + [Pers] -> + [Partner] = ets:lookup(person, Pers#person.married_to), + ets:insert(person, Pers#person{married_to = undefined}), + ets:insert(person, Partner#person{married_to = undefined}) + end, + {atomic, ok}. + +dets_update_records(Name) -> + case dets:lookup(person, Name) of + [] -> + mnesia:abort(no_such_person); + [Pers] -> + [Partner] = dets:lookup(person, Pers#person.married_to), + dets:insert(person, Pers#person{married_to = undefined}), + dets:insert(person, Partner#person{married_to = undefined}) + end, + {atomic, ok}. + +write_records_fun(Pers, Partner) -> + fun() -> + P = #person{children = [ulla, bella]}, + mnesia:write(P#person{name = Pers, married_to = Partner}), + mnesia:write(P#person{name = Partner, married_to = Pers}) + end. + +write_records(Pers, Partner) -> + mnesia:transaction(write_records_fun(Pers, Partner)). + +sync_dirty_write_records(Pers, Partner) -> + {atomic, mnesia:sync_dirty(write_records_fun(Pers, Partner))}. + +async_dirty_write_records(Pers, Partner) -> + {atomic, mnesia:async_dirty(write_records_fun(Pers, Partner))}. + +ets_write_records(Pers, Partner) -> + {atomic, mnesia:ets(write_records_fun(Pers, Partner))}. + +s_write_records(Pers, Partner) -> + F = fun() -> + P = #person{children = [ulla, bella]}, + mnesia:s_write(P#person{name = Pers, married_to = Partner}), + mnesia:s_write(P#person{name = Partner, married_to = Pers}) + end, + mnesia:transaction(F). + +dirty_write_records(Pers, Partner) -> + P = #person{children = [ulla, bella]}, + mnesia:dirty_write(P#person{name = Pers, married_to = Partner}), + mnesia:dirty_write(P#person{name = Partner, married_to = Pers}), + {atomic, ok}. + +ets_opt_write_records(Pers, Partner) -> + case mnesia:table_info(person, where_to_commit) of + [{N, ram_copies}] when N == node() -> + ets_write_records(Pers, Partner); + _ -> + throw(skipped) + end. + +bif_opt_write_records(Pers, Partner) -> + case mnesia:table_info(person, where_to_commit) of + [{N, ram_copies}] when N == node() -> + bif_write_records(Pers, Partner); + _ -> + throw(skipped) + end. + +bif_write_records(Pers, Partner) -> + P = #person{children = [ulla, bella]}, + ets:insert(person, P#person{name = Pers, married_to = Partner}), + ets:insert(person, P#person{name = Partner, married_to = Pers}), + {atomic, ok}. + +dets_opt_write_records(Pers, Partner) -> + case mnesia:table_info(person, where_to_commit) of + [{N, disc_only_copies}] when N == node() -> + dets_write_records(Pers, Partner); + _ -> + throw(skipped) + end. + +dets_write_records(Pers, Partner) -> + P = #person{children = [ulla, bella]}, + dets:insert(person, P#person{name = Pers, married_to = Partner}), + dets:insert(person, P#person{name = Partner, married_to = Pers}), + {atomic, ok}. + +read_records_fun(Pers, Partner) -> + fun() -> + case {mnesia:read({person, Pers}), + mnesia:read({person, Partner})} of + {[_], [_]} -> + ok; + _ -> + mnesia:abort(no_such_person) + end + end. + +read_records(Pers, Partner) -> + mnesia:transaction(read_records_fun(Pers, Partner)). + +sync_dirty_read_records(Pers, Partner) -> + {atomic, mnesia:sync_dirty(read_records_fun(Pers, Partner))}. + +async_dirty_read_records(Pers, Partner) -> + {atomic, mnesia:async_dirty(read_records_fun(Pers, Partner))}. + +ets_read_records(Pers, Partner) -> + {atomic, mnesia:ets(read_records_fun(Pers, Partner))}. + +dirty_read_records(Pers, Partner) -> + case {mnesia:dirty_read({person, Pers}), + mnesia:dirty_read({person, Partner})} of + {[_], [_]} -> + {atomic, ok}; + _ -> + mnesia:abort(no_such_person) + end. + +bif_read_records(Pers, Partner) -> + case {ets:lookup(person, Pers), + ets:lookup(person, Partner)} of + {[_], [_]} -> + {atomic, ok}; + _ -> + mnesia:abort(no_such_person) + end. + +dets_read_records(Pers, Partner) -> + case {dets:lookup(person, Pers), + dets:lookup(person, Partner)} of + {[_], [_]} -> + {atomic, ok}; + _ -> + mnesia:abort(no_such_person) + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +run(Nodes, Config, FunOverhead) -> + Meters = meters(), + io:format("Run ~w meters with table config: ~w~n", [length(Meters), Config]), + rpc:multicall(Nodes, mnesia, lkill, []), + start(Nodes, Config), + Res = [run_meter(Data, Nodes, FunOverhead) || Data <- Meters], + stop(Nodes), + Res. + +run_meter(M, Nodes, FunOverhead) when is_record(M, meter) -> + io:format(".", []), + case catch init_records(M#meter.init, ?TIMES) of + {atomic, ok} -> + rpc:multicall(Nodes, mnesia, dump_log, []), + case tc(M#meter.meter, ?TIMES) of + {ok, Micros} -> + M#meter{micros = lists:max([0, Micros - FunOverhead])}; + {error, Reason} -> + M#meter{micros = Reason} + end; + Res -> + M#meter{micros = Res} + end. + +start(Nodes, Config) -> + mnesia:delete_schema(Nodes), + ok = mnesia:create_schema(Nodes), + Args = [[{dump_log_write_threshold, ?TIMES div 2}, + {dump_log_time_threshold, timer:hours(10)}]], + lists:foreach(fun(Node) -> rpc:call(Node, mnesia, start, Args) end, Nodes), + Attrs = record_info(fields, person), + TabDef = [{attributes, Attrs} | Config], + {atomic, _} = mnesia:create_table(person, TabDef). + +stop(Nodes) -> + rpc:multicall(Nodes, mnesia, stop, []). + +%% Generate some dummy persons +init_records(_Fun, 0) -> + {atomic, ok}; +init_records(Fun, Times) -> + {atomic, ok} = Fun(Times, 0 - Times), + init_records(Fun, Times - 1). + +tc(Fun, Times) -> + case catch timer:tc(?MODULE, repeat_meter, [Fun, Times]) of + {Micros, ok} -> + {ok, Micros div Times}; + {_Micros, {error, Reason}} -> + {error, Reason}; + {'EXIT', Reason} -> + {error, Reason} + end. + +%% The meter must return {atomic, ok} +repeat_meter(Meter, Times) -> + repeat_meter(Meter, {atomic, ok}, Times). + +repeat_meter(_, {atomic, ok}, 0) -> + ok; +repeat_meter(Meter, {atomic, _Result}, Times) when Times > 0 -> + repeat_meter(Meter, Meter(Times), Times - 1); +repeat_meter(_Meter, Reason, _Times) -> + {error, Reason}. + diff --git a/lib/mnesia/test/mnesia_nice_coverage_test.erl b/lib/mnesia/test/mnesia_nice_coverage_test.erl new file mode 100644 index 0000000000..aa9339f6b9 --- /dev/null +++ b/lib/mnesia/test/mnesia_nice_coverage_test.erl @@ -0,0 +1,227 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(mnesia_nice_coverage_test). +-author('[email protected]'). +-compile([export_all]). +-include("mnesia_test_lib.hrl"). + +-record(nice_tab, {key, val}). + +init_per_testcase(Func, Conf) -> + mnesia_test_lib:init_per_testcase(Func, Conf). + +fin_per_testcase(Func, Conf) -> + mnesia_test_lib:fin_per_testcase(Func, Conf). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +all(doc) -> + ["Test nice usage of the entire API", + "Invoke all functions in the API, at least once.", + "Try to verify that all functions exists and that they perform", + "reasonable things when used in the most simple way."]; +all(suite) -> [nice]. + +nice(doc) -> [""]; +nice(suite) -> []; +nice(Config) when is_list(Config) -> + %% The whole test suite is one huge test case for the time beeing + + [Node1, Node2] = Nodes = ?acquire_nodes(2, Config), + Attrs = record_info(fields, nice_tab), + + initialize(Attrs, Node1), + dirty_access(Node1), + success_and_fail(), + index_mgt(), + + adm(Attrs, Node1, Node2), + snmp(Node1, Node2), + backup(Node1), + ?verify_mnesia(Nodes, []). + +initialize(Attrs, Node1) -> + ?match(Version when is_list(Version), mnesia:system_info(version)), + + Schema = [{name, nice_tab}, + {attributes, Attrs}, {ram_copies, [Node1]}], + + ?match({_, _}, mnesia:system_info(schema_version)), + ?match({atomic, ok}, mnesia:create_table(Schema)), + + ?match(ok, mnesia:info()), + ?match(set, mnesia:table_info(nice_tab, type)), + ?match(ok, mnesia:schema()), + ?match(ok, mnesia:schema(nice_tab)), + ok. + +dirty_access(Node1) -> + TwoThree = #nice_tab{key=23, val=23}, + TwoFive = #nice_tab{key=25, val=25}, + ?match([], mnesia:dirty_slot(nice_tab, 0)), + ?match(ok, mnesia:dirty_write(TwoThree)), + ?match([TwoThree], mnesia:dirty_read({nice_tab, 23})), + ?match(ok, mnesia:dirty_write(TwoFive)), + ?match(ok, mnesia:dirty_delete_object(TwoFive)), + + ?match(23, mnesia:dirty_first(nice_tab)), + ?match('$end_of_table', mnesia:dirty_next(nice_tab, 23)), + ?match([TwoThree], mnesia:dirty_match_object(TwoThree)), + ?match(ok, mnesia:dirty_delete({nice_tab, 23})), + + CounterSchema = [{ram_copies, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(nice_counter_tab, CounterSchema)), + TwoFour = {nice_counter_tab, 24, 24}, + ?match(ok, mnesia:dirty_write(TwoFour)), + ?match(34, mnesia:dirty_update_counter({nice_counter_tab, 24}, 10)), + TF = {nice_counter_tab, 24, 34}, + ?match([TF], mnesia:dirty_read({nice_counter_tab, 24})), + ?match(ok, mnesia:dirty_delete({nice_counter_tab, 24})), + ?match(ok, mnesia:dirty_delete_object(TF)), + ok. + +success_and_fail() -> + ?match({atomic, a_good_trans}, mnesia:transaction(fun() ->good_trans()end)), + + BadFun = + fun() -> + Two = #nice_tab{key=2, val=12}, + ?match([Two], mnesia:match_object(#nice_tab{key='$1', val=12})), + ?match([#nice_tab{key=3, val=13}], mnesia:wread({nice_tab, 3})), + ?match(ok, mnesia:delete({nice_tab, 1})), + ?match(ok, mnesia:delete_object(Two)), + mnesia:abort(bad_trans), + ?match(bad, trans) + end, + ?match({aborted, bad_trans}, mnesia:transaction(BadFun)), + ?match(L when is_list(L), mnesia:error_description(no_exists)), + ?match({atomic, ok}, mnesia:transaction(fun(A) -> lock(), A end, [ok])), + ?match({atomic, ok}, mnesia:transaction(fun(A) -> lock(), A end, [ok], 3)), + ok. + +good_trans() -> + ?match([], mnesia:read(nice_tab, 3)), + ?match([], mnesia:read({nice_tab, 3})), + ?match(ok, mnesia:write(#nice_tab{key=14, val=4})), + ?match([14], mnesia:all_keys(nice_tab)), + + Records = [ #nice_tab{key=K, val=K+10} || K <- lists:seq(1, 10) ], + Ok = [ ok || _ <- Records], + ?match(Ok, lists:map(fun(R) -> mnesia:write(R) end, Records)), + a_good_trans. + + +lock() -> + ?match(ok, mnesia:s_write(#nice_tab{key=22, val=22})), + ?match(ok, mnesia:read_lock_table(nice_tab)), + ?match(ok, mnesia:write_lock_table(nice_tab)), + ok. + +index_mgt() -> + UniversalRec = #nice_tab{key=4711, val=4711}, + ?match(ok, mnesia:dirty_write(UniversalRec)), + ValPos = #nice_tab.val, + ?match({atomic, ok}, mnesia:add_table_index(nice_tab, ValPos)), + + IndexFun = + fun() -> + ?match([UniversalRec], + mnesia:index_read(nice_tab, 4711, ValPos)), + Pat = #nice_tab{key='$1', val=4711}, + ?match([UniversalRec], + mnesia:index_match_object(Pat, ValPos)), + index_trans + end, + ?match({atomic, index_trans}, mnesia:transaction(IndexFun, infinity)), + ?match([UniversalRec], + mnesia:dirty_index_read(nice_tab, 4711, ValPos)), + ?match([UniversalRec], + mnesia:dirty_index_match_object(#nice_tab{key='$1', val=4711}, ValPos)), + + ?match({atomic, ok}, mnesia:del_table_index(nice_tab, ValPos)), + ok. + +adm(Attrs, Node1, Node2) -> + This = node(), + ?match({ok, This}, mnesia:subscribe(system)), + ?match({atomic, ok}, + mnesia:add_table_copy(nice_tab, Node2, disc_only_copies)), + ?match({atomic, ok}, + mnesia:change_table_copy_type(nice_tab, Node2, ram_copies)), + ?match({atomic, ok}, mnesia:del_table_copy(nice_tab, Node1)), + ?match(stopped, rpc:call(Node1, mnesia, stop, [])), + ?match([], mnesia_test_lib:start_mnesia([Node1, Node2], [nice_tab])), + ?match(ok, mnesia:wait_for_tables([schema], infinity)), + + Transformer = fun(Rec) -> + list_to_tuple(tuple_to_list(Rec) ++ [initial_value]) + end, + ?match({atomic, ok}, + mnesia:transform_table(nice_tab, Transformer, Attrs ++ [extra])), + + ?match({atomic, ok}, mnesia:delete_table(nice_tab)), + DumpSchema = [{name, nice_tab}, {attributes, Attrs}, {ram_copies, [Node2]}], + ?match({atomic, ok}, mnesia:create_table(DumpSchema)), + ?match({atomic, ok}, mnesia:dump_tables([nice_tab])), + ?match({atomic, ok}, mnesia:move_table_copy(nice_tab, Node2, Node1)), + + ?match(yes, mnesia:force_load_table(nice_counter_tab)), + ?match(dumped, mnesia:dump_log()), + ok. + +backup(Node1) -> + Tab = backup_nice, + Def = [{disc_copies, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + ?match({ok,_,_}, mnesia:activate_checkpoint([{name, cp}, {max, [Tab]}])), + File = "nice_backup.BUP", + File2 = "nice_backup2.BUP", + File3 = "nice_backup3.BUP", + ?match(ok, mnesia:backup_checkpoint(cp, File)), + ?match(ok, mnesia:backup_checkpoint(cp, File, mnesia_backup)), + ?match(ok, mnesia:deactivate_checkpoint(cp)), + ?match(ok, mnesia:backup(File)), + ?match(ok, mnesia:backup(File, mnesia_backup)), + + Fun = fun(X, Acc) -> {[X], Acc} end, + ?match({ok, 0}, mnesia:traverse_backup(File, File2, Fun, 0)), + ?match({ok, 0}, mnesia:traverse_backup(File, mnesia_backup, dummy, read_only, Fun, 0)), + ?match(ok, mnesia:install_fallback(File)), + ?match(ok, mnesia:uninstall_fallback()), + ?match(ok, mnesia:install_fallback(File, mnesia_backup)), + ?match(ok, mnesia:dump_to_textfile(File3)), + ?match({atomic, ok}, mnesia:load_textfile(File3)), + ?match(ok, file:delete(File)), + ?match(ok, file:delete(File2)), + ?match(ok, file:delete(File3)), + ok. + +snmp(Node1, Node2) -> + Tab = nice_snmp, + Def = [{disc_copies, [Node1]}, {ram_copies, [Node2]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + ?match({aborted, {badarg, Tab, _}}, mnesia:snmp_open_table(Tab, [])), + ?match({atomic, ok}, mnesia:snmp_open_table(Tab, [{key, integer}])), + ?match(endOfTable, mnesia:snmp_get_next_index(Tab, [0])), + ?match(undefined, mnesia:snmp_get_row(Tab, [0])), + ?match(undefined, mnesia:snmp_get_mnesia_key(Tab, [0])), + ?match({atomic, ok}, mnesia:snmp_close_table(Tab)), + ok. + diff --git a/lib/mnesia/test/mnesia_qlc_test.erl b/lib/mnesia/test/mnesia_qlc_test.erl new file mode 100644 index 0000000000..1e4f776c7d --- /dev/null +++ b/lib/mnesia/test/mnesia_qlc_test.erl @@ -0,0 +1,475 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2004-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(mnesia_qlc_test). + +-compile(export_all). + +-export([all/1]). + +-include("mnesia_test_lib.hrl"). +-include_lib("stdlib/include/qlc.hrl"). + +init_per_testcase(Func, Conf) -> + setup(Conf), + mnesia_test_lib:init_per_testcase(Func, Conf). + +fin_per_testcase(Func, Conf) -> + mnesia_test_lib:fin_per_testcase(Func, Conf). + +all(doc) -> + ["Test that the qlc mnesia interface works as expected."]; +all(suite) -> + case code:which(qlc) of + non_existing -> []; + _ -> + all_qlc() + end. + +all_qlc() -> + [dirty, trans, frag, info, mnesia_down]. + +init_testcases(Type,Config) -> + Nodes = [N1,N2] = ?acquire_nodes(2, Config), + ?match({atomic, ok}, mnesia:create_table(a, [{Type,[N1]}, {index,[3]}])), + ?match({atomic, ok}, mnesia:create_table(b, [{Type,[N2]}])), + Write = fun(Id) -> + ok = mnesia:write({a, {a,Id}, 100 - Id}), + ok = mnesia:write({b, {b,100-Id}, Id}) + end, + All = fun() -> [Write(Id) || Id <- lists:seq(1,10)], ok end, + ?match({atomic, ok}, mnesia:sync_transaction(All)), + Nodes. + +%% Test cases +dirty(suite) -> + [dirty_nice_ram_copies, + dirty_nice_disc_copies, + dirty_nice_disc_only_copies]. + +dirty_nice_ram_copies(Setup) -> dirty_nice(Setup,ram_copies). +dirty_nice_disc_copies(Setup) -> dirty_nice(Setup,disc_copies). +dirty_nice_disc_only_copies(Setup) -> dirty_nice(Setup,disc_only_copies). + +dirty_nice(suite, _) -> []; +dirty_nice(doc, _) -> []; +dirty_nice(Config, Type) when is_list(Config) -> + Ns = init_testcases(Type,Config), + QA = handle(<<"[Q || Q = {_,{_,Key},Val} <- mnesia:table(a)," + " Val == 90 + Key]">>), + QB = handle(<<"[Q || Q = {_,{_,Key},Val} <- mnesia:table(b)," + " Key == 90 + Val]">>), + QC = qlc:sort(mnesia:table(a, [{n_objects,1}, {lock,write}, {traverse, select}])), + QD = qlc:sort(mnesia:table(a, [{n_objects,1}, {traverse,{select,[{'$1',[],['$1']}]}}])), + + FA = fun() -> qlc:e(QA) end, + FB = fun() -> qlc:e(QB) end, + FC = fun() -> qlc:e(QC) end, + FD = fun() -> qlc:e(QD) end, + + %% Currently unsupported + ?match({'EXIT',{aborted,no_transaction}}, FA()), + ?match({'EXIT',{aborted,no_transaction}}, FB()), + %% + CRes = lists:sort(mnesia:dirty_match_object(a, {'_','_','_'})), + ?match([{a,{a,5},95}], mnesia:async_dirty(FA)), + ?match([{b,{b,95},5}], mnesia:async_dirty(FB)), + ?match(CRes, mnesia:async_dirty(FC)), + ?match(CRes, mnesia:async_dirty(FD)), + ?match([{a,{a,5},95}], mnesia:sync_dirty(FA)), + ?match([{b,{b,95},5}], mnesia:sync_dirty(FB)), + ?match(CRes, mnesia:sync_dirty(FC)), + ?match([{a,{a,5},95}], mnesia:activity(async_dirty, FA)), + ?match([{b,{b,95},5}], mnesia:activity(async_dirty, FB)), + ?match([{a,{a,5},95}], mnesia:activity(sync_dirty, FA)), + ?match([{b,{b,95},5}], mnesia:activity(sync_dirty, FB)), + ?match(CRes, mnesia:activity(async_dirty,FC)), + case Type of + disc_only_copies -> skip; + _ -> + ?match([{a,{a,5},95}], mnesia:ets(FA)), + ?match([{a,{a,5},95}], mnesia:activity(ets, FA)) + end, + ?verify_mnesia(Ns, []). + +trans(suite) -> + [trans_nice_ram_copies, + trans_nice_disc_copies, + trans_nice_disc_only_copies, + atomic + ]. + +trans_nice_ram_copies(Setup) -> trans_nice(Setup,ram_copies). +trans_nice_disc_copies(Setup) -> trans_nice(Setup,disc_copies). +trans_nice_disc_only_copies(Setup) -> trans_nice(Setup,disc_only_copies). + +trans_nice(suite, _) -> []; +trans_nice(doc, _) -> []; +trans_nice(Config, Type) when is_list(Config) -> + Ns = init_testcases(Type,Config), + QA = handle(<<"[Q || Q = {_,{_,Key},Val} <- mnesia:table(a)," + " Val == 90 + Key]">>), + QB = handle(<<"[Q || Q = {_,{_,Key},Val} <- mnesia:table(b)," + " Key == 90 + Val]">>), + QC = handle(recs(), + <<"[Q || Q = #a{v=91} <- mnesia:table(a)]" + >>), + + QD = qlc:sort(mnesia:table(a, [{n_objects,1}, {lock,write}, {traverse, select}])), + QE = qlc:sort(mnesia:table(a, [{n_objects,1}, {traverse,{select,[{'$1',[],['$1']}]}}])), + + DRes = lists:sort(mnesia:dirty_match_object(a, {'_','_','_'})), + + FA = fun() -> qlc:e(QA) end, + FB = fun() -> qlc:e(QB) end, + FC = fun() -> qlc:e(QC) end, + FD = fun() -> qlc:e(QD) end, + FE = fun() -> qlc:e(QE) end, + + ?match({atomic,[{a,{a,5},95}]}, mnesia:transaction(FA)), + ?match({atomic,[{b,{b,95},5}]}, mnesia:transaction(FB)), + ?match({atomic,[{a,{a,9},91}]}, mnesia:transaction(FC)), + ?match({atomic,[{a,{a,5},95}]}, mnesia:sync_transaction(FA)), + ?match({atomic,[{b,{b,95},5}]}, mnesia:sync_transaction(FB)), + ?match({atomic,[{a,{a,9},91}]}, mnesia:sync_transaction(FC)), + ?match([{a,{a,5},95}], mnesia:activity(transaction,FA)), + ?match([{b,{b,95},5}], mnesia:activity(transaction,FB)), + ?match([{a,{a,9},91}], mnesia:activity(transaction,FC)), + ?match([{a,{a,5},95}], mnesia:activity(sync_transaction,FA)), + ?match([{b,{b,95},5}], mnesia:activity(sync_transaction,FB)), + ?match([{a,{a,9},91}], mnesia:activity(sync_transaction,FC)), + + ?match({atomic, DRes}, mnesia:transaction(FD)), + ?match({atomic, DRes}, mnesia:transaction(FE)), + + Rest = fun(Cursor,Loop) -> + case qlc:next_answers(Cursor, 1) of + [] -> []; + [A]-> [A|Loop(Cursor,Loop)] + end + end, + Loop = fun() -> + Cursor = qlc:cursor(QD), + Rest(Cursor,Rest) + end, + ?match({atomic, DRes}, mnesia:transaction(Loop)), + + ?verify_mnesia(Ns, []). + +%% -record(a, {k,v}). +%% -record(b, {k,v}). +%% -record(k, {t,v}). + +recs() -> + <<"-record(a, {k,v}). " + "-record(b, {k,v}). " + "-record(k, {t,v}). " + >>. + +atomic(suite) -> [atomic_eval]; +atomic(doc) -> []. + +atomic_eval(suite) -> []; +atomic_eval(doc) -> []; +atomic_eval(Config) -> + Ns = init_testcases(ram_copies, Config), + Q1 = handle(recs(), + <<"[Q || Q = #a{k={_,9}} <- mnesia:table(a)]" + >>), + Eval = fun(Q) -> + {qlc:e(Q), + mnesia:system_info(held_locks)} + end, + Self = self(), + ?match({[{a,{a,9},91}], [{{a,'______WHOLETABLE_____'},read,{tid,_,Self}}]}, + ok(Eval,[Q1])), + + Q2 = handle(recs(), + <<"[Q || Q = #a{k={a,9}} <- mnesia:table(a)]" + >>), + + ?match({[{a,{a,9},91}],[{{a,{a,9}},read,{tid,_,Self}}]}, + ok(Eval,[Q2])), + + Flush = fun(Loop) -> %% Clean queue + receive _ -> Loop(Loop) + after 0 -> ok end + end, + + Flush(Flush), + + GrabLock = fun(Father) -> + mnesia:read(a, {a,9}, write), + Father ! locked, + receive cont -> ok end end, + + Pid1 = spawn(fun() -> ?match(ok, ok(GrabLock, [Self])) end), + ?match(locked,receive locked -> locked after 5000 -> timeout end), %% Wait + + put(count,0), + Restart = fun(Locker,Fun) -> + Count = get(count), + case {Count,(catch Fun())} of + {0, {'EXIT', R}} -> + Locker ! cont, + put(count, Count+1), + erlang:yield(), + exit(R); + Else -> + Else + end + end, + + ?match({1,{[{a,{a,9},91}], [{{a,'______WHOLETABLE_____'},read,{tid,_,Self}}]}}, + ok(Restart,[Pid1,fun() -> Eval(Q1) end])), + + Pid2 = spawn(fun() -> ?match(ok, ok(GrabLock, [Self])) end), + ?match(locked,receive locked -> locked after 5000 -> timeout end), %% Wait + put(count,0), + ?match({1,{[{a,{a,9},91}],[{{a,{a,9}},read,{tid,_,Self}}]}}, + ok(Restart,[Pid2, fun() -> Eval(Q2) end])), + +%% Basic test + Cursor = fun() -> + QC = qlc:cursor(Q1), + qlc:next_answers(QC) + end, + + ?match([{a,{a,9},91}], ok(Cursor, [])), + %% Lock + + Pid3 = spawn(fun() -> ?match(ok, ok(GrabLock, [Self])) end), + ?match(locked,receive locked -> locked after 5000 -> timeout end), %% Wait + put(count,0), + + ?match({1,[{a,{a,9},91}]}, ok(Restart,[Pid3, Cursor])), + QC1 = ok(fun() -> qlc:cursor(Q1) end, []), + ?match({'EXIT', _}, qlc:next_answers(QC1)), + ?match({aborted,_}, ok(fun()->qlc:next_answers(QC1)end,[])), + ?verify_mnesia(Ns, []). + + +frag(suite) -> []; +frag(doc) -> []; +frag(Config) -> + Ns = init_testcases(ram_copies,Config), + QA = handle(<<"[Q || Q = {_,{_,Key},Val} <- mnesia:table(a)," + " Val == 90 + Key]">>), + QB = handle(<<"[Q || Q = {_,{_,Key},Val} <- mnesia:table(b)," + " Key == 90 + Val]">>), + + Activate = + fun(Tab) -> + ?match({atomic,ok},mnesia:change_table_frag(Tab, {activate, []})), + Dist = mnesia_frag_test:frag_dist(Tab), + ?match({atomic,ok},mnesia:change_table_frag(Tab,{add_frag,Dist})) + end, + Activate(a), + Activate(b), + + Fun = fun(Tab) -> mnesia:table_info(Tab, frag_names) end, + FTs = mnesia:activity(sync_dirty, Fun, [a], mnesia_frag) ++ + mnesia:activity(sync_dirty, Fun, [b], mnesia_frag), + Size = fun(Tab) -> mnesia:dirty_rpc(Tab, mnesia, table_info, [Tab,size]) end, + + %% Verify that all data doesn't belong to the same frag. + ?match([], [{Tab,Size(Tab)} || Tab <- FTs, + Size(Tab) =< 0]), + + FA = fun() -> qlc:e(QA) end, + FB = fun() -> qlc:e(QB) end, + ?match([{a,{a,5},95}], mnesia:activity(transaction,FA,[],mnesia_frag)), + ?match([{b,{b,95},5}], mnesia:activity(transaction,FB,[],mnesia_frag)), + + ?verify_mnesia(Ns, []). + +info(suite) -> []; +info(doc) -> []; +info(Config) -> + Ns = init_testcases(ram_copies, Config), + Q1 = handle(recs(), + <<"[Q || Q = #a{k={_,9}} <- mnesia:table(a)]" + >>), + + Q2 = handle(recs(), + <<"[Q || Q = #a{k={a,9}} <- mnesia:table(a)]" + >>), + + Q3 = handle(recs(), + <<"[Q || Q = #a{v=91} <- mnesia:table(a)]" + >>), + + %% FIXME compile and check results! + + ?match(ok,io:format("~s~n",[qlc:info(Q1)])), + ?match(ok,io:format("~s~n",[qlc:info(Q2)])), + ?match(ok,io:format("~s~n",[qlc:info(Q3)])), + + ?verify_mnesia(Ns, []). + +ok(Fun,A) -> + case mnesia:transaction(Fun,A) of + {atomic, R} -> R; + E -> E + end. + + +mnesia_down(suite) -> []; +mnesia_down(doc) -> + ["Test bug OTP-7968, which crashed mnesia when a" + "mnesia_down came after qlc had been invoked"]; +mnesia_down(Config) when is_list(Config) -> + [N1,N2] = init_testcases(ram_copies,Config), + QB = handle(<<"[Q || Q = {_,{_,Key},Val} <- mnesia:table(b)," + " Val == Key - 90]">>), + + Tester = self(), + + Eval = fun() -> + Cursor = qlc:cursor(QB), %% Forces another process + Res = qlc:next_answers(Cursor), + Tester ! {qlc, self(), Res}, + {Mod, Tid, Ts} = get(mnesia_activity_state), + receive + continue -> + io:format("Continuing ~p ~p ~n",[self(), {Mod, Tid, Ts}]), + io:format("ETS ~p~n",[ets:tab2list(element(2,Ts))]), + io:format("~p~n",[process_info(self(),messages)]), + Res + end + end, + spawn(fun() -> TransRes = mnesia:transaction(Eval), Tester ! {test,TransRes} end), + + TMInfo = fun() -> + TmInfo = mnesia_tm:get_info(5000), + mnesia_tm:display_info(user, TmInfo) + end, + receive + {qlc, QPid, QRes} -> + ?match([{b,{b,95},5}], QRes), + TMInfo(), + mnesia_test_lib:kill_mnesia([N2]), + %%timer:sleep(1000), + QPid ! continue + after 2000 -> + exit(timeout1) + end, + + receive + {test, QRes2} -> + ?match({atomic, [{b,{b,95},5}]}, QRes2) + after 2000 -> + exit(timeout2) + end, + + ?verify_mnesia([N1], [N2]). + + +nested_qlc(suite) -> []; +nested_qlc(doc) -> + ["Test bug in OTP-7968 (the second problem) where nested" + "transaction don't work as expected"]; +nested_qlc(Config) when is_list(Config) -> + Ns = init_testcases(ram_copies,Config), + Res = as_with_bs(), + ?match([_|_], Res), + top_as_with_some_bs(10), + + ?verify_mnesia(Ns, []). + + +%% Code from Daniel +bs_by_a_id(A_id) -> + find(qlc:q([ B || B={_,_,F_id} <- mnesia:table(b), F_id == A_id])). + +as_with_bs() -> + find(qlc:q([ {A,bs_by_a_id(Id)} || + A = {_, {a,Id}, _} <- mnesia:table(a)])). + +top_as_with_some_bs(Limit) -> + top( + qlc:q([ {A,bs_by_a_id(Id)} || + A = {_, {a,Id}, _} <- mnesia:table(a)]), + Limit, + fun(A1,A2) -> A1 < A2 end + ). + +% --- utils + +find(Q) -> + F = fun() -> qlc:e(Q) end, + {atomic, Res} = mnesia:transaction(F), + Res. + +% --- it returns top Limit results from query Q ordered by Order sort function +top(Q, Limit, Order) -> + Do = fun() -> + OQ = qlc:sort(Q, [{order,Order}]), + QC = qlc:cursor(OQ), + Res = qlc:next_answers(QC, Limit), + qlc:delete_cursor(QC), + Res + end, + {atomic, Res} = mnesia:transaction(Do), + Res. + +%% To keep mnesia suite backward compatible, +%% we compile the queries in runtime when qlc is available +%% Compiles and returns a handle to a qlc +handle(Expr) -> + handle(<<>>,Expr). +handle(Records,Expr) -> + case catch handle2(Records,Expr) of + {ok, Handle} -> + Handle; + Else -> + ?match(ok, Else) + end. + +handle2(Records,Expr) -> + {FN,Mod} = temp_name(), + ModStr = list_to_binary("-module(" ++ atom_to_list(Mod) ++ ").\n"), + Prog = << + ModStr/binary, + "-include_lib(\"stdlib/include/qlc.hrl\").\n", + "-export([tmp/0]).\n", + Records/binary,"\n", + "tmp() ->\n", +%% " _ = (catch throw(fvalue_not_reset))," + " qlc:q( ", + Expr/binary,").\n">>, + + ?match(ok,file:write_file(FN,Prog)), + {ok,Forms} = epp:parse_file(FN,"",""), + {ok,Mod,Bin} = compile:forms(Forms), + code:load_binary(Mod,FN,Bin), + {ok, Mod:tmp()}. + +setup(Config) -> + put(mts_config,Config), + put(mts_tf_counter,0). + +temp_name() -> + Conf = get(mts_config), + C = get(mts_tf_counter), + put(mts_tf_counter,C+1), + {filename:join([proplists:get_value(priv_dir,Conf, "."), + "tempfile"++integer_to_list(C)++".tmp"]), + list_to_atom("tmp" ++ integer_to_list(C))}. diff --git a/lib/mnesia/test/mnesia_recovery_test.erl b/lib/mnesia/test/mnesia_recovery_test.erl new file mode 100644 index 0000000000..f6ecf2ce2e --- /dev/null +++ b/lib/mnesia/test/mnesia_recovery_test.erl @@ -0,0 +1,1701 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(mnesia_recovery_test). +-author('[email protected]'). +-compile([export_all]). + +-include("mnesia_test_lib.hrl"). +-include_lib("kernel/include/file.hrl"). + +init_per_testcase(Func, Conf) -> + mnesia_test_lib:init_per_testcase(Func, Conf). + +fin_per_testcase(Func, Conf) -> + mnesia_test_lib:fin_per_testcase(Func, Conf). + +-define(receive_messages(Msgs), receive_messages(Msgs, ?FILE, ?LINE)). + +% First Some debug logging +-define(dgb, true). +-ifdef(dgb). +-define(dl(X, Y), ?verbose("**TRACING: " ++ X ++ "**~n", Y)). +-else. +-define(dl(X, Y), ok). +-endif. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +all(doc) -> + ["Verify recoverability", + "Verify that the effects of committed transactions are preserved", + "after recovery from system failures. It must be possible to", + "restore the tables to a consistent state on a node, from (any kind", + "of) replica on other nodes as well as from local disk on the failed", + "node. The system must also recover from instantaneous", + "interruption causing disk files to not be completely synchronized."]; + +all(suite) -> + [ + mnesia_down, + explicit_stop, + coord_dies, + schema_trans, + async_dirty, + sync_dirty, + sym_trans, + asym_trans, + after_full_disc_partition, + after_corrupt_files, + disc_less, + garb_decision, + system_upgrade + ]. + +schema_trans(suite) -> + [{mnesia_schema_recovery_test, all}]. + +tpcb_config(ReplicaType, _NodeConfig, Nodes) -> + [{n_branches, 5}, + {n_drivers_per_node, 5}, + {replica_nodes, Nodes}, + {driver_nodes, Nodes}, + {use_running_mnesia, true}, + {report_interval, infinity}, + {n_accounts_per_branch, 20}, + {replica_type, ReplicaType}]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +mnesia_down(doc) -> + [" Various tests about recovery when mnesia goes down on one or several nodes."]; +mnesia_down(suite) -> + [ + mnesia_down_during_startup, + master_node_tests, + read_during_down, + with_checkpoint, + delete_during_start + ]. + +master_node_tests(doc) -> + ["Verify that mnesia loads the correct data after it has been down, regarding master node settings."]; +master_node_tests(suite) -> + [ + no_master_2, + no_master_3, + one_master_2, + one_master_3, + two_master_2, + two_master_3, + all_master_2, + all_master_3 + ]. + +no_master_2(suite) -> []; +no_master_2(Config) when is_list(Config) -> mnesia_down_2(no, Config). + +no_master_3(suite) -> []; +no_master_3(Config) when is_list(Config) -> mnesia_down_3(no, Config). + +one_master_2(suite) -> []; +one_master_2(Config) when is_list(Config) -> mnesia_down_2(one, Config). + +one_master_3(suite) -> []; +one_master_3(Config) when is_list(Config) -> mnesia_down_3(one, Config). + +two_master_2(suite) -> []; +two_master_2(Config) when is_list(Config) -> mnesia_down_2(two, Config). + +two_master_3(suite) -> []; +two_master_3(Config) when is_list(Config) -> mnesia_down_3(two, Config). + +all_master_2(suite) -> []; +all_master_2(Config) when is_list(Config) -> mnesia_down_2(all, Config). + +all_master_3(suite) -> []; +all_master_3(Config) when is_list(Config) -> mnesia_down_3(all, Config). + +mnesia_down_2(Masters, Config) -> + Nodes = [N1, N2] = ?acquire_nodes(2, Config), + ?match({atomic, ok}, mnesia:create_table(tab1, [{ram_copies, Nodes}])), + ?match({atomic, ok}, mnesia:create_table(tab2, [{disc_copies, Nodes}])), + ?match({atomic, ok}, mnesia:create_table(tab3, [{disc_only_copies, Nodes}])), + ?match({atomic, ok}, mnesia:create_table(tab4, [{ram_copies, [N1]}])), + ?match({atomic, ok}, mnesia:create_table(tab5, [{ram_copies, [N2]}])), + ?match({atomic, ok}, mnesia:create_table(tab6, [{disc_copies, [N1]}])), + ?match({atomic, ok}, mnesia:create_table(tab7, [{disc_copies, [N2]}])), + ?match({atomic, ok}, mnesia:create_table(tab8, [{disc_only_copies, [N1]}])), + ?match({atomic, ok}, mnesia:create_table(tab9, [{disc_only_copies, [N2]}])), + ?match({atomic, ok}, mnesia:create_table(tab10, [{ram_copies, [N1]}, {disc_copies, [N2]}])), + ?match({atomic, ok}, mnesia:create_table(tab11, [{ram_copies, [N2]}, {disc_copies, [N1]}])), + ?match({atomic, ok}, mnesia:create_table(tab12, [{ram_copies, [N1]}, {disc_only_copies, [N2]}])), + ?match({atomic, ok}, mnesia:create_table(tab13, [{ram_copies, [N2]}, {disc_only_copies, [N1]}])), + ?match({atomic, ok}, mnesia:create_table(tab14, [{disc_only_copies, [N1]}, {disc_copies, [N2]}])), + ?match({atomic, ok}, mnesia:create_table(tab15, [{disc_only_copies, [N2]}, {disc_copies, [N1]}])), + + Tabs = [tab1, tab2, tab3, tab4, tab5, tab6, tab7, tab8, + tab9, tab10, tab11, tab12, tab13, tab14, tab15], + [?match(ok, rpc:call(Node, mnesia, wait_for_tables, [Tabs, 10000])) || Node <- Nodes], + [insert_data(Tab, 20) || Tab <- Tabs], + + VTabs = + case Masters of + no -> + Tabs -- [tab4, tab5]; % ram copies + one -> + ?match(ok, rpc:call(N1, mnesia, set_master_nodes, [[N1]])), + Tabs -- [tab1, tab4, tab5, tab10, tab12]; % ram_copies + two -> + ?match(ok, rpc:call(N1, mnesia, set_master_nodes, [Nodes])), + Tabs -- [tab4, tab5]; + all -> + [?match(ok, rpc:call(Node, mnesia, set_master_nodes, [[Node]])) || Node <- Nodes], + Tabs -- [tab1, tab4, tab5, tab10, tab11, tab12, tab13] + end, + + mnesia_test_lib:kill_mnesia([N1]), + ?match([], mnesia_test_lib:start_mnesia(Nodes, Tabs)), + + ?match([], mnesia_test_lib:kill_mnesia([N2])), + ?match([], mnesia_test_lib:start_mnesia(Nodes, Tabs)), + + [?match(ok, rpc:call(N1, ?MODULE, verify_data, [Tab, 20])) || Tab <- VTabs], + [?match(ok, rpc:call(N2, ?MODULE, verify_data, [Tab, 20])) || Tab <- VTabs], + ?verify_mnesia(Nodes, []). + +mnesia_down_3(Masters, Config) -> + Nodes = [N1, N2, N3] = ?acquire_nodes(3, Config), + ?match({atomic, ok}, mnesia:create_table(tab1, [{ram_copies, Nodes}])), + ?match({atomic, ok}, mnesia:create_table(tab2, [{disc_copies, Nodes}])), + ?match({atomic, ok}, mnesia:create_table(tab3, [{disc_only_copies, Nodes}])), + ?match({atomic, ok}, mnesia:create_table(tab4, [{ram_copies, [N1]}])), + ?match({atomic, ok}, mnesia:create_table(tab5, [{ram_copies, [N2]}])), + ?match({atomic, ok}, mnesia:create_table(tab16, [{ram_copies, [N3]}])), + ?match({atomic, ok}, mnesia:create_table(tab6, [{disc_copies, [N1]}])), + ?match({atomic, ok}, mnesia:create_table(tab7, [{disc_copies, [N2]}])), + ?match({atomic, ok}, mnesia:create_table(tab17, [{disc_copies, [N3]}])), + ?match({atomic, ok}, mnesia:create_table(tab8, [{disc_only_copies, [N1]}])), + ?match({atomic, ok}, mnesia:create_table(tab9, [{disc_only_copies, [N2]}])), + ?match({atomic, ok}, mnesia:create_table(tab18, [{disc_only_copies, [N3]}])), + ?match({atomic, ok}, mnesia:create_table(tab10, [{ram_copies, [N1]}, {disc_copies, [N2, N3]}])), + ?match({atomic, ok}, mnesia:create_table(tab11, [{ram_copies, [N2]}, {disc_copies, [N3, N1]}])), + ?match({atomic, ok}, mnesia:create_table(tab19, [{ram_copies, [N3]}, {disc_copies, [N1, N2]}])), + ?match({atomic, ok}, mnesia:create_table(tab12, [{ram_copies, [N1]}, {disc_only_copies, [N2, N3]}])), + ?match({atomic, ok}, mnesia:create_table(tab13, [{ram_copies, [N2]}, {disc_only_copies, [N3, N1]}])), + ?match({atomic, ok}, mnesia:create_table(tab20, [{ram_copies, [N3]}, {disc_only_copies, [N1, N2]}])), + ?match({atomic, ok}, mnesia:create_table(tab14, [{disc_only_copies, [N1]}, {disc_copies, [N2, N3]}])), + ?match({atomic, ok}, mnesia:create_table(tab15, [{disc_only_copies, [N2]}, {disc_copies, [N3, N1]}])), + ?match({atomic, ok}, mnesia:create_table(tab21, [{disc_only_copies, [N3]}, {disc_copies, [N1, N2]}])), + + Tabs = [tab1, tab2, tab3, tab4, tab5, tab6, tab7, tab8, + tab9, tab10, tab11, tab12, tab13, tab14, tab15, + tab16, tab17, tab18, tab19, tab20, tab21], + [?match(ok, rpc:call(Node, mnesia, wait_for_tables, [Tabs, 10000])) || Node <- Nodes], + [insert_data(Tab, 20) || Tab <- Tabs], + + VTabs = + case Masters of + no -> + Tabs -- [tab4, tab5, tab16]; % ram copies + one -> + ?match(ok, rpc:call(N1, mnesia, set_master_nodes, [[N1]])), + Tabs -- [tab1, tab4, tab5, tab16, tab10, tab12]; % ram copies + two -> + ?match(ok, rpc:call(N1, mnesia, set_master_nodes, [Nodes])), + Tabs -- [tab4, tab5, tab16]; % ram copies + all -> + [?match(ok, rpc:call(Node, mnesia, set_master_nodes, [[Node]])) || Node <- Nodes], + Tabs -- [tab1, tab4, tab5, tab16, tab10, + tab11, tab19, tab12, tab13, tab20] % ram copies + end, + + mnesia_test_lib:kill_mnesia([N1]), + ?match([], mnesia_test_lib:start_mnesia(Nodes, Tabs)), + + ?match([], mnesia_test_lib:kill_mnesia([N2])), + ?match([], mnesia_test_lib:start_mnesia(Nodes, Tabs)), + + ?match([], mnesia_test_lib:kill_mnesia([N3])), + ?match([], mnesia_test_lib:start_mnesia(Nodes, Tabs)), + + ?match([], mnesia_test_lib:kill_mnesia([N2, N1])), + ?match([], mnesia_test_lib:start_mnesia(Nodes, Tabs)), + + ?match([], mnesia_test_lib:kill_mnesia([N2, N3])), + ?match([], mnesia_test_lib:start_mnesia(Nodes, Tabs)), + + ?match([], mnesia_test_lib:kill_mnesia([N1, N3])), + ?match([], mnesia_test_lib:start_mnesia(Nodes, Tabs)), + + [?match(ok, rpc:call(N1, ?MODULE, verify_data, [Tab, 20])) || Tab <- VTabs], + [?match(ok, rpc:call(N2, ?MODULE, verify_data, [Tab, 20])) || Tab <- VTabs], + [?match(ok, rpc:call(N3, ?MODULE, verify_data, [Tab, 20])) || Tab <- VTabs], + + ?verify_mnesia(Nodes, []). + + +read_during_down(doc) -> + ["Verify that read operation can continue to read when mnesia goes down"]; +read_during_down(suite) -> + [ + dirty_read_during_down, + trans_read_during_down + ]. + +dirty_read_during_down(suite) -> + []; +dirty_read_during_down(Config) when is_list(Config) -> + read_during_down(dirty, Config). + +trans_read_during_down(suite) -> + []; +trans_read_during_down(Config) when is_list(Config) -> + read_during_down(trans, Config). + + +read_during_down(Op, Config) when is_list(Config) -> + Ns = [N1|TNs] = ?acquire_nodes(3, Config), + Tabs = [ram, disc, disco], + + ?match({atomic, ok}, mnesia:create_table(ram, [{ram_copies, TNs}])), + ?match({atomic, ok}, mnesia:create_table(disc, [{disc_copies, TNs}])), + ?match({atomic, ok}, mnesia:create_table(disco, [{disc_only_copies, TNs}])), + + %% Create some work for mnesia_controller when a node goes down + [{atomic, ok} = mnesia:create_table(list_to_atom("temp" ++ integer_to_list(N)), + [{ram_copies, Ns}]) || N <- lists:seq(1, 50)], + + Write = fun(Tab) -> mnesia:write({Tab, key, val}) end, + ?match([ok,ok,ok], + [mnesia:sync_dirty(Write, [Tab]) || Tab <- Tabs]), + + Readers = [spawn_link(N1, ?MODULE, reader, [Tab, Op]) || Tab <- Tabs], + [_|_] = W2R= [mnesia:table_info(Tab, where_to_read) || Tab <- Tabs], + ?log("W2R ~p~n", [W2R]), + loop_and_kill_mnesia(10, hd(W2R), Tabs), + [Pid ! self() || Pid <- Readers], + ?match([ok, ok, ok], [receive ok -> ok after 1000 -> {Pid, mnesia_lib:dist_coredump()} end || Pid <- Readers]), + ?verify_mnesia(Ns, []). + +reader(Tab, OP) -> + Res = case OP of + dirty -> + catch mnesia:dirty_read({Tab, key}); + trans -> + Read = fun() -> mnesia:read({Tab, key}) end, + {_, Temp} = mnesia:transaction(Read), + Temp + end, + case Res of + [{Tab, key, val}] -> ok; + Else -> + ?error("Expected ~p Got ~p ~n", [[{Tab, key, val}], Else]), + erlang:error(test_failed) + end, + receive Pid -> + Pid ! ok + after 50 -> + reader(Tab, OP) + end. + +loop_and_kill_mnesia(0, _Node, _Tabs) -> ok; +loop_and_kill_mnesia(N, Node, Tabs) -> + mnesia_test_lib:kill_mnesia([Node]), + timer:sleep(100), + ?match([], mnesia_test_lib:start_mnesia([Node], Tabs)), + [KN | _] = W2R= [mnesia:table_info(Tab, where_to_read) || Tab <- Tabs], + ?match([KN, KN,KN], W2R), + timer:sleep(100), + loop_and_kill_mnesia(N-1, KN, Tabs). + +mnesia_down_during_startup(doc) -> + ["Verify that mnesia can come back up again in a consistent state", + "after it has gone down during startup (with different store and", + "when it goes down in different situations"]; +mnesia_down_during_startup(suite) -> + [ + mnesia_down_during_startup_disk_ram, + mnesia_down_during_startup_init_ram, + mnesia_down_during_startup_init_disc, + mnesia_down_during_startup_init_disc_only, + mnesia_down_during_startup_tm_ram, + mnesia_down_during_startup_tm_disc, + mnesia_down_during_startup_tm_disc_only + ]. + +mnesia_down_during_startup_disk_ram(suite) -> []; +mnesia_down_during_startup_disk_ram(Config) when is_list(Config)-> + [Node1, Node2] = ?acquire_nodes(2, Config ++ + [{tc_timeout, timer:minutes(2)}]), + Tab = down_during_startup, + Def = [{ram_copies, [Node2]}, {disc_copies, [Node1]}], + + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + ?match(ok, mnesia:dirty_write({Tab, 876234, test_ok})), + timer:sleep(500), + mnesia_test_lib:kill_mnesia([Node1, Node2]), + timer:sleep(500), + mnesia_test_lib:start_mnesia([Node1, Node2], [Tab]), + mnesia_test_lib:kill_mnesia([Node1]), + timer:sleep(500), + ?match([], mnesia_test_lib:start_mnesia([Node1], [Tab])), + ?match([{Tab, 876234, test_ok}], mnesia:dirty_read({Tab,876234})), + ?verify_mnesia([Node1, Node2], []). + +mnesia_down_during_startup_init_ram(suite) -> []; +mnesia_down_during_startup_init_ram(Config) when is_list(Config) -> + ?is_debug_compiled, + DP = {mnesia_loader, do_get_network_copy}, + Type = ram_copies, + mnesia_down_during_startup2(Config, Type, DP, self()). + +mnesia_down_during_startup_init_disc(suite) -> []; +mnesia_down_during_startup_init_disc(Config) when is_list(Config) -> + ?is_debug_compiled, + DP = {mnesia_loader, do_get_network_copy}, + Type = disc_copies, + mnesia_down_during_startup2(Config, Type, DP, self()). + +mnesia_down_during_startup_init_disc_only(suite) -> []; +mnesia_down_during_startup_init_disc_only(Config) when is_list(Config) -> + ?is_debug_compiled, + DP = {mnesia_loader, do_get_network_copy}, + Type = disc_only_copies, + mnesia_down_during_startup2(Config, Type, DP, self()). + +mnesia_down_during_startup_tm_ram(suite) -> []; +mnesia_down_during_startup_tm_ram(Config) when is_list(Config) -> + ?is_debug_compiled, + DP = {mnesia_tm, init}, + Type = ram_copies, + mnesia_down_during_startup2(Config, Type, DP, self()). + +mnesia_down_during_startup_tm_disc(suite) -> []; +mnesia_down_during_startup_tm_disc(Config) when is_list(Config) -> + ?is_debug_compiled, + DP = {mnesia_tm, init}, + Type = disc_copies, + mnesia_down_during_startup2(Config, Type, DP, self()). + +mnesia_down_during_startup_tm_disc_only(suite) -> []; +mnesia_down_during_startup_tm_disc_only(Config) when is_list(Config) -> + ?is_debug_compiled, + DP = {mnesia_tm, init}, + Type = disc_only_copies, + mnesia_down_during_startup2(Config, Type, DP, self()). + +mnesia_down_during_startup2(Config, ReplicaType, Debug_Point, _Father) -> + ?log("TC~n mnesia_down_during_startup with type ~w and stops at ~w~n", + [ReplicaType, Debug_Point]), + Tpcb_tabs = [history,teller,account,branch], + Nodes = ?acquire_nodes(2, Config), + Node1 = hd(Nodes), + {success, [A]} = ?start_activities([Node1]), + TpcbConfig = tpcb_config(ReplicaType, 2, Nodes), + mnesia_tpcb:init(TpcbConfig), + A ! fun () -> mnesia_tpcb:run(TpcbConfig) end, + ?match_receive(timeout), + timer:sleep(timer:seconds(10)), % Let tpcb run for a while + mnesia_tpcb:stop(), + ?match(ok, mnesia_tpcb:verify_tabs()), + mnesia_test_lib:kill_mnesia([Node1]), + timer:sleep(timer:seconds(2)), + Self = self(), + TestFun = fun(_MnesiaEnv, _EvalEnv) -> + ?deactivate_debug_fun(Debug_Point), + Self ! fun_done, + spawn(mnesia_test_lib, kill_mnesia, [[Node1]]) + end, + ?activate_debug_fun(Debug_Point, TestFun, []), % Kill when debug has been reached + mnesia:start(), + Res = receive fun_done -> ok after timer:minutes(3) -> timeout end, % Wait till it's killed + ?match(ok, Res), + ?match(ok, timer:sleep(timer:seconds(2))), % Wait a while, at least till it dies; + ?match([], mnesia_test_lib:start_mnesia([Node1], Tpcb_tabs)), + ?match(ok, mnesia_tpcb:verify_tabs()), % Verify it + ?verify_mnesia(Nodes, []). + + +with_checkpoint(doc) -> + ["Restart mnesia with checkpoint"]; +with_checkpoint(suite) -> + [with_checkpoint_same, with_checkpoint_other]. + +with_checkpoint_same(suite) -> []; +with_checkpoint_same(Config) when is_list(Config) -> + with_checkpoint(Config, same). + +with_checkpoint_other(suite) -> []; +with_checkpoint_other(Config) when is_list(Config) -> + with_checkpoint(Config, other). + +with_checkpoint(Config, Type) when is_list(Config) -> + Nodes = [Node1, Node2] = ?acquire_nodes(2, Config), + Kill = case Type of + same -> %% Node1 is the one used for creating the checkpoint + Node1; %% and which we bring down + other -> + Node2 %% Here we bring node2 down.. + end, + + ?match({atomic, ok}, mnesia:create_table(ram, [{ram_copies, Nodes}])), + ?match({atomic, ok}, mnesia:create_table(disc, [{disc_copies, Nodes}])), + ?match({atomic, ok}, mnesia:create_table(disco, [{disc_only_copies, Nodes}])), + Tabs = [ram, disc, disco], + + ?match({ok, sune, _}, mnesia:activate_checkpoint([{name, sune}, + {max, mnesia:system_info(tables)}, + {ram_overrides_dump, true}])), + + ?match([], check_retainers(sune, Nodes)), + + ?match(ok, mnesia:deactivate_checkpoint(sune)), + ?match([], check_chkp(Nodes)), + + timer:sleep(500), %% Just to help debugging the io:formats now comes in the + %% correct order... :-) + + ?match({ok, sune, _}, mnesia:activate_checkpoint([{name, sune}, + {max, mnesia:system_info(tables)}, + {ram_overrides_dump, true}])), + + [[mnesia:dirty_write({Tab,Key,Key}) || Key <- lists:seq(1,10)] || Tab <- Tabs], + + mnesia_test_lib:kill_mnesia([Kill]), + timer:sleep(100), + mnesia_test_lib:start_mnesia([Kill], Tabs), + io:format("Mnesia on ~p started~n", [Kill]), + ?match([], check_retainers(sune, Nodes)), + ?match(ok, mnesia:deactivate_checkpoint(sune)), + ?match([], check_chkp(Nodes)), + + case Kill of + Node1 -> + ignore; + Node2 -> + mnesia_test_lib:kill_mnesia([Kill]), + timer:sleep(500), %% Just to help debugging + ?match({ok, sune, _}, mnesia:activate_checkpoint([{name, sune}, + {max, mnesia:system_info(tables)}, + {ram_overrides_dump, true}])), + + [[mnesia:dirty_write({Tab,Key,Key+2}) || Key <- lists:seq(1,10)] || + Tab <- Tabs], + + mnesia_test_lib:start_mnesia([Kill], Tabs), + io:format("Mnesia on ~p started ~n", [Kill]), + ?match([], check_retainers(sune, Nodes)), + ?match(ok, mnesia:deactivate_checkpoint(sune)), + ?match([], check_chkp(Nodes)), + ok + end, + ?verify_mnesia(Nodes, []). + +check_chkp(Nodes) -> + {Good, Bad} = rpc:multicall(Nodes, ?MODULE, check, []), + lists:flatten(Good ++ Bad). + +check() -> + [PCP] = ets:match_object(mnesia_gvar, {pending_checkpoint_pids, '_'}), + [PC] = ets:match_object(mnesia_gvar, {pending_checkpoints, '_'}), + [CPN] = ets:match_object(mnesia_gvar, {checkpoints, '_'}), + F = lists:filter(fun({_, []}) -> false; (_W) -> true end, + [PCP,PC,CPN]), + CPP = ets:match_object(mnesia_gvar, {{checkpoint, '_'}, '_'}), + Rt = ets:match_object(mnesia_gvar, {{'_', {retainer, '_'}}, '_'}), + F ++ CPP ++ Rt. + + +check_retainers(CHP, Nodes) -> + {[R1,R2], []} = rpc:multicall(Nodes, ?MODULE, get_all_retainers, [CHP]), + (R1 -- R2) ++ (R2 -- R1). + +get_all_retainers(CHP) -> + Tabs = mnesia:system_info(local_tables), + Iter = fun(Tab) -> + {ok, Res} = + mnesia_checkpoint:iterate(CHP, Tab, fun(R, A) -> [R|A] end, [], + retainer, checkpoint), +%% io:format("Retainer content ~w ~n", [Res]), + Res + end, + Elements = [Iter(Tab) || Tab <- Tabs], + lists:sort(lists:flatten(Elements)). + +delete_during_start(doc) -> + ["Test that tables can be delete during start, hopefully with tables" + " in the loader queue or soon to be"]; +delete_during_start(suite) -> []; +delete_during_start(Config) when is_list(Config) -> + [N1, N2, N3] = Nodes = ?acquire_nodes(3, Config), + Tabs = [list_to_atom("tab" ++ integer_to_list(I)) || I <- lists:seq(1, 30)], + ?match({atomic, ok}, mnesia:change_table_copy_type(schema, N2, ram_copies)), + ?match({atomic, ok}, mnesia:change_table_copy_type(schema, N3, ram_copies)), + + [?match({atomic, ok},mnesia:create_table(Tab, [{ram_copies,Nodes}])) || Tab <- Tabs], + lists:foldl(fun(Tab, I) -> + ?match({atomic, ok}, + mnesia:change_table_load_order(Tab,I)), + I+1 + end, 1, Tabs), + mnesia_test_lib:kill_mnesia([N2,N3]), +%% timer:sleep(500), + ?match({[ok,ok],[]}, rpc:multicall([N2,N3], mnesia,start, + [[{extra_db_nodes,[N1]}]])), + [Tab1,Tab2,Tab3|_] = Tabs, + ?match({atomic, ok}, mnesia:delete_table(Tab1)), + ?match({atomic, ok}, mnesia:delete_table(Tab2)), + + ?log("W4T ~p~n", [rpc:multicall([N2,N3], mnesia, wait_for_tables, [[Tab1,Tab2,Tab3],1])]), + + Remain = Tabs--[Tab1,Tab2], + ?match(ok, rpc:call(N2, mnesia, wait_for_tables, [Remain,10000])), + ?match(ok, rpc:call(N3, mnesia, wait_for_tables, [Remain,10000])), + + ?match(ok, rpc:call(N2, ?MODULE, verify_where2read, [Remain])), + ?match(ok, rpc:call(N3, ?MODULE, verify_where2read, [Remain])), + + ?verify_mnesia(Nodes, []). + +verify_where2read([Tab|Tabs]) -> + true = (node() == mnesia:table_info(Tab,where_to_read)), + verify_where2read(Tabs); +verify_where2read([]) -> ok. + + +%%------------------------------------------------------------------------------------------- +explicit_stop(doc) -> + ["Stop Mnesia in different situations"]; +explicit_stop(suite) -> + [explicit_stop_during_snmp]. +%% This is a bad implementation, but at least gives a indication if something is wrong +explicit_stop_during_snmp(suite) -> []; +explicit_stop_during_snmp(Config) when is_list(Config) -> + Nodes = ?acquire_nodes(2, Config), + [Node1, Node2] = Nodes, + Tab = snmp_tab, + Def = [{attributes, [key, value]}, + {snmp, [{key, integer}]}, + {mnesia_test_lib:storage_type(disc_copies, Config), + [Node1, Node2]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write({Tab, 1, 1}) end)), + + Do_trans_Pid1 = spawn_link(Node2, ?MODULE, do_trans_loop, [Tab, self()]), + Do_trans_Pid2 = spawn_link(?MODULE, do_trans_loop, [Tab, self()]), + Start_stop_Pid = spawn_link(?MODULE, start_stop, [Node1, 10, self()]), + receive + test_done -> + ok + after timer:minutes(5) -> + ?error("test case time out~n", []) + end, + ?verify_mnesia(Nodes, []), + exit(Do_trans_Pid1, kill), + exit(Do_trans_Pid2, kill), + exit(Start_stop_Pid, kill), + ok. + +do_trans_loop(Tab, Father) -> + %% Do not trap exit + do_trans_loop2(Tab, Father). +do_trans_loop2(Tab, Father) -> + Trans = + fun() -> + [{Tab, 1, Val}] = mnesia:read({Tab, 1}), + mnesia:write({Tab, 1, Val + 1}) + end, + case mnesia:transaction(Trans) of + {atomic, ok} -> + timer:sleep(200), + do_trans_loop2(Tab, Father); + {aborted, {node_not_running, N}} when N == node() -> + timer:sleep(200), + do_trans_loop2(Tab, Father); + {aborted, {no_exists, Tab}} -> + timer:sleep(200), + do_trans_loop2(Tab, Father); + Else -> + ?error("Transaction failed: ~p ~n", [Else]), + Father ! test_done, + exit(shutdown) + end. + +start_stop(_Node1, 0, Father) -> + Father ! test_done, + exit(shutdown); +start_stop(Node1, N, Father) when N > 0-> + timer:sleep(timer:seconds(5)), + ?match(stopped, rpc:call(Node1, mnesia, stop, [])), + timer:sleep(timer:seconds(2)), + ?match([], mnesia_test_lib:start_mnesia([Node1])), + start_stop(Node1, N-1, Father). + +coord_dies(suite) -> []; +coord_dies(doc) -> [""]; +coord_dies(Config) when is_list(Config) -> + Nodes = [N1, N2] = ?acquire_nodes(2, Config), + ?match({atomic, ok}, mnesia:create_table(tab1, [{ram_copies, Nodes}])), + ?match({atomic, ok}, mnesia:create_table(tab2, [{ram_copies, [N1]}])), + ?match({atomic, ok}, mnesia:create_table(tab3, [{ram_copies, [N2]}])), + Tester = self(), + + U1 = fun(Tab) -> + [{Tab,key,Val}] = mnesia:read(Tab,key,write), + mnesia:write({Tab,key, Val+1}), + Tester ! {self(),continue}, + receive + continue -> exit(crash) + end + end, + U2 = fun(Tab) -> + [{Tab,key,Val}] = mnesia:read(Tab,key,write), + mnesia:write({Tab,key, Val+1}), + mnesia:transaction(U1, [Tab]) + end, + [mnesia:dirty_write(Tab,{Tab,key,0}) || Tab <- [tab1,tab2,tab3]], + Pid1 = spawn(fun() -> mnesia:transaction(U2, [tab1]) end), + Pid2 = spawn(fun() -> mnesia:transaction(U2, [tab2]) end), + Pid3 = spawn(fun() -> mnesia:transaction(U2, [tab3]) end), + [receive {Pid,continue} -> ok end || Pid <- [Pid1,Pid2,Pid3]], + Pid1 ! continue, Pid2 ! continue, Pid3 ! continue, + ?match({atomic,[{_,key,1}]}, mnesia:transaction(fun() -> mnesia:read({tab1,key}) end)), + ?match({atomic,[{_,key,1}]}, mnesia:transaction(fun() -> mnesia:read({tab2,key}) end)), + ?match({atomic,[{_,key,1}]}, mnesia:transaction(fun() -> mnesia:read({tab3,key}) end)), + + Pid4 = spawn(fun() -> mnesia:transaction(U2, [tab1]) end), + Pid5 = spawn(fun() -> mnesia:transaction(U2, [tab2]) end), + Pid6 = spawn(fun() -> mnesia:transaction(U2, [tab3]) end), + erlang:monitor(process, Pid4),erlang:monitor(process, Pid5),erlang:monitor(process, Pid6), + + [receive {Pid,continue} -> ok end || Pid <- [Pid4,Pid5,Pid6]], + exit(Pid4,crash), + ?match_receive({'DOWN',_,_,Pid4, _}), + ?match({atomic,[{_,key,1}]}, mnesia:transaction(fun() -> mnesia:read({tab1,key}) end)), + exit(Pid5,crash), + ?match_receive({'DOWN',_,_,Pid5, _}), + ?match({atomic,[{_,key,1}]}, mnesia:transaction(fun() -> mnesia:read({tab2,key}) end)), + exit(Pid6,crash), + ?match_receive({'DOWN',_,_,Pid6, _}), + ?match({atomic,[{_,key,1}]}, mnesia:transaction(fun() -> mnesia:read({tab3,key}) end)), + + ?verify_mnesia(Nodes, []). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +sym_trans(doc) -> + ["Recovery of symmetrical transactions in a couple of different", + "situations; when coordinator or participant or node dies"]; + +sym_trans(suite) -> + [sym_trans_before_commit_kill_coord_node, %% coordinator node dies + sym_trans_before_commit_kill_coord_pid, %% coordinator process dies + sym_trans_before_commit_kill_part_after_ask, %% participating node dies + sym_trans_before_commit_kill_part_before_ask, + sym_trans_after_commit_kill_coord_node, + sym_trans_after_commit_kill_coord_pid, + sym_trans_after_commit_kill_part_after_ask, + sym_trans_after_commit_kill_part_do_commit_pre, + sym_trans_after_commit_kill_part_do_commit_post]. + +%kill_after_debug_point(Config, TestCase, {Debug_node, Debug_Point}, TransFun, Tab) + +sym_trans_before_commit_kill_coord_node(suite) -> []; +sym_trans_before_commit_kill_coord_node(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab = sym_trans_before_commit_kill_coord, + Def = [{attributes, [key, value]}, {ram_copies, [Part2]},{disc_copies, [Coord, Part1]}], + kill_after_debug_point(Coord, {Coord, {mnesia_tm, multi_commit_sym}}, + do_sym_trans, [{Tab, Def}], Nodes). + +sym_trans_before_commit_kill_coord_pid(suite) -> []; +sym_trans_before_commit_kill_coord_pid(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab = sym_trans_before_commit_kill_coord, + Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}], + kill_after_debug_point(coord_pid, {Coord, {mnesia_tm, multi_commit_sym}}, + do_sym_trans, [{Tab, Def}], Nodes). + +sym_trans_before_commit_kill_part_after_ask(suite) -> []; +sym_trans_before_commit_kill_part_after_ask(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab = sym_trans_before_commit_kill_part_after_ask, + Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}], + kill_after_debug_point(Part1, {Coord, {mnesia_tm, multi_commit_sym}}, + do_sym_trans, [{Tab, Def}], Nodes). + +sym_trans_before_commit_kill_part_before_ask(suite) -> []; +sym_trans_before_commit_kill_part_before_ask(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab = sym_trans_before_commit_kill_part_before_ask, + Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}], + kill_after_debug_point(Part1, {Part1, {mnesia_tm, doit_ask_commit}}, + do_sym_trans, [{Tab, Def}], Nodes). + +sym_trans_after_commit_kill_coord_node(suite) -> []; +sym_trans_after_commit_kill_coord_node(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab = sym_trans_after_commit_kill_coord, + Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}], + kill_after_debug_point(Coord, {Coord, {mnesia_tm, multi_commit_sym, post}}, + do_sym_trans, [{Tab, Def}], Nodes). + +sym_trans_after_commit_kill_coord_pid(suite) -> []; +sym_trans_after_commit_kill_coord_pid(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab = sym_trans_after_commit_kill_coord, + Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}], + kill_after_debug_point(coord_pid, {Coord, {mnesia_tm, multi_commit_sym, post}}, + do_sym_trans, [{Tab,Def}], Nodes). + +sym_trans_after_commit_kill_part_after_ask(suite) -> []; +sym_trans_after_commit_kill_part_after_ask(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab = sym_trans_after_commit_kill_part_after_ask, + Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}], + kill_after_debug_point(Part1, {Coord, {mnesia_tm, multi_commit_sym, post}}, + do_sym_trans, [{Tab, Def}], Nodes). + +sym_trans_after_commit_kill_part_do_commit_pre(suite) -> []; +sym_trans_after_commit_kill_part_do_commit_pre(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab = sym_trans_after_commit_kill_part_do_commit_pre, + Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}], + TransFun = do_sym_trans, + kill_after_debug_point(Part1, {Part1, {mnesia_tm, do_commit, pre}}, + TransFun, [{Tab, Def}], Nodes). + +sym_trans_after_commit_kill_part_do_commit_post(suite) -> []; +sym_trans_after_commit_kill_part_do_commit_post(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab = sym_trans_after_commit_kill_part_do_commit_post, + Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}], + TransFun = do_sym_trans, + kill_after_debug_point(Part1, {Part1, {mnesia_tm, do_commit, post}}, + TransFun, [{Tab, Def}], Nodes). + +do_sym_trans([Tab], _Fahter) -> + ?dl("Starting SYM_TRANS with active debug fun ", []), + Trans = fun() -> + [{_,_,Val}] = mnesia:read({Tab, 1}), + mnesia:write({Tab, 1, Val+1}) + end, + Res = mnesia:transaction(Trans), + case Res of + {atomic, ok} -> ok; + {aborted, _Reason} -> ok; + Else -> ?error("Wrong output from mensia:transaction(FUN):~n ~p~n", + [Else]) + end, + ?dl("SYM_TRANSACTION done: ~p (deactiv dbgfun) ", [Res]), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +sync_dirty(doc) -> + ["Verify recovery of synchronously operations in a couple of different", + "situations"]; +sync_dirty(suite) -> + [sync_dirty_pre_kill_part, + sync_dirty_pre_kill_coord_node, + sync_dirty_pre_kill_coord_pid, + sync_dirty_post_kill_part, + sync_dirty_post_kill_coord_node, + sync_dirty_post_kill_coord_pid + ]. + +sync_dirty_pre_kill_part(suite) -> []; +sync_dirty_pre_kill_part(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab = sync_dirty_pre, + Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}], + TransFun = do_sync_dirty, + kill_after_debug_point(Part1, {Part1, {mnesia_tm, sync_dirty, pre}}, + TransFun, [{Tab, Def}], Nodes). + +sync_dirty_pre_kill_coord_node(suite) -> []; +sync_dirty_pre_kill_coord_node(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab = sync_dirty_pre, + Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}], + TransFun = do_sync_dirty, + kill_after_debug_point(Coord, {Part1, {mnesia_tm, sync_dirty, pre}}, + TransFun, [{Tab, Def}], Nodes). + +sync_dirty_pre_kill_coord_pid(suite) -> []; +sync_dirty_pre_kill_coord_pid(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab = sync_dirty_pre, + Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}], + TransFun = do_sync_dirty, + kill_after_debug_point(coord_pid, {Part1, {mnesia_tm, sync_dirty, pre}}, + TransFun, [{Tab, Def}], Nodes). + +sync_dirty_post_kill_part(suite) -> []; +sync_dirty_post_kill_part(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab = sync_dirty_post, + Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}], + TransFun = do_sync_dirty, + kill_after_debug_point(Part1, {Part1, {mnesia_tm, sync_dirty, post}}, + TransFun, [{Tab, Def}], Nodes). + +sync_dirty_post_kill_coord_node(suite) -> []; +sync_dirty_post_kill_coord_node(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab = sync_dirty_post, + Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}], + TransFun = do_sync_dirty, + kill_after_debug_point(Coord, {Part1, {mnesia_tm, sync_dirty, post}}, + TransFun, [{Tab, Def}], Nodes). + +sync_dirty_post_kill_coord_pid(suite) -> []; +sync_dirty_post_kill_coord_pid(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab = sync_dirty_post, + Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}], + TransFun = do_sync_dirty, + kill_after_debug_point(coord_pid, {Part1, {mnesia_tm, sync_dirty, post}}, + TransFun, [{Tab, Def}], Nodes). + +do_sync_dirty([Tab], _Father) -> + ?dl("Starting SYNC_DIRTY", []), + SYNC = fun() -> + [{_,_,Val}] = mnesia:read({Tab, 1}), + mnesia:write({Tab, 1, Val+1}) + end, + {_, Res} = ?match(ok, mnesia:sync_dirty(SYNC)), + ?dl("SYNC_DIRTY done: ~p ", [Res]), + ok. + +async_dirty(doc) -> + ["Verify recovery of asynchronously dirty operations in a couple of different", + "situations"]; +async_dirty(suite) -> + [async_dirty_pre_kill_part, + async_dirty_pre_kill_coord_node, + async_dirty_pre_kill_coord_pid, + async_dirty_post_kill_part, + async_dirty_post_kill_coord_node, + async_dirty_post_kill_coord_pid]. + +async_dirty_pre_kill_part(suite) -> []; +async_dirty_pre_kill_part(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab = async_dirty_pre, + Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}], + TransFun = do_async_dirty, + kill_after_debug_point(Part1, {Part1, {mnesia_tm, async_dirty, pre}}, + TransFun, [{Tab, Def}], Nodes). + +async_dirty_pre_kill_coord_node(suite) -> []; +async_dirty_pre_kill_coord_node(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab = async_dirty_pre, + Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}], + TransFun = do_async_dirty, + kill_after_debug_point(Coord, {Part1, {mnesia_tm, async_dirty, pre}}, + TransFun, [{Tab, Def}], Nodes). + +async_dirty_pre_kill_coord_pid(suite) -> []; +async_dirty_pre_kill_coord_pid(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab = async_dirty_pre, + Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}], + TransFun = do_async_dirty, + kill_after_debug_point(coord_pid, {Part1, {mnesia_tm, async_dirty, pre}}, + TransFun, [{Tab, Def}], Nodes). + +async_dirty_post_kill_part(suite) -> []; +async_dirty_post_kill_part(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab = async_dirty_post, + Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}], + TransFun = do_async_dirty, + kill_after_debug_point(Part1, {Part1, {mnesia_tm, async_dirty, post}}, + TransFun, [{Tab, Def}], Nodes). + +async_dirty_post_kill_coord_node(suite) -> []; +async_dirty_post_kill_coord_node(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab = async_dirty_post, + Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}], + TransFun = do_async_dirty, + kill_after_debug_point(Coord, {Part1, {mnesia_tm, async_dirty, post}}, + TransFun, [{Tab, Def}], Nodes). + +async_dirty_post_kill_coord_pid(suite) -> []; +async_dirty_post_kill_coord_pid(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab = async_dirty_post, + Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}], + TransFun = do_async_dirty, + kill_after_debug_point(coord_pid, {Part1, {mnesia_tm, async_dirty, post}}, + TransFun, [{Tab, Def}], Nodes). + +do_async_dirty([Tab], _Fahter) -> + ?dl("Starting ASYNC", []), + ASYNC = fun() -> + [{_,_,Val}] = mnesia:read({Tab, 1}), + mnesia:write({Tab, 1, Val+1}) + end, + {_, Res} = ?match(ok, mnesia:async_dirty(ASYNC)), + ?dl("ASYNC done: ~p ", [Res]), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +asym_trans(doc) -> + ["Recovery of asymmetrical transactions in a couple of different", + "situations, currently the error cases are not covered, i.e. ", + "not tested are the situations when we kill mnesia or a process", + "during a recovery"]; +asym_trans(suite) -> + [ + asym_trans_kill_part_ask, + asym_trans_kill_part_commit_vote, + asym_trans_kill_part_pre_commit, + asym_trans_kill_part_log_commit, + asym_trans_kill_part_do_commit, + asym_trans_kill_coord_got_votes, + asym_trans_kill_coord_pid_got_votes, + asym_trans_kill_coord_log_commit_rec, + asym_trans_kill_coord_pid_log_commit_rec, + asym_trans_kill_coord_log_commit_dec, + asym_trans_kill_coord_pid_log_commit_dec, + asym_trans_kill_coord_rec_acc_pre_commit_log_commit, + asym_trans_kill_coord_pid_rec_acc_pre_commit_log_commit, + asym_trans_kill_coord_rec_acc_pre_commit_done_commit, + asym_trans_kill_coord_pid_rec_acc_pre_commit_done_commit + ]. + +asym_trans_kill_part_ask(suite) -> []; +asym_trans_kill_part_ask(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab1 = {asym1, [{ram_copies, [Part2]}, {disc_copies, [Coord]}]}, + Tab2 = {asym2, [{ram_copies, [Coord]}, {disc_copies, [Part1]}]}, + TransFun = do_asym_trans, + kill_after_debug_point(Part1, {Part1, {mnesia_tm, doit_ask_commit}}, + TransFun, [Tab1, Tab2], Nodes). + +asym_trans_kill_part_commit_vote(suite) -> []; +asym_trans_kill_part_commit_vote(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab1 = {asym1, [{ram_copies, [Part2]}, {disc_copies, [Coord]}]}, + Tab2 = {asym2, [{ram_copies, [Coord]}, {disc_copies, [Part1]}]}, + TransFun = do_asym_trans, + kill_after_debug_point(Part1, {Part1, {mnesia_tm, commit_participant, vote_yes}}, + TransFun, [Tab1, Tab2], Nodes). + +asym_trans_kill_part_pre_commit(suite) -> []; +asym_trans_kill_part_pre_commit(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab1 = {asym1, [{ram_copies, [Part2]}, {disc_copies, [Coord]}]}, + Tab2 = {asym2, [{ram_copies, [Coord]}, {disc_copies, [Part1]}]}, + TransFun = do_asym_trans, + kill_after_debug_point(Part1, {Part1, {mnesia_tm, commit_participant, pre_commit}}, + TransFun, [Tab1, Tab2], Nodes). + +asym_trans_kill_part_log_commit(suite) -> []; +asym_trans_kill_part_log_commit(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab1 = {asym1, [{ram_copies, [Part2]}, {disc_copies, [Coord]}]}, + Tab2 = {asym2, [{ram_copies, [Coord]}, {disc_copies, [Part1]}]}, + TransFun = do_asym_trans, + kill_after_debug_point(Part1, {Part1, {mnesia_tm, commit_participant, log_commit}}, + TransFun, [Tab1, Tab2], Nodes). + +asym_trans_kill_part_do_commit(suite) -> []; +asym_trans_kill_part_do_commit(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab1 = {asym1, [{ram_copies, [Part2]}, {disc_copies, [Coord]}]}, + Tab2 = {asym2, [{ram_copies, [Coord]}, {disc_copies, [Part1]}]}, + TransFun = do_asym_trans, + kill_after_debug_point(Part1, {Part1, {mnesia_tm, commit_participant, do_commit}}, + TransFun, [Tab1, Tab2], Nodes). + +asym_trans_kill_coord_got_votes(suite) -> []; +asym_trans_kill_coord_got_votes(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab1 = {asym1, [{ram_copies, [Part2]}, {disc_copies, [Coord]}]}, + Tab2 = {asym2, [{ram_copies, [Coord]}, {disc_copies, [Part1]}]}, + TransFun = do_asym_trans, + kill_after_debug_point(Coord, {Coord, {mnesia_tm, multi_commit_asym_got_votes}}, + TransFun, [Tab1, Tab2], Nodes). + +asym_trans_kill_coord_pid_got_votes(suite) -> []; +asym_trans_kill_coord_pid_got_votes(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab1 = {asym1, [{ram_copies, [Part2]}, {disc_copies, [Coord]}]}, + Tab2 = {asym2, [{ram_copies, [Coord]}, {disc_copies, [Part1]}]}, + TransFun = do_asym_trans, + kill_after_debug_point(coord_pid, {Coord, {mnesia_tm, multi_commit_asym_got_votes}}, + TransFun, [Tab1, Tab2], Nodes). + +asym_trans_kill_coord_log_commit_rec(suite) -> []; +asym_trans_kill_coord_log_commit_rec(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab1 = {asym1, [{ram_copies, [Part2]}, {disc_copies, [Coord]}]}, + Tab2 = {asym2, [{ram_copies, [Coord]}, {disc_copies, [Part1]}]}, + TransFun = do_asym_trans, + kill_after_debug_point(Coord, {Coord, {mnesia_tm, multi_commit_asym_log_commit_rec}}, + TransFun, [Tab1, Tab2], Nodes). + +asym_trans_kill_coord_pid_log_commit_rec(suite) -> []; +asym_trans_kill_coord_pid_log_commit_rec(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab1 = {asym1, [{ram_copies, [Part2]}, {disc_copies, [Coord]}]}, + Tab2 = {asym2, [{ram_copies, [Coord]}, {disc_copies, [Part1]}]}, + TransFun = do_asym_trans, + kill_after_debug_point(coord_pid, {Coord, {mnesia_tm, multi_commit_asym_log_commit_rec}}, + TransFun, [Tab1, Tab2], Nodes). + +asym_trans_kill_coord_log_commit_dec(suite) -> []; +asym_trans_kill_coord_log_commit_dec(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab1 = {asym1, [{ram_copies, [Part2]}, {disc_copies, [Coord]}]}, + Tab2 = {asym2, [{ram_copies, [Coord]}, {disc_copies, [Part1]}]}, + TransFun = do_asym_trans, + kill_after_debug_point(Coord, {Coord, {mnesia_tm, multi_commit_asym_log_commit_dec}}, + TransFun, [Tab1, Tab2], Nodes). + +asym_trans_kill_coord_pid_log_commit_dec(suite) -> []; +asym_trans_kill_coord_pid_log_commit_dec(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab1 = {asym1, [{ram_copies, [Part2]}, {disc_copies, [Coord]}]}, + Tab2 = {asym2, [{ram_copies, [Coord]}, {disc_copies, [Part1]}]}, + TransFun = do_asym_trans, + kill_after_debug_point(coord_pid, {Coord, {mnesia_tm, multi_commit_asym_log_commit_dec}}, + TransFun, [Tab1, Tab2], Nodes). + +asym_trans_kill_coord_rec_acc_pre_commit_log_commit(suite) -> []; +asym_trans_kill_coord_rec_acc_pre_commit_log_commit(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab1 = {asym1, [{ram_copies, [Part2]}, {disc_copies, [Coord]}]}, + Tab2 = {asym2, [{ram_copies, [Coord]}, {disc_copies, [Part1]}]}, + TransFun = do_asym_trans, + kill_after_debug_point(Coord, {Coord, {mnesia_tm, rec_acc_pre_commit_log_commit}}, + TransFun, [Tab1, Tab2], Nodes). + +asym_trans_kill_coord_pid_rec_acc_pre_commit_log_commit(suite) -> []; +asym_trans_kill_coord_pid_rec_acc_pre_commit_log_commit(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab1 = {asym1, [{ram_copies, [Part2]}, {disc_copies, [Coord]}]}, + Tab2 = {asym2, [{ram_copies, [Coord]}, {disc_copies, [Part1]}]}, + TransFun = do_asym_trans, + kill_after_debug_point(coord_pid, {Coord, {mnesia_tm, rec_acc_pre_commit_log_commit}}, + TransFun, [Tab1, Tab2], Nodes). + +asym_trans_kill_coord_rec_acc_pre_commit_done_commit(suite) -> []; +asym_trans_kill_coord_rec_acc_pre_commit_done_commit(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab1 = {asym1, [{ram_copies, [Part2]}, {disc_copies, [Coord]}]}, + Tab2 = {asym2, [{ram_copies, [Coord]}, {disc_copies, [Part1]}]}, + TransFun = do_asym_trans, + kill_after_debug_point(Coord, {Coord, {mnesia_tm, rec_acc_pre_commit_done_commit}}, + TransFun, [Tab1, Tab2], Nodes). + +asym_trans_kill_coord_pid_rec_acc_pre_commit_done_commit(suite) -> []; +asym_trans_kill_coord_pid_rec_acc_pre_commit_done_commit(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab1 = {asym1, [{ram_copies, [Part2]}, {disc_copies, [Coord]}]}, + Tab2 = {asym2, [{ram_copies, [Coord]}, {disc_copies, [Part1]}]}, + TransFun = do_asym_trans, + kill_after_debug_point(coord_pid, {Coord, {mnesia_tm, rec_acc_pre_commit_done_commit}}, + TransFun, [Tab1, Tab2], Nodes). + +do_asym_trans([Tab1, Tab2 | _R], Garbhandler) -> + ?dl("Starting asym trans ", []), + ASym_Trans = fun() -> + TidTs = {_Mod, Tid, _Store} = + mnesia:get_activity_id(), + ?verbose("===> asym_trans: ~w~n", [TidTs]), + Garbhandler ! {trans_id, Tid}, + [{_, _, Val1}] = mnesia:read({Tab1, 1}), + [{_, _, Val2}] = mnesia:read({Tab2, 1}), + mnesia:write({Tab1, 1, Val1+1}), + mnesia:write({Tab2, 1, Val2+1}) + end, + Res = mnesia:transaction(ASym_Trans), + case Res of + {atomic, ok} -> ok; + {aborted, _Reason} -> ok; + _Else -> ?error("Wrong output from mensia:transaction(FUN):~n ~p~n", [Res]) + end, + ?dl("Asym trans finished with: ~p ", [Res]). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +kill_after_debug_point(Kill, {DebugNode, Debug_Point}, TransFun, TabsAndDefs, Nodes) -> + [Coord | _rest] = Nodes, + + Create = fun({Tab, Def}) -> ?match({atomic, ok}, mnesia:create_table(Tab, Def)) end, + lists:foreach(Create, TabsAndDefs), + Tabs = [T || {T, _} <- TabsAndDefs], + Write = fun(Tab) -> ?match(ok, mnesia:dirty_write({Tab, 1, 100})) end, + lists:foreach(Write, Tabs), + + Self = self(), + SyncFun = fun(_Env1, _Env2) -> % Just Sync with test prog + Self ! {self(), fun_in_position}, + ?dl("SyncFun, sending fun_in_position ", []), + receive continue -> + ?dl("SyncFun received continue ",[]), + ok + after timer:seconds(60) -> + ?error("Timeout in sync_fun on ~p~n", [node()]) + end + end, + + Garb_handler = spawn_link(?MODULE, garb_handler, [[]]), + + ?remote_activate_debug_fun(DebugNode, Debug_Point, SyncFun, []), + ?dl("fun_in_position activated at ~p with ~p", [DebugNode, Debug_Point]), + %% Spawn and do the transaction + Pid = spawn(Coord, ?MODULE, TransFun, [Tabs, Garb_handler]), + %% Wait till all the Nodes are in correct position + [{StoppedPid,_}] = ?receive_messages([fun_in_position]), + ?dl("Received fun_in_position; Removing the debug funs ~p", [DebugNode]), + ?remote_deactivate_debug_fun(DebugNode, Debug_Point), + + case Kill of + coord_pid -> + ?dl("Intentionally killing pid ~p ", [Pid]), + exit(Pid, normal); + Node -> + mnesia_test_lib:kill_mnesia([Node]) + end, + + StoppedPid ! continue, %% Send continue, it may still be alive + + %% Start and check that the databases are consistent + ?dl("Done, Restarting and verifying result ",[]), + case Kill of + coord_pid -> ok; + _ -> % Ok, mnesia on some node was killed restart it + timer:sleep(timer:seconds(3)), %% Just let it have the time to die + ?match(ok, rpc:call(Kill, mnesia, start, [[]])), + ?match(ok, rpc:call(Kill, mnesia, wait_for_tables, [Tabs, 60000])) + end, + Trans_res = verify_tabs(Tabs, Nodes), + case TransFun of + do_asym_trans -> + %% Verifies that decisions are garbed, only valid for asym_tran + Garb_handler ! {get_tids, self()}, + Tid_list = receive + {tids, List} -> + ?dl("Fun rec ~w", [List]), + List + end, + garb_of_decisions(Kill, Nodes, Tid_list, Trans_res); + _ -> + ignore + end, + ?verify_mnesia(Nodes, []). + +garb_of_decisions(Kill, Nodes, Tid_list, Trans_res) -> + [Coord, Part1, Part2] = Nodes, + %% Check that decision log is empty on all nodes after the trans is finished + verify_garb_decision_log(Nodes, Tid_list), + case Trans_res of + aborted -> + %% Check that aborted trans have not been restarted!! + ?match(1, length(Tid_list)), + %% Check the transient decision logs + %% A transaction should only be aborted in an early stage of + %% the trans before the any Node have logged anything + verify_garb_transient_logs(Nodes, Tid_list, aborted), + %% And only when the coordinator are have died + %% Else he would have restarted the transaction + ?match(Kill, Coord); + updated -> + case length(Tid_list) of + 1 -> + %% If there was only one transaction, it should be logged as + %% comitted on every node! + [Tid1] = Tid_list, + verify_garb_transient_logs(Nodes, [Tid1], committed); + 2 -> + %% If there is two transaction id, then the first + %% TID should have been aborted and the transaction + %% restarted with a new TID + [Tid1, Tid2] = Tid_list, + verify_garb_transient_logs(Nodes, [Tid1], aborted), + %% If mnesia is killed on a node i.e Coord and Part1 than they + %% won't know about the restarted trans! The rest of the nodes + %% should know that the trans was committed + case Kill of + coord_pid -> + verify_garb_transient_logs(Nodes, [Tid2], committed); + Coord -> + verify_garb_transient_logs([Part1, Part2], [Tid2], committed), + verify_garb_transient_logs([Coord], [Tid2], not_found); + Part1 -> + verify_garb_transient_logs([Coord, Part2], [Tid2], committed), + verify_garb_transient_logs([Part1], [Tid2], not_found) + end + end + end. + +verify_garb_decision_log([], _Tids) -> ok; +verify_garb_decision_log([Node|R], Tids) -> + Check = fun(Tid) -> %% Node, Tid used in debugging! + ?match({{not_found, _}, Node, Tid}, + {outcome(Tid, [mnesia_decision]), Node, Tid}) + end, + rpc:call(Node, lists, foreach, [Check, Tids]), + verify_garb_decision_log(R, Tids). + +verify_garb_transient_logs([], _Tids, _) -> ok; +verify_garb_transient_logs([Node|R], Tids, Exp_Res) -> + Check = fun(Tid) -> + LatestTab = mnesia_lib:val(latest_transient_decision), + PrevTabs = mnesia_lib:val(previous_transient_decisions), + case outcome(Tid, [LatestTab | PrevTabs]) of + {found, {_, [{_,_Tid, Exp_Res}]}} -> ok; + {not_found, _} when Exp_Res == not_found -> ok; + {not_found, _} when Exp_Res == aborted -> ok; + Else -> ?error("Expected ~p in trans ~p on ~p got ~p~n", + [Exp_Res, Tid, Node, Else]) + end + end, + rpc:call(Node, lists, foreach, [Check, Tids]), + verify_garb_transient_logs(R, Tids, Exp_Res). + +outcome(Tid, Tabs) -> + outcome(Tid, Tabs, Tabs). + +outcome(Tid, [Tab | Tabs], AllTabs) -> + case catch ets:lookup(Tab, Tid) of + {'EXIT', _} -> + outcome(Tid, Tabs, AllTabs); + [] -> + outcome(Tid, Tabs, AllTabs); + Val -> + {found, {Tab, Val}} + end; +outcome(_Tid, [], AllTabs) -> + {not_found, AllTabs}. + + +verify_tabs([Tab|R], Nodes) -> + [_Coord, Part1, Part2 | _rest] = Nodes, + Read = fun() -> mnesia:read({Tab, 1}) end, + {success, A} = ?match({atomic, _}, mnesia:transaction(Read)), + ?match(A, rpc:call(Part1, mnesia, transaction, [Read])), + ?match(A, rpc:call(Part2, mnesia, transaction, [Read])), + {atomic, [{Tab, 1, Res}]} = A, + verify_tabs(R, Nodes, Res). + +verify_tabs([], _Nodes, Res) -> + case Res of + 100 -> aborted; + 101 -> updated + end; + +verify_tabs([Tab | Rest], Nodes, Res) -> + [Coord, Part1, Part2 | _rest] = Nodes, + Read = fun() -> mnesia:read({Tab, 1}) end, + Exp = {atomic, [{Tab, 1, Res}]}, + ?match(Exp, rpc:call(Coord, mnesia, transaction, [Read])), + ?match(Exp, rpc:call(Part1, mnesia, transaction, [Read])), + ?match(Exp, rpc:call(Part2, mnesia, transaction, [Read])), + verify_tabs(Rest, Nodes, Res). + +%% Gather TIDS and send them to requesting process and exit! +garb_handler(List) -> + receive + {trans_id, ID} -> garb_handler([ID|List]); + {get_tids, Pid} -> Pid ! {tids, lists:reverse(List)} + end. + +%%%%%%%%%%%%%%%%%%%%%%% +receive_messages([], _File, _Line) -> []; +receive_messages(ListOfMsgs, File, Line) -> + receive + {Pid, Msg} -> + case lists:member(Msg, ListOfMsgs) of + false -> + mnesia_test_lib:log("<>WARNING<>~n" + "Received unexpected msg~n ~p ~n" + "While waiting for ~p~n", + [{Pid, Msg}, ListOfMsgs], File, Line), + receive_messages(ListOfMsgs, File, Line); + true -> + ?dl("Got msg ~p from ~p ", [Msg, node(Pid)]), + [{Pid, Msg} | receive_messages(ListOfMsgs -- [Msg], File, Line)] + end; + Else -> mnesia_test_lib:log("<>WARNING<>~n" + "Recevied unexpected or bad formatted msg~n ~p ~n" + "While waiting for ~p~n", + [Else, ListOfMsgs], File, Line), + receive_messages(ListOfMsgs, File, Line) + after timer:minutes(2) -> + ?error("Timeout in receive msgs while waiting for ~p~n", + [ListOfMsgs]) + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +after_full_disc_partition(doc) -> + ["Verify that the database does not get corrupt", + "when Mnesia encounters a full disc partition"]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% interrupted_fallback_start +%% is implemented in consistency interupted_install_fallback! +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +after_corrupt_files(doc) -> + ["Verify that mnesia (and dets) can handle corrupt files"]; +after_corrupt_files(suite) -> % cope with unsynced disks + [after_corrupt_files_decision_log_head, + after_corrupt_files_decision_log_tail, + after_corrupt_files_latest_log_head, + after_corrupt_files_latest_log_tail, + after_corrupt_files_table_dat_head, + after_corrupt_files_table_dat_tail, + after_corrupt_files_schema_dat_head, + after_corrupt_files_schema_dat_tail + ]. + +after_corrupt_files_decision_log_head(suite) -> []; +after_corrupt_files_decision_log_head(Config) when is_list(Config) -> + after_corrupt_files(Config, "DECISION.LOG", head, repair). + +after_corrupt_files_decision_log_tail(suite) -> []; +after_corrupt_files_decision_log_tail(Config) when is_list(Config) -> + after_corrupt_files(Config, "DECISION.LOG", tail, repair). + +after_corrupt_files_latest_log_head(suite) -> []; +after_corrupt_files_latest_log_head(Config) when is_list(Config) -> + after_corrupt_files(Config, "LATEST.LOG", head, repair). + +after_corrupt_files_latest_log_tail(suite) -> []; +after_corrupt_files_latest_log_tail(Config) when is_list(Config) -> + after_corrupt_files(Config, "LATEST.LOG", tail, repair). + +after_corrupt_files_table_dat_head(suite) -> []; +after_corrupt_files_table_dat_head(Config) when is_list(Config) -> + after_corrupt_files(Config, "rec_files.DAT", head, crash). + +after_corrupt_files_table_dat_tail(suite) -> []; +after_corrupt_files_table_dat_tail(Config) when is_list(Config) -> + after_corrupt_files(Config, "rec_files.DAT", tail, repair). + +after_corrupt_files_schema_dat_head(suite) -> []; +after_corrupt_files_schema_dat_head(Config) when is_list(Config) -> + after_corrupt_files(Config, "schema.DAT", head, crash). + +after_corrupt_files_schema_dat_tail(suite) -> []; +after_corrupt_files_schema_dat_tail(Config) when is_list(Config) -> + after_corrupt_files(Config, "schema.DAT", tail, crash). + + + +%%% BUGBUG: We should also write testcase's for autorepair=false i.e. +%%% not the standard case! +after_corrupt_files(Config, File, Where, Behaviour) -> + [Node] = ?acquire_nodes(1, Config ++ [{tc_timeout, timer:minutes(2)}]), + Tab = rec_files, + Def = [{disc_only_copies, [Node]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + insert_data(Tab, 100), + Dir = mnesia:system_info(directory), + mnesia_test_lib:kill_mnesia([Node]), + timer:sleep(timer:seconds(10)), % Let dets finish whatever it does + + DirFile = Dir ++ "/" ++ File, + + {ok, Fd} = file:open(DirFile, read_write), + {ok, FileInfo} = file:read_file_info(DirFile), + case Where of + head -> + ?match({ok, _NewP}, file:position(Fd, {bof, 1})), + ?match(ok, file:write(Fd, [255, 255, 255, 255, 255, 255, 255, 255, 254])), + ok; + tail -> + Size = FileInfo#file_info.size, + Half = Size div 2, + + ?dl(" Size = ~p Half = ~p ", [Size, Half]), + ?match({ok, _NewP}, file:position(Fd, {bof, Half})), + ?match(ok, file:truncate(Fd)), + ok + end, + ?match(ok, file:close(Fd)), + + ?warning("++++++SOME OF THE after_corrupt* TEST CASES WILL INTENTIONALLY CRASH MNESIA+++++++~n", []), + Pid = spawn_link(?MODULE, mymnesia_start, [self()]), + receive + {Pid, ok} -> + ?match(ok, mnesia:wait_for_tables([schema, Tab], 10000)), + ?match(ok, verify_data(Tab, 100)), + case mnesia_monitor:get_env(auto_repair) of + false -> + ?error("Mnesia should have crashed in ~p ~p ~n", + [File, Where]); + true -> + ok + end, + ?verify_mnesia([Node], []); + {Pid, {error, ED}} -> + case {mnesia_monitor:get_env(auto_repair), Behaviour} of + {true, repair} -> + ?error("Mnesia crashed with ~p: in ~p ~p ~n", + [ED, File, Where]); + _ -> %% Every other can crash! + ok + end, + ?verify_mnesia([], [Node]); + Msg -> + ?error("~p ~p: Got ~p during start of Mnesia~n", + [File, Where, Msg]) + end. + +mymnesia_start(Tester) -> + Res = mnesia:start(), + unlink(Tester), + Tester ! {self(), Res}. + +verify_data(_, 0) -> ok; +verify_data(Tab, N) -> + Actual = mnesia:dirty_read({Tab, N}), + Expected = [{Tab, N, N}], + if + Expected == Actual -> + verify_data(Tab, N - 1); + true -> + mnesia:schema(Tab), + {not_equal, node(), Expected, Actual} + end. + +insert_data(_Tab, 0) -> ok; +insert_data(Tab, N) -> + ok = mnesia:sync_dirty(fun() -> mnesia:write({Tab, N, N}) end), + insert_data(Tab, N-1). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +disc_less(doc) -> + ["Here is a simple test case of a simple recovery of a disc less node. " + "However a lot more test cases involving disc less nodes should " + "be written"]; +disc_less(suite) -> []; +disc_less(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + case mnesia_test_lib:diskless(Config) of + true -> skip; + false -> + ?match({atomic, ok}, mnesia:change_table_copy_type(schema, Node3, ram_copies)) + end, + Tab1 = disc_less1, + Tab2 = disc_less2, + Tab3 = disc_less3, + Def1 = [{ram_copies, [Node3]}, {disc_copies, [Node1, Node2]}], + Def2 = [{ram_copies, [Node3]}, {disc_copies, [Node1]}], + Def3 = [{ram_copies, [Node3]}, {disc_copies, [Node2]}], + ?match({atomic, ok}, mnesia:create_table(Tab1, Def1)), + ?match({atomic, ok}, mnesia:create_table(Tab2, Def2)), + ?match({atomic, ok}, mnesia:create_table(Tab3, Def3)), + insert_data(Tab1, 100), + insert_data(Tab2, 100), + insert_data(Tab3, 100), + + mnesia_test_lib:kill_mnesia([Node1, Node2]), + timer:sleep(500), + mnesia_test_lib:kill_mnesia([Node3]), + ?match(ok, rpc:call(Node1, mnesia, start, [])), + ?match(ok, rpc:call(Node2, mnesia, start, [])), + + timer:sleep(500), + ?match(ok, rpc:call(Node3, mnesia, start, [[{extra_db_nodes, [Node1, Node2]}]])), + ?match(ok, rpc:call(Node3, mnesia, wait_for_tables, [[Tab1, Tab2, Tab3], 20000])), + + ?match(ok, rpc:call(Node3, ?MODULE, verify_data, [Tab1, 100])), + ?match(ok, rpc:call(Node3, ?MODULE, verify_data, [Tab2, 100])), + ?match(ok, rpc:call(Node3, ?MODULE, verify_data, [Tab3, 100])), + + + ?match(ok, rpc:call(Node2, ?MODULE, verify_data, [Tab1, 100])), + ?match(ok, rpc:call(Node2, ?MODULE, verify_data, [Tab2, 100])), + ?match(ok, rpc:call(Node2, ?MODULE, verify_data, [Tab3, 100])), + + ?match(ok, rpc:call(Node1, ?MODULE, verify_data, [Tab1, 100])), + ?match(ok, rpc:call(Node1, ?MODULE, verify_data, [Tab2, 100])), + ?match(ok, rpc:call(Node1, ?MODULE, verify_data, [Tab3, 100])), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +system_upgrade(doc) -> + ["Test on-line and off-line upgrade of the Mnesia application"]. + +garb_decision(doc) -> + ["Test that decisions are garbed correctly."]; +garb_decision(suite) -> []; +garb_decision(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + check_garb(Nodes), + ?match({atomic, ok},mnesia:create_table(a, [{disc_copies, Nodes}])), + check_garb(Nodes), + ?match({atomic, ok},mnesia:create_table(b, [{ram_copies, Nodes}])), + check_garb(Nodes), + ?match({atomic, ok},mnesia:create_table(c, [{ram_copies, [Node1, Node3]}, + {disc_copies, [Node2]}])), + check_garb(Nodes), + ?match({atomic, ok},mnesia:create_table(d, [{disc_copies, [Node1, Node3]}, + {ram_copies, [Node2]}])), + check_garb(Nodes), + + W = fun(Tab) -> mnesia:write({Tab,1,1}) end, + A = fun(Tab) -> mnesia:write({Tab,1,1}), exit(1) end, + + ?match({atomic, ok}, mnesia:transaction(W,[a])), + check_garb(Nodes), + ?match({atomic, ok}, mnesia:transaction(W,[b])), + check_garb(Nodes), + ?match({atomic, ok}, mnesia:transaction(W,[c])), + check_garb(Nodes), + ?match({atomic, ok}, mnesia:transaction(W,[d])), + check_garb(Nodes), + ?match({aborted,1}, mnesia:transaction(A,[a])), + check_garb(Nodes), + ?match({aborted,1}, mnesia:transaction(A,[b])), + check_garb(Nodes), + ?match({aborted,1}, mnesia:transaction(A,[c])), + check_garb(Nodes), + ?match({aborted,1}, mnesia:transaction(A,[d])), + check_garb(Nodes), + + rpc:call(Node2, mnesia, lkill, []), + ?match({atomic, ok}, mnesia:transaction(W,[a])), + ?match({atomic, ok}, mnesia:transaction(W,[b])), + ?match({atomic, ok}, mnesia:transaction(W,[c])), + ?match({atomic, ok}, mnesia:transaction(W,[d])), + check_garb(Nodes), + ?match([], mnesia_test_lib:start_mnesia([Node2])), + check_garb(Nodes), + timer:sleep(2000), + check_garb(Nodes), + %%%%%% Check transient_decision logs %%%%% + + ?match(dumped, mnesia:dump_log()), sys:get_status(mnesia_recover), % sync + [{atomic, ok} = mnesia:transaction(W,[a]) || _ <- lists:seq(1,30)], + ?match(dumped, mnesia:dump_log()), sys:get_status(mnesia_recover), % sync + TD0 = mnesia_lib:val(latest_transient_decision), + ?match(0, ets:info(TD0, size)), + {atomic, ok} = mnesia:transaction(W,[a]), + ?match(dumped, mnesia:dump_log()), sys:get_status(mnesia_recover), % sync + ?match(TD0, mnesia_lib:val(latest_transient_decision)), + [{atomic, ok} = mnesia:transaction(W,[a]) || _ <- lists:seq(1,30)], + ?match(dumped, mnesia:dump_log()), sys:get_status(mnesia_recover), % sync + ?match(false, TD0 =:= mnesia_lib:val(latest_transient_decision)), + ?match(true, lists:member(TD0, mnesia_lib:val(previous_transient_decisions))), + ?verify_mnesia(Nodes, []). + +check_garb(Nodes) -> + rpc:multicall(Nodes, sys, get_status, [mnesia_recover]), + ?match({_, []},rpc:multicall(Nodes, erlang, apply, [fun check_garb/0, []])). + +check_garb() -> + try + Ds = ets:tab2list(mnesia_decision), + Check = fun({trans_tid,serial, _}) -> false; + ({mnesia_down,_,_,_}) -> false; + (_Else) -> true + end, + Node = node(), + ?match({Node, []}, {node(), lists:filter(Check, Ds)}) + catch _:_ -> ok + end, + ok. diff --git a/lib/mnesia/test/mnesia_registry_test.erl b/lib/mnesia/test/mnesia_registry_test.erl new file mode 100644 index 0000000000..2305ef93b7 --- /dev/null +++ b/lib/mnesia/test/mnesia_registry_test.erl @@ -0,0 +1,137 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1998-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(mnesia_registry_test). +-author('[email protected]'). +-compile([export_all]). +-include("mnesia_test_lib.hrl"). + +init_per_testcase(Func, Conf) -> + mnesia_test_lib:init_per_testcase(Func, Conf). + +fin_per_testcase(Func, Conf) -> + mnesia_test_lib:fin_per_testcase(Func, Conf). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +all(doc) -> + ["Test the mnesia_registry module"]; +all(suite) -> + [ + good_dump, + bad_dump + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +good_dump(doc) -> + ["Dump a faked C-node registry"]; +good_dump(suite) -> []; +good_dump(Config) when is_list(Config) -> + [Node] = Nodes = ?acquire_nodes(1, Config), + T1 = gordon, + ?match(ok, mnesia_registry:create_table(T1)), + One = {T1, 1, 0, integer, 0, 10}, + Two = {T1, "two", 3, integer, 0, 20}, + Three = {T1, 3, 0, string, 6, "thirty"}, + ?match(ok, mnesia:dirty_write(One)), + ?match(ok, mnesia:dirty_write(Two)), + ?match(ok, mnesia:dirty_write(Three)), + ?match([One], mnesia:dirty_read({T1, 1})), + ?match([_ | _], dump_registry(Node, T1)), + + NewOne = {T1, 1, 0, integer, 0, 1}, + NewFour = {T1, "4", 1, string, 4, "four"}, + + ?match([NewOne], mnesia:dirty_read({T1, 1})), + ?match([Two], mnesia:dirty_read({T1, "two"})), + ?match([], mnesia:dirty_read({T1, 3})), + ?match([NewFour], mnesia:dirty_read({T1, "4"})), + + T2 = blixt, + ?match({'EXIT', {aborted, {no_exists, _}}}, + mnesia:dirty_read({T2, 1})), + ?match([_ |_], dump_registry(Node, T2)), + + NewOne2 = setelement(1, NewOne, T2), + NewFour2 = setelement(1, NewFour, T2), + + ?match([NewOne2], mnesia:dirty_read({T2, 1})), + ?match([], mnesia:dirty_read({T2, "two"})), + ?match([], mnesia:dirty_read({T2, 3})), + ?match([NewFour2], mnesia:dirty_read({T2, "4"})), + ?match([_One2, NewFour2], lists:sort(restore_registry(Node, T2))), + + ?verify_mnesia(Nodes, []). + +dump_registry(Node, Tab) -> + case rpc:call(Node, mnesia_registry, start_dump, [Tab, self()]) of + Pid when is_pid(Pid) -> + Pid ! {write, 1, 0, integer, 0, 1}, + Pid ! {delete, 3}, + Pid ! {write, "4", 1, string, 4, "four"}, + Pid ! {commit, self()}, + receive + {ok, Pid} -> + [{Tab, "4", 1, string, 4, "four"}, + {Tab, 1, 0, integer, 0, 1}]; + {'EXIT', Pid, Reason} -> + exit(Reason) + end; + {badrpc, Reason} -> + exit(Reason) + end. + +restore_registry(Node, Tab) -> + case rpc:call(Node, mnesia_registry, start_restore, [Tab, self()]) of + {size, Pid, N, _LargestKeySize, _LargestValSize} -> + Pid ! {send_records, self()}, + receive_records(Tab, N); + {badrpc, Reason} -> + exit(Reason) + end. + +receive_records(Tab, N) when N > 0 -> + receive + {restore, KeySize, ValSize, ValType, Key, Val} -> + [{Tab, Key, KeySize, ValType, ValSize, Val} | receive_records(Tab, N -1)]; + {'EXIT', _Pid, Reason} -> + exit(Reason) + end; +receive_records(_Tab, 0) -> + []. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +bad_dump(doc) -> + ["Intentionally fail with the dump of a faked C-node registry"]; +bad_dump(suite) -> []; +bad_dump(Config) when is_list(Config) -> + [Node] = Nodes = ?acquire_nodes(1, Config), + + OldTab = ming, + ?match({'EXIT', {aborted, _}}, mnesia_registry:start_restore(no_tab, self())), + ?match({atomic, ok}, mnesia:create_table(OldTab, [{attributes, [a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q]}])), + ?match({'EXIT',{aborted,{bad_type,_}}}, dump_registry(Node, OldTab)), + ?match(stopped, mnesia:stop()), + + ?match({'EXIT', {aborted, _}}, mnesia_registry:create_table(down_table)), + ?match({'EXIT', {aborted, _}}, mnesia_registry:start_restore(no_tab, self())), + ?match({'EXIT', {aborted, _}}, dump_registry(Node, down_dump)), + + ?verify_mnesia([], Nodes). + diff --git a/lib/mnesia/test/mnesia_schema_recovery_test.erl b/lib/mnesia/test/mnesia_schema_recovery_test.erl new file mode 100644 index 0000000000..387238ae6b --- /dev/null +++ b/lib/mnesia/test/mnesia_schema_recovery_test.erl @@ -0,0 +1,787 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1998-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(mnesia_schema_recovery_test). +-author('[email protected]'). +-compile([export_all]). +-include("mnesia_test_lib.hrl"). + +init_per_testcase(Func, Conf) -> + mnesia_test_lib:init_per_testcase(Func, Conf). + +fin_per_testcase(Func, Conf) -> + mnesia_test_lib:fin_per_testcase(Func, Conf). + +-define(receive_messages(Msgs), receive_messages(Msgs, ?FILE, ?LINE)). + +% First Some debug logging +-define(dgb, true). +-ifdef(dgb). +-define(dl(X, Y), ?verbose("**TRACING: " ++ X ++ "**~n", Y)). +-else. +-define(dl(X, Y), ok). +-endif. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +all(doc) -> + ["Verify recoverabiliy of schema transactions.", + " Verify that a schema transaction", + " can be completed when it has been logged correctly and Mnesia", + " crashed before the log has been dumped. Then the transaction ", + " should be handled during the log dump at startup" + ]; +all(suite) -> + [interrupted_before_log_dump, + interrupted_after_log_dump]. + +interrupted_before_log_dump(suite) -> + [interrupted_before_create_ram, + interrupted_before_create_disc, + interrupted_before_create_disc_only, + interrupted_before_create_nostore, + interrupted_before_delete_ram, + interrupted_before_delete_disc, + interrupted_before_delete_disc_only, + interrupted_before_add_ram, + interrupted_before_add_disc, + interrupted_before_add_disc_only, + interrupted_before_add_kill_copier, + interrupted_before_move_ram, + interrupted_before_move_disc, + interrupted_before_move_disc_only, + interrupted_before_move_kill_copier, + interrupted_before_delcopy_ram, + interrupted_before_delcopy_disc, + interrupted_before_delcopy_disc_only, + interrupted_before_delcopy_kill_copier, + interrupted_before_addindex_ram, + interrupted_before_addindex_disc, + interrupted_before_addindex_disc_only, + interrupted_before_delindex_ram, + interrupted_before_delindex_disc, + interrupted_before_delindex_disc_only, + interrupted_before_change_type_ram2disc, + interrupted_before_change_type_ram2disc_only, + interrupted_before_change_type_disc2ram, + interrupted_before_change_type_disc2disc_only, + interrupted_before_change_type_disc_only2ram, + interrupted_before_change_type_disc_only2disc, + interrupted_before_change_type_other_node, + interrupted_before_change_schema_type %% Change schema table copy type!! + ]. + +interrupted_after_log_dump(suite) -> + [interrupted_after_create_ram, + interrupted_after_create_disc, + interrupted_after_create_disc_only, + interrupted_after_create_nostore, + interrupted_after_delete_ram, + interrupted_after_delete_disc, + interrupted_after_delete_disc_only, + interrupted_after_add_ram, + interrupted_after_add_disc, + interrupted_after_add_disc_only, + interrupted_after_add_kill_copier, + interrupted_after_move_ram, + interrupted_after_move_disc, + interrupted_after_move_disc_only, + interrupted_after_move_kill_copier, + interrupted_after_delcopy_ram, + interrupted_after_delcopy_disc, + interrupted_after_delcopy_disc_only, + interrupted_after_delcopy_kill_copier, + interrupted_after_addindex_ram, + interrupted_after_addindex_disc, + interrupted_after_addindex_disc_only, + interrupted_after_delindex_ram, + interrupted_after_delindex_disc, + interrupted_after_delindex_disc_only, + interrupted_after_change_type_ram2disc, + interrupted_after_change_type_ram2disc_only, + interrupted_after_change_type_disc2ram, + interrupted_after_change_type_disc2disc_only, + interrupted_after_change_type_disc_only2ram, + interrupted_after_change_type_disc_only2disc, + interrupted_after_change_type_other_node, + interrupted_after_change_schema_type %% Change schema table copy type!! + +% interrupted_before_change_access_mode, +% interrupted_before_transform, +% interrupted_before_restore, + ]. + +interrupted_before_create_ram(suite) -> []; +interrupted_before_create_ram(Config) when is_list(Config) -> + KillAt = {mnesia_dumper, dump_schema_op}, + interrupted_create(Config, ram_copies, all, KillAt). + +interrupted_before_create_disc(suite) -> []; +interrupted_before_create_disc(Config) when is_list(Config) -> + KillAt = {mnesia_dumper, dump_schema_op}, + interrupted_create(Config, disc_copies, all, KillAt). + +interrupted_before_create_disc_only(suite) -> []; +interrupted_before_create_disc_only(Config) when is_list(Config) -> + KillAt = {mnesia_dumper, dump_schema_op}, + interrupted_create(Config, disc_only_copies, all, KillAt). + +interrupted_before_create_nostore(suite) -> []; +interrupted_before_create_nostore(Config) when is_list(Config) -> + KillAt = {mnesia_dumper, dump_schema_op}, + interrupted_create(Config, ram_copies, one, KillAt). + +interrupted_after_create_ram(suite) -> []; +interrupted_after_create_ram(Config) when is_list(Config) -> + KillAt = {mnesia_dumper, post_dump}, + interrupted_create(Config, ram_copies, all, KillAt). + +interrupted_after_create_disc(suite) -> []; +interrupted_after_create_disc(Config) when is_list(Config) -> + KillAt = {mnesia_dumper, post_dump}, + interrupted_create(Config, disc_copies, all, KillAt). + +interrupted_after_create_disc_only(suite) -> []; +interrupted_after_create_disc_only(Config) when is_list(Config) -> + KillAt = {mnesia_dumper, post_dump}, + interrupted_create(Config, disc_only_copies, all, KillAt). + +interrupted_after_create_nostore(suite) -> []; +interrupted_after_create_nostore(Config) when is_list(Config) -> + KillAt = {mnesia_dumper, post_dump}, + interrupted_create(Config, ram_copies, one, KillAt). + +%%% After dump don't need debug point +interrupted_create(Config, Type, _Where, {mnesia_dumper, post_dump}) -> + [Node1] = Nodes = ?acquire_nodes(1, [{tc_timeout, timer:seconds(30)} | Config]), + ?match({atomic, ok},mnesia:create_table(itrpt, [{Type, Nodes}])), + ?match({atomic, ok},mnesia:create_table(test, [{disc_copies,[Node1]}])), + ?match(ok, mnesia:dirty_write({itrpt, before, 1})), + ?match(ok, mnesia:dirty_write({test, found_in_log, 1})), + ?match(stopped, mnesia:stop()), + ?match([], mnesia_test_lib:start_mnesia([Node1], [itrpt,test])), + %% Verify + ?match([{test, found_in_log, 1}], mnesia:dirty_read({test, found_in_log})), + case Type of + ram_copies -> + ?match([], mnesia:dirty_read({itrpt, before})); + _ -> + ?match([{itrpt, before, 1}], mnesia:dirty_read({itrpt, before})) + end, + ?verify_mnesia(Nodes, []); +interrupted_create(Config, Type, Where, KillAt) -> + ?is_debug_compiled, + [Node1, Node2] = Nodes = ?acquire_nodes(2, [{tc_timeout, timer:seconds(30)} | Config]), + {success, [A]} = ?start_activities([Node2]), + setup_dbgpoint(KillAt, Node2), + + if %% CREATE TABLE + Where == all -> % tables on both nodes + A ! fun() -> mnesia:create_table(itrpt, [{Type, Nodes}]) end; + true -> % no table on the killed node + A ! fun() -> mnesia:create_table(itrpt, [{Type, [Node1]}]) end + end, + + kill_at_debug(), + ?match([], mnesia_test_lib:start_mnesia([Node2], [itrpt])), + %% Verify + ?match(ok, mnesia:dirty_write({itrpt, before, 1})), + verify_tab(Node1, Node2), + ?verify_mnesia(Nodes, []). + +interrupted_before_delete_ram(suite) -> []; +interrupted_before_delete_ram(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_delete(Config, ram_copies, Debug_Point). +interrupted_before_delete_disc(suite) -> []; +interrupted_before_delete_disc(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_delete(Config, disc_copies, Debug_Point). +interrupted_before_delete_disc_only(suite) -> []; +interrupted_before_delete_disc_only(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_delete(Config, disc_only_copies, Debug_Point). + +interrupted_after_delete_ram(suite) -> []; +interrupted_after_delete_ram(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_delete(Config, ram_copies, Debug_Point). +interrupted_after_delete_disc(suite) -> []; +interrupted_after_delete_disc(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_delete(Config, disc_copies, Debug_Point). +interrupted_after_delete_disc_only(suite) -> []; +interrupted_after_delete_disc_only(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_delete(Config, disc_only_copies, Debug_Point). + +interrupted_delete(Config, Type, KillAt) -> + ?is_debug_compiled, + [Node1, Node2] = Nodes = ?acquire_nodes(2, [{tc_timeout, timer:seconds(30)} | Config]), + Tab = itrpt, + ?match({atomic, ok}, mnesia:create_table(Tab, [{Type, [Node2]}])), + ?match(ok, mnesia:dirty_write({Tab, before, 1})), + {_Alive, Kill} = {Node1, Node2}, + {success, [A]} = ?start_activities([Kill]), + + setup_dbgpoint(KillAt, Kill), + A ! fun() -> mnesia:delete_table(Tab) end, + + kill_at_debug(), + ?match([], mnesia_test_lib:start_mnesia([Node2], [])), + Bad = {badrpc, {'EXIT', {aborted,{no_exists, Tab, all}}}}, + ?match(Bad, rpc:call(Node1, mnesia, table_info, [Tab, all])), + ?match(Bad, rpc:call(Node2, mnesia, table_info, [Tab, all])), + ?verify_mnesia(Nodes, []). + +interrupted_before_add_ram(suite) -> []; +interrupted_before_add_ram(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_add(Config, ram_copies, kill_reciever, Debug_Point). +interrupted_before_add_disc(suite) -> []; +interrupted_before_add_disc(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_add(Config, disc_copies, kill_reciever, Debug_Point). +interrupted_before_add_disc_only(suite) -> []; +interrupted_before_add_disc_only(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_add(Config, disc_only_copies, kill_reciever, Debug_Point). +interrupted_before_add_kill_copier(suite) -> []; +interrupted_before_add_kill_copier(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_add(Config, ram_copies, kill_copier, Debug_Point). + +interrupted_after_add_ram(suite) -> []; +interrupted_after_add_ram(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_add(Config, ram_copies, kill_reciever, Debug_Point). +interrupted_after_add_disc(suite) -> []; +interrupted_after_add_disc(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_add(Config, disc_copies, kill_reciever, Debug_Point). +interrupted_after_add_disc_only(suite) -> []; +interrupted_after_add_disc_only(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_add(Config, disc_only_copies, kill_reciever, Debug_Point). +interrupted_after_add_kill_copier(suite) -> []; +interrupted_after_add_kill_copier(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_add(Config, ram_copies, kill_copier, Debug_Point). + +%%% After dump don't need debug point +interrupted_add(Config, Type, _Where, {mnesia_dumper, post_dump}) -> + [Node1, Node2] = Nodes = + ?acquire_nodes(2, [{tc_timeout, timer:seconds(30)} | Config]), + Tab = itrpt, + ?match({atomic, ok}, mnesia:create_table(Tab, [{Type, [Node2]}, {local_content,true}])), + ?match({atomic, ok},mnesia:create_table(test, [{disc_copies,[Node1]}])), + ?match({atomic, ok}, mnesia:add_table_copy(Tab, Node1, Type)), + ?match(ok, mnesia:dirty_write({itrpt, before, 1})), + ?match(ok, mnesia:dirty_write({test, found_in_log, 1})), + ?match(stopped, mnesia:stop()), + ?match([], mnesia_test_lib:start_mnesia([Node1], [itrpt,test])), + %% Verify + ?match([{test, found_in_log, 1}], mnesia:dirty_read({test, found_in_log})), + case Type of + ram_copies -> + ?match([], mnesia:dirty_read({itrpt, before})); + _ -> + ?match([{itrpt, before, 1}], mnesia:dirty_read({itrpt, before})) + end, + ?verify_mnesia(Nodes, []); +interrupted_add(Config, Type, Who, KillAt) -> + ?is_debug_compiled, + [Node1, Node2] = Nodes = + ?acquire_nodes(2, [{tc_timeout, timer:seconds(30)} | Config]), + {_Alive, Kill} = + if Who == kill_reciever -> + {Node1, Node2}; + true -> + {Node2, Node1} + end, + {success, [A]} = ?start_activities([Kill]), + Tab = itrpt, + ?match({atomic, ok}, mnesia:create_table(Tab, [{Type, [Node1]}])), + ?match(ok, mnesia:dirty_write({Tab, before, 1})), + + setup_dbgpoint(KillAt, Kill), + + A ! fun() -> mnesia:add_table_copy(Tab, Node2, Type) end, + kill_at_debug(), + ?match([], mnesia_test_lib:start_mnesia([Kill], [itrpt])), + verify_tab(Node1, Node2), + ?verify_mnesia(Nodes, []). + +interrupted_before_move_ram(suite) -> []; +interrupted_before_move_ram(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_move(Config, ram_copies, kill_reciever, Debug_Point). +interrupted_before_move_disc(suite) -> []; +interrupted_before_move_disc(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_move(Config, disc_copies, kill_reciever, Debug_Point). +interrupted_before_move_disc_only(suite) -> []; +interrupted_before_move_disc_only(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_move(Config, disc_only_copies, kill_reciever, Debug_Point). +interrupted_before_move_kill_copier(suite) -> []; +interrupted_before_move_kill_copier(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_move(Config, ram_copies, kill_copier, Debug_Point). + +interrupted_after_move_ram(suite) -> []; +interrupted_after_move_ram(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_move(Config, ram_copies, kill_reciever, Debug_Point). +interrupted_after_move_disc(suite) -> []; +interrupted_after_move_disc(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_move(Config, disc_copies, kill_reciever, Debug_Point). +interrupted_after_move_disc_only(suite) -> []; +interrupted_after_move_disc_only(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_move(Config, disc_only_copies, kill_reciever, Debug_Point). +interrupted_after_move_kill_copier(suite) -> []; +interrupted_after_move_kill_copier(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_move(Config, ram_copies, kill_copier, Debug_Point). + +%%% After dump don't need debug point +interrupted_move(Config, Type, _Where, {mnesia_dumper, post_dump}) -> + [Node1, Node2] = Nodes = + ?acquire_nodes(2, [{tc_timeout, timer:seconds(30)} | Config]), + Tab = itrpt, + ?match({atomic, ok},mnesia:create_table(test, [{disc_copies,[Node1]}])), + ?match({atomic, ok}, mnesia:create_table(Tab, [{Type, [Node1]}])), + ?match(ok, mnesia:dirty_write({itrpt, before, 1})), + ?match({atomic, ok}, mnesia:move_table_copy(Tab, Node1, Node2)), + ?match(ok, mnesia:dirty_write({itrpt, aFter, 1})), + ?match(ok, mnesia:dirty_write({test, found_in_log, 1})), + ?match(stopped, mnesia:stop()), + ?match([], mnesia_test_lib:start_mnesia([Node1], [itrpt,test])), + %% Verify + ?match([{test, found_in_log, 1}], mnesia:dirty_read({test, found_in_log})), + ?match([{itrpt, before, 1}], mnesia:dirty_read({itrpt, before})), + ?match([{itrpt, aFter, 1}], mnesia:dirty_read({itrpt, aFter})), + ?verify_mnesia(Nodes, []); +interrupted_move(Config, Type, Who, KillAt) -> + ?is_debug_compiled, + [Node1, Node2] = Nodes = + ?acquire_nodes(2, [{tc_timeout, timer:seconds(30)} | Config]), + Tab = itrpt, + ?match({atomic, ok}, mnesia:create_table(Tab, [{Type, [Node1]}])), + ?match(ok, mnesia:dirty_write({Tab, before, 1})), + + {_Alive, Kill} = + if Who == kill_reciever -> + if Type == ram_copies -> + {atomic, ok} = mnesia:dump_tables([Tab]); + true -> + ignore + end, + {Node1, Node2}; + true -> + {Node2, Node1} + end, + + {success, [A]} = ?start_activities([Kill]), + + setup_dbgpoint(KillAt, Kill), + A ! fun() -> mnesia:move_table_copy(Tab, Node1, Node2) end, + kill_at_debug(), + ?match([], mnesia_test_lib:start_mnesia([Kill], [itrpt])), + verify_tab(Node1, Node2), + ?verify_mnesia(Nodes, []). + +interrupted_before_delcopy_ram(suite) -> []; +interrupted_before_delcopy_ram(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_delcopy(Config, ram_copies, kill_reciever, Debug_Point). +interrupted_before_delcopy_disc(suite) -> []; +interrupted_before_delcopy_disc(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_delcopy(Config, disc_copies, kill_reciever, Debug_Point). +interrupted_before_delcopy_disc_only(suite) -> []; +interrupted_before_delcopy_disc_only(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_delcopy(Config, disc_only_copies, kill_reciever, Debug_Point). +interrupted_before_delcopy_kill_copier(suite) -> []; +interrupted_before_delcopy_kill_copier(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_delcopy(Config, ram_copies, kill_copier, Debug_Point). + +interrupted_after_delcopy_ram(suite) -> []; +interrupted_after_delcopy_ram(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_delcopy(Config, ram_copies, kill_reciever, Debug_Point). +interrupted_after_delcopy_disc(suite) -> []; +interrupted_after_delcopy_disc(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_delcopy(Config, disc_copies, kill_reciever, Debug_Point). +interrupted_after_delcopy_disc_only(suite) -> []; +interrupted_after_delcopy_disc_only(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_delcopy(Config, disc_only_copies, kill_reciever, Debug_Point). +interrupted_after_delcopy_kill_copier(suite) -> []; +interrupted_after_delcopy_kill_copier(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_delcopy(Config, ram_copies, kill_copier, Debug_Point). + + +%%% After dump don't need debug point +interrupted_delcopy(Config, Type, _Where, {mnesia_dumper, post_dump}) -> + [Node1, Node2] = Nodes = + ?acquire_nodes(2, [{tc_timeout, timer:seconds(30)} | Config]), + Tab = itrpt, + ?match({atomic, ok},mnesia:create_table(test, [{disc_copies,[Node1]}])), + ?match({atomic, ok}, mnesia:create_table(Tab, [{Type, [Node1,Node2]}])), + ?match({atomic, ok}, mnesia:del_table_copy(Tab, Node1)), + ?match(ok, mnesia:dirty_write({test, found_in_log, 1})), + ?match(stopped, mnesia:stop()), + ?match([], mnesia_test_lib:start_mnesia([Node1], [test])), + %% Verify + ?match([{test, found_in_log, 1}], mnesia:dirty_read({test, found_in_log})), + ?match([Node2], mnesia:table_info(itrpt,Type)), + ?verify_mnesia(Nodes, []); +interrupted_delcopy(Config, Type, Who, KillAt) -> + ?is_debug_compiled, + [Node1, Node2] = Nodes = + ?acquire_nodes(2, [{tc_timeout, timer:seconds(30)} | Config]), + Tab = itrpt, + ?match({atomic, ok}, mnesia:create_table(Tab, [{Type, [Node1, Node2]}])), + ?match(ok, mnesia:dirty_write({Tab, before, 1})), + + {_Alive, Kill} = + if Who == kill_reciever -> + {Node1, Node2}; + true -> + if + Type == ram_copies -> + {atomic, ok} = mnesia:dump_tables([Tab]); + true -> + ignore + end, + {Node2, Node1} + end, + + {success, [A]} = ?start_activities([Kill]), + setup_dbgpoint(KillAt, Kill), + A ! fun() -> mnesia:del_table_copy(Tab, Node2) end, + kill_at_debug(), + ?match([], mnesia_test_lib:start_mnesia([Kill], [itrpt])), + verify_tab(Node1, Node2), + ?verify_mnesia(Nodes, []). + +interrupted_before_addindex_ram(suite) -> []; +interrupted_before_addindex_ram(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_addindex(Config, ram_copies, Debug_Point). +interrupted_before_addindex_disc(suite) -> []; +interrupted_before_addindex_disc(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_addindex(Config, disc_copies, Debug_Point). +interrupted_before_addindex_disc_only(suite) -> []; +interrupted_before_addindex_disc_only(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_addindex(Config, disc_only_copies, Debug_Point). + +interrupted_after_addindex_ram(suite) -> []; +interrupted_after_addindex_ram(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_addindex(Config, ram_copies, Debug_Point). +interrupted_after_addindex_disc(suite) -> []; +interrupted_after_addindex_disc(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_addindex(Config, disc_copies, Debug_Point). +interrupted_after_addindex_disc_only(suite) -> []; +interrupted_after_addindex_disc_only(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_addindex(Config, disc_only_copies, Debug_Point). + + +%%% After dump don't need debug point +interrupted_addindex(Config, Type, {mnesia_dumper, post_dump}) -> + [Node1] = Nodes = ?acquire_nodes(1, [{tc_timeout, timer:seconds(30)} | Config]), + Tab = itrpt, + ?match({atomic,ok},mnesia:create_table(Tab, [{Type, Nodes}])), + ?match({atomic,ok},mnesia:create_table(test, [{disc_copies,[Node1]}])), + ?match({atomic,ok}, mnesia:add_table_index(Tab, val)), + ?match(ok, mnesia:dirty_write({itrpt, before, 1})), + ?match(ok, mnesia:dirty_write({test, found_in_log, 1})), + ?match(stopped, mnesia:stop()), + ?match([], mnesia_test_lib:start_mnesia([Node1], [itrpt,test])), + %% Verify + ?match([{test, found_in_log, 1}], mnesia:dirty_read({test, found_in_log})), + case Type of + ram_copies -> + ?match([], mnesia:dirty_index_read(itrpt, 1, val)); + _ -> + ?match([{itrpt, before, 1}], mnesia:dirty_index_read(itrpt, 1, val)) + end, + ?verify_mnesia(Nodes, []); +interrupted_addindex(Config, Type, KillAt) -> + ?is_debug_compiled, + [Node1, Node2] = Nodes = ?acquire_nodes(2, [{tc_timeout, timer:seconds(30)} | Config]), + Tab = itrpt, + ?match({atomic, ok}, mnesia:create_table(Tab, [{Type, [Node1]}])), + ?match(ok, mnesia:dirty_write({Tab, before, 1})), + {_Alive, Kill} = {Node1, Node2}, + {success, [A]} = ?start_activities([Kill]), + + setup_dbgpoint(KillAt, Kill), + A ! fun() -> mnesia:add_table_index(Tab, val) end, + kill_at_debug(), + ?match([], mnesia_test_lib:start_mnesia([Node2], [])), + + verify_tab(Node1, Node2), + ?match([{Tab, b, a}, {Tab, a, a}], + rpc:call(Node1, mnesia, dirty_index_read, [itrpt, a, val])), + ?match([{Tab, b, a}, {Tab, a, a}], + rpc:call(Node2, mnesia, dirty_index_read, [itrpt, a, val])), + ?verify_mnesia(Nodes, []). + +interrupted_before_delindex_ram(suite) -> []; +interrupted_before_delindex_ram(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_delindex(Config, ram_copies, Debug_Point). +interrupted_before_delindex_disc(suite) -> []; +interrupted_before_delindex_disc(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_delindex(Config, disc_copies, Debug_Point). +interrupted_before_delindex_disc_only(suite) -> []; +interrupted_before_delindex_disc_only(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_delindex(Config, disc_only_copies, Debug_Point). + +interrupted_after_delindex_ram(suite) -> []; +interrupted_after_delindex_ram(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_delindex(Config, ram_copies, Debug_Point). +interrupted_after_delindex_disc(suite) -> []; +interrupted_after_delindex_disc(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_delindex(Config, disc_copies, Debug_Point). +interrupted_after_delindex_disc_only(suite) -> []; +interrupted_after_delindex_disc_only(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_delindex(Config, disc_only_copies, Debug_Point). + +%%% After dump don't need debug point +interrupted_delindex(Config, Type, {mnesia_dumper, post_dump}) -> + [Node1] = Nodes = ?acquire_nodes(1, [{tc_timeout, timer:seconds(30)} | Config]), + Tab = itrpt, + ?match({atomic,ok},mnesia:create_table(Tab, [{Type, Nodes},{index,[val]}])), + ?match({atomic,ok},mnesia:create_table(test, [{disc_copies,[Node1]}])), + ?match({atomic,ok}, mnesia:del_table_index(Tab, val)), + ?match(ok, mnesia:dirty_write({itrpt, before, 1})), + ?match(ok, mnesia:dirty_write({test, found_in_log, 1})), + ?match(stopped, mnesia:stop()), + ?match([], mnesia_test_lib:start_mnesia([Node1], [itrpt,test])), + %% Verify + ?match([{test, found_in_log, 1}], mnesia:dirty_read({test, found_in_log})), + ?match({'EXIT',{aborted,{badarg,_}}}, mnesia:dirty_index_read(itrpt, 1, val)), + ?verify_mnesia(Nodes, []); + +interrupted_delindex(Config, Type, KillAt) -> + ?is_debug_compiled, + [Node1, Node2] = Nodes = ?acquire_nodes(2, [{tc_timeout, timer:seconds(30)} | Config]), + Tab = itrpt, + ?match({atomic, ok}, mnesia:create_table(Tab, [{index, [val]}, + {Type, [Node1]}])), + ?match(ok, mnesia:dirty_write({Tab, before, 1})), + {_Alive, Kill} = {Node1, Node2}, + {success, [A]} = ?start_activities([Kill]), + setup_dbgpoint(KillAt, Kill), + A ! fun() -> mnesia:del_table_index(Tab, val) end, + kill_at_debug(), + ?match([], mnesia_test_lib:start_mnesia([Node2], [])), + verify_tab(Node1, Node2), + ?match({badrpc, _}, rpc:call(Node1, mnesia, dirty_index_read, [itrpt, a, val])), + ?match({badrpc, _}, rpc:call(Node2, mnesia, dirty_index_read, [itrpt, a, val])), + ?match([], rpc:call(Node1, mnesia, table_info, [Tab, index])), + ?match([], rpc:call(Node2, mnesia, table_info, [Tab, index])), + ?verify_mnesia(Nodes, []). + +interrupted_before_change_type_ram2disc(suite) -> []; +interrupted_before_change_type_ram2disc(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_change_type(Config, ram_copies, disc_copies, changer, Debug_Point). +interrupted_before_change_type_ram2disc_only(suite) -> []; +interrupted_before_change_type_ram2disc_only(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_change_type(Config, ram_copies, disc_only_copies, changer, Debug_Point). +interrupted_before_change_type_disc2ram(suite) -> []; +interrupted_before_change_type_disc2ram(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_change_type(Config, disc_copies, ram_copies, changer, Debug_Point). +interrupted_before_change_type_disc2disc_only(suite) -> []; +interrupted_before_change_type_disc2disc_only(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_change_type(Config, disc_copies, disc_only_copies, changer, Debug_Point). +interrupted_before_change_type_disc_only2ram(suite) -> []; +interrupted_before_change_type_disc_only2ram(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_change_type(Config, disc_only_copies, ram_copies, changer, Debug_Point). +interrupted_before_change_type_disc_only2disc(suite) -> []; +interrupted_before_change_type_disc_only2disc(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_change_type(Config, disc_only_copies, disc_copies, changer, Debug_Point). +interrupted_before_change_type_other_node(suite) -> []; +interrupted_before_change_type_other_node(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_change_type(Config, ram_copies, disc_copies, the_other_one, Debug_Point). + +interrupted_after_change_type_ram2disc(suite) -> []; +interrupted_after_change_type_ram2disc(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_change_type(Config, ram_copies, disc_copies, changer, Debug_Point). +interrupted_after_change_type_ram2disc_only(suite) -> []; +interrupted_after_change_type_ram2disc_only(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_change_type(Config, ram_copies, disc_only_copies, changer, Debug_Point). +interrupted_after_change_type_disc2ram(suite) -> []; +interrupted_after_change_type_disc2ram(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_change_type(Config, disc_copies, ram_copies, changer, Debug_Point). +interrupted_after_change_type_disc2disc_only(suite) -> []; +interrupted_after_change_type_disc2disc_only(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_change_type(Config, disc_copies, disc_only_copies, changer, Debug_Point). +interrupted_after_change_type_disc_only2ram(suite) -> []; +interrupted_after_change_type_disc_only2ram(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_change_type(Config, disc_only_copies, ram_copies, changer, Debug_Point). +interrupted_after_change_type_disc_only2disc(suite) -> []; +interrupted_after_change_type_disc_only2disc(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_change_type(Config, disc_only_copies, disc_copies, changer, Debug_Point). +interrupted_after_change_type_other_node(suite) -> []; +interrupted_after_change_type_other_node(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_change_type(Config, ram_copies, disc_copies, the_other_one, Debug_Point). + +interrupted_change_type(Config, FromType, ToType, Who, KillAt) -> + ?is_debug_compiled, + [Node1, Node2] = Nodes = ?acquire_nodes(2, [{tc_timeout, timer:seconds(30)} | Config]), + Tab = itrpt, + ?match({atomic, ok}, mnesia:create_table(Tab, [{FromType, [Node2, Node1]}])), + ?match(ok, mnesia:dirty_write({Tab, before, 1})), + + {_Alive, Kill} = + if Who == changer -> {Node1, Node2}; + true -> {Node2, Node1} + end, + + {success, [A]} = ?start_activities([Kill]), + setup_dbgpoint(KillAt, Kill), + A ! fun() -> mnesia:change_table_copy_type(Tab, Node2, ToType) end, + kill_at_debug(), + ?match([], mnesia_test_lib:start_mnesia(Nodes, [itrpt])), + verify_tab(Node1, Node2), + ?match(FromType, rpc:call(Node1, mnesia, table_info, [Tab, storage_type])), + ?match(ToType, rpc:call(Node2, mnesia, table_info, [Tab, storage_type])), + ?verify_mnesia(Nodes, []). + +interrupted_before_change_schema_type(suite) -> []; +interrupted_before_change_schema_type(Config) when is_list(Config) -> + KillAt = {mnesia_dumper, dump_schema_op}, + interrupted_change_schema_type(Config, KillAt). + +interrupted_after_change_schema_type(suite) -> []; +interrupted_after_change_schema_type(Config) when is_list(Config) -> + KillAt = {mnesia_dumper, post_dump}, + interrupted_change_schema_type(Config, KillAt). + +-define(cleanup(N, Config), + mnesia_test_lib:prepare_test_case([{reload_appls, [mnesia]}], + N, Config, ?FILE, ?LINE)). + +interrupted_change_schema_type(Config, KillAt) -> + ?is_debug_compiled, + [Node1, Node2] = Nodes = ?acquire_nodes(2, [{tc_timeout, timer:seconds(30)} | Config]), + + Tab = itrpt, + ?match({atomic, ok}, mnesia:create_table(Tab, [{ram_copies, [Node2, Node1]}])), + ?match(ok, mnesia:dirty_write({Tab, before, 1})), + + {success, [A]} = ?start_activities([Node2]), + setup_dbgpoint(KillAt, Node2), + + A ! fun() -> mnesia:change_table_copy_type(schema, Node2, ram_copies) end, + kill_at_debug(), + ?match(ok, rpc:call(Node2, mnesia, start, [[{extra_db_nodes, [Node1, Node2]}]])), + ?match(ok, rpc:call(Node2, mnesia, wait_for_tables, [[itrpt, schema], 2000])), + ?match(disc_copies, rpc:call(Node1, mnesia, table_info, [schema, storage_type])), + ?match(ram_copies, rpc:call(Node2, mnesia, table_info, [schema, storage_type])), + + %% Go back to disc_copies !! + {success, [B]} = ?start_activities([Node2]), + setup_dbgpoint(KillAt, Node2), + B ! fun() -> mnesia:change_table_copy_type(schema, Node2, disc_copies) end, + kill_at_debug(), + + ?match(ok, rpc:call(Node2, mnesia, start, [[{extra_db_nodes, [Node1, Node2]}]])), + ?match(ok, rpc:call(Node2, mnesia, wait_for_tables, [[itrpt, schema], 2000])), + ?match(disc_copies, rpc:call(Node1, mnesia, table_info, [schema, storage_type])), + ?match(disc_copies, rpc:call(Node2, mnesia, table_info, [schema, storage_type])), + + ?verify_mnesia(Nodes, []), + ?cleanup(2, Config). + +%%% Helpers +verify_tab(Node1, Node2) -> + ?match({atomic, ok}, + rpc:call(Node1, mnesia, transaction, [fun() -> mnesia:dirty_write({itrpt, a, a}) end])), + ?match({atomic, ok}, + rpc:call(Node2, mnesia, transaction, [fun() -> mnesia:dirty_write({itrpt, b, a}) end])), + ?match([{itrpt,a,a}], rpc:call(Node1, mnesia, dirty_read, [{itrpt, a}])), + ?match([{itrpt,a,a}], rpc:call(Node2, mnesia, dirty_read, [{itrpt, a}])), + ?match([{itrpt,b,a}], rpc:call(Node1, mnesia, dirty_read, [{itrpt, b}])), + ?match([{itrpt,b,a}], rpc:call(Node2, mnesia, dirty_read, [{itrpt, b}])), + ?match([{itrpt,before,1}], rpc:call(Node1, mnesia, dirty_read, [{itrpt, before}])), + ?match([{itrpt,before,1}], rpc:call(Node2, mnesia, dirty_read, [{itrpt, before}])). + +setup_dbgpoint(DbgPoint, Where) -> + Self = self(), + TestFun = fun(_, [InitBy]) -> + case InitBy of + schema_prepare -> + ignore; + schema_begin -> + ignore; + _Other -> + ?deactivate_debug_fun(DbgPoint), + unlink(Self), + Self ! {fun_done, node()}, + timer:sleep(infinity) + end + end, + %% Kill when debug has been reached + ?remote_activate_debug_fun(Where, DbgPoint, TestFun, []). + +kill_at_debug() -> + %% Wait till it's killed + receive + {fun_done, Node} -> + ?match([], mnesia_test_lib:kill_mnesia([Node])) + after + timer:minutes(1) -> ?error("Timeout in kill_at_debug", []) + end. + diff --git a/lib/mnesia/test/mnesia_test_lib.erl b/lib/mnesia/test/mnesia_test_lib.erl new file mode 100644 index 0000000000..1e98f017f7 --- /dev/null +++ b/lib/mnesia/test/mnesia_test_lib.erl @@ -0,0 +1,1058 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +%%% Author: Hakan Mattsson [email protected] +%%% Purpose: Test case support library +%%% +%%% This test suite may be run as a part of the Grand Test Suite +%%% of Erlang. The Mnesia test suite is structured in a hierarchy. +%%% Each test case is implemented as an exported function with arity 1. +%%% Test case identifiers must have the following syntax: {Module, Function}. +%%% +%%% The driver of the test suite runs in two passes as follows: +%%% first the test case function is invoked with the atom 'suite' as +%%% single argument. The returned value is treated as a list of sub +%%% test cases. If the list of sub test cases is [] the test case +%%% function is invoked again, this time with a list of nodes as +%%% argument. If the list of sub test cases is not empty, the test +%%% case driver applies the algorithm recursively on each element +%%% in the list. +%%% +%%% All test cases are written in such a manner +%%% that they start to invoke ?acquire_nodes(X, Config) +%%% in order to prepare the test case execution. When that is +%%% done, the test machinery ensures that at least X number +%%% of nodes are connected to each other. If too few nodes was +%%% specified in the Config, the test case is skipped. If there +%%% was enough node names in the Config, X of them are selected +%%% and if some of them happens to be down they are restarted +%%% via the slave module. When all nodes are up and running a +%%% disk resident schema is created on all nodes and Mnesia is +%%% started a on all nodes. This means that all test cases may +%%% assume that Mnesia is up and running on all acquired nodes. +%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% +%%% doc(TestCases) +%%% +%%% Generates a test spec from parts of the test case structure +%%% +%%% struct(TestCases) +%%% +%%% Prints out the test case structure +%%% +%%% test(TestCases) +%%% +%%% Run parts of the test suite. Uses test/2. +%%% Reads Config from mnesia_test.config and starts them if neccessary. +%%% Kills Mnesia and wipes out the Mnesia directories as a starter. +%%% +%%% test(TestCases, Config) +%%% +%%% Run parts of the test suite on the given Nodes, +%%% assuming that the nodes are up and running. +%%% Kills Mnesia and wipes out the Mnesia directories as a starter. +%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-module(mnesia_test_lib). +-author('[email protected]'). +-export([ + log/2, + log/4, + verbose/4, + default_config/0, + diskless/1, + eval_test_case/3, + test_driver/2, + test_case_evaluator/3, + activity_evaluator/1, + flush/0, + pick_msg/0, + start_activities/1, + start_transactions/1, + start_transactions/2, + start_sync_transactions/1, + start_sync_transactions/2, + sync_trans_tid_serial/1, + prepare_test_case/5, + select_nodes/4, + init_nodes/3, + error/4, + slave_start_link/0, + slave_start_link/1, + slave_sup/0, + + start_mnesia/1, + start_mnesia/2, + start_appls/2, + start_appls/3, + start_wait/2, + storage_type/2, + stop_mnesia/1, + stop_appls/2, + sort/1, + kill_mnesia/1, + kill_appls/2, + verify_mnesia/4, + shutdown/0, + verify_replica_location/5, + lookup_config/2, + sync_tables/2, + remote_start/3, + remote_stop/1, + remote_kill/1, + + reload_appls/2, + + remote_activate_debug_fun/6, + do_remote_activate_debug_fun/6, + + test/1, + test/2, + doc/1, + struct/1, + init_per_testcase/2, + fin_per_testcase/2, + kill_tc/2 + ]). + +-include("mnesia_test_lib.hrl"). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% included for test server compatibility +%% assume that all test cases only takes Config as sole argument +init_per_testcase(_Func, Config) -> + global:register_name(mnesia_global_logger, group_leader()), + Config. + +fin_per_testcase(_Func, Config) -> + global:unregister_name(mnesia_global_logger), + %% Nodes = select_nodes(all, Config, ?FILE, ?LINE), + %% rpc:multicall(Nodes, mnesia, lkill, []), + Config. + +%% Use ?log(Format, Args) as wrapper +log(Format, Args, LongFile, Line) -> + File = filename:basename(LongFile), + Format2 = lists:concat([File, "(", Line, ")", ": ", Format]), + log(Format2, Args). + +log(Format, Args) -> + case global:whereis_name(mnesia_global_logger) of + undefined -> + io:format(user, Format, Args); + Pid -> + io:format(Pid, Format, Args) + end. + +verbose(Format, Args, File, Line) -> + Arg = mnesia_test_verbose, + case get(Arg) of + false -> + ok; + true -> + log(Format, Args, File, Line); + undefined -> + case init:get_argument(Arg) of + {ok, List} when is_list(List) -> + case lists:last(List) of + ["true"] -> + put(Arg, true), + log(Format, Args, File, Line); + _ -> + put(Arg, false), + ok + end; + _ -> + put(Arg, false), + ok + end + end. + +-record('REASON', {file, line, desc}). + +error(Format, Args, File, Line) -> + global:send(mnesia_global_logger, {failed, File, Line}), + Fail = #'REASON'{file = filename:basename(File), + line = Line, + desc = Args}, + case global:whereis_name(mnesia_test_case_sup) of + undefined -> + ignore; + Pid -> + Pid ! Fail +%% global:send(mnesia_test_case_sup, Fail), + end, + log("<>ERROR<>~n" ++ Format, Args, File, Line). + +storage_type(Default, Config) -> + case diskless(Config) of + true -> + ram_copies; + false -> + Default + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +default_config() -> + [{nodes, default_nodes()}]. + +default_nodes() -> + mk_nodes(3, []). + +mk_nodes(0, Nodes) -> + Nodes; +mk_nodes(N, []) -> + mk_nodes(N - 1, [node()]); +mk_nodes(N, Nodes) when N > 0 -> + Head = hd(Nodes), + [Name, Host] = node_to_name_and_host(Head), + Nodes ++ [mk_node(I, Name, Host) || I <- lists:seq(1, N)]. + +mk_node(N, Name, Host) -> + list_to_atom(lists:concat([Name ++ integer_to_list(N) ++ "@" ++ Host])). + +slave_start_link() -> + slave_start_link(node()). + +slave_start_link(Node) -> + [Local, Host] = node_to_name_and_host(Node), + {Mega, Sec, Micro} = erlang:now(), + List = [Local, "_", Mega, "_", Sec, "_", Micro], + Name = list_to_atom(lists:concat(List)), + slave_start_link(list_to_atom(Host), Name). + +slave_start_link(Host, Name) -> + slave_start_link(Host, Name, 10). + +slave_start_link(Host, Name, Retries) -> + Debug = atom_to_list(mnesia:system_info(debug)), + Args = "-mnesia debug " ++ Debug ++ + " -pa " ++ + filename:dirname(code:which(?MODULE)) ++ + " -pa " ++ + filename:dirname(code:which(mnesia)), + case starter(Host, Name, Args) of + {ok, NewNode} -> + ?match(pong, net_adm:ping(NewNode)), + {ok, Cwd} = file:get_cwd(), + Path = code:get_path(), + ok = rpc:call(NewNode, file, set_cwd, [Cwd]), + true = rpc:call(NewNode, code, set_path, [Path]), + spawn_link(NewNode, ?MODULE, slave_sup, []), + rpc:multicall([node() | nodes()], global, sync, []), + {ok, NewNode}; + {error, Reason} when Retries == 0-> + {error, Reason}; + {error, Reason} -> + io:format("Could not start slavenode ~p ~p retrying~n", + [{Host, Name, Args}, Reason]), + timer:sleep(500), + slave_start_link(Host, Name, Retries - 1) + end. + +starter(Host, Name, Args) -> + case os:type() of + vxworks -> + X = test_server:start_node(Name, slave, [{args,Args}]), + timer:sleep(5000), + X; + _ -> + slave:start(Host, Name, Args) + end. + +slave_sup() -> + process_flag(trap_exit, true), + receive + {'EXIT', _, _} -> + case os:type() of + vxworks -> + erlang:halt(); + _ -> + ignore + end + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Index the test case structure + +doc(TestCases) when is_list(TestCases) -> + test(TestCases, suite), + SuiteFname = "index.html", + io:format("Generating HTML test specification to file: ~s~n", + [SuiteFname]), + {ok, Fd} = file:open(SuiteFname, [write]), + io:format(Fd, "<TITLE>Test specification for ~p</TITLE>.~n", [TestCases]), + io:format(Fd, "<H1>Test specification for ~p</H1>~n", [TestCases]), + io:format(Fd, "Test cases which not are implemented yet are written in <B>bold face</B>.~n~n", []), + + io:format(Fd, "<BR><BR>~n", []), + io:format(Fd, "~n<DL>~n", []), + do_doc(Fd, TestCases, []), + io:format(Fd, "</DL>~n", []), + file:close(Fd); +doc(TestCases) -> + doc([TestCases]). + +do_doc(Fd, [H | T], List) -> + case H of + {Module, TestCase} when is_atom(Module), is_atom(TestCase) -> + do_doc(Fd, Module, TestCase, List); + TestCase when is_atom(TestCase), List == [] -> + do_doc(Fd, mnesia_SUITE, TestCase, List); + TestCase when is_atom(TestCase) -> + do_doc(Fd, hd(List), TestCase, List) + end, + do_doc(Fd, T, List); +do_doc(_, [], _) -> + ok. + +do_doc(Fd, Module, TestCase, List) -> + case get_suite(Module, TestCase) of + [] -> + %% Implemented leaf test case + Head = ?flat_format("<A HREF=~p.html#~p_1>{~p, ~p}</A>}", + [Module, TestCase, Module, TestCase]), + print_doc(Fd, Module, TestCase, Head); + Suite when is_list(Suite) -> + %% Test suite + Head = ?flat_format("{~p, ~p}", [Module, TestCase]), + print_doc(Fd, Module, TestCase, Head), + io:format(Fd, "~n<DL>~n", []), + do_doc(Fd, Suite, [Module | List]), + io:format(Fd, "</DL>~n", []); + 'NYI' -> + %% Not yet implemented + Head = ?flat_format("<B>{~p, ~p}</B>", [Module, TestCase]), + print_doc(Fd, Module, TestCase, Head) + end. + +print_doc(Fd, Mod, Fun, Head) -> + case catch (apply(Mod, Fun, [doc])) of + {'EXIT', _} -> + io:format(Fd, "<DT>~s</DT>~n", [Head]); + Doc when is_list(Doc) -> + io:format(Fd, "<DT><U>~s</U><BR><DD>~n", [Head]), + print_rows(Fd, Doc), + io:format(Fd, "</DD><BR><BR>~n", []) + end. + +print_rows(_Fd, []) -> + ok; +print_rows(Fd, [H | T]) when is_list(H) -> + io:format(Fd, "~s~n", [H]), + print_rows(Fd, T); +print_rows(Fd, [H | T]) when is_integer(H) -> + io:format(Fd, "~s~n", [[H | T]]). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Show the test case structure + +struct(TestCases) -> + T = test(TestCases, suite), + struct(T, ""). + +struct({Module, TestCase}, Indentation) + when is_atom(Module), is_atom(TestCase) -> + log("~s{~p, ~p} ...~n", [Indentation, Module, TestCase]); +struct({Module, TestCase, Other}, Indentation) + when is_atom(Module), is_atom(TestCase) -> + log("~s{~p, ~p} ~p~n", [Indentation, Module, TestCase, Other]); +struct([], _) -> + ok; +struct([TestCase | TestCases], Indentation) -> + struct(TestCase, Indentation), + struct(TestCases, Indentation); +struct({TestCase, []}, Indentation) -> + struct(TestCase, Indentation); +struct({TestCase, SubTestCases}, Indentation) when is_list(SubTestCases) -> + struct(TestCase, Indentation), + struct(SubTestCases, Indentation ++ " "). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Execute the test cases + +test(TestCases) -> + test(TestCases, []). + +test(TestCases, suite) when is_list(TestCases) -> + test_driver(TestCases, suite); +test(TestCases, Config) when is_list(TestCases) -> + D1 = lists:duplicate(10, $=), + D2 = lists:duplicate(10, $ ), + log("~n~s TEST CASES: ~p~n ~sCONFIG: ~p~n~n", [D1, TestCases, D2, Config]), + test_driver(TestCases, Config); +test(TestCase, Config) -> + test([TestCase], Config). + +test_driver([], _Config) -> + []; +test_driver([T|TestCases], Config) -> + L1 = test_driver(T, Config), + L2 = test_driver(TestCases, Config), + [L1|L2]; +test_driver({Module, TestCases}, Config) when is_list(TestCases)-> + test_driver(default_module(Module, TestCases), Config); +test_driver({_, {Module, TestCase}}, Config) -> + test_driver({Module, TestCase}, Config); +test_driver({Module, TestCase}, Config) -> + Sec = timer:seconds(1) * 1000, + case get_suite(Module, TestCase) of + [] when Config == suite -> + {Module, TestCase, 'IMPL'}; + [] -> + log("Eval test case: ~w~n", [{Module, TestCase}]), + {T, Res} = + timer:tc(?MODULE, eval_test_case, [Module, TestCase, Config]), + log("Tested ~w in ~w sec~n", [TestCase, T div Sec]), + {T div Sec, Res}; + Suite when is_list(Suite), Config == suite -> + Res = test_driver(default_module(Module, Suite), Config), + {{Module, TestCase}, Res}; + Suite when is_list(Suite) -> + log("Expand test case ~w~n", [{Module, TestCase}]), + Def = default_module(Module, Suite), + {T, Res} = timer:tc(?MODULE, test_driver, [Def, Config]), + {T div Sec, {{Module, TestCase}, Res}}; + 'NYI' when Config == suite -> + {Module, TestCase, 'NYI'}; + 'NYI' -> + log("<WARNING> Test case ~w NYI~n", [{Module, TestCase}]), + {0, {skip, {Module, TestCase}, "NYI"}} + end; +test_driver(TestCase, Config) -> + DefaultModule = mnesia_SUITE, + log("<>WARNING<> Missing module in test case identifier. " + "{~w, ~w} assumed~n", [DefaultModule, TestCase]), + test_driver({DefaultModule, TestCase}, Config). + +default_module(DefaultModule, TestCases) when is_list(TestCases) -> + Fun = fun(T) -> + case T of + {_, _} -> true; + T -> {true, {DefaultModule, T}} + end + end, + lists:zf(Fun, TestCases). + +%% Returns a list (possibly empty) or the atom 'NYI' +get_suite(Mod, Fun) -> + case catch (apply(Mod, Fun, [suite])) of + {'EXIT', _} -> 'NYI'; + List when is_list(List) -> List + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +eval_test_case(Mod, Fun, Config) -> + flush(), + global:register_name(mnesia_test_case_sup, self()), + Flag = process_flag(trap_exit, true), + Pid = spawn_link(?MODULE, test_case_evaluator, [Mod, Fun, [Config]]), + R = wait_for_evaluator(Pid, Mod, Fun, Config), + global:unregister_name(mnesia_test_case_sup), + process_flag(trap_exit, Flag), + R. + +flush() -> + receive Msg -> [Msg | flush()] + after 0 -> [] + end. + +wait_for_evaluator(Pid, Mod, Fun, Config) -> + receive + {'EXIT', Pid, {test_case_ok, _PidRes}} -> + Errors = flush(), + Res = + case Errors of + [] -> ok; + Errors -> failed + end, + {Res, {Mod, Fun}, Errors}; + {'EXIT', Pid, {skipped, Reason}} -> + log("<WARNING> Test case ~w skipped, because ~p~n", + [{Mod, Fun}, Reason]), + Mod:fin_per_testcase(Fun, Config), + {skip, {Mod, Fun}, Reason}; + {'EXIT', Pid, Reason} -> + log("<>ERROR<> Eval process ~w exited, because ~p~n", + [{Mod, Fun}, Reason]), + Mod:fin_per_testcase(Fun, Config), + {crash, {Mod, Fun}, Reason} + end. + +test_case_evaluator(Mod, Fun, [Config]) -> + NewConfig = Mod:init_per_testcase(Fun, Config), + R = apply(Mod, Fun, [NewConfig]), + Mod:fin_per_testcase(Fun, NewConfig), + exit({test_case_ok, R}). + +activity_evaluator(Coordinator) -> + activity_evaluator_loop(Coordinator), + exit(normal). + +activity_evaluator_loop(Coordinator) -> + receive + begin_trans -> + transaction(Coordinator, 0); + {begin_trans, MaxRetries} -> + transaction(Coordinator, MaxRetries); + end_trans -> + end_trans; + Fun when is_function(Fun) -> + Coordinator ! {self(), Fun()}, + activity_evaluator_loop(Coordinator); +% {'EXIT', Coordinator, Reason} -> +% Reason; + ExitExpr -> +% ?error("activity_evaluator_loop ~p ~p: exit(~p)~n}", [Coordinator, self(), ExitExpr]), + exit(ExitExpr) + end. + +transaction(Coordinator, MaxRetries) -> + Fun = fun() -> + Coordinator ! {self(), begin_trans}, + activity_evaluator_loop(Coordinator) + end, + Coordinator ! {self(), mnesia:transaction(Fun, MaxRetries)}, + activity_evaluator_loop(Coordinator). + +pick_msg() -> + receive + Message -> Message + after 4000 -> timeout + end. + +start_activities(Nodes) -> + Fun = fun(N) -> spawn_link(N, ?MODULE, activity_evaluator, [self()]) end, + Pids = mapl(Fun, Nodes), + {success, Pids}. + +mapl(Fun, [H|T]) -> + Res = Fun(H), + [Res|mapl(Fun, T)]; +mapl(_Fun, []) -> + []. + +diskless(Config) -> + case lists:keysearch(diskless, 1, Config) of + {value, {diskless, true}} -> + true; + _Else -> + false + end. + + +start_transactions(Pids) -> + Fun = fun(Pid) -> + Pid ! begin_trans, + ?match_receive({Pid, begin_trans}) + end, + mapl(Fun, Pids). + +start_sync_transactions(Pids) -> + Nodes = [node(Pid) || Pid <- Pids], + Fun = fun(Pid) -> + sync_trans_tid_serial(Nodes), + Pid ! begin_trans, + ?match_receive({Pid, begin_trans}) + end, + mapl(Fun, Pids). + + +start_transactions(Pids, MaxRetries) -> + Fun = fun(Pid) -> + Pid ! {begin_trans, MaxRetries}, + ?match_receive({Pid, begin_trans}) + end, + mapl(Fun, Pids). + +start_sync_transactions(Pids, MaxRetries) -> + Nodes = [node(Pid) || Pid <- Pids], + Fun = fun(Pid) -> + sync_trans_tid_serial(Nodes), + Pid ! {begin_trans, MaxRetries}, + ?match_receive({Pid, begin_trans}) + end, + mapl(Fun, Pids). + +sync_trans_tid_serial(Nodes) -> + Fun = fun() -> mnesia:write_lock_table(schema) end, + rpc:multicall(Nodes, mnesia, transaction, [Fun]). + +select_nodes(N, Config, File, Line) -> + prepare_test_case([], N, Config, File, Line). + +prepare_test_case(Actions, N, Config, File, Line) -> + NodeList1 = lookup_config(nodes, Config), + NodeList2 = lookup_config(nodenames, Config), %% For testserver + NodeList3 = append_unique(NodeList1, NodeList2), + This = node(), + All = [This | lists:delete(This, NodeList3)], + Selected = pick_nodes(N, All, File, Line), + case diskless(Config) of + true -> + ok; + false -> + rpc:multicall(Selected, application, set_env,[mnesia, schema_location, opt_disc]) + end, + do_prepare(Actions, Selected, All, Config, File, Line). + +do_prepare([], Selected, _All, _Config, _File, _Line) -> + Selected; +do_prepare([{init_test_case, Appls} | Actions], Selected, All, Config, File, Line) -> + set_kill_timer(Config), + Started = init_nodes(Selected, File, Line), + All2 = append_unique(Started, All), + Alive = mnesia_lib:intersect(nodes() ++ [node()], All2), + kill_appls(Appls, Alive), + process_flag(trap_exit, true), + do_prepare(Actions, Started, All2, Config, File, Line); +do_prepare([delete_schema | Actions], Selected, All, Config, File, Line) -> + Alive = mnesia_lib:intersect(nodes() ++ [node()], All), + case diskless(Config) of + true -> + skip; + false -> + Del = fun(Node) -> + case mnesia:delete_schema([Node]) of + ok -> ok; + {error, {"All nodes not running",_}} -> + ok; + Else -> + ?log("Delete schema error ~p ~n", [Else]) + end + end, + lists:foreach(Del, Alive) + end, + do_prepare(Actions, Selected, All, Config, File, Line); +do_prepare([create_schema | Actions], Selected, All, Config, File, Line) -> + case diskless(Config) of + true -> + skip; + _Else -> + case mnesia:create_schema(Selected) of + ok -> + ignore; + BadNodes -> + ?fatal("Cannot create Mnesia schema on ~p~n", [BadNodes]) + end + end, + do_prepare(Actions, Selected, All, Config, File, Line); +do_prepare([{start_appls, Appls} | Actions], Selected, All, Config, File, Line) -> + case start_appls(Appls, Selected, Config) of + [] -> ok; + Bad -> ?fatal("Cannot start appls ~p: ~p~n", [Appls, Bad]) + end, + do_prepare(Actions, Selected, All, Config, File, Line); +do_prepare([{reload_appls, Appls} | Actions], Selected, All, Config, File, Line) -> + reload_appls(Appls, Selected), + do_prepare(Actions, Selected, All, Config, File, Line). + +set_kill_timer(Config) -> + case init:get_argument(mnesia_test_timeout) of + {ok, _ } -> ok; + _ -> + Time0 = + case lookup_config(tc_timeout, Config) of + [] -> timer:minutes(5); + ConfigTime when is_integer(ConfigTime) -> ConfigTime + end, + Mul = try + test_server:timetrap_scale_factor() + catch _:_ -> 1 end, + (catch test_server:timetrap(Mul*Time0 + 1000)), + spawn_link(?MODULE, kill_tc, [self(),Time0*Mul]) + end. + +kill_tc(Pid, Time) -> + receive + after Time -> + case process_info(Pid) of + undefined -> ok; + _ -> + ?error("Watchdog in test case timed out " + "in ~p min~n", [Time div (1000*60)]), + Files = mnesia_lib:dist_coredump(), + ?log("Cores dumped to:~n ~p~n", [Files]), + %% Genarate erlang crashdumps. + %% GenDump = fun(Node) -> + %% File = "CRASH_" ++ atom_to_list(Node) ++ ".dump", + %% rpc:call(Node, os, putenv, ["ERL_CRASH_DUMP", File]), + %% rpc:cast(Node, erlang, halt, ["RemoteTimeTrap"]) + %% end, + %% [GenDump(Node) || Node <- nodes()], + + %% erlang:halt("DebugTimeTrap"), + exit(Pid, kill) + end + end. + + +append_unique([], List) -> List; +append_unique([H|R], List) -> + case lists:member(H, List) of + true -> append_unique(R, List); + false -> [H | append_unique(R, List)] + end. + +pick_nodes(all, Nodes, File, Line) -> + pick_nodes(length(Nodes), Nodes, File, Line); +pick_nodes(N, [H | T], File, Line) when N > 0 -> + [H | pick_nodes(N - 1, T, File, Line)]; +pick_nodes(0, _Nodes, _File, _Line) -> + []; +pick_nodes(N, [], File, Line) -> + ?skip("Test case (~p(~p)) ignored: ~p nodes missing~n", + [File, Line, N]). + +init_nodes([Node | Nodes], File, Line) -> + case net_adm:ping(Node) of + pong -> + [Node | init_nodes(Nodes, File, Line)]; + pang -> + [Name, Host] = node_to_name_and_host(Node), + case slave_start_link(Host, Name) of + {ok, Node1} -> + Path = code:get_path(), + true = rpc:call(Node1, code, set_path, [Path]), + [Node1 | init_nodes(Nodes, File, Line)]; + Other -> + ?skip("Test case (~p(~p)) ignored: cannot start node ~p: ~p~n", + [File, Line, Node, Other]) + end + end; +init_nodes([], _File, _Line) -> + []. + +%% Returns [Name, Host] +node_to_name_and_host(Node) -> + string:tokens(atom_to_list(Node), [$@]). + +lookup_config(Key,Config) -> + case lists:keysearch(Key,1,Config) of + {value,{Key,Val}} -> + Val; + _ -> + [] + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +start_appls(Appls, Nodes) -> + start_appls(Appls, Nodes, [], [schema]). + +start_appls(Appls, Nodes, Config) -> + start_appls(Appls, Nodes, Config, [schema]). + +start_appls([Appl | Appls], Nodes, Config, Tabs) -> + {Started, BadStarters} = + rpc:multicall(Nodes, ?MODULE, remote_start, [Appl, Config, Nodes]), + BadS = [{Node, Appl, Res} || {Node, Res} <- Started, Res /= ok], + BadN = [{BadNode, Appl, bad_start} || BadNode <- BadStarters], + Bad = BadS ++ BadN, + case Appl of + mnesia when Bad == [] -> + sync_tables(Nodes, Tabs); + _ -> + ignore + end, + Bad ++ start_appls(Appls, Nodes, Config, Tabs); +start_appls([], _Nodes, _Config, _Tabs) -> + []. + +remote_start(mnesia, Config, Nodes) -> + case diskless(Config) of + true -> + application_controller:set_env(mnesia, + extra_db_nodes, + Nodes -- [node()]), + application_controller:set_env(mnesia, + schema_location, + ram); + false -> + application_controller:set_env(mnesia, + schema_location, + opt_disc), + ignore + end, + {node(), mnesia:start()}; +remote_start(Appl, _Config, _Nodes) -> + Res = + case application:start(Appl) of + {error, {already_started, Appl}} -> + ok; + Other -> + Other + end, + {node(), Res}. + +%% Start Mnesia on all given nodes and wait for specified +%% tables to be accessible on each node. The atom all means +%% that we should wait for all tables to be loaded +%% +%% Returns a list of error tuples {BadNode, mnesia, Reason} +start_mnesia(Nodes) -> + start_appls([mnesia], Nodes). +start_mnesia(Nodes, Tabs) when is_list(Nodes) -> + start_appls([mnesia], Nodes, [], Tabs). + +%% Wait for the tables to be accessible from all nodes in the list +%% and that all nodes are aware of that the other nodes also ... +sync_tables(Nodes, Tabs) -> + Res = send_wait(Nodes, Tabs, []), + if + Res == [] -> + mnesia:transaction(fun() -> mnesia:write_lock_table(schema) end), + Res; + true -> + Res + end. + +send_wait([Node | Nodes], Tabs, Pids) -> + Pid = spawn_link(Node, ?MODULE, start_wait, [self(), Tabs]), + send_wait(Nodes, Tabs, [Pid | Pids]); +send_wait([], _Tabs, Pids) -> + rec_wait(Pids, []). + +rec_wait([Pid | Pids], BadRes) -> + receive + {'EXIT', Pid, R} -> + rec_wait(Pids, [{node(Pid), bad_wait, R} | BadRes]); + {Pid, ok} -> + rec_wait(Pids, BadRes); + {Pid, {error, R}} -> + rec_wait(Pids, [{node(Pid), bad_wait, R} | BadRes]) + end; +rec_wait([], BadRes) -> + BadRes. + +start_wait(Coord, Tabs) -> + process_flag(trap_exit, true), + Mon = whereis(mnesia_monitor), + case catch link(Mon) of + {'EXIT', _} -> + unlink(Coord), + Coord ! {self(), {error, {node_not_running, node()}}}; + _ -> + Res = start_wait_loop(Tabs), + unlink(Mon), + unlink(Coord), + Coord ! {self(), Res} + end. + +start_wait_loop(Tabs) -> + receive + {'EXIT', Pid, Reason} -> + {error, {start_wait, Pid, Reason}} + after 0 -> + case mnesia:wait_for_tables(Tabs, timer:seconds(30)) of + ok -> + verify_nodes(Tabs); + {timeout, BadTabs} -> + log("<>WARNING<> Wait for tables ~p: ~p~n", [node(), Tabs]), + start_wait_loop(BadTabs); + {error, Reason} -> + {error, {start_wait, Reason}} + end + end. + +verify_nodes(Tabs) -> + verify_nodes(Tabs, 0). + +verify_nodes([], _) -> + ok; + +verify_nodes([Tab| Tabs], N) -> + ?match(X when is_atom(X), mnesia_lib:val({Tab, where_to_read})), + Nodes = mnesia:table_info(Tab, where_to_write), + Copies = + mnesia:table_info(Tab, disc_copies) ++ + mnesia:table_info(Tab, disc_only_copies) ++ + mnesia:table_info(Tab, ram_copies), + Local = mnesia:table_info(Tab, local_content), + case Copies -- Nodes of + [] -> + verify_nodes(Tabs, 0); + _Else when Local == true, Nodes /= [] -> + verify_nodes(Tabs, 0); + Else -> + N2 = + if + N > 20 -> + log("<>WARNING<> ~w Waiting for table: ~p on ~p ~n", + [node(), Tab, Else]), + 0; + true -> N+1 + end, + timer:sleep(500), + verify_nodes([Tab| Tabs], N2) + end. + + +%% Nicely stop Mnesia on all given nodes +%% +%% Returns a list of error tuples {BadNode, Reason} +stop_mnesia(Nodes) when is_list(Nodes) -> + stop_appls([mnesia], Nodes). + +stop_appls([Appl | Appls], Nodes) when is_list(Nodes) -> + {Stopped, BadNodes} = rpc:multicall(Nodes, ?MODULE, remote_stop, [Appl]), + BadS =[{Node, Appl, Res} || {Node, Res} <- Stopped, Res /= stopped], + BadN =[{BadNode, Appl, bad_node} || BadNode <- BadNodes], + BadS ++ BadN ++ stop_appls(Appls, Nodes); +stop_appls([], _Nodes) -> + []. + +remote_stop(mnesia) -> + {node(), mnesia:stop()}; +remote_stop(Appl) -> + {node(), application:stop(Appl)}. + +remote_kill([Appl | Appls]) -> + catch Appl:lkill(), + application:stop(Appl), + remote_kill(Appls); +remote_kill([]) -> + ok. + +%% Abruptly kill Mnesia on all given nodes +%% Returns [] +kill_appls(Appls, Nodes) when is_list(Nodes) -> + verbose("<>WARNING<> Intentionally killing ~p: ~w...~n", + [Appls, Nodes], ?FILE, ?LINE), + rpc:multicall(Nodes, ?MODULE, remote_kill, [Appls]), + []. + +kill_mnesia(Nodes) when is_list(Nodes) -> + kill_appls([mnesia], Nodes). + +reload_appls([Appl | Appls], Selected) -> + kill_appls([Appl], Selected), + timer:sleep(1000), + Ok = {[ok || _N <- Selected], []}, + {Ok2temp, Empty} = rpc:multicall(Selected, application, unload, [Appl]), + Conv = fun({error,{not_loaded,mnesia}}) -> ok; (Else) -> Else end, + Ok2 = {lists:map(Conv, Ok2temp), Empty}, + + Ok3 = rpc:multicall(Selected, application, load, [Appl]), + if + Ok /= Ok2 -> + ?fatal("Cannot unload appl ~p: ~p~n", [Appl, Ok2]); + Ok /= Ok3 -> + ?fatal("Cannot load appl ~p: ~p~n", [Appl, Ok3]); + true -> + ok + end, + reload_appls(Appls, Selected); +reload_appls([], _Selected) -> + ok. + +shutdown() -> + log("<>WARNING<> Intentionally shutting down all nodes... ~p~n", + [nodes() ++ [node()]]), + rpc:multicall(nodes(), erlang, halt, []), + erlang:halt(). + +verify_mnesia(Ups, Downs, File, Line) when is_list(Ups), is_list(Downs) -> + BadUps = + [N || N <- Ups, rpc:call(N, mnesia, system_info, [is_running]) /= yes], + BadDowns = + [N || N <- Downs, rpc:call(N, mnesia, system_info, [is_running]) == yes], + if + BadUps == [] -> + ignore; + true -> + error("Mnesia is not running as expected: ~p~n", + [BadUps], File, Line) + end, + if + BadDowns == [] -> + ignore; + true -> + error("Mnesia is not stopped as expected: ~p~n", + [BadDowns], File, Line) + end, + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +verify_replica_location(Tab, [], [], [], _) -> + ?match({'EXIT', _}, mnesia:table_info(Tab, ram_copies)), + ?match({'EXIT', _}, mnesia:table_info(Tab, disc_copies)), + ?match({'EXIT', _}, mnesia:table_info(Tab, disc_only_copies)), + ?match({'EXIT', _}, mnesia:table_info(Tab, where_to_write)), + ?match({'EXIT', _}, mnesia:table_info(Tab, where_to_read)), + []; + +verify_replica_location(Tab, DiscOnly0, Ram0, Disc0, AliveNodes0) -> +%% sync_tables(AliveNodes0, [Tab]), + AliveNodes = lists:sort(AliveNodes0), + DiscOnly = lists:sort(DiscOnly0), + Ram = lists:sort(Ram0), + Disc = lists:sort(Disc0), + Write = ignore_dead(DiscOnly ++ Ram ++ Disc, AliveNodes), + Read = ignore_dead(DiscOnly ++ Ram ++ Disc, AliveNodes), + This = node(), + + timer:sleep(100), + + S1 = ?match(AliveNodes, lists:sort(mnesia:system_info(running_db_nodes))), + S2 = ?match(DiscOnly, lists:sort(mnesia:table_info(Tab, disc_only_copies))), + S3 = ?match(Ram, lists:sort(mnesia:table_info(Tab, ram_copies))), + S4 = ?match(Disc, lists:sort(mnesia:table_info(Tab, disc_copies))), + S5 = ?match(Write, lists:sort(mnesia:table_info(Tab, where_to_write))), + S6 = case lists:member(This, Read) of + true -> + ?match(This, mnesia:table_info(Tab, where_to_read)); + false -> + ?match(true, lists:member(mnesia:table_info(Tab, where_to_read), Read)) + end, + lists:filter(fun({success,_}) -> false; (_) -> true end, [S1,S2,S3,S4,S5,S6]). + +ignore_dead(Nodes, AliveNodes) -> + Filter = fun(Node) -> lists:member(Node, AliveNodes) end, + lists:sort(lists:zf(Filter, Nodes)). + + +remote_activate_debug_fun(N, I, F, C, File, Line) -> + Pid = spawn_link(N, ?MODULE, do_remote_activate_debug_fun, [self(), I, F, C, File, Line]), + receive + {activated, Pid} -> ok; + {'EXIT', Pid, Reason} -> {error, Reason} + end. + +do_remote_activate_debug_fun(From, I, F, C, File, Line) -> + mnesia_lib:activate_debug_fun(I, F, C, File, Line), + From ! {activated, self()}, + timer:sleep(infinity). % Dies whenever the test process dies !! + + +sort(L) when is_list(L) -> + lists:sort(L); +sort({atomic, L}) when is_list(L) -> + {atomic, lists:sort(L)}; +sort({ok, L}) when is_list(L) -> + {ok, lists:sort(L)}; +sort(W) -> + W. diff --git a/lib/mnesia/test/mnesia_test_lib.hrl b/lib/mnesia/test/mnesia_test_lib.hrl new file mode 100644 index 0000000000..85f12200d4 --- /dev/null +++ b/lib/mnesia/test/mnesia_test_lib.hrl @@ -0,0 +1,132 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +-define(log(Format,Args),mnesia_test_lib:log(Format,Args,?FILE,?LINE)). +-define(warning(Format,Args),?log("<>WARNING<>~n " ++ Format,Args)). +-define(error(Format,Args), + mnesia_test_lib:error(Format,Args,?FILE,?LINE)). +-define(verbose(Format,Args),mnesia_test_lib:verbose(Format,Args,?FILE,?LINE)). + +-define(fatal(Format,Args), + ?error(Format, Args), + exit({test_case_fatal, Format, Args, ?FILE, ?LINE})). + +-define(skip(Format,Args), + ?warning(Format, Args), + exit({skipped, ?flat_format(Format, Args)})). + +-define(flat_format(Format,Args), + lists:flatten(io_lib:format(Format, Args))). + +-define(sort(What), mnesia_test_lib:sort(What)). + +-define(ignore(Expr), + fun() -> + AcTuAlReS = (catch (Expr)), + ?verbose("ok, ~n Result as expected:~p~n",[AcTuAlReS]), + AcTuAlReS + end()). + +-define(match(ExpectedRes,Expr), + fun() -> + AcTuAlReS = (catch (Expr)), + case AcTuAlReS of + ExpectedRes -> + ?verbose("ok, ~n Result as expected:~p~n",[AcTuAlReS]), + {success,AcTuAlReS}; + _ -> + ?error("Not Matching Actual result was:~n ~p~n", + [AcTuAlReS]), + {fail,AcTuAlReS} + end + end()). + +-define(match_inverse(NotExpectedRes,Expr), + fun() -> + AcTuAlReS = (catch (Expr)), + case AcTuAlReS of + NotExpectedRes -> + ?error("Not matching Actual result was:~n ~p~n", + [AcTuAlReS]), + {fail,AcTuAlReS}; + _ -> + ?verbose("ok, ~n Result as expected: ~p~n",[AcTuAlReS]), + {success,AcTuAlReS} + end + end()). + +-define(match_receive(ExpectedMsg), + ?match(ExpectedMsg,mnesia_test_lib:pick_msg())). + +%% ExpectedMsgs must be completely bound +-define(match_multi_receive(ExpectedMsgs), + fun() -> + TmPeXpCtEdMsGs = lists:sort(ExpectedMsgs), + ?match(TmPeXpCtEdMsGs, + lists:sort(lists:map(fun(_) -> + mnesia_test_lib:pick_msg() + end, + TmPeXpCtEdMsGs))) + end()). + +-define(start_activities(Nodes), + mnesia_test_lib:start_activities(Nodes)). + +-define(start_transactions(Pids), + mnesia_test_lib:start_transactions(Pids)). + +-define(acquire_nodes(N, Config), + mnesia_test_lib:prepare_test_case([{init_test_case, [mnesia]}, + delete_schema, + create_schema, + {start_appls, [mnesia]}], + N, Config, ?FILE, ?LINE)). + +-define(activate_debug_fun(I, F, C), + mnesia_lib:activate_debug_fun(I, F, C, ?FILE, ?LINE)). + +-define(remote_activate_debug_fun(N, I, F, C), + ?match(ok, mnesia_test_lib:remote_activate_debug_fun(N, I, F, C, + ?FILE, ?LINE))). + +-define(deactivate_debug_fun(I), + mnesia_lib:deactivate_debug_fun(I, ?FILE, ?LINE)). + +-define(remote_deactivate_debug_fun(N, I), + rpc:call(N, mnesia_lib, deactivate_debug_fun, [I, ?FILE, ?LINE])). + +-define(is_debug_compiled, + case mnesia_lib:is_debug_compiled() of + false -> + ?skip("Mnesia is not debug compiled, test case ignored.~n", []); + _OhTeR -> + ok + end). + +-define(needs_disc(Config), + case mnesia_test_lib:diskless(Config) of + false -> + ok; + true -> + ?skip("Must have disc, test case ignored.~n", []) + end). + +-define(verify_mnesia(Ups, Downs), + mnesia_test_lib:verify_mnesia(Ups, Downs, ?FILE, ?LINE)). diff --git a/lib/mnesia/test/mnesia_tpcb.erl b/lib/mnesia/test/mnesia_tpcb.erl new file mode 100644 index 0000000000..903c53a21c --- /dev/null +++ b/lib/mnesia/test/mnesia_tpcb.erl @@ -0,0 +1,1268 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% +%% MODULE +%% +%% mnesia_tpcb - TPC-B benchmarking of Mnesia +%% +%% DESCRIPTION +%% +%% The metrics used in the TPC-B benchmark are throughput as measured +%% in transactions per second (TPS). The benchmark uses a single, +%% simple update-intensive transaction to load the database system. +%% The single transaction type provides a simple, repeatable +%% unit of work, and is designed to exercise the basic components of +%% a database system. +%% +%% The definition of the TPC-B states lots of detailed rules and +%% conditions that must be fullfilled, e.g. how the ACID (atomicity, +%% consistency, isolation and durability) properties are verified, +%% how the random numbers must be distributed, minimum sizes of +%% the different types of records, minimum duration of the benchmark, +%% formulas to calculate prices (dollars per tps), disclosure issues +%% etc. Please, see http://www.tpc.org/ about the nitty gritty details. +%% +%% The TPC-B benchmark is stated in terms of a hypothetical bank. The +%% bank has one or more branches. Each branch has multiple tellers. The +%% bank has many customers, each with an account. The database represents +%% the cash position of each entity (branch, teller and account) and a +%% history of recent transactions run by the bank. The transaction +%% represents the work done when a customer makes a deposit or a +%% withdrawal against his account. The transaction is performed by a +%% teller at some branch. +%% +%% Each process that performs TPC-B transactions is called a driver. +%% Drivers generates teller_id, account_id and delta amount of +%% money randomly. An account, a teller and a branch are read, their +%% balances are adjusted and a history record is created. The driver +%% measures the time for 3 reads, 3 writes and 1 create. +%% +%% GETTING STARTED +%% +%% Generate tables and run with default configuration: +%% +%% mnesia_tpcb:start(). +%% +%% A little bit more advanced; +%% +%% spawn(mnesia_tpcb, start, [[[{n_drivers_per_node, 8}, {stop_after, infinity}]]), +%% mnesia_tpcb:stop(). +%% +%% Really advanced; +%% +%% mnesia_tpcb:init(([{n_branches, 8}, {replica_type, disc_only_copies}]), +%% mnesia_tpcb:run(([{n_drivers_per_node, 8}]), +%% mnesia_tpcb:run(([{n_drivers_per_node, 64}]). +%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-module(mnesia_tpcb). +-author('[email protected]'). + +-export([ + config/2, + count_balance/0, + driver_init/2, + init/1, + reporter_init/2, + run/1, + start/0, + start/1, + start/2, + stop/0, + real_trans/5, + verify_tabs/0, + reply_gen_branch/3, + frag_add_delta/7, + + conflict_test/1, + dist_test/1, + replica_test/1, + sticky_replica_test/1, + remote_test/1, + remote_frag2_test/1 + ]). + +-define(SECOND, 1000000). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% Account record, total size must be at least 100 bytes + +-define(ACCOUNT_FILLER, + {123456789012345678901234567890123456789012345678901234567890, + 123456789012345678901234567890123456789012345678901234567890, + 123456789012345678901234567890123456789012345678901234}). + +-record(account, + { + id = 0, % Unique account id + branch_id = 0, % Branch where the account is held + balance = 0, % Account balance + filler = ?ACCOUNT_FILLER % Gap filler to ensure size >= 100 bytes + }). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% Branch record, total size must be at least 100 bytes + +-define(BRANCH_FILLER, + {123456789012345678901234567890123456789012345678901234567890, + 123456789012345678901234567890123456789012345678901234567890, + 123456789012345678901234567890123456789012345678901234567890}). + +-record(branch, + { + id = 0, % Unique branch id + balance = 0, % Total balance of whole branch + filler = ?BRANCH_FILLER % Gap filler to ensure size >= 100 bytes + }). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% Teller record, total size must be at least 100 bytes + +-define(TELLER_FILLER, + {123456789012345678901234567890123456789012345678901234567890, + 123456789012345678901234567890123456789012345678901234567890, + 1234567890123456789012345678901234567890123456789012345678}). + +-record(teller, + { + id = 0, % Unique teller id + branch_id = 0, % Branch where the teller is located + balance = 0, % Teller balance + filler = ?TELLER_FILLER % Gap filler to ensure size >= 100 bytes + }). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% History record, total size must be at least 50 bytes + +-define(HISTORY_FILLER, 1234567890). + +-record(history, + { + history_id = {0, 0}, % {DriverId, DriverLocalHistoryid} + time_stamp = now(), % Time point during active transaction + branch_id = 0, % Branch associated with teller + teller_id = 0, % Teller invlolved in transaction + account_id = 0, % Account updated by transaction + amount = 0, % Amount (delta) specified by transaction + filler = ?HISTORY_FILLER % Gap filler to ensure size >= 50 bytes + }). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-record(tab_config, + { + db_nodes = [node()], + n_replicas = 1, % Ignored for non-fragmented tables + replica_nodes = [node()], + replica_type = ram_copies, + use_running_mnesia = false, + n_fragments = 0, + n_branches = 1, + n_tellers_per_branch = 10, % Must be 10 + n_accounts_per_branch = 100000, % Must be 100000 + branch_filler = ?BRANCH_FILLER, + account_filler = ?ACCOUNT_FILLER, + teller_filler = ?TELLER_FILLER + }). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-record(run_config, + { + driver_nodes = [node()], + n_drivers_per_node = 1, + use_running_mnesia = false, + stop_after = timer:minutes(15), % Minimum 15 min + report_interval = timer:minutes(1), + use_sticky_locks = false, + spawn_near_branch = false, + activity_type = transaction, + reuse_history_id = false + }). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-record(time, + { + n_trans = 0, + min_n = 0, + max_n = 0, + acc_time = 0, + max_time = 0 + }). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-record(driver_state, + { + driver_id, + driver_node, + seed, + n_local_branches, + local_branches, + tab_config, + run_config, + history_id, + time = #time{}, + acc_time = #time{}, + reuse_history_id + }). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-record(reporter_state, + { + driver_pids, + starter_pid, + n_iters = 0, + prev_tps = 0, + curr = #time{}, + acc = #time{}, + init_micros, + prev_micros, + run_config + }). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% One driver on each node, table not replicated + +config(frag_test, ReplicaType) -> + Remote = nodes(), + Local = node(), + Nodes = [Local | Remote], + [ + {n_branches, length(Nodes)}, + {n_fragments, length(Nodes)}, + {replica_nodes, Nodes}, + {db_nodes, Nodes}, + {driver_nodes, Nodes}, + {n_accounts_per_branch, 100}, + {replica_type, ReplicaType}, + {stop_after, timer:minutes(1)}, + {report_interval, timer:seconds(10)}, + {reuse_history_id, true} + ]; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% One driver on each node, table replicated to two nodes. + +config(frag2_test, ReplicaType) -> + Remote = nodes(), + Local = node(), + Nodes = [Local | Remote], + [ + {n_branches, length(Nodes)}, + {n_fragments, length(Nodes)}, + {n_replicas, 2}, + {replica_nodes, Nodes}, + {db_nodes, Nodes}, + {driver_nodes, Nodes}, + {n_accounts_per_branch, 100}, + {replica_type, ReplicaType}, + {stop_after, timer:minutes(1)}, + {report_interval, timer:seconds(10)}, + {reuse_history_id, true} + ]; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% One driver on this node, table replicated to all nodes. + +config(replica_test, ReplicaType) -> + Remote = nodes(), + Local = node(), + Nodes = [Local | Remote], + [ + {db_nodes, Nodes}, + {driver_nodes, [Local]}, + {replica_nodes, Nodes}, + {n_accounts_per_branch, 100}, + {replica_type, ReplicaType}, + {stop_after, timer:minutes(1)}, + {report_interval, timer:seconds(10)}, + {reuse_history_id, true} + ]; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% One driver on this node, table replicated to all nodes. + +config(sticky_replica_test, ReplicaType) -> + Remote = nodes(), + Local = node(), + Nodes = [Local | Remote], + [ + {db_nodes, Nodes}, + {driver_nodes, [node()]}, + {replica_nodes, Nodes}, + {n_accounts_per_branch, 100}, + {replica_type, ReplicaType}, + {use_sticky_locks, true}, + {stop_after, timer:minutes(1)}, + {report_interval, timer:seconds(10)}, + {reuse_history_id, true} + ]; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Ten drivers per node, tables replicated to all nodes, lots of branches + +config(dist_test, ReplicaType) -> + Remote = nodes(), + Local = node(), + Nodes = [Local | Remote], + [ + {db_nodes, Nodes}, + {driver_nodes, Nodes}, + {replica_nodes, Nodes}, + {n_drivers_per_node, 10}, + {n_branches, 10 * length(Nodes) * 100}, + {n_accounts_per_branch, 10}, + {replica_type, ReplicaType}, + {stop_after, timer:minutes(1)}, + {report_interval, timer:seconds(10)}, + {reuse_history_id, true} + ]; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Ten drivers per node, tables replicated to all nodes, single branch + +config(conflict_test, ReplicaType) -> + Remote = nodes(), + Local = node(), + Nodes = [Local | Remote], + [ + {db_nodes, Nodes}, + {driver_nodes, Nodes}, + {replica_nodes, Nodes}, + {n_drivers_per_node, 10}, + {n_branches, 1}, + {n_accounts_per_branch, 10}, + {replica_type, ReplicaType}, + {stop_after, timer:minutes(1)}, + {report_interval, timer:seconds(10)}, + {reuse_history_id, true} + ]; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% One driver on this node, table replicated to all other nodes. + +config(remote_test, ReplicaType) -> + Remote = nodes(), + Local = node(), + Nodes = [Local | Remote], + [ + {db_nodes, Nodes}, + {driver_nodes, [Local]}, + {replica_nodes, Remote}, + {n_accounts_per_branch, 100}, + {replica_type, ReplicaType}, + {stop_after, timer:minutes(1)}, + {report_interval, timer:seconds(10)}, + {reuse_history_id, true} + ]; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% One driver on this node, table replicated to two other nodes. + +config(remote_frag2_test, ReplicaType) -> + Remote = nodes(), + Local = node(), + Nodes = [Local | Remote], + [ + {n_branches, length(Remote)}, + {n_fragments, length(Remote)}, + {n_replicas, 2}, + {replica_nodes, Remote}, + {db_nodes, Nodes}, + {driver_nodes, [Local]}, + {n_accounts_per_branch, 100}, + {replica_type, ReplicaType}, + {stop_after, timer:minutes(1)}, + {report_interval, timer:seconds(10)}, + {reuse_history_id, true} + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +start(What, ReplicaType) -> + spawn_link(?MODULE, start, [config(What, ReplicaType)]). + +replica_test(ReplicaType) -> + start(replica_test, ReplicaType). + +sticky_replica_test(ReplicaType) -> + start(sticky_replica_test, ReplicaType). + +dist_test(ReplicaType) -> + start(dist_test, ReplicaType). + +conflict_test(ReplicaType) -> + start(conflict_test, ReplicaType). + +remote_test(ReplicaType) -> + start(remote_test, ReplicaType). + +remote_frag2_test(ReplicaType) -> + start(remote_frag2_test, ReplicaType). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Args is a list of {Key, Val} tuples where Key is a field name +%% in either the record tab_config or run_config. Unknown keys are ignored. + +start() -> + start([]). +start(Args) -> + init(Args), + run(Args). + +list2rec(List, Fields, DefaultTuple) -> + [Name|Defaults] = tuple_to_list(DefaultTuple), + List2 = list2rec(List, Fields, Defaults, []), + list_to_tuple([Name] ++ List2). + +list2rec(_List, [], [], Acc) -> + Acc; +list2rec(List, [F|Fields], [D|Defaults], Acc) -> + {Val, List2} = + case lists:keysearch(F, 1, List) of + false -> + {D, List}; + {value, {F, NewVal}} -> + {NewVal, lists:keydelete(F, 1, List)} + end, + list2rec(List2, Fields, Defaults, Acc ++ [Val]). + +stop() -> + case whereis(mnesia_tpcb) of + undefined -> + {error, not_running}; + Pid -> + sync_stop(Pid) + end. + +sync_stop(Pid) -> + Pid ! {self(), stop}, + receive + {Pid, {stopped, Res}} -> Res + after timer:minutes(1) -> + exit(Pid, kill), + {error, brutal_kill} + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% Initialization + +%% Args is a list of {Key, Val} tuples where Key is a field name +%% in the record tab_config, unknown keys are ignored. + +init(Args) -> + TabConfig0 = list2rec(Args, record_info(fields, tab_config), #tab_config{}), + TabConfig = + if + TabConfig0#tab_config.n_fragments =:= 0 -> + TabConfig0#tab_config{n_replicas = length(TabConfig0#tab_config.replica_nodes)}; + true -> + TabConfig0 + end, + Tags = record_info(fields, tab_config), + Fun = fun(F, Pos) -> {{F, element(Pos, TabConfig)}, Pos + 1} end, + {List, _} = lists:mapfoldl(Fun, 2, Tags), + io:format("TPC-B: Table config: ~p ~n", [List]), + + DbNodes = TabConfig#tab_config.db_nodes, + stop(), + if + TabConfig#tab_config.use_running_mnesia =:= true -> + ignore; + true -> + rpc:multicall(DbNodes, mnesia, lkill, []), + case mnesia:delete_schema(DbNodes) of + ok -> + case mnesia:create_schema(DbNodes) of + ok -> + {Replies, BadNodes} = + rpc:multicall(DbNodes, mnesia, start, []), + case [Res || Res <- Replies, Res =/= ok] of + [] when BadNodes =:= [] -> + ok; + BadRes -> + io:format("TPC-B: <ERROR> " + "Failed to start ~p: ~p~n", + [BadNodes, BadRes]), + exit({start_failed, BadRes, BadNodes}) + end; + {error, Reason} -> + io:format("TPC-B: <ERROR> " + "Failed to create schema on disc: ~p~n", + [Reason]), + exit({create_schema_failed, Reason}) + end; + {error, Reason} -> + io:format("TPC-B: <ERROR> " + "Failed to delete schema on disc: ~p~n", + [Reason]), + exit({delete_schema_failed, Reason}) + end + end, + gen_tabs(TabConfig). + +gen_tabs(TC) -> + create_tab(TC, branch, record_info(fields, branch), + undefined), + create_tab(TC, account, record_info(fields, account), + {branch, #account.branch_id}), + create_tab(TC, teller, record_info(fields, teller), + {branch, #teller.branch_id}), + create_tab(TC, history, record_info(fields, history), + {branch, #history.branch_id}), + + NB = TC#tab_config.n_branches, + NT = TC#tab_config.n_tellers_per_branch, + NA = TC#tab_config.n_accounts_per_branch, + io:format("TPC-B: Generating ~p branches a ~p bytes~n", + [NB, size(term_to_binary(default_branch(TC)))]), + io:format("TPC-B: Generating ~p * ~p tellers a ~p bytes~n", + [NB, NT, size(term_to_binary(default_teller(TC)))]), + io:format("TPC-B: Generating ~p * ~p accounts a ~p bytes~n", + [NB, NA, size(term_to_binary(default_account(TC)))]), + io:format("TPC-B: Generating 0 history records a ~p bytes~n", + [size(term_to_binary(default_history(TC)))]), + gen_branches(TC), + + case verify_tabs() of + ok -> + ignore; + {error, Reason} -> + io:format("TPC-B: <ERROR> Inconsistent tables: ~w~n", + [Reason]), + exit({inconsistent_tables, Reason}) + end. + +create_tab(TC, Name, Attrs, _ForeignKey) when TC#tab_config.n_fragments =:= 0 -> + Nodes = TC#tab_config.replica_nodes, + Type = TC#tab_config.replica_type, + Def = [{Type, Nodes}, {attributes, Attrs}], + create_tab(Name, Def); +create_tab(TC, Name, Attrs, ForeignKey) -> + NReplicas = TC#tab_config.n_replicas, + NodePool = TC#tab_config.replica_nodes, + Type = TC#tab_config.replica_type, + NF = TC#tab_config.n_fragments, + Props = [{n_fragments, NF}, + {node_pool, NodePool}, + {n_copies(Type), NReplicas}, + {foreign_key, ForeignKey}], + Def = [{frag_properties, Props}, + {attributes, Attrs}], + create_tab(Name, Def). + +create_tab(Name, Def) -> + mnesia:delete_table(Name), + case mnesia:create_table(Name, Def) of + {atomic, ok} -> + ok; + {aborted, Reason} -> + io:format("TPC-B: <ERROR> failed to create table ~w ~w: ~p~n", + [Name, Def, Reason]), + exit({create_table_failed, Reason}) + end. + +n_copies(Type) -> + case Type of + ram_copies -> n_ram_copies; + disc_copies -> n_disc_copies; + disc_only_copies -> n_disc_only_copies + end. + +gen_branches(TC) -> + First = 0, + Last = First + TC#tab_config.n_branches - 1, + GenPids = gen_branches(TC, First, Last, []), + wait_for_gen(GenPids). + +wait_for_gen([]) -> + ok; +wait_for_gen(Pids) -> + receive + {branch_generated, Pid} -> wait_for_gen(lists:delete(Pid, Pids)); + Exit -> + exit({tpcb_failed, Exit}) + end. + +gen_branches(TC, BranchId, Last, UsedNs) when BranchId =< Last -> + UsedNs2 = get_branch_nodes(BranchId, UsedNs), + Node = hd(UsedNs2), + Pid = spawn_link(Node, ?MODULE, reply_gen_branch, + [self(), TC, BranchId]), + [Pid | gen_branches(TC, BranchId + 1, Last, UsedNs2)]; +gen_branches(_, _, _, _) -> + []. + +reply_gen_branch(ReplyTo, TC, BranchId) -> + gen_branch(TC, BranchId), + ReplyTo ! {branch_generated, self()}, + unlink(ReplyTo). + +%% Returns a new list of nodes with the best node as head +get_branch_nodes(BranchId, UsedNs) -> + WriteNs = table_info({branch, BranchId}, where_to_write), + WeightedNs = [{n_duplicates(N, UsedNs, 0), N} || N <- WriteNs], + [{_, LeastUsed} | _ ] = lists:sort(WeightedNs), + [LeastUsed | UsedNs]. + +n_duplicates(_N, [], Count) -> + Count; +n_duplicates(N, [N | Tail], Count) -> + n_duplicates(N, Tail, Count + 1); +n_duplicates(N, [_ | Tail], Count) -> + n_duplicates(N, Tail, Count). + +gen_branch(TC, BranchId) -> + A = default_account(TC), + NA = TC#tab_config.n_accounts_per_branch, + FirstA = BranchId * NA, + ArgsA = [FirstA, FirstA + NA - 1, BranchId, A], + ok = mnesia:activity(async_dirty, fun gen_accounts/4, ArgsA, mnesia_frag), + + T = default_teller(TC), + NT = TC#tab_config.n_tellers_per_branch, + FirstT = BranchId * NT, + ArgsT = [FirstT, FirstT + NT - 1, BranchId, T], + ok = mnesia:activity(async_dirty, fun gen_tellers/4, ArgsT, mnesia_frag), + + B = default_branch(TC), + FunB = fun() -> mnesia:write(branch, B#branch{id = BranchId}, write) end, + ok = mnesia:activity(sync_dirty, FunB, [], mnesia_frag). + +gen_tellers(Id, Last, BranchId, T) when Id =< Last -> + mnesia:write(teller, T#teller{id = Id, branch_id=BranchId}, write), + gen_tellers(Id + 1, Last, BranchId, T); +gen_tellers(_, _, _, _) -> + ok. + +gen_accounts(Id, Last, BranchId, A) when Id =< Last -> + mnesia:write(account, A#account{id = Id, branch_id=BranchId}, write), + gen_accounts(Id + 1, Last, BranchId, A); +gen_accounts(_, _, _, _) -> + ok. + +default_branch(TC) -> #branch{filler = TC#tab_config.branch_filler}. +default_teller(TC) -> #teller{filler = TC#tab_config.teller_filler}. +default_account(TC) -> #account{filler = TC#tab_config.account_filler}. +default_history(_TC) -> #history{}. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% Run the benchmark + +%% Args is a list of {Key, Val} tuples where Key is a field name +%% in the record run_config, unknown keys are ignored. +run(Args) -> + RunConfig = list2rec(Args, record_info(fields, run_config), #run_config{}), + Tags = record_info(fields, run_config), + Fun = fun(F, Pos) -> {{F, element(Pos, RunConfig)}, Pos + 1} end, + {List, _} = lists:mapfoldl(Fun, 2, Tags), + io:format("TPC-B: Run config: ~p ~n", [List]), + + Pid = spawn_link(?MODULE, reporter_init, [self(), RunConfig]), + receive + {Pid, {stopped, Res}} -> + Res; % Stopped by other process + Else -> + {tpcb_got, Else} + after RunConfig#run_config.stop_after -> + sync_stop(Pid) + end. + +reporter_init(Starter, RC) -> + register(mnesia_tpcb, self()), + process_flag(trap_exit, true), + DbNodes = mnesia:system_info(db_nodes), + if + RC#run_config.use_running_mnesia =:= true -> + ignore; + true -> + {Replies, BadNodes} = + rpc:multicall(DbNodes, mnesia, start, []), + case [Res || Res <- Replies, Res =/= ok] of + [] when BadNodes =:= [] -> + ok; + BadRes -> + io:format("TPC-B: <ERROR> " + "Failed to start ~w: ~p~n", + [BadNodes, BadRes]), + exit({start_failed, BadRes, BadNodes}) + end, + verify_tabs() + end, + + N = table_info(branch, size), + NT = table_info(teller, size) div N, + NA = table_info(account, size) div N, + + {Type, NF, RepNodes} = table_storage(branch), + TC = #tab_config{n_fragments = NF, + n_branches = N, + n_tellers_per_branch = NT, + n_accounts_per_branch = NA, + db_nodes = DbNodes, + replica_nodes = RepNodes, + replica_type = Type + }, + Drivers = start_drivers(RC, TC), + Now = now_to_micros(erlang:now()), + State = #reporter_state{driver_pids = Drivers, + run_config = RC, + starter_pid = Starter, + init_micros = Now, + prev_micros = Now + }, + case catch reporter_loop(State) of + {'EXIT', Reason} -> + io:format("TPC-B: Abnormal termination: ~p~n", [Reason]), + if + RC#run_config.use_running_mnesia =:= true -> + ignore; + true -> + rpc:multicall(DbNodes, mnesia, lkill, []) + end, + unlink(Starter), + Starter ! {self(), {stopped, {error, Reason}}}, % To be sure + exit(shutdown); + {ok, Stopper, State2} -> + Time = State2#reporter_state.acc, + Res = + case verify_tabs() of + ok -> + {ok, Time}; + {error, Reason} -> + io:format("TPC-B: <ERROR> Inconsistent tables, ~p~n", + [{error, Reason}]), + {error, Reason} + end, + if + RC#run_config.use_running_mnesia =:= true -> + ignore; + true -> + rpc:multicall(DbNodes, mnesia, stop, []) + end, + unlink(Starter), + Starter ! {self(), {stopped, Res}}, + if + Stopper =/= Starter -> + Stopper ! {self(), {stopped, Res}}; + true -> + ignore + end, + exit(shutdown) + end. + +table_info(Tab, Item) -> + Fun = fun() -> mnesia:table_info(Tab, Item) end, + mnesia:activity(sync_dirty, Fun, mnesia_frag). + +%% Returns {Storage, NFragments, ReplicaNodes} +table_storage(Tab) -> + case mnesia:table_info(branch, frag_properties) of + [] -> + NFO = 0, + NR = length(mnesia:table_info(Tab, ram_copies)), + ND = length(mnesia:table_info(Tab, disc_copies)), + NDO = length(mnesia:table_info(Tab, disc_only_copies)), + if + NR =/= 0 -> {ram_copies, NFO, NR}; + ND =/= 0 -> {disc_copies, NFO, ND}; + NDO =/= 0 -> {disc_copies, NFO, NDO} + end; + Props -> + {value, NFO} = lists:keysearch(n_fragments, 1, Props), + NR = table_info(Tab, n_ram_copies), + ND = table_info(Tab, n_disc_copies), + NDO = table_info(Tab, n_disc_only_copies), + if + NR =/= 0 -> {ram_copies, NFO, NR}; + ND =/= 0 -> {disc_copies, NFO, ND}; + NDO =/= 0 -> {disc_copies, NFO, NDO} + end + end. + +reporter_loop(State) -> + RC = State#reporter_state.run_config, + receive + {From, stop} -> + {ok, From, call_drivers(State, stop)}; + {'EXIT', Pid, Reason} when Pid =:= State#reporter_state.starter_pid -> + %% call_drivers(State, stop), + exit({starter_died, Pid, Reason}) + after RC#run_config.report_interval -> + Iters = State#reporter_state.n_iters, + State2 = State#reporter_state{n_iters = Iters + 1}, + case call_drivers(State2, report) of + State3 when State3#reporter_state.driver_pids =/= [] -> + State4 = State3#reporter_state{curr = #time{}}, + reporter_loop(State4); + _ -> + exit(drivers_died) + end + end. + +call_drivers(State, Msg) -> + Drivers = State#reporter_state.driver_pids, + lists:foreach(fun(Pid) -> Pid ! {self(), Msg} end, Drivers), + State2 = show_report(calc_reports(Drivers, State)), + case Msg =:= stop of + true -> + Acc = State2#reporter_state.acc, + Init = State2#reporter_state.init_micros, + show_report(State2#reporter_state{n_iters = 0, + curr = Acc, + prev_micros = Init}); + false -> + ignore + end, + State2. + +calc_reports([], State) -> + State; +calc_reports([Pid|Drivers], State) -> + receive + {'EXIT', P, Reason} when P =:= State#reporter_state.starter_pid -> + exit({starter_died, P, Reason}); + {'EXIT', Pid, Reason} -> + exit({driver_died, Pid, Reason}); + {Pid, Time} when is_record(Time, time) -> + %% io:format("~w: ~w~n", [Pid, Time]), + A = add_time(State#reporter_state.acc, Time), + C = add_time(State#reporter_state.curr, Time), + State2 = State#reporter_state{acc = A, curr = C}, + calc_reports(Drivers, State2) + end. + +add_time(Acc, New) -> + Acc#time{n_trans = New#time.n_trans + Acc#time.n_trans, + min_n = lists:min([New#time.n_trans, Acc#time.min_n] -- [0]), + max_n = lists:max([New#time.n_trans, Acc#time.max_n]), + acc_time = New#time.acc_time + Acc#time.acc_time, + max_time = lists:max([New#time.max_time, Acc#time.max_time])}. + +-define(AVOID_DIV_ZERO(_What_), try (_What_) catch _:_ -> 0 end). + +show_report(State) -> + Now = now_to_micros(erlang:now()), + Iters = State#reporter_state.n_iters, + Time = State#reporter_state.curr, + Max = Time#time.max_time, + N = Time#time.n_trans, + Avg = ?AVOID_DIV_ZERO(Time#time.acc_time div N), + AliveN = length(State#reporter_state.driver_pids), + Tps = ?AVOID_DIV_ZERO((?SECOND * AliveN) div Avg), + PrevTps= State#reporter_state.prev_tps, + {DiffSign, DiffTps} = signed_diff(Iters, Tps, PrevTps), + Unfairness = ?AVOID_DIV_ZERO(Time#time.max_n / Time#time.min_n), + BruttoAvg = ?AVOID_DIV_ZERO((Now - State#reporter_state.prev_micros) div N), +%% io:format("n_iters=~p, n_trans=~p, n_drivers=~p, avg=~p, now=~p, prev=~p~n", +%% [Iters, N, AliveN, BruttoAvg, Now, State#reporter_state.prev_micros]), + BruttoTps = ?AVOID_DIV_ZERO(?SECOND div BruttoAvg), + case Iters > 0 of + true -> + io:format("TPC-B: ~p iter ~s~p diff ~p (~p) tps ~p avg micros ~p max micros ~p unfairness~n", + [Iters, DiffSign, DiffTps, Tps, BruttoTps, Avg, Max, Unfairness]); + false -> + io:format("TPC-B: ~p (~p) transactions per second, " + "duration of longest transaction was ~p milliseconds~n", + [Tps, BruttoTps, Max div 1000]) + end, + State#reporter_state{prev_tps = Tps, prev_micros = Now}. + +signed_diff(Iters, Curr, Prev) -> + case Iters > 1 of + true -> sign(Curr - Prev); + false -> sign(0) + end. + +sign(N) when N > 0 -> {"+", N}; +sign(N) -> {"", N}. + +now_to_micros({Mega, Secs, Micros}) -> + DT = calendar:now_to_datetime({Mega, Secs, 0}), + S = calendar:datetime_to_gregorian_seconds(DT), + (S * ?SECOND) + Micros. + +start_drivers(RC, TC) -> + LastHistoryId = table_info(history, size), + Reuse = RC#run_config.reuse_history_id, + DS = #driver_state{tab_config = TC, + run_config = RC, + n_local_branches = 0, + local_branches = [], + history_id = LastHistoryId, + reuse_history_id = Reuse}, + Nodes = RC#run_config.driver_nodes, + NB = TC#tab_config.n_branches, + First = 0, + AllBranches = lists:seq(First, First + NB - 1), + ND = RC#run_config.n_drivers_per_node, + Spawn = fun(Spec) -> + Node = Spec#driver_state.driver_node, + spawn_link(Node, ?MODULE, driver_init, [Spec, AllBranches]) + end, + Specs = [DS#driver_state{driver_id = Id, driver_node = N} + || N <- Nodes, + Id <- lists:seq(1, ND)], + Specs2 = lists:sort(lists:flatten(Specs)), + {Specs3, OrphanBranches} = alloc_local_branches(AllBranches, Specs2, []), + case length(OrphanBranches) of + N when N =< 10 -> + io:format("TPC-B: Orphan branches: ~p~n", [OrphanBranches]); + N -> + io:format("TPC-B: Orphan branches: ~p~n", [N]) + end, + [Spawn(Spec) || Spec <- Specs3]. + +alloc_local_branches([BranchId | Tail], Specs, OrphanBranches) -> + Nodes = table_info({branch, BranchId}, where_to_write), + LocalSpecs = [DS || DS <- Specs, + lists:member(DS#driver_state.driver_node, Nodes)], + case lists:keysort(#driver_state.n_local_branches, LocalSpecs) of + [] -> + alloc_local_branches(Tail, Specs, [BranchId | OrphanBranches]); + [DS | _] -> + LocalNB = DS#driver_state.n_local_branches + 1, + LocalBranches = [BranchId | DS#driver_state.local_branches], + DS2 = DS#driver_state{n_local_branches = LocalNB, + local_branches = LocalBranches}, + Specs2 = Specs -- [DS], + Specs3 = [DS2 | Specs2], + alloc_local_branches(Tail, Specs3, OrphanBranches) + end; +alloc_local_branches([], Specs, OrphanBranches) -> + {Specs, OrphanBranches}. + +driver_init(DS, AllBranches) -> + Seed = erlang:now(), + DS2 = + if + DS#driver_state.n_local_branches =:= 0 -> + DS#driver_state{seed = Seed, + n_local_branches = length(AllBranches), + local_branches = AllBranches}; + true -> + DS#driver_state{seed = Seed} + end, + io:format("TPC-B: Driver ~p started as ~p on node ~p with ~p local branches~n", + [DS2#driver_state.driver_id, self(), node(), DS2#driver_state.n_local_branches]), + driver_loop(DS2). + +driver_loop(DS) -> + receive + {From, report} -> + From ! {self(), DS#driver_state.time}, + Acc = add_time(DS#driver_state.time, DS#driver_state.acc_time), + DS2 = DS#driver_state{time=#time{}, acc_time = Acc}, % Reset timer + DS3 = calc_trans(DS2), + driver_loop(DS3); + {From, stop} -> + Acc = add_time(DS#driver_state.time, DS#driver_state.acc_time), + io:format("TPC-B: Driver ~p (~p) on node ~p stopped: ~w~n", + [DS#driver_state.driver_id, self(), node(self()), Acc]), + From ! {self(), DS#driver_state.time}, + unlink(From), + exit(stopped) + after 0 -> + DS2 = calc_trans(DS), + driver_loop(DS2) + end. + +calc_trans(DS) -> + {Micros, DS2} = time_trans(DS), + Time = DS2#driver_state.time, + Time2 = Time#time{n_trans = Time#time.n_trans + 1, + acc_time = Time#time.acc_time + Micros, + max_time = lists:max([Micros, Time#time.max_time]) + }, + case DS#driver_state.reuse_history_id of + false -> + HistoryId = DS#driver_state.history_id + 1, + DS2#driver_state{time=Time2, history_id = HistoryId}; + true -> + DS2#driver_state{time=Time2} + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Generate teller_id, account_id and delta +%% Time the TPC-B transaction +time_trans(DS) -> + OldSeed = get(random_seed), % Avoid interference with Mnesia + put(random_seed, DS#driver_state.seed), + Random = random:uniform(), + NewSeed = get(random_seed), + case OldSeed of + undefined -> erase(random_seed); + _ -> put(random_seed, OldSeed) + end, + + TC = DS#driver_state.tab_config, + RC = DS#driver_state.run_config, + {Branchid, Args} = random_to_args(Random, DS), + {Fun, Mod} = trans_type(TC, RC), + {Time, Res} = timer:tc(?MODULE, real_trans, [RC, Branchid, Fun, Args, Mod]), + + case Res of + AccountBal when is_integer(AccountBal) -> + {Time, DS#driver_state{seed = NewSeed}}; + Other -> + exit({crash, Other, Args, Random, DS}) + end. + +random_to_args(Random, DS) -> + DriverId = DS#driver_state.driver_id, + TC = DS#driver_state.tab_config, + HistoryId = DS#driver_state.history_id, + Delta = trunc(Random * 1999998) - 999999, % -999999 <= Delta <= +999999 + + Branches = DS#driver_state.local_branches, + NB = DS#driver_state.n_local_branches, + NT = TC#tab_config.n_tellers_per_branch, + NA = TC#tab_config.n_accounts_per_branch, + Tmp = trunc(Random * NB * NT), + BranchPos = (Tmp div NT) + 1, + BranchId = + case TC#tab_config.n_fragments of + 0 -> BranchPos - 1; + _ -> lists:nth(BranchPos, Branches) + end, + RelativeTellerId = Tmp div NT, + TellerId = (BranchId * NT) + RelativeTellerId, + {AccountBranchId, AccountId} = + if + Random >= 0.85, NB > 1 -> + %% Pick from a remote account + TmpAccountId= trunc(Random * (NB - 1) * NA), + TmpAccountBranchId = TmpAccountId div NA, + if + TmpAccountBranchId =:= BranchId -> + {TmpAccountBranchId + 1, TmpAccountId + NA}; + true -> + {TmpAccountBranchId, TmpAccountId} + end; + true -> + %% Pick from a local account + RelativeAccountId = trunc(Random * NA), + TmpAccountId = (BranchId * NA) + RelativeAccountId, + {BranchId, TmpAccountId} + end, + + {BranchId, [DriverId, BranchId, TellerId, AccountBranchId, AccountId, HistoryId, Delta]}. + +real_trans(RC, BranchId, Fun, Args, Mod) -> + Type = RC#run_config.activity_type, + case RC#run_config.spawn_near_branch of + false -> + mnesia:activity(Type, Fun, Args, Mod); + true -> + Node = table_info({branch, BranchId}, where_to_read), + case rpc:call(Node, mnesia, activity, [Type, Fun, Args, Mod]) of + {badrpc, Reason} -> exit(Reason); + Other -> Other + end + end. + +trans_type(TC, RC) -> + if + TC#tab_config.n_fragments =:= 0, + RC#run_config.use_sticky_locks =:= false -> + {fun add_delta/7, mnesia}; + TC#tab_config.n_fragments =:= 0, + RC#run_config.use_sticky_locks =:= true -> + {fun sticky_add_delta/7, mnesia}; + TC#tab_config.n_fragments > 0, + RC#run_config.use_sticky_locks =:= false -> + {fun frag_add_delta/7, mnesia_frag} + end. + +%% +%% Runs the TPC-B defined transaction and returns NewAccountBalance +%% + +add_delta(DriverId, BranchId, TellerId, _AccountBranchId, AccountId, HistoryId, Delta) -> + %% Grab write lock already when the record is read + + %% Add delta to branch balance + [B] = mnesia:read(branch, BranchId, write), + NewB = B#branch{balance = B#branch.balance + Delta}, + ok = mnesia:write(branch, NewB, write), + + %% Add delta to teller balance + [T] = mnesia:read(teller, TellerId, write), + NewT = T#teller{balance = T#teller.balance + Delta}, + ok = mnesia:write(teller, NewT, write), + + %% Add delta to account balance + [A] = mnesia:read(account, AccountId, write), + NewA = A#account{balance = A#account.balance + Delta}, + ok = mnesia:write(account, NewA, write), + + %% Append to history log + History = #history{history_id = {DriverId, HistoryId}, + account_id = AccountId, + teller_id = TellerId, + branch_id = BranchId, + amount = Delta + }, + ok = mnesia:write(history, History, write), + + %% Return account balance + NewA#account.balance. + +sticky_add_delta(DriverId, BranchId, TellerId, _AccountBranchId, AccountId, HistoryId, Delta) -> + %% Grab orinary read lock when the record is read + %% Grab sticky write lock when the record is written + %% This transaction would benefit of an early stick_write lock at read + + %% Add delta to branch balance + [B] = mnesia:read(branch, BranchId, read), + NewB = B#branch{balance = B#branch.balance + Delta}, + ok = mnesia:write(branch, NewB, sticky_write), + + %% Add delta to teller balance + [T] = mnesia:read(teller, TellerId, read), + NewT = T#teller{balance = T#teller.balance + Delta}, + ok = mnesia:write(teller, NewT, sticky_write), + + %% Add delta to account balance + [A] = mnesia:read(account, AccountId, read), + NewA = A#account{balance = A#account.balance + Delta}, + ok = mnesia:write(account, NewA, sticky_write), + + %% Append to history log + History = #history{history_id = {DriverId, HistoryId}, + account_id = AccountId, + teller_id = TellerId, + branch_id = BranchId, + amount = Delta + }, + ok = mnesia:write(history, History, sticky_write), + + %% Return account balance + NewA#account.balance. + +frag_add_delta(DriverId, BranchId, TellerId, AccountBranchId, AccountId, HistoryId, Delta) -> + %% Access fragmented table + %% Grab write lock already when the record is read + + %% Add delta to branch balance + [B] = mnesia:read(branch, BranchId, write), + NewB = B#branch{balance = B#branch.balance + Delta}, + ok = mnesia:write(NewB), + + %% Add delta to teller balance + [T] = mnesia:read({teller, BranchId}, TellerId, write), + NewT = T#teller{balance = T#teller.balance + Delta}, + ok = mnesia:write(NewT), + + %% Add delta to account balance + %%io:format("frag_add_delta(~p): ~p\n", [node(), {account, BranchId, AccountId}]), + [A] = mnesia:read({account, AccountBranchId}, AccountId, write), + NewA = A#account{balance = A#account.balance + Delta}, + ok = mnesia:write(NewA), + + %% Append to history log + History = #history{history_id = {DriverId, HistoryId}, + account_id = AccountId, + teller_id = TellerId, + branch_id = BranchId, + amount = Delta + }, + ok = mnesia:write(History), + + %% Return account balance + NewA#account.balance. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Verify table consistency + +verify_tabs() -> + Nodes = mnesia:system_info(running_db_nodes), + case lists:member(node(), Nodes) of + true -> + Tabs = [branch, teller, account, history], + io:format("TPC-B: Verifying tables: ~w~n", [Tabs]), + rpc:multicall(Nodes, mnesia, wait_for_tables, [Tabs, infinity]), + + Fun = fun() -> + mnesia:write_lock_table(branch), + mnesia:write_lock_table(teller), + mnesia:write_lock_table(account), + mnesia:write_lock_table(history), + {Res, BadNodes} = + rpc:multicall(Nodes, ?MODULE, count_balance, []), + check_balance(Res, BadNodes) + end, + case mnesia:transaction(Fun) of + {atomic, Res} -> Res; + {aborted, Reason} -> {error, Reason} + end; + false -> + {error, "Must be initiated from a running db_node"} + end. + +%% Returns a list of {Table, Node, Balance} tuples +%% Assumes that no updates are performed + +-record(summary, {table, node, balance, size}). + +count_balance() -> + [count_balance(branch, #branch.balance), + count_balance(teller, #teller.balance), + count_balance(account, #account.balance)]. + +count_balance(Tab, BalPos) -> + Frags = table_info(Tab, frag_names), + count_balance(Tab, Frags, 0, 0, BalPos). + +count_balance(Tab, [Frag | Frags], Bal, Size, BalPos) -> + First = mnesia:dirty_first(Frag), + {Bal2, Size2} = count_frag_balance(Frag, First, Bal, Size, BalPos), + count_balance(Tab, Frags, Bal2, Size2, BalPos); +count_balance(Tab, [], Bal, Size, _BalPos) -> + #summary{table = Tab, node = node(), balance = Bal, size = Size}. + +count_frag_balance(_Frag, '$end_of_table', Bal, Size, _BalPos) -> + {Bal, Size}; +count_frag_balance(Frag, Key, Bal, Size, BalPos) -> + [Record] = mnesia:dirty_read({Frag, Key}), + Bal2 = Bal + element(BalPos, Record), + Next = mnesia:dirty_next(Frag, Key), + count_frag_balance(Frag, Next, Bal2, Size + 1, BalPos). + +check_balance([], []) -> + mnesia:abort({"No balance"}); +check_balance(Summaries, []) -> + [One | Rest] = lists:flatten(Summaries), + Balance = One#summary.balance, + %% Size = One#summary.size, + case [S || S <- Rest, S#summary.balance =/= Balance] of + [] -> + ok; + BadSummaries -> + mnesia:abort({"Bad balance", One, BadSummaries}) + end; +check_balance(_, BadNodes) -> + mnesia:abort({"Bad nodes", BadNodes}). diff --git a/lib/mnesia/test/mnesia_trans_access_test.erl b/lib/mnesia/test/mnesia_trans_access_test.erl new file mode 100644 index 0000000000..c67382e694 --- /dev/null +++ b/lib/mnesia/test/mnesia_trans_access_test.erl @@ -0,0 +1,1254 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(mnesia_trans_access_test). +-author('[email protected]'). +-compile([export_all]). +-include("mnesia_test_lib.hrl"). + +init_per_testcase(Func, Conf) -> + mnesia_test_lib:init_per_testcase(Func, Conf). + +fin_per_testcase(Func, Conf) -> + mnesia_test_lib:fin_per_testcase(Func, Conf). + +-define(receive_messages(Msgs), mnesia_recovery_test:receive_messages(Msgs, ?FILE, ?LINE)). + +% First Some debug logging +-define(dgb, true). +-ifdef(dgb). +-define(dl(X, Y), ?verbose("**TRACING: " ++ X ++ "**~n", Y)). +-else. +-define(dl(X, Y), ok). +-endif. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +all(doc) -> + ["Evil access of records in the scope of transactions", + "Invoke all functions in the API and try to cover all legal uses", + "cases as well the illegal dito. This is a complement to the", + "other more explicit test cases."]; +all(suite) -> + [ + write, read, wread, delete, delete_object, + match_object, select, select14, all_keys, + transaction, nested_activities, + index_tabs, index_lifecycle + ]. + +%% Write records + +write(suite) -> []; +write(Config) when is_list(Config) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = write, + Schema = [{name, Tab}, {attributes, [k, v]}, {ram_copies, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Schema)), + + ?match({aborted, {bad_type, _}}, + mnesia:transaction(fun() -> mnesia:write([]) end)), + ?match({aborted, {bad_type, _}}, + mnesia:transaction(fun() -> mnesia:write({Tab, 2}) end)), + ?match({aborted, _}, + mnesia:transaction(fun() -> mnesia:write({foo, 2}) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write({Tab, 1, 2}) end)), + + ?match({'EXIT', {aborted, no_transaction}}, mnesia:write({Tab, 1, 2})), + ?verify_mnesia(Nodes, []). + +%% Read records + +read(suite) -> []; +read(Config) when is_list(Config) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = read, + Schema = [{name, Tab}, {type, bag}, {attributes, [k, v]}, {ram_copies, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Schema)), + + OneRec = {Tab, 1, 2}, + TwoRec = {Tab, 1, 3}, + ?match({aborted, {bad_type, _}}, + mnesia:transaction(fun() -> mnesia:read([]) end)), + ?match({aborted, {bad_type, _}}, + mnesia:transaction(fun() -> mnesia:read({Tab}) end)), + ?match({aborted, {bad_type, _}} + , mnesia:transaction(fun() -> mnesia:read(OneRec) end)), + ?match({atomic, []}, + mnesia:transaction(fun() -> mnesia:read({Tab, 1}) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write(OneRec) end)), + ?match({atomic, [OneRec]}, + mnesia:transaction(fun() -> mnesia:read({Tab, 1}) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write(TwoRec) end)), + ?match({atomic, [OneRec, TwoRec]}, + mnesia:transaction(fun() -> mnesia:read({Tab, 1}) end)), + + ?match({'EXIT', {aborted, no_transaction}}, mnesia:read({Tab, 1})), + ?verify_mnesia(Nodes, []). + +%% Read records and set write lock + +wread(suite) -> []; +wread(Config) when is_list(Config) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = wread, + Schema = [{name, Tab}, {type, set}, {attributes, [k, v]}, {ram_copies, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Schema)), + + OneRec = {Tab, 1, 2}, + TwoRec = {Tab, 1, 3}, + ?match({aborted, {bad_type, _}}, + mnesia:transaction(fun() -> mnesia:wread([]) end)), + ?match({aborted, {bad_type, _}}, + mnesia:transaction(fun() -> mnesia:wread({Tab}) end)), + ?match({aborted, {bad_type, _}} + , mnesia:transaction(fun() -> mnesia:wread(OneRec) end)), + + ?match({atomic, []}, + mnesia:transaction(fun() -> mnesia:wread({Tab, 1}) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write(OneRec) end)), + + ?match({atomic, [OneRec]}, + mnesia:transaction(fun() -> mnesia:wread({Tab, 1}) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write(TwoRec) end)), + ?match({atomic, [TwoRec]}, + mnesia:transaction(fun() -> mnesia:wread({Tab, 1}) end)), + + ?match({'EXIT', {aborted, no_transaction}}, mnesia:wread({Tab, 1})), + ?verify_mnesia(Nodes, []). + +%% Delete record + +delete(suite) -> []; +delete(Config) when is_list(Config) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = delete, + Schema = [{name, Tab}, {type, bag}, {attributes, [k, v]}, {ram_copies, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Schema)), + + ?match({aborted, {bad_type, _}}, + mnesia:transaction(fun() -> mnesia:delete([]) end)), + ?match({aborted, {bad_type, _}}, + mnesia:transaction(fun() -> mnesia:delete({Tab}) end)), + ?match({aborted, {bad_type, _}} + , mnesia:transaction(fun() -> mnesia:delete({Tab, 1, 2}) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:delete({Tab, 1}) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write({Tab, 1, 2}) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:delete({Tab, 1}) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write({Tab, 1, 2}) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write({Tab, 1, 2}) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:delete({Tab, 1}) end)), + + ?match({'EXIT', {aborted, no_transaction}}, mnesia:delete({Tab, 1})), + ?verify_mnesia(Nodes, []). + +%% Delete matching record + +delete_object(suite) -> []; +delete_object(Config) when is_list(Config) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = delete_object, + Schema = [{name, Tab}, {type, bag}, {attributes, [k, v]}, {ram_copies, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Schema)), + + OneRec = {Tab, 1, 2}, + ?match({aborted, {bad_type, _}}, + mnesia:transaction(fun() -> mnesia:delete_object([]) end)), + ?match({aborted, {bad_type, _}}, + mnesia:transaction(fun() -> mnesia:delete_object({Tab}) end)), + ?match({aborted, {bad_type, _}}, + mnesia:transaction(fun() -> mnesia:delete_object({Tab, 1}) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:delete_object(OneRec) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write(OneRec) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:delete_object(OneRec) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write(OneRec) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write(OneRec) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:delete_object(OneRec) end)), + + ?match({'EXIT', {aborted, no_transaction}}, mnesia:delete_object(OneRec)), + + ?match({aborted, {bad_type, Tab, _}}, + mnesia:transaction(fun() -> mnesia:delete_object({Tab, {['_']}, 21}) end)), + ?match({aborted, {bad_type, Tab, _}}, + mnesia:transaction(fun() -> mnesia:delete_object({Tab, {['$5']}, 21}) end)), + + ?verify_mnesia(Nodes, []). + +%% Read matching records + +match_object(suite) -> []; +match_object(Config) when is_list(Config) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = match, + Schema = [{name, Tab}, {attributes, [k, v]}, {ram_copies, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Schema)), + + OneRec = {Tab, 1, 2}, + OnePat = {Tab, '$1', 2}, + ?match({atomic, []}, + mnesia:transaction(fun() -> mnesia:match_object(OnePat) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write(OneRec) end)), + ?match({atomic, [OneRec]}, + mnesia:transaction(fun() -> mnesia:match_object(OnePat) end)), + + ?match({aborted, _}, + mnesia:transaction(fun() -> mnesia:match_object({foo, '$1', 2}) end)), + ?match({aborted, _}, + mnesia:transaction(fun() -> mnesia:match_object({[], '$1', 2}) end)), + + ?match({'EXIT', {aborted, no_transaction}}, mnesia:match_object(OnePat)), + ?verify_mnesia(Nodes, []). + +%% select +select(suite) -> []; +select(Config) when is_list(Config) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = match, + Schema = [{name, Tab}, {attributes, [k, v]}, {ram_copies, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Schema)), + + OneRec = {Tab, 1, 2}, + TwoRec = {Tab, 2, 3}, + OnePat = [{{Tab, '$1', 2}, [], ['$_']}], + ?match({atomic, []}, + mnesia:transaction(fun() -> mnesia:select(Tab, OnePat) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write(OneRec) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write(TwoRec) end)), + ?match({atomic, [OneRec]}, + mnesia:transaction(fun() -> mnesia:select(Tab, OnePat) end)), + + ?match({aborted, _}, + mnesia:transaction(fun() -> mnesia:select(Tab, {match, '$1', 2}) end)), + ?match({aborted, _}, + mnesia:transaction(fun() -> mnesia:select(Tab, [{'_', [], '$1'}]) end)), + + ?match({'EXIT', {aborted, no_transaction}}, mnesia:select(Tab, OnePat)), + ?verify_mnesia(Nodes, []). + + +%% more select +select14(suite) -> []; +select14(Config) when is_list(Config) -> + [Node1,Node2] = Nodes = ?acquire_nodes(2, Config), + Tab1 = select14_ets, + Tab2 = select14_dets, + Tab3 = select14_remote, + Tab4 = select14_remote_dets, + Schemas = [[{name, Tab1}, {attributes, [k, v]}, {ram_copies, [Node1]}], + [{name, Tab2}, {attributes, [k, v]}, {disc_only_copies, [Node1]}], + [{name, Tab3}, {attributes, [k, v]}, {ram_copies, [Node2]}], + [{name, Tab4}, {attributes, [k, v]}, {disc_only_copies, [Node2]}]], + [?match({atomic, ok}, mnesia:create_table(Schema)) || Schema <- Schemas], + + %% Some Helpers + Trans = fun(Fun) -> mnesia:transaction(Fun) end, + LoopHelp = fun('$end_of_table',_) -> []; + ({Recs,Cont},Fun) -> + Sel = mnesia:select(Cont), + Recs ++ Fun(Sel, Fun) + end, + Loop = fun(Table,Pattern) -> + Sel = mnesia:select(Table, Pattern, 1, read), + Res = LoopHelp(Sel,LoopHelp), + case mnesia:table_info(Table, type) of + ordered_set -> Res; + _ -> lists:sort(Res) + end + end, + Test = + fun(Tab) -> + OneRec = {Tab, 1, 2}, + TwoRec = {Tab, 2, 3}, + OnePat = [{{Tab, '$1', 2}, [], ['$_']}], + All = [OneRec,TwoRec], + AllPat = [{'_', [], ['$_']}], + + ?match({atomic, []}, Trans(fun() -> Loop(Tab, OnePat) end)), + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write(OneRec) end)), + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write(TwoRec) end)), + ?match({atomic, [OneRec]}, Trans(fun() -> Loop(Tab, OnePat) end)), + ?match({atomic, All}, Trans(fun() -> Loop(Tab, AllPat) end)), + + {atomic,{_, Cont}} = Trans(fun() -> mnesia:select(Tab, OnePat, 1, read) end), + ?match({aborted, wrong_transaction}, Trans(fun() -> mnesia:select(Cont) end)), + + ?match({aborted, _}, Trans(fun() -> mnesia:select(Tab, {match, '$1', 2},1,read) end)), + ?match({aborted, _}, Trans(fun() -> mnesia:select(Tab, [{'_', [], '$1'}],1,read) end)), + ?match({aborted, _}, Trans(fun() -> mnesia:select(sune) end)), + ?match({'EXIT', {aborted, no_transaction}}, mnesia:select(Tab, OnePat,1,read)), + ?match({aborted, {badarg,sune}}, + Trans(fun() -> mnesia:select(sune) end)) + end, + Test(Tab1), + Test(Tab2), + Test(Tab3), + Test(Tab4), + ?verify_mnesia(Nodes, []). + + +%% Pick all keys from table + +all_keys(suite) ->[]; +all_keys(Config) when is_list(Config) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = all_keys, + Schema = [{name, Tab}, {type, bag}, {attributes, [k, v]}, {ram_copies, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Schema)), + + Write = fun() -> mnesia:write({Tab, 14, 4}) end, + AllKeys = fun() -> mnesia:all_keys(Tab) end, + + ?match({atomic, []}, mnesia:transaction(AllKeys)), + + ?match({atomic, ok}, mnesia:transaction(Write)), + ?match({atomic, [14]}, mnesia:transaction(AllKeys)), + + ?match({atomic, ok}, mnesia:transaction(Write)), + ?match({atomic, [14]}, mnesia:transaction(AllKeys)), + + ?match({aborted, _}, + mnesia:transaction(fun() -> mnesia:all_keys(foo) end)), + ?match({aborted, _}, + mnesia:transaction(fun() -> mnesia:all_keys([]) end)), + + ?match({'EXIT', {aborted, no_transaction}}, mnesia:all_keys(Tab)), + ?verify_mnesia(Nodes, []). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Use and misuse transactions + +transaction(suite) -> []; +transaction(Config) when is_list(Config) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + ?match({atomic, ali_baba}, mnesia:transaction(fun() -> ali_baba end)), + ?match({aborted, _}, mnesia:transaction(no_fun)), + ?match({aborted, _}, mnesia:transaction(?MODULE, no_fun, [foo])), + + {success, [A, B, C, D, E, F, G, H]} = + ?start_activities(lists:duplicate(8, Node1)), + ?start_transactions([A, B, C, D, E, F, G, H]), + + A ! fun() -> mnesia:abort(abort_bad_trans) end, + ?match_receive({A, {aborted, abort_bad_trans}}), + + B ! fun() -> erlang:error(exit_here) end, + ?match_receive({B, {aborted, _}}), + + C ! fun() -> throw(throw_bad_trans) end, + ?match_receive({C, {aborted, {throw, throw_bad_trans}}}), + + D ! fun() -> exit(exit_bad_trans) end, + ?match_receive({D, {aborted, exit_bad_trans}}), + + E ! fun() -> exit(normal) end, + ?match_receive({E, {aborted, normal}}), + + F ! fun() -> exit(abnormal) end, + ?match_receive({F, {aborted, abnormal}}), + + G ! fun() -> exit(G, abnormal) end, + ?match_receive({'EXIT', G, abnormal}), + + H ! fun() -> exit(H, kill) end, + ?match_receive({'EXIT', H, killed}), + + ?match({atomic, ali_baba}, + mnesia:transaction(fun() -> ali_baba end, infinity)), + ?match({atomic, ali_baba}, mnesia:transaction(fun() -> ali_baba end, 1)), + ?match({atomic, ali_baba}, mnesia:transaction(fun() -> ali_baba end, 0)), + ?match({aborted, Reason8} when element(1, Reason8) == badarg, mnesia:transaction(fun() -> ali_baba end, -1)), + ?match({aborted, Reason1} when element(1, Reason1) == badarg, mnesia:transaction(fun() -> ali_baba end, foo)), + Fun = fun() -> + ?match(true, mnesia:is_transaction()), + ?match({atomic, ok}, + mnesia:transaction(fun() -> ?match(true, mnesia:is_transaction()),ok end)), ok end, + ?match({atomic, ok}, mnesia:transaction(Fun)), + ?verify_mnesia(Nodes, []). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +nested_activities(suite) -> + [ + basic_nested, + nested_transactions, + mix_of_nested_activities + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% ensure that nested transactions behave correctly +%% We create a particular table that is used by this test only +-record(ntab, {a, b}). +basic_nested(doc) -> ["Test the basic functionality of nested transactions"]; +basic_nested(suite) -> []; +basic_nested(Config) when is_list(Config) -> + Nodes = ?acquire_nodes(3, Config), + Args = [{ram_copies, Nodes}, + {attributes, record_info(fields, ntab)}], + ?match({atomic, ok}, mnesia:create_table(ntab, Args)), + do_nested(top), + case mnesia_test_lib:diskless(Config) of + false -> + lists:foreach(fun(N) -> + ?match({atomic, ok}, + mnesia:change_table_copy_type(ntab, N, disc_only_copies)) + end, Nodes), + do_nested(top); + true -> + skip + end, + ?verify_mnesia(Nodes, []). + +do_nested(How) -> + F1 = fun() -> + mnesia:write(#ntab{a= 1}), + mnesia:write(#ntab{a= 2}) + end, + F2 = fun() -> + mnesia:read({ntab, 1}) + end, + ?match({atomic, ok}, mnesia:transaction(F1)), + ?match({atomic, _}, mnesia:transaction(F2)), + + ?match({atomic, {aborted, _}}, + mnesia:transaction(fun() -> n_f1(), + mnesia:transaction(fun() -> n_f2() end) + end)), + + ?match({atomic, {aborted, _}}, + mnesia:transaction(fun() -> n_f1(), + mnesia:transaction(fun() -> n_f3() end) + end)), + ?match({atomic, {atomic, [#ntab{a = 5}]}}, + mnesia:transaction(fun() -> mnesia:write(#ntab{a = 5}), + mnesia:transaction(fun() -> n_f4() end) + end)), + Cyclic = fun() -> mnesia:abort({cyclic,a,a,a,a,a}) end, %% Ugly + NodeNotR = fun() -> mnesia:abort({node_not_running, testNode}) end, + + TestAbort = fun(Fun) -> + case get(restart_counter) of + undefined -> + put(restart_counter, 1), + Fun(); + _ -> + erase(restart_counter), + ok + end + end, + + ?match({atomic,{atomic,ok}}, + mnesia:transaction(fun()->mnesia:transaction(TestAbort, + [Cyclic])end)), + + ?match({atomic,{atomic,ok}}, + mnesia:transaction(fun()->mnesia:transaction(TestAbort, + [NodeNotR])end)), + + %% Now try the restart thingie + case How of + top -> + Pids = [spawn(?MODULE, do_nested, [{spawned, self()}]), + spawn(?MODULE, do_nested, [{spawned, self()}]), + spawn(?MODULE, do_nested, [{spawned, self()}]), + spawn(?MODULE, do_nested, [{spawned, self()}])], + ?match({info, _, _}, mnesia_tm:get_info(2000)), + lists:foreach(fun(P) -> receive + {P, ok} -> ok + end + end, Pids), + ?match([], [Tab || Tab <- ets:all(), mnesia_trans_store == ets:info(Tab, name)]); + + {spawned, Pid} -> + ?match({info, _, _}, mnesia_tm:get_info(2000)), + Pid ! {self(), ok}, + exit(normal) + end. + + +n_f1() -> + mnesia:read({ntab, 1}), + mnesia:write(#ntab{a = 3}). + +n_f2() -> + mnesia:write(#ntab{a = 4}), + erlang:error(exit_here). + +n_f3() -> + mnesia:write(#ntab{a = 4}), + throw(funky). + +n_f4() -> + mnesia:read({ntab, 5}). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +nested_transactions(doc) -> + ["Verify that nested_transactions are handled as expected"]; +nested_transactions(suite) -> + [nested_trans_both_ok, + nested_trans_child_dies, + nested_trans_parent_dies, + nested_trans_both_dies]. + +nested_trans_both_ok(suite) -> []; +nested_trans_both_ok(Config) when is_list(Config) -> + nested_transactions(Config, ok, ok). + +nested_trans_child_dies(suite) -> []; +nested_trans_child_dies(Config) when is_list(Config) -> + nested_transactions(Config, abort, ok). + +nested_trans_parent_dies(suite) -> []; +nested_trans_parent_dies(Config) when is_list(Config) -> + nested_transactions(Config, ok, abort). + +nested_trans_both_dies(suite) -> []; +nested_trans_both_dies(Config) when is_list(Config) -> + nested_transactions(Config, abort, abort). + +nested_transactions(Config, Child, Father) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + Tab = nested_trans, + + Def = + case mnesia_test_lib:diskless(Config) of + true -> + [{name, Tab}, {ram_copies, Nodes}]; + false -> + [{name, Tab}, {ram_copies, [Node1]}, + {disc_copies, [Node2]}, {disc_only_copies, [Node3]}] + end, + + ?match({atomic, ok}, mnesia:create_table(Def)), + ?match(ok, mnesia:dirty_write({Tab, father, not_updated})), + ?match(ok, mnesia:dirty_write({Tab, child, not_updated})), + + ChildOk = fun() -> mnesia:write({Tab, child, updated}) end, + ChildAbort = fun() -> + mnesia:write({Tab, child, updated}), + erlang:error(exit_here) + end, + + Child_Fun = % Depending of test case + case Child of + ok -> ChildOk; + abort -> ChildAbort + end, + + FatherOk = fun() -> mnesia:transaction(Child_Fun), + mnesia:write({Tab, father, updated}) + end, + + FatherAbort = fun() -> mnesia:transaction(Child_Fun), + mnesia:write({Tab, father, updated}), + erlang:error(exit_here) + end, + + {FatherRes, ChildRes} = % Depending of test case + case Father of + ok -> ?match({atomic, ok}, mnesia:transaction(FatherOk)), + case Child of + ok -> {[{Tab, father, updated}], [{Tab, child, updated}]}; + _ -> {[{Tab, father, updated}], [{Tab, child, not_updated}]} + end; + abort -> ?match({aborted, _}, mnesia:transaction(FatherAbort)), + {[{Tab, father, not_updated}], [{Tab, child, not_updated}]} + end, + + %% Syncronize things!! + ?match({atomic, ok}, mnesia:sync_transaction(fun() -> mnesia:write({Tab, sync, sync}) end)), + + ?match(ChildRes, rpc:call(Node1, mnesia, dirty_read, [{Tab, child}])), + ?match(ChildRes, rpc:call(Node2, mnesia, dirty_read, [{Tab, child}])), + ?match(ChildRes, rpc:call(Node3, mnesia, dirty_read, [{Tab, child}])), + + ?match(FatherRes, rpc:call(Node1, mnesia, dirty_read, [{Tab, father}])), + ?match(FatherRes, rpc:call(Node2, mnesia, dirty_read, [{Tab, father}])), + ?match(FatherRes, rpc:call(Node3, mnesia, dirty_read, [{Tab, father}])), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +mix_of_nested_activities(doc) -> + ["Verify that dirty operations in a transaction are handled like ", + "normal transactions"]; +mix_of_nested_activities(suite) -> []; +mix_of_nested_activities(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + Tab = tab, + + Def = + case mnesia_test_lib:diskless(Config) of + true -> [{ram_copies, Nodes}]; + false -> + [{ram_copies, [Node1]}, + {disc_copies, [Node2]}, + {disc_only_copies, [Node3]}] + end, + + ?match({atomic, ok}, mnesia:create_table(Tab, [{type,bag}|Def])), + Activities = [transaction, sync_transaction, + ets, async_dirty, sync_dirty], + %% Make a test for all 3000 combinations + Tests = [[A,B,C,D,E] || + A <- Activities, + B <- Activities, + C <- Activities, + D <- Activities, + E <- Activities], + Foreach = + fun(Test,No) -> + Result = lists:reverse(Test), + ?match({No,Result},{No,catch apply_op({Tab,No},Test)}), + No+1 + end, + lists:foldl(Foreach, 0, Tests), + ?verify_mnesia(Nodes, []). + +apply_op(Oid,[Type]) -> + check_res(Type,mnesia:Type(fun() -> [Type|read_op(Oid)] end)); +apply_op(Oid = {Tab,Key},[Type|Next]) -> + check_res(Type,mnesia:Type(fun() -> + Prev = read_op(Oid), + mnesia:write({Tab,Key,[Type|Prev]}), + apply_op(Oid,Next) + end)). + +check_res(transaction, {atomic,Res}) -> + Res; +check_res(sync_transaction, {atomic,Res}) -> + Res; +check_res(async_dirty, Res) when is_list(Res) -> + Res; +check_res(sync_dirty, Res) when is_list(Res) -> + Res; +check_res(ets, Res) when is_list(Res) -> + Res; +check_res(Type,Res) -> + ?match(bug,{Type,Res}). + +read_op(Oid) -> + case lists:reverse(mnesia:read(Oid)) of + [] -> []; + [{_,_,Ops}|_] -> + Ops + end. + +index_tabs(suite) -> + [ + index_match_object, + index_read, + index_update, + index_write + ]. + +%% Read matching records by using an index + +index_match_object(suite) -> []; +index_match_object(Config) when is_list(Config) -> + [Node1, Node2] = Nodes = ?acquire_nodes(2, Config), + Tab = index_match_object, + Schema = [{name, Tab}, {attributes, [k, v, e]}, {ram_copies, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Schema)), + ValPos = 3, + BadValPos = ValPos + 2, + ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)), + + ?match({atomic, []}, + mnesia:transaction(fun() -> mnesia:index_match_object({Tab, '$1', 2}, ValPos) end)), + OneRec = {Tab, {1, 1}, 2, {1, 1}}, + OnePat = {Tab, '$1', 2, '_'}, + BadPat = {Tab, '$1', '$2', '_'}, %% See ref guide + + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write(OneRec) end)), + + Imatch = fun(Patt, Pos) -> + mnesia:transaction(fun() -> lists:sort(mnesia:index_match_object(Patt, Pos)) end) + end, + ?match({atomic, [OneRec]}, Imatch(OnePat, ValPos)), + ?match({aborted, _}, Imatch(OnePat, BadValPos)), + ?match({aborted, _}, Imatch({foo, '$1', 2, '_'}, ValPos)), + ?match({aborted, _}, Imatch({[], '$1', 2, '_'}, ValPos)), + ?match({aborted, _}, Imatch(BadPat, ValPos)), + ?match({'EXIT', {aborted, no_transaction}}, mnesia:index_match_object(OnePat, ValPos)), + + Another = {Tab, {3,1}, 2, {4,4}}, + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write(Another) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write({Tab, {4, 4}, 3, {4, 4}}) end)), + + ?match({atomic, [OneRec]}, Imatch({Tab, {1,1}, 2, {1,1}}, ValPos)), + ?match({atomic, [OneRec]}, Imatch({Tab, {1,1}, 2, '$1'}, ValPos)), + ?match({atomic, [OneRec]}, Imatch({Tab, '$1', 2, {1,1}}, ValPos)), + ?match({atomic, [OneRec]}, Imatch({Tab, '$1', 2, '$1'}, ValPos)), + ?match({atomic, [OneRec]}, Imatch({Tab, {1, '$1'}, 2, '_'}, ValPos)), + ?match({atomic, [OneRec]}, Imatch({Tab, {'$2', '$1'}, 2, {'_', '$1'}}, ValPos)), + ?match({atomic, [OneRec, Another]}, Imatch({Tab, '_', 2, '_'}, ValPos)), + + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write({Tab, 4, 5, {7, 4}}) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write({Tab, 7, 5, {7, 5}}) end)), + + ?match({atomic, [{Tab, 4, 5, {7, 4}}]}, Imatch({Tab, '$1', 5, {'_', '$1'}}, ValPos)), + + ?match({atomic, [OneRec]}, rpc:call(Node2, mnesia, transaction, + [fun() -> + lists:sort(mnesia:index_match_object({Tab, {1,1}, 2, + {1,1}}, ValPos)) + end])), + ?verify_mnesia(Nodes, []). + +%% Read records by using an index + +index_read(suite) -> []; +index_read(Config) when is_list(Config) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = index_read, + Schema = [{name, Tab}, {attributes, [k, v]}, {ram_copies, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Schema)), + ValPos = 3, + BadValPos = ValPos + 1, + ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)), + + OneRec = {Tab, 1, 2}, + ?match({atomic, []}, + mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write(OneRec) end)), + ?match({atomic, [OneRec]}, + mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end)), + ?match({aborted, _}, + mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, BadValPos) end)), + ?match({aborted, _}, + mnesia:transaction(fun() -> mnesia:index_read(foo, 2, ValPos) end)), + ?match({aborted, _}, + mnesia:transaction(fun() -> mnesia:index_read([], 2, ValPos) end)), + + ?match({'EXIT', {aborted, no_transaction}}, mnesia:index_read(Tab, 2, ValPos)), + ?verify_mnesia(Nodes, []). + +index_update(suite) -> [index_update_set, index_update_bag]; +index_update(doc) -> ["See Ticket OTP-2083, verifies that a table with a index is " + "update in the correct way i.e. the index finds the correct " + "records after a update"]. +index_update_set(suite) -> []; +index_update_set(Config)when is_list(Config) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = index_test, + Schema = [{name, Tab}, {attributes, [k, v1, v2, v3]}, {ram_copies, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Schema)), + ValPos = v1, + ValPos2 = v3, + ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)), + + Pat1 = {Tab, '$1', 2, '$2', '$3'}, + Pat2 = {Tab, '$1', '$2', '$3', '$4'}, + + Rec1 = {Tab, 1, 2, 3, 4}, + Rec2 = {Tab, 2, 2, 13, 14}, + Rec3 = {Tab, 1, 12, 13, 14}, + Rec4 = {Tab, 4, 2, 13, 14}, + + ?match({atomic, []}, + mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write(Rec1) end)), + ?match({atomic, [Rec1]}, + mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end)), + + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write(Rec2) end)), + {atomic, R1} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end), + ?match([Rec1, Rec2], lists:sort(R1)), + + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write(Rec3) end)), + {atomic, R2} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end), + ?match([Rec2], lists:sort(R2)), + ?match({atomic, [Rec2]}, + mnesia:transaction(fun() -> mnesia:index_match_object(Pat1, ValPos) end)), + + {atomic, R3} = mnesia:transaction(fun() -> mnesia:match_object(Pat2) end), + ?match([Rec3, Rec2], lists:sort(R3)), + + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write(Rec4) end)), + {atomic, R4} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end), + ?match([Rec2, Rec4], lists:sort(R4)), + + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:delete({Tab, 4}) end)), + ?match({atomic, [Rec2]}, + mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end)), + + ?match({atomic, ok}, mnesia:del_table_index(Tab, ValPos)), + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write(Rec4) end)), + ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)), + ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos2)), + + {atomic, R5} = mnesia:transaction(fun() -> mnesia:match_object(Pat2) end), + ?match([Rec3, Rec2, Rec4], lists:sort(R5)), + + {atomic, R6} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end), + ?match([Rec2, Rec4], lists:sort(R6)), + + ?match({atomic, []}, + mnesia:transaction(fun() -> mnesia:index_read(Tab, 4, ValPos2) end)), + {atomic, R7} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 14, ValPos2) end), + ?match([Rec3, Rec2, Rec4], lists:sort(R7)), + + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write(Rec1) end)), + {atomic, R8} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end), + ?match([Rec1, Rec2, Rec4], lists:sort(R8)), + ?match({atomic, [Rec1]}, + mnesia:transaction(fun() -> mnesia:index_read(Tab, 4, ValPos2) end)), + {atomic, R9} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 14, ValPos2) end), + ?match([Rec2, Rec4], lists:sort(R9)), + + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:delete_object(Rec2) end)), + {atomic, R10} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end), + ?match([Rec1, Rec4], lists:sort(R10)), + ?match({atomic, [Rec1]}, + mnesia:transaction(fun() -> mnesia:index_read(Tab, 4, ValPos2) end)), + ?match({atomic, [Rec4]}, + mnesia:transaction(fun() -> mnesia:index_read(Tab, 14, ValPos2) end)), + + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:delete({Tab, 4}) end)), + {atomic, R11} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end), + ?match([Rec1], lists:sort(R11)), + ?match({atomic, [Rec1]},mnesia:transaction(fun() -> mnesia:index_read(Tab, 4, ValPos2) end)), + ?match({atomic, []},mnesia:transaction(fun() -> mnesia:index_read(Tab, 14, ValPos2) end)), + + ?verify_mnesia(Nodes, []). + +index_update_bag(suite) -> []; +index_update_bag(Config)when is_list(Config) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = index_test, + Schema = [{name, Tab}, + {type, bag}, + {attributes, [k, v1, v2, v3]}, + {ram_copies, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Schema)), + ValPos = v1, + ValPos2 = v3, + + ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)), + + Pat1 = {Tab, '$1', 2, '$2', '$3'}, + Pat2 = {Tab, '$1', '$2', '$3', '$4'}, + + Rec1 = {Tab, 1, 2, 3, 4}, + Rec2 = {Tab, 2, 2, 13, 14}, + Rec3 = {Tab, 1, 12, 13, 14}, + Rec4 = {Tab, 4, 2, 13, 4}, + Rec5 = {Tab, 1, 2, 234, 14}, + + %% Simple Index + ?match({atomic, []}, + mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write(Rec1) end)), + ?match({atomic, [Rec1]}, + mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end)), + + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write(Rec2) end)), + {atomic, R1} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end), + ?match([Rec1, Rec2], lists:sort(R1)), + + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write(Rec3) end)), + {atomic, R2} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end), + ?match([Rec1, Rec2], lists:sort(R2)), + + {atomic, R3} = mnesia:transaction(fun() -> mnesia:index_match_object(Pat1, ValPos) end), + ?match([Rec1, Rec2], lists:sort(R3)), + + {atomic, R4} = mnesia:transaction(fun() -> mnesia:match_object(Pat2) end), + ?match([Rec1, Rec3, Rec2], lists:sort(R4)), + + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write(Rec4) end)), + {atomic, R5} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end), + ?match([Rec1, Rec2, Rec4], lists:sort(R5)), + + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:delete({Tab, 4}) end)), + {atomic, R6} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end), + ?match([Rec1, Rec2], lists:sort(R6)), + + %% OTP-6587 Needs some whitebox testing to see that the index table is cleaned correctly + + [IPos] = mnesia_lib:val({Tab,index}), + ITab = mnesia_lib:val({index_test,{index, IPos}}), + io:format("~n Index ~p @ ~p => ~p ~n~n",[IPos,ITab, ets:tab2list(ITab)]), + ?match([{2,1},{2,2},{12,1}], ets:tab2list(ITab)), + + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write(Rec5) end)), + {atomic, R60} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end), + ?match([Rec1,Rec5,Rec2], lists:sort(R60)), + + ?match([{2,1},{2,2},{12,1}], ets:tab2list(ITab)), + + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:delete_object(Rec3) end)), + {atomic, R61} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end), + ?match([Rec1,Rec5,Rec2], lists:sort(R61)), + {atomic, R62} = mnesia:transaction(fun() -> mnesia:index_read(Tab,12, ValPos) end), + ?match([], lists:sort(R62)), + ?match([{2,1},{2,2}], ets:tab2list(ITab)), + + %% reset for rest of testcase + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write(Rec3) end)), + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:delete_object(Rec5) end)), + {atomic, R6} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end), + ?match([Rec1, Rec2], lists:sort(R6)), + %% OTP-6587 + + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:delete_object(Rec1) end)), + ?match({atomic, [Rec2]}, + mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end)), + {atomic, R7} = mnesia:transaction(fun() -> mnesia:match_object(Pat2) end), + ?match([Rec3, Rec2], lists:sort(R7)), + + %% Two indexies + ?match({atomic, ok}, mnesia:del_table_index(Tab, ValPos)), + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write(Rec1) end)), + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write(Rec4) end)), + ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)), + ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos2)), + + {atomic, R8} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end), + ?match([Rec1, Rec2, Rec4], lists:sort(R8)), + + {atomic, R9} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 4, ValPos2) end), + ?match([Rec1, Rec4], lists:sort(R9)), + {atomic, R10} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 14, ValPos2) end), + ?match([Rec3, Rec2], lists:sort(R10)), + + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write(Rec5) end)), + {atomic, R11} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end), + ?match([Rec1, Rec5, Rec2, Rec4], lists:sort(R11)), + {atomic, R12} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 4, ValPos2) end), + ?match([Rec1, Rec4], lists:sort(R12)), + {atomic, R13} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 14, ValPos2) end), + ?match([Rec5, Rec3, Rec2], lists:sort(R13)), + + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:delete_object(Rec1) end)), + {atomic, R14} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end), + ?match([Rec5, Rec2, Rec4], lists:sort(R14)), + ?match({atomic, [Rec4]}, + mnesia:transaction(fun() -> mnesia:index_read(Tab, 4, ValPos2) end)), + {atomic, R15} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 14, ValPos2) end), + ?match([Rec5, Rec3, Rec2], lists:sort(R15)), + + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:delete_object(Rec5) end)), + {atomic, R16} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end), + ?match([Rec2, Rec4], lists:sort(R16)), + ?match({atomic, [Rec4]}, mnesia:transaction(fun()->mnesia:index_read(Tab, 4, ValPos2) end)), + {atomic, R17} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 14, ValPos2) end), + ?match([Rec3, Rec2], lists:sort(R17)), + + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write(Rec1) end)), + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:delete({Tab, 1}) end)), + {atomic, R18} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end), + ?match([Rec2, Rec4], lists:sort(R18)), + ?match({atomic, [Rec4]}, mnesia:transaction(fun()->mnesia:index_read(Tab, 4, ValPos2) end)), + {atomic, R19} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 14, ValPos2) end), + ?match([Rec2], lists:sort(R19)), + + ?verify_mnesia(Nodes, []). + + +index_write(suite) -> []; +index_write(doc) -> ["See ticket OTP-8072"]; +index_write(Config)when is_list(Config) -> + Nodes = ?acquire_nodes(1, Config), + mnesia:create_table(a, [{index, [val]}]), + mnesia:create_table(counter, []), + + CreateIfNonExist = + fun(Index) -> + case mnesia:index_read(a, Index, 3) of + [] -> + Id = mnesia:dirty_update_counter(counter, id, 1), + New = {a, Id, Index}, + mnesia:write(New), + New; + [Found] -> + Found + end + end, + + Trans = fun(A) -> + mnesia:transaction(CreateIfNonExist, [A]) + %% This works better most of the time + %% And it is allowed to fail since it's dirty + %% mnesia:async_dirty(CreateIfNonExist, [A]) + end, + + Self = self(), + Update = fun() -> + Res = lists:map(Trans, lists:seq(1,10)), + Self ! {self(), Res} + end, + + Pids = [spawn(Update) || _ <- lists:seq(1,5)], + + Gather = fun(Pid, Acc) -> receive {Pid, Res} -> [Res|Acc] end end, + Results = lists:foldl(Gather, [], Pids), + Expected = hd(Results), + Check = fun(Res) -> ?match(Expected, Res) end, + lists:foreach(Check, Results), + ?verify_mnesia(Nodes, []). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Add and drop indecies + +index_lifecycle(suite) -> + [ + add_table_index_ram, + add_table_index_disc, + add_table_index_disc_only, + create_live_table_index_ram, + create_live_table_index_disc, + create_live_table_index_disc_only, + del_table_index_ram, + del_table_index_disc, + del_table_index_disc_only, + idx_schema_changes + ]. + +add_table_index_ram(suite) -> []; +add_table_index_ram(Config) when is_list(Config) -> + add_table_index(Config, ram_copies). + +add_table_index_disc(suite) -> []; +add_table_index_disc(Config) when is_list(Config) -> + add_table_index(Config, disc_copies). + +add_table_index_disc_only(suite) -> []; +add_table_index_disc_only(Config) when is_list(Config) -> + add_table_index(Config, disc_only_copies). + +%% Add table index + +add_table_index(Config, Storage) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = add_table_index, + Schema = [{name, Tab}, {attributes, [k, v]}, {Storage, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Schema)), + ValPos = 3, + BadValPos = ValPos + 1, + ?match({aborted, Reason41 } when element(1, Reason41) == bad_type, + mnesia:add_table_index(Tab, BadValPos)), + ?match({aborted,Reason42 } when element(1, Reason42) == bad_type, + mnesia:add_table_index(Tab, 2)), + ?match({aborted, Reason43 } when element(1, Reason43) == bad_type, + mnesia:add_table_index(Tab, 1)), + ?match({aborted, Reason44 } when element(1, Reason44) == bad_type, + mnesia:add_table_index(Tab, 0)), + ?match({aborted, Reason45 } when element(1, Reason45) == bad_type, + mnesia:add_table_index(Tab, -1)), + ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)), + ?match({aborted, Reason46 } when element(1, Reason46) == already_exists, + mnesia:add_table_index(Tab, ValPos)), + + NestedFun = fun() -> + ?match({aborted, nested_transaction}, + mnesia:add_table_index(Tab, ValPos)), + ok + end, + ?match({atomic, ok}, mnesia:transaction(NestedFun)), + ?verify_mnesia(Nodes, []). + +create_live_table_index_ram(suite) -> []; +create_live_table_index_ram(Config) when is_list(Config) -> + create_live_table_index(Config, ram_copies). + +create_live_table_index_disc(suite) -> []; +create_live_table_index_disc(Config) when is_list(Config) -> + create_live_table_index(Config, disc_copies). + +create_live_table_index_disc_only(suite) -> []; +create_live_table_index_disc_only(Config) when is_list(Config) -> + create_live_table_index(Config, disc_only_copies). + +create_live_table_index(Config, Storage) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = create_live_table_index, + Schema = [{name, Tab}, {attributes, [k, v]}, {Storage, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Schema)), + ValPos = 3, + mnesia:dirty_write({Tab, 1, 2}), + + Fun = fun() -> + ?match(ok, mnesia:write({Tab, 2, 2})), + ok + end, + ?match({atomic, ok}, mnesia:transaction(Fun)), + ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)), + ?match({atomic, [{Tab, 1, 2},{Tab, 2, 2}]}, + mnesia:transaction(fun() -> lists:sort(mnesia:index_read(Tab, 2, ValPos)) + end)), + ?verify_mnesia(Nodes, []). + +%% Drop table index + +del_table_index_ram(suite) ->[]; +del_table_index_ram(Config) when is_list(Config) -> + del_table_index(Config, ram_copies). + +del_table_index_disc(suite) ->[]; +del_table_index_disc(Config) when is_list(Config) -> + del_table_index(Config, disc_copies). + +del_table_index_disc_only(suite) ->[]; +del_table_index_disc_only(Config) when is_list(Config) -> + del_table_index(Config, disc_only_copies). + +del_table_index(Config, Storage) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = del_table_index, + Schema = [{name, Tab}, {attributes, [k, v]}, {Storage, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Schema)), + ValPos = 3, + BadValPos = ValPos + 1, + ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)), + ?match({aborted,Reason} when element(1, Reason) == no_exists, + mnesia:del_table_index(Tab, BadValPos)), + ?match({atomic, ok}, mnesia:del_table_index(Tab, ValPos)), + + ?match({aborted,Reason1} when element(1, Reason1) == no_exists, + mnesia:del_table_index(Tab, ValPos)), + NestedFun = + fun() -> + ?match({aborted, nested_transaction}, + mnesia:del_table_index(Tab, ValPos)), + ok + end, + ?match({atomic, ok}, mnesia:transaction(NestedFun)), + ?verify_mnesia(Nodes, []). + +idx_schema_changes(suite) -> [idx_schema_changes_ram, + idx_schema_changes_disc, + idx_schema_changes_disc_only]; +idx_schema_changes(doc) -> + ["Tests that index tables are handled correctly when schema changes.", + "For example when a replica is deleted or inserted", + "TICKET OTP-2XXX (ELVIRA)"]. + +idx_schema_changes_ram(suite) -> []; +idx_schema_changes_ram(Config) when is_list(Config) -> + idx_schema_changes(Config, ram_copies). +idx_schema_changes_disc(suite) -> []; +idx_schema_changes_disc(Config) when is_list(Config) -> + idx_schema_changes(Config, disc_copies). +idx_schema_changes_disc_only(suite) -> []; +idx_schema_changes_disc_only(Config) when is_list(Config) -> + idx_schema_changes(Config, disc_only_copies). + +idx_schema_changes(Config, Storage) -> + [N1, N2] = Nodes = ?acquire_nodes(2, Config), + Tab = index_schema_changes, + Idx = 3, + Schema = [{name, Tab}, {index, [Idx]}, {attributes, [k, v]}, {Storage, Nodes}], + ?match({atomic, ok}, mnesia:create_table(Schema)), + + {Storage1, Storage2} = + case Storage of + disc_only_copies -> + {ram_copies, disc_copies}; + disc_copies -> + {disc_only_copies, ram_copies}; + ram_copies -> + {disc_copies, disc_only_copies} + end, + + Write = fun(N) -> + mnesia:write({Tab, N, N+50}) + end, + + [mnesia:sync_transaction(Write, [N]) || N <- lists:seq(1, 10)], + ?match([{Tab, 1, 51}], rpc:call(N1, mnesia, dirty_index_read, [Tab, 51, Idx])), + ?match([{Tab, 1, 51}], rpc:call(N2, mnesia, dirty_index_read, [Tab, 51, Idx])), + + ?match({atomic, ok}, mnesia:change_table_copy_type(Tab, N1, Storage1)), + + ?match({atomic, ok}, rpc:call(N1, mnesia, sync_transaction, [Write, [17]])), + ?match({atomic, ok}, rpc:call(N2, mnesia, sync_transaction, [Write, [18]])), + + ?match([{Tab, 17, 67}], rpc:call(N2, mnesia, dirty_index_read, [Tab, 67, Idx])), + ?match([{Tab, 18, 68}], rpc:call(N1, mnesia, dirty_index_read, [Tab, 68, Idx])), + + ?match({atomic, ok}, mnesia:del_table_copy(Tab, N1)), + ?match({atomic, ok}, rpc:call(N1, mnesia, sync_transaction, [Write, [11]])), + ?match({atomic, ok}, rpc:call(N2, mnesia, sync_transaction, [Write, [12]])), + + ?match([{Tab, 11, 61}], rpc:call(N2, mnesia, dirty_index_read, [Tab, 61, Idx])), + ?match([{Tab, 12, 62}], rpc:call(N1, mnesia, dirty_index_read, [Tab, 62, Idx])), + + ?match({atomic, ok}, mnesia:move_table_copy(Tab, N2, N1)), + ?match({atomic, ok}, rpc:call(N1, mnesia, sync_transaction, [Write, [19]])), + ?match({atomic, ok}, rpc:call(N2, mnesia, sync_transaction, [Write, [20]])), + + ?match([{Tab, 19, 69}], rpc:call(N2, mnesia, dirty_index_read, [Tab, 69, Idx])), + ?match([{Tab, 20, 70}], rpc:call(N1, mnesia, dirty_index_read, [Tab, 70, Idx])), + + ?match({atomic, ok}, mnesia:add_table_copy(Tab, N2, Storage)), + ?match({atomic, ok}, rpc:call(N1, mnesia, sync_transaction, [Write, [13]])), + ?match({atomic, ok}, rpc:call(N2, mnesia, sync_transaction, [Write, [14]])), + + ?match([{Tab, 13, 63}], rpc:call(N2, mnesia, dirty_index_read, [Tab, 63, Idx])), + ?match([{Tab, 14, 64}], rpc:call(N1, mnesia, dirty_index_read, [Tab, 64, Idx])), + + ?match({atomic, ok}, mnesia:change_table_copy_type(Tab, N2, Storage2)), + + ?match({atomic, ok}, rpc:call(N1, mnesia, sync_transaction, [Write, [15]])), + ?match({atomic, ok}, rpc:call(N2, mnesia, sync_transaction, [Write, [16]])), + + ?match([{Tab, 15, 65}], rpc:call(N2, mnesia, dirty_index_read, [Tab, 65, Idx])), + ?match([{Tab, 16, 66}], rpc:call(N1, mnesia, dirty_index_read, [Tab, 66, Idx])), + + ?verify_mnesia(Nodes, []). diff --git a/lib/mnesia/test/mt b/lib/mnesia/test/mt new file mode 100755 index 0000000000..25243f1149 --- /dev/null +++ b/lib/mnesia/test/mt @@ -0,0 +1,60 @@ +#! /bin/sh -f +# ``The contents of this file are subject to the Erlang Public License, +# Version 1.1, (the "License"); you may not use this file except in +# compliance with the License. You should have received a copy of the +# Erlang Public License along with this software. If not, it can be +# retrieved via the world wide web at http://www.erlang.org/. +# +# Software distributed under the License is distributed on an "AS IS" +# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +# the License for the specific language governing rights and limitations +# under the License. +# +# The Initial Developer of the Original Code is Ericsson Utvecklings AB. +# Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings +# AB. All Rights Reserved.'' +# +# $Id$ +# +# +# Author: Hakan Mattsson <[email protected]> +# Purpose: Simplified execution of the test suite +# +# Usage: mt <args to erlang startup script> + +#top=".." +top="$ERL_TOP/lib/mnesia" +h=`hostname` +p="-pa $top/examples -pa $top/ebin -pa $top/test -mnesia_test_verbose true" +log=test_log$$ +latest=test_log_latest +args=${1+"$@"} +erlcmd="erl -sname a $p $args -mnesia_test_timeout" +erlcmd1="erl -sname a1 $p $args" +erlcmd2="erl -sname a2 $p $args" + +xterm -geometry 70x20+0+550 -T a1 -e $erlcmd1 & +xterm -geometry 70x20+450+550 -T a2 -e $erlcmd2 & + +rm "$latest" 2>/dev/null +ln -s "$log" "$latest" +touch "$log" + +echo "$erlcmd1" +echo "" +echo "$erlcmd2" +echo "" +echo "$erlcmd" +echo "" +echo "Give the following command in order to see the outcome from node a@$h"":" +echo "" +echo " less test_log$$" + +ostype=`uname -s` +if [ "$ostype" = "SunOS" ] ; then + /usr/openwin/bin/xterm -geometry 145x40+0+0 -T a -l -lf "$log" -e $erlcmd & +else + xterm -geometry 145x40+0+0 -T a -e script -f -c "$erlcmd" "$log" & +fi +tail -f "$log" | egrep 'Eval|<>ERROR|NYI' + diff --git a/lib/mnesia/test/mt.erl b/lib/mnesia/test/mt.erl new file mode 100644 index 0000000000..f69c4a11fd --- /dev/null +++ b/lib/mnesia/test/mt.erl @@ -0,0 +1,262 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1997-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +%%% Author: Hakan Mattsson [email protected] +%%% Purpose: Nice shortcuts intended for testing of Mnesia +%%% +%%% See the mnesia_SUITE module about the structure of +%%% the test suite. +%%% +%%% See the mnesia_test_lib module about the test case execution. +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-module(mt). +-author('[email protected]'). +-export([ + t/0, t/1, t/2, t/3, % Run test cases + loop/1, loop/2, loop/3, % loop test cases + doc/0, doc/1, % Generate test case doc + struct/0, struct/1, % View test suite struct + shutdown/0, ping/0, start_nodes/0, % Node admin + read_config/0, write_config/1 % Config admin + ]). + +-include("mnesia_test_lib.hrl"). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Aliases for the (sub) test suites +alias(all) -> mnesia_SUITE; +alias(atomicity) -> mnesia_atomicity_test; +alias(backup) -> mnesia_evil_backup; +alias(config) -> mnesia_config_test; +alias(consistency) -> mnesia_consistency_test; +alias(dirty) -> mnesia_dirty_access_test; +alias(durability) -> mnesia_durability_test; +alias(evil) -> mnesia_evil_coverage_test; +alias(qlc) -> mnesia_qlc_test; +alias(examples) -> mnesia_examples_test; +alias(frag) -> mnesia_frag_test; +alias(heavy) -> {mnesia_SUITE, heavy}; +alias(install) -> mnesia_install_test; +alias(isolation) -> mnesia_isolation_test; +alias(light) -> {mnesia_SUITE, light}; +alias(measure) -> mnesia_measure_test; +alias(medium) -> {mnesia_SUITE, medium}; +alias(nice) -> mnesia_nice_coverage_test; +alias(recover) -> mnesia_recover_test; +alias(recovery) -> mnesia_recovery_test; +alias(registry) -> mnesia_registry_test; +alias(suite) -> mnesia_SUITE; +alias(trans) -> mnesia_trans_access_test; +alias(Other) -> Other. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Resolves the name of test suites and test cases +%% according to the alias definitions. Single atoms +%% are assumed to be the name of a test suite. +resolve(Suite0) when is_atom(Suite0) -> + case alias(Suite0) of + Suite when is_atom(Suite) -> + {Suite, all}; + {Suite, Case} -> + {Suite, Case} + end; +resolve({Suite0, Case}) when is_atom(Suite0), is_atom(Case) -> + case alias(Suite0) of + Suite when is_atom(Suite) -> + {Suite, Case}; + {Suite, Case2} -> + {Suite, Case2} + end; +resolve(List) when is_list(List) -> + [resolve(Case) || Case <- List]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Run one or more test cases + +%% Run the default test case with default config +t() -> + t(read_test_case()). + +%% Resolve the test case name and run the test case +%% The test case is noted as default test case +%% and the outcome of the tests are written to +%% to a file. +t(silly) -> + mnesia_install_test:silly(); +t(diskless) -> + %% Run the default test case with default config, + %% but diskless + t(read_test_case(), diskless); +t(Case) -> + %% Use the default config + t(Case, read_config()). + +t(Case, Config) when Config == diskless -> + %% Run the test case with default config, but diskless + Config2 = [{diskless, true} | read_config()], + t(Case, Config2); +t(Mod, Fun) when is_atom(Mod), is_atom(Fun) -> + %% Run the test case with default config + t({Mod, Fun}, read_config()); +t(RawCase, Config) when is_list(Config) -> + %% Resolve the test case name and run the test case + Case = resolve(RawCase), + write_test_case(Case), + Res = mnesia_test_lib:test(Case, Config), + append_test_case_info(Case, Res). + +t(Mod, Fun, Config) when Config == diskless -> + t({Mod, Fun}, diskless). + +config_fname() -> + "mnesia_test_case_config". + +%% Read default config file +read_config() -> + Fname = config_fname(), + mnesia_test_lib:log("Consulting file ~s...~n", [Fname]), + case file:consult(Fname) of + {ok, Config} -> + mnesia_test_lib:log("Read config ~w~n", [Config]), + Config; + _Error -> + Config = mnesia_test_lib:default_config(), + mnesia_test_lib:log("<>WARNING<> Using default config: ~w~n", [Config]), + Config + end. + +%% Write new default config file +write_config(Config) when is_list(Config) -> + Fname = config_fname(), + {ok, Fd} = file:open(Fname, write), + write_list(Fd, Config), + file:close(Fd). + +write_list(Fd, [H | T]) -> + ok = io:format(Fd, "~p.~n",[H]), + write_list(Fd, T); +write_list(_, []) -> + ok. + +test_case_fname() -> + "mnesia_test_case_info". + +%% Read name of test case +read_test_case() -> + Fname = test_case_fname(), + case file:open(Fname, [read]) of + {ok, Fd} -> + Res = io:read(Fd, []), + file:close(Fd), + case Res of + {ok, TestCase} -> + mnesia_test_lib:log("Using test case ~w from file ~s~n", + [TestCase, Fname]), + TestCase; + {error, _} -> + default_test_case(Fname) + end; + {error, _} -> + default_test_case(Fname) + end. + +default_test_case(Fname) -> + TestCase = all, + mnesia_test_lib:log("<>WARNING<> Cannot read file ~s, " + "using default test case: ~w~n", + [Fname, TestCase]), + TestCase. + +write_test_case(TestCase) -> + Fname = test_case_fname(), + {ok, Fd} = file:open(Fname, write), + ok = io:format(Fd, "~p.~n",[TestCase]), + file:close(Fd). + +append_test_case_info(TestCase, TestCaseInfo) -> + Fname = test_case_fname(), + {ok, Fd} = file:open(Fname, [read, write]), + ok = io:format(Fd, "~p.~n",[TestCase]), + ok = io:format(Fd, "~p.~n",[TestCaseInfo]), + file:close(Fd), + TestCaseInfo. + +%% Generate HTML pages from the test case structure +doc() -> + doc(suite). + +doc(Case) -> + mnesia_test_lib:doc(resolve(Case)). + +%% Display out the test case structure +struct() -> + struct(suite). + +struct(Case) -> + mnesia_test_lib:struct([resolve(Case)]). + +%% Shutdown all nodes with erlang:halt/0 +shutdown() -> + mnesia_test_lib:shutdown(). + +%% Ping all nodes in config spec +ping() -> + Config = read_config(), + Nodes = mnesia_test_lib:select_nodes(all, Config, ?FILE, ?LINE), + [{N, net_adm:ping(N)} || N <- Nodes]. + +%% Slave start all nodes in config spec +start_nodes() -> + Config = read_config(), + Nodes = mnesia_test_lib:select_nodes(all, Config, ?FILE, ?LINE), + mnesia_test_lib:init_nodes(Nodes, ?FILE, ?LINE), + ping(). + +%% loop one testcase /suite until it fails + +loop(Case) -> + loop_1(Case,-1,read_config()). + +loop(M,F) when is_atom(F) -> + loop_1({M,F},-1,read_config()); +loop(Case,N) when is_integer(N) -> + loop_1(Case, N,read_config()). + +loop(M,F,N) when is_integer(N) -> + loop_1({M,F},N,read_config()). + +loop_1(Case,N,Config) when N /= 0 -> + io:format("Loop test ~p ~n", [abs(N)]), + case ok_result(Res = t(Case,Config)) of + true -> + loop_1(Case,N-1,Config); + error -> + Res + end; +loop_1(_,_,_) -> + ok. + +ok_result([{_T,{ok,_,_}}|R]) -> + ok_result(R); +ok_result([{_T,{TC,List}}|R]) when is_tuple(TC), is_list(List) -> + ok_result(List) andalso ok_result(R); +ok_result([]) -> true; +ok_result(_) -> error. diff --git a/lib/mnesia/vsn.mk b/lib/mnesia/vsn.mk index 31cc8f8513..2780b737b6 100644 --- a/lib/mnesia/vsn.mk +++ b/lib/mnesia/vsn.mk @@ -1,7 +1,8 @@ -MNESIA_VSN = 4.4.13 +MNESIA_VSN = 4.4.14 -TICKETS = OTP-8402 OTP-8406 +TICKETS = OTP-8519 +#TICKETS_4.4.13 = OTP-8402 OTP-8406 #TICKETS_4.4.12 = OTP-8250 #TICKETS_4.4.11 = OTP-8074 #TICKETS_4.4.10 = OTP-7928 OTP-7968 OTP-8002 diff --git a/lib/public_key/src/pubkey_crypto.erl b/lib/public_key/src/pubkey_crypto.erl index 4ab655e977..7b7abb1c56 100644 --- a/lib/public_key/src/pubkey_crypto.erl +++ b/lib/public_key/src/pubkey_crypto.erl @@ -106,6 +106,11 @@ sign(DigestType, PlainText, #'RSAPrivateKey'{modulus = N, publicExponent = E, crypto:mpint(N), crypto:mpint(D)]); +sign(none, Hash, #'DSAPrivateKey'{p = P, q = Q, g = G, x = X}) -> + crypto:dss_sign(none, Hash, + [crypto:mpint(P), crypto:mpint(Q), + crypto:mpint(G), crypto:mpint(X)]); + sign(sha, PlainText, #'DSAPrivateKey'{p = P, q = Q, g = G, x = X}) -> crypto:dss_sign(sized_binary(PlainText), [crypto:mpint(P), crypto:mpint(Q), @@ -128,6 +133,12 @@ verify(DigestType, PlainText, Signature, sized_binary(Signature), [crypto:mpint(Exp), crypto:mpint(Mod)]); +verify(none, Hash, Signature, Key, #'Dss-Parms'{p = P, q = Q, g = G}) -> + crypto:dss_verify(none, Hash, + sized_binary(Signature), + [crypto:mpint(P), crypto:mpint(Q), + crypto:mpint(G), crypto:mpint(Key)]); + verify(sha, PlainText, Signature, Key, #'Dss-Parms'{p = P, q = Q, g = G}) -> crypto:dss_verify(sized_binary(PlainText), sized_binary(Signature), diff --git a/lib/public_key/src/public_key.erl b/lib/public_key/src/public_key.erl index d1d45f21a0..12354eee5d 100644 --- a/lib/public_key/src/public_key.erl +++ b/lib/public_key/src/public_key.erl @@ -360,7 +360,9 @@ verify_signature(PlainText, DigestType, Signature, #'RSAPublicKey'{} = Key, pubkey_crypto:verify(DigestType, PlainText, Signature, Key, KeyParams); verify_signature(PlainText, sha, Signature, Key, #'Dss-Parms'{} = KeyParams) when is_binary(PlainText), is_binary(Signature), is_integer(Key) -> - pubkey_crypto:verify(sha, PlainText, Signature, Key, KeyParams). + pubkey_crypto:verify(sha, PlainText, Signature, Key, KeyParams); +verify_signature(Hash, none, Signature, Key, KeyParams) -> + pubkey_crypto:verify(none, Hash, Signature, Key, KeyParams). verify_signature(DerCert, Key, #'Dss-Parms'{} = KeyParams) when is_binary(DerCert), is_integer(Key) -> diff --git a/lib/snmp/test/snmp_agent_test.erl b/lib/snmp/test/snmp_agent_test.erl index 2534147769..9d2e9969c4 100644 --- a/lib/snmp/test/snmp_agent_test.erl +++ b/lib/snmp/test/snmp_agent_test.erl @@ -5260,7 +5260,35 @@ otp_1131_2(X) -> ?P(otp_1131_2), otp_1131(X). otp_1131_3(X) -> %% <CONDITIONAL-SKIP> - Skippable = [{unix, [darwin]}], + %% This is intended to catch Montavista Linux 4.0/ppc (2.6.5) + %% Montavista Linux looks like a Debian distro (/etc/issue) + LinuxVersionVerify = + fun() -> + case os:cmd("uname -m") of + "ppc" ++ _ -> + case file:read_file_info("/etc/issue") of + {ok, _} -> + case os:cmd("grep -i montavista /etc/issue") of + Info when (is_list(Info) andalso + (length(Info) > 0)) -> + case os:version() of + {2, 6, 10} -> + true; + _ -> + false + end; + _ -> % Maybe plain Debian or Ubuntu + false + end; + _ -> + %% Not a Debian based distro + false + end; + _ -> + false + end + end, + Skippable = [{unix, [darwin, {linux, LinuxVersionVerify}]}], Condition = fun() -> ?OS_BASED_SKIP(Skippable) end, ?NON_PC_TC_MAYBE_SKIP(X, Condition), %% </CONDITIONAL-SKIP> diff --git a/lib/snmp/test/snmp_manager_test.erl b/lib/snmp/test/snmp_manager_test.erl index 518b8b34de..cef96417dc 100644 --- a/lib/snmp/test/snmp_manager_test.erl +++ b/lib/snmp/test/snmp_manager_test.erl @@ -795,6 +795,35 @@ notify_started02(suite) -> []; notify_started02(Config) when is_list(Config) -> process_flag(trap_exit, true), put(tname,ns02), + + %% <CONDITIONAL-SKIP> + %% The point of this is to catch machines running + %% SLES9 (2.6.5) + LinuxVersionVerify = + fun() -> + case os:cmd("uname -m") of + "i686" ++ _ -> +%% io:format("found an i686 machine, " +%% "now check version~n", []), + case os:version() of + {2, 6, Rev} when Rev >= 16 -> + true; + {2, Min, _} when Min > 6 -> + true; + {Maj, _, _} when Maj > 2 -> + true; + _ -> + false + end; + _ -> + true + end + end, + Skippable = [{unix, [{linux, LinuxVersionVerify}]}], + Condition = fun() -> ?OS_BASED_SKIP(Skippable) end, + ?NON_PC_TC_MAYBE_SKIP(Config, Condition), + %% </CONDITIONAL-SKIP> + p("starting with Config: ~n~p", [Config]), ConfDir = ?config(manager_conf_dir, Config), diff --git a/lib/snmp/test/snmp_manager_user_test.erl b/lib/snmp/test/snmp_manager_user_test.erl index 24ed3b0b73..0f47d70873 100644 --- a/lib/snmp/test/snmp_manager_user_test.erl +++ b/lib/snmp/test/snmp_manager_user_test.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2004-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 2004-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% @@ -822,10 +822,39 @@ register_monitor_and_crash3(doc) -> "Start a single user process, " "register-monitor one user and register one user, " "crash the single user process."; -register_monitor_and_crash3(Conf) when is_list(Conf) -> +register_monitor_and_crash3(Conf) when is_list(Conf) -> + process_flag(trap_exit, true), put(tname,rlac3), + + %% <CONDITIONAL-SKIP> + %% The point of this is to catch machines running + %% SLES9 (2.6.5) + LinuxVersionVerify = + fun() -> + case os:cmd("uname -m") of + "i686" ++ _ -> +%% io:format("found an i686 machine, " +%% "now check version~n", []), + case os:version() of + {2, 6, Rev} when Rev >= 16 -> + true; + {2, Min, _} when Min > 6 -> + true; + {Maj, _, _} when Maj > 2 -> + true; + _ -> + false + end; + _ -> + true + end + end, + Skippable = [{unix, [{linux, LinuxVersionVerify}]}], + Condition = fun() -> ?OS_BASED_SKIP(Skippable) end, + ?NON_PC_TC_MAYBE_SKIP(Conf, Condition), + %% </CONDITIONAL-SKIP> + p("start"), - process_flag(trap_exit, true), ConfDir = ?config(manager_conf_dir, Conf), DbDir = ?config(manager_db_dir, Conf), diff --git a/lib/snmp/test/snmp_test_lib.erl b/lib/snmp/test/snmp_test_lib.erl index 2586b66a13..54839d989b 100644 --- a/lib/snmp/test/snmp_test_lib.erl +++ b/lib/snmp/test/snmp_test_lib.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2002-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 2002-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% @@ -172,7 +172,17 @@ os_based_skip(Skippable) when is_list(Skippable) -> {value, {OsFam, OsName}} -> true; {value, {OsFam, OsNames}} when is_list(OsNames) -> - lists:member(OsName, OsNames); + case lists:member(OsName, OsNames) of + true -> + true; + false -> + case lists:keymember(OsName, 1, OsNames) of + {value, {OsName, Check}} when is_function(Check) -> + Check(); + _ -> + false + end + end; _ -> false end diff --git a/lib/ssl/doc/src/notes.xml b/lib/ssl/doc/src/notes.xml index 8028e94484..f213bd11ae 100644 --- a/lib/ssl/doc/src/notes.xml +++ b/lib/ssl/doc/src/notes.xml @@ -32,22 +32,23 @@ </p> <section><title>SSL 3.11.1</title> - + <section><title>Fixed Bugs and Malfunctions</title> - <list> - <item> + <list> + <item> <p> Fixed handling of several ssl/tls packets arriving at the - same time. This was broken during a refactoring of the + same time. This was broken during a refactoring of the code.</p> - <p> - Own Id: OTP-8679</p> - </item> + <p> + Own Id: OTP-8679</p> + </item> </list> </section> + <section><title>Improvements and New Features</title> - <list> + <list> <item> <p> Added missing checks for padding and Mac value. Removed @@ -75,13 +76,6 @@ </item> <item> <p> - New ssl now support client/server-certificates signed by - dsa keys.</p> - <p> - Own Id: OTP-8587</p> - </item> - <item> - <p> Alert handling has been improved to better handle unexpected but valid messages and the implementation is also changed to avoid timing related issues that could @@ -94,7 +88,7 @@ </item> </list> </section> - + </section> <section><title>SSL 3.11</title> diff --git a/lib/ssl/src/ssl.appup.src b/lib/ssl/src/ssl.appup.src index e8ae6846aa..52a41617bb 100644 --- a/lib/ssl/src/ssl.appup.src +++ b/lib/ssl/src/ssl.appup.src @@ -1,6 +1,7 @@ %% -*- erlang -*- {"%VSN%", [ + {"3.11", [{restart_application, ssl}]}, {"3.10", [{restart_application, ssl}]}, {"3.10.1", [{restart_application, ssl}]}, {"3.10.2", [{restart_application, ssl}]}, @@ -13,6 +14,7 @@ {"3.10.9", [{restart_application, ssl}]} ], [ + {"3.11", [{restart_application, ssl}]}, {"3.10", [{restart_application, ssl}]}, {"3.10.1", [{restart_application, ssl}]}, {"3.10.2", [{restart_application, ssl}]}, diff --git a/lib/ssl/src/ssl_ssl3.erl b/lib/ssl/src/ssl_ssl3.erl index 1cecd10e81..400298a322 100644 --- a/lib/ssl/src/ssl_ssl3.erl +++ b/lib/ssl/src/ssl_ssl3.erl @@ -147,7 +147,7 @@ suites() -> ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA, ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA, ?TLS_RSA_WITH_AES_128_CBC_SHA, - %% ?TLS_RSA_WITH_IDEA_CBC_SHA, + ?TLS_RSA_WITH_IDEA_CBC_SHA, ?TLS_RSA_WITH_RC4_128_SHA, ?TLS_RSA_WITH_RC4_128_MD5, ?TLS_RSA_WITH_DES_CBC_SHA diff --git a/lib/ssl/test/ssl_test_lib.erl b/lib/ssl/test/ssl_test_lib.erl index d11acc8130..40715dbf30 100644 --- a/lib/ssl/test/ssl_test_lib.erl +++ b/lib/ssl/test/ssl_test_lib.erl @@ -319,24 +319,34 @@ cert_options(Config) -> make_dsa_cert(Config) -> - ServerCaInfo = {ServerCaCert, _} = erl_make_certs:make_cert([{key, dsa}]), - {ServerCert, ServerCertKey} = erl_make_certs:make_cert([{key, dsa}, {issuer, ServerCaInfo}]), - ServerCaCertFile = filename:join([?config(priv_dir, Config), - "server", "dsa_cacerts.pem"]), - ServerCertFile = filename:join([?config(priv_dir, Config), - "server", "dsa_cert.pem"]), - ServerKeyFile = filename:join([?config(priv_dir, Config), - "server", "dsa_key.pem"]), - - public_key:der_to_pem(ServerCaCertFile, [{cert, ServerCaCert, not_encrypted}]), - public_key:der_to_pem(ServerCertFile, [{cert, ServerCert, not_encrypted}]), - public_key:der_to_pem(ServerKeyFile, [ServerCertKey]), - + + {ServerCaCertFile, ServerCertFile, ServerKeyFile} = make_dsa_cert_files("server", Config), + {ClientCaCertFile, ClientCertFile, ClientKeyFile} = make_dsa_cert_files("client", Config), [{server_dsa_opts, [{ssl_imp, new},{reuseaddr, true}, {cacertfile, ServerCaCertFile}, - {certfile, ServerCertFile}, {keyfile, ServerKeyFile}]} | Config]. + {certfile, ServerCertFile}, {keyfile, ServerKeyFile}]}, + {client_dsa_opts, [{ssl_imp, new},{reuseaddr, true}, + {cacertfile, ClientCaCertFile}, + {certfile, ClientCertFile}, {keyfile, ClientKeyFile}]} + | Config]. + + +make_dsa_cert_files(RoleStr, Config) -> + CaInfo = {CaCert, _} = erl_make_certs:make_cert([{key, dsa}]), + {Cert, CertKey} = erl_make_certs:make_cert([{key, dsa}, {issuer, CaInfo}]), + CaCertFile = filename:join([?config(priv_dir, Config), + RoleStr, "dsa_cacerts.pem"]), + CertFile = filename:join([?config(priv_dir, Config), + RoleStr, "dsa_cert.pem"]), + KeyFile = filename:join([?config(priv_dir, Config), + RoleStr, "dsa_key.pem"]), + public_key:der_to_pem(CaCertFile, [{cert, CaCert, not_encrypted}]), + public_key:der_to_pem(CertFile, [{cert, Cert, not_encrypted}]), + public_key:der_to_pem(KeyFile, [CertKey]), + {CaCertFile, CertFile, KeyFile}. + start_upgrade_server(Args) -> Result = spawn_link(?MODULE, run_upgrade_server, [Args]), receive diff --git a/lib/ssl/test/ssl_to_openssl_SUITE.erl b/lib/ssl/test/ssl_to_openssl_SUITE.erl index e4c77b2fb4..4981ac0424 100644 --- a/lib/ssl/test/ssl_to_openssl_SUITE.erl +++ b/lib/ssl/test/ssl_to_openssl_SUITE.erl @@ -143,7 +143,9 @@ all(doc) -> all(suite) -> [erlang_client_openssl_server, erlang_server_openssl_client, - erlang_server_openssl_client_dsa_cert, + %% Comment out when new crypto sign functions is available + %%erlang_client_openssl_server_dsa_cert, + %%erlang_server_openssl_client_dsa_cert, erlang_server_openssl_client_reuse_session, erlang_client_openssl_server_renegotiate, erlang_client_openssl_server_no_wrap_sequence_number, @@ -250,18 +252,70 @@ erlang_server_openssl_client(Config) when is_list(Config) -> %%-------------------------------------------------------------------- +erlang_client_openssl_server_dsa_cert(doc) -> + ["Test erlang server with openssl client"]; +erlang_client_openssl_server_dsa_cert(suite) -> + []; +erlang_client_openssl_server_dsa_cert(Config) when is_list(Config) -> + process_flag(trap_exit, true), + ClientOpts = ?config(client_dsa_opts, Config), + ServerOpts = ?config(server_dsa_opts, Config), + + {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), + + Data = "From openssl to erlang", + + Port = ssl_test_lib:inet_port(node()), + CaCertFile = proplists:get_value(cacertfile, ServerOpts), + CertFile = proplists:get_value(certfile, ServerOpts), + KeyFile = proplists:get_value(keyfile, ServerOpts), + + Cmd = "openssl s_server -accept " ++ integer_to_list(Port) ++ + " -cert " ++ CertFile ++ " -CAfile " ++ CaCertFile + ++ " -key " ++ KeyFile ++ " -Verify 2 -tls1 -msg", + + test_server:format("openssl cmd: ~p~n", [Cmd]), + + OpensslPort = open_port({spawn, Cmd}, [stderr_to_stdout]), + + wait_for_openssl_server(), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, + erlang_ssl_receive, [Data]}}, + {options, ClientOpts}]), + + port_command(OpensslPort, Data), + + ssl_test_lib:check_result(Client, ok), + + %% Clean close down! Server needs to be closed first !! + close_port(OpensslPort), + + ssl_test_lib:close(Client), + process_flag(trap_exit, false), + ok. + +%%-------------------------------------------------------------------- + erlang_server_openssl_client_dsa_cert(doc) -> ["Test erlang server with openssl client"]; erlang_server_openssl_client_dsa_cert(suite) -> []; erlang_server_openssl_client_dsa_cert(Config) when is_list(Config) -> process_flag(trap_exit, true), + ClientOpts = ?config(client_dsa_opts, Config), ServerOpts = ?config(server_dsa_opts, Config), {_, ServerNode, _} = ssl_test_lib:run_where(Config), Data = "From openssl to erlang", - + CaCertFile = proplists:get_value(cacertfile, ClientOpts), + CertFile = proplists:get_value(certfile, ClientOpts), + KeyFile = proplists:get_value(keyfile, ClientOpts), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, {mfa, {?MODULE, erlang_ssl_receive, [Data]}}, @@ -269,7 +323,8 @@ erlang_server_openssl_client_dsa_cert(Config) when is_list(Config) -> Port = ssl_test_lib:inet_port(Server), Cmd = "openssl s_client -port " ++ integer_to_list(Port) ++ - " -host localhost -tls1 -msg", + " -host localhost " ++ " -cert " ++ CertFile ++ " -CAfile " ++ CaCertFile + ++ " -key " ++ KeyFile ++ " -tls1 -msg", test_server:format("openssl cmd: ~p~n", [Cmd]), @@ -283,8 +338,6 @@ erlang_server_openssl_client_dsa_cert(Config) when is_list(Config) -> close_port(OpenSslPort), process_flag(trap_exit, false), ok. - - %%-------------------------------------------------------------------- erlang_server_openssl_client_reuse_session(doc) -> diff --git a/lib/ssl/vsn.mk b/lib/ssl/vsn.mk index 5d8be1cd0b..813ce91e32 100644 --- a/lib/ssl/vsn.mk +++ b/lib/ssl/vsn.mk @@ -23,7 +23,6 @@ TICKETS = OTP-8679 \ OTP-7047 \ OTP-7049 \ OTP-8568 \ - OTP-8587 \ OTP-8588 #TICKETS_3.11 = OTP-8517 \ diff --git a/lib/wx/doc/src/notes.xml b/lib/wx/doc/src/notes.xml index 92933c348b..34c56091aa 100644 --- a/lib/wx/doc/src/notes.xml +++ b/lib/wx/doc/src/notes.xml @@ -31,6 +31,23 @@ <p>This document describes the changes made to the wxErlang application.</p> +<section><title>Wx 0.98.6</title> + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Calling <c>sys:get_status()</c> for processes that have + globally registered names that were not atoms would cause + a crash. Corrected. (Thanks to Steve Vinoski.)</p> + <p> + Own Id: OTP-8656</p> + </item> + </list> + </section> + +</section> + <section><title>Wx 0.98.5</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/wx/vsn.mk b/lib/wx/vsn.mk index 54ab92cad2..4ed22d2256 100644 --- a/lib/wx/vsn.mk +++ b/lib/wx/vsn.mk @@ -1,6 +1,8 @@ -WX_VSN = 0.98.5 +WX_VSN = 0.98.6 -TICKETS = OTP-8330 OTP-8461 OTP-8408 OTP-8455 OTP-8462 +TICKETS = OTP-8656 + +TICKETS_0.98.5 = OTP-8330 OTP-8461 OTP-8408 OTP-8455 OTP-8462 TICKETS_0.98.4 = OTP-8243 OTP-8250 OTP-8292 TICKETS_0.98.3 = OTP-8138 OTP-8126 OTP-8083 TICKETS_0.98.2 = OTP-7943 |