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
141 changes: 139 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,15 @@ on:
branches: [ main ]
workflow_dispatch:
inputs:
reason:
description: 'Reason'
reason:
description: 'Reason'
required: false
default: 'Manual run'

concurrency:
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}
cancel-in-progress: true

env:
DOTNET_NOLOGO: true
INCLUDE_SYMBOLS: true
Expand Down Expand Up @@ -118,6 +122,139 @@ jobs:
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

# Cross-OS build + unit test matrix (#191).
#
# This is deliberately ADDITIVE to `pr-checks` above, not a replacement for it:
# - `pr-checks` (ubuntu-only) remains the merge-gating job and is the only job that runs the
# FULL test suite, including Testcontainers/live-DB/real-efcpt integration tests, and
# publishes code coverage. Its job id/name is unchanged.
# - `build-test-matrix` proves the library actually builds and its non-integration unit/logic
# test suite actually passes on windows-latest and macos-latest, not just ubuntu - catching
# path-separator, line-ending, and other OS-specific behavior that an ubuntu-only pipeline
# can never surface.
# - Integration tests (Testcontainers, live cloud secrets, the real `efcpt` CLI, and the
# net472 Framework-MSBuild host tests) are excluded on EVERY leg here via
# `--filter "Category!=Integration"` - they require Docker with Linux containers and/or the
# efcpt tool and are not run on windows/macos GitHub-hosted runners. That coverage (including
# the net472 MSBuild-host task-loading path) stays on the ubuntu `pr-checks` job, which runs
# the full suite. See tests tagged `[Trait("Category", "Integration")]`.
build-test-matrix:
name: build-test-matrix
if: github.event_name == 'pull_request'
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
runs-on: ${{ matrix.os }}
timeout-minutes: 30
steps:
- name: Checkout
uses: actions/checkout@v7

- name: Setup .NET
uses: actions/setup-dotnet@v5
with:
dotnet-version: |
8.0.x
9.0.x
10.0.x

- name: Restore
run: dotnet restore JD.Efcpt.Build.sln --use-lock-file

- name: Build (Release)
run: dotnet build JD.Efcpt.Build.sln --configuration Release --no-restore /p:ContinuousIntegrationBuild=true

# Category!=Integration excludes Testcontainers/live-DB/real-efcpt tests (they require
# Docker with Linux containers and/or the erikej.efcorepowertools.cli global tool, neither
# of which is installed on this leg - those tests keep running on ubuntu via `pr-checks`).
- name: Test (excluding integration)
run: dotnet test JD.Efcpt.Build.sln --configuration Release --no-build --filter "Category!=Integration"

# Samples build (#191): proves the documentation samples under samples/ actually compile in CI,
# without a live database. Every sample is built with -p:EfcptEnabled=false, which fully
# short-circuits the JD.Efcpt.Build/JD.Efcpt.Sdk generation pipeline (see
# src/JD.Efcpt.Build/buildTransitive/JD.Efcpt.Build.props) so no DB connection, DACPAC query, or
# `efcpt` invocation ever happens - this job only proves the sample *projects* are well-formed
# and reference real, resolvable packages/SDKs, not that generation works end to end (that is
# covered by the integration tests in `pr-checks`).
samples-build:
name: samples-build
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Checkout
uses: actions/checkout@v7

- name: Setup .NET
uses: actions/setup-dotnet@v5
with:
dotnet-version: |
8.0.x
9.0.x
10.0.x

- name: Restore and build library
run: |
dotnet restore JD.Efcpt.Build.sln --use-lock-file
dotnet build JD.Efcpt.Build.sln --configuration Release --no-restore /p:ContinuousIntegrationBuild=true

# Packs the just-built library into ./artifacts (and ./pkg for the JD.Efcpt.Sdk MSBuild SDK),
# which each sample's nuget.config consumes as a local source. These packages carry the
# default 1.0.0 version, and samples reference them via Version="*" - this assumes the
# locally-packed 1.0.0 sorts at or above any version already published to nuget.org. Revisit
# this pin when a real >=1.0 release exists (a future 1.x on nuget.org would out-sort the
# local 1.0.0 and the samples could silently resolve the published package instead).
- name: Pack local NuGet feed consumed by samples
run: |
dotnet pack JD.Efcpt.Build.sln --configuration Release --no-build -o ./artifacts -p:ContinuousIntegrationBuild=true
mkdir -p pkg
cp artifacts/JD.Efcpt.Sdk.*.nupkg pkg/

