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 sealed 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());
        }
    }
}