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.

200 lines
6.9 KiB

преди 14 години
  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. %% trunc_io does not correctly calc safe size of pid/port/numbers/funs
  119. FudgeLen = calculate_fudge(FmtArgs, 50),
  120. {FmtStr, Args} = build_fmt_args(FmtArgs),
  121. try
  122. Str = lists:flatten(lager_trunc_io:format(FmtStr, Args, MaxLen)),
  123. ?WHENFAIL(begin
  124. io:format(user, "FmtStr: ~p\n", [FmtStr]),
  125. io:format(user, "Args: ~p\n", [Args]),
  126. io:format(user, "FudgeLen: ~p\n", [FudgeLen]),
  127. io:format(user, "MaxLen: ~p\n", [MaxLen]),
  128. io:format(user, "ActLen: ~p\n", [length(Str)]),
  129. io:format(user, "Str: ~p\n", [Str])
  130. end,
  131. %% Make sure the result is a printable list
  132. %% and if the format string is less than the length,
  133. %% the result string is less than the length.
  134. conjunction([{printable, Str == "" orelse
  135. io_lib:printable_list(Str)},
  136. {length, length(FmtStr) > MaxLen orelse
  137. length(Str) =< MaxLen + FudgeLen}]))
  138. catch
  139. _:Err ->
  140. io:format(user, "\nException: ~p\n", [Err]),
  141. io:format(user, "FmtStr: ~p\n", [FmtStr]),
  142. io:format(user, "Args: ~p\n", [Args]),
  143. false
  144. end
  145. end).
  146. %%====================================================================
  147. %% Internal helpers
  148. %%====================================================================
  149. %% Build a tuple of {Fmt, Args} from a gen_fmt_args() return
  150. build_fmt_args(FmtArgs) ->
  151. F = fun({Fmt, Arg}, {FmtStr0, Args0}) ->
  152. {FmtStr0 ++ Fmt, Args0 ++ [Arg]};
  153. ({Fmt, Arg1, Arg2}, {FmtStr0, Args0}) ->
  154. {FmtStr0 ++ Fmt, Args0 ++ [Arg1, Arg2]};
  155. (Str, {FmtStr0, Args0}) ->
  156. {FmtStr0 ++ Str, Args0}
  157. end,
  158. lists:foldl(F, {"", []}, FmtArgs).
  159. calculate_fudge([], Acc) ->
  160. Acc;
  161. calculate_fudge([{"~62P", _Arg, _Depth}|T], Acc) ->
  162. calculate_fudge(T, Acc+62);
  163. calculate_fudge([{Fmt, Arg}|T], Acc) when
  164. Fmt == "~f"; Fmt == "~10.f";
  165. Fmt == "~g"; Fmt == "~10.g";
  166. Fmt == "~e"; Fmt == "~10.e";
  167. Fmt == "~x"; Fmt == "~X" ->
  168. calculate_fudge(T, Acc + length(lists:flatten(io_lib:format(Fmt, [Arg]))));
  169. calculate_fudge([_|T], Acc) ->
  170. calculate_fudge(T, Acc).
  171. -endif. % (EQC).
  172. -endif. % (TEST).