Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

239 рядки
8.5 KiB

12 роки тому
14 роки тому
  1. %% -------------------------------------------------------------------
  2. %%
  3. %% trunc_io_eqc: QuickCheck test for trunc_io:format with maxlen
  4. %%
  5. %% Copyright (c) 2011-2012 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, prop_equivalence/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, 30,
  35. {spawn,
  36. [
  37. {timeout, 15, ?_assertEqual(true, eqc:quickcheck(eqc:testing_time(14, ?QC_OUT(prop_format()))))},
  38. {timeout, 15, ?_assertEqual(true, eqc:quickcheck(eqc:testing_time(14, ?QC_OUT(prop_equivalence()))))}
  39. ]
  40. }}.
  41. %%====================================================================
  42. %% Shell helpers
  43. %%====================================================================
  44. test() ->
  45. test(100).
  46. test(N) ->
  47. quickcheck(numtests(N, prop_format())).
  48. check() ->
  49. check(prop_format(), current_counterexample()).
  50. %%====================================================================
  51. %% Generators
  52. %%====================================================================
  53. gen_fmt_args() ->
  54. list(oneof([gen_print_str(),
  55. "~~",
  56. {"~10000000.p", gen_any(5)},
  57. {"~w", gen_any(5)},
  58. {"~s", oneof([gen_print_str(), gen_atom(), gen_quoted_atom(), gen_print_bin()])},
  59. {"~1000000.P", gen_any(5), 4},
  60. {"~W", gen_any(5), 4},
  61. {"~i", gen_any(5)},
  62. {"~B", nat()},
  63. {"~b", nat()},
  64. {"~X", nat(), "0x"},
  65. {"~x", nat(), "0x"},
  66. {"~.10#", nat()},
  67. {"~.10+", nat()},
  68. {"~.36B", nat()},
  69. {"~1000000.62P", gen_any(5), 4},
  70. {"~c", gen_char()},
  71. {"~tc", gen_char()},
  72. {"~f", real()},
  73. {"~10.f", real()},
  74. {"~g", real()},
  75. {"~10.g", real()},
  76. {"~e", real()},
  77. {"~10.e", real()}
  78. ])).
  79. %% Generates a printable string
  80. gen_print_str() ->
  81. ?LET(Xs, list(char()), [X || X <- Xs, io_lib:printable_list([X]), X /= $~]).
  82. gen_print_bin() ->
  83. ?LET(Xs, gen_print_str(), list_to_binary(Xs)).
  84. gen_any(MaxDepth) ->
  85. oneof([largeint(),
  86. gen_atom(),
  87. gen_quoted_atom(),
  88. nat(),
  89. %real(),
  90. binary(),
  91. gen_bitstring(),
  92. gen_pid(),
  93. gen_port(),
  94. gen_ref(),
  95. gen_fun()] ++
  96. [?LAZY(list(gen_any(MaxDepth - 1))) || MaxDepth /= 0] ++
  97. [?LAZY(gen_tuple(gen_any(MaxDepth - 1))) || MaxDepth /= 0]).
  98. gen_atom() ->
  99. elements([abc, def, ghi]).
  100. gen_quoted_atom() ->
  101. elements(['abc@bar', '@bar', '10gen']).
  102. gen_bitstring() ->
  103. ?LET(XS, binary(), <<XS/binary, 1:7>>).
  104. gen_tuple(Gen) ->
  105. ?LET(Xs, list(Gen), list_to_tuple(Xs)).
  106. gen_max_len() -> %% Generate length from 3 to whatever. Needs space for ... in output
  107. ?LET(Xs, int(), 3 + abs(Xs)).
  108. gen_pid() ->
  109. ?LAZY(spawn(fun() -> ok end)).
  110. gen_port() ->
  111. ?LAZY(begin
  112. Port = erlang:open_port({spawn, "true"}, []),
  113. erlang:port_close(Port),
  114. Port
  115. end).
  116. gen_ref() ->
  117. ?LAZY(make_ref()).
  118. gen_fun() ->
  119. ?LAZY(fun() -> ok end).
  120. gen_char() ->
  121. oneof(lists:seq($A, $z)).
  122. %%====================================================================
  123. %% Property
  124. %%====================================================================
  125. %% Checks that trunc_io:format produces output less than or equal to MaxLen
  126. prop_format() ->
  127. ?FORALL({FmtArgs, MaxLen}, {gen_fmt_args(), gen_max_len()},
  128. begin
  129. %% Because trunc_io will print '...' when its running out of
  130. %% space, even if the remaining space is less than 3, it
  131. %% doesn't *exactly* stick to the specified limit.
  132. %% Also, since we don't truncate terms not printed with
  133. %% ~p/~P/~w/~W/~s, we also need to calculate the wiggle room
  134. %% for those. Hence the fudge factor calculated below.
  135. FudgeLen = calculate_fudge(FmtArgs, 50),
  136. {FmtStr, Args} = build_fmt_args(FmtArgs),
  137. try
  138. Str = lists:flatten(lager_trunc_io:format(FmtStr, Args, MaxLen)),
  139. ?WHENFAIL(begin
  140. io:format(user, "FmtStr: ~p\n", [FmtStr]),
  141. io:format(user, "Args: ~p\n", [Args]),
  142. io:format(user, "FudgeLen: ~p\n", [FudgeLen]),
  143. io:format(user, "MaxLen: ~p\n", [MaxLen]),
  144. io:format(user, "ActLen: ~p\n", [length(Str)]),
  145. io:format(user, "Str: ~p\n", [Str])
  146. end,
  147. %% Make sure the result is a printable list
  148. %% and if the format string is less than the length,
  149. %% the result string is less than the length.
  150. conjunction([{printable, Str == "" orelse
  151. io_lib:printable_list(Str)},
  152. {length, length(FmtStr) > MaxLen orelse
  153. length(Str) =< MaxLen + FudgeLen}]))
  154. catch
  155. _:Err ->
  156. io:format(user, "\nException: ~p\n", [Err]),
  157. io:format(user, "FmtStr: ~p\n", [FmtStr]),
  158. io:format(user, "Args: ~p\n", [Args]),
  159. false
  160. end
  161. end).
  162. %% Checks for equivalent formatting to io_lib
  163. prop_equivalence() ->
  164. ?FORALL(FmtArgs, gen_fmt_args(),
  165. begin
  166. {FmtStr, Args} = build_fmt_args(FmtArgs),
  167. Expected = lists:flatten(io_lib:format(FmtStr, Args)),
  168. Actual = lists:flatten(lager_trunc_io:format(FmtStr, Args, 10485760)),
  169. ?WHENFAIL(begin
  170. io:format(user, "FmtStr: ~p\n", [FmtStr]),
  171. io:format(user, "Args: ~p\n", [Args]),
  172. io:format(user, "Expected: ~p\n", [Expected]),
  173. io:format(user, "Actual: ~p\n", [Actual])
  174. end,
  175. Expected == Actual)
  176. end).
  177. %%====================================================================
  178. %% Internal helpers
  179. %%====================================================================
  180. %% Build a tuple of {Fmt, Args} from a gen_fmt_args() return
  181. build_fmt_args(FmtArgs) ->
  182. F = fun({Fmt, Arg}, {FmtStr0, Args0}) ->
  183. {FmtStr0 ++ Fmt, Args0 ++ [Arg]};
  184. ({Fmt, Arg1, Arg2}, {FmtStr0, Args0}) ->
  185. {FmtStr0 ++ Fmt, Args0 ++ [Arg1, Arg2]};
  186. (Str, {FmtStr0, Args0}) ->
  187. {FmtStr0 ++ Str, Args0}
  188. end,
  189. lists:foldl(F, {"", []}, FmtArgs).
  190. calculate_fudge([], Acc) ->
  191. Acc;
  192. calculate_fudge([{"~62P", _Arg, _Depth}|T], Acc) ->
  193. calculate_fudge(T, Acc+62);
  194. calculate_fudge([{Fmt, Arg}|T], Acc) when
  195. Fmt == "~f"; Fmt == "~10.f";
  196. Fmt == "~g"; Fmt == "~10.g";
  197. Fmt == "~e"; Fmt == "~10.e";
  198. Fmt == "~x"; Fmt == "~X";
  199. Fmt == "~B"; Fmt == "~b"; Fmt == "~36B";
  200. Fmt == "~.10#"; Fmt == "~10+" ->
  201. calculate_fudge(T, Acc + length(lists:flatten(io_lib:format(Fmt, [Arg]))));
  202. calculate_fudge([_|T], Acc) ->
  203. calculate_fudge(T, Acc).
  204. -endif. % (EQC).
  205. -endif. % (TEST).