|
1 | 1 | from __future__ import annotations |
2 | 2 |
|
3 | | -import contextlib |
4 | | -import os |
5 | | -import sys |
6 | | -from pathlib import Path |
| 3 | +from dataclasses import dataclass |
7 | 4 | from typing import TYPE_CHECKING |
8 | 5 |
|
9 | 6 | import pytest |
| 7 | +from google.api_core.client_options import ClientOptions |
10 | 8 | from google.auth.credentials import AnonymousCredentials, Credentials |
11 | 9 | from google.cloud import spanner |
12 | 10 |
|
13 | | -from pytest_databases.docker import DockerServiceRegistry |
14 | | -from pytest_databases.helpers import simple_string_hash |
| 11 | +from pytest_databases.types import ServiceContainer |
15 | 12 |
|
16 | 13 | if TYPE_CHECKING: |
17 | 14 | from collections.abc import Generator |
18 | 15 |
|
| 16 | + from pytest_databases._service import DockerService |
19 | 17 |
|
20 | | -COMPOSE_PROJECT_NAME: str = f"pytest-databases-spanner-{simple_string_hash(__file__)}" |
21 | 18 |
|
| 19 | +@dataclass |
| 20 | +class SpannerService(ServiceContainer): |
| 21 | + credentials: Credentials |
22 | 22 |
|
23 | | -def spanner_responsive( |
24 | | - host: str, |
25 | | - spanner_port: int, |
26 | | - spanner_instance: str, |
27 | | - spanner_database: str, |
28 | | - spanner_project: str, |
29 | | - spanner_credentials: Credentials, |
30 | | -) -> bool: |
31 | | - try: |
32 | | - spanner_client = spanner.Client(project=spanner_project, credentials=spanner_credentials) |
33 | | - instance = spanner_client.instance(spanner_instance) |
34 | | - with contextlib.suppress(Exception): |
35 | | - instance.create() |
36 | | - |
37 | | - database = instance.database(spanner_database) |
38 | | - with contextlib.suppress(Exception): |
39 | | - database.create() |
40 | | - |
41 | | - with database.snapshot() as snapshot: |
42 | | - resp = next(iter(snapshot.execute_sql("SELECT 1"))) |
43 | | - return resp[0] == 1 |
44 | | - except Exception: # noqa: BLE001 |
45 | | - return False |
46 | | - |
| 23 | + @property |
| 24 | + def endpoint(self) -> str: |
| 25 | + return f"{self.host}:{self.port}" |
47 | 26 |
|
48 | | -@pytest.fixture(scope="session") |
49 | | -def spanner_compose_project_name() -> str: |
50 | | - return os.environ.get("COMPOSE_PROJECT_NAME", COMPOSE_PROJECT_NAME) |
| 27 | + @property |
| 28 | + def client_options(self) -> ClientOptions: |
| 29 | + return ClientOptions(api_endpoint=self.endpoint) |
51 | 30 |
|
52 | 31 |
|
53 | 32 | @pytest.fixture(autouse=False, scope="session") |
54 | | -def spanner_docker_services( |
55 | | - spanner_compose_project_name: str, worker_id: str = "main" |
56 | | -) -> Generator[DockerServiceRegistry, None, None]: |
57 | | - if os.getenv("GITHUB_ACTIONS") == "true" and sys.platform != "linux": |
58 | | - pytest.skip("Docker not available on this platform") |
59 | | - |
60 | | - with DockerServiceRegistry(worker_id, compose_project_name=spanner_compose_project_name) as registry: |
61 | | - yield registry |
62 | | - |
63 | | - |
64 | | -@pytest.fixture(scope="session") |
65 | | -def spanner_port() -> int: |
66 | | - return 9010 |
67 | | - |
68 | | - |
69 | | -@pytest.fixture(scope="session") |
70 | | -def spanner_instance() -> str: |
71 | | - return "test-instance" |
72 | | - |
73 | | - |
74 | | -@pytest.fixture(scope="session") |
75 | | -def spanner_database() -> str: |
76 | | - return "test-database" |
77 | | - |
78 | | - |
79 | | -@pytest.fixture(scope="session") |
80 | | -def spanner_project() -> str: |
81 | | - return "emulator-test-project" |
82 | | - |
83 | | - |
84 | | -@pytest.fixture(scope="session") |
85 | | -def spanner_credentials() -> Credentials: |
86 | | - return AnonymousCredentials() |
87 | | - |
88 | | - |
89 | | -@pytest.fixture(scope="session") |
90 | | -def spanner_docker_compose_files() -> list[Path]: |
91 | | - return [Path(Path(__file__).parent / "docker-compose.spanner.yml")] |
92 | | - |
93 | | - |
94 | | -@pytest.fixture(scope="session") |
95 | | -def default_spanner_service_name() -> str: |
96 | | - return "spanner" |
97 | | - |
98 | | - |
99 | | -@pytest.fixture(scope="session") |
100 | | -def spanner_docker_ip(spanner_docker_services: DockerServiceRegistry) -> str: |
101 | | - return spanner_docker_services.docker_ip |
102 | | - |
103 | | - |
104 | | -@pytest.fixture(autouse=False, scope="session") |
105 | | -def spanner_service( |
106 | | - spanner_docker_services: DockerServiceRegistry, |
107 | | - default_spanner_service_name: str, |
108 | | - spanner_docker_compose_files: list[Path], |
109 | | - spanner_docker_ip: str, |
110 | | - spanner_port: int, |
111 | | - spanner_instance: str, |
112 | | - spanner_database: str, |
113 | | - spanner_project: str, |
114 | | - spanner_credentials: Credentials, |
115 | | -) -> Generator[None, None, None]: |
116 | | - os.environ["SPANNER_EMULATOR_HOST"] = f"{spanner_docker_ip}:{spanner_port}" |
117 | | - os.environ["SPANNER_DATABASE"] = spanner_database |
118 | | - os.environ["SPANNER_INSTANCE"] = spanner_instance |
119 | | - os.environ["SPANNER_PORT"] = str(spanner_port) |
120 | | - os.environ["GOOGLE_CLOUD_PROJECT"] = spanner_project |
121 | | - spanner_docker_services.start( |
122 | | - name=default_spanner_service_name, |
123 | | - docker_compose_files=spanner_docker_compose_files, |
124 | | - timeout=60, |
125 | | - check=spanner_responsive, |
126 | | - spanner_port=spanner_port, |
127 | | - spanner_instance=spanner_instance, |
128 | | - spanner_database=spanner_database, |
129 | | - spanner_project=spanner_project, |
130 | | - spanner_credentials=spanner_credentials, |
131 | | - ) |
132 | | - yield |
| 33 | +def spanner_service(docker_service: DockerService) -> Generator[SpannerService, None, None]: |
| 34 | + with docker_service.run( |
| 35 | + image="gcr.io/cloud-spanner-emulator/emulator:latest", |
| 36 | + name="spanner", |
| 37 | + container_port=9010, |
| 38 | + wait_for_log="gRPC server listening at", |
| 39 | + ) as service: |
| 40 | + yield SpannerService( |
| 41 | + host=service.host, |
| 42 | + port=service.port, |
| 43 | + credentials=AnonymousCredentials(), |
| 44 | + ) |
133 | 45 |
|
134 | 46 |
|
135 | 47 | @pytest.fixture(autouse=False, scope="session") |
136 | 48 | def spanner_startup_connection( |
137 | | - spanner_service: DockerServiceRegistry, |
138 | | - spanner_project: str, |
139 | | - spanner_credentials: Credentials, |
| 49 | + spanner_service: SpannerService, |
140 | 50 | ) -> Generator[spanner.Client, None, None]: |
141 | | - c = spanner.Client(project=spanner_project, credentials=spanner_credentials) |
142 | | - yield c |
| 51 | + client = spanner.Client( |
| 52 | + project=spanner_service.project, |
| 53 | + credentials=spanner_service.credentials, |
| 54 | + client_options=spanner_service.client_options, |
| 55 | + ) |
| 56 | + try: |
| 57 | + yield client |
| 58 | + finally: |
| 59 | + client.close() |
0 commit comments