aboutsummaryrefslogtreecommitdiffstats
path: root/lib/stdlib/test/erl_expand_records_SUITE.erl
diff options
context:
space:
mode:
authorErlang/OTP <[email protected]>2009-11-20 14:54:40 +0000
committerErlang/OTP <[email protected]>2009-11-20 14:54:40 +0000
commit84adefa331c4159d432d22840663c38f155cd4c1 (patch)
treebff9a9c66adda4df2106dfd0e5c053ab182a12bd /lib/stdlib/test/erl_expand_records_SUITE.erl
downloadotp-84adefa331c4159d432d22840663c38f155cd4c1.tar.gz
otp-84adefa331c4159d432d22840663c38f155cd4c1.tar.bz2
otp-84adefa331c4159d432d22840663c38f155cd4c1.zip
The R13B03 release.OTP_R13B03
Diffstat (limited to 'lib/stdlib/test/erl_expand_records_SUITE.erl')
-rw-r--r--lib/stdlib/test/erl_expand_records_SUITE.erl823
1 files changed, 823 insertions, 0 deletions
diff --git a/lib/stdlib/test/erl_expand_records_SUITE.erl b/lib/stdlib/test/erl_expand_records_SUITE.erl
new file mode 100644
index 0000000000..1d621c65df
--- /dev/null
+++ b/lib/stdlib/test/erl_expand_records_SUITE.erl
@@ -0,0 +1,823 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2005-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(erl_expand_records_SUITE).
+
+%-define(debug, true).
+
+-ifdef(debug).
+-define(line, put(line, ?LINE), ).
+-define(config(X,Y), foo).
+-define(privdir, "erl_expand_records_SUITE_priv").
+-define(t, test_server).
+-else.
+-include("test_server.hrl").
+-define(privdir, ?config(priv_dir, Config)).
+-endif.
+
+-export([all/1, init_per_testcase/2, fin_per_testcase/2]).
+
+-export([abstract_module/1, attributes/1, expr/1, guard/1,
+ init/1, pattern/1, strict/1, update/1,
+ tickets/1, otp_5915/1, otp_7931/1, otp_5990/1,
+ otp_7078/1, otp_7101/1]).
+
+% Default timetrap timeout (set in init_per_testcase).
+-define(default_timeout, ?t:minutes(1)).
+
+init_per_testcase(_Case, Config) ->
+ ?line Dog = ?t:timetrap(?default_timeout),
+ [{watchdog, Dog} | Config].
+
+fin_per_testcase(_Case, _Config) ->
+ Dog = ?config(watchdog, _Config),
+ test_server:timetrap_cancel(Dog),
+ ok.
+
+all(suite) ->
+ [abstract_module, attributes, expr, guard, init, pattern,
+ strict, update, tickets].
+
+abstract_module(doc) ->
+ "Compile an abstract module.";
+abstract_module(suite) -> [];
+abstract_module(Config) when is_list(Config) ->
+ %% erl_expand_records does not handle abstract modules. But anyway...
+ File = filename("param.erl", Config),
+ Beam = filename("param.beam", Config),
+ Test = <<"-module(param, [A, B]).
+
+ -export([args/1]).
+
+ args(C) ->
+ X = local(C),
+ Z = new(A, B),
+ {X, Z}.
+
+ local(C) ->
+ module_info(C).
+ ">>,
+
+ ?line ok = file:write_file(File, Test),
+ ?line {ok, param} = compile:file(File, [{outdir,?privdir}]),
+
+ ?line ok = file:delete(File),
+ ?line ok = file:delete(Beam),
+ ok.
+
+attributes(doc) ->
+ "Import module and functions.";
+attributes(suite) -> [];
+attributes(Config) when is_list(Config) ->
+ Ts = [
+ <<"-import(erl_expand_records_SUITE).
+ -import(lists, [append/2, reverse/1]).
+
+ -record(r, {a,b}).
+
+ t() ->
+ [2,1] = reverse(append([1],[2])),
+ 3 = length([1,2,3]),
+ 3 = record_info(size, r),
+ [a, b] = record_info(fields, r),
+ [] = erl_expand_records_SUITE:attributes(suite),
+ ok.
+ ">>
+ ],
+ ?line run(Config, Ts),
+ ok.
+
+expr(doc) ->
+ "Some expressions.";
+expr(suite) -> [];
+expr(Config) when is_list(Config) ->
+ Ts = [
+ <<"
+ -record(r, {a,b,c}).
+
+ t() ->
+ [1,2] = [R#r.a || R <- [#r{a = 1}, #r{a = 2}, #r{a = 3}],
+ R#r.a < 3],
+ [1,2] = [R#r.a || R <- [#r{a = 1}, #r{a = 2}, #r{a = 3}],
+ begin R#r.a < 3 end],
+ [1,2,3] = [R#r.a || R <- [#r{a = 1}, #r{a = 2}, #r{a = 3}],
+ begin is_record(R, r) end],
+ [1,2,3] = [R#r.a || R <- [#r{a = 1}, #r{a = 2}, #r{a = 3}],
+ begin erlang:is_record(R, r) end],
+ ok.
+ ">>,
+ <<"
+ -record(r, {a,b,c}).
+
+ f(X) -> X.
+
+ t() ->
+ A = {$c, 1, 3.14, a, \"hi\", [], [a,b]},
+ R = #r{a = element(6, A), b = #r.b},
+ 3 = R#r.b,
+ <<1:8>> = <<(begin erlang:element(2, A) end):8>>,
+ self() ! {a, message, []},
+ One = 1 = fun f/1(1),
+ 2 = fun(X) -> X end(One + One),
+ 3 = fun exprec_test:f/1(3),
+ 4 = {exprec_test,f}(4),
+ 5 = ''.f(5),
+ L = receive
+ {a,message,L0} ->
+ L0
+ end,
+ case catch a.b.c:foo(bar) of
+ {'EXIT', _} -> ok
+ end,
+ _ = receive %Suppress warning.
+ noop ->
+ 1/(length(L) - 0)
+ after 0 ->
+ ok
+ end,
+ if
+ R#r.c =:= undefined ->
+ ok;
+ true ->
+ not_ok
+ end.
+ ">>
+ ],
+
+ %% The code above should run equally well with and without
+ %% strict record tests.
+ ?line run(Config, Ts, [no_strict_record_tests]),
+ ?line run(Config, Ts, [strict_record_tests]),
+
+ ok.
+
+guard(doc) ->
+ "is_record in guards.";
+guard(suite) -> [];
+guard(Config) when is_list(Config) ->
+ File = filename("guard.erl", Config),
+ Beam = filename("guard.beam", Config),
+ Test = <<"-module(guard, [A, B]).
+
+ -export([t/1]).
+
+ -record(r, {a,b}).
+
+ t(_) when is_record(3, r) ->
+ 1;
+ t(_) when is_record(a, r) ->
+ 2;
+ t(_) when is_record(3.14, r) ->
+ 3;
+ t(_) when is_record([], r) ->
+ 4;
+ t(_) when is_record([a], r) ->
+ 5;
+ t(_) when is_record($a, r) ->
+ 6;
+ t(_) when is_record(\"foo\", r) ->
+ 7;
+ t(_) when is_record(#r.a, r) ->
+ 8;
+ t(_) when is_record(<<\"foo\">>, r) -> % line 23
+ 9;
+ t(_) when is_record(1 + 2, r) ->
+ 10;
+ t(_) when is_record(+ 3, r) ->
+ 11;
+ t(_) ->
+ 12.
+ ">>,
+
+ ?line ok = file:write_file(File, Test),
+ ?line {ok, guard, Ws} = compile:file(File, [return,{outdir,?privdir}]),
+ ?line Warnings = [L || {_File,WL} <- Ws, {L,_M,nomatch_guard} <- WL],
+ ?line [7,9,11,13,15,17,19,21,23,25,27] = Warnings,
+
+ ?line ok = file:delete(File),
+ ?line ok = file:delete(Beam),
+ ok.
+
+init(doc) ->
+ "Wildcard initialisation.";
+init(suite) -> [];
+init(Config) when is_list(Config) ->
+ Ts = [
+ <<"
+ -record(r, {a,b,c,d = foo}).
+
+ t() ->
+ R = #r{_ = init, b = b},
+ #r{c = init, b = b, a = init} = R,
+ case R of
+ #r{b = b, _ = init} -> ok;
+ _ -> not_ok
+ end.
+ ">>
+ ],
+ ?line run(Config, Ts),
+ ok.
+
+pattern(doc) ->
+ "Some patterns.";
+pattern(suite) -> [];
+pattern(Config) when is_list(Config) ->
+ Ts = [
+ <<"-import(erl_expand_records_SUITE).
+ -import(lists, [append/2, reverse/1]).
+
+ -record(r, {a,b}).
+
+ t() ->
+ 1 = t(#r{}),
+ 2 = t($a),
+ 3 = t(1000),
+ 4 = t({1000}),
+ 5 = t(3),
+ 6 = t(-3.14),
+ 7 = t({4.0}),
+ 8 = t(3.14),
+ 9 = t(\"str\"),
+ 10 = t([]),
+ 11 = t([a|b]),
+ 12 = t(\"string\"),
+ 13 = t({[]}),
+ 14 = t({a,b}),
+ 15 = t({{}}),
+ 16 = t({tuple,tupel}),
+ 17 = t(4),
+ 18 = t(10),
+ 19 = t({a}),
+ 20 = t(<<100:8,220:8>>),
+ 21 = t(#r{a = #r{}}),
+ 22 = t(2),
+ 23 = t(#r{a = #r{}, b = b}),
+ 24 = t(a.b.c),
+ ok.
+
+ t(a.b.c) ->
+ 24;
+ t($a) ->
+ 2;
+ t(3) ->
+ 5;
+ t(3.14) ->
+ 8;
+ t(\"str\") ->
+ 9;
+ t([]) ->
+ 10;
+ t([a|b]) ->
+ 11;
+ t(L) when is_list(L) ->
+ 12;
+ t({L}) when list(L) ->
+ 13;
+ t({a,b}) ->
+ 14;
+ t({T}) when is_tuple(T) ->
+ 15;
+ t(+ 4) ->
+ 17;
+ t(3+7) ->
+ 18;
+ t(<<A:8, (100+120):8>>) when A =:= 100 ->
+ 20;
+ t(#r{a = #r{}, b = undefined}) ->
+ 21;
+ t(#r.a) ->
+ 22;
+ t(A) when is_record(A, r), record(element(2, A), r) ->
+ 23;
+ t(A) when is_record(A, r) ->
+ 1;
+ t(I) when is_integer(I) ->
+ 3;
+ t({I}) when integer(I) ->
+ 4;
+ t({F}) when float(F) ->
+ 7;
+ t({A} = B) when A < B ->
+ 19;
+ t(F) when is_float(F) ->
+ 6;
+ t(T) when tuple(T) ->
+ 16.
+ ">>
+ ],
+ ?line run(Config, Ts),
+ ok.
+
+strict(doc) ->
+ "";
+strict(suite) -> [];
+strict(Config) when is_list(Config) ->
+ Ts1 = [
+ <<"-record(r1, {a,b}).
+ -record(r2, {a,b}).
+
+ t() ->
+ A = #r1{a = 1, b = 2},
+ ok = try
+ {1, 2} = {A#r2.a, A#r2.b},
+ not_ok
+ catch error:{badrecord,r2} -> ok
+ end,
+ try
+ case foo of
+ _ when A#r2.a =:= 1 -> not_ok
+ end
+ catch error:_ -> ok
+ end.
+ ">>
+ ],
+ ?line run(Config, Ts1, [strict_record_tests]),
+
+ Ts2 = [
+ <<"-record(r1, {a,b}).
+ -record(r2, {a,b}).
+
+ t() ->
+ A = #r1{a = 1, b = 2},
+ {1, 2} = {A#r2.a, A#r2.b},
+ case foo of
+ _ when A#r2.a =:= 1 -> ok
+ end.
+ ">>
+ ],
+ ?line run(Config, Ts2, [no_strict_record_tests]),
+ ok.
+
+update(doc) ->
+ "Record updates.";
+update(suite) -> [];
+update(Config) when is_list(Config) ->
+ Ts = [
+ <<"-record(r, {a,b,c,d,e,f}).
+
+ t() ->
+ R0 = #r{},
+ R1 = R0#r{a = #r.a, e = {x,y}},
+ 2 = R1#r.a,
+ R2 = R1#r{},
+ true = R1 =:= R2,
+ R3 = R2#r{c = fun(X) -> X end,
+ d = <<\"foo\">>,
+ e = [x,y,z],
+ f = {R0,R1}},
+ R4 = R3#r{a = R3#r{b = #r{}}},
+ true = erlang:is_record((R4#r.a)#r.b, r),
+ #r{a = R0, b = 3, c = 3.14, d = [], e = [[]], f = [{}]} =
+ R4#r{a = R0, b = 3, c = 3.14, d = [], e = [[]], f = [{}]},
+ ok.
+
+ %% Just playing around a bit...
+ t1() ->
+ ((#r{a = (#r{b = #r{}})#r{a = #r{}}})#r{b = #r{}})#r{c = #r{}}.
+
+ t2() ->
+ R0 = #r{},
+ #r{_ = R0#r{a = ok}}.
+ ">>
+ ],
+ ?line run(Config, Ts),
+ ok.
+
+tickets(suite) ->
+ [otp_5915, otp_7931, otp_5990, otp_7078, otp_7101].
+
+otp_5915(doc) ->
+ "Strict record tests in guards.";
+otp_5915(suite) -> [];
+otp_5915(Config) when is_list(Config) ->
+ %% These tests are also run by the compiler's record_SUITE.
+ Ts = [
+ <<"-record(r, {a = 4,b}).
+ -record(r1, {a,b}).
+ -record(r2, {a = #r1{},b,c=length([1,2,3])}).
+ -record(r3, {a = fun(_) -> #r1{} end(1), b}).
+
+ t() ->
+ foo = fun(A) when A#r1.a > A#r1.b -> foo end(#r1{b = 2}),
+ 0 = fun(A) when A#r2.a -> 0 end(#r2{a = true}),
+ 1 = fun(A) when (#r1{a = A})#r1.a > 2 -> 1 end(3),
+ 2 = fun(N) when ((#r2{a = #r{a = 4}, b = length([a,b,c])})#r2.a)#r.a > N ->
+ 2 end(2),
+ 3 = fun(A) when (A#r2.a)#r1.a =:= 3 -> 3 end(#r2{a = #r1{a = 3}}),
+ ok = fun() ->
+ F = fun(A) when record(A#r.a, r1) -> 4;
+ (A) when record(A#r1.a, r1) -> 5
+ end,
+ 5 = F(#r1{a = #r1{}}),
+ 4 = F(#r{a = #r1{}}),
+ ok
+ end(),
+ 3 = fun(A) when record(A#r1.a, r),
+ (A#r1.a)#r.a > 3 -> 3
+ end(#r1{a = #r{a = 4}}),
+ 7 = fun(A) when record(A#r3.a, r1) -> 7 end(#r3{}),
+ [#r1{a = 2,b = 1}] =
+ fun() ->
+ [A || A <- [#r1{a = 1, b = 3},
+ #r2{a = 2,b = 1},
+ #r1{a = 2, b = 1}],
+ A#r1.a >
+ A#r1.b]
+ end(),
+ {[_],b} =
+ fun(L) ->
+ %% A is checked only once:
+ R1 = [{A,B} || A <- L, A#r1.a, B <- L, A#r1.b],
+ A = #r2{a = true},
+ %% A is checked again:
+ B = if A#r1.a -> a; true -> b end,
+ {R1,B}
+ end([#r1{a = true, b = true}]),
+
+ p = fun(A) when (A#r1.a =:= 2) or (A#r2.a =:= 1) -> o;
+ (_) -> p
+ end(#r1{a = 2}),
+
+ o = fun(A) when (A#r1.a =:= 2) orelse (A#r2.a =:= 1) -> o;
+ (_) -> p
+ end(#r1{a = 2}),
+
+ 3 = fun(A) when A#r1.a > 3,
+ record(A, r1) -> 3
+ end(#r1{a = 5}),
+
+ ok = fun() ->
+ F = fun(A) when (A#r2.a =:= 1) orelse (A#r2.a) -> 2;
+ (A) when (A#r1.a =:= 1) orelse (A#r1.a) -> 1;
+ (A) when (A#r2.a =:= 2) andalso (A#r2.b) -> 3
+ end,
+ 1 = F(#r1{a = 1}),
+ 2 = F(#r2{a = true}),
+ 3 = F(#r2{a = 2, b = true}),
+ ok
+ end(),
+
+ b = fun(A) when false or not (A#r.a =:= 1) -> a;
+ (_) -> b
+ end(#r1{a = 1}),
+ b = fun(A) when not (A#r.a =:= 1) or false -> a;
+ (_) -> b
+ end(#r1{a = 1}),
+
+ ok = fun() ->
+ F = fun(A) when not (A#r.a =:= 1) -> yes;
+ (_) -> no
+ end,
+ no = F(#r1{a = 2}),
+ yes = F(#r{a = 2}),
+ no = F(#r{a = 1}),
+ ok
+ end(),
+
+ a = fun(A) when record(A, r),
+ A#r.a =:= 1,
+ A#r.b =:= 2 ->a
+ end(#r{a = 1, b = 2}),
+ a = fun(A) when erlang:is_record(A, r),
+ A#r.a =:= 1,
+ A#r.b =:= 2 -> a
+ end(#r{a = 1, b = 2}),
+ a = fun(A) when is_record(A, r),
+ A#r.a =:= 1,
+ A#r.b =:= 2 -> a
+ end(#r{a = 1, b = 2}),
+
+ nop = fun(A) when (is_record(A, r1) and (A#r1.a > 3)) or (A#r2.a < 1) ->
+ japp;
+ (_) ->
+ nop
+ end(#r2{a = 0}),
+ nop = fun(A) when (A#r1.a > 3) or (A#r2.a < 1) -> japp;
+ (_) ->
+ nop
+ end(#r2{a = 0}),
+
+ ok = fun() ->
+ F = fun(A) when (A#r1.a =:= 2) or (A#r2.a =:= 1) -> o;
+ (_) -> p
+ end,
+ p = F(#r2{a = 1}),
+ p = F(#r1{a = 2}),
+ ok
+ end(),
+
+ ok = fun() ->
+ F = fun(A) when fail, A#r1.a; A#r1.a -> ab;
+ (_) -> bu
+ end,
+ ab = F(#r1{a = true}),
+ bu = F(#r2{a = true}),
+ ok
+ end(),
+
+ both = fun(A) when A#r.a, A#r.b -> both
+ end(#r{a = true, b = true}),
+
+ ok = fun() ->
+ F = fun(A, B) when ((A#r1.a) orelse (B#r2.a))
+ or (B#r2.b) or (A#r1.b) -> true;
+ (_, _) -> false
+ end,
+ true = F(#r1{a = false, b = false}, #r2{a = false, b = true}),
+ false = F(#r1{a = true, b = true}, #r1{a = false, b = true}),
+ ok
+ end(),
+
+ ok.
+ ">>
+ ],
+ ?line run(Config, Ts, [strict_record_tests]),
+ ok.
+
+otp_7931(doc) ->
+ "Test optimization of record accesses and is_record/3 tests in guards";
+otp_7931(suite) -> [];
+otp_7931(Config) when is_list(Config) ->
+ Ts = [
+ <<"-record(r, {a = 4,b}).
+ -record(r1, {a,b}).
+ -record(r2, {a = #r1{},b,c=length([1,2,3])}).
+ -record(r3, {a = fun(_) -> #r1{} end(1), b}).
+
+ t() ->
+ ok = fun() ->
+ F = fun(F, [H,H|T]) when is_record(H, r) ->
+ [H|F(F, T)];
+ (F, [H|T]) when is_record(H, r) ->
+ [H|F(F, T)];
+ (_, []) -> []
+ end,
+ [#r{a=4,b=7},#r{a=1,b=42}] =
+ F(F, [#r{a=4,b=7},#r{a=4,b=7},#r{a=1,b=42}]),
+ {'EXIT',_} = (catch F(F, [#r1{}])),
+ ok
+ end(),
+
+ true = fun() ->
+ R = #r{},
+ if is_record(R, r) -> true; true -> false end
+ end(),
+
+ ok = fun() ->
+ F = fun(true, B) when B#r1.a -> ok;
+ (false, _) -> error
+ end,
+ ok = F(true, #r1{a=true}),
+ error = F(false, anything_goes),
+ {'EXIT',_} = (catch F(true, #r1{})),
+ {'EXIT',_} = (catch F(true, #r{})),
+ ok
+ end(),
+
+ ok = fun() ->
+ F = fun([{a,R}=T]) when R#r.a =:= 42 ->
+ {ok,tuple_size(T)};
+ ([{a,R}=T]) when R#r1.a =:= 7 ->
+ {ok,tuple_size(T)};
+ (_) -> error
+ end,
+ {ok,2} = F([{a,#r{a=42}}]),
+ {ok,2} = F([{a,#r1{a=7}}]),
+ error = F([{a,#r1{}}]),
+ error = F({a,b,c}),
+ error = F([]),
+ ok
+ end(),
+
+ ok = fun() ->
+ F = fun(X, Y, Z) when is_record(X, r1) andalso
+ (is_record(Y, r2) orelse
+ is_record(Z, r3)) -> true;
+ (_, _, _) -> false
+ end,
+ true = F(#r1{}, #r2{}, #r3{}),
+ true = F(#r1{}, #r2{}, blurf),
+ true = F(#r1{}, blurf, #r3{}),
+ false = F(#r1{}, blurf, blurf),
+ false = F(blurf, #r2{}, #r3{}),
+ false = F(blurf, #r2{}, blurf),
+ false = F(blurf, blurf, #r3{}),
+ false = F(blurf, blurf, blurf),
+ ok
+ end(),
+
+ ok = fun() ->
+ F = fun(R=#r{a=42}) when R#r.b =:= 7 ->
+ {ok,R};
+ (_) -> error
+ end,
+ {ok,#r{a=42,b=7}} = F(#r{a=42,b=7}),
+ error = F(#r{}),
+ error = F([a,b,c]),
+ ok
+ end(),
+
+ ok.
+ ">>
+ ],
+ ?line run(Config, Ts, [strict_record_tests]),
+ ok.
+
+otp_5990(doc) ->
+ "OTP-5990. {erlang,is_record}.";
+otp_5990(suite) -> [];
+otp_5990(Config) when is_list(Config) ->
+ Ts = [
+ <<"
+ -record(r, {a,b,c}).
+
+ t() ->
+ [1,2,3] = [R#r.a || R <- [#r{a = 1}, #r{a = 2}, #r{a = 3}],
+ begin {erlang,is_record}(R, r) end],
+ [1,2,3] = [R#r.a || R <- [#r{a = 1}, #r{a = 2}, #r{a = 3}],
+ begin {erlang,is_record}(R, r) end],
+ ok.
+ ">>,
+
+ <<"
+ -record('OrdSet', {orddata = {},
+ ordtype = {}}).
+
+ to_sets(S) when tuple(S#'OrdSet'.ordtype) ->
+ ok.
+
+ lc(S) ->
+ [X || X <- [S], tuple(X#'OrdSet'.ordtype)].
+
+ t() ->
+ S = #'OrdSet'{},
+ ok = to_sets(S),
+ [S] = lc(S),
+ ok.
+ ">>
+ ],
+ ?line run(Config, Ts, [strict_record_tests]),
+ ok.
+
+
+otp_7078(doc) ->
+ "OTP-7078. Record update: missing test.";
+otp_7078(suite) -> [];
+otp_7078(Config) when is_list(Config) ->
+ Ts = [
+ <<"
+ -record(r, {f}).
+ -record(r2, {}).
+
+ t() ->
+ {'EXIT',_} = (catch (#r2{})#r{}),
+ {'EXIT',_} = (catch (#r2{})#r{f = 2}),
+ ok.
+ ">>,
+
+ <<"
+ -record(r, {f}).
+
+ maker(F) ->
+ put(a, get(a)+1),
+ #r{f = F}.
+
+ t() ->
+ put(a, 0),
+ (maker(2))#r{},
+ 1 = get(a),
+ ok.
+ ">>
+
+ ],
+ ?line run(Config, Ts, [strict_record_tests]),
+ ok.
+
+-record(otp_7101, {a,b,c=[],d=[],e=[]}).
+
+otp_7101(doc) ->
+ "OTP-7101. Record update: more than one call to setelement/3.";
+otp_7101(suite) -> [];
+otp_7101(Config) when is_list(Config) ->
+ Rec = #otp_7101{},
+
+ %% Spawn a tracer process to count the number of setelement/3 calls.
+ %% The tracer will forward all trace messages to us.
+ Self = self(),
+ Tracer = spawn_link(fun() -> otp_7101_tracer(Self, 0) end),
+ ?line 1 = erlang:trace_pattern({erlang,setelement,3}, true),
+ ?line erlang:trace(self(), true, [{tracer,Tracer},call]),
+
+ %% Update the record.
+ ?line #otp_7101{a=2,b=1,c=[],d=[],e=[]} = otp_7101_update1(Rec),
+ ?line #otp_7101{a=1,b=2,c=[],d=[],e=[]} = otp_7101_update2(Rec),
+ ?line #otp_7101{a=2,b=1,c=[],d=[],e=[]} = otp_7101_update3(Rec),
+ ?line #otp_7101{a=1,b=2,c=[],d=[],e=[]} = otp_7101_update4(Rec),
+
+ %% Verify that setelement/3 was called the same number of times as
+ %% the number of record updates.
+ ?line Ref = erlang:trace_delivered(Self),
+ receive
+ {trace_delivered, Self, Ref} ->
+ Tracer ! done
+ end,
+ ?line 1 = erlang:trace_pattern({erlang,setelement,3}, false),
+ receive
+ 4 ->
+ ok;
+ Other ->
+ ?line ?t:fail({unexpected,Other})
+ end.
+
+otp_7101_tracer(Parent, N) ->
+ receive
+ {trace,Parent,call,{erlang,setelement,[_,_,_]}} ->
+ otp_7101_tracer(Parent, N+1);
+ done ->
+ Parent ! N
+ end.
+
+otp_7101_update1(R) ->
+ R#otp_7101{b=1,
+ a=2}.
+
+otp_7101_update2(R) ->
+ R#otp_7101{a=1,
+ b=2}.
+
+otp_7101_update3(R) ->
+ R#otp_7101{b=1,a=2}.
+
+otp_7101_update4(R) ->
+ R#otp_7101{a=1,b=2}.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+run(Config, Tests) ->
+ run(Config, Tests, []).
+
+run(Config, Tests, Opts) ->
+ F = fun(P) ->
+ {SourceFile, Mod} = compile_file_mod(Config),
+ _ = compile_file(Config, P, Opts),
+ AbsFile = filename:rootname(SourceFile, ".erl"),
+ code:purge(Mod),
+ code:load_abs(AbsFile, Mod),
+%io:format("run~n"),
+ case catch Mod:t() of
+ {'EXIT', _Reason} = Error ->
+ ?t:format("failed, got ~p~n", [Error]),
+ fail();
+ ok ->
+ ok
+ end
+ end,
+ lists:foreach(F, Tests).
+
+%% Compiles a test module and returns the list of errors and warnings.
+
+compile_file(Config, Test0, Opts0) ->
+ {File, _Mod} = compile_file_mod(Config),
+ Filename = 'exprec_test.erl',
+ Test = list_to_binary(["-module(exprec_test). "
+ "-compile(export_all). ",
+ Test0]),
+ File = filename(Filename, Config),
+ Opts = [export_all,return,{outdir,?privdir}|Opts0],
+ ok = file:write_file(File, Test),
+ {ok, _M, Ws} = compile:file(File, Opts),
+ warnings(File, Ws).
+
+compile_file_mod(Config) ->
+ {filename('exprec_test.erl', Config), exprec_test}.
+
+filename(Name, Config) when is_atom(Name) ->
+ filename(atom_to_list(Name), Config);
+filename(Name, Config) ->
+ filename:join(?privdir, Name).
+
+warnings(File, Ws) ->
+ case lists:append([W || {F, W} <- Ws, F =:= File]) of
+ [] -> [];
+ L -> {warnings, L}
+ end.
+
+fail() ->
+ io:format("failed~n"),
+ ?t:fail().