File: PersistentStorage\AbstractPersistentStorageTests.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.
 
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Storage;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.VisualStudio.LanguageServices.UnitTests;
using Roslyn.Test.Utilities;
using Roslyn.Utilities;
using Xunit;
 
namespace Microsoft.CodeAnalysis.UnitTests.WorkspaceServices
{
    [UseExportProvider]
    public abstract class AbstractPersistentStorageTests : IDisposable
    {
        public enum Size
        {
            Small,
            Medium,
            Large,
            ExtraLarge,
        }
 
        private const int Iterations = 20;
 
        private const int NumThreads = 10;
        private const string PersistentFolderPrefix = "PersistentStorageTests_";
 
        private readonly Encoding _encoding = Encoding.UTF8;
 
        private AbstractPersistentStorageService? _storageService;
        private readonly DisposableDirectory _persistentFolderRoot;
        private readonly TempDirectory _persistentFolder;
 
        // 256k (larger than the 100k that CloudCache uses to decide when to dump to an external file).
        // See https://dev.azure.com/devdiv/DevDiv/_git/VS.CloudCache?path=%2Fsrc%2FMicrosoft.VisualStudio.Cache%2FCacheService.cs&version=GBmain&line=35&lineEnd=36&lineStartColumn=1&lineEndColumn=1&lineStyle=plain&_a=contents
        private const int ExtraLargeSize = 256 * 1024;
        private const int LargeSize = (int)(SQLite.v2.SQLitePersistentStorage.MaxPooledByteArrayLength * 2);
        private const int MediumSize = (int)(SQLite.v2.SQLitePersistentStorage.MaxPooledByteArrayLength / 2);
 
        private const string SmallData1 = "Hello ESENT";
        private const string SmallData2 = "Goodbye ESENT";
 
        private static readonly string MediumData1 = string.Join(",", Enumerable.Repeat(SmallData1, MediumSize / SmallData1.Length));
        private static readonly string MediumData2 = string.Join(",", Enumerable.Repeat(SmallData2, MediumSize / SmallData2.Length));
 
        private static readonly string LargeData1 = string.Join(",", Enumerable.Repeat(SmallData1, LargeSize / SmallData1.Length));
        private static readonly string LargeData2 = string.Join(",", Enumerable.Repeat(SmallData2, LargeSize / SmallData2.Length));
 
        private static readonly string ExtraLargeData1 = string.Join(",", Enumerable.Repeat(SmallData1, ExtraLargeSize / SmallData1.Length));
        private static readonly string ExtraLargeData2 = string.Join(",", Enumerable.Repeat(SmallData2, ExtraLargeSize / SmallData2.Length));
 
        private static readonly Checksum s_checksum1 = Checksum.Create("1");
        private static readonly Checksum s_checksum2 = Checksum.Create("2");
 
        static AbstractPersistentStorageTests()
        {
            Assert.NotEqual(s_checksum1, s_checksum2);
 
            Assert.True(MediumData1.Length < SQLite.v2.SQLitePersistentStorage.MaxPooledByteArrayLength);
            Assert.True(MediumData2.Length < SQLite.v2.SQLitePersistentStorage.MaxPooledByteArrayLength);
 
            Assert.True(LargeData1.Length > SQLite.v2.SQLitePersistentStorage.MaxPooledByteArrayLength);
            Assert.True(LargeData2.Length > SQLite.v2.SQLitePersistentStorage.MaxPooledByteArrayLength);
        }
 
        protected AbstractPersistentStorageTests()
        {
            _persistentFolderRoot = new DisposableDirectory(new TempRoot());
            _persistentFolder = _persistentFolderRoot.CreateDirectory(PersistentFolderPrefix + Guid.NewGuid());
 
            ThreadPool.GetMinThreads(out var workerThreads, out var completionPortThreads);
            ThreadPool.SetMinThreads(Math.Max(workerThreads, NumThreads), completionPortThreads);
        }
 
        public void Dispose()
        {
            // This should cause the service to release the cached connection it maintains for the primary workspace
            _storageService?.GetTestAccessor().Shutdown();
            _persistentFolderRoot.Dispose();
        }
 
        private static string GetData1(Size size)
            => size == Size.Small ? SmallData1 :
               size == Size.Medium ? MediumData1 :
               size == Size.Large ? LargeData1 : ExtraLargeData1;
 
        private static string GetData2(Size size)
            => size == Size.Small ? SmallData2 :
               size == Size.Medium ? MediumData2 :
               size == Size.Large ? LargeData2 : ExtraLargeData2;
 
        private static Checksum? GetChecksum1(bool withChecksum)
            => withChecksum ? s_checksum1 : null;
 
        private static Checksum? GetChecksum2(bool withChecksum)
            => withChecksum ? s_checksum2 : null;
 
        [Fact]
        public async Task TestNullFilePaths()
        {
            var solution = CreateOrOpenSolution(nullPaths: true);
 
            var streamName = "stream";
 
            var storage = await GetStorageAsync(solution);
            var project = solution.Projects.First();
            var document = project.Documents.First();
            Assert.False(await storage.WriteStreamAsync(project, streamName, EncodeString("")));
            Assert.False(await storage.WriteStreamAsync(document, streamName, EncodeString("")));
 
            Assert.Null(await storage.ReadStreamAsync(project, streamName));
            Assert.Null(await storage.ReadStreamAsync(document, streamName));
        }
 
