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
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ below exist because ordinary review does not catch it.
| Test class | Guards |
|---|---|
| `ChangelogConsistencyTests` | The changelog lives in four files (root README + one per package). Asserts the shipped version is documented, no README runs ahead of `Directory.Build.props`, package changelogs are a subset of the root's, and sections are newest-first. |
| `PackagingConventionTests` | Every package ships its own README as `PackageReadmeFile`; `.targets` ship to both `build/` and `buildTransitive/`, reference a real `IDesignTimeServices` in their own assembly, and set `ForProvider` on satellites but not on core. |
| `PackagingConventionTests` | Every package ships its own README as `PackageReadmeFile`; `.targets` ship to both `build/` and `buildTransitive/`, reference a real `IDesignTimeServices` in their own assembly, and set `ForProvider` on satellites but not on core. Package validation is enabled and its baseline is the shipped version or the release before it, never older — the baseline is what `dotnet pack` diffs the public surface against (CP0002 on a removed member), and one left behind stops seeing API added since it. |
| `ClaudeMdConsistencyTests` | This file. Prose cannot be asserted, so it checks the falsifiable parts: cited paths and file names exist, annotation keys under a prefix this repo owns are declared somewhere, `Type.Member` references resolve, and the stated size of the Npgsql whitelist matches it. Those are what a rename rots silently — and the count claim had already gone stale by two. |
| `BuilderApiParityTests` | Every key in a satellite's annotation whitelist is reachable from a builder method. `SqlServer:DataCompression` sat whitelisted with no API for a full release; this catches that class of drift by invoking every builder extension and diffing the keys it sets. |
| `SecurityPolicyConsistencyTests` | SECURITY.md's supported-versions table names the minor being shipped, and its `< x.y` row meets it. The table is prose a version bump forgets — the sibling repository shipped 6.2.0 with the table still saying 6.1.x. |
Expand Down
23 changes: 23 additions & 0 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,29 @@
a clean checkout where bin/ is empty, which is how CI found it.
-->

<!--
Every pack is checked against the last release for API breaks.

At pack time the SDK restores each package at the baseline version from nuget.org and runs
ApiCompat over the public surface — a removed or changed member fails the pack
(CP0001/CP0002/...), in CI's Pack job and in the release gate. Until now a removed public
member packed cleanly, and whether a release broke anyone rested on reading the diff.

The baseline is the last release. When Version is bumped for the next one it stays as the
release being superseded, and PackagingConventionTests requires it to be that release or the
shipped version — never older — so it moves with every bump instead of fossilising. For a
release that breaks on purpose: run
`dotnet pack -c Release /p:ApiCompatGenerateSuppressionFile=true`, read the generated
CompatibilitySuppressions.xml — each entry is one breaking change and belongs in the
changelog — commit it, and delete it after the release once the baseline moves past it. A
new package with no release yet sets PackageValidationBaselineVersion empty in its own
csproj until it has one.
-->
<PropertyGroup>
<EnablePackageValidation>true</EnablePackageValidation>
<PackageValidationBaselineVersion>5.0.3</PackageValidationBaselineVersion>
</PropertyGroup>

<!--
Debugging support for consumers.

Expand Down
39 changes: 39 additions & 0 deletions test/EFCore.ComplexIndexes.Tests/PackagingConventionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,45 @@ public void Sbom_filter_covers_every_private_reference()
+ "consumers never receive.");
}

/// <summary>
/// Package validation compares each pack against <c>PackageValidationBaselineVersion</c>. A
/// baseline left behind stops seeing API added since it — a member introduced in 5.1 and
/// removed in 5.2 is invisible to a 5.0 baseline — so it may be the version being shipped
/// (between releases, when <c>Version</c> is the last release) or the release directly before
/// it (once <c>Version</c> is bumped for the next one), and never older. That lags by at most
/// one release and forces the move at every version bump.
/// </summary>
[TestMethod(DisplayName = "The package-validation baseline is the shipped version or the release before it")]
public void Package_validation_baseline_is_the_shipped_version_or_the_release_before_it()
{
var props = XDocument.Load(RepositoryLayout.BuildProps);

Assert.AreEqual(
"true", props.Descendants("EnablePackageValidation").SingleOrDefault()?.Value.Trim(),
"Directory.Build.props does not enable package validation, so a removed public member "
+ "packs cleanly and whether a release breaks anyone rests on reading the diff.");

var shipped = Version.Parse(props.Descendants("Version").Single().Value);
var previous = ChangelogVersions().Where(version => version < shipped).DefaultIfEmpty().Max();
var baseline = props.Descendants("PackageValidationBaselineVersion").SingleOrDefault()?.Value.Trim();

Assert.IsNotNull(baseline, "Directory.Build.props sets no PackageValidationBaselineVersion.");

var allowed = new[] { shipped, previous }.Where(version => version is not null).Distinct().ToList();

Assert.IsTrue(
allowed.Contains(Version.Parse(baseline)),
$"PackageValidationBaselineVersion is {baseline}, but Directory.Build.props ships {shipped} "
+ $"and the release before it is {previous?.ToString() ?? "none"}. The baseline must be one of "
+ "those two — move it to the release just superseded when bumping Version, or API added "
+ "since the old baseline goes unvalidated.");
}

// The root README's "## What changed in x.y.z" headings — the same source ChangelogConsistencyTests reads.
private static IEnumerable<Version> ChangelogVersions() =>
Regex.Matches(File.ReadAllText(RepositoryLayout.RootReadme), @"^## What changed in (\d+\.\d+\.\d+)\s*$", RegexOptions.Multiline)
.Select(match => Version.Parse(match.Groups[1].Value));

private static string ReleaseWorkflow =>
Path.Combine(RepositoryLayout.Root, ".github", "workflows", "release.yml");

Expand Down
Loading