From 12eb1cc8cad131be1de3cea8837a8f273a2e6336 Mon Sep 17 00:00:00 2001 From: darkdrag00n Date: Thu, 29 Jun 2023 22:18:18 +0530 Subject: [PATCH] Add interpreter test cases --- runtime/interpreter/value_range.go | 24 +- runtime/sema/type.go | 7 + runtime/tests/checker/range_value_test.go | 4 +- runtime/tests/interpreter/range_value_test.go | 504 +++++++++++++++++- 4 files changed, 505 insertions(+), 34 deletions(-) diff --git a/runtime/interpreter/value_range.go b/runtime/interpreter/value_range.go index 41a8859adc..0683ed6f32 100644 --- a/runtime/interpreter/value_range.go +++ b/runtime/interpreter/value_range.go @@ -40,9 +40,15 @@ func NewInclusiveRangeValue( panic(errors.NewUnreachableError()) } - step := getValueForIntegerType(1, rangeType.ElementType) + step := GetValueForIntegerType(1, rangeType.ElementType) if startComparable.Greater(interpreter, endInclusiveComparable, locationRange) { - // TODO: Disallow unsigned integers to have a negative step. + elemSemaTy := interpreter.MustConvertStaticToSemaType(rangeType.ElementType) + if _, ok := sema.AllUnsignedIntegerTypesSet[elemSemaTy]; ok { + panic(InclusiveRangeConstructionError{ + LocationRange: locationRange, + Message: fmt.Sprintf("step value cannot be negative for unsigned integer type %s", elemSemaTy), + }) + } negatedStep, ok := step.Negate(interpreter, locationRange).(IntegerValue) if !ok { @@ -65,10 +71,8 @@ func NewInclusiveRangeValueWithStep( rangeType InclusiveRangeStaticType, ) *CompositeValue { - // TODO: Validate that if start > end, then the type is signed integer. - // Validate that the step is non-zero. - if step.Equal(interpreter, locationRange, getValueForIntegerType(0, rangeType.ElementType)) { + if step.Equal(interpreter, locationRange, GetValueForIntegerType(0, rangeType.ElementType)) { panic(InclusiveRangeConstructionError{ LocationRange: locationRange, Message: "step value cannot be zero", @@ -79,8 +83,8 @@ func NewInclusiveRangeValueWithStep( // If start < end, step must be > 0 // If start > end, step must be < 0 // If start == end, step doesn't matter. - if (start.Less(interpreter, end, locationRange) && step.Less(interpreter, getValueForIntegerType(0, rangeType.ElementType), locationRange)) || - (start.Greater(interpreter, end, locationRange) && step.Greater(interpreter, getValueForIntegerType(0, rangeType.ElementType), locationRange)) { + if (start.Less(interpreter, end, locationRange) && step.Less(interpreter, GetValueForIntegerType(0, rangeType.ElementType), locationRange)) || + (start.Greater(interpreter, end, locationRange) && step.Greater(interpreter, GetValueForIntegerType(0, rangeType.ElementType), locationRange)) { panic(InclusiveRangeConstructionError{ LocationRange: locationRange, @@ -173,7 +177,7 @@ func rangeContains( panic(errors.NewUnreachableError()) } - result = diff.Mod(interpreter, step, locationRange).Equal(interpreter, locationRange, getValueForIntegerType(0, rangeType.ElementType)) + result = diff.Mod(interpreter, step, locationRange).Equal(interpreter, locationRange, GetValueForIntegerType(0, rangeType.ElementType)) } } @@ -182,7 +186,7 @@ func rangeContains( // Get the provided int64 value in the required staticType. // Note: Assumes that the provided value fits within the constraints of the staticType. -func getValueForIntegerType(value int64, staticType StaticType) IntegerValue { +func GetValueForIntegerType(value int64, staticType StaticType) IntegerValue { switch staticType { case PrimitiveStaticTypeInt: return NewUnmeteredIntValueFromInt64(value) @@ -242,7 +246,7 @@ func getFieldAsIntegerValue( rangeValue.GetField( interpreter, locationRange, - sema.InclusiveRangeTypeStartFieldName, + name, ), ) } diff --git a/runtime/sema/type.go b/runtime/sema/type.go index c4cf834680..9520ae3ab9 100644 --- a/runtime/sema/type.go +++ b/runtime/sema/type.go @@ -3239,6 +3239,8 @@ var AllUnsignedIntegerTypes = []Type{ Word256Type, } +var AllUnsignedIntegerTypesSet = make(map[Type]struct{}) + var AllIntegerTypes = append( append( AllUnsignedIntegerTypes[:], @@ -3386,6 +3388,11 @@ func init() { ) } } + + // Populate AllUnsignedIntegerTypesSet + for _, ty := range AllUnsignedIntegerTypes { + AllUnsignedIntegerTypesSet[ty] = struct{}{} + } } func NumberConversionFunctionType(numberType Type) *FunctionType { diff --git a/runtime/tests/checker/range_value_test.go b/runtime/tests/checker/range_value_test.go index 8adfd7135f..7f5be159da 100644 --- a/runtime/tests/checker/range_value_test.go +++ b/runtime/tests/checker/range_value_test.go @@ -277,14 +277,14 @@ func TestInclusiveRangeConstructionValid(t *testing.T) { } func TestInclusiveRangeConstructionInvalid(t *testing.T) { - // t.Parallel() + t.Parallel() baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation) baseValueActivation.DeclareValue(stdlib.InclusiveRangeConstructorFunction) runInvalidCase := func(t *testing.T, label, code string, expectedErrorTypes []interface{}) { t.Run(label, func(t *testing.T) { - // t.Parallel() + t.Parallel() _, err := ParseAndCheckWithOptions(t, code, ParseAndCheckOptions{ diff --git a/runtime/tests/interpreter/range_value_test.go b/runtime/tests/interpreter/range_value_test.go index 4d873d1457..cb17855eec 100644 --- a/runtime/tests/interpreter/range_value_test.go +++ b/runtime/tests/interpreter/range_value_test.go @@ -20,6 +20,7 @@ package interpreter_test import ( "fmt" + "strings" "testing" "github.com/stretchr/testify/require" @@ -28,8 +29,22 @@ import ( "github.com/onflow/cadence/runtime/interpreter" "github.com/onflow/cadence/runtime/sema" "github.com/onflow/cadence/runtime/stdlib" + "github.com/onflow/cadence/runtime/tests/utils" + . "github.com/onflow/cadence/runtime/tests/utils" ) +type containsTestCase struct { + param int64 + expectedWithoutStep bool + expectedWithStep bool +} + +type inclusiveRangeConstructionTest struct { + ty sema.Type + s, e, step int64 + containsTests []containsTestCase +} + func TestInclusiveRange(t *testing.T) { t.Parallel() @@ -39,39 +54,343 @@ func TestInclusiveRange(t *testing.T) { baseActivation := activations.NewActivation(nil, interpreter.BaseActivation) interpreter.Declare(baseActivation, stdlib.InclusiveRangeConstructorFunction) - runValidCase := func(t *testing.T, memberType sema.Type, withStep bool) { - t.Run(memberType.String(), func(t *testing.T) { + unsignedContainsTestCases := []containsTestCase{ + { + param: 1, + expectedWithoutStep: true, + expectedWithStep: false, + }, + { + param: 12, + expectedWithoutStep: false, + expectedWithStep: false, + }, + { + param: 10, + expectedWithoutStep: true, + expectedWithStep: true, + }, + { + param: 0, + expectedWithoutStep: true, + expectedWithStep: true, + }, + { + param: 2, + expectedWithoutStep: true, + expectedWithStep: true, + }, + } + + signedContainsTestCasesForward := []containsTestCase{ + { + param: 1, + expectedWithoutStep: true, + expectedWithStep: false, + }, + { + param: 100, + expectedWithoutStep: false, + expectedWithStep: false, + }, + { + param: -100, + expectedWithoutStep: false, + expectedWithStep: false, + }, + { + param: 0, + expectedWithoutStep: true, + expectedWithStep: true, + }, + { + param: 4, + expectedWithoutStep: true, + expectedWithStep: true, + }, + { + param: 10, + expectedWithoutStep: true, + expectedWithStep: true, + }, + } + signedContainsTestCasesBackward := []containsTestCase{ + { + param: 1, + expectedWithoutStep: true, + expectedWithStep: false, + }, + { + param: 12, + expectedWithoutStep: false, + expectedWithStep: false, + }, + { + param: -12, + expectedWithoutStep: false, + expectedWithStep: false, + }, + { + param: 10, + expectedWithoutStep: true, + expectedWithStep: true, + }, + { + param: -10, + expectedWithoutStep: true, + expectedWithStep: true, + }, + { + param: -8, + expectedWithoutStep: true, + expectedWithStep: true, + }, + } + + validTestCases := []inclusiveRangeConstructionTest{ + // Int* + { + ty: sema.IntType, + s: 0, + e: 10, + step: 2, + containsTests: signedContainsTestCasesForward, + }, + { + ty: sema.IntType, + s: 10, + e: -10, + step: -2, + containsTests: signedContainsTestCasesBackward, + }, + { + ty: sema.Int8Type, + s: 0, + e: 10, + step: 2, + containsTests: signedContainsTestCasesForward, + }, + { + ty: sema.Int8Type, + s: 10, + e: -10, + step: -2, + containsTests: signedContainsTestCasesBackward, + }, + { + ty: sema.Int16Type, + s: 0, + e: 10, + step: 2, + containsTests: signedContainsTestCasesForward, + }, + { + ty: sema.Int16Type, + s: 10, + e: -10, + step: -2, + containsTests: signedContainsTestCasesBackward, + }, + { + ty: sema.Int32Type, + s: 0, + e: 10, + step: 2, + containsTests: signedContainsTestCasesForward, + }, + { + ty: sema.Int32Type, + s: 10, + e: -10, + step: -2, + containsTests: signedContainsTestCasesBackward, + }, + { + ty: sema.Int64Type, + s: 0, + e: 10, + step: 2, + containsTests: signedContainsTestCasesForward, + }, + { + ty: sema.Int64Type, + s: 10, + e: -10, + step: -2, + containsTests: signedContainsTestCasesBackward, + }, + { + ty: sema.Int128Type, + s: 0, + e: 10, + step: 2, + containsTests: signedContainsTestCasesForward, + }, + { + ty: sema.Int128Type, + s: 10, + e: -10, + step: -2, + containsTests: signedContainsTestCasesBackward, + }, + { + ty: sema.Int256Type, + s: 0, + e: 10, + step: 2, + containsTests: signedContainsTestCasesForward, + }, + { + ty: sema.Int256Type, + s: 10, + e: -10, + step: -2, + containsTests: signedContainsTestCasesBackward, + }, + + // UInt* + { + ty: sema.UIntType, + s: 0, + e: 10, + step: 2, + containsTests: unsignedContainsTestCases, + }, + { + ty: sema.UInt8Type, + s: 0, + e: 10, + step: 2, + containsTests: unsignedContainsTestCases, + }, + { + ty: sema.UInt16Type, + s: 0, + e: 10, + step: 2, + containsTests: unsignedContainsTestCases, + }, + { + ty: sema.UInt32Type, + s: 0, + e: 10, + step: 2, + containsTests: unsignedContainsTestCases, + }, + { + ty: sema.UInt64Type, + s: 0, + e: 10, + step: 2, + containsTests: unsignedContainsTestCases, + }, + { + ty: sema.UInt128Type, + s: 0, + e: 10, + step: 2, + containsTests: unsignedContainsTestCases, + }, + { + ty: sema.UInt256Type, + s: 0, + e: 10, + step: 2, + containsTests: unsignedContainsTestCases, + }, + + // Word* + { + ty: sema.Word8Type, + s: 0, + e: 10, + step: 2, + containsTests: unsignedContainsTestCases, + }, + { + ty: sema.Word16Type, + s: 0, + e: 10, + step: 2, + containsTests: unsignedContainsTestCases, + }, + { + ty: sema.Word32Type, + s: 0, + e: 10, + step: 2, + containsTests: unsignedContainsTestCases, + }, + { + ty: sema.Word64Type, + s: 0, + e: 10, + step: 2, + containsTests: unsignedContainsTestCases, + }, + { + ty: sema.Word128Type, + s: 0, + e: 10, + step: 2, + containsTests: unsignedContainsTestCases, + }, + { + ty: sema.Word256Type, + s: 0, + e: 10, + step: 2, + containsTests: unsignedContainsTestCases, + }, + } + + runValidCase := func(t *testing.T, testCase inclusiveRangeConstructionTest, withStep bool) { + t.Run(testCase.ty.String(), func(t *testing.T) { t.Parallel() + // Generate code for the contains calls. + var containsCode string + for i, tc := range testCase.containsTests { + containsCode += fmt.Sprintf("\nlet c_%d = r.contains(%d)", i, tc.param) + } + var code string if withStep { code = fmt.Sprintf( ` - let s : %s = 10 - let e : %s = 20 - let step : %s = 2 + let s : %s = %d + let e : %s = %d + let step : %s = %d let r = InclusiveRange(s, e, step: step) - let count = r.count - - let containsTest = r.contains(s) + %s `, - memberType.String(), memberType.String(), memberType.String()) + testCase.ty.String(), + testCase.s, + testCase.ty.String(), + testCase.e, + testCase.ty.String(), + testCase.step, + containsCode, + ) } else { code = fmt.Sprintf( ` - let s : %s = 10 - let e : %s = 20 + let s : %s = %d + let e : %s = %d let r = InclusiveRange(s, e) - let count = r.count - - let containsTest = r.contains(s) + %s `, - memberType.String(), memberType.String()) + testCase.ty.String(), + testCase.s, + testCase.ty.String(), + testCase.e, + containsCode, + ) } - _, err := parseCheckAndInterpretWithOptions(t, code, + inter, err := parseCheckAndInterpretWithOptions(t, code, ParseCheckAndInterpretOptions{ CheckerConfig: &sema.Config{ BaseValueActivation: baseValueActivation, @@ -83,23 +402,164 @@ func TestInclusiveRange(t *testing.T) { ) require.NoError(t, err) + + elementType := interpreter.ConvertSemaToStaticType( + nil, + testCase.ty, + ) + rangeType := interpreter.NewInclusiveRangeStaticType(nil, elementType) + + var expectedRangeValue *interpreter.CompositeValue + + if withStep { + expectedRangeValue = interpreter.NewInclusiveRangeValueWithStep( + inter, + interpreter.EmptyLocationRange, + interpreter.GetValueForIntegerType(testCase.s, elementType), + interpreter.GetValueForIntegerType(testCase.e, elementType), + interpreter.GetValueForIntegerType(testCase.step, elementType), + rangeType, + ) + } else { + expectedRangeValue = interpreter.NewInclusiveRangeValue( + inter, + interpreter.EmptyLocationRange, + interpreter.GetValueForIntegerType(testCase.s, elementType), + interpreter.GetValueForIntegerType(testCase.e, elementType), + rangeType, + ) + } + + utils.AssertValuesEqual( + t, + inter, + expectedRangeValue, + inter.Globals.Get("r").GetValue(), + ) + + // Check that contains returns correct information. + for i, tc := range testCase.containsTests { + var expectedValue interpreter.Value + if withStep { + expectedValue = interpreter.AsBoolValue(tc.expectedWithStep) + } else { + expectedValue = interpreter.AsBoolValue(tc.expectedWithoutStep) + } + + utils.AssertValuesEqual( + t, + inter, + expectedValue, + inter.Globals.Get(fmt.Sprintf("c_%d", i)).GetValue(), + ) + } }) } - runValidCaseWithoutStep := func(t *testing.T, memberType sema.Type) { - runValidCase(t, memberType, false) + // Run each test case with and without step. + for _, testCase := range validTestCases { + runValidCase(t, testCase, true) + runValidCase(t, testCase, false) } - runValidCaseWithStep := func(t *testing.T, memberType sema.Type) { - runValidCase(t, memberType, true) +} + +func TestInclusiveRangeConstructionInvalid(t *testing.T) { + t.Parallel() + + baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation) + baseValueActivation.DeclareValue(stdlib.InclusiveRangeConstructorFunction) + + baseActivation := activations.NewActivation(nil, interpreter.BaseActivation) + interpreter.Declare(baseActivation, stdlib.InclusiveRangeConstructorFunction) + + runInvalidCase := func(t *testing.T, label, code string, expectedError error, expectedMessage string) { + t.Run(label, func(t *testing.T) { + t.Parallel() + + _, err := parseCheckAndInterpretWithOptions(t, code, + ParseCheckAndInterpretOptions{ + CheckerConfig: &sema.Config{ + BaseValueActivation: baseValueActivation, + }, + Config: &interpreter.Config{ + BaseActivation: baseActivation, + }, + }, + ) + + RequireError(t, err) + + require.ErrorAs(t, err, expectedError) + require.True(t, strings.Contains(err.Error(), expectedMessage)) + }) } for _, integerType := range sema.AllIntegerTypes { + // Only test leaf types switch integerType { case sema.IntegerType, sema.SignedIntegerType: continue } - runValidCaseWithStep(t, integerType) - runValidCaseWithoutStep(t, integerType) + typeString := integerType.String() + + // step = 0. + runInvalidCase( + t, + typeString, + fmt.Sprintf("let r = InclusiveRange(%s(1), %s(2), step: %s(0))", typeString, typeString, typeString), + &interpreter.InclusiveRangeConstructionError{}, + "step value cannot be zero", + ) + + // step takes sequence away from end. + runInvalidCase( + t, + typeString, + fmt.Sprintf("let r = InclusiveRange(%s(40), %s(2), step: %s(2))", typeString, typeString, typeString), + &interpreter.InclusiveRangeConstructionError{}, + "sequence is moving away from end", + ) + } + + // Additional invalid cases for signed integer types + for _, integerType := range sema.AllSignedIntegerTypes { + // Only test leaf types + switch integerType { + case sema.SignedIntegerType: + continue + } + + typeString := integerType.String() + + // step takes sequence away from end with step being negative. + // This would be a checker error for unsigned integers but a + // runtime error in signed integers. + runInvalidCase( + t, + typeString, + fmt.Sprintf("let r = InclusiveRange(%s(4), %s(100), step: %s(-2))", typeString, typeString, typeString), + &interpreter.InclusiveRangeConstructionError{}, + "sequence is moving away from end", + ) + } + + // Additional invalid cases for unsigned integer types + for _, integerType := range sema.AllUnsignedIntegerTypes { + // Only test leaf types + switch integerType { + case sema.IntegerType: + continue + } + + typeString := integerType.String() + + runInvalidCase( + t, + typeString, + fmt.Sprintf("let r = InclusiveRange(%s(40), %s(1))", typeString, typeString), + &interpreter.InclusiveRangeConstructionError{}, + "step value cannot be negative for unsigned integer type", + ) } }