You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

244 lines
8.6 KiB

преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 11 години
преди 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, 60,
  35. {spawn,
  36. [
  37. {timeout, 30, ?_assertEqual(true, eqc:quickcheck(eqc:testing_time(14, ?QC_OUT(prop_format()))))},
  38. {timeout, 30, ?_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(), gen_iolist(5)])},
  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 /= $~, X < 255]).
  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_iolist(0) ->
  99. [];
  100. gen_iolist(Depth) ->
  101. list(oneof([gen_char(), gen_print_str(), gen_print_bin(), gen_iolist(Depth-1)])).
  102. gen_atom() ->
  103. elements([abc, def, ghi]).
  104. gen_quoted_atom() ->
  105. elements(['abc@bar', '@bar', '10gen']).
  106. gen_bitstring() ->
  107. ?LET(XS, binary(), <<XS/binary, 1:7>>).
  108. gen_tuple(Gen) ->
  109. ?LET(Xs, list(Gen), list_to_tuple(Xs)).
  110. gen_max_len() -> %% Generate length from 3 to whatever. Needs space for ... in output
  111. ?LET(Xs, int(), 3 + abs(Xs)).
  112. gen_pid() ->
  113. ?LAZY(spawn(fun() -> ok end)).
  114. gen_port() ->
  115. ?LAZY(begin
  116. Port = erlang:open_port({spawn, "true"}, []),
  117. catch(erlang:port_close(Port)),
  118. Port
  119. end).
  120. gen_ref() ->
  121. ?LAZY(make_ref()).
  122. gen_fun() ->
  123. ?LAZY(fun() -> ok end).
  124. gen_char() ->
  125. oneof(lists:seq($A, $z)).
  126. %%====================================================================
  127. %% Property
  128. %%====================================================================
  129. %% Checks that trunc_io:format produces output less than or equal to MaxLen
  130. prop_format() ->
  131. ?FORALL({FmtArgs, MaxLen}, {gen_fmt_args(), gen_max_len()},
  132. begin
  133. %% Because trunc_io will print '...' when its running out of
  134. %% space, even if the remaining space is less than 3, it
  135. %% doesn't *exactly* stick to the specified limit.
  136. %% Also, since we don't truncate terms not printed with
  137. %% ~p/~P/~w/~W/~s, we also need to calculate the wiggle room
  138. %% for those. Hence the fudge factor calculated below.
  139. FudgeLen = calculate_fudge(FmtArgs, 50),
  140. {FmtStr, Args} = build_fmt_args(FmtArgs),
  141. try
  142. Str = lists:flatten(lager_trunc_io:format(FmtStr, Args, MaxLen)),
  143. ?WHENFAIL(begin
  144. io:format(user, "FmtStr: ~p\n", [FmtStr]),
  145. io:format(user, "Args: ~p\n", [Args]),
  146. io:format(user, "FudgeLen: ~p\n", [FudgeLen]),
  147. io:format(user, "MaxLen: ~p\n", [MaxLen]),
  148. io:format(user, "ActLen: ~p\n", [length(Str)]),
  149. io:format(user, "Str: ~p\n", [Str])
  150. end,
  151. %% Make sure the result is a printable list
  152. %% and if the format string is less than the length,
  153. %% the result string is less than the length.
  154. conjunction([{printable, Str == "" orelse
  155. io_lib:printable_list(Str)},
  156. {length, length(FmtStr) > MaxLen orelse
  157. length(Str) =< MaxLen + FudgeLen}]))
  158. catch
  159. _:Err ->
  160. io:format(user, "\nException: ~p\n", [Err]),
  161. io:format(user, "FmtStr: ~p\n", [FmtStr]),
  162. io:format(user, "Args: ~p\n", [Args]),
  163. false
  164. end
  165. end).
  166. %% Checks for equivalent formatting to io_lib
  167. prop_equivalence() ->
  168. ?FORALL(FmtArgs, gen_fmt_args(),
  169. begin
  170. {FmtStr, Args} = build_fmt_args(FmtArgs),
  171. Expected = lists:flatten(io_lib:format(FmtStr, Args)),
  172. Actual = lists:flatten(lager_trunc_io:format(FmtStr, Args, 10485760)),
  173. ?WHENFAIL(begin
  174. io:format(user, "FmtStr: ~p\n", [FmtStr]),
  175. io:format(user, "Args: ~p\n", [Args]),
  176. io:format(user, "Expected: ~p\n", [Expected]),
  177. io:format(user, "Actual: ~p\n", [Actual])
  178. end,
  179. Expected == Actual)
  180. end).
  181. %%====================================================================
  182. %% Internal helpers
  183. %%====================================================================
  184. %% Build a tuple of {Fmt, Args} from a gen_fmt_args() return
  185. build_fmt_args(FmtArgs) ->
  186. F = fun({Fmt, Arg}, {FmtStr0, Args0}) ->
  187. {FmtStr0 ++ Fmt, Args0 ++ [Arg]};
  188. ({Fmt, Arg1, Arg2}, {FmtStr0, Args0}) ->
  189. {FmtStr0 ++ Fmt, Args0 ++ [Arg1, Arg2]};
  190. (Str, {FmtStr0, Args0}) ->
  191. {FmtStr0 ++ Str, Args0}
  192. end,
  193. lists:foldl(F, {"", []}, FmtArgs).
  194. calculate_fudge([], Acc) ->
  195. Acc;
  196. calculate_fudge([{"~62P", _Arg, _Depth}|T], Acc) ->
  197. calculate_fudge(T, Acc+62);
  198. calculate_fudge([{Fmt, Arg}|T], Acc) when
  199. Fmt == "~f"; Fmt == "~10.f";
  200. Fmt == "~g"; Fmt == "~10.g";
  201. Fmt == "~e"; Fmt == "~10.e";
  202. Fmt == "~x"; Fmt == "~X";
  203. Fmt == "~B"; Fmt == "~b"; Fmt == "~36B";
  204. Fmt == "~.10#"; Fmt == "~10+" ->
  205. calculate_fudge(T, Acc + length(lists:flatten(io_lib:format(Fmt, [Arg]))));
  206. calculate_fudge([_|T], Acc) ->
  207. calculate_fudge(T, Acc).
  208. -endif. % (EQC).
  209. -endif. % (TEST).