Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ jobs:

# Verify installation works across platforms after publishing
test-installation:
needs: publish
needs: [build, publish]
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
Expand All @@ -214,15 +214,21 @@ jobs:

- name: Test installation from PyPI
if: github.event.inputs.test_pypi != 'true'
shell: bash
env:
BENCHBOX_VERSION: ${{ needs.build.outputs.version }}
run: |
pip install benchbox
pip install "benchbox==${BENCHBOX_VERSION}"
Comment thread
joeharris76 marked this conversation as resolved.
python -c "import importlib.util; import benchbox; assert importlib.util.find_spec('pandas') is None, 'published core wheel must not pull pandas'; print(f'BenchBox {benchbox.__version__} installed successfully (minimal import surface)')"
benchbox --help

- name: Test installation from Test PyPI
if: github.event.inputs.test_pypi == 'true'
shell: bash
env:
BENCHBOX_VERSION: ${{ needs.build.outputs.version }}
run: |
pip install -i https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ benchbox
pip install -i https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ "benchbox==${BENCHBOX_VERSION}"
python -c "import importlib.util; import benchbox; assert importlib.util.find_spec('pandas') is None, 'published core wheel must not pull pandas'; print(f'BenchBox {benchbox.__version__} installed successfully (minimal import surface)')"
benchbox --help

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ work:
(bare-column projection, G-11 rule), and add funding to the matching
EXPECTED_VIEWS set in test_duckdb_browser_contract.py. No new base-table
column, so no read_model_version bump.
notes: >-

