org.opencypher.tools.tck.api.Scenario.scala Maven / Gradle / Ivy
/*
* Copyright (c) 2015-2019 "Neo Technology,"
* Network Engine for Objects in Lund AB [http://neotechnology.com]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Attribution Notice under the terms of the Apache License 2.0
*
* This work was created by the collective efforts of the openCypher community.
* Without limiting the terms of Section 6, any Derivative Work that is not
* approved by the public consensus process of the openCypher Implementers Group
* should not be described as “Cypher” (and Cypher® is a registered trademark of
* Neo4j Inc.) or as "openCypher". Extensions by implementers or prototypes or
* proposals for change that have been documented or implemented should only be
* described as "implementation extensions to Cypher" or as "proposed changes to
* Cypher that are not yet approved by the openCypher community".
*/
package org.opencypher.tools.tck.api
import gherkin.pickles.Pickle
import org.junit.jupiter.api.function.Executable
import org.opencypher.tools.tck.SideEffectOps
import org.opencypher.tools.tck.SideEffectOps._
import org.opencypher.tools.tck.api.Graph.Result
import org.opencypher.tools.tck.api.events.TCKEvents
import org.opencypher.tools.tck.api.events.TCKEvents.{StepFinished, StepStarted, setStepFinished, setStepStarted}
import org.opencypher.tools.tck.values.CypherValue
import scala.compat.Platform.EOL
import scala.language.implicitConversions
import scala.util.{Failure, Success, Try}
case class Scenario(featureName: String, name: String, tags: Set[String], steps: List[Step], source: Pickle) {
self =>
override def toString = s"""Feature "$featureName": Scenario "$name""""
def apply(graph: => Graph): Executable = new Executable {
override def execute(): Unit = {
val g = graph // ensure that lazy parameter is only evaluated once
try {
TCKEvents.setScenario(self)
executeOnGraph(g)
} finally g.close()
}
}
def executeOnGraph(empty: Graph): Unit = {
steps.foldLeft(ScenarioExecutionContext(empty)) { (context, step) =>
{
val eventId = setStepStarted(StepStarted(step))
val stepResult: Either[ScenarioFailedException, ScenarioExecutionContext] = (context, step) match {
case (ctx, Execute(query, qt, _)) =>
Right(ctx.execute(query, qt))
case (ctx, Measure(_)) =>
Right(ctx.measure)
case (ctx, RegisterProcedure(signature, table, _)) =>
ctx.graph match {
case support: ProcedureSupport =>
support.registerProcedure(signature, table)
case _ =>
}
Right(ctx)
case (ctx, ExpectResult(expected, _, sorted)) =>
ctx.lastResult match {
case Right(records) =>
val correctResult =
if (sorted)
expected == records
else
expected.equalsUnordered(records)
if (!correctResult) {
val detail = if (sorted) "ordered rows" else "in any order of rows"
Left(ScenarioFailedException(s"${EOL}Expected ($detail):$EOL$expected${EOL}Actual:$EOL$records"))
} else {
Right(ctx)
}
case Left(error) =>
Left(ScenarioFailedException(s"Expected: $expected, got error $error", error.exception.orNull))
}
case (ctx, e @ ExpectError(errorType, phase, detail, _)) =>
ctx.lastResult match {
case Left(error) =>
if (error.errorType != errorType)
Left(
ScenarioFailedException(
s"Wrong error type: expected $errorType, got ${error.errorType}",
error.exception.orNull))
if (error.phase != phase)
Left(
ScenarioFailedException(
s"Wrong error phase: expected $phase, got ${error.phase}",
error.exception.orNull))
if (error.detail != detail)
Left(
ScenarioFailedException(
s"Wrong error detail: expected $detail, got ${error.detail}",
error.exception.orNull))
else {
Right(ctx)
}
case Right(records) =>
Left(ScenarioFailedException(s"Expected: $e, got records $records"))
}
case (ctx, SideEffects(expected, _)) =>
val before = ctx.state
val after = ctx.measure.state
val diff = before diff after
if (diff != expected)
Left(
ScenarioFailedException(
s"${EOL}Expected side effects:$EOL$expected${EOL}Actual side effects:$EOL$diff"))
else Right(ctx)
case (ctx, Parameters(ps, _)) =>
Right(ctx.copy(parameters = ps))
case (ctx, _: Dummy) => Right(ctx)
case (_, s) =>
throw new UnsupportedOperationException(s"Unsupported step: $s")
}
stepResult match {
case Right(ctx) =>
setStepFinished(StepFinished(step, Right(ctx.lastResult), eventId))
ctx
case Left(throwable) =>
setStepFinished(StepFinished(step, Left(throwable), eventId))
throw throwable
}
}
}
}
def validate() = {
// TODO:
// validate similar to FeatureFormatValidator
// there should be at least one Execute(_, ExecQuery)
// there should be either a ExpectResult with ExpectSideEffects or ExpectError
// etc..
}
case class ScenarioExecutionContext(
graph: Graph,
lastResult: Result = Right(CypherValueRecords.empty),
state: State = State(),
parameters: Map[String, CypherValue] = Map.empty) {
def execute(query: String, queryType: QueryType): ScenarioExecutionContext = {
val (g, r) = graph.execute(query, parameters, queryType)
copy(graph = g, lastResult = r)
}
def measure: ScenarioExecutionContext = {
Try(SideEffectOps.measureState(graph)) match {
case Success(measuredState) => copy(state = measuredState)
case Failure(error) =>
val msg = s"Side effect measurement failed with $error"
throw ScenarioFailedException(msg, error)
}
}
}
case class ScenarioFailedException(msg: String, cause: Throwable = null)
extends Throwable(s"$self failed with message: $msg", cause)
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy