Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## Unreleased

- Added a simple line-by-line diff to `t.assert_equals()` and `t.assert_covers()`
failure messages (gh-412).
- Fixed a bug when server initialization didn't reset artifact handling,
causing stale artifacts to persist after server restarts (gh-409).
- Fixed a bug when the JUnit reporter generated invalid XML for parameterized
Expand Down
21 changes: 20 additions & 1 deletion luatest/assertions.lua
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
local math = require('math')

local comparator = require('luatest.comparator')
local diff = require('luatest.diff')
local mismatch_formatter = require('luatest.mismatch_formatter')
local pp = require('luatest.pp')
local log = require('luatest.log')
Expand Down Expand Up @@ -83,6 +84,12 @@ local function error_msg_equality(actual, expected, deep_analysis)
if success then
result = table.concat({result, mismatchResult}, '\n')
end

local diff_result = diff.build_line_diff(expected, actual)
if diff_result then
result = table.concat({result, 'diff:', diff_result}, '\n')
end

return result
end
return string.format("expected: %s, actual: %s",
Expand Down Expand Up @@ -470,7 +477,19 @@ end
function M.assert_covers(actual, expected, message)
if not table_covers(actual, expected) then
local str_actual, str_expected = prettystr_pairs(actual, expected)
failure(string.format('expected %s to cover %s', str_actual, str_expected), message, 2)
local sliced_actual = table_slice(actual, expected)

local parts = {
string.format('expected %s to cover %s', str_actual, str_expected),
}

local diff_result = diff.build_line_diff(expected, sliced_actual)
if diff_result then
table.insert(parts, 'diff:')
table.insert(parts, diff_result)
end

failure(table.concat(parts, '\n'), message, 2)
end
end

Expand Down
104 changes: 104 additions & 0 deletions luatest/diff.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
local pp = require("luatest.pp")

local M = {}

local function diff_by_lines(text1, text2)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I presume you used this wiki page as a reference? It'd be nice to put a link to it somewhere in a comment.

local lines1 = string.split(text1, '\n')
local lines2 = string.split(text2, '\n')

local m = #lines1
local n = #lines2
local lcs = {}

for i = 0, m do
lcs[i] = {}
lcs[i][0] = 0
end

for j = 0, n do
lcs[0][j] = 0
end

for i = 1, m do
for j = 1, n do
if lines1[i] == lines2[j] then
lcs[i][j] = lcs[i - 1][j - 1] + 1
else
local left = lcs[i - 1][j]
local top = lcs[i][j - 1]
lcs[i][j] = left >= top and left or top
end
end
end

local diffs = {}
local i = m
local j = n

while i > 0 or j > 0 do
if i > 0 and j > 0 and lines1[i] == lines2[j] then
table.insert(diffs, 1, {'equal', lines1[i]})
i = i - 1
j = j - 1
elseif j > 0 and (i == 0 or lcs[i][j - 1] >= lcs[i - 1][j]) then
table.insert(diffs, 1, {'insert', lines2[j]})
j = j - 1
else
table.insert(diffs, 1, {'delete', lines1[i]})
i = i - 1
end
end

return diffs
end

local function prettify_patch(diffs)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't look like there's much point in splitting this function in two. I'd simply merge prettify_patch into diff_by_lines.

local out = {}

for _, diff in ipairs(diffs) do
local tag, line = diff[1], diff[2]
if tag == 'equal' then
table.insert(out, ' ' .. line)
elseif tag == 'delete' then
table.insert(out, '-' .. line)
elseif tag == 'insert' then
table.insert(out, '+' .. line)
end
end

if #out == 0 then
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How can it be? Why do we need to care, anyways?

return nil
end

return table.concat(out, '\n')
end

--- Build a simple line-by-line diff for expected and actual values
-- serialized to text. Returns nil when values can't be serialized
-- or there is no diff.
function M.build_line_diff(expected, actual)
local old = pp.LINE_LENGTH
pp.LINE_LENGTH = 0
local expected_text = pp.tostring(expected)
local actual_text = pp.tostring(actual)
pp.LINE_LENGTH = old

if expected_text == nil or actual_text == nil then
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAICS pp.tostring never returns nil.

return nil
end

if expected_text == actual_text then
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need this check. actual and expected aren't supposed to be the same, but even if they are, we can print the diff without +-.

return nil
end

local diffs = diff_by_lines(expected_text, actual_text)
local patch_text = prettify_patch(diffs)

if patch_text == '' or patch_text == nil then
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need this special case, either.

return nil
end

return patch_text
end

return M
Loading
Loading