 8d9b62daf3
			
		
	
	8d9b62daf3
	
	
	
		
			
			This commit implements Phase 2 of the CHORUS Task Execution Engine development plan, providing a comprehensive execution environment abstraction layer with Docker container sandboxing support. ## New Features ### Core Sandbox Interface - Comprehensive ExecutionSandbox interface with isolated task execution - Support for command execution, file I/O, environment management - Resource usage monitoring and sandbox lifecycle management - Standardized error handling with SandboxError types and categories ### Docker Container Sandbox Implementation - Full Docker API integration with secure container creation - Transparent repository mounting with configurable read/write access - Advanced security policies with capability dropping and privilege controls - Comprehensive resource limits (CPU, memory, disk, processes, file handles) - Support for tmpfs mounts, masked paths, and read-only bind mounts - Container lifecycle management with proper cleanup and health monitoring ### Security & Resource Management - Configurable security policies with SELinux, AppArmor, and Seccomp support - Fine-grained capability management with secure defaults - Network isolation options with configurable DNS and proxy settings - Resource monitoring with real-time CPU, memory, and network usage tracking - Comprehensive ulimits configuration for process and file handle limits ### Repository Integration - Seamless repository mounting from local paths to container workspaces - Git configuration support with user credentials and global settings - File inclusion/exclusion patterns for selective repository access - Configurable permissions and ownership for mounted repositories ### Testing Infrastructure - Comprehensive test suite with 60+ test cases covering all functionality - Docker integration tests with Alpine Linux containers (skipped in short mode) - Mock sandbox implementation for unit testing without Docker dependencies - Security policy validation tests with read-only filesystem enforcement - Resource usage monitoring and cleanup verification tests ## Technical Details ### Dependencies Added - github.com/docker/docker v28.4.0+incompatible - Docker API client - github.com/docker/go-connections v0.6.0 - Docker connection utilities - github.com/docker/go-units v0.5.0 - Docker units and formatting - Associated Docker API dependencies for complete container management ### Architecture - Interface-driven design enabling multiple sandbox implementations - Comprehensive configuration structures for all sandbox aspects - Resource usage tracking with detailed metrics collection - Error handling with retryable error classification - Proper cleanup and resource management throughout sandbox lifecycle ### Compatibility - Maintains backward compatibility with existing CHORUS architecture - Designed for future integration with Phase 3 Core Task Execution Engine - Extensible design supporting additional sandbox implementations (VM, process) This Phase 2 implementation provides the foundation for secure, isolated task execution that will be integrated with the AI model providers from Phase 1 in the upcoming Phase 3 development. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
		
			
				
	
	
		
			496 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			496 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package assert
 | |
| 
 | |
| import (
 | |
| 	"bytes"
 | |
| 	"fmt"
 | |
| 	"reflect"
 | |
| 	"time"
 | |
| )
 | |
| 
 | |
| // Deprecated: CompareType has only ever been for internal use and has accidentally been published since v1.6.0. Do not use it.
 | |
| type CompareType = compareResult
 | |
| 
 | |
| type compareResult int
 | |
| 
 | |
| const (
 | |
| 	compareLess compareResult = iota - 1
 | |
| 	compareEqual
 | |
| 	compareGreater
 | |
| )
 | |
| 
 | |
| var (
 | |
| 	intType   = reflect.TypeOf(int(1))
 | |
| 	int8Type  = reflect.TypeOf(int8(1))
 | |
| 	int16Type = reflect.TypeOf(int16(1))
 | |
| 	int32Type = reflect.TypeOf(int32(1))
 | |
| 	int64Type = reflect.TypeOf(int64(1))
 | |
| 
 | |
| 	uintType   = reflect.TypeOf(uint(1))
 | |
| 	uint8Type  = reflect.TypeOf(uint8(1))
 | |
| 	uint16Type = reflect.TypeOf(uint16(1))
 | |
| 	uint32Type = reflect.TypeOf(uint32(1))
 | |
| 	uint64Type = reflect.TypeOf(uint64(1))
 | |
| 
 | |
| 	uintptrType = reflect.TypeOf(uintptr(1))
 | |
| 
 | |
| 	float32Type = reflect.TypeOf(float32(1))
 | |
| 	float64Type = reflect.TypeOf(float64(1))
 | |
| 
 | |
| 	stringType = reflect.TypeOf("")
 | |
| 
 | |
| 	timeType  = reflect.TypeOf(time.Time{})
 | |
| 	bytesType = reflect.TypeOf([]byte{})
 | |
| )
 | |
