Skip to content

Commit 65c1221

Browse files
authored
tests: tag targettable xfails (#3)
* tests: tag targettable xfails * tools: add porting backlog report * test: add placeholder gfql plans for return ordering/skip * test: add placeholder gfql plans for orderby/limit cases * test: auto-placehold target extension xfails * test: generate clause plans for target xfails * test: generate expr-aware plans for target xfails * test: extend target expr coverage for map/type conversion * docs: add changelog * docs: align changelog template * docs: add develop guide and release entry
1 parent 9e2edc0 commit 65c1221

File tree

15 files changed

+1387
-54
lines changed

15 files changed

+1387
-54
lines changed

CHANGELOG.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Changelog
2+
3+
All notable changes to the tck-gfql project are documented in this file. The PyGraphistry client and other Graphistry components are tracked in the main [Graphistry major release history documentation](https://graphistry.zendesk.com/hc/en-us/articles/360033184174-Enterprise-Release-List-Downloads).
4+
5+
The changelog format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
6+
This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) and all breaking changes are explictly noted here.
7+
8+
## [Development]
9+
<!-- Do Not Erase This Section - Used for tracking unreleased changes -->
10+
11+
### Added
12+
- None.
13+
14+
### Changed
15+
- None.
16+
17+
## [0.1.0 - 2026-01-03]
18+
19+
### Added
20+
- **GFQL plans**: Auto-generate clause + expression plans for target extension xfail scenarios (table ops + expr DSL).
21+
- **GFQL plan DSL**: Added expression AST helpers (`col`, `lit`, `param`, `func`, `binary`, `unary`, `list`, `map`, `index`, `star`) for non-executable plan capture.
22+
- **Docs**: Documented generated xfail plans and plan helpers in `tests/cypher_tck/README.md`.
23+
24+
### Changed
25+
- **GFQL plan generation**: Expanded target expr coverage to include map and type conversion buckets.

DEVELOP.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Development Setup
2+
3+
See also [CONTRIBUTING.md](CONTRIBUTING.md) (if/when added) and the project README.
4+
5+
This repo focuses on the openCypher TCK conformance harness for GFQL and uses
6+
the local `plans/` clone of the TCK repo for reference (not vendored).
7+
8+
## Local Tests
9+
10+
```bash
11+
pytest tests/cypher_tck -xvs
12+
TEST_CUDF=1 pytest tests/cypher_tck -xvs
13+
```
14+
15+
When running from a sibling `pygraphistry` checkout, set:
16+
17+
```bash
18+
PYTHONPATH=/path/to/pygraphistry python -m tests.cypher_tck.porting_backlog
19+
```
20+
21+
## CI
22+
23+
GitHub Actions runs the suite on PRs. See `.github/workflows`.
24+
25+
## Publish: Merge, Tag, & Release
26+
27+
1. Update `CHANGELOG.md` in your PR branch
28+
- Move changes from `## [Development]` into a dated release section (e.g., `## [0.1.0 - YYYY-MM-DD]`).
29+
- Keep `## [Development]` with empty headings.
30+
31+
2. Merge the PR to `main` (via GitHub UI or `gh pr merge`).
32+
33+
3. Switch to `main` and pull the merged changes:
34+
```bash
35+
git checkout main
36+
git pull
37+
```
38+
39+
4. Tag the repository with the new version number (semantic versioning):
40+
```bash
41+
git tag -a vX.Y.Z -m "vX.Y.Z"
42+
git push origin vX.Y.Z
43+
```
44+
45+
5. Create a GitHub Release with notes from the changelog:
46+
```bash
47+
gh release create vX.Y.Z --title "vX.Y.Z" --notes "See CHANGELOG.md for details."
48+
```

tests/cypher_tck/GAP_ANALYSIS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ translation guidelines.
99
- If a scenario is marked skip/xfail, add or reference a gap entry below.
1010
- Keep the entry short, with a clear root cause and a proposed path forward.
1111
- Update the entry when the gap is partially or fully addressed.
12+
- Targettable capability tags (e.g., `target-table-ops`, `target-expr-dsl`) are
13+
auto-applied to xfail scenarios by feature path for tracking near-term work.
1214

1315
## Entry format
1416
- **ID**: Short identifier (G1, G2, ...)

tests/cypher_tck/README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,28 @@ optional cuDF runs when enabled.
2121
- `engine='cudf'` (only when `TEST_CUDF=1` and cudf is available)
2222
- Record unsupported scenarios with explicit xfail/skip reasons and capability tags.
2323
- Preserve traceability to the original Cypher query and expected results.
24+
- Capability tags include `target-table-ops`, `target-expr-dsl`, `defer-quantifier`,
25+
`defer-path-enum`, `defer-unwind`, `defer-union`.
2426

2527
## Running
2628
```bash
2729
pytest tests/cypher_tck -xvs
2830
TEST_CUDF=1 pytest tests/cypher_tck -xvs
2931
```
3032

33+
## Porting backlog
34+
```bash
35+
PYGRAPHISTRY_PATH=/path/to/pygraphistry python -m tests.cypher_tck.porting_backlog
36+
python -m tests.cypher_tck.porting_backlog
37+
BACKLOG_LIMIT=20 python -m tests.cypher_tck.porting_backlog
38+
```
39+
3140
## Notes
3241
- The TCK repo is not vendored; use the local clone under `plans/`.
3342
- Each translated scenario should include a reference back to the TCK path,
3443
the original Cypher, and the expected rows or aggregates.
44+
- For xfail scenarios, `gfql` may contain a non-executable plan built with
45+
`tests.cypher_tck.gfql_plan` to document the intended translation. When a
46+
target-table-ops or target-expr-dsl scenario lacks a manual plan, a minimal
47+
clause-based plan is generated from the Cypher text at load time.
3548
- Track feature gaps and workarounds in `tests/cypher_tck/GAP_ANALYSIS.md`.

tests/cypher_tck/gfql_plan.py

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
from __future__ import annotations
2+
3+
from dataclasses import dataclass
4+
from typing import Any, Dict, Iterable, Optional, Tuple
5+
6+
7+
@dataclass(frozen=True)
8+
class PlanStep:
9+
op: str
10+
args: Dict[str, Any]
11+
12+
13+
@dataclass(frozen=True)
14+
class Expr:
15+
op: str
16+
args: Dict[str, Any]
17+
18+
19+
def step(op: str, **kwargs: Any) -> PlanStep:
20+
return PlanStep(op=op, args=kwargs)
21+
22+
23+
def expr(op: str, **kwargs: Any) -> Expr:
24+
return Expr(op=op, args=kwargs)
25+
26+
27+
def col(name: str) -> Expr:
28+
return expr("col", name=name)
29+
30+
31+
def lit(value: Any) -> Expr:
32+
return expr("lit", value=value)
33+
34+
35+
def param(name: str) -> Expr:
36+
return expr("param", name=name)
37+
38+
39+
def func(name: str, args: Iterable[Any]) -> Expr:
40+
return expr("func", name=name, args=tuple(args))
41+
42+
43+
def unary(op: str, value: Any) -> Expr:
44+
return expr("unary", op=op, value=value)
45+
46+
47+
def binary(op: str, left: Any, right: Any) -> Expr:
48+
return expr("binary", op=op, left=left, right=right)
49+
50+
51+
def list_(items: Iterable[Any]) -> Expr:
52+
return expr("list", items=tuple(items))
53+
54+
55+
def map_(items: Iterable[Tuple[str, Any]]) -> Expr:
56+
return expr("map", items=tuple(items))
57+
58+
59+
def index(base: Any, key: Any) -> Expr:
60+
return expr("index", base=base, key=key)
61+
62+
63+
def star() -> Expr:
64+
return expr("star")
65+
66+
67+
def raw(text: str) -> Expr:
68+
return expr("raw", text=text)
69+
70+
71+
def distinct_expr(value: Any) -> Expr:
72+
return expr("distinct", value=value)
73+
74+
75+
def plan(*steps: PlanStep) -> Tuple[PlanStep, ...]:
76+
return steps
77+
78+
79+
def match(*chain: Any) -> PlanStep:
80+
return step("match", chain=chain)
81+
82+
83+
def rows(table: str, source: Optional[str] = None) -> PlanStep:
84+
args: Dict[str, Any] = {"table": table}
85+
if source is not None:
86+
args["source"] = source
87+
return step("rows", **args)
88+
89+
90+
def select(items: Iterable[Tuple[str, Any]]) -> PlanStep:
91+
return step("select", items=tuple(items))
92+
93+
94+
def order_by(keys: Iterable[Tuple[str, Any]]) -> PlanStep:
95+
return step("order_by", keys=tuple(keys))
96+
97+
98+
def skip(value: Any) -> PlanStep:
99+
return step("skip", value=value)
100+
101+
102+
def limit(value: Any) -> PlanStep:
103+
return step("limit", value=value)
104+
105+
106+
def distinct() -> PlanStep:
107+
return step("distinct")
108+
109+
110+
def group_by(keys: Iterable[Any]) -> PlanStep:
111+
return step("group_by", keys=tuple(keys))
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
from __future__ import annotations
2+
3+
import os
4+
import sys
5+
from collections import Counter
6+
from typing import Iterable, List
7+
8+
from tests.cypher_tck.models import Scenario
9+
10+
_PYGRAPHISTRY_PATH = os.environ.get("PYGRAPHISTRY_PATH")
11+
if _PYGRAPHISTRY_PATH and _PYGRAPHISTRY_PATH not in sys.path:
12+
sys.path.insert(0, _PYGRAPHISTRY_PATH)
13+
14+
try:
15+
from tests.cypher_tck.scenarios import SCENARIOS
16+
except ModuleNotFoundError as exc: # pragma: no cover - import-time guard
17+
if exc.name == "graphistry":
18+
raise SystemExit(
19+
"graphistry is required to import scenario modules. "
20+
"Set PYGRAPHISTRY_PATH=/path/to/pygraphistry or install graphistry."
21+
) from exc
22+
raise
23+
24+
25+
TARGET_TAGS = (
26+
"target-table-ops",
27+
"target-expr-dsl",
28+
)
29+
30+
DEFER_TAGS = (
31+
"defer-quantifier",
32+
"defer-path-enum",
33+
"defer-expr-advanced",
34+
"defer-unwind",
35+
"defer-union",
36+
)
37+
38+
39+
def _filter_target(scenarios: Iterable[Scenario], tag: str) -> List[Scenario]:
40+
return [
41+
scenario
42+
for scenario in scenarios
43+
if scenario.status == "xfail"
44+
and scenario.gfql is None
45+
and tag in scenario.tags
46+
]
47+
48+
49+
def _print_bucket(title: str, scenarios: List[Scenario], limit: int) -> None:
50+
print(f"{title}: {len(scenarios)}")
51+
for scenario in scenarios[:limit]:
52+
print(f"- {scenario.key} | {scenario.feature_path} | {scenario.scenario}")
53+
if len(scenarios) > limit:
54+
print(f"- ... {len(scenarios) - limit} more")
55+
56+
57+
def main() -> None:
58+
limit = int(os.environ.get("BACKLOG_LIMIT", "40"))
59+
scenarios = list(SCENARIOS)
60+
61+
print("GFQL porting backlog (xfail + gfql missing)")
62+
print("Target tags:", ", ".join(TARGET_TAGS))
63+
print("Defer tags:", ", ".join(DEFER_TAGS))
64+
print()
65+
66+
tag_counts = Counter()
67+
for scenario in scenarios:
68+
if scenario.status != "xfail" or scenario.gfql is not None:
69+
continue
70+
tag_counts.update(scenario.tags)
71+
72+
print("Top tag counts (xfail + gfql missing):")
73+
for tag, count in tag_counts.most_common(12):
74+
print(f"- {tag}: {count}")
75+
print()
76+
77+
for tag in TARGET_TAGS:
78+
tagged = _filter_target(scenarios, tag)
79+
tagged.sort(key=lambda s: (s.feature_path, s.key))
80+
_print_bucket(f"Backlog for {tag}", tagged, limit)
81+
print()
82+
83+
84+
if __name__ == "__main__":
85+
main()

0 commit comments

Comments
 (0)