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
6 changes: 5 additions & 1 deletion .github/workflows/dotnet-desktop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -86,14 +86,18 @@ jobs:
run: |
vstest.console "artifacts/bin/*/release/*Test.exe" /Framework:.NETFramework,Version=v4.7.2

- name: Run tests via vstest.console .NET FW Part 2
- name: Run tests via vstest.console .NET FW - Part 2
run: |
vstest.console "artifacts/bin/*/release_net472/*Test.exe" /Framework:.NETFramework,Version=v4.7.2

- name: Run tests via vstest.console .NET
run: |
vstest.console "artifacts/bin/*/release_net8.0/*Test.dll" /Framework:.NETCoreApp,Version=v8.0

- name: Run tests via vstest.console .NET - Part 2
run: |
vstest.console "artifacts/bin/*/release/*Tests.dll" /Framework:.NETCoreApp,Version=v8.0

# Upload the package: https://github.com/marketplace/actions/upload-a-build-artifact
- name: Upload build artifacts
uses: actions/upload-artifact@v4
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net472</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<IsPackable>false</IsPackable>
<OutputType>Exe</OutputType>
<LangVersion>14.0</LangVersion>
<TestTargetProject>..\..\Confuser.CLI\Confuser.CLI.csproj</TestTargetProject>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

<ItemGroup>
Expand All @@ -20,8 +22,10 @@
</ItemGroup>

<ItemGroup Label="Nuget Dependencies">
<PackageReference Include="MedallionShell" Version="1.6.2" />
<PackageReference Include="Moq" Version="4.20.72" />
<PackageReference Include="Microsoft.Build.Tasks.Core" Version="17.11.48" PrivateAssets="all" />
<PackageReference Include="TruePath.SystemIo" Version="1.12.0" />
<PackageReference Include="Verify.XunitV3" Version="31.13.5" />
<PackageReference Include="xunit.v3" Version="3.2.2" />
</ItemGroup>
Expand All @@ -32,6 +36,11 @@
<ProjectReference Include="..\..\Confuser.MSBuild.Tasks\Confuser.MSBuild.Tasks.csproj" />
</ItemGroup>

<ItemGroup>
<None Include="TestProjects\**\*" CopyToOutputDirectory="Always" />
<Compile Remove="TestProjects\**\*" />
</ItemGroup>

<ItemGroup>
<!--<RuntimeProjectFramework Include="$(TargetFrameworks)" />-->
<RuntimeProjectFramework Include="net472" />
Expand All @@ -55,15 +64,19 @@
<_RuntimeOutputsNet>@(RuntimeOutputsNet)</_RuntimeOutputsNet>
</PropertyGroup>
<ItemGroup>
<_RuntimeOutputsNetFrameworkFiles Include="$([System.IO.Path]::GetDirectoryName($(_RuntimeOutputsNetFramework)))\*.*"/>
<_RuntimeOutputsNetFrameworkFiles Include="$([System.IO.Path]::GetDirectoryName($(_RuntimeOutputsNetFramework)))\*.*" />
</ItemGroup>
<Copy SourceFiles="@(_RuntimeOutputsNetFrameworkFiles)"
Condition="'$(_RuntimeOutputsNetFramework)' != ''"
DestinationFolder="$(OutDir)test\net472\" />
<Copy SourceFiles="$([System.IO.Path]::GetDirectoryName($(_RuntimeOutputsNet)))\*.*"
Condition="'$(_RuntimeOutputsNet)' != ''"
DestinationFolder="$(OutDir)test\net8.0\" />
<Copy SourceFiles="@(_RuntimeOutputsNetFrameworkFiles)" Condition="'$(_RuntimeOutputsNetFramework)' != ''" DestinationFolder="$(OutDir)test\net472\" />
<Copy SourceFiles="$([System.IO.Path]::GetDirectoryName($(_RuntimeOutputsNet)))\*.*" Condition="'$(_RuntimeOutputsNet)' != ''" DestinationFolder="$(OutDir)test\net8.0\" />

</Target>

<ItemGroup>
<AssemblyAttribute Include="Confuser.MSBuild.Tasks.Tests.SolutionMetadataAttribute">
<_Parameter1>$(ArtifactsPath)\..</_Parameter1>
<_Parameter2>$(ArtifactsPath)</_Parameter2>
<_Parameter3>$(VersionPrefix)</_Parameter3>
</AssemblyAttribute>
</ItemGroup>

</Project>
34 changes: 34 additions & 0 deletions Tests/Confuser.MSBuild.Tasks.Tests/ConfusionTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Xunit;

