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
13 changes: 8 additions & 5 deletions docs/engineering/testing-style.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,15 +64,18 @@ sized to need:
like a BCL type shadows it there: tests under `Debug/` declare `OpenCode.Sdk.Tests.Debug` and
qualify `System.Diagnostics.Debug`. A guard test refuses two source types that project onto
one test path.
- In SDK.Tests, a helper or fixture used by one slice lives in that slice's `Support/` or
`Fixtures/`. Helpers and fixtures used by several slices stay in the test project's root
`Support/` and `Fixtures/`. Embedded fixtures retain their `Fixtures.` resource-name prefix
through explicit `LogicalName` metadata. Live-server harness code lives in
- In SDK.Tests and Tools.Tests, a helper or fixture used by one slice lives in that slice's
`Support/` or `Fixtures/`. Helpers and fixtures used by several slices stay in the test project's
root `Support/` and `Fixtures/`. Tools.Tests also keeps its Roslyn compiler helpers
(`GeneratedSourceCompiler` and its closure) in the root `Support/`, whichever slices use them
today. Embedded fixtures retain their `Fixtures.` resource-name prefix through explicit
`LogicalName` metadata. Live-server harness code lives in
`tests/OpenCode.Sdk.Tests/Harness/`. Files another test project links, and the
embedded-fixture loader, stay in `tests/Shared/`, compiled through explicit links. There is no
separate test-support project.
- The mirror rule also applies to test infrastructure: `Harness/X.cs` is tested in
`Harness/XTests.cs`, and `Support/X.cs` is tested in `Support/XTests.cs`. A subject with
`Harness/XTests.cs`, and `Support/X.cs` is tested in `Support/XTests.cs`, including the
`Support/` folder in Tools.Tests. A subject with
several test files keeps those files beside the subject.

## 2. Filesystem rule
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using System.Text.Json;
using System.Text.Json.Nodes;
using OpenCode.Sdk.Tools.Clones;
using OpenCode.Sdk.Tools.Tests.Clones.Support;
using OpenCode.Sdk.Tools.Tests.Support;

namespace OpenCode.Sdk.Tools.Tests.Clones;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
using Testably.Abstractions.Testing;
using Testably.Abstractions.Testing.Initializer;

namespace OpenCode.Sdk.Tools.Tests.Benchmarks;
namespace OpenCode.Sdk.Tools.Tests.Commands;

public sealed class CompareBenchmarksCommandTests
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
using Testably.Abstractions.Testing;
using Testably.Abstractions.Testing.Initializer;

namespace OpenCode.Sdk.Tools.Tests.TestRoster.Support;
namespace OpenCode.Sdk.Tools.Tests.Commands.Support;

/// <summary>A verify-test-roster scenario: the committed roster file plus one TRX per module that
/// ran. Roster legs and TRX files are declared in domain terms; the JSON and XML live here.</summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using System.Globalization;

namespace OpenCode.Sdk.Tools.Tests.TestRoster.Support;
namespace OpenCode.Sdk.Tools.Tests.Commands.Support;

/// <summary>Builds the minimal TRX documents the reader consumes: a TestRun whose
/// ResultSummary/Counters report the given totals.</summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using System.IO.Abstractions;
using Microsoft.Extensions.DependencyInjection;
using OpenCode.Sdk.Tools.Generator.Refresh.Abstractions;
using OpenCode.Sdk.Tools.Tests.Clones.Support;
using OpenCode.Sdk.Tools.Tests.Support;
using Spectre.Console;
using Spectre.Console.Cli.Testing;
using Spectre.Console.Testing;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
using System.Text.Json.Nodes;
using Microsoft.Extensions.DependencyInjection;
using OpenCode.Sdk.Tools.TestRoster;
using OpenCode.Sdk.Tools.Tests.TestRoster.Support;
using OpenCode.Sdk.Tools.Tests.Commands.Support;
using Spectre.Console;
using Spectre.Console.Cli.Testing;
using Spectre.Console.Testing;
using Testably.Abstractions.Testing;

namespace OpenCode.Sdk.Tools.Tests.TestRoster;
namespace OpenCode.Sdk.Tools.Tests.Commands;

public sealed class VerifyTestRosterCommandTests
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using OpenCode.Sdk.Tools.Generator.Binding;
using OpenCode.Sdk.Tools.Generator.Binding.Models;
using static OpenCode.Sdk.Tools.Tests.Support.OwnershipPlanData;
using static OpenCode.Sdk.Tools.Tests.Generator.Binding.Support.OwnershipPlanData;

