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
76 changes: 43 additions & 33 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -146,42 +146,52 @@ jobs:
"test Integration/Consumers/AnalyzersOff/AnalyzersOff.csproj"
"run Integration/Consumers/AssertionXml/OpenApi/OpenApi.csproj"
)
for consumer in "${consumers[@]}"; do
dotnet restore "${consumer#* }"
done
for consumer in "${consumers[@]}"; do
dotnet build "${consumer#* }" --configuration Release --no-restore
done

# Prove the consumers restored the freshly packed packages and not stale ones from the cache.
packages="${NUGET_PACKAGES:-$HOME/.nuget/packages}"
checked=0
for nupkg in artifacts/packages/*.0.0.0-ci.nupkg; do
name="$(basename "$nupkg" .nupkg)"
id="${name%.0.0.0-ci}"
lower="$(echo "$id" | tr "[:upper:]" "[:lower:]")"
restored="$packages/$lower/0.0.0-ci/$lower.0.0.0-ci.nupkg.sha512"
if [ -f "$restored" ]; then
expected="$(openssl dgst -sha512 -binary "$nupkg" | openssl base64 -A)"
if [ "$(cat "$restored")" != "$expected" ]; then
echo "::error::Restored $id 0.0.0-ci differs from the freshly packed package"
exit 1
plain_assertions=Integration/Consumers/AssertionXml/PlainAssertions/PlainAssertions.csproj
# Restore separately for each TFM; leave net11.0 assets for the later default-framework checks.
for framework in net10.0 net11.0; do
for consumer in "${consumers[@]}"; do
dotnet restore "${consumer#* }" -p:ConsumerFramework="$framework"
done
dotnet restore "$plain_assertions" -p:ConsumerFramework="$framework"
for consumer in "${consumers[@]}"; do
dotnet build "${consumer#* }" --configuration Release --no-restore -p:ConsumerFramework="$framework"
done
dotnet build "$plain_assertions" --configuration Release --no-restore -p:ConsumerFramework="$framework"

# Prove the consumers restored the freshly packed packages and not stale ones from the cache.
packages="${NUGET_PACKAGES:-$HOME/.nuget/packages}"
checked=0
for nupkg in artifacts/packages/*.0.0.0-ci.nupkg; do
name="$(basename "$nupkg" .nupkg)"
id="${name%.0.0.0-ci}"
lower="$(echo "$id" | tr "[:upper:]" "[:lower:]")"
restored="$packages/$lower/0.0.0-ci/$lower.0.0.0-ci.nupkg.sha512"
if [ -f "$restored" ]; then
expected="$(openssl dgst -sha512 -binary "$nupkg" | openssl base64 -A)"
if [ "$(cat "$restored")" != "$expected" ]; then
echo "::error::Restored $id 0.0.0-ci differs from the freshly packed package"
exit 1
fi
checked=$((checked + 1))
fi
checked=$((checked + 1))
done
if [ "$checked" -eq 0 ]; then
echo "::error::No freshly packed package was restored by the consumers"
exit 1
fi
done
if [ "$checked" -eq 0 ]; then
echo "::error::No freshly packed package was restored by the consumers"
exit 1
fi
echo "Verified $checked restored package(s) match artifacts/packages"
echo "Verified $checked restored package(s) match artifacts/packages"

for consumer in "${consumers[@]}"; do
if [ "${consumer%% *}" = "test" ]; then
dotnet test "${consumer#* }" --configuration Release --no-build
else
dotnet run --project "${consumer#* }" --configuration Release --no-build
fi
for consumer in "${consumers[@]}"; do
if [ "${consumer%% *}" = "test" ]; then
dotnet test "${consumer#* }" --configuration Release --no-build -p:ConsumerFramework="$framework"
else
dotnet run --project "${consumer#* }" --configuration Release --no-build -p:ConsumerFramework="$framework"
fi
done
dotnet run --project "$plain_assertions" -c Release --no-build --no-restore -p:ConsumerFramework="$framework" -- Probe.A-B Consumer.AssertionXml.FirstProbe
dotnet run --project "$plain_assertions" -c Release --no-build --no-restore -p:ConsumerFramework="$framework" -- Probe.A_B Consumer.AssertionXml.SecondProbe
ConsumerFramework="$framework" node Integration/Consumers/AssertionXml/verify-generation.mjs First Probe.A-B
ConsumerFramework="$framework" node Integration/Consumers/AssertionXml/verify-generation.mjs Second Probe.A_B
done

- name: Check isolated pre-lifecycle assertion registration
Expand Down
9 changes: 9 additions & 0 deletions .globalconfig
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ dotnet_diagnostic.CS8632.severity = none
dotnet_diagnostic.IDE0003.severity = warning
dotnet_diagnostic.IDE0005.severity = warning
dotnet_diagnostic.IDE0010.severity = none
dotnet_diagnostic.IDE0028.severity = suggestion
dotnet_diagnostic.IDE0036.severity = warning
dotnet_diagnostic.IDE0040.severity = none
dotnet_diagnostic.IDE0040WithoutSuggestion.severity = none
Expand All @@ -68,6 +69,14 @@ dotnet_diagnostic.IDE0072.severity = none
dotnet_diagnostic.IDE0083.severity = none
dotnet_diagnostic.IDE0130.severity = none
dotnet_diagnostic.IDE0160.severity = none
dotnet_diagnostic.IDE0300.severity = suggestion
dotnet_diagnostic.IDE0301.severity = suggestion
dotnet_diagnostic.IDE0302.severity = suggestion
dotnet_diagnostic.IDE0303.severity = suggestion
dotnet_diagnostic.IDE0304.severity = suggestion
dotnet_diagnostic.IDE0305.severity = suggestion
dotnet_diagnostic.IDE0306.severity = suggestion
dotnet_diagnostic.IDE0410.severity = suggestion
dotnet_diagnostic.IDE1006.severity = warning
dotnet_diagnostic.SA1000.severity = none
dotnet_diagnostic.SA1009.severity = none
Expand Down
5 changes: 3 additions & 2 deletions Integration/Consumers/AssertionXml/verify-generation.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ import { readFileSync, statSync } from 'node:fs';
import { fileURLToPath } from 'node:url';

const [project, assemblyName, previousWriteTime] = process.argv.slice(2);
const framework = process.env.ConsumerFramework || 'net11.0';
assert.ok(['First', 'Second'].includes(project), 'Select the First or Second probe project.');
assert.ok(assemblyName, 'An expected assembly name is required.');
const root = fileURLToPath(new URL(`./${project}/`, import.meta.url));
const sourcePath = `${root}obj/Release/net11.0/CratisSpecificationsAssertionFailures.g.cs`;
const sourcePath = `${root}obj/Release/${framework}/CratisSpecificationsAssertionFailures.g.cs`;
const source = readFileSync(sourcePath, 'utf8');
const encodedName = Buffer.from(assemblyName).toString('base64')
.replaceAll('+', '_p').replaceAll('/', '_s').replaceAll('=', '_e');
Expand All @@ -19,7 +20,7 @@ assert.ok(source.includes(`namespace Cratis.Generated.A${encodedName};`), 'Gener
assert.ok(source.includes('[global::System.Runtime.CompilerServices.ModuleInitializer]'), 'Automatic registration must remain.');
assert.ok(source.includes('internal static void Register() => global::Cratis.Specifications.AssertionFailureRegistration.Register();'),
'The generated initializer must call the native assertion registration.');
const xml = readFileSync(`${root}bin/Release/net11.0/${assemblyName}.xml`, 'utf8');
const xml = readFileSync(`${root}bin/Release/${framework}/${assemblyName}.xml`, 'utf8');
assert.ok(xml.includes(`name="M:${registrationType}.Register"`), 'XML must document the assembly-specific registration.');
assert.ok(xml.includes('Registers the XUnit assertion failure factory'), 'Registration documentation must remain.');
if (previousWriteTime !== undefined) {
Expand Down
3 changes: 2 additions & 1 deletion Integration/Consumers/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
<Project>
<!-- Consumers deliberately use ordinary SDK defaults, not the library's packaging settings. -->
<PropertyGroup>
<TargetFramework>net11.0</TargetFramework>
<ConsumerFramework Condition="'$(ConsumerFramework)' == ''">net11.0</ConsumerFramework>
<TargetFramework>$(ConsumerFramework)</TargetFramework>
<IsPackable>false</IsPackable>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
Expand Down
14 changes: 7 additions & 7 deletions Source/Runner.Generator/SpecificationModelBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ public static bool ReferencesRunner(Compilation compilation) =>
}

var chain = Chain(type);
var errors = new List<string>();
var diagnostics = new List<DiagnosticModel>();
List<string> errors = [];
List<DiagnosticModel> diagnostics = [];
var facts = Facts(chain, compilation, errors, diagnostics);
if (facts.Count == 0 && errors.Count == 0) return null;

Expand Down Expand Up @@ -106,14 +106,14 @@ static bool IsInGenericType(INamedTypeSymbol type)

static List<string> TypeNames(INamedTypeSymbol type)
{
var names = new List<string>();
List<string> names = [];
for (var current = type; current is not null; current = current.ContainingType) names.Insert(0, current.Name);
return names;
}

static List<INamedTypeSymbol> Chain(INamedTypeSymbol type)
{
var chain = new List<INamedTypeSymbol>();
List<INamedTypeSymbol> chain = [];
for (var current = type; current is not null && current.ToDisplayString() != SpecificationType; current = current.BaseType)
{
chain.Insert(0, current);
Expand Down Expand Up @@ -146,11 +146,11 @@ static EquatableArray<MethodCall> Lifecycle(List<INamedTypeSymbol> chain, Compil

static List<FactModel> Facts(List<INamedTypeSymbol> chain, Compilation compilation, List<string> errors, List<DiagnosticModel> diagnostics)
{
var facts = new List<FactModel>();
var seen = new HashSet<string>(StringComparer.Ordinal);
List<FactModel> facts = [];
HashSet<string> seen = [with(StringComparer.Ordinal)];
for (var index = chain.Count - 1; index >= 0; index--)
{
var own = new List<FactModel>();
List<FactModel> own = [];
foreach (var method in chain[index].GetMembers().OfType<IMethodSymbol>())
{
var attribute = method.GetAttributes().FirstOrDefault(_ => _.AttributeClass?.ToDisplayString() == FactAttribute);
Expand Down
2 changes: 1 addition & 1 deletion Source/Runner/Reporting/JsonReportWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public static string Render(ReportTree tree)
writer.WriteEndObject();
}

return Encoding.UTF8.GetString(stream.ToArray()) + "\n";
return Encoding.UTF8.GetString(stream.GetBuffer(), 0, (int)stream.Length) + "\n";
}

static double Milliseconds(TimeSpan duration) => Math.Round(duration.TotalMilliseconds, 3);
Expand Down
2 changes: 1 addition & 1 deletion Source/Runner/Scheduling/RunCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ internal sealed class RunCache
/// <returns>The cache.</returns>
public static RunCache Load(string path)
{
var entries = new Dictionary<string, RunCacheEntry>(StringComparer.Ordinal);
Dictionary<string, RunCacheEntry> entries = [with(StringComparer.Ordinal)];
try
{
if (!File.Exists(path)) return new(entries);
Expand Down
7 changes: 4 additions & 3 deletions Source/Specifications/SpecificationDisplayNames.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public static string ForClass(string @namespace, IEnumerable<string> typeNames)
{
var segments = @namespace.Length == 0 ? [] : @namespace.Split('.');
var start = Array.FindIndex(segments, _ => _.StartsWith("for_", StringComparison.Ordinal));
var levels = new List<string>();
List<string> levels = [];
if (start >= 0)
{
levels.Add(Readable(segments[start].Substring(4)));
Expand All @@ -54,8 +54,9 @@ public static string ForClass(string @namespace, IEnumerable<string> typeNames)
/// <returns>The readable class name.</returns>
public static string ForType(Type type)
{
var names = new List<string>();
for (var current = type; current is not null; current = current.DeclaringType) names.Insert(0, current.Name);
List<string> names = [];
for (var current = type; current is not null; current = current.DeclaringType) names.Add(current.Name);
names.Reverse();
return ForClass(type.Namespace ?? string.Empty, names);
}

Expand Down
Loading