| 
 | |
| func compare(obj1, obj2 interface{}, kind reflect.Kind) (compareResult, bool) {
 | |
| 	obj1Value := reflect.ValueOf(obj1)
 | |
| 	obj2Value := reflect.ValueOf(obj2)
 | |
| 
 | |
| 	// throughout this switch we try and avoid calling .Convert() if possible,
 | |
| 	// as this has a pretty big performance impact
 | |
| 	switch kind {
 | |
| 	case reflect.Int:
 | |
| 		{
 | |
| 			intobj1, ok := obj1.(int)
 | |
| 			if !ok {
 | |
| 				intobj1 = obj1Value.Convert(intType).Interface().(int)
 | |
| 			}
 | |
| 			intobj2, ok := obj2.(int)
 | |
| 			if !ok {
 | |
| 				intobj2 = obj2Value.Convert(intType).Interface().(int)
 | |
| 			}
 | |
| 			if intobj1 > intobj2 {
 | |
| 				return compareGreater, true
 | |
| 			}
 | |
| 			if intobj1 == intobj2 {
 | |
| 				return compareEqual, true
 | |
| 			}
 | |
| 			if intobj1 < intobj2 {
 | |
| 				return compareLess, true
 | |
| 			}
 | |
| 		}
 | |
| 	case reflect.Int8:
 | |
| 		{
 | |
| 			int8obj1, ok := obj1.(int8)
 | |
| 			if !ok {
 | |
| 				int8obj1 = obj1Value.Convert(int8Type).Interface().(int8)
 | |
| 			}
 | |
| 			int8obj2, ok := obj2.(int8)
 | |
| 			if !ok {
 | |
| 				int8obj2 = obj2Value.Convert(int8Type).Interface().(int8)
 | |
| 			}
 | |
| 			if int8obj1 > int8obj2 {
 | |
| 				return compareGreater, true
 | |
| 			}
 | |
| 			if int8obj1 == int8obj2 {
 | |
| 				return compareEqual, true
 | |
| 			}
 | |
| 			if int8obj1 < int8obj2 {
 | |
| 				return compareLess, true
 | |
| 			}
 | |
| 		}
 | |
| 	case reflect.Int16:
 | |
| 		{
 | |
| 			int16obj1, ok := obj1.(int16)
 | |
| 			if !ok {
 | |
| 				int16obj1 = obj1Value.Convert(int16Type).Interface().(int16)
 | |
| 			}
 | |
| 			int16obj2, ok := obj2.(int16)
 | |
| 			if !ok {
 | |
| 				int16obj2 = obj2Value.Convert(int16Type).Interface().(int16)
 | |
| 			}
 | |
| 			if int16obj1 > int16obj2 {
 | |
| 				return compareGreater, true
 | |
| 			}
 | |
| 			if int16obj1 == int16obj2 {
 | |
| 				return compareEqual, true
 | |
| 			}
 | |
| 			if int16obj1 < int16obj2 {
 | |
| 				return compareLess, true
 | |
| 			}
 | |
| 		}
 | |
| 	case reflect.Int32:
 | |
| 		{
 | |
| 			int32obj1, ok := obj1.(int32)
 | |
| 			if !ok {
 | |
| 				int32obj1 = obj1Value.Convert(int32Type).Interface().(int32)
 | |
| 			}
 | |
| 			int32obj2, ok := obj2.(int32)
 | |
| 			if !ok {
 | |
| 				int32obj2 = obj2Value.Convert(int32Type).Interface().(int32)
 | |
| 			}
 | |
| 			if int32obj1 > int32obj2 {
 | |
| 				return compareGreater, true
 | |
| 			}
 | |
| 			if int32obj1 == int32obj2 {
 | |
| 				return compareEqual, true
 | |
| 			}
 | |
| 			if int32obj1 < int32obj2 {
 | |
| 				return compareLess, true
 | |
| 			}
 | |
| 		}
 | |
| 	case reflect.Int64:
 | |
| 		{
 | |
| 			int64obj1, ok := obj1.(int64)
 | |
| 			if !ok {
 | |
| 				int64obj1 = obj1Value.Convert(int64Type).Interface().(int64)
 | |
| 			}
 | |
| 			int64obj2, ok := obj2.(int64)
 | |
| 			if !ok {
 | |
| 				int64obj2 = obj2Value.Convert(int64Type).Interface().(int64)
 | |
| 			}
 | |
| 			if int64obj1 > int64obj2 {
 | |
| 				return compareGreater, true
 | |
| 			}
 | |
| 			if int64obj1 == int64obj2 {
 | |
| 				return compareEqual, true
 | |
| 			}
 | |
| 			if int64obj1 < int64obj2 {
 | |
| 				return compareLess, true
 | |
| 			}
 | |
| 		}
 | |
| 	case reflect.Uint:
 | |
| 		{
 | |
| 			uintobj1, ok := obj1.(uint)
 | |
| 			if !ok {
 | |
| 				uintobj1 = obj1Value.Convert(uintType).Interface().(uint)
 | |
| 			}
 | |
| 			uintobj2, ok := obj2.(uint)
 | |
| 			if !ok {
 | |
| 				uintobj2 = obj2Value.Convert(uintType).Interface().(uint)
 | |
| 			}
 | |
| 			if uintobj1 > uintobj2 {
 | |
| 				return compareGreater, true
 | |
| 			}
 | |
| 			if uintobj1 == uintobj2 {
 | |
| 				return compareEqual, true
 | |
| 			}
 | |
| 			if uintobj1 < uintobj2 {
 | |
| 				return compareLess, true
 | |
| 			}
 | |
| 		}
 | |
| 	case reflect.Uint8:
 | |
| 		{
 | |
| 			uint8obj1, ok := obj1.(uint8)
 | |
| 			if !ok {
 | |
| 				uint8obj1 = obj1Value.Convert(uint8Type).Interface().(uint8)
 | |
| 			}
 | |
| 			uint8obj2, ok := obj2.(uint8)
 | |
| 			if !ok {
 | |
| 				uint8obj2 = obj2Value.Convert(uint8Type).Interface().(uint8)
 | |
| 			}
 | |
| 			if uint8obj1 > uint8obj2 {
 | |
| 				return compareGreater, true
 | |
| 			}
 | |
| 			if uint8obj1 == uint8obj2 {
 | |
| 				return compareEqual, true
 | |
| 			}
 | |
| 			if uint8obj1 < uint8obj2 {
 | |
| 				return compareLess, true
 | |
| 			}
 | |
| 		}
 | |
| 	case reflect.Uint16:
 | |
| 		{
 | |
| 			uint16obj1, ok := obj1.(uint16)
 | |
| 			if !ok {
 | |
| 				uint16obj1 = obj1Value.Convert(uint16Type).Interface().(uint16)
 | |
| 			}
 | |
| 			uint16obj2, ok := obj2.(uint16)
 | |
| 			if !ok {
 | |
| 				uint16obj2 = obj2Value.Convert(uint16Type).Interface().(uint16)
 | |
| 			}
 | |
| 			if uint16obj1 > uint16obj2 {
 | |
| 				return compareGreater, true
 | |
| 			}
 | |
| 			if uint16obj1 == uint16obj2 {
 | |
| 				return compareEqual, true
 | |
| 			}
 | |
| 			if uint16obj1 < uint16obj2 {
 | |
| 				return compareLess, true
 | |
| 			}
 | |
| 		}
 | |
| 	case reflect.Uint32:
 | |
| 		{
 | |
| 			uint32obj1, ok := obj1.(uint32)
 | |
| 			if !ok {
 | |
| 				uint32obj1 = obj1Value.Convert(uint32Type).Interface().(uint32)
 | |
| 			}
 | |
| 			uint32obj2, ok := obj2.(uint32)
 | |
| 			if !ok {
 | |
| 				uint32obj2 = obj2Value.Convert(uint32Type).Interface().(uint32)
 | |
| 			}
 | |
| 			if uint32obj1 > uint32obj2 {
 | |
| 				return compareGreater, true
 | |
| 			}
 | |
| 			if uint32obj1 == uint32obj2 {
 | |
| 				return compareEqual, true
 | |
| 			}
 | |
| 			if uint32obj1 < uint32obj2 {
 | |
| 				return compareLess, true
 | |
| 			}
 | |
| 		}
 | |
| 	case reflect.Uint64:
 | |
| 		{
 | |
| 			uint64obj1, ok := obj1.(uint64)
 | |
| 			if !ok {
 | |
| 				uint64obj1 = obj1Value.Convert(uint64Type).Interface().(uint64)
 | |
| 			}
 | |
| 			uint64obj2, ok := obj2.(uint64)
 | |
| 			if !ok {
 | |
| 				uint64obj2 = obj2Value.Convert(uint64Type).Interface().(uint64)
 | |
| 			}
 | |
| 			if uint64obj1 > uint64obj2 {
 | |
| 				return compareGreater, true
 | |
| 			}
 | |
| 			if uint64obj1 == uint64obj2 {
 | |
| 				return compareEqual, true
 | |
| 			}
 | |
| 			if uint64obj1 < uint64obj2 {
 | |
| 				return compareLess, true
 | |
| 			}
 | |
| 		}
 | |
| 	case reflect.Float32:
 | |
| 		{
 | |
| 			float32obj1, ok := obj1.(float32)
 | |
| 			if !ok {
 | |
| 				float32obj1 = obj1Value.Convert(float32Type).Interface().(float32)
 | |
| 			}
 | |
| 			float32obj2, ok := obj2.(float32)
 | |
| 			if !ok {
 | |
| 				float32obj2 = obj2Value.Convert(float32Type).Interface().(float32)
 | |
| 			}
 | |
| 			if float32obj1 > float32obj2 {
 | |
| 				return compareGreater, true
 | |
| 			}
 | |
| 			if float32obj1 == float32obj2 {
 | |
| 				return compareEqual, true
 | |
| 			}
 | |
| 			if float32obj1 < float32obj2 {
 | |
| 				return compareLess, true
 | |
| 			}
 | |
| 		}
 | |
| 	case reflect.Float64:
 | |
| 		{
 | |
| 			float64obj1, ok := obj1.(float64)
 | |
| 			if !ok {
 | |
| 				float64obj1 = obj1Value.Convert(float64Type).Interface().(float64)
 | |
| 			}
 | |
| 			float64obj2, ok := obj2.(float64)
 | |
| 			if !ok {
 | |
| 				float64obj2 = obj2Value.Convert(float64Type).Interface().(float64)
 | |
| 			}
 | |
| 			if float64obj1 > float64obj2 {
 | |
| 				return compareGreater, true
 | |
| 			}
 | |
| 			if float64obj1 == float64obj2 {
 | |
| 				return compareEqual, true
 | |
| 			}
 | |
| 			if float64obj1 < float64obj2 {
 | |
| 				return compareLess, true
 | |
| 			}
 | |
| 		}
 | |
| 	case reflect.String:
 | |
| 		{
 | |
| 			stringobj1, ok := obj1.(string)
 | |
| 			if !ok {
 | |
| 				stringobj1 = obj1Value.Convert(stringType).Interface().(string)
 | |
| 			}
 | |
| 			stringobj2, ok := obj2.(string)
 | |
| 			if !ok {
 | |
| 				stringobj2 = obj2Value.Convert(stringType).Interface().(string)
 | |
| 			}
 | |
| 			if stringobj1 > stringobj2 {
 | |
| 				return compareGreater, true
 | |
| 			}
 | |
| 			if stringobj1 == stringobj2 {
 | |
| 				return compareEqual, true
 | |
| 			}
 | |
| 			if stringobj1 < stringobj2 {
 | |
| 				return compareLess, true
 | |
| 			}
 | |
| 		}
 | |
| 	// Check for known struct types we can check for compare results.
 | |
| 	case reflect.Struct:
 | |
| 		{
 | |
| 			// All structs enter here. We're not interested in most types.
 | |
| 			if !obj1Value.CanConvert(timeType) {
 | |
| 				break
 | |
| 			}
 | |
| 
 | |
| 			// time.Time can be compared!
 | |
| 			timeObj1, ok := obj1.(time.Time)
 | |
| 			if !ok {
 | |
| 				timeObj1 = obj1Value.Convert(timeType).Interface().(time.Time)
 | |
| 			}
 | |
| 
 | |
| 			timeObj2, ok := obj2.(time.Time)
 | |
| 			if !ok {
 | |
| 				timeObj2 = obj2Value.Convert(timeType).Interface().(time.Time)
 | |
| 			}
 | |
| 
 | |
| 			if timeObj1.Before(timeObj2) {
 | |
| 				return compareLess, true
 | |
| 			}
 | |
| 			if timeObj1.Equal(timeObj2) {
 | |
| 				return compareEqual, true
 | |
| 			}
 | |
| 			return compareGreater, true
 | |
| 		}
 | |
| 	case reflect.Slice:
 | |
| 		{
 | |
| 			// We only care about the []byte type.
 | |
| 			if !obj1Value.CanConvert(bytesType) {
 | |
| 				break
 | |
| 			}
 | |
| 
 | |
| 			// []byte can be compared!
 | |
| 			bytesObj1, ok := obj1.([]byte)
 | |
| 			if !ok {
 | |
| 				bytesObj1 = obj1Value.Convert(bytesType).Interface().([]byte)
 | |
| 
 | |
| 			}
 | |
| 			bytesObj2, ok := obj2.([]byte)
 | |
| 			if !ok {
 | |
| 				bytesObj2 = obj2Value.Convert(bytesType).Interface().([]byte)
 | |
| 			}
 | |
| 
 | |
| 			return compareResult(bytes.Compare(bytesObj1, bytesObj2)), true
 | |
| 		}
 | |
| 	case reflect.Uintptr:
 | |
| 		{
 | |
| 			uintptrObj1, ok := obj1.(uintptr)
 | |
| 			if !ok {
 | |
| 				uintptrObj1 = obj1Value.Convert(uintptrType).Interface().(uintptr)
 | |
| 			}
 | |
| 			uintptrObj2, ok := obj2.(uintptr)
 | |
| 			if !ok {
 | |
| 				uintptrObj2 = obj2Value.Convert(uintptrType).Interface().(uintptr)
 | |
| 			}
 | |
| 			if uintptrObj1 > uintptrObj2 {
 | |
| 				return compareGreater, true
 | |
| 			}
 | |
| 			if uintptrObj1 == uintptrObj2 {
 | |
| 				return compareEqual, true
 | |
| 			}
 | |
| 			if uintptrObj1 < uintptrObj2 {
 | |
| 				return compareLess, true
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return compareEqual, false
 | |
| }
 | |
| 
 | |
| // Greater asserts that the first element is greater than the second
 | |
| //
 | |
| //	assert.Greater(t, 2, 1)
 | |
| //	assert.Greater(t, float64(2), float64(1))
 | |
| //	assert.Greater(t, "b", "a")
 | |
| func Greater(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool {
 | |
| 	if h, ok := t.(tHelper); ok {
 | |
| 		h.Helper()
 | |
| 	}
 | |
| 	failMessage := fmt.Sprintf("\"%v\" is not greater than \"%v\"", e1, e2)
 | |
| 	return compareTwoValues(t, e1, e2, []compareResult{compareGreater}, failMessage, msgAndArgs...)
 | |
| }
 | |
| 
 | |
| // GreaterOrEqual asserts that the first element is greater than or equal to the second
 | |
| //
 | |
| //	assert.GreaterOrEqual(t, 2, 1)
 | |
| //	assert.GreaterOrEqual(t, 2, 2)
 | |
| //	assert.GreaterOrEqual(t, "b", "a")
 | |
| //	assert.GreaterOrEqual(t, "b", "b")
 | |
| func GreaterOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool {
 | |
| 	if h, ok := t.(tHelper); ok {
 | |
| 		h.Helper()
 | |
| 	}
 | |
| 	failMessage := fmt.Sprintf("\"%v\" is not greater than or equal to \"%v\"", e1, e2)
 | |
| 	return compareTwoValues(t, e1, e2, []compareResult{compareGreater, compareEqual}, failMessage, msgAndArgs...)
 | |
| }
 | |
| 
 | |
| // Less asserts that the first element is less than the second
 | |
| //
 | |
| //	assert.Less(t, 1, 2)
 | |
| //	assert.Less(t, float64(1), float64(2))
 | |
| //	assert.Less(t, "a", "b")
 | |
| func Less(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool {
 | |
| 	if h, ok := t.(tHelper); ok {
 | |
| 		h.Helper()
 | |
| 	}
 | |
| 	failMessage := fmt.Sprintf("\"%v\" is not less than \"%v\"", e1, e2)
 | |
| 	return compareTwoValues(t, e1, e2, []compareResult{compareLess}, failMessage, msgAndArgs...)
 | |
| }
 | |
| 
 | |
| // LessOrEqual asserts that the first element is less than or equal to the second
 | |
| //
 | |
| //	assert.LessOrEqual(t, 1, 2)
 | |
| //	assert.LessOrEqual(t, 2, 2)
 | |
| //	assert.LessOrEqual(t, "a", "b")
 | |
| //	assert.LessOrEqual(t, "b", "b")
 | |
| func LessOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool {
 | |
| 	if h, ok := t.(tHelper); ok {
 | |
| 		h.Helper()
 | |
| 	}
 | |
| 	failMessage := fmt.Sprintf("\"%v\" is not less than or equal to \"%v\"", e1, e2)
 | |
| 	return compareTwoValues(t, e1, e2, []compareResult{compareLess, compareEqual}, failMessage, msgAndArgs...)
 | |
| }
 | |
| 
 | |
| // Positive asserts that the specified element is positive
 | |
| //
 | |
| //	assert.Positive(t, 1)
 | |
| //	assert.Positive(t, 1.23)
 | |
| func Positive(t TestingT, e interface{}, msgAndArgs ...interface{}) bool {
 | |
| 	if h, ok := t.(tHelper); ok {
 | |
| 		h.Helper()
 | |
| 	}
 | |
| 	zero := reflect.Zero(reflect.TypeOf(e))
 | |
| 	failMessage := fmt.Sprintf("\"%v\" is not positive", e)
 | |
| 	return compareTwoValues(t, e, zero.Interface(), []compareResult{compareGreater}, failMessage, msgAndArgs...)
 | |
| }
 | |
| 
 | |
| // Negative asserts that the specified element is negative
 | |
| //
 | |
| //	assert.Negative(t, -1)
 | |
| //	assert.Negative(t, -1.23)
 | |
| func Negative(t TestingT, e interface{}, msgAndArgs ...interface{}) bool {
 | |
| 	if h, ok := t.(tHelper); ok {
 | |
| 		h.Helper()
 | |
| 	}
 | |
| 	zero := reflect.Zero(reflect.TypeOf(e))
 | |
| 	failMessage := fmt.Sprintf("\"%v\" is not negative", e)
 | |
| 	return compareTwoValues(t, e, zero.Interface(), []compareResult{compareLess}, failMessage, msgAndArgs...)
 | |
| }
 | |
| 
 | |
| func compareTwoValues(t TestingT, e1 interface{}, e2 interface{}, allowedComparesResults []compareResult, failMessage string, msgAndArgs ...interface{}) bool {
 | |
| 	if h, ok := t.(tHelper); ok {
 | |
| 		h.Helper()
 | |
| 	}
 | |
| 
 | |
| 	e1Kind := reflect.ValueOf(e1).Kind()
 | |
| 	e2Kind := reflect.ValueOf(e2).Kind()
 | |
| 	if e1Kind != e2Kind {
 | |
| 		return Fail(t, "Elements should be the same type", msgAndArgs...)
 | |
| 	}
 | |
| 
 | |
| 	compareResult, isComparable := compare(e1, e2, e1Kind)
 | |
| 	if !isComparable {
 | |
| 		return Fail(t, fmt.Sprintf(`Can not compare type "%T"`, e1), msgAndArgs...)
 | |
| 	}
 | |
| 
 | |
| 	if !containsValue(allowedComparesResults, compareResult) {
 | |
| 		return Fail(t, failMessage, msgAndArgs...)
 | |
| 	}
 | |
| 
 | |
| 	return true
 | |
| }
 | |
| 
 | |
| func containsValue(values []compareResult, value compareResult) bool {
 | |
| 	for _, v := range values {
 | |
| 		if v == value {
 | |
| 			return true
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return false
 | |
| }
 |