mirror of
https://github.com/Buriburizaem0n/nezha_domains.git
synced 2026-09-21 02:30:14 +00:00
test(agentcompat): add exact stress scenario
Co-authored-by: naiba/CloudCode <hi+cloudcode@nai.ba>
This commit is contained in:
@@ -0,0 +1,174 @@
|
||||
//go:build linux && agentcompat
|
||||
|
||||
package scenario
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/nezhahq/nezha/integration/agentcompat/internal/client"
|
||||
processharness "github.com/nezhahq/nezha/integration/agentcompat/internal/process"
|
||||
)
|
||||
|
||||
type stressSQLiteHoldControlProbe struct {
|
||||
mu sync.Mutex
|
||||
calls []string
|
||||
started chan struct{}
|
||||
done chan struct{}
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
func newStressSQLiteHoldControlProbe() *stressSQLiteHoldControlProbe {
|
||||
return &stressSQLiteHoldControlProbe{started: make(chan struct{}), done: make(chan struct{})}
|
||||
}
|
||||
|
||||
func (probe *stressSQLiteHoldControlProbe) record(call string) {
|
||||
probe.mu.Lock()
|
||||
defer probe.mu.Unlock()
|
||||
probe.calls = append(probe.calls, call)
|
||||
}
|
||||
|
||||
func (probe *stressSQLiteHoldControlProbe) ArmSQLiteHold(context.Context) (client.SQLiteHoldReceipt, error) {
|
||||
probe.record("arm")
|
||||
return client.SQLiteHoldReceipt{ID: "ERERERERERERERERERERERERERERERERERERERERERE", State: client.SQLiteHoldStateArmed}, nil
|
||||
}
|
||||
|
||||
func (probe *stressSQLiteHoldControlProbe) WaitForSQLiteHold(ctx context.Context, receipt client.SQLiteHoldReceipt, target client.SQLiteHoldState) (client.SQLiteHoldReceipt, error) {
|
||||
if target == client.SQLiteHoldStateSelected {
|
||||
select {
|
||||
case <-probe.started:
|
||||
case <-ctx.Done():
|
||||
return client.SQLiteHoldReceipt{}, ctx.Err()
|
||||
}
|
||||
}
|
||||
probe.record("wait-" + string(target))
|
||||
receipt.State = target
|
||||
return receipt, nil
|
||||
}
|
||||
|
||||
func (probe *stressSQLiteHoldControlProbe) ReleaseSQLiteHold(context.Context, client.SQLiteHoldReceipt) (client.SQLiteHoldReceipt, error) {
|
||||
probe.record("release")
|
||||
probe.once.Do(func() { close(probe.done) })
|
||||
return client.SQLiteHoldReceipt{State: client.SQLiteHoldStateReleased}, nil
|
||||
}
|
||||
|
||||
func (probe *stressSQLiteHoldControlProbe) AbortSQLiteHold(context.Context, client.SQLiteHoldReceipt) (client.SQLiteHoldReceipt, error) {
|
||||
probe.record("abort")
|
||||
probe.once.Do(func() { close(probe.done) })
|
||||
return client.SQLiteHoldReceipt{State: client.SQLiteHoldStateAborted}, nil
|
||||
}
|
||||
|
||||
type stressSQLiteJournalWatchProbe struct {
|
||||
control *stressSQLiteHoldControlProbe
|
||||
}
|
||||
|
||||
func (watch stressSQLiteJournalWatchProbe) Wait(ctx context.Context) error {
|
||||
select {
|
||||
case <-watch.control.done:
|
||||
watch.control.record("watch-wait")
|
||||
return nil
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
func (watch stressSQLiteJournalWatchProbe) Close() error {
|
||||
watch.control.record("watch-close")
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestDrainStressSQLiteJournalOrdersHoldLifecycleBeforeCompletion(t *testing.T) {
|
||||
// Given
|
||||
control := newStressSQLiteHoldControlProbe()
|
||||
journalPath := filepath.Join(t.TempDir(), "dashboard.sqlite-journal")
|
||||
|
||||
// When
|
||||
err := drainStressSQLiteJournal(t.Context(), control, func(ctx context.Context) error {
|
||||
control.record("writer-start")
|
||||
close(control.started)
|
||||
select {
|
||||
case <-control.done:
|
||||
control.record("writer-complete")
|
||||
return nil
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
}, journalPath, func(path string) (stressSQLiteJournalWatch, error) {
|
||||
require.Equal(t, journalPath, path)
|
||||
control.record("watch-open")
|
||||
return stressSQLiteJournalWatchProbe{control: control}, nil
|
||||
})
|
||||
|
||||
// Then
|
||||
require.NoError(t, err)
|
||||
control.mu.Lock()
|
||||
calls := append([]string(nil), control.calls...)
|
||||
control.mu.Unlock()
|
||||
require.Less(t, callIndex(calls, "arm"), callIndex(calls, "writer-start"))
|
||||
require.Less(t, callIndex(calls, "writer-start"), callIndex(calls, "wait-selected"))
|
||||
require.Less(t, callIndex(calls, "wait-selected"), callIndex(calls, "wait-finalizing"))
|
||||
require.Less(t, callIndex(calls, "wait-finalizing"), callIndex(calls, "watch-open"))
|
||||
require.Less(t, callIndex(calls, "watch-open"), callIndex(calls, "release"))
|
||||
require.Less(t, callIndex(calls, "release"), callIndex(calls, "watch-wait"))
|
||||
require.Less(t, callIndex(calls, "release"), callIndex(calls, "writer-complete"))
|
||||
require.Less(t, callIndex(calls, "watch-wait"), callIndex(calls, "watch-close"))
|
||||
}
|
||||
|
||||
func TestDrainStressSQLiteJournalAbortsWriterWhenWatchCannotOpen(t *testing.T) {
|
||||
// Given
|
||||
control := newStressSQLiteHoldControlProbe()
|
||||
watchErr := errors.New("watch unavailable")
|
||||
|
||||
// When
|
||||
err := drainStressSQLiteJournal(t.Context(), control, func(ctx context.Context) error {
|
||||
close(control.started)
|
||||
select {
|
||||
case <-control.done:
|
||||
return nil
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
}, filepath.Join(t.TempDir(), "dashboard.sqlite-journal"), func(string) (stressSQLiteJournalWatch, error) {
|
||||
return nil, watchErr
|
||||
})
|
||||
|
||||
// Then
|
||||
require.ErrorIs(t, err, watchErr)
|
||||
control.mu.Lock()
|
||||
calls := append([]string(nil), control.calls...)
|
||||
control.mu.Unlock()
|
||||
require.Contains(t, calls, "abort")
|
||||
}
|
||||
|
||||
func TestObserveStressDashboardSQLiteJournalRejectsFirstHeldSample(t *testing.T) {
|
||||
// Given
|
||||
path := filepath.Join(t.TempDir(), "dashboard.sqlite-journal")
|
||||
require.NoError(t, os.WriteFile(path, []byte("journal"), 0o600))
|
||||
file, err := os.Open(path)
|
||||
require.NoError(t, err)
|
||||
observer := observeStressDashboardSQLiteJournal(path)
|
||||
|
||||
// When
|
||||
heldErr := observer(t.Context(), processharness.Sample{PID: os.Getpid()})
|
||||
require.NoError(t, file.Close())
|
||||
releasedErr := observer(t.Context(), processharness.Sample{PID: os.Getpid()})
|
||||
|
||||
// Then
|
||||
require.ErrorIs(t, heldErr, ErrStressSQLiteJournalNotDrained)
|
||||
require.NoError(t, releasedErr)
|
||||
}
|
||||
|
||||
func callIndex(calls []string, wanted string) int {
|
||||
for index, call := range calls {
|
||||
if call == wanted {
|
||||
return index
|
||||
}
|
||||
}
|
||||
return len(calls)
|
||||
}
|
||||
Reference in New Issue
Block a user