Die zufaelligen roten Laeufe der vollen Suite (jedes Mal ein anderer Test, zuletzt PromptHistoryTests mit einer reinen Funktion) hatten eine Ursache: Zwanzig Testklassen riefen beim Aufraeumen SqliteConnection.ClearAllPools() auf. Das ist prozessweit und nahm einem parallel laufenden Test die Verbindung mitten im Oeffnen weg - ObjectDisposedException in ApplyPragmas. - SqliteStorage.ReleasePooledConnections(pfad) leert nur die Pools der Datenbanken unter einer Datei oder einem Verzeichnis. - BackupService oeffnet die Quelle fuer VACUUM INTO ohne Pool: eine einmalige Kopie soll die Datei danach nicht offen halten. - PoolReleaseTests: die freigegebene Datenbank laesst sich loeschen, die andere arbeitet weiter. Vier volle Laeufe hintereinander gruen. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
62 lines
2.3 KiB
C#
62 lines
2.3 KiB
C#
using ClawdDotNet.Core.Storage;
|
|
using Shouldly;
|
|
|
|
namespace ClawdDotNet.Core.Tests.Storage;
|
|
|
|
/// <summary>
|
|
/// Releasing the pool of one database must leave every other database alone. The tests used
|
|
/// the process-wide ClearAllPools to clean up, and a test running in parallel lost its
|
|
/// connection in the middle of opening it — one red run in every few, always another test.
|
|
/// </summary>
|
|
public sealed class PoolReleaseTests : IDisposable
|
|
{
|
|
private readonly string _root = Path.Combine(Path.GetTempPath(), "clawd-tests", "pool-" + Guid.NewGuid().ToString("N")[..8]);
|
|
|
|
public void Dispose()
|
|
{
|
|
SqliteStorage.ReleasePooledConnections(_root);
|
|
try { Directory.Delete(_root, recursive: true); }
|
|
catch { /* cleaning up is secondary */ }
|
|
}
|
|
|
|
private static async Task TouchAsync(SqliteStorage storage)
|
|
=> await storage.WriteAsync(async connection =>
|
|
{
|
|
await using var cmd = connection.CreateCommand();
|
|
cmd.CommandText = "INSERT OR REPLACE INTO ToolState (Key, Value) VALUES ('k', 'v')";
|
|
await cmd.ExecuteNonQueryAsync();
|
|
}, default);
|
|
|
|
[Fact]
|
|
public async Task Releasing_one_directory_frees_its_files_and_nothing_else()
|
|
{
|
|
var first = new SqliteStorage(Path.Combine(_root, "a", "state.db"));
|
|
var second = new SqliteStorage(Path.Combine(_root, "b", "state.db"));
|
|
await TouchAsync(first);
|
|
await TouchAsync(second);
|
|
|
|
SqliteStorage.ReleasePooledConnections(Path.Combine(_root, "a"));
|
|
|
|
// The released database can go — on Windows an open handle would prevent that.
|
|
Should.NotThrow(() => Directory.Delete(Path.Combine(_root, "a"), recursive: true));
|
|
|
|
// The other one keeps working, and so does its pool.
|
|
await TouchAsync(second);
|
|
await using var connection = await second.OpenConnectionAsync(default);
|
|
await using var cmd = connection.CreateCommand();
|
|
cmd.CommandText = "SELECT Value FROM ToolState WHERE Key = 'k'";
|
|
(await cmd.ExecuteScalarAsync()).ShouldBe("v");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task A_database_file_can_be_named_directly()
|
|
{
|
|
var path = Path.Combine(_root, "c", "state.db");
|
|
await TouchAsync(new SqliteStorage(path));
|
|
|
|
SqliteStorage.ReleasePooledConnections(path);
|
|
|
|
Should.NotThrow(() => File.Delete(path));
|
|
}
|
|
}
|