tck

package
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 2, 2026 License: MIT Imports: 6 Imported by: 0

Documentation

Overview

Package tck records the conformance evolution of the GoGraph Cypher engine against the openCypher Technology Compatibility Kit.

Conformance History

Parser-level pass rate (grammar + AST round-trip):

Sprint 26: ~70%  — baseline after initial grammar implementation
Sprint 27: ~72%  — write-clause parser support
Sprint 28: ~74%  — expression improvements, MATCH patterns
Sprint 29: 76.5% → 90.7% — normalizeSingleQuotes pre-processor resolved 579
           single-quote-string scenarios; DDL/procedure scenarios added
Sprint 30: 90.7% — Bolt server; no parser changes
Sprint 31: 90.7% — godog execution runner added; parser rate unchanged;
           execution-level rate baseline = 10.4% (407/3897 scenarios)
Sprint 43: 100.0% — task #402 closed the last grammar-gap-literal sub-class;
           parser is fully green (3897/3897)

Execution-level pass rate (full godog runner, tckExecutionBaseline gate):

Sprint 31: 10.4% (407/3897) — baseline
Sprint 37: 24.8% (968/3897)
Sprint 42: 29.6% (1152/3897)
Sprint 46: 39.4% (1536/3897)
Sprints 58–64 (rounds 22–64, 2026-05-28/29): 100.0% (3897/3897) — FULLY GREEN
  Key uplifts: error-step regex (R58), VLE cross-pattern no-repeat-rel (R59+R61),
  PatternComprehension + percentile guard (R60), CREATE-multiplicity counter (R62),
  per-CREATE-instance edge labels + multigraph adjlist (R63), named-path
  leading-hop reconstruction (R64).

The enforced gate is const tckExecutionBaseline = 3897 in runner_test.go. Any PR that lowers the passing count is rejected by CI. See docs/tck/DIVERGENCES.md for the full audit trail.

Package tck implements the openCypher Technology Compatibility Kit (TCK) parser-only scenario runner. It reads the vendored Gherkin feature files from the embedded [features] directory, extracts every "When executing query:" step, and runs each Cypher string through [parser.Parse].

Scope

The runner covers all 220 feature files from the openCypher TCK corpus (opencypher/openCypher@main, retrieved 2026-05-20). Raw scenario count before expansion is 1 615. After [parseFeatureFile] expands Scenario Outline blocks by substituting each Examples table row, the effective corpus grows to 3 897 scenarios. Of these, 914 are excluded from the pass-rate gate because they exercise grammar features not yet supported by the antlr/grammars-v4 grammar pinned at commit 284602b. See SkipReason for the full taxonomy.

The 2 983 remaining scenarios must pass at 100 %. A regression drops the pass rate below 100 % and causes [TestTCKParserOnly] to fail, blocking CI.

Concurrency

[TestTCKParserOnly] is a standard Go test. It may be run under -race without additional synchronisation because every scenario invokes [parser.Parse] in its own goroutine via t.Run, and parser.Parse is documented as concurrency-safe.

Feature file provenance

Feature files under features/ are vendored from:

https://github.com/opencypher/openCypher/tree/main/tck/features

Licensed under the Apache License, Version 2.0. See individual file headers.

Index

Examples

Constants

This section is empty.

Variables

View Source
var WriteFeatureDirs = []string{
	"features/clauses/create",
	"features/clauses/merge",
	"features/clauses/delete",
	"features/clauses/set",
	"features/clauses/remove",
}

WriteFeatureDirs lists the TCK feature directories that contain write-clause scenarios.

Functions

func FeatureFiles

func FeatureFiles() fs.FS

FeatureFiles returns the embedded openCypher TCK feature file tree. It is exposed for use by the external test package (tck_test) so that the godog runner can load feature files from the embedded FS without importing godog in non-test production code.

Types

type DDLScenario

type DDLScenario struct {
	// Name is a short identifier for the scenario, used as the sub-test name.
	Name string
	// Query is the Cypher statement to execute.
	Query string
	// WantErr reports whether the scenario expects an error from the engine.
	WantErr bool
	// ErrContains is a substring expected in the error message when WantErr is
	// true. An empty string means any non-nil error satisfies the expectation.
	ErrContains string
}

DDLScenario represents one engine-level integration scenario for DDL/procedure/parameter testing.

func DDLScenarios

func DDLScenarios() []DDLScenario

DDLScenarios returns the set of DDL/procedure/parameter integration scenarios. These test the execution engine directly, not just parsing. They cover extensions added in Sprint 29 (index DDL, constraint DDL, parameters, and built-in procedure calls).

