Skip to content

Commit f0bd652

Browse files
committed
Densified test files via DataRow consolidation, comment removal, and shared error-assertion helpers (AssertSqlError / AssertSqlMessage).
1 parent ab6d88a commit f0bd652

41 files changed

Lines changed: 1401 additions & 3841 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

SqlServerSimulator.Tests.EFCore/EFCoreApply.cs

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,7 @@ namespace SqlServerSimulator;
44
/// End-to-end tests for the LINQ shapes EF Core 10 translates to
55
/// <c>CROSS APPLY</c> / <c>OUTER APPLY</c>: <c>SelectMany</c> over a filtered
66
/// collection navigation, and the same shape with <c>DefaultIfEmpty</c> for
7-
/// the OUTER variant. EF Core 10's emission for top-N projections moved to
8-
/// <c>ROW_NUMBER() OVER</c> + <c>LEFT JOIN</c>, so APPLY only emerges
9-
/// here — when the inner correlates by something more than top-level
10-
/// equality.
7+
/// the OUTER variant.
118
/// </summary>
129
[TestClass]
1310
public class EFCoreApply
@@ -35,9 +32,6 @@ private static TestDbContext SeededContext()
3532
[TestMethod]
3633
public void SelectMany_FilteredNavigation_EmitsCrossApply()
3734
{
38-
// EF Core 10 emits CROSS APPLY for SelectMany over a filtered
39-
// collection navigation. Verifies end-to-end correctness against
40-
// the shape probe-confirmed against real SQL Server 2025.
4135
using var context = SeededContext();
4236
var pairs = context.Authors
4337
.SelectMany(a => a.Books.Where(b => b.Score > 10),
@@ -52,8 +46,7 @@ public void SelectMany_FilteredNavigation_EmitsCrossApply()
5246
[TestMethod]
5347
public void SelectMany_FilterReferencesOuter_EmitsCrossApply()
5448
{
55-
// Inner WHERE references both outer and inner columns —
56-
// can't lift to a JOIN, must be APPLY.
49+
// Inner WHERE references both outer and inner columns — can't lift to JOIN, must be APPLY.
5750
using var context = SeededContext();
5851
var pairs = context.Authors
5952
.SelectMany(a => a.Books.Where(b => b.Score >= a.Id * 5),

SqlServerSimulator.Tests.EFCore/EFCoreDateTime.cs

Lines changed: 13 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,11 @@
11
namespace SqlServerSimulator;
22

33
/// <summary>
4-
/// Exercises the simulator's <c>datetime2(N)</c> and <c>datetimeoffset(N)</c>
5-
/// column support through EF Core's idiomatic surface. Confirms full-precision
6-
/// (precision 7) round trips and lower-precision (precision 3 / 0) rounding-
7-
/// on-store behavior land correctly through EF's parameter binding and reader
8-
/// hydration. Datetimeoffset additionally pins offset preservation across the
9-
/// round trip and equality-by-UTC-instant in <c>WHERE</c>.
4+
/// Exercises <c>datetime2(N)</c> and <c>datetimeoffset(N)</c> column support
5+
/// through EF Core. Confirms full-precision (precision 7) round trips,
6+
/// lower-precision rounding-on-store, datetimeoffset offset preservation,
7+
/// equality-by-UTC-instant, and legacy datetime tick-quantization round-trip.
108
/// </summary>
11-
/// <remarks>
12-
/// Only <see cref="DateTime"/> and <see cref="DateTimeOffset"/> properties are
13-
/// covered — see <see cref="SimulatedDbParameter"/> for the per-mapping
14-
/// compatibility table explaining why the other date/time configurations
15-
/// (<c>DateOnly → date</c>, <c>TimeOnly → time</c>, <c>TimeSpan → time</c>,
16-
/// <c>DateTime → date</c>) are unreachable through EF Core until a bridge
17-
/// adapter ships.
18-
/// </remarks>
199
[TestClass]
2010
public class EFCoreDateTime
2111
{
@@ -69,7 +59,7 @@ public void Insert_NullableDateTime_AcceptsValue()
6959
[TestMethod]
7060
public void Insert_LowerPrecisionColumn_RoundsHalfUp()
7161
{
72-
// Updated is datetime2(3); 0.5ms above a millisecond boundary rounds to next ms.
62+
// Updated is datetime2(3); 0.5ms above ms boundary rounds to next ms.
7363
using var context = new TestDbContext(TestDbContext.CreateEventsSimulation());
7464
var updated = new DateTime(2026, 5, 4, 13, 45, 30, 100).AddTicks(5_000);
7565
_ = context.Events.Add(new Event { Id = 1, CreatedAt = new DateTime(2026, 5, 4), Updated = updated });
@@ -90,8 +80,7 @@ public void Where_FiltersByDateTimeEquality()
9080
new Event { Id = 3, CreatedAt = new DateTime(2026, 5, 4, 15, 0, 0) });
9181
_ = context.SaveChanges();
9282

93-
var match = context.Events.Where(e => e.CreatedAt == target).Select(e => e.Id).Single();
94-
Assert.AreEqual(2, match);
83+
Assert.AreEqual(2, context.Events.Where(e => e.CreatedAt == target).Select(e => e.Id).Single());
9584
}
9685

9786
[TestMethod]
@@ -146,7 +135,7 @@ public void Insert_NullableDateTimeOffset_AcceptsNullAndValue()
146135
[TestMethod]
147136
public void Insert_DateTimeOffset_LowerPrecisionColumn_RoundsHalfUp()
148137
{
149-
// Cancelled is datetimeoffset(3); 0.5ms above a millisecond boundary rounds to next ms.
138+
// Cancelled is datetimeoffset(3); 0.5ms above ms boundary rounds to next ms.
150139
using var context = new TestDbContext(TestDbContext.CreateEventsSimulation());
151140
var cancelled = new DateTimeOffset(2026, 5, 4, 13, 45, 30, 100, TimeSpan.FromHours(-7)).AddTicks(5_000);
152141
_ = context.Events.Add(new Event { Id = 1, CreatedAt = new DateTime(2026, 5, 4), OccurredAt = DateTimeOffset.UnixEpoch, Cancelled = cancelled });
@@ -159,8 +148,7 @@ public void Insert_DateTimeOffset_LowerPrecisionColumn_RoundsHalfUp()
159148
[TestMethod]
160149
public void Where_FiltersByDateTimeOffsetEquality_CrossOffset()
161150
{
162-
// The stored value and the parameter share a UTC instant but carry
163-
// different offsets; equality should still match.
151+
// Stored value and parameter share UTC instant but carry different offsets; equality should match.
164152
using var context = new TestDbContext(TestDbContext.CreateEventsSimulation());
165153
var east = new DateTimeOffset(2026, 5, 4, 20, 45, 30, TimeSpan.FromHours(7));
166154
context.Events.AddRange(
@@ -169,30 +157,25 @@ public void Where_FiltersByDateTimeOffsetEquality_CrossOffset()
169157
_ = context.SaveChanges();
170158

171159
var west = new DateTimeOffset(2026, 5, 4, 6, 45, 30, TimeSpan.FromHours(-7));
172-
var match = context.Events.Where(e => e.OccurredAt == west).Select(e => e.Id).Single();
173-
Assert.AreEqual(1, match);
160+
Assert.AreEqual(1, context.Events.Where(e => e.OccurredAt == west).Select(e => e.Id).Single());
174161
}
175162

176163
[TestMethod]
177164
public void Insert_LegacyDateTime_RoundTripsAtTickGranularity()
178165
{
179-
// Legacy datetime stores 1/300-second ticks. .997 input lands on
180-
// tick 299 — preserved by EF Core's reader hydration since SqlClient
181-
// reconstructs DateTime ticks deterministically from the stored unit.
166+
// Legacy datetime stores 1/300-second ticks; .997 input → tick 299 = 9_966_666 100-ns ticks past second.
182167
using var context = new TestDbContext(TestDbContext.CreateEventsSimulation());
183168

184169
var started = new DateTime(2026, 5, 4, 13, 45, 30, 997);
185170
_ = context.Events.Add(new Event { Id = 1, CreatedAt = new DateTime(2026, 5, 4), Started = started });
186171
_ = context.SaveChanges();
187172

188-
// Round-trip preserves the tick-quantized value, not the raw ms.
189173
var read = context.Events.Select(e => e.Started).First();
190174
Assert.IsNotNull(read);
191175
Assert.AreEqual(started.Date, read.Value.Date);
192176
Assert.AreEqual(started.Hour, read.Value.Hour);
193177
Assert.AreEqual(started.Minute, read.Value.Minute);
194178
Assert.AreEqual(started.Second, read.Value.Second);
195-
// .997 ms input → tick 299 → stored at 9_966_666 100-ns ticks past the second.
196179
Assert.AreEqual(9_966_666, read.Value.Ticks % TimeSpan.TicksPerSecond);
197180
}
198181

@@ -228,8 +211,7 @@ public void Where_FiltersByLegacyDateTimeEquality()
228211
new Event { Id = 3, CreatedAt = new DateTime(2026, 5, 4), Started = new DateTime(2026, 5, 4, 15, 0, 0) });
229212
_ = context.SaveChanges();
230213

231-
var match = context.Events.Where(e => e.Started == target).Select(e => e.Id).Single();
232-
Assert.AreEqual(2, match);
214+
Assert.AreEqual(2, context.Events.Where(e => e.Started == target).Select(e => e.Id).Single());
233215
}
234216

235217
[TestMethod]
@@ -246,9 +228,7 @@ public void Insert_LegacyDateTime_AtMin_RoundTrips()
246228
[TestMethod]
247229
public void Where_DateTimeYearExtraction_TranslatesToDatepart()
248230
{
249-
// EF Core translates .Year (and .Month / .Day / .Hour / etc.) to
250-
// DATEPART(year, col). Common in real apps for "events this year"
251-
// filters.
231+
// EF Core translates .Year to DATEPART(year, col).
252232
var simulation = TestDbContext.CreateEventsSimulation();
253233
using (var seed = new TestDbContext(simulation))
254234
{
@@ -267,7 +247,6 @@ public void Where_DateTimeYearExtraction_TranslatesToDatepart()
267247
[TestMethod]
268248
public void Projection_DateTimeAddDays_TranslatesToDateadd()
269249
{
270-
// EF Core translates .AddDays(N) to DATEADD(day, CAST(N AS int), col).
271250
var simulation = TestDbContext.CreateEventsSimulation();
272251
using (var seed = new TestDbContext(simulation))
273252
{
@@ -281,7 +260,6 @@ public void Projection_DateTimeAddDays_TranslatesToDateadd()
281260
}
282261

283262
using var context = new TestDbContext(simulation);
284-
var rolled = context.Events.Select(e => e.CreatedAt.AddDays(7)).Single();
285-
Assert.AreEqual(new DateTime(2024, 6, 8), rolled);
263+
Assert.AreEqual(new DateTime(2024, 6, 8), context.Events.Select(e => e.CreatedAt.AddDays(7)).Single());
286264
}
287265
}

SqlServerSimulator.Tests.EFCore/EFCoreDecimal.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,6 @@ public void OrderBy_DecimalAscending()
9090
[TestMethod]
9191
public void Cast_DecimalToDouble_ProjectsAsFloat()
9292
{
93-
// (double)decimalCol — EF Core emits CAST(... AS float). Common when
94-
// Math functions or aggregations need a wider numeric type.
9593
using var context = new TestDbContext(TestDbContext.CreateProductsSimulation());
9694
_ = context.Products.Add(new Product { Id = 1, Price = 19.99m });
9795
_ = context.SaveChanges();

SqlServerSimulator.Tests.EFCore/EFCoreMath.cs

Lines changed: 11 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,11 @@ namespace SqlServerSimulator;
22

33
/// <summary>
44
/// End-to-end coverage for EF Core 10's translation of <c>Math.X</c> LINQ
5-
/// methods. Probe-confirmed (2026-05-09): EF emits the SQL function
6-
/// directly for <c>Math.Round</c> / <c>Floor</c> / <c>Ceiling</c> /
7-
/// <c>Pow</c> / <c>Sqrt</c> / <c>Sign</c> / <c>Log</c> / <c>Exp</c> /
8-
/// <c>Log10</c>, and emits <c>ROUND(x, 0, 1)</c> for <c>Math.Truncate</c>
9-
/// (the truncate-mode third-arg form). <c>Math.Abs</c> already routes
10-
/// through the existing <c>AbsoluteValue</c> path.
5+
/// methods. EF emits the SQL function directly for <c>Math.Round</c> /
6+
/// <c>Floor</c> / <c>Ceiling</c> / <c>Pow</c> / <c>Sqrt</c> / <c>Sign</c> /
7+
/// <c>Log</c> / <c>Exp</c> / <c>Log10</c>, and emits <c>ROUND(x, 0, 1)</c>
8+
/// for <c>Math.Truncate</c>. <c>Math.Abs</c> routes through the existing
9+
/// <c>AbsoluteValue</c> path.
1110
/// </summary>
1211
[TestClass]
1312
public class EFCoreMath
@@ -28,7 +27,6 @@ private static TestDbContext SeededProductsContext()
2827
[TestMethod]
2928
public void Round_DecimalProperty()
3029
{
31-
// Math.Round(p.Price, 2) → ROUND([p].[Price], 2) — half-away-from-zero.
3230
using var context = SeededProductsContext();
3331
var rounded = context.Products
3432
.OrderBy(p => p.Id)
@@ -40,7 +38,6 @@ public void Round_DecimalProperty()
4038
[TestMethod]
4139
public void RoundToInteger()
4240
{
43-
// Math.Round(p.Price) → ROUND([p].[Price], 0) — half-away-from-zero.
4441
using var context = SeededProductsContext();
4542
var rounded = context.Products
4643
.OrderBy(p => p.Id)
@@ -52,7 +49,6 @@ public void RoundToInteger()
5249
[TestMethod]
5350
public void Floor_DecimalProperty()
5451
{
55-
// Math.Floor(p.Price) → FLOOR([p].[Price]).
5652
using var context = SeededProductsContext();
5753
var floors = context.Products
5854
.OrderBy(p => p.Id)
@@ -75,10 +71,7 @@ public void Ceiling_DecimalProperty()
7571
[TestMethod]
7672
public void Sign_IntFromOrderBy()
7773
{
78-
// EF Core emits SIGN([t].[Id]) for Math.Sign(int); the result is
79-
// an int column. Math.Sign(decimal) → int has a server-side type
80-
// mismatch (SQL Server's SIGN(decimal) returns decimal, but EF
81-
// reads the column as int) — that route isn't exercised here.
74+
// Math.Sign(decimal) → CAST mismatch end-to-end (CLAUDE.md); exercising the int route only.
8275
using var context = SeededProductsContext();
8376
var signs = context.Products
8477
.OrderBy(p => p.Id)
@@ -90,7 +83,6 @@ public void Sign_IntFromOrderBy()
9083
[TestMethod]
9184
public void Abs_DecimalProperty()
9285
{
93-
// Math.Abs(p.Price) → ABS([p].[Price]) — preserves decimal(p,s).
9486
using var context = SeededProductsContext();
9587
var values = context.Products
9688
.OrderBy(p => p.Id)
@@ -102,7 +94,6 @@ public void Abs_DecimalProperty()
10294
[TestMethod]
10395
public void Truncate_DecimalProperty_EmitsRoundWithTruncateFlag()
10496
{
105-
// Math.Truncate(p.Price) → ROUND([p].[Price], 0, 1) — truncate mode.
10697
using var context = SeededProductsContext();
10798
var truncated = context.Products
10899
.OrderBy(p => p.Id)
@@ -114,16 +105,14 @@ public void Truncate_DecimalProperty_EmitsRoundWithTruncateFlag()
114105
[TestMethod]
115106
public void Power_AndSqrt_OverFloatProjection()
116107
{
117-
// Math.Pow / Math.Sqrt require float operands at the LINQ side
118-
// (EF Core's translator doesn't widen decimal to double
119-
// automatically), so route through a CAST to double.
108+
// Math.Pow / Sqrt require float operands at the LINQ side (EF doesn't auto-widen decimal → double).
120109
using var context = SeededProductsContext();
121110
var values = context.Products
122111
.Where(p => p.Price > 0)
123112
.OrderBy(p => p.Id)
124113
.Select(p => new { Pow = Math.Pow((double)p.Price, 2), Root = Math.Sqrt((double)p.Price) })
125114
.ToArray();
126-
// Price is decimal(10,2) so 12.345 stores as 12.35 → 12.35^2 = 152.5225.
115+
// Price is decimal(10,2), so 12.345 stores as 12.35 → 12.35^2 = 152.5225.
127116
AreClose(152.5225m, (decimal)values[0].Pow, 0.001m);
128117
AreClose(3.5142m, (decimal)values[0].Root, 0.001m);
129118
AreClose(0.25m, (decimal)values[1].Pow, 0.001m);
@@ -139,8 +128,8 @@ public void LogAndExp_FloatProjection()
139128
.Select(p => new { Log = Math.Log((double)p.Price), Exp = Math.Exp(1.0) })
140129
.ToArray();
141130
Assert.HasCount(2, values);
142-
AreClose(2.5133m, (decimal)values[0].Log, 0.001m); // log(12.345)
143-
AreClose(2.7183m, (decimal)values[0].Exp, 0.001m); // e
131+
AreClose(2.5133m, (decimal)values[0].Log, 0.001m);
132+
AreClose(2.7183m, (decimal)values[0].Exp, 0.001m);
144133
}
145134

146135
[TestMethod]
@@ -152,7 +141,7 @@ public void Log10_FloatProjection()
152141
.OrderBy(p => p.Id)
153142
.Select(p => Math.Log10((double)p.Price))
154143
.ToArray();
155-
AreClose(1.0915m, (decimal)values[0], 0.001m); // log10(12.345)
144+
AreClose(1.0915m, (decimal)values[0], 0.001m);
156145
}
157146

158147
private static void AreClose(decimal expected, decimal actual, decimal tolerance)

0 commit comments

Comments
 (0)