1%% Licensed under the Apache License, Version 2.0 (the "License"); you may
2%% not use this file except in compliance with the License. You may obtain
3%% a copy of the License at <http://www.apache.org/licenses/LICENSE-2.0>
4%%
5%% Unless required by applicable law or agreed to in writing, software
6%% distributed under the License is distributed on an "AS IS" BASIS,
7%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
8%% See the License for the specific language governing permissions and
9%% limitations under the License.
10%%
11%% Alternatively, you may use this file under the terms of the GNU Lesser
12%% General Public License (the "LGPL") as published by the Free Software
13%% Foundation; either version 2.1, or (at your option) any later version.
14%% If you wish to allow use of your version of this file only under the
15%% terms of the LGPL, you should delete the provisions above and replace
16%% them with the notice and other provisions required by the LGPL; see
17%% <http://www.gnu.org/licenses/>. If you do not delete the provisions
18%% above, a recipient may use your version of this file under the terms of
19%% either the Apache License or the LGPL.
20%%
21%% @copyright 2004-2007 Mickaël Rémond, Richard Carlsson
22%% @author Mickaël Rémond <mickael.remond@process-one.net>
23%%   [http://www.process-one.net/]
24%% @author Richard Carlsson <carlsson.richard@gmail.com>
25%% @private
26%% @see eunit
27%% @doc Utility functions for eunit
28
29-module(eunit_lib).
30
31-include("eunit.hrl").
32-include("eunit_internal.hrl").
33
34
35-export([dlist_next/1, uniq/1, fun_parent/1, is_string/1, command/1,
36	 command/2, command/3, trie_new/0, trie_store/2, trie_match/2,
37	 split_node/1, consult_file/1, list_dir/1, format_exit_term/1,
38	 format_exception/1, format_exception/2, format_error/1, format_error/2,
39         format_stacktrace/1, is_not_test/1]).
40
41-define(DEFAULT_DEPTH, 20).
42
43%% Type definitions for describing exceptions
44%%
45%% @type exception() = {exceptionClass(), Reason::term(), stackTrace()}
46%%
47%% @type exceptionClass() = error | exit | throw
48%%
49%% @type stackTrace() = [{moduleName(), functionName(), arity() | argList()}]
50%%
51%% @type moduleName() = atom()
52%% @type functionName() = atom()
53%% @type argList() = [term()]
54%% @type fileName() = string()
55
56
57%% ---------------------------------------------------------------------
58%% Formatting of error descriptors
59format_exception(Exception) ->
60    format_exception(Exception, ?DEFAULT_DEPTH).
61
62format_exception({Class,Term,Trace}, Depth)
63  when is_atom(Class), is_list(Trace) ->
64    case is_stacktrace(Trace) of
65	true ->
66	    io_lib:format("~ts**~w:~ts",
67			  [format_stacktrace(Trace), Class,
68                           format_term(Term, Depth)]);
69	false ->
70	    format_term(Term, Depth)
71    end;
72format_exception(Term, Depth) ->
73    format_term(Term, Depth).
74
75format_term(Term, Depth) ->
76    io_lib:format("~tP\n", [Term, Depth]).
77
78format_exit_term(Term) ->
79    {Reason, Trace} = analyze_exit_term(Term),
80    io_lib:format("~tP~ts", [Reason, 15, Trace]).
81
82analyze_exit_term({Reason, [_|_]=Trace}=Term) ->
83    case is_stacktrace(Trace) of
84	true ->
85	    {Reason, format_stacktrace(Trace)};
86	false ->
87	    {Term, ""}
88    end;
89analyze_exit_term(Term) ->
90    {Term, ""}.
91
92is_stacktrace([]) ->
93    true;
94is_stacktrace([{M,F,A,L}|Fs])
95  when is_atom(M), is_atom(F), is_integer(A), is_list(L) ->
96    is_stacktrace(Fs);
97is_stacktrace([{M,F,As,L}|Fs])
98  when is_atom(M), is_atom(F), is_list(As), is_list(L) ->
99    is_stacktrace(Fs);
100is_stacktrace([{M,F,A}|Fs]) when is_atom(M), is_atom(F), is_integer(A) ->
101    is_stacktrace(Fs);
102is_stacktrace([{M,F,As}|Fs]) when is_atom(M), is_atom(F), is_list(As) ->
103    is_stacktrace(Fs);
104is_stacktrace(_) ->
105    false.
106
107format_stacktrace(Trace) ->
108    format_stacktrace(Trace, "in function", "in call from").
109
110format_stacktrace([{M,F,A,L}|Fs], Pre, Pre1) when is_integer(A) ->
111    [io_lib:fwrite("~ts ~w:~tw/~w~ts\n",
112                   [Pre, M, F, A, format_stacktrace_location(L)])
113     | format_stacktrace(Fs, Pre1, Pre1)];
114format_stacktrace([{M,F,As,L}|Fs], Pre, Pre1) when is_list(As) ->
115    A = length(As),
116    C = case is_op(M,F,A) of
117	    true when A =:= 1 ->
118		[A1] = As,
119		io_lib:fwrite("~ts ~ts", [F,format_arg(A1)]);
120	    true when A =:= 2 ->
121		[A1, A2] = As,
122		io_lib:fwrite("~ts ~ts ~ts",
123			      [format_arg(A1),F,format_arg(A2)]);
124	    false ->
125		io_lib:fwrite("~tw(~ts)", [F,format_arglist(As)])
126	end,
127    [io_lib:fwrite("~ts ~w:~tw/~w~ts\n  called as ~ts\n",
128		   [Pre,M,F,A,format_stacktrace_location(L),C])
129     | format_stacktrace(Fs,Pre1,Pre1)];
130format_stacktrace([{M,F,As}|Fs], Pre, Pre1) ->
131    format_stacktrace([{M,F,As,[]}|Fs], Pre, Pre1);
132format_stacktrace([],_Pre,_Pre1) ->
133    "".
134
135format_stacktrace_location(Location) ->
136    File = proplists:get_value(file, Location),
137    Line = proplists:get_value(line, Location),
138    if File =/= undefined, Line =/= undefined ->
139            io_lib:format(" (~ts, line ~w)", [File, Line]);
140       true ->
141            ""
142    end.
143
144format_arg(A) ->
145    io_lib:format("~tP",[A,15]).
146
147format_arglist([A]) ->
148    format_arg(A);
149format_arglist([A|As]) ->
150    [io_lib:format("~tP,",[A,15]) | format_arglist(As)];
151format_arglist([]) ->
152    "".
153
154is_op(erlang, F, A) ->
155    erl_internal:arith_op(F, A)
156	orelse erl_internal:bool_op(F, A)
157	orelse erl_internal:comp_op(F, A)
158	orelse erl_internal:list_op(F, A)
159	orelse erl_internal:send_op(F, A);
160is_op(_M, _F, _A) ->
161    false.
162
163format_error(Error) ->
164    format_error(Error, ?DEFAULT_DEPTH).
165
166format_error({bad_test, Term}, Depth) ->
167    error_msg("bad test descriptor", "~tP", [Term, Depth]);
168format_error({bad_generator, {{M,F,A}, Term}}, Depth) ->
169    error_msg(io_lib:format("result from generator ~w:~tw/~w is not a test",
170                            [M,F,A]),
171              "~tP", [Term, Depth]);
172format_error({generator_failed, {{M,F,A}, Exception}}, Depth) ->
173    error_msg(io_lib:format("test generator ~w:~tw/~w failed",[M,F,A]),
174              "~ts", [format_exception(Exception, Depth)]);
175format_error({no_such_function, {M,F,A}}, _)
176  when is_atom(M), is_atom(F), is_integer(A) ->
177    error_msg(io_lib:format("no such function: ~w:~tw/~w", [M,F,A]),
178	      "", []);
179format_error({module_not_found, M}, _) ->
180    error_msg("test module not found", "~tp", [M]);
181format_error({application_not_found, A}, _) when is_atom(A) ->
182    error_msg("application not found", "~w", [A]);
183format_error({file_read_error, {_R, Msg, F}}, _) ->
184    error_msg("error reading file", "~ts: ~ts", [Msg, F]);
185format_error({setup_failed, Exception}, Depth) ->
186    error_msg("context setup failed", "~ts",
187	      [format_exception(Exception, Depth)]);
188format_error({cleanup_failed, Exception}, Depth) ->
189    error_msg("context cleanup failed", "~ts",
190	      [format_exception(Exception, Depth)]);
191format_error({{bad_instantiator, {{M,F,A}, Term}}, _DummyException}, Depth) ->
192    error_msg(io_lib:format("result from instantiator ~w:~tw/~w is not a test",
193                            [M,F,A]),
194              "~tP", [Term, Depth]);
195format_error({instantiation_failed, Exception}, Depth) ->
196    error_msg("instantiation of subtests failed", "~ts",
197	      [format_exception(Exception, Depth)]).
198
199error_msg(Title, Fmt, Args) ->
200    Msg = io_lib:format("**"++Fmt, Args),    % gets indentation right
201    io_lib:fwrite("*** ~ts ***\n~ts\n\n", [Title, Msg]).
202
203-ifdef(TEST).
204format_exception_test_() ->
205    [?_assertMatch(
206        "\nymmud:rorre"++_,
207        lists:reverse(lists:flatten(
208          format_exception(try erlang:error(dummy)
209                           catch C:R:S -> {C, R, S}
210                           end)))),
211     ?_assertMatch(
212        "\nymmud:rorre"++_,
213        lists:reverse(lists:flatten(
214          format_exception(try erlang:error(dummy, [a])
215                           catch C:R:S -> {C, R, S}
216                           end))))].
217-endif.
218
219%% ---------------------------------------------------------------------
220%% detect common return values that are definitely not tests
221
222is_not_test(T) ->
223    case T of
224        ok -> true;
225        error -> true;
226        true -> true;
227        false -> true;
228        undefined -> true;
229        {ok, _} -> true;
230        {error, _} -> true;
231        {'EXIT', _} -> true;
232        N when is_number(N) -> true;
233        [N|_] when is_number(N) -> true;
234        X when is_binary(X) -> true;
235        X when is_pid(X) -> true;
236        X when is_port(X) -> true;
237        X when is_reference(X) -> true;
238        _ -> false
239    end.
240
241%% ---------------------------------------------------------------------
242%% Deep list iterator; accepts improper lists/sublists, and also accepts
243%% non-lists on the top level. Nonempty strings (not deep strings) are
244%% recognized as separate elements, even on the top level. (It is not
245%% recommended to include integers in the deep list, since a list of
246%% integers is likely to be interpreted as a string.). The result is
247%% always presented as a list (which may be improper), which is either
248%% empty or otherwise has a non-list head element.
249
250dlist_next([X | Xs] = Xs0) when is_list(X) ->
251    case is_nonempty_string(X) of
252	true -> Xs0;
253	false -> dlist_next(X, Xs)
254    end;
255dlist_next([_|_] = Xs) ->
256    case is_nonempty_string(Xs) of
257	true -> [Xs];
258	false -> Xs
259    end;
260dlist_next([]) ->
261    [];
262dlist_next(X) ->
263    [X].
264
265%% the first two clauses avoid pushing empty lists on the stack
266dlist_next([X], Ys) when is_list(X) ->
267    case is_nonempty_string(X) of
268	true -> [X | Ys];
269	false -> dlist_next(X, Ys)
270    end;
271dlist_next([X], Ys) ->
272    [X | Ys];
273dlist_next([X | Xs], Ys) when is_list(X) ->
274    case is_nonempty_string(X) of
275	true -> [X | [Xs | Ys]];
276	false -> dlist_next(X, [Xs | Ys])
277    end;
278dlist_next([X | Xs], Ys) ->
279    [X | [Xs | Ys]];
280dlist_next([], Xs) ->
281    dlist_next(Xs).
282
283
284-ifdef(TEST).
285dlist_test_() ->
286    {"deep list traversal",
287     [{"non-list term -> singleton list",
288       ?_test([any] = dlist_next(any))},
289      {"empty list -> empty list",
290       ?_test([] = dlist_next([]))},
291      {"singleton list -> singleton list",
292       ?_test([any] = dlist_next([any]))},
293      {"taking the head of a flat list",
294       ?_test([a,b,c] = dlist_next([a,b,c]))},
295      {"skipping an initial empty list",
296       ?_test([a,b,c] = dlist_next([[],a,b,c]))},
297      {"skipping nested initial empty lists",
298       ?_test([a,b,c] = dlist_next([[[[]]],a,b,c]))},
299      {"skipping a final empty list",
300       ?_test([] = dlist_next([[]]))},
301      {"skipping nested final empty lists",
302       ?_test([] = dlist_next([[[[]]]]))},
303      {"the first element is in a sublist",
304       ?_test([a,b,c] = dlist_next([[a],b,c]))},
305      {"recognizing a naked string",
306       ?_test(["abc"] = dlist_next("abc"))},
307      {"recognizing a wrapped string",
308       ?_test(["abc"] = dlist_next(["abc"]))},
309      {"recognizing a leading string",
310       ?_test(["abc",a,b,c] = dlist_next(["abc",a,b,c]))},
311      {"recognizing a nested string",
312       ?_test(["abc"] = dlist_next([["abc"]]))},
313      {"recognizing a leading string in a sublist",
314       ?_test(["abc",a,b,c] = dlist_next([["abc"],a,b,c]))},
315      {"traversing an empty list",
316       ?_test([] = dlist_flatten([]))},
317      {"traversing a flat list",
318       ?_test([a,b,c] = dlist_flatten([a,b,c]))},
319      {"traversing a deep list",
320       ?_test([a,b,c] = dlist_flatten([[],[a,[b,[]],c],[]]))},
321      {"traversing a deep but empty list",
322       ?_test([] = dlist_flatten([[],[[[]]],[]]))}
323     ]}.
324
325%% test support
326dlist_flatten(Xs) ->
327    case dlist_next(Xs) of
328	[X | Xs1] -> [X | dlist_flatten(Xs1)];
329	[] -> []
330    end.
331-endif.
332
333
334%% ---------------------------------------------------------------------
335%% Check for proper Unicode-stringness.
336
337is_string([C | Cs]) when is_integer(C), C >= 0, C =< 16#10ffff ->
338    is_string(Cs);
339is_string([_ | _]) ->
340    false;
341is_string([]) ->
342    true;
343is_string(_) ->
344    false.
345
346is_nonempty_string([]) -> false;
347is_nonempty_string(Cs) -> is_string(Cs).
348
349-ifdef(TEST).
350is_string_test_() ->
351    {"is_string",
352     [{"no non-lists", ?_assert(not is_string($A))},
353      {"no non-integer lists", ?_assert(not is_string([true]))},
354      {"empty string", ?_assert(is_string(""))},
355      {"ascii string", ?_assert(is_string(lists:seq(0, 127)))},
356      {"latin-1 string", ?_assert(is_string(lists:seq(0, 255)))},
357      {"unicode string",
358       ?_assert(is_string([0, $A, 16#10fffe, 16#10ffff]))},
359      {"not above unicode range",
360       ?_assert(not is_string([0, $A, 16#110000]))},
361      {"no negative codepoints", ?_assert(not is_string([$A, -1, 0]))}
362     ]}.
363-endif.
364
365
366%% ---------------------------------------------------------------------
367%% Splitting a full node name into basename and hostname,
368%% using 'localhost' as the default hostname
369
370split_node(N) when is_atom(N) -> split_node(atom_to_list(N));
371split_node(Cs) -> split_node_1(Cs, []).
372
373split_node_1([$@ | Cs], As) -> split_node_2(As, Cs);
374split_node_1([C | Cs], As) -> split_node_1(Cs, [C | As]);
375split_node_1([], As) ->  split_node_2(As, "localhost").
376
377split_node_2(As, Cs) ->
378    {list_to_atom(lists:reverse(As)), list_to_atom(Cs)}.
379
380%% ---------------------------------------------------------------------
381%% Get the name of the containing function for a fun. (This is encoded
382%% in the name of the generated function that implements the fun.)
383fun_parent(F) ->
384    {module, M} = erlang:fun_info(F, module),
385    {name, N} = erlang:fun_info(F, name),
386    case erlang:fun_info(F, type) of
387	{type, external} ->
388	    {arity, A} = erlang:fun_info(F, arity),
389	    {M, N, A};
390	{type, local} ->
391            [$-|S] = atom_to_list(N),
392            [S2, T] = string:split(S, "/", trailing),
393            {M, list_to_atom(S2), element(1, string:to_integer(T))}
394    end.
395
396-ifdef(TEST).
397fun_parent_test() ->
398    {?MODULE,fun_parent_test,0} = fun_parent(fun (A) -> {ok,A} end).
399-endif.
400
401%% ---------------------------------------------------------------------
402%% Ye olde uniq function
403
404uniq([X, X | Xs]) -> uniq([X | Xs]);
405uniq([X | Xs]) -> [X | uniq(Xs)];
406uniq([]) -> [].
407
408-ifdef(TEST).
409-dialyzer({[no_fail_call, no_improper_lists], uniq_test_/0}).
410uniq_test_() ->
411    {"uniq",
412     [?_assertError(function_clause, uniq(ok)),
413      ?_assertError(function_clause, uniq([1|2])),
414      ?_test([] = uniq([])),
415      ?_test([1,2,3] = uniq([1,2,3])),
416      ?_test([1,2,3] = uniq([1,2,2,3])),
417      ?_test([1,2,3,2,1] = uniq([1,2,2,3,2,2,1])),
418      ?_test([1,2,3] = uniq([1,1,1,2,2,2,3,3,3])),
419      ?_test(["1","2","3"] = uniq(["1","1","2","2","3","3"]))
420     ]}.
421-endif.
422
423%% ---------------------------------------------------------------------
424%% Replacement for os:cmd
425
426%% TODO: Better cmd support, especially on Windows (not much tested)
427%% TODO: Can we capture stderr separately somehow?
428
429command(Cmd) ->
430    command(Cmd, "").
431
432command(Cmd, Dir) ->
433    command(Cmd, Dir, []).
434
435command(Cmd, Dir, Env) ->
436    CD = if Dir =:= "" -> [];
437	    true -> [{cd, Dir}]
438	 end,
439    SetEnv = if Env =:= [] -> [];
440		true -> [{env, Env}]
441	     end,
442    Opt = CD ++ SetEnv ++ [stream, exit_status, use_stdio,
443			   stderr_to_stdout, in, eof],
444    P = open_port({spawn, Cmd}, Opt),
445    get_data(P, []).
446
447get_data(P, D) ->
448    receive
449	{P, {data, D1}} ->
450	    get_data(P, [D1|D]);
451	{P, eof} ->
452	    port_close(P),
453	    receive
454		{P, {exit_status, N}} ->
455		    {N, normalize(lists:flatten(lists:reverse(D)))}
456	    end
457    end.
458
459normalize([$\r, $\n | Cs]) ->
460    [$\n | normalize(Cs)];
461normalize([$\r | Cs]) ->
462    [$\n | normalize(Cs)];
463normalize([C | Cs]) ->
464    [C | normalize(Cs)];
465normalize([]) ->
466    [].
467
468-ifdef(TEST).
469
470-dialyzer({no_match, cmd_test_/0}).
471cmd_test_() ->
472    ([?_test({0, "hello\n"} = ?_cmd_("echo hello"))]
473     ++ case os:type() of
474	    {unix, _} ->
475		unix_cmd_tests();
476	    {win32, _} ->
477		win32_cmd_tests();
478	    _ ->
479		[]
480	end).
481
482unix_cmd_tests() ->
483    [{"command execution, status, and output",
484      [?_cmd("echo hello"),
485       ?_assertCmdStatus(0, "true"),
486       ?_assertCmdStatus(1, "false"),
487       ?_assertCmd("true"),
488       ?_assertCmdOutput("hello\n", "echo hello"),
489       ?_assertCmdOutput("hello", "echo -n hello")
490      ]},
491     {"file setup and cleanup",
492      setup,
493      fun () -> ?cmd("mktemp tmp.XXXXXXXX") end,
494      fun (File) -> ?cmd("rm " ++ File) end,
495      fun (File) ->
496	      [?_assertCmd("echo xyzzy >" ++ File),
497	       ?_assertCmdOutput("xyzzy\n", "cat " ++ File)]
498      end}
499    ].
500
501win32_cmd_tests() ->
502    [{"command execution, status, and output",
503      [?_cmd("echo hello"),
504       ?_assertCmdOutput("hello\n", "echo hello")
505      ]}
506    ].
507
508-endif. % TEST
509
510
511%% ---------------------------------------------------------------------
512%% Wrapper around file:path_consult
513
514%% @throws {file_read_error, {Reason::atom(), Message::string(),
515%%                            fileName()}}
516
517consult_file(File) ->
518    case file:path_consult(["."]++code:get_path(), File) of
519	{ok, Data, _Path} ->
520	    Data;
521	{error, Reason} ->
522	    Msg = file:format_error(Reason),
523	    throw({file_read_error, {Reason, Msg, File}})
524    end.
525
526%% ---------------------------------------------------------------------
527%% Wrapper around file:list_dir
528
529%% @throws {file_read_error, {Reason::atom(), Message::string(),
530%%                            fileName()}}
531
532list_dir(Dir) ->
533    case file:list_dir(Dir) of
534	{ok, Fs} ->
535	    Fs;
536	{error, Reason} ->
537	    Msg = file:format_error(Reason),
538	    throw({file_read_error, {Reason, Msg, Dir}})
539    end.
540
541%% ---------------------------------------------------------------------
542%% A trie for remembering and checking least specific cancelled events
543%% (an empty list `[]' simply represents a stored empty list, i.e., all
544%% events will match, while an empty tree means that no events match).
545
546trie_new() ->
547    gb_trees:empty().
548
549trie_store([_ | _], []) ->
550    [];
551trie_store([E | Es], T) ->
552    case gb_trees:lookup(E, T) of
553	none ->
554	    if Es =:= [] ->
555		    gb_trees:insert(E, [], T);
556	       true ->
557		    gb_trees:insert(E, trie_store(Es, gb_trees:empty()),
558				    T)
559	    end;
560	{value, []} ->
561	    T;  %% prefix already stored
562	{value, T1} ->
563	    gb_trees:update(E, trie_store(Es, T1), T)
564    end;
565trie_store([], _T) ->
566    [].
567
568trie_match([_ | _], []) ->
569    prefix;
570trie_match([E | Es], T) ->
571    case gb_trees:lookup(E, T) of
572	none ->
573	    no;
574	{value, []} ->
575	    if Es =:= [] -> exact;
576	       true -> prefix
577	    end;
578	{value, T1} ->
579	    trie_match(Es, T1)
580    end;
581trie_match([], []) ->
582    exact;
583trie_match([], _T) ->
584    no.
585
586-ifdef(TEST).
587
588trie_test_() ->
589    [{"basic representation",
590      [?_assert(trie_new() =:= gb_trees:empty()),
591       ?_assert(trie_store([1], trie_new())
592		=:= gb_trees:insert(1, [], gb_trees:empty())),
593       ?_assert(trie_store([1,2], trie_new())
594		=:= gb_trees:insert(1,
595				    gb_trees:insert(2, [],
596						    gb_trees:empty()),
597				    gb_trees:empty())),
598       ?_assert([] =:= trie_store([1], [])),
599       ?_assert([] =:= trie_store([], gb_trees:empty()))
600      ]},
601     {"basic storing and matching",
602      [?_test(no = trie_match([], trie_new())),
603       ?_test(exact = trie_match([], trie_store([], trie_new()))),
604       ?_test(no = trie_match([], trie_store([1], trie_new()))),
605       ?_test(exact = trie_match([1], trie_store([1], trie_new()))),
606       ?_test(prefix = trie_match([1,2], trie_store([1], trie_new()))),
607       ?_test(no = trie_match([1], trie_store([1,2], trie_new()))),
608       ?_test(no = trie_match([1,3], trie_store([1,2], trie_new()))),
609       ?_test(exact = trie_match([1,2,3,4,5],
610				 trie_store([1,2,3,4,5], trie_new()))),
611       ?_test(prefix = trie_match([1,2,3,4,5],
612				  trie_store([1,2,3], trie_new()))),
613       ?_test(no = trie_match([1,2,2,4,5],
614			       trie_store([1,2,3], trie_new())))
615      ]},
616     {"matching with partially overlapping patterns",
617      setup,
618      fun () ->
619	      trie_store([1,3,2], trie_store([1,2,3], trie_new()))
620      end,
621      fun (T) ->
622	      [?_test(no = trie_match([], T)),
623	       ?_test(no = trie_match([1], T)),
624	       ?_test(no = trie_match([1,2], T)),
625	       ?_test(no = trie_match([1,3], T)),
626	       ?_test(exact = trie_match([1,2,3], T)),
627	       ?_test(exact = trie_match([1,3,2], T)),
628	       ?_test(no = trie_match([1,2,2], T)),
629	       ?_test(no = trie_match([1,3,3], T)),
630	       ?_test(prefix = trie_match([1,2,3,4], T)),
631	       ?_test(prefix = trie_match([1,3,2,1], T))]
632      end},
633     {"matching with more general pattern overriding less general",
634      setup,
635      fun () -> trie_store([1], trie_store([1,2,3], trie_new())) end,
636      fun (_) -> ok end,
637      fun (T) ->
638   	      [?_test(no = trie_match([], T)),
639	       ?_test(exact = trie_match([1], T)),
640 	       ?_test(prefix = trie_match([1,2], T)),
641 	       ?_test(prefix = trie_match([1,2,3], T)),
642 	       ?_test(prefix = trie_match([1,2,3,4], T))]
643      end}
644    ].
645
646-endif.  % TEST
647