diff --git a/sc/shared/src/test/scala/sigmastate/helpers/SigmaPPrint.scala b/parsers/shared/src/test/scala/sigmastate/helpers/SigmaPPrint.scala similarity index 98% rename from sc/shared/src/test/scala/sigmastate/helpers/SigmaPPrint.scala rename to parsers/shared/src/test/scala/sigmastate/helpers/SigmaPPrint.scala index 1b0f7b112d..24aaeddefd 100644 --- a/sc/shared/src/test/scala/sigmastate/helpers/SigmaPPrint.scala +++ b/parsers/shared/src/test/scala/sigmastate/helpers/SigmaPPrint.scala @@ -5,15 +5,13 @@ import org.ergoplatform.ErgoBox.RegisterId import org.ergoplatform.settings.ErgoAlgos import pprint.{PPrinter, Tree} import sigma.ast.SCollection.{SBooleanArray, SByteArray, SByteArray2} -import sigma.ast._ +import sigma.ast.{ConstantNode, FuncValue, MethodCall, ValueCompanion, _} import sigma.crypto.EcPointType import sigma.data.{AvlTreeData, AvlTreeFlags, CollType, PrimitiveType, TrivialProp} import sigma.serialization.GroupElementSerializer import sigma.{Coll, GroupElement} -import sigma.ast.{ConstantNode, FuncValue, ValueCompanion} -import sigmastate._ import sigmastate.crypto.GF2_192_Poly -import sigma.ast.MethodCall + import java.math.BigInteger import scala.collection.compat.immutable.ArraySeq import scala.collection.mutable diff --git a/parsers/shared/src/test/scala/sigmastate/lang/LangTests.scala b/parsers/shared/src/test/scala/sigmastate/lang/LangTests.scala index 32943bca44..de83070ac3 100644 --- a/parsers/shared/src/test/scala/sigmastate/lang/LangTests.scala +++ b/parsers/shared/src/test/scala/sigmastate/lang/LangTests.scala @@ -79,4 +79,10 @@ trait LangTests extends Matchers with NegativeTesting { node }))(tree) } + + /** Execute the given `block` having `version` as both activated and ErgoTree version. */ + def runWithVersion[T](version: Byte)(block: => T): T = { + VersionContext.withVersions(version, version)(block) + } + } diff --git a/parsers/shared/src/test/scala/sigmastate/lang/SigmaParserTest.scala b/parsers/shared/src/test/scala/sigmastate/lang/SigmaParserTest.scala index 02b28f86ca..dc63330f95 100644 --- a/parsers/shared/src/test/scala/sigmastate/lang/SigmaParserTest.scala +++ b/parsers/shared/src/test/scala/sigmastate/lang/SigmaParserTest.scala @@ -14,6 +14,7 @@ import SigmaPredef.PredefinedFuncRegistry import sigma.ast.syntax._ import sigmastate.lang.parsers.ParserException import sigma.serialization.OpCodes +import sigmastate.helpers.SigmaPPrint class SigmaParserTest extends AnyPropSpec with ScalaCheckPropertyChecks with Matchers with LangTests { import StdSigmaBuilder._ @@ -34,6 +35,17 @@ class SigmaParserTest extends AnyPropSpec with ScalaCheckPropertyChecks with Mat } } + /** Checks parsing result, printing the actual value as a test vector if expected value + * is not equal to actual. + */ + def checkParsed(x: String, expected: SValue) = { + val parsed = parse(x) + if (expected != parsed) { + SigmaPPrint.pprintln(parsed, width = 100) + } + parsed shouldBe expected + } + def parseWithException(x: String): SValue = { SigmaParser(x) match { case Parsed.Success(v, _) => v diff --git a/sc/jvm/src/test/scala/sigmastate/helpers/SigmaPPrintSpec.scala b/sc/jvm/src/test/scala/sigmastate/helpers/SigmaPPrintSpec.scala index 54c0f652dc..ffd591b0df 100644 --- a/sc/jvm/src/test/scala/sigmastate/helpers/SigmaPPrintSpec.scala +++ b/sc/jvm/src/test/scala/sigmastate/helpers/SigmaPPrintSpec.scala @@ -8,7 +8,6 @@ import sigma.SigmaDslTesting import sigma.ast._ import sigma.data.{AvlTreeData, AvlTreeFlags, CBox, CollType, Digest32Coll} import ErgoTree.HeaderType -import sigmastate.eval._ import sigma.ast.MethodCall import sigma.serialization.OpCodes import sigmastate.utils.Helpers diff --git a/sc/shared/src/test/scala/sigma/LanguageSpecificationBase.scala b/sc/shared/src/test/scala/sigma/LanguageSpecificationBase.scala new file mode 100644 index 0000000000..2bb44fc910 --- /dev/null +++ b/sc/shared/src/test/scala/sigma/LanguageSpecificationBase.scala @@ -0,0 +1,126 @@ +package sigma + +import org.scalatest.BeforeAndAfterAll +import sigma.ast.JitCost +import sigma.eval.{EvalSettings, Profiler} +import sigmastate.CompilerCrossVersionProps +import sigmastate.interpreter.CErgoTreeEvaluator +import scala.util.Success + +/** Base class for language test suites (one suite for each language version: 5.0, 6.0, etc.) + * Each suite tests every method of every SigmaDsl type to be equivalent to + * the evaluation of the corresponding ErgoScript operation. + * + * The properties of this suite exercise two interpreters: the current (aka `old` + * interpreter) and the new interpreter for a next soft-fork. After the soft-fork is + * released, the new interpreter becomes current at which point the `old` and `new` + * interpreters in this suite should be equivalent. This change is reflected in this + * suite by commiting changes in expected values. + * The `old` and `new` interpreters are compared like the following: + * 1) for existingFeature the interpreters should be equivalent + * 2) for changedFeature the test cases contain different expected values + * 3) for newFeature the old interpreter should throw and the new interpreter is checked + * against expected values. + * + * This suite can be used for Cost profiling, i.e. measurements of operations times and + * comparing them with cost parameters of the operations. + * + * The following settings should be specified for profiling: + * isMeasureOperationTime = true + * isMeasureScriptTime = true + * isLogEnabled = false + * printTestVectors = false + * costTracingEnabled = false + * isTestRun = true + * perTestWarmUpIters = 1 + * nBenchmarkIters = 1 + */ +abstract class LanguageSpecificationBase extends SigmaDslTesting + with CompilerCrossVersionProps + with BeforeAndAfterAll { suite => + + /** Version of the language (ErgoScript/ErgoTree) which is specified by this suite. */ + def languageVersion: Byte + + /** Use VersionContext so that each property in this suite runs under correct + * parameters. + */ + protected override def testFun_Run(testName: String, testFun: => Any): Unit = { + VersionContext.withVersions(activatedVersionInTests, ergoTreeVersionInTests) { + super.testFun_Run(testName, testFun) + } + } + + implicit override val generatorDrivenConfig: PropertyCheckConfiguration = PropertyCheckConfiguration(minSuccessful = 30) + + val evalSettingsInTests = CErgoTreeEvaluator.DefaultEvalSettings.copy( + isMeasureOperationTime = true, + isMeasureScriptTime = true, + isLogEnabled = false, // don't commit the `true` value (travis log is too high) + printTestVectors = false, // don't commit the `true` value (travis log is too high) + + /** Should always be enabled in tests (and false by default) + * Should be disabled for cost profiling, which case the new costs are not checked. + */ + costTracingEnabled = true, + profilerOpt = Some(CErgoTreeEvaluator.DefaultProfiler), + isTestRun = true + ) + + def warmupSettings(p: Profiler) = evalSettingsInTests.copy( + isLogEnabled = false, + printTestVectors = false, + profilerOpt = Some(p) + ) + + implicit override def evalSettings: EvalSettings = { + warmupProfiler match { + case Some(p) => warmupSettings(p) + case _ => evalSettingsInTests + } + } + + override val perTestWarmUpIters = 0 + + override val nBenchmarkIters = 0 + + override val okRunTestsWithoutMCLowering: Boolean = true + + implicit def IR = createIR() + + def testCases[A, B](cases: Seq[(A, Expected[B])], f: Feature[A, B]) = { + val table = Table(("x", "y"), cases: _*) + forAll(table) { (x, expectedRes) => + val res = f.checkEquality(x) + val resValue = res.map(_._1) + val (expected, expDetailsOpt) = expectedRes.newResults(ergoTreeVersionInTests) + checkResult(resValue, expected.value, failOnTestVectors = true, + "SigmaDslSpecifiction#testCases: compare expected new result with res = f.checkEquality(x)") + res match { + case Success((value, details)) => + details.cost shouldBe JitCost(expected.verificationCost.get) + expDetailsOpt.foreach(expDetails => + if (details.trace != expDetails.trace) { + printCostDetails(f.script, details) + details.trace shouldBe expDetails.trace + } + ) + } + } + } + + override protected def beforeAll(): Unit = { + prepareSamples[BigInt] + prepareSamples[GroupElement] + prepareSamples[AvlTree] + prepareSamples[Box] + prepareSamples[PreHeader] + prepareSamples[Header] + prepareSamples[(BigInt, BigInt)] + prepareSamples[(GroupElement, GroupElement)] + prepareSamples[(AvlTree, AvlTree)] + prepareSamples[(Box, Box)] + prepareSamples[(PreHeader, PreHeader)] + prepareSamples[(Header, Header)] + } +} diff --git a/sc/shared/src/test/scala/sigma/SigmaDslSpecification.scala b/sc/shared/src/test/scala/sigma/LanguageSpecificationV5.scala similarity index 96% rename from sc/shared/src/test/scala/sigma/SigmaDslSpecification.scala rename to sc/shared/src/test/scala/sigma/LanguageSpecificationV5.scala index c820e65e73..700b48fd13 100644 --- a/sc/shared/src/test/scala/sigma/SigmaDslSpecification.scala +++ b/sc/shared/src/test/scala/sigma/LanguageSpecificationV5.scala @@ -5,31 +5,28 @@ import org.ergoplatform._ import org.ergoplatform.settings.ErgoAlgos import org.scalacheck.Arbitrary._ import org.scalacheck.{Arbitrary, Gen} -import org.scalatest.BeforeAndAfterAll import scorex.crypto.authds.avltree.batch._ import scorex.crypto.authds.{ADKey, ADValue} import scorex.crypto.hash.{Blake2b256, Digest32} import scorex.util.ModifierId import sigma.Extensions.{ArrayOps, CollOps} +import sigma.ast.ErgoTree.{HeaderType, ZeroHeader} import sigma.ast.SCollection._ -import sigma.ast._ import sigma.ast.syntax._ +import sigma.ast.{Apply, MethodCall, PropertyCall, _} +import sigma.data.OrderingOps._ import sigma.data.RType._ import sigma.data._ +import sigma.eval.Extensions.{ByteExt, IntExt, LongExt, ShortExt} +import sigma.eval.{CostDetails, EvalSettings, SigmaDsl, TracedCost} +import sigma.exceptions.InvalidType +import sigma.serialization.ValueCodes.OpCode import sigma.util.Extensions.{BooleanOps, IntOps, LongOps} import sigma.{VersionContext, ast, data, _} -import ErgoTree.{HeaderType, ZeroHeader} -import sigma.eval.{CostDetails, EvalSettings, Profiler, SigmaDsl, TracedCost} -import sigmastate._ import sigmastate.eval.Extensions.AvlTreeOps -import sigma.eval.Extensions.{ByteExt, IntExt, LongExt, ShortExt} -import OrderingOps._ import sigmastate.eval._ import sigmastate.helpers.TestingHelpers._ import sigmastate.interpreter._ -import sigma.ast.{Apply, MethodCall, PropertyCall} -import sigma.exceptions.InvalidType -import sigma.serialization.ValueCodes.OpCode import sigmastate.utils.Extensions._ import sigmastate.utils.Helpers import sigmastate.utils.Helpers._ @@ -38,122 +35,18 @@ import java.math.BigInteger import scala.collection.compat.immutable.ArraySeq import scala.util.{Failure, Success} -/** This suite tests every method of every SigmaDsl type to be equivalent to - * the evaluation of the corresponding ErgoScript operation. - * - * The properties of this suite excercise two interpreters: the current (aka `old` - * interpreter) and the new interpreter for a next soft-fork. After the soft-fork is - * released, the new interpreter becomes current at which point the `old` and `new` - * interpreters in this suite should be equivalent. This change is reflected in this - * suite by commiting changes in expected values. - * The `old` and `new` interpreters are compared like the following: - * 1) for existingFeature the interpreters should be equivalent - * 2) for changedFeature the test cases contain different expected values - * 3) for newFeature the old interpreter should throw and the new interpreter is checked - * against expected values. - * - * This suite can be used for Cost profiling, i.e. measurements of operations times and - * comparing them with cost parameteres of the operations. + +/** This suite tests all operations for v5.0 version of the language. + * The base classes establish the infrastructure for the tests. * - * The following settings should be specified for profiling: - * isMeasureOperationTime = true - * isMeasureScriptTime = true - * isLogEnabled = false - * printTestVectors = false - * costTracingEnabled = false - * isTestRun = true - * perTestWarmUpIters = 1 - * nBenchmarkIters = 1 + * @see SigmaDslSpecificationBase */ -class SigmaDslSpecification extends SigmaDslTesting - with CompilerCrossVersionProps - with BeforeAndAfterAll { suite => - - /** Use VersionContext so that each property in this suite runs under correct - * parameters. - */ - protected override def testFun_Run(testName: String, testFun: => Any): Unit = { - VersionContext.withVersions(activatedVersionInTests, ergoTreeVersionInTests) { - super.testFun_Run(testName, testFun) - } - } - - implicit override val generatorDrivenConfig = PropertyCheckConfiguration(minSuccessful = 30) +class LanguageSpecificationV5 extends LanguageSpecificationBase { suite => - val evalSettingsInTests = CErgoTreeEvaluator.DefaultEvalSettings.copy( - isMeasureOperationTime = true, - isMeasureScriptTime = true, - isLogEnabled = false, // don't commit the `true` value (travis log is too high) - printTestVectors = false, // don't commit the `true` value (travis log is too high) - - /** Should always be enabled in tests (and false by default) - * Should be disabled for cost profiling, which case the new costs are not checked. - */ - costTracingEnabled = true, - - profilerOpt = Some(CErgoTreeEvaluator.DefaultProfiler), - isTestRun = true - ) - - def warmupSettings(p: Profiler) = evalSettingsInTests.copy( - isLogEnabled = false, - printTestVectors = false, - profilerOpt = Some(p) - ) - - implicit override def evalSettings: EvalSettings = { - warmupProfiler match { - case Some(p) => warmupSettings(p) - case _ => evalSettingsInTests - } - } - - override val perTestWarmUpIters = 0 - - override val nBenchmarkIters = 0 - - override val okRunTestsWithoutMCLowering: Boolean = true - - implicit def IR = createIR() - - def testCases[A, B](cases: Seq[(A, Expected[B])], f: Feature[A, B]) = { - val table = Table(("x", "y"), cases:_*) - forAll(table) { (x, expectedRes) => - val res = f.checkEquality(x) - val resValue = res.map(_._1) - val (expected, expDetailsOpt) = expectedRes.newResults(ergoTreeVersionInTests) - checkResult(resValue, expected.value, failOnTestVectors = true, - "SigmaDslSpecifiction#testCases: compare expected new result with res = f.checkEquality(x)") - res match { - case Success((value, details)) => - details.cost shouldBe JitCost(expected.verificationCost.get) - expDetailsOpt.foreach(expDetails => - if (details.trace != expDetails.trace) { - printCostDetails(f.script, details) - details.trace shouldBe expDetails.trace - } - ) - } - } - } + override def languageVersion: Byte = VersionContext.JitActivationVersion import TestData._ - override protected def beforeAll(): Unit = { - prepareSamples[BigInt] - prepareSamples[GroupElement] - prepareSamples[AvlTree] - prepareSamples[Box] - prepareSamples[PreHeader] - prepareSamples[Header] - prepareSamples[(BigInt, BigInt)] - prepareSamples[(GroupElement, GroupElement)] - prepareSamples[(AvlTree, AvlTree)] - prepareSamples[(Box, Box)] - prepareSamples[(PreHeader, PreHeader)] - prepareSamples[(Header, Header)] - } - ///===================================================== /// CostDetails shared among test cases ///----------------------------------------------------- @@ -232,17 +125,6 @@ class SigmaDslSpecification extends SigmaDslTesting /// Boolean type operations ///----------------------------------------------------- - property("Boolean methods equivalence") { - val toByte = newFeature((x: Boolean) => x.toByte, "{ (x: Boolean) => x.toByte }") - - val cases = Seq( - (true, Success(1.toByte)), - (false, Success(0.toByte)) - ) - - testCases(cases, toByte) - } - property("BinXor(logical XOR) equivalence") { val binXor = existingFeature((x: (Boolean, Boolean)) => x._1 ^ x._2, "{ (x: (Boolean, Boolean)) => x._1 ^ x._2 }", @@ -1057,31 +939,6 @@ class SigmaDslSpecification extends SigmaDslTesting swapArgs(LE_cases, cost = 1768, newCostDetails = binaryRelationCostDetails(GE, SByte)), ">=", GE.apply)(_ >= _) } - - property("Byte methods equivalence (new features)") { - lazy val toBytes = newFeature((x: Byte) => x.toBytes, "{ (x: Byte) => x.toBytes }") - lazy val toAbs = newFeature((x: Byte) => x.toAbs, "{ (x: Byte) => x.toAbs }") - lazy val compareTo = newFeature( - (x: (Byte, Byte)) => x._1.compareTo(x._2), - "{ (x: (Byte, Byte)) => x._1.compareTo(x._2) }") - - lazy val bitOr = newFeature( - { (x: (Byte, Byte)) => (x._1 | x._2).toByteExact }, - "{ (x: (Byte, Byte)) => (x._1 | x._2).toByteExact }") - - lazy val bitAnd = newFeature( - { (x: (Byte, Byte)) => (x._1 & x._2).toByteExact }, - "{ (x: (Byte, Byte)) => (x._1 & x._2).toByteExact }") - - forAll { x: Byte => - Seq(toBytes, toAbs).foreach(f => f.checkEquality(x)) - } - - forAll { x: (Byte, Byte) => - Seq(compareTo, bitOr, bitAnd).foreach(_.checkEquality(x)) - } - } - property("Short methods equivalence") { SShort.upcast(0.toShort) shouldBe 0.toShort // boundary test case SShort.downcast(0.toShort) shouldBe 0.toShort // boundary test case @@ -1362,29 +1219,6 @@ class SigmaDslSpecification extends SigmaDslTesting ">=", GE.apply)(_ >= _) } - property("Short methods equivalence (new features)") { - lazy val toBytes = newFeature((x: Short) => x.toBytes, "{ (x: Short) => x.toBytes }") - lazy val toAbs = newFeature((x: Short) => x.toAbs, "{ (x: Short) => x.toAbs }") - - lazy val compareTo = newFeature((x: (Short, Short)) => x._1.compareTo(x._2), - "{ (x: (Short, Short)) => x._1.compareTo(x._2) }") - - lazy val bitOr = newFeature( - { (x: (Short, Short)) => (x._1 | x._2).toShortExact }, - "{ (x: (Short, Short)) => x._1 | x._2 }") - - lazy val bitAnd = newFeature( - { (x: (Short, Short)) => (x._1 & x._2).toShortExact }, - "{ (x: (Short, Short)) => x._1 & x._2 }") - - forAll { x: Short => - Seq(toBytes, toAbs).foreach(_.checkEquality(x)) - } - forAll { x: (Short, Short) => - Seq(compareTo, bitOr, bitAnd).foreach(_.checkEquality(x)) - } - } - property("Int methods equivalence") { SInt.upcast(0) shouldBe 0 // boundary test case SInt.downcast(0) shouldBe 0 // boundary test case @@ -1665,28 +1499,6 @@ class SigmaDslSpecification extends SigmaDslTesting ">=", GE.apply)(_ >= _) } - property("Int methods equivalence (new features)") { - lazy val toBytes = newFeature((x: Int) => x.toBytes, "{ (x: Int) => x.toBytes }") - lazy val toAbs = newFeature((x: Int) => x.toAbs, "{ (x: Int) => x.toAbs }") - lazy val compareTo = newFeature((x: (Int, Int)) => x._1.compareTo(x._2), - "{ (x: (Int, Int)) => x._1.compareTo(x._2) }") - - lazy val bitOr = newFeature( - { (x: (Int, Int)) => x._1 | x._2 }, - "{ (x: (Int, Int)) => x._1 | x._2 }") - - lazy val bitAnd = newFeature( - { (x: (Int, Int)) => x._1 & x._2 }, - "{ (x: (Int, Int)) => x._1 & x._2 }") - - forAll { x: Int => - Seq(toBytes, toAbs).foreach(_.checkEquality(x)) - } - forAll { x: (Int, Int) => - Seq(compareTo, bitOr, bitAnd).foreach(_.checkEquality(x)) - } - } - property("Long downcast and upcast identity") { forAll { x: Long => SLong.upcast(x) shouldBe x // boundary test case @@ -1984,28 +1796,6 @@ class SigmaDslSpecification extends SigmaDslTesting ">=", GE.apply)(_ >= _) } - property("Long methods equivalence (new features)") { - lazy val toBytes = newFeature((x: Long) => x.toBytes, "{ (x: Long) => x.toBytes }") - lazy val toAbs = newFeature((x: Long) => x.toAbs, "{ (x: Long) => x.toAbs }") - lazy val compareTo = newFeature((x: (Long, Long)) => x._1.compareTo(x._2), - "{ (x: (Long, Long)) => x._1.compareTo(x._2) }") - - lazy val bitOr = newFeature( - { (x: (Long, Long)) => x._1 | x._2 }, - "{ (x: (Long, Long)) => x._1 | x._2 }") - - lazy val bitAnd = newFeature( - { (x: (Long, Long)) => x._1 & x._2 }, - "{ (x: (Long, Long)) => x._1 & x._2 }") - - forAll { x: Long => - Seq(toBytes, toAbs).foreach(_.checkEquality(x)) - } - forAll { x: (Long, Long) => - Seq(compareTo, bitOr, bitAnd).foreach(_.checkEquality(x)) - } - } - property("BigInt methods equivalence") { verifyCases( { @@ -2264,59 +2054,6 @@ class SigmaDslSpecification extends SigmaDslTesting ">=", GE.apply)(o.gteq(_, _)) } - property("BigInt methods equivalence (new features)") { - // TODO v6.0: the behavior of `upcast` for BigInt is different from all other Numeric types (see https://github.com/ScorexFoundation/sigmastate-interpreter/issues/877) - // The `Upcast(bigInt, SBigInt)` node is never produced by ErgoScript compiler, but is still valid ErgoTree. - // It makes sense to fix this inconsistency as part of upcoming forks - assertExceptionThrown( - SBigInt.upcast(CBigInt(new BigInteger("0", 16)).asInstanceOf[AnyVal]), - _.getMessage.contains("Cannot upcast value") - ) - - // TODO v6.0: the behavior of `downcast` for BigInt is different from all other Numeric types (see https://github.com/ScorexFoundation/sigmastate-interpreter/issues/877) - // The `Downcast(bigInt, SBigInt)` node is never produced by ErgoScript compiler, but is still valid ErgoTree. - // It makes sense to fix this inconsistency as part of HF - assertExceptionThrown( - SBigInt.downcast(CBigInt(new BigInteger("0", 16)).asInstanceOf[AnyVal]), - _.getMessage.contains("Cannot downcast value") - ) - - val toByte = newFeature((x: BigInt) => x.toByte, - "{ (x: BigInt) => x.toByte }", - FuncValue(Vector((1, SBigInt)), Downcast(ValUse(1, SBigInt), SByte))) - - val toShort = newFeature((x: BigInt) => x.toShort, - "{ (x: BigInt) => x.toShort }", - FuncValue(Vector((1, SBigInt)), Downcast(ValUse(1, SBigInt), SShort))) - - val toInt = newFeature((x: BigInt) => x.toInt, - "{ (x: BigInt) => x.toInt }", - FuncValue(Vector((1, SBigInt)), Downcast(ValUse(1, SBigInt), SInt))) - - val toLong = newFeature((x: BigInt) => x.toLong, - "{ (x: BigInt) => x.toLong }", - FuncValue(Vector((1, SBigInt)), Downcast(ValUse(1, SBigInt), SLong))) - - lazy val toBytes = newFeature((x: BigInt) => x.toBytes, "{ (x: BigInt) => x.toBytes }") - lazy val toAbs = newFeature((x: BigInt) => x.toAbs, "{ (x: BigInt) => x.toAbs }") - - lazy val compareTo = newFeature((x: (BigInt, BigInt)) => x._1.compareTo(x._2), - "{ (x: (BigInt, BigInt)) => x._1.compareTo(x._2) }") - - lazy val bitOr = newFeature({ (x: (BigInt, BigInt)) => x._1 | x._2 }, - "{ (x: (BigInt, BigInt)) => x._1 | x._2 }") - - lazy val bitAnd = newFeature({ (x: (BigInt, BigInt)) => x._1 & x._2 }, - "{ (x: (BigInt, BigInt)) => x._1 & x._2 }") - - forAll { x: BigInt => - Seq(toByte, toShort, toInt, toLong, toBytes, toAbs).foreach(_.checkEquality(x)) - } - forAll { x: (BigInt, BigInt) => - Seq(compareTo, bitOr, bitAnd).foreach(_.checkEquality(x)) - } - } - /** Executed a series of test cases of NEQ operation verify using two _different_ * data instances `x` and `y`. * @param cost the expected cost of `verify` (the same for all cases) @@ -4051,16 +3788,6 @@ class SigmaDslSpecification extends SigmaDslTesting ))) } - property("Box properties equivalence (new features)") { - // TODO v6.0: related to https://github.com/ScorexFoundation/sigmastate-interpreter/issues/416 - val getReg = newFeature((x: Box) => x.getReg[Int](1).get, - "{ (x: Box) => x.getReg[Int](1).get }") - - forAll { box: Box => - Seq(getReg).foreach(_.checkEquality(box)) - } - } - property("Conditional access to registers") { def boxWithRegisters(regs: AdditionalRegisters): Box = { SigmaDsl.Box(testBox(20, TrueTree, 0, Seq(), regs)) @@ -7638,36 +7365,6 @@ class SigmaDslSpecification extends SigmaDslTesting preGeneratedSamples = Some(samples)) } - // TODO v6.0 (3h): https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479 - property("Coll find method equivalence") { - val find = newFeature((x: Coll[Int]) => x.find({ (v: Int) => v > 0 }), - "{ (x: Coll[Int]) => x.find({ (v: Int) => v > 0} ) }") - forAll { x: Coll[Int] => - find.checkEquality(x) - } - } - - // TODO v6.0 (3h): https://github.com/ScorexFoundation/sigmastate-interpreter/issues/418 - property("Coll bitwise methods equivalence") { - val shiftRight = newFeature( - { (x: Coll[Boolean]) => - if (x.size > 2) x.slice(0, x.size - 2) else Colls.emptyColl[Boolean] - }, - "{ (x: Coll[Boolean]) => x >> 2 }") - forAll { x: Array[Boolean] => - shiftRight.checkEquality(Colls.fromArray(x)) - } - } - - // TODO v6.0 (3h): https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479 - property("Coll diff methods equivalence") { - val diff = newFeature((x: (Coll[Int], Coll[Int])) => x._1.diff(x._2), - "{ (x: (Coll[Int], Coll[Int])) => x._1.diff(x._2) }") - forAll { (x: Coll[Int], y: Coll[Int]) => - diff.checkEquality((x, y)) - } - } - property("Coll fold method equivalence") { val n = ExactNumeric.IntIsExactNumeric val costDetails1 = TracedCost( @@ -9079,17 +8776,6 @@ class SigmaDslSpecification extends SigmaDslTesting ) )) } - // TODO v6.0: implement Option.fold (see https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479) - property("Option new methods") { - val n = ExactNumeric.LongIsExactNumeric - val fold = newFeature({ (x: Option[Long]) => x.fold(5.toLong)( (v: Long) => n.plus(v, 1) ) }, - "{ (x: Option[Long]) => x.fold(5, { (v: Long) => v + 1 }) }") - - forAll { x: Option[Long] => - Seq(fold).map(_.checkEquality(x)) - } - } - property("Option fold workaround method") { val costDetails1 = TracedCost( traceBase ++ Array( @@ -9525,24 +9211,6 @@ class SigmaDslSpecification extends SigmaDslTesting preGeneratedSamples = Some(Seq())) } - // TODO v6.0 (3h): implement allZK func https://github.com/ScorexFoundation/sigmastate-interpreter/issues/543 - property("allZK equivalence") { - lazy val allZK = newFeature((x: Coll[SigmaProp]) => SigmaDsl.allZK(x), - "{ (x: Coll[SigmaProp]) => allZK(x) }") - forAll { x: Coll[SigmaProp] => - allZK.checkEquality(x) - } - } - - // TODO v6.0 (3h): implement anyZK func https://github.com/ScorexFoundation/sigmastate-interpreter/issues/543 - property("anyZK equivalence") { - lazy val anyZK = newFeature((x: Coll[SigmaProp]) => SigmaDsl.anyZK(x), - "{ (x: Coll[SigmaProp]) => anyZK(x) }") - forAll { x: Coll[SigmaProp] => - anyZK.checkEquality(x) - } - } - property("allOf equivalence") { def costDetails(i: Int) = TracedCost(traceBase :+ ast.SeqCostItem(CompanionDesc(AND), PerItemCost(JitCost(10), JitCost(5), 32), i)) verifyCases( diff --git a/sc/shared/src/test/scala/sigma/LanguageSpecificationV6.scala b/sc/shared/src/test/scala/sigma/LanguageSpecificationV6.scala new file mode 100644 index 0000000000..e82bcd886b --- /dev/null +++ b/sc/shared/src/test/scala/sigma/LanguageSpecificationV6.scala @@ -0,0 +1,348 @@ +package sigma + +import sigma.ast.{Apply, Downcast, FixedCost, FixedCostItem, FuncValue, GetVar, Global, JitCost, MethodCall, NamedDesc, OptionGet, SBigInt, SByte, SGlobalMethods, SInt, SLong, SShort, STypeVar, ValUse} +import sigma.data.{CBigInt, ExactNumeric, RType} +import sigma.eval.{SigmaDsl, TracedCost} +import sigma.util.Extensions.{BooleanOps, ByteOps, IntOps, LongOps} +import sigmastate.exceptions.MethodNotFound + +import java.math.BigInteger +import scala.util.Success + +/** This suite tests all operations for v6.0 version of the language. + * The base classes establish the infrastructure for the tests. + * + * @see SigmaDslSpecificationBase + */ +class LanguageSpecificationV6 extends LanguageSpecificationBase { suite => + override def languageVersion: Byte = VersionContext.V6SoftForkVersion + + implicit override def evalSettings = super.evalSettings.copy(printTestVectors = true) + + + val baseTrace = Array( + FixedCostItem(Apply), + FixedCostItem(FuncValue), + FixedCostItem(GetVar), + FixedCostItem(OptionGet), + FixedCostItem(FuncValue.AddToEnvironmentDesc, FixedCost(JitCost(5))) + ) + + property("Boolean.toByte") { + val toByte = newFeature((x: Boolean) => x.toByte, "{ (x: Boolean) => x.toByte }", + sinceVersion = VersionContext.V6SoftForkVersion + ) + + val cases = Seq( + (true, Success(1.toByte)), + (false, Success(0.toByte)) + ) + + if (toByte.isSupportedIn(VersionContext.current)) { + // TODO v6.0: implement as part of https://github.com/ScorexFoundation/sigmastate-interpreter/pull/932 + assertExceptionThrown( + testCases(cases, toByte), + rootCauseLike[MethodNotFound]("Cannot find method") + ) + } + else + testCases(cases, toByte) + } + + property("Byte methods equivalence (new features)") { + // TODO v6.0: implement as part of https://github.com/ScorexFoundation/sigmastate-interpreter/issues/474 + if (activatedVersionInTests < VersionContext.V6SoftForkVersion) { + // NOTE, for such versions the new features are not supported + // which is checked below + + lazy val toAbs = newFeature((x: Byte) => x.toAbs, "{ (x: Byte) => x.toAbs }", + sinceVersion = VersionContext.V6SoftForkVersion) + + lazy val compareTo = newFeature( + (x: (Byte, Byte)) => x._1.compareTo(x._2), + "{ (x: (Byte, Byte)) => x._1.compareTo(x._2) }", + sinceVersion = VersionContext.V6SoftForkVersion) + + lazy val bitOr = newFeature( + { (x: (Byte, Byte)) => (x._1 | x._2).toByteExact }, + "{ (x: (Byte, Byte)) => (x._1 | x._2) }", + sinceVersion = VersionContext.V6SoftForkVersion) + + lazy val bitAnd = newFeature( + { (x: (Byte, Byte)) => (x._1 & x._2).toByteExact }, + "{ (x: (Byte, Byte)) => (x._1 & x._2) }", + sinceVersion = VersionContext.V6SoftForkVersion) + + forAll { x: Byte => + Seq(toAbs).foreach(f => f.checkEquality(x)) + } + + forAll { x: (Byte, Byte) => + Seq(compareTo, bitOr, bitAnd).foreach(_.checkEquality(x)) + } + } + } + + // TODO v6.0: enable as part of https://github.com/ScorexFoundation/sigmastate-interpreter/issues/474 + property("Short methods equivalence (new features)") { + if (activatedVersionInTests < VersionContext.V6SoftForkVersion) { + // NOTE, for such versions the new features are not supported + // which is checked below + + lazy val toAbs = newFeature((x: Short) => x.toAbs, "{ (x: Short) => x.toAbs }", + sinceVersion = VersionContext.V6SoftForkVersion) + + lazy val compareTo = newFeature((x: (Short, Short)) => x._1.compareTo(x._2), + "{ (x: (Short, Short)) => x._1.compareTo(x._2) }", + sinceVersion = VersionContext.V6SoftForkVersion) + + lazy val bitOr = newFeature( + { (x: (Short, Short)) => (x._1 | x._2).toShortExact }, + "{ (x: (Short, Short)) => x._1 | x._2 }", + sinceVersion = VersionContext.V6SoftForkVersion) + + lazy val bitAnd = newFeature( + { (x: (Short, Short)) => (x._1 & x._2).toShortExact }, + "{ (x: (Short, Short)) => x._1 & x._2 }", + sinceVersion = VersionContext.V6SoftForkVersion) + + forAll { x: Short => + Seq(toAbs).foreach(_.checkEquality(x)) + } + forAll { x: (Short, Short) => + Seq(compareTo, bitOr, bitAnd).foreach(_.checkEquality(x)) + } + } + } + + property("Int methods equivalence (new features)") { + if (activatedVersionInTests < VersionContext.V6SoftForkVersion) { + // NOTE, for such versions the new features are not supported + // which is checked below + lazy val toAbs = newFeature((x: Int) => x.toAbs, "{ (x: Int) => x.toAbs }", + sinceVersion = VersionContext.V6SoftForkVersion) + lazy val compareTo = newFeature((x: (Int, Int)) => x._1.compareTo(x._2), + "{ (x: (Int, Int)) => x._1.compareTo(x._2) }", + sinceVersion = VersionContext.V6SoftForkVersion) + lazy val bitOr = newFeature( + { (x: (Int, Int)) => x._1 | x._2 }, + "{ (x: (Int, Int)) => x._1 | x._2 }", + sinceVersion = VersionContext.V6SoftForkVersion) + lazy val bitAnd = newFeature( + { (x: (Int, Int)) => x._1 & x._2 }, + "{ (x: (Int, Int)) => x._1 & x._2 }", + sinceVersion = VersionContext.V6SoftForkVersion) + forAll { x: Int => + Seq(toAbs).foreach(_.checkEquality(x)) + } + forAll { x: (Int, Int) => + Seq(compareTo, bitOr, bitAnd).foreach(_.checkEquality(x)) + } + } + } + + property("Long methods equivalence (new features)") { + if (activatedVersionInTests < VersionContext.V6SoftForkVersion) { + // NOTE, for such versions the new features are not supported + // which is checked below + lazy val toAbs = newFeature((x: Long) => x.toAbs, "{ (x: Long) => x.toAbs }", + sinceVersion = VersionContext.V6SoftForkVersion) + lazy val compareTo = newFeature((x: (Long, Long)) => x._1.compareTo(x._2), + "{ (x: (Long, Long)) => x._1.compareTo(x._2) }", + sinceVersion = VersionContext.V6SoftForkVersion) + + lazy val bitOr = newFeature( + { (x: (Long, Long)) => x._1 | x._2 }, + "{ (x: (Long, Long)) => x._1 | x._2 }", + sinceVersion = VersionContext.V6SoftForkVersion) + + lazy val bitAnd = newFeature( + { (x: (Long, Long)) => x._1 & x._2 }, + "{ (x: (Long, Long)) => x._1 & x._2 }", + sinceVersion = VersionContext.V6SoftForkVersion) + + forAll { x: Long => + Seq(toAbs).foreach(_.checkEquality(x)) + } + forAll { x: (Long, Long) => + Seq(compareTo, bitOr, bitAnd).foreach(_.checkEquality(x)) + } + } + + } + + property("BigInt methods equivalence (new features)") { + // TODO v6.0: the behavior of `upcast` for BigInt is different from all other Numeric types (see https://github.com/ScorexFoundation/sigmastate-interpreter/issues/877) + // The `Upcast(bigInt, SBigInt)` node is never produced by ErgoScript compiler, but is still valid ErgoTree. + // It makes sense to fix this inconsistency as part of upcoming forks + assertExceptionThrown( + SBigInt.upcast(CBigInt(new BigInteger("0", 16)).asInstanceOf[AnyVal]), + _.getMessage.contains("Cannot upcast value") + ) + + // TODO v6.0: the behavior of `downcast` for BigInt is different from all other Numeric types (see https://github.com/ScorexFoundation/sigmastate-interpreter/issues/877) + // The `Downcast(bigInt, SBigInt)` node is never produced by ErgoScript compiler, but is still valid ErgoTree. + // It makes sense to fix this inconsistency as part of HF + assertExceptionThrown( + SBigInt.downcast(CBigInt(new BigInteger("0", 16)).asInstanceOf[AnyVal]), + _.getMessage.contains("Cannot downcast value") + ) + + if (activatedVersionInTests < VersionContext.V6SoftForkVersion) { + // NOTE, for such versions the new features are not supported + // which is checked below + val toByte = newFeature((x: BigInt) => x.toByte, + "{ (x: BigInt) => x.toByte }", + FuncValue(Vector((1, SBigInt)), Downcast(ValUse(1, SBigInt), SByte)), + sinceVersion = VersionContext.V6SoftForkVersion) + val toShort = newFeature((x: BigInt) => x.toShort, + "{ (x: BigInt) => x.toShort }", + FuncValue(Vector((1, SBigInt)), Downcast(ValUse(1, SBigInt), SShort)), + sinceVersion = VersionContext.V6SoftForkVersion) + val toInt = newFeature((x: BigInt) => x.toInt, + "{ (x: BigInt) => x.toInt }", + FuncValue(Vector((1, SBigInt)), Downcast(ValUse(1, SBigInt), SInt)), + sinceVersion = VersionContext.V6SoftForkVersion) + val toLong = newFeature((x: BigInt) => x.toLong, + "{ (x: BigInt) => x.toLong }", + FuncValue(Vector((1, SBigInt)), Downcast(ValUse(1, SBigInt), SLong)), + sinceVersion = VersionContext.V6SoftForkVersion) + lazy val toAbs = newFeature((x: BigInt) => x.toAbs, "{ (x: BigInt) => x.toAbs }", + sinceVersion = VersionContext.V6SoftForkVersion) + lazy val compareTo = newFeature((x: (BigInt, BigInt)) => x._1.compareTo(x._2), + "{ (x: (BigInt, BigInt)) => x._1.compareTo(x._2) }", + sinceVersion = VersionContext.V6SoftForkVersion) + lazy val bitOr = newFeature({ (x: (BigInt, BigInt)) => x._1 | x._2 }, + "{ (x: (BigInt, BigInt)) => x._1 | x._2 }", + sinceVersion = VersionContext.V6SoftForkVersion) + lazy val bitAnd = newFeature({ (x: (BigInt, BigInt)) => x._1 & x._2 }, + "{ (x: (BigInt, BigInt)) => x._1 & x._2 }", + sinceVersion = VersionContext.V6SoftForkVersion) + + forAll { x: BigInt => + Seq(toByte, toShort, toInt, toLong, toAbs).foreach(_.checkEquality(x)) + } + forAll { x: (BigInt, BigInt) => + Seq(compareTo, bitOr, bitAnd).foreach(_.checkEquality(x)) + } + } + } + + property("Box properties equivalence (new features)") { + // TODO v6.0: related to https://github.com/ScorexFoundation/sigmastate-interpreter/issues/416 + val getReg = newFeature((x: Box) => x.getReg[Int](1).get, + "{ (x: Box) => x.getReg[Int](1).get }", + sinceVersion = VersionContext.V6SoftForkVersion) + + if (activatedVersionInTests < VersionContext.V6SoftForkVersion) { + // NOTE, for such versions getReg is not supported + // which is checked below + + forAll { box: Box => + Seq(getReg).foreach(_.checkEquality(box)) + } + } + } + + // TODO v6.0 (3h): https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479 + property("Coll find method equivalence") { + val find = newFeature((x: Coll[Int]) => x.find({ (v: Int) => v > 0 }), + "{ (x: Coll[Int]) => x.find({ (v: Int) => v > 0} ) }", + sinceVersion = VersionContext.V6SoftForkVersion) + + if (activatedVersionInTests < VersionContext.V6SoftForkVersion) { + // NOTE, for such versions getReg is not supported + // which is checked below + + forAll { x: Coll[Int] => + find.checkEquality(x) + } + } + } + + // TODO v6.0 (3h): https://github.com/ScorexFoundation/sigmastate-interpreter/issues/418 + property("Coll bitwise methods equivalence") { + val shiftRight = newFeature( + { (x: Coll[Boolean]) => + if (x.size > 2) x.slice(0, x.size - 2) else Colls.emptyColl[Boolean] + }, + "{ (x: Coll[Boolean]) => x >> 2 }", + sinceVersion = VersionContext.V6SoftForkVersion) + + if (activatedVersionInTests < VersionContext.V6SoftForkVersion) { + // NOTE, for such versions getReg is not supported + // which is checked below + + forAll { x: Array[Boolean] => + shiftRight.checkEquality(Colls.fromArray(x)) + } + } + } + + // TODO v6.0 (3h): https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479 + property("Coll diff methods equivalence") { + val diff = newFeature((x: (Coll[Int], Coll[Int])) => x._1.diff(x._2), + "{ (x: (Coll[Int], Coll[Int])) => x._1.diff(x._2) }", + sinceVersion = VersionContext.V6SoftForkVersion) + + if (activatedVersionInTests < VersionContext.V6SoftForkVersion) { + // NOTE, for such versions getReg is not supported + // which is checked below + + forAll { (x: Coll[Int], y: Coll[Int]) => + diff.checkEquality((x, y)) + } + } + } + + // TODO v6.0: implement Option.fold (see https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479) + property("Option new methods") { + val n = ExactNumeric.LongIsExactNumeric + val fold = newFeature({ (x: Option[Long]) => x.fold(5.toLong)( (v: Long) => n.plus(v, 1) ) }, + "{ (x: Option[Long]) => x.fold(5, { (v: Long) => v + 1 }) }", + sinceVersion = VersionContext.V6SoftForkVersion) + + if (activatedVersionInTests < VersionContext.V6SoftForkVersion) { + // NOTE, for such versions getReg is not supported + // which is checked below + + forAll { x: Option[Long] => + Seq(fold).map(_.checkEquality(x)) + } + } + } + + // TODO v6.0 (3h): implement allZK func https://github.com/ScorexFoundation/sigmastate-interpreter/issues/543 + property("allZK equivalence") { + lazy val allZK = newFeature((x: Coll[SigmaProp]) => SigmaDsl.allZK(x), + "{ (x: Coll[SigmaProp]) => allZK(x) }", + sinceVersion = VersionContext.V6SoftForkVersion) + + if (activatedVersionInTests < VersionContext.V6SoftForkVersion) { + // NOTE, for such versions getReg is not supported + // which is checked below + + forAll { x: Coll[SigmaProp] => + allZK.checkEquality(x) + } + } + } + + // TODO v6.0 (3h): implement anyZK func https://github.com/ScorexFoundation/sigmastate-interpreter/issues/543 + property("anyZK equivalence") { + lazy val anyZK = newFeature((x: Coll[SigmaProp]) => SigmaDsl.anyZK(x), + "{ (x: Coll[SigmaProp]) => anyZK(x) }", + sinceVersion = VersionContext.V6SoftForkVersion) + + if (activatedVersionInTests < VersionContext.V6SoftForkVersion) { + // NOTE, for such versions getReg is not supported + // which is checked below + + forAll { x: Coll[SigmaProp] => + anyZK.checkEquality(x) + } + } + } + + +} diff --git a/sc/shared/src/test/scala/sigma/SigmaDslTesting.scala b/sc/shared/src/test/scala/sigma/SigmaDslTesting.scala index 31e873699b..37fb0e6503 100644 --- a/sc/shared/src/test/scala/sigma/SigmaDslTesting.scala +++ b/sc/shared/src/test/scala/sigma/SigmaDslTesting.scala @@ -123,6 +123,9 @@ class SigmaDslTesting extends AnyPropSpec /** Type descriptor for type B. */ def tB: RType[B] + /** Checks if this feature is supported in the given version context. */ + def isSupportedIn(vc: VersionContext): Boolean + /** Script containing this feature. */ def script: String @@ -176,14 +179,42 @@ class SigmaDslTesting extends AnyPropSpec true } + /** Checks the result of feature execution against expected result. + * If settings.failOnTestVectors == true, then print out actual cost results + * + * @param res the result of feature execution + * @param expected the expected result + */ + protected def checkResultAgainstExpected(res: Try[(B, CostDetails)], expected: Expected[B]): Unit = { + val newRes = expected.newResults(ergoTreeVersionInTests) + val expectedTrace = newRes._2.fold(Seq.empty[CostItem])(_.trace) + if (expectedTrace.isEmpty) { + // new cost expectation is missing, print out actual cost results + if (evalSettings.printTestVectors) { + res.foreach { case (_, newDetails) => + printCostDetails(script, newDetails) + } + } + } + else { + // new cost expectation is specified, compare it with the actual result + res.foreach { case (_, newDetails) => + if (newDetails.trace != expectedTrace) { + printCostDetails(script, newDetails) + newDetails.trace shouldBe expectedTrace + } + } + } + } + /** v3 and v4 implementation*/ - private var _oldF: CompiledFunc[A, B] = _ + private var _oldF: Try[CompiledFunc[A, B]] = _ def oldF: CompiledFunc[A, B] = { if (_oldF == null) { - _oldF = oldImpl() - checkExpectedExprIn(_oldF) + _oldF = Try(oldImpl()) + _oldF.foreach(cf => checkExpectedExprIn(cf)) } - _oldF + _oldF.getOrThrow } /** v5 implementation*/ @@ -252,13 +283,21 @@ class SigmaDslTesting extends AnyPropSpec fail( s"""Should succeed with the same value or fail with the same exception, but was: - |First result: $b1 - |Second result: $b2 + |First result: ${errorWithStack(b1)} + |Second result: ${errorWithStack(b2)} |Root cause: $cause |""".stripMargin) } } + private def errorWithStack[A](e: Try[A]): String = e match { + case Failure(t) => + val sw = new java.io.StringWriter + t.printStackTrace(new java.io.PrintWriter(sw)) + sw.toString + case _ => e.toString + } + /** Creates a new ErgoLikeContext using given [[CContext]] as template. * Copies most of the data from ctx and the missing data is taken from the args. * This is a helper method to be used in tests only. @@ -501,6 +540,8 @@ class SigmaDslTesting extends AnyPropSpec implicit val cs = compilerSettingsInTests + override def isSupportedIn(vc: VersionContext): Boolean = true + /** in v5.x the old and the new interpreters are the same */ override val oldImpl = () => funcJit[A, B](script) override val newImpl = () => funcJit[A, B](script) @@ -611,28 +652,10 @@ class SigmaDslTesting extends AnyPropSpec checkResult(funcRes.map(_._1), expected.value, failOnTestVectors, "ExistingFeature#verifyCase: ") - val newRes = expected.newResults(ergoTreeVersionInTests) - val expectedTrace = newRes._2.fold(Seq.empty[CostItem])(_.trace) - if (expectedTrace.isEmpty) { - // new cost expectation is missing, print out actual cost results - if (evalSettings.printTestVectors) { - funcRes.foreach { case (_, newDetails) => - printCostDetails(script, newDetails) - } - } - } - else { - // new cost expectation is specified, compare it with the actual result - funcRes.foreach { case (_, newDetails) => - if (newDetails.trace != expectedTrace) { - printCostDetails(script, newDetails) - newDetails.trace shouldBe expectedTrace - } - } - } - + checkResultAgainstExpected(funcRes, expected) checkVerify(input, expected) } + } /** Descriptor of a language feature which is changed in v5.0. @@ -671,6 +694,8 @@ class SigmaDslTesting extends AnyPropSpec implicit val cs = compilerSettingsInTests + override def isSupportedIn(vc: VersionContext): Boolean = true + /** Apply given function to the context variable 1 */ private def getApplyExpr(funcValue: SValue) = { val sType = Evaluation.rtypeToSType(RType[A]) @@ -833,8 +858,17 @@ class SigmaDslTesting extends AnyPropSpec * This in not yet implemented and will be finished in v6.0. * In v5.0 is only checks that some features are NOT implemented, i.e. work for * negative tests. + * + * @param sinceVersion language version (protocol) when the feature is introduced, see + * [[VersionContext]] + * @param script the script to be tested against semantic function + * @param scalaFuncNew semantic function which defines expected behavior of the given script + * @param expectedExpr expected ErgoTree expression which corresponds to the given script + * @param printExpectedExpr if true, print the test vector for expectedExpr when it is None + * @param logScript if true, log scripts to console */ case class NewFeature[A, B]( + sinceVersion: Byte, script: String, override val scalaFuncNew: A => B, expectedExpr: Option[SValue], @@ -842,25 +876,30 @@ class SigmaDslTesting extends AnyPropSpec logScript: Boolean = LogScriptDefault )(implicit IR: IRContext, override val evalSettings: EvalSettings, val tA: RType[A], val tB: RType[B]) extends Feature[A, B] { + + override def isSupportedIn(vc: VersionContext): Boolean = + vc.activatedVersion >= sinceVersion + override def scalaFunc: A => B = { x => sys.error(s"Semantic Scala function is not defined for old implementation: $this") } implicit val cs = compilerSettingsInTests - /** in v5.x the old and the new interpreters are the same */ + /** Starting from v5.x the old and the new interpreters are the same */ val oldImpl = () => funcJit[A, B](script) - val newImpl = oldImpl // funcJit[A, B](script) // TODO v6.0: use actual new implementation here (https://github.com/ScorexFoundation/sigmastate-interpreter/issues/910) + val newImpl = oldImpl - /** In v5.x this method just checks the old implementations fails on the new feature. */ + /** Check the new implementation works equal to the semantic function. + * This method also checks the old implementations fails on the new feature. + */ override def checkEquality(input: A, logInputOutput: Boolean = false): Try[(B, CostDetails)] = { - val oldRes = Try(oldF(input)) - oldRes.isFailure shouldBe true - if (!(newImpl eq oldImpl)) { - val newRes = VersionContext.withVersions(activatedVersionInTests, ergoTreeVersionInTests) { - checkEq(scalaFuncNew)(newF)(input) - } + if (this.isSupportedIn(VersionContext.current)) { + checkEq(scalaFuncNew)(newF)(input) + } else { + val oldRes = Try(oldF(input)) + oldRes.isFailure shouldBe true + oldRes } - oldRes } override def checkExpected(input: A, expected: Expected[B]): Unit = { @@ -880,7 +919,10 @@ class SigmaDslTesting extends AnyPropSpec printTestCases: Boolean, failOnTestVectors: Boolean): Unit = { val res = checkEquality(input, printTestCases).map(_._1) - res.isFailure shouldBe true + if (this.isSupportedIn(VersionContext.current)) { + res shouldBe expectedResult + } else + res.isFailure shouldBe true Try(scalaFuncNew(input)) shouldBe expectedResult } @@ -889,8 +931,11 @@ class SigmaDslTesting extends AnyPropSpec printTestCases: Boolean, failOnTestVectors: Boolean): Unit = { val funcRes = checkEquality(input, printTestCases) - funcRes.isFailure shouldBe true - Try(scalaFunc(input)) shouldBe expected.value + if (this.isSupportedIn(VersionContext.current)) { + checkResultAgainstExpected(funcRes, expected) + } else + funcRes.isFailure shouldBe true + Try(scalaFuncNew(input)) shouldBe expected.value } } @@ -958,6 +1003,20 @@ class SigmaDslTesting extends AnyPropSpec } } + /** Used when the old and new value are the same for all versions + * and the expected costs are not specified. + * + * @param value expected result of tested function + * @param expectedDetails expected cost details for all versions + */ + def apply[A](value: Try[A], expectedDetails: CostDetails): Expected[A] = + new Expected(ExpectedResult(value, None)) { + override val newResults = defaultNewResults.map { + case (ExpectedResult(v, _), _) => + (ExpectedResult(v, None), Some(expectedDetails)) + } + } + /** Used when the old and new value and costs are the same for all versions. * * @param value expected result of tested function @@ -1045,6 +1104,7 @@ class SigmaDslTesting extends AnyPropSpec /** Describes a NEW language feature which must NOT be supported in v4 and * must BE supported in v5 of the language. * + * @param sinceVersion language version (protocol) when the feature is introduced, see [[VersionContext]] * @param scalaFunc semantic function which defines expected behavior of the given script * @param script the script to be tested against semantic function * @param expectedExpr expected ErgoTree expression which corresponds to the given script @@ -1052,9 +1112,9 @@ class SigmaDslTesting extends AnyPropSpec * various ways */ def newFeature[A: RType, B: RType] - (scalaFunc: A => B, script: String, expectedExpr: SValue = null) + (scalaFunc: A => B, script: String, expectedExpr: SValue = null, sinceVersion: Byte = VersionContext.JitActivationVersion) (implicit IR: IRContext, es: EvalSettings): Feature[A, B] = { - NewFeature(script, scalaFunc, Option(expectedExpr)) + NewFeature(sinceVersion, script, scalaFunc, Option(expectedExpr)) } val contextGen: Gen[Context] = ergoLikeContextGen.map(c => c.toSigmaContext()) diff --git a/sc/shared/src/test/scala/sigmastate/CompilerCrossVersionProps.scala b/sc/shared/src/test/scala/sigmastate/CompilerCrossVersionProps.scala index 89d15dd4df..4062f13686 100644 --- a/sc/shared/src/test/scala/sigmastate/CompilerCrossVersionProps.scala +++ b/sc/shared/src/test/scala/sigmastate/CompilerCrossVersionProps.scala @@ -12,12 +12,11 @@ trait CompilerCrossVersionProps extends CrossVersionProps with CompilerTestsBase (implicit pos: Position): Unit = { super.property(testName, testTags:_*)(testFun) - val testName2 = s"${testName}_MCLowering" - super.property2(testName2, testTags:_*) { - if (okRunTestsWithoutMCLowering) { - _lowerMethodCalls.withValue(false) { - testFun_Run(testName2, testFun) - } + if (okRunTestsWithoutMCLowering) { + val testName2 = s"${testName}_MCLowering" + _lowerMethodCalls.withValue(false) { + // run testFun for all versions again, but now with this flag + super.property(testName2, testTags:_*)(testFun) } } } diff --git a/sc/shared/src/test/scala/sigmastate/lang/SigmaBinderTest.scala b/sc/shared/src/test/scala/sigmastate/lang/SigmaBinderTest.scala index b4b4ad20cd..aa552e9b69 100644 --- a/sc/shared/src/test/scala/sigmastate/lang/SigmaBinderTest.scala +++ b/sc/shared/src/test/scala/sigmastate/lang/SigmaBinderTest.scala @@ -10,10 +10,12 @@ import sigma.ast.syntax.SValue import sigmastate._ import sigmastate.interpreter.Interpreter.ScriptEnv import SigmaPredef.PredefinedFuncRegistry +import sigma.VersionContext import sigma.ast.syntax._ import sigma.compiler.phases.SigmaBinder import sigma.eval.SigmaDsl import sigma.exceptions.BinderException +import sigmastate.helpers.SigmaPPrint class SigmaBinderTest extends AnyPropSpec with ScalaCheckPropertyChecks with Matchers with LangTests { import StdSigmaBuilder._ @@ -29,6 +31,17 @@ class SigmaBinderTest extends AnyPropSpec with ScalaCheckPropertyChecks with Mat res } + /** Checks that parsing and binding results in the expected value. + * @return the inferred type of the expression + */ + def checkBound(env: ScriptEnv, x: String, expected: SValue) = { + val bound = bind(env, x) + if (expected != bound) { + SigmaPPrint.pprintln(bound, width = 100) + } + bound shouldBe expected + } + private def fail(env: ScriptEnv, x: String, expectedLine: Int, expectedCol: Int): Unit = { val builder = TransformingSigmaBuilder val ast = SigmaParser(x).get.value diff --git a/sc/shared/src/test/scala/sigmastate/lang/SigmaTyperTest.scala b/sc/shared/src/test/scala/sigmastate/lang/SigmaTyperTest.scala index 6b93b098ea..3b2e130100 100644 --- a/sc/shared/src/test/scala/sigmastate/lang/SigmaTyperTest.scala +++ b/sc/shared/src/test/scala/sigmastate/lang/SigmaTyperTest.scala @@ -21,6 +21,7 @@ import sigma.serialization.generators.ObjectGenerators import sigma.ast.Select import sigma.compiler.phases.{SigmaBinder, SigmaTyper} import sigma.exceptions.TyperException +import sigmastate.helpers.SigmaPPrint class SigmaTyperTest extends AnyPropSpec with ScalaCheckPropertyChecks with Matchers with LangTests with ObjectGenerators { @@ -28,6 +29,7 @@ class SigmaTyperTest extends AnyPropSpec private val predefFuncRegistry = new PredefinedFuncRegistry(StdSigmaBuilder) import predefFuncRegistry._ + /** Checks that parsing, binding and typing of `x` results in the given expected value. */ def typecheck(env: ScriptEnv, x: String, expected: SValue = null): SType = { try { val builder = TransformingSigmaBuilder @@ -39,7 +41,12 @@ class SigmaTyperTest extends AnyPropSpec val typer = new SigmaTyper(builder, predefinedFuncRegistry, typeEnv, lowerMethodCalls = true) val typed = typer.typecheck(bound) assertSrcCtxForAllNodes(typed) - if (expected != null) typed shouldBe expected + if (expected != null) { + if (expected != typed) { + SigmaPPrint.pprintln(typed, width = 100) + } + typed shouldBe expected + } typed.tpe } catch { case e: Exception => throw e