namespace Confuser.MSBuild.Tasks.Tests {
public class ConfusionTest(ITestOutputHelper testOutputHelper) : ProjectTestBase(testOutputHelper) {

[Theory]
[InlineData("HelloWorld")]
public async Task Confuse_Exe_ShouldSucceed(string projectName) {
HashSet<string> expectedObjArtifacts =
[
$"{projectName}.dll"
];

var hostExeFile = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? $"{projectName}.exe" : projectName;
HashSet<string> expectedBinArtifacts =
[
$"{projectName}.dll",
hostExeFile,
$"{projectName}.runtimeconfig.json",
$"{projectName}.deps.json",
];

var result = await ExecuteTargets(projectName, "Restore", "Build");

Assert.True(result.ExitCode == 0);
AssertIncludes(expectedObjArtifacts, result.IntermediateArtifacts.Select(a => a.FileName).ToList());
AssertIncludes(expectedBinArtifacts, result.OutputArtifacts.Select(a => a.FileName).ToList());
}
}
}
62 changes: 62 additions & 0 deletions Tests/Confuser.MSBuild.Tasks.Tests/DotNetCliHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// SPDX-FileCopyrightText: 2025 Cesium contributors <https://github.com/ForNeVeR/Cesium>
//
// SPDX-License-Identifier: MIT
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using TruePath;
using Xunit;

namespace Confuser.MSBuild.Tasks.Tests {
internal class DotNetCliHelper {

public static async Task<IReadOnlyDictionary<string, string>> EvaluateMSBuildProperties(
ITestOutputHelper output,
string projectPath,
IReadOnlyDictionary<string, string>? env = null,
params string[] propertyNames) {
if (!propertyNames.Any())
return new Dictionary<string, string>();

var result = await ExecUtil.Run(
output,
ExecUtil.DotNetHost,
AbsolutePath.CurrentWorkingDirectory,
["msbuild", $"\"{projectPath}\"", $"-getProperty:{string.Join(",", propertyNames)}"],
null,
additionalEnvironment: env);
var resultString = result.StandardOutput;
if (propertyNames.Length == 1)
return new Dictionary<string, string> { { propertyNames[0], resultString } };

var resultJson = JsonDocument.Parse(resultString);
var propertiesJson = resultJson.RootElement.GetProperty("Properties").EnumerateObject().ToArray();

return propertiesJson
.ToDictionary(property => property.Name, property => property.Value.GetString() ?? string.Empty);
}

public static async Task<IEnumerable<(string identity, string? fullPath)>> EvaluateMSBuildItem(
ITestOutputHelper output,
string projectPath,
string itemName,
IReadOnlyDictionary<string, string>? env = null) {
var result = await ExecUtil.Run(
output,
ExecUtil.DotNetHost,
AbsolutePath.CurrentWorkingDirectory,
["msbuild", $"\"{projectPath}\"", $"-getItem:{itemName}"],
null,
additionalEnvironment: env);
var resultString = result.StandardOutput;
var resultJson = JsonDocument.Parse(resultString);
var itemsJson = resultJson.RootElement.GetProperty("Items").EnumerateObject().ToArray();
var itemsDict = itemsJson.ToDictionary(item => item.Name, item => item.Value.EnumerateArray());

return itemsDict[itemName].Select(meta => (meta.GetProperty("Identity").GetString()!, meta.GetProperty("FullPath").GetString()));
}
}
}
52 changes: 52 additions & 0 deletions Tests/Confuser.MSBuild.Tasks.Tests/ExecUtil.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// SPDX-FileCopyrightText: 2025 Cesium contributors <https://github.com/ForNeVeR/Cesium>
//
// SPDX-License-Identifier: MIT
using System.Collections.Generic;
using System.Threading.Tasks;
using Medallion.Shell;
using TruePath;
using Xunit;

namespace Confuser.MSBuild.Tasks.Tests {
internal class ExecUtil {
public static readonly LocalPath DotNetHost = new("dotnet");

public static async Task<CommandResult> Run(
ITestOutputHelper? output,
LocalPath executable,
AbsolutePath workingDirectory,
string[] args,
string? inputContent = null,
IReadOnlyDictionary<string, string>? additionalEnvironment = null) {
output?.WriteLine($"$ {executable} {string.Join(" ", args)}");
var command = Command.Run(executable.Value, args, o =>
{
o.WorkingDirectory(workingDirectory.Value);
if (inputContent is { }) {
o.StartInfo(_ => _.RedirectStandardInput = true);
}

if (additionalEnvironment != null) {
foreach (var (key, value) in additionalEnvironment) {
o.EnvironmentVariable(key, value);
}
}
});
if (inputContent is { }) {
command.StandardInput.Write(inputContent);
command.StandardInput.Close();
}

var result = await command.Task;
foreach (var s in result.StandardOutput.Split("\n"))
output?.WriteLine(s.TrimEnd());
if (result.StandardError.Trim() != "") {
foreach (var s in result.StandardError.Split("\n"))
output?.WriteLine($"[ERR] {s.TrimEnd()}");
}

output?.WriteLine($"Command exit code: {result.ExitCode}");
return result;
}
}
}
162 changes: 162 additions & 0 deletions Tests/Confuser.MSBuild.Tasks.Tests/PathExtensions._cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;

