// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Avalonia.Headless.NUnit;
using AwesomeAssertions;
using ICSharpCode.Decompiler;
using ICSharpCode.ILSpy.AppEnv;
using ICSharpCode.ILSpy.Languages;
using NUnit.Framework;
namespace ICSharpCode.ILSpy.Tests.Languages;
///
/// End-to-end smoke test of the multi-assembly solution (.sln) export path that backs the
/// "Save Code" entry when several assemblies are selected. Drives
/// directly (no file picker)
/// and asserts a solution file plus one project per assembly is produced.
///
[TestFixture]
public class SolutionExportTests
{
[AvaloniaTest]
public async Task CreateSolution_Writes_Sln_And_Per_Assembly_Projects()
{
var (_, vm) = await TestHarness.BootAsync();
// Two tiny emitted fixtures keep the full-solution decompile fast; what this exercises is the
// writer's per-assembly project layout, not decompile breadth.
var assemblies = new[] {
await vm.OpenFixtureAsync("FixtureA"),
await vm.OpenFixtureAsync("FixtureB"),
};
var language = AppComposition.Current.GetExport()
.Languages.OfType().First();
var tempDir = Path.Combine(Path.GetTempPath(), "ILSpySln_" + System.Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(tempDir);
var slnPath = Path.Combine(tempDir, "Solution.sln");
try
{
var result = await ICSharpCode.ILSpy.SolutionWriter.CreateSolutionAsync(slnPath, language, assemblies, CancellationToken.None, new DecompilerSettings());
result.Success.Should().BeTrue(
"the solution export should succeed for valid assemblies. Status:\n" + result.StatusText);
File.Exists(slnPath).Should().BeTrue("the .sln file must be written to the chosen path");
foreach (var a in assemblies)
{
var projectDir = Path.Combine(tempDir, a.ShortName);
Directory.EnumerateFiles(projectDir, "*.csproj").Should().HaveCount(1,
$"each assembly gets its own subdirectory with one project file ({a.ShortName})");
Directory.EnumerateFiles(projectDir, "*.cs", SearchOption.AllDirectories).Should().NotBeEmpty(
$"the decompiled project for {a.ShortName} must contain at least one source file");
}
result.StatusText.Should().Contain("Created the Visual Studio Solution file",
"a successful run reports the solution-written breadcrumb");
}
finally
{
try
{ Directory.Delete(tempDir, recursive: true); }
catch { /* best-effort cleanup */ }
}
}
[AvaloniaTest]
public async Task CreateSolution_Aborts_On_Duplicate_Assembly_Names()
{
var (_, vm) = await TestHarness.BootAsync(3);
var assembly = vm.AssemblyTreeModel.AssemblyList!.GetAssemblies()
.First(a => a.IsLoadedAsValidAssembly && a.ShortName != TreeNavigation.CoreLibName);
await assembly.GetLoadResultAsync();
var language = AppComposition.Current.GetExport()
.Languages.OfType().First();
var tempDir = Path.Combine(Path.GetTempPath(), "ILSpySlnDup_" + System.Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(tempDir);
var slnPath = Path.Combine(tempDir, "Solution.sln");
try
{
// The same assembly twice collides on ShortName: the writer must refuse rather than
// clobber one project directory with another.
var result = await ICSharpCode.ILSpy.SolutionWriter.CreateSolutionAsync(
slnPath, language, new[] { assembly, assembly }, CancellationToken.None, new DecompilerSettings());
result.Success.Should().BeFalse("duplicate assembly names cannot produce a valid solution");
result.StatusText.Should().Contain("Duplicate assembly names");
File.Exists(slnPath).Should().BeFalse("no solution file is written when the export aborts");
}
finally
{
try
{ Directory.Delete(tempDir, recursive: true); }
catch { /* best-effort cleanup */ }
}
}
}