kubo/repo/fsrepo/migrations/atomicfile/atomicfile_test.go
Marcin Rataj 6a008fc74c
refactor: apply go fix modernizers from Go 1.26 (#11190)
* chore: apply go fix modernizers from Go 1.26

automated refactoring: interface{} to any, slices.Contains,
and other idiomatic updates.

* feat(ci): add `go fix` check to Go analysis workflow

ensures Go 1.26 modernizers are applied, fails CI if `go fix ./...`
produces any changes (similar to existing `go fmt` enforcement)
2026-02-11 01:01:32 +01:00

209 lines
4.7 KiB
Go

package atomicfile
import (
"bytes"
"fmt"
"os"
"path/filepath"
"runtime"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// TestNew_Success verifies atomic file creation
func TestNew_Success(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "test.txt")
af, err := New(path, 0644)
require.NoError(t, err)
defer func() { _ = af.Abort() }()
// Verify temp file exists
assert.FileExists(t, af.File.Name())
// Verify temp file is in same directory
assert.Equal(t, dir, filepath.Dir(af.File.Name()))
}
// TestClose_Success verifies atomic replacement
func TestClose_Success(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "test.txt")
af, err := New(path, 0644)
require.NoError(t, err)
content := []byte("test content")
_, err = af.Write(content)
require.NoError(t, err)
tempName := af.File.Name()
require.NoError(t, af.Close())
// Verify target file exists with correct content
data, err := os.ReadFile(path)
require.NoError(t, err)
assert.Equal(t, content, data)
// Verify temp file removed
assert.NoFileExists(t, tempName)
}
// TestAbort_Success verifies cleanup
func TestAbort_Success(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "test.txt")
af, err := New(path, 0644)
require.NoError(t, err)
tempName := af.File.Name()
require.NoError(t, af.Abort())
// Verify temp file removed
assert.NoFileExists(t, tempName)
// Verify target not created
assert.NoFileExists(t, path)
}
// TestAbort_ErrorHandling tests error capture
func TestAbort_ErrorHandling(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "test.txt")
af, err := New(path, 0644)
require.NoError(t, err)
// Close file to force close error
af.File.Close()
// Remove temp file to force remove error
os.Remove(af.File.Name())
err = af.Abort()
// Should get both errors
require.Error(t, err)
assert.Contains(t, err.Error(), "abort failed")
}
// TestClose_CloseError verifies cleanup on close failure
func TestClose_CloseError(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "test.txt")
af, err := New(path, 0644)
require.NoError(t, err)
tempName := af.File.Name()
// Close file to force close error
af.File.Close()
err = af.Close()
require.Error(t, err)
// Verify temp file cleaned up even on error
assert.NoFileExists(t, tempName)
}
// TestReadFrom verifies io.Copy integration
func TestReadFrom(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "test.txt")
af, err := New(path, 0644)
require.NoError(t, err)
defer func() { _ = af.Abort() }()
content := []byte("test content from reader")
n, err := af.ReadFrom(bytes.NewReader(content))
require.NoError(t, err)
assert.Equal(t, int64(len(content)), n)
}
// TestFilePermissions verifies mode is set correctly
func TestFilePermissions(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "test.txt")
af, err := New(path, 0600)
require.NoError(t, err)
_, err = af.Write([]byte("test"))
require.NoError(t, err)
require.NoError(t, af.Close())
info, err := os.Stat(path)
require.NoError(t, err)
// On Unix, check exact permissions
if runtime.GOOS != "windows" {
mode := info.Mode().Perm()
assert.Equal(t, os.FileMode(0600), mode)
}
}
// TestMultipleAbortsSafe verifies calling Abort multiple times is safe
func TestMultipleAbortsSafe(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "test.txt")
af, err := New(path, 0644)
require.NoError(t, err)
tempName := af.File.Name()
// First abort should succeed
require.NoError(t, af.Abort())
assert.NoFileExists(t, tempName, "temp file should be removed after first abort")
// Second abort should handle gracefully (file already gone)
err = af.Abort()
// Error is acceptable since file is already removed, but it should not panic
t.Logf("Second Abort() returned: %v", err)
}
// TestNoTempFilesAfterOperations verifies no .tmp-* files remain after operations
func TestNoTempFilesAfterOperations(t *testing.T) {
const testIterations = 5
tests := []struct {
name string
operation func(*File) error
}{
{"close", (*File).Close},
{"abort", (*File).Abort},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
dir := t.TempDir()
// Perform multiple operations
for i := range testIterations {
path := filepath.Join(dir, fmt.Sprintf("test%d.txt", i))
af, err := New(path, 0644)
require.NoError(t, err)
_, err = af.Write([]byte("test data"))
require.NoError(t, err)
require.NoError(t, tt.operation(af))
}
// Check for any .tmp-* files
tmpFiles, err := filepath.Glob(filepath.Join(dir, ".tmp-*"))
require.NoError(t, err)
assert.Empty(t, tmpFiles, "should be no temp files after %s", tt.name)
})
}
}