Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
c65ad1e
SBRP validation tests
ellahathaway Nov 2, 2023
ce1a70d
Style fixes, add config and utilities, use one test project
ellahathaway Nov 6, 2023
19b8f42
Add whitespace, remove System.Compression references
ellahathaway Nov 6, 2023
8ab54ea
Fix nulls in DummyAttributeTypeProvider, newline in Utilities, -sb co…
ellahathaway Nov 7, 2023
43f3fb8
Skip validation tests on Windows
ellahathaway Nov 10, 2023
d46daca
Merge branch 'main' of https://github.com/dotnet/source-build-referen…
ellahathaway Nov 10, 2023
c290b6c
Removed extra skip
ellahathaway Nov 10, 2023
696909b
Merge branch 'main' into validate-sbrp
ellahathaway Nov 16, 2023
df2ba21
Merge branch 'main' of https://github.com/dotnet/source-build-referen…
ellahathaway Nov 20, 2023
2aae3ce
Check env vars in tests, refactor validation test structure, omit typ…
ellahathaway Nov 20, 2023
417c995
Abstract xunit version, check env vars in config
ellahathaway Nov 27, 2023
bd33306
Clean up Config.cs
ellahathaway Nov 27, 2023
3c6c97c
Use const for source-build-reference-packages
ellahathaway Nov 28, 2023
dcb854c
Merge branch 'main' of https://github.com/dotnet/source-build-referen…
ellahathaway Dec 11, 2023
a4fd3f5
Use runtime config properties, move logic from Config.cs to PathUtili…
ellahathaway Dec 11, 2023
c00d134
Merge branch 'main' of https://github.com/dotnet/source-build-referen…
ellahathaway Dec 15, 2023
cb1b815
Move skip to constructor, static methods, remove spaces, sb path
ellahathaway Dec 15, 2023
f8eeb84
Merge branch 'main' into validate-sbrp
ViktorHofer Dec 18, 2023
a756e4a
Update tests/SbrpTests/SbrpTests.Tests.csproj
ViktorHofer Dec 18, 2023
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 eng/Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
</ItemGroup>

<ItemGroup Condition="'$(Test)' == 'true'">
<ProjectToBuild Include="$(RepoRoot)tests\GenerateScriptTests\GenerateScript.Tests.csproj" Test="$(Test)" />
<ProjectToBuild Include="$(RepoRoot)tests\SbrpTests\SbrpTests.Tests.csproj" Test="$(Test)" />
</ItemGroup>

<ItemGroup Condition="'$(GeneratePackageSource)' != 'true' and '$(Test)' != 'true'">
Expand Down
2 changes: 2 additions & 0 deletions eng/Versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,7 @@
<MicrosoftNETCoreILDAsmVersion>6.0.0-preview.6.21352.12</MicrosoftNETCoreILDAsmVersion>
<!-- SDK dependencies -->
<MicrosoftDotNetGenAPITaskPackageVersion>9.0.100-alpha.1.23618.1</MicrosoftDotNetGenAPITaskPackageVersion>
<!-- xUnit dependencies -->
<XunitSkippableFactVersion>1.4.13</XunitSkippableFactVersion>
Comment on lines +24 to +25
Copy link
Member

@ViktorHofer ViktorHofer Dec 18, 2023

Choose a reason for hiding this comment

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

It's absolutely fine to use SkippableFact here (as you don't want to run these tests in source build without prebuilts). Just wanted to let you know that we already have in-built support for this and more in Arcade via Microsoft.DotNet.XUnitExtensions.

Copy link
Member Author

Choose a reason for hiding this comment

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

This is great to know! Thanks for pointing this out.

</PropertyGroup>
</Project>
8 changes: 0 additions & 8 deletions tests/GenerateScriptTests/GenerateScript.Tests.csproj

This file was deleted.

30 changes: 30 additions & 0 deletions tests/SbrpTests/DummyAttributeTypeProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Reflection;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;

namespace SbrpTests;

