ronantakizawa/codereview-bench huggingface dataset:
Repository: syncthing/syncthing
File: lib/db/lowlevel.go (Go)

Code before:
}

// repairSequence makes sure the sequence numbers in the sequence keys match
// those in the corresponding file entries. It returns the amount of fixed entries.
func (db *Lowlevel) repairSequence(folders []string) (int, error) {
	t, err := db.newReadWriteTransaction()
	if err != nil {
		return 0, err
	}
	defer t.close()

	var dk, sk []byte
	fixed := 0

	for _, folderStr := range folders {
		var meta *metadataTracker
		folder := []byte(folderStr)

		sk, err := t.keyer.GenerateSequenceKey(sk, folder, 0)
		if err != nil {
			return 0, err
		}

		dbi, err := t.NewPrefixIterator(sk.WithoutSequence())
		if err != nil {
			return 0, err
		}
		defer dbi.Release()

		for dbi.Next() {
			dk = dbi.Value()
			// Check that the sequence from the key matches the
			// sequence in the file.
			fi, ok, err := t.getFileTrunc(dk, true)
			if err != nil {
				return 0, err
			}
			// Shouldn't ever happen.
			if !ok {
				if err := t.Delete(sk); err != nil {
					return 0, err
				}
				continue
			}
			if seq := t.keyer.SequenceFromSequenceKey(dbi.Key()); seq == fi.SequenceNo() {
				continue
			}

			// Mismatch: Bump sequence and rewrite.
			fixed++
			if meta == nil {

Review comment (suggestion):
I think this should hold the gc lock as well. Probably won't cause issues now, but it rewrites FileInfos and gets called at random times so in principle they are in the same territory.

Code after:
			continue
		}
		if err := t.Delete(key); err != nil {
			return err
		}
	}
	it.Release()
	if err := it.Error(); err != nil {
		return err
	}

	// Remember the number of unique keys we kept until the next pass.
	db.gcKeyCount = matchedBlocks

	if err := t.Commit(); err != nil {
		return err
	}

	return db.Compact()
}

// repairSequenceGCLocked makes sure the sequence numbers in the sequence keys
// match those in the corresponding file entries. It returns the amount of fixed
// entries.
func (db *Lowlevel) repairSequenceGCLocked(folderStr string, meta *metadataTracker) (int, error) {
	t, err := db.newReadWriteTransaction()
	if err != nil {
		return 0, err
	}
	defer t.close()

	fixed := 0

	folder := []byte(folderStr)

	// First check that every file entry has a matching sequence entry
	// (this was previously db schema upgrade to 9).

	dk, err := t.keyer.GenerateDeviceFileKey(nil, folder, protocol.LocalDeviceID[:], nil)
	if err != nil {
		return 0, err
	}
	it, err := t.NewPrefixIterator(dk.WithoutName())
	if err != nil {
		return 0, err
	}
	defer it.Release()

	var sk sequenceKey
	for it.Next() {
		intf, err := t.unmarshalTrunc(it.Value(), true)
Repository: syncthing/syncthing
File: cmd/syncthing/main.go (Go)

Code before:

	if options.generateDir != "" {
		if err := generate(options.generateDir); err != nil {
			l.Warnln("Failed to generate config and keys:", err)
			os.Exit(syncthing.ExitError.AsInt())
		}
		return
	}

	// Ensure that our home directory exists.
	if err := ensureDir(locations.GetBaseDir(locations.ConfigBaseDir), 0700); err != nil {
		l.Warnln("Failure on home directory:", err)
		os.Exit(syncthing.ExitError.AsInt())
	}

	if options.upgradeTo != "" {
		err := upgrade.ToURL(options.upgradeTo)
		if err != nil {
			l.Warnln("Error while Upgrading:", err)
			os.Exit(syncthing.ExitError.AsInt())
		}
		l.Infoln("Upgraded from", options.upgradeTo)
		return
	}

	checkUpgradeExitOnErr := func() upgrade.Release {
		release, err := checkUpgrade()
		if err != nil {
			l.Warnln("Upgrade:", err)
			if _, ok := err.(errNoUpgrade); ok {
				os.Exit(syncthing.ExitNoUpgradeAvailable.AsInt())
			}
			os.Exit(syncthing.ExitError.AsInt())
		}
		return release
	}

	if options.doUpgradeCheck {
		_ = checkUpgradeExitOnErr()
		return
	}

	if options.doUpgrade {
		release := checkUpgradeExitOnErr()
		if err := performUpgrade(release); err != nil {
			l.Warnln("Upgrade:", err)
			os.Exit(syncthing.ExitError.AsInt())
		}
		l.Infof("Upgraded to %q", release.Tag)
		os.Exit(syncthing.ExitUpgrade.AsInt())
	}

Review comment (suggestion):
I think this should be a normal function despite the given rationale. Closures have a significant mental cost, at least for me, which is not necessary in this case

Code after:

	if options.generateDir != "" {
		if err := generate(options.generateDir); err != nil {
			l.Warnln("Failed to generate config and keys:", err)
			os.Exit(syncthing.ExitError.AsInt())
		}
		return
	}

	// Ensure that our home directory exists.
	if err := ensureDir(locations.GetBaseDir(locations.ConfigBaseDir), 0700); err != nil {
		l.Warnln("Failure on home directory:", err)
		os.Exit(syncthing.ExitError.AsInt())
	}

	if options.upgradeTo != "" {
		err := upgrade.ToURL(options.upgradeTo)
		if err != nil {
			l.Warnln("Error while Upgrading:", err)
			os.Exit(syncthing.ExitError.AsInt())
		}
		l.Infoln("Upgraded from", options.upgradeTo)
		return
	}

	if options.doUpgradeCheck {
		if _, err := checkUpgrade(); err != nil {
			l.Warnln("Checking for upgrade:", err)
			os.Exit(exitCodeForUpgrade(err))
		}
		return
	}

	if options.doUpgrade {
		release, err := checkUpgrade()
		if err == nil {
			err = performUpgrade(release)
		}
		if err != nil {
			l.Warnln("Upgrade:", err)
			os.Exit(exitCodeForUpgrade(err))
		}
		l.Infof("Upgraded to %q", release.Tag)
		os.Exit(syncthing.ExitUpgrade.AsInt())
	}

	if options.resetDatabase {
		if err := resetDB(); err != nil {
			l.Warnln("Resetting database:", err)
			os.Exit(syncthing.ExitError.AsInt())
		}
Repository: syncthing/syncthing
File: lib/db/lowlevel.go (Go)

Code before:

func NewLowlevel(backend backend.Backend, opts ...Option) *Lowlevel {
	db := &Lowlevel{
		Backend:            backend,
		folderIdx:          newSmallIndex(backend, []byte{KeyTypeFolderIdx}),
		deviceIdx:          newSmallIndex(backend, []byte{KeyTypeDeviceIdx}),
		gcMut:              sync.NewRWMutex(),
		gcStop:             make(chan struct{}),
		indirectGCInterval: indirectGCDefaultInterval,
		recheckInterval:    recheckDefaultInterval,
	}
	for _, opt := range opts {
		opt(db)
	}
	db.keyer = newDefaultKeyer(db.folderIdx, db.deviceIdx)
	go db.gcRunner()
	return db
}

type Option func(*Lowlevel)

// WithRecheckInterval sets the time interval in between metadata recalculations
// and consistency checks.
func WithRecheckInterval(dur time.Duration) Option {
	return func(db *Lowlevel) {
		if dur != 0 {
			db.recheckInterval = dur
		}
	}
}

// WithIndirectGCInterval sets the time interval in between GC runs.
func WithIndirectGCInterval(dur time.Duration) Option {
	return func(db *Lowlevel) {
		if dur != 0 {
			db.indirectGCInterval = dur
		}
	}
}

func (db *Lowlevel) Close() error {
	close(db.gcStop)
	return db.Backend.Close()
}

// ListFolders returns the list of folders currently in the database
func (db *Lowlevel) ListFolders() []string {
	return db.folderIdx.Values()
}

// updateRemoteFiles adds a list of fileinfos to the database and updates the

Review comment (suggestion):
So about that, I would do `STRECHECKDBEVERY=0 syncthing` to force a check *now*. This breaks that, I'd have to say `STRECHECKDBEVERY=1s` instead. Which is maybe fine... At least for the GC interval zero isn't a great choice. (We could say that zero means no GC, but it's also intuitive enough to say `STGCINDIRECTEVERY=100000d` or so...)

Code after:

func NewLowlevel(backend backend.Backend, opts ...Option) *Lowlevel {
	db := &Lowlevel{
		Backend:            backend,
		folderIdx:          newSmallIndex(backend, []byte{KeyTypeFolderIdx}),
		deviceIdx:          newSmallIndex(backend, []byte{KeyTypeDeviceIdx}),
		gcMut:              sync.NewRWMutex(),
		gcStop:             make(chan struct{}),
		indirectGCInterval: indirectGCDefaultInterval,
		recheckInterval:    recheckDefaultInterval,
	}
	for _, opt := range opts {
		opt(db)
	}
	db.keyer = newDefaultKeyer(db.folderIdx, db.deviceIdx)
	go db.gcRunner()
	return db
}

type Option func(*Lowlevel)

// WithRecheckInterval sets the time interval in between metadata recalculations
// and consistency checks.
func WithRecheckInterval(dur time.Duration) Option {
	return func(db *Lowlevel) {
		if dur > 0 {
			db.recheckInterval = dur
		}
	}
}

// WithIndirectGCInterval sets the time interval in between GC runs.
func WithIndirectGCInterval(dur time.Duration) Option {
	return func(db *Lowlevel) {
		if dur > 0 {
			db.indirectGCInterval = dur
		}
	}
}

func (db *Lowlevel) Close() error {
	close(db.gcStop)
	return db.Backend.Close()
}

// ListFolders returns the list of folders currently in the database
func (db *Lowlevel) ListFolders() []string {
	return db.folderIdx.Values()
}

// updateRemoteFiles adds a list of fileinfos to the database and updates the
Repository: syncthing/syncthing
File: lib/model/folder.go (Go)

Code before:
func (f *folder) SchedulePull() {
	select {
	case f.pullScheduled <- struct{}{}:
	default:
		// We might be busy doing a pull and thus not reading from this
		// channel. The channel is 1-buffered, so one notification will be
		// queued to ensure we recheck after the pull, but beyond that we must
		// make sure to not block index receiving.
	}
}

func (f *folder) Jobs(_, _ int) ([]string, []string, int) {
	return nil, nil, 0
}

func (f *folder) Scan(subdirs []string) error {
	<-f.initialScanFinished
	return f.doInSync(func() error { return f.scanSubdirs(subdirs) })
}

// doInSync allows to run functions synchronously in folder.serve from exported,
// asynchronously called methods.
func (f *folder) doInSync(fn func() error) error {
	req := syncRequest{
		fn:  fn,
		err: make(chan error),
	}

	select {
	case f.doInSyncChan <- req:
		return <-req.err
	case <-f.ctx.Done():
		return f.ctx.Err()
	}
}

func (f *folder) Reschedule() {
	if f.scanInterval == 0 {
		return
	}
	// Sleep a random time between 3/4 and 5/4 of the configured interval.
	sleepNanos := (f.scanInterval.Nanoseconds()*3 + rand.Int63n(2*f.scanInterval.Nanoseconds())) / 4
	interval := time.Duration(sleepNanos) * time.Nanosecond
	l.Debugln(f, "next rescan in", interval)
	f.scanTimer.Reset(interval)
}

func (f *folder) Delay(next time.Duration) {
	f.scanDelay <- next
}


Review comment (bug):
```suggestion
		err: make(chan error, 1),
```

lest we deadlock the loop if we take the ctx.Done() branch below

Code after:
func (f *folder) SchedulePull() {
	select {
	case f.pullScheduled <- struct{}{}:
	default:
		// We might be busy doing a pull and thus not reading from this
		// channel. The channel is 1-buffered, so one notification will be
		// queued to ensure we recheck after the pull, but beyond that we must
		// make sure to not block index receiving.
	}
}

func (f *folder) Jobs(_, _ int) ([]string, []string, int) {
	return nil, nil, 0
}

func (f *folder) Scan(subdirs []string) error {
	<-f.initialScanFinished
	return f.doInSync(func() error { return f.scanSubdirs(subdirs) })
}

// doInSync allows to run functions synchronously in folder.serve from exported,
// asynchronously called methods.
func (f *folder) doInSync(fn func() error) error {
	req := syncRequest{
		fn:  fn,
		err: make(chan error, 1),
	}

	select {
	case f.doInSyncChan <- req:
		return <-req.err
	case <-f.ctx.Done():
		return f.ctx.Err()
	}
}

func (f *folder) Reschedule() {
	if f.scanInterval == 0 {
		return
	}
	// Sleep a random time between 3/4 and 5/4 of the configured interval.
	sleepNanos := (f.scanInterval.Nanoseconds()*3 + rand.Int63n(2*f.scanInterval.Nanoseconds())) / 4
	interval := time.Duration(sleepNanos) * time.Nanosecond
	l.Debugln(f, "next rescan in", interval)
	f.scanTimer.Reset(interval)
}

func (f *folder) Delay(next time.Duration) {
	f.scanDelay <- next
}
Repository: syncthing/syncthing
File: lib/model/folder_recvonly_test.go (Go)

Code before:

	must(t, m.ScanFolder("ro"))

	// Everything should be in sync.

	size := globalSize(t, m, "ro")
	if size.Files != 1 || size.Directories != 1 {
		t.Fatalf("Global: expected 1 file and 1 directory: %+v", size)
	}
	size = localSize(t, m, "ro")
	if size.Files != 1 || size.Directories != 1 {
		t.Fatalf("Local: expected 1 file and 1 directory: %+v", size)
	}
	size = needSize(t, m, "ro")
	if size.Files+size.Directories > 0 {
		t.Fatalf("Need: expected nothing: %+v", size)
	}
	size = receiveOnlyChangedSize(t, m, "ro")
	if size.Files+size.Directories > 0 {
		t.Fatalf("ROChanged: expected nothing: %+v", size)
	}

	// Create a file and modify another

	const file = "foo"
	const knownFile = "knownDir/knownFile"
	must(t, writeFile(ffs, file, []byte("hello\n"), 0644))
	must(t, writeFile(ffs, knownFile, []byte("bye\n"), 0644))

	must(t, m.ScanFolder("ro"))

	size = receiveOnlyChangedSize(t, m, "ro")
	if size.Files != 2 {
		t.Fatalf("Receive only: expected 2 files: %+v", size)
	}

	// Do the same changes on the remote

	files := make([]protocol.FileInfo, 0, 2)
	snap := f.fset.Snapshot()
	snap.WithHave(protocol.LocalDeviceID, func(fi db.FileIntf) bool {
		if n := fi.FileName(); n != file && n != knownFile {
			return true
		}
		f := fi.(protocol.FileInfo)
		f.LocalFlags = 0
		f.Version = protocol.Vector{}.Update(device1.Short())
		files = append(files, f)
		return true
	})
	snap.Release()

Review comment (bug):
It does not like this on Windows, hence fails. 
If I change it to:
```
knownFile = "knownDir\\knownFile"
```
it passes.

Code after:
	// Scan the folder.

	must(t, m.ScanFolder("ro"))

	// Everything should be in sync.

	size := globalSize(t, m, "ro")
	if size.Files != 1 || size.Directories != 1 {
		t.Fatalf("Global: expected 1 file and 1 directory: %+v", size)
	}
	size = localSize(t, m, "ro")
	if size.Files != 1 || size.Directories != 1 {
		t.Fatalf("Local: expected 1 file and 1 directory: %+v", size)
	}
	size = needSize(t, m, "ro")
	if size.Files+size.Directories > 0 {
		t.Fatalf("Need: expected nothing: %+v", size)
	}
	size = receiveOnlyChangedSize(t, m, "ro")
	if size.Files+size.Directories > 0 {
		t.Fatalf("ROChanged: expected nothing: %+v", size)
	}

	// Create a file and modify another

	const file = "foo"
	knownFile := filepath.Join("knownDir", "knownFile")
	must(t, writeFile(ffs, file, []byte("hello\n"), 0644))
	must(t, writeFile(ffs, knownFile, []byte("bye\n"), 0644))

	must(t, m.ScanFolder("ro"))

	size = receiveOnlyChangedSize(t, m, "ro")
	if size.Files != 2 {
		t.Fatalf("Receive only: expected 2 files: %+v", size)
	}

	// Do the same changes on the remote

	files := make([]protocol.FileInfo, 0, 2)
	snap := f.fset.Snapshot()
	snap.WithHave(protocol.LocalDeviceID, func(fi db.FileIntf) bool {
		if n := fi.FileName(); n != file && n != knownFile {
			return true
		}
		f := fi.(protocol.FileInfo)
		f.LocalFlags = 0
		f.Version = protocol.Vector{}.Update(device1.Short())
		files = append(files, f)
		return true
	})
Repository: syncthing/syncthing
File: lib/osutil/atomic.go (Go)

Code before:
// invalid to call Write() or Close() after Close().
func (w *AtomicWriter) Close() error {
	if w.err != nil {
		return w.err
	}

	// Try to not leave temp file around, but ignore error.
	defer w.fs.Remove(w.next.Name())

	if err := w.next.Sync(); err != nil {
		w.err = err
		return err
	}

	if err := w.next.Close(); err != nil {
		w.err = err
		return err
	}

	if err := w.fs.Rename(w.next.Name(), w.path); err != nil {
		w.err = err
		return err
	}

	// fsync the directory too
	if fd, err := w.fs.Open(filepath.Dir(w.next.Name())); err == nil {
		fd.Sync()
		fd.Close()
	}

	// Set w.err to return appropriately for any future operations.
	w.err = ErrClosed

	return nil
}


Review comment (security):
I'm not gonna try to do a stat and defer an undo of this. We're supposed to overwrite and destroy this file, with a file that we *have* write permissions on, and if it didn't help to set the old file to writeable then who knows what's even happening any more

Code after:
// Close closes the temporary file and renames it to the final path. It is
// invalid to call Write() or Close() after Close().
func (w *AtomicWriter) Close() error {
	if w.err != nil {
		return w.err
	}

	// Try to not leave temp file around, but ignore error.
	defer w.fs.Remove(w.next.Name())

	if err := w.next.Sync(); err != nil {
		w.err = err
		return err
	}

	if err := w.next.Close(); err != nil {
		w.err = err
		return err
	}

	err := w.fs.Rename(w.next.Name(), w.path)
	if runtime.GOOS == "windows" && fs.IsPermission(err) {
		// On Windows, we might not be allowed to rename over the file
		// because it's read-only. Get us some write permissions and try
		// again.
		_ = w.fs.Chmod(w.path, 0644)
		err = w.fs.Rename(w.next.Name(), w.path)
	}
	if err != nil {
		w.err = err
		return err
	}

	// fsync the directory too
	if fd, err := w.fs.Open(filepath.Dir(w.next.Name())); err == nil {
		fd.Sync()
		fd.Close()
	}

	// Set w.err to return appropriately for any future operations.
	w.err = ErrClosed

	return nil
}
Repository: syncthing/syncthing
File: lib/model/model.go (Go)

Code before:
func (s folderDeviceSet) hasDevice(dev protocol.DeviceID) bool {
	for _, devices := range s {
		if _, ok := devices[dev]; ok {
			return true
		}
	}
	return false
}

type fileInfoBatch struct {
	infos         []protocol.FileInfo
	size          int
	nonEmptyInfos int
	flushFn       func([]protocol.FileInfo) error
}

func newFileInfoBatch(fn func([]protocol.FileInfo) error) *fileInfoBatch {
	return &fileInfoBatch{
		infos:   make([]protocol.FileInfo, 0, maxBatchSizeFiles),
		flushFn: fn,
	}
}

func (b *fileInfoBatch) append(f protocol.FileInfo) {
	b.infos = append(b.infos, f)
	if !f.IsDeleted() {
		b.nonEmptyInfos++
		b.size += f.ProtoSize()
	}
}

func (b *fileInfoBatch) flushIfFull() error {
	if b.nonEmptyInfos >= maxBatchSizeFiles || b.size >= maxBatchSizeBytes {
		return b.flush()
	}
	return nil
}

func (b *fileInfoBatch) flush() error {
	if len(b.infos) == 0 {
		return nil
	}
	if err := b.flushFn(b.infos); err != nil {
		return err
	}
	b.reset()
	return nil
}

func (b *fileInfoBatch) reset() {
	b.infos = b.infos[:0]

Review comment (performance):
This means no flushing on delete detection and thus huge batches when a lot is deleted at once. And I don't think it's necessary anyway: If you just don't call `flushIfFull` between adding the newly added file and checking/adding a corresponding deleted file you are fine too.

Code after:
	b.reset()
	return nil
}

func (b *fileInfoBatch) reset() {
	b.infos = b.infos[:0]
	b.size = 0
}

// syncMutexMap is a type safe wrapper for a sync.Map that holds mutexes
type syncMutexMap struct {
	inner stdsync.Map
}

func (m *syncMutexMap) Get(key string) sync.Mutex {
	v, _ := m.inner.LoadOrStore(key, sync.NewMutex())
	return v.(sync.Mutex)
}

// sanitizePath takes a string that might contain all kinds of special
// characters and makes a valid, similar, path name out of it.
//
// Spans of invalid characters, whitespace and/or non-UTF-8 sequences are
// replaced by a single space. The result is always UTF-8 and contains only
// printable characters, as determined by unicode.IsPrint.
//
// Invalid characters are non-printing runes, things not allowed in file names
// in Windows, and common shell metacharacters. Even if asterisks and pipes
// and stuff are allowed on Unixes in general they might not be allowed by
// the filesystem and may surprise the user and cause shell oddness. This
// function is intended for file names we generate on behalf of the user,
// and surprising them with odd shell characters in file names is unkind.
//
// We include whitespace in the invalid characters so that multiple
// whitespace is collapsed to a single space. Additionally, whitespace at
// either end is removed.
func sanitizePath(path string) string {
	var b strings.Builder

	prev := ' '
	for _, c := range path {
		if !unicode.IsPrint(c) || c == unicode.ReplacementChar ||
			strings.ContainsRune(`<>:"'/\|?*[]{};:!@$%&^#`, c) {
			c = ' '
		}

		if !(c == ' ' && prev == ' ') {
			b.WriteRune(c)
		}
		prev = c
	}
Repository: syncthing/syncthing
File: lib/model/model_test.go (Go)

Code before:
	if failed {
		t.Fail()
	}
}

func TestBlockListMap(t *testing.T) {
	wcfg, fcfg := tmpDefaultWrapper()
	m := setupModel(wcfg)
	defer cleanupModel(m)

	ffs := fcfg.Filesystem()
	must(t, writeFile(ffs, "one", []byte("content"), 0644))
	must(t, writeFile(ffs, "two", []byte("content"), 0644))
	must(t, writeFile(ffs, "three", []byte("content"), 0644))
	must(t, writeFile(ffs, "four", []byte("content"), 0644))
	must(t, writeFile(ffs, "five", []byte("content"), 0644))

	m.ScanFolders()

	// Reach info the model, grab the fileset.

	m.fmut.RLock()
	fset := m.folderFiles["default"]
	m.fmut.RUnlock()

	snap := fset.Snapshot()
	defer snap.Release()
	fi, ok := snap.Get(protocol.LocalDeviceID, "one")
	if !ok {
		t.Error("failed to find existing file")
	}
	var paths []string

	must(t, snap.WithBlocksHash(fi.BlocksHash, func(fi db.FileIntf) bool {
		paths = append(paths, fi.FileName())
		return true
	}))
	snap.Release()

	expected := []string{"one", "two", "three", "four", "five"}
	if !equalStringsInAnyOrder(paths, expected) {
		t.Errorf("expected %q got %q", expected, paths)
	}

	// Fudge the files around
	// Remove
	must(t, ffs.Remove("one"))

	// Modify
	must(t, ffs.Remove("two"))
	must(t, writeFile(ffs, "two", []byte("mew-content"), 0644))

Review comment (suggestion):
```suggestion
	count := 0
	snap := dbSnapshot(t, m, "default")
```

Code after:
		t.Error("failed to find existing file")
	}
	var paths []string

	snap.WithBlocksHash(fi.BlocksHash, func(fi db.FileIntf) bool {
		paths = append(paths, fi.FileName())
		return true
	})
	snap.Release()

	expected := []string{"one", "two", "three", "four", "five"}
	if !equalStringsInAnyOrder(paths, expected) {
		t.Errorf("expected %q got %q", expected, paths)
	}

	// Fudge the files around
	// Remove
	must(t, ffs.Remove("one"))

	// Modify
	must(t, ffs.Remove("two"))
	must(t, writeFile(ffs, "two", []byte("mew-content"), 0644))

	// Rename
	must(t, ffs.Rename("three", "new-three"))

	// Change type
	must(t, ffs.Remove("four"))
	must(t, ffs.Mkdir("four", 0644))

	m.ScanFolders()

	// Check we're left with 2 of the 5
	snap = dbSnapshot(t, m, "default")
	defer snap.Release()

	paths = paths[:0]
	snap.WithBlocksHash(fi.BlocksHash, func(fi db.FileIntf) bool {
		paths = append(paths, fi.FileName())
		return true
	})
	snap.Release()

	expected = []string{"new-three", "five"}
	if !equalStringsInAnyOrder(paths, expected) {
		t.Errorf("expected %q got %q", expected, paths)
	}
}

func equalStringsInAnyOrder(a, b []string) bool {
	if len(a) != len(b) {
Repository: syncthing/syncthing
File: lib/model/model_test.go (Go)

Code before:
	snap.Release()

	expected := []string{"one", "two", "three", "four", "five"}
	if !equalStringsInAnyOrder(paths, expected) {
		t.Errorf("expected %q got %q", expected, paths)
	}

	// Fudge the files around
	// Remove
	must(t, ffs.Remove("one"))

	// Modify
	must(t, ffs.Remove("two"))
	must(t, writeFile(ffs, "two", []byte("mew-content"), 0644))

	// Rename
	must(t, ffs.Rename("three", "new-three"))

	// Change type
	must(t, ffs.Remove("four"))
	must(t, ffs.Mkdir("four", 0644))

	m.ScanFolders()

	// Check we're left with 2 of the 5
	snap = fset.Snapshot()
	defer snap.Release()

	paths = paths[:0]
	must(t, snap.WithBlocksHash(fi.BlocksHash, func(fi db.FileIntf) bool {
		paths = append(paths, fi.FileName())
		return true
	}))
	snap.Release()

	expected = []string{"new-three", "five"}
	if !equalStringsInAnyOrder(paths, expected) {
		t.Errorf("expected %q got %q", expected, paths)
	}
}

func equalStringsInAnyOrder(a, b []string) bool {
	if len(a) != len(b) {
		return false
	}
	sort.Strings(a)
	sort.Strings(b)
	for i := range a {
		if a[i] != b[i] {
			return false
		}

Review comment (suggestion):
```suggestion
	snap = dbSnapshot(t, m, "default")
```

Code after:
	snap.WithBlocksHash(fi.BlocksHash, func(fi db.FileIntf) bool {
		paths = append(paths, fi.FileName())
		return true
	})
	snap.Release()

	expected = []string{"new-three", "five"}
	if !equalStringsInAnyOrder(paths, expected) {
		t.Errorf("expected %q got %q", expected, paths)
	}
}

func equalStringsInAnyOrder(a, b []string) bool {
	if len(a) != len(b) {
		return false
	}
	sort.Strings(a)
	sort.Strings(b)
	for i := range a {
		if a[i] != b[i] {
			return false
		}
	}
	return true
}