diff --git a/.fallout/build.schema.json b/.fallout/build.schema.json
index 8274ca869..fde15dc2b 100644
--- a/.fallout/build.schema.json
+++ b/.fallout/build.schema.json
@@ -148,6 +148,16 @@
"type": "string",
"default": "Secrets must be entered via 'fallout :secrets [profile]'"
},
+ "NuGetSource": {
+ "type": "string"
+ },
+ "PublishTo": {
+ "type": "array",
+ "description": "Publish only to these named targets (default: all configured PublishTargets)",
+ "items": {
+ "type": "string"
+ }
+ },
"Solution": {
"type": "string",
"description": "Path to a solution file that is automatically loaded"
diff --git a/AssemblyInfo.cs b/AssemblyInfo.cs
index c5e26e6d6..c7362d976 100644
--- a/AssemblyInfo.cs
+++ b/AssemblyInfo.cs
@@ -2,21 +2,22 @@
[assembly: InternalsVisibleTo("Fallout.Build")]
[assembly: InternalsVisibleTo("Fallout.Build.Shared")]
-[assembly: InternalsVisibleTo("Fallout.Build.Tests")]
+[assembly: InternalsVisibleTo("Fallout.Build.Specs")]
[assembly: InternalsVisibleTo("Fallout.Common")]
-[assembly: InternalsVisibleTo("Fallout.Common.Tests")]
+[assembly: InternalsVisibleTo("Fallout.Common.Specs")]
[assembly: InternalsVisibleTo("Fallout.Cli")]
-[assembly: InternalsVisibleTo("Fallout.Cli.Tests")]
-[assembly: InternalsVisibleTo("Fallout.ProjectModel.Tests")]
+[assembly: InternalsVisibleTo("Fallout.Cli.Specs")]
+[assembly: InternalsVisibleTo("Fallout.ProjectModel.Specs")]
[assembly: InternalsVisibleTo("Fallout.SourceGenerators")]
+[assembly: InternalsVisibleTo("Fallout.Solution.Codegen")]
[assembly: InternalsVisibleTo("Fallout.Solution")]
-[assembly: InternalsVisibleTo("Fallout.Solution.Tests")]
+[assembly: InternalsVisibleTo("Fallout.Solution.Specs")]
[assembly: InternalsVisibleTo("Fallout.Persistence.Solution")]
[assembly: InternalsVisibleTo("Fallout.Persistence.Solution.Tests")]
[assembly: InternalsVisibleTo("Fallout.Tooling")]
-[assembly: InternalsVisibleTo("Fallout.Tooling.Tests")]
+[assembly: InternalsVisibleTo("Fallout.Tooling.Specs")]
[assembly: InternalsVisibleTo("Fallout.Utilities.IO.Globbing")]
-[assembly: InternalsVisibleTo("Fallout.Utilities.Tests")]
+[assembly: InternalsVisibleTo("Fallout.Utilities.Specs")]
// External extensions — kept as Nuke.* until those projects rebrand independently.
[assembly: InternalsVisibleTo("Nuke.VisualStudio")]
diff --git a/Directory.Build.props b/Directory.Build.props
index c47fd492a..7365dbf02 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -22,7 +22,7 @@
README.md
-
+
False
@@ -53,7 +53,7 @@
-
+
diff --git a/build/Build.cs b/build/Build.cs
index fc884944a..fdf7dae5b 100644
--- a/build/Build.cs
+++ b/build/Build.cs
@@ -98,7 +98,7 @@ private static int ParseMajor(string informationalVersion)
from framework in project.GetTargetFrameworks()
select (project, framework);
- IEnumerable ITest.TestProjects => Partition.GetCurrent(Solution.GetAllProjects("*.Tests"));
+ IEnumerable ITest.TestProjects => Partition.GetCurrent(Solution.GetAllProjects("*.Specs"));
[Parameter]
public int TestDegreeOfParallelism { get; } = 1;
diff --git a/build/_build.csproj b/build/_build.csproj
index cee68e3d4..069426f46 100644
--- a/build/_build.csproj
+++ b/build/_build.csproj
@@ -14,6 +14,14 @@
false
+
+
+ Build
+ $(MSBuildProjectDirectory)\..\src\Fallout.Solution.Codegen\Fallout.Solution.Codegen.csproj
+ $(MSBuildProjectDirectory)\..
+
+
False
@@ -72,5 +80,6 @@
+
diff --git a/docs/agents/conventions.md b/docs/agents/conventions.md
index bd74b12ec..4bd698142 100644
--- a/docs/agents/conventions.md
+++ b/docs/agents/conventions.md
@@ -5,6 +5,8 @@ Three groups: conventions to respect, things never to do, and the tool-wrapper r
## Conventions worth respecting
- **Centralized package versions** — add new packages to `Directory.Packages.props`, never inline. Adding a *meaningful* library (not a tiny transitive helper)? Add a row to [docs/dependencies.md](https://github.com/Fallout-build/Fallout/blob/main/docs/dependencies.md) in the same PR — reviewers will ask.
+- **Test naming follows AV1600 behavior-focused style.** Write test names as short, present-tense descriptions of observable behavior, not method calls. Example good: `Missing_changelog_does_not_add_a_full_changelog_link_to_release_notes`. Example bad: `GetNuGetReleaseNotes_WithMissingChangelog_AndGitHubRepository_DoesNotThrow`. Focus on "what happens" not "what method is called" (see [AV1600](references/testability.md#AV1620)).
+- **Test files and classes use `Specs` suffix.** Name test projects `Foo.Specs`, test files `FooSpecs.cs`, and test classes `FooSpecs`. Use `Specs` not `Test` or `Tests` — it clarifies that the class describes the expected behavior (specification) of the subject under test.
- **Tool wrappers**: copy/paste from neighbours; cover full commands; use ``, ``, ``/``, ``, `` in `help`; don't write `secret: false` or `default: xxx`. See [Tool wrapper recipe](#tool-wrapper-recipe) below.
- **Tests next to code, separate folder**: every `Foo` project under `src/` has a sibling `Foo.Tests` project under `tests/`. Mirror the namespace.
- **No IDE-specific style files committed.** `.editorconfig` and `*.DotSettings` were removed during the takeover — relying on `dotnet format` defaults and review.
diff --git a/docs/agents/issue-and-pr-style.md b/docs/agents/issue-and-pr-style.md
index 35af054f5..cced61e26 100644
--- a/docs/agents/issue-and-pr-style.md
+++ b/docs/agents/issue-and-pr-style.md
@@ -75,6 +75,7 @@ Closes #
in a series). Summarize the need in a line — don't recite the issue's
Problem/Outcome/criteria back; the reader can click through. The PR explains
*the change*; the issue holds *the requirement*.
+- **Create PRs as draft by default.** Use `gh pr create --draft` unless the user explicitly asks for a ready-for-review PR. Convert to ready only when the user explicitly requests it. This keeps incomplete work from accidentally entering review and ensures work stays flexible during early development.
- Add the `⚠️ Breaking change` callout **only** when the change is breaking —
see the [PR-creation flow](release-and-versioning.md#pr-creation-flow) for
what that requires.
diff --git a/docs/dependencies.md b/docs/dependencies.md
index 6e8cbcbba..e88890001 100644
--- a/docs/dependencies.md
+++ b/docs/dependencies.md
@@ -90,13 +90,17 @@ Still on Matt's personal NuGet account; supply-chain SPOF, high-priority to repl
|---|---|
| `xunit` (+ `runner.visualstudio`) | Test framework |
| `Microsoft.NET.Test.Sdk` | Test host wiring |
-| `FluentAssertions` | Readable assertion DSL |
+| `FluentAssertions` | Readable assertion DSL — see [licensing note](#fluentassertions-licensing) below |
| `Verify.Xunit` (+ `.DiffPlex`, `.SourceGenerators`) | Snapshot-based testing (the `*.verified.txt` / `*.received.txt` pattern) |
| `coverlet.msbuild` | Code coverage |
| `GitHubActionsTestLogger` | Format test output for GitHub Actions annotations |
| `Basic.Reference.Assemblies.NetStandard20` | Reference assemblies for source-generator compile tests |
| `NetArchTest.Rules` | Architecture-fitness tests (e.g. `Fallout.Core` purity); broader suite tracked in #95 |
+### FluentAssertions licensing
+
+As of **v8.0** (Jan 2025) FluentAssertions dropped Apache 2.0 for the proprietary **Xceed Community License**: free for open-source / non-commercial use, paid (per-seat) for commercial use. v7.x remains Apache 2.0. We pin **8.x** (`Directory.Packages.props`) and stay current — this is fine for Fallout because it's (a) an OSS project covered by the free community license, and (b) a **test-only / dev-time** dependency that is never redistributed to consumers of the framework. The standard assertion convention (xUnit + FluentAssertions + Verify) is unchanged — see [conventions.md](agents/conventions.md).
+
## Build-time CLI tools (`PackageDownload`)
These are downloaded by `build/_build.csproj` for use during this repo's own build — not shipped to consumers.
diff --git a/fallout.slnx b/fallout.slnx
index 59d5c32e7..79fada201 100644
--- a/fallout.slnx
+++ b/fallout.slnx
@@ -27,6 +27,7 @@
+
@@ -35,22 +36,22 @@
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
diff --git a/src/Fallout.Build/Host.Activation.cs b/src/Fallout.Build/Host.Activation.cs
index 8d611ed92..42dc98f58 100644
--- a/src/Fallout.Build/Host.Activation.cs
+++ b/src/Fallout.Build/Host.Activation.cs
@@ -29,9 +29,15 @@ internal static IEnumerable AvailableTypes
private static bool IsRunning(Type hostType)
{
var propertyName = $"IsRunning{hostType.Name}";
- var member = hostType.GetProperty(propertyName, ReflectionUtility.Static)
- .NotNull($"Host type '{hostType.Name}' defines no property '{propertyName}'");
- return member.GetValue();
+ var member = hostType.GetProperty(propertyName, ReflectionUtility.Static);
+
+ // A public Host subclass that doesn't follow the `IsRunning{Name}` convention is
+ // not a runnable host — treat it as not-running rather than throwing. Host
+ // auto-detection runs in FalloutBuild's static ctor, so an exception here crashes
+ // every build that merely references such an assembly. The Nuke.* transition shim
+ // emits exactly such a subclass (`Nuke.Common.Host`), which made any shim-referencing
+ // build abort on startup. See Fallout.Canary#3.
+ return member is not null && member.GetValue();
}
private static Host CreateHost(Type hostType)
diff --git a/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsJob.cs b/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsJob.cs
index ab19c6ad4..40969f717 100644
--- a/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsJob.cs
+++ b/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsJob.cs
@@ -10,6 +10,7 @@ public class GitHubActionsJob : ConfigurationEntity
{
public string Name { get; set; }
public GitHubActionsImage Image { get; set; }
+ public string[] RunsOnLabels { get; set; } = new string[0];
public int TimeoutMinutes { get; set; }
public string ConcurrencyGroup { get; set; }
public string EnvironmentName { get; set; }
@@ -24,7 +25,14 @@ public override void Write(CustomFileWriter writer)
using (writer.Indent())
{
writer.WriteLine($"name: {Name}");
- writer.WriteLine($"runs-on: {Image.GetValue()}");
+ if (RunsOnLabels.Length > 0)
+ {
+ writer.WriteLine($"runs-on: [{RunsOnLabels.JoinCommaSpace()}]");
+ }
+ else
+ {
+ writer.WriteLine($"runs-on: {Image.GetValue()}");
+ }
if (TimeoutMinutes > 0)
{
diff --git a/src/Fallout.Common/CI/GitHubActions/GitHubActionsAttribute.cs b/src/Fallout.Common/CI/GitHubActions/GitHubActionsAttribute.cs
index 262a7b9b7..54bf1f241 100644
--- a/src/Fallout.Common/CI/GitHubActions/GitHubActionsAttribute.cs
+++ b/src/Fallout.Common/CI/GitHubActions/GitHubActionsAttribute.cs
@@ -93,6 +93,16 @@ public GitHubActionsAttribute(
public string[] InvokedTargets { get; set; } = new string[0];
+ ///
+ /// Runner labels emitted verbatim as runs-on: [label1, label2, ...], for selecting a
+ /// self-hosted runner pool by OS/arch/capability (e.g. ["self-hosted", "linux", "x64"]).
+ ///
+ /// When non-empty this replaces the runs-on: image for the job and requires exactly one
+ /// image (no matrix). The constructor-mandated is then ignored
+ /// for runs-on: and only names the job.
+ ///
+ public string[] RunsOnLabels { get; set; } = new string[0];
+
public GitHubActionsSubmodules Submodules
{
set => _submodules = value;
@@ -176,6 +186,10 @@ public override ConfigurationEntity GetConfiguration(IReadOnlyCollection 0 || configuration.DetailedTriggers.Length > 0,
$"Workflows must define either shorthand '{nameof(On)}' or '{nameof(On)}*' triggers");
+ Assert.True(RunsOnLabels.Length == 0 || _images.Length == 1,
+ $"Cannot use '{nameof(RunsOnLabels)}' with multiple images; labels resolve a single job's runner");
+ Assert.True(RunsOnLabels.All(x => !x.IsNullOrWhiteSpace()),
+ $"'{nameof(RunsOnLabels)}' entries must not be null, empty, or whitespace");
return configuration;
}
@@ -185,6 +199,7 @@ protected virtual GitHubActionsJob GetJobs(GitHubActionsImage image, IReadOnlyCo
return new GitHubActionsJob
{
Name = image.GetValue().Replace(".", "_"),
+ RunsOnLabels = RunsOnLabels,
EnvironmentName = EnvironmentName,
EnvironmentUrl = EnvironmentUrl,
Steps = GetSteps(relevantTargets).ToArray(),
diff --git a/src/Fallout.Common/ChangeLog/ChangeLogTasks.cs b/src/Fallout.Common/ChangeLog/ChangeLogTasks.cs
index 2d5d0f307..c354b0293 100644
--- a/src/Fallout.Common/ChangeLog/ChangeLogTasks.cs
+++ b/src/Fallout.Common/ChangeLog/ChangeLogTasks.cs
@@ -21,10 +21,12 @@ public static class ChangelogTasks
{
public static string GetNuGetReleaseNotes(string changelogFile, GitRepository repository = null)
{
+ AbsolutePath changelogPath = changelogFile;
+
// URL-encode characters MSBuild treats as command-line/property metacharacters.
// Without this, MSBuild's property parser splits on ; (turning a long release note
// into multiple bogus arguments) and chokes on stray " in the value.
- var changelogSectionNotes = ExtractChangelogSectionNotes(changelogFile)
+ var changelogSectionNotes = ExtractChangelogSectionNotes(changelogPath)
.Select(x => x.Replace("- ", "\u2022 ")
.Replace("* ", "\u2022 ")
.Replace("+ ", "\u2022 ")
@@ -32,10 +34,10 @@ public static string GetNuGetReleaseNotes(string changelogFile, GitRepository re
.Replace(",", "%2C")
.Replace(";", "%3B")).ToList();
- if (repository.IsGitHubRepository())
+ if (repository.IsGitHubRepository() && changelogPath.FileExists())
{
changelogSectionNotes.Add(string.Empty);
- changelogSectionNotes.Add($"Full changelog at {repository.GetGitHubBrowseUrl(changelogFile)}");
+ changelogSectionNotes.Add($"Full changelog at {repository.GetGitHubBrowseUrl(changelogPath, itemType: GitHubItemType.File)}");
}
return changelogSectionNotes.JoinNewLine();
diff --git a/src/Fallout.Migrate/Fallout.Migrate.csproj b/src/Fallout.Migrate/Fallout.Migrate.csproj
index 4427b3e44..7018259d3 100644
--- a/src/Fallout.Migrate/Fallout.Migrate.csproj
+++ b/src/Fallout.Migrate/Fallout.Migrate.csproj
@@ -11,7 +11,7 @@
-
+
diff --git a/src/Fallout.Solution.Codegen/Fallout.Solution.Codegen.csproj b/src/Fallout.Solution.Codegen/Fallout.Solution.Codegen.csproj
new file mode 100644
index 000000000..f54edf0d6
--- /dev/null
+++ b/src/Fallout.Solution.Codegen/Fallout.Solution.Codegen.csproj
@@ -0,0 +1,26 @@
+
+
+
+ Exe
+ net10.0
+ false
+ Pre-build codegen for the strongly-typed [Solution] accessor (Solution.g.cs). The net10 default that frees the Roslyn generator from the solution-parser dependency cone; reuses the real Fallout.Persistence.Solution parser and the shared SolutionEmitter.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Fallout.Solution.Codegen/Program.cs b/src/Fallout.Solution.Codegen/Program.cs
new file mode 100644
index 000000000..1fa4580f9
--- /dev/null
+++ b/src/Fallout.Solution.Codegen/Program.cs
@@ -0,0 +1,109 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text.Json.Nodes;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Fallout.Common;
+using Fallout.Common.IO;
+using Fallout.Common.Utilities;
+using Fallout.Solutions;
+
+// Pre-build codegen for the strongly-typed [Solution] accessor.
+//
+// Usage:
+// Fallout.Solution.Codegen --root --out [--source ...]
+//
+// Discovers [Solution(GenerateProjects = true)] members by parsing the given source files
+// syntactically (no compilation available pre-build), resolves each solution file, and writes
+// .g.cs into using the shared SolutionEmitter. No-op (exit 0) if none found.
+
+var root = GetOption("--root") ?? Directory.GetCurrentDirectory();
+var outDir = GetOption("--out") ?? Path.Combine(root, "obj");
+var sources = GetOptions("--source").Where(File.Exists).ToList();
+if (sources.Count == 0)
+ sources = Directory.EnumerateFiles(root, "*.cs", SearchOption.AllDirectories).ToList();
+
+var rootDirectory = (AbsolutePath)root;
+Directory.CreateDirectory(outDir);
+
+foreach (var (memberName, relativePath, fancyNames) in DiscoverSolutionMembers(sources))
+{
+ var solutionFile = !string.IsNullOrEmpty(relativePath)
+ ? rootDirectory / relativePath
+ : GetSolutionFileFromParametersFile(rootDirectory, memberName);
+
+ var solution = solutionFile.ReadSolution();
+ File.WriteAllText(Path.Combine(outDir, memberName + ".g.cs"), SolutionEmitter.Emit(solution, memberName, fancyNames));
+ Console.WriteLine($"Fallout.Solution.Codegen: generated {memberName}.g.cs from {solutionFile}");
+}
+
+return 0;
+
+string GetOption(string name)
+{
+ var index = Array.IndexOf(args, name);
+ return index >= 0 && index + 1 < args.Length ? args[index + 1] : null;
+}
+
+IEnumerable GetOptions(string name)
+{
+ for (var i = 0; i < args.Length - 1; i++)
+ if (args[i] == name)
+ yield return args[i + 1];
+}
+
+static IEnumerable<(string MemberName, string RelativePath, bool FancyNames)> DiscoverSolutionMembers(IEnumerable files)
+{
+ foreach (var file in files)
+ {
+ var root = CSharpSyntaxTree.ParseText(File.ReadAllText(file)).GetRoot();
+ foreach (var member in root.DescendantNodes().OfType())
+ {
+ if (member is not (FieldDeclarationSyntax or PropertyDeclarationSyntax))
+ continue;
+
+ foreach (var attribute in member.AttributeLists.SelectMany(x => x.Attributes))
+ {
+ if (attribute.Name.ToString() is not ("Solution" or "SolutionAttribute"))
+ continue;
+
+ var arguments = attribute.ArgumentList?.Arguments ?? default;
+ var generateProjects = arguments.Any(x =>
+ x.NameEquals?.Name.Identifier.Text == "GenerateProjects" &&
+ x.Expression is LiteralExpressionSyntax { Token.Value: true });
+ if (!generateProjects)
+ continue;
+
+ var fancyNames = arguments.Any(x =>
+ x.NameEquals?.Name.Identifier.Text == "FancyNames" &&
+ x.Expression is LiteralExpressionSyntax { Token.Value: true });
+
+ var relativePath = arguments
+ .Where(x => x.NameEquals == null && x.NameColon == null)
+ .Select(x => (x.Expression as LiteralExpressionSyntax)?.Token.Value as string)
+ .FirstOrDefault(x => !string.IsNullOrEmpty(x));
+
+ var memberName = member switch
+ {
+ FieldDeclarationSyntax field => field.Declaration.Variables.First().Identifier.Text,
+ PropertyDeclarationSyntax property => property.Identifier.Text,
+ _ => null,
+ };
+
+ if (memberName != null)
+ yield return (memberName, relativePath, fancyNames);
+ }
+ }
+ }
+}
+
+static AbsolutePath GetSolutionFileFromParametersFile(AbsolutePath rootDirectory, string memberName)
+{
+ var parametersFile = Constants.GetDefaultParametersFile(rootDirectory);
+ Assert.FileExists(parametersFile);
+ var obj = JsonNode.Parse(File.ReadAllText(parametersFile)).NotNull().AsObject();
+ var solutionRelativePath = obj[memberName].NotNull($"Property '{memberName}' does not exist in '{parametersFile}'.").GetValue();
+ return rootDirectory / solutionRelativePath.NotNull();
+}
diff --git a/src/Fallout.Solution.Codegen/build/Fallout.Solution.Codegen.targets b/src/Fallout.Solution.Codegen/build/Fallout.Solution.Codegen.targets
new file mode 100644
index 000000000..db80e5574
--- /dev/null
+++ b/src/Fallout.Solution.Codegen/build/Fallout.Solution.Codegen.targets
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+ <_FalloutSolutionCodegenOut>$(IntermediateOutputPath)FalloutSolution
+ <_FalloutSolutionCodegenRoot Condition="'$(FalloutBaseDirectory)' != ''">$(FalloutBaseDirectory)
+ <_FalloutSolutionCodegenRoot Condition="'$(_FalloutSolutionCodegenRoot)' == ''">$(MSBuildProjectDirectory)
+ <_FalloutSolutionCodegenInvoke Condition="'$(FalloutSolutionCodegenAssembly)' != ''">dotnet "$(FalloutSolutionCodegenAssembly)"
+ <_FalloutSolutionCodegenInvoke Condition="'$(_FalloutSolutionCodegenInvoke)' == '' and '$(FalloutSolutionCodegenProject)' != ''">dotnet run -c $(Configuration) --project "$(FalloutSolutionCodegenProject)" --
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Fallout.SourceGenerators/Fallout.SourceGenerators.csproj b/src/Fallout.SourceGenerators/Fallout.SourceGenerators.csproj
index 4bb79c6b1..0b94b8db1 100644
--- a/src/Fallout.SourceGenerators/Fallout.SourceGenerators.csproj
+++ b/src/Fallout.SourceGenerators/Fallout.SourceGenerators.csproj
@@ -11,6 +11,11 @@
+
+
+
+
+
diff --git a/src/Fallout.SourceGenerators/StronglyTypedSolutionGenerator.cs b/src/Fallout.SourceGenerators/StronglyTypedSolutionGenerator.cs
index 689f6ef9a..199cdc540 100644
--- a/src/Fallout.SourceGenerators/StronglyTypedSolutionGenerator.cs
+++ b/src/Fallout.SourceGenerators/StronglyTypedSolutionGenerator.cs
@@ -1,22 +1,21 @@
-using System;
-using System.Collections.Generic;
+using System;
using System.IO;
using System.Linq;
using System.Text.Json.Nodes;
-using System.Text.RegularExpressions;
using Microsoft.CodeAnalysis;
-using Microsoft.CodeAnalysis.CSharp;
-using Microsoft.CodeAnalysis.CSharp.Syntax;
using Fallout.Common;
using Fallout.Common.IO;
using Fallout.Solutions;
using Fallout.Common.Utilities;
-using Fallout.Common.Utilities.Collections;
-using Scriban;
-using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
namespace Fallout.SourceGenerators;
+///
+/// In-compiler generator for the strongly-typed [Solution] accessor. This is the
+/// toggle fallback (live in-IDE updates); the default is the net10 pre-build
+/// Fallout.Solution.Codegen console. Both share ,
+/// so output is identical. The MSBuild targets suppress this generator when console-mode is active.
+///
[Generator]
public class StronglyTypedSolutionGenerator : ISourceGenerator
{
@@ -28,6 +27,12 @@ public void Execute(GeneratorExecutionContext context)
{
try
{
+ // Toggle: when the net10 pre-build console owns solution codegen, this fallback no-ops
+ // (so only one path emits Solution.g.cs). Default/unset keeps the generator running.
+ context.AnalyzerConfigOptions.GlobalOptions.TryGetValue("build_property.FalloutSolutionCodegenMode", out var codegenMode);
+ if (string.Equals(codegenMode, "Build", StringComparison.OrdinalIgnoreCase))
+ return;
+
var allTypes = context.Compilation.Assembly.GlobalNamespace.GetAllTypes();
var members = allTypes.SelectMany(x => x.GetMembers())
.Where(x => x is IPropertySymbol or IFieldSymbol)
@@ -47,93 +52,7 @@ public void Execute(GeneratorExecutionContext context)
var fancyNaming = attribute.NamedArguments.SingleOrDefault(x => x.Key == "FancyNames").Value.Value as bool? ?? false;
var solution = solutionFile.ReadSolution();
- var hintName = member.Name + ".g.cs";
- var declaration = GetDeclaration(solution);
-
- // lang=csharp
- context.AddSource(hintName, $"""
- //
-
- using Fallout.Persistence.Solution.Model;
- using Fallout.Solutions;
- using Fallout.Common.IO;
- using System.Runtime.CompilerServices;
-
- {declaration}
- """);
-
- continue;
-
- string GetDeclaration(IProjectContainer container, string folderName = null)
- {
- var prefix = new string('_', container.Descendants(x => x.Parent).Count() + 1);
- var model = new
- {
- IsSolution = container is Solution,
- SolutionReference = container is Solution ? "this" : "solution",
- Name = GetEscapedName(container switch
- {
- Solution => member.Name,
- SolutionFolder folder => folderName ?? folder.Name,
- _ => throw new ArgumentOutOfRangeException(nameof(container), container, null)
- }),
- Projects = container.Projects.OrderBy(x => x.Name).Select(x => new
- {
- x.Name,
- EscapedName = GetEscapedName(x.Name),
- }),
- Folders = container.SolutionFolders.OrderBy(x => x.Name).Select(x => new
- {
- x.Name,
- TypeName = $"{prefix}{GetEscapedName(x.Name)}",
- EscapedName = GetEscapedName(x.Name),
- }),
- Declarations = container.SolutionFolders.OrderBy(x => x.Name)
- .Select(x => GetDeclaration(x, $"{prefix}{GetEscapedName(x.Name)}")).ToArray(),
- };
-
- // lang=csharp
- var template = Template.Parse("""
- {{~ if is_solution ~}}
- internal class {{ name }}(SolutionModel model, AbsolutePath path) : Fallout.Solutions.Solution(model, path)
- {{~ else ~}}
- internal class {{ name }}(SolutionFolderModel model, Fallout.Solutions.Solution solution) : Fallout.Solutions.SolutionFolder(model, solution)
- {{~ end ~}}
- {
- {{~ for project in projects ~}}
- public Fallout.Solutions.Project {{ project.escaped_name }} => this.GetProject("{{ project.name }}");
- {{~ end ~}}
-
- {{~ for folder in folders ~}}
- public {{ folder.type_name }} {{ folder.escaped_name }} => Unsafe.As<{{ folder.type_name }}>(this.GetSolutionFolder("{{ folder.name }}"));
- {{~ end ~}}
-
- {{~ for declaration in declarations ~}}
- {{ declaration }}
- {{~ end ~}}
- }
- """);
- return template.Render(model);
-
- // C# identifiers can't start with a digit and can only contain
- // letters/digits/underscore. The old regex `(^[\W^\d]|[\W])` was
- // attempting both but the `^\d` inside the character class was
- // misread as "match `^` or `\d`" rather than "starts with digit".
- // Folders like `00-Build` produced invalid output. Upstream fix
- // from nuke-build/nuke#1581.
- string GetEscapedName(string name)
- {
- name = name
- // .Replace(".", fancyNaming ? "丨" : "_")
- .Replace(".", fancyNaming ? "٠" : "_")
- .ReplaceRegex(@"[^A-Za-z0-9_]", _ => "_");
-
- if (Regex.IsMatch(name, @"^[0-9]"))
- return "_" + name;
-
- return name;
- }
- }
+ context.AddSource(member.Name + ".g.cs", SolutionEmitter.Emit(solution, member.Name, fancyNaming));
}
}
catch (Exception exception)
diff --git a/src/shared/SolutionEmitter.cs b/src/shared/SolutionEmitter.cs
new file mode 100644
index 000000000..9e934de2b
--- /dev/null
+++ b/src/shared/SolutionEmitter.cs
@@ -0,0 +1,101 @@
+using System;
+using System.Linq;
+using System.Text.RegularExpressions;
+using Fallout.Common.Utilities;
+using Fallout.Common.Utilities.Collections;
+using Scriban;
+
+namespace Fallout.Solutions;
+
+///
+/// Shared emit logic for the strongly-typed [Solution] accessor (Solution.g.cs).
+/// Compiled (via linked source) into both the in-compiler StronglyTypedSolutionGenerator
+/// (the toggle fallback) and the net10 pre-build Fallout.Solution.Codegen console (the
+/// default), so the two paths produce byte-identical output.
+///
+internal static class SolutionEmitter
+{
+ public static string Emit(Solution solution, string memberName, bool fancyNaming)
+ {
+ var declaration = GetDeclaration(solution);
+
+ // lang=csharp
+ return $"""
+ //
+
+ using Fallout.Persistence.Solution.Model;
+ using Fallout.Solutions;
+ using Fallout.Common.IO;
+ using System.Runtime.CompilerServices;
+
+ {declaration}
+ """;
+
+ string GetDeclaration(IProjectContainer container, string folderName = null)
+ {
+ var prefix = new string('_', container.Descendants(x => x.Parent).Count() + 1);
+ var model = new
+ {
+ IsSolution = container is Solution,
+ SolutionReference = container is Solution ? "this" : "solution",
+ Name = GetEscapedName(container switch
+ {
+ Solution => memberName,
+ SolutionFolder folder => folderName ?? folder.Name,
+ _ => throw new ArgumentOutOfRangeException(nameof(container), container, null)
+ }),
+ Projects = container.Projects.OrderBy(x => x.Name).Select(x => new
+ {
+ x.Name,
+ EscapedName = GetEscapedName(x.Name),
+ }),
+ Folders = container.SolutionFolders.OrderBy(x => x.Name).Select(x => new
+ {
+ x.Name,
+ TypeName = $"{prefix}{GetEscapedName(x.Name)}",
+ EscapedName = GetEscapedName(x.Name),
+ }),
+ Declarations = container.SolutionFolders.OrderBy(x => x.Name)
+ .Select(x => GetDeclaration(x, $"{prefix}{GetEscapedName(x.Name)}")).ToArray(),
+ };
+
+ // lang=csharp
+ var template = Template.Parse("""
+ {{~ if is_solution ~}}
+ internal class {{ name }}(SolutionModel model, AbsolutePath path) : Fallout.Solutions.Solution(model, path)
+ {{~ else ~}}
+ internal class {{ name }}(SolutionFolderModel model, Fallout.Solutions.Solution solution) : Fallout.Solutions.SolutionFolder(model, solution)
+ {{~ end ~}}
+ {
+ {{~ for project in projects ~}}
+ public Fallout.Solutions.Project {{ project.escaped_name }} => this.GetProject("{{ project.name }}");
+ {{~ end ~}}
+
+ {{~ for folder in folders ~}}
+ public {{ folder.type_name }} {{ folder.escaped_name }} => Unsafe.As<{{ folder.type_name }}>(this.GetSolutionFolder("{{ folder.name }}"));
+ {{~ end ~}}
+
+ {{~ for declaration in declarations ~}}
+ {{ declaration }}
+ {{~ end ~}}
+ }
+ """);
+ return template.Render(model);
+
+ // C# identifiers can't start with a digit and can only contain
+ // letters/digits/underscore. Folders like `00-Build` would otherwise
+ // produce invalid output (nuke-build/nuke#1581).
+ string GetEscapedName(string name)
+ {
+ name = name
+ .Replace(".", fancyNaming ? "٠" : "_")
+ .ReplaceRegex(@"[^A-Za-z0-9_]", _ => "_");
+
+ if (Regex.IsMatch(name, @"^[0-9]"))
+ return "_" + name;
+
+ return name;
+ }
+ }
+ }
+}
diff --git a/tests/Consumers/README.md b/tests/Consumers/README.md
index f75febc7d..1501019f7 100644
--- a/tests/Consumers/README.md
+++ b/tests/Consumers/README.md
@@ -46,8 +46,8 @@ The `Nuke.Consumer` doesn't pin anything (it references the in-repo shim assembl
## What's NOT tested
- Actual build *execution* (see above — runtime activation is fragile)
-- CI-host integration (GitHubActions, AzurePipelines, etc.) — covered by `tests/Fallout.Common.Tests/CI/`
+- CI-host integration (GitHubActions, AzurePipelines, etc.) — covered by `tests/Fallout.Common.Specs/CI/`
- Tool wrappers — covered by their own generated tests
-- Source generator behaviour — covered by `tests/Fallout.SourceGenerators.Tests/`
+- Source generator behaviour — covered by `tests/Fallout.SourceGenerators.Specs/`
These consumer projects are a **sentinel for shape changes**, not a place to demo features. Don't add consumer projects covering specific subsystems — those go in their own focused tests.
diff --git a/tests/Fallout.Build.Tests/BuildExecutorTest.cs b/tests/Fallout.Build.Specs/BuildExecutorSpecs.cs
similarity index 98%
rename from tests/Fallout.Build.Tests/BuildExecutorTest.cs
rename to tests/Fallout.Build.Specs/BuildExecutorSpecs.cs
index 98728a01b..00b8b9484 100644
--- a/tests/Fallout.Build.Tests/BuildExecutorTest.cs
+++ b/tests/Fallout.Build.Specs/BuildExecutorSpecs.cs
@@ -1,19 +1,19 @@
-using System;
+using System;
using System.Linq;
using FluentAssertions;
using Fallout.Common.Execution;
using Fallout.Common.Utilities.Collections;
using Xunit;
-namespace Fallout.Common.Tests.Execution;
+namespace Fallout.Common.Specs.Execution;
-public class BuildExecutorTest
+public class BuildExecutorSpecs
{
private ExecutableTarget A = new() { Name = nameof(A), Status = ExecutionStatus.Scheduled };
private ExecutableTarget B = new() { Name = nameof(B), Status = ExecutionStatus.Scheduled };
private ExecutableTarget C = new() { Name = nameof(C), Status = ExecutionStatus.Scheduled };
- public BuildExecutorTest()
+ public BuildExecutorSpecs()
{
C.ExecutionDependencies.Add(B);
B.ExecutionDependencies.Add(A);
diff --git a/tests/Fallout.Build.Tests/CompletionUtilityTest.TestGetCompletionItemsFromSchema.verified.txt b/tests/Fallout.Build.Specs/CompletionUtilitySpecs.TestGetCompletionItemsFromSchema.verified.txt
similarity index 100%
rename from tests/Fallout.Build.Tests/CompletionUtilityTest.TestGetCompletionItemsFromSchema.verified.txt
rename to tests/Fallout.Build.Specs/CompletionUtilitySpecs.TestGetCompletionItemsFromSchema.verified.txt
diff --git a/tests/Fallout.Build.Tests/CompletionUtilityTest.TestGetCompletionItemsParameterBuild.verified.txt b/tests/Fallout.Build.Specs/CompletionUtilitySpecs.TestGetCompletionItemsParameterBuild.verified.txt
similarity index 100%
rename from tests/Fallout.Build.Tests/CompletionUtilityTest.TestGetCompletionItemsParameterBuild.verified.txt
rename to tests/Fallout.Build.Specs/CompletionUtilitySpecs.TestGetCompletionItemsParameterBuild.verified.txt
diff --git a/tests/Fallout.Build.Tests/CompletionUtilityTest.TestGetCompletionItemsTargetBuild.verified.txt b/tests/Fallout.Build.Specs/CompletionUtilitySpecs.TestGetCompletionItemsTargetBuild.verified.txt
similarity index 100%
rename from tests/Fallout.Build.Tests/CompletionUtilityTest.TestGetCompletionItemsTargetBuild.verified.txt
rename to tests/Fallout.Build.Specs/CompletionUtilitySpecs.TestGetCompletionItemsTargetBuild.verified.txt
diff --git a/tests/Fallout.Build.Tests/CompletionUtilityTest.cs b/tests/Fallout.Build.Specs/CompletionUtilitySpecs.cs
similarity index 93%
rename from tests/Fallout.Build.Tests/CompletionUtilityTest.cs
rename to tests/Fallout.Build.Specs/CompletionUtilitySpecs.cs
index 6a60cd592..9212cf01d 100644
--- a/tests/Fallout.Build.Tests/CompletionUtilityTest.cs
+++ b/tests/Fallout.Build.Specs/CompletionUtilitySpecs.cs
@@ -10,15 +10,15 @@
using VerifyXunit;
using Xunit;
-namespace Fallout.Common.Tests;
+namespace Fallout.Common.Specs;
-public class CompletionUtilityTest
+public class CompletionUtilitySpecs
{
private readonly VerifySettings _verifySettings;
private static AbsolutePath RootDirectory => Constants.TryGetRootDirectoryFrom(EnvironmentInfo.WorkingDirectory).NotNull();
- private static AbsolutePath TestDirectory => RootDirectory / "tests" / "Fallout.Build.Tests";
+ private static AbsolutePath TestDirectory => RootDirectory / "tests" / "Fallout.Build.Specs";
- public CompletionUtilityTest()
+ public CompletionUtilitySpecs()
{
_verifySettings = new VerifySettings();
_verifySettings.DontIgnoreEmptyCollections();
@@ -27,7 +27,7 @@ public CompletionUtilityTest()
[Fact]
public async Task TestGetCompletionItemsTargetBuild()
{
- var file = TestDirectory / "SchemaUtilityTest.TestTargetBuild.verified.json";
+ var file = TestDirectory / "SchemaUtilitySpecs.TestTargetBuild.verified.json";
var schema = JsonDocument.Parse(file.ReadAllText());
var items = CompletionUtility.GetItemsFromSchema(schema, new[] { "dev" });
await Verifier.Verify(items, _verifySettings);
@@ -36,7 +36,7 @@ public async Task TestGetCompletionItemsTargetBuild()
[Fact]
public async Task TestGetCompletionItemsParameterBuild()
{
- var file = TestDirectory / "SchemaUtilityTest.TestParameterBuild.verified.json";
+ var file = TestDirectory / "SchemaUtilitySpecs.TestParameterBuild.verified.json";
var schema = JsonDocument.Parse(file.ReadAllText());
var items = CompletionUtility.GetItemsFromSchema(schema, []);
await Verifier.Verify(items, _verifySettings);
diff --git a/tests/Fallout.Build.Tests/ControlFlowTest.cs b/tests/Fallout.Build.Specs/ControlFlowSpecs.cs
similarity index 88%
rename from tests/Fallout.Build.Tests/ControlFlowTest.cs
rename to tests/Fallout.Build.Specs/ControlFlowSpecs.cs
index d995cbbfb..8d35a70f3 100644
--- a/tests/Fallout.Build.Tests/ControlFlowTest.cs
+++ b/tests/Fallout.Build.Specs/ControlFlowSpecs.cs
@@ -5,9 +5,9 @@
// ReSharper disable ArgumentsStyleLiteral
-namespace Fallout.Common.Tests;
+namespace Fallout.Common.Specs;
-public class ControlFlowTest
+public class ControlFlowSpecs
{
[Fact]
public void Test()
diff --git a/tests/Fallout.Build.Tests/DefaultInterfaceExecutionTest.cs b/tests/Fallout.Build.Specs/DefaultInterfaceExecutionSpecs.cs
similarity index 93%
rename from tests/Fallout.Build.Tests/DefaultInterfaceExecutionTest.cs
rename to tests/Fallout.Build.Specs/DefaultInterfaceExecutionSpecs.cs
index 722dc70cf..e5f46616b 100644
--- a/tests/Fallout.Build.Tests/DefaultInterfaceExecutionTest.cs
+++ b/tests/Fallout.Build.Specs/DefaultInterfaceExecutionSpecs.cs
@@ -1,13 +1,13 @@
-using System;
+using System;
using System.Linq;
using System.Linq.Expressions;
using FluentAssertions;
using Fallout.Common.Execution;
using Xunit;
-namespace Fallout.Common.Tests.Execution;
+namespace Fallout.Common.Specs.Execution;
-public class DefaultInterfaceExecutionTest
+public class DefaultInterfaceExecutionSpecs
{
public static string Description = "description";
public static Action Action = () => { };
@@ -185,15 +185,11 @@ private class OverriddenDuplicatedTargetTestBuild : FalloutBuild, ITestBuild, ID
private interface ITestBuild
{
- string Description => DefaultInterfaceExecutionTest.Description;
-
- Action Action => DefaultInterfaceExecutionTest.Action;
-
- Expression> Requirement => DefaultInterfaceExecutionTest.Requirement;
-
- Func StaticCondition => DefaultInterfaceExecutionTest.StaticCondition;
-
- Func DynamicCondition => DefaultInterfaceExecutionTest.DynamicCondition;
+ public string Description => DefaultInterfaceExecutionSpecs.Description;
+ public Action Action => DefaultInterfaceExecutionSpecs.Action;
+ public Expression> Requirement => DefaultInterfaceExecutionSpecs.Requirement;
+ public Func StaticCondition => DefaultInterfaceExecutionSpecs.StaticCondition;
+ public Func DynamicCondition => DefaultInterfaceExecutionSpecs.DynamicCondition;
Target A => _ => _
.Description(Description)
diff --git a/tests/Fallout.Build.Tests/ExecutableTargetFactoryTest.cs b/tests/Fallout.Build.Specs/ExecutableTargetFactorySpecs.cs
similarity index 98%
rename from tests/Fallout.Build.Tests/ExecutableTargetFactoryTest.cs
rename to tests/Fallout.Build.Specs/ExecutableTargetFactorySpecs.cs
index 168797267..668117743 100644
--- a/tests/Fallout.Build.Tests/ExecutableTargetFactoryTest.cs
+++ b/tests/Fallout.Build.Specs/ExecutableTargetFactorySpecs.cs
@@ -5,9 +5,9 @@
using Fallout.Common.Execution;
using Xunit;
-namespace Fallout.Common.Tests.Execution;
+namespace Fallout.Common.Specs.Execution;
-public class ExecutableTargetFactoryTest
+public class ExecutableTargetFactorySpecs
{
[Fact]
public void Test()
diff --git a/tests/Fallout.Build.Tests/ExecutionPlannerTest.cs b/tests/Fallout.Build.Specs/ExecutionPlannerSpecs.cs
similarity index 97%
rename from tests/Fallout.Build.Tests/ExecutionPlannerTest.cs
rename to tests/Fallout.Build.Specs/ExecutionPlannerSpecs.cs
index b8e51e94f..1586198d3 100644
--- a/tests/Fallout.Build.Tests/ExecutionPlannerTest.cs
+++ b/tests/Fallout.Build.Specs/ExecutionPlannerSpecs.cs
@@ -5,9 +5,9 @@
using Fallout.Common.Execution;
using Xunit;
-namespace Fallout.Common.Tests.Execution;
+namespace Fallout.Common.Specs.Execution;
-public class ExecutionPlannerTest
+public class ExecutionPlannerSpecs
{
private ExecutableTarget A = new() { Name = nameof(A), IsDefault = true };
private ExecutableTarget B = new() { Name = nameof(B) };
diff --git a/tests/Fallout.Build.Tests/ExecutionTestsInitializer.cs b/tests/Fallout.Build.Specs/ExecutionSpecsInitializer.cs
similarity index 85%
rename from tests/Fallout.Build.Tests/ExecutionTestsInitializer.cs
rename to tests/Fallout.Build.Specs/ExecutionSpecsInitializer.cs
index 30ec8acd4..8f39f1338 100644
--- a/tests/Fallout.Build.Tests/ExecutionTestsInitializer.cs
+++ b/tests/Fallout.Build.Specs/ExecutionSpecsInitializer.cs
@@ -1,9 +1,9 @@
-using System;
+using System;
using System.Linq;
using System.Runtime.CompilerServices;
using Fallout.Common.Execution;
-namespace Fallout.Common.Tests;
+namespace Fallout.Common.Specs;
public static class ExecutionTestsInitializer
{
diff --git a/tests/Fallout.Build.Tests/Fallout.Build.Tests.csproj b/tests/Fallout.Build.Specs/Fallout.Build.Specs.csproj
similarity index 100%
rename from tests/Fallout.Build.Tests/Fallout.Build.Tests.csproj
rename to tests/Fallout.Build.Specs/Fallout.Build.Specs.csproj
diff --git a/tests/Fallout.Build.Tests/GitRepositoryTest.cs b/tests/Fallout.Build.Specs/GitRepositorySpecs.cs
similarity index 91%
rename from tests/Fallout.Build.Tests/GitRepositoryTest.cs
rename to tests/Fallout.Build.Specs/GitRepositorySpecs.cs
index 6fa952f64..02f326352 100644
--- a/tests/Fallout.Build.Tests/GitRepositoryTest.cs
+++ b/tests/Fallout.Build.Specs/GitRepositorySpecs.cs
@@ -1,13 +1,13 @@
-using System;
+using System;
using System.IO;
using System.Linq;
using FluentAssertions;
using Fallout.Common.Git;
using Xunit;
-namespace Fallout.Common.Tests;
+namespace Fallout.Common.Specs;
-public class GitRepositoryTest
+public class GitRepositorySpecs
{
[Theory]
[InlineData("https://github.com/nuke-build", "github.com", "nuke-build")]
@@ -27,7 +27,7 @@ public class GitRepositoryTest
[InlineData("https://git.test.org:1234/test/test", "git.test.org", "test/test")]
[InlineData("git://git.test.org:1234/test/test", "git.test.org", "test/test")]
[InlineData("git://git.test.org/test/test", "git.test.org", "test/test")]
- public void FromUrlTest(string url, string endpoint, string identifier)
+ public void FromUrlSpec(string url, string endpoint, string identifier)
{
var repository = GitRepository.FromUrl(url);
repository.Endpoint.Should().Be(endpoint);
@@ -39,14 +39,14 @@ public void FromUrlTest(string url, string endpoint, string identifier)
[InlineData("git@git.test.org:test", GitProtocol.Ssh)]
[InlineData("ssh://git.test.org:1234/test/test", GitProtocol.Ssh)]
[InlineData("git://git.test.org:1234/test/test", GitProtocol.Ssh)]
- public void FromUrlProtocolTest(string url, GitProtocol protocol)
+ public void FromUrlProtocolSpec(string url, GitProtocol protocol)
{
var repository = GitRepository.FromUrl(url);
repository.Protocol.Should().Be(protocol);
}
[Fact]
- public void FromDirectoryTest()
+ public void FromDirectorySpec()
{
var repository = GitRepository.FromLocalDirectory(Directory.GetCurrentDirectory()).NotNull();
repository.Endpoint.Should().NotBeNullOrEmpty();
diff --git a/tests/Fallout.Build.Tests/HostInitializer.cs b/tests/Fallout.Build.Specs/HostInitializer.cs
similarity index 90%
rename from tests/Fallout.Build.Tests/HostInitializer.cs
rename to tests/Fallout.Build.Specs/HostInitializer.cs
index 05bfd7f54..730047626 100644
--- a/tests/Fallout.Build.Tests/HostInitializer.cs
+++ b/tests/Fallout.Build.Specs/HostInitializer.cs
@@ -1,9 +1,9 @@
-using System;
+using System;
using System.Linq;
using System.Runtime.CompilerServices;
using Fallout.Common.Utilities;
-namespace Fallout.Common.Tests;
+namespace Fallout.Common.Specs;
public static class HostInitializer
{
diff --git a/tests/Fallout.Build.Tests/ParameterServiceTest.cs b/tests/Fallout.Build.Specs/ParameterServiceSpecs.cs
similarity index 98%
rename from tests/Fallout.Build.Tests/ParameterServiceTest.cs
rename to tests/Fallout.Build.Specs/ParameterServiceSpecs.cs
index 92a2618cf..ef7dd981e 100644
--- a/tests/Fallout.Build.Tests/ParameterServiceTest.cs
+++ b/tests/Fallout.Build.Specs/ParameterServiceSpecs.cs
@@ -7,9 +7,9 @@
using Xunit;
using static Fallout.Common.Utilities.ReflectionUtility;
-namespace Fallout.Common.Tests;
+namespace Fallout.Common.Specs;
-public class ParameterServiceTest
+public class ParameterServiceSpecs
{
private ParameterService GetService(string[] commandLineArguments = null, IDictionary environmentVariables = null)
{
diff --git a/tests/Fallout.Build.Tests/SchemaUtilityTest.TestCustomParameterAttribute.verified.json b/tests/Fallout.Build.Specs/SchemaUtilitySpecs.TestCustomParameterAttribute.verified.json
similarity index 100%
rename from tests/Fallout.Build.Tests/SchemaUtilityTest.TestCustomParameterAttribute.verified.json
rename to tests/Fallout.Build.Specs/SchemaUtilitySpecs.TestCustomParameterAttribute.verified.json
diff --git a/tests/Fallout.Build.Tests/SchemaUtilityTest.TestEmptyBuild.verified.json b/tests/Fallout.Build.Specs/SchemaUtilitySpecs.TestEmptyBuild.verified.json
similarity index 100%
rename from tests/Fallout.Build.Tests/SchemaUtilityTest.TestEmptyBuild.verified.json
rename to tests/Fallout.Build.Specs/SchemaUtilitySpecs.TestEmptyBuild.verified.json
diff --git a/tests/Fallout.Build.Tests/SchemaUtilityTest.TestGetBuildSchema.verified.json b/tests/Fallout.Build.Specs/SchemaUtilitySpecs.TestGetBuildSchema.verified.json
similarity index 100%
rename from tests/Fallout.Build.Tests/SchemaUtilityTest.TestGetBuildSchema.verified.json
rename to tests/Fallout.Build.Specs/SchemaUtilitySpecs.TestGetBuildSchema.verified.json
diff --git a/tests/Fallout.Build.Tests/SchemaUtilityTest.TestParameterBuild.verified.json b/tests/Fallout.Build.Specs/SchemaUtilitySpecs.TestParameterBuild.verified.json
similarity index 100%
rename from tests/Fallout.Build.Tests/SchemaUtilityTest.TestParameterBuild.verified.json
rename to tests/Fallout.Build.Specs/SchemaUtilitySpecs.TestParameterBuild.verified.json
diff --git a/tests/Fallout.Build.Tests/SchemaUtilityTest.TestTargetBuild.verified.json b/tests/Fallout.Build.Specs/SchemaUtilitySpecs.TestTargetBuild.verified.json
similarity index 100%
rename from tests/Fallout.Build.Tests/SchemaUtilityTest.TestTargetBuild.verified.json
rename to tests/Fallout.Build.Specs/SchemaUtilitySpecs.TestTargetBuild.verified.json
diff --git a/tests/Fallout.Build.Tests/SchemaUtilityTest.cs b/tests/Fallout.Build.Specs/SchemaUtilitySpecs.cs
similarity index 97%
rename from tests/Fallout.Build.Tests/SchemaUtilityTest.cs
rename to tests/Fallout.Build.Specs/SchemaUtilitySpecs.cs
index 745585966..106a1ef41 100644
--- a/tests/Fallout.Build.Tests/SchemaUtilityTest.cs
+++ b/tests/Fallout.Build.Specs/SchemaUtilitySpecs.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.ComponentModel;
using System.Linq;
using System.Threading.Tasks;
@@ -11,9 +11,9 @@
#pragma warning disable CS0169 // Field is never used
-namespace Fallout.Common.Tests;
+namespace Fallout.Common.Specs;
-public class SchemaUtilityTest
+public class SchemaUtilitySpecs
{
[Fact]
public Task TestEmptyBuild()
diff --git a/tests/Fallout.Cli.Tests/VerifyTestsInitializer.cs b/tests/Fallout.Build.Specs/VerifySpecsInitializer.cs
similarity index 89%
rename from tests/Fallout.Cli.Tests/VerifyTestsInitializer.cs
rename to tests/Fallout.Build.Specs/VerifySpecsInitializer.cs
index 3f4852254..ff5f8c9a5 100644
--- a/tests/Fallout.Cli.Tests/VerifyTestsInitializer.cs
+++ b/tests/Fallout.Build.Specs/VerifySpecsInitializer.cs
@@ -1,9 +1,9 @@
-using System;
+using System;
using System.Linq;
using System.Runtime.CompilerServices;
using VerifyTests;
-namespace Fallout.Cli.Tests;
+namespace Fallout.Common.Specs;
public static class VerifyTestsInitializer
{
diff --git a/tests/Fallout.Build.Tests/parameters.json b/tests/Fallout.Build.Specs/parameters.json
similarity index 54%
rename from tests/Fallout.Build.Tests/parameters.json
rename to tests/Fallout.Build.Specs/parameters.json
index 0b2ee0afb..1d765acc2 100644
--- a/tests/Fallout.Build.Tests/parameters.json
+++ b/tests/Fallout.Build.Specs/parameters.json
@@ -1,5 +1,5 @@
{
- "$schema": "./SchemaUtilityTest.TestTargetBuild.verified.json",
+ "$schema": "./SchemaUtilitySpecs.TestTargetBuild.verified.json",
"Verbosity": "Minimal",
"Target": ["ExplicitTarget", "InheritedTarget"]
}
diff --git a/tests/Fallout.Cli.Tests/BuildProjectResolverTests.cs b/tests/Fallout.Cli.Specs/BuildProjectResolverSpecs.cs
similarity index 98%
rename from tests/Fallout.Cli.Tests/BuildProjectResolverTests.cs
rename to tests/Fallout.Cli.Specs/BuildProjectResolverSpecs.cs
index 21d2a120d..679c86075 100644
--- a/tests/Fallout.Cli.Tests/BuildProjectResolverTests.cs
+++ b/tests/Fallout.Cli.Specs/BuildProjectResolverSpecs.cs
@@ -5,9 +5,9 @@
using FluentAssertions;
using Xunit;
-namespace Fallout.Cli.Tests;
+namespace Fallout.Cli.Specs;
-public class BuildProjectResolverTests
+public class BuildProjectResolverSpecs
{
[Fact]
public void Resolve_ConventionDefault_ReturnsBuildSlashUnderscoreBuildCsproj()
diff --git a/tests/Fallout.Cli.Tests/CakeConversionTests.cs b/tests/Fallout.Cli.Specs/CakeConversionSpecs.cs
similarity index 90%
rename from tests/Fallout.Cli.Tests/CakeConversionTests.cs
rename to tests/Fallout.Cli.Specs/CakeConversionSpecs.cs
index 8357034f7..9e999a6da 100644
--- a/tests/Fallout.Cli.Tests/CakeConversionTests.cs
+++ b/tests/Fallout.Cli.Specs/CakeConversionSpecs.cs
@@ -1,4 +1,4 @@
-using System.Collections.Generic;
+using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using FluentAssertions;
@@ -8,9 +8,9 @@
using VerifyXunit;
using Xunit;
-namespace Fallout.Cli.Tests;
+namespace Fallout.Cli.Specs;
-public class CakeConversionTests
+public class CakeConversionSpecs
{
private static AbsolutePath RootDirectory => Constants.TryGetRootDirectoryFrom(EnvironmentInfo.WorkingDirectory);
@@ -37,7 +37,7 @@ public void TestPackages()
packages.Should().NotContain(x => x.Id.Contains("Cake"));
}
- private static AbsolutePath CakeScriptsDirectory => RootDirectory / "tests" / "Fallout.Cli.Tests" / "cake-scripts";
+ private static AbsolutePath CakeScriptsDirectory => RootDirectory / "tests" / "Fallout.Cli.Specs" / "cake-scripts";
public static IEnumerable