diff --git a/src/lux_case.erl b/src/lux_case.erl index 3a723cfa..82159b95 100644 --- a/src/lux_case.erl +++ b/src/lux_case.erl @@ -457,18 +457,23 @@ print_fail(OldI0, NewI, File, Results, {Actual, Rest}; <<"success pattern matched ", _/binary>> -> {Actual, Rest}; - _ when is_atom(Actual) -> + _ when is_atom(Actual), is_binary(Rest) -> {atom_to_list(Actual), Rest}; + _ when is_atom(Actual), is_atom(Rest) -> + {atom_to_list(Actual), list_to_binary(atom_to_list(Rest))}; _ when is_binary(Actual) -> {<<"error">>, Actual} end, + Diff = lux_utils:shrink_diff(Expected, NewRest), FailBin = ?l2b( [ io_lib:format("expected\n\t~s\n", [simple_to_string(Expected)]), - io_lib:format("actual ~s\n\t~s", - [NewActual, simple_to_string(NewRest)]) + io_lib:format("actual ~s\n\t~s\n", + [NewActual, simple_to_string(NewRest)]), + io_lib:format("diff\n\t~s", + [simple_to_string(Diff)]) ]), case OldI0#istate.progress of silent -> diff --git a/src/lux_debug.erl b/src/lux_debug.erl index 9fc2e41a..d8a3fb8f 100644 --- a/src/lux_debug.erl +++ b/src/lux_debug.erl @@ -999,7 +999,7 @@ tail_format("compact", Format, Data) -> io:format(Format, Data); tail_format("verbose", Format, Data) -> Str = lists:flatten(io_lib:format(Format, Data)), - io:format(lux_utils:dequote(Str)). + io:format(lux_log:dequote(Str)). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/src/lux_diff.erl b/src/lux_diff.erl index 83707674..5c2db74a 100644 --- a/src/lux_diff.erl +++ b/src/lux_diff.erl @@ -12,7 +12,7 @@ -module(lux_diff). -export([ - compare/2, compare/3, + compare/2, compare2/3, split_diff/1, apply_verbose_diff/2, apply_compact_diff/2, test/0, test2/2 @@ -30,17 +30,13 @@ %% both lists. The other elements can be merged in afterwards. This %% greatly speeds up the case when only a few elements are the same. --spec(compare([elem()],[elem()]) -> compact_diff()). -compare(A, B) -> - compare(A, B, default_match()). - --spec(compare(A :: elem_list(),B :: elem_list(), match_fun()) -> - compact_diff()). -compare(A, A, _Fun) -> +-spec(compare(A::elem_list(), B::elem_list()) -> compact_diff()). +compare(A, A) -> [A]; -compare(A, B, Fun) -> +compare(A, B) -> ASame = A -- (A -- B), BSame = B -- (B -- A), + Fun = default_match(), CompactDiff = compare2(ASame, BSame, Fun), merge_unique(A, B, CompactDiff, []). @@ -80,7 +76,7 @@ grab_until([X|Xs], Y, Acc, Add) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Calcuate shortest edit list to go from A to B --spec(compare2(A :: elem_list(),B :: elem_list(), match_fun()) -> +-spec(compare2(A::elem_list(), B::elem_list(), Fun::match_fun()) -> compact_diff()). compare2(A, B, Fun) -> DataA = list_to_tuple(A), @@ -88,8 +84,10 @@ compare2(A, B, Fun) -> DownVector = ets:new(down, [private]), UpVector = ets:new(up, [private]), try - Diff = lcs(DataA, 0, size(DataA), DataB, 0, size(DataB), - DownVector, UpVector, Fun, []), + Diff = lcs(DataA, 0, size(DataA), + DataB, 0, size(DataB), + DownVector, UpVector, + Fun, []), merge_cleanup(Diff,[]) after ets:delete(DownVector), @@ -144,7 +142,9 @@ merge_cleanup_keep([Keep|Rest], Acc, KeepAcc) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Longest Common Subsequence -lcs(DataA, LowerA0, UpperA0, DataB, LowerB0, UpperB0, DownVector, UpVector, +lcs(DataA, LowerA0, UpperA0, + DataB, LowerB0, UpperB0, + DownVector, UpVector, Fun, Acc0) -> %% Skip equal lines at start {LowerA, LowerB, StartKeep} = @@ -197,12 +197,18 @@ lcs(DataA, LowerA0, UpperA0, DataB, LowerB0, UpperB0, DownVector, UpVector, LowerB =:= UpperB -> acc_add(StartKeep++EndKeep, Acc0); true -> - {SX, SY} = sms(DataA, LowerA, UpperA, DataB, LowerB, UpperB, - DownVector, UpVector, Fun), - Acc1 = lcs(DataA, LowerA, SX, DataB, LowerB, SY, DownVector, - UpVector, Fun, acc_add(StartKeep,Acc0)), - Acc2 = lcs(DataA, SX, UpperA, DataB, SY, UpperB, DownVector, - UpVector, Fun, Acc1), + {SX, SY} = sms(DataA, LowerA, UpperA, + DataB, LowerB, UpperB, + DownVector, UpVector, + Fun), + Acc1 = lcs(DataA, LowerA, SX, + DataB, LowerB, SY, + DownVector, UpVector, + Fun, acc_add(StartKeep,Acc0)), + Acc2 = lcs(DataA, SX, UpperA, + DataB, SY, UpperB, + DownVector, UpVector, + Fun, Acc1), acc_add(EndKeep, Acc2) end. @@ -216,7 +222,10 @@ acc_add(E, Acc) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Find Shortest Middle Sname -sms(DataA, LowerA, UpperA, DataB, LowerB, UpperB, DownVector, UpVector, Fun) -> +sms(DataA, LowerA, UpperA, + DataB, LowerB, UpperB, + DownVector, UpVector, + Fun) -> DownK = LowerA - LowerB, UpK = UpperA - UpperB, %% @@ -228,37 +237,59 @@ sms(DataA, LowerA, UpperA, DataB, LowerB, UpperB, DownVector, UpVector, Fun) -> vset(DownVector, DownK+1, LowerA), vset(UpVector, UpK-1, UpperA), %% - sms_d(0, MaxD, DataA, LowerA, UpperA, DataB, LowerB, UpperB, DownVector, - UpVector, DownK, UpK, Delta, OddDelta, Fun). + sms_d(0, MaxD, + DataA, LowerA, UpperA, + DataB, LowerB, UpperB, + DownVector, UpVector, DownK, UpK, + Delta, OddDelta, Fun). %% D loop -sms_d(D, MaxD, _DataA, _LowerA, _UpperA, _DataB, _LowerB, _UpperB, _DownVector, - _UpVector, _DownK, _UpK, _Delta, _OddDelta, _Fun) when D > MaxD -> +sms_d(D, MaxD, + _DataA, _LowerA, _UpperA, + _DataB, _LowerB, _UpperB, + _DownVector,_UpVector,_DownK,_UpK, + _Delta, _OddDelta, _Fun) when D > MaxD -> throw(error); -sms_d(D, MaxD, DataA, LowerA, UpperA, DataB, LowerB, UpperB, DownVector, - UpVector, DownK, UpK, Delta, OddDelta, Fun) -> - case sms_d_k_f(DownK-D, D, DataA, LowerA, UpperA, DataB, LowerB, UpperB, - DownVector, UpVector, DownK, UpK, Delta, OddDelta, Fun) of +sms_d(D, MaxD, + DataA, LowerA, UpperA, + DataB, LowerB, UpperB, + DownVector, UpVector, DownK, UpK, + Delta, OddDelta, Fun) -> + case sms_d_k_f(DownK-D, D, + DataA, LowerA, UpperA, + DataB, LowerB, UpperB, + DownVector, UpVector, DownK, UpK, + Delta, OddDelta, Fun) of not_found -> - case sms_d_k_r(UpK-D, D, DataA, LowerA, UpperA, DataB, LowerB, - UpperB, DownVector, UpVector, DownK, UpK, + case sms_d_k_r(UpK-D, D, + DataA, LowerA, UpperA, + DataB, LowerB, UpperB, + DownVector, UpVector, DownK, UpK, Delta, OddDelta, Fun) of not_found -> - sms_d(D+1, MaxD, DataA, LowerA, UpperA, DataB, LowerB, - UpperB, DownVector, UpVector, DownK, UpK, Delta, - OddDelta, Fun); + sms_d(D+1, MaxD, + DataA, LowerA, UpperA, + DataB, LowerB, UpperB, + DownVector, UpVector, DownK, UpK, + Delta, OddDelta, Fun); Point -> Point end; Point -> Point end. %% Forward snake -sms_d_k_f(K, D, _DataA, _LowerA, _UpperA, _DataB, _LowerB, _UpperB, - _DownVector, _UpVector, DownK, _UpK, _Delta, _OddDelta, _Fun) +sms_d_k_f(K, D, + _DataA, _LowerA, _UpperA, + _DataB, _LowerB, _UpperB, + _DownVector, _UpVector, DownK, _UpK, + _Delta, _OddDelta, _Fun) when K > (DownK+D) -> not_found; -sms_d_k_f(K, D, DataA, LowerA, UpperA, DataB, LowerB, UpperB, - DownVector, UpVector, DownK, UpK, Delta, OddDelta, Fun) -> +sms_d_k_f(K, D, + DataA, LowerA, UpperA, + DataB, LowerB, UpperB, + DownVector, UpVector, DownK, UpK, + Delta, OddDelta, Fun) -> if K =:= (DownK - D) -> X = vget(DownVector, K+1); %% Down @@ -301,23 +332,33 @@ sms_d_k_f(K, D, DataA, LowerA, UpperA, DataB, LowerB, UpperB, UpVK =< DownVK -> {DownVK, DownVK-K}; true -> - sms_d_k_f(K+2, D, DataA, LowerA, UpperA, DataB, - LowerB, UpperB, DownVector, UpVector, - DownK, UpK, Delta, OddDelta, Fun) + sms_d_k_f(K+2, D, + DataA, LowerA, UpperA, + DataB, LowerB, UpperB, + DownVector, UpVector, DownK, UpK, + Delta, OddDelta, Fun) end; true -> - sms_d_k_f(K+2, D, DataA, LowerA, UpperA, DataB, - LowerB, UpperB, DownVector, UpVector, - DownK, UpK, Delta, OddDelta, Fun) + sms_d_k_f(K+2, D, + DataA, LowerA, UpperA, + DataB, LowerB, UpperB, + DownVector, UpVector, DownK, UpK, + Delta, OddDelta, Fun) end. %% Backward snake -sms_d_k_r(K, D, _DataA, _LowerA, _UpperA, _DataB, _LowerB, _UpperB, - _DownVector, _UpVector, _DownK, UpK, _Delta, _OddDelta, _Fun) +sms_d_k_r(K, D, + _DataA, _LowerA, _UpperA, + _DataB, _LowerB, _UpperB, + _DownVector, _UpVector, _DownK, UpK, + _Delta, _OddDelta, _Fun) when K > (UpK+D) -> not_found; -sms_d_k_r(K, D, DataA, LowerA, UpperA, DataB, LowerB, UpperB, - DownVector, UpVector, DownK, UpK, Delta, OddDelta, Fun) -> +sms_d_k_r(K, D, + DataA, LowerA, UpperA, + DataB, LowerB, UpperB, + DownVector, UpVector, DownK, UpK, + Delta, OddDelta, Fun) -> if K =:= (UpK+D) -> X = vget(UpVector, K-1); %% Up @@ -360,14 +401,18 @@ sms_d_k_r(K, D, DataA, LowerA, UpperA, DataB, LowerB, UpperB, UpVK =< DownVK -> {DownVK, DownVK-K}; true -> - sms_d_k_r(K+2, D, DataA, LowerA, UpperA, DataB, LowerB, - UpperB, DownVector, UpVector, DownK, UpK, Delta, - OddDelta, Fun) + sms_d_k_r(K+2, D, + DataA, LowerA, UpperA, + DataB, LowerB, UpperB, + DownVector, UpVector, DownK, UpK, + Delta, OddDelta, Fun) end; true -> - sms_d_k_r(K+2, D, DataA, LowerA, UpperA, DataB, LowerB, - UpperB, DownVector, UpVector, DownK, UpK, Delta, - OddDelta, Fun) + sms_d_k_r(K+2, D, + DataA, LowerA, UpperA, + DataB, LowerB, UpperB, + DownVector, UpVector, DownK, UpK, + Delta, OddDelta, Fun) end. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -391,7 +436,7 @@ split_diff([[H|T]|Rest], Acc) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Apply diff to list --spec(apply_verbose_diff(A :: elem_list(), Diff :: verbose_diff()) -> +-spec(apply_verbose_diff(A::elem_list(), Diff::verbose_diff()) -> elem_list()). apply_verbose_diff(A, Diff) -> apply_verbose_diff(A, Diff, []). @@ -405,7 +450,7 @@ apply_verbose_diff([A|As], [{'-',A}|Rest], Acc) -> apply_verbose_diff([A|As], [{'=',A}|Rest], Acc) -> apply_verbose_diff(As, Rest, [A|Acc]). --spec(apply_compact_diff(A :: elem_list(), Diff :: compact_diff()) -> +-spec(apply_compact_diff(A::elem_list(), Diff::compact_diff()) -> elem_list()). apply_compact_diff(A, Diff) -> apply_compact_diff(A, Diff, []). @@ -509,9 +554,9 @@ test(N, Max, Var) -> test2(A, B) -> Fun = default_match(), erlang:garbage_collect(), - {Time2,CompactDiff1} = timer:tc(fun() -> compare(A,B,Fun) end), + {Time2,CompactDiff1} = timer:tc(fun() -> compare(A ,B) end), erlang:garbage_collect(), - {Time1,CompactDiff2} = timer:tc(fun() -> compare2(A,B,Fun) end), + {Time1,CompactDiff2} = timer:tc(fun() -> compare2(A, B, Fun) end), try [] = validate_diff(CompactDiff1, []), [] = validate_diff(CompactDiff2, []), diff --git a/src/lux_html_annotate.erl b/src/lux_html_annotate.erl index f7886a4f..b38e47bb 100644 --- a/src/lux_html_annotate.erl +++ b/src/lux_html_annotate.erl @@ -330,6 +330,7 @@ annotate_event_log(#astate{log_file=EventLog} = A) when is_list(EventLog) -> Result = lux_log:parse_result(ResultBins), {Annotated, Files} = interleave_code(A3, Events, Script, 1, 999999, [], []), + Html = html_events(A3, EventLog2, ConfigLog, Script, Result, Timers, Files, Logs, Annotated, ConfigBins), {ok, Csv, Html}; @@ -495,8 +496,7 @@ html_events(A, EventLog, ConfigLog, Script, Result, ["

Elapsed time: ", DiffStr, "

\n"] end, RelEventLogDir = filename:split(drop_run_log_prefix(A, EventLogDir)), - RelSummaryLogDir = filename:join([".." || _ <- RelEventLogDir]), - RelSummaryLog = filename:join([RelSummaryLogDir, "lux_summary.log.html"]), + RelSummaryLog = dotdot(RelEventLogDir, "lux_summary.log.html"), [ lux_html_utils:html_header(["Lux event log (", Dir, ")"]), "\n", lux_html_utils:html_href("h2", "", "", "#annotate", PrefixScript), @@ -537,6 +537,12 @@ html_events(A, EventLog, ConfigLog, Script, Result, lux_html_utils:html_footer() ]. +dotdot(["."], Base) -> + Base; +dotdot(Path, Base) -> + DotDots = filename:join([".." || _ <- Path]), + filename:join([DotDots, Base]). + html_stats(OrigTimers) -> Timers = strip_timers(OrigTimers, []), [ @@ -667,152 +673,127 @@ html_result(Tag, {result, Result}, HtmlLog) -> "

Reason

", html_div(<<"event">>, lux_utils:expand_lines(Reason)) ]; - {warning, RawLineNo, Expected0, Actual, Details} -> + {How, RawLineNo, Expected, Actual, Details} + when How =:= fail; How =:= warning -> Anchor = RawLineNo, - Expected = lux_utils:expand_lines(Expected0), - Expected2 = lux_utils:normalize_newlines(Expected), - Expected3 = binary:split(Expected2, <<"\\R">>, [global]), - Diff = lux_utils:diff(Expected3, Details), - HtmlDiff = html_diff(Diff), + HtmlDiff = html_diff(Expected, Details), [ "\n<", Tag, ">Result: ", lux_html_utils:html_href([HtmlLog, "#failed"], "FAILED"), " at line ", lux_html_utils:html_href([HtmlLog, "#", Anchor], Anchor), "\n", - "

Expected

", - html_div(<<"event">>, lux_utils:expand_lines(Expected3)), - "

Actual: ", lux_html_utils:html_quote(Actual), "

", - [ - "\n
",
-              lux_utils:expand_lines(HtmlDiff),
-              "
" - ] - ]; - {fail, RawLineNo, Expected0, Actual, Details} -> - Anchor = RawLineNo, - Expected = lux_utils:expand_lines(Expected0), - Expected2 = lux_utils:normalize_newlines(Expected), - Expected3 = binary:split(Expected2, <<"\\R">>, [global]), - Diff = lux_utils:diff(Expected3, Details), - HtmlDiff = html_diff(Diff), - [ - "\n<", Tag, ">Result: ", - lux_html_utils:html_href([HtmlLog, "#failed"], "FAILED"), - " at line ", - lux_html_utils:html_href([HtmlLog, "#", Anchor], Anchor), - "\n", - "

Expected

", - html_div(<<"event">>, lux_utils:expand_lines(Expected3)), + + "

Expected:

", + html_div(<<"event">>, lux_utils:expand_lines(Expected)), + "

Actual: ", lux_html_utils:html_quote(Actual), "

", - [ - "\n
",
-              lux_utils:expand_lines(HtmlDiff),
-              "
", - lux_html_utils:html_anchor("failed", "") - ] + "
",
+             lux_html_utils:html_quote(lux_utils:expand_lines(Details)),
+             "
", + + lux_html_utils:html_anchor("failed", ""), + "\n

Diff:

", + "
",
+             HtmlDiff,
+             "
" ] end. -html_diff(Diff) -> - html_diff(Diff, [], first, false). - -html_diff([H|T], Acc, Where, Rep) -> - Plain = "", - Bold = "b", - case H of - {'=', Com} -> - html_diff(T, [{<<" ">>, "black",Bold,clean,Com}|Acc], middle, Rep); - {'+', Ins} when Where =/= first, element(1,hd(T)) =:= common -> - html_diff(T, [{<<"+ ">>,"blue",Bold,clean,Ins}|Acc], middle, Rep); - {'+', Ins} -> - html_diff(T, [{<<" ">>,"black",Plain,clean,Ins}|Acc], middle, Rep); - {'-', Del} -> - html_diff(T, [{<<"- ">>,"red",Bold,clean,Del}|Acc], middle, Rep); - {'!', Ins, Del} when Where =:= first, T =:= [] -> - %% Display single replace as insert - {Clean, _Del2, Ins2} = html_part(Del, Ins), -%% html_diff(T, [{<<"- ">>,"red",Bold,Clean,Del2}, -%% {<<"+ ">>,"blue",Bold,Clean,Ins2}|Acc], middle, true) - html_diff(T, [{<<" ">>,"black",Bold,Clean,Ins2}|Acc],middle, true); - {'!', Ins, Del} -> - {Clean, Del2, Ins2} = html_part(Del, Ins), - html_diff(T, [{<<"- ">>,"red",Bold,Clean,Del2}, - {<<"+ ">>,"blue",Bold,Clean,Ins2}|Acc], middle, true) +html_diff(Expected, Details) -> + lux_utils:diff_iter(Expected, Details, deep, fun emit/4). + +emit(Op, Mode, Context, Acc) when Mode =:= flat; + Mode =:= deep -> + case Op of + {common, Common} -> + [Acc, + "\n", html_color(common, Mode, <<" ">>, + lux_utils:shrink_lines(Common))]; + {del, Del} -> + [Acc, + "\n", html_color(del, Mode, <<"- ">>, Del)]; + {add, Add} -> + Prefix = + case Context of + first -> <<" ">>; + middle -> <<"+ ">>; + last -> <<" ">> + end, + [Acc, + "\n", html_color(add, Mode, Prefix, Add)]; + {replace, Del, Add} when Mode =:= flat -> + [ + Acc, + "\n", html_color(del, Mode, <<"- ">>, Del), + "\n", html_color(add, Mode, <<"+ ">>, Add) + ]; + {nested, Del, Add, [_SingleNestedOp]} when Mode =:= deep -> + %% Skip underline + [ + Acc, + "\n", html_color(del, Mode, <<"- ">>, Del), + "\n", html_color(add, Mode, <<"+ ">>, Add) + ]; + {nested, _OrigDel, _OrigAdd, RevNestedAcc} when Mode =:= deep -> + NestedAcc = lists:reverse(RevNestedAcc), + Del = ?l2b(nested_emit(del, NestedAcc, [])), + Add = ?l2b(nested_emit(add, NestedAcc, [])), + Del2 = binary:split(Del, <<"\n">>, [global]), + Add2 = binary:split(Add, <<"\n">>, [global]), + [ + Acc, + "\n", html_color(del, nested, <<"- ">>, Del2), + "\n", html_color(add, nested, <<"+ ">>, Add2) + ] end; -html_diff([], Acc, _Where, Rep) -> - html_color(lists:reverse(Acc), Rep). - -html_part([Del], [Ins]) -> - Diff = lux_utils:diff(?b2l(Del), ?b2l(Ins)), - html_part_diff(Diff, [], []); -html_part(Del, Ins) -> - {clean, Del, Ins}. - -html_part_diff([H|T], DelAcc, InsAcc) -> - Underline = "u", - case H of - {'=', Com} -> - CleanCom = lux_html_utils:html_quote(Com), - html_part_diff(T, - [CleanCom|DelAcc], - [CleanCom|InsAcc]); - {'+', Ins} -> - html_part_diff(T, - DelAcc, - [tag(Underline, - lux_html_utils:html_quote(Ins))|InsAcc]); - {'-', Del} -> - html_part_diff(T, - [tag(Underline, - lux_html_utils:html_quote(Del))|DelAcc], - InsAcc); - {'!', Ins, Del} -> - html_part_diff(T, - [tag(Underline, - lux_html_utils:html_quote(Del))|DelAcc], - [tag(Underline, - lux_html_utils:html_quote(Ins))|InsAcc]) +emit(Op, Mode, _Context, Acc) when Mode =:= nested -> + [Op | Acc]. + +nested_emit(Op, [NestedOp | Rest], Acc) -> + case NestedOp of + {common, Common} -> + NewAcc = [Acc, lux_html_utils:html_quote(Common)], + nested_emit(Op, Rest, NewAcc); + {del, Del} when Op =:= del -> + NewAcc = [Acc, tag(<<"u">>, lux_html_utils:html_quote(Del))], + nested_emit(Op, Rest, NewAcc); + {add, Add} when Op =:= add -> + NewAcc = [Acc, tag(<<"u">>, lux_html_utils:html_quote(Add))], + nested_emit(Op, Rest, NewAcc); + {replace, Del, Add} -> + nested_emit(Op, [{del, Del}, {add, Add} | Rest], Acc); + _Ignore -> + nested_emit(Op, Rest, Acc) end; -html_part_diff([], DelAcc, InsAcc) -> - {noclean, - [?l2b(lists:reverse(DelAcc))], - [?l2b(lists:reverse(InsAcc))]}. - -html_color([{Prefix, Color, Style, Clean, [Line|Lines]} | LineSpec], Delay) -> - [ - ?l2b([if - Delay =:= true -> ""; - Color =:= "black" -> ""; - true -> lux_html_utils:html_anchor("failed", "") - end, - "", - opt_tag(Style, opt_clean(Prefix, Clean, Line)), - ""]) - | html_color([{Prefix, Color, Style, Clean, Lines} | LineSpec], Delay) - ]; -html_color([{_Prefix, _Color, _Style, _Clean, []} | LineSpec], _Delay) -> - html_color(LineSpec, false); -html_color([], _Delay) -> - []. - -opt_clean(Prefix, Clean, Line) -> +nested_emit(_Op, [], Acc) -> + Acc. + +html_color(Op, Mode, Prefix, Text) -> + Color = + case Op of + common -> <<"black">>; + del -> <<"red">>; + add -> <<"blue">> + end, + Bold = <<"b">>, + QuotedPrefix = lux_html_utils:html_quote(Prefix), [ - lux_html_utils:html_quote(Prefix), - case Clean of - clean -> lux_html_utils:html_quote(Line); - noclean -> Line - end + <<">, Color, <<"\">">>, + tag(Bold, html_expand_lines(Text, QuotedPrefix, Mode)), + <<"">> ]. -opt_tag(Tag, Text) -> - case Tag of - "" -> Text; - _ -> tag(Tag, Text) - end. +html_expand_lines([], _QuotedPrefix, _Quote) -> + []; +html_expand_lines([H|T], QuotedPrefix, nested) -> + [[QuotedPrefix, H] | [[<<"\n">>, QuotedPrefix, L] || L <- T]]; +html_expand_lines([H|T], QuotedPrefix, _Quotequote) -> + [[QuotedPrefix, lux_html_utils:html_quote(H)] | + [[<<"\n">>, QuotedPrefix, lux_html_utils:html_quote(L)] || L <- T]]. tag(Tag, Text) -> - ["<", Tag, ">", Text, ""]. + [<<"<">>, Tag, <<">">>, Text, <<">, Tag, <<">">>]. html_config(Config) when is_list(Config) -> html_div(<<"event">>, lux_utils:expand_lines(Config)); @@ -820,7 +801,6 @@ html_config(Config) when is_binary(Config) -> html_div(<<"event">>, Config). html_logs(A, [{log, ShellName, Stdin, Stdout} | Logs]) -> - [ "\n\n ", "Shell ", ShellName, "", diff --git a/src/lux_html_utils.erl b/src/lux_html_utils.erl index 5bc53da2..05cfad65 100644 --- a/src/lux_html_utils.erl +++ b/src/lux_html_utils.erl @@ -25,7 +25,7 @@ safe_write_file(File, IoList) when is_list(File) -> if Res =:= ok; Res =:= {error, eexist} -> TmpFile = File ++ ".tmp", - case file:write_file(TmpFile, IoList) of + case file:write_file(TmpFile, IoList, [raw]) of ok -> case file:rename(TmpFile, File) of ok -> @@ -90,7 +90,7 @@ html_quote(IoList) -> safe_ctrl(Char) -> if - Char < 32, Char =/= 10 -> + Char < 32, Char =/= $\t, Char =/= $\n, Char =/= $\r -> [""]; true -> Char diff --git a/src/lux_interpret.erl b/src/lux_interpret.erl index 51697c27..81fb4c51 100644 --- a/src/lux_interpret.erl +++ b/src/lux_interpret.erl @@ -96,8 +96,8 @@ collect_macros(#istate{orig_file = OrigFile} = I, OrigCmds) -> lux_utils:foldl_cmds(Collect, [], OrigFile, [], OrigCmds). loop(#istate{mode = stopping, - shells = [], - active_shell = undefined} = I) -> + shells = [], + active_shell = undefined} = I) -> %% Stop main I; loop(#istate{commands = [], call_level = CallLevel} = I) diff --git a/src/lux_log.erl b/src/lux_log.erl index c3eb0593..c26b200f 100644 --- a/src/lux_log.erl +++ b/src/lux_log.erl @@ -16,7 +16,7 @@ open_event_log/5, close_event_log/1, write_event/4, scan_events/1, parse_events/2, extract_timers/1, timers_to_csv/1, parse_io_logs/2, open_config_log/3, close_config_log/2, - safe_format/5, safe_write/4 + safe_format/5, safe_write/4, dequote/1 ]). -include_lib("kernel/include/file.hrl"). @@ -687,17 +687,6 @@ parse_other_file(EndTag, SubFile, Events, Acc) when is_binary(SubFile) -> events = SubEvents2}, do_parse_events(Events2, [E | Acc]). -split_lines(<<>>) -> - []; -split_lines(Bin) -> - Opts = [global], - Replace = fun(NL, B) -> binary:replace(B , NL, <<"\n">>, Opts) end, - NLs = [<<"[\\r\\n]+">>, <<"\\r\\n">>, - <<"\n\r">>, <<"\r\n">>, - <<"\\n">>, <<"\\r">>], - Normalized = lists:foldl(Replace, Bin, NLs), - binary:split(Normalized, <<"\n">>, Opts). - extract_timers(Events) -> {_SendEvent, RevTimers} = extract_timers(Events, undefined, [<<>>], [], []), @@ -951,6 +940,31 @@ parse_result(RawResult) -> %% io:format("Result: ~p\n", [R]), {result, R}. +normalize_newlines(IoList) -> + re:replace(IoList, <<"(\\\\r\\\\n|\\\\r|\\\\n)">>, <<"\\\\n">>, + [global, {return, binary}]). + +split_lines(IoList) -> + Normalized = normalize_newlines(IoList), + Lines = binary:split(Normalized, <<"\\n">>, [global]), + join_lines(Lines, []). + +join_lines([H | T], Acc) -> + Sz = byte_size(H)-1, + case H of + <<_:Sz/binary, "\\">> -> + case T of + [H2 | T2] -> + join_lines(T2, [<> | Acc]); + [] -> + join_lines(T, [H | Acc]) + end; + _ -> + join_lines(T, [H | Acc]) + end; +join_lines([], Acc) -> + lists:reverse(Acc). + unquote(Bin) -> Quote = <<"\"">>, Size = byte_size(Bin)-2, @@ -961,6 +975,26 @@ unquote(Bin) -> {plain, Plain} end. +dequote(" expect " ++ _ = L) -> + re:replace(L, <<"\\\\\\\\R">>, <<"\n ">>, [global, {return, list}]); +dequote([$\"|T]) -> + [$\"|dequote1(T)]; +dequote([H|T]) -> + [H|dequote(T)]; +dequote([]) -> + []. + +dequote1([$\\,$\\|T]) -> + [$\\|dequote1(T)]; +dequote1([$\\,$r,$\\,$n|T]) -> + "\n " ++ dequote1(T); +dequote1([$\\,$n|T]) -> + "\n " ++ dequote1(T); +dequote1([H|T]) -> + [H|dequote1(T)]; +dequote1([]) -> + []. + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Config log @@ -1127,7 +1161,7 @@ safe_write(Progress, LogFun, Fd0, Bin) when is_binary(Bin) -> ok; verbose when Verbose -> try - io:format("~s", [lux_utils:dequote(?b2l(Bin))]) + io:format("~s", [dequote(?b2l(Bin))]) catch _:VReason -> exit({safe_write, verbose, Bin, VReason}) diff --git a/src/lux_suite.erl b/src/lux_suite.erl index 8db267c8..c816ded0 100644 --- a/src/lux_suite.erl +++ b/src/lux_suite.erl @@ -261,7 +261,14 @@ annotate_event_log(R, Script, NewSummary, CaseLogDir, Opts) -> Base ++ ".event.log"]), SuiteLogDir = R#rstate.log_dir, NoHtmlOpts = lists:keydelete(html, 1, Opts), - ok = annotate_log(false, EventLog, SuiteLogDir, NoHtmlOpts); + case annotate_log(false, EventLog, SuiteLogDir, NoHtmlOpts) of + ok -> + ok; + {error, File, ReasonStr} -> + io:format("\nINTERNAL LUX ERROR\n\t~s:\n\t~s\n", + [File, ReasonStr]), + ok + end; true -> ok end. @@ -376,6 +383,8 @@ flatten_results(Groups) -> {ok, Res, Script, "0", []}; skip -> {ok, Res, Script, "0", []}; + warning -> + {ok, Res, Script, "0", []}; {error_line, RawLineNo, Reason} -> {error, Script, RawLineNo, Reason}; {error, [Reason]} -> @@ -726,8 +735,8 @@ run_cases(OrigR, [{SuiteFile,{ok,Script}, P, LenP} | Scripts], end, AllWarnings = OrigR#rstate.warnings ++ RunWarnings, NewR2 = NewR#rstate{warnings = AllWarnings}, - ok = annotate_event_log(NewR2, Script, NewSummary, - CaseLogDir, Opts), + annotate_event_log(NewR2, Script, NewSummary, + CaseLogDir, Opts), _ = write_results(NewR2, NewSummary, NewResults), run_cases(NewR2, NewScripts, NewSummary, NewResults, Max, CC+1, List, NewOpaque) diff --git a/src/lux_utils.erl b/src/lux_utils.erl index 7e68e55c..d12020df 100644 --- a/src/lux_utils.erl +++ b/src/lux_utils.erl @@ -12,12 +12,12 @@ summary/2, summary_prio/1, multiply/2, drop_prefix/1, drop_prefix/2, normalize/1, strip_leading_whitespaces/1, strip_trailing_whitespaces/1, - normalize_newlines/1, expand_lines/1, + normalize_newlines/1, expand_lines/1, split_lines/1, shrink_lines/1, to_string/1, capitalize/1, tag_prefix/2, progress_write/2, fold_files/5, foldl_cmds/5, foldl_cmds/6, - pretty_full_lineno/1, pretty_filename/1, filename_split/1, dequote/1, + pretty_full_lineno/1, pretty_filename/1, filename_split/1, now_to_string/1, datetime_to_string/1, verbatim_match/2, - diff/2, + diff/2, equal/2, diff_iter/3, diff_iter/4, shrink_diff/2, cmd/1, cmd_expected/1, perms/1, pick_opt/3]). @@ -293,26 +293,6 @@ capitalize([H | T]) -> capitalize([] = L) -> L. -dequote(" expect " ++ _ = L) -> - re:replace(L, <<"\\\\\\\\R">>, <<"\n ">>, [global, {return, list}]); -dequote([$\"|T]) -> - [$\"|dequote1(T)]; -dequote([H|T]) -> - [H|dequote(T)]; -dequote([]) -> - []. - -dequote1([$\\,$\\|T]) -> - [$\\|dequote1(T)]; -dequote1([$\\,$r,$\\,$n|T]) -> - "\n " ++ dequote1(T); -dequote1([$\\,$n|T]) -> - "\n " ++ dequote1(T); -dequote1([H|T]) -> - [H|dequote1(T)]; -dequote1([]) -> - []. - progress_write(Progress, String) -> case Progress of silent -> ok; @@ -542,176 +522,41 @@ verbatim_collect2(_Actual, _Expected, _Orig, _Base, _Pos, _Len) -> %% No match nomatch. -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Diff - -%% diff_files(OldFile, NewFile) -> -%% {OldFile, {ok, OldBin}} = {OldFile, file:read_file(OldFile)}, -%% {NewFile, {ok, NewBin}} = {NewFile, file:read_file(NewFile)}, -%% diff(split_lines(OldBin), split_lines(NewBin)). -%% -%% split_lines(<<"">>) -> -%% []; -%% split_lines(Bin) when is_binary(Bin) -> -%% Opts = [global], -%% Bin2 = normalize_regexp(Bin), -%% binary:split(Bin2, <<"\\\\R">>, Opts). - -diff(OldBins, NewBins) -> - Old = numerate_lines(OldBins), - New = numerate_lines(NewBins), - Patch = diff(New, Old, [], 0), - merge(Patch, Old). - -numerate_lines(List) -> - numerate_lines(List, 1, []). - -numerate_lines([H|T], N, Acc) -> - numerate_lines(T, N+1, [{N,H}|Acc]); -numerate_lines([], _N, Acc) -> - lists:reverse(Acc). - --type patch() :: {From :: non_neg_integer(), - To :: non_neg_integer()} | - binary(). - --type diff() :: {'=',[binary()]} | - {'+',[binary()]} | - {'-',[binary()]} | - {'!',[binary()],[binary()]}. - --spec diff(New :: [{non_neg_integer(),binary()}], - Old :: [{non_neg_integer(),binary()}], - [patch()], - Min :: non_neg_integer()) -> - [patch()]. - -diff([], _, Patch,_Min) -> - lists:reverse(Patch); -diff([{_,Line}|T]=New, Old, Patch, Min) -> - case match(New, Old, Min) of - {yes, From, To, Rest} -> - diff(Rest, Old, [{From,To}|Patch], To); - no -> - diff(T, Old, [Line|Patch], Min) - end. - -match([{_,NewElem}|NewT]=New, [{From,OldElem}|OldT], Min) when From > Min -> - case equal(OldElem, NewElem) of - match -> extend_match(NewT, OldT, From, From); - nomatch -> match(New, OldT, Min) - end; -match(New, [_|T], Min) -> - match(New, T, Min); -match(_New, [], _Min) -> - no. - -extend_match([{_,NewElem}|NewT]=New, [{To,OldElem}|OldT], From, PrevTo) -> - case equal(OldElem, NewElem) of - match -> extend_match(NewT, OldT, From, To); - nomatch -> {yes, From, PrevTo, New} - end; -extend_match(New, _, From, To) -> - {yes, From, To, New}. - -equal(Expected, Expected) -> - match; -equal(<<"">>, _Actual) -> - nomatch; -equal("", _Actual) -> - nomatch; -equal(Expected0, Actual) when is_binary(Expected0); is_list(Expected0) -> - Expected = normalize_regexp(Expected0), - try - re:run(Actual, Expected,[{capture, none}, notempty]) - catch _:_ -> - nomatch - end; -equal(_Expected, _Actual) -> - nomatch. - -normalize_regexp(<> = RegExp) - when Prefix =/= <<"^">> -> - normalize_regexp(<<"^", RegExp/binary>>); -normalize_regexp(RegExp) when is_binary(RegExp) -> - Size = byte_size(RegExp)-1, - case RegExp of - <<_:Size/binary, "$">> -> - RegExp; - _ -> - normalize_regexp(<>) - end; -normalize_regexp([Prefix|RegExp]) - when Prefix =/= $^ -> - normalize_regexp([$^|RegExp]); -normalize_regexp(RegExp) when is_list(RegExp) -> - case lists:last(RegExp) of - $\$ -> - RegExp; - _ -> - normalize_regexp(RegExp++"$") - end. - normalize_newlines(IoList) -> - re:replace(IoList, <<"(\r\n|\r|\n)">>, <<"\\\\R">>, - [global, {return, binary}]). + normalize_newlines(IoList, <<"\\\\R">>). + +normalize_newlines(IoList, To) -> + re:replace(IoList, <<"(\r\n|\r|\n)">>, To, [global, {return, binary}]). -expand_lines([]) -> - []; -expand_lines([Line]) -> +expand_lines([] = Line) -> + Line; +expand_lines([_] = Line) -> Line; expand_lines([Line | Lines]) -> [Line, "\n", expand_lines(Lines)]. --spec merge([patch()], Old :: [{non_neg_integer(),binary()}]) -> - [diff()]. - -merge(Patch, Old) -> - merge(Patch, Old, 1, []). - -merge([{From,To}|Patch], Old, Next, Acc) -> - {Next2, Common} = get_lines(From, To, Old, []), - Acc2 = - if - From > Next -> - %% Add missing lines - {_, Delete} = get_lines(Next, From-1, Old, []), - add({'=',Common}, add({'-',Delete}, Acc)); - true -> - add({'=',Common},Acc) - end, - merge(Patch, Old, Next2, Acc2); -merge([Insert|Patch], Old, Next, Acc) -> - merge(Patch, Old, Next, add({'+',[Insert]},Acc)); -merge([], Old, Next, Acc) -> - %% Add missing lines in the end - Fun = fun({N,L}, A) when N >= Next -> [L|A]; - (_,A) -> A - end, - Acc2 = - case lists:foldl(Fun, [], Old) of - [] -> Acc; - Delete -> add({'-',lists:reverse(Delete)}, Acc) - end, - lists:reverse(Acc2). - -get_lines(_, To, [{To,Line}|_Rest], Acc) -> - {To+1, lists:reverse([Line|Acc])}; -get_lines(From, To, [{From,Line}|Rest], Acc) -> - get_lines(From+1, To, Rest, [Line|Acc]); -get_lines(From, To, [_|Rest], Acc) -> - get_lines(From, To, Rest, Acc). - -add({'+',Curr}, [{'+',Prev}|Merge]) -> - [{'+',Prev++Curr}|Merge]; -add({'-',Curr}, [{'-',Prev}|Merge]) -> - [{'-',Prev++Curr}|Merge]; -add({'-',Delete}, [{'+',Insert}|Merge]) -> - [{'!',Insert,Delete}|Merge]; -add({'+',Insert}, [{'-',Delete}|Merge]) -> - [{'!',Delete,Insert}|Merge]; -add(Curr, Merge) -> - [Curr|Merge]. +split_lines(IoList) -> + split_lines(IoList, <<"\\\\R">>). + +split_lines(IoList, To) -> + Normalized = normalize_newlines(IoList, To), + binary:split(Normalized, To, [global]). + +shrink_lines(Lines) -> + case Lines of + [H1, H2, H3 | HT] -> + case lists:reverse(HT) of + [T1, T2, T3, _T4, _T5, _T6 | TT] -> + Len = ?l2b(?i2l(length(TT)+3)), + [H1, H2, H3, + <<"... ", Len/binary," lines not shown...">>, + T3, T2, T1]; + _ -> + Lines + end; + _ -> + Lines + end. cmd(Cmd) -> Output = os:cmd(Cmd++"; echo $?"), @@ -749,3 +594,134 @@ pick_opt(Tag, [{_Tag, _Val} | Opts], Val) -> pick_opt(Tag, Opts, Val); pick_opt(_Tag, [], Val) -> Val. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Diff + +diff(Old, New) -> + Equal = + fun(O, N) -> + case lux_utils:equal(O, N) of + match -> true; + nomatch -> false + end + end, + lux_diff:compare2(Old, New, Equal). + +equal(Expected, Expected) -> + match; +equal(Expected0, Actual) when is_binary(Expected0); is_list(Expected0) -> + Expected = normalize_regexp(Expected0), + try + re:run(Actual, Expected,[{capture, none}, notempty]) + catch _:_ -> + nomatch + end. + +normalize_regexp(<> = RegExp) + when Prefix =/= <<"^">> -> + normalize_regexp(<<"^", RegExp/binary>>); +normalize_regexp(RegExp) when is_binary(RegExp) -> + Size = byte_size(RegExp)-1, + case RegExp of + <<_:Size/binary, "$">> -> + RegExp; + _ -> + normalize_regexp(<>) + end; +normalize_regexp([Prefix|RegExp]) + when Prefix =/= $^ -> + normalize_regexp([$^|RegExp]); +normalize_regexp(RegExp) when is_list(RegExp) -> + case lists:last(RegExp) of + $\$ -> + RegExp; + _ -> + normalize_regexp(RegExp++"$") + end. + +-type elem() :: binary() | char(). +-type op() :: {common,[elem()]} | + {del, [elem()]} | + {add,[elem()]} | + {replace, Del :: [elem()], Add :: [elem()]} | + {nested, Del :: [elem()], Add :: [elem()], NestedAcc :: acc()}. +-type context() :: first | middle | last. % Single diff implies 'last' +-type mode() :: flat | deep | nested. +-type acc() :: term(). +-type callback() :: fun((op(), mode(), context(), acc()) -> acc()). +-spec diff_iter([binary()], [binary()], mode(), callback()) -> acc(). +-type diff() :: lux_diff:compact_diff(). +diff_iter(Old, New, Mode, Fun) when Mode =:= flat; Mode =:= deep -> + Diff = diff(Old, New), + diff_iter(Diff, Mode, Fun). + +-spec diff_iter(diff(), mode(), callback()) -> acc(). +diff_iter(Diff, Mode, Fun) -> + InitialAcc = [], + diff_iter_loop(Diff, Mode, Fun, InitialAcc). + +diff_iter_loop([H|T], Mode, Fun, Acc) -> + Context = context(Acc, T), + case H of + Common when is_list(Common) -> + NewAcc = Fun({common,Common}, Mode, Context, Acc), + diff_iter_loop(T, Mode, Fun, NewAcc); + {'-', Del} when element(1, hd(T)) =:= '+' -> + Add = element(2, hd(T)), + diff_iter_loop([{'!',Del,Add} | tl(T)], Mode, Fun, Acc); + {'-', Del} -> + NewAcc = Fun({del,Del}, Mode, Context, Acc), + diff_iter_loop(T, Mode, Fun, NewAcc); + {'+', Add} when element(1, hd(T)) =:= '-' -> + Del = element(2, hd(T)), + diff_iter_loop([{'!',Del,Add} | tl(T)], Mode, Fun, Acc); + {'+', Add} -> + NewAcc = Fun({add,Add}, Mode, Context, Acc), + diff_iter_loop(T, Mode, Fun, NewAcc); + {'!', Del, Add} when Mode =:= deep -> + DelChars = ?b2l(?l2b(expand_lines(Del))), + AddChars = ?b2l(?l2b(expand_lines(Add))), + NestedDiff = lux_diff:compare(DelChars, AddChars), + NestedAcc = diff_iter(NestedDiff, nested, Fun), + DeepAcc = Fun({nested,Del,Add,NestedAcc}, Mode, Context, Acc), + diff_iter_loop(T, Mode, Fun, DeepAcc); + {'!', Del, Add} when Mode =:= flat; + Mode =:= nested -> + NewAcc = Fun({replace,Del,Add}, Mode, Context, Acc), + diff_iter_loop(T, Mode, Fun, NewAcc) + end; +diff_iter_loop([], _Mode, _Fun, Acc) -> + Acc. + +context(_Acc, []) -> + last; +context([], _Tail) -> + first; +context(_Aacc, _Tail) -> + middle. + +shrink_diff(Old, New) when is_binary(Old), is_binary(New) -> + ToIoList = + fun ({Sign, Bin}) -> + Prefix = + case Sign of + '+' -> "+ "; + '-' -> "- "; + '=' -> " " + end, + [Prefix, Bin, "\n"] + end, + Diff = diff(split_lines(Old, <<"\n">>), + split_lines(New, <<"\n">>)), + ShrinkedDiff = shrink(Diff, []), + Expanded = lux_diff:split_diff(ShrinkedDiff), + iolist_to_binary(lists:map(ToIoList, Expanded)). + +shrink([Common | T], Acc) when is_list(Common) -> + Shrinked = shrink_lines(Common), + shrink(T, [Shrinked | Acc]); +shrink([Other | T], Acc) -> + shrink(T, [Other | Acc]); +shrink([], Acc) -> + lists:reverse(Acc). diff --git a/test/history.delux b/test/history.delux index 968c858a..433d26e4 100644 --- a/test/history.delux +++ b/test/history.delux @@ -2,6 +2,8 @@ [include include/macros.luxinc] +[config timeout=20000] + [shell plain] [progress plain] -LUX ERROR|0 test runs diff --git a/test/loop_nested_breaks.delux b/test/loop_nested_breaks.delux index 40964414..c72f466f 100644 --- a/test/loop_nested_breaks.delux +++ b/test/loop_nested_breaks.delux @@ -1,3 +1,5 @@ +[doc Demonstrate fail in nested loop] + [config case_timeout=100000] [global inner_file=loop_nested_breaks_inner.tmp] diff --git a/test/plain.delux b/test/plain.delux index c8a26ed3..f96f9181 100644 --- a/test/plain.delux +++ b/test/plain.delux @@ -5,19 +5,20 @@ [shell test] -LUX ERROR [invoke eval $OK "mkdir -p $LUX_EXTRA_LOGS/$LUX_SHELLNAME"] - !../bin/lux --progress=summary --timeout=5000 --log_dir=$LUX_EXTRA_LOGS/$LUX_SHELLNAME --html=validate plain + !../bin/lux --progress=summary --timeout=5000 \ + --log_dir=$LUX_EXTRA_LOGS/$LUX_SHELLNAME --html=validate plain """? test case : .*plain/nested_fail_html.lux -result : FAIL at 6:4:12 +result : FAIL at 8:4:12 expected - * -actual fail pattern matched "xxx" +.*SH-PROMPT: +actual fail pattern matched "echo xxx" """ """? successful : 0 failed : 1 - .*plain/nested_fail_html.lux:6:4:12 - fail pattern matched "xxx" +.*plain/nested_fail_html.lux:8:4:12 - fail pattern matched "echo xxx" summary : FAIL """ diff --git a/test/plain/nested_fail_html.lux b/test/plain/nested_fail_html.lux index ad83351b..26e49fa5 100644 --- a/test/plain/nested_fail_html.lux +++ b/test/plain/nested_fail_html.lux @@ -1,3 +1,5 @@ +[doc Demonstrate a nested fail] + # before include [include nested_fail_html.luxinc] # after include diff --git a/test/plain/nested_fail_html.luxinc b/test/plain/nested_fail_html.luxinc index 75c247e5..3892c202 100644 --- a/test/plain/nested_fail_html.luxinc +++ b/test/plain/nested_fail_html.luxinc @@ -7,8 +7,8 @@ # after macro foo [macro bar] # before fail - -xxx + -echo xxx !echo xxx - [sleep 1] + ?SH-PROMPT: # after fail [endmacro]