diff --git a/filesystem/ext4/directory_test.go b/filesystem/ext4/directory_test.go index 80f52482..085d2052 100644 --- a/filesystem/ext4/directory_test.go +++ b/filesystem/ext4/directory_test.go @@ -2,6 +2,8 @@ package ext4 import ( "testing" + + "github.com/diskfs/go-diskfs/testhelper" ) func TestDirectoryToBytes(t *testing.T) { @@ -17,7 +19,7 @@ func TestDirectoryToBytes(t *testing.T) { b := dir.toBytes(bytesPerBlock, directoryChecksumAppender(sb.checksumSeed, 2, 0)) // read the bytes from the disk - diff, diffString := dumpByteSlicesWithDiffs(b, expected, 32, false, true, true) + diff, diffString := testhelper.DumpByteSlicesWithDiffs(b, expected, 32, false, true, true) if diff { t.Errorf("directory.toBytes() mismatched, actual then expected\n%s", diffString) } diff --git a/filesystem/ext4/groupdescriptors_test.go b/filesystem/ext4/groupdescriptors_test.go index 27f87d23..27c09fb7 100644 --- a/filesystem/ext4/groupdescriptors_test.go +++ b/filesystem/ext4/groupdescriptors_test.go @@ -5,6 +5,7 @@ import ( "os" "testing" + "github.com/diskfs/go-diskfs/testhelper" "github.com/go-test/deep" ) @@ -60,7 +61,7 @@ func TestGroupDescriptorToBytes(t *testing.T) { } b := gd.toBytes(sb.gdtChecksumType(), sb.checksumSeed) expected = expected[:64] - diff, diffString := dumpByteSlicesWithDiffs(b, expected, 32, false, true, true) + diff, diffString := testhelper.DumpByteSlicesWithDiffs(b, expected, 32, false, true, true) if diff { t.Errorf("groupdescriptor.toBytes() mismatched, actual then expected\n%s", diffString) } @@ -94,7 +95,7 @@ func TestGroupDescriptorsToBytes(t *testing.T) { descriptors: groupdescriptors, } b := gds.toBytes(sb.gdtChecksumType(), sb.checksumSeed) - diff, diffString := dumpByteSlicesWithDiffs(b, expected, 32, false, true, true) + diff, diffString := testhelper.DumpByteSlicesWithDiffs(b, expected, 32, false, true, true) if diff { t.Errorf("groupDescriptors.toBytes() mismatched, actual then expected\n%s", diffString) } diff --git a/filesystem/ext4/superblock_test.go b/filesystem/ext4/superblock_test.go index 0265988d..f63c7f81 100644 --- a/filesystem/ext4/superblock_test.go +++ b/filesystem/ext4/superblock_test.go @@ -4,6 +4,7 @@ import ( "reflect" "testing" + "github.com/diskfs/go-diskfs/testhelper" "github.com/go-test/deep" ) @@ -32,7 +33,7 @@ func TestSuperblockToBytes(t *testing.T) { if err != nil { t.Fatalf("Failed to serialize superblock: %v", err) } - diff, diffString := dumpByteSlicesWithDiffs(b, expected, 32, false, true, true) + diff, diffString := testhelper.DumpByteSlicesWithDiffs(b, expected, 32, false, true, true) if diff { t.Errorf("superblock.toBytes() mismatched, actual then expected\n%s", diffString) } diff --git a/filesystem/ext4/util_test.go b/filesystem/ext4/util_test.go index dfa8c780..d7a66c36 100644 --- a/filesystem/ext4/util_test.go +++ b/filesystem/ext4/util_test.go @@ -2,7 +2,6 @@ package ext4 import ( "bytes" - "fmt" "testing" ) @@ -64,137 +63,3 @@ func TestMinString(t *testing.T) { }) } } - -// dumpByteSlice dump a byte slice in hex and optionally ASCII format. -// Optionally but position at the beginning of each row, like xxd. -// Optionally convert to ASCII at end of each row, like xxd. -// Can show positions at beginning of each row in hex, decimal or both. -// Can filter out all rows except those containing given positions in showOnlyBytes. If showOnlyBytes is nil, all rows are shown. -// If showOnlyBytes is not nil, even an empty slice, will only show those rows that contain the given positions. -func dumpByteSlice(b []byte, bytesPerRow int, showASCII, showPosHex, showPosDec bool, showOnlyBytes []int) (out string) { - var ascii []byte - // go through each byte. - // At each position: - // - if we are at the end of a row, print the ASCII representation of the row. - // - if we are at the middle of a row, add an extra space - // - if we are still in the byte slice, print the byte in hex with a space before it. - // - if we are past the end of the row, print spaces. - showOnlyMap := make(map[int]bool) - for _, v := range showOnlyBytes { - showOnlyMap[v] = true - } - // run by rows - numRows := len(b) / bytesPerRow - if len(b)%bytesPerRow != 0 { - numRows++ - } - for i := 0; i < numRows; i++ { - firstByte := i * bytesPerRow - lastByte := firstByte + bytesPerRow - var row string - // row header includes optional position numbers - if showPosHex { - row += fmt.Sprintf("%08x ", firstByte) - } - if showPosDec { - row += fmt.Sprintf("%4d ", firstByte) - } - row += ": " - for j := firstByte; j < lastByte; j++ { - // every 8 bytes add extra spacing to make it easier to read - if j%8 == 0 { - row += " " - } - // regular byte, print in hex - if j < len(b) { - hex := fmt.Sprintf(" %02x", b[j]) - if showOnlyBytes != nil && showOnlyMap[j] { - hex = "\033[1m\033[31m" + hex + "\033[0m" - } - row += hex - } else { - row += " " - } - switch { - case j >= len(b): - // past end of byte slice, print spaces - ascii = append(ascii, ' ') - case b[j] < 32 || b[j] > 126: - // unprintable characters, print a dot - ascii = append(ascii, '.') - default: - // printable characters, print the character - ascii = append(ascii, b[j]) - } - } - // end of row, print the ASCII representation and a newline - if showASCII { - row += fmt.Sprintf(" %s", string(ascii)) - ascii = ascii[:0] - } - row += "\n" - - // calculate if we should include this row - var includeRow = true - if showOnlyBytes != nil { - includeRow = false - for j := firstByte; j < lastByte; j++ { - if showOnlyMap[j] { - includeRow = true - break - } - } - } - if includeRow { - out += row - } - } - return out -} - -// diff -type diff struct { - Offset int - ByteA byte - ByteB byte -} - -// compareByteSlices compares two byte slices position by position. If the byte slices are identical, diffs is length 0, -// otherwise it contains the positions of the differences. -func compareByteSlices(a, b []byte) (diffs []diff) { - maxSize := len(a) - if len(b) > maxSize { - maxSize = len(b) - } - for i := 0; i < maxSize; i++ { - switch { - case i >= len(a): - diffs = append(diffs, diff{Offset: i, ByteA: 0, ByteB: b[i]}) - case i >= len(b): - diffs = append(diffs, diff{Offset: i, ByteA: a[i], ByteB: 0}) - case a[i] != b[i]: - diffs = append(diffs, diff{Offset: i, ByteA: a[i], ByteB: b[i]}) - } - } - return diffs -} - -// dumpByteSlicesWithDiffs show two byte slices in hex and ASCII format, with differences highlighted. -// -//nolint:unparam // sure, bytesPerRow always is 32, but it could be something else -func dumpByteSlicesWithDiffs(a, b []byte, bytesPerRow int, showASCII, showPosHex, showPosDec bool) (different bool, out string) { - diffs := compareByteSlices(a, b) - // if there are no differences, just return an empty string - if len(diffs) == 0 { - return false, "" - } - - showOnlyBytes := make([]int, len(diffs)) - for i, d := range diffs { - showOnlyBytes[i] = d.Offset - } - out = dumpByteSlice(a, bytesPerRow, showASCII, showPosHex, showPosDec, showOnlyBytes) - out += "\n" - out += dumpByteSlice(b, bytesPerRow, showASCII, showPosHex, showPosDec, showOnlyBytes) - return true, out -} diff --git a/testhelper/diff.go b/testhelper/diff.go new file mode 100644 index 00000000..4a9bbb5a --- /dev/null +++ b/testhelper/diff.go @@ -0,0 +1,135 @@ +package testhelper + +import "fmt" + +// dumpByteSlice dump a byte slice in hex and optionally ASCII format. +// Optionally but position at the beginning of each row, like xxd. +// Optionally convert to ASCII at end of each row, like xxd. +// Can show positions at beginning of each row in hex, decimal or both. +// Can filter out all rows except those containing given positions in showOnlyBytes. If showOnlyBytes is nil, all rows are shown. +// If showOnlyBytes is not nil, even an empty slice, will only show those rows that contain the given positions. +func dumpByteSlice(b []byte, bytesPerRow int, showASCII, showPosHex, showPosDec bool, showOnlyBytes []int) (out string) { + var ascii []byte + // go through each byte. + // At each position: + // - if we are at the end of a row, print the ASCII representation of the row. + // - if we are at the middle of a row, add an extra space + // - if we are still in the byte slice, print the byte in hex with a space before it. + // - if we are past the end of the row, print spaces. + showOnlyMap := make(map[int]bool) + for _, v := range showOnlyBytes { + showOnlyMap[v] = true + } + // run by rows + numRows := len(b) / bytesPerRow + if len(b)%bytesPerRow != 0 { + numRows++ + } + for i := 0; i < numRows; i++ { + firstByte := i * bytesPerRow + lastByte := firstByte + bytesPerRow + var row string + // row header includes optional position numbers + if showPosHex { + row += fmt.Sprintf("%08x ", firstByte) + } + if showPosDec { + row += fmt.Sprintf("%4d ", firstByte) + } + row += ": " + for j := firstByte; j < lastByte; j++ { + // every 8 bytes add extra spacing to make it easier to read + if j%8 == 0 { + row += " " + } + // regular byte, print in hex + if j < len(b) { + hex := fmt.Sprintf(" %02x", b[j]) + if showOnlyBytes != nil && showOnlyMap[j] { + hex = "\033[1m\033[31m" + hex + "\033[0m" + } + row += hex + } else { + row += " " + } + switch { + case j >= len(b): + // past end of byte slice, print spaces + ascii = append(ascii, ' ') + case b[j] < 32 || b[j] > 126: + // unprintable characters, print a dot + ascii = append(ascii, '.') + default: + // printable characters, print the character + ascii = append(ascii, b[j]) + } + } + // end of row, print the ASCII representation and a newline + if showASCII { + row += fmt.Sprintf(" %s", string(ascii)) + ascii = ascii[:0] + } + row += "\n" + + // calculate if we should include this row + var includeRow = true + if showOnlyBytes != nil { + includeRow = false + for j := firstByte; j < lastByte; j++ { + if showOnlyMap[j] { + includeRow = true + break + } + } + } + if includeRow { + out += row + } + } + return out +} + +// diff +type diff struct { + Offset int + ByteA byte + ByteB byte +} + +// compareByteSlices compares two byte slices position by position. If the byte slices are identical, diffs is length 0, +// otherwise it contains the positions of the differences. +func compareByteSlices(a, b []byte) (diffs []diff) { + maxSize := len(a) + if len(b) > maxSize { + maxSize = len(b) + } + for i := 0; i < maxSize; i++ { + switch { + case i >= len(a): + diffs = append(diffs, diff{Offset: i, ByteA: 0, ByteB: b[i]}) + case i >= len(b): + diffs = append(diffs, diff{Offset: i, ByteA: a[i], ByteB: 0}) + case a[i] != b[i]: + diffs = append(diffs, diff{Offset: i, ByteA: a[i], ByteB: b[i]}) + } + } + return diffs +} + +// dumpByteSlicesWithDiffs show two byte slices in hex and ASCII format, with differences highlighted. +func DumpByteSlicesWithDiffs(a, b []byte, bytesPerRow int, showASCII, showPosHex, showPosDec bool) (different bool, out string) { + diffs := compareByteSlices(a, b) + // if there are no differences, just return an empty string + if len(diffs) == 0 { + return false, "" + } + + showOnlyBytes := make([]int, len(diffs)) + for i, d := range diffs { + showOnlyBytes[i] = d.Offset + } + out = dumpByteSlice(a, bytesPerRow, showASCII, showPosHex, showPosDec, showOnlyBytes) + out += "\n" + out += dumpByteSlice(b, bytesPerRow, showASCII, showPosHex, showPosDec, showOnlyBytes) + return true, out +}