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),
"", Tag, ">\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),
- "", Tag, ">\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", ""),
+ "\nDiff:
",
+ ""
]
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, ">"].
+ [<<"<">>, 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]