aboutsummaryrefslogtreecommitdiffstats
path: root/lib/stdlib/test/erl_anno_SUITE.erl
diff options
context:
space:
mode:
authorSverker Eriksson <[email protected]>2017-08-30 20:55:08 +0200
committerSverker Eriksson <[email protected]>2017-08-30 20:55:08 +0200
commit7c67bbddb53c364086f66260701bc54a61c9659c (patch)
tree92ab0d4b91d5e2f6e7a3f9d61ea25089e8a71fe0 /lib/stdlib/test/erl_anno_SUITE.erl
parent97dc5e7f396129222419811c173edc7fa767b0f8 (diff)
parent3b7a6ffddc819bf305353a593904cea9e932e7dc (diff)
downloadotp-7c67bbddb53c364086f66260701bc54a61c9659c.tar.gz
otp-7c67bbddb53c364086f66260701bc54a61c9659c.tar.bz2
otp-7c67bbddb53c364086f66260701bc54a61c9659c.zip
Merge tag 'OTP-19.0' into sverker/19/binary_to_atom-utf8-crash/ERL-474/OTP-14590
Diffstat (limited to 'lib/stdlib/test/erl_anno_SUITE.erl')
-rw-r--r--lib/stdlib/test/erl_anno_SUITE.erl488
1 files changed, 488 insertions, 0 deletions
diff --git a/lib/stdlib/test/erl_anno_SUITE.erl b/lib/stdlib/test/erl_anno_SUITE.erl
new file mode 100644
index 0000000000..71ea9c3e1e
--- /dev/null
+++ b/lib/stdlib/test/erl_anno_SUITE.erl
@@ -0,0 +1,488 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2001-2016. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(erl_anno_SUITE).
+
+%%-define(debug, true).
+
+-ifdef(debug).
+-include_lib("common_test/include/ct.hrl").
+-define(format(S, A), io:format(S, A)).
+-else.
+-include_lib("common_test/include/ct.hrl").
+-define(format(S, A), ok).
+-endif.
+
+-export([all/0, suite/0, groups/0, init_per_suite/1, end_per_suite/1,
+ init_per_group/2, end_per_group/2,
+ init_per_testcase/2, end_per_testcase/2]).
+
+-export([new/1, is_anno/1, generated/1, end_location/1, file/1,
+ line/1, location/1, record/1, text/1, bad/1]).
+
+-export([parse_abstract/1, mapfold_anno/1]).
+
+all() ->
+ [{group, anno}, {group, parse}].
+
+groups() ->
+ [{anno, [], [new, is_anno, generated, end_location, file,
+ line, location, record, text, bad]},
+ {parse, [], [parse_abstract, mapfold_anno]}].
+
+suite() ->
+ [{ct_hooks,[ts_install_cth]},
+ {timetrap,{minutes,1}}].
+
+init_per_suite(Config) ->
+ Config.
+
+end_per_suite(_Config) ->
+ ok.
+
+init_per_group(_GroupName, Config) ->
+ Config.
+
+end_per_group(_GroupName, Config) ->
+ Config.
+
+init_per_testcase(_Case, Config) ->
+ Config.
+
+end_per_testcase(_Case, _Config) ->
+ ok.
+
+-define(INFO(T, V), {T, V}).
+
+-dialyzer({no_fail_call, new/1}).
+%% Test erl_anno:new/1.
+new(_Config) ->
+ {'EXIT', {badarg, _}} =
+ (catch erl_anno:new([{location,1},{text, "text"}])), % badarg
+ ok.
+
+%% Test erl_anno:is_anno/1.
+is_anno(_Config) ->
+ false = erl_anno:is_anno(a),
+ false = erl_anno:is_anno({a}),
+ false = erl_anno:is_anno([]),
+ false = erl_anno:is_anno([{location, 1}|{generated, true}]),
+ false = erl_anno:is_anno([{generated,false}]),
+ false = erl_anno:is_anno([{generated,true}]),
+ false = erl_anno:is_anno([{location,1},{file,nofile}]),
+ false = erl_anno:is_anno([{location,1},{text,notext}]),
+
+ true = erl_anno:is_anno(erl_anno:new(1)),
+ A0 = erl_anno:new({1, 17}),
+ true = erl_anno:is_anno(A0),
+ A1 = erl_anno:set_generated(true, A0),
+ true = erl_anno:is_anno(A1),
+ A2 = erl_anno:set_file("", A1),
+ true = erl_anno:is_anno(A2),
+ A3 = erl_anno:set_record(true, A2),
+ true = erl_anno:is_anno(A3),
+ A4 = erl_anno:set_text("text", A3),
+ true = erl_anno:is_anno(A4),
+ A5 = erl_anno:set_file(<<"filename">>, A4),
+ true = erl_anno:is_anno(A5),
+ ok.
+
+%% Test 'generated'.
+generated(_Config) ->
+ test(1, [{generated, true}, {generated, false}]),
+ test(1, [{generated, false}, {generated, true}, {generated, false}]),
+ test({1, 17}, [{generated, false},
+ {generated, true},
+ {generated, false}]),
+ test({1, 17}, [{text, "text", [{end_location, {1, 21}}, {length, 4}]},
+ {generated, false},
+ {generated, true},
+ {generated, false}]),
+ test(1, [{generated, false},
+ {generated, true},
+ {generated, false}]),
+ test(1, [{text, "text", [{end_location, 1}, {length, 4}]},
+ {generated, false},
+ {generated, true},
+ {generated, false}]),
+ ok.
+
+%% Test 'end_location'.
+end_location(_Config) ->
+ test({1, 17}, [{text, "TEXT", [{end_location, {1, 21}}, {length, 4}]},
+ {text, "TEXT\n", [{end_location, {2, 1}}, {length, 5}]},
+ {text, "TEXT\ntxt", [{end_location, {2, 4}}, {length, 8}]}]),
+ test(1, [{text, "TEXT", [{end_location, 1}, {length, 4}]},
+ {text, "TEXT\n", [{end_location, 2}, {length, 5}]},
+ {text, "TEXT\ntxt", [{end_location, 2}, {length, 8}]}]),
+ ok.
+
+%% Test 'file'.
+file(_Config) ->
+ test(1, [{file, "name"}, {file, ""}]),
+ test({1, 17}, [{file, "name"}, {file, ""}]),
+ ok.
+
+%% Test 'line'.
+line(_Config) ->
+ test(1, [{line, 17, [{location, 17}]},
+ {location, {9, 8}, [{line, 9}, {column, 8}]},
+ {line, 14, [{location, {14, 8}}]}]),
+ ok.
+
+%% Test 'location'.
+location(_Config) ->
+ test(1, [{location, 2, [{line,2}]},
+ {location, {1, 17}, [{line, 1}, {column, 17}]},
+ {location, {9, 6}, [{line, 9}, {column, 6}]},
+ {location, 9, [{column, undefined}]}]),
+ test(1, [{generated, true},
+ {location, 2, [{line,2}]},
+ {location, {1, 17}, [{line, 1}, {column, 17}]},
+ {location, {9, 6}, [{line, 9}, {column, 6}]},
+ {location, 9, [{column, undefined}]}]),
+ test(1, [{record, true},
+ {location, 2, [{line,2}]},
+ {location, {1, 17}, [{line, 1}, {column, 17}]},
+ {location, {9, 6}, [{line, 9}, {column, 6}]},
+ {location, 9, [{column, undefined}]}]),
+ ok.
+
+%% Test 'record'.
+record(_Config) ->
+ test({1, 17}, [{record, true}, {record, false}]),
+ test(1, [{record, true}, {record, false}]),
+ test({1, 17}, [{generated, false},
+ {generated, true},
+ {generated, false}]),
+ test({1, 17}, [{text, "text", [{end_location, {1, 21}}, {length, 4}]},
+ {generated, false},
+ {generated, true},
+ {generated, false}]),
+ test(1, [{generated, false},
+ {generated, true},
+ {generated, false}]),
+ test(1, [{text, "text", [{end_location, 1}, {length, 4}]},
+ {generated, false},
+ {generated, true},
+ {generated, false}]),
+ ok.
+
+%% Test 'text'.
+text(_Config) ->
+ test(1, [{text, "text", [{end_location, 1}, {length, 4}]},
+ {text, "", [{end_location, 1}, {length, 0}]}]),
+ test({1, 17}, [{text, "text", [{end_location, {1,21}}, {length, 4}]},
+ {text, "", [{end_location, {1,17}}, {length, 0}]}]),
+ ok.
+
+-dialyzer({[no_opaque, no_fail_call], bad/1}).
+%% Test bad annotations.
+bad(_Config) ->
+ Line = erl_anno:new(1),
+ LineColumn = erl_anno:new({1, 17}),
+ {'EXIT', {badarg, _}} =
+ (catch erl_anno:set_generated(true, bad)), % 3rd arg not opaque
+ {'EXIT', {badarg, _}} =
+ (catch erl_anno:set_generated(false, bad)), % 3rd arg not opaque
+ {'EXIT', {badarg, _}} =
+ (catch erl_anno:set_generated(19, Line)),
+ {'EXIT', {badarg, _}} =
+ (catch erl_anno:set_generated(19, LineColumn)),
+
+ {'EXIT', {badarg, _}} =
+ (catch erl_anno:generated(bad)), % 1st arg not opaque
+ {'EXIT', {badarg, _}} =
+ (catch erl_anno:end_location(bad)), % 1st arg not opaque
+ {'EXIT', {badarg, _}} =
+ (catch erl_anno:file(bad)), % 1st arg not opaque
+ {'EXIT', {badarg, _}} =
+ (catch erl_anno:text(bad)), % 1st arg not opaque
+ {'EXIT', {badarg, _}} =
+ (catch erl_anno:record(bad)), % 1st arg not opaque
+ ok.
+
+%% Test erl_parse:new_anno/1, erl_parse:anno_to_term/1,
+%% and erl_parse:anno_from_term/1.
+parse_abstract(_Config) ->
+ T = sample_term(),
+ A = erl_parse:abstract(T, [{line,17}]),
+ T1 = erl_parse:anno_to_term(A),
+ Abstr = erl_parse:new_anno(T1),
+ T = erl_parse:normalise(Abstr),
+ Abstr2 = erl_parse:anno_from_term(T1),
+ T = erl_parse:normalise(Abstr2),
+ ok.
+
+%% Test erl_parse:{map_anno/2,fold_anno/3, and mapfold_anno/3}.
+mapfold_anno(_Config) ->
+ T = sample_term(),
+ Abstr = erl_parse:abstract(T),
+ CF = fun(Anno, {L, D}) ->
+ {erl_anno:new(L), {L+1, dict:store(L, Anno, D)}}
+ end,
+ {U, {N, D}} = erl_parse:mapfold_anno(CF, {1, dict:new()}, Abstr),
+ SeqA = erl_parse:fold_anno(fun(Anno, Acc) -> [Anno|Acc] end, [], U),
+ Seq = [erl_anno:location(A) || A <- SeqA],
+ Seq = lists:seq(N-1, 1, -1),
+ NF = fun(Anno) ->
+ L = erl_anno:location(Anno),
+ dict:fetch(L, D)
+ end,
+ Abstr = erl_parse:map_anno(NF, U),
+ ok.
+
+sample_term() ->
+ %% This is just a sample.
+ {3,a,4.0,"foo",<<"bar">>,#{a => <<19:64/unsigned-little>>},
+ [1000,2000]}.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+test(StartLocation, Updates) ->
+ S0 = init(StartLocation),
+ A0 = erl_anno:new(StartLocation),
+ chk(S0, A0, []),
+ eval(Updates, S0, A0).
+
+eval([], _S0, _A0) ->
+ ok;
+eval([{Item, Value}|Updates], S0, A0) ->
+ {S, A} = set(Item, Value, A0, S0, []),
+ eval(Updates, S, A);
+eval([{Item, Value, Secondary}|Updates], S0, A0) ->
+ {S, A} = set(Item, Value, A0, S0, Secondary),
+ eval(Updates, S, A).
+
+init({Line, Column}) ->
+ lists:sort([{location, {Line, Column}} | default()]);
+init(Line) when is_integer(Line) ->
+ lists:sort([{location, Line} | default()]).
+
+set(Item, Value, Anno0, State0, Secondary) ->
+ true = lists:member(Item, primary_items()),
+ ?format("Set '~w' to ~p\n", [Item, Value]),
+ State = set_value(Item, Value, State0),
+ Anno = anno_set(Item, Value, Anno0),
+ ?format("State0 ~p\n", [State0]),
+ ?format("State ~p\n", [State]),
+ ?format("Anno0 ~p\n", [anno_to_term(Anno0)]),
+ ?format("Anno ~p\n", [anno_to_term(Anno)]),
+ chk(State, Anno, Secondary),
+ ok = frame(Anno0, Anno, Secondary),
+ {State, Anno}.
+
+frame(OldAnno, NewAnno, Secondary) ->
+ SecItems = [I || {I, _} <- Secondary],
+ Frame = secondary_items() -- (SecItems ++ primary_items()),
+ ?format("Frame items ~p\n", [Frame]),
+ frame1(Frame, OldAnno, NewAnno).
+
+frame1([], _OldAnno, _NewAnno) ->
+ ok;
+frame1([Item|Items], OldAnno, NewAnno) ->
+ V1 = anno_info(OldAnno, Item),
+ V2 = anno_info(NewAnno, Item),
+ ok = check_value(Item, V1, V2),
+ frame1(Items, OldAnno, NewAnno).
+
+chk(State, Anno, Secondary) ->
+ ok = check_simple(Anno),
+ ok = chk_primary(State, Anno),
+ ok = check_secondary(Secondary, State, Anno).
+
+chk_primary(State, Anno) ->
+ chk_primary(primary_items(), State, Anno).
+
+chk_primary([], _State, _Anno) ->
+ ok;
+chk_primary([Item | Items], State, Anno) ->
+ V1 = primary_value(Item, State),
+ V2 = anno_info(Anno, Item),
+ ok = check_value(Item, V1, V2),
+ chk_primary(Items, State, Anno).
+
+check_secondary([], _State, _Anno) ->
+ ok;
+check_secondary([{Item, _}=V1 | Secondary], State, Anno) ->
+ V2 = anno_info(Anno, Item),
+ case {V1, V2} of
+ {{Item, undefined}, undefined} ->
+ ok;
+ _ ->
+ ok = check_value(Item, V1, V2)
+ end,
+ check_secondary(Secondary, State, Anno).
+
+check_value(Item, V1, V2) ->
+ ?format("~w: V1 ~p\n", [Item, V1]),
+ ?format("~w: V2 ~p\n", [Item, V2]),
+ case V1 =:= V2 of
+ true ->
+ ok;
+ false ->
+ io:format("~w: expected ~p\n got ~p\n", [Item, V1, V2]),
+ exit({V1, V2})
+ end.
+
+check_simple(Anno) ->
+ Term = anno_to_term(Anno),
+ case find_defaults(Term) of
+ [] ->
+ ok;
+ Ds ->
+ io:format("found default values ~w in ~p\n", [Ds, Anno]),
+ exit({defaults, Anno})
+ end,
+ case check_simple1(Term) of
+ true ->
+ ok;
+ false ->
+ io:format("not simple ~p\n", [Anno]),
+ exit({not_simple, Anno})
+ end.
+
+check_simple1(L) when is_integer(L) ->
+ true;
+check_simple1({L, C}) when is_integer(L), is_integer(C) ->
+ true;
+check_simple1(List) ->
+ case lists:sort(List) of
+ [{location, _}] ->
+ false;
+ _ ->
+ true
+ end.
+
+find_defaults(L) when is_list(L) ->
+ [I ||
+ I <- default_items(),
+ {I1, Value} <- L,
+ I =:= I1,
+ Value =:= default_value(I)];
+find_defaults(_) ->
+ [].
+
+anno_to_term(Anno) ->
+ T = erl_anno:to_term(Anno),
+ maybe_sort(T).
+
+maybe_sort(L) when is_list(L) ->
+ lists:sort(L);
+maybe_sort(T) ->
+ T.
+
+anno_set(file, Value, Anno) ->
+ erl_anno:set_file(Value, Anno);
+anno_set(generated, Value, Anno) ->
+ erl_anno:set_generated(Value, Anno);
+anno_set(line, Value, Anno) ->
+ erl_anno:set_line(Value, Anno);
+anno_set(location, Value, Anno) ->
+ erl_anno:set_location(Value, Anno);
+anno_set(record, Value, Anno) ->
+ erl_anno:set_record(Value, Anno);
+anno_set(text, Value, Anno) ->
+ erl_anno:set_text(Value, Anno).
+
+anno_info(Anno, Item) ->
+ Value =
+ case Item of
+ column ->
+ erl_anno:column(Anno);
+ generated ->
+ erl_anno:generated(Anno);
+ end_location ->
+ erl_anno:end_location(Anno);
+ file ->
+ erl_anno:file(Anno);
+ length ->
+ case erl_anno:text(Anno) of
+ undefined ->
+ undefined;
+ Text ->
+ length(Text)
+ end;
+ line ->
+ erl_anno:line(Anno);
+ location ->
+ erl_anno:location(Anno);
+ record ->
+ erl_anno:record(Anno);
+ text ->
+ erl_anno:text(Anno);
+ _ ->
+ erlang:error(badarg, [Anno, Item])
+ end,
+ if
+ Value =:= undefined ->
+ undefined;
+ true ->
+ {Item, Value}
+ end.
+
+%%% Originally 'location' was primary while 'line' and 'column' were
+%%% secondary (their values are determined by 'location'). But since
+%%% set_line() is used kind of frequently, 'line' is also primary,
+%%% and 'location' secondary (depends on 'line'). 'line' need to be
+%%% handled separately.
+
+set_value(line, Line, State) ->
+ {location, Location} = primary_value(location, State),
+ NewLocation = case Location of
+ {_, Column} ->
+ {Line, Column};
+ _ ->
+ Line
+ end,
+ set_value(location, NewLocation, State);
+set_value(Item, Value, State) ->
+ lists:ukeymerge(1, [{Item, Value}], State).
+
+primary_value(line, State) ->
+ {location, Location} = primary_value(location, State),
+ {line, case Location of
+ {Line, _} ->
+ Line;
+ Line ->
+ Line
+ end};
+primary_value(Item, State) ->
+ case lists:keyfind(Item, 1, State) of
+ false ->
+ undefined;
+ Tuple ->
+ Tuple
+ end.
+
+default() ->
+ [{Tag, default_value(Tag)} || Tag <- default_items()].
+
+primary_items() ->
+ [file, generated, line, location, record, text].
+
+secondary_items() ->
+ %% 'length' has not been implemented
+ [column, end_location, length, line, location].
+
+default_items() ->
+ [generated, record].
+
+default_value(generated) -> false;
+default_value(record) -> false.