Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 114 additions & 0 deletions .github/workflows/integration-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
name: Tests & Coverage

on:
pull_request:
branches:
- '**'
push:
branches:
- master

jobs:
test:
name: Tests & Code Coverage
runs-on: ubuntu-latest
timeout-minutes: 25

permissions:
contents: read
pull-requests: write
checks: write

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.0.x'

- name: Restore dependencies
run: dotnet restore src/Dime.Repositories.slnx

- name: Build solution
run: dotnet build src/Dime.Repositories.slnx -c Release --no-restore

- name: Run unit tests with coverage
run: |
dotnet test src/test/Dime.Repositories.Sql.EntityFramework.Tests/Dime.Repositories.Sql.EntityFramework.Tests.csproj \
-c Release --no-build \
--logger "trx;LogFileName=unit-tests.trx" \
--results-directory TestResults/unit \
--collect:"XPlat Code Coverage" \
--settings src/coverlet.runsettings

- name: Run integration tests with coverage
timeout-minutes: 15
run: |
dotnet test src/test/Dime.Repositories.Sql.EntityFramework.IntegrationTests/Dime.Repositories.Sql.EntityFramework.IntegrationTests.csproj \
-c Release --no-build \
--logger "trx;LogFileName=integration-tests.trx" \
--results-directory TestResults/integration \
--collect:"XPlat Code Coverage" \
--settings src/coverlet.runsettings

- name: Install ReportGenerator
if: always()
run: dotnet tool install -g dotnet-reportgenerator-globaltool

- name: Merge coverage and generate report
if: always()
run: |
reportgenerator \
"-reports:TestResults/**/coverage.cobertura.xml" \
"-targetdir:CoverageReport" \
"-reporttypes:Html;MarkdownSummaryGithub;Cobertura;TextSummary" \
"-title:Dime.Repositories Coverage"

- name: Append coverage to job summary
if: always()
run: |
if [ -f CoverageReport/SummaryGithub.md ]; then
cat CoverageReport/SummaryGithub.md >> $GITHUB_STEP_SUMMARY
fi
if [ -f CoverageReport/Summary.txt ]; then
echo "::group::Coverage summary"
cat CoverageReport/Summary.txt
echo "::endgroup::"
fi

- name: Post coverage comment on PR
if: always() && github.event_name == 'pull_request'
uses: irongut/CodeCoverageSummary@v1.3.0
with:
filename: CoverageReport/Cobertura.xml
badge: true
fail_below_min: false
format: markdown
hide_branch_rate: false
hide_complexity: true
indicators: true
output: both
thresholds: '60 90'

- name: Attach coverage comment to PR
if: always() && github.event_name == 'pull_request'
uses: marocchino/sticky-pull-request-comment@v2
with:
recreate: true
path: code-coverage-results.md

- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: test-results
path: TestResults

- name: Upload coverage report
if: always()
uses: actions/upload-artifact@v4
with:
name: coverage-report
path: CoverageReport
40 changes: 40 additions & 0 deletions .github/workflows/unit-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: Unit Tests

on:
pull_request:
branches:
- '**'
push:
branches:
- master

jobs:
unit:
name: Unit tests (SQLite in-memory)
runs-on: ubuntu-latest
timeout-minutes: 10

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.0.x'

- name: Restore dependencies
run: dotnet restore src/Dime.Repositories.slnx

- name: Build unit test project
run: dotnet build src/test/Dime.Repositories.Sql.EntityFramework.Tests/Dime.Repositories.Sql.EntityFramework.Tests.csproj -c Release --no-restore

- name: Run unit tests
run: dotnet test src/test/Dime.Repositories.Sql.EntityFramework.Tests/Dime.Repositories.Sql.EntityFramework.Tests.csproj -c Release --no-build --logger trx --results-directory TestResults

- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: unit-test-results
path: TestResults
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@
*.vs
**/packages
**/*.coverage
**/BenchmarkDotNet.Artifacts/
CoverageReport/
TestResults/
4 changes: 4 additions & 0 deletions src/Dime.Repositories.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
</Folder>
<Folder Name="/Tests/">
<Project Path="test/Dime.Repositories.Sql.EntityFramework.Tests/Dime.Repositories.Sql.EntityFramework.Tests.csproj" />
<Project Path="test/Dime.Repositories.Sql.EntityFramework.IntegrationTests/Dime.Repositories.Sql.EntityFramework.IntegrationTests.csproj" />
</Folder>
<Folder Name="/benchmarks/">
<Project Path="benchmarks/Dime.Repositories.Benchmarks/Dime.Repositories.Benchmarks.csproj" />
</Folder>
<Project Path="core/Dime.Repositories.Sql/Dime.Repositories.Sql.csproj" />
<Project Path="core/Dime.Repositories/Dime.Repositories.csproj" />
Expand Down
105 changes: 105 additions & 0 deletions src/benchmarks/Dime.Repositories.Benchmarks/BenchmarkFixture.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.EntityFrameworkCore;

