diff --git a/test/trunc_io_eqc.erl b/test/trunc_io_eqc.erl new file mode 100644 index 0000000..549c483 --- /dev/null +++ b/test/trunc_io_eqc.erl @@ -0,0 +1,157 @@ +%% ------------------------------------------------------------------- +%% +%% trunc_io_eqc: QuickCheck test for trunc_io:format with maxlen +%% +%% Copyright (c) 2007-2011 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]). + +-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_() -> + {spawn, + [?_assertEqual(true, quickcheck(numtests(500, ?QC_OUT(prop_format()))))] + }. + +%%==================================================================== +%% 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(), + {"~p", gen_any(5)}, + {"~w", gen_any(5)}, + {"~s", gen_print_str()} + ])). + + +%% Generates a printable string +gen_print_str() -> + ?LET(Xs, list(char()), [X || X <- Xs, io_lib:printable_list([X]), X /= $~]). + +gen_any(MaxDepth) -> + oneof([largeint(), + gen_atom(), + nat(), + binary(), + 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_atom() -> + elements([abc, def, ghi]). + +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). + +%%==================================================================== +%% 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 + {FmtStr, Args} = build_fmt_args(FmtArgs), + try + Str = lists:flatten(trunc_io:format(FmtStr, Args, MaxLen)), + ?WHENFAIL(begin + io:format("FmtStr: ~p\n", [FmtStr]), + io:format("Args: ~p\n", [Args]), + io:format("MaxLen: ~p\n", [MaxLen]), + io:format("ActLen: ~p\n", [length(Str)]), + io:format("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}])) + catch + _:Err -> + io:format(user, "\nException: ~p\n", [Err]), + io:format("FmtStr: ~p\n", [FmtStr]), + io:format("Args: ~p\n", [Args]), + false + end + 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]}; + (Str, {FmtStr0, Args0}) -> + {FmtStr0 ++ Str, Args0} + end, + lists:foldl(F, {"", []}, FmtArgs). + +-endif. % (TEST). +-endif. % (EQC).