Initial QSfera import

This commit is contained in:
Курнат Андрей
2026-06-07 10:20:04 +03:00
commit 2315f25754
16485 changed files with 4826827 additions and 0 deletions
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 Antithesis
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -0,0 +1,214 @@
//go:build enable_antithesis_sdk
// Package assert enables defining [test properties] about your program or [workload]. It is part of the [Antithesis Go SDK], which enables Go applications to integrate with the [Antithesis platform].
//
// Code that uses this package should be instrumented with the [antithesis-go-generator] utility. This step is required for the Always, Sometime, and Reachable methods. It is not required for the Unreachable and AlwaysOrUnreachable methods, but it will improve the experience of using them.
//
// These functions are no-ops with minimal performance overhead when called outside of the Antithesis environment. However, if the environment variable ANTITHESIS_SDK_LOCAL_OUTPUT is set, these functions will log to the file pointed to by that variable using a structured JSON format defined [here]. This allows you to make use of the Antithesis assertions package in your regular testing, or even in production. In particular, very few assertions frameworks offer a convenient way to define [Sometimes assertions], but they can be quite useful even outside Antithesis.
//
// Each function in this package takes a parameter called message, which is a human readable identifier used to aggregate assertions. Antithesis generates one test property per unique message and this test property will be named "<message>" in the [triage report].
//
// This test property either passes or fails, which depends upon the evaluation of every assertion that shares its message. Different assertions in different parts of the code should have different message, but the same assertion should always have the same message even if it is moved to a different file.
//
// Each function also takes a parameter called details, which is a key-value map of optional additional information provided by the user to add context for assertion failures. The information that is logged will appear in the [triage report], under the details section of the corresponding property. Normally the values passed to details are evaluated at runtime.
//
// [Antithesis Go SDK]: https://antithesis.com/docs/using_antithesis/sdk/go/
// [Antithesis platform]: https://antithesis.com
// [test properties]: https://antithesis.com/docs/properties_assertions/properties/
// [workload]: https://antithesis.com/docs/test_templates/first_test/
// [antithesis-go-generator]: https://antithesis.com/docs/using_antithesis/sdk/go/instrumentor/
// [triage report]: https://antithesis.com/docs/reports/
// [here]: https://antithesis.com/docs/using_antithesis/sdk/fallback/
// [Sometimes assertions]: https://antithesis.com/docs/best_practices/sometimes_assertions/
//
// [details]: https://antithesis.com/docs/reports/
package assert
import (
"encoding/json"
"fmt"
)
type assertInfo struct {
Location *locationInfo `json:"location"`
Details map[string]any `json:"details"`
AssertType string `json:"assert_type"`
DisplayType string `json:"display_type"`
Message string `json:"message"`
Id string `json:"id"`
Hit bool `json:"hit"`
MustHit bool `json:"must_hit"`
Condition bool `json:"condition"`
}
// Create a custom json marshaler for assertInfo so that we can force Errors to be marshaled with their error details.
// Without this, custom errors are marshaled as an empty object because the default json marshaler doesn't include the error
// (because it's a method - not an exported struct field).
func (f assertInfo) MarshalJSON() ([]byte, error) {
type alias assertInfo // prevent infinite recursion
a := alias(f)
if a.Details != nil {
a.Details = normalizeMap(a.Details)
}
return json.Marshal(a)
}
type jsonError struct {
innerError error
}
func (e jsonError) MarshalJSON() ([]byte, error) {
// Marshal this as the debug output string instead of e.Error(). These should be equivalent, but Sprintf correctly
// handles nil values for us (which otherwise are annoying to defend against due to this - https://go.dev/doc/faq#nil_error)
return json.Marshal(fmt.Sprintf("%+v", e.innerError))
}
// Recursively replace any `error` with jsonError while doing a deep copy.
// Most of the logic is in the normalize method below. This method exists to localize the type assertions
// and provide a function that takes in/out a map instead of any.
func normalizeMap(v map[string]any) map[string]any {
return normalize(v).(map[string]any)
}
func normalize(input any) any {
// This switch will miss some cases (pointers, structs, non-any types), but should catch a very large proportion of real error interfaces
// in real details objects. We can augment this if we find other cases common enough to support.
switch inputTyped := input.(type) {
case error:
// Check if the underlying error implements json.Marshaler, so that if the error
// already knows who to marshal itself, we don't override that.
if _, ok := inputTyped.(json.Marshaler); ok {
return inputTyped
} else {
return jsonError{inputTyped}
}
case map[string]any:
out := make(map[string]any, len(inputTyped))
for k, v := range inputTyped {
out[k] = normalize(v)
}
return out
case []any:
out := make([]any, len(inputTyped))
for i := range inputTyped {
out[i] = normalize(inputTyped[i])
}
return out
default:
return input
}
}
type wrappedAssertInfo struct {
A *assertInfo `json:"antithesis_assert"`
}
// --------------------------------------------------------------------------------
// Assertions
// --------------------------------------------------------------------------------
const (
wasHit = true
mustBeHit = true
optionallyHit = false
expectingTrue = true
)
const (
universalTest = "always"
existentialTest = "sometimes"
reachabilityTest = "reachability"
)
const (
alwaysDisplay = "Always"
alwaysOrUnreachableDisplay = "AlwaysOrUnreachable"
sometimesDisplay = "Sometimes"
reachableDisplay = "Reachable"
unreachableDisplay = "Unreachable"
)
// Always asserts that condition is true every time this function is called, and that it is called at least once. The corresponding test property will be viewable in the Antithesis SDK: Always group of your triage report.
func Always(condition bool, message string, details map[string]any) {
locationInfo := newLocationInfo(offsetAPICaller)
id := makeKey(message, locationInfo)
assertImpl(condition, message, details, locationInfo, wasHit, mustBeHit, universalTest, alwaysDisplay, id)
}
// AlwaysOrUnreachable asserts that condition is true every time this function is called. The corresponding test property will pass if the assertion is never encountered (unlike Always assertion types). This test property will be viewable in the “Antithesis SDK: Always” group of your triage report.
func AlwaysOrUnreachable(condition bool, message string, details map[string]any) {
locationInfo := newLocationInfo(offsetAPICaller)
id := makeKey(message, locationInfo)
assertImpl(condition, message, details, locationInfo, wasHit, optionallyHit, universalTest, alwaysOrUnreachableDisplay, id)
}
// Sometimes asserts that condition is true at least one time that this function was called. (If the assertion is never encountered, the test property will therefore fail.) This test property will be viewable in the “Antithesis SDK: Sometimes” group.
func Sometimes(condition bool, message string, details map[string]any) {
locationInfo := newLocationInfo(offsetAPICaller)
id := makeKey(message, locationInfo)
assertImpl(condition, message, details, locationInfo, wasHit, mustBeHit, existentialTest, sometimesDisplay, id)
}
// Unreachable asserts that a line of code is never reached. The corresponding test property will fail if this function is ever called. (If it is never called the test property will therefore pass.) This test property will be viewable in the “Antithesis SDK: Reachablity assertions” group.
func Unreachable(message string, details map[string]any) {
locationInfo := newLocationInfo(offsetAPICaller)
id := makeKey(message, locationInfo)
assertImpl(false, message, details, locationInfo, wasHit, optionallyHit, reachabilityTest, unreachableDisplay, id)
}
// Reachable asserts that a line of code is reached at least once. The corresponding test property will pass if this function is ever called. (If it is never called the test property will therefore fail.) This test property will be viewable in the “Antithesis SDK: Reachablity assertions” group.
func Reachable(message string, details map[string]any) {
locationInfo := newLocationInfo(offsetAPICaller)
id := makeKey(message, locationInfo)
assertImpl(true, message, details, locationInfo, wasHit, mustBeHit, reachabilityTest, reachableDisplay, id)
}
// AssertRaw is a low-level method designed to be used by third-party frameworks. Regular users of the assert package should not call it.
func AssertRaw(cond bool, message string, details map[string]any,
classname, funcname, filename string, line int,
hit bool, mustHit bool,
assertType string, displayType string,
id string,
) {
assertImpl(cond, message, details,
&locationInfo{classname, funcname, filename, line, columnUnknown},
hit, mustHit,
assertType, displayType,
id)
}
func assertImpl(cond bool, message string, details map[string]any,
loc *locationInfo,
hit bool, mustHit bool,
assertType string, displayType string,
id string,
) {
trackerEntry := assertTracker.getTrackerEntry(id, loc.Filename, loc.Classname)
// Always grab the Filename and Classname captured when the trackerEntry was established
// This provides the consistency needed between instrumentation-time and runtime
if loc.Filename != trackerEntry.Filename {
loc.Filename = trackerEntry.Filename
}
if loc.Classname != trackerEntry.Classname {
loc.Classname = trackerEntry.Classname
}
aI := &assertInfo{
Hit: hit,
MustHit: mustHit,
AssertType: assertType,
DisplayType: displayType,
Message: message,
Condition: cond,
Id: id,
Location: loc,
Details: details,
}
trackerEntry.emit(aI)
}
func makeKey(message string, _ *locationInfo) string {
return message
}
@@ -0,0 +1,16 @@
//go:build !enable_antithesis_sdk
package assert
func Always(condition bool, message string, details map[string]any) {}
func AlwaysOrUnreachable(condition bool, message string, details map[string]any) {}
func Sometimes(condition bool, message string, details map[string]any) {}
func Unreachable(message string, details map[string]any) {}
func Reachable(message string, details map[string]any) {}
func AssertRaw(cond bool, message string, details map[string]any,
classname, funcname, filename string, line int,
hit bool, mustHit bool,
assertType string, displayType string,
id string,
) {
}
@@ -0,0 +1,30 @@
package assert
// Allowable numeric types of comparison parameters
type Number interface {
~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint8 | ~uint16 | ~uint32 | ~float32 | ~float64 | ~uint64 | ~uint | ~uintptr
}
// Internally, numeric guidanceFn Operands only use these
type operandConstraint interface {
int32 | int64 | uint64 | float64
}
type numConstraint interface {
uint64 | float64
}
// Used for boolean assertions
type NamedBool struct {
First string `json:"first"`
Second bool `json:"second"`
}
// Convenience function to construct a NamedBool used for boolean assertions
func NewNamedBool(first string, second bool) *NamedBool {
p := NamedBool{
First: first,
Second: second,
}
return &p
}
@@ -0,0 +1,66 @@
//go:build enable_antithesis_sdk
package assert
import (
"sync"
"github.com/antithesishq/antithesis-sdk-go/internal"
)
// TODO: Tracker is intended to prevent sending the same guidance
// more than once. In this case, we always send, so the tracker
// is not presently used.
type booleanGuidance struct {
n int
}
type booleanGuidanceTracker map[string]*booleanGuidance
var (
boolean_guidance_tracker booleanGuidanceTracker = make(booleanGuidanceTracker)
boolean_guidance_tracker_mutex sync.Mutex
boolean_guidance_info_mutex sync.Mutex
)
func (tracker booleanGuidanceTracker) getTrackerEntry(messageKey string) *booleanGuidance {
var trackerEntry *booleanGuidance
var ok bool
if tracker == nil {
return nil
}
boolean_guidance_tracker_mutex.Lock()
defer boolean_guidance_tracker_mutex.Unlock()
if trackerEntry, ok = boolean_guidance_tracker[messageKey]; !ok {
trackerEntry = newBooleanGuidance()
tracker[messageKey] = trackerEntry
}
return trackerEntry
}
// Create a boolean guidance tracker
func newBooleanGuidance() *booleanGuidance {
trackerInfo := booleanGuidance{}
return &trackerInfo
}
func (tI *booleanGuidance) send_value(bgI *booleanGuidanceInfo) {
if tI == nil {
return
}
boolean_guidance_info_mutex.Lock()
defer boolean_guidance_info_mutex.Unlock()
// The tracker entry should be consulted to determine
// if this Guidance info has already been sent, or not.
emitBooleanGuidance(bgI)
}
func emitBooleanGuidance(bgI *booleanGuidanceInfo) error {
return internal.Json_data(map[string]any{"antithesis_guidance": bgI})
}
@@ -0,0 +1,58 @@
//go:build enable_antithesis_sdk
package assert
import (
"path"
"runtime"
"strings"
)
// stackFrameOffset indicates how many frames to go up in the
// call stack to find the filename/location/line info. As
// this work is always done in NewLocationInfo(), the offset is
// specified from the perspective of NewLocationInfo
type stackFrameOffset int
// Order is important here since iota is being used
const (
offsetNewLocationInfo stackFrameOffset = iota
offsetHere
offsetAPICaller
offsetAPICallersCaller
)
// locationInfo represents the attributes known at instrumentation time
// for each Antithesis assertion discovered
type locationInfo struct {
Classname string `json:"class"`
Funcname string `json:"function"`
Filename string `json:"file"`
Line int `json:"begin_line"`
Column int `json:"begin_column"`
}
// columnUnknown is used when the column associated with
// a locationInfo is not available
const columnUnknown = 0
// NewLocationInfo creates a locationInfo directly from
// the current execution context
func newLocationInfo(nframes stackFrameOffset) *locationInfo {
// Get location info and add to details
funcname := "*function*"
classname := "*class*"
pc, filename, line, ok := runtime.Caller(int(nframes))
if !ok {
filename = "*file*"
line = 0
} else {
if this_func := runtime.FuncForPC(pc); this_func != nil {
fullname := this_func.Name()
funcname = path.Ext(fullname)
classname, _ = strings.CutSuffix(fullname, funcname)
funcname = funcname[1:]
}
}
return &locationInfo{classname, funcname, filename, line, columnUnknown}
}
@@ -0,0 +1,323 @@
//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
@@ -0,0 +1,330 @@
//go:build enable_antithesis_sdk
package assert
// A type for writing raw assertions.
// guidanceFnType allows the assertion to provide guidance to
// the Antithesis platform when testing in Antithesis.
// Regular users of the assert package should not use it.
type guidanceFnType int
const (
guidanceFnMaximize guidanceFnType = iota // Maximize (left - right) values
guidanceFnMinimize // Minimize (left - right) values
guidanceFnWantAll // Encourages fuzzing explorations where boolean values are true
guidanceFnWantNone // Encourages fuzzing explorations where boolean values are false
guidanceFnExplore
)
// guidanceFnExplore
func get_guidance_type_string(gt guidanceFnType) string {
switch gt {
case guidanceFnMaximize, guidanceFnMinimize:
return "numeric"
case guidanceFnWantAll, guidanceFnWantNone:
return "boolean"
case guidanceFnExplore:
return "json"
}
return ""
}
type numericOperands[T operandConstraint] struct {
Left T `json:"left"`
Right T `json:"right"`
}
type guidanceInfo struct {
Data any `json:"guidance_data,omitempty"`
Location *locationInfo `json:"location"`
GuidanceType string `json:"guidance_type"`
Message string `json:"message"`
Id string `json:"id"`
Maximize bool `json:"maximize"`
Hit bool `json:"hit"`
}
type booleanGuidanceInfo struct {
Data any `json:"guidance_data,omitempty"`
Location *locationInfo `json:"location"`
GuidanceType string `json:"guidance_type"`
Message string `json:"message"`
Id string `json:"id"`
Maximize bool `json:"maximize"`
Hit bool `json:"hit"`
}
func uses_maximize(gt guidanceFnType) bool {
return gt == guidanceFnMaximize || gt == guidanceFnWantAll
}
func newOperands[T Number](left, right T) any {
switch any(left).(type) {
case int8, int16, int32:
return numericOperands[int32]{int32(left), int32(right)}
case int, int64:
return numericOperands[int64]{int64(left), int64(right)}
case uint8, uint16, uint32, uint, uint64, uintptr:
return numericOperands[uint64]{uint64(left), uint64(right)}
case float32, float64:
return numericOperands[float64]{float64(left), float64(right)}
}
return nil
}
func build_numeric_guidance[T Number](gt guidanceFnType, message string, left, right T, loc *locationInfo, id string, hit bool) *guidanceInfo {
operands := newOperands(left, right)
if !hit {
operands = nil
}
gI := guidanceInfo{
GuidanceType: get_guidance_type_string(gt),
Message: message,
Id: id,
Location: loc,
Maximize: uses_maximize(gt),
Data: operands,
Hit: hit,
}
return &gI
}
type namedBoolDictionary map[string]bool
func build_boolean_guidance(gt guidanceFnType, message string, named_bools []NamedBool,
loc *locationInfo,
id string, hit bool) *booleanGuidanceInfo {
var guidance_data any
// To ensure the sequence and naming for the named_bool values
if hit {
named_bool_dictionary := namedBoolDictionary{}
for _, named_bool := range named_bools {
named_bool_dictionary[named_bool.First] = named_bool.Second
}
guidance_data = named_bool_dictionary
}
bgI := booleanGuidanceInfo{
GuidanceType: get_guidance_type_string(gt),
Message: message,
Id: id,
Location: loc,
Maximize: uses_maximize(gt),
Data: guidance_data,
Hit: hit,
}
return &bgI
}
func behavior_to_guidance(behavior string) guidanceFnType {
guidance := guidanceFnExplore
switch behavior {
case "maximize":
guidance = guidanceFnMaximize
case "minimize":
guidance = guidanceFnMinimize
case "all":
guidance = guidanceFnWantAll
case "none":
guidance = guidanceFnWantNone
}
return guidance
}
func numericGuidanceImpl[T Number](left, right T, message, id string, loc *locationInfo, guidanceFn guidanceFnType, hit bool) {
tI := numeric_guidance_tracker.getTrackerEntry(id, gapTypeForOperand(left), uses_maximize(guidanceFn))
gI := build_numeric_guidance(guidanceFn, message, left, right, loc, id, hit)
send_value_if_needed(tI, gI)
}
func booleanGuidanceImpl(named_bools []NamedBool, message, id string, loc *locationInfo, guidanceFn guidanceFnType, hit bool) {
tI := boolean_guidance_tracker.getTrackerEntry(id)
bgI := build_boolean_guidance(guidanceFn, message, named_bools, loc, id, hit)
tI.send_value(bgI)
}
// NumericGuidanceRaw is a low-level method designed to be used by third-party frameworks. Regular users of the assert package should not call it.
func NumericGuidanceRaw[T Number](
left, right T,
message, id string,
classname, funcname, filename string,
line int,
behavior string,
hit bool,
) {
loc := &locationInfo{classname, funcname, filename, line, columnUnknown}
guidanceFn := behavior_to_guidance(behavior)
numericGuidanceImpl(left, right, message, id, loc, guidanceFn, hit)
}
// BooleanGuidanceRaw is a low-level method designed to be used by third-party frameworks. Regular users of the assert package should not call it.
func BooleanGuidanceRaw(
named_bools []NamedBool,
message, id string,
classname, funcname, filename string,
line int,
behavior string,
hit bool,
) {
loc := &locationInfo{classname, funcname, filename, line, columnUnknown}
guidanceFn := behavior_to_guidance(behavior)
booleanGuidanceImpl(named_bools, message, id, loc, guidanceFn, hit)
}
func add_numeric_details[T Number](details map[string]any, left, right T) map[string]any {
// ----------------------------------------------------
// Can not use maps.Clone() until go 1.21.0 or above
// enhancedDetails := maps.Clone(details)
// ----------------------------------------------------
enhancedDetails := map[string]any{}
for k, v := range details {
enhancedDetails[k] = v
}
enhancedDetails["left"] = left
enhancedDetails["right"] = right
return enhancedDetails
}
func add_boolean_details(details map[string]any, named_bools []NamedBool) map[string]any {
// ----------------------------------------------------
// Can not use maps.Clone() until go 1.21.0 or above
// enhancedDetails := maps.Clone(details)
// ----------------------------------------------------
enhancedDetails := map[string]any{}
for k, v := range details {
enhancedDetails[k] = v
}
for _, named_bool := range named_bools {
enhancedDetails[named_bool.First] = named_bool.Second
}
return enhancedDetails
}
// Equivalent to asserting Always(left > right, message, details). Information about left and right will automatically be added to the details parameter, with keys left and right. If you use this function for assertions that compare numeric quantities, you may help Antithesis find more bugs.
func AlwaysGreaterThan[T Number](left, right T, message string, details map[string]any) {
loc := newLocationInfo(offsetAPICaller)
id := makeKey(message, loc)
condition := left > right
all_details := add_numeric_details(details, left, right)
assertImpl(condition, message, all_details, loc, wasHit, mustBeHit, universalTest, alwaysDisplay, id)
numericGuidanceImpl(left, right, message, id, loc, guidanceFnMinimize, wasHit)
}
// Equivalent to asserting Always(left >= right, message, details). Information about left and right will automatically be added to the details parameter, with keys left and right. If you use this function for assertions that compare numeric quantities, you may help Antithesis find more bugs.
func AlwaysGreaterThanOrEqualTo[T Number](left, right T, message string, details map[string]any) {
loc := newLocationInfo(offsetAPICaller)
id := makeKey(message, loc)
condition := left >= right
all_details := add_numeric_details(details, left, right)
assertImpl(condition, message, all_details, loc, wasHit, mustBeHit, universalTest, alwaysDisplay, id)
numericGuidanceImpl(left, right, message, id, loc, guidanceFnMinimize, wasHit)
}
// Equivalent to asserting Sometimes(T left > T right, message, details). Information about left and right will automatically be added to the details parameter, with keys left and right. If you use this function for assertions that compare numeric quantities, you may help Antithesis find more bugs.
func SometimesGreaterThan[T Number](left, right T, message string, details map[string]any) {
loc := newLocationInfo(offsetAPICaller)
id := makeKey(message, loc)
condition := left > right
all_details := add_numeric_details(details, left, right)
assertImpl(condition, message, all_details, loc, wasHit, mustBeHit, existentialTest, sometimesDisplay, id)
numericGuidanceImpl(left, right, message, id, loc, guidanceFnMaximize, wasHit)
}
// Equivalent to asserting Sometimes(T left >= T right, message, details). Information about left and right will automatically be added to the details parameter, with keys left and right. If you use this function for assertions that compare numeric quantities, you may help Antithesis find more bugs.
func SometimesGreaterThanOrEqualTo[T Number](left, right T, message string, details map[string]any) {
loc := newLocationInfo(offsetAPICaller)
id := makeKey(message, loc)
condition := left >= right
all_details := add_numeric_details(details, left, right)
assertImpl(condition, message, all_details, loc, wasHit, mustBeHit, existentialTest, sometimesDisplay, id)
numericGuidanceImpl(left, right, message, id, loc, guidanceFnMaximize, wasHit)
}
// Equivalent to asserting Always(left < right, message, details). Information about left and right will automatically be added to the details parameter, with keys left and right. If you use this function for assertions that compare numeric quantities, you may help Antithesis find more bugs.
func AlwaysLessThan[T Number](left, right T, message string, details map[string]any) {
loc := newLocationInfo(offsetAPICaller)
id := makeKey(message, loc)
condition := left < right
all_details := add_numeric_details(details, left, right)
assertImpl(condition, message, all_details, loc, wasHit, mustBeHit, universalTest, alwaysDisplay, id)
numericGuidanceImpl(left, right, message, id, loc, guidanceFnMaximize, wasHit)
}
// Equivalent to asserting Always(left <= right, message, details). Information about left and right will automatically be added to the details parameter, with keys left and right. If you use this function for assertions that compare numeric quantities, you may help Antithesis find more bugs.
func AlwaysLessThanOrEqualTo[T Number](left, right T, message string, details map[string]any) {
loc := newLocationInfo(offsetAPICaller)
id := makeKey(message, loc)
condition := left <= right
all_details := add_numeric_details(details, left, right)
assertImpl(condition, message, all_details, loc, wasHit, mustBeHit, universalTest, alwaysDisplay, id)
numericGuidanceImpl(left, right, message, id, loc, guidanceFnMaximize, wasHit)
}
// Equivalent to asserting Sometimes(T left < T right, message, details). Information about left and right will automatically be added to the details parameter, with keys left and right. If you use this function for assertions that compare numeric quantities, you may help Antithesis find more bugs.
func SometimesLessThan[T Number](left, right T, message string, details map[string]any) {
loc := newLocationInfo(offsetAPICaller)
id := makeKey(message, loc)
condition := left < right
all_details := add_numeric_details(details, left, right)
assertImpl(condition, message, all_details, loc, wasHit, mustBeHit, existentialTest, sometimesDisplay, id)
numericGuidanceImpl(left, right, message, id, loc, guidanceFnMinimize, wasHit)
}
// Equivalent to asserting Sometimes(T left <= T right, message, details). Information about left and right will automatically be added to the details parameter, with keys left and right. If you use this function for assertions that compare numeric quantities, you may help Antithesis find more bugs.
func SometimesLessThanOrEqualTo[T Number](left, right T, message string, details map[string]any) {
loc := newLocationInfo(offsetAPICaller)
id := makeKey(message, loc)
condition := left <= right
all_details := add_numeric_details(details, left, right)
assertImpl(condition, message, all_details, loc, wasHit, mustBeHit, existentialTest, sometimesDisplay, id)
numericGuidanceImpl(left, right, message, id, loc, guidanceFnMinimize, wasHit)
}
// Asserts that every time this is called, at least one bool in named_bools is true. Equivalent to Always(named_bools[0].second || named_bools[1].second || ..., message, details). If you use this for assertions about the behavior of booleans, you may help Antithesis find more bugs. Information about named_bools will automatically be added to the details parameter, and the keys will be the names of the bools.
func AlwaysSome(named_bools []NamedBool, message string, details map[string]any) {
loc := newLocationInfo(offsetAPICaller)
id := makeKey(message, loc)
disjunction := false
for _, named_bool := range named_bools {
if named_bool.Second {
disjunction = true
break
}
}
all_details := add_boolean_details(details, named_bools)
assertImpl(disjunction, message, all_details, loc, wasHit, mustBeHit, universalTest, alwaysDisplay, id)
booleanGuidanceImpl(named_bools, message, id, loc, guidanceFnWantNone, wasHit)
}
// Asserts that at least one time this is called, every bool in named_bools is true. Equivalent to Sometimes(named_bools[0].second && named_bools[1].second && ..., message, details). If you use this for assertions about the behavior of booleans, you may help Antithesis find more bugs. Information about named_bools will automatically be added to the details parameter, and the keys will be the names of the bools.
func SometimesAll(named_bools []NamedBool, message string, details map[string]any) {
loc := newLocationInfo(offsetAPICaller)
id := makeKey(message, loc)
conjunction := true
for _, named_bool := range named_bools {
if !named_bool.Second {
conjunction = false
break
}
}
all_details := add_boolean_details(details, named_bools)
assertImpl(conjunction, message, all_details, loc, wasHit, mustBeHit, existentialTest, sometimesDisplay, id)
booleanGuidanceImpl(named_bools, message, id, loc, guidanceFnWantAll, wasHit)
}
@@ -0,0 +1,34 @@
//go:build !enable_antithesis_sdk
package assert
func AlwaysGreaterThan[T Number](left, right T, message string, details map[string]any) {}
func AlwaysGreaterThanOrEqualTo[T Number](left, right T, message string, details map[string]any) {}
func SometimesGreaterThan[T Number](left, right T, message string, details map[string]any) {}
func SometimesGreaterThanOrEqualTo[T Number](left, right T, message string, details map[string]any) {}
func AlwaysLessThan[T Number](left, right T, message string, details map[string]any) {}
func AlwaysLessThanOrEqualTo[T Number](left, right T, message string, details map[string]any) {}
func SometimesLessThan[T Number](left, right T, message string, details map[string]any) {}
func SometimesLessThanOrEqualTo[T Number](left, right T, message string, details map[string]any) {}
func AlwaysSome(named_bool []NamedBool, message string, details map[string]any) {}
func SometimesAll(named_bool []NamedBool, message string, details map[string]any) {}
func NumericGuidanceRaw[T Number](left, right T,
message, id string,
classname, funcname, filename string,
line int,
behavior string,
hit bool,
) {
}
func BooleanGuidanceRaw(
named_bools []NamedBool,
message, id string,
classname, funcname, filename string,
line int,
behavior string,
hit bool,
) {
}
@@ -0,0 +1,111 @@
//go:build enable_antithesis_sdk
package assert
import (
"runtime"
"sync"
"sync/atomic"
"github.com/antithesishq/antithesis-sdk-go/internal"
)
type trackerInfo struct {
Filename string
Classname string
PassCount int
FailCount int
}
type emitTracker map[string]*trackerInfo
// assert_tracker (global) keeps track of the unique asserts evaluated
var (
assertTracker emitTracker = make(emitTracker)
trackerMutex sync.Mutex
trackerInfoMutex sync.Mutex
)
func (tracker emitTracker) getTrackerEntry(messageKey string, filename, classname string) *trackerInfo {
var trackerEntry *trackerInfo
var ok bool
if tracker == nil {
return nil
}
trackerMutex.Lock()
defer trackerMutex.Unlock()
if trackerEntry, ok = tracker[messageKey]; !ok {
trackerEntry = newTrackerInfo(filename, classname)
tracker[messageKey] = trackerEntry
}
return trackerEntry
}
func newTrackerInfo(filename, classname string) *trackerInfo {
trackerInfo := trackerInfo{
PassCount: 0,
FailCount: 0,
Filename: filename,
Classname: classname,
}
return &trackerInfo
}
func (ti *trackerInfo) emit(ai *assertInfo) {
if ti == nil || ai == nil {
return
}
// Registrations are just sent to voidstar
hit := ai.Hit
if !hit {
emitAssert(ai)
return
}
var err error
cond := ai.Condition
trackerInfoMutex.Lock()
defer trackerInfoMutex.Unlock()
if cond {
if ti.PassCount == 0 {
err = emitAssert(ai)
}
if err == nil {
ti.PassCount++
}
return
}
if ti.FailCount == 0 {
err = emitAssert(ai)
}
if err == nil {
ti.FailCount++
}
}
func versionMessage() {
languageBlock := map[string]any{
"name": "Go",
"version": runtime.Version(),
}
versionBlock := map[string]any{
"language": languageBlock,
"sdk_version": internal.SDK_Version,
"protocol_version": internal.Protocol_Version,
}
internal.Json_data(map[string]any{"antithesis_sdk": versionBlock})
}
// package-level flag
var hasEmitted atomic.Bool // initialzed to false
func emitAssert(ai *assertInfo) error {
if hasEmitted.CompareAndSwap(false, true) {
versionMessage()
}
return internal.Json_data(wrappedAssertInfo{ai})
}
@@ -0,0 +1,100 @@
//go:build enable_antithesis_sdk
package internal
import (
"encoding/json"
"log"
"math/rand"
"os"
)
func Json_data(v any) error {
if data, err := json.Marshal(v); err != nil {
return err
} else {
handler.output(string(data))
return nil
}
}
func Get_random() uint64 {
return handler.random()
}
func Notify(edge uint64) bool {
return handler.notify(edge)
}
func InitCoverage(num_edges uint64, symbols string) uint64 {
return handler.init_coverage(num_edges, symbols)
}
type libHandler interface {
output(message string)
random() uint64
notify(edge uint64) bool
init_coverage(num_edges uint64, symbols string) uint64
}
const (
errorLogLinePrefix = "[* antithesis-sdk-go *]"
)
var handler libHandler
type localHandler struct {
outputFile *os.File // can be nil
}
func (h *localHandler) output(message string) {
msg_len := len(message)
if msg_len == 0 {
return
}
if h.outputFile != nil {
h.outputFile.WriteString(message + "\n")
}
}
func (h *localHandler) random() uint64 {
return rand.Uint64()
}
func (h *localHandler) notify(edge uint64) bool {
return false
}
func (h *localHandler) init_coverage(num_edges uint64, symbols string) uint64 {
return 0
}
func init() {
handler = init_in_antithesis()
if handler == nil {
// Otherwise fallback to the local handler.
handler = openLocalHandler()
}
}
// If `localOutputEnvVar` is set to a non-empty path, attempt to open that path and truncate the file
// to serve as the log file of the local handler.
// Otherwise, we don't have a log file, and logging is a no-op in the local handler.
func openLocalHandler() *localHandler {
path, is_set := os.LookupEnv(localOutputEnvVar)
if !is_set || len(path) == 0 {
return &localHandler{nil}
}
// Open the file R/W (create if needed and possible)
file, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0644)
if err != nil {
log.Printf("%s Failed to open path %s: %v", errorLogLinePrefix, path, err)
file = nil
} else if err = file.Truncate(0); err != nil {
log.Printf("%s Failed to truncate file at %s: %v", errorLogLinePrefix, path, err)
file = nil
}
return &localHandler{file}
}
@@ -0,0 +1,12 @@
package internal
// --------------------------------------------------------------------------------
// Versions
// --------------------------------------------------------------------------------
const SDK_Version = "0.7.0"
const Protocol_Version = "1.1.0"
// --------------------------------------------------------------------------------
// Environment Vars
// --------------------------------------------------------------------------------
const localOutputEnvVar = "ANTITHESIS_SDK_LOCAL_OUTPUT"
@@ -0,0 +1,160 @@
//go:build enable_antithesis_sdk && linux && amd64 && cgo
package internal
import (
"fmt"
"unsafe"
"os"
)
// --------------------------------------------------------------------------------
// To build and run an executable with this package
//
// CC=clang CGO_ENABLED=1 go run ./main.go
// --------------------------------------------------------------------------------
// \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/
//
// The commented lines below, and the `import "C"` line which must directly follow
// the commented lines are used by CGO. They are load-bearing, and should not be
// changed without first understanding how CGO uses them.
//
// \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/
// #cgo LDFLAGS: -ldl
//
// #include <dlfcn.h>
// #include <stdbool.h>
// #include <stdint.h>
// #include <stdlib.h>
//
// typedef void (*go_fuzz_json_data_fn)(const char *data, size_t size);
// void
// go_fuzz_json_data(void *f, const char *data, size_t size) {
// ((go_fuzz_json_data_fn)f)(data, size);
// }
//
// typedef void (*go_fuzz_flush_fn)(void);
// void
// go_fuzz_flush(void *f) {
// ((go_fuzz_flush_fn)f)();
// }
//
// typedef uint64_t (*go_fuzz_get_random_fn)(void);
// uint64_t
// go_fuzz_get_random(void *f) {
// return ((go_fuzz_get_random_fn)f)();
// }
//
// typedef bool (*go_notify_coverage_fn)(size_t);
// int
// go_notify_coverage(void *f, size_t edges) {
// bool b = ((go_notify_coverage_fn)f)(edges);
// return b ? 1 : 0;
// }
//
// typedef uint64_t (*go_init_coverage_fn)(size_t num_edges, const char *symbols);
// uint64_t
// go_init_coverage(void *f, size_t num_edges, const char *symbols) {
// return ((go_init_coverage_fn)f)(num_edges, symbols);
// }
//
import "C"
const (
defaultNativeLibraryPath = "/usr/lib/libvoidstar.so"
)
type voidstarHandler struct {
fuzzJsonData unsafe.Pointer
fuzzFlush unsafe.Pointer
fuzzGetRandom unsafe.Pointer
initCoverage unsafe.Pointer
notifyCoverage unsafe.Pointer
}
func (h *voidstarHandler) output(message string) {
msg_len := len(message)
if msg_len == 0 {
return
}
cstrMessage := C.CString(message)
defer C.free(unsafe.Pointer(cstrMessage))
C.go_fuzz_json_data(h.fuzzJsonData, cstrMessage, C.ulong(msg_len))
C.go_fuzz_flush(h.fuzzFlush)
}
func (h *voidstarHandler) random() uint64 {
return uint64(C.go_fuzz_get_random(h.fuzzGetRandom))
}
func (h *voidstarHandler) init_coverage(num_edge uint64, symbols string) uint64 {
cstrSymbols := C.CString(symbols)
defer C.free(unsafe.Pointer(cstrSymbols))
return uint64(C.go_init_coverage(h.initCoverage, C.ulong(num_edge), cstrSymbols))
}
func (h *voidstarHandler) notify(edge uint64) bool {
ival := int(C.go_notify_coverage(h.notifyCoverage, C.ulong(edge)))
return ival == 1
}
// Attempt to load libvoidstar and some symbols from `path`
func openSharedLib(path string) (*voidstarHandler, error) {
cstrPath := C.CString(path)
defer C.free(unsafe.Pointer(cstrPath))
dlError := func(message string) error {
return fmt.Errorf("%s: (%s)", message, C.GoString(C.dlerror()))
}
sharedLib := C.dlopen(cstrPath, C.int(C.RTLD_NOW))
if sharedLib == nil {
return nil, dlError("Can not load the Antithesis native library")
}
loadFunc := func(name string) (symbol unsafe.Pointer, err error) {
cstrName := C.CString(name)
defer C.free(unsafe.Pointer(cstrName))
if symbol = C.dlsym(sharedLib, cstrName); symbol == nil {
err = dlError(fmt.Sprintf("Can not access symbol %s", name))
}
return
}
fuzzJsonData, err := loadFunc("fuzz_json_data")
if err != nil {
return nil, err
}
fuzzFlush, err := loadFunc("fuzz_flush")
if err != nil {
return nil, err
}
fuzzGetRandom, err := loadFunc("fuzz_get_random")
if err != nil {
return nil, err
}
notifyCoverage, err := loadFunc("notify_coverage")
if err != nil {
return nil, err
}
initCoverage, err := loadFunc("init_coverage_module")
if err != nil {
return nil, err
}
return &voidstarHandler{fuzzJsonData, fuzzFlush, fuzzGetRandom, initCoverage, notifyCoverage}, nil
}
// If we have a file at `defaultNativeLibraryPath`, we load the shared library
// (and panic on any error encountered during load).
func init_in_antithesis() libHandler {
if _, err := os.Stat(defaultNativeLibraryPath); err == nil {
handler, err := openSharedLib(defaultNativeLibraryPath)
if err != nil {
panic(err)
}
return handler
}
return nil
}
@@ -0,0 +1,7 @@
//go:build enable_antithesis_sdk && (!linux || !amd64 || !cgo)
package internal
func init_in_antithesis() libHandler {
return nil
}