Notes on supported DDL syntax:

  • CREATE INDEX: the engine's DDL parser requires the optional IF NOT EXISTS clause to appear before the index name, i.e.: CREATE INDEX [IF NOT EXISTS] [name] FOR (n:Label) ON (n.prop)

  • CREATE CONSTRAINT: the engine uses the pre-4.x ASSERT syntax, i.e.: CREATE CONSTRAINT [name] ON (n:Label) ASSERT n.prop IS UNIQUE

  • Parameters in standalone RETURN require a driving clause. Use MATCH (n) RETURN $param to ensure the param is resolved at execution time.

type Scenario

type Scenario struct {
	// File is the path of the feature file relative to the embed root, e.g.
	// "features/clauses/return/Return1.feature".
	File string
	// Feature is the Feature: header from the containing feature file.
	Feature string
	// Name is the Scenario: text, including its [N] index prefix where present.
	Name string
	// Tags are the @tag annotations on the Scenario block.
	Tags []string
	// Query is the Cypher string from the "When executing query:" step.
	Query string
	// SyntaxErrorType is non-empty when the scenario expects a SyntaxError,
	// e.g. "UndefinedVariable", "UnexpectedSyntax".
	SyntaxErrorType string
	// SkipReason is non-empty when the scenario is excluded from the pass-rate
	// gate. It records the grammar-gap category that caused the exclusion.
	SkipReason SkipReason
}

Scenario represents a single Gherkin Scenario extracted from a feature file. Fields are exported so that test code in external test packages can read them.

Example

ExampleScenario shows how the harness consumer turns a scenario's Query into an AST: this is the core operation the parser-only runner performs for every non-skipped scenario. Here a representative query is parsed directly so the example stays deterministic and independent of corpus ordering.

package main

import (
	"fmt"

	"github.com/FlavioCFOliveira/GoGraph/cypher/parser"
	"github.com/FlavioCFOliveira/GoGraph/cypher/tck"
)

func main() {
	// A Scenario carries the Cypher string lifted from its
	// "When executing query:" step; feed that to parser.Parse.
	s := &tck.Scenario{
		File:  "features/clauses/return/Return1.feature",
		Name:  "[1] Return a single value",
		Query: "RETURN 1 AS one",
	}

	_, err := parser.Parse(s.Query)
	fmt.Println("parses cleanly:", err == nil)
}
Output:
parses cleanly: true

func LoadScenarios

func LoadScenarios() ([]*Scenario, error)

LoadScenarios walks the embedded feature directory and parses all Gherkin files, returning every Scenario that contains a "When executing query:" step. Each returned Scenario has its SkipReason field set.

LoadScenarios is safe to call concurrently.

Example

ExampleLoadScenarios loads the embedded TCK corpus and inspects one scenario. The corpus content is fixed at build time, so "the corpus is non-empty" and "each scenario carries a query and a file path" are stable facts to assert; the exact scenario count is intentionally not pinned here.

package main

import (
	"fmt"

	"github.com/FlavioCFOliveira/GoGraph/cypher/tck"
)

func main() {
	scenarios, err := tck.LoadScenarios()
	if err != nil {
		fmt.Println("load error:", err)
		return
	}

	fmt.Println("corpus non-empty:", len(scenarios) > 0)

	// Every scenario exposes the query string and originating feature file.
	first := scenarios[0]
	fmt.Println("has query:", first.Query != "")
	fmt.Println("has file:", first.File != "")
}
Output:
corpus non-empty: true
has query: true
has file: true

func LoadWriteScenarios

func LoadWriteScenarios() ([]*Scenario, error)

LoadWriteScenarios returns all scenarios from write-clause feature files. It is a filtered view of LoadScenarios, restricted to the directories listed in WriteFeatureDirs. Each returned Scenario has its SkipReason field set by [classifySkip], including scenarios expanded from Scenario Outline blocks.

LoadWriteScenarios is safe to call concurrently.

func (*Scenario) WantParseError

func (s *Scenario) WantParseError() bool

WantParseError reports whether the scenario expects [parser.Parse] to return a non-nil error.

type SkipReason

type SkipReason string

SkipReason categorises why a scenario is excluded from the pass-rate gate. Every category corresponds to a known gap between the antlr/grammars-v4 grammar (commit 284602b) and the full openCypher specification.

