From d230cbaa023576bb484b60714e985cb530e85a47 Mon Sep 17 00:00:00 2001 From: Andrew Thompson Date: Thu, 7 Jul 2011 17:23:55 -0400 Subject: [PATCH] More fixes found by QC and some QC improvements --- src/trunc_io.erl | 73 +++++++++++++++++++++++++++++-------------- test/trunc_io_eqc.erl | 30 ++++++++++++++++-- 2 files changed, 78 insertions(+), 25 deletions(-) diff --git a/src/trunc_io.erl b/src/trunc_io.erl index 23d4080..288c384 100644 --- a/src/trunc_io.erl +++ b/src/trunc_io.erl @@ -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). diff --git a/test/trunc_io_eqc.erl b/test/trunc_io_eqc.erl index 4b96aab..f97830a 100644 --- a/test/trunc_io_eqc.erl +++ b/test/trunc_io_eqc.erl @@ -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,