Parcourir la source

More fixes found by QC and some QC improvements

pull/4/head
Andrew Thompson il y a 14 ans
Parent
révision
d230cbaa02
2 fichiers modifiés avec 78 ajouts et 25 suppressions
  1. +50
    -23
      src/trunc_io.erl
  2. +28
    -2
      test/trunc_io_eqc.erl

+ 50
- 23
src/trunc_io.erl Voir le fichier

@ -35,7 +35,7 @@
format(String, Args, Max) ->
Parts = re:split(String,
"(~(?:-??\\d+\\.|\\*\\.|)(?:-??\d+\\.|\\*\\.|)(?:-??\\d+|\\*|)(?:t|)(?:[cfegswpWPBX#bx+ni]))",
"(~(?:-??\\d+\\.|\\*\\.|\\.|)(?:-??\\d+\\.|\\*\\.|\\.|)(?:-??\\d+|\\*|)(?:t|)(?:[cfegswpWPBX#bx+ni~]))",
[{return, list}, trim]),
Maxlen = Max - length(String),
format(Parts, Args, Maxlen, [], []).
@ -46,25 +46,37 @@ format([], _Args, Max, Acc, ArgAcc) ->
format([[] | T], Args, Max, Acc, ArgAcc) ->
% discard the null list generated by split
format(T, Args, Max, Acc, ArgAcc);
format(["~~" | T], Args, Max, Acc, ArgAcc) ->
format(T, Args, Max+1, ["~~" | Acc], ArgAcc);
format(["~n" | T], Args, Max, Acc, ArgAcc) ->
% ignore newlines for the purposes of argument indexing
format(T, Args, Max+1, ["~n" | Acc], ArgAcc);
format(["~i" | T], [AH | AT], Max, Acc, ArgAcc) ->
% ~i means ignore this argument, but we'll just pass it through
format(T, AT, Max+2, ["~i" | Acc], [AH | ArgAcc]);
format([[$~|H]| T], [AH1, AH2 | AT], Max, Acc, ArgAcc) when H == "W"; H == "P"; H == "X" ->
% these crazy ones consume TWO arguments, just pass them through
% because W and P implicitly limit size so we trust the user knows
% what they're doing. Unfortunately we can't change Max at all because
% depth based limits are not of known length.
format(T, AT, Max, [[$~ | H] | Acc], [AH2, AH1 | ArgAcc]);
format([[$~|H]| T], [AH1, AH2 | AT], Max, Acc, ArgAcc) when H == "X"; H == "x" ->
%% ~X consumes 2 arguments. It only prints integers so we can leave it alone
format(T, AT, Max, ["~X" | Acc], [AH2, AH1 | ArgAcc]);
format([[$~|H]| T], [AH1, _AH2 | AT], Max, Acc, ArgAcc) when H == "W"; H == "P" ->
%% ~P and ~W consume 2 arguments, the second one being a depth limiter.
%% trunc_io isn't (yet) depth aware, so we can't honor this format string
%% safely at the moment, so just treat it like a regular ~p
%% TODO support for depth limiting
case print(AH1, Max + 2) of
{_Res, Max} ->
% this isn't the last argument, but it consumed all available space
% delay calculating the print size until the end
format(T, AT, Max + 2, ["~s" | Acc], [{future, AH1} | ArgAcc]);
{String, Length} ->
format(T, AT, Max + 2 - Length, ["~s" | Acc], [String | ArgAcc])
end;
format([[$~|H]| T], [AH | AT], Max, Acc, ArgAcc) when length(H) == 1 ->
% single character format specifier, relatively simple
case H of
_ when H == "p"; H == "w"; H == "s" ->
%okay, these are prime candidates for rewriting
case print(AH, Max + 2) of
{_Res, Max} when AT /= [] ->
{_Res, Max} ->
% this isn't the last argument, but it consumed all available space
% delay calculating the print size until the end
format(T, AT, Max + 2, ["~s" | Acc], [{future, AH} | ArgAcc]);
@ -80,16 +92,18 @@ format([[$~|H]| T], [AH | AT], Max, Acc, ArgAcc) when length(H) == 1 ->
end;
_ ->
% whatever, just pass them on through
format(T, AT, Max, [[$~ | H], Acc], [AH | ArgAcc])
format(T, AT, Max, [[$~ | H] | Acc], [AH | ArgAcc])
end;
format([[$~|H]| T], [AH | AT], Max, Acc, ArgAcc) ->
%% TODO
% complicated format specifier, gonna have to parse it..
case re:run(H, "^(?:-??(\\d+|\\*)\\.|)(?:-??(\\d+|\\*)\\.|)(-??\\d+|\\*|)(t|)([cfegswpWPBX#bx+ni])$", [{capture, all_but_first, list}]) of
{match, [_F, _P, _Pad, _Mod, C]} when C == "p"; C=="w"; C=="s" ->
%case re:run(H, "^(?:-??(\\d+|\\*)\\.|\\.|)(?:-??(\\d+|\\*)\\.|\\.|)(-??.|)(t|)([cfegswpWPBX#bx+ni])$", [{capture, all_but_first, list}]) of
%% its actually simpler to just look at the last character in the string
case lists:nth(length(H), H) of
C when C == $p; C == $w; C == $s ->
%{match, [_F, _P, _Pad, _Mod, C]} when C == "p"; C=="w"; C=="s" ->
%okay, these are prime candidates for rewriting
case print(AH, Max + length(H) + 1) of
{_Res, Max} when AT /= [] ->
{_Res, Max} ->
% this isn't the last argument, but it consumed all available space
% delay calculating the print size until the end
format(T, AT, Max + length(H) + 1, ["~s" | Acc], [{future, AH} | ArgAcc]);
@ -103,18 +117,31 @@ format([[$~|H]| T], [AH | AT], Max, Acc, ArgAcc) ->
end,
format(T, AT, Max + length(H) + 1 - RealLen, ["~s" | Acc], [Value | ArgAcc])
end;
{match, [_F, _P, _Pad, _Mod, C]} when C == "P"; C=="W" ->
% these crazy ones consume TWO arguments, just pass them through
% because W and P implicitly limit size so we trust the user knows
% what they're doing. Unfortunately we can't change Max at all because
% depth based limits are not of known length.
C when C == $P; C == $W ->
%{match, [_F, _P, _Pad, _Mod, C]} when C == "P"; C == "W" ->
%% ~P and ~W consume 2 arguments, the second one being a depth limiter.
%% trunc_io isn't (yet) depth aware, so we can't honor this format string
%% safely at the moment, so just treat it like a regular ~p
%% TODO support for depth limiting
[_ | AT2] = AT,
case print(AH, Max + 2) of
{_Res, Max} ->
% this isn't the last argument, but it consumed all available space
% delay calculating the print size until the end
format(T, AT2, Max + 2, ["~s" | Acc], [{future, AH} | ArgAcc]);
{String, Length} ->
format(T, AT2, Max + 2 - Length, ["~s" | Acc], [String | ArgAcc])
end;
C when C == $X; C == $x ->
%{match, [_F, _P, _Pad, _Mod, C]} when C == "X"; C == "x" ->
%% ~X consumes 2 arguments. It only prints integers so we can leave it alone
[AH2 | AT2] = AT,
format(T, AT2, Max, [[$~|H]|Acc], [AH2, AH |ArgAcc]);
{match, _} ->
format(T, AT, Max, [H | Acc], [AH|ArgAcc]);
nomatch ->
io:format("unable to match format pattern ~~~p~n", [H]),
format(T, AT, Max, [H | Acc], [AH|ArgAcc])
_ ->
format(T, AT, Max, [[$~|H] | Acc], [AH|ArgAcc])
%nomatch ->
%io:format("unable to match format pattern ~~~p~n", [H]),
%format(T, AT, Max, [[$~|H] | Acc], [AH|ArgAcc])
end;
format([H | T], Args, Max, Acc, ArgAcc) ->
format(T, Args, Max, [H | Acc], ArgAcc).

