Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bdf0e21a29 | ||
|
|
9e4d3fdb54 |
@@ -32,6 +32,14 @@ latest run passes.
|
|||||||
write stop counting, and the part of its file written is removed. A file that
|
write stop counting, and the part of its file written is removed. A file that
|
||||||
cannot be deleted still counts until the next start; one already deleted by
|
cannot be deleted still counts until the next start; one already deleted by
|
||||||
hand counts as freed
|
hand counts as freed
|
||||||
|
- 2026-10-03: `backend/script/lint` says what went wrong with its
|
||||||
|
`.golangci.yml` check (issue #34). On a hash mismatch it says to compare the
|
||||||
|
file with the org standard: if they differ, restore the org standard; if they
|
||||||
|
are the same, the org standard changed, so update `GOLANGCI_CONFIG_SHA256` in
|
||||||
|
that script. It used to say only to restore the file, which loops once the org
|
||||||
|
standard itself has moved. A missing `.golangci.yml`, and a `sha256sum` that
|
||||||
|
is missing or prints no hash, each get their own message instead of being
|
||||||
|
reported as a mismatch; every one still fails the lint
|
||||||
- 2026-10-03: the Go tests run with the race detector and coverage (issue #88):
|
- 2026-10-03: the Go tests run with the race detector and coverage (issue #88):
|
||||||
`backend/script/test` runs `go test -timeout 30s -race -cover ./...` and, if
|
`backend/script/test` runs `go test -timeout 30s -race -cover ./...` and, if
|
||||||
that fails, runs it again with `-v` and fails. Go's `-timeout` bounds the
|
that fails, runs it again with `-v` and fails. Go's `-timeout` bounds the
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
"log/slog"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
@@ -69,9 +70,11 @@ type Buffer struct {
|
|||||||
// except in tests that hold the write open or make it fail.
|
// except in tests that hold the write open or make it fail.
|
||||||
fileCreated func(f *os.File)
|
fileCreated func(f *os.File)
|
||||||
// files are the report files that may be deleted to make room,
|
// files are the report files that may be deleted to make room,
|
||||||
// oldest first: those in dataDir at start, then each one this
|
// in name order, which is oldest first: those in dataDir at
|
||||||
// buffer writes, once it is complete. A file still being written
|
// start, and each one this buffer writes, put in at its place by
|
||||||
// is not among them. filesBytes is their total size.
|
// name once it is complete, even when an older file's write
|
||||||
|
// completes after a newer one's. A file still being written is
|
||||||
|
// not among them. filesBytes is their total size.
|
||||||
files []reportFile
|
files []reportFile
|
||||||
filesBytes int64
|
filesBytes int64
|
||||||
log *slog.Logger
|
log *slog.Logger
|
||||||
@@ -311,9 +314,16 @@ func (b *Buffer) writeFile(data []byte) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
b.usedBytes += size
|
b.usedBytes += size
|
||||||
b.files = append(b.files, reportFile{name: name, size: size})
|
|
||||||
b.filesBytes += size
|
b.filesBytes += size
|
||||||
|
|
||||||
|
// At its place by name, not at the end: another write, of a newer
|
||||||
|
// file, may have completed while this one was being written.
|
||||||
|
i, _ := slices.BinarySearchFunc(b.files, name,
|
||||||
|
func(f reportFile, target string) int {
|
||||||
|
return strings.Compare(f.name, target)
|
||||||
|
})
|
||||||
|
b.files = slices.Insert(b.files, i, reportFile{name: name, size: size})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -835,3 +835,85 @@ func hasReportFile(t *testing.T, dir string) bool {
|
|||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestFilesDeletedOldestFirstWhenWritesOverlap holds the write of an
|
||||||
|
// older report file open until a newer one's write completes, then
|
||||||
|
// releases it. When room is needed, the older file is deleted first,
|
||||||
|
// though its write was the last to complete.
|
||||||
|
func TestFilesDeletedOldestFirstWhenWritesOverlap(t *testing.T) {
|
||||||
|
const maxBytes = 1000
|
||||||
|
|
||||||
|
report := map[string]string{"id": "overlap"}
|
||||||
|
|
||||||
|
t.Setenv("DATA_DIR", t.TempDir())
|
||||||
|
t.Setenv("DATA_DIR_MAX_BYTES", strconv.Itoa(maxBytes))
|
||||||
|
|
||||||
|
buf := startBuffer(t)
|
||||||
|
|
||||||
|
created := make(chan string)
|
||||||
|
release := make(chan struct{})
|
||||||
|
|
||||||
|
buf.OnFileCreated(func(f *os.File) {
|
||||||
|
created <- f.Name()
|
||||||
|
|
||||||
|
<-release
|
||||||
|
})
|
||||||
|
|
||||||
|
err := buf.Append(report)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("older report: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
flushed := make(chan error)
|
||||||
|
|
||||||
|
go func() { flushed <- buf.Flush() }()
|
||||||
|
|
||||||
|
older := <-created
|
||||||
|
|
||||||
|
// Only the older write is held; the newer one goes through, and
|
||||||
|
// so do the writes after it, the final one at stop included.
|
||||||
|
var newer string
|
||||||
|
|
||||||
|
buf.OnFileCreated(func(f *os.File) { newer = f.Name() })
|
||||||
|
|
||||||
|
// Errorf, not Fatalf, until the older write is released, so that a
|
||||||
|
// failure here does not leave it held.
|
||||||
|
err = buf.Append(report)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("newer report: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = buf.Flush()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("flush of the newer report: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
close(release)
|
||||||
|
|
||||||
|
err = <-flushed
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("flush of the older report: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
info, err := os.Stat(newer)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("stat %s: %v", newer, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A report that fits beside the newer file alone, so deleting the
|
||||||
|
// older one makes exactly the room it needs.
|
||||||
|
pad := maxBytes - int(info.Size()) - lineBytes(t, map[string]string{"id": ""})
|
||||||
|
|
||||||
|
err = buf.Append(map[string]string{"id": strings.Repeat("a", pad)})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("report that needs room: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if exists(t, older) {
|
||||||
|
t.Fatal("older report file kept when room was needed")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !exists(t, newer) {
|
||||||
|
t.Fatal("newer report file deleted before the older one")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+20
-2
@@ -14,16 +14,34 @@ set -eu
|
|||||||
|
|
||||||
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
||||||
|
|
||||||
|
# The sha256 of the org standard .golangci.yml. When that file changes in
|
||||||
|
# sneak/prompts and is copied here again, this changes with it.
|
||||||
GOLANGCI_CONFIG_SHA256="a79b63a254602a5318db5d0e9a06bc71b84bf0c1d896305229d8bfed1d1b1776"
|
GOLANGCI_CONFIG_SHA256="a79b63a254602a5318db5d0e9a06bc71b84bf0c1d896305229d8bfed1d1b1776"
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
cd "$ROOT"
|
cd "$ROOT"
|
||||||
|
if [ ! -f .golangci.yml ]; then
|
||||||
|
echo "backend/.golangci.yml is missing. Copy the org standard verbatim" >&2
|
||||||
|
echo "from https://git.eeqj.de/sneak/prompts/raw/branch/main/.golangci.yml" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
actual="$(sha256sum .golangci.yml | cut -d' ' -f1)"
|
actual="$(sha256sum .golangci.yml | cut -d' ' -f1)"
|
||||||
|
if [ -z "$actual" ]; then
|
||||||
|
echo "sha256sum is missing or printed no hash, so" >&2
|
||||||
|
echo "backend/.golangci.yml could not be checked." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
if [ "$actual" != "$GOLANGCI_CONFIG_SHA256" ]; then
|
if [ "$actual" != "$GOLANGCI_CONFIG_SHA256" ]; then
|
||||||
echo ".golangci.yml has drifted from the org standard." >&2
|
echo "backend/.golangci.yml does not match GOLANGCI_CONFIG_SHA256" >&2
|
||||||
|
echo "in backend/script/lint." >&2
|
||||||
echo " expected $GOLANGCI_CONFIG_SHA256" >&2
|
echo " expected $GOLANGCI_CONFIG_SHA256" >&2
|
||||||
echo " actual $actual" >&2
|
echo " actual $actual" >&2
|
||||||
echo "Restore it verbatim from sneak/prompts; do not edit it." >&2
|
echo "Compare it with the org standard," >&2
|
||||||
|
echo "https://git.eeqj.de/sneak/prompts/raw/branch/main/.golangci.yml" >&2
|
||||||
|
echo "- If they differ, it was edited here: restore the org standard" >&2
|
||||||
|
echo " verbatim. Do not edit it." >&2
|
||||||
|
echo "- If they are the same, the org standard changed: set" >&2
|
||||||
|
echo " GOLANGCI_CONFIG_SHA256 in backend/script/lint to the actual hash." >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
golangci-lint run ./...
|
golangci-lint run ./...
|
||||||
|
|||||||
Reference in New Issue
Block a user