# samples/Samples.sln aggregates a stale project reference (simple-sql-integration, removed
# from the repo) and cannot currently be restored as a whole; build each sample
# solution/project individually instead (see samples/README.md for what each demonstrates).
#
# Each restore passes the local packed feed EXPLICITLY via --source (which overrides the
# NuGet.config <packageSources> for the restore) so it never depends on a given sample's
# per-sample nuget.config resolving the right relative path. This is belt-and-suspenders with
# the samples/NuGet.config fix: e.g. sdk-zero-config's nuget.config has no <clear/> and used
# to inherit samples/NuGet.config's bogus "../packages" source -> NU1301. nuget.org is
# included for the samples' EF Core / Aspire / SQL SDK dependencies; the artifacts feed
# (repo root, populated by the Pack step above) supplies JD.Efcpt.Build / JD.Efcpt.Sdk.
- name: Build samples (EfcptEnabled=false - no live DB required)
shell: bash
run: |
set -euo pipefail
FEED="$GITHUB_WORKSPACE/artifacts"
declare -a SAMPLES=(
"samples/sdk-zero-config/SdkZeroConfigSample.sln"
"samples/dacpac-zero-config/ZeroConfigDacpac.sln"
"samples/microsoft-build-sql-zero-config/ZeroConfigMsBuildSql.sln"
"samples/simple-generation/SimpleGenerationSample.sln"
"samples/msbuild-sdk-sql-proj-generation/SimpleGenerationSample.sln"
"samples/schema-organization/SchemaOrganization.sln"
"samples/custom-renaming/CustomRenaming.sln"
"samples/database-first-sql-generation/DatabaseFirstSqlProj.sln"
"samples/connection-string-sqlite/ConnectionStringSqliteSample.sln"
"samples/connection-string-mssql/ConnectionStringMssql.sln"
"samples/aspnet-core-appsettings/AspNetCoreAppSettings.sln"
"samples/split-data-and-models-between-multiple-projects/SampleApp.slnx"
)
for sample in "${SAMPLES[@]}"; do
echo "::group::Build $sample"
dotnet restore "$sample" \
--source "https://api.nuget.org/v3/index.json" \
--source "$FEED"
dotnet build "$sample" --configuration Release --no-restore -p:EfcptEnabled=false
echo "::endgroup::"
done

# NOTE: samples/custom-provider/Acme.Efcpt.Mongo is intentionally NOT built here - it is
# a member of JD.Efcpt.Build.sln and is already compiled by the "Restore and build
# library" step above, so building it again would be redundant.

