Skip to content

feat(testing): add IGrainStorage provider test kit - #10490

Merged
ReubenBond merged 12 commits into
dotnet:mainfrom
ReubenBond:rb-fix-pr-9742
Aug 12, 2026
Merged

ReubenBond merged 12 commits into
dotnet:mainfrom
ReubenBond:rb-fix-pr-9742

Conversation

@ReubenBond

@ReubenBond ReubenBond commented Aug 11, 2026 •

Copy link
Copy Markdown
Member

Problem

Orleans storage providers need a reusable public contract suite for validating IGrainStorage behavior. The original implementation in #9742 could no longer merge cleanly and its CI autobuild was broken by repository feed changes.

Solution

  • Add the Microsoft.Orleans.Persistence.TestKit package with reusable xUnit storage contract tests and an InProcessTestClusterBuilder fixture.
  • Cover read/write/clear lifecycles, optimistic concurrency, ETags, missing-state reset, key forms, state-name isolation, Cyrillic values, and parallel operations.
  • Exercise the kit against memory and Azure Table storage.
  • Integrate the package, tests, generated API baseline, package README, and current solution layout.

Rationale

A shared suite makes provider behavior consistent and gives third-party provider authors the same contract coverage as Orleans-owned implementations. Provider-specific exception subclasses and both delete-on-clear and tombstone-on-clear semantics remain supported.

Supersedes #9742.

Microsoft Reviewers: Open in CodeFlow

Copilot AI and others added 11 commits August 11, 2026 10:45
Co-authored-by: ReubenBond <203839+ReubenBond@users.noreply.github.com>
Co-authored-by: ReubenBond <203839+ReubenBond@users.noreply.github.com>
Co-authored-by: ReubenBond <203839+ReubenBond@users.noreply.github.com>
Co-authored-by: ReubenBond <203839+ReubenBond@users.noreply.github.com>
Co-authored-by: ReubenBond <203839+ReubenBond@users.noreply.github.com>
Co-authored-by: ReubenBond <203839+ReubenBond@users.noreply.github.com>
…scenarios

Co-authored-by: ReubenBond <203839+ReubenBond@users.noreply.github.com>
Co-authored-by: ReubenBond <203839+ReubenBond@users.noreply.github.com>
Rebase the test kit integration onto the current solution layout, restore repository package sources, and make the reusable storage contract tests compile and pass across supported target frameworks.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Accept provider-specific inconsistency exception subclasses and both deletion and tombstone ETag behavior when clearing missing state.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Require Orleans 10 or later in the package documentation.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI lite review requested due to automatic review settings August 12, 2026 15:03
@ReubenBond
ReubenBond marked this pull request as ready for review August 12, 2026 15:04
@ReubenBond
ReubenBond merged commit 3b40ddb into dotnet:main Aug 12, 2026
66 of 69 checks passed
@ReubenBond
ReubenBond deleted the rb-fix-pr-9742 branch August 12, 2026 15:04

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new reusable test-kit library to Orleans for validating IGrainStorage implementations via a shared xUnit contract suite, plus sample/adopter tests to exercise it against existing providers.

Changes:

  • Introduces Microsoft.Orleans.Persistence.TestKit with an InProcessTestClusterBuilder-based fixture and a base GrainStorageTestRunner contract suite.
  • Adds a new test project demonstrating the kit against MemoryGrainStorage.
  • Integrates the kit into existing Azure Table storage tests and solution/project references (including API baseline).
Show a summary per file
File Description
Orleans.slnx Adds the new test kit project and its tests to the solution.
src/Orleans.Persistence.TestKit/Orleans.Persistence.TestKit.csproj Defines the new public test kit package/project configuration.
src/Orleans.Persistence.TestKit/README.md Documents usage, available tests, and integration patterns for the kit.
src/Orleans.Persistence.TestKit/GrainStorageTestRunner.cs Implements the reusable IGrainStorage contract test suite and helpers.
src/Orleans.Persistence.TestKit/GrainStorageTestFixture.cs Provides an in-proc test cluster fixture to configure and resolve the storage provider under test.
src/Orleans.Persistence.TestKit/TestStates/TestState1.cs Adds a canonical test state type used by the contract suite.
src/api/Orleans.Persistence.TestKit/Orleans.Persistence.TestKit.cs Adds the generated public API baseline for the new package.
test/Orleans.Persistence.TestKit.Tests/Orleans.Persistence.TestKit.Tests.csproj Adds a new test project to validate/demo the kit.
test/Orleans.Persistence.TestKit.Tests/MemoryGrainStorageTests.cs Demonstrates and runs the contract suite against Memory grain storage.
test/Extensions/Orleans.Azure.Tests/Persistence/AzureTableGrainStorageTestKitTests.cs Runs the contract suite against Azure Table grain storage (skippable).
test/Extensions/Orleans.Azure.Tests/Orleans.Azure.Tests.csproj References the new test kit from the Azure test project.

Review details

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Suppressed comments (1)

src/Orleans.Persistence.TestKit/GrainStorageTestRunner.cs:96

  • Typo in local variable name: "recordExitsAfterClearing" should be "recordExistsAfterClearing".
        var recordExitsAfterClearing = grainState.RecordExists;
        Assert.False(recordExitsAfterClearing);
  • Files reviewed: 11/11 changed files
  • Comments generated: 7
  • Review effort level: Lite

<TargetFrameworks>$(DefaultTargetFrameworks)</TargetFrameworks>
<OrleansBuildTimeCodeGen>true</OrleansBuildTimeCodeGen>
<IsOrleansFrameworkPart>false</IsOrleansFrameworkPart>
<IsPackable>false</IsPackable>
Comment on lines +70 to +72
Assert.Equal(grainState.ETag, storedGrainState.ETag);
Assert.Equal(grainState.State, storedGrainState.State);
Assert.True(storedGrainState.RecordExists);
Comment on lines +88 to +89
var recordExitsAfterWriting = grainState.RecordExists;
Assert.True(recordExitsAfterWriting);
Comment on lines +98 to +101
var storedGrainState = new GrainState<T> { State = new T(), ETag = clearedStateVersion };
await Storage.WriteStateAsync(grainTypeName, grainId, storedGrainState).ConfigureAwait(false);
Assert.Equal(storedGrainState.State, Activator.CreateInstance<T>());
Assert.True(storedGrainState.RecordExists);
Comment on lines +193 to +199
int StartOfRange = 33900;
int CountOfRange = countOfGrains;

// Since the version is NULL, storage provider tries to insert this data as new state.
// If there is already data with this class, the writing fails and the storage provider throws.
var grainStates = Enumerable.Range(StartOfRange, CountOfRange)
.Select(i => GetTestReferenceAndState($"{prefix}-{Guid.NewGuid():N}-{i}", null))
Comment on lines +215 to +217
int MaxNumberOfThreads = Environment.ProcessorCount * 3;

await Parallel.ForEachAsync(grainStates, new ParallelOptions { MaxDegreeOfParallelism = MaxNumberOfThreads }, async (grainData, ct) =>
Comment on lines +102 to +103
var builder = new InProcessTestClusterBuilder();
builder.ConfigureSilo((_, siloBuilder) => ConfigureSilo(siloBuilder));
@github-actions github-actions Bot locked and limited conversation to collaborators Sep 12, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants