-
Notifications
You must be signed in to change notification settings - Fork 3
Speed up integration tests and fix a couple flaky ones #53
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,98 @@ | ||
| using Npgsql; | ||
|
|
||
| namespace SAMA.Tests.Integration; | ||
|
|
||
| [TestClass] | ||
| public static class AssemblyHooks | ||
| { | ||
| [AssemblyInitialize] | ||
| public static async Task CleanupStaleTestSchemasAsync(TestContext _) | ||
| { | ||
| var connString = IntegrationTestBase.GetAdminConnectionString(); | ||
| await using var dataSource = NpgsqlDataSource.Create(connString); | ||
| await using var conn = await dataSource.OpenConnectionAsync(); | ||
| await using var cmd = conn.CreateCommand(); | ||
|
|
||
| // Find test schemas older than 1 hour based on GUIDv7 timestamp in name | ||
| cmd.CommandText = "SELECT nspname FROM pg_namespace WHERE nspname LIKE 'test_%'"; | ||
| await using var reader = await cmd.ExecuteReaderAsync(); | ||
|
|
||
| var staleSchemas = new List<string>(); | ||
| var cutoff = DateTimeOffset.UtcNow.AddHours(-1); | ||
|
|
||
| while (await reader.ReadAsync()) | ||
| { | ||
| var schemaName = reader.GetString(0); | ||
| if (TryGetSchemaTimestamp(schemaName, out var timestamp) && timestamp < cutoff) | ||
| { | ||
| staleSchemas.Add(schemaName); | ||
| } | ||
| } | ||
|
|
||
| await reader.CloseAsync(); | ||
|
|
||
| foreach (var schema in staleSchemas) | ||
| { | ||
| try | ||
| { | ||
| await using var dropCmd = conn.CreateCommand(); | ||
| dropCmd.CommandText = $"DROP SCHEMA IF EXISTS {schema} CASCADE"; | ||
| await dropCmd.ExecuteNonQueryAsync(); | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| System.Diagnostics.Debug.WriteLine($"Failed to drop stale schema {schema}: {ex.Message}"); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| [AssemblyCleanup] | ||
| public static async Task CleanupAllSchemasAsync() | ||
| { | ||
|
arktronic-sep marked this conversation as resolved.
|
||
| foreach (var state in IntegrationTestBase.AllClassStates) | ||
| { | ||
| try | ||
| { | ||
| await using var conn = await state.DataSource.OpenConnectionAsync(); | ||
| await using var cmd = conn.CreateCommand(); | ||
| cmd.CommandText = $"DROP SCHEMA IF EXISTS {state.SchemaName} CASCADE"; | ||
| await cmd.ExecuteNonQueryAsync(); | ||
| } | ||
|
Comment on lines
+56
to
+60
|
||
| catch (Exception ex) | ||
| { | ||
| System.Diagnostics.Debug.WriteLine($"Failed to drop schema {state.SchemaName}: {ex.Message}"); | ||
| } | ||
| finally | ||
| { | ||
| await state.DataSource.DisposeAsync(); | ||
| } | ||
|
arktronic-sep marked this conversation as resolved.
|
||
| } | ||
| } | ||
|
|
||
| private static bool TryGetSchemaTimestamp(string schemaName, out DateTimeOffset timestamp) | ||
| { | ||
| // Schema names are: test_{classname}_{guidv7hex} | ||
| // GUIDv7 has the unix timestamp in ms in the first 48 bits | ||
| timestamp = default; | ||
| var lastUnderscore = schemaName.LastIndexOf('_'); | ||
| if (lastUnderscore < 0 || lastUnderscore + 1 >= schemaName.Length) | ||
| { | ||
| return false; | ||
| } | ||
|
|
||
| var guidHex = schemaName[(lastUnderscore + 1)..]; | ||
| if (guidHex.Length != 32 || !Guid.TryParse(guidHex, out _)) | ||
| { | ||
| return false; | ||
| } | ||
|
|
||
| // First 12 hex chars = 48 bits = unix timestamp in milliseconds | ||
| if (!long.TryParse(guidHex[..12], System.Globalization.NumberStyles.HexNumber, null, out var unixMs)) | ||
| { | ||
| return false; | ||
| } | ||
|
|
||
| timestamp = DateTimeOffset.FromUnixTimeMilliseconds(unixMs); | ||
| return timestamp.Year is >= 2024 and <= 2100; | ||
|
Comment on lines
+83
to
+96
|
||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
DROP SCHEMA is built via string interpolation with an identifier coming from the database (pg_namespace). If a schema name ever contains characters that require quoting, this will fail; and interpolating identifiers directly is also avoidable risk. Consider quoting the identifier (e.g., via NpgsqlCommandBuilder.QuoteIdentifier) when constructing the DROP SCHEMA statement.