namespace OpenCode.Sdk.Tools.Tests.Generator.Binding;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using OpenCode.Sdk.Tools.Generator.Binding;
using OpenCode.Sdk.Tools.Generator.Binding.Models;
using OpenCode.Sdk.Tools.Tests.Support;
using static OpenCode.Sdk.Tools.Tests.Support.OwnershipPlanData;
using static OpenCode.Sdk.Tools.Tests.Generator.Binding.Support.OwnershipPlanData;

namespace OpenCode.Sdk.Tools.Tests.Generator.Binding;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
using OpenCode.Sdk.Tools.Generator.Binding;
using OpenCode.Sdk.Tools.Generator.Binding.Models;
using OpenCode.Sdk.Tools.Tests.Generator.Binding.Support;
using TUnit.Assertions.Enums;
using static OpenCode.Sdk.Tools.Tests.Generator.Binding.Support.OwnershipPlanData;
using static OpenCode.Sdk.Tools.Tests.Support.BindingScenarioData;
using static OpenCode.Sdk.Tools.Tests.Support.OwnershipPlanData;

namespace OpenCode.Sdk.Tools.Tests.Generator.Binding;

Expand Down Expand Up @@ -73,7 +74,7 @@ public async Task Place_Should_Place_A_Model_By_A_Group_Prefix_Alias()
{
var errors = new BindingErrorCollector();

var placement = Place(errors, Models(ModelsDevRefreshed), Ownership(FamiliesWithModelAlias()));
var placement = Place(errors, OwnershipPlanData.Models(ModelsDevRefreshed), Ownership(FamiliesWithModelAlias()));

await Assert.That(errors.Count).IsEqualTo(0);
await Assert.That(placement.HomeOf(ModelsDevRefreshed)).IsEqualTo(TypeHome.Family(LanguageModels));
Expand All @@ -87,7 +88,7 @@ public async Task Place_Should_Refuse_An_Owner_Row_That_Confirms_A_Family_Prefix
{
var errors = new BindingErrorCollector();

var placement = Place(errors, Models(model),
var placement = Place(errors, OwnershipPlanData.Models(model),
Ownership(prefixAlias ? FamiliesWithModelAlias() : Families(), owners: [Owner(match, family)]));

var exception = Assert.Throws<BindingException>(errors.ThrowIfAny);
Expand All @@ -102,7 +103,7 @@ public async Task Place_Should_Refuse_A_Prefix_Alias_When_An_Owner_Row_Places_It
{
var errors = new BindingErrorCollector();

var placement = Place(errors, Models(ModelsDevRefreshed),
var placement = Place(errors, OwnershipPlanData.Models(ModelsDevRefreshed),
Ownership(FamiliesWithModelAlias(), owners: [Owner("ModelsDev", OtherFamily)]));

await Assert.That(placement.HomeOf(ModelsDevRefreshed)).IsEqualTo(TypeHome.Family(OtherFamily));
Expand Down Expand Up @@ -138,7 +139,7 @@ public async Task Place_Should_Refuse_A_Prefix_Alias_That_Another_Family_Already
{
var errors = new BindingErrorCollector();

_ = Place(errors, Models(SessionInfo), Ownership(FamiliesWithModelAlias(SessionAlias)));
_ = Place(errors, OwnershipPlanData.Models(SessionInfo), Ownership(FamiliesWithModelAlias(SessionAlias)));

await AssertRefusedAsync(errors, SessionAlias, $"the name prefix places models in both '{LanguageModels}' and '{Sessions}'");
}
Expand All @@ -149,7 +150,7 @@ public async Task Place_Should_Refuse_A_Prefix_Alias_That_Repeats_A_Prefix_Of_It
{
var errors = new BindingErrorCollector();

_ = Place(errors, Models(ModelInfo), Ownership(FamiliesWithModelAlias(ModelAlias)));
_ = Place(errors, OwnershipPlanData.Models(ModelInfo), Ownership(FamiliesWithModelAlias(ModelAlias)));

await AssertRefusedAsync(errors, ModelAlias, $"the prefix alias repeats a prefix family '{LanguageModels}' already has");
}
Expand All @@ -163,7 +164,7 @@ public async Task Place_Should_Prefer_The_Longest_Matching_Family_Prefix()
{
var errors = new BindingErrorCollector();

var placement = Place(errors, Models("PersistentPtyInfo", "PersistentStore"), Ownership(PersistentFamilies()));
var placement = Place(errors, OwnershipPlanData.Models("PersistentPtyInfo", "PersistentStore"), Ownership(PersistentFamilies()));

await Assert.That(errors.Count).IsEqualTo(0);
await Assert.That(placement.HomeOf("PersistentPtyInfo")).IsEqualTo(TypeHome.Family("PersistentPtys"));
Expand All @@ -175,7 +176,7 @@ public async Task Place_Should_Not_Match_A_Family_Prefix_When_The_Next_Character
{
var errors = new BindingErrorCollector();

var placement = Place(errors, Models("FormInfo", "FormatInfo"));
var placement = Place(errors, OwnershipPlanData.Models("FormInfo", "FormatInfo"));

await Assert.That(placement.HomeOf("FormInfo")).IsEqualTo(TypeHome.Family("Forms"));
await Assert.That(placement.Unplaced).IsEquivalentTo(["FormatInfo"]);
Expand All @@ -186,7 +187,7 @@ public async Task Place_Should_Match_A_Family_Prefix_Ignoring_Its_Case()
{
var errors = new BindingErrorCollector();

var placement = Place(errors, Models("WebSearchResult"));
var placement = Place(errors, OwnershipPlanData.Models("WebSearchResult"));

await Assert.That(errors.Count).IsEqualTo(0);
await Assert.That(placement.HomeOf("WebSearchResult")).IsEqualTo(TypeHome.Family("Websearch"));
Expand Down Expand Up @@ -236,7 +237,7 @@ public async Task Place_Should_Place_A_Body_Request_With_The_Family_That_Sends_I
{
var errors = new BindingErrorCollector();

var placement = Place(errors, Models(SessionLoginRequest), bodyOperations: [BodyOperation(Credentials, SessionLoginRequest)]);
var placement = Place(errors, OwnershipPlanData.Models(SessionLoginRequest), bodyOperations: [BodyOperation(Credentials, SessionLoginRequest)]);

await Assert.That(errors.Count).IsEqualTo(0);
await Assert.That(placement.HomeOf(SessionLoginRequest)).IsEqualTo(TypeHome.Family(Credentials));
Expand All @@ -248,7 +249,7 @@ public async Task Place_Should_Place_A_Binary_Body_By_Its_Name_And_Not_List_It_A
{
var errors = new BindingErrorCollector();

var placement = Place(errors, Models("SessionUpload"), bodyOperations: [BodyOperation(Credentials, "SessionUpload", isBinary: true)]);
var placement = Place(errors, OwnershipPlanData.Models("SessionUpload"), bodyOperations: [BodyOperation(Credentials, "SessionUpload", isBinary: true)]);

await Assert.That(errors.Count).IsEqualTo(0);
await Assert.That(placement.HomeOf("SessionUpload")).IsEqualTo(TypeHome.Family(Sessions));
Expand All @@ -260,7 +261,7 @@ public async Task Place_Should_Refuse_A_Body_Request_When_Two_Families_Send_It()
{
var errors = new BindingErrorCollector();

_ = Place(errors, Models(SessionLoginRequest),
_ = Place(errors, OwnershipPlanData.Models(SessionLoginRequest),
bodyOperations: [BodyOperation(Credentials, SessionLoginRequest), BodyOperation(McpServers, SessionLoginRequest)]);

await AssertRefusedAsync(errors, SessionLoginRequest, "has no single family root");
Expand All @@ -273,7 +274,7 @@ public async Task Place_Should_Place_An_Unprefixed_Model_By_Its_Owner_Row(string
{
var errors = new BindingErrorCollector();

var placement = Place(errors, Models(model), Ownership(owners: [Owner(match, owner)]));
var placement = Place(errors, OwnershipPlanData.Models(model), Ownership(owners: [Owner(match, owner)]));

await Assert.That(errors.Count).IsEqualTo(0);
await Assert.That(placement.HomeOf(model)).IsEqualTo(TypeHome.Family(owner));
Expand All @@ -284,7 +285,7 @@ public async Task Place_Should_Prefer_An_Owner_Row_Over_A_Family_Prefix()
{
var errors = new BindingErrorCollector();

var placement = Place(errors, Models("SessionError"), Ownership(owners: [Owner("SessionError", TypeHome.CommonSlice)]));
var placement = Place(errors, OwnershipPlanData.Models("SessionError"), Ownership(owners: [Owner("SessionError", TypeHome.CommonSlice)]));

await Assert.That(errors.Count).IsEqualTo(0);
await Assert.That(placement.HomeOf("SessionError")).IsEqualTo(TypeHome.Family(TypeHome.CommonSlice));
Expand All @@ -295,7 +296,7 @@ public async Task Place_Should_Refuse_A_Model_When_Two_Owner_Rows_Match_It()
{
var errors = new BindingErrorCollector();

_ = Place(errors, Models(ProviderAuthMethod), Ownership(owners: [Owner("Provider", Credentials), Owner(ProviderAuth, McpServers)]));
_ = Place(errors, OwnershipPlanData.Models(ProviderAuthMethod), Ownership(owners: [Owner("Provider", Credentials), Owner(ProviderAuth, McpServers)]));

await AssertRefusedAsync(errors, ProviderAuthMethod, "all match the model");
}
Expand All @@ -315,7 +316,7 @@ public async Task Place_Should_Refuse_An_Owner_Row_Whose_Owner_Is_Not_An_Admitte
{
var errors = new BindingErrorCollector();

_ = Place(errors, Models(ProviderAuthMethod), Ownership(owners: [Owner(ProviderAuth, "Providers")]));
_ = Place(errors, OwnershipPlanData.Models(ProviderAuthMethod), Ownership(owners: [Owner(ProviderAuth, "Providers")]));

await AssertRefusedAsync(errors, ProviderAuth, "is neither an admitted family nor 'Common'");
}
Expand All @@ -325,7 +326,7 @@ public async Task Place_Should_Refuse_An_Owner_Row_Without_A_Reason()
{
var errors = new BindingErrorCollector();

_ = Place(errors, Models(ProviderAuthMethod), Ownership(owners: [Owner(ProviderAuth, Credentials, reason: BlankReason)]));
_ = Place(errors, OwnershipPlanData.Models(ProviderAuthMethod), Ownership(owners: [Owner(ProviderAuth, Credentials, reason: BlankReason)]));

await AssertRefusedAsync(errors, ProviderAuth, "must declare a reason");
}
Expand All @@ -335,7 +336,7 @@ public async Task Place_Should_List_An_Unprefixed_Model_As_Unplaced_When_No_Owne
{
var errors = new BindingErrorCollector();

var placement = Place(errors, Models("OutputFormat"));
var placement = Place(errors, OwnershipPlanData.Models("OutputFormat"));

await Assert.That(errors.Count).IsEqualTo(0);
await Assert.That(placement.Homes.ContainsKey("OutputFormat")).IsFalse();
Expand All @@ -350,7 +351,7 @@ public async Task Place_Should_Give_A_Sessions_Model_And_Its_Companions_The_Area
{
var errors = new BindingErrorCollector();

var placement = Place(errors, Models(SessionCreated, "SessionCreatedData", "SessionCreatedDurable"),
var placement = Place(errors, OwnershipPlanData.Models(SessionCreated, "SessionCreatedData", "SessionCreatedDurable"),
Ownership(areas: [Area(Lifecycle, SessionCreated)]));

await Assert.That(errors.Count).IsEqualTo(0);
Expand All @@ -362,7 +363,7 @@ public async Task Place_Should_Leave_The_Area_Empty_When_A_Sessions_Model_Matche
{
var errors = new BindingErrorCollector();

var placement = Place(errors, Models(SessionCreated, SessionInfo), Ownership(areas: [Area(Lifecycle, SessionCreated)]));
var placement = Place(errors, OwnershipPlanData.Models(SessionCreated, SessionInfo), Ownership(areas: [Area(Lifecycle, SessionCreated)]));

await Assert.That(errors.Count).IsEqualTo(0);
await Assert.That(placement.HomeOf(SessionInfo)).IsEqualTo(TypeHome.Family(Sessions));
Expand All @@ -373,7 +374,7 @@ public async Task Place_Should_Not_Match_An_Area_Pattern_When_The_Next_Character
{
var errors = new BindingErrorCollector();

var placement = Place(errors, Models("SessionForkBoundary", "SessionForked"), Ownership(areas: [Area("Fork", "SessionFork")]));
var placement = Place(errors, OwnershipPlanData.Models("SessionForkBoundary", "SessionForked"), Ownership(areas: [Area("Fork", "SessionFork")]));

await Assert.That(errors.Count).IsEqualTo(0);
await Assert.That(placement.HomeOf("SessionForkBoundary")).IsEqualTo(SessionsArea("Fork"));
Expand All @@ -385,7 +386,7 @@ public async Task Place_Should_Not_Give_An_Area_When_Another_Family_Owns_The_Mod
{
var errors = new BindingErrorCollector();

var placement = Place(errors, Models("ToolPart", "ToolDefinition"),
var placement = Place(errors, OwnershipPlanData.Models("ToolPart", "ToolDefinition"),
Ownership(owners: [Owner("ToolPart", Sessions), Owner("ToolDefinition", McpServers)], areas: [Area("Turn", "Tool")]));

await Assert.That(errors.Count).IsEqualTo(0);
Expand All @@ -398,7 +399,7 @@ public async Task Place_Should_Not_Give_An_Area_To_A_Body_Request()
{
var errors = new BindingErrorCollector();

var placement = Place(errors, Models("SessionCreateRequest", "SessionCreateOptions"), Ownership(areas: [Area("Create", "SessionCreate")]),
var placement = Place(errors, OwnershipPlanData.Models("SessionCreateRequest", "SessionCreateOptions"), Ownership(areas: [Area("Create", "SessionCreate")]),
bodyOperations: [BodyOperation(Sessions, "SessionCreateRequest")]);

await Assert.That(errors.Count).IsEqualTo(0);
Expand All @@ -411,7 +412,7 @@ public async Task Place_Should_Refuse_A_Model_When_Two_Areas_Match_It()
{
var errors = new BindingErrorCollector();

_ = Place(errors, Models(SessionCreated), Ownership(areas: [Area(Lifecycle, SessionCreated), Area("Events", "Session")]));
_ = Place(errors, OwnershipPlanData.Models(SessionCreated), Ownership(areas: [Area(Lifecycle, SessionCreated), Area("Events", "Session")]));

await AssertRefusedAsync(errors, SessionCreated, "place the model in more than one area");
}
Expand All @@ -421,7 +422,7 @@ public async Task Place_Should_Refuse_An_Area_Pattern_That_Places_No_Model()
{
var errors = new BindingErrorCollector();

_ = Place(errors, Models(SessionCreated), Ownership(areas: [Area(Lifecycle, SessionCreated, "SessionMoved")]));
_ = Place(errors, OwnershipPlanData.Models(SessionCreated), Ownership(areas: [Area(Lifecycle, SessionCreated, "SessionMoved")]));

await AssertRefusedAsync(errors, $"{Lifecycle}/SessionMoved", "session area pattern places no generated model");
}
Expand All @@ -437,7 +438,7 @@ public async Task Place_Should_Refuse_A_Reserved_Area_Name(string area)
{
var errors = new BindingErrorCollector();

_ = Place(errors, Models(SessionCreated), Ownership(areas: [Area(area, SessionCreated)]));
_ = Place(errors, OwnershipPlanData.Models(SessionCreated), Ownership(areas: [Area(area, SessionCreated)]));

await AssertRefusedAsync(errors, area, "reserved");
}
Expand All @@ -447,7 +448,7 @@ public async Task Place_Should_Refuse_An_Area_Row_Without_A_Reason()
{
var errors = new BindingErrorCollector();

_ = Place(errors, Models(SessionCreated), Ownership(areas: [Area(Lifecycle, SessionCreated) with { Reason = BlankReason }]));
_ = Place(errors, OwnershipPlanData.Models(SessionCreated), Ownership(areas: [Area(Lifecycle, SessionCreated) with { Reason = BlankReason }]));

await AssertRefusedAsync(errors, Lifecycle, "session area curation must declare a reason");
}
Expand All @@ -459,7 +460,7 @@ public async Task Place_Should_Refuse_Area_Rows_When_No_Sessions_Family_Is_Admit
var groups = Families();
_ = groups.Remove("session");

_ = Place(errors, Models(SessionCreated), Ownership(groups, areas: [Area(Lifecycle, SessionCreated)]));
_ = Place(errors, OwnershipPlanData.Models(SessionCreated), Ownership(groups, areas: [Area(Lifecycle, SessionCreated)]));

await AssertRefusedAsync(errors, "sessionAreas", "session areas need the 'Sessions' family");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
using OpenCode.Sdk.Tools.Generator.Binding.Models;
using OpenCode.Sdk.Tools.Generator.Ingestion.Models;
using OpenCode.Sdk.Tools.Tests.Support;
using static OpenCode.Sdk.Tools.Tests.Generator.Binding.Support.OwnershipPlanData;
using static OpenCode.Sdk.Tools.Tests.Support.BindingScenarioData;
using static OpenCode.Sdk.Tools.Tests.Support.OwnershipPlanData;

namespace OpenCode.Sdk.Tools.Tests.Generator.Binding;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
using OpenCode.Sdk.Tools.Tests.Support;
using static OpenCode.Sdk.Tools.Tests.Support.BindingScenarioData;

namespace OpenCode.Sdk.Tools.Tests.Generator.Binding;
namespace OpenCode.Sdk.Tools.Tests.Generator.Binding.Models;

/// <summary>
/// The <c>enumMemberNames</c> curation channel: a reason-bearing row names the C# member one
Expand Down
Loading
Loading