File: ProjectSystemShim\CPS\SourceFileHandlingTests.cs
Web Access
Project: src\src\VisualStudio\CSharp\Test\Microsoft.VisualStudio.LanguageServices.CSharp.UnitTests.csproj (Microsoft.VisualStudio.LanguageServices.CSharp.UnitTests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
#nullable disable
 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim.Framework;
using Roslyn.Test.Utilities;
using Xunit;
 
namespace Roslyn.VisualStudio.CSharp.UnitTests.ProjectSystemShim.CPS
{
    using static CSharpHelpers;
 
    [UseExportProvider]
    [Trait(Traits.Feature, Traits.Features.ProjectSystemShims)]
    public class SourceFileHandlingTests
    {
        [WpfFact]
        public async Task AddRemoveSourceFile_CPS()
        {
            using var environment = new TestEnvironment();
            using var project = await CreateCSharpCPSProjectAsync(environment, "project1");
            IEnumerable<Document> GetCurrentDocuments() => environment.Workspace.CurrentSolution.Projects.Single().Documents;
 
            Assert.Empty(GetCurrentDocuments());
 
            // Add source file
            var sourceFileFullPath = @"c:\source.cs";
            project.AddSourceFile(sourceFileFullPath);
            Assert.True(GetCurrentDocuments().Any(s => s.FilePath == sourceFileFullPath));
 
            // Remove source file
            project.RemoveSourceFile(sourceFileFullPath);
            Assert.Empty(GetCurrentDocuments());
        }
 
        [WpfFact]
        public async Task AddRemoveAdditionalFile_CPS()
        {
            using var environment = new TestEnvironment();
            using var project = await CreateCSharpCPSProjectAsync(environment, "project1");
            IEnumerable<TextDocument> GetCurrentAdditionalDocuments() => environment.Workspace.CurrentSolution.Projects.Single().AdditionalDocuments;
            Assert.Empty(GetCurrentAdditionalDocuments());
 
            // Add additional file
            var additionalFileFullPath = @"c:\source.cs";
            project.AddAdditionalFile(additionalFileFullPath);
            Assert.True(GetCurrentAdditionalDocuments().Any(s => s.FilePath == additionalFileFullPath));
 
            // Remove additional file
            project.RemoveAdditionalFile(additionalFileFullPath);
            Assert.Empty(GetCurrentAdditionalDocuments());
        }
 
        [WpfFact]
        public async Task ReorderSourceFiles_CPS()
        {
            using var environment = new TestEnvironment();
            using var project = await CreateCSharpCPSProjectAsync(environment, "project1");
            IEnumerable<Document> GetCurrentDocuments() => environment.Workspace.CurrentSolution.Projects.Single().Documents;
            VersionStamp GetVersion() => environment.Workspace.CurrentSolution.Projects.Single().Version;
 
            Assert.Empty(GetCurrentDocuments());
 
            var sourceFileFullPath1 = @"c:\source1.cs";
            var sourceFileFullPath2 = @"c:\source2.cs";
            var sourceFileFullPath3 = @"c:\source3.cs";
            var sourceFileFullPath4 = @"c:\source4.cs";
            var sourceFileFullPath5 = @"c:\source5.cs";
            project.AddSourceFile(sourceFileFullPath1);
            project.AddSourceFile(sourceFileFullPath2);
            project.AddSourceFile(sourceFileFullPath3);
            project.AddSourceFile(sourceFileFullPath4);
            project.AddSourceFile(sourceFileFullPath5);
 
            project.RemoveSourceFile(sourceFileFullPath2);
            project.AddSourceFile(sourceFileFullPath2);
 
            project.RemoveSourceFile(sourceFileFullPath4);
            project.AddSourceFile(sourceFileFullPath4);
 
            var oldVersion = GetVersion();
 
            project.ReorderSourceFiles([sourceFileFullPath5, sourceFileFullPath4, sourceFileFullPath3, sourceFileFullPath2, sourceFileFullPath1]);
 
            var newVersion = GetVersion();
 
            project.ReorderSourceFiles([sourceFileFullPath5, sourceFileFullPath4, sourceFileFullPath3, sourceFileFullPath2, sourceFileFullPath1]);
 
            var newSameVersion = GetVersion();
 
            // Reordering should result in a new version if the order is different. If it's the same, the version should stay the same.
            Assert.NotEqual(oldVersion, newVersion);
            Assert.Equal(newVersion, newSameVersion);
 
            var documents = GetCurrentDocuments().ToArray();
 
            Assert.Equal(documents[0].FilePath, sourceFileFullPath5, StringComparer.OrdinalIgnoreCase);
            Assert.Equal(documents[1].FilePath, sourceFileFullPath4, StringComparer.OrdinalIgnoreCase);
            Assert.Equal(documents[2].FilePath, sourceFileFullPath3, StringComparer.OrdinalIgnoreCase);
            Assert.Equal(documents[3].FilePath, sourceFileFullPath2, StringComparer.OrdinalIgnoreCase);
            Assert.Equal(documents[4].FilePath, sourceFileFullPath1, StringComparer.OrdinalIgnoreCase);
        }
 
        [WpfFact]
        public async Task ReorderSourceFilesBatch_CPS()
        {
            using var environment = new TestEnvironment();
            using var project = await CreateCSharpCPSProjectAsync(environment, "project1");
            IEnumerable<Document> GetCurrentDocuments() => environment.Workspace.CurrentSolution.Projects.Single().Documents;
 
            Assert.Empty(GetCurrentDocuments());
 
            var sourceFileFullPath1 = @"c:\source1.cs";
            var sourceFileFullPath2 = @"c:\source2.cs";
            var sourceFileFullPath3 = @"c:\source3.cs";
            var sourceFileFullPath4 = @"c:\source4.cs";
            var sourceFileFullPath5 = @"c:\source5.cs";
 
            // Add a file outside the batch.
            project.AddSourceFile(sourceFileFullPath2);
 
            var disposableBatchScope = await project.CreateBatchScopeAsync(CancellationToken.None).ConfigureAwait(false);
            await using (disposableBatchScope.ConfigureAwait(false))
            {
                project.AddSourceFile(sourceFileFullPath1);
                project.AddSourceFile(sourceFileFullPath3);
                project.AddSourceFile(sourceFileFullPath4);
                project.AddSourceFile(sourceFileFullPath5);
 
                // Removing path2 to test removal of a file the actual internal project state has changed outside of the batch.
                project.RemoveSourceFile(sourceFileFullPath2);
 
                // Removing path4 to test remove of a file when it was also added in a batch.
                project.RemoveSourceFile(sourceFileFullPath4);
 
                project.ReorderSourceFiles([sourceFileFullPath5, sourceFileFullPath3, sourceFileFullPath1]);
            }
 
            var documents = GetCurrentDocuments().ToArray();
 
            Assert.Equal(documents[0].FilePath, sourceFileFullPath5, StringComparer.OrdinalIgnoreCase);
            Assert.Equal(documents[1].FilePath, sourceFileFullPath3, StringComparer.OrdinalIgnoreCase);
            Assert.Equal(documents[2].FilePath, sourceFileFullPath1, StringComparer.OrdinalIgnoreCase);
        }
 
        [WpfFact]
        public async Task ReorderSourceFilesBatchWithReAdding_CPS()
        {
            using var environment = new TestEnvironment();
            using var project = await CreateCSharpCPSProjectAsync(environment, "project1");
            IEnumerable<Document> GetCurrentDocuments() => environment.Workspace.CurrentSolution.Projects.Single().Documents;
 
            Assert.Empty(GetCurrentDocuments());
 
            var sourceFileFullPath1 = @"c:\source1.cs";
            var sourceFileFullPath2 = @"c:\source2.cs";
            var sourceFileFullPath3 = @"c:\source3.cs";
            var sourceFileFullPath4 = @"c:\source4.cs";
            var sourceFileFullPath5 = @"c:\source5.cs";
 
            // Add a file outside the batch.
            project.AddSourceFile(sourceFileFullPath2);
 
            var disposableBatchScope = await project.CreateBatchScopeAsync(CancellationToken.None).ConfigureAwait(false);
            await using (disposableBatchScope.ConfigureAwait(false))
            {
                project.AddSourceFile(sourceFileFullPath1);
                project.AddSourceFile(sourceFileFullPath3);
                project.AddSourceFile(sourceFileFullPath4);
                project.AddSourceFile(sourceFileFullPath5);
 
                // Removing path2 to test removal of a file the actual internal project state has changed outside of the batch.
                project.RemoveSourceFile(sourceFileFullPath2);
 
                // Removing path4 to test remove of a file when it was also added in a batch.
                project.RemoveSourceFile(sourceFileFullPath4);
 
                project.ReorderSourceFiles([sourceFileFullPath5, sourceFileFullPath3, sourceFileFullPath1]);
 
                // Re-adding / re-removing / re-adding again.
                project.AddSourceFile(sourceFileFullPath2);
                project.AddSourceFile(sourceFileFullPath4);
                project.RemoveSourceFile(sourceFileFullPath2);
                project.RemoveSourceFile(sourceFileFullPath4);
                project.AddSourceFile(sourceFileFullPath2);
                project.AddSourceFile(sourceFileFullPath4);
 
                project.ReorderSourceFiles([sourceFileFullPath5, sourceFileFullPath4, sourceFileFullPath3, sourceFileFullPath2, sourceFileFullPath1]);
            }
 
            var documents = GetCurrentDocuments().ToArray();
 
            Assert.Equal(documents[0].FilePath, sourceFileFullPath5, StringComparer.OrdinalIgnoreCase);
            Assert.Equal(documents[1].FilePath, sourceFileFullPath4, StringComparer.OrdinalIgnoreCase);
            Assert.Equal(documents[2].FilePath, sourceFileFullPath3, StringComparer.OrdinalIgnoreCase);
            Assert.Equal(documents[3].FilePath, sourceFileFullPath2, StringComparer.OrdinalIgnoreCase);
            Assert.Equal(documents[4].FilePath, sourceFileFullPath1, StringComparer.OrdinalIgnoreCase);
        }
 
        [WpfFact]
        public async Task ReorderSourceFilesBatchAddAfterReorder_CPS()
        {
            using var environment = new TestEnvironment();
            using var project = await CreateCSharpCPSProjectAsync(environment, "project1");
            IEnumerable<Document> GetCurrentDocuments() => environment.Workspace.CurrentSolution.Projects.Single().Documents;
 
            Assert.Empty(GetCurrentDocuments());
 
            var sourceFileFullPath1 = @"c:\source1.cs";
            var sourceFileFullPath2 = @"c:\source2.cs";
            var sourceFileFullPath3 = @"c:\source3.cs";
            var sourceFileFullPath4 = @"c:\source4.cs";
            var sourceFileFullPath5 = @"c:\source5.cs";
 
            var disposableBatchScope = await project.CreateBatchScopeAsync(CancellationToken.None).ConfigureAwait(false);
            await using (disposableBatchScope.ConfigureAwait(false))
            {
                project.AddSourceFile(sourceFileFullPath1);
                project.AddSourceFile(sourceFileFullPath2);
 
                project.ReorderSourceFiles([sourceFileFullPath2, sourceFileFullPath1]);
 
                project.AddSourceFile(sourceFileFullPath3);
                project.AddSourceFile(sourceFileFullPath4);
                project.AddSourceFile(sourceFileFullPath5);
            }
 
            project.ReorderSourceFiles([sourceFileFullPath5, sourceFileFullPath4, sourceFileFullPath3, sourceFileFullPath2, sourceFileFullPath1]);
 
            var documents = GetCurrentDocuments().ToArray();
 
            Assert.Equal(documents[0].FilePath, sourceFileFullPath5, StringComparer.OrdinalIgnoreCase);
            Assert.Equal(documents[1].FilePath, sourceFileFullPath4, StringComparer.OrdinalIgnoreCase);
            Assert.Equal(documents[2].FilePath, sourceFileFullPath3, StringComparer.OrdinalIgnoreCase);
            Assert.Equal(documents[3].FilePath, sourceFileFullPath2, StringComparer.OrdinalIgnoreCase);
            Assert.Equal(documents[4].FilePath, sourceFileFullPath1, StringComparer.OrdinalIgnoreCase);
        }
 
        [WpfFact]
        public async Task ReorderSourceFilesBatchRemoveAfterReorder_CPS()
        {
            using var environment = new TestEnvironment();
            using var project = await CreateCSharpCPSProjectAsync(environment, "project1");
            IEnumerable<Document> GetCurrentDocuments() => environment.Workspace.CurrentSolution.Projects.Single().Documents;
 
            Assert.Empty(GetCurrentDocuments());
 
            var sourceFileFullPath1 = @"c:\source1.cs";
            var sourceFileFullPath2 = @"c:\source2.cs";
            var sourceFileFullPath3 = @"c:\source3.cs";
            var sourceFileFullPath4 = @"c:\source4.cs";
            var sourceFileFullPath5 = @"c:\source5.cs";
 
            var disposableBatchScope = await project.CreateBatchScopeAsync(CancellationToken.None).ConfigureAwait(false);
            await using (disposableBatchScope.ConfigureAwait(false))
            {
                project.AddSourceFile(sourceFileFullPath1);
                project.AddSourceFile(sourceFileFullPath2);
                project.AddSourceFile(sourceFileFullPath3);
                project.AddSourceFile(sourceFileFullPath4);
                project.AddSourceFile(sourceFileFullPath5);
 
                project.ReorderSourceFiles([sourceFileFullPath5, sourceFileFullPath4, sourceFileFullPath3, sourceFileFullPath2, sourceFileFullPath1]);
 
                project.RemoveSourceFile(sourceFileFullPath3);
                project.RemoveSourceFile(sourceFileFullPath4);
                project.RemoveSourceFile(sourceFileFullPath5);
            }
 
            project.ReorderSourceFiles([sourceFileFullPath2, sourceFileFullPath1]);
 
            var documents = GetCurrentDocuments().ToArray();
 
            Assert.Equal(documents[0].FilePath, sourceFileFullPath2, StringComparer.OrdinalIgnoreCase);
            Assert.Equal(documents[1].FilePath, sourceFileFullPath1, StringComparer.OrdinalIgnoreCase);
        }
 
        [WpfFact]
        public async Task ReorderSourceFilesExceptions_CPS()
        {
            using var environment = new TestEnvironment();
            using var project = await CreateCSharpCPSProjectAsync(environment, "project1");
            IEnumerable<Document> GetCurrentDocuments() => environment.Workspace.CurrentSolution.Projects.Single().Documents;
 
            Assert.Empty(GetCurrentDocuments());
 
            var sourceFileFullPath1 = @"c:\source1.cs";
            var sourceFileFullPath2 = @"c:\source2.cs";
            var sourceFileFullPath3 = @"c:\source3.cs";
            var sourceFileFullPath4 = @"c:\source4.cs";
            var sourceFileFullPath5 = @"c:\source5.cs";
            project.AddSourceFile(sourceFileFullPath1);
            project.AddSourceFile(sourceFileFullPath2);
            project.AddSourceFile(sourceFileFullPath3);
            project.AddSourceFile(sourceFileFullPath4);
            project.AddSourceFile(sourceFileFullPath5);
 
            project.RemoveSourceFile(sourceFileFullPath2);
            project.AddSourceFile(sourceFileFullPath2);
 
            project.RemoveSourceFile(sourceFileFullPath4);
            project.AddSourceFile(sourceFileFullPath4);
 
            // This should throw due to not passing all of the files.
            Assert.Throws<ArgumentException>(() => project.ReorderSourceFiles([sourceFileFullPath4, sourceFileFullPath5]));
 
            // This should throw because the path does not exist in the project.
            Assert.Throws<InvalidOperationException>(() => project.ReorderSourceFiles([@"C:\invalid source file", sourceFileFullPath2, sourceFileFullPath3, sourceFileFullPath4, sourceFileFullPath5]));
 
            Assert.Throws<ArgumentOutOfRangeException>(() => project.ReorderSourceFiles(new List<string>()));
            Assert.Throws<ArgumentOutOfRangeException>(() => project.ReorderSourceFiles(null));
        }
 
        [WpfFact]
        public async Task ReorderSourceFilesBatchExceptions_CPS()
        {
            using var environment = new TestEnvironment();
            using var project = await CreateCSharpCPSProjectAsync(environment, "project1");
            IEnumerable<Document> GetCurrentDocuments() => environment.Workspace.CurrentSolution.Projects.Single().Documents;
 
            Assert.Empty(GetCurrentDocuments());
 
            var sourceFileFullPath1 = @"c:\source1.cs";
            var sourceFileFullPath2 = @"c:\source2.cs";
            var sourceFileFullPath3 = @"c:\source3.cs";
            var sourceFileFullPath4 = @"c:\source4.cs";
            var sourceFileFullPath5 = @"c:\source5.cs";
 
            var disposableBatchScope = await project.CreateBatchScopeAsync(CancellationToken.None).ConfigureAwait(false);
            await using var _ = disposableBatchScope.ConfigureAwait(false);
 
            Assert.Throws<ArgumentException>(() => project.ReorderSourceFiles([sourceFileFullPath4, sourceFileFullPath5]));
            Assert.Throws<ArgumentException>(() => project.ReorderSourceFiles([@"C:\invalid source file"])); // no files were added, therefore we should get an argument exception
            Assert.Throws<ArgumentOutOfRangeException>(() => project.ReorderSourceFiles(new List<string>()));
            Assert.Throws<ArgumentOutOfRangeException>(() => project.ReorderSourceFiles(null));
 
            project.AddSourceFile(sourceFileFullPath1);
 
            // Test before we add/remove the rest of source files in the batch.
            Assert.Throws<ArgumentException>(() => project.ReorderSourceFiles([sourceFileFullPath4, sourceFileFullPath5]));
            Assert.Throws<InvalidOperationException>(() => project.ReorderSourceFiles([@"C:\invalid source file"]));
            Assert.Throws<ArgumentOutOfRangeException>(() => project.ReorderSourceFiles(new List<string>()));
            Assert.Throws<ArgumentOutOfRangeException>(() => project.ReorderSourceFiles(null));
 
            project.AddSourceFile(sourceFileFullPath2);
            project.AddSourceFile(sourceFileFullPath3);
            project.AddSourceFile(sourceFileFullPath4);
            project.AddSourceFile(sourceFileFullPath5);
 
            project.RemoveSourceFile(sourceFileFullPath2);
            project.AddSourceFile(sourceFileFullPath2);
 
            project.RemoveSourceFile(sourceFileFullPath4);
            project.AddSourceFile(sourceFileFullPath4);
 
            Assert.Throws<ArgumentException>(() => project.ReorderSourceFiles([sourceFileFullPath4, sourceFileFullPath5]));
            Assert.Throws<InvalidOperationException>(() => project.ReorderSourceFiles([@"C:\invalid source file", sourceFileFullPath2, sourceFileFullPath3, sourceFileFullPath4, sourceFileFullPath5]));
            Assert.Throws<ArgumentOutOfRangeException>(() => project.ReorderSourceFiles(new List<string>()));
            Assert.Throws<ArgumentOutOfRangeException>(() => project.ReorderSourceFiles(null));
        }
 
        [WpfFact]
        public async Task ReorderSourceFilesBatchExceptionRemoveFile_CPS()
        {
            using var environment = new TestEnvironment();
            using var project = await CreateCSharpCPSProjectAsync(environment, "project1");
            IEnumerable<Document> GetCurrentDocuments() => environment.Workspace.CurrentSolution.Projects.Single().Documents;
 
            Assert.Empty(GetCurrentDocuments());
 
            var sourceFileFullPath1 = @"c:\source1.cs";
            var sourceFileFullPath2 = @"c:\source2.cs";
 
            project.AddSourceFile(sourceFileFullPath1);
            project.AddSourceFile(sourceFileFullPath2);
 
            var disposableBatchScope = await project.CreateBatchScopeAsync(CancellationToken.None).ConfigureAwait(false);
            await using (disposableBatchScope.ConfigureAwait(false))
            {
                project.RemoveSourceFile(sourceFileFullPath2);
                Assert.Throws<InvalidOperationException>(() => project.ReorderSourceFiles([sourceFileFullPath2]));
            }
 
            var documents = GetCurrentDocuments().ToArray();
 
            Assert.Equal(documents[0].FilePath, sourceFileFullPath1, StringComparer.OrdinalIgnoreCase);
        }
    }
}