Skip to content

Commit 06da938

Browse files
committed
Fix #3725: Improve test to trigger empty batch bug
1 parent abc7a9a commit 06da938

File tree

2 files changed

+72
-53
lines changed

2 files changed

+72
-53
lines changed

src/NHibernate.Test/Ado/BatcherFixture.cs

Lines changed: 36 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -306,44 +306,54 @@ public void AbstractBatcherLogFormattedSql()
306306
}
307307

308308
[Test]
309-
[Description("The batcher should handle empty batch execution without throwing exceptions.")]
310-
public void EmptyBatchShouldNotThrowException()
309+
[Description("Inserting exactly BatchSize entities should not throw on commit. See GH-3725.")]
310+
public void InsertExactlyBatchSizeEntitiesShouldNotThrowOnCommit()
311311
{
312-
// This test verifies that batchers handle empty batches correctly
313-
// DbBatchBatcher had a bug where ExecuteBatch was called on an empty batch,
314-
// causing InvalidOperationException: CommandText property has not been initialized
315-
// See GH-3725
312+
// This test verifies that DbBatchBatcher handles empty batches correctly.
313+
// The bug (GH-3725): When inserting exactly BatchSize entities, the batch auto-executes
314+
// when full (via ExecuteBatchWithTiming), which clears _currentBatch but NOT _batchCommand.
315+
// On commit, ExecuteBatch() is called, sees _batchCommand is set, and calls DoExecuteBatch
316+
// on an empty _currentBatch, causing InvalidOperationException.
316317

317-
using var session = OpenSession();
318-
using var transaction = session.BeginTransaction();
318+
// BatchSize is configured as 10 in this fixture
319+
const int batchSize = 10;
319320

320-
// Execute queries that don't add to the batch
321-
_ = session.Query<VerySimple>().FirstOrDefault();
321+
using (var session = OpenSession())
322+
using (var transaction = session.BeginTransaction())
323+
{
324+
// Insert exactly BatchSize entities - this fills the batch and triggers auto-execution
325+
for (int i = 0; i < batchSize; i++)
326+
{
327+
session.Save(new VerySimple { Id = 1000 + i, Name = $"Test{i}", Weight = i * 1.1 });
328+
}
322329

323-
// Prepare a new command which triggers ExecuteBatch on any existing batch
324-
// If the previous command didn't add anything to the batch, this would fail
325-
// before the fix with InvalidOperationException
326-
_ = session.Query<VerySimple>().FirstOrDefault();
330+
// Commit triggers ExecuteBatch() which would fail on empty batch without the fix
331+
transaction.Commit();
332+
}
327333

328-
// Test passes if no exception is thrown
329-
transaction.Commit();
334+
Cleanup();
330335
}
331336

332337
[Test]
333-
[Description("Flush with no pending operations should handle empty batch correctly.")]
334-
public void FlushEmptyBatchShouldNotThrowException()
338+
[Description("Inserting a multiple of BatchSize entities should not throw on commit. See GH-3725.")]
339+
public void InsertMultipleOfBatchSizeEntitiesShouldNotThrowOnCommit()
335340
{
336-
using var session = OpenSession();
337-
using var transaction = session.BeginTransaction();
341+
// Same issue as above but with multiple full batches
342+
const int batchSize = 10;
343+
const int multiplier = 3;
338344

339-
// Query without any modifications
340-
var count = session.Query<VerySimple>().Count();
341-
Assert.That(count, Is.GreaterThanOrEqualTo(0));
345+
using (var session = OpenSession())
346+
using (var transaction = session.BeginTransaction())
347+
{
348+
for (int i = 0; i < batchSize * multiplier; i++)
349+
{
350+
session.Save(new VerySimple { Id = 2000 + i, Name = $"Test{i}", Weight = i * 1.1 });
351+
}
342352

343-
// Flush with no pending batch operations should not throw
344-
Assert.DoesNotThrow(() => session.Flush());
353+
transaction.Commit();
354+
}
345355

346-
transaction.Commit();
356+
Cleanup();
347357
}
348358
}
349359
}

src/NHibernate.Test/Async/Ado/BatcherFixture.cs

