8000 test(spanner): cleanup orphaned test tables and re-enable integration tests by camposperez-hub · Pull Request #4427 · googleapis/mcp-toolbox · GitHub
[go: up one dir, main page]

Skip to content

test(spanner): cleanup orphaned test tables and re-enable integration tests - #4427

Open
camposperez-hub wants to merge 4 commits into
mainfrom
fix/reenable-spanner-integration-tests
Open

camposperez-hub wants to merge 4 commits into
mainfrom
fix/reenable-spanner-integration-tests

Conversation

@camposperez-hub
Copy link
Copy Markdown
Collaborator

Description

Spanner integration tests have been failing and suppressed in CI (|| echo "Integration tests failed.") because test_database on spanner-testing accumulated 5,000 leaked test tables and 250 leaked property graphs (hitting Spanner's per-database schema limits of 5,000 tables and 250 property graphs).

This PR resolves the resource leak, speeds up DDL setup/teardown, and re-enables the Spanner integration test check in CI:

  • Timestamped Test Resource Naming (<prefix>_<unix_ts>_<uuid>): Prefixes Spanner test tables and property graphs with their Unix creation timestamp (newSpannerTestResourceName) so age-based garbage collection can identify stale resources without interfering with concurrent test runs.
  • Orphaned Resource Cleanup (> 2h + legacy un-timestamped resources): Adds cleanupOldSpannerResources and cleanupOldSpannerPgTables to query INFORMATION_SCHEMA.PROPERTY_GRAPHS and INFORMATION_SCHEMA.TABLES before test execution and drop orphaned resources older than 2 hours in dependency tiers (PROPERTY GRAPH -> edge_table_* -> node_table_* / other test tables) in DDL batches of 1,000.
  • Batched DDL Setup & Atomic Teardown: Consolidates the 7 sequential CREATE DDL operations and 7 sequential DROP DDL operations in TestSpannerToolEndpoints into 1 batched CREATE UpdateDatabaseDdl call and 1 atomic DROP UpdateDatabaseDdl call with context.WithoutCancel(ctx), preventing partial teardown leaks on timeout.
  • Clean Server Shutdown Between Tests: Adds stopSpannerServer (cmd.Close(), cmd.Wait(...), and http.DefaultClient.CloseIdleConnections()) so TestSpannerPostgresqlToolEndpoints does not reuse stale keep-alive connections to :5000 from TestSpannerToolEndpoints.
  • Re-enable Spanner CI Check: Removes || echo "Integration tests failed." from the spanner shard in .ci/integration.cloudbuild.yaml.

PR Checklist

  • Make sure to open an issue as a bug/issue before writing your code!
  • Ensure you have manually reviewed the entire diff before requesting a review
  • Ensure the tests and linter pass
  • Code coverage does not decrease (if any source code was changed)
  • Appropriate docs were updated (if necessary)
  • Make sure to add ! if this involves a breaking change

@camposperez-hub
camposperez-hub requested review from a team as code owners November 5, 2026 22:43
@gemini-code-assist gemini-code-assist Bot left a comment
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces automated cleanup of orphaned Spanner resources (tables and property graphs) older than two hours during integration tests, utilizing a new naming helper newSpannerTestResourceName that embeds creation timestamps. It also updates teardown steps to use context.WithoutCancel to ensure cleanup runs even if the main test context is canceled, and configures the CI pipeline to fail if Spanner integration tests fail. Feedback suggests reverting the Spanner client initialization to use context.Background() instead of the test context to prevent background session pool maintenance from stopping, and wrapping the context.WithoutCancel teardown contexts with a timeout to avoid blocking indefinitely if Spanner hangs.


// Create Spanner client (for queries)
dataClient, err := spanner.NewClient(context.Background(), db)
dataClient, err := spanner.NewClient(ctx, db)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Passing a context with a timeout or cancellation (like the test's ctx) to spanner.NewClient is generally discouraged. The Spanner client uses this context to run background session pool maintenance tasks. If the context is canceled, the session pool maintainer will stop, which can cause subsequent operations to fail. It is recommended to keep using context.Background() for client initialization, and pass the test-specific ctx to individual queries and transactions instead.

Suggested change
dataClient, err := spanner.NewClient(ctx, db)
dataClient, err := spanner.NewClient(context.Background(), db)

Comment on lines +711 to +719
op, err = adminClient.UpdateDatabaseDdl(context.WithoutCancel(ctx), &databasepb.UpdateDatabaseDdlRequest{
Database: dbString,
Statements: []string{fmt.Sprintf("DROP TABLE IF EXISTS %s", tableName)},
})
if err != nil {
t.Errorf("unable to start drop %s operation: %s", tableName, err)
return
}
opErr := op.Wait(ctx)
opErr := op.Wait(context.WithoutCancel(ctx))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using context.WithoutCancel(ctx) without a timeout means that if the Spanner admin client or connection hangs during teardown, the test runner will block indefinitely. It is safer to wrap it with a reasonable timeout (e.g., 2 minutes) to ensure the teardown eventually times out and fails fast if Spanner is unresponsive.

		teardownCtx, cancel := context.WithTimeout(context.WithoutCancel(ctx), 2*time.Minute)
		defer cancel()
		op, err = adminClient.UpdateDatabaseDdl(teardownCtx, &databasepb.UpdateDatabaseDdlRequest{
			Database:   dbString,
			Statements: []string{fmt.Sprintf("DROP TABLE IF EXISTS %s", tableName)},
		})
		if err != nil {
			t.Errorf("unable to start drop %s operation: %s", tableName, err)
			return
		}
		opErr := op.Wait(teardownCtx)

Comment on lines +462 to +471
teardownCtx := context.WithoutCancel(ctx)
op, err = adminClient.UpdateDatabaseDdl(teardownCtx, &databasepb.UpdateDatabaseDdlRequest{
Database: dbString,
Statements: []string{fmt.Sprintf("DROP TABLE IF EXISTS %s", tableName)},
})
if err != nil {
t.Errorf("unable to start drop %s operation: %s", tableName, err)
return
}
opErr := op.Wait(ctx)
opErr := op.Wait(teardownCtx)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using context.WithoutCancel(ctx) without a timeout means that if the Spanner admin client or connection hangs during teardown, the test runner will block indefinitely. It is safer to wrap it with a reasonable timeout (e.g., 2 minutes) to ensure the teardown eventually times out and fails fast if Spanner is unresponsive.

		teardownCtx, cancel := context.WithTimeout(context.WithoutCancel(ctx), 2*time.Minute)
		defer cancel()
		op, err = adminClient.UpdateDatabaseDdl(teardownCtx, &databasepb.UpdateDatabaseDdlRequest{
			Database:   dbString,
			Statements: []string{fmt.Sprintf("DROP TABLE IF EXISTS %s", tableName)},
		})
		if err != nil {
			t.Errorf("unable to start drop %s operation: %s", tableName, err)
			return
		}
		opErr := op.Wait(teardownCtx)

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

0