private async Task ExecuteTestCaseAsync(
                Catalog2RegistrationConfiguration config,
                HiveMerger target,
                Dictionary <NuGetVersion, string> versionToNormalized,
                TestCase testCase)
            {
                // ARRANGE
                var existing   = testCase.Existing;
                var allUpdated = testCase.Updated;

                // Determine the sets of expected modified versions, deleted versions, and resulting versions.
                var updatedGrouped    = allUpdated.ToLookup(x => x.IsDelete);
                var possibleDeletes   = updatedGrouped[true].Select(x => x.Version);
                var modified          = updatedGrouped[false].Select(x => x.Version).OrderBy(v => v).ToList();
                var deleted           = existing.Intersect(possibleDeletes).OrderBy(v => v).ToList();
                var unchanged         = existing.Except(allUpdated.Select(x => x.Version)).OrderBy(v => v).ToList();
                var expectedRemaining = modified.Concat(unchanged).OrderBy(v => v).ToList();

                // Build the input data structures.
                var sortedCatalog = MakeSortedCatalog(allUpdated);
                var indexInfo     = MakeIndexInfo(existing, config.MaxLeavesPerPage, versionToNormalized);

                // Determine which pages will definitely not be affected.
                var unaffectedPages = new List <PageInfo>();

                if (existing.Any())
                {
                    var minVersion = allUpdated.Min(x => x.Version);
                    unaffectedPages = indexInfo
                                      .Items
                                      .Take(indexInfo.Items.Count - 1)
                                      .Where(x => x.Upper < minVersion)
                                      .ToList();
                }

                // ACT
                var result = await target.MergeAsync(indexInfo, sortedCatalog);

                // ASSERT

                // Verify the definitely unaffected pages are in still in the index and not loaded.
                foreach (var page in unaffectedPages)
                {
                    Assert.Contains(page, indexInfo.Items);
                    Assert.False(page.IsPageFetched, "A page before the lowest version input version must not be fetched.");
                }

                // Verify the modified version set.
                Assert.Equal(modified, result.ModifiedLeaves.Select(x => x.Version).OrderBy(v => v).ToList());

                // Verify the deleted version set.
                Assert.Equal(deleted, result.DeletedLeaves.Select(x => x.Version).OrderBy(v => v).ToList());

                // Verify the resulting set of versions.
                var actualRemaining = await GetVersionsAsync(indexInfo);

                Assert.Equal(expectedRemaining, actualRemaining);

                // Verify all but the last page are full.
                foreach (var pageInfo in indexInfo.Items.AsEnumerable().Reverse().Skip(1))
                {
                    Assert.True(
                        pageInfo.Count == config.MaxLeavesPerPage,
                        "All but the last page must have the maximum number of leaf items per page.");
                }

                // Verify the last page is not too full.
                if (indexInfo.Items.Count > 0)
                {
                    Assert.True(
                        indexInfo.Items.Last().Count <= config.MaxLeavesPerPage,
                        "The last page must have less than or equal to the maximum number of leaf items per page.");
                }

                // Verify the page bounds are in ascending order.
                var bounds = indexInfo.Items.SelectMany(GetUniqueBounds).ToList();

                for (var i = 1; i < bounds.Count; i++)
                {
                    Assert.True(bounds[i - 1] < bounds[i], "Each page bound must be less than the next.");
                }

                // Verify the modified pages are in the index.
                foreach (var pageInfo in result.ModifiedPages)
                {
                    Assert.True(indexInfo.Items.Contains(pageInfo), "A modified page must be in the index.");
                }

                // Verify the leaf item infos match the leaf items.
                Assert.True(
                    indexInfo.Items.Count == indexInfo.Index.Items.Count,
                    "The number of page infos must match the number of page items.");
                for (var pageIndex = 0; pageIndex < indexInfo.Items.Count; pageIndex++)
                {
                    var pageInfo  = indexInfo.Items[pageIndex];
                    var leafInfos = await pageInfo.GetLeafInfosAsync();

                    var page = await pageInfo.GetPageAsync();

                    Assert.True(
                        page.Items.Count == leafInfos.Count,
                        "The number of leaf items must match the number of leaf item infos.");

                    for (var leafIndex = 0; leafIndex < page.Items.Count; leafIndex++)
                    {
                        var leafInfoVersion = leafInfos[leafIndex].Version;
                        var leafItemVersion = NuGetVersion.Parse(page.Items[leafIndex].CatalogEntry.Version);
                        Assert.True(
                            leafInfoVersion == leafItemVersion,
                            "The list of leaf info versions must match the leaf item versions.");
                    }
                }

                // Verify the modified leaves and deleted leafs are disjoint sets.
                var modifiedVersions = new HashSet <NuGetVersion>(result.ModifiedLeaves.Select(x => x.Version));
                var deletedVersions  = new HashSet <NuGetVersion>(result.DeletedLeaves.Select(x => x.Version));

                Assert.True(
                    !modifiedVersions.Overlaps(deletedVersions),
                    "The deleted leaves and modified leaves must be disjoint sets.");

                // Verify the modified leaves are visible in an inlined page or a downloaded page.
                foreach (var leafInfo in result.ModifiedLeaves)
                {
                    foreach (var pageInfo in indexInfo.Items)
                    {
                        if (leafInfo.Version < pageInfo.Lower || leafInfo.Version > pageInfo.Upper)
                        {
                            continue;
                        }

                        Assert.True(
                            pageInfo.IsInlined || pageInfo.IsPageFetched,
                            "A page bounding a modified leaf must either be inlined or the page must be fetched.");

                        var pageLeafInfos = await pageInfo.GetLeafInfosAsync();

                        Assert.True(
                            pageLeafInfos.Any(x => x.Version == leafInfo.Version),
                            "A modified leaf must be in the page bounding its version.");
                    }
                }

                // Verify the deleted leaves are not visible in any of the inlined or downloaded pages.
                foreach (var leafInfo in result.DeletedLeaves)
                {
                    foreach (var pageInfo in indexInfo.Items)
                    {
                        var pageLeafInfos = await pageInfo.GetLeafInfosAsync();

                        Assert.True(
                            !pageLeafInfos.Any(x => x.Version == leafInfo.Version),
                            "A deleted leaf must not be in any page.");
                    }
                }
            }