internal class DummyAttributeTypeProvider : ICustomAttributeTypeProvider<Type?>
{
public static readonly DummyAttributeTypeProvider Instance = new();

public Type? GetPrimitiveType(PrimitiveTypeCode typeCode) => default(Type);

public Type? GetSystemType() => default(Type);

public Type? GetSZArrayType(Type? elementType) => default(Type);

public Type? GetTypeFromDefinition(MetadataReader reader, TypeDefinitionHandle handle, byte rawTypeKind) => default(Type);

public Type? GetTypeFromReference(MetadataReader reader, TypeReferenceHandle handle, byte rawTypeKind) => default(Type);

public Type? GetTypeFromSerializedName(string name) => default(Type);

public PrimitiveTypeCode GetUnderlyingEnumType(Type? type) => default(PrimitiveTypeCode);

public bool IsSystemType(Type? type) => default(bool);
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
using System.Text;
using Xunit.Abstractions;

namespace GenerateScriptTests;
namespace SbrpTests;

internal static class ExecuteHelper
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
using Xunit;
using Xunit.Abstractions;

namespace GenerateScriptTests;
namespace SbrpTests;

public class GenerateScriptTests
{
Expand All @@ -28,13 +28,11 @@ public enum PackageType
};

public string SandboxDirectory { get; set; }
public string RepoRoot { get; set; }
public ITestOutputHelper output { get; set; }
public ITestOutputHelper Output { get; set; }

public GenerateScriptTests(ITestOutputHelper output)
{
this.output = output;
RepoRoot = Environment.CurrentDirectory.Substring(0, Environment.CurrentDirectory.IndexOf("artifacts"));
Output = output;
SandboxDirectory = Path.Combine(Environment.CurrentDirectory, $"GenerateTests-{DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString()}");
Directory.CreateDirectory(SandboxDirectory);
}
Expand All @@ -43,25 +41,25 @@ public GenerateScriptTests(ITestOutputHelper output)
[MemberData(nameof(GenerateScriptTests.Data), MemberType = typeof(GenerateScriptTests))]
public void VerifyGenerateScript(string package, string version, PackageType type)
{
string command = Path.Combine(RepoRoot, RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "generate.cmd" : "generate.sh");
string command = Path.Combine(PathUtilities.GetRepoRoot(), RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "generate.cmd" : "generate.sh");
string arguments = $"-p {package},{version} -x -d {SandboxDirectory}";
string pkgSrcDirectory = string.Empty;
string pkgSandboxDirectory = Path.Combine(SandboxDirectory, package.ToLower(), version);

switch (type)
{
case PackageType.Reference:
pkgSrcDirectory = Path.Combine(RepoRoot, "src", "referencePackages", "src", package.ToLower(), version);
pkgSrcDirectory = Path.Combine(PathUtilities.GetRepoRoot(), "src", "referencePackages", "src", package.ToLower(), version);
break;
case PackageType.Text:
arguments += " -t text";
pkgSrcDirectory = Path.Combine(RepoRoot, "src", "textOnlyPackages", "src", package.ToLower(), version);
pkgSrcDirectory = Path.Combine(PathUtilities.GetRepoRoot(), "src", "textOnlyPackages", "src", package.ToLower(), version);
break;
}

ExecuteHelper.ExecuteProcessValidateExitCode(command, arguments, output);
ExecuteHelper.ExecuteProcessValidateExitCode(command, arguments, Output);

string diff = ExecuteHelper.ExecuteProcess("git", $"diff --no-index {pkgSrcDirectory} {pkgSandboxDirectory}", output, true).StdOut;
string diff = ExecuteHelper.ExecuteProcess("git", $"diff --no-index {pkgSrcDirectory} {pkgSandboxDirectory}", Output, true).StdOut;
if (diff != string.Empty)
{
Assert.Fail($"Regenerated package '{package}' does not match the checked-in content. {Environment.NewLine}"
Expand Down
26 changes: 26 additions & 0 deletions tests/SbrpTests/PathUtilities.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.IO;
using System.Runtime.CompilerServices;

namespace SbrpTests;

internal static class PathUtilities
{
public static string GetRepoRoot () =>
(string)AppContext.GetData("SbrpTests.RepoRoot")!;

public static string GetSourceBuildRepoRoot ()
{
var artifactsDir = (string)AppContext.GetData ("SbrpTests.ArtifactsDir")!;
return Path.Combine(artifactsDir, "sb", "src");
}

public static string GetSourceBuildPackagesShippingDir ()
{
var configuration = (string)AppContext.GetData ("SbrpTests.Configuration")!;
return Path.Combine(GetSourceBuildRepoRoot(), "artifacts", "packages", configuration, "Shipping");
}
}
27 changes: 27 additions & 0 deletions tests/SbrpTests/SbrpTests.Tests.csproj
Copy link
Member

Choose a reason for hiding this comment

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

The file name feels a little odd to me. Any reason it can be simplified to SbrpTests.csproj?

Copy link
Member

Choose a reason for hiding this comment

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

The .Tests project name suffix is important in order for Arcade to recognize the project as a test project: https://github.com/dotnet/arcade/blob/3531486ae4b4e78149d815d99db3d3a0e8a3c64d/src/Microsoft.DotNet.Arcade.Sdk/tools/Tests.props#L16

That said, SBRP.Tests.csproj would also work.

Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>$(NetCurrent)</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="NuGet.Packaging" Version="$(NuGetPackagingVersion)" />
<PackageReference Include="Xunit.SkippableFact" Version="$(XunitSkippableFactVersion)" />
</ItemGroup>

<ItemGroup>
<RuntimeHostConfigurationOption Include="SbrpTests.RepoRoot">
<Value>$(RepoRoot)</Value>
</RuntimeHostConfigurationOption>

<RuntimeHostConfigurationOption Include="SbrpTests.Configuration">
<Value>$(Configuration)</Value>
</RuntimeHostConfigurationOption>

<RuntimeHostConfigurationOption Include="SbrpTests.ArtifactsDir">
<Value>$(ArtifactsDir)</Value>
</RuntimeHostConfigurationOption>
</ItemGroup>

</Project>
166 changes: 166 additions & 0 deletions tests/SbrpTests/ValidationTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reflection;
using System.Reflection.Metadata;
using System.Reflection.PortableExecutable;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using NuGet.Packaging;
using NuGet.Packaging.Signing;
using Xunit;
using Xunit.Abstractions;

namespace SbrpTests;

public class ValidationTests
{
private const string SbrpAttributeType = "System.Reflection.AssemblyMetadataAttribute";
private const string SbrpRepoIdentifier = "source-build-reference-packages";
private const string VersionPattern = @"(\.\d)+([\-\w])*";
public ITestOutputHelper Output { get; set; }

public ValidationTests(ITestOutputHelper output)
{
Output = output;
Skip.If(RuntimeInformation.IsOSPlatform(OSPlatform.Windows), "Validation tests are not supported on Windows.");
}

[SkippableFact]
public void ValidateSbrpAttribute()
{
string[] packages = GetPackages();

HashSet<string> targetAndTextOnlyPacks = new (
Directory.GetDirectories(Path.Combine(PathUtilities.GetSourceBuildRepoRoot(), "src/targetPacks/ILsrc"))
.Union(Directory.GetDirectories(Path.Combine(PathUtilities.GetSourceBuildRepoRoot(), "src/textOnlyPackages/src")))
.Select(x => Path.GetFileName(x).ToLower())
);

var filteredPackages = packages
.Where(package =>
{
string packageName = Path.GetFileNameWithoutExtension(package).ToLower();
packageName = Regex.Replace(packageName, VersionPattern, string.Empty);
return !targetAndTextOnlyPacks.Contains(packageName);
});

Output.WriteLine($"Checking {filteredPackages.Count()} packages for SBRP attribute.");

foreach (var package in filteredPackages)
{
string tempDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName(), Path.GetFileNameWithoutExtension(package));

try
{
Directory.CreateDirectory(tempDirectory);
ZipFile.ExtractToDirectory(package, tempDirectory);
var dlls = Directory.GetFiles(tempDirectory, "*.dll", SearchOption.AllDirectories);

foreach (var dll in dlls)
{
using FileStream stream = new (dll, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
using PEReader peReader = new (stream);
MetadataReader reader = peReader.GetMetadataReader();

Assert.True(HasSbrpAttribute(reader), $"{package}/{Path.GetRelativePath(tempDirectory, dll)} does not contain the {SbrpAttributeType} attribute with key='source' and value='{SbrpRepoIdentifier}'.");
}
}
finally
{
Directory.Delete(tempDirectory, true);
}
}
}

[SkippableFact]
public async Task ValidateSignatures()
{
string[] packages = GetPackages();

ISignatureVerificationProvider[] trustProviders = [new SignatureTrustAndValidityVerificationProvider()];
PackageSignatureVerifier verifier = new (trustProviders);
var settings = SignedPackageVerifierSettings.GetDefault();

Output.WriteLine($"Checking {packages.Count()} packages for signatures.");

foreach (var package in packages)
{
bool isSigned = await IsPackageSignedAsync(package, verifier, settings);
Assert.False(isSigned, $"{package} is signed. Signed packages are not allowed in {SbrpRepoIdentifier}.");
}
}

private static string[] GetPackages()
{
string buildPackagesDirectory = PathUtilities.GetSourceBuildPackagesShippingDir();

string[] packages = Directory.GetFiles(buildPackagesDirectory, "*.nupkg", SearchOption.AllDirectories);

if (packages.Length == 0)
{
throw new FileNotFoundException($"No packages found in {buildPackagesDirectory}");
}
return packages;
}

private static bool HasSbrpAttribute(MetadataReader reader) =>
reader.CustomAttributes
.Select(attrHandle => reader.GetCustomAttribute(attrHandle))
.Any(attr => IsAttributeSbrp(reader, attr));

private static bool IsAttributeSbrp(MetadataReader reader, CustomAttribute attr)
{
string attributeType = string.Empty;

if (attr.Constructor.Kind == HandleKind.MemberReference)
{
var mref = reader.GetMemberReference((MemberReferenceHandle)attr.Constructor);
if (mref.Parent.Kind == HandleKind.TypeReference)
{
var tref = reader.GetTypeReference((TypeReferenceHandle)mref.Parent);
attributeType = $"{reader.GetString(tref.Namespace)}.{reader.GetString(tref.Name)}";
}
else if (mref.Parent.Kind == HandleKind.TypeDefinition)
{
var tdef = reader.GetTypeDefinition((TypeDefinitionHandle)mref.Parent);
attributeType = $"{reader.GetString(tdef.Namespace)}.{reader.GetString(tdef.Name)}";
}
}
else if (attr.Constructor.Kind == HandleKind.MethodDefinition)
{
var mdef = reader.GetMethodDefinition((MethodDefinitionHandle)attr.Constructor);
var tdef = reader.GetTypeDefinition(mdef.GetDeclaringType());
attributeType = $"{reader.GetString(tdef.Namespace)}.{reader.GetString(tdef.Name)}";
}

if (attributeType == SbrpAttributeType)
{
var decodedValue = attr.DecodeValue(DummyAttributeTypeProvider.Instance);
try
{
return decodedValue.FixedArguments[0].Value?.ToString() == "source" && decodedValue.FixedArguments[1].Value?.ToString() == SbrpRepoIdentifier;
}
catch
{
throw new InvalidOperationException($"{SbrpAttributeType} is not formatted properly with a key, value pair.");
}
}

return false;
}

private static async Task<bool> IsPackageSignedAsync(string packagePath, PackageSignatureVerifier verifier, SignedPackageVerifierSettings settings)
{
using PackageArchiveReader packageReader = new (packagePath);
var result = await verifier.VerifySignaturesAsync(packageReader, settings, CancellationToken.None);
return result.IsSigned;
}
}