| %% ------------------------------------------------------------------- |
| %% |
| %% 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"}, []), |
| 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). |