diff --git a/.github/workflows/dotnet-desktop.yml b/.github/workflows/dotnet-desktop.yml index d007c7eb..9fc13da4 100644 --- a/.github/workflows/dotnet-desktop.yml +++ b/.github/workflows/dotnet-desktop.yml @@ -86,7 +86,7 @@ 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 @@ -94,6 +94,10 @@ jobs: 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 diff --git a/Tests/Confuser.MSBuild.Tasks.Tests/Confuser.MSBuild.Tasks.Tests.csproj b/Tests/Confuser.MSBuild.Tasks.Tests/Confuser.MSBuild.Tasks.Tests.csproj index 7cc018c0..fbd8b959 100644 --- a/Tests/Confuser.MSBuild.Tasks.Tests/Confuser.MSBuild.Tasks.Tests.csproj +++ b/Tests/Confuser.MSBuild.Tasks.Tests/Confuser.MSBuild.Tasks.Tests.csproj @@ -1,10 +1,12 @@  - net472 + net8.0 false Exe + 14.0 ..\..\Confuser.CLI\Confuser.CLI.csproj + true @@ -20,8 +22,10 @@ + + @@ -32,6 +36,11 @@ + + + + + @@ -55,15 +64,19 @@ <_RuntimeOutputsNet>@(RuntimeOutputsNet) - <_RuntimeOutputsNetFrameworkFiles Include="$([System.IO.Path]::GetDirectoryName($(_RuntimeOutputsNetFramework)))\*.*"/> + <_RuntimeOutputsNetFrameworkFiles Include="$([System.IO.Path]::GetDirectoryName($(_RuntimeOutputsNetFramework)))\*.*" /> - - + + + + + <_Parameter1>$(ArtifactsPath)\.. + <_Parameter2>$(ArtifactsPath) + <_Parameter3>$(VersionPrefix) + + + diff --git a/Tests/Confuser.MSBuild.Tasks.Tests/ConfusionTest.cs b/Tests/Confuser.MSBuild.Tasks.Tests/ConfusionTest.cs new file mode 100644 index 00000000..c1b1d226 --- /dev/null +++ b/Tests/Confuser.MSBuild.Tasks.Tests/ConfusionTest.cs @@ -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 expectedObjArtifacts = + [ + $"{projectName}.dll" + ]; + + var hostExeFile = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? $"{projectName}.exe" : projectName; + HashSet 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()); + } + } +} diff --git a/Tests/Confuser.MSBuild.Tasks.Tests/DotNetCliHelper.cs b/Tests/Confuser.MSBuild.Tasks.Tests/DotNetCliHelper.cs new file mode 100644 index 00000000..44276314 --- /dev/null +++ b/Tests/Confuser.MSBuild.Tasks.Tests/DotNetCliHelper.cs @@ -0,0 +1,62 @@ +// SPDX-FileCopyrightText: 2025 Cesium contributors +// +// 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> EvaluateMSBuildProperties( + ITestOutputHelper output, + string projectPath, + IReadOnlyDictionary? env = null, + params string[] propertyNames) { + if (!propertyNames.Any()) + return new Dictionary(); + + 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 { { 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> EvaluateMSBuildItem( + ITestOutputHelper output, + string projectPath, + string itemName, + IReadOnlyDictionary? 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())); + } + } +} diff --git a/Tests/Confuser.MSBuild.Tasks.Tests/ExecUtil.cs b/Tests/Confuser.MSBuild.Tasks.Tests/ExecUtil.cs new file mode 100644 index 00000000..2cc27f42 --- /dev/null +++ b/Tests/Confuser.MSBuild.Tasks.Tests/ExecUtil.cs @@ -0,0 +1,52 @@ +// SPDX-FileCopyrightText: 2025 Cesium contributors +// +// 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 Run( + ITestOutputHelper? output, + LocalPath executable, + AbsolutePath workingDirectory, + string[] args, + string? inputContent = null, + IReadOnlyDictionary? 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; + } + } +} diff --git a/Tests/Confuser.MSBuild.Tasks.Tests/PathExtensions._cs b/Tests/Confuser.MSBuild.Tasks.Tests/PathExtensions._cs new file mode 100644 index 00000000..60082be0 --- /dev/null +++ b/Tests/Confuser.MSBuild.Tasks.Tests/PathExtensions._cs @@ -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 path) => + path.Length > 0 && IsDirectorySeparator(path[^1]); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool IsDirectorySeparator(char c) { + return c == DirectorySeparatorChar || c == AltDirectorySeparatorChar; + } + /// Returns a comparison that can be used to compare file and directory names for equality. + 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(); + } + + /// + /// Get the common path length from the start of the string. + /// + 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; + } + + /// + /// Gets the count of common characters from the left optionally ignoring case + /// + 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; + } + + /// + /// Returns true if the two paths have the same root + /// + 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; + } + } +} diff --git a/Tests/Confuser.MSBuild.Tasks.Tests/ProjectTestBase.cs b/Tests/Confuser.MSBuild.Tasks.Tests/ProjectTestBase.cs new file mode 100644 index 00000000..65267d79 --- /dev/null +++ b/Tests/Confuser.MSBuild.Tasks.Tests/ProjectTestBase.cs @@ -0,0 +1,202 @@ +// SPDX-FileCopyrightText: 2025 Cesium contributors +// +// SPDX-License-Identifier: MIT +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Text.Json; +using System.Threading.Tasks; +using TruePath; +using Xunit; +using Xunit.Sdk; + +namespace Confuser.MSBuild.Tasks.Tests { + // Adapted from SdkTestBase.cs + public abstract class ProjectTestBase : IDisposable { + private readonly ITestOutputHelper _testOutputHelper; + private readonly string _temporaryPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + private readonly Dictionary _dotNetEnvVars; + + private string NuGetConfigPath => Path.Combine(_temporaryPath, "NuGet.config"); + + public ProjectTestBase(ITestOutputHelper testOutputHelper) { + _testOutputHelper = testOutputHelper; + _dotNetEnvVars = new() { ["NUGET_PACKAGES"] = Path.Combine(_temporaryPath, "package-cache") }; + + File.Delete(_temporaryPath); + + _testOutputHelper.WriteLine($"Test projects folder: {_temporaryPath}"); + + var assemblyPath = Assembly.GetExecutingAssembly().Location; + var testDataPath = Path.Combine(Path.GetDirectoryName(assemblyPath)!, "TestProjects"); + _testOutputHelper.WriteLine($"Copying TestProjects to {_temporaryPath}..."); + CopyDirectoryRecursive(testDataPath, _temporaryPath); + +#if DEBUG + var nupkgPath = (SolutionMetadata.SourceRoot / "artifacts/package/debug").Canonicalize(); +#else + var nupkgPath = (SolutionMetadata.SourceRoot / "artifacts/package/release").Canonicalize(); +#endif + _testOutputHelper.WriteLine($"Local NuGet feed: {nupkgPath}."); + EmitNuGetConfig(NuGetConfigPath, nupkgPath); + } + + public static void AssertIncludes(IReadOnlyCollection expected, IReadOnlyCollection all) { + var foundItems = all.Where(expected.Contains).ToList(); + var remainingItems = expected.Except(foundItems).ToList(); + if (remainingItems.Count != 0) + throw new XunitException($"Expected elements are missing: [{string.Join(", ", remainingItems)}]"); + } + + + + private static void EmitNuGetConfig(string configFilePath, AbsolutePath packageSourcePath) { + File.WriteAllText(configFilePath, $""" + + + + + + """); + } + + protected async Task ExecuteTargets(string projectName, params string[] targets) { + var projectFile = $"{projectName}/{projectName}.csproj"; + var joinedTargets = string.Join(";", targets); + var testProjectFile = Path.GetFullPath(Path.Combine(_temporaryPath, projectFile)); + var testProjectFolder = Path.GetDirectoryName(testProjectFile) ?? throw new ArgumentNullException(nameof(testProjectFile)); + var binLogFile = Path.Combine(testProjectFolder, $"build_result_{projectName}_{DateTime.UtcNow:yyyy-dd-M_HH-mm-s}.binlog"); + + const string objFolderPropertyName = "IntermediateOutputPath"; + const string binFolderPropertyName = "OutDir"; + + var startInfo = new ProcessStartInfo { + WorkingDirectory = testProjectFolder, + FileName = "dotnet", + ArgumentList = { "msbuild", testProjectFile, $"/t:{joinedTargets}", "/restore", $"/bl:{binLogFile}" }, + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true, + UseShellExecute = false, + }; + foreach (var pair in _dotNetEnvVars) { + var name = pair.Key; + var var = pair.Value; + startInfo.Environment[name] = var; + } + + using var process = new Process(); + process.StartInfo = startInfo; + var stdOutOutput = ""; + var stdErrOutput = ""; + process.OutputDataReceived += (_, e) => + { + if (!string.IsNullOrEmpty(e.Data)) { + stdOutOutput += e.Data + Environment.NewLine; + _testOutputHelper.WriteLine($"[stdout]: {e.Data}"); + } + }; + + process.ErrorDataReceived += (_, e) => + { + if (!string.IsNullOrEmpty(e.Data)) { + stdErrOutput += e.Data + Environment.NewLine; + _testOutputHelper.WriteLine($"[stderr]: {e.Data}"); + } + }; + + process.Start(); + + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + + process.WaitForExit(); + + var success = process.ExitCode == 0; + + _testOutputHelper.WriteLine(success + ? "Build succeeded" + : $"Build failed with exit code {process.ExitCode}"); + + var properties = await DotNetCliHelper.EvaluateMSBuildProperties( + _testOutputHelper, + testProjectFile, + env: _dotNetEnvVars, + objFolderPropertyName, + binFolderPropertyName); + _testOutputHelper.WriteLine($"Properties request result: {JsonSerializer.Serialize(properties, new JsonSerializerOptions { WriteIndented = false })}"); + + var binFolder = NormalizePath(Path.GetFullPath(properties[binFolderPropertyName], testProjectFolder)); + var objFolder = NormalizePath(Path.GetFullPath(properties[objFolderPropertyName], testProjectFolder)); + + var binArtifacts = CollectArtifacts(binFolder); + var objArtifacts = CollectArtifacts(objFolder); + + var result = new BuildResult(process.ExitCode, stdOutOutput, stdErrOutput, binArtifacts, objArtifacts); + _testOutputHelper.WriteLine($"Build result: {JsonSerializer.Serialize(result, new JsonSerializerOptions { WriteIndented = true })}"); + return result; + + IReadOnlyCollection CollectArtifacts(string folder) { + _testOutputHelper.WriteLine($"Collecting artifacts from '{folder}' folder"); + return Directory.Exists(folder) + ? Directory.GetFiles(folder, "*.*", SearchOption.AllDirectories) + .Select(path => new BuildArtifact(Path.GetRelativePath(folder, path), path)) + .ToList() + : Array.Empty(); + } + } + + private static void CopyDirectoryRecursive(string source, string target) { + Directory.CreateDirectory(target); + + foreach (var subDirPath in Directory.GetDirectories(source)) { + var dirName = Path.GetFileName(subDirPath); + CopyDirectoryRecursive(subDirPath, Path.Combine(target, dirName)); + } + + foreach (var filePath in Directory.GetFiles(source)) { + var fileName = Path.GetFileName(filePath); + File.Copy(filePath, Path.Combine(target, fileName)); + } + } + + private static string NormalizePath(string path) { + var normalizedPath = new Uri(path).LocalPath; + return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) + ? normalizedPath + : normalizedPath.Replace('\\', '/'); + } + + private void ClearOutput() { + Directory.Delete(_temporaryPath, true); + } + + public void Dispose() { + ClearOutput(); + } + + protected class BuildResult( + int exitCode, + string stdOutOutput, + string stdErrOutput, + IReadOnlyCollection outputArtifacts, + IReadOnlyCollection intermediateArtifacts) { + public int ExitCode { get; private set; } = exitCode; + public string StdOutOutput { get; private set; } = stdOutOutput; + public string StdErrOutput { get; private set; } = stdErrOutput; + public IReadOnlyCollection OutputArtifacts { get; private set; } = outputArtifacts; + public IReadOnlyCollection IntermediateArtifacts { get; private set; } = intermediateArtifacts; + } + + protected class BuildArtifact( + string fileName, + string fullPath) { + public string FileName { get; private set; } = fileName; + public string FullPath { get; private set; } = fullPath; + } + } +} diff --git a/Tests/Confuser.MSBuild.Tasks.Tests/SolutionMetadata.cs b/Tests/Confuser.MSBuild.Tasks.Tests/SolutionMetadata.cs new file mode 100644 index 00000000..5ae3789c --- /dev/null +++ b/Tests/Confuser.MSBuild.Tasks.Tests/SolutionMetadata.cs @@ -0,0 +1,19 @@ +// SPDX-FileCopyrightText: 2025 Cesium contributors +// +// SPDX-License-Identifier: MIT +using System; +using System.Reflection; +using TruePath; + +namespace Confuser.MSBuild.Tasks.Tests { + internal class SolutionMetadata { + public static AbsolutePath SourceRoot => new(ResolvedAttribute.SourceRoot); + public static AbsolutePath ArtifactsRoot => new(ResolvedAttribute.ArtifactsRoot); + public static string VersionPrefix => ResolvedAttribute.VersionPrefix; + + private static SolutionMetadataAttribute ResolvedAttribute => + typeof(SolutionMetadata).Assembly.GetCustomAttribute() + ?? throw new Exception($"Missing {nameof(SolutionMetadataAttribute)} metadata attribute."); + + } +} diff --git a/Tests/Confuser.MSBuild.Tasks.Tests/SolutionMetadataAttribute.cs b/Tests/Confuser.MSBuild.Tasks.Tests/SolutionMetadataAttribute.cs new file mode 100644 index 00000000..29a39a47 --- /dev/null +++ b/Tests/Confuser.MSBuild.Tasks.Tests/SolutionMetadataAttribute.cs @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: 2025 Cesium contributors +// +// SPDX-License-Identifier: MIT +using System; + +namespace Confuser.MSBuild.Tasks.Tests { + internal class SolutionMetadataAttribute : Attribute { + public string SourceRoot { get; } + public string ArtifactsRoot { get; } + public string VersionPrefix { get; } + + public SolutionMetadataAttribute(string sourceRoot, string artifactsRoot, string versionPrefix) { + SourceRoot = sourceRoot; + ArtifactsRoot = artifactsRoot; + VersionPrefix = versionPrefix; + } + } +} diff --git a/Tests/Confuser.MSBuild.Tasks.Tests/TestProjects/HelloWorld/HelloWorld.csproj b/Tests/Confuser.MSBuild.Tasks.Tests/TestProjects/HelloWorld/HelloWorld.csproj new file mode 100644 index 00000000..d77595d9 --- /dev/null +++ b/Tests/Confuser.MSBuild.Tasks.Tests/TestProjects/HelloWorld/HelloWorld.csproj @@ -0,0 +1,14 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + diff --git a/Tests/Confuser.MSBuild.Tasks.Tests/TestProjects/HelloWorld/Program.cs b/Tests/Confuser.MSBuild.Tasks.Tests/TestProjects/HelloWorld/Program.cs new file mode 100644 index 00000000..f7f02a1c --- /dev/null +++ b/Tests/Confuser.MSBuild.Tasks.Tests/TestProjects/HelloWorld/Program.cs @@ -0,0 +1 @@ +Console.WriteLine("Hello, World!"); diff --git a/Tests/Confuser.MSBuild.Tasks.Tests/VerifyTestBase.cs b/Tests/Confuser.MSBuild.Tasks.Tests/VerifyTestBase.cs index c8fa5b39..b72881aa 100644 --- a/Tests/Confuser.MSBuild.Tasks.Tests/VerifyTestBase.cs +++ b/Tests/Confuser.MSBuild.Tasks.Tests/VerifyTestBase.cs @@ -1,8 +1,7 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +// SPDX-FileCopyrightText: 2025 Cesium contributors +// +// SPDX-License-Identifier: MIT +using System; using VerifyTests; namespace Confuser.MSBuild.Tasks.Tests {