File: SourceGeneration\AdditionalSourcesCollectionTests.cs
Web Access
Project: src\src\Compilers\CSharp\Test\Semantic\Microsoft.CodeAnalysis.CSharp.Semantic.UnitTests.csproj (Microsoft.CodeAnalysis.CSharp.Semantic.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.
 
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Test.Utilities;
using Xunit;
 
namespace Microsoft.CodeAnalysis.CSharp.Semantic.UnitTests.SourceGeneration
{
    public class AdditionalSourcesCollectionTests
         : CSharpTestBase
    {
        [Theory]
        [InlineData("abc", "abc.cs")]
        [InlineData("abc.cs")]
        [InlineData("abc+nested.cs")]
        [InlineData("abc`1.cs")]
        [InlineData("abc.vb", "abc.vb.cs")]
        [InlineData("abc.generated.cs")]
        [InlineData("abc_-_", "abc_-_.cs")]
        [InlineData("abc - generated.cs")]
        [InlineData("abc\u0064.cs", "abcd.cs")]
        [InlineData("abc(1).cs")]
        [InlineData("abc[1].cs")]
        [InlineData("abc{1}.cs")]
        [InlineData("..", "...cs")]
        [InlineData(".", "..cs")]
        [InlineData("abc/", "abc/.cs")]
        [InlineData("abc/ ", "abc/ .cs")]
        [InlineData("a/b/c", "a/b/c.cs")]
        [InlineData("a\\b/c", "a/b/c.cs")]
        [InlineData(" abc ", " abc .cs")]
        [InlineData(" abc/generated.cs")]
        [InlineData(" a/ b/ generated.cs")]
        [WorkItem(58476, "https://github.com/dotnet/roslyn/issues/58476")]
        public void HintName_ValidValues(string hintName, string? expectedName = null)
        {
            expectedName ??= hintName;
 
            AdditionalSourcesCollection asc = new AdditionalSourcesCollection(".cs");
            asc.Add(hintName, SourceText.From("public class D{}", Encoding.UTF8));
            Assert.True(asc.Contains(expectedName));
 
            var sources = asc.ToImmutableAndFree();
            var source = Assert.Single(sources);
            Assert.True(source.HintName.EndsWith(".cs"));
            Assert.Equal(expectedName, source.HintName, StringComparer.OrdinalIgnoreCase);
        }
 
        [Theory]
        [InlineData("abc")] // abc.vb
        [InlineData("abc.cs")] //abc.cs.vb
        [InlineData("abc.vb")] // abc.vb
        public void HintName_WithExtension(string hintName)
        {
            AdditionalSourcesCollection asc = new AdditionalSourcesCollection(".vb");
            asc.Add(hintName, SourceText.From("public class D{}", Encoding.UTF8));
            Assert.True(asc.Contains(hintName));
 
            var sources = asc.ToImmutableAndFree();
            Assert.Single(sources);
            Assert.True(sources[0].HintName.EndsWith(".vb"));
        }
 
        [Theory]
        [InlineData("/abc/def.cs")]
        [InlineData("\\")]
        [InlineData(":")]
        [InlineData("*")]
        [InlineData("?")]
        [InlineData("\"")]
        [InlineData("<")]
        [InlineData(">")]
        [InlineData("|")]
        [InlineData("\0")]
        [InlineData("abc\u00A0.cs")] // unicode non-breaking space
        [InlineData("/..")]
        [InlineData("\\..")]
        [InlineData("//")]
        [InlineData("\\\\")]
        [InlineData("a//b")]
        [InlineData("a\\\\b")]
        [InlineData("../")]
        [InlineData("./")]
        [InlineData(" /")]
        [InlineData(" /generated.cs")]
        [InlineData(" /a/generated.cs")]
        [InlineData(" /abc")]
        [InlineData(" /a/ b/c/ ")]
        [InlineData(" a/ b /c ")]
        [InlineData(" /a/ b/c/ generated.cs")]
        [InlineData(" a/ b /c generated.cs")]
        [InlineData(" abc /generated.cs")]
        public void HintName_InvalidValues(string hintName)
        {
            AdditionalSourcesCollection asc = new AdditionalSourcesCollection(".cs");
            var exception = Assert.Throws<ArgumentException>(nameof(hintName), () => asc.Add(hintName, SourceText.From("public class D{}", Encoding.UTF8)));
 
            Assert.Contains(hintName.Replace('\\', '/'), exception.Message);
        }
 
        [Fact]
        public void AddedSources_Are_Deterministic()
        {
            // a few manual simple ones
            AdditionalSourcesCollection asc = new AdditionalSourcesCollection(".cs");
            asc.Add("file3.cs", SourceText.From("", Encoding.UTF8));
            asc.Add("file1.cs", SourceText.From("", Encoding.UTF8));
            asc.Add("file2.cs", SourceText.From("", Encoding.UTF8));
            asc.Add("file5.cs", SourceText.From("", Encoding.UTF8));
            asc.Add("file4.cs", SourceText.From("", Encoding.UTF8));
 
            var sources = asc.ToImmutableAndFree();
            var hintNames = sources.Select(s => s.HintName).ToArray();
            Assert.Equal(new[]
            {
                "file3.cs",
                "file1.cs",
                "file2.cs",
                "file5.cs",
                "file4.cs"
            }, hintNames);
 
            // generate a long random list, remembering the order we added them
            Random r = new Random();
            string[] names = new string[1000];
            asc = new AdditionalSourcesCollection(".cs");
            for (int i = 0; i < 1000; i++)
            {
                names[i] = CSharpTestBase.GetUniqueName() + ".cs";
                asc.Add(names[i], SourceText.From("", Encoding.UTF8));
            }
 
            sources = asc.ToImmutableAndFree();
            hintNames = sources.Select(s => s.HintName).ToArray();
            Assert.Equal(names, hintNames);
        }
 
        [Theory]
        [InlineData("file.cs", "file.cs")]
        [InlineData("file", "file")]
        [InlineData("file", "file.cs")]
        [InlineData("file.cs", "file")]
        [InlineData("file.cs", "file.CS")]
        [InlineData("file.CS", "file.cs")]
        [InlineData("file", "file.CS")]
        [InlineData("file.CS", "file")]
        [InlineData("File", "file")]
        [InlineData("file", "File")]
        public void Hint_Name_Must_Be_Unique(string hintName1, string hintName2)
        {
            AdditionalSourcesCollection asc = new AdditionalSourcesCollection(".cs");
            asc.Add(hintName1, SourceText.From("", Encoding.UTF8));
            var exception = Assert.Throws<ArgumentException>("hintName", () => asc.Add(hintName2, SourceText.From("", Encoding.UTF8)));
 
            Assert.Contains(hintName2, exception.Message);
        }
 
        [Fact]
        public void Hint_Name_Must_Be_Unique_When_Combining_Sources()
        {
            AdditionalSourcesCollection asc = new AdditionalSourcesCollection(".cs");
            asc.Add("hintName1", SourceText.From("", Encoding.UTF8));
            asc.Add("hintName2", SourceText.From("", Encoding.UTF8));
 
            AdditionalSourcesCollection asc2 = new AdditionalSourcesCollection(".cs");
            asc2.Add("hintName3", SourceText.From("", Encoding.UTF8));
            asc2.Add("hintName1", SourceText.From("", Encoding.UTF8));
 
            var exception = Assert.Throws<ArgumentException>("hintName", () => asc.CopyTo(asc2));
            Assert.Contains("hintName1", exception.Message);
        }
 
        [Theory]
        [InlineData("file.cs", "file.cs")]
        [InlineData("file", "file")]
        [InlineData("file", "file.cs")]
        [InlineData("file.cs", "file")]
        [InlineData("file.CS", "file")]
        [InlineData("file", "file.CS")]
        [InlineData("File", "file.cs")]
        [InlineData("File.cs", "file")]
        [InlineData("File.cs", "file.CS")]
        public void Contains(string addHintName, string checkHintName)
        {
            AdditionalSourcesCollection asc = new AdditionalSourcesCollection(".cs");
            asc.Add(addHintName, SourceText.From("", Encoding.UTF8));
            Assert.True(asc.Contains(checkHintName));
        }
 
        [Theory]
        [InlineData("file.cs", "file.cs")]
        [InlineData("file", "file")]
        [InlineData("file", "file.cs")]
        [InlineData("file.cs", "file")]
        public void Remove(string addHintName, string removeHintName)
        {
            AdditionalSourcesCollection asc = new AdditionalSourcesCollection(".cs");
            asc.Add(addHintName, SourceText.From("", Encoding.UTF8));
            asc.RemoveSource(removeHintName);
            var sources = asc.ToImmutableAndFree();
            Assert.Empty(sources);
        }
 
        [Fact]
        public void SourceTextRequiresEncoding()
        {
            AdditionalSourcesCollection asc = new AdditionalSourcesCollection(".cs");
 
            // fine
            asc.Add("file1.cs", SourceText.From("", Encoding.UTF8));
            asc.Add("file2.cs", SourceText.From("", Encoding.UTF32));
            asc.Add("file3.cs", SourceText.From("", Encoding.Unicode));
 
            // no encoding
            Assert.Throws<ArgumentException>(() => asc.Add("file4.cs", SourceText.From("")));
 
            // explicit null encoding
            Assert.Throws<ArgumentException>(() => asc.Add("file5.cs", SourceText.From("", encoding: null)));
 
            var exception = Assert.Throws<ArgumentException>(() => asc.Add("file5.cs", SourceText.From("", encoding: null)));
 
            // check the exception contains the expected hintName
            Assert.Contains("file5.cs", exception.Message);
        }
    }
}