package utils
import (
"context"
"fmt"
"math/rand"
"net"
"os"
"slices"
"strconv"
"strings"
"sync"
"testing"
"time"
"github.com/shirou/gopsutil/v3/process"
"golang.org/x/exp/maps"
ss "github.com/pancsta/asyncmachine-go/internal/testing/states"
am "github.com/pancsta/asyncmachine-go/pkg/machine"
ssnode "github.com/pancsta/asyncmachine-go/pkg/node/states"
ssrpc "github.com/pancsta/asyncmachine-go/pkg/rpc/states"
)
const EnvAmTestRunner = "AM_TEST_RUNNER"
var (
ports []int
ConnInit sync .Mutex
)
func RandPort (min , max int ) string {
p := rand .Intn (max -min +1 ) + min
for slices .Contains (ports , p ) {
p = rand .Intn (max -min +1 ) + min
}
ports = append (ports , p )
return strconv .Itoa (p )
}
func RandListener (host string ) net .Listener {
ConnInit .Lock ()
defer ConnInit .Unlock ()
l , err := net .Listen ("tcp4" , host +":0" )
if err == nil {
return l
}
panic ("could not create listener on " + host )
}
func NewRels (t *testing .T , initialState am .S ) *am .Machine {
mach := am .New (context .Background (), ss .States , &am .Opts {
Id : "t-" + t .Name ()})
err := mach .VerifyStates (ss .Names )
if err != nil {
t .Fatal (err )
}
mach .SemLogger ().SetLevel (am .EnvLogLevel (os .Getenv (am .EnvAmLog )))
if os .Getenv (am .EnvAmDebug ) != "" && os .Getenv (EnvAmTestRunner ) == "" {
mach .HandlerTimeout = 2 * time .Minute
}
if initialState != nil {
mach .Set (initialState , nil )
}
mach .SemLogger ().SetArgsMapper (am .NewArgsMapper (am .LogArgs , 50 ))
return mach
}
func NewRelsRpcWorker (t *testing .T , initialState am .S ) *am .Machine {
ssStruct := am .SchemaMerge (ssrpc .WorkerSchema , ss .States )
ssNames := am .SAdd (ss .Names , ssrpc .WorkerStates .Names ())
mach := am .New (context .Background (), ssStruct , &am .Opts {
Id : "t-" + t .Name ()})
err := mach .VerifyStates (ssNames )
if err != nil {
t .Fatal (err )
}
mach .SemLogger ().SetLevel (am .EnvLogLevel (os .Getenv (am .EnvAmLog )))
if os .Getenv (am .EnvAmDebug ) != "" && os .Getenv (EnvAmTestRunner ) == "" {
mach .HandlerTimeout = 2 * time .Minute
}
if initialState != nil {
mach .Set (initialState , nil )
}
return mach
}
var (
RelsNodeWorkerSchema = am .SchemaMerge (ssnode .WorkerSchema , ss .States )
RelsNodeWorkerStates = am .SAdd (ss .Names , ssnode .WorkerStates .Names ())
)
func NewRelsNodeWorker (t *testing .T , initialState am .S ) *am .Machine {
mach := am .New (context .Background (), RelsNodeWorkerSchema , &am .Opts {
Id : "t-" + t .Name ()})
err := mach .VerifyStates (RelsNodeWorkerStates )
if err != nil {
t .Fatal (err )
}
mach .SemLogger ().SetLevel (am .EnvLogLevel (os .Getenv (am .EnvAmLog )))
if os .Getenv (am .EnvAmDebug ) != "" && os .Getenv (EnvAmTestRunner ) == "" {
mach .HandlerTimeout = 2 * time .Minute
}
if initialState != nil {
mach .Set (initialState , nil )
}
mach .SemLogger ().SetArgsMapper (am .NewArgsMapper (am .LogArgs , 50 ))
return mach
}
func NewNoRels (t *testing .T , initialState am .S ) *am .Machine {
mach := am .New (context .Background (), am .Schema {
ss .A : {},
ss .B : {},
ss .C : {},
ss .D : {},
}, &am .Opts {Id : "t-" + t .Name ()})
err := mach .VerifyStates (ss .Names )
if err != nil {
t .Fatal (err )
}
mach .SemLogger ().SetLevel (am .EnvLogLevel (os .Getenv (am .EnvAmLog )))
if os .Getenv (am .EnvAmDebug ) != "" && os .Getenv (EnvAmTestRunner ) == "" {
mach .HandlerTimeout = 2 * time .Minute
}
if initialState != nil {
mach .Set (initialState , nil )
}
mach .SemLogger ().SetArgsMapper (am .NewArgsMapper (am .LogArgs , 50 ))
return mach
}
func NewNoRelsRpcWorker (t *testing .T , initialState am .S ) *am .Machine {
ssStruct := am .SchemaMerge (ssrpc .WorkerSchema , am .Schema {
ss .A : {},
ss .B : {},
ss .C : {},
ss .D : {},
})
ssNames := am .SAdd (ss .Names , ssrpc .WorkerStates .Names ())
mach := am .New (context .Background (), ssStruct , &am .Opts {Id : "t-" + t .Name ()})
err := mach .VerifyStates (ssNames )
if err != nil {
t .Fatal (err )
}
mach .SemLogger ().SetLevel (am .EnvLogLevel (os .Getenv (am .EnvAmLog )))
if os .Getenv (am .EnvAmDebug ) != "" && os .Getenv (EnvAmTestRunner ) == "" {
mach .HandlerTimeout = 2 * time .Minute
}
if initialState != nil {
mach .Set (initialState , nil )
}
mach .SemLogger ().SetArgsMapper (am .NewArgsMapper (am .LogArgs , 50 ))
return mach
}
func NewNoRelsRpcWorkerSchema (
t *testing .T , initialState am .S , overlay am .Schema ) *am .Machine {
ssStruct := am .SchemaMerge (ssrpc .WorkerSchema , am .SchemaMerge (am .Schema {
ss .A : {},
ss .B : {},
ss .C : {},
ss .D : {},
}, overlay ))
ssNames := am .SAdd (ss .Names , ssrpc .WorkerStates .Names ())
mach := am .New (context .Background (), ssStruct , &am .Opts {Id : "t-" + t .Name ()})
err := mach .VerifyStates (ssNames )
if err != nil {
t .Fatal (err )
}
mach .SemLogger ().SetLevel (am .EnvLogLevel (os .Getenv (am .EnvAmLog )))
if os .Getenv (am .EnvAmDebug ) != "" && os .Getenv (EnvAmTestRunner ) == "" {
mach .HandlerTimeout = 2 * time .Minute
}
if initialState != nil {
mach .Set (initialState , nil )
}
mach .SemLogger ().SetArgsMapper (am .NewArgsMapper (am .LogArgs , 50 ))
return mach
}
func NewCustom (t *testing .T , states am .Schema ) *am .Machine {
mach := am .New (context .Background (), states , &am .Opts {
Id : "t-" + t .Name ()})
err := mach .VerifyStates (append (maps .Keys (states ), am .StateException ))
if err != nil {
t .Fatal (err )
}
mach .SemLogger ().SetLevel (am .EnvLogLevel (os .Getenv (am .EnvAmLog )))
if os .Getenv (am .EnvAmDebug ) != "" && os .Getenv (EnvAmTestRunner ) == "" {
mach .HandlerTimeout = 2 * time .Minute
}
mach .SemLogger ().SetArgsMapper (am .NewArgsMapper (am .LogArgs , 50 ))
return mach
}
func NewCustomRpcWorker (t *testing .T , states am .Schema ) *am .Machine {
ssStruct := am .SchemaMerge (ssrpc .WorkerSchema , states )
ssNames := am .SAdd (maps .Keys (states ), ssrpc .WorkerStates .Names ())
mach := am .New (context .Background (), ssStruct , &am .Opts {
Id : "t-" + t .Name ()})
err := mach .VerifyStates (ssNames )
if err != nil {
t .Fatal (err )
}
mach .SemLogger ().SetLevel (am .EnvLogLevel (os .Getenv (am .EnvAmLog )))
if os .Getenv (am .EnvAmDebug ) != "" && os .Getenv (EnvAmTestRunner ) == "" {
mach .HandlerTimeout = 2 * time .Minute
}
mach .SemLogger ().SetArgsMapper (am .NewArgsMapper (am .LogArgs , 50 ))
return mach
}
func KillProcessesByName (
processName string ) (killedPIDs []int32 , errs []error ) {
processes , err := process .Processes ()
if err != nil {
err := fmt .Errorf ("failed to get process list: %w" , err )
return nil , []error {err }
}
for _ , p := range processes {
name , err := p .Name ()
if err != nil {
continue
}
if strings .EqualFold (name , processName ) {
pid := p .Pid
if termErr := p .Terminate (); termErr != nil {
if os .IsPermission (termErr ) {
err := fmt .Errorf ("permission denied for PID %d (%s): %w" ,
pid , name , termErr )
errs = append (errs , err )
continue
}
if killErr := p .Kill (); killErr != nil {
killErr := fmt .Errorf ("failed to force kill PID %d (%s): %w" ,
pid , name , killErr )
errs = append (errs , killErr )
continue
}
}
time .Sleep (50 * time .Millisecond )
stillRunning , checkErr := p .IsRunning ()
if checkErr == nil && !stillRunning {
killedPIDs = append (killedPIDs , pid )
} else if checkErr != nil {
errs = append (errs , fmt .Errorf (
"could not verify status of PID %d (%s): %w" , pid , name , checkErr ))
} else {
errs = append (errs , fmt .Errorf (
"PID %d (%s) is still running after termination attempts" ,
pid , name ))
}
}
}
return killedPIDs , errs
}
The pages are generated with Golds v0.8.2 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .