%% -------------------------------------------------------------------
|
|
%%
|
|
%% trunc_io_eqc: QuickCheck test for trunc_io:format with maxlen
|
|
%%
|
|
%% Copyright (c) 2011-2012 Basho Technologies, Inc. All Rights Reserved.
|
|
%%
|
|
%% This file is provided to you 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.
|
|
%%
|
|
%% -------------------------------------------------------------------
|
|
-module(trunc_io_eqc).
|
|
|
|
-ifdef(TEST).
|
|
-ifdef(EQC).
|
|
-export([test/0, test/1, check/0, prop_format/0, prop_equivalence/0]).
|
|
|
|
-include_lib("eqc/include/eqc.hrl").
|
|
-include_lib("eunit/include/eunit.hrl").
|
|
|
|
-define(QC_OUT(P),
|
|
eqc:on_output(fun(Str, Args) -> io:format(user, Str, Args) end, P)).
|
|
|
|
%%====================================================================
|
|
%% eunit test
|
|
%%====================================================================
|
|
|
|
eqc_test_() ->
|
|
{timeout, 60,
|
|
{spawn,
|
|
[
|
|
{timeout, 30, ?_assertEqual(true, eqc:quickcheck(eqc:testing_time(14, ?QC_OUT(prop_format()))))},
|
|
{timeout, 30, ?_assertEqual(true, eqc:quickcheck(eqc:testing_time(14, ?QC_OUT(prop_equivalence()))))}
|
|
]
|
|
}}.
|
|
|
|
%%====================================================================
|
|
%% Shell helpers
|
|
%%====================================================================
|
|
|
|
test() ->
|
|
test(100).
|
|
|
|
test(N) ->
|
|
quickcheck(numtests(N, prop_format())).
|
|
|
|
check() ->
|
|
check(prop_format(), current_counterexample()).
|
|
|
|
%%====================================================================
|
|
%% Generators
|
|
%%====================================================================
|
|
|
|
gen_fmt_args() ->
|
|
list(oneof([gen_print_str(),
|
|
"~~",
|
|
{"~10000000.p", gen_any(5)},
|
|
{"~w", gen_any(5)},
|
|
{"~s", oneof([gen_print_str(), gen_atom(), gen_quoted_atom(), gen_print_bin(), gen_iolist(5)])},
|
|
{"~1000000.P", gen_any(5), 4},
|
|
{"~W", gen_any(5), 4},
|
|
{"~i", gen_any(5)},
|
|
{"~B", nat()},
|
|
{"~b", nat()},
|
|
{"~X", nat(), "0x"},
|
|
{"~x", nat(), "0x"},
|
|
{"~.10#", nat()},
|
|
{"~.10+", nat()},
|
|
{"~.36B", nat()},
|
|
{"~1000000.62P", gen_any(5), 4},
|
|
{"~c", gen_char()},
|
|
{"~tc", gen_char()},
|
|
{"~f", real()},
|
|
{"~10.f", real()},
|
|
{"~g", real()},
|
|
{"~10.g", real()},
|
|
{"~e", real()},
|
|
{"~10.e", real()}
|
|
])).
|
|
|
|
|
|
%% Generates a printable string
|
|
gen_print_str() ->
|
|
?LET(Xs, list(char()), [X || X <- Xs, io_lib:printable_list([X]), X /= $~, X < 255]).
|
|
|
|
gen_print_bin() ->
|
|
?LET(Xs, gen_print_str(), list_to_binary(Xs)).
|
|
|
|
gen_any(MaxDepth) ->
|
|
oneof([largeint(),
|
|
gen_atom(),
|
|
gen_quoted_atom(),
|
|
nat(),
|
|
%real(),
|
|
binary(),
|
|
gen_bitstring(),
|
|
gen_pid(),
|
|
gen_port(),
|
|
gen_ref(),
|
|
gen_fun()] ++
|
|
[?LAZY(list(gen_any(MaxDepth - 1))) || MaxDepth /= 0] ++
|
|
[?LAZY(gen_tuple(gen_any(MaxDepth - 1))) || MaxDepth /= 0]).
|
|
|
|
gen_iolist(0) ->
|
|
[];
|
|
gen_iolist(Depth) ->
|
|
list(oneof([gen_char(), gen_print_str(), gen_print_bin(), gen_iolist(Depth-1)])).
|
|
|
|
gen_atom() ->
|
|
elements([abc, def, ghi]).
|
|
|
|
gen_quoted_atom() ->
|
|
elements(['abc@bar', '@bar', '10gen']).
|
|
|
|
gen_bitstring() ->
|
|
?LET(XS, binary(), <<XS/binary, 1:7>>).
|
|
|
|
gen_tuple(Gen) ->
|
|
?LET(Xs, list(Gen), list_to_tuple(Xs)).
|
|
|
|
gen_max_len() -> %% Generate length from 3 to whatever. Needs space for ... in output
|
|
?LET(Xs, int(), 3 + abs(Xs)).
|
|
|
|
gen_pid() ->
|
|
?LAZY(spawn(fun() -> ok end)).
|
|
|
|
gen_port() ->
|
|
?LAZY(begin
|
|
Port = erlang:open_port({spawn, "true"}, []),
|
|
catch(erlang:port_close(Port)),
|
|
Port
|
|
end).
|
|
|
|
gen_ref() ->
|
|
?LAZY(make_ref()).
|
|
|
|
gen_fun() ->
|
|
?LAZY(fun() -> ok end).
|
|
|
|
gen_char() ->
|
|
oneof(lists:seq($A, $z)).
|
|
|
|
%%====================================================================
|
|
%% Property
|
|
%%====================================================================
|
|
|
|
%% Checks that trunc_io:format produces output less than or equal to MaxLen
|
|
prop_format() ->
|
|
?FORALL({FmtArgs, MaxLen}, {gen_fmt_args(), gen_max_len()},
|
|
begin
|
|
%% Because trunc_io will print '...' when its running out of
|
|
%% space, even if the remaining space is less than 3, it
|
|
%% doesn't *exactly* stick to the specified limit.
|
|
|
|
%% Also, since we don't truncate terms not printed with
|
|
%% ~p/~P/~w/~W/~s, we also need to calculate the wiggle room
|
|
%% for those. Hence the fudge factor calculated below.
|
|
FudgeLen = calculate_fudge(FmtArgs, 50),
|
|
{FmtStr, Args} = build_fmt_args(FmtArgs),
|
|
try
|
|
Str = lists:flatten(lager_trunc_io:format(FmtStr, Args, MaxLen)),
|
|
?WHENFAIL(begin
|
|
io:format(user, "FmtStr: ~p\n", [FmtStr]),
|
|
io:format(user, "Args: ~p\n", [Args]),
|
|
io:format(user, "FudgeLen: ~p\n", [FudgeLen]),
|
|
io:format(user, "MaxLen: ~p\n", [MaxLen]),
|
|
io:format(user, "ActLen: ~p\n", [length(Str)]),
|
|
io:format(user, "Str: ~p\n", [Str])
|
|
end,
|
|
%% Make sure the result is a printable list
|
|
%% and if the format string is less than the length,
|
|
%% the result string is less than the length.
|
|
conjunction([{printable, Str == "" orelse
|
|
io_lib:printable_list(Str)},
|
|
{length, length(FmtStr) > MaxLen orelse
|
|
length(Str) =< MaxLen + FudgeLen}]))
|
|
catch
|
|
_:Err ->
|
|
io:format(user, "\nException: ~p\n", [Err]),
|
|
io:format(user, "FmtStr: ~p\n", [FmtStr]),
|
|
io:format(user, "Args: ~p\n", [Args]),
|
|
false
|
|
end
|
|
end).
|
|
|
|
%% Checks for equivalent formatting to io_lib
|
|
prop_equivalence() ->
|
|
?FORALL(FmtArgs, gen_fmt_args(),
|
|
begin
|
|
{FmtStr, Args} = build_fmt_args(FmtArgs),
|
|
Expected = lists:flatten(io_lib:format(FmtStr, Args)),
|
|
Actual = lists:flatten(lager_trunc_io:format(FmtStr, Args, 10485760)),
|
|
?WHENFAIL(begin
|
|
io:format(user, "FmtStr: ~p\n", [FmtStr]),
|
|
io:format(user, "Args: ~p\n", [Args]),
|
|
io:format(user, "Expected: ~p\n", [Expected]),
|
|
io:format(user, "Actual: ~p\n", [Actual])
|
|
end,
|
|
Expected == Actual)
|
|
end).
|
|
|
|
|
|
%%====================================================================
|
|
%% Internal helpers
|
|
%%====================================================================
|
|
|
|
%% Build a tuple of {Fmt, Args} from a gen_fmt_args() return
|
|
build_fmt_args(FmtArgs) ->
|
|
F = fun({Fmt, Arg}, {FmtStr0, Args0}) ->
|
|
{FmtStr0 ++ Fmt, Args0 ++ [Arg]};
|
|
({Fmt, Arg1, Arg2}, {FmtStr0, Args0}) ->
|
|
{FmtStr0 ++ Fmt, Args0 ++ [Arg1, Arg2]};
|
|
(Str, {FmtStr0, Args0}) ->
|
|
{FmtStr0 ++ Str, Args0}
|
|
end,
|
|
lists:foldl(F, {"", []}, FmtArgs).
|
|
|
|
calculate_fudge([], Acc) ->
|
|
Acc;
|
|
calculate_fudge([{"~62P", _Arg, _Depth}|T], Acc) ->
|
|
calculate_fudge(T, Acc+62);
|
|
calculate_fudge([{Fmt, Arg}|T], Acc) when
|
|
Fmt == "~f"; Fmt == "~10.f";
|
|
Fmt == "~g"; Fmt == "~10.g";
|
|
Fmt == "~e"; Fmt == "~10.e";
|
|
Fmt == "~x"; Fmt == "~X";
|
|
Fmt == "~B"; Fmt == "~b"; Fmt == "~36B";
|
|
Fmt == "~.10#"; Fmt == "~10+" ->
|
|
calculate_fudge(T, Acc + length(lists:flatten(io_lib:format(Fmt, [Arg]))));
|
|
calculate_fudge([_|T], Acc) ->
|
|
calculate_fudge(T, Acc).
|
|
|
|
-endif. % (EQC).
|
|
-endif. % (TEST).
|