Skip to content
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@
_BIN_ENV_VAR = "BIGTABLE_ACCELERATOR_BIN"

# Wheels ship the binary at this path relative to the `_accelerator/` package.
# Windows wheels bundle it with a `.exe` suffix (see `_default_binary_path`).
_DEFAULT_BIN_RELATIVE_PATH = "bin/accelerator"
_WINDOWS_BIN_SUFFIX = ".exe"

# The daemon writes the principal it resolved to this file in its tempdir
# (alongside the socket) before binding, so the client can verify it matches
Expand All @@ -58,8 +60,14 @@


def _default_binary_path() -> str | None:
bundled = os.path.join(os.path.dirname(__file__), _DEFAULT_BIN_RELATIVE_PATH)
return bundled if os.path.isfile(bundled) else None
base = os.path.join(os.path.dirname(__file__), _DEFAULT_BIN_RELATIVE_PATH)
# Windows wheels bundle the daemon as `accelerator.exe`; every other
# platform ships it without a suffix.
candidates = (base + _WINDOWS_BIN_SUFFIX, base) if os.name == "nt" else (base,)
for path in candidates:
if os.path.isfile(path):
return path
return None


def _resolve_binary_path() -> str:
Expand Down
3 changes: 3 additions & 0 deletions packages/google-cloud-bigtable/noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@
"pytest-asyncio==0.21.2",
RUFF_VERSION,
"pyyaml==6.0.2",
# Used by the accelerator pre-release suite for subprocess/FD/tempdir leak
# detection (tests/system/data/accelerator/_harness.py).
"psutil",
]
SYSTEM_TEST_LOCAL_DEPENDENCIES: List[str] = []
SYSTEM_TEST_DEPENDENCIES: List[str] = []
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Pre-release integration tests for the Bigtable accelerator.

Every test in this package exercises the *real* shipped path — the real
``BigtableDataClient`` with ``use_accelerator`` set, the real
``AcceleratorDaemon`` spawning the real bundled Go binary, the real UDS
``_AcceleratorClient``, and (for correctness/stress/backend-error tests) a real
Bigtable instance. Error conditions are induced through real inputs (a bogus
daemon binary, killing the real daemon process, hitting real backend error
conditions), never by substituting a production component with a fake.

The one place a non-production binary appears is the controlled-binary factory in
``_harness`` (bad / slow daemons). Those are fed as *inputs* to the real
``AcceleratorDaemon`` to deterministically drive its start-failure and
startup-race code paths, which a healthy binary cannot exercise.
"""
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Shared base class + fixtures for the accelerator pre-release suite.

This module is written async-first and converted to a sync twin
(``_base_autogen``) by CrossSync, so async test files and their generated sync
twins can share the exact same fixtures. Everything here drives the *real*
shipped path: ``CrossSync.DataClient(..., use_accelerator=...)`` producing a real
target that spawns the real ``AcceleratorDaemon`` over the real bundled binary.

The class reuses ``SystemTestRunner`` (temporary instance/table/family creation,
stale-instance cleanup) and adds accelerator-aware client/table fixtures plus a
``janitor`` for per-test row cleanup.
"""

import os

import pytest

from google.cloud.bigtable.data._cross_sync import CrossSync
from google.cloud.bigtable.data.mutations import DeleteAllFromRow, RowMutationEntry

from . import _harness
from .. import SystemTestRunner

__CROSS_SYNC_OUTPUT__ = "tests.system.data.accelerator._base_autogen"


@CrossSync.convert_class(sync_name="AcceleratorTestBase")
class AcceleratorTestBaseAsync(SystemTestRunner):
"""Base for accelerator system tests.

Subclasses inherit the accelerator-aware fixtures below. Every table fixture
asserts that the accelerator ended up in the expected state (active for
``accel_table``, native for ``native_table``) so a silent fallback surfaces
as a test failure rather than passing on the wrong code path.
"""

@pytest.fixture(scope="session", autouse=True)
def _require_accel_env(self):
"""Cleanly skip the whole suite when the environment can't support it.

Runs before the (expensive) instance/table fixtures because it is an
autouse session fixture, so a missing binary or emulator-only setup
skips instead of erroring out mid-provisioning.
"""
_harness.require_real_bigtable_or_skip()
_harness.require_binary_or_skip()

def _make_client(self, use_accelerator=None):
"""Build a real data client with the given accelerator setting."""
project = os.getenv("GOOGLE_CLOUD_PROJECT") or None
return CrossSync.DataClient(project=project, use_accelerator=use_accelerator)

def assert_accelerator_active(self, table):
assert table._accelerator_client is not None, (
"expected the accelerator to be active (use_accelerator=True) but the "
"target fell back to native. Check ADC principal / identity "
"verification and that the bundled daemon binary can start."
)

def assert_native(self, table):
assert table._accelerator_client is None, (
"expected a native target (use_accelerator=False) but an accelerator "
"client was attached."
)

@CrossSync.convert
@CrossSync.pytest_fixture(scope="session")
async def client(self):
"""Default-on client (``use_accelerator=None``).

Also backs the ``project_id`` fixture from ``SystemTestRunner``.
"""
async with self._make_client() as client:
yield client

@CrossSync.convert
@CrossSync.pytest_fixture(scope="session")
async def accel_client(self):
async with self._make_client(use_accelerator=True) as client:
yield client