namespace Dime.Repositories.Benchmarks
{
/// <summary>
/// File-based SQLite fixture. Seeds 1,000 Blogs x ~10 Posts x ~15 Tags
/// (10k Posts + 15k Tags; Cartesian fan-out of 150 join rows per Blog
/// when both collections are joined in a single query).
/// </summary>
internal sealed class BenchmarkFixture : IDisposable
{
public const int BlogCount = 1000;
public const int PostsPerBlog = 10;
public const int TagsPerBlog = 15;

public string DbPath { get; }
public string ConnectionString { get; }
public DbContextOptions<BloggingContext> Options { get; }

public BenchmarkFixture()
{
DbPath = Path.Combine(
Path.GetTempPath(),
$"dime-repo-bench-{Guid.NewGuid():N}.db");
ConnectionString = $"Data Source={DbPath}";

Options = new DbContextOptionsBuilder<BloggingContext>()
.UseSqlite(ConnectionString)
.Options;

Seed();
}

private void Seed()
{
using BloggingContext context = new(Options);
context.Database.EnsureDeleted();
context.Database.EnsureCreated();

// Disable change tracking on the seed context — pure inserts.
context.ChangeTracker.AutoDetectChangesEnabled = false;

List<Blog> blogs = new(BlogCount);
for (int i = 0; i < BlogCount; i++)
blogs.Add(new Blog { Url = $"http://sample.com/blog/{i}" });

context.Blogs.AddRange(blogs);
context.SaveChanges();

// Seed Posts and Tags in batches to keep memory bounded.
const int batchSize = 200;
for (int start = 0; start < blogs.Count; start += batchSize)
{
int end = Math.Min(start + batchSize, blogs.Count);

for (int i = start; i < end; i++)
{
int blogId = blogs[i].BlogId;

for (int p = 0; p < PostsPerBlog; p++)
{
context.Posts.Add(new Post
{
BlogId = blogId,
Title = $"Post {p} for blog {blogId}",
Content = $"Content body for post {p} of blog {blogId}. Padded so allocations are noticeable."
});
}

for (int t = 0; t < TagsPerBlog; t++)
{
context.Tags.Add(new Tag
{
BlogId = blogId,
Name = $"tag-{t}-blog-{blogId}"
});
}
}

context.SaveChanges();
context.ChangeTracker.Clear();
}
}

public BloggingContext CreateContext() => new(Options);

public void Dispose()
{
try
{
// Force SQLite to release the file handle on Windows.
Microsoft.Data.Sqlite.SqliteConnection.ClearAllPools();
if (File.Exists(DbPath))
File.Delete(DbPath);
}
catch
{
// Best-effort cleanup; benchmark teardown shouldn't fail the run.
}
}
}
}
59 changes: 59 additions & 0 deletions src/benchmarks/Dime.Repositories.Benchmarks/BloggingContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore;

namespace Dime.Repositories.Benchmarks
{
/// <summary>
/// Copy of the Blog/Post/Tag model used in the unit tests. Duplicated here so the benchmark
/// project doesn't take a dependency on the test project (which carries MSTest/Test SDK and
/// would otherwise pollute the benchmark's runtime/build). Keep the schema in sync with
/// <c>src/test/Dime.Repositories.Sql.EntityFramework.Tests/Helpers/BloggingContext.cs</c>.
/// </summary>
public class BloggingContext : DbContext
{
public BloggingContext()
{ }

public BloggingContext(DbContextOptions<BloggingContext> options)
: base(options)
{ }

public DbSet<Blog> Blogs { get; set; }
public DbSet<Post> Posts { get; set; }
public DbSet<Tag> Tags { get; set; }

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>().HasKey(c => c.BlogId);
modelBuilder.Entity<Tag>().HasKey(c => c.TagId);
}
}

public class Blog
{
public int BlogId { get; set; }
public string Url { get; set; }

public List<Post> Posts { get; set; }
public List<Tag> Tags { get; set; }
}

public class Post
{
public int PostId { get; set; }
public string Title { get; set; }
public string Content { get; set; }

public int BlogId { get; set; }
public Blog Blog { get; set; }
}

public class Tag
{
public int TagId { get; set; }
public string Name { get; set; }

public int BlogId { get; set; }
public Blog Blog { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>latest</LangVersion>
<Nullable>disable</Nullable>
<IsPackable>false</IsPackable>
<!-- Belt-and-braces: ensure this project never shows up in `dotnet test` discovery. -->
<IsTestProject>false</IsTestProject>
<ServerGarbageCollection>true</ServerGarbageCollection>
<ConcurrentGarbageCollection>true</ConcurrentGarbageCollection>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.15.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\providers\EntityFramework\Dime.Repositories.Sql.EntityFramework.csproj" />
</ItemGroup>

</Project>
10 changes: 10 additions & 0 deletions src/benchmarks/Dime.Repositories.Benchmarks/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using BenchmarkDotNet.Running;

namespace Dime.Repositories.Benchmarks
{
public static class Program
{
public static void Main(string[] args)
=> BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args);
}
}
Loading
Loading