+ 28
- 2
test/trunc_io_eqc.erl Voir le fichier

@ -60,9 +60,29 @@ check() ->
gen_fmt_args() ->
list(oneof([gen_print_str(),
"~~",
{"~p", gen_any(5)},
{"~w", gen_any(5)},
{"~s", gen_print_str()}
{"~s", gen_print_str()},
{"~P", gen_any(5), 4},
{"~W", gen_any(5), 4},
{"~i", gen_any(5)},
{"~B", nat()},
{"~b", nat()},
{"~X", nat(), "0x"},
{"~x", nat(), "0x"},
{"~.10#", nat()},
{"~.10+", nat()},
{"~.36B", nat()},
{"~62P", gen_any(5), 4},
{"~c", gen_char()},
{"~tc", gen_char()}
%{"~f", real()}, %% floats like to make the fudge limit fail, so don't enable them
%{"~10.f", real()},
%{"~g", real()},
%{"~10.g", real()},
%{"~e", real()},
%{"~10.e", real()}
])).
@ -74,6 +94,7 @@ gen_any(MaxDepth) ->
oneof([largeint(),
gen_atom(),
nat(),
%real(),
binary(),
gen_pid(),
gen_port(),
@ -106,6 +127,9 @@ gen_ref() ->
gen_fun() ->
?LAZY(fun() -> ok end).
gen_char() ->
oneof(lists:seq($A, $z)).
%%====================================================================
%% Property
@ -115,7 +139,7 @@ gen_fun() ->
prop_format() ->
?FORALL({FmtArgs, MaxLen}, {gen_fmt_args(), gen_max_len()},
begin
FudgeLen = 31, %% trunc_io does not correctly calc safe size of pid/port/numbers/funs
FudgeLen = 50, %% trunc_io does not correctly calc safe size of pid/port/numbers/funs
{FmtStr, Args} = build_fmt_args(FmtArgs),
try
Str = lists:flatten(trunc_io:format(FmtStr, Args, MaxLen)),
@ -151,6 +175,8 @@ prop_format() ->
build_fmt_args(FmtArgs) ->
F = fun({Fmt, Arg}, {FmtStr0, Args0}) ->
{FmtStr0 ++ Fmt, Args0 ++ [Arg]};
({Fmt, Arg1, Arg2}, {FmtStr0, Args0}) ->
{FmtStr0 ++ Fmt, Args0 ++ [Arg1, Arg2]};
(Str, {FmtStr0, Args0}) ->
{FmtStr0 ++ Str, Args0}
end,

Chargement…
Annuler
Enregistrer