Files
aego/asset/pipeline/source_temp_test.go
sanspie a46347e2c6
ci-platforms / darwin arm64 smoke (push) Skipped
ci / build (amd64, linux, ubuntu-24.04) (push) Failing after 12m19s
ci-platforms / darwin amd64 smoke (push) Skipped
ci-platforms / windows amd64 smoke (push) Skipped
github-native / gitea-pending (push) Skipped
github-native / darwin amd64 native (push) Skipped
github-native / linux amd64 native (push) Skipped
github-native / windows amd64 native (push) Skipped
github-native / darwin arm64 native (push) Skipped
github-native / gitea-result (push) Skipped
ci / checks (push) Failing after 23m57s
ci / release (push) Skipped
GitHub / windows amd64 native GitHub-hosted native CI: failure
GitHub / darwin arm64 native GitHub-hosted native CI: failure
GitHub / darwin amd64 native GitHub-hosted native CI: failure
GitHub / linux amd64 native GitHub-hosted native CI: success
editor: add art studio and stage 12.5 workflows
2026-11-08 18:47:57 +03:00

126 lines
3.8 KiB
Go

package pipeline
import (
"bytes"
"errors"
"os"
"path/filepath"
"testing"
)
func TestRemoveSourceTemp(t *testing.T) {
for _, tc := range []struct {
name string
change string
wantErr bool
}{
{name: "unchanged"},
{name: "already-missing", change: "missing"},
{name: "replacement-with-identical-bytes", change: "replacement", wantErr: true},
{name: "symlink-replacement", change: "symlink", wantErr: true},
{name: "changed-bytes-with-restored-timestamp", change: "contents", wantErr: true},
{name: "unknown-owner", change: "unknown", wantErr: true},
} {
t.Run(tc.name, func(t *testing.T) {
dir := t.TempDir()
root, err := os.OpenRoot(dir)
if err != nil {
t.Fatal(err)
}
defer root.Close()
const name = ".aego-owned.tmp"
ownedBytes := []byte("owned bytes")
if err := os.WriteFile(filepath.Join(dir, name), ownedBytes, 0o600); err != nil {
t.Fatal(err)
}
owned, err := root.Lstat(name)
if err != nil {
t.Fatal(err)
}
want := ownedBytes
switch tc.change {
case "missing":
if err := root.Remove(name); err != nil {
t.Fatal(err)
}
case "replacement":
// Retain the original inode so the filesystem cannot reuse it.
if err := root.Rename(name, "original"); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(dir, name), ownedBytes, 0o600); err != nil {
t.Fatal(err)
}
if err := os.Chtimes(filepath.Join(dir, name), owned.ModTime(), owned.ModTime()); err != nil {
t.Fatal(err)
}
case "symlink":
if err := root.Rename(name, "original"); err != nil {
t.Fatal(err)
}
if err := os.Symlink("original", filepath.Join(dir, name)); err != nil {
t.Skipf("symlinks unavailable: %v", err)
}
case "contents":
want = []byte("other bytes") // Same length and inode as the owned file.
if err := os.WriteFile(filepath.Join(dir, name), want, 0o600); err != nil {
t.Fatal(err)
}
if err := os.Chtimes(filepath.Join(dir, name), owned.ModTime(), owned.ModTime()); err != nil {
t.Fatal(err)
}
current, err := root.Lstat(name)
if err != nil || !os.SameFile(owned, current) || !owned.ModTime().Equal(current.ModTime()) {
t.Fatalf("could not reproduce same-inode, same-timestamp edit: %v", err)
}
case "unknown":
owned = nil
}
err = removeSourceTemp(root, name, owned, ownedBytes)
if (err != nil) != tc.wantErr {
t.Errorf("cleanup error = %v, want error %v", err, tc.wantErr)
}
got, err := os.ReadFile(filepath.Join(dir, name))
if tc.wantErr {
if err != nil || !bytes.Equal(got, want) {
t.Errorf("external file was changed or removed: %q, %v", got, err)
}
} else if !errors.Is(err, os.ErrNotExist) {
t.Errorf("owned temporary file remains: %q, %v", got, err)
}
})
}
}
func TestReplaceSourceCleansOnlyItsFailedTemporaryFile(t *testing.T) {
dir := t.TempDir()
root, err := os.OpenRoot(dir)
if err != nil {
t.Fatal(err)
}
defer root.Close()
if err := root.Mkdir("assets", 0o755); err != nil {
t.Fatal(err)
}
if err := replaceSource(root, "assets/source.txt", []byte("candidate")); err != nil {
t.Fatal(err)
}
got, err := os.ReadFile(filepath.Join(dir, "assets", "source.txt"))
if err != nil || string(got) != "candidate" {
t.Fatalf("successful publication: %q, %v", got, err)
}
if err := root.Mkdir("assets/destination", 0o755); err != nil {
t.Fatal(err)
}
if err := replaceSource(root, "assets/destination", []byte("cannot replace a directory")); err == nil {
t.Fatal("publication over a directory unexpectedly succeeded")
}
entries, err := os.ReadDir(filepath.Join(dir, "assets"))
if err != nil {
t.Fatal(err)
}
if len(entries) != 2 || entries[0].Name() != "destination" || entries[1].Name() != "source.txt" {
t.Fatalf("failed publication left unexpected files: %v", entries)
}
}