release:
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
Expand Down
11 changes: 11 additions & 0 deletions samples/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<Project>
<!--
Sample projects are stand-alone example solutions, not part of the library's shipped source
tree. Being the nearest Directory.Build.props to any project under samples/, this file takes
precedence over the repository root's Directory.Build.props, so samples do not inherit
repo-wide CI strictness (e.g. TreatWarningsAsErrors) that isn't relevant to example code.
-->
<PropertyGroup>
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
</PropertyGroup>
</Project>
12 changes: 12 additions & 0 deletions samples/Directory.Packages.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<Project>
<!--
Sample projects are stand-alone, documentation-oriented example solutions that pin their own
package versions inline (see each sample's *.csproj). They intentionally opt OUT of the
repository root's central package management (Directory.Packages.props) so that this file,
being the nearest Directory.Packages.props to any project under samples/, takes precedence and
disables CPM instead of inheriting the root's <PackageVersion> pins.
-->
<PropertyGroup>
<ManagePackageVersionsCentrally>false</ManagePackageVersionsCentrally>
</PropertyGroup>
</Project>
8 changes: 7 additions & 1 deletion samples/NuGet.config
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@
<configuration>
<packageSources>
<clear />
<add key="local-packages" value="../packages" />
<!--
Local feed for samples that don't define their own <clear/>'d nuget.config (e.g.
sdk-zero-config, connection-string-sqlite): points at the repo-root artifacts/ folder the CI
samples-build job packs into. Was previously "../packages" (a directory that never exists),
which caused inheriting samples to fail restore with NU1301.
-->
<add key="local-packages" value="../artifacts" />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
</packageSources>
</configuration>
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>

Expand Down
2 changes: 1 addition & 1 deletion samples/database-first-sql-generation/nuget.config
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<configuration>
<packageSources>
<clear />
<add key="local-dev" value="C:\tmp\local-packages" />
<add key="local-dev" value="../../artifacts" />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
</packageSources>
</configuration>
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
-->
<Project Sdk="JD.Efcpt.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,7 @@ await Given("a context whose client returns an empty value", () => BuildContext(
#region Real Endpoint (skipped by default)

[SkippableFact]
[Trait("Category", "Integration")]
public void Real_secrets_manager_endpoint_resolves_secret()
{
var secretId = Environment.GetEnvironmentVariable("EFCPT_TEST_AWS_SECRET_ID");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,7 @@ await Given("a context whose client returns an empty value", () => BuildContext(
#region Real Endpoint (skipped by default)

[SkippableFact]
[Trait("Category", "Integration")]
public void Real_key_vault_endpoint_resolves_secret()
{
var vaultUri = Environment.GetEnvironmentVariable("EFCPT_TEST_AZURE_KEYVAULT_URI");
Expand Down
1 change: 1 addition & 0 deletions tests/JD.Efcpt.Build.Tests/DirectDacpacTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,7 @@ await Given("pre-built DACPAC file", SetupWithPrebuiltDacpac)

[Scenario("Pipeline succeeds with real efcpt using direct DACPAC")]
[Fact]
[Trait("Category", "Integration")]
public async Task Pipeline_succeeds_with_direct_dacpac_real_efcpt()
{
await Given("pre-built DACPAC file", SetupWithPrebuiltDacpac)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ namespace JD.Efcpt.Build.Tests.Integration;

[Feature("End-to-End Reverse Engineering: generates and compiles EF models from SQL Server using Testcontainers")]
[Collection(nameof(AssemblySetup))]
[Trait("Category", "Integration")]
public sealed partial class EndToEndReverseEngineeringTests(ITestOutputHelper output) : TinyBddXunitBase(output)
{
private sealed record TestContext(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ namespace JD.Efcpt.Build.Tests.Integration;
/// </summary>
[Feature("FirebirdSchemaReader: reads and fingerprints Firebird schema using Testcontainers")]
[Collection(nameof(AssemblySetup))]
[Trait("Category", "Integration")]
public sealed partial class FirebirdSchemaIntegrationTests(ITestOutputHelper output) : TinyBddXunitBase(output)
{
private sealed record TestContext(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ namespace JD.Efcpt.Build.Tests.Integration;

[Feature("MySqlSchemaReader: reads and fingerprints MySQL schema using Testcontainers")]
[Collection(nameof(AssemblySetup))]
[Trait("Category", "Integration")]
public sealed partial class MySqlSchemaIntegrationTests(ITestOutputHelper output) : TinyBddXunitBase(output)
{
private sealed record TestContext(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ namespace JD.Efcpt.Build.Tests.Integration;
/// </remarks>
[Feature("OracleSchemaReader: reads and fingerprints Oracle schema using Testcontainers")]
[Collection(nameof(AssemblySetup))]
[Trait("Category", "Integration")]
public sealed partial class OracleSchemaIntegrationTests(ITestOutputHelper output) : TinyBddXunitBase(output)
{
private sealed record TestContext(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ namespace JD.Efcpt.Build.Tests.Integration;

[Feature("PostgreSqlSchemaReader: reads and fingerprints PostgreSQL schema using Testcontainers")]
[Collection(nameof(AssemblySetup))]
[Trait("Category", "Integration")]
public sealed partial class PostgreSqlSchemaIntegrationTests(ITestOutputHelper output) : TinyBddXunitBase(output)
{
private sealed record TestContext(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ namespace JD.Efcpt.Build.Tests.Integration;

[Feature("QuerySchemaMetadata task: queries real SQL Server database schema")]
[Collection(nameof(AssemblySetup))]
[Trait("Category", "Integration")]
public sealed partial class QuerySchemaMetadataIntegrationTests(ITestOutputHelper output) : TinyBddXunitBase(output)
{
private sealed record TestContext(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ namespace JD.Efcpt.Build.Tests.Integration;
/// </remarks>
[Feature("SnowflakeSchemaReader: reads and fingerprints Snowflake schema using LocalStack")]
[Collection(nameof(AssemblySetup))]
[Trait("Category", "Integration")]
public sealed partial class SnowflakeSchemaIntegrationTests(ITestOutputHelper output) : TinyBddXunitBase(output)
{
private static readonly string? LocalStackAuthToken =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ namespace JD.Efcpt.Build.Tests.Integration;

[Feature("SqlServerSchemaReader: reads and fingerprints SQL Server schema using Testcontainers")]
[Collection(nameof(AssemblySetup))]
[Trait("Category", "Integration")]
public sealed partial class SqlServerSchemaIntegrationTests(ITestOutputHelper output) : TinyBddXunitBase(output)
{
private sealed record TestContext(
Expand Down
1 change: 1 addition & 0 deletions tests/JD.Efcpt.Build.Tests/PipelineTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,7 @@ await Given("folders with existing dacpac", () => SetupWithExistingDacpac(SetupF

[Scenario("End-to-end builds real dacpac and runs real efcpt CLI")]
[Fact]
[Trait("Category", "Integration")]
public Task End_to_end_generates_dacpac_and_runs_real_efcpt()
=> Given("folders setup", SetupFolders)
.When("resolve inputs", ResolveInputs)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ namespace JD.Efcpt.Sdk.IntegrationTests;
/// Detailed tests for code generation output.
/// </summary>
[Collection("Code Generation Tests")]
[Trait("Category", "Integration")]
public class CodeGenerationTests : IDisposable
{
private readonly SdkPackageTestFixture _fixture;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ namespace JD.Efcpt.Sdk.IntegrationTests;
/// unless the user opts back in via EfcptRunDuringDesignTimeBuild=true.
/// </summary>
[Collection("Design-Time Build Tests")]
[Trait("Category", "Integration")]
public class DesignTimeBuildTests : IDisposable
{
private const string SkippedMessage = "[Efcpt] Skipping EF Core Power Tools generation pipeline";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ namespace JD.Efcpt.Sdk.IntegrationTests;
/// fallback mechanism - this is the primary validation that VS builds work.
/// </remarks>
[Collection("Framework MSBuild Tests")]
[Trait("Category", "Integration")]
public class FrameworkMsBuildTests : IDisposable
{
private readonly SdkPackageTestFixture _fixture;
Expand Down
Loading
Loading