namespace Confuser.MSBuild.Tasks.Tests {
internal static class PathExtensions {
internal static bool EndsInDirectorySeparator(ReadOnlySpan<char> path) =>
path.Length > 0 && IsDirectorySeparator(path[^1]);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static bool IsDirectorySeparator(char c) {
return c == DirectorySeparatorChar || c == AltDirectorySeparatorChar;
}
/// <summary>Returns a comparison that can be used to compare file and directory names for equality.</summary>
public static string GetRelativePath(string relativeTo, string path) {
ArgumentNullException.ThrowIfNull(relativeTo);
ArgumentNullException.ThrowIfNull(path);

if (PathInternal.IsEffectivelyEmpty(relativeTo.AsSpan()))
throw new ArgumentException("Arg_PathEmpty", nameof(relativeTo));
if (PathInternal.IsEffectivelyEmpty(path.AsSpan()))
throw new ArgumentException("Arg_PathEmpty", nameof(path));

relativeTo = Path.GetFullPath(relativeTo);
path = Path.GetFullPath(path);

// Need to check if the roots are different- if they are we need to return the "to" path.
if (!AreRootsEqual(relativeTo, path, StringComparison.OrdinalIgnoreCase))
return path;

int commonLength = GetCommonPathLength(relativeTo, path, ignoreCase: true);

// If there is nothing in common they can't share the same root, return the "to" path as is.
if (commonLength == 0)
return path;

// Trailing separators aren't significant for comparison
int relativeToLength = relativeTo.Length;
if (EndsInDirectorySeparator(relativeTo.AsSpan()))
relativeToLength--;

bool pathEndsInSeparator = EndsInDirectorySeparator(path.AsSpan());
int pathLength = path.Length;
if (pathEndsInSeparator)
pathLength--;

// If we have effectively the same path, return "."
if (relativeToLength == pathLength && commonLength >= relativeToLength) return ".";

// We have the same root, we need to calculate the difference now using the
// common Length and Segment count past the length.
//
// Some examples:
//
// C:\Foo C:\Bar L3, S1 -> ..\Bar
// C:\Foo C:\Foo\Bar L6, S0 -> Bar
// C:\Foo\Bar C:\Bar\Bar L3, S2 -> ..\..\Bar\Bar
// C:\Foo\Foo C:\Foo\Bar L7, S1 -> ..\Bar

var sb = new StringBuilder(260);
sb.EnsureCapacity(Math.Max(relativeTo.Length, path.Length));

// Add parent segments for segments past the common on the "from" path
if (commonLength < relativeToLength) {
sb.Append("..");

for (int i = commonLength + 1; i < relativeToLength; i++) {
if (IsDirectorySeparator(relativeTo[i])) {
sb.Append(Path.DirectorySeparatorChar);
sb.Append("..");
}
}
}
else if (IsDirectorySeparator(path[commonLength])) {
// No parent segments and we need to eat the initial separator
// (C:\Foo C:\Foo\Bar case)
commonLength++;
}

// Now add the rest of the "to" path, adding back the trailing separator
int differenceLength = pathLength - commonLength;
if (pathEndsInSeparator)
differenceLength++;

if (differenceLength > 0) {
if (sb.Length > 0) {
sb.Append(Path.DirectorySeparatorChar);
}

sb.Append(path.AsSpan(commonLength, differenceLength).ToString());
}

return sb.ToString();
}

/// <summary>
/// Get the common path length from the start of the string.
/// </summary>
internal static int GetCommonPathLength(string first, string second, bool ignoreCase) {
int commonChars = EqualStartingCharacterCount(first, second, ignoreCase: ignoreCase);

// If nothing matches
if (commonChars == 0)
return commonChars;

// Or we're a full string and equal length or match to a separator
if (commonChars == first.Length
&& (commonChars == second.Length || IsDirectorySeparator(second[commonChars])))
return commonChars;

if (commonChars == second.Length && IsDirectorySeparator(first[commonChars]))
return commonChars;

// It's possible we matched somewhere in the middle of a segment e.g. C:\Foodie and C:\Foobar.
while (commonChars > 0 && !IsDirectorySeparator(first[commonChars - 1]))
commonChars--;

return commonChars;
}

/// <summary>
/// Gets the count of common characters from the left optionally ignoring case
/// </summary>
internal static int EqualStartingCharacterCount(string? first, string? second, bool ignoreCase) {
if (string.IsNullOrEmpty(first) || string.IsNullOrEmpty(second)) {
return 0;
}

int commonLength = first.AsSpan().CommonPrefixLength(second);
if (ignoreCase) {
for (; (uint)commonLength < (uint)first.Length; commonLength++) {
if (commonLength >= second.Length ||
char.ToUpperInvariant(first[commonLength]) != char.ToUpperInvariant(second[commonLength])) {
break;
}
}
}
return commonLength;
}

/// <summary>
/// Returns true if the two paths have the same root
/// </summary>
internal static bool AreRootsEqual(string? first, string? second, StringComparison comparisonType) {
int firstRootLength = GetRootLength(first.AsSpan());
int secondRootLength = GetRootLength(second.AsSpan());

return firstRootLength == secondRootLength
&& string.Compare(
strA: first,
indexA: 0,
strB: second,
indexB: 0,
length: firstRootLength,
comparisonType: comparisonType) == 0;
}
}
}
Loading
Loading