diff --git a/AGENTS.md b/AGENTS.md index 1d4c5634a20..18d0e3ef4d7 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,138 +1,335 @@ -# Codex Agent Playbook +AGENTS.md Welcome, AI Agent! Your persistence, curiosity, and craftsmanship make a difference. Take your time, work methodically, validate thoroughly, and iterate. This repository is large and tests can take time — that’s expected and supported. +You need to read the entire AGENTS.md file and follow all instructions exactly. Keep this fresh in your context as you work. + > **Timebox:** Aim to complete each autonomous run in **15–30 minutes**. +--- + +## Read‑Me‑Now: Zero‑Exception Test‑First Rule (Stricter) + +**You may not touch production code until a smallest‑scope failing automated test exists inside this repo and you have captured its report snippet.** +No exceptions. A user‑provided stack trace or “obvious” contract violation is **not** a substitute for a failing test in the repository. + +**Auto‑stop:** If you realize you patched production before creating the failing test, **stop**, revert the patch, and resume from “Reproduce first”. + +**Traceability trio (must appear in your handoff):** + +1. **Preamble** (what you’re about to do + exact commands) +2. **Evidence** (surefire/failsafe snippet from this repo) +3. **Plan** (one and only one `in_progress` step) + +It is illegal to `-am` when running tests! +It is illegal to `-q` when running tests! + +--- + ## Purpose & Contract -- **Bold goal:** deliver correct, minimal, well‑tested changes with clear handoff. No monkey‑patching or band‑aid fixes — always fix the underlying problem at its source. -- **Bias to action:** when inputs are ambiguous, choose a reasonable path, state assumptions, and proceed. -- **Ask only when blocked or irreversible:** escalate only if truly blocked (permissions, missing deps, conflicting requirements) or if a choice is high‑risk/irreversible. -- **Definition of Done** - - Code formatted and imports sorted. - - Compiles with a quick profile / targeted modules. - - Relevant module tests pass; failures triaged or crisply explained. - - Only necessary files changed; headers correct for new files. - - Clear final summary: what changed, why, where, how verified, next steps. + +* **Bold goal:** deliver correct, minimal, well‑tested changes with clear handoff. No monkey‑patching or band‑aid fixes — always fix the underlying problem at its source. +* **Bias to action:** when inputs are ambiguous, choose a reasonable path, state assumptions, and proceed. +* **Ask only when blocked or irreversible:** escalate only if truly blocked (permissions, missing deps, conflicting requirements) or if a choice is high‑risk/irreversible. +* **Definition of Done** + + * Code formatted and imports sorted. + * Compiles with a quick profile / targeted modules. + * Relevant module tests pass; failures triaged or crisply explained. + * Only necessary files changed; headers correct for new files. + * Clear final summary: what changed, why, where, how verified, next steps. + * **Evidence present:** failing test output (pre‑fix) and passing output (post‑fix) are both shown. ### No Monkey‑Patching or Band‑Aid Fixes (Non‑Negotiable) This repository requires durable, root‑cause fixes. Superficial changes that mask symptoms, mute tests, or add ad‑hoc toggles are not acceptable. What this means in practice -- Find and fix the root cause in the correct layer/module. -- Add or adjust targeted tests that fail before the fix and pass after. -- Keep changes minimal and surgical; do not widen APIs/configs to “make tests green”. -- Maintain consistency with existing style and architecture; prefer refactoring over hacks. + +* Find and fix the root cause in the correct layer/module. +* Add or adjust targeted tests that fail before the fix and pass after. +* Keep changes minimal and surgical; do not widen APIs/configs to “make tests green”. +* Maintain consistency with existing style and architecture; prefer refactoring over hacks. Strictly avoid -- Sleeping/timeouts to hide race conditions or flakiness. -- Broad catch‑and‑ignore or logging‑and‑continue of exceptions. -- Muting, deleting, or weakening assertions in tests to pass builds. -- Reflection or internal state manipulation to bypass proper interfaces. -- Feature flags/toggles that disable validation or logic instead of fixing it. -- Changing public APIs or configs without necessity and clear rationale tied to the root cause. + +* Sleeping/timeouts to hide race conditions or flakiness. +* Broad catch‑and‑ignore or logging‑and‑continue of exceptions. +* Muting, deleting, or weakening assertions in tests to pass builds. +* Reflection or internal state manipulation to bypass proper interfaces. +* Feature flags/toggles that disable validation or logic instead of fixing it. +* Changing public APIs or configs without necessity and clear rationale tied to the root cause. Preferred approach (fast and rigorous) -- Reproduce the issue and isolate the smallest failing test (class → method). -- Trace to the true source; fix it in the right module. -- Add focused tests covering the behavior and any critical edge cases. -- Run tight, targeted verifies for the impacted module(s) and broaden scope only if needed. + +* Reproduce the issue and isolate the smallest failing test (class → method). +* Trace to the true source; fix it in the right module. +* Add focused tests covering the behavior and any critical edge cases. +* Run tight, targeted verifies for the impacted module(s) and broaden scope only if needed. Review bar and enforcement -- Treat this policy as a blocking requirement. Changes that resemble workarounds will be rejected. -- Your final handoff must demonstrate: failing test before the fix, explanation of the root cause, minimal fix at source, and passing targeted tests after. + +* Treat this policy as a blocking requirement. Changes that resemble workarounds will be rejected. +* Your final handoff must demonstrate: failing test before the fix, explanation of the root cause, minimal fix at source, and passing targeted tests after. + +--- + +## Enforcement & Auto‑Fail Triggers + +Your run is **invalid** and must be restarted from “Reproduce first” if any of the following occur: + +* You modify production code before adding and running the smallest failing test in this repo. +* You proceed without pasting a surefire/failsafe report snippet from `target/*-reports/`. +* Your plan does not have **exactly one** `in_progress` step. +* You run tests using `-am` or `-q`. +* You treat a narrative failure description or external stack trace as equivalent to an in‑repo failing test. + +**Recovery procedure:** +Update the plan (`in_progress: create failing test`), post a preamble, create the failing test, run it, capture the report snippet, then resume. + +--- + +## Preamble & Evidence Protocol (Mandatory) + +Before any grouped actions (builds, tests, patches), post a **short preamble**: + +**Preamble template** + +``` +Preamble: Reproduce bug at smallest scope. +Module: +Commands: + mvn -o -pl -Dtest=Class#method verify | tail -500 +Expectation: test fails with the reported error. +``` + +After each grouped action, post an **Evidence block**: + +**Evidence template** + +``` +Evidence: +Command: mvn -o -pl -Dtest=Class#method verify +Report: /target/surefire-reports/.txt +Snippet: + +``` + +--- + +## Living Plan Protocol (Sharper) + +Maintain a **living plan** with checklist items (5–7 words each). Keep **exactly one** `in_progress`. + +**Plan format** + +``` +Plan +- [done] sanity build quick profile +- [in_progress] add smallest failing test +- [todo] minimal root-cause fix +- [todo] rerun focused then module tests +- [todo] format, verify, summary +``` + +**Rule:** If you deviate, update the plan **first** (switch `in_progress`), then proceed. Do not let plan and actions drift out of sync. + +--- ## Environment -- **JDK:** 11 (minimum). The project builds and runs on Java 11+. -- **Maven default:** run **offline** using `-o` whenever possible. -- **Network:** only when needed to fetch missing deps/plugins; then rerun the exact command **without** `-o` once, and return to offline. -- **Large project:** some module test suites can take **5–10 minutes**. Be patient, but bias toward **targeted** runs to keep momentum. + +* **JDK:** 11 (minimum). The project builds and runs on Java 11+. +* **Maven default:** run **offline** using `-o` whenever possible. +* **Network:** only when needed to fetch missing deps/plugins; then rerun the exact command **without** `-o` once, and return to offline. +* **Large project:** some module test suites can take **5–10 minutes**. Be patient, but bias toward **targeted** runs to keep momentum. ### Maven `-am` usage (house rule) `-am` (also-make) pulls in required upstream modules. That’s helpful for **compiles**, but hazardous for **tests**: Maven will advance included modules to the same lifecycle phase and run **their** tests too. **Rule of thumb** -- ✅ Use `-am` **only** for compile/verify with tests skipped (e.g. `-Pquick`).: - - `mvn -o -pl -am -Pquick verify` -- ❌ Do **not** use `-am` with `verify` when tests are enabled. + +* ✅ Use `-am` **only** for compile/verify with tests skipped (e.g. `-Pquick`).: + + * `mvn -o -pl -am -Pquick install` +* ❌ Do **not** use `-am` with `verify` when tests are enabled. **Two-step pattern (fast + safe)** -1) **Compile deps fast (skip tests):** - `mvn -o -pl -am -Pquick verify` -2) **Run tests:** + +1. **Compile deps fast (skip tests):** + `mvn -o -pl -am -Pquick install` +2. **Run tests:** `mvn -o -pl verify | tail -500` It is illegal to `-am` when running tests! It is illegal to `-q` when running tests! +--- + ## Quick Start (First 10 Minutes) + 1. **Discover** - - List modules: inspect root `pom.xml` (aggregator) and the module tree (see “Maven Module Overview” below). - - Search fast with ripgrep: `rg -n ""` + + * List modules: inspect root `pom.xml` (aggregator) and the module tree (see “Maven Module Overview” below). + * Search fast with ripgrep: `rg -n ""` 2. **Build sanity (fast, skip tests)** - - **Preferred:** `mvn -o -Pquick install | tail -200` - - **Alternative:** `mvn -o -Pquick verify | tail -200` + + * **Preferred:** `mvn -o -Pquick install | tail -200` + * **Alternative:** `mvn -o -Pquick install | tail -200` 3. **Format (Java, imports, XML)** - - `mvn -o -q -T 2C formatter:format impsort:sort xml-format:xml-format` + + * `mvn -o -q -T 2C formatter:format impsort:sort xml-format:xml-format` 4. **Targeted tests (tight loops)** - - By module (incl. deps): `mvn -o -pl verify | tail -500` - - Single class: `mvn -o -pl -Dtest=ClassName verify | tail -500` - - Single method: `mvn -o -pl -Dtest=ClassName#method verify | tail -500` + + * By module: `mvn -o -pl verify | tail -500` + * Single class: `mvn -o -pl -Dtest=ClassName verify | tail -500` + * Single method: `mvn -o -pl -Dtest=ClassName#method verify | tail -500` 5. **Inspect failures** - - **Unit (Surefire):** `/target/surefire-reports/` - - **IT (Failsafe):** `/target/failsafe-reports/` + + * **Unit (Surefire):** `/target/surefire-reports/` + * **IT (Failsafe):** `/target/failsafe-reports/` It is illegal to `-am` when running tests! It is illegal to `-q` when running tests! +--- + +## Bugfix Workflow (Mandatory) + +* **Reproduce first:** write the smallest focused test (class/method) that reproduces the reported bug **inside this repo**. Run it and confirm it fails with the same error/stacktrace. **Do not proceed without this.** +* **Keep the test as‑is:** do not weaken assertions or mute the failure. The failing test is your proof you’ve hit the right code path. +* **Fix at the root:** implement the minimal, surgical change in the correct module that addresses the underlying cause (no band‑aids). +* **Verify locally:** re‑run the focused test, then the surrounding module’s tests. Use targeted Maven invocations (class/method → module). Avoid `-am` with tests. +* **Broaden if needed:** only after green targeted runs, expand scope to neighboring modules when changes cross boundaries. +* **Document clearly:** in your final handoff, show the failing test before the fix, the root cause, the minimal fix, and passing tests after. Include **preamble** and **evidence** blocks. + +### Hard Gates (Do Not Proceed Unless True) + +* A failing test exists at the smallest scope (method/class) reproducing the report. + + * Show the failing command and include a snippet of the error/stack from `target/*-reports/`. +* **No production patch before the failing test is observed and recorded.** +* Test runs avoid `-am` and `-q`. + + * Use `-am` only with `-Pquick` to compile deps with tests skipped, then run tests without `-am`. +* Maintain a living plan with exactly one `in_progress` step; send a short preamble before long actions. + +### Required Sequence + +1. **Reproduce first** + + * Add the smallest failing test in the correct module. + * Run it directly: `mvn -o -pl -Dtest=Class#method verify | tail -500` + * Inspect `target/surefire-reports/` (or `target/failsafe-reports/`) and capture the failure. +2. **Fix at the root (minimal, surgical)** + + * Change the correct layer; avoid widening APIs/configs. +3. **Verify locally (tight loops)** + + * Re-run the exact test selection; then run the whole module. +4. **Broaden only if necessary** + + * Expand scope when changes cross module boundaries or neighbors fail. +5. **Document clearly** + + * Include: failing output (pre‑fix), root cause, minimal fix, passing output (post‑fix). + +### Quick Self‑Check Before First Code Patch + +1. Do I have a failing test and its report snippet saved? +2. Am I using legal Maven flags for tests (no `-am`, no `-q`)? +3. Is my next step in the plan marked `in_progress` and did I state a preamble? +4. Is my fix located at the correct source of truth, not a workaround? + +--- ## Working Loop -- **Plan** - - Break task into **small, verifiable steps**; keep one step in progress. - - Announce a short preamble before long actions (builds/tests). - - Decide and proceed autonomously; document assumptions inline. -- **Change** - - Make minimal, surgical edits. Keep style and structure consistent. -- **Format** - - `mvn -o -q -T 2C formatter:format impsort:sort xml-format:xml-format` -- **Compile (fast)** - - **Iterate locally:** `mvn -o -pl -am -Pquick verify | tail -500` -- **Test** - - Start with the smallest scope that exercises your change (class → module). - - For integration‑impacted changes, run module `verify` (includes ITs). -- **Triage** - - Read reports; fix root cause; expand scope **only when needed**. -- **Iterate** - - Keep moving without waiting for permission between steps. Escalate only at blocking points. - - Repeat until **Definition of Done** is satisfied. + +* **Plan** + + * Break task into **small, verifiable steps**; keep one step in progress. + * Announce a short preamble before long actions (builds/tests). + * Decide and proceed autonomously; document assumptions inline. +* **Change** + + * Make minimal, surgical edits. Keep style and structure consistent. +* **Format** + + * `mvn -o -q -T 2C formatter:format impsort:sort xml-format:xml-format` +* **Compile (fast)** + + * **Iterate locally:** `mvn -o -pl -am -Pquick install | tail -500` +* **Test** + + * Start with the smallest scope that exercises your change (class → module). + * For integration‑impacted changes, run module `verify` (includes ITs). +* **Triage** + + * Read reports; fix root cause; expand scope **only when needed**. +* **Iterate** + + * Keep moving without waiting for permission between steps. Escalate only at blocking points. + * Repeat until **Definition of Done** is satisfied. It is illegal to `-am` when running tests! It is illegal to `-q` when running tests! -## Planning & Progress -- **Living plan:** update as you learn; one active step at a time (5–7 words each). -- **Progress updates:** one crisp sentence when switching steps or after long runs. -- **Decide early:** if scope is unclear, pick the most reasonable option, note the assumption, and continue. -- **Escalate sparingly:** ask only if options diverge significantly in cost/impact or you are blocked (permissions, network policy, missing secrets). -- **Checkpoint cadence:** inform to maintain visibility; do **not** block on approvals unless required. +--- ## Testing Strategy -- **Prefer module tests you touched:** `-pl ` -- **Narrow further** to a class/method for tight loops; then broaden to the module. -- **Expand scope** when: - - Your change crosses module boundaries, or - - Neighbor module failures indicate integration impact. -- **Read reports** - - Surefire (unit): `target/surefire-reports/` - - Failsafe (IT): `target/failsafe-reports/` -- **Helpful flags** - - `-Dtest=Class#method` (unit selection) - - `-Dit.test=ITClass#method` (integration selection) - - `-DtrimStackTrace=false` (full traces) - - `-DskipITs` (focus on unit tests) - - `-DfailIfNoTests=false` (when selecting a class that has no tests on some platforms) + +* **Prefer module tests you touched:** `-pl ` +* **Narrow further** to a class/method for tight loops; then broaden to the module. +* **Expand scope** when: + + * Your change crosses module boundaries, or + * Neighbor module failures indicate integration impact. +* **Read reports** + + * Surefire (unit): `target/surefire-reports/` + * Failsafe (IT): `target/failsafe-reports/` +* **Helpful flags** + + * `-Dtest=Class#method` (unit selection) + * `-Dit.test=ITClass#method` (integration selection) + * `-DtrimStackTrace=false` (full traces) + * `-DskipITs` (focus on unit tests) + * `-DfailIfNoTests=false` (when selecting a class that has no tests on some platforms) + +### Optional: Redirect test stdout/stderr to files + +To help automated agents inspect what a test printed to the console, you may redirect `System.out`/`System.err` to per‑class files generated by Surefire/Failsafe. This is **optional** and should be used only when it aids triage—house rules still apply (no `-am` with tests, no `-q`). + +**Unit tests (Surefire):** + +```bash +mvn -o -pl -Dtest=ClassName[#method] -Dmaven.test.redirectTestOutputToFile=true verify | tail -500 +``` + +Logs will appear under: + +``` +/target/surefire-reports/ClassName-output.txt +``` + +**Integration tests (Failsafe):** + +```bash +mvn -o -pl -Dit.test=ITClassName[#method] -Dmaven.test.redirectTestOutputToFile=true verify | tail -500 +``` + +Logs will appear under: + +``` +/target/failsafe-reports/ITClassName-output.txt +``` + +Notes: +* Capture is **per test class**, not per method. Multiple methods in the same class share one `*-output.txt`. +* Only output actually written to the console is captured. If your logging configuration writes solely to files, you won’t see it here. +* Continue to include the normal **Evidence** snippet from the Surefire/Failsafe report. You may additionally quote lines from the corresponding `*-output.txt` when useful for debugging. + +--- ## Assertions: Make invariants explicit @@ -140,36 +337,38 @@ Assertions are executable claims about what must be true. They’re the fastest **Two useful flavors** -- **Temporary tripwires (debug asserts):** Add while hunting a failing test or weird behavior. Keep them cheap, contextual, and local to the suspect path. Remove after the mystery is solved **or** convert to permanent checks if the invariant is genuinely important. -- **Permanent contracts:** Encode **preconditions** (valid inputs), **postconditions** (valid outputs), and **invariants** (state that must always hold). These stay and prevent regressions. +* **Temporary tripwires (debug asserts):** Add while hunting a failing test or weird behavior. Keep them cheap, contextual, and local to the suspect path. Remove after the mystery is solved **or** convert to permanent checks if the invariant is genuinely important. +* **Permanent contracts:** Encode **preconditions** (valid inputs), **postconditions** (valid outputs), and **invariants** (state that must always hold). These stay and prevent regressions. **Where to add assertions** -- At **module boundaries** and **after parsing/external calls** (validate assumptions about returned/decoded data). -- Around **state transitions** (illegal transitions should fail loudly). -- In **concurrency hotspots** (e.g., “lock must be held”, “no concurrent mutation”). -- Before/after **caching, batching, or memoization** (keys, sizes, ordering, monotonicity). -- For **exhaustive enums** in `switch` statements (treat unexpected values as hard errors). +* At **module boundaries** and **after parsing/external calls** (validate assumptions about returned/decoded data). +* Around **state transitions** (illegal transitions should fail loudly). +* In **concurrency hotspots** (e.g., “lock must be held”, “no concurrent mutation”). +* Before/after **caching, batching, or memoization** (keys, sizes, ordering, monotonicity). +* For **exhaustive enums** in `switch` statements (treat unexpected values as hard errors). **How to write good assertions** -- One fact per assert. Fail **fast**, fail **usefully**. -- Include **stable context** in the message (ids, sizes, states) so the failure is self‑explanatory. -- Avoid side effects in the condition or message. Assertions may be disabled in some runtimes. -- Keep them **cheap**: no I/O, heavy allocations, or deep logging in the message. -- Don’t use asserts for **user‑facing validation**. Raise exceptions for expected bad inputs. +* One fact per assert. Fail **fast**, fail **usefully**. +* Include **stable context** in the message (ids, sizes, states) so the failure is self‑explanatory. +* Avoid side effects in the condition or message. Assertions may be disabled in some runtimes. +* Keep them **cheap**: no I/O, heavy allocations, or deep logging in the message. +* Don’t use asserts for **user‑facing validation**. Raise exceptions for expected bad inputs. **Java specifics** -- **Enable VM assertions in tests.** Tests must run with `-ea` so `assert` is active. -- Use **`assert`** for debug‑only invariants that “cannot happen.” Use **exceptions** for runtime guarantees: - - Preconditions: `IllegalArgumentException` / `Objects.requireNonNull` (or Guava `Preconditions` if present). - - Invariants: `IllegalStateException`. -- Prefer treating unexpected enum values as **hard errors** rather than adding a quiet `default` path. +* **Enable VM assertions in tests.** Tests must run with `-ea` so `assert` is active. +* Use **`assert`** for debug‑only invariants that “cannot happen.” Use **exceptions** for runtime guarantees: + + * Preconditions: `IllegalArgumentException` / `Objects.requireNonNull` (or Guava `Preconditions` if present). + * Invariants: `IllegalStateException`. +* Prefer treating unexpected enum values as **hard errors** rather than adding a quiet `default` path. **Concrete examples** Precondition (permanent) + ```java void setPort(int port) { if (port < 1 || port > 65_535) { @@ -180,6 +379,7 @@ void setPort(int port) { ``` Invariant (permanent) + ```java void advance(State next) { if (!allowedTransitions.get(state).contains(next)) { @@ -190,12 +390,14 @@ void advance(State next) { ``` Debug tripwire (temporary; remove or convert later) + ```java // Narrow a flaky failure around ordering assert isSorted(results) : "unsorted results, size=" + results.size() + " ids=" + ids(results); ``` Unreachable (hard error) + ```java switch (kind) { case A: return handleA(); @@ -206,6 +408,7 @@ switch (kind) { ``` Concurrency assumption + ```java synchronized void put(String k, String v) { assert Thread.holdsLock(this) : "put must hold instance monitor"; @@ -213,29 +416,42 @@ synchronized void put(String k, String v) { } ``` - House rule: Asserts are allowed and encouraged. Removing or weakening an assertion to “make it pass” is strictly forbidden — fix the cause, not the guardrail. +--- ## Triage Playbook -- **Missing dep/plugin offline** - - Remedy: **rerun the exact command without `-o`** once to fetch; then return offline. -- **Compilation errors** - - Fix imports, generics, visibility; re‑run quick verify (skip tests) in the **module**. -- **Flaky/slow tests** - - Run the specific failing test; read its report; stabilize root cause before broad runs. -- **Formatting failures** - - Run formatter/import/XML sort; re‑verify. -- **License header missing** - - Add header for **new** files only (see “Source File Headers”); **do not** change years on existing files. + +* **Missing dep/plugin offline** + + * Remedy: **rerun the exact command without `-o`** once to fetch; then return offline. +* **Compilation errors** + + * Fix imports, generics, visibility; re‑run quick install (skip tests) in the **module**. +* **Flaky/slow tests** + + * Run the specific failing test; read its report; stabilize root cause before broad runs. +* **Formatting failures** + + * Run formatter/import/XML sort; re‑verify. +* **License header missing** + + * Add header for **new** files only (see “Source File Headers”); **do not** change years on existing files. + +--- ## Code Formatting -- **Always run before finalizing:** - - `mvn -o -q -T 2C formatter:format impsort:sort xml-format:xml-format` -- **Style:** no wildcard imports; 120‑char width; curly braces always; LF line endings. -- **Tip:** formatting/import sort may be validated during `verify`. Running the commands proactively avoids CI/style failures. + +* **Always run before finalizing:** + + * `mvn -o -q -T 2C formatter:format impsort:sort xml-format:xml-format` +* **Style:** no wildcard imports; 120‑char width; curly braces always; LF line endings. +* **Tip:** formatting/import sort may be validated during `verify`. Running the commands proactively avoids CI/style failures. + +--- ## Source File Headers + Use this exact header for **new Java files only** (replace `${year}` with current year): ``` @@ -249,74 +465,115 @@ Use this exact header for **new Java files only** (replace `${year}` with curren * * SPDX-License-Identifier: BSD-3-Clause *******************************************************************************/ - ``` +``` Use this exact header. Be very precise. Do **not** modify existing headers’ years. +--- + ## Pre‑Commit Checklist -- **Format:** `mvn -o -q -T 2C formatter:format impsort:sort xml-format:xml-format` -- **Compile (fast path):** `mvn -o -Pquick verify | tail -200` -- **Tests (targeted):** `mvn -o -pl verify | tail -500` (broaden scope if needed) -- **Reports:** zero new failures in `target/surefire-reports/` or `target/failsafe-reports/`, or explain precisely. + +* **Format:** `mvn -o -q -T 2C formatter:format impsort:sort xml-format:xml-format` +* **Compile (fast path):** `mvn -o -Pquick install | tail -200` +* **Tests (targeted):** `mvn -o -pl verify | tail -500` (broaden scope if needed) +* **Reports:** zero new failures in `target/surefire-reports/` or `target/failsafe-reports/`, or explain precisely. +* **Evidence:** include pre‑fix failing snippet and post‑fix passing summary. + +--- ## Navigation & Search -- Fast file search: `rg --files` -- Fast content search: `rg -n ""` -- Read big files in chunks: - - `sed -n '1,200p' path/to/File.java` - - `sed -n '201,400p' path/to/File.java` + +* Fast file search: `rg --files` +* Fast content search: `rg -n ""` +* Read big files in chunks: + + * `sed -n '1,200p' path/to/File.java` + * `sed -n '201,400p' path/to/File.java` + +--- ## Autonomy Rules (Act > Ask) -- **Default:** act with assumptions. Document assumptions in your plan and final answer. -- **Keep going:** chain steps without waiting for permission; send short progress updates before long actions. -- **Ask only when:** - - Blocked by sandbox/approvals/network policy or missing secrets. - - The decision is destructive/irreversible, repo‑wide, or impacts public APIs. - - Adding dependencies, changing build profiles, or altering licensing. -- **Prefer reversible moves:** take the smallest local change that unblocks progress; validate with targeted tests before expanding scope. -- **Choose defaults** - - **Tests:** start with `-pl `, then `-Dtest=Class#method` / `-Dit.test=ITClass#method`. - - **Build:** use `-o` quick/profiled commands; briefly drop `-o` to fetch missing deps, then return offline. - - **Formatting:** run formatter/impsort/xml‑format proactively before verify. - - **Reports:** read surefire/failsafe locally; expand scope only when necessary. -- **Error handling** - - On compile/test failure: fix root cause locally, rerun targeted tests, then broaden. - - On flaky tests: rerun class/method; stabilize cause before repo‑wide runs. - - On formatting/license issues: apply prescribed commands/headers immediately. -- **Communication** - - **Preambles:** 1–2 sentences grouping upcoming actions. - - **Updates:** inform to maintain visibility; do **not** request permission unless in “Ask only when” above. + +* **Default:** act with assumptions. Document assumptions in your plan and final answer. +* **Keep going:** chain steps without waiting for permission; send short progress updates before long actions. +* **Ask only when:** + + * Blocked by sandbox/approvals/network policy or missing secrets. + * The decision is destructive/irreversible, repo‑wide, or impacts public APIs. + * Adding dependencies, changing build profiles, or altering licensing. +* **Prefer reversible moves:** take the smallest local change that unblocks progress; validate with targeted tests before expanding scope. +* **Choose defaults** + + * **Tests:** start with `-pl `, then `-Dtest=Class#method` / `-Dit.test=ITClass#method`. + * **Build:** use `-o` quick/profiled commands; briefly drop `-o` to fetch missing deps, then return offline. + * **Formatting:** run formatter/impsort/xml‑format proactively before verify. + * **Reports:** read surefire/failsafe locally; expand scope only when necessary. +* **Error handling** + + * On compile/test failure: fix root cause locally, rerun targeted tests, then broaden. + * On flaky tests: rerun class/method; stabilize cause before repo‑wide runs. + * On formatting/license issues: apply prescribed commands/headers immediately. +* **Communication** + + * **Preambles:** 1–2 sentences grouping upcoming actions. + * **Updates:** inform to maintain visibility; do **not** request permission unless in “Ask only when” above. + +--- ## Answer Template (Use This) -- **What changed:** summary of approach and rationale. -- **Files touched:** list file paths. -- **Commands run:** key build/test commands. -- **Verification:** which tests passed, where you checked reports. -- **Assumptions:** key assumptions and autonomous decisions you made. -- **Limitations:** anything left or risky edge cases. -- **Next steps:** optional suggestions for follow‑ups. + +* **What changed:** summary of approach and rationale. +* **Files touched:** list file paths. +* **Commands run:** key build/test commands. +* **Verification:** which tests passed, where you checked reports. +* **Evidence:** failing output (pre‑fix) and passing output (post‑fix) snippets. +* **Assumptions:** key assumptions and autonomous decisions you made. +* **Limitations:** anything left or risky edge cases. +* **Next steps:** optional suggestions for follow‑ups. + +--- ## Running Tests -- By module: - - `mvn -o -pl core/sail/shacl verify | tail -500` -- Entire repo: - - `mvn -o verify` (long; only when appropriate) -- Useful flags: - - `-Dtest=ClassName` - - `-Dtest=ClassName#method` - - `-Dit.test=ITClass#method` - - `-DtrimStackTrace=false` + +* By module: + + * `mvn -o -pl core/sail/shacl verify | tail -500` +* Entire repo: + + * `mvn -o verify` (long; only when appropriate) +* Useful flags: + + * `-Dtest=ClassName` + * `-Dtest=ClassName#method` + * `-Dit.test=ITClass#method` + * `-DtrimStackTrace=false` + +--- ## Build -- **Build without tests (fast path):** - - `mvn -o -Pquick verify` -- **Verify with tests:** - - Targeted module(s): `mvn -o -pl verify` - - Entire repo: `mvn -o verify` (use only when appropriate) -- **When offline fails due to missing deps:** - - Re‑run the **exact** command **without** `-o` once to fetch, then return to `-o`. + +* **Build without tests (fast path):** + + * `mvn -o -Pquick install` +* **Verify with tests:** + + * Targeted module(s): `mvn -o -pl verify` + * Entire repo: `mvn -o verify` (use only when appropriate) +* **When offline fails due to missing deps:** + + * Re‑run the **exact** command **without** `-o` once to fetch, then return to `-o`. + +--- + +## Prohibited Misinterpretations + +* A user stack trace, reproduction script, or verbal description **is not evidence**. You must implement the smallest failing test **inside this repo**. +* “Obvious” contract violations (e.g., iterator returns `null`) still require a failing test first. Assumptions are not substitutes for evidence. +* “Quick fixes” are not quick if they bypass the workflow. They create audit gaps and regressions. + +--- ## Maven Module Overview @@ -443,10 +700,9 @@ rdf4j: root project ``` ## Safety & Boundaries -- Don’t commit or push unless explicitly asked. -- Don’t add new dependencies without explicit approval. -- Use approvals sparingly: request approval only for network fetches when offline fails, destructive operations, or repo‑wide impacts. Otherwise proceed locally and continue working. +* Don’t commit or push unless explicitly asked. +* Don’t add new dependencies without explicit approval. It is illegal to `-am` when running tests! It is illegal to `-q` when running tests! diff --git a/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/ArrayBindingSet.java b/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/ArrayBindingSet.java index 728960aab39..08b7960e499 100644 --- a/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/ArrayBindingSet.java +++ b/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/ArrayBindingSet.java @@ -143,10 +143,7 @@ public Function getDirectGetBinding(String bindingName } return a -> { Value value = a.values[index]; - if (value == NULL_VALUE) { - value = null; - } - if (value != null) { + if (value != null && value != NULL_VALUE) { return new SimpleBinding(bindingName, value); } else { return null; @@ -416,7 +413,7 @@ public ArrayBindingSetIterator() { @Override public boolean hasNext() { while (index < values.length) { - if (values[index] != null) { + if (values[index] != null && values[index] != NULL_VALUE) { return true; } index++; @@ -426,20 +423,10 @@ public boolean hasNext() { @Override public Binding next() { - while (index < values.length) { - if (values[index] != null) { - String name = bindingNames[index]; - Value value = values[index++]; - if (value == NULL_VALUE) { - value = null; - } - if (value != null) { - return new SimpleBinding(name, value); - } else { - return null; - } - } - index++; + if (hasNext()) { + String name = bindingNames[index]; + Value value = values[index++]; + return new SimpleBinding(name, value); } throw new NoSuchElementException(); diff --git a/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/ProjectionRemovalOptimizer.java b/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/ProjectionRemovalOptimizer.java index f3364e20f63..d146c52ba9f 100644 --- a/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/ProjectionRemovalOptimizer.java +++ b/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/ProjectionRemovalOptimizer.java @@ -79,6 +79,7 @@ private ProjectionFinder() { @Override public void meet(Projection node) throws RuntimeException { super.meet(node); + VariableFinder findVariables = new VariableFinder(); node.visit(findVariables); diff --git a/core/queryalgebra/evaluation/src/test/java/org/eclipse/rdf4j/query/algebra/evaluation/ArrayBindingSetNullHandlingTest.java b/core/queryalgebra/evaluation/src/test/java/org/eclipse/rdf4j/query/algebra/evaluation/ArrayBindingSetNullHandlingTest.java new file mode 100644 index 00000000000..0bb55852ccd --- /dev/null +++ b/core/queryalgebra/evaluation/src/test/java/org/eclipse/rdf4j/query/algebra/evaluation/ArrayBindingSetNullHandlingTest.java @@ -0,0 +1,66 @@ +/** + * ****************************************************************************** + * Copyright (c) 2025 Eclipse RDF4J contributors. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Distribution License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + * ****************************************************************************** + */ +package org.eclipse.rdf4j.query.algebra.evaluation; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.eclipse.rdf4j.model.vocabulary.OWL; +import org.eclipse.rdf4j.query.Binding; +import org.junit.jupiter.api.Test; + +/** + * Reproduces a NullPointerException when an ArrayBindingSet contains an explicit null (UNDEF) binding value and is + * copied into a QueryBindingSet. Prior to the fix, iterating the ArrayBindingSet could yield a null Binding, which + * caused NPE in QueryBindingSet.addBinding. + */ +public class ArrayBindingSetNullHandlingTest { + + @Test + public void iteratorShouldNotReturnNullBindings() { + ArrayBindingSet bs = new ArrayBindingSet("myVar", "unbound", "mappingProp", "const"); + // Explicitly set an UNDEF/null binding using the direct setter (stores a sentinel NULL_VALUE) + bs.getDirectSetBinding("myVar").accept(null, bs); + // Add a real binding so iteration has at least one valid element + bs.getDirectSetBinding("mappingProp").accept(OWL.EQUIVALENTCLASS, bs); + + for (Binding b : bs) { + assertNotNull(b, "iterator must not yield null Binding elements"); + } + } + + @Test + public void copyingToQueryBindingSetMustSkipUndefBindings() { + ArrayBindingSet bs = new ArrayBindingSet("myVar", "unbound", "mappingProp", "const"); + // myVar is explicitly present with UNDEF value + bs.getDirectSetBinding("myVar").accept(null, bs); + // mappingProp has a concrete value + bs.getDirectSetBinding("mappingProp").accept(OWL.EQUIVALENTCLASS, bs); + + // Creating a QueryBindingSet from the ArrayBindingSet should not throw + QueryBindingSet qbs = assertDoesNotThrow(() -> new QueryBindingSet(bs)); + + assertTrue(qbs.hasBinding("mappingProp")); + assertEquals(OWL.EQUIVALENTCLASS, qbs.getValue("mappingProp")); + // UNDEF binding must not appear in the resulting set + assertFalse(qbs.hasBinding("myVar")); + assertEquals(1, qbs.size()); + } + +} diff --git a/core/queryparser/sparql/src/main/java/org/eclipse/rdf4j/query/parser/sparql/WildcardProjectionProcessor.java b/core/queryparser/sparql/src/main/java/org/eclipse/rdf4j/query/parser/sparql/WildcardProjectionProcessor.java index dac7778fd77..e7a17cdf5ad 100644 --- a/core/queryparser/sparql/src/main/java/org/eclipse/rdf4j/query/parser/sparql/WildcardProjectionProcessor.java +++ b/core/queryparser/sparql/src/main/java/org/eclipse/rdf4j/query/parser/sparql/WildcardProjectionProcessor.java @@ -58,8 +58,12 @@ public static void process(ASTOperationContainer container) throws MalformedQuer whereClause.jjtAccept(collector, null); Set selectClauses = collector.getSelectClauses(); + // process nested SELECT wildcards deepest-first so inner subqueries are expanded + // before their parents collect variables + java.util.List ordered = new java.util.ArrayList<>(selectClauses); + ordered.sort(java.util.Comparator.comparingInt(WildcardProjectionProcessor::depth).reversed()); - for (ASTSelect selectClause : selectClauses) { + for (ASTSelect selectClause : ordered) { if (selectClause.isWildcard()) { ASTSelectQuery q = (ASTSelectQuery) selectClause.jjtGetParent(); @@ -95,6 +99,16 @@ public static void process(ASTOperationContainer container) throws MalformedQuer } } + private static int depth(Node n) { + int d = 0; + Node p = n.jjtGetParent(); + while (p != null) { + d++; + p = p.jjtGetParent(); + } + return d; + } + private static void addQueryVars(ASTWhereClause queryBody, Node wildcardNode) throws MalformedQueryException { QueryVariableCollector visitor = new QueryVariableCollector(); diff --git a/core/sail/memory/src/test/java/org/eclipse/rdf4j/sail/memory/MemoryStoreUndefBindingQueryTest.java b/core/sail/memory/src/test/java/org/eclipse/rdf4j/sail/memory/MemoryStoreUndefBindingQueryTest.java new file mode 100644 index 00000000000..8511552e2d4 --- /dev/null +++ b/core/sail/memory/src/test/java/org/eclipse/rdf4j/sail/memory/MemoryStoreUndefBindingQueryTest.java @@ -0,0 +1,267 @@ +/** + * ****************************************************************************** + * Copyright (c) 2025 Eclipse RDF4J contributors. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Distribution License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + * ****************************************************************************** + */ +package org.eclipse.rdf4j.sail.memory; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.eclipse.rdf4j.model.impl.SimpleNamespace; +import org.eclipse.rdf4j.model.util.Values; +import org.eclipse.rdf4j.model.vocabulary.FOAF; +import org.eclipse.rdf4j.model.vocabulary.OWL; +import org.eclipse.rdf4j.query.BindingSet; +import org.eclipse.rdf4j.repository.sail.SailRepository; +import org.eclipse.rdf4j.repository.sail.SailRepositoryConnection; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * Reproduces the scenario described in the issue: a GRAPH clause containing an UNDEF binding should not cause a + * NullPointerException during evaluation when joined with another pattern. The query should evaluate without error and + * produce one solution binding for mappingProp. + */ +public class MemoryStoreUndefBindingQueryTest { + + private SailRepository repository; + + @BeforeEach + public void setUp() { + repository = new SailRepository(new MemoryStore()); + repository.init(); + } + + @AfterEach + public void tearDown() { + if (repository != null) { + repository.shutDown(); + } + } + + @Test + public void testGraphBindUndefDoesNotThrowAndBindsMappingProp() { + try (SailRepositoryConnection conn = repository.getConnection()) { + SimpleNamespace NS1 = new SimpleNamespace("ex1", "http://example.org/"); + SimpleNamespace NS2 = new SimpleNamespace("ex2", "http://example.org/2/"); + + // Add a statement so that the named graph exists + conn.add(Values.iri(NS1, "A1"), OWL.EQUIVALENTCLASS, Values.iri(NS2, "A2"), + Values.iri("http://example.org/")); + + String sparql = "PREFIX rdfs: \n" + + "PREFIX owl: \n" + + "SELECT * {\n" + + " ?mappingProp rdfs:subPropertyOf* owl:equivalentClass .\n" + + " GRAPH {\n" + + " BIND(?unbound as ?myVar)\n" + + " # Also reproduces with: VALUES(?myVar) { (UNDEF) }\n" + + " }\n" + + "}"; + + var query = conn.prepareTupleQuery(sparql); + + int count = 0; + try (var res = query.evaluate()) { + while (res.hasNext()) { + BindingSet bs = res.next(); + assertTrue(bs.hasBinding("mappingProp"), "Expected mappingProp binding"); + assertEquals(OWL.EQUIVALENTCLASS, bs.getValue("mappingProp")); + count++; + } + } + assertEquals(1, count, "Expected exactly one result row"); + } + } + + @Test + public void testSubselect() { + try (SailRepositoryConnection conn = repository.getConnection()) { + SimpleNamespace NS1 = new SimpleNamespace("ex1", "http://example.org/"); + SimpleNamespace NS2 = new SimpleNamespace("ex2", "http://example.org/2/"); + + // Add a statement so that the named graph exists + conn.add(Values.iri(NS1, "A1"), FOAF.KNOWS, Values.iri(NS2, "A2"), + Values.iri("http://example.org/")); + + String sparql = "PREFIX rdfs: \n" + + "PREFIX owl: \n" + + "SELECT * WHERE { SELECT * WHERE {\n" + + " ?a ?prop ?b .\n" + + "}}"; + + var query = conn.prepareTupleQuery(sparql); + + int count = 0; + try (var res = query.evaluate()) { + while (res.hasNext()) { + BindingSet bs = res.next(); + assertFalse(bs.isEmpty(), "Expected non-empty binding set"); + assertTrue(bs.hasBinding("a"), "Expected binding, was: " + bs); + count++; + } + } + assertEquals(1, count, "Expected exactly one result row"); + } + } + + @Test + public void testSubSubselect() { + try (SailRepositoryConnection conn = repository.getConnection()) { + SimpleNamespace NS1 = new SimpleNamespace("ex1", "http://example.org/"); + SimpleNamespace NS2 = new SimpleNamespace("ex2", "http://example.org/2/"); + + // Add a statement so that the named graph exists + conn.add(Values.iri(NS1, "A1"), FOAF.KNOWS, Values.iri(NS2, "A2"), + Values.iri("http://example.org/")); + + String sparql = "PREFIX rdfs: \n" + + "PREFIX owl: \n" + + "SELECT * WHERE { SELECT * WHERE { SELECT * WHERE {\n" + + " ?a ?prop ?b .\n" + + "}}}"; + + var query = conn.prepareTupleQuery(sparql); + + int count = 0; + try (var res = query.evaluate()) { + while (res.hasNext()) { + BindingSet bs = res.next(); + assertFalse(bs.isEmpty(), "Expected non-empty binding set"); + assertTrue(bs.hasBinding("a"), "Expected binding, was: " + bs); + count++; + } + } + assertEquals(1, count, "Expected exactly one result row"); + } + } + + @Test + public void testSubSubselect2() { + try (SailRepositoryConnection conn = repository.getConnection()) { + SimpleNamespace NS1 = new SimpleNamespace("ex1", "http://example.org/"); + SimpleNamespace NS2 = new SimpleNamespace("ex2", "http://example.org/2/"); + + // Add a statement so that the named graph exists + conn.add(Values.iri(NS1, "A1"), FOAF.KNOWS, Values.iri(NS2, "A2"), + Values.iri("http://example.org/")); + + String sparql = "PREFIX rdfs: \n" + + "PREFIX owl: \n" + + "SELECT * WHERE { BIND(1 as ?one)\n {SELECT * WHERE { BIND(1 as ?one)\n {SELECT * WHERE { SELECT * WHERE {\n" + + " ?a ?prop ?b .\n" + + "}}}}}}"; + + var query = conn.prepareTupleQuery(sparql); + + int count = 0; + try (var res = query.evaluate()) { + while (res.hasNext()) { + BindingSet bs = res.next(); + assertFalse(bs.isEmpty(), "Expected non-empty binding set"); + assertTrue(bs.hasBinding("a"), "Expected binding, was: " + bs); + count++; + } + } + assertEquals(1, count, "Expected exactly one result row"); + } + } + + // Temporary helper for debugging the failing test: dump optimized plan + @Test + public void debugExplainSubSubselect() { + try (SailRepositoryConnection conn = repository.getConnection()) { + SimpleNamespace NS1 = new SimpleNamespace("ex1", "http://example.org/"); + SimpleNamespace NS2 = new SimpleNamespace("ex2", "http://example.org/2/"); + + conn.add(Values.iri(NS1, "A1"), FOAF.KNOWS, Values.iri(NS2, "A2"), + Values.iri("http://example.org/")); + + String sparql = "PREFIX rdfs: \n" + + "PREFIX owl: \n" + + "SELECT * WHERE { SELECT * WHERE { SELECT * WHERE {\n" + + " ?a ?prop ?b .\n" + + "}}}"; + + var query = conn.prepareTupleQuery(sparql); + System.out.println("\n==== Optimized Plan (debug) ===="); + System.out.println(query.explain(org.eclipse.rdf4j.query.explanation.Explanation.Level.Optimized)); + System.out.println("==== Executed Plan (debug) ===="); + System.out.println(query.explain(org.eclipse.rdf4j.query.explanation.Explanation.Level.Executed)); + + try (var res = query.evaluate()) { + System.out.println("==== Results (debug subsubselect) ===="); + while (res.hasNext()) { + var bs = res.next(); + System.out.println(bs); + } + } + } + } + + @Test + public void debugExplainSubselect() { + try (SailRepositoryConnection conn = repository.getConnection()) { + SimpleNamespace NS1 = new SimpleNamespace("ex1", "http://example.org/"); + SimpleNamespace NS2 = new SimpleNamespace("ex2", "http://example.org/2/"); + + conn.add(Values.iri(NS1, "A1"), FOAF.KNOWS, Values.iri(NS2, "A2"), + Values.iri("http://example.org/")); + + String sparql = "PREFIX rdfs: \n" + + "PREFIX owl: \n" + + "SELECT * WHERE { SELECT * WHERE {\n" + + " ?a ?prop ?b .\n" + + "}}"; + + var query = conn.prepareTupleQuery(sparql); + System.out.println("\n==== Optimized Plan (debug subselect) ===="); + System.out.println(query.explain(org.eclipse.rdf4j.query.explanation.Explanation.Level.Optimized)); + System.out.println("==== Executed Plan (debug subselect) ===="); + System.out.println(query.explain(org.eclipse.rdf4j.query.explanation.Explanation.Level.Executed)); + + try (var res = query.evaluate()) { + while (res.hasNext()) { + res.next(); + } + } + } + } + + @Test + public void debugAllVariablesUsedInQueryForSubSubselect() throws Exception { + String sparql = "PREFIX rdfs: \n" + + "PREFIX owl: \n" + + "SELECT * WHERE { SELECT * WHERE { SELECT * WHERE {\n" + + " ?a ?prop ?b .\n" + + "}}}"; + + var pq = org.eclipse.rdf4j.query.parser.QueryParserUtil + .parseQuery(org.eclipse.rdf4j.query.QueryLanguage.SPARQL, sparql, null); + var te = pq.getTupleExpr(); + org.eclipse.rdf4j.query.algebra.QueryRoot root; + if (te instanceof org.eclipse.rdf4j.query.algebra.QueryRoot) { + root = (org.eclipse.rdf4j.query.algebra.QueryRoot) te; + } else { + root = new org.eclipse.rdf4j.query.algebra.QueryRoot(te); + } + String[] all = org.eclipse.rdf4j.query.algebra.evaluation.impl.ArrayBindingBasedQueryEvaluationContext + .findAllVariablesUsedInQuery(root); + System.out.println("==== allVariables (debug subsubselect) ===="); + for (String v : all) { + System.out.println(v); + } + System.out.println("==== tuple expr (raw) ===="); + System.out.println(te); + } +} diff --git a/testsuites/sparql/src/main/java/org/eclipse/rdf4j/testsuite/sparql/tests/BindTest.java b/testsuites/sparql/src/main/java/org/eclipse/rdf4j/testsuite/sparql/tests/BindTest.java index 84023599e7d..ce05e7fa492 100644 --- a/testsuites/sparql/src/main/java/org/eclipse/rdf4j/testsuite/sparql/tests/BindTest.java +++ b/testsuites/sparql/src/main/java/org/eclipse/rdf4j/testsuite/sparql/tests/BindTest.java @@ -16,6 +16,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNull; +import java.util.Arrays; import java.util.List; import java.util.function.Supplier; import java.util.stream.Stream; @@ -135,7 +136,8 @@ private void testSES2250BindErrors(RepositoryConnection conn) { TupleQuery tq = conn.prepareTupleQuery(QueryLanguage.SPARQL, qb); try (TupleQueryResult evaluate = tq.evaluate()) { - assertFalse(evaluate.hasNext(), "The query should not return a result"); + assertFalse(evaluate.hasNext(), + "The query should not return a result: " + Arrays.toString(evaluate.stream().toArray())); } }