        [Theory, CombinatorialData, WorkItem("https://devdiv.visualstudio.com/DevDiv/_queries/edit/1436188")]
        public async Task CacheDirectoryInPathWithSingleQuote(Size size, bool withChecksum, [CombinatorialRange(0, Iterations)] int iteration)
        {
            _ = iteration;
            using var folderRoot = new DisposableDirectory(new TempRoot());
            var folder = folderRoot.CreateDirectory(PersistentFolderPrefix + "'" + Guid.NewGuid());
            var solution = CreateOrOpenSolution(folder);
 
            var streamName1 = "PersistentService_Solution_WriteReadDifferentInstances1";
            var streamName2 = "PersistentService_Solution_WriteReadDifferentInstances2";
 
            {
                var storage = await GetStorageAsync(solution, folder);
                Assert.True(await storage.WriteStreamAsync(streamName1, EncodeString(GetData1(size)), GetChecksum1(withChecksum)));
                Assert.True(await storage.WriteStreamAsync(streamName2, EncodeString(GetData2(size)), GetChecksum2(withChecksum)));
            }
 
            {
                var storage = await GetStorageAsync(solution, folder);
                Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(streamName1, GetChecksum1(withChecksum))));
                Assert.Equal(GetData2(size), ReadStringToEnd(await storage.ReadStreamAsync(streamName2, GetChecksum2(withChecksum))));
            }
        }
 
        [Theory, CombinatorialData]
        public async Task PersistentService_Solution_WriteReadDifferentInstances(Size size, bool withChecksum, [CombinatorialRange(0, Iterations)] int iteration)
        {
            _ = iteration;
            var solution = CreateOrOpenSolution();
            var streamName1 = "PersistentService_Solution_WriteReadDifferentInstances1";
            var streamName2 = "PersistentService_Solution_WriteReadDifferentInstances2";
 
            {
                var storage = await GetStorageAsync(solution);
                Assert.True(await storage.WriteStreamAsync(streamName1, EncodeString(GetData1(size)), GetChecksum1(withChecksum)));
                Assert.True(await storage.WriteStreamAsync(streamName2, EncodeString(GetData2(size)), GetChecksum2(withChecksum)));
            }
 
            {
                var storage = await GetStorageAsync(solution);
                Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(streamName1, GetChecksum1(withChecksum))));
                Assert.Equal(GetData2(size), ReadStringToEnd(await storage.ReadStreamAsync(streamName2, GetChecksum2(withChecksum))));
            }
        }
 
        [Theory, CombinatorialData]
        public async Task PersistentService_Solution_WriteReadReopenSolution(Size size, bool withChecksum, [CombinatorialRange(0, Iterations)] int iteration)
        {
            _ = iteration;
            var solution = CreateOrOpenSolution();
            var streamName1 = "PersistentService_Solution_WriteReadReopenSolution1";
            var streamName2 = "PersistentService_Solution_WriteReadReopenSolution2";
 
            {
                var storage = await GetStorageAsync(solution);
                Assert.True(await storage.WriteStreamAsync(streamName1, EncodeString(GetData1(size)), GetChecksum1(withChecksum)));
                Assert.True(await storage.WriteStreamAsync(streamName2, EncodeString(GetData2(size)), GetChecksum2(withChecksum)));
            }
 
            solution = CreateOrOpenSolution();
 
            {
                var storage = await GetStorageAsync(solution);
                Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(streamName1, GetChecksum1(withChecksum))));
                Assert.Equal(GetData2(size), ReadStringToEnd(await storage.ReadStreamAsync(streamName2, GetChecksum2(withChecksum))));
            }
        }
 
        [Theory, CombinatorialData]
        public async Task PersistentService_Solution_WriteReadSameInstance(Size size, bool withChecksum, [CombinatorialRange(0, Iterations)] int iteration)
        {
            _ = iteration;
            var solution = CreateOrOpenSolution();
            var streamName1 = "PersistentService_Solution_WriteReadSameInstance1";
            var streamName2 = "PersistentService_Solution_WriteReadSameInstance2";
 
            var storage = await GetStorageAsync(solution);
            Assert.True(await storage.WriteStreamAsync(streamName1, EncodeString(GetData1(size)), GetChecksum1(withChecksum)));
            Assert.True(await storage.WriteStreamAsync(streamName2, EncodeString(GetData2(size)), GetChecksum2(withChecksum)));
 
            Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(streamName1, GetChecksum1(withChecksum))));
            Assert.Equal(GetData2(size), ReadStringToEnd(await storage.ReadStreamAsync(streamName2, GetChecksum2(withChecksum))));
        }
 
        [Theory, CombinatorialData]
        public async Task PersistentService_Project_WriteReadSameInstance(Size size, bool withChecksum, [CombinatorialRange(0, Iterations)] int iteration)
        {
            _ = iteration;
            var solution = CreateOrOpenSolution();
            var streamName1 = "PersistentService_Project_WriteReadSameInstance1";
            var streamName2 = "PersistentService_Project_WriteReadSameInstance2";
 
            var storage = await GetStorageAsync(solution);
            var project = solution.Projects.Single();
 
            Assert.True(await storage.WriteStreamAsync(project, streamName1, EncodeString(GetData1(size)), GetChecksum1(withChecksum)));
            Assert.True(await storage.WriteStreamAsync(project, streamName2, EncodeString(GetData2(size)), GetChecksum2(withChecksum)));
 
            Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(project, streamName1, GetChecksum1(withChecksum))));
            Assert.Equal(GetData2(size), ReadStringToEnd(await storage.ReadStreamAsync(project, streamName2, GetChecksum2(withChecksum))));
        }
 
        [Theory, CombinatorialData]
        public async Task PersistentService_Document_WriteReadSameInstance(Size size, bool withChecksum, [CombinatorialRange(0, Iterations)] int iteration)
        {
            _ = iteration;
            var solution = CreateOrOpenSolution();
            var streamName1 = "PersistentService_Document_WriteReadSameInstance1";
            var streamName2 = "PersistentService_Document_WriteReadSameInstance2";
 
            var storage = await GetStorageAsync(solution);
            var document = solution.Projects.Single().Documents.Single();
 
            Assert.True(await storage.WriteStreamAsync(document, streamName1, EncodeString(GetData1(size)), GetChecksum1(withChecksum)));
            Assert.True(await storage.WriteStreamAsync(document, streamName2, EncodeString(GetData2(size)), GetChecksum2(withChecksum)));
 
            Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(document, streamName1, GetChecksum1(withChecksum))));
            Assert.Equal(GetData2(size), ReadStringToEnd(await storage.ReadStreamAsync(document, streamName2, GetChecksum2(withChecksum))));
        }
 
        [Theory, CombinatorialData]
        public async Task PersistentService_Solution_SimultaneousWrites([CombinatorialRange(0, Iterations)] int iteration)
        {
            _ = iteration;
            var solution = CreateOrOpenSolution();
 
            var streamName1 = "PersistentService_Solution_SimultaneousWrites1";
 
            var storage = await GetStorageAsync(solution);
            DoSimultaneousWrites(s => storage.WriteStreamAsync(streamName1, EncodeString(s)));
            var value = int.Parse(ReadStringToEnd(await storage.ReadStreamAsync(streamName1)));
            Assert.True(value >= 0);
            Assert.True(value < NumThreads);
        }
 
        [Theory, CombinatorialData]
        public async Task PersistentService_Project_SimultaneousWrites([CombinatorialRange(0, Iterations)] int iteration)
        {
            _ = iteration;
            var solution = CreateOrOpenSolution();
 
            var streamName1 = "PersistentService_Project_SimultaneousWrites1";
 
            var storage = await GetStorageAsync(solution);
            DoSimultaneousWrites(s => storage.WriteStreamAsync(solution.Projects.Single(), streamName1, EncodeString(s)));
            var value = int.Parse(ReadStringToEnd(await storage.ReadStreamAsync(solution.Projects.Single(), streamName1)));
            Assert.True(value >= 0);
            Assert.True(value < NumThreads);
        }
 
        [Theory, CombinatorialData]
        public async Task PersistentService_Document_SimultaneousWrites([CombinatorialRange(0, Iterations)] int iteration)
        {
            _ = iteration;
            var solution = CreateOrOpenSolution();
 
            var streamName1 = "PersistentService_Document_SimultaneousWrites1";
 
            var storage = await GetStorageAsync(solution);
            DoSimultaneousWrites(s => storage.WriteStreamAsync(solution.Projects.Single().Documents.Single(), streamName1, EncodeString(s)));
            var value = int.Parse(ReadStringToEnd(await storage.ReadStreamAsync(solution.Projects.Single().Documents.Single(), streamName1)));
            Assert.True(value >= 0);
            Assert.True(value < NumThreads);
        }
 
        [Theory, CombinatorialData]
        public async Task PersistentService_Solution_SimultaneousReads(Size size, bool withChecksum, [CombinatorialRange(0, Iterations)] int iteration)
        {
            _ = iteration;
            var solution = CreateOrOpenSolution();
            var streamName1 = "PersistentService_Solution_SimultaneousReads1";
 
            var storage = await GetStorageAsync(solution);
            Assert.True(await storage.WriteStreamAsync(streamName1, EncodeString(GetData1(size)), GetChecksum1(withChecksum)));
            DoSimultaneousReads(async () => ReadStringToEnd(await storage.ReadStreamAsync(streamName1, GetChecksum1(withChecksum))), GetData1(size));
        }
 
        [Theory, CombinatorialData]
        public async Task PersistentService_Project_SimultaneousReads(Size size, bool withChecksum, [CombinatorialRange(0, Iterations)] int iteration)
        {
            _ = iteration;
            var solution = CreateOrOpenSolution();
            var streamName1 = "PersistentService_Project_SimultaneousReads1";
 
            var storage = await GetStorageAsync(solution);
            Assert.True(await storage.WriteStreamAsync(solution.Projects.Single(), streamName1, EncodeString(GetData1(size)), GetChecksum1(withChecksum)));
            DoSimultaneousReads(async () => ReadStringToEnd(await storage.ReadStreamAsync(solution.Projects.Single(), streamName1, GetChecksum1(withChecksum))), GetData1(size));
        }
 
        [Theory, CombinatorialData]
        public async Task PersistentService_Document_SimultaneousReads(Size size, bool withChecksum, [CombinatorialRange(0, Iterations)] int iteration)
        {
            _ = iteration;
 
            var solution = CreateOrOpenSolution();
            var streamName1 = "PersistentService_Document_SimultaneousReads1";
 
            var storage = await GetStorageAsync(solution);
            Assert.True(await storage.WriteStreamAsync(solution.Projects.Single().Documents.Single(), streamName1, EncodeString(GetData1(size)), GetChecksum1(withChecksum)));
            DoSimultaneousReads(async () => ReadStringToEnd(await storage.ReadStreamAsync(solution.Projects.Single().Documents.Single(), streamName1, GetChecksum1(withChecksum))), GetData1(size));
        }
 
        [Theory, CombinatorialData]
        public async Task TestReadChecksumReturnsNullWhenNeverWritten([CombinatorialRange(0, Iterations)] int iteration)
        {
            _ = iteration;
            var solution = CreateOrOpenSolution();
 
            var streamName1 = "TestReadChecksumReturnsNullWhenNeverWritten";
 
            var storage = await GetStorageAsync(solution);
            Assert.False(await storage.ChecksumMatchesAsync(streamName1, s_checksum1));
        }
 
        [Theory, CombinatorialData]
        public async Task TestCanReadWithNullChecksumSomethingWrittenWithNonNullChecksum(Size size, [CombinatorialRange(0, Iterations)] int iteration)
        {
            _ = iteration;
            var solution = CreateOrOpenSolution();
 
            var streamName1 = "TestCanReadWithNullChecksumSomethingWrittenWithNonNullChecksum";
 
            {
                var storage = await GetStorageAsync(solution);
                Assert.True(await storage.WriteStreamAsync(streamName1, EncodeString(GetData1(size)), s_checksum1));
            }
 
            {
                var storage = await GetStorageAsync(solution);
                Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(streamName1, checksum: null)));
            }
        }
 
        [Theory, CombinatorialData]
        public async Task TestCannotReadWithMismatchedChecksums(Size size, [CombinatorialRange(0, Iterations)] int iteration)
        {
            _ = iteration;
            var solution = CreateOrOpenSolution();
 
            var streamName1 = "TestCannotReadWithMismatchedChecksums";
 
            {
                var storage = await GetStorageAsync(solution);
                Assert.True(await storage.WriteStreamAsync(streamName1, EncodeString(GetData1(size)), s_checksum1));
            }
 
            {
                var storage = await GetStorageAsync(solution);
                Assert.Null(await storage.ReadStreamAsync(streamName1, s_checksum2));
            }
        }
 
        [Theory, CombinatorialData]
        public async Task TestCannotReadChecksumIfWriteDidNotIncludeChecksum(Size size, [CombinatorialRange(0, Iterations)] int iteration)
        {
            _ = iteration;
            var solution = CreateOrOpenSolution();
 
            var streamName1 = "TestCannotReadChecksumIfWriteDidNotIncludeChecksum";
 
            {
                var storage = await GetStorageAsync(solution);
                Assert.True(await storage.WriteStreamAsync(streamName1, EncodeString(GetData1(size)), checksum: null));
            }
 
            {
                var storage = await GetStorageAsync(solution);
                Assert.False(await storage.ChecksumMatchesAsync(streamName1, s_checksum1));
            }
        }
 
        [Theory, CombinatorialData]
        public async Task TestReadChecksumProducesWrittenChecksum(Size size, [CombinatorialRange(0, Iterations)] int iteration)
        {
            _ = iteration;
            var solution = CreateOrOpenSolution();
 
            var streamName1 = "TestReadChecksumProducesWrittenChecksum";
 
            {
                var storage = await GetStorageAsync(solution);
                Assert.True(await storage.WriteStreamAsync(streamName1, EncodeString(GetData1(size)), checksum: s_checksum1));
            }
 
            {
                var storage = await GetStorageAsync(solution);
                Assert.True(await storage.ChecksumMatchesAsync(streamName1, s_checksum1));
            }
        }
 
        [Theory, CombinatorialData]
        public async Task TestReadChecksumProducesLastWrittenChecksum1(Size size, [CombinatorialRange(0, Iterations)] int iteration)
        {
            _ = iteration;
            var solution = CreateOrOpenSolution();
 
            var streamName1 = "TestReadChecksumProducesLastWrittenChecksum1";
 
            {
                var storage = await GetStorageAsync(solution);
                Assert.True(await storage.WriteStreamAsync(streamName1, EncodeString(GetData1(size)), checksum: s_checksum1));
                Assert.True(await storage.WriteStreamAsync(streamName1, EncodeString(GetData1(size)), checksum: null));
            }
 
            {
                var storage = await GetStorageAsync(solution);
                Assert.False(await storage.ChecksumMatchesAsync(streamName1, s_checksum1));
            }
        }
 
        [Theory, CombinatorialData]
        public async Task TestReadChecksumProducesLastWrittenChecksum2(Size size, [CombinatorialRange(0, Iterations)] int iteration)
        {
            _ = iteration;
            var solution = CreateOrOpenSolution();
 
            var streamName1 = "TestReadChecksumProducesLastWrittenChecksum2";
 
            {
                var storage = await GetStorageAsync(solution);
                Assert.True(await storage.WriteStreamAsync(streamName1, EncodeString(GetData1(size)), checksum: null));
                Assert.True(await storage.WriteStreamAsync(streamName1, EncodeString(GetData1(size)), checksum: s_checksum1));
            }
 
            {
                var storage = await GetStorageAsync(solution);
                Assert.True(await storage.ChecksumMatchesAsync(streamName1, s_checksum1));
            }
        }
 
        [Theory, CombinatorialData]
        public async Task TestReadChecksumProducesLastWrittenChecksum3(Size size, [CombinatorialRange(0, Iterations)] int iteration)
        {
            _ = iteration;
            var solution = CreateOrOpenSolution();
 
            var streamName1 = "TestReadChecksumProducesLastWrittenChecksum3";
 
            {
                var storage = await GetStorageAsync(solution);
                Assert.True(await storage.WriteStreamAsync(streamName1, EncodeString(GetData1(size)), checksum: s_checksum1));
                Assert.True(await storage.WriteStreamAsync(streamName1, EncodeString(GetData1(size)), checksum: s_checksum2));
            }
 
            {
                var storage = await GetStorageAsync(solution);
                Assert.True(await storage.ChecksumMatchesAsync(streamName1, s_checksum2));
            }
        }
 
        [Theory, CombinatorialData]
        public async Task TestOpenWithSolutionKeyReadWithDocumentKey(Size size, [CombinatorialRange(0, Iterations)] int iteration)
        {
            _ = iteration;
            var solution = CreateOrOpenSolution();
            var document = solution.Projects.Single().Documents.Single();
 
            var streamName1 = "stream";
 
            {
                var storage = await GetStorageAsync(solution);
                await storage.WriteStreamAsync(document, streamName1, EncodeString(GetData1(size)), checksum: s_checksum1);
            }
 
            {
                var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution));
                Assert.True(await storage.ChecksumMatchesAsync(DocumentKey.ToDocumentKey(document), streamName1, s_checksum1));
                Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(DocumentKey.ToDocumentKey(document), streamName1)));
            }
        }
 
        [Theory, CombinatorialData]
        public async Task TestOpenWithSolutionKeyReadWithDocument(Size size, [CombinatorialRange(0, Iterations)] int iteration)
        {
            _ = iteration;
            var solution = CreateOrOpenSolution();
            var document = solution.Projects.Single().Documents.Single();
 
            var streamName1 = "stream";
 
            {
                var storage = await GetStorageAsync(solution);
                await storage.WriteStreamAsync(document, streamName1, EncodeString(GetData1(size)), checksum: s_checksum1);
            }
 
            {
                var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution));
                Assert.True(await storage.ChecksumMatchesAsync(document, streamName1, s_checksum1));
                Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(document, streamName1)));
            }
        }
 
        [Theory, CombinatorialData]
        public async Task TestOpenWithSolutionReadWithDocumentKey(Size size, [CombinatorialRange(0, Iterations)] int iteration)
        {
            _ = iteration;
            var solution = CreateOrOpenSolution();
            var document = solution.Projects.Single().Documents.Single();
 
            var streamName1 = "stream";
 
            {
                var storage = await GetStorageAsync(solution);
                await storage.WriteStreamAsync(document, streamName1, EncodeString(GetData1(size)), checksum: s_checksum1);
            }
 
            {
                var storage = await GetStorageAsync(solution);
                Assert.True(await storage.ChecksumMatchesAsync(DocumentKey.ToDocumentKey(document), streamName1, s_checksum1));
                Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(DocumentKey.ToDocumentKey(document), streamName1)));
            }
        }
 
        [Theory, CombinatorialData]
        public async Task TestOpenWithSolutionReadWithDocument(Size size, [CombinatorialRange(0, Iterations)] int iteration)
        {
            _ = iteration;
            var solution = CreateOrOpenSolution();
            var document = solution.Projects.Single().Documents.Single();
 
            var streamName1 = "stream";
 
            {
                var storage = await GetStorageAsync(solution);
                await storage.WriteStreamAsync(document, streamName1, EncodeString(GetData1(size)), checksum: s_checksum1);
            }
 
            {
                var storage = await GetStorageAsync(solution);
                Assert.True(await storage.ChecksumMatchesAsync(document, streamName1, s_checksum1));
                Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(document, streamName1)));
            }
        }
 
        [Theory, CombinatorialData]
        public async Task TestOpenWithSolutionReadWithDocumentKeyAndDocument1(Size size, [CombinatorialRange(0, Iterations)] int iteration)
        {
            _ = iteration;
            var solution = CreateOrOpenSolution();
            var document = solution.Projects.Single().Documents.Single();
 
            var streamName1 = "stream";
 
            {
                var storage = await GetStorageAsync(solution);
                await storage.WriteStreamAsync(document, streamName1, EncodeString(GetData1(size)), checksum: s_checksum1);
            }
 
            {
                var storage = await GetStorageAsync(solution);
                Assert.True(await storage.ChecksumMatchesAsync(DocumentKey.ToDocumentKey(document), streamName1, s_checksum1));
                Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(DocumentKey.ToDocumentKey(document), streamName1)));
 
                Assert.True(await storage.ChecksumMatchesAsync(document, streamName1, s_checksum1));
                Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(document, streamName1)));
            }
        }
 
        [Theory, CombinatorialData]
        public async Task TestOpenWithSolutionReadWithDocumentKeyAndDocument2(Size size, [CombinatorialRange(0, Iterations)] int iteration)
        {
            _ = iteration;
            var solution = CreateOrOpenSolution();
            var document = solution.Projects.Single().Documents.Single();
 
            var streamName1 = "stream";
 
            {
                var storage = await GetStorageAsync(solution);
                await storage.WriteStreamAsync(document, streamName1, EncodeString(GetData1(size)), checksum: s_checksum1);
            }
 
            {
                var storage = await GetStorageAsync(solution);
                Assert.True(await storage.ChecksumMatchesAsync(document, streamName1, s_checksum1));
                Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(document, streamName1)));
 
                Assert.True(await storage.ChecksumMatchesAsync(DocumentKey.ToDocumentKey(document), streamName1, s_checksum1));
                Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(DocumentKey.ToDocumentKey(document), streamName1)));
            }
        }
 
        [Theory, CombinatorialData]
        public async Task TestOpenWithSolutionKeyReadWithDocumentKeyAndDocument1(Size size, [CombinatorialRange(0, Iterations)] int iteration)
        {
            _ = iteration;
            var solution = CreateOrOpenSolution();
            var document = solution.Projects.Single().Documents.Single();
 
            var streamName1 = "stream";
 
            {
                var storage = await GetStorageAsync(solution);
                await storage.WriteStreamAsync(document, streamName1, EncodeString(GetData1(size)), checksum: s_checksum1);
            }
 
            {
                var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution));
                Assert.True(await storage.ChecksumMatchesAsync(DocumentKey.ToDocumentKey(document), streamName1, s_checksum1));
                Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(DocumentKey.ToDocumentKey(document), streamName1)));
 
                Assert.True(await storage.ChecksumMatchesAsync(document, streamName1, s_checksum1));
                Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(document, streamName1)));
            }
        }
 
        [Theory, CombinatorialData]
        public async Task TestOpenWithSolutionKeyReadWithDocumentKeyAndDocument2(Size size, [CombinatorialRange(0, Iterations)] int iteration)
        {
            _ = iteration;
            var solution = CreateOrOpenSolution();
            var document = solution.Projects.Single().Documents.Single();
 
            var streamName1 = "stream";
 
            {
                var storage = await GetStorageAsync(solution);
                await storage.WriteStreamAsync(document, streamName1, EncodeString(GetData1(size)), checksum: s_checksum1);
            }
 
            {
                var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution));
                Assert.True(await storage.ChecksumMatchesAsync(document, streamName1, s_checksum1));
                Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(document, streamName1)));
 
                Assert.True(await storage.ChecksumMatchesAsync(DocumentKey.ToDocumentKey(document), streamName1, s_checksum1));
                Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(DocumentKey.ToDocumentKey(document), streamName1)));
            }
        }
 
        [Theory, CombinatorialData]
        public async Task TestOpenWithSolutionKeyReadWithDocumentKey_WriteWithSolutionKey(Size size, [CombinatorialRange(0, Iterations)] int iteration)
        {
            _ = iteration;
            var solution = CreateOrOpenSolution();
            var document = solution.Projects.Single().Documents.Single();
 
            var streamName1 = "stream";
 
            {
                var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution));
                await storage.WriteStreamAsync(document, streamName1, EncodeString(GetData1(size)), checksum: s_checksum1);
            }
 
            {
                var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution));
                Assert.True(await storage.ChecksumMatchesAsync(DocumentKey.ToDocumentKey(document), streamName1, s_checksum1));
                Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(DocumentKey.ToDocumentKey(document), streamName1)));
            }
        }
 
        [Theory, CombinatorialData]
        public async Task TestOpenWithSolutionKeyReadWithDocument_WriteWithSolutionKey(Size size, [CombinatorialRange(0, Iterations)] int iteration)
        {
            _ = iteration;
            var solution = CreateOrOpenSolution();
            var document = solution.Projects.Single().Documents.Single();
 
            var streamName1 = "stream";
 
            {
                var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution));
                await storage.WriteStreamAsync(document, streamName1, EncodeString(GetData1(size)), checksum: s_checksum1);
            }
 
            {
                var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution));
                Assert.True(await storage.ChecksumMatchesAsync(document, streamName1, s_checksum1));
                Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(document, streamName1)));
            }
        }
 
        [Theory, CombinatorialData]
        public async Task TestOpenWithSolutionReadWithDocumentKey_WriteWithSolutionKey(Size size, [CombinatorialRange(0, Iterations)] int iteration)
        {
            _ = iteration;
            var solution = CreateOrOpenSolution();
            var document = solution.Projects.Single().Documents.Single();
 
            var streamName1 = "stream";
 
            {
                var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution));
                await storage.WriteStreamAsync(document, streamName1, EncodeString(GetData1(size)), checksum: s_checksum1);
            }
 
            {
                var storage = await GetStorageAsync(solution);
                Assert.True(await storage.ChecksumMatchesAsync(DocumentKey.ToDocumentKey(document), streamName1, s_checksum1));
                Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(DocumentKey.ToDocumentKey(document), streamName1)));
            }
        }
 
        [Theory, CombinatorialData]
        public async Task TestOpenWithSolutionReadWithDocument_WriteWithSolutionKey(Size size, [CombinatorialRange(0, Iterations)] int iteration)
        {
            _ = iteration;
            var solution = CreateOrOpenSolution();
            var document = solution.Projects.Single().Documents.Single();
 
            var streamName1 = "stream";
 
            {
                var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution));
                await storage.WriteStreamAsync(document, streamName1, EncodeString(GetData1(size)), checksum: s_checksum1);
            }
 
            {
                var storage = await GetStorageAsync(solution);
                Assert.True(await storage.ChecksumMatchesAsync(document, streamName1, s_checksum1));
                Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(document, streamName1)));
            }
        }
 
        [Theory, CombinatorialData]
        public async Task TestOpenWithSolutionReadWithDocumentKeyAndDocument1_WriteWithSolutionKey(Size size, [CombinatorialRange(0, Iterations)] int iteration)
        {
            _ = iteration;
            var solution = CreateOrOpenSolution();
            var document = solution.Projects.Single().Documents.Single();
 
            var streamName1 = "stream";
 
            {
                var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution));
                await storage.WriteStreamAsync(document, streamName1, EncodeString(GetData1(size)), checksum: s_checksum1);
            }
 
            {
                var storage = await GetStorageAsync(solution);
                Assert.True(await storage.ChecksumMatchesAsync(DocumentKey.ToDocumentKey(document), streamName1, s_checksum1));
                Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(DocumentKey.ToDocumentKey(document), streamName1)));
 
                Assert.True(await storage.ChecksumMatchesAsync(document, streamName1, s_checksum1));
                Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(document, streamName1)));
            }
        }
 
        [Theory, CombinatorialData]
        public async Task TestOpenWithSolutionReadWithDocumentKeyAndDocument2_WriteWithSolutionKey(Size size, [CombinatorialRange(0, Iterations)] int iteration)
        {
            _ = iteration;
            var solution = CreateOrOpenSolution();
            var document = solution.Projects.Single().Documents.Single();
 
            var streamName1 = "stream";
 
            {
                var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution));
                await storage.WriteStreamAsync(document, streamName1, EncodeString(GetData1(size)), checksum: s_checksum1);
            }
 
            {
                var storage = await GetStorageAsync(solution);
                Assert.True(await storage.ChecksumMatchesAsync(document, streamName1, s_checksum1));
                Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(document, streamName1)));
 
                Assert.True(await storage.ChecksumMatchesAsync(DocumentKey.ToDocumentKey(document), streamName1, s_checksum1));
                Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(DocumentKey.ToDocumentKey(document), streamName1)));
            }
        }
 
        [Theory, CombinatorialData]
        public async Task TestOpenWithSolutionKeyReadWithDocumentKeyAndDocument1_WriteWithSolutionKey(Size size, [CombinatorialRange(0, Iterations)] int iteration)
        {
            _ = iteration;
            var solution = CreateOrOpenSolution();
            var document = solution.Projects.Single().Documents.Single();
 
            var streamName1 = "stream";
 
            {
                var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution));
                await storage.WriteStreamAsync(document, streamName1, EncodeString(GetData1(size)), checksum: s_checksum1);
            }
 
            {
                var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution));
                Assert.True(await storage.ChecksumMatchesAsync(DocumentKey.ToDocumentKey(document), streamName1, s_checksum1));
                Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(DocumentKey.ToDocumentKey(document), streamName1)));
 
                Assert.True(await storage.ChecksumMatchesAsync(document, streamName1, s_checksum1));
                Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(document, streamName1)));
            }
        }
 
        [Theory, CombinatorialData]
        public async Task TestOpenWithSolutionKeyReadWithDocumentKeyAndDocument2_WriteWithSolutionKey(Size size, [CombinatorialRange(0, Iterations)] int iteration)
        {
            _ = iteration;
            var solution = CreateOrOpenSolution();
            var document = solution.Projects.Single().Documents.Single();
 
            var streamName1 = "stream";
 
            {
                var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution));
                await storage.WriteStreamAsync(document, streamName1, EncodeString(GetData1(size)), checksum: s_checksum1);
            }
 
            {
                var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution));
                Assert.True(await storage.ChecksumMatchesAsync(document, streamName1, s_checksum1));
                Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(document, streamName1)));
 
                Assert.True(await storage.ChecksumMatchesAsync(DocumentKey.ToDocumentKey(document), streamName1, s_checksum1));
                Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(DocumentKey.ToDocumentKey(document), streamName1)));
            }
        }
 
        [Fact, WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1174219")]
        public void CacheDirectoryShouldNotBeAtRoot()
        {
            var workspace = new AdhocWorkspace(FeaturesTestCompositions.Features.GetHostServices());
            workspace.AddSolution(SolutionInfo.Create(SolutionId.CreateNewId(), new VersionStamp(), @"D:\git\PCLCrypto\PCLCrypto.sln"));
 
            var configuration = workspace.Services.GetRequiredService<IPersistentStorageConfiguration>();
            var location = configuration.TryGetStorageLocation(SolutionKey.ToSolutionKey(workspace.CurrentSolution));
            Assert.False(location?.StartsWith("/") ?? false);
        }
 
        [Theory, CombinatorialData]
        public async Task PersistentService_ReadByteTwice(Size size, bool withChecksum, [CombinatorialRange(0, Iterations)] int iteration)
        {
            _ = iteration;
            var solution = CreateOrOpenSolution();
            var streamName1 = "PersistentService_ReadByteTwice";
 
            {
                var storage = await GetStorageAsync(solution);
                Assert.True(await storage.WriteStreamAsync(streamName1, EncodeString(GetData1(size)), GetChecksum1(withChecksum)));
            }
 
            {
                var storage = await GetStorageAsync(solution);
                using var stream = await storage.ReadStreamAsync(streamName1, GetChecksum1(withChecksum));
                Contract.ThrowIfNull(stream);
                stream.ReadByte();
                stream.ReadByte();
            }
        }
 
        [Theory, CombinatorialData]
        public async Task TestPersistSyntaxTreeIndex([CombinatorialRange(0, Iterations)] int iteration)
        {
            _ = iteration;
            var solution = CreateOrOpenSolution();
            var id = DocumentId.CreateNewId(solution.Projects.Single().Id);
            solution = solution.AddDocument(id, "file.cs", "class C { void M() }", filePath: Path.Combine(_persistentFolder.Path, "file.cs"));
 
            var document = solution.GetRequiredDocument(id);
 
            {
                _ = await GetStorageAsync(solution);
                var index = await SyntaxTreeIndex.GetRequiredIndexAsync(document, default);
                await index.SaveAsync(document, _storageService!);
 
                var index2 = await SyntaxTreeIndex.LoadAsync(_storageService!, DocumentKey.ToDocumentKey(document), checksum: null, new StringTable(), default);
                Assert.NotNull(index2);
            }
        }
 
        [Theory, CombinatorialData]
        public async Task TestPersistTopLevelSyntaxTreeIndex([CombinatorialRange(0, Iterations)] int iteration)
        {
            _ = iteration;
            var solution = CreateOrOpenSolution();
            var id = DocumentId.CreateNewId(solution.Projects.Single().Id);
            solution = solution.AddDocument(id, "file.cs", "class C { void M() }", filePath: Path.Combine(_persistentFolder.Path, "file.cs"));
 
            var document = solution.GetRequiredDocument(id);
 
            {
                _ = await GetStorageAsync(solution);
                var index = await TopLevelSyntaxTreeIndex.GetRequiredIndexAsync(document, default);
                await index.SaveAsync(document, _storageService!);
 
                var index2 = await TopLevelSyntaxTreeIndex.LoadAsync(_storageService!, DocumentKey.ToDocumentKey(document), checksum: null, new StringTable(), default);
                Assert.NotNull(index2);
            }
        }
 
        private static void DoSimultaneousReads(Func<Task<string>> read, string expectedValue)
        {
            var barrier = new Barrier(NumThreads);
            var countdown = new CountdownEvent(NumThreads);
 
            var exceptions = new List<Exception>();
            for (var i = 0; i < NumThreads; i++)
            {
                Task.Run(async () =>
                {
                    barrier.SignalAndWait();
                    try
                    {
                        Assert.Equal(expectedValue, await read());
                    }
                    catch (Exception ex)
                    {
                        lock (exceptions)
                        {
                            exceptions.Add(ex);
                        }
                    }
 
                    countdown.Signal();
                });
            }
 
            countdown.Wait();
 
            Assert.Equal([], exceptions);
        }
 
        private static void DoSimultaneousWrites(Func<string, Task> write)
        {
            var barrier = new Barrier(NumThreads);
            var countdown = new CountdownEvent(NumThreads);
 
            var exceptions = new List<Exception>();
            for (var i = 0; i < NumThreads; i++)
            {
                ThreadPool.QueueUserWorkItem(s =>
                {
                    var id = (int)s;
                    barrier.SignalAndWait();
                    try
                    {
                        write(id + "").Wait();
                    }
                    catch (Exception ex)
                    {
                        lock (exceptions)
                        {
                            exceptions.Add(ex);
                        }
                    }
 
                    countdown.Signal();
                }, i);
            }
 
            countdown.Wait();
 
            Assert.Empty(exceptions);
        }
 
        protected Solution CreateOrOpenSolution(TempDirectory? persistentFolder = null, bool nullPaths = false)
        {
            persistentFolder ??= _persistentFolder;
            _storageService?.GetTestAccessor().Shutdown();
            var solutionFile = persistentFolder.CreateOrOpenFile("Solution1.sln").WriteAllText("");
 
            var info = SolutionInfo.Create(SolutionId.CreateNewId(), VersionStamp.Create(), solutionFile.Path);
 
            var workspace = new AdhocWorkspace(VisualStudioTestCompositions.LanguageServices.GetHostServices());
            workspace.AddSolution(info);
 
            var solution = workspace.CurrentSolution;
 
            var projectFile = persistentFolder.CreateOrOpenFile("Project1.csproj").WriteAllText("");
            solution = solution.AddProject(ProjectInfo.Create(ProjectId.CreateNewId(), VersionStamp.Create(), "Project1", "Project1", LanguageNames.CSharp,
                filePath: nullPaths ? null : projectFile.Path));
            var project = solution.Projects.Single();
 
            var documentFile = persistentFolder.CreateOrOpenFile("Document1.cs").WriteAllText("");
            solution = solution.AddDocument(DocumentInfo.Create(DocumentId.CreateNewId(project.Id), "Document1",
                filePath: nullPaths ? null : documentFile.Path));
 
            // Apply this to the workspace so our Solution is the primary branch ID, which matches our usual behavior
            workspace.TryApplyChanges(solution);
 
            return workspace.CurrentSolution;
        }
 
        internal async Task<IChecksummedPersistentStorage> GetStorageAsync(
            Solution solution,
            TempDirectory? persistentFolder = null,
            IPersistentStorageFaultInjector? faultInjector = null,
            bool throwOnFailure = true)
        {
            // If we handed out one for a previous test, we need to shut that down first
            persistentFolder ??= _persistentFolder;
            _storageService?.GetTestAccessor().Shutdown();
            var configuration = new MockPersistentStorageConfiguration(solution.Id, persistentFolder.Path, throwOnFailure);
 
            _storageService = (AbstractPersistentStorageService)solution.Workspace.Services.SolutionServices.GetPersistentStorageService();
            var storage = await _storageService.GetStorageAsync(
                SolutionKey.ToSolutionKey(solution), configuration, faultInjector, CancellationToken.None);
 
            // If we're injecting faults, we expect things to be strange
            if (faultInjector == null)
            {
                Assert.NotEqual(NoOpPersistentStorage.TestAccessor.GetStorageInstance(SolutionKey.ToSolutionKey(solution)), storage);
            }
 
            return storage;
        }
 
        internal async Task<IChecksummedPersistentStorage> GetStorageFromKeyAsync(
            HostWorkspaceServices services, SolutionKey solutionKey, IPersistentStorageFaultInjector? faultInjector = null)
        {
            var configuration = new MockPersistentStorageConfiguration(solutionKey.Id, _persistentFolder.Path, throwOnFailure: true);
 
            _storageService = (AbstractPersistentStorageService)services.SolutionServices.GetPersistentStorageService();
            var storage = await _storageService.GetStorageAsync(
                solutionKey, configuration, faultInjector, CancellationToken.None);
 
            // If we're injecting faults, we expect things to be strange
            if (faultInjector == null)
            {
                Assert.NotEqual(NoOpPersistentStorage.TestAccessor.GetStorageInstance(solutionKey), storage);
            }
 
            return storage;
        }
 
        private class MockPersistentStorageConfiguration : IPersistentStorageConfiguration
        {
            private readonly SolutionId _solutionId;
            private readonly string _storageLocation;
 
            public MockPersistentStorageConfiguration(SolutionId solutionId, string storageLocation, bool throwOnFailure)
            {
                _solutionId = solutionId;
                _storageLocation = storageLocation;
                ThrowOnFailure = throwOnFailure;
            }
 
            public bool ThrowOnFailure { get; }
 
            public string? TryGetStorageLocation(SolutionKey solutionKey)
                => solutionKey.Id == _solutionId ? _storageLocation : null;
        }
 
        protected Stream EncodeString(string text)
        {
            var bytes = _encoding.GetBytes(text);
            var stream = new MemoryStream(bytes);
            return stream;
        }
 
        private string ReadStringToEnd(Stream? stream)
        {
            Contract.ThrowIfNull(stream);
            using (stream)
            {
                using var memoryStream = new MemoryStream();
                stream.CopyTo(memoryStream);
                return _encoding.GetString(memoryStream.ToArray());
            }
        }
    }
}