const (
	// SkipNone means the scenario is included in the pass-rate gate.
	SkipNone SkipReason = ""

	// SkipPlaceholder excludes Scenario Outline template rows that contain
	// angle-bracket placeholders (<pattern>, <yield>, etc.) which are not
	// valid Cypher.
	SkipPlaceholder SkipReason = "placeholder-template"

	// SkipSingleQuoteString excludes queries with multi-word single-quoted
	// string literals (e.g. 'The Matrix'). The grammar tokenises them as a
	// char literal followed by an identifier, producing a spurious parse error.
	SkipSingleQuoteString SkipReason = "single-quote-string"

	// SkipVarlenExplicitBound is retained for reference. The skip condition was
	// resolved via normalizeVarlenBounds in cypher/parser/normalize.go,
	// which pre-processes unsigned integer range bounds into their negated form so
	// that the ANTLR lexer emits DIGIT tokens instead of ID tokens.
	//
	//nolint:unused // retained for documentation; normalizeVarlenBounds resolves the gap
	SkipVarlenExplicitBound SkipReason = "varlen-explicit-bound"

	// SkipVarlenDotDot is retained for documentation. The skip condition was
	// removed because normalizeVarlenDotDot in cypher/parser/normalize.go
	// pre-processes [..], [N..], [..M], [N..M] patterns (without *) to their
	// equivalent [*..], [*N..], [*..M], [*N..M] forms before lexing.
	//
	//nolint:unused // retained for documentation; normalizeVarlenDotDot resolves the gap
	SkipVarlenDotDot SkipReason = "varlen-dotdot"

	// SkipChainedWith is retained for documentation. The skip condition was
	// removed because MultiPartQ() in the generated parser was modified to
	// consume readingStatement* segments interleaved with each WITH clause,
	// enabling unlimited chaining of WITH clauses in a query.
	//
	//nolint:unused // retained for documentation; MultiPartQ modification resolves the gap
	SkipChainedWith SkipReason = "chained-with"

	// SkipNegHexOct is retained for documentation. The skip condition was
	// removed because normalizeNegHexOct in cypher/parser/normalize.go
	// rewrites -0x… and -0o… literals to (0-0x…) / (0-0o…) before lexing,
	// which the grammar accepts as a binary subtraction expression.
	//
	//nolint:unused // retained for documentation; normalizeNegHexOct resolves the gap
	SkipNegHexOct SkipReason = "neg-hex-oct"

	// SkipLeadingDotFloat is retained for documentation. The skip condition was
	// removed because .5, -.5, and similar leading-dot floats already parse
	// correctly via the ANTLR grammar without preprocessing. The skip was
	// overly conservative.
	//
	//nolint:unused // retained for documentation; leading-dot floats parse correctly without a skip
	SkipLeadingDotFloat SkipReason = "leading-dot-float"

	// SkipZeroDotFloat is retained for documentation. The skip condition was
	// removed because normalizeZeroDotFloat in cypher/parser/normalize.go
	// pre-processes 0.NNN literals to .NNN before lexing, resolving the gap.
	//
	//nolint:unused // retained for documentation; normalizeZeroDotFloat resolves the gap
	SkipZeroDotFloat SkipReason = "zero-dot-float"

	// SkipDoubleNot is retained for documentation. The skip condition was
	// removed because normalizeDoubleNot in cypher/parser/normalize.go
	// applies double-negation elimination (NOT NOT expr → expr, NOT NOT NOT expr
	// → NOT expr) before lexing, resolving the grammar gap.
	//
	//nolint:unused // retained for documentation; normalizeDoubleNot resolves the gap
	SkipDoubleNot SkipReason = "double-not"

	// SkipCallNoParen is retained for documentation. The skip condition was
	// removed because QueryCallSt() in the generated parser was modified to
	// make the argument parentheses optional for in-query CALL, matching the
	// behaviour of StandaloneCall.
	//
	//nolint:unused // retained for documentation; QueryCallSt modification resolves the gap
	SkipCallNoParen SkipReason = "call-no-paren"

	// SkipOverflowAsSema is retained for documentation. The skip condition was
	// removed because IntegerOverflow and FloatingPointOverflow are now listed in
	// parseTimeErrors: the visitor returns a non-nil error for overflow, which
	// satisfies the TCK expectation of a compile-time SyntaxError.
	//
	//nolint:unused // retained for documentation; overflow scenarios now handled via parseTimeErrors
	SkipOverflowAsSema SkipReason = "overflow-as-sema"

	// SkipGrammarGapLiteral excludes specific literal scenarios where the
	// grammar is more permissive than the specification:
	//
	//   - InvalidUnicodeLiteral / InvalidUnicodeCharacter: the grammar does not
	//     validate unicode escape sequences or disallow non-ASCII operator
	//     characters.
	//   - InvalidNumberLiteral: the grammar tokenises malformed hex/integer
	//     literals as two valid tokens rather than a single error token.
	//   - UnexpectedSyntax on map keys starting with a digit: the grammar
	//     permits them.
	//   - UnexpectedSyntax on pattern expressions in RETURN/WITH/SET: the
	//     grammar accepts them; the restriction is semantic.
	SkipGrammarGapLiteral SkipReason = "grammar-gap-literal"

	// SkipLongFloatSema is retained for documentation. The skip condition was
	// removed because strconv.ParseFloat handles very long but finite decimal
	// float literals correctly, rounding to the nearest IEEE-754 double without
	// error; the visitor no longer raises a SemaError for such literals.
	//
	//nolint:unused // retained for documentation; long valid floats parse correctly without a skip
	SkipLongFloatSema SkipReason = "long-float-sema"
)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL