Documentation
¶
Overview ¶
Package spanemuboost starts Cloud Spanner Emulator and experimental Spanner Omni runtimes for tests, then bootstraps instances, databases, schema, and Spanner clients.
The emulator path is the default and stable path. Use SetupEmulatorWithClients for simple tests. For many independent cases against either backend, share one runtime with NewLazyRuntime and create one database per case with WithRandomDatabaseID.
The Omni path uses BackendOmni through the backend-neutral APIs such as Setup, Run, SetupWithClients, RunWithClients, OpenClients, SetupClients, and NewLazyRuntime. Omni support is experimental. The shared-runtime pattern is especially important for Omni because each started Omni runtime owns one Spanner Omni container.
Index ¶
- Constants
- func EndpointConfigured() bool
- func EndpointConfiguredForBackend(backend Backend) bool
- func NewEmulator(ctx context.Context, options ...Option) (emulator *tcspanner.Container, teardown func(), err error)deprecated
- func RecommendedOmniClientConfig() spanner.ClientConfig
- func RuntimePlatform(ctx context.Context, runtime RuntimeHandle) (string, error)
- func SaveEndpoint(path string, endpoint Endpoint) error
- func Serve(ctx context.Context, backend Backend, endpointPath string, options ...Option) error
- func ServeFromConfig(ctx context.Context, cfg ServeConfig) error
- func StopFromConfig(ctx context.Context, cfg StopConfig) error
- type AttachedRuntime
- func (a *AttachedRuntime) ClientOptions() []option.ClientOption
- func (a *AttachedRuntime) Close() error
- func (a *AttachedRuntime) DatabaseID() string
- func (a *AttachedRuntime) DatabasePath() string
- func (a *AttachedRuntime) InstanceID() string
- func (a *AttachedRuntime) InstancePath() string
- func (a *AttachedRuntime) ProjectID() string
- func (a *AttachedRuntime) ProjectPath() string
- func (a *AttachedRuntime) URI() string
- type Backend
- type Clients
- func NewClients(ctx context.Context, emulator *tcspanner.Container, options ...Option) (clients *Clients, teardown func(), err error)deprecated
- func NewEmulatorWithClients(ctx context.Context, options ...Option) (emulator *tcspanner.Container, clients *Clients, teardown func(), err error)deprecated
- func OpenClients(ctx context.Context, runtime RuntimeHandle, options ...Option) (*Clients, error)
- func SetupClients(tb testing.TB, runtime RuntimeHandle, options ...Option) *Clients
- type Emulator
- func (e *Emulator) ClientOptions() []option.ClientOption
- func (e *Emulator) Close() error
- func (e *Emulator) Container() *tcspanner.Container
- func (e *Emulator) DatabaseID() string
- func (e *Emulator) DatabasePath() string
- func (e *Emulator) InstanceID() string
- func (e *Emulator) InstancePath() string
- func (e *Emulator) ProjectID() string
- func (e *Emulator) ProjectPath() string
- func (e *Emulator) TestMain(m *testing.M)
- func (e *Emulator) URI() string
- type Endpoint
- type Env
- type LazyEmulator
- type LazyRuntime
- type Option
- func DisableAutoConfig() Option
- func DisableBackendGuardrails() Option
- func DisableQueryNullFilteredIndexCheck() Option
- func EnableAutoConfig() Option
- func EnableDatabaseAutoConfigOnly() Option
- func EnableEmulatorStdoutCopy() Option
- func EnableFaultInjection() Option
- func EnableInstanceAutoConfigOnly() Option
- func EnableLogRequests() Option
- func ForceSchemaTeardown() Option
- func SkipSchemaTeardown() Option
- func WithChangeStreamPartitionTokenAliveSeconds(seconds int) Option
- func WithClientConfig(config spanner.ClientConfig) Option
- func WithClientOptionsForClient(option ...option.ClientOption) Option
- func WithContainerCustomizers(containerCustomizers ...testcontainers.ContainerCustomizer) Option
- func WithContainerImage(image string) Option
- func WithContainerProvider(provider testcontainers.ProviderType) Option
- func WithDatabaseDialect(dialect databasepb.DatabaseDialect) Option
- func WithDatabaseID(databaseID string) Option
- func WithEmulatorImage(image string) Optiondeprecated
- func WithInstanceID(instanceID string) Option
- func WithMaxDatabasesPerInstance(n int) Option
- func WithProjectID(projectID string) Option
- func WithRandomDatabaseID() Option
- func WithRandomInstanceID() Option
- func WithRandomProjectID() Option
- func WithSetupDDLs(ddls []string) Option
- func WithSetupDMLs(dmls []spanner.Statement) Option
- func WithSetupRawDMLs(rawDMLs []string) Option
- func WithStrictTeardown() Optiondeprecated
- func WithoutRandomDatabaseID() Option
- func WithoutRandomInstanceID() Option
- func WithoutRandomProjectID() Option
- type Runtime
- type RuntimeEnv
- type RuntimeHandle
- type ServeConfig
- type StopConfig
Examples ¶
Constants ¶
const ( DefaultEmulatorImage = "gcr.io/cloud-spanner-emulator/emulator:1.5.54" DefaultProjectID = "emulator-project" DefaultInstanceID = "emulator-instance" DefaultDatabaseID = "emulator-database" )
Variables ¶
This section is empty.
Functions ¶
func EndpointConfigured ¶ added in v0.4.4
func EndpointConfigured() bool
EndpointConfigured reports whether external endpoint env vars are set in the current process environment. It does not validate that LoadEndpoint succeeds.
func EndpointConfiguredForBackend ¶ added in v0.4.4
EndpointConfiguredForBackend reports whether an endpoint for the requested backend is configured. Unlike EndpointConfigured, this ignores unrelated backend URI env vars so callers can distinguish Omni from emulator endpoints.
func NewEmulator
deprecated
func NewEmulator(ctx context.Context, options ...Option) (emulator *tcspanner.Container, teardown func(), err error)
Deprecated: Use SetupEmulator (for tests) or RunEmulator instead.
NewEmulator initializes Cloud Spanner Emulator. The emulator will be closed when teardown is called. You should call it.
func RecommendedOmniClientConfig ¶ added in v0.4.0
func RecommendedOmniClientConfig() spanner.ClientConfig
RecommendedOmniClientConfig returns the recommended spanner.ClientConfig for a Go Spanner data client connecting to the experimental Omni backend. The helper remains part of the backend-neutral API surface, but its Omni-specific recommendations may evolve before v1.
Example (ExternalClient) ¶
package main
import (
"context"
"log"
"os"
"cloud.google.com/go/spanner"
"github.com/apstndb/spanemuboost"
)
func main() {
if os.Getenv("SPANEMUBOOST_ENABLE_OMNI_TESTS") != "1" {
return
}
ctx := context.Background()
runtime, err := spanemuboost.Run(ctx, spanemuboost.BackendOmni,
spanemuboost.WithRandomDatabaseID(),
)
if err != nil {
log.Printf("failed to start Omni runtime: %v", err)
return
}
defer runtime.Close() //nolint:errcheck
client, err := spanner.NewClientWithConfig(
ctx,
runtime.DatabasePath(),
spanemuboost.RecommendedOmniClientConfig(),
runtime.ClientOptions()...,
)
if err != nil {
log.Printf("failed to create Omni client: %v", err)
return
}
defer client.Close()
_ = client
}
Output:
func RuntimePlatform ¶ added in v0.4.0
func RuntimePlatform(ctx context.Context, runtime RuntimeHandle) (string, error)
RuntimePlatform returns the actual resolved container platform (for example, "linux/amd64", "linux/arm64", or, when available, a variant-qualified value such as "linux/arm64/v8") for a package-provided runtime handle. When the underlying runtime only exposes partial metadata, it may return an OS-only value such as "linux". AttachedRuntime values return "attached" because they do not own a container.
It accepts the same started and lazy handles as OpenClients and SetupClients, and resolves lazy handles by starting them on first use.
func SaveEndpoint ¶ added in v0.4.4
SaveEndpoint writes endpoint metadata as JSON with mode 0600.
func Serve ¶ added in v0.4.4
Serve starts a backend runtime, writes its Endpoint metadata when endpointPath is non-empty, and blocks until ctx is canceled. The runtime is closed before Serve returns. When an endpoint file was written, it is removed on exit so stale metadata is not left behind.
func ServeFromConfig ¶ added in v0.4.4
func ServeFromConfig(ctx context.Context, cfg ServeConfig) error
ServeFromConfig starts a backend and blocks until interrupted.
func StopFromConfig ¶ added in v0.4.4
func StopFromConfig(ctx context.Context, cfg StopConfig) error
StopFromConfig sends SIGTERM to a spanemuboost serve process and waits for it to exit. The process is identified by StopConfig.PIDFile or lifecycle metadata in StopConfig.EndpointFile. Remote or manually started endpoints without a PID cannot be stopped through this API.
Types ¶
type AttachedRuntime ¶ added in v0.4.4
type AttachedRuntime struct {
// contains filtered or unexported fields
}
AttachedRuntime is a Runtime connected to an already-running backend. AttachedRuntime.Close does not stop the remote process or container.
func NewAttachedRuntime ¶ added in v0.4.4
func NewAttachedRuntime(endpoint Endpoint, options ...Option) (*AttachedRuntime, error)
NewAttachedRuntime connects to endpoint without starting a container.
func NewAttachedRuntimeFromEnv ¶ added in v0.4.4
func NewAttachedRuntimeFromEnv(options ...Option) (*AttachedRuntime, error)
NewAttachedRuntimeFromEnv is a convenience wrapper around LoadEndpoint and NewAttachedRuntime.
func (*AttachedRuntime) ClientOptions ¶ added in v0.4.4
func (a *AttachedRuntime) ClientOptions() []option.ClientOption
ClientOptions returns transport options for the attached backend. Options passed via WithClientOptionsForClient are applied in OpenClients and SetupClients, not here.
func (*AttachedRuntime) Close ¶ added in v0.4.4
func (a *AttachedRuntime) Close() error
Close is a no-op for attached runtimes because this handle does not own the remote backend lifecycle.
func (*AttachedRuntime) DatabaseID ¶ added in v0.4.4
func (a *AttachedRuntime) DatabaseID() string
func (*AttachedRuntime) DatabasePath ¶ added in v0.4.4
func (a *AttachedRuntime) DatabasePath() string
func (*AttachedRuntime) InstanceID ¶ added in v0.4.4
func (a *AttachedRuntime) InstanceID() string
func (*AttachedRuntime) InstancePath ¶ added in v0.4.4
func (a *AttachedRuntime) InstancePath() string
func (*AttachedRuntime) ProjectID ¶ added in v0.4.4
func (a *AttachedRuntime) ProjectID() string
func (*AttachedRuntime) ProjectPath ¶ added in v0.4.4
func (a *AttachedRuntime) ProjectPath() string
func (*AttachedRuntime) URI ¶ added in v0.4.4
func (a *AttachedRuntime) URI() string
type Backend ¶ added in v0.4.0
type Backend string
Backend identifies the runtime implementation to start. Callers should use the exported Backend* constants; other values are rejected.
const ( // BackendEmulator starts the Cloud Spanner Emulator backend. BackendEmulator Backend = "emulator" // BackendOmni starts the experimental Spanner Omni backend. // Backend-specific behavior for Omni may change before v1. // // Each started Omni runtime owns one Spanner Omni container. Plan for roughly // 4 GiB of memory per concurrently running Omni container, and keep tests // that start Omni runtimes serial unless the host has enough spare memory. // spanemuboost does not serialize Omni runtime lifetimes globally because // such a lock would be process-local and surprising for callers that // intentionally provision multiple independent runtimes. // // Use [RecommendedOmniClientConfig] for external Go clients. BackendOmni Backend = "omni" )
type Clients ¶
type Clients struct {
// InstanceClient enables instance-level administrative operations.
// For Omni, instance lifecycle operations are backend-limited and may fail.
InstanceClient *instance.InstanceAdminClient
DatabaseClient *database.DatabaseAdminClient
Client *spanner.Client
ProjectID, InstanceID, DatabaseID string
// contains filtered or unexported fields
}
Clients holds Spanner clients and manages the lifecycle of schema resources (instances and databases) auto-created during bootstrap.
By default, auto-created resources with fixed IDs are dropped on Clients.Close, while resources with random IDs are not (since they never collide). Use ForceSchemaTeardown or SkipSchemaTeardown to override.
For RunEmulatorWithClients/SetupEmulatorWithClients, teardown is disabled because the emulator container owns the resource lifecycle; use ForceSchemaTeardown to override.
func NewClients
deprecated
added in
v0.2.0
func NewClients(ctx context.Context, emulator *tcspanner.Container, options ...Option) (clients *Clients, teardown func(), err error)
Deprecated: Use SetupClients (for tests) or OpenClients instead.
NewClients setup existing Cloud Spanner Emulator with Spanner clients. The clients will be closed when teardown is called. You should call it.
Example ¶
package main
import (
"context"
"fmt"
"log"
"cloud.google.com/go/spanner"
"github.com/apstndb/spanemuboost"
)
func main() {
ctx := context.Background()
emulator, emulatorTeardown, err := spanemuboost.NewEmulator(ctx,
spanemuboost.EnableInstanceAutoConfigOnly(),
)
if err != nil {
log.Fatalln(err)
return
}
defer emulatorTeardown()
var pks []int64
for i := range 10 {
func() {
clients, clientsTeardown, err := spanemuboost.NewClients(ctx, emulator,
spanemuboost.EnableDatabaseAutoConfigOnly(),
spanemuboost.WithRandomDatabaseID(),
spanemuboost.WithSetupDDLs([]string{"CREATE TABLE tbl (PK INT64 PRIMARY KEY)"}),
spanemuboost.WithSetupDMLs([]spanner.Statement{
{SQL: "INSERT INTO tbl(PK) VALUES(@i)", Params: map[string]any{"i": i}},
}),
)
if err != nil {
log.Fatalln(err)
return
}
defer clientsTeardown()
err = clients.Client.Single().Query(ctx, spanner.NewStatement("SELECT PK FROM tbl")).Do(func(r *spanner.Row) error {
var pk int64
if err := r.ColumnByName("PK", &pk); err != nil {
return err
}
pks = append(pks, pk)
return nil
})
if err != nil {
log.Fatalln(err)
}
}()
}
fmt.Println(pks)
}
Output: [0 1 2 3 4 5 6 7 8 9]
func NewEmulatorWithClients
deprecated
func NewEmulatorWithClients(ctx context.Context, options ...Option) (emulator *tcspanner.Container, clients *Clients, teardown func(), err error)
Deprecated: Use SetupEmulatorWithClients (for tests) or RunEmulatorWithClients instead.
NewEmulatorWithClients initializes Cloud Spanner Emulator with Spanner clients. The emulator and clients will be closed when teardown is called. You should call it.
Example ¶
package main
import (
"context"
"fmt"
"log"
"cloud.google.com/go/spanner"
"github.com/apstndb/spanemuboost"
)
func main() {
ctx := context.Background()
_, clients, teardown, err := spanemuboost.NewEmulatorWithClients(ctx)
if err != nil {
log.Fatalln(err)
return
}
defer teardown()
err = clients.Client.Single().Query(ctx, spanner.NewStatement("SELECT 1")).Do(func(r *spanner.Row) error {
fmt.Println(r)
return nil
})
if err != nil {
log.Fatalln(err)
}
}
Output: {fields: [type:{code:INT64}], values: [string_value:"1"]}
func OpenClients ¶ added in v0.3.0
OpenClients connects to an existing RuntimeHandle and opens Spanner clients. Supported handles are *Emulator, *LazyRuntime, *LazyEmulator, and the Runtime returned by Run or Setup. When a lazy runtime is passed, it is started automatically on first use. The parameter type is intentionally limited to package-provided runtime values so callers can use lazy runtime handles without adding another startup method to the public Runtime interface. Options inherit the runtime's projectID, instanceID, and databaseID. When reopening against an existing runtime, automatic create and teardown behavior is disabled by default, so clients target the existing instance and database unless explicitly overridden where supported (for example via EnableAutoConfig). Call Clients.Close to close the clients when done. In tests, prefer SetupClients which handles cleanup automatically.
Example ¶
package main
import (
"context"
"fmt"
"log"
"cloud.google.com/go/spanner"
"github.com/apstndb/spanemuboost"
)
func main() {
ctx := context.Background()
emu, err := spanemuboost.RunEmulator(ctx,
spanemuboost.EnableInstanceAutoConfigOnly(),
)
if err != nil {
log.Fatalln(err)
return
}
defer emu.Close() //nolint:errcheck
var pks []int64
for i := range 10 {
func() {
clients, err := spanemuboost.OpenClients(ctx, emu,
spanemuboost.WithRandomDatabaseID(),
spanemuboost.WithSetupDDLs([]string{"CREATE TABLE tbl (PK INT64 PRIMARY KEY)"}),
spanemuboost.WithSetupDMLs([]spanner.Statement{
{SQL: "INSERT INTO tbl(PK) VALUES(@i)", Params: map[string]any{"i": i}},
}),
)
if err != nil {
log.Fatalln(err)
return
}
defer clients.Close() //nolint:errcheck
err = clients.Client.Single().Query(ctx, spanner.NewStatement("SELECT PK FROM tbl")).Do(func(r *spanner.Row) error {
var pk int64
if err := r.ColumnByName("PK", &pk); err != nil {
return err
}
pks = append(pks, pk)
return nil
})
if err != nil {
log.Fatalln(err)
}
}()
}
fmt.Println(pks)
}
Output: [0 1 2 3 4 5 6 7 8 9]
func SetupClients ¶ added in v0.3.0
func SetupClients(tb testing.TB, runtime RuntimeHandle, options ...Option) *Clients
SetupClients opens Spanner clients against an existing RuntimeHandle and registers cleanup via testing.TB.Cleanup. It calls testing.TB.Fatal on setup error. Supported handles are *Emulator, *LazyRuntime, *LazyEmulator, and the Runtime returned by Run or Setup. When a lazy runtime is passed, it is started automatically on first use. The parameter type is intentionally limited to package-provided runtime values so callers can use lazy runtime handles without adding another startup method to the public Runtime interface. Options inherit the runtime's projectID, instanceID, and databaseID. Use OpenClients if you need a context.Context or are not in a test.
func (*Clients) ClientOptions ¶ added in v0.3.3
func (c *Clients) ClientOptions() []option.ClientOption
ClientOptions returns the option.ClientOption values used to connect to the emulator. This is useful when callers need to create additional gRPC clients (e.g., with custom interceptors) against the same emulator without holding a separate *Emulator reference.
func (*Clients) Close ¶ added in v0.3.0
Close closes all Spanner clients. By default, auto-created resources with fixed IDs are dropped during Close after the data client is closed and before the admin clients are closed. See ForceSchemaTeardown and SkipSchemaTeardown. spanner.Client.Close does not return an error, so only admin client and resource cleanup errors are returned. Close is nil-safe and idempotent. After the first call, subsequent calls return the result of that first call.
func (*Clients) DatabasePath ¶ added in v0.2.2
func (*Clients) InstancePath ¶ added in v0.2.2
func (*Clients) ProjectPath ¶ added in v0.2.2
type Emulator ¶ added in v0.3.0
type Emulator struct {
// contains filtered or unexported fields
}
Emulator wraps a Cloud Spanner Emulator container. Use RunEmulator or SetupEmulator to create one.
func RunEmulator ¶ added in v0.3.0
RunEmulator starts a Cloud Spanner Emulator container and performs any configured bootstrap (instance/database creation, DDL, DML). Call Emulator.Close to terminate the container when done. In tests, prefer SetupEmulator which handles cleanup automatically. In TestMain, use this function since testing.M does not implement testing.TB.
func SetupEmulator ¶ added in v0.3.0
SetupEmulator starts a Cloud Spanner Emulator and registers cleanup via testing.TB.Cleanup. It calls testing.TB.Fatal on setup error. Use RunEmulator if you need a context.Context or are not in a test. Note that testing.M does not implement testing.TB, so use RunEmulator in TestMain.
func (*Emulator) ClientOptions ¶ added in v0.3.0
func (e *Emulator) ClientOptions() []option.ClientOption
ClientOptions returns option.ClientOption values configured for connecting to this emulator (endpoint, insecure credentials, no authentication).
The endpoint uses the passthrough:/// scheme to bypass gRPC name resolution and avoid the slow authentication code path that would otherwise be triggered when grpc.NewClient (dns resolver by default) is used by the auth layer. This mirrors the approach used by the Spanner client library's SPANNER_EMULATOR_HOST handling (googleapis/google-cloud-go#10947), as well as the Bigtable and Datastore SDKs for their emulator paths.
Currently the auth layer uses grpc.DialContext (passthrough by default), so this is a defensive measure for the planned migration to grpc.NewClient.
func (*Emulator) Close ¶ added in v0.3.0
Close terminates the emulator container. Close is nil-safe and idempotent. After the first call, subsequent calls return the result of that first call.
func (*Emulator) Container ¶ added in v0.3.0
Container returns the underlying *tcspanner.Container for direct access. Most users should use Emulator.URI or Emulator.ClientOptions instead.
func (*Emulator) DatabaseID ¶ added in v0.3.0
DatabaseID returns the database ID configured for this emulator.
func (*Emulator) DatabasePath ¶ added in v0.3.0
DatabasePath returns the database resource path.
func (*Emulator) InstanceID ¶ added in v0.3.0
InstanceID returns the instance ID configured for this emulator.
func (*Emulator) InstancePath ¶ added in v0.3.0
InstancePath returns the instance resource path.
func (*Emulator) ProjectID ¶ added in v0.3.0
ProjectID returns the project ID configured for this emulator.
func (*Emulator) ProjectPath ¶ added in v0.3.0
ProjectPath returns the project resource path.
func (*Emulator) TestMain ¶ added in v0.3.4
TestMain runs m.Run(), closes the emulator, and calls os.Exit with the appropriate code. A close failure is logged and causes a non-zero exit code.
Because TestMain calls os.Exit, it must be the last statement in your TestMain function. If you need additional cleanup, refer to the source of this method and write the logic manually.
Usage in TestMain:
func TestMain(m *testing.M) {
var err error
emulator, err = spanemuboost.RunEmulator(context.Background(),
spanemuboost.EnableInstanceAutoConfigOnly(),
)
if err != nil { log.Fatal(err) }
emulator.TestMain(m)
}
func (*Emulator) URI ¶ added in v0.3.0
URI returns the gRPC endpoint (host:port) of the emulator, suitable for use as SPANNER_EMULATOR_HOST.
In serial tests, you can use testing.T.Setenv to set the environment variable:
t.Setenv("SPANNER_EMULATOR_HOST", emu.URI())
Note that testing.T.Setenv panics if the test or an ancestor has called testing.T.Parallel. Prefer Emulator.ClientOptions when possible.
type Endpoint ¶ added in v0.4.4
type Endpoint struct {
Backend Backend `json:"backend"`
URI string `json:"uri"`
ProjectID string `json:"project_id"`
InstanceID string `json:"instance_id"`
// Lifecycle metadata is populated by spanemuboost serve and used by stop.
ManagedBy string `json:"managed_by,omitempty"`
PID int `json:"pid,omitempty"`
StartedAt string `json:"started_at,omitempty"`
}
Endpoint describes a running spanemuboost-compatible Spanner backend that clients can attach to without starting a new container.
func EndpointFromRuntime ¶ added in v0.4.4
EndpointFromRuntime builds an Endpoint from a started Runtime.
func LoadEndpoint ¶ added in v0.4.4
LoadEndpoint reads connection metadata from SPANEMUBOOST_ENDPOINT_FILE or backend-specific URI env vars.
When SPANEMUBOOST_ENDPOINT_FILE is set, the JSON file takes precedence. Otherwise Omni is selected when SPANEMUBOOST_OMNI_URI is set, and the emulator path is selected when SPANEMUBOOST_EMULATOR_URI is set.
func LoadEndpointForBackend ¶ added in v0.4.4
LoadEndpointForBackend reads endpoint metadata for the requested backend. When SPANEMUBOOST_ENDPOINT_FILE is set, the file backend must match. Otherwise only the URI env var for the requested backend is considered.
func ReadEndpointFile ¶ added in v0.4.4
ReadEndpointFile loads an Endpoint from a JSON file written by SaveEndpoint or `spanemuboost serve`.
type Env ¶ added in v0.3.0
type Env struct {
*Clients
// contains filtered or unexported fields
}
Env combines an Emulator with Clients for the single-call use case. Use RunEmulatorWithClients or SetupEmulatorWithClients to create one.
func RunEmulatorWithClients ¶ added in v0.3.0
RunEmulatorWithClients starts a Cloud Spanner Emulator and opens Spanner clients. Call Env.Close to close clients and terminate the container. In tests, prefer SetupEmulatorWithClients which handles cleanup automatically.
Example ¶
package main
import (
"context"
"fmt"
"log"
"cloud.google.com/go/spanner"
"github.com/apstndb/spanemuboost"
)
func main() {
ctx := context.Background()
env, err := spanemuboost.RunEmulatorWithClients(ctx)
if err != nil {
log.Fatalln(err)
return
}
defer env.Close() //nolint:errcheck
err = env.Client.Single().Query(ctx, spanner.NewStatement("SELECT 1")).Do(func(r *spanner.Row) error {
fmt.Println(r)
return nil
})
if err != nil {
log.Fatalln(err)
}
}
Output: {fields: [type:{code:INT64}], values: [string_value:"1"]}
func SetupEmulatorWithClients ¶ added in v0.3.0
SetupEmulatorWithClients starts a Cloud Spanner Emulator with clients and registers cleanup via testing.TB.Cleanup. It calls testing.TB.Fatal on setup error. Use RunEmulatorWithClients if you need a context.Context or are not in a test.
type LazyEmulator ¶ added in v0.3.3
type LazyEmulator struct {
// contains filtered or unexported fields
}
LazyEmulator defers emulator startup until first use. Use NewLazyEmulator in a package-level var, then pass directly to SetupClients or OpenClients. Call LazyEmulator.Setup or LazyEmulator.Get for standalone access. LazyEmulator.Close is safe to call even if the emulator was never started (no-op).
Example ¶
package main
import (
"context"
"fmt"
"log"
"cloud.google.com/go/spanner"
"github.com/apstndb/spanemuboost"
)
func main() {
ctx := context.Background()
lazy := spanemuboost.NewLazyEmulator(
spanemuboost.EnableInstanceAutoConfigOnly(),
)
defer func() {
if err := lazy.Close(); err != nil {
log.Printf("failed to close lazy emulator: %v", err)
}
}()
// OpenClients accepts a *LazyEmulator directly and starts it on first use.
clients, err := spanemuboost.OpenClients(ctx, lazy,
spanemuboost.WithRandomDatabaseID(),
)
if err != nil {
log.Fatalln(err)
return
}
defer func() {
if err := clients.Close(); err != nil {
log.Printf("failed to close clients: %v", err)
}
}()
err = clients.Client.Single().Query(ctx, spanner.NewStatement("SELECT 1")).Do(func(r *spanner.Row) error {
fmt.Println(r)
return nil
})
if err != nil {
log.Fatalln(err)
}
}
Output: {fields: [type:{code:INT64}], values: [string_value:"1"]}
func NewLazyEmulator ¶ added in v0.3.3
func NewLazyEmulator(options ...Option) *LazyEmulator
NewLazyEmulator creates a LazyEmulator that will start an emulator with the given options on first use. The emulator is not started until it is passed to SetupClients, OpenClients, or until LazyEmulator.Setup / LazyEmulator.Get is called directly.
func (*LazyEmulator) Close ¶ added in v0.3.3
func (le *LazyEmulator) Close() error
Close terminates the emulator if it was started. No-op otherwise. Close is nil-safe and idempotent — subsequent calls return the result of the first call. Close waits for any in-progress initialization to complete before checking. If Close is called before any Get or Setup, the emulator will never be started.
func (*LazyEmulator) Get ¶ added in v0.3.3
func (le *LazyEmulator) Get(ctx context.Context) (*Emulator, error)
Get starts the emulator on first call (thread-safe via sync.Once) and returns the cached *Emulator on subsequent calls.
func (*LazyEmulator) Setup ¶ added in v0.3.3
func (le *LazyEmulator) Setup(tb testing.TB) *Emulator
Setup starts the emulator on first call (thread-safe via sync.Once) and returns the cached *Emulator on subsequent calls. It calls testing.TB.Fatal if startup fails. For use with SetupClients or OpenClients, you can pass *LazyEmulator directly without calling Setup.
func (*LazyEmulator) TestMain ¶ added in v0.3.4
func (le *LazyEmulator) TestMain(m *testing.M)
TestMain runs m.Run(), closes the lazy emulator, and calls os.Exit with the appropriate code. A close failure is logged and causes a non-zero exit code. If the emulator was never started, Close is a no-op.
Because TestMain calls os.Exit, it must be the last statement in your TestMain function. If you need additional cleanup, refer to the source of this method and write the logic manually.
Usage in TestMain:
var lazyEmu = spanemuboost.NewLazyEmulator(spanemuboost.EnableInstanceAutoConfigOnly())
func TestMain(m *testing.M) { lazyEmu.TestMain(m) }
type LazyRuntime ¶ added in v0.4.0
type LazyRuntime struct {
// contains filtered or unexported fields
}
LazyRuntime defers startup of the selected backend until first use. Use NewLazyRuntime in a package-level var, then pass it directly to SetupClients or OpenClients. Call LazyRuntime.Setup or LazyRuntime.Get for standalone access, and pair it with LazyRuntime.Close or LazyRuntime.TestMain when you need the lazy handle to own lifecycle cleanup. LazyRuntime.Close is safe to call even if the runtime was never started (no-op).
Example ¶
package main
import (
"context"
"fmt"
"log"
"cloud.google.com/go/spanner"
"github.com/apstndb/spanemuboost"
)
func main() {
ctx := context.Background()
lazy := spanemuboost.NewLazyRuntime(
spanemuboost.BackendEmulator,
spanemuboost.EnableInstanceAutoConfigOnly(),
)
defer func() {
if err := lazy.Close(); err != nil {
log.Printf("failed to close lazy runtime: %v", err)
}
}()
// OpenClients accepts a *LazyRuntime directly and starts it on first use.
clients, err := spanemuboost.OpenClients(ctx, lazy,
spanemuboost.WithRandomDatabaseID(),
)
if err != nil {
log.Fatalln(err)
return
}
defer func() {
if err := clients.Close(); err != nil {
log.Printf("failed to close clients: %v", err)
}
}()
err = clients.Client.Single().Query(ctx, spanner.NewStatement("SELECT 1")).Do(func(r *spanner.Row) error {
fmt.Println(r)
return nil
})
if err != nil {
log.Fatalln(err)
}
}
Output: {fields: [type:{code:INT64}], values: [string_value:"1"]}
func NewLazyRuntime ¶ added in v0.4.0
func NewLazyRuntime(backend Backend, options ...Option) *LazyRuntime
NewLazyRuntime creates a LazyRuntime that will start the selected backend with the given options on first use.
Example (ValidationCases) ¶
package main
import (
"context"
"log"
"cloud.google.com/go/spanner"
"cloud.google.com/go/spanner/admin/database/apiv1/databasepb"
"github.com/apstndb/spanemuboost"
)
func main() {
ctx := context.Background()
// LazyRuntime shares one backend container across all cases. The same
// database-per-case loop works with BackendOmni; omit
// EnableInstanceAutoConfigOnly there because Omni uses its built-in instance.
lazy := spanemuboost.NewLazyRuntime(
spanemuboost.BackendEmulator,
spanemuboost.EnableInstanceAutoConfigOnly(),
)
defer func() {
if err := lazy.Close(); err != nil {
log.Printf("failed to close runtime: %v", err)
}
}()
baseDDLs := []string{
"CREATE TABLE Singers (SingerId INT64, Name STRING(MAX)) PRIMARY KEY (SingerId)",
}
baseDMLs := []string{
"INSERT INTO Singers (SingerId, Name) VALUES (1, 'Marc Richards')",
}
cases := []struct {
name string
setupDDLs []string
ddl string
dml spanner.Statement
query string
}{
{
name: "ddl",
ddl: "CREATE TABLE Albums (SingerId INT64, AlbumId INT64) PRIMARY KEY (SingerId, AlbumId)",
},
{
name: "dml",
setupDDLs: []string{
"CREATE TABLE Albums (SingerId INT64, AlbumId INT64, Title STRING(MAX)) PRIMARY KEY (SingerId, AlbumId)",
},
dml: spanner.NewStatement("INSERT INTO Albums (SingerId, AlbumId, Title) VALUES (1, 1, 'Total Junk')"),
},
{
name: "query",
query: "SELECT Name FROM Singers WHERE SingerId = 1",
},
}
for _, tc := range cases {
// Setup options replace previous values, so compose shared setup and
// case-specific setup before calling WithSetupDDLs.
setupDDLs := append([]string{}, baseDDLs...)
setupDDLs = append(setupDDLs, tc.setupDDLs...)
// WithRandomDatabaseID isolates cases without starting another backend
// container. For Omni, random project and instance IDs are not the
// normal isolation mechanism.
clients, err := spanemuboost.OpenClients(ctx, lazy,
spanemuboost.WithRandomDatabaseID(),
spanemuboost.WithSetupDDLs(setupDDLs),
spanemuboost.WithSetupRawDMLs(baseDMLs),
)
if err != nil {
log.Printf("%s: SETUP_INVALID: %v", tc.name, err)
continue
}
func() {
defer func() {
if err := clients.Close(); err != nil {
log.Printf("%s: close clients: %v", tc.name, err)
}
}()
var err error
switch {
case tc.ddl != "":
// Candidate DDL is executed after setup so setup failures and
// candidate failures can be reported separately.
op, opErr := clients.DatabaseClient.UpdateDatabaseDdl(ctx, &databasepb.UpdateDatabaseDdlRequest{
Database: clients.DatabasePath(),
Statements: []string{tc.ddl},
})
if opErr != nil {
err = opErr
} else {
err = op.Wait(ctx)
}
case tc.dml.SQL != "":
_, err = clients.Client.ReadWriteTransaction(ctx, func(ctx context.Context, txn *spanner.ReadWriteTransaction) error {
_, err := txn.Update(ctx, tc.dml)
return err
})
case tc.query != "":
iter := clients.Client.Single().Query(ctx, spanner.NewStatement(tc.query))
err = iter.Do(func(*spanner.Row) error { return nil })
}
if err != nil {
log.Printf("%s: INVALID: %v", tc.name, err)
return
}
log.Printf("%s: VALID", tc.name)
}()
}
}
Output:
func NewLazyRuntimeFromEnvOrStart ¶ added in v0.4.4
func NewLazyRuntimeFromEnvOrStart(backend Backend, options ...Option) (*LazyRuntime, error)
NewLazyRuntimeFromEnvOrStart returns a LazyRuntime that attaches to an external endpoint when one is configured for the requested backend. When no matching endpoint env vars are set, it defers container startup until first use. When endpoint env vars are set but LoadEndpointForBackend fails, it returns an error instead of falling back to cold start.
Options passed to the constructor apply to both cold-start and attach paths. Database bootstrap options such as WithRandomDatabaseID and WithSetupDDLs are honored when OpenClients or SetupClients runs against the lazy handle.
func (*LazyRuntime) Close ¶ added in v0.4.0
func (lr *LazyRuntime) Close() error
Close terminates the runtime if it was started. No-op otherwise. Close is nil-safe and idempotent — subsequent calls return the result of the first call. Close waits for any in-progress initialization to complete before checking. If Close is called before any Get or Setup, the runtime will never be started.
func (*LazyRuntime) Get ¶ added in v0.4.0
func (lr *LazyRuntime) Get(ctx context.Context) (Runtime, error)
Get starts the selected backend on first call (thread-safe via sync.Once) and returns the cached Runtime on subsequent calls.
func (*LazyRuntime) Setup ¶ added in v0.4.0
func (lr *LazyRuntime) Setup(tb testing.TB) Runtime
Setup starts the selected backend on first call (thread-safe via sync.Once) and returns the cached Runtime on subsequent calls. It calls testing.TB.Fatal if startup fails. For use with SetupClients or OpenClients, you can pass *LazyRuntime directly without calling Setup.
func (*LazyRuntime) TestMain ¶ added in v0.4.0
func (lr *LazyRuntime) TestMain(m *testing.M)
TestMain runs m.Run(), closes the lazy runtime, and calls os.Exit with the appropriate code. A close failure is logged and causes a non-zero exit code. If the runtime was never started, Close is a no-op.
Because TestMain calls os.Exit, it must be the last statement in your TestMain function. If you need additional cleanup, refer to the source of this method and write the logic manually.
Usage in TestMain:
var lazy = spanemuboost.NewLazyRuntime(
spanemuboost.BackendEmulator,
spanemuboost.EnableInstanceAutoConfigOnly(),
)
func TestMain(m *testing.M) { lazy.TestMain(m) }
type Option ¶
type Option func(*emulatorOptions) error
Option configures spanemuboost runtime bootstrap behavior. Use the package-provided With*, Without*, Enable*, Disable*, Force*, and Skip* helpers; external Option implementations are not supported.
func DisableAutoConfig ¶
func DisableAutoConfig() Option
DisableAutoConfig disables auto config.(default enable)
func DisableBackendGuardrails ¶ added in v0.4.0
func DisableBackendGuardrails() Option
DisableBackendGuardrails disables backend-specific validation and coercion.
By default, spanemuboost rejects known-invalid backend configurations early with human-readable errors. Use this option only when trying a newer backend version whose constraints may have changed.
func DisableQueryNullFilteredIndexCheck ¶ added in v0.4.2
func DisableQueryNullFilteredIndexCheck() Option
DisableQueryNullFilteredIndexCheck disables the emulator's safeguard that rejects queries against NULL_FILTERED indexes (the emulator's --disable_query_null_filtered_index_check flag). Emulator-only.
Production Spanner answers such queries; the emulator rejects them by default because the result set can legitimately differ from a base-table scan. Use this option only in tests that intentionally exercise reads against NULL_FILTERED indexes and have accounted for that difference.
func EnableAutoConfig ¶
func EnableAutoConfig() Option
EnableAutoConfig enables auto config.(default enable)
func EnableDatabaseAutoConfigOnly ¶ added in v0.2.0
func EnableDatabaseAutoConfigOnly() Option
EnableDatabaseAutoConfigOnly enables only database auto-creation and keeps instance auto-creation disabled.
func EnableEmulatorStdoutCopy ¶ added in v0.4.2
func EnableEmulatorStdoutCopy() Option
EnableEmulatorStdoutCopy enables copying the emulator backend's stdout to the gateway's stdout (the emulator's --copy_emulator_stdout flag). The gateway already copies the backend's stderr by default; this option adds the matching stdout stream for debugging. Emulator-only.
func EnableFaultInjection ¶ added in v0.2.5
func EnableFaultInjection() Option
EnableFaultInjection enables fault injection of Cloud Spanner Emulator (the emulator's --enable_fault_injection flag). Emulator-only.
func EnableInstanceAutoConfigOnly ¶ added in v0.2.0
func EnableInstanceAutoConfigOnly() Option
EnableInstanceAutoConfigOnly enables only instance auto-creation and keeps database auto-creation disabled.
func EnableLogRequests ¶ added in v0.4.2
func EnableLogRequests() Option
EnableLogRequests enables gRPC request and response logging in the emulator gateway (the emulator's --log_requests flag). Useful when debugging test failures; output is written to the container's stdout. Emulator-only.
func ForceSchemaTeardown ¶ added in v0.3.2
func ForceSchemaTeardown() Option
ForceSchemaTeardown forces schema resource cleanup on Clients.Close, dropping any auto-created database or instance before closing the Go clients.
By default, schema teardown is enabled for fixed IDs and disabled for random IDs. This option overrides that default for all resources.
func SkipSchemaTeardown ¶ added in v0.3.2
func SkipSchemaTeardown() Option
SkipSchemaTeardown disables schema resource cleanup on Clients.Close. Auto-created databases and instances will not be dropped on close.
By default, schema teardown is enabled for fixed IDs; use this option to opt out.
func WithChangeStreamPartitionTokenAliveSeconds ¶ added in v0.4.2
WithChangeStreamPartitionTokenAliveSeconds overrides the alive time of change stream partition tokens (the emulator's --override_change_stream_partition_token_alive_seconds flag). seconds must be positive. Emulator-only.
Per the upstream help text, the effective alive time becomes seconds..2*seconds (the emulator's default is 20..40s, which differs from production Spanner). This flag is emulator-only and has no effect on production Spanner.
func WithClientConfig ¶ added in v0.2.0
func WithClientConfig(config spanner.ClientConfig) Option
WithClientConfig sets spanner.ClientConfig for managed data clients created by spanemuboost, including OpenClients, RunWithClients, and SetupWithClients.
When this option is not used, spanemuboost sets DisableNativeMetrics to true by default, since the Spanner native metrics infrastructure is unnecessary for emulator connections and can add overhead (metadata server lookups, monitoring exporter creation).
For Omni managed clients with backend guardrails enabled, spanemuboost applies the recommended Omni defaults from RecommendedOmniClientConfig, including DisableNativeMetrics and IsExperimentalHost, overriding those two fields even when they were set explicitly in the provided config. DisableBackendGuardrails keeps the provided config untouched. RecommendedOmniClientConfig remains the recommended base for external Go clients.
func WithClientOptionsForClient ¶ added in v0.2.5
func WithClientOptionsForClient(option ...option.ClientOption) Option
WithClientOptionsForClient configures ClientOption for Clients.Client.
func WithContainerCustomizers ¶ added in v0.2.5
func WithContainerCustomizers(containerCustomizers ...testcontainers.ContainerCustomizer) Option
WithContainerCustomizers adds low-level testcontainers customizers to backend runtime containers.
Prefer WithContainerProvider instead of passing testcontainers.WithProvider directly when selecting Docker or Podman. If a customizer does set the provider, it is applied after SPANEMUBOOST_TESTCONTAINERS_PROVIDER and can override that environment default.
func WithContainerImage ¶ added in v0.4.0
WithContainerImage configures the container image used for the selected backend. Empty string will be ignored.
func WithContainerProvider ¶ added in v0.4.3
func WithContainerProvider(provider testcontainers.ProviderType) Option
WithContainerProvider configures the testcontainers provider used to start backend runtime containers.
Use testcontainers.ProviderPodman when running against Podman and Testcontainers-Go cannot auto-detect Podman from DOCKER_HOST. This is common with macOS Podman machine forwarded sockets whose host path does not contain "podman.sock".
This option overrides SPANEMUBOOST_TESTCONTAINERS_PROVIDER. If several provider customizers are supplied explicitly, the last one applied to the Testcontainers request wins.
func WithDatabaseDialect ¶
func WithDatabaseDialect(dialect databasepb.DatabaseDialect) Option
WithDatabaseDialect configures the database dialect.
func WithDatabaseID ¶
WithDatabaseID configures the database ID. Empty string resets to default.
func WithEmulatorImage
deprecated
Deprecated: WithEmulatorImage is a deprecated alias for WithContainerImage. Empty string will be ignored.
func WithInstanceID ¶
WithInstanceID configures the instance ID. Empty string resets to default.
func WithMaxDatabasesPerInstance ¶ added in v0.4.2
WithMaxDatabasesPerInstance overrides the emulator's maximum number of databases per instance (the emulator's --override_max_databases_per_instance flag). n must be positive. Emulator-only.
Per the upstream help text, the emulator only honors values greater than Spanner's default limit (100); smaller values are ignored. If the MAX_DATABASES_PER_INSTANCE environment variable is also set on the container, it takes precedence over this flag.
func WithProjectID ¶
WithProjectID configures the project ID. Empty string resets to default.
func WithRandomDatabaseID ¶ added in v0.2.3
func WithRandomDatabaseID() Option
WithRandomDatabaseID enables the random database ID. Default is disabled. This clears any previously set database ID (including inherited values from OpenClients).
Because a random ID will never match an existing database, this option also enables database auto-creation (sets disableCreateDatabase to false). To disable creation again, call DisableAutoConfig after this option.
func WithRandomInstanceID ¶ added in v0.2.4
func WithRandomInstanceID() Option
WithRandomInstanceID enables the random instance ID. Default is disabled. This clears any previously set instance ID (including inherited values from OpenClients).
Because a random ID will never match an existing instance, this option also enables instance auto-creation (sets disableCreateInstance to false). To disable creation again, call DisableAutoConfig after this option.
func WithRandomProjectID ¶ added in v0.2.4
func WithRandomProjectID() Option
WithRandomProjectID enables the random project ID. Default is disabled. This clears any previously set project ID (including inherited values from OpenClients).
func WithSetupDDLs ¶
WithSetupDDLs sets DDLs to be executed. Calling this multiple times replaces the previous value.
func WithSetupDMLs ¶
WithSetupDMLs sets DMLs in spanner.Statement to be executed. Calling this multiple times replaces the previous value. This is mutually exclusive with WithSetupRawDMLs; the last one called wins.
func WithSetupRawDMLs ¶
WithSetupRawDMLs sets string DMLs to be executed. Calling this multiple times replaces the previous value. This is mutually exclusive with WithSetupDMLs; the last one called wins.
func WithStrictTeardown
deprecated
added in
v0.3.0
func WithStrictTeardown() Option
Deprecated: Use ForceSchemaTeardown instead.
func WithoutRandomDatabaseID ¶ added in v0.2.4
func WithoutRandomDatabaseID() Option
WithoutRandomDatabaseID disables the random database ID. Default is disabled.
func WithoutRandomInstanceID ¶ added in v0.2.4
func WithoutRandomInstanceID() Option
WithoutRandomInstanceID disables the random instance ID. Default is disabled.
func WithoutRandomProjectID ¶ added in v0.2.4
func WithoutRandomProjectID() Option
WithoutRandomProjectID disables the random project ID. Default is disabled.
type Runtime ¶ added in v0.4.0
type Runtime interface {
RuntimeHandle
URI() string
ClientOptions() []option.ClientOption
Close() error
ProjectID() string
InstanceID() string
DatabaseID() string
ProjectPath() string
InstancePath() string
DatabasePath() string
}
Runtime is a started backend-neutral Spanner-compatible test runtime returned by Run or Setup.
This backend-neutral API surface is intended to remain the primary public entry point. Backend-specific behavior may evolve independently, especially for the experimental BackendOmni backend.
Implementations are provided by this package.
func Run ¶ added in v0.4.0
Run starts the selected backend and returns it as a backend-neutral runtime. When backend is BackendOmni, backend-specific behavior remains experimental and each runtime owns a Spanner Omni container. Avoid concurrent Omni runtime startup unless the host has enough memory for about 4 GiB per container.
func Setup ¶ added in v0.4.0
Setup starts the selected backend and registers cleanup with testing.TB.Cleanup. When backend is BackendOmni, backend-specific behavior remains experimental and each runtime owns a Spanner Omni container. Avoid combining Omni startup with t.Parallel unless the host has enough memory for about 4 GiB per container, or share a single runtime with NewLazyRuntime.
type RuntimeEnv ¶ added in v0.4.0
type RuntimeEnv struct {
*Clients
// contains filtered or unexported fields
}
RuntimeEnv combines a Runtime with Clients for backend-neutral startup. When created with BackendOmni, backend-specific behavior remains experimental.
func RunWithClients ¶ added in v0.4.0
RunWithClients starts the selected backend and returns managed clients. When backend is BackendOmni, backend-specific behavior remains experimental and each runtime owns a Spanner Omni container. Avoid concurrent Omni runtime startup unless the host has enough memory for about 4 GiB per container.
func SetupWithClients ¶ added in v0.4.0
func SetupWithClients(tb testing.TB, backend Backend, options ...Option) *RuntimeEnv
SetupWithClients starts the selected backend with managed clients and registers cleanup with testing.TB.Cleanup. When backend is BackendOmni, backend-specific behavior remains experimental and each runtime owns a Spanner Omni container. Avoid combining Omni startup with t.Parallel unless the host has enough memory for about 4 GiB per container, or share a single runtime with NewLazyRuntime.
func (*RuntimeEnv) Close ¶ added in v0.4.0
func (e *RuntimeEnv) Close() error
Close closes the clients and then terminates the runtime. Close is nil-safe and idempotent. After the first call, subsequent calls return the result of that first call.
func (*RuntimeEnv) Runtime ¶ added in v0.4.0
func (e *RuntimeEnv) Runtime() Runtime
Runtime returns the started runtime behind this environment.
type RuntimeHandle ¶ added in v0.4.0
type RuntimeHandle interface {
// contains filtered or unexported methods
}
RuntimeHandle is a package-provided runtime value accepted by OpenClients and SetupClients.
Supported handles are started Runtime values returned by Run or Setup, as well as *Emulator, *LazyRuntime, *LazyEmulator, and *AttachedRuntime. External implementations are not supported.
type ServeConfig ¶ added in v0.4.4
ServeConfig configures ServeFromConfig.
func ParseServeArgs ¶ added in v0.4.4
func ParseServeArgs(args []string) (ServeConfig, error)
ParseServeArgs parses `spanemuboost serve <emulator|omni> --endpoint-file path [--pid-file path] [--with-default-database]`.
type StopConfig ¶ added in v0.4.4
StopConfig configures StopFromConfig.
func ParseStopArgs ¶ added in v0.4.4
func ParseStopArgs(args []string) (StopConfig, error)
ParseStopArgs parses `spanemuboost stop --endpoint-file path [--pid-file path]`.