選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

208 行
7.3 KiB

  1. %% -------------------------------------------------------------------
  2. %%
  3. %% trunc_io_eqc: QuickCheck test for trunc_io:format with maxlen
  4. %%
  5. %% Copyright (c) 2007-2011 Basho Technologies, Inc. All Rights Reserved.
  6. %%
  7. %% This file is provided to you under the Apache License,
  8. %% Version 2.0 (the "License"); you may not use this file
  9. %% except in compliance with the License. You may obtain
  10. %% a copy of the License at
  11. %%
  12. %% http://www.apache.org/licenses/LICENSE-2.0
  13. %%
  14. %% Unless required by applicable law or agreed to in writing,
  15. %% software distributed under the License is distributed on an
  16. %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  17. %% KIND, either express or implied. See the License for the
  18. %% specific language governing permissions and limitations
  19. %% under the License.
  20. %%
  21. %% -------------------------------------------------------------------
  22. -module(trunc_io_eqc).
  23. -ifdef(TEST).
  24. -ifdef(EQC).
  25. -export([test/0, test/1, check/0, prop_format/0]).
  26. -include_lib("eqc/include/eqc.hrl").
  27. -include_lib("eunit/include/eunit.hrl").
  28. -define(QC_OUT(P),
  29. eqc:on_output(fun(Str, Args) -> io:format(user, Str, Args) end, P)).
  30. %%====================================================================
  31. %% eunit test
  32. %%====================================================================
  33. eqc_test_() ->
  34. {timeout, 300,
  35. {spawn,
  36. [?_assertEqual(true, quickcheck(numtests(500, ?QC_OUT(prop_format()))))]
  37. }}.
  38. %%====================================================================
  39. %% Shell helpers
  40. %%====================================================================
  41. test() ->
  42. test(100).
  43. test(N) ->
  44. quickcheck(numtests(N, prop_format())).
  45. check() ->
  46. check(prop_format(), current_counterexample()).
  47. %%====================================================================
  48. %% Generators
  49. %%====================================================================
  50. gen_fmt_args() ->
  51. list(oneof([gen_print_str(),
  52. "~~",
  53. {"~p", gen_any(5)},
  54. {"~w", gen_any(5)},
  55. {"~s", gen_print_str()},
  56. {"~P", gen_any(5), 4},
  57. {"~W", gen_any(5), 4},
  58. {"~i", gen_any(5)},
  59. {"~B", nat()},
  60. {"~b", nat()},
  61. {"~X", nat(), "0x"},
  62. {"~x", nat(), "0x"},
  63. {"~.10#", nat()},
  64. {"~.10+", nat()},
  65. {"~.36B", nat()},
  66. {"~62P", gen_any(5), 4},
  67. {"~c", gen_char()},
  68. {"~tc", gen_char()},
  69. {"~f", real()},
  70. {"~10.f", real()},
  71. {"~g", real()},
  72. {"~10.g", real()},
  73. {"~e", real()},
  74. {"~10.e", real()}
  75. ])).
  76. %% Generates a printable string
  77. gen_print_str() ->
  78. ?LET(Xs, list(char()), [X || X <- Xs, io_lib:printable_list([X]), X /= $~]).
  79. gen_any(MaxDepth) ->
  80. oneof([largeint(),
  81. gen_atom(),
  82. nat(),
  83. %real(),
  84. binary(),
  85. gen_pid(),
  86. gen_port(),
  87. gen_ref(),
  88. gen_fun()] ++
  89. [?LAZY(list(gen_any(MaxDepth - 1))) || MaxDepth /= 0] ++
  90. [?LAZY(gen_tuple(gen_any(MaxDepth - 1))) || MaxDepth /= 0]).
  91. gen_atom() ->
  92. elements([abc, def, ghi]).
  93. gen_tuple(Gen) ->
  94. ?LET(Xs, list(Gen), list_to_tuple(Xs)).
  95. gen_max_len() -> %% Generate length from 3 to whatever. Needs space for ... in output
  96. ?LET(Xs, int(), 3 + abs(Xs)).
  97. gen_pid() ->
  98. ?LAZY(spawn(fun() -> ok end)).
  99. gen_port() ->
  100. ?LAZY(begin
  101. Port = erlang:open_port({spawn, "true"}, []),
  102. erlang:port_close(Port),
  103. Port
  104. end).
  105. gen_ref() ->
  106. ?LAZY(make_ref()).
  107. gen_fun() ->
  108. ?LAZY(fun() -> ok end).
  109. gen_char() ->
  110. oneof(lists:seq($A, $z)).
  111. %%====================================================================
  112. %% Property
  113. %%====================================================================
  114. %% Checks that trunc_io:format produces output less than or equal to MaxLen
  115. prop_format() ->
  116. ?FORALL({FmtArgs, MaxLen}, {gen_fmt_args(), gen_max_len()},
  117. begin
  118. %% Because trunc_io will print '...' when its running out of
  119. %% space, even if the remaining space is less than 3, it
  120. %% doesn't *exactly* stick to the specified limit.
  121. %% Also, since we don't truncate terms not printed with
  122. %% ~p/~P/~w/~W/~s, we also need to calculate the wiggle room
  123. %% for those. Hence the fudge factor calculated below.
  124. FudgeLen = calculate_fudge(FmtArgs, 50),
  125. {FmtStr, Args} = build_fmt_args(FmtArgs),
  126. try
  127. Str = lists:flatten(lager_trunc_io:format(FmtStr, Args, MaxLen)),
  128. ?WHENFAIL(begin
  129. io:format(user, "FmtStr: ~p\n", [FmtStr]),
  130. io:format(user, "Args: ~p\n", [Args]),
  131. io:format(user, "FudgeLen: ~p\n", [FudgeLen]),
  132. io:format(user, "MaxLen: ~p\n", [MaxLen]),
  133. io:format(user, "ActLen: ~p\n", [length(Str)]),
  134. io:format(user, "Str: ~p\n", [Str])
  135. end,
  136. %% Make sure the result is a printable list
  137. %% and if the format string is less than the length,
  138. %% the result string is less than the length.
  139. conjunction([{printable, Str == "" orelse
  140. io_lib:printable_list(Str)},
  141. {length, length(FmtStr) > MaxLen orelse
  142. length(Str) =< MaxLen + FudgeLen}]))
  143. catch
  144. _:Err ->
  145. io:format(user, "\nException: ~p\n", [Err]),
  146. io:format(user, "FmtStr: ~p\n", [FmtStr]),
  147. io:format(user, "Args: ~p\n", [Args]),
  148. false
  149. end
  150. end).
  151. %%====================================================================
  152. %% Internal helpers
  153. %%====================================================================
  154. %% Build a tuple of {Fmt, Args} from a gen_fmt_args() return
  155. build_fmt_args(FmtArgs) ->
  156. F = fun({Fmt, Arg}, {FmtStr0, Args0}) ->
  157. {FmtStr0 ++ Fmt, Args0 ++ [Arg]};
  158. ({Fmt, Arg1, Arg2}, {FmtStr0, Args0}) ->
  159. {FmtStr0 ++ Fmt, Args0 ++ [Arg1, Arg2]};
  160. (Str, {FmtStr0, Args0}) ->
  161. {FmtStr0 ++ Str, Args0}
  162. end,
  163. lists:foldl(F, {"", []}, FmtArgs).
  164. calculate_fudge([], Acc) ->
  165. Acc;
  166. calculate_fudge([{"~62P", _Arg, _Depth}|T], Acc) ->
  167. calculate_fudge(T, Acc+62);
  168. calculate_fudge([{Fmt, Arg}|T], Acc) when
  169. Fmt == "~f"; Fmt == "~10.f";
  170. Fmt == "~g"; Fmt == "~10.g";
  171. Fmt == "~e"; Fmt == "~10.e";
  172. Fmt == "~x"; Fmt == "~X";
  173. Fmt == "~B"; Fmt == "~b"; Fmt == "~36B";
  174. Fmt == "~.10#"; Fmt == "~10+" ->
  175. calculate_fudge(T, Acc + length(lists:flatten(io_lib:format(Fmt, [Arg]))));
  176. calculate_fudge([_|T], Acc) ->
  177. calculate_fudge(T, Acc).
  178. -endif. % (EQC).
  179. -endif. % (TEST).