The live tests reached one shared bus and assert, read and remove the mesh's own objects by their fixed names, so packages run in parallel deleted what each other read and the suite passed only one package at a time; a red suite read as noise. internal/testbus starts a server per test, linked in at the nats-server release go.mod pins, and a test holds that pin to the catalogue's bus image and to the facts snapshot's bus when there is one, so the tests never run a bus the mesh does not. The waiter test read a timing (the most connections held at one look) and now reads the state it means (the fewest held across the wait). make check runs the packages in parallel under the race detector, with a timeout.
324 lines
8.2 KiB
Go
324 lines
8.2 KiB
Go
//go:build enable_antithesis_sdk
|
|
|
|
package assert
|
|
|
|
import (
|
|
"math"
|
|
"sync"
|
|
|
|
"github.com/antithesishq/antithesis-sdk-go/internal"
|
|
)
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// IntegerGap is used for:
|
|
// - int, int8, int16, int32, int64:
|
|
// - uint, uint8, uint16, uint32, uint64, uintptr:
|
|
//
|
|
// FloatGap is used for:
|
|
// - float32, float64
|
|
// --------------------------------------------------------------------------------
|
|
type numericGapType int
|
|
|
|
const (
|
|
integerGapType numericGapType = iota
|
|
floatGapType
|
|
)
|
|
|
|
func gapTypeForOperand[T Number](num T) numericGapType {
|
|
gapType := integerGapType
|
|
|
|
switch any(num).(type) {
|
|
case float32, float64:
|
|
gapType = floatGapType
|
|
}
|
|
return gapType
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// numericGuidanceTracker - Tracking Info for Numeric Guidance
|
|
//
|
|
// For GuidanceFnMaximize:
|
|
// - gap is the largest value sent so far
|
|
//
|
|
// For GuidanceFnMinimize:
|
|
// - gap is the most negative value sent so far
|
|
//
|
|
// --------------------------------------------------------------------------------
|
|
type numericGuidanceInfo struct {
|
|
gap any
|
|
descriminator numericGapType
|
|
maximize bool
|
|
}
|
|
|
|
type numericGuidanceTracker map[string]*numericGuidanceInfo
|
|
|
|
var (
|
|
numeric_guidance_tracker numericGuidanceTracker = make(numericGuidanceTracker)
|
|
numeric_guidance_tracker_mutex sync.Mutex
|
|
numeric_guidance_info_mutex sync.Mutex
|
|
)
|
|
|
|
func (tracker numericGuidanceTracker) getTrackerEntry(messageKey string, trackerType numericGapType, maximize bool) *numericGuidanceInfo {
|
|
var trackerEntry *numericGuidanceInfo
|
|
var ok bool
|
|
|
|
if tracker == nil {
|
|
return nil
|
|
}
|
|
|
|
numeric_guidance_tracker_mutex.Lock()
|
|
defer numeric_guidance_tracker_mutex.Unlock()
|
|
if trackerEntry, ok = numeric_guidance_tracker[messageKey]; !ok {
|
|
trackerEntry = newNumericGuidanceInfo(trackerType, maximize)
|
|
tracker[messageKey] = trackerEntry
|
|
}
|
|
|
|
return trackerEntry
|
|
}
|
|
|
|
// Create an numeric guidance entry
|
|
func newNumericGuidanceInfo(trackerType numericGapType, maximize bool) *numericGuidanceInfo {
|
|
|
|
var gap any
|
|
if trackerType == integerGapType {
|
|
gap = newGapValue(uint64(math.MaxUint64), maximize)
|
|
} else {
|
|
gap = newGapValue(float64(math.MaxFloat64), maximize)
|
|
}
|
|
trackerInfo := numericGuidanceInfo{
|
|
maximize: maximize,
|
|
descriminator: trackerType,
|
|
gap: gap,
|
|
}
|
|
return &trackerInfo
|
|
}
|
|
|
|
func (tI *numericGuidanceInfo) should_maximize() bool {
|
|
return tI.maximize
|
|
}
|
|
|
|
func (tI *numericGuidanceInfo) is_integer_gap() bool {
|
|
return tI.descriminator == integerGapType
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// Represents integral and floating point extremes
|
|
// --------------------------------------------------------------------------------
|
|
type gapValue[T numConstraint] struct {
|
|
gap_size T
|
|
gap_is_negative bool
|
|
}
|
|
|
|
func newGapValue[T numConstraint](sz T, is_neg bool) any {
|
|
switch any(sz).(type) {
|
|
case uint64:
|
|
return gapValue[uint64]{gap_size: uint64(sz), gap_is_negative: is_neg}
|
|
|
|
case float64:
|
|
return gapValue[float64]{gap_size: float64(sz), gap_is_negative: is_neg}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func is_same_sign(left_val int64, right_val int64) bool {
|
|
same_sign := false
|
|
if left_val < 0 {
|
|
// left is negative
|
|
if right_val < 0 {
|
|
same_sign = true
|
|
}
|
|
} else {
|
|
// left is non-negative
|
|
if right_val >= 0 {
|
|
same_sign = true
|
|
}
|
|
}
|
|
return same_sign
|
|
}
|
|
|
|
func abs_int64(val int64) uint64 {
|
|
if val >= 0 {
|
|
return uint64(val)
|
|
}
|
|
return uint64(0 - val)
|
|
}
|
|
|
|
func is_greater_than[T numConstraint](left gapValue[T], right gapValue[T]) bool {
|
|
if !left.gap_is_negative && !right.gap_is_negative {
|
|
return left.gap_size > right.gap_size
|
|
}
|
|
if !left.gap_is_negative && right.gap_is_negative {
|
|
return true // any positive is greater than a negative
|
|
}
|
|
if left.gap_is_negative && right.gap_is_negative {
|
|
return right.gap_size > left.gap_size
|
|
}
|
|
if left.gap_is_negative && !right.gap_is_negative {
|
|
return false // any negative is less than a positive
|
|
}
|
|
return false
|
|
}
|
|
|
|
func is_less_than[T numConstraint](left gapValue[T], right gapValue[T]) bool {
|
|
if !left.gap_is_negative && !right.gap_is_negative {
|
|
return left.gap_size < right.gap_size
|
|
}
|
|
if !left.gap_is_negative && right.gap_is_negative {
|
|
return false // any positive is greater than a negative
|
|
}
|
|
if left.gap_is_negative && right.gap_is_negative {
|
|
return right.gap_size < left.gap_size
|
|
}
|
|
if left.gap_is_negative && !right.gap_is_negative {
|
|
return true // any negative is less than a positive
|
|
}
|
|
return true
|
|
}
|
|
|
|
func send_value_if_needed(tI *numericGuidanceInfo, gI *guidanceInfo) {
|
|
if tI == nil {
|
|
return
|
|
}
|
|
|
|
numeric_guidance_info_mutex.Lock()
|
|
defer numeric_guidance_info_mutex.Unlock()
|
|
|
|
// if this is a catalog entry (gI.hit is false)
|
|
// do not update the reference gap in the tracker (tI *numericGuidanceInfo)
|
|
if !gI.Hit {
|
|
emitGuidance(gI)
|
|
return
|
|
}
|
|
|
|
should_send := false
|
|
maximize := tI.should_maximize()
|
|
|
|
var gap gapValue[uint64]
|
|
var float_gap gapValue[float64]
|
|
|
|
// Needs to have individual case statements to assist
|
|
// the compiler to infer the actual type of the var named 'operands'
|
|
switch operands := (gI.Data).(type) {
|
|
case numericOperands[int32]:
|
|
gap = makeGap(operands)
|
|
case numericOperands[int64]:
|
|
gap = makeGap(operands)
|
|
case numericOperands[uint64]:
|
|
gap = makeGap(operands)
|
|
case numericOperands[float64]:
|
|
float_gap = makeFloatGap(operands)
|
|
}
|
|
|
|
var prev_gap gapValue[uint64]
|
|
var prev_float_gap gapValue[float64]
|
|
has_prev_gap := false
|
|
has_prev_float_gap := false
|
|
|
|
prev_gap, has_prev_gap = tI.gap.(gapValue[uint64])
|
|
if !has_prev_gap {
|
|
prev_float_gap, has_prev_float_gap = tI.gap.(gapValue[float64])
|
|
}
|
|
|
|
if has_prev_gap {
|
|
if maximize {
|
|
should_send = is_greater_than(gap, prev_gap)
|
|
} else {
|
|
should_send = is_less_than(gap, prev_gap)
|
|
}
|
|
}
|
|
|
|
if has_prev_float_gap {
|
|
if maximize {
|
|
should_send = is_greater_than(float_gap, prev_float_gap)
|
|
} else {
|
|
should_send = is_less_than(float_gap, prev_float_gap)
|
|
}
|
|
}
|
|
|
|
if should_send {
|
|
if tI.is_integer_gap() {
|
|
tI.gap = gap
|
|
} else {
|
|
tI.gap = float_gap
|
|
}
|
|
emitGuidance(gI)
|
|
}
|
|
}
|
|
|
|
func emitGuidance(gI *guidanceInfo) error {
|
|
return internal.Json_data(map[string]any{"antithesis_guidance": gI})
|
|
}
|
|
|
|
// When left and right are the same sign (both negative, or both non-negative)
|
|
// Calculate: <result> = (left - right). The gap_size is abs(<result>) and
|
|
// gap_is_negative is (right > left)
|
|
func makeGap[Op operandConstraint](operand numericOperands[Op]) gapValue[uint64] {
|
|
|
|
var gap_size uint64
|
|
var gap_is_negative bool
|
|
|
|
switch this_op := any(operand).(type) {
|
|
case numericOperands[int32]:
|
|
result := int64(this_op.Left) - int64(this_op.Right)
|
|
gap_size = abs_int64(result)
|
|
gap_is_negative = result < 0
|
|
|
|
case numericOperands[int64]:
|
|
if is_same_sign(this_op.Left, this_op.Right) {
|
|
result := int64(this_op.Left) - int64(this_op.Right)
|
|
gap_size = abs_int64(result)
|
|
gap_is_negative = result < 0
|
|
break
|
|
}
|
|
|
|
// Otherwise left and right are opposite signs
|
|
// gap = abs(left) + abs(right)
|
|
// gap_is_negative = left < right
|
|
left_gap_size := abs_int64(this_op.Left)
|
|
right_gap_size := abs_int64(this_op.Right)
|
|
gap_size = left_gap_size + right_gap_size
|
|
gap_is_negative = this_op.Left < this_op.Right
|
|
|
|
case numericOperands[uint64]:
|
|
left_val := this_op.Left
|
|
right_val := this_op.Right
|
|
gap_is_negative = false
|
|
if left_val < right_val {
|
|
gap_is_negative = true
|
|
gap_size = right_val - left_val
|
|
} else {
|
|
gap_size = left_val - right_val
|
|
}
|
|
|
|
default:
|
|
zero_gap, _ := newGapValue(uint64(0), false).(gapValue[uint64])
|
|
return zero_gap
|
|
}
|
|
|
|
this_gap, _ := newGapValue(gap_size, gap_is_negative).(gapValue[uint64])
|
|
return this_gap
|
|
} // MakeGap
|
|
|
|
func makeFloatGap[Op operandConstraint](operand numericOperands[Op]) gapValue[float64] {
|
|
switch this_op := any(operand).(type) {
|
|
case numericOperands[float64]:
|
|
left_val := this_op.Left
|
|
right_val := this_op.Right
|
|
gap_is_negative := false
|
|
var gap_size float64
|
|
if left_val < right_val {
|
|
gap_is_negative = true
|
|
gap_size = right_val - left_val
|
|
} else {
|
|
gap_size = left_val - right_val
|
|
}
|
|
|
|
this_gap, _ := newGapValue(gap_size, gap_is_negative).(gapValue[float64])
|
|
return this_gap
|
|
|
|
default:
|
|
zero_gap, _ := newGapValue(float64(0.0), false).(gapValue[float64])
|
|
return zero_gap
|
|
}
|
|
} // MakeFloatGap
|