Lines changed: 36 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
using NHibernate.AdoNet;
1313
using NHibernate.Cfg;
1414
using NUnit.Framework;
15-
using NHibernate.Linq;
1615

1716
namespace NHibernate.Test.Ado
1817
{
@@ -279,44 +278,54 @@ public async Task AbstractBatcherLogFormattedSqlAsync()
279278
}
280279

281280
[Test]
282-
[Description("The batcher should handle empty batch execution without throwing exceptions.")]
283-
public async Task EmptyBatchShouldNotThrowExceptionAsync()
281+
[Description("Inserting exactly BatchSize entities should not throw on commit. See GH-3725.")]
282+
public async Task InsertExactlyBatchSizeEntitiesShouldNotThrowOnCommitAsync()
284283
{
285-
// This test verifies that batchers handle empty batches correctly
286-
// DbBatchBatcher had a bug where ExecuteBatch was called on an empty batch,
287-
// causing InvalidOperationException: CommandText property has not been initialized
288-
// See GH-3725
284+
// This test verifies that DbBatchBatcher handles empty batches correctly.
285+
// The bug (GH-3725): When inserting exactly BatchSize entities, the batch auto-executes
286+
// when full (via ExecuteBatchWithTiming), which clears _currentBatch but NOT _batchCommand.
287+
// On commit, ExecuteBatch() is called, sees _batchCommand is set, and calls DoExecuteBatch
288+
// on an empty _currentBatch, causing InvalidOperationException.
289289

290-
using var session = OpenSession();
291-
using var transaction = session.BeginTransaction();
290+
// BatchSize is configured as 10 in this fixture
291+
const int batchSize = 10;
292292

293-
// Execute queries that don't add to the batch
294-
_ = await (session.Query<VerySimple>().FirstOrDefaultAsync());
293+
using (var session = OpenSession())
294+
using (var transaction = session.BeginTransaction())
295+
{
296+
// Insert exactly BatchSize entities - this fills the batch and triggers auto-execution
297+
for (int i = 0; i < batchSize; i++)
298+
{
299+
await (session.SaveAsync(new VerySimple { Id = 1000 + i, Name = $"Test{i}", Weight = i * 1.1 }));
300+
}
295301

296-
// Prepare a new command which triggers ExecuteBatch on any existing batch
297-
// If the previous command didn't add anything to the batch, this would fail
298-
// before the fix with InvalidOperationException
299-
_ = await (session.Query<VerySimple>().FirstOrDefaultAsync());
302+
// Commit triggers ExecuteBatch() which would fail on empty batch without the fix
303+
await (transaction.CommitAsync());
304+
}
300305

301-
// Test passes if no exception is thrown
302-
await (transaction.CommitAsync());
306+
await (CleanupAsync());
303307
}
304308

305309
[Test]
306-
[Description("Flush with no pending operations should handle empty batch correctly.")]
307-
public async Task FlushEmptyBatchShouldNotThrowExceptionAsync()
310+
[Description("Inserting a multiple of BatchSize entities should not throw on commit. See GH-3725.")]
311+
public async Task InsertMultipleOfBatchSizeEntitiesShouldNotThrowOnCommitAsync()
308312
{
309-
using var session = OpenSession();
310-
using var transaction = session.BeginTransaction();
313+
// Same issue as above but with multiple full batches
314+
const int batchSize = 10;
315+
const int multiplier = 3;
311316

312-
// Query without any modifications
313-
var count = await (session.Query<VerySimple>().CountAsync());
314-
Assert.That(count, Is.GreaterThanOrEqualTo(0));
317+
using (var session = OpenSession())
318+
using (var transaction = session.BeginTransaction())
319+
{
320+
for (int i = 0; i < batchSize * multiplier; i++)
321+
{
322+
await (session.SaveAsync(new VerySimple { Id = 2000 + i, Name = $"Test{i}", Weight = i * 1.1 }));
323+
}
315324

316-
// Flush with no pending batch operations should not throw
317-
Assert.DoesNotThrowAsync(() => session.FlushAsync());
325+
await (transaction.CommitAsync());
326+
}
318327

319-
await (transaction.CommitAsync());
328+
await (CleanupAsync());
320329
}
321330
}
322331
}

0 commit comments

Comments
 (0)