|  | %% ------------------------------------------------------------------- | 
|  | %% | 
|  | %% 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). |