Remove test-only Library wrappers

WalkSelectedFiles had no caller. DeletePositions, GetPositionByFileID,
MGetFile, MatchFileRows, ValidateFilesQuery, MatchLiveFiles,
TrackingCandidatesPage, ReadFileTracking, ImportArchivedInventory and
MoveFileToPath were only called by tests, which now use the batch and
compiled-query methods production uses.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Samuel CuiandClaude Opus 5.5 committed 2026-10-31 21:42:46 +08:00
1 parent bc0ee85151
commit 4814bd8beb
21 files changed
+53 -220

No files matched your search

+3 -1
View File
@@ -206,7 +206,9 @@ func TestLibrarySearchCatalogContentAndPagination(t *testing.T) {
allIDs = append(allIDs, file.ID)
}
require.Equal(t, test.want, allIDs)
matches, err := api.lib.MatchFileRows(ctx, idsToMatch, entity.FileScope_FILE_SCOPE_ALL, test.query)
compiled, err := api.lib.CompileFilesQuery(test.query)
require.NoError(t, err)
matches, err := api.lib.MatchFileQueryRows(ctx, idsToMatch, entity.FileScope_FILE_SCOPE_ALL, compiled)
require.NoError(t, err)
require.Len(t, matches, len(test.want))
for _, id := range test.want {
+2 -1
View File
@@ -186,8 +186,9 @@ func TestConcurrentFirstRemoveInitializesTrashWithoutSerializingPhysicalMoves(t
for index, file := range files {
original, err := f.exe.Lib().GetFileLocation(ctx, file.ID)
require.NoError(t, err)
tracking, err := f.exe.Lib().ReadFileTracking(ctx, file.ID)
keys, err := f.exe.Lib().ReadFileTrackingKeys(ctx, []int64{file.ID})
require.NoError(t, err)
tracking := keys[file.ID]
if index < 2 {
require.Nil(t, original)
require.Empty(t, tracking)
@@ -437,8 +437,9 @@ func TestScanDetachedMatchingRequiresEarlierObservedAbsence(t *testing.T) {
ctx := context.Background()
old, err := f.exe.Lib().LocationOriginalsPage(ctx, f.locations[0].ID, "", 10)
require.NoError(t, err)
keys, err := f.exe.Lib().ReadFileTracking(ctx, old[0].FileID)
tracking, err := f.exe.Lib().ReadFileTrackingKeys(ctx, []int64{old[0].FileID})
require.NoError(t, err)
keys := tracking[old[0].FileID]
if len(keys) == 0 {
t.Skip("filesystem provides no native tracking identity")
}
@@ -81,9 +81,10 @@ func TestNativeRelocationPreservesOpaqueObservedSignature(t *testing.T) {
require.NoError(t, err)
rows, err := exe.Lib().LocationOriginalsPage(ctx, source.ID, "", 10)
require.NoError(t, err)
keys, err := exe.Lib().TrackingCandidatesPage(ctx, source, 0, 10)
require.NoError(t, err)
row := rows[0]
tracking, err := exe.Lib().ReadFileTrackingKeys(ctx, []int64{row.FileID})
require.NoError(t, err)
keys := tracking[row.FileID]
row.Signature = []byte{0, 255, 42}
observation := &library.ObservedEntry{Path: row.Path, FileID: row.FileID, Size: row.Size, Mode: row.Mode,
MtimeNS: row.MtimeNS, Hash: row.Hash, Signature: row.Signature, TrackingKeys: keys}
-21
View File
@@ -313,27 +313,6 @@ func archiveDirectoryPrefix(directory string) string {
return directory + "/"
}
// ImportArchivedInventory is reserved for explicit inventory-import workflows and fixtures.
// Archive and Volume Scan must not invoke this to infer logical identity from signatures.
// Each invocation creates new organization; callers select this one-shot import explicitly.
func (l *Library) ImportArchivedInventory(ctx context.Context) error {
var after int64
for {
var ids []int64
if err := l.readDB().WithContext(ctx).Model(ModelPosition).Where("id > ? AND is_dir = ?", after, false).
Where("signature IS NOT NULL").Order("id").Limit(batchSize).Pluck("id", &ids).Error; err != nil {
return err
}
if len(ids) == 0 {
return nil
}
if _, err := l.ImportArchivePositions(ctx, ids); err != nil {
return err
}
after = ids[len(ids)-1]
}
}
// CreateArchiveFile allocates a raw input's logical identity before any physical transfer.
func (l *Library) CreateArchiveFile(ctx context.Context, targetPath string) (*File, error) {
if err := entity.ValidateRelativePath(targetPath); err != nil {
-9
View File
@@ -199,11 +199,6 @@ func (l *Library) MoveFile(ctx context.Context, file *File) error {
return l.moveTree(ctx, file, file.Name)
}
// MoveFileToPath resolves a shared relative shortcut without pre-creating directories.
func (l *Library) MoveFileToPath(ctx context.Context, file *File, targetPath string) error {
return l.moveTree(ctx, file, targetPath)
}
func validateFileParent(tx *gorm.DB, parentID, movingID int64) error {
// Follow only the bounded destination ancestry; no whole-tree query or filesystem access is needed.
seen := make(map[int64]struct{})
@@ -303,10 +298,6 @@ func (l *Library) newTrash(ctx context.Context, tx *gorm.DB) (*File, error) {
}
}
func (l *Library) MGetFile(ctx context.Context, ids ...int64) (map[int64]*File, error) {
return l.mGetFile(ctx, l.readDB().WithContext(ctx), ids...)
}
func (l *Library) mGetFile(ctx context.Context, tx *gorm.DB, ids ...int64) (map[int64]*File, error) {
// Keep the persistence read independent from the compatibility projection.
files, err := readFileRows(tx, ids...)
+1 -1
View File
@@ -67,7 +67,7 @@ func TestOriginalPublicationRetainsExistingCoverageAsOwnVersion(t *testing.T) {
require.NoError(t, err)
_, err = lib.DeleteLocation(ctx, location.ID, false)
require.NoError(t, err)
require.NoError(t, lib.DeletePositions(ctx, copy.ID))
require.NoError(t, lib.db.Delete(&Position{}, copy.ID).Error)
versions, _, err = lib.ListFileVersions(ctx, file.ID, 0, 10)
require.NoError(t, err)
require.Equal(t, []*FileVersion{saved}, versions)
+3 -1
View File
@@ -127,7 +127,9 @@ func TestFileQueryNumericNegationFactsAndUnknowns(t *testing.T) {
// An observed linked entry has no regular-file size, independently of Catalog membership.
t.Run("Location", func(t *testing.T) {
indexes, err := lib.MatchLiveFiles(ctx, test.query, rows)
compiled, err := lib.CompileFilesQuery(test.query)
require.NoError(t, err)
indexes, err := lib.MatchLiveQuery(ctx, compiled, rows)
require.NoError(t, err)
var names []string
for _, index := range indexes {
-102
View File
@@ -1,102 +0,0 @@
package library
import (
"context"
"fmt"
"io/fs"
"path"
"sort"
"strings"
"github.com/samuelncui/yatm/entity"
)
// WalkSelectedFiles expands logical selections in bounded pages with full Library-relative paths.
func (l *Library) WalkSelectedFiles(ctx context.Context, ids []int64, yield func(*File, string) error) error {
// Keep only explicit root identities; descendants need no whole-tree visited set.
selected := make(map[int64]struct{}, len(ids))
for _, id := range ids {
if id <= 0 {
return fmt.Errorf("invalid selected File ID %d", id)
}
selected[id] = struct{}{}
}
ordered := make([]int64, 0, len(selected))
for id := range selected {
ordered = append(ordered, id)
}
sort.Slice(ordered, func(i, j int) bool { return ordered[i] < ordered[j] })
// Resolve complete root paths and remove roots already covered by another selected ancestor.
for _, id := range ordered {
file, err := l.GetFile(ctx, id)
if err != nil {
return err
}
parts := []string{file.Name}
parentID := file.ParentID
covered := false
for parentID != 0 {
if len(parts) > 256 {
return fmt.Errorf("selected Library path exceeds 256 levels or is cyclic")
}
if _, ok := selected[parentID]; ok {
covered = true
break
}
parent, err := l.GetFile(ctx, parentID)
if err != nil {
return err
}
parts = append(parts, parent.Name)
parentID = parent.ParentID
}
if covered {
continue
}
for left, right := 0, len(parts)-1; left < right; left, right = left+1, right-1 {
parts[left], parts[right] = parts[right], parts[left]
}
if err := l.walkSelected(ctx, file, strings.Join(parts, "/"), 0, yield); err != nil {
return err
}
}
return nil
}
func (l *Library) walkSelected(ctx context.Context, file *File, target string, depth int, yield func(*File, string) error) error {
// Validate every resulting logical target before descending or yielding content.
if err := ctx.Err(); err != nil {
return err
}
if depth > 256 {
return fmt.Errorf("selected Library tree exceeds 256 levels")
}
if err := entity.ValidateRelativePath(target); err != nil {
return err
}
if !fs.FileMode(file.Mode).IsDir() {
if !fs.FileMode(file.Mode).IsRegular() {
return fmt.Errorf("selected File is not regular, file_id=%d", file.ID)
}
return yield(file, target)
}
// Retain one bounded direct-child page at each active depth.
var after string
for {
rows, err := l.ListPage(ctx, file.ID, after, batchSize)
if err != nil {
return err
}
if len(rows) == 0 {
return nil
}
for _, row := range rows {
if err := l.walkSelected(ctx, row, path.Join(target, row.Name), depth+1, yield); err != nil {
return err
}
}
after = rows[len(rows)-1].Name
}
}
-20
View File
@@ -47,12 +47,6 @@ type FileTrackingKey struct {
LocationID int64 `gorm:"index" json:"location_id"`
}
// ReadFileTracking reads only identity evidence for an explicitly selected original.
func (l *Library) ReadFileTracking(ctx context.Context, fileID int64) ([]*FileTrackingKey, error) {
keys, err := l.ReadFileTrackingKeys(ctx, []int64{fileID})
return keys[fileID], err
}
// ReadFileTrackingKeys reads the retained evidence of several Files at once, keyed by File ID, so a
// caller that already holds recorded associations does not query once per row.
func (l *Library) ReadFileTrackingKeys(ctx context.Context, fileIDs []int64) (map[int64][]*FileTrackingKey, error) {
@@ -69,17 +63,3 @@ func (l *Library) ReadFileTrackingKeys(ctx context.Context, fileIDs []int64) (ma
}
return result, nil
}
// TrackingCandidatesPage excludes every original still bound to another Location.
// A retained key without a FileLocation is eligible only after a successful removal.
func (l *Library) TrackingCandidatesPage(ctx context.Context, location *Location, after int64, limit int) ([]*FileTrackingKey, error) {
ids := l.readDB().WithContext(ctx).Model(&FileTrackingKey{}).Select("file_tracking_keys.file_id").
Joins("JOIN locations ON locations.id = file_tracking_keys.location_id").
Joins("LEFT JOIN file_locations ON file_locations.file_id = file_tracking_keys.file_id").
Where("file_tracking_keys.file_id > ? AND locations.executor_id = ?", after, location.ExecutorID).
Where("file_locations.file_id IS NULL OR file_locations.location_id = ?", location.ID).
Group("file_tracking_keys.file_id").Order("file_tracking_keys.file_id").Limit(limit)
var keys []*FileTrackingKey
err := l.readDB().WithContext(ctx).Where("file_id IN (?)", ids).Order("file_id, kind").Find(&keys).Error
return keys, err
}
+11 -7
View File
@@ -69,7 +69,7 @@ func TestListParentsRetainsDeepRootAndRejectsInvalidAncestry(t *testing.T) {
require.ErrorContains(t, err, "exceeds")
}
func TestMoveFileToPathRollsBackNestedDirectories(t *testing.T) {
func TestMoveFileToNestedPathRollsBackNestedDirectories(t *testing.T) {
// A nested rename may allocate directories before discovering the final destination is a descendant.
ctx := context.Background()
lib := newJSONLTestLibrary(t)
@@ -79,7 +79,8 @@ func TestMoveFileToPathRollsBackNestedDirectories(t *testing.T) {
require.NoError(t, err)
// Keep the failed allocation, original root and child inside one rollback boundary.
require.Error(t, lib.MoveFileToPath(ctx, root, "root/new/renamed"))
root.Name = "root/new/renamed"
require.Error(t, lib.MoveFile(ctx, root))
created, err := lib.GetByName(ctx, root.ID, "new")
require.NoError(t, err)
require.Nil(t, created)
@@ -92,14 +93,15 @@ func TestMoveFileToPathRollsBackNestedDirectories(t *testing.T) {
require.Equal(t, root.ID, storedChild.ParentID)
}
func TestMoveFileToPathCreatesValidNestedDestination(t *testing.T) {
func TestMoveFileToNestedPathCreatesValidNestedDestination(t *testing.T) {
// A normal nested move keeps the File identity and user metadata while allocating its target directories.
ctx := context.Background()
lib := newJSONLTestLibrary(t)
file := &File{Name: "before.txt", Note: "keep organization"}
require.NoError(t, lib.SaveFile(ctx, file))
id := file.ID
require.NoError(t, lib.MoveFileToPath(ctx, file, "folder/sub/after.txt"))
file.Name = "folder/sub/after.txt"
require.NoError(t, lib.MoveFile(ctx, file))
require.Equal(t, id, file.ID)
require.Equal(t, "after.txt", file.Name)
@@ -113,13 +115,14 @@ func TestMoveFileToPathCreatesValidNestedDestination(t *testing.T) {
require.Nil(t, old)
}
func TestMoveFileToPathPreservesWhitespaceInsideComponents(t *testing.T) {
func TestMoveFileToNestedPathPreservesWhitespaceInsideComponents(t *testing.T) {
// Whitespace inside a validated full target path belongs to the logical name component.
ctx := context.Background()
lib := newJSONLTestLibrary(t)
file := &File{Name: "before.txt"}
require.NoError(t, lib.SaveFile(ctx, file))
require.NoError(t, lib.MoveFileToPath(ctx, file, "folder /after.txt"))
file.Name = "folder /after.txt"
require.NoError(t, lib.MoveFile(ctx, file))
// Resolving the directory directly avoids any presentation-level path normalization.
parent, err := lib.GetFile(ctx, file.ParentID)
@@ -136,7 +139,8 @@ func TestMoveFileRequiresExistingSource(t *testing.T) {
lib := newJSONLTestLibrary(t)
file := &File{ID: 99999, Name: "missing.txt"}
require.ErrorIs(t, lib.MoveFile(ctx, file), ErrFileNotFound)
require.ErrorIs(t, lib.MoveFileToPath(ctx, file, "new/missing.txt"), ErrFileNotFound)
file.Name = "new/missing.txt"
require.ErrorIs(t, lib.MoveFile(ctx, file), ErrFileNotFound)
// Both entry points leave an empty catalog rather than inventing the requested source identity.
rows, err := lib.List(ctx, 0)
+1 -1
View File
@@ -46,7 +46,7 @@ func TestFileVersionOwnsArchivedFactsWithoutCopyOwnership(t *testing.T) {
require.Len(t, copies[one.ID], 1)
require.Len(t, copies[two.ID], 1)
require.Equal(t, copy.ID, copies[one.ID][0].ID)
require.NoError(t, lib.DeletePositions(ctx, copy.ID))
require.NoError(t, lib.db.Delete(&Position{}, copy.ID).Error)
kept, err := lib.GetFileVersion(ctx, version.ID)
require.NoError(t, err)
require.Equal(t, signature, kept.Signature)
-9
View File
@@ -9,15 +9,6 @@ import (
"github.com/samuelncui/yatm/entity"
)
// MatchFileRows applies Library catalog predicates to one bounded traversal batch.
func (l *Library) MatchFileRows(ctx context.Context, ids []int64, scope entity.FileScope, query string) (map[int64]bool, error) {
compiled, err := l.CompileFilesQuery(query)
if err != nil {
return nil, err
}
return l.MatchFileQueryRows(ctx, ids, scope, compiled)
}
// MatchFileQueryRows shares catalog filtering with Search without recompiling each traversal batch.
func (l *Library) MatchFileQueryRows(ctx context.Context, ids []int64, scope entity.FileScope, query *FilesQuery) (map[int64]bool, error) {
// Empty traversal batches require no catalog read.
+1 -1
View File
@@ -292,7 +292,7 @@ func TestLibraryLegacyImportConvertsDirectoryModeAtTheBoundary(t *testing.T) {
require.NoError(t, lib.Import(ctx, bytes.NewReader(backup), false))
// The current catalog persists explicit types and keeps the original hierarchy.
files, err := lib.MGetFile(ctx, TrashFileID, 9, 10)
files, err := lib.ReadFileRows(ctx, []int64{TrashFileID, 9, 10})
require.NoError(t, err)
require.Equal(t, entity.FileKind_FILE_KIND_DIRECTORY, files[TrashFileID].Kind)
require.Equal(t, entity.FileKind_FILE_KIND_DIRECTORY, files[9].Kind)
+2 -1
View File
@@ -30,7 +30,8 @@ func TestLibraryPathsPreserveLiteralUTF8(t *testing.T) {
// A replacement name is literal too; it never denotes a backslash-separated path.
file := &File{ParentID: directory.ID, Name: "before", Kind: entity.FileKind_FILE_KIND_REGULAR}
require.NoError(t, lib.SaveFile(ctx, file))
require.NoError(t, lib.MoveFileToPath(ctx, file, name))
file.Name = name
require.NoError(t, lib.MoveFile(ctx, file))
require.Equal(t, name, file.Name)
require.Equal(t, directory.ID, file.ParentID)
found, err = lib.GetByPath(ctx, directory.ID, name)
-16
View File
@@ -112,22 +112,6 @@ func (l *Library) CompileFilesQuery(query string) (*FilesQuery, error) {
return result, nil
}
// ValidateFilesQuery shares the predicate compiler with every Files read.
func (l *Library) ValidateFilesQuery(query string) error {
_, err := l.CompileFilesQuery(query)
return err
}
// MatchLiveFiles evaluates the ordinary query compiler over bound observations, not a cached tree.
// It returns matching input indexes without persisting the rows or reading the filesystem.
func (l *Library) MatchLiveFiles(ctx context.Context, query string, rows []LiveQueryRow) ([]int, error) {
compiled, err := l.CompileFilesQuery(query)
if err != nil {
return nil, err
}
return l.MatchLiveQuery(ctx, compiled, rows)
}
// MatchLiveQuery evaluates one compiled predicate over a bounded batch of observations.
func (l *Library) MatchLiveQuery(ctx context.Context, query *FilesQuery, rows []LiveQueryRow) ([]int, error) {
// An empty predicate accepts the observed batch without an extra Catalog read.
+10 -4
View File
@@ -38,13 +38,17 @@ func TestLiveQueriesShareCatalogGrammarWithoutAdmission(t *testing.T) {
{"location:9", []int{}},
} {
t.Run(test.query, func(t *testing.T) {
matched, err := lib.MatchLiveFiles(ctx, test.query, rows)
compiled, err := lib.CompileFilesQuery(test.query)
require.NoError(t, err)
matched, err := lib.MatchLiveQuery(ctx, compiled, rows)
require.NoError(t, err)
require.Equal(t, test.want, matched)
})
}
require.Error(t, lib.ValidateFilesQuery("unsupported:value"))
require.Error(t, lib.ValidateFilesQuery("has:invalid"))
_, err := lib.CompileFilesQuery("unsupported:value")
require.Error(t, err)
_, err = lib.CompileFilesQuery("has:invalid")
require.Error(t, err)
var count int64
require.NoError(t, db.Model(ModelFile).Count(&count).Error)
require.Equal(t, int64(1), count)
@@ -56,7 +60,9 @@ func TestLiveQueryAcrossProjectionBatches(t *testing.T) {
for index := range rows {
rows[index] = LiveQueryRow{Name: fmt.Sprintf("item-%d", index), Size: int64(index), MtimeNS: time.Now().UnixNano()}
}
matched, err := lib.MatchLiveFiles(context.Background(), "size:[128 TO 130]", rows)
compiled, err := lib.CompileFilesQuery("size:[128 TO 130]")
require.NoError(t, err)
matched, err := lib.MatchLiveQuery(context.Background(), compiled, rows)
require.NoError(t, err)
require.Equal(t, []int{128, 129, 130}, matched)
}
+4 -2
View File
@@ -248,8 +248,9 @@ func TestLocationConfigurationKeepsTrackingEvidence(t *testing.T) {
require.NoError(t, err)
require.Len(t, rows, 1)
fileID := rows[0].FileID
before, err := l.ReadFileTracking(ctx, fileID)
tracking, err := l.ReadFileTrackingKeys(ctx, []int64{fileID})
require.NoError(t, err)
before := tracking[fileID]
require.Len(t, before, 1)
// Both a rule change and a root change keep the evidence and the association.
@@ -259,8 +260,9 @@ func TestLocationConfigurationKeepsTrackingEvidence(t *testing.T) {
require.NoError(t, err)
require.Equal(t, s.RootPath, updated.RootPath)
require.Equal(t, "/future", updated.Config.Ignore.Text)
after, err := l.ReadFileTracking(ctx, fileID)
tracking, err = l.ReadFileTrackingKeys(ctx, []int64{fileID})
require.NoError(t, err)
after := tracking[fileID]
require.Len(t, after, 1)
require.Equal(t, before[0].KeyValue, after[0].KeyValue)
remaining, err := l.LocationOriginalsPage(ctx, s.ID, "", 10)
@@ -102,8 +102,9 @@ func TestSingleAndBatchObservationPreserveTrackingDecisions(t *testing.T) {
entry.TrackingKeys = nil
_, err = admit()
require.NoError(t, err)
keys, err := lib.ReadFileTracking(ctx, original.FileID)
tracking, err := lib.ReadFileTrackingKeys(ctx, []int64{original.FileID})
require.NoError(t, err)
keys := tracking[original.FileID]
require.Len(t, keys, 1)
after, err := lib.GetLocation(ctx, location.ID)
require.NoError(t, err)
@@ -114,8 +115,9 @@ func TestSingleAndBatchObservationPreserveTrackingDecisions(t *testing.T) {
cleared, err := admit()
require.NoError(t, err)
require.Equal(t, original.FileID, cleared.FileID)
keys, err = lib.ReadFileTracking(ctx, original.FileID)
tracking, err = lib.ReadFileTrackingKeys(ctx, []int64{original.FileID})
require.NoError(t, err)
keys = tracking[original.FileID]
require.Empty(t, keys)
})
}
-15
View File
@@ -85,14 +85,6 @@ func (l *Library) SavePosition(ctx context.Context, position *Position) error {
})
}
func (l *Library) GetPositionByFileID(ctx context.Context, fileID int64) ([]*Position, error) {
results, err := l.MGetPositionByFileID(ctx, fileID)
if err != nil {
return nil, err
}
return results[fileID], nil
}
// GetPosition returns one physical Position by its Library identity.
func (l *Library) GetPosition(ctx context.Context, id int64) (*Position, error) {
position := new(Position)
@@ -185,13 +177,6 @@ func (l *Library) ListMediaFilePositions(
return positions, nil
}
func (l *Library) DeletePositions(ctx context.Context, ids ...int64) error {
if err := l.db.WithContext(ctx).Where("id IN (?)", ids).Delete(ModelPosition).Error; err != nil {
return fmt.Errorf("delete positions failed, %w", err)
}
return nil
}
// BuildPositionTree adds directly queryable parent paths and derived directory rows to a sorted manifest.
func BuildPositionTree(ctx context.Context, source PositionSource, yield func(*Position) error) error {
if source == nil {
+6 -3
View File
@@ -36,7 +36,10 @@ func TestApplyScanUpdatesPhysicalPositionsAtomically(t *testing.T) {
return nil
})
require.NoError(t, err)
require.NoError(t, lib.ImportArchivedInventory(ctx))
var archived []int64
require.NoError(t, lib.db.Model(ModelPosition).Where("is_dir = ? AND signature IS NOT NULL", false).Order("id").Pluck("id", &archived).Error)
_, err = lib.ImportArchivePositions(ctx, archived)
require.NoError(t, err)
oldFile, err := lib.GetByPath(ctx, Root.ID, "Unforged/Volume/changed.txt")
require.NoError(t, err)
@@ -75,9 +78,9 @@ func TestApplyScanUpdatesPhysicalPositionsAtomically(t *testing.T) {
parents, err := lib.ListParents(ctx, preserved.ID)
require.NoError(t, err)
require.NotEqual(t, int64(TrashFileID), parents[0].ID)
copies, err := lib.GetPositionByFileID(ctx, oldFile.ID)
copies, err := lib.MGetPositionByFileID(ctx, oldFile.ID)
require.NoError(t, err)
require.Empty(t, copies)
require.Empty(t, copies[oldFile.ID])
}
func TestApplyScanRollsBackInvalidDiff(t *testing.T) {