@CrossSync.convert
@CrossSync.pytest_fixture(scope="session")
async def native_client(self):
async with self._make_client(use_accelerator=False) as client:
yield client

@CrossSync.convert
@CrossSync.pytest_fixture(scope="session")
async def accel_table(self, accel_client, instance_id, table_id):
async with accel_client.get_table(instance_id, table_id) as table:
self.assert_accelerator_active(table)
yield table

@CrossSync.convert
@CrossSync.pytest_fixture(scope="session")
async def native_table(self, native_client, instance_id, table_id):
async with native_client.get_table(instance_id, table_id) as table:
self.assert_native(table)
yield table

@CrossSync.convert
@CrossSync.pytest_fixture(scope="session")
async def default_table(self, client, instance_id, table_id):
async with client.get_table(instance_id, table_id) as table:
yield table

@CrossSync.convert
@CrossSync.pytest_fixture(scope="function")
async def janitor(self, native_table):
"""Track written row keys and delete them after each test.

Deletion goes through the native path so cleanup never depends on the
component under test. Yields an object with ``.track(key)``.
"""

class _Janitor:
def __init__(self):
self.keys = set()

def track(self, key):
self.keys.add(key)
return key

j = _Janitor()
yield j
if j.keys:
entries = [RowMutationEntry(key, [DeleteAllFromRow()]) for key in j.keys]
await native_table.bulk_mutate_rows(entries)
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# This file is automatically generated by CrossSync. Do not edit manually.

"""Shared base class + fixtures for the accelerator pre-release suite.

This module is written async-first and converted to a sync twin
(``_base_autogen``) by CrossSync, so async test files and their generated sync
twins can share the exact same fixtures. Everything here drives the *real*
shipped path: ``CrossSync.DataClient(..., use_accelerator=...)`` producing a real
target that spawns the real ``AcceleratorDaemon`` over the real bundled binary.

The class reuses ``SystemTestRunner`` (temporary instance/table/family creation,
stale-instance cleanup) and adds accelerator-aware client/table fixtures plus a
``janitor`` for per-test row cleanup.
"""

import os

import pytest

from google.cloud.bigtable.data._cross_sync import CrossSync
from google.cloud.bigtable.data.mutations import DeleteAllFromRow, RowMutationEntry

from .. import SystemTestRunner
from . import _harness


class AcceleratorTestBase(SystemTestRunner):
"""Base for accelerator system tests.

Subclasses inherit the accelerator-aware fixtures below. Every table fixture
asserts that the accelerator ended up in the expected state (active for
``accel_table``, native for ``native_table``) so a silent fallback surfaces
as a test failure rather than passing on the wrong code path.
"""

@pytest.fixture(scope="session", autouse=True)
def _require_accel_env(self):
"""Cleanly skip the whole suite when the environment can't support it.

Runs before the (expensive) instance/table fixtures because it is an
autouse session fixture, so a missing binary or emulator-only setup
skips instead of erroring out mid-provisioning."""
_harness.require_real_bigtable_or_skip()
_harness.require_binary_or_skip()

def _make_client(self, use_accelerator=None):
"""Build a real data client with the given accelerator setting."""
project = os.getenv("GOOGLE_CLOUD_PROJECT") or None
return CrossSync._Sync_Impl.DataClient(
project=project, use_accelerator=use_accelerator
)

def assert_accelerator_active(self, table):
assert table._accelerator_client is not None, (
"expected the accelerator to be active (use_accelerator=True) but the target fell back to native. Check ADC principal / identity verification and that the bundled daemon binary can start."
)

def assert_native(self, table):
assert table._accelerator_client is None, (
"expected a native target (use_accelerator=False) but an accelerator client was attached."
)

@pytest.fixture(scope="session")
def client(self):
"""Default-on client (``use_accelerator=None``).

Also backs the ``project_id`` fixture from ``SystemTestRunner``."""
with self._make_client() as client:
yield client

@pytest.fixture(scope="session")
def accel_client(self):
with self._make_client(use_accelerator=True) as client:
yield client

@pytest.fixture(scope="session")
def native_client(self):
with self._make_client(use_accelerator=False) as client:
yield client

@pytest.fixture(scope="session")
def accel_table(self, accel_client, instance_id, table_id):
with accel_client.get_table(instance_id, table_id) as table:
self.assert_accelerator_active(table)
yield table

@pytest.fixture(scope="session")
def native_table(self, native_client, instance_id, table_id):
with native_client.get_table(instance_id, table_id) as table:
self.assert_native(table)
yield table

@pytest.fixture(scope="session")
def default_table(self, client, instance_id, table_id):
with client.get_table(instance_id, table_id) as table:
yield table

@pytest.fixture(scope="function")
def janitor(self, native_table):
"""Track written row keys and delete them after each test.

Deletion goes through the native path so cleanup never depends on the
component under test. Yields an object with ``.track(key)``."""

class _Janitor:
def __init__(self):
self.keys = set()

def track(self, key):
self.keys.add(key)
return key

j = _Janitor()
yield j
if j.keys:
entries = [RowMutationEntry(key, [DeleteAllFromRow()]) for key in j.keys]
native_table.bulk_mutate_rows(entries)
Loading
Loading