aboutsummaryrefslogtreecommitdiffstats
path: root/lib/diameter
diff options
context:
space:
mode:
authorAnders Svensson <[email protected]>2011-12-09 11:35:37 +0100
committerAnders Svensson <[email protected]>2011-12-09 11:35:37 +0100
commit23c1d8c8dafc9094e462f7b220f9faec5eec3ad8 (patch)
tree1c6f180487ae1a2c8a4e2e28ece5e226eb5f1f88 /lib/diameter
parent2ead2d78f94bda90309077080c6b6d7a5bb72d9c (diff)
parent57d5564fca1f6d5e57199e1dcdca1d64284ecae8 (diff)
downloadotp-23c1d8c8dafc9094e462f7b220f9faec5eec3ad8.tar.gz
otp-23c1d8c8dafc9094e462f7b220f9faec5eec3ad8.tar.bz2
otp-23c1d8c8dafc9094e462f7b220f9faec5eec3ad8.zip
Merge branch 'anders/diameter/suites_and_dialyzer/OTP-9781'
* anders/diameter/suites_and_dialyzer/OTP-9781: Tell dialyzer not to warn about unused functions Add compiler testcases for generation and compilation to beam Fix semantic checks on AVP qualifiers Minor codegen/debug fix Deal with crypto:start() failure in TLS suite Don't assume empty sndrcvinfo in diameter_sctp
Diffstat (limited to 'lib/diameter')
-rw-r--r--lib/diameter/src/compiler/diameter_codegen.erl30
-rw-r--r--lib/diameter/src/compiler/diameter_dict_util.erl83
-rw-r--r--lib/diameter/src/compiler/diameter_nowarn.erl41
-rw-r--r--lib/diameter/src/modules.mk1
-rw-r--r--lib/diameter/src/transport/diameter_sctp.erl18
-rw-r--r--lib/diameter/test/diameter_app_SUITE.erl1
-rw-r--r--lib/diameter/test/diameter_compiler_SUITE.erl197
-rw-r--r--lib/diameter/test/diameter_tls_SUITE.erl2
8 files changed, 313 insertions, 60 deletions
diff --git a/lib/diameter/src/compiler/diameter_codegen.erl b/lib/diameter/src/compiler/diameter_codegen.erl
index 6763e06140..1e31c40afe 100644
--- a/lib/diameter/src/compiler/diameter_codegen.erl
+++ b/lib/diameter/src/compiler/diameter_codegen.erl
@@ -98,15 +98,15 @@ file(F, Outdir, Mode) ->
get_value(Key, Plist) ->
proplists:get_value(Key, Plist, []).
-write(Path, [C|_] = Spec)
- when is_integer(C) ->
- w(Path, Spec, "~s");
-write(Path, Spec) ->
- w(Path, Spec, "~p.").
+write(Path, Str) ->
+ w(Path, Str, "~s").
-w(Path, Spec, Fmt) ->
+write_term(Path, T) ->
+ w(Path, T, "~p.").
+
+w(Path, T, Fmt) ->
{ok, Fd} = file:open(Path, [write]),
- io:fwrite(Fd, Fmt ++ "~n", [Spec]),
+ io:fwrite(Fd, Fmt ++ "~n", [T]),
file:close(Fd).
codegen(File, Spec, Outdir, Mode) ->
@@ -121,7 +121,7 @@ mod(_, {ok, Mod}) ->
Mod.
gen(spec, Spec, _Mod, Path) ->
- write(Path ++ ".spec", [?VERSION | Spec]);
+ write_term(Path ++ ".spec", [?VERSION | Spec]);
gen(hrl, Spec, Mod, Path) ->
gen_hrl(Path ++ ".hrl", Mod, Spec);
@@ -129,7 +129,7 @@ gen(hrl, Spec, Mod, Path) ->
gen(erl, Spec, Mod, Path) ->
Forms = [{?attribute, module, Mod},
{?attribute, compile, [{parse_transform, diameter_exprecs}]},
- {?attribute, compile, [nowarn_unused_function]},
+ {?attribute, compile, [{parse_transform, diameter_nowarn}]},
{?attribute, export, [{name, 0},
{id, 0},
{vendor_id, 0},
@@ -173,7 +173,7 @@ gen(erl, Spec, Mod, Path) ->
gen_erl(Path, insert_hrl_forms(Spec, Forms)).
gen_erl(Path, Forms) ->
- getr(debug) andalso write(Path ++ ".forms", Forms),
+ getr(debug) andalso write_term(Path ++ ".forms", Forms),
write(Path ++ ".erl",
header() ++ erl_prettypr:format(erl_syntax:form_list(Forms))).
@@ -835,15 +835,15 @@ avp_info(Entry) -> %% {Name, Arity}
[A] -> {A, {0,1}};
{Q,T} ->
{A,_} = avp_info(T),
- {A, arity(Q)}
+ {A, arity(T,Q)}
end.
%% Normalize arity to 1 or {N,X} where N is an integer. A record field
%% for an AVP is list-valued iff the normalized arity is not 1.
-arity('*' = Inf) -> {0, Inf};
-arity({'*', N}) -> {0, N};
-arity({1,1}) -> 1;
-arity(T) -> T.
+arity({{_}}, '*' = Inf) -> {0, Inf};
+arity([_], '*' = Inf) -> {0, Inf};
+arity({_}, '*' = Inf) -> {1, Inf};
+arity(_, {_,_} = Q) -> Q.
prefix(Spec) ->
case orddict:find(prefix, Spec) of
diff --git a/lib/diameter/src/compiler/diameter_dict_util.erl b/lib/diameter/src/compiler/diameter_dict_util.erl
index 2207925e49..e4cd29ab7f 100644
--- a/lib/diameter/src/compiler/diameter_dict_util.erl
+++ b/lib/diameter/src/compiler/diameter_dict_util.erl
@@ -174,7 +174,13 @@ fmt(message_application_id_mismatch) ->
fmt(invalid_avp_order) ->
"AVP reference ~c~s~c at line ~p breaks fixed/required/optional order";
-fmt(invalid_qualifier) ->
+fmt(required_avp_has_zero_max_arity) ->
+ "Required AVP has maximum arity 0 at line ~p";
+fmt(required_avp_has_zero_min_arity) ->
+ "Required AVP has minimum arity 0 at line ~p";
+fmt(optional_avp_has_nonzero_min_arity) ->
+ "Optional AVP has non-zero minimum arity at line ~p";
+fmt(qualifier_has_min_greater_than_max) ->
"Qualifier ~p*~p at line ~p has Min > Max";
fmt(avp_already_referenced) ->
"AVP ~s at line ~p already referenced at line ~p";
@@ -300,7 +306,7 @@ body(avp_types, {Name, Code, Type, Flags}) ->
body(messages, {"answer-message", _, _, [], Avps}) ->
[?NL, ?NL, ?INDENT,
"answer-message ::= < Diameter Header: code, ERR [PXY] >",
- f_avps(Avps)];
+ f_avps(Avps)];
body(messages, {Name, Code, Flags, ApplId, Avps}) ->
[?NL, ?NL, ?INDENT, word(Name), " ::= ", header(Code, Flags, ApplId),
f_avps(Avps)];
@@ -326,7 +332,8 @@ f_avps(L) ->
[[?NL, ?INDENT, ?INDENT, f_avp(A)] || A <- L].
f_avp({Q, A}) ->
- f_avp(f_qual(Q), f_delim(A));
+ [D | _] = Avp = f_delim(A),
+ f_avp(f_qual(D, Q), Avp);
f_avp(A) ->
f_avp("", f_delim(A)).
@@ -337,17 +344,19 @@ f_delim({A}) ->
f_delim([A]) ->
[$[, word(A), $]].
-f_avp(Q, Avp) ->
+f_avp(Q, [L, Avp, R]) ->
Len = length(lists:flatten([Q])),
- [io_lib:format("~*s", [-1*max(Len+1, 6) , Q]), Avp].
+ [io_lib:format("~*s", [-1*max(Len+1, 6) , Q]), L, " ", Avp, " ", R].
-f_qual('*') ->
+f_qual(${, '*') ->
+ "1*"; %% Equivalent to "*" but the more common/obvious rendition
+f_qual(_, '*') ->
"*";
-f_qual({'*', N}) ->
+f_qual(_, {'*', N}) ->
[$*, ?I(N)];
-f_qual({N, '*'}) ->
+f_qual(_, {N, '*'}) ->
[?I(N), $*];
-f_qual({M,N}) ->
+f_qual(_, {M,N}) ->
[?I(M), $*, ?I(N)].
section(Key) ->
@@ -500,7 +509,17 @@ make_body(Avps) ->
avp([false, D, Avp]) ->
avp(D, Avp);
avp([Q, D, Avp]) ->
- {qual(Q), avp(D, Avp)}.
+ case {qual(D, Q), avp(D, Avp)} of
+ {{0,1}, A} when D == $[ ->
+ A;
+ {{1,1}, A} ->
+ A;
+ T ->
+ T
+ end.
+%% Could just store the qualifier as a pair in all cases but the more
+%% compact form is easier to parse visually so live with a bit of
+%% mapping. Ditto the use of '*'.
avp(D, {'AVP', _}) ->
delim(D, "AVP");
@@ -514,14 +533,40 @@ delim(${, N) ->
delim($[, N) ->
[N].
-qual({true, {_,_,N}}) ->
- {'*', N};
-qual({{_,_,N}, true}) ->
+%% There's a difference between max = 0 and not specifying an AVP:
+%% reception of an AVP with max = 0 will always be an error, otherwise
+%% it depends on the existence of 'AVP' and the M flag.
+
+qual(${, {{_,L,0}, _}) ->
+ ?RETURN(required_avp_has_zero_min_arity, [L]);
+qual(${, {_, {_,L,0}}) ->
+ ?RETURN(required_avp_has_zero_max_arity, [L]);
+
+qual($[, {{_,L,N}, _})
+ when 0 < N ->
+ ?RETURN(optional_avp_has_nonzero_min_arity, [L]);
+
+qual(_, {{_,L,Min}, {_,_,Max}})
+ when Min > Max ->
+ ?RETURN(qualifier_has_min_greater_than_max, [Min, Max, L]);
+
+qual(_, true) ->
+ '*';
+
+qual(${, {true, {_,_,N}}) ->
+ {1, N};
+qual(_, {true, {_,_,N}}) ->
+ {0, N};
+
+qual(D, {{_,_,N}, true})
+ when D == ${, N == 1;
+ D /= ${, N == 0 ->
+ '*';
+qual(_, {{_,_,N}, true}) ->
{N, '*'};
-qual({{_,_,N},{_,_,M}}) ->
- {N, M};
-qual(true) ->
- '*'.
+
+qual(_, {{_,_,Min}, {_,_,Max}}) ->
+ {Min, Max}.
%% Optional reports when running verbosely.
report(What, [F | A])
@@ -851,10 +896,6 @@ xa(Ds, [[Qual, D, {'AVP', Line}] | Avps], Dict, Key, Name) ->
xa([], [[_Qual, D, {_, Line, Name}] | _], _, _, _) ->
?RETURN(invalid_avp_order, [D, Name, close(D), Line]);
-xa([D|_], [[{{_, Line, Min}, {_, _, Max}}, D, _] | _], _, _, _)
- when Min > Max ->
- ?RETURN(invalid_qualifier, [Min, Max, Line]);
-
xa([D|_] = Ds, [[Qual, D, {_, Line, AvpName}] | Avps], Dict, Key, Name) ->
xa(Ds,
Avps,
diff --git a/lib/diameter/src/compiler/diameter_nowarn.erl b/lib/diameter/src/compiler/diameter_nowarn.erl
new file mode 100644
index 0000000000..6c17af6563
--- /dev/null
+++ b/lib/diameter/src/compiler/diameter_nowarn.erl
@@ -0,0 +1,41 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2010-2011. 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%
+%%
+
+%%
+%% A parse transform to work around dialyzer currently not
+%% understanding nowarn_unused_function except on individual
+%% functions. The include of diameter_gen.hrl by generated dictionary
+%% modules contains code that may not be called depending on the
+%% dictionary. (The relay dictionary for example.)
+%%
+%% Even called functions may contain cases that aren't used for a
+%% particular dictionary. This also causes dialyzer to complain but
+%% there's no way to silence it in this case.
+%%
+
+-module(diameter_nowarn).
+
+-export([parse_transform/2]).
+
+parse_transform(Forms, _Options) ->
+ [{attribute, ?LINE, compile, {nowarn_unused_function, {F,A}}}
+ || {function, _, F, A, _} <- Forms]
+ ++ Forms.
+%% Note that dialyzer also doesn't understand {nowarn_unused_function, FAs}
+%% with FAs a list of tuples.
diff --git a/lib/diameter/src/modules.mk b/lib/diameter/src/modules.mk
index c5d448b2ff..11d354e57e 100644
--- a/lib/diameter/src/modules.mk
+++ b/lib/diameter/src/modules.mk
@@ -66,6 +66,7 @@ CT_MODULES = \
base/diameter_info \
compiler/diameter_codegen \
compiler/diameter_exprecs \
+ compiler/diameter_nowarn \
compiler/diameter_dict_scanner \
compiler/diameter_dict_util \
compiler/diameter_make
diff --git a/lib/diameter/src/transport/diameter_sctp.erl b/lib/diameter/src/transport/diameter_sctp.erl
index 209f8c01c1..68b0342cd5 100644
--- a/lib/diameter/src/transport/diameter_sctp.erl
+++ b/lib/diameter/src/transport/diameter_sctp.erl
@@ -546,10 +546,10 @@ send(Sock, AssocId, Stream, Bin) ->
%% recv/2
%% Association established ...
-recv({[], #sctp_assoc_change{state = comm_up,
- outbound_streams = OS,
- inbound_streams = IS,
- assoc_id = Id}},
+recv({_, #sctp_assoc_change{state = comm_up,
+ outbound_streams = OS,
+ inbound_streams = IS,
+ assoc_id = Id}},
#transport{assoc_id = undefined,
mode = {T, _},
socket = Sock}
@@ -562,7 +562,7 @@ recv({[], #sctp_assoc_change{state = comm_up,
streams = {IS, OS}});
%% ... or not: try the next address.
-recv({[], #sctp_assoc_change{} = E},
+recv({_, #sctp_assoc_change{} = E},
#transport{assoc_id = undefined,
socket = Sock,
mode = {connect = C, {[RA|RAs], RP, Es}}}
@@ -570,7 +570,7 @@ recv({[], #sctp_assoc_change{} = E},
S#transport{mode = {C, connect(Sock, RAs, RP, [{RA,E} | Es])}};
%% Lost association after establishment.
-recv({[], #sctp_assoc_change{}}, _) ->
+recv({_, #sctp_assoc_change{}}, _) ->
stop;
%% Inbound Diameter message.
@@ -580,7 +580,7 @@ recv({[#sctp_sndrcvinfo{stream = Id}], Bin}, #transport{parent = Pid})
bin = Bin}),
ok;
-recv({[], #sctp_shutdown_event{assoc_id = Id}},
+recv({_, #sctp_shutdown_event{assoc_id = Id}},
#transport{assoc_id = Id}) ->
stop;
@@ -593,10 +593,10 @@ recv({[], #sctp_shutdown_event{assoc_id = Id}},
%% disabled by default so don't handle it. We could simply disable
%% events we don't react to but don't.
-recv({[], #sctp_paddr_change{}}, _) ->
+recv({_, #sctp_paddr_change{}}, _) ->
ok;
-recv({[], #sctp_pdapi_event{}}, _) ->
+recv({_, #sctp_pdapi_event{}}, _) ->
ok.
%% up/1
diff --git a/lib/diameter/test/diameter_app_SUITE.erl b/lib/diameter/test/diameter_app_SUITE.erl
index 808f2cd30d..53332af626 100644
--- a/lib/diameter/test/diameter_app_SUITE.erl
+++ b/lib/diameter/test/diameter_app_SUITE.erl
@@ -48,6 +48,7 @@
diameter_dict_parser,
diameter_dict_util,
diameter_exprecs,
+ diameter_nowarn,
diameter_make]).
-define(HELP_MODULES, [diameter_dbg,
diff --git a/lib/diameter/test/diameter_compiler_SUITE.erl b/lib/diameter/test/diameter_compiler_SUITE.erl
index cc4b1ddac5..66d788f6ec 100644
--- a/lib/diameter/test/diameter_compiler_SUITE.erl
+++ b/lib/diameter/test/diameter_compiler_SUITE.erl
@@ -22,7 +22,6 @@
%%
-module(diameter_compiler_SUITE).
--compile({no_auto_import, [error/2]}).
-export([suite/0,
all/0,
@@ -30,14 +29,16 @@
end_per_suite/1]).
%% testcases
--export([format/1,
- replace/1, replace/2]).
+-export([format/1, format/2,
+ replace/1, replace/2,
+ generate/1, generate/4, generate/0]).
-export([dict/0]). %% fake dictionary module
-define(base, "base_rfc3588.dia").
-define(util, diameter_util).
-define(S, atom_to_list).
+-define(L, integer_to_list).
%% ===========================================================================
@@ -46,7 +47,10 @@
%% returned in the first element of the error tuple returned by
%% diameter_dict_util:parse/2.
-define(REPLACE,
- [{scan,
+ [{ok,
+ "",
+ ""},
+ {scan,
"@id 0",
"@id \\&"},
{scan,
@@ -154,9 +158,126 @@
{invalid_avp_order,
"CEA ::=",
"{Result-Code} &"},
- {invalid_qualifier,
- "CEA ::=.*",
- "& 3*2"},
+ {ok,
+ "{ Product-Name",
+ "* &"},
+ {required_avp_has_zero_max_arity,
+ "{ Product-Name",
+ "*0 &"},
+ {required_avp_has_zero_min_arity,
+ "{ Product-Name",
+ "0* &"},
+ {required_avp_has_zero_min_arity,
+ "{ Product-Name",
+ "0*0 &"},
+ {ok,
+ "{ Product-Name",
+ "*1 &"},
+ {ok,
+ "{ Product-Name",
+ "1* &"},
+ {ok,
+ "{ Product-Name",
+ "1*1 &"},
+ {ok,
+ "{ Product-Name",
+ "2* &"},
+ {ok,
+ "{ Product-Name",
+ "*2 &"},
+ {ok,
+ "{ Product-Name",
+ "2*2 &"},
+ {ok,
+ "{ Product-Name",
+ "2*3 &"},
+ {qualifier_has_min_greater_than_max,
+ "{ Product-Name",
+ "3*2 &"},
+ {ok,
+ "\\[ Origin-State-Id",
+ "* &"},
+ {ok,
+ "\\[ Origin-State-Id",
+ "0* &"},
+ {ok,
+ "\\[ Origin-State-Id",
+ "*0 &"},
+ {ok,
+ "\\[ Origin-State-Id",
+ "0*0 &"},
+ {ok,
+ "\\[ Origin-State-Id",
+ "0*1 &"},
+ {ok,
+ "\\[ Origin-State-Id",
+ "0*2 &"},
+ {ok,
+ "\\[ Origin-State-Id",
+ "*1 &"},
+ {optional_avp_has_nonzero_min_arity,
+ "\\[ Origin-State-Id",
+ "1* &"},
+ {optional_avp_has_nonzero_min_arity,
+ "\\[ Origin-State-Id",
+ "1*1 &"},
+ {ok,
+ "\\[ Origin-State-Id",
+ "*2 &"},
+ {optional_avp_has_nonzero_min_arity,
+ "\\[ Origin-State-Id",
+ "2* &"},
+ {optional_avp_has_nonzero_min_arity,
+ "\\[ Origin-State-Id",
+ "2*2 &"},
+ {optional_avp_has_nonzero_min_arity,
+ "\\[ Origin-State-Id",
+ "2*3 &"},
+ {optional_avp_has_nonzero_min_arity,
+ "\\[ Origin-State-Id",
+ "3*2 &"},
+ {ok,
+ "^ *< Session-Id",
+ "* &"},
+ {ok,
+ "^ *< Session-Id",
+ "*0 &"},
+ {ok,
+ "^ *< Session-Id",
+ "0* &"},
+ {ok,
+ "^ *< Session-Id",
+ "0*0 &"},
+ {ok,
+ "^ *< Session-Id",
+ "0*1 &"},
+ {ok,
+ "^ *< Session-Id",
+ "0*2 &"},
+ {ok,
+ "^ *< Session-Id",
+ "*1 &"},
+ {ok,
+ "^ *< Session-Id",
+ "1* &"},
+ {ok,
+ "^ *< Session-Id",
+ "1*1 &"},
+ {ok,
+ "^ *< Session-Id",
+ "*2 &"},
+ {ok,
+ "^ *< Session-Id",
+ "2* &"},
+ {ok,
+ "^ *< Session-Id",
+ "2*2 &"},
+ {ok,
+ "^ *< Session-Id",
+ "2*3 &"},
+ {qualifier_has_min_greater_than_max,
+ "^ *< Session-Id",
+ "3*2 &"},
{avp_already_referenced,
"CER ::=.*",
"& {Origin-Host}"},
@@ -213,7 +334,8 @@ suite() ->
all() ->
[format,
- replace].
+ replace,
+ generate].
%% Error handling testcases will make an erroneous dictionary out of
%% the base dictionary and check that the expected error results.
@@ -229,24 +351,35 @@ end_per_suite(_Config) ->
%% ===========================================================================
%% testcases
+%% format/1
+%%
%% Ensure that parse o format is the identity map.
+
format(Config) ->
Bin = proplists:get_value(base, Config),
- {ok, Dict} = diameter_dict_util:parse(Bin, []),
+ [] = ?util:run([{?MODULE, [format, M, Bin]}
+ || E <- ?REPLACE,
+ {ok, M} <- [norm(E)]]).
+
+format(Mods, Bin) ->
+ B = modify(Bin, Mods),
+ {ok, Dict} = diameter_dict_util:parse(B, []),
{ok, D} = diameter_dict_util:parse(diameter_dict_util:format(Dict), []),
{Dict, Dict} = {Dict, D}.
%% replace/1
+%%
+%% Ensure the expected success/error when parsing a morphed common
+%% dictionary.
replace(Config) ->
Bin = proplists:get_value(base, Config),
- [] = ?util:run([{?MODULE, [replace, E, Bin]} || E <- ?REPLACE]).
-
-replace({E, RE, Repl}, Bin) ->
- replace({E, [{RE, Repl}]}, Bin);
+ [] = ?util:run([{?MODULE, [replace, N, Bin]}
+ || E <- ?REPLACE,
+ N <- [norm(E)]]).
replace({E, Mods}, Bin) ->
- B = iolist_to_binary(lists:foldl(fun re/2, Bin, Mods)),
+ B = modify(Bin, Mods),
case {E, diameter_dict_util:parse(B, [{include, here()}]), Mods} of
{ok, {ok, Dict}, _} ->
Dict;
@@ -260,8 +393,44 @@ replace({E, Mods}, Bin) ->
re({RE, Repl}, Bin) ->
re:replace(Bin, RE, Repl, [multiline]).
+%% generate/1
+%%
+%% Ensure success when generating code and compiling.
+
+generate() ->
+ [{timetrap, {seconds, length(?REPLACE)}}].
+
+generate(Config) ->
+ Bin = proplists:get_value(base, Config),
+ Rs = lists:zip(?REPLACE, lists:seq(1, length(?REPLACE))),
+ [] = ?util:run([{?MODULE, [generate, M, Bin, N, T]}
+ || {E,N} <- Rs,
+ {ok, M} <- [norm(E)],
+ T <- [erl, hrl, spec]]).
+
+generate(Mods, Bin, N, Mode) ->
+ B = modify(Bin, Mods ++ [{"@name .*", "@name dict" ++ ?L(N)}]),
+ {ok, Dict} = diameter_dict_util:parse(B, []),
+ File = "dict" ++ integer_to_list(N),
+ {_, ok} = {Dict, diameter_codegen:from_dict("dict",
+ Dict,
+ [{name, File},
+ {prefix, "base"},
+ debug],
+ Mode)},
+ Mode == erl
+ andalso ({ok, _} = compile:file(File ++ ".erl", [return_errors])).
+
%% ===========================================================================
+modify(Bin, Mods) ->
+ lists:foldl(fun re/2, Bin, Mods).
+
+norm({E, RE, Repl}) ->
+ {E, [{RE, Repl}]};
+norm({_,_} = T) ->
+ T.
+
nochar(Char, Str, Err) ->
Err == parse orelse not lists:member(Char, Str) orelse Str.
diff --git a/lib/diameter/test/diameter_tls_SUITE.erl b/lib/diameter/test/diameter_tls_SUITE.erl
index a325ca33eb..38282282b8 100644
--- a/lib/diameter/test/diameter_tls_SUITE.erl
+++ b/lib/diameter/test/diameter_tls_SUITE.erl
@@ -157,7 +157,7 @@ init_per_suite(Config) ->
try
false /= os:find_executable("openssl")
orelse throw({?MODULE, no_openssl}),
- ok == crypto:start()
+ ok == (catch crypto:start())
orelse throw({?MODULE, no_crypto}),
Config
catch