DONE in PR #1080: r.funding added to result_detail_metrics in
duckdb_builder.py + browser-duckdb-schema.sql; EXPECTED_VIEWS guards it.
needs: [w1]
Expand Down
4 changes: 2 additions & 2 deletions _project/config/fast_test_lane_policy.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"enabled": true,
"max_fast_tests": 25190,
"_ceiling_note": "Bumped 13200 -> 22000 on 2026-04-13 to reflect grown fast-lane (20,184 collected). Bumped 22000 -> 22100 on 2026-05-10 for joinorder-canonical-foundation Phase-1 (data_fetch + scale-factor + surface-field tests; 22038 collected). Bumped 22100 -> 22400 on 2026-05-11 for joinorder-canonical-cutover canonical SQL/DataFrame, MCP, publishing, and corpus-inventory guards; 22317 collected. Bumped 22400 -> 22500 on 2026-05-13 for UAT enabled-platform remediation coverage; pr-preflight collected 22419. Bumped 22500 -> 22510 on 2026-05-15 after rebasing onto develop at 36ab01058; pr-preflight collected 22501 before this branch's new public theme contract tests, which are medium-marked to avoid fast-lane growth. Bumped 22510 -> 22530 on 2026-05-14 for pr-review-followup batch coverage (joinorder dataframe family, pg_mooncake adapter, landing quickstart validation, explorer read-model retry); pr-preflight collected 22520. Bumped 22530 -> 22540 on 2026-05-15 for results-explorer-post-theme-reconcile count-aware Home/script coverage; CI collected 22537. Bumped 22540 -> 22543 on 2026-05-16 for pr-review-followups coverage on UAT CLI dispatch, UAT release-gate envelope scoping, and develop-post-merge orphan ordering; ci-lint collected 22543. Bumped 22543 -> 22550 on 2026-05-16 for prompts landing Phase 1 CLI hygiene validator/prompt-shape coverage; pr-preflight collected 22550. Bumped 22550 -> 22553 on 2026-05-16 for prompts landing Phase 2 cost-class and registry prompt-safety coverage; timing policy collected 22553. Bumped 22553 -> 22558 on 2026-05-16 for prompts landing Phase 3 platform-option and TPC-DS dsdgen validator coverage; timing_policy_check collected 22558. Bumped 22558 -> 22561 on 2026-05-16 for prompts landing Phase 4 provenance template and capture-plan footer coverage; timing_policy_check collected 22561. Bumped 22561 -> 22563 on 2026-05-16 for prompts landing review fixes covering MCP cost-gating parity and compare summary discipline; targeted lane count increased by two fast tests. Bumped 22563 -> 22568 on 2026-05-16 for prompts landing Phase 5 MCP parity prompt-surface coverage; timing_policy_check collected 22568. Bumped 22568 -> 22570 on 2026-05-16 for prompts landing MCP real-tool parity and platform-option gap follow-up coverage; timing_policy_check collected 22570. Bumped 22570 -> 22572 on 2026-05-19 for pr-review-followups prompt regressions covering credential-before-smoke ordering and smoke-scale dsdgen warning behavior; timing_policy_check collected 22572. Set 22572 -> 25000 on 2026-05-19 by maintainer direction during pr-review-followups cleanup. Ratchet down if the lane contracts; never raise without justification. Bumped 25000 -> 25050 on 2026-07-16 for tuning-mode-vocabulary-and-facet-implementation-20260712 (ADR-2 canonical_mode/tuned-fallback/official-refusal coverage, the PACKAGED_RESOURCE composition pin added when merging develop's #1188 packaged-template tier, and the physical_mechanisms unknown-vs-empty ingest-pipeline regression tests); develop alone collected 24995, this branch's merge collected 25022. Bumped 25050 -> 25060 on 2026-07-17 for tuning-capability-registry-coverage-20260716 (alias-resolution invariant tests and derived generator-coverage drift guards for the 9 newly-registered platforms); CI collected 25052 on the merge ref. Bumped 25060 -> 25080 on 2026-07-17 composing #1198's registry-coverage guards with #1176's provenance/hash export coverage (test_tuning_provenance_export.py + test_requested_config_hash.py); both branches independently bumped from 25050 (#1198: 25052 collected; #1176 pre-compose: 25062 collected); composed merge tree collects 25077. Bumped 25080 -> 25140 on 2026-07-17 for tuning-from-config-forwarding-sweep-20260716 (test_tuning_config_forwarding.py's registry-driven parametrized test, one case per registered platform adapter, verifying from_config forwards tuning_enabled/tuning_config/unified_tuning_configuration/tuning_source/tuning_source_file); timing_policy_check collected 25128. Bumped 25140 -> 25180 on 2026-07-18 for the todo-db-tracker local-SQLite spike (tests/unit/scripts/test_todo_db.py: 49 fast unit tests covering the enforced lifecycle invariants, archive import, and concurrency guards); develop collected 25128, this branch collects 25177. Bumped 25180 -> 25190 on 2026-07-18 composing the todo-db spike branch with develop's post-25128 growth: the branch alone collects 25177 (49 fast tracker tests; the 11 wrapper tests are medium-marked and collect 0 under -m fast), but the PR merge ref collects 25187.",
"max_fast_tests": 25220,
"_ceiling_note": "Bumped 13200 -> 22000 on 2026-04-13 to reflect grown fast-lane (20,184 collected). Bumped 22000 -> 22100 on 2026-05-10 for joinorder-canonical-foundation Phase-1 (data_fetch + scale-factor + surface-field tests; 22038 collected). Bumped 22100 -> 22400 on 2026-05-11 for joinorder-canonical-cutover canonical SQL/DataFrame, MCP, publishing, and corpus-inventory guards; 22317 collected. Bumped 22400 -> 22500 on 2026-05-13 for UAT enabled-platform remediation coverage; pr-preflight collected 22419. Bumped 22500 -> 22510 on 2026-05-15 after rebasing onto develop at 36ab01058; pr-preflight collected 22501 before this branch's new public theme contract tests, which are medium-marked to avoid fast-lane growth. Bumped 22510 -> 22530 on 2026-05-14 for pr-review-followup batch coverage (joinorder dataframe family, pg_mooncake adapter, landing quickstart validation, explorer read-model retry); pr-preflight collected 22520. Bumped 22530 -> 22540 on 2026-05-15 for results-explorer-post-theme-reconcile count-aware Home/script coverage; CI collected 22537. Bumped 22540 -> 22543 on 2026-05-16 for pr-review-followups coverage on UAT CLI dispatch, UAT release-gate envelope scoping, and develop-post-merge orphan ordering; ci-lint collected 22543. Bumped 22543 -> 22550 on 2026-05-16 for prompts landing Phase 1 CLI hygiene validator/prompt-shape coverage; pr-preflight collected 22550. Bumped 22550 -> 22553 on 2026-05-16 for prompts landing Phase 2 cost-class and registry prompt-safety coverage; timing policy collected 22553. Bumped 22553 -> 22558 on 2026-05-16 for prompts landing Phase 3 platform-option and TPC-DS dsdgen validator coverage; timing_policy_check collected 22558. Bumped 22558 -> 22561 on 2026-05-16 for prompts landing Phase 4 provenance template and capture-plan footer coverage; timing_policy_check collected 22561. Bumped 22561 -> 22563 on 2026-05-16 for prompts landing review fixes covering MCP cost-gating parity and compare summary discipline; targeted lane count increased by two fast tests. Bumped 22563 -> 22568 on 2026-05-16 for prompts landing Phase 5 MCP parity prompt-surface coverage; timing_policy_check collected 22568. Bumped 22568 -> 22570 on 2026-05-16 for prompts landing MCP real-tool parity and platform-option gap follow-up coverage; timing_policy_check collected 22570. Bumped 22570 -> 22572 on 2026-05-19 for pr-review-followups prompt regressions covering credential-before-smoke ordering and smoke-scale dsdgen warning behavior; timing_policy_check collected 22572. Set 22572 -> 25000 on 2026-05-19 by maintainer direction during pr-review-followups cleanup. Ratchet down if the lane contracts; never raise without justification. Bumped 25000 -> 25050 on 2026-07-16 for tuning-mode-vocabulary-and-facet-implementation-20260712 (ADR-2 canonical_mode/tuned-fallback/official-refusal coverage, the PACKAGED_RESOURCE composition pin added when merging develop's #1188 packaged-template tier, and the physical_mechanisms unknown-vs-empty ingest-pipeline regression tests); develop alone collected 24995, this branch's merge collected 25022. Bumped 25050 -> 25060 on 2026-07-17 for tuning-capability-registry-coverage-20260716 (alias-resolution invariant tests and derived generator-coverage drift guards for the 9 newly-registered platforms); CI collected 25052 on the merge ref. Bumped 25060 -> 25080 on 2026-07-17 composing #1198's registry-coverage guards with #1176's provenance/hash export coverage (test_tuning_provenance_export.py + test_requested_config_hash.py); both branches independently bumped from 25050 (#1198: 25052 collected; #1176 pre-compose: 25062 collected); composed merge tree collects 25077. Bumped 25080 -> 25140 on 2026-07-17 for tuning-from-config-forwarding-sweep-20260716 (test_tuning_config_forwarding.py's registry-driven parametrized test, one case per registered platform adapter, verifying from_config forwards tuning_enabled/tuning_config/unified_tuning_configuration/tuning_source/tuning_source_file); timing_policy_check collected 25128. Bumped 25140 -> 25180 on 2026-07-18 for the todo-db-tracker local-SQLite spike (tests/unit/scripts/test_todo_db.py: 49 fast unit tests covering the enforced lifecycle invariants, archive import, and concurrency guards); develop collected 25128, this branch collects 25177. Bumped 25180 -> 25190 on 2026-07-18 composing the todo-db spike branch with develop's post-25128 growth: the branch alone collects 25177 (49 fast tracker tests; the 11 wrapper tests are medium-marked and collect 0 under -m fast), but the PR merge ref collects 25187. Bumped 25190 -> 25220 on 2026-07-18 for the post-CODEOWNERS-retirement merge queue, composed once so the queued branches carry byte-identical policy content instead of five conflicting bumps (#1142 seed-validation exclusion coverage +10; #1116 bot-finding sweep regressions +4; #1202 capability-registry/report regressions ~+8; #1206 object-schema loader regressions ~+5; #1114 branch-rename test moves +0): develop alone collects 25187; the queue's final merge ref is expected to collect ~25214.",
"forbidden_marker_expressions": [
"resource_heavy",
"stress",
Expand Down
5 changes: 4 additions & 1 deletion _project/scripts/build_joinorder_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,10 @@
from pathlib import Path
from typing import Any

import tomllib
try:
import tomllib
except ModuleNotFoundError: # Python 3.10: stdlib tomllib is 3.11+
import tomli as tomllib # type: ignore[no-redef]

# Shared, single-source-of-truth logical-hash algorithm. The build script runs
# under the repo-root uv environment, so it imports the same primitives the
Expand Down
12 changes: 12 additions & 0 deletions benchbox/cli/commands/run_official.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,18 @@ def run_official(ctx, benchmark, platform, scale, phases, streams, seed, output_
console.print(f"[red]Error: --streams must be a non-negative integer, got: {streams}[/red]")
sys.exit(1)

# Reject an explicit --streams 1 outright rather than letting it silently
# become 2: _resolve_requested_stream_count() (benchbox/platforms/base/
# execution.py) floors any resolved value to the TPC throughput minimum
# of 2 because there is no wire-level way to distinguish "explicitly
# requested 1" from the schema's own default of 1 at that boundary. A
# user who explicitly typed --streams 1 here would otherwise see their
# own value silently overridden with no error, so catch it at the one
# place we still know it was explicit.
if streams == 1:
console.print("[red]Error: --streams must be >= 2 (TPC throughput minimum); got: 1[/red]")
sys.exit(1)

console.print("[bold blue]TPC-Compliant Official Benchmark Run[/bold blue]")
for label, value in [
("Benchmark", f"TPC-{benchmark.upper()}"),
Expand Down
110 changes: 110 additions & 0 deletions benchbox/core/tpch/generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,87 @@ def _load_generator_specs() -> dict[str, Any]:
_TPCH_TABLE_CODES = dict(_GENERATOR_SPECS["table_codes"])
_TPCH_BASE_ROW_COUNTS = dict(_GENERATOR_SPECS["base_row_counts"])

# Chunk size for the streaming trailing-delimiter rewrite (bytes).
_NORMALIZE_CHUNK_SIZE = 1 << 20


def _has_trailing_delimiter(path: Path) -> bool:
"""Probe the last bytes of ``path`` for a classic dbgen trailing delimiter.

dbgen row framing is decided at compile time (EOL_HANDLING), so every row
in a file shares the same framing; inspecting the file tail is sufficient
to classify the whole file.
"""
size = path.stat().st_size
if size == 0:
return False
with path.open("rb") as handle:
handle.seek(max(0, size - 2))
tail = handle.read()
return tail.endswith((b"|\n", b"|"))


def normalize_tbl_trailing_delimiters(path: Path) -> bool:
"""Strip exactly one trailing ``|`` before each newline, rewriting in place.

The bundled dbgen binaries are all compiled with -DEOL_HANDLING (#1069)
and emit no trailing delimiter, so this is normally an O(1) tail-probe
no-op. It remains a safety net for classic dbgen file-mode output (a
stale locally-built binary predating that CFLAGS fix, or any source
compile on a platform outside the precompiled matrix): TPCCompiler's
``needs_compilation()`` only checks whether a binary file already exists,
with no staleness/CFLAGS check against current source, so a pre-existing
``_sources/tpc-h/dbgen/dbgen`` is reused as-is and never recompiled.

Only the delimiter immediately before the line terminator is removed;
interior empty fields are preserved. In ``.tbl`` format a trailing empty
field is a delimiter artifact, not a NULL (see
:func:`benchbox.utils.file_format.get_data_extension`).

The rewrite streams fixed-size chunks through a temporary file, so large
scale-factor outputs are never loaded into memory. Files that are already
clean are detected with an O(1) tail probe and left untouched, which also
makes the operation idempotent.

Args:
path: Uncompressed ``.tbl`` (or ``.tbl.N`` chunk) file to normalize.

Returns:
True if the file was rewritten, False if it was already clean.
"""
if not _has_trailing_delimiter(path):
return False

tmp_path = path.with_name(path.name + ".normalize.tmp")
try:
with path.open("rb") as src, tmp_path.open("wb") as dst:
carry = b""
while True:
chunk = src.read(_NORMALIZE_CHUNK_SIZE)
if not chunk:
break
data = carry + chunk
# Hold back a chunk-final "|": it may be followed by "\n" in
# the next chunk and must be stripped together with it.
if data.endswith(b"|"):
carry = b"|"
data = data[:-1]
else:
carry = b""
# "\n" only occurs at row boundaries, so "|\n" is always the
# trailing delimiter; str.replace removes exactly one "|" per
# newline and never touches interior empty fields.
dst.write(data.replace(b"|\n", b"\n"))
# A held-back "|" at EOF means the final row is unterminated but
# still carries the trailing delimiter; drop it.
with contextlib.suppress(OSError):
shutil.copystat(path, tmp_path)
os.replace(tmp_path, path)
finally:
with contextlib.suppress(OSError):
tmp_path.unlink(missing_ok=True)
return True


class TPCHDataGenerator(CompressionMixin, CloudStorageGeneratorMixin, VerbosityMixin):
"""TPC-H data generator.
Expand Down Expand Up @@ -974,6 +1055,16 @@ def _finalize_generation(self, target_dir: Path) -> dict[str, Path | list[Path]]
"""
table_paths, precompressed_tables = self._gather_generated_table_paths(target_dir)

# Classic (non -z) dbgen writes a trailing field delimiter on every
# row. Normalize file-mode output before any compression or data
# organization so results match the streaming path byte-for-byte.
# The bundled binaries are clean (#1069), so this is normally an O(1)
# no-op; it remains a safety net for a stale locally-built dbgen that
# predates -DEOL_HANDLING (needs_compilation() has no staleness
# check, so an existing binary is never recompiled - see
# normalize_tbl_trailing_delimiters's docstring).
self._normalize_table_delimiters(table_paths, precompressed_tables)

# Data organization is a post-generation output mode. When configured,
# prefer organized outputs over raw compression artifacts.
if self._data_organization_config is not None:
Expand All @@ -995,6 +1086,25 @@ def _finalize_generation(self, target_dir: Path) -> dict[str, Path | list[Path]]
self._write_manifest(target_dir, table_paths)
return table_paths

def _normalize_table_delimiters(
self,
table_paths: dict[str, Path | list[Path]],
precompressed_tables: set[str],
) -> None:
"""Strip classic dbgen trailing row delimiters from uncompressed outputs.

Covers both single-file mode (``customer.tbl``) and parallel chunked
mode (``customer.tbl.1`` ... ``customer.tbl.N``). Pre-compressed
tables are skipped: they come from the streaming (-z) path or a
previous run and are already normalized.
"""
for table_name, paths in table_paths.items():
if table_name in precompressed_tables:
continue
for file_path in paths if isinstance(paths, list) else [paths]:
if normalize_tbl_trailing_delimiters(file_path):
self.log_verbose(f"Stripped trailing row delimiters from {file_path.name}")

def _apply_data_organization(
self,
target_dir: Path,
Expand Down
2 changes: 1 addition & 1 deletion benchbox/experimental/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@
- ``aiml_functions`` - AI/ML SQL function benchmarks
- ``multiregion`` - Multi-region latency and orchestration
- ``gpu`` - GPU detection, metrics, and benchmark primitives
- ``concurrency`` - Concurrent load testing patterns
- ``load_testing`` - Concurrent load testing patterns
"""
Loading
Loading