Ejemplo n.º 2
0
            public Facts(ITestOutputHelper output)
            {
                Storage       = new Mock <IHiveStorage>();
                Merger        = new Mock <IHiveMerger>();
                EntityBuilder = new Mock <IEntityBuilder>();
                Options       = new Mock <IOptionsSnapshot <Catalog2RegistrationConfiguration> >();
                Logger        = output.GetLogger <HiveUpdater>();

                Config             = new Catalog2RegistrationConfiguration();
                Hive               = HiveType.SemVer2;
                ReplicaHives       = new List <HiveType>();
                Id                 = "NuGet.Versioning";
                Entries            = new List <CatalogCommitItem>();
                EntryToCatalogLeaf = new Dictionary <CatalogCommitItem, PackageDetailsCatalogLeaf>(
                    ReferenceEqualityComparer <CatalogCommitItem> .Default);
                RegistrationIndex = new RegistrationIndex
                {
                    Items = new List <RegistrationPage>
                    {
                        new RegistrationPage
                        {
                            Lower = "1.0.0",
                            Upper = "3.0.0",
                            Count = 2,
                            Items = new List <RegistrationLeafItem>
                            {
                                new RegistrationLeafItem
                                {
                                    Url          = $"https://example/reg/{Id.ToLowerInvariant()}/1.0.0.json",
                                    CatalogEntry = new RegistrationCatalogEntry
                                    {
                                        Version = "1.0.0",
                                    }
                                },
                                new RegistrationLeafItem
                                {
                                    Url          = $"https://example/reg/{Id.ToLowerInvariant()}/3.0.0.json",
                                    CatalogEntry = new RegistrationCatalogEntry
                                    {
                                        Version = "3.0.0",
                                    }
                                },
                            }
                        }
                    }
                };
                MergeResult = new HiveMergeResult(
                    new HashSet <PageInfo>(),
                    new HashSet <LeafInfo>(),
                    new HashSet <LeafInfo>());
                RegistrationLeaf   = new RegistrationLeaf();
                RegistrationCommit = new CatalogCommit(
                    "b580f835-f041-4361-aa46-57e5dc338a63",
                    new DateTimeOffset(2019, 10, 25, 0, 0, 0, TimeSpan.Zero));

                Options.Setup(x => x.Value).Returns(() => Config);
                Storage
                .Setup(x => x.ReadIndexOrNullAsync(It.IsAny <HiveType>(), It.IsAny <string>()))
                .ReturnsAsync(() => RegistrationIndex);
                var concreteHiveMerger = new HiveMerger(Options.Object, output.GetLogger <HiveMerger>());

                Merger
                .Setup(x => x.MergeAsync(It.IsAny <IndexInfo>(), It.IsAny <IReadOnlyList <CatalogCommitItem> >()))
                .Returns <IndexInfo, IReadOnlyList <CatalogCommitItem> >((i, e) => concreteHiveMerger.MergeAsync(i, e));
                EntityBuilder
                .Setup(x => x.NewLeaf(It.IsAny <RegistrationLeafItem>()))
                .Returns(() => RegistrationLeaf);
                EntityBuilder
                .Setup(x => x.UpdateNonInlinedPageItem(
                           It.IsAny <RegistrationPage>(),
                           It.IsAny <HiveType>(),
                           It.IsAny <string>(),
                           It.IsAny <int>(),
                           It.IsAny <NuGetVersion>(),
                           It.IsAny <NuGetVersion>()))
                .Callback <RegistrationPage, HiveType, string, int, NuGetVersion, NuGetVersion>((p, h, id, c, l, u) =>
                {
                    p.Url = $"https://example/reg/" +
                            $"{id.ToLowerInvariant()}/" +
                            $"{l.ToNormalizedString().ToLowerInvariant()}/" +
                            $"{u.ToNormalizedString().ToLowerInvariant()}.json";
                });

                Target = new HiveUpdater(
                    Storage.Object,
                    Merger.Object,
                    EntityBuilder.Object,
                    Options.Object,
                    Logger);
            }