private async Task VerifyWriteBytesIfContentMismatchedAsync(
            FileContentTable fileContentTable,
            string targetRelPath,
            string contents,
            bool expectWrite,
            VersionedFileIdentityAndContentInfo?originalDestinationInfo = null)
        {
            byte[]                  encoded      = Encoding.UTF8.GetBytes(contents);
            ContentHash             contentsHash = ContentHashingUtilities.HashBytes(encoded);
            ConditionalUpdateResult result       =
                await fileContentTable.WriteBytesIfContentMismatchedAsync(GetFullPath(targetRelPath), encoded, contentsHash);

            XAssert.AreEqual(expectWrite, !result.Elided, "Decision to write was incorrect.");

            FileContentTableExtensions.VersionedFileIdentityAndContentInfoWithOrigin targetInfo =
                await fileContentTable.GetAndRecordContentHashAsync(GetFullPath(targetRelPath));

            XAssert.AreEqual(FileContentTableExtensions.ContentHashOrigin.Cached, targetInfo.Origin, "Write didn't record the target hash.");
            XAssert.AreEqual(
                contentsHash,
                targetInfo.VersionedFileIdentityAndContentInfo.FileContentInfo.Hash,
                "Hashes should match after the write.");
            XAssert.AreEqual(contents, File.ReadAllText(GetFullPath(targetRelPath)), "Hashes match but content does not");

            if (originalDestinationInfo.HasValue)
            {
                VerifyDestinationVersion(result, originalDestinationInfo.Value);
            }
        }
        public async Task CopyIfContentMismatchedAsyncWithMismatchedDestination()
        {
            const string TargetContent = "Target!!!!!!!!!";

            var fileContentTable = FileContentTable.CreateNew();

            WriteFile(FileA, TargetContent);
            WriteFile(FileB, "Nope");

            FileContentTableExtensions.VersionedFileIdentityAndContentInfoWithOrigin sourceInfo =
                await fileContentTable.GetAndRecordContentHashAsync(GetFullPath(FileA));

            XAssert.AreEqual(FileContentTableExtensions.ContentHashOrigin.NewlyHashed, sourceInfo.Origin);

            FileContentTableExtensions.VersionedFileIdentityAndContentInfoWithOrigin targetInfo =
                await fileContentTable.GetAndRecordContentHashAsync(GetFullPath(FileB));

            XAssert.AreEqual(FileContentTableExtensions.ContentHashOrigin.NewlyHashed, targetInfo.Origin);

            XAssert.AreNotEqual(
                sourceInfo.VersionedFileIdentityAndContentInfo.FileContentInfo.Hash,
                targetInfo.VersionedFileIdentityAndContentInfo.FileContentInfo.Hash);

            await
            VerifyCopyIfContentMismatchedAsync(
                fileContentTable,
                FileA,
                FileB,
                sourceInfo.VersionedFileIdentityAndContentInfo.FileContentInfo,
                expectedCopy : true,
                originalDestinationInfo : targetInfo.VersionedFileIdentityAndContentInfo);
        }
        private async Task VerifyCopyIfContentMismatchedAsync(
            FileContentTable fileContentTable,
            string sourceRelPath,
            string destRelPath,
            FileContentInfo sourceVersionedHash,
            bool expectedCopy,
            VersionedFileIdentityAndContentInfo?originalDestinationInfo = null)
        {
            ConditionalUpdateResult result =
                await fileContentTable.CopyIfContentMismatchedAsync(GetFullPath(sourceRelPath), GetFullPath(destRelPath), sourceVersionedHash);

            XAssert.AreEqual(expectedCopy, !result.Elided, "Decision to copy or elide was incorrect.");

            FileContentTableExtensions.VersionedFileIdentityAndContentInfoWithOrigin targetInfo =
                await fileContentTable.GetAndRecordContentHashAsync(GetFullPath(destRelPath));

            XAssert.AreEqual(FileContentTableExtensions.ContentHashOrigin.Cached, targetInfo.Origin, "Copy didn't record the target hash.");
            XAssert.AreEqual(
                sourceVersionedHash.Hash,
                targetInfo.VersionedFileIdentityAndContentInfo.FileContentInfo.Hash,
                "Hashes should match after the copy.");
            XAssert.AreEqual(
                File.ReadAllText(GetFullPath(sourceRelPath)),
                File.ReadAllText(GetFullPath(destRelPath)),
                "Hashes match but content does not");

            if (originalDestinationInfo.HasValue)
            {
                VerifyDestinationVersion(result, originalDestinationInfo.Value);
            }
        }
        public async Task CopyIfContentMismatchedAsyncFixpoint()
        {
            const string TargetContent = "Target!!!!!!!!!";

            var fileContentTable = FileContentTable.CreateNew();

            WriteFile(FileA, TargetContent);

            FileContentTableExtensions.VersionedFileIdentityAndContentInfoWithOrigin sourceInfo =
                await fileContentTable.GetAndRecordContentHashAsync(GetFullPath(FileA));

            XAssert.AreEqual(FileContentTableExtensions.ContentHashOrigin.NewlyHashed, sourceInfo.Origin);

            await
            VerifyCopyIfContentMismatchedAsync(
                fileContentTable,
                FileA,
                FileB,
                sourceInfo.VersionedFileIdentityAndContentInfo.FileContentInfo,
                expectedCopy : true);
            await
            VerifyCopyIfContentMismatchedAsync(
                fileContentTable,
                FileA,
                FileB,
                sourceInfo.VersionedFileIdentityAndContentInfo.FileContentInfo,
                expectedCopy : false);
            await
            VerifyCopyIfContentMismatchedAsync(
                fileContentTable,
                FileA,
                FileB,
                sourceInfo.VersionedFileIdentityAndContentInfo.FileContentInfo,
                expectedCopy : false);
        }
        public async Task GetAndRecordContentHashAsyncWithEmptyFileContentTable()
        {
            var fileContentTable = FileContentTable.CreateNew();

            WriteFile(FileA, "Some string");

            FileContentTableExtensions.VersionedFileIdentityAndContentInfoWithOrigin result =
                await fileContentTable.GetAndRecordContentHashAsync(GetFullPath(FileA));

            XAssert.AreEqual(FileContentTableExtensions.ContentHashOrigin.NewlyHashed, result.Origin);
            XAssert.AreEqual(
                await ContentHashingUtilities.HashFileAsync(GetFullPath(FileA)),
                result.VersionedFileIdentityAndContentInfo.FileContentInfo.Hash);
        }
        public async Task GetAndRecordContentHashAsyncWithMatch()
        {
            ContentHash fakeHash = ContentHashingUtilities.CreateRandom();

            var fileContentTable = FileContentTable.CreateNew();

            WriteFile(FileA, "Some string");

            using (FileStream fs = File.OpenRead(GetFullPath(FileA)))
            {
                // Note that this content hash is clearly wrong, but also not all zeroes.
                fileContentTable.RecordContentHash(fs, fakeHash);
            }

            FileContentTableExtensions.VersionedFileIdentityAndContentInfoWithOrigin result =
                await fileContentTable.GetAndRecordContentHashAsync(GetFullPath(FileA));

            XAssert.AreEqual(FileContentTableExtensions.ContentHashOrigin.Cached, result.Origin);
            XAssert.AreEqual(fakeHash, result.VersionedFileIdentityAndContentInfo.FileContentInfo.Hash);
        }
        public async Task WriteBytesIfContentMismatchedAsyncWithMatchingDestination()
        {
            const string TargetContent = "Target!!!!!!!!!";

            var fileContentTable = FileContentTable.CreateNew();

            WriteFile(FileB, TargetContent);

            FileContentTableExtensions.VersionedFileIdentityAndContentInfoWithOrigin targetInfo =
                await fileContentTable.GetAndRecordContentHashAsync(GetFullPath(FileB));

            XAssert.AreEqual(FileContentTableExtensions.ContentHashOrigin.NewlyHashed, targetInfo.Origin);

            await
            VerifyWriteBytesIfContentMismatchedAsync(
                fileContentTable,
                FileB,
                TargetContent,
                expectWrite : false,
                originalDestinationInfo : targetInfo.VersionedFileIdentityAndContentInfo);
        }
        public async Task GetAndRecordContentHashAsyncWithMismatch()
        {
            var fileContentTable = FileContentTable.CreateNew();

            using (FileStream fs = File.Open(GetFullPath(FileA), FileMode.CreateNew, FileAccess.Write))
            {
                // Register an empty file.
                fileContentTable.RecordContentHash(fs, ContentHashingUtilities.EmptyHash);

                byte[] content = Encoding.UTF8.GetBytes("Some new content");
                await fs.WriteAsync(content, 0, content.Length);
            }

            FileContentTableExtensions.VersionedFileIdentityAndContentInfoWithOrigin result =
                await fileContentTable.GetAndRecordContentHashAsync(GetFullPath(FileA));

            XAssert.AreEqual(FileContentTableExtensions.ContentHashOrigin.NewlyHashed, result.Origin);
            XAssert.AreEqual(
                await ContentHashingUtilities.HashFileAsync(GetFullPath(FileA)),
                result.VersionedFileIdentityAndContentInfo.FileContentInfo.Hash);
        }