示例#1
0
        public void FromChanges_AddingToEmpty()
        {
            const string projectPath     = @"c:\somefolder\someproject\a.csproj";
            var          targetFramework = new TargetFramework("tfm1");

            var catalogs         = IProjectCatalogSnapshotFactory.Create();
            var previousSnapshot = TargetedDependenciesSnapshot.CreateEmpty(projectPath, targetFramework, catalogs);

            var resolvedTop = IDependencyModelFactory.FromJson(@"
                {
                    ""ProviderType"": ""Xxx"",
                    ""Id"": ""dependency1"",
                    ""Name"": ""Dependency1"",
                    ""Caption"": ""Dependency1"",
                    ""Resolved"": ""true"",
                    ""TopLevel"": ""true""
                }",
                                                               icon: KnownMonikers.Uninstall,
                                                               expandedIcon: KnownMonikers.Uninstall);

            var unresolved = IDependencyModelFactory.FromJson(@"
                {
                    ""ProviderType"": ""Xxx"",
                    ""Id"": ""dependency2"",
                    ""Name"": ""Dependency2"",
                    ""Caption"": ""Dependency2"",
                    ""Resolved"": ""false"",
                    ""TopLevel"": ""false""
                }",
                                                              icon: KnownMonikers.Uninstall,
                                                              expandedIcon: KnownMonikers.Uninstall);

            var changes = new DependenciesChangesBuilder();

            changes.Added(resolvedTop);
            changes.Added(unresolved);

            const string updatedProjectPath = "updatedProjectPath";

            var snapshot = TargetedDependenciesSnapshot.FromChanges(
                updatedProjectPath,
                previousSnapshot,
                changes.Build(),
                catalogs,
                ImmutableArray <IDependenciesSnapshotFilter> .Empty,
                new Dictionary <string, IProjectDependenciesSubTreeProvider>(),
                null);

            Assert.NotSame(previousSnapshot, snapshot);
            Assert.Same(updatedProjectPath, snapshot.ProjectPath);
            Assert.Same(catalogs, snapshot.Catalogs);
            Assert.True(snapshot.HasUnresolvedDependency);
            AssertEx.CollectionLength(snapshot.DependenciesWorld, 2);
            AssertEx.CollectionLength(snapshot.TopLevelDependencies, 1);
            Assert.True(resolvedTop.Matches(snapshot.TopLevelDependencies.Single(), targetFramework));
            Assert.True(resolvedTop.Matches(snapshot.DependenciesWorld["tfm1\\Xxx\\dependency1"], targetFramework));
            Assert.True(unresolved.Matches(snapshot.DependenciesWorld["tfm1\\Xxx\\dependency2"], targetFramework));
        }
        public void FromChanges_NoChangesAfterBeforeRemoveFilterDeclinedChange()
        {
            const string projectPath = @"c:\somefolder\someproject\a.csproj";

            var targetFramework = new TargetFramework("tfm1");

            var dependencyTop1 = new TestDependency
            {
                ProviderType   = "Xxx",
                Id             = Dependency.GetID(targetFramework, "Xxx", "topdependency1"),
                Name           = "TopDependency1",
                Caption        = "TopDependency1",
                SchemaItemType = "Xxx",
                Resolved       = true,
                TopLevel       = true
            };

            var dependencyChild1 = new TestDependency
            {
                ProviderType   = "Xxx",
                Id             = Dependency.GetID(targetFramework, "Xxx", "childdependency1"),
                Name           = "ChildDependency1",
                Caption        = "ChildDependency1",
                SchemaItemType = "Xxx",
                Resolved       = true,
                TopLevel       = false
            };

            var catalogs         = IProjectCatalogSnapshotFactory.Create();
            var previousSnapshot = new TargetedDependenciesSnapshot(
                projectPath,
                targetFramework,
                catalogs,
                new IDependency[] { dependencyTop1, dependencyChild1 }.ToImmutableDictionary(d => d.Id).WithComparers(StringComparer.OrdinalIgnoreCase));

            var changes = new DependenciesChangesBuilder();

            changes.Removed(dependencyTop1.ProviderType, dependencyTop1.Id);

            var snapshotFilter = new TestDependenciesSnapshotFilter();

            var snapshot = TargetedDependenciesSnapshot.FromChanges(
                projectPath,
                previousSnapshot,
                changes.TryBuildChanges() !,
                catalogs,
                ImmutableArray.Create <IDependenciesSnapshotFilter>(snapshotFilter),
                new Dictionary <string, IProjectDependenciesSubTreeProvider>(),
                null);

            Assert.Same(previousSnapshot, snapshot);
            Assert.True(snapshotFilter.Completed);
        }
示例#3
0
        public void FromChanges_NoChangesAfterBeforeRemoveFilterDeclinedChange()
        {
            const string projectPath = @"c:\somefolder\someproject\a.csproj";

            var targetFramework = new TargetFramework("tfm1");

            var dependencyTop1 = IDependencyFactory.FromJson(@"
                {
                    ""ProviderType"": ""Xxx"",
                    ""Id"": ""tfm1\\xxx\\topdependency1"",
                    ""Name"":""TopDependency1"",
                    ""Caption"":""TopDependency1"",
                    ""SchemaItemType"":""Xxx"",
                    ""Resolved"":""true"",
                    ""TopLevel"":""true""
                }");

            var dependencyChild1 = IDependencyFactory.FromJson(@"
                {
                    ""ProviderType"": ""Xxx"",
                    ""Id"": ""tfm1\\xxx\\childdependency1"",
                    ""Name"":""ChildDependency1"",
                    ""Caption"":""ChildDependency1"",
                    ""SchemaItemType"":""Xxx"",
                    ""Resolved"":""true"",
                    ""TopLevel"":""false""
                }");

            var catalogs         = IProjectCatalogSnapshotFactory.Create();
            var previousSnapshot = ITargetedDependenciesSnapshotFactory.Implement(
                projectPath: projectPath,
                targetFramework: targetFramework,
                catalogs: catalogs,
                dependenciesWorld: new [] { dependencyTop1, dependencyChild1 },
                topLevelDependencies: new [] { dependencyTop1 });

            var changes = new DependenciesChangesBuilder();

            changes.Removed(dependencyTop1.ProviderType, dependencyTop1.Id);

            var snapshotFilter = new TestDependenciesSnapshotFilter();

            var snapshot = TargetedDependenciesSnapshot.FromChanges(
                projectPath,
                previousSnapshot,
                changes.Build(),
                catalogs,
                ImmutableArray.Create <IDependenciesSnapshotFilter>(snapshotFilter),
                new Dictionary <string, IProjectDependenciesSubTreeProvider>(),
                null);

            Assert.Same(previousSnapshot, snapshot);
        }
示例#4
0
        public void FromChanges_NoChangesAfterBeforeRemoveFilterDeclinedChange()
        {
            const string projectPath = @"c:\somefolder\someproject\a.csproj";

            var targetFramework = new TargetFramework("tfm1");

            var dependencyTop1 = new TestDependency
            {
                ProviderType   = "Xxx",
                Id             = Dependency.GetID(targetFramework, "Xxx", "topdependency1"),
                Name           = "TopDependency1",
                Caption        = "TopDependency1",
                SchemaItemType = "Xxx",
                Resolved       = true,
                TopLevel       = true
            };

            var dependencyChild1 = new TestDependency
            {
                ProviderType   = "Xxx",
                Id             = Dependency.GetID(targetFramework, "Xxx", "childdependency1"),
                Name           = "ChildDependency1",
                Caption        = "ChildDependency1",
                SchemaItemType = "Xxx",
                Resolved       = true,
                TopLevel       = false
            };

            var catalogs         = IProjectCatalogSnapshotFactory.Create();
            var previousSnapshot = ITargetedDependenciesSnapshotFactory.Implement(
                projectPath: projectPath,
                targetFramework: targetFramework,
                catalogs: catalogs,
                dependenciesWorld: new [] { dependencyTop1, dependencyChild1 },
                topLevelDependencies: new [] { dependencyTop1 });

            var changes = new DependenciesChangesBuilder();

            changes.Removed(dependencyTop1.ProviderType, dependencyTop1.Id);

            var snapshotFilter = new TestDependenciesSnapshotFilter();

            var snapshot = TargetedDependenciesSnapshot.FromChanges(
                projectPath,
                previousSnapshot,
                changes.Build(),
                catalogs,
                ImmutableArray.Create <IDependenciesSnapshotFilter>(snapshotFilter),
                new Dictionary <string, IProjectDependenciesSubTreeProvider>(),
                null);

            Assert.Same(previousSnapshot, snapshot);
        }
        private static ImmutableDictionary <ITargetFramework, TargetedDependenciesSnapshot> CreateDependenciesByTargetFramework(
            string projectPath,
            IProjectCatalogSnapshot catalogs,
            params ITargetFramework[] targetFrameworks)
        {
            var dic = ImmutableDictionary <ITargetFramework, TargetedDependenciesSnapshot> .Empty;

            foreach (var targetFramework in targetFrameworks)
            {
                dic = dic.Add(targetFramework, TargetedDependenciesSnapshot.CreateEmpty(projectPath, targetFramework, catalogs));
            }

            return(dic);
        }
        public static TargetedDependenciesSnapshot FromChanges(
            string projectPath,
            ITargetFramework targetFramework,
            ITargetedDependenciesSnapshot previousSnapshot,
            IDependenciesChanges changes,
            IProjectCatalogSnapshot catalogs,
            IEnumerable <IDependenciesSnapshotFilter> snapshotFilters,
            out bool anyChanges)
        {
            var newSnapshot = new TargetedDependenciesSnapshot(projectPath, targetFramework, previousSnapshot, catalogs);

            anyChanges = newSnapshot.MergeChanges(changes, snapshotFilters);
            return(newSnapshot);
        }
        public void CreateEmpty()
        {
            const string projectPath     = @"c:\somefolder\someproject\a.csproj";
            var          targetFramework = new TargetFramework("tfm1");
            var          catalogs        = IProjectCatalogSnapshotFactory.Create();

            var snapshot = TargetedDependenciesSnapshot.CreateEmpty(projectPath, targetFramework, catalogs);

            Assert.Same(projectPath, snapshot.ProjectPath);
            Assert.Same(targetFramework, snapshot.TargetFramework);
            Assert.Same(catalogs, snapshot.Catalogs);
            Assert.False(snapshot.HasReachableVisibleUnresolvedDependency);
            Assert.Empty(snapshot.TopLevelDependencies);
            Assert.Empty(snapshot.DependenciesWorld);
            Assert.False(snapshot.CheckForUnresolvedDependencies("foo"));
            Assert.Empty(snapshot.GetDependencyChildren(new TestDependency()));
        }
示例#8
0
        private bool MergeChanges(
            ImmutableDictionary <ITargetFramework, IDependenciesChanges> changes,
            IProjectCatalogSnapshot catalogs,
            IEnumerable <IDependenciesSnapshotFilter> snapshotFilters,
            IEnumerable <IProjectDependenciesSubTreeProvider> subTreeProviders,
            HashSet <string> projectItemSpecs)
        {
            var anyChanges = false;
            var builder    = _targets.ToBuilder();

            foreach (var change in changes)
            {
                builder.TryGetValue(change.Key, out ITargetedDependenciesSnapshot previousSnapshot);
                var newTargetedSnapshot = TargetedDependenciesSnapshot.FromChanges(
                    ProjectPath,
                    change.Key,
                    previousSnapshot,
                    change.Value,
                    catalogs,
                    snapshotFilters,
                    subTreeProviders,
                    projectItemSpecs,
                    out bool anyTfmChanges);
                builder[change.Key] = newTargetedSnapshot;

                if (anyTfmChanges)
                {
                    anyChanges = true;
                }
            }

            // now get rid of empty target frameworks (if there no any dependencies for them)
            foreach (var targetKvp in builder.ToList())
            {
                if (targetKvp.Value.DependenciesWorld.Count <= 0)
                {
                    anyChanges = true;
                    builder.Remove(targetKvp.Key);
                }
            }

            _targets = builder.ToImmutableDictionary();

            return(anyChanges);
        }
        public void FromChanges_NullChanges_Throws()
        {
            const string projectPath      = @"c:\somefolder\someproject\a.csproj";
            var          targetFramework  = new TargetFramework("tfm1");
            var          catalogs         = IProjectCatalogSnapshotFactory.Create();
            var          previousSnapshot = TargetedDependenciesSnapshot.CreateEmpty(projectPath, targetFramework, catalogs);

            Assert.Throws <ArgumentNullException>(
                "changes",
                () => TargetedDependenciesSnapshot.FromChanges(
                    projectPath,
                    previousSnapshot,
                    null !,
                    catalogs,
                    ImmutableArray <IDependenciesSnapshotFilter> .Empty,
                    new Dictionary <string, IProjectDependenciesSubTreeProvider>(),
                    null));
        }
        public void FromChanges_NoChanges()
        {
            const string projectPath      = @"c:\somefolder\someproject\a.csproj";
            var          targetFramework  = new TargetFramework("tfm1");
            var          catalogs         = IProjectCatalogSnapshotFactory.Create();
            var          previousSnapshot = TargetedDependenciesSnapshot.CreateEmpty(projectPath, targetFramework, catalogs);

            var snapshot = TargetedDependenciesSnapshot.FromChanges(
                projectPath,
                previousSnapshot,
                changes: null,
                catalogs,
                ImmutableArray <IDependenciesSnapshotFilter> .Empty,
                new Dictionary <string, IProjectDependenciesSubTreeProvider>(),
                null);

            Assert.Same(previousSnapshot, snapshot);
        }
        public void TConstructor()
        {
            const string projectPath     = @"c:\somefolder\someproject\a.csproj";
            var          targetFramework = new TargetFramework("tfm1");

            var catalogs = IProjectCatalogSnapshotFactory.Create();
            var snapshot = new TargetedDependenciesSnapshot(
                projectPath,
                targetFramework,
                catalogs,
                ImmutableDictionary <string, IDependency> .Empty);

            Assert.NotNull(snapshot.TargetFramework);
            Assert.Equal("tfm1", snapshot.TargetFramework.FullName);
            Assert.Equal(projectPath, snapshot.ProjectPath);
            Assert.Equal(catalogs, snapshot.Catalogs);
            Assert.Empty(snapshot.TopLevelDependencies);
            Assert.Empty(snapshot.DependenciesWorld);
        }
        public DependenciesSnapshot SetTargets(
            ImmutableArray <ITargetFramework> targetFrameworks,
            ITargetFramework activeTargetFramework)
        {
            bool activeChanged = !activeTargetFramework.Equals(ActiveTargetFramework);

            ImmutableDictionary <ITargetFramework, TargetedDependenciesSnapshot> map = DependenciesByTargetFramework;

            var diff = new SetDiff <ITargetFramework>(map.Keys, targetFrameworks);

            map = map.RemoveRange(diff.Removed);
            map = map.AddRange(
                diff.Added.Select(
                    added => new KeyValuePair <ITargetFramework, TargetedDependenciesSnapshot>(
                        added,
                        TargetedDependenciesSnapshot.CreateEmpty(ProjectPath, added, null))));

            if (activeChanged || !ReferenceEquals(map, DependenciesByTargetFramework))
            {
                return(new DependenciesSnapshot(ProjectPath, activeTargetFramework, map));
            }

            return(this);
        }
        /// <summary>
        /// Updates the <see cref="TargetedDependenciesSnapshot"/> corresponding to <paramref name="changedTargetFramework"/>,
        /// returning either:
        /// <list type="bullet">
        ///   <item>An updated <see cref="DependenciesSnapshot"/> object, or</item>
        ///   <item>the immutable <paramref name="previousSnapshot"/> if no changes were made.</item>
        /// </list>
        /// </summary>
        /// <remarks>
        /// As part of the update, each <see cref="IDependenciesSnapshotFilter"/> in <paramref name="snapshotFilters"/>
        /// is given a chance to influence the addition and removal of dependency data in the returned snapshot.
        /// </remarks>
        /// <returns>An updated snapshot, or <paramref name="previousSnapshot"/> if no changes occured.</returns>
        public static DependenciesSnapshot FromChanges(
            string projectPath,
            DependenciesSnapshot previousSnapshot,
            ITargetFramework changedTargetFramework,
            IDependenciesChanges?changes,
            IProjectCatalogSnapshot?catalogs,
            ImmutableArray <ITargetFramework> targetFrameworks,
            ITargetFramework?activeTargetFramework,
            ImmutableArray <IDependenciesSnapshotFilter> snapshotFilters,
            IReadOnlyDictionary <string, IProjectDependenciesSubTreeProvider> subTreeProviderByProviderType,
            IImmutableSet <string>?projectItemSpecs)
        {
            Requires.NotNullOrWhiteSpace(projectPath, nameof(projectPath));
            Requires.NotNull(previousSnapshot, nameof(previousSnapshot));
            Requires.NotNull(changedTargetFramework, nameof(changedTargetFramework));
            Requires.Argument(!snapshotFilters.IsDefault, nameof(snapshotFilters), "Cannot be default.");
            Requires.NotNull(subTreeProviderByProviderType, nameof(subTreeProviderByProviderType));

            var builder = previousSnapshot.DependenciesByTargetFramework.ToBuilder();

            if (!builder.TryGetValue(changedTargetFramework, out TargetedDependenciesSnapshot previousTargetedSnapshot))
            {
                previousTargetedSnapshot = TargetedDependenciesSnapshot.CreateEmpty(projectPath, changedTargetFramework, catalogs);
            }

            bool builderChanged = false;

            var newTargetedSnapshot = TargetedDependenciesSnapshot.FromChanges(
                projectPath,
                previousTargetedSnapshot,
                changes,
                catalogs,
                snapshotFilters,
                subTreeProviderByProviderType,
                projectItemSpecs);

            if (!ReferenceEquals(previousTargetedSnapshot, newTargetedSnapshot))
            {
                builder[changedTargetFramework] = newTargetedSnapshot;
                builderChanged = true;
            }

            SyncTargetFrameworks();

            activeTargetFramework ??= previousSnapshot.ActiveTargetFramework;

            if (builderChanged)
            {
                // Dependencies-by-target-framework has changed
                return(new DependenciesSnapshot(
                           projectPath,
                           activeTargetFramework,
                           builder.ToImmutable()));
            }

            if (!activeTargetFramework.Equals(previousSnapshot.ActiveTargetFramework))
            {
                // The active target framework changed
                return(new DependenciesSnapshot(
                           projectPath,
                           activeTargetFramework,
                           previousSnapshot.DependenciesByTargetFramework));
            }

            if (projectPath != previousSnapshot.ProjectPath)
            {
                // The project path changed
                return(new DependenciesSnapshot(
                           projectPath,
                           activeTargetFramework,
                           previousSnapshot.DependenciesByTargetFramework));
            }

            // Nothing has changed, so return the same snapshot
            return(previousSnapshot);

            void SyncTargetFrameworks()
            {
                // Only sync if a the full list of target frameworks has been provided
                if (targetFrameworks.IsDefault)
                {
                    return;
                }

                // This is a long-winded way of doing this that minimises allocations

                // Ensure all required target frameworks are present
                foreach (ITargetFramework targetFramework in targetFrameworks)
                {
                    if (!builder.ContainsKey(targetFramework))
                    {
                        builder.Add(targetFramework, TargetedDependenciesSnapshot.CreateEmpty(projectPath, targetFramework, catalogs));
                        builderChanged = true;
                    }
                }

                // Remove any extra target frameworks
                if (builder.Count != targetFrameworks.Length)
                {
                    IEnumerable <ITargetFramework> targetFrameworksToRemove = builder.Keys.Except(targetFrameworks);

                    foreach (ITargetFramework targetFramework in targetFrameworksToRemove)
                    {
                        builder.Remove(targetFramework);
                    }

                    builderChanged = true;
                }
            }
        }
示例#14
0
        /// <summary>
        /// Returns a IDependencyViewModel for given dependency.
        /// </summary>
        public static IDependencyViewModel ToViewModel(this IDependency dependency, TargetedDependenciesSnapshot snapshot)
        {
            bool hasUnresolvedDependency = !dependency.Resolved || snapshot.ShouldAppearUnresolved(dependency);

            return(new DependencyViewModel(dependency, hasUnresolvedDependency: hasUnresolvedDependency));
        }
示例#15
0
        public void FromChanges_ReportedChangesAfterBeforeAddFilterDeclinedChange()
        {
            const string projectPath     = @"c:\somefolder\someproject\a.csproj";
            var          targetFramework = new TargetFramework("tfm1");

            var dependencyTop1 = IDependencyFactory.FromJson(@"
                {
                    ""ProviderType"": ""Xxx"",
                    ""Id"": ""tfm1\\xxx\\topdependency1"",
                    ""Name"":""TopDependency1"",
                    ""Caption"":""TopDependency1"",
                    ""SchemaItemType"":""Xxx"",
                    ""Resolved"":""true"",
                    ""TopLevel"":""true""
                }");

            var dependencyChild1 = IDependencyFactory.FromJson(@"
                {
                    ""ProviderType"": ""Xxx"",
                    ""Id"": ""tfm1\\xxx\\childdependency1"",
                    ""Name"":""ChildDependency1"",
                    ""Caption"":""ChildDependency1"",
                    ""SchemaItemType"":""Xxx"",
                    ""Resolved"":""true"",
                    ""TopLevel"":""false""
                }");

            var dependencyModelNew1 = IDependencyModelFactory.FromJson(@"
                {
                    ""ProviderType"": ""Xxx"",
                    ""Id"": ""newdependency1"",
                    ""Name"":""NewDependency1"",
                    ""Caption"":""NewDependency1"",
                    ""SchemaItemType"":""Xxx"",
                    ""Resolved"":""true""
                }",
                                                                       icon: KnownMonikers.Uninstall,
                                                                       expandedIcon: KnownMonikers.Uninstall);

            var catalogs         = IProjectCatalogSnapshotFactory.Create();
            var previousSnapshot = ITargetedDependenciesSnapshotFactory.Implement(
                projectPath: projectPath,
                targetFramework: targetFramework,
                catalogs: catalogs,
                dependenciesWorld: new [] { dependencyTop1, dependencyChild1 },
                topLevelDependencies: new [] { dependencyTop1 });

            var changes = new DependenciesChangesBuilder();

            changes.Added(dependencyModelNew1);

            var filterAddedDependency = new TestDependency {
                Id = "unexpected", TopLevel = true
            };

            var snapshotFilter = new TestDependenciesSnapshotFilter()
                                 .BeforeAddReject(@"tfm1\xxx\newdependency1", addOrUpdate: filterAddedDependency);

            var snapshot = TargetedDependenciesSnapshot.FromChanges(
                projectPath,
                previousSnapshot,
                changes.Build(),
                catalogs,
                ImmutableArray.Create <IDependenciesSnapshotFilter>(snapshotFilter),
                new Dictionary <string, IProjectDependenciesSubTreeProvider>(),
                null);

            Assert.NotSame(previousSnapshot, snapshot);

            Assert.Same(previousSnapshot.TargetFramework, snapshot.TargetFramework);
            Assert.Same(previousSnapshot.ProjectPath, snapshot.ProjectPath);
            Assert.Same(previousSnapshot.Catalogs, snapshot.Catalogs);

            AssertEx.CollectionLength(snapshot.TopLevelDependencies, 2);
            Assert.Contains(dependencyTop1, snapshot.TopLevelDependencies);
            Assert.Contains(filterAddedDependency, snapshot.TopLevelDependencies);

            AssertEx.CollectionLength(snapshot.DependenciesWorld, 3);
            Assert.Contains(dependencyTop1, snapshot.DependenciesWorld.Values);
            Assert.Contains(dependencyChild1, snapshot.DependenciesWorld.Values);
            Assert.Contains(filterAddedDependency, snapshot.DependenciesWorld.Values);
        }
        public void FromChanges_UpdatesTopLevelDependencies()
        {
            const string projectPath     = @"c:\somefolder\someproject\a.csproj";
            var          targetFramework = new TargetFramework("tfm1");

            var dependencyTopPrevious = new TestDependency
            {
                ProviderType   = "Xxx",
                Id             = "tfm1\\xxx\\topdependency1",
                Name           = "TopDependency1",
                Caption        = "TopDependency1",
                SchemaItemType = "Xxx",
                Resolved       = true
            };

            var dependencyModelTopAdded = new TestDependencyModel
            {
                ProviderType   = "Xxx",
                Id             = "topdependency1",
                Name           = "TopDependency1",
                Caption        = "TopDependency1",
                SchemaItemType = "Xxx",
                Resolved       = true,
                Icon           = KnownMonikers.Uninstall,
                ExpandedIcon   = KnownMonikers.Uninstall
            };

            var dependencyTopUpdated = new TestDependency
            {
                ProviderType   = "Xxx",
                Id             = "tfm1\\xxx\\topdependency1",
                Name           = "TopDependency1",
                Caption        = "TopDependency1",
                SchemaItemType = "Xxx",
                Resolved       = true
            };

            var catalogs         = IProjectCatalogSnapshotFactory.Create();
            var previousSnapshot = new TargetedDependenciesSnapshot(
                projectPath,
                targetFramework,
                catalogs,
                new IDependency[] { dependencyTopPrevious }.ToImmutableDictionary(d => d.Id).WithComparers(StringComparer.OrdinalIgnoreCase));

            var changes = new DependenciesChangesBuilder();

            changes.Added(dependencyModelTopAdded);

            var snapshotFilter = new TestDependenciesSnapshotFilter()
                                 .BeforeAddAccept(@"tfm1\xxx\topdependency1", dependencyTopUpdated);

            var snapshot = TargetedDependenciesSnapshot.FromChanges(
                projectPath,
                previousSnapshot,
                changes.TryBuildChanges() !,
                catalogs,
                ImmutableArray.Create <IDependenciesSnapshotFilter>(snapshotFilter),
                new Dictionary <string, IProjectDependenciesSubTreeProvider>(),
                null);

            Assert.True(snapshotFilter.Completed);

            Assert.NotSame(previousSnapshot, snapshot);
            Assert.Same(dependencyTopUpdated, snapshot.TopLevelDependencies.Single());
        }
        public void FromChanges_ReportedChangesAfterBeforeAddFilterDeclinedChange()
        {
            const string projectPath     = @"c:\somefolder\someproject\a.csproj";
            var          targetFramework = new TargetFramework("tfm1");

            var dependencyTop1 = new TestDependency
            {
                ProviderType   = "Xxx",
                Id             = "tfm1\\xxx\\topdependency1",
                Name           = "TopDependency1",
                Caption        = "TopDependency1",
                SchemaItemType = "Xxx",
                Resolved       = true,
                TopLevel       = true
            };

            var dependencyChild1 = new TestDependency
            {
                ProviderType   = "Xxx",
                Id             = "tfm1\\xxx\\childdependency1",
                Name           = "ChildDependency1",
                Caption        = "ChildDependency1",
                SchemaItemType = "Xxx",
                Resolved       = true,
                TopLevel       = false
            };

            var dependencyModelNew1 = new TestDependencyModel
            {
                ProviderType   = "Xxx",
                Id             = "newdependency1",
                Name           = "NewDependency1",
                Caption        = "NewDependency1",
                SchemaItemType = "Xxx",
                Resolved       = true,
                Icon           = KnownMonikers.Uninstall,
                ExpandedIcon   = KnownMonikers.Uninstall
            };

            var catalogs         = IProjectCatalogSnapshotFactory.Create();
            var previousSnapshot = new TargetedDependenciesSnapshot(
                projectPath,
                targetFramework,
                catalogs,
                new IDependency[] { dependencyTop1, dependencyChild1 }.ToImmutableDictionary(d => d.Id).WithComparers(StringComparer.OrdinalIgnoreCase));

            var changes = new DependenciesChangesBuilder();

            changes.Added(dependencyModelNew1);

            var filterAddedDependency = new TestDependency {
                Id = "unexpected", TopLevel = true
            };

            var snapshotFilter = new TestDependenciesSnapshotFilter()
                                 .BeforeAddReject(@"tfm1\xxx\newdependency1", addOrUpdate: filterAddedDependency);

            var snapshot = TargetedDependenciesSnapshot.FromChanges(
                projectPath,
                previousSnapshot,
                changes.TryBuildChanges() !,
                catalogs,
                ImmutableArray.Create <IDependenciesSnapshotFilter>(snapshotFilter),
                new Dictionary <string, IProjectDependenciesSubTreeProvider>(),
                null);

            Assert.True(snapshotFilter.Completed);

            Assert.NotSame(previousSnapshot, snapshot);

            Assert.Same(previousSnapshot.TargetFramework, snapshot.TargetFramework);
            Assert.Same(previousSnapshot.ProjectPath, snapshot.ProjectPath);
            Assert.Same(previousSnapshot.Catalogs, snapshot.Catalogs);

            AssertEx.CollectionLength(snapshot.TopLevelDependencies, 2);
            Assert.Contains(dependencyTop1, snapshot.TopLevelDependencies);
            Assert.Contains(filterAddedDependency, snapshot.TopLevelDependencies);

            AssertEx.CollectionLength(snapshot.DependenciesWorld, 3);
            Assert.Contains(dependencyTop1, snapshot.DependenciesWorld.Values);
            Assert.Contains(dependencyChild1, snapshot.DependenciesWorld.Values);
            Assert.Contains(filterAddedDependency, snapshot.DependenciesWorld.Values);
        }
        public void FromChanges_RemovedAndAddedChanges()
        {
            const string projectPath     = @"c:\somefolder\someproject\a.csproj";
            var          targetFramework = new TargetFramework("tfm1");

            var dependencyTop1 = new TestDependency
            {
                ProviderType   = "Xxx",
                Id             = "tfm1\\xxx\\topdependency1",
                Name           = "TopDependency1",
                Caption        = "TopDependency1",
                SchemaItemType = "Xxx",
                Resolved       = true,
                TopLevel       = true
            };

            var dependencyChild1 = new TestDependency
            {
                ProviderType   = "Xxx",
                Id             = "tfm1\\xxx\\childdependency1",
                Name           = "ChildDependency1",
                Caption        = "ChildDependency1",
                SchemaItemType = "Xxx",
                Resolved       = true,
                TopLevel       = false
            };

            var dependencyModelAdded1 = new TestDependencyModel
            {
                ProviderType   = "Xxx",
                Id             = "addeddependency1",
                Name           = "AddedDependency1",
                Caption        = "AddedDependency1",
                SchemaItemType = "Xxx",
                Resolved       = true,
                TopLevel       = false,
                Icon           = KnownMonikers.Uninstall,
                ExpandedIcon   = KnownMonikers.Uninstall
            };

            var dependencyModelAdded2 = new TestDependencyModel
            {
                ProviderType   = "Xxx",
                Id             = "addeddependency2",
                Name           = "AddedDependency2",
                Caption        = "AddedDependency2",
                SchemaItemType = "Xxx",
                Resolved       = true,
                TopLevel       = false,
                Icon           = KnownMonikers.Uninstall,
                ExpandedIcon   = KnownMonikers.Uninstall
            };

            var dependencyModelAdded3 = new TestDependencyModel
            {
                ProviderType   = "Xxx",
                Id             = "addeddependency3",
                Name           = "AddedDependency3",
                Caption        = "AddedDependency3",
                SchemaItemType = "Xxx",
                Resolved       = true,
                TopLevel       = false,
                Icon           = KnownMonikers.Uninstall,
                ExpandedIcon   = KnownMonikers.Uninstall
            };

            var dependencyAdded2Changed = new TestDependency
            {
                ProviderType   = "Xxx",
                Id             = "tfm1\\xxx\\addeddependency2",
                Name           = "AddedDependency2Changed",
                Caption        = "AddedDependency2Changed",
                SchemaItemType = "Xxx",
                Resolved       = true,
                TopLevel       = true
            };

            var dependencyRemoved1 = new TestDependency
            {
                ProviderType   = "Xxx",
                Id             = "tfm1\\xxx\\Removeddependency1",
                Name           = "RemovedDependency1",
                Caption        = "RemovedDependency1",
                SchemaItemType = "Xxx",
                Resolved       = true,
                TopLevel       = false
            };

            var dependencyInsteadRemoved1 = new TestDependency
            {
                ProviderType   = "Xxx",
                Id             = "tfm1\\xxx\\InsteadRemoveddependency1",
                Name           = "InsteadRemovedDependency1",
                Caption        = "InsteadRemovedDependency1",
                SchemaItemType = "Xxx",
                Resolved       = true,
                TopLevel       = false
            };

            Assert.True(dependencyTop1.TopLevel);
            Assert.False(dependencyChild1.TopLevel);
            Assert.False(dependencyRemoved1.TopLevel);

            var catalogs         = IProjectCatalogSnapshotFactory.Create();
            var previousSnapshot = new TargetedDependenciesSnapshot(
                projectPath,
                targetFramework,
                catalogs,
                new IDependency[] { dependencyTop1, dependencyChild1, dependencyRemoved1 }.ToImmutableDictionary(d => d.Id).WithComparers(StringComparer.OrdinalIgnoreCase));

            var changes = new DependenciesChangesBuilder();

            changes.Added(dependencyModelAdded1);
            changes.Added(dependencyModelAdded2);
            changes.Added(dependencyModelAdded3);
            changes.Removed("Xxx", "Removeddependency1");

            var snapshotFilter = new TestDependenciesSnapshotFilter()
                                 .BeforeAddReject(@"tfm1\xxx\addeddependency1")
                                 .BeforeAddAccept(@"tfm1\xxx\addeddependency2", dependencyAdded2Changed)
                                 .BeforeAddAccept(@"tfm1\xxx\addeddependency3")
                                 .BeforeRemoveAccept(@"tfm1\xxx\Removeddependency1", dependencyInsteadRemoved1);

            var snapshot = TargetedDependenciesSnapshot.FromChanges(
                projectPath,
                previousSnapshot,
                changes.TryBuildChanges() !,
                catalogs,
                ImmutableArray.Create <IDependenciesSnapshotFilter>(snapshotFilter),
                new Dictionary <string, IProjectDependenciesSubTreeProvider>(),
                null);

            Assert.True(snapshotFilter.Completed);

            Assert.NotSame(previousSnapshot, snapshot);

            Assert.Same(previousSnapshot.TargetFramework, snapshot.TargetFramework);
            Assert.Same(projectPath, snapshot.ProjectPath);
            Assert.Same(catalogs, snapshot.Catalogs);
            AssertEx.CollectionLength(snapshot.TopLevelDependencies, 2);
            Assert.Contains(snapshot.TopLevelDependencies, x => x.Id.Equals(@"tfm1\xxx\topdependency1"));
            Assert.Contains(snapshot.TopLevelDependencies, x => x.Id.Equals(@"tfm1\xxx\addeddependency2") && x.Caption.Equals("AddedDependency2Changed"));
            AssertEx.CollectionLength(snapshot.DependenciesWorld, 5);
            Assert.True(snapshot.DependenciesWorld.ContainsKey(@"tfm1\xxx\topdependency1"));
            Assert.True(snapshot.DependenciesWorld.ContainsKey(@"tfm1\xxx\childdependency1"));
            Assert.True(snapshot.DependenciesWorld.ContainsKey(@"tfm1\xxx\addeddependency2"));
            Assert.True(snapshot.DependenciesWorld.ContainsKey(@"tfm1\xxx\InsteadRemoveddependency1"));
            Assert.True(snapshot.DependenciesWorld.ContainsKey(@"tfm1\xxx\addeddependency3"));
        }
示例#19
0
        public void FromChanges_RemovedAndAddedChanges()
        {
            const string projectPath     = @"c:\somefolder\someproject\a.csproj";
            var          targetFramework = new TargetFramework("tfm1");

            var dependencyTop1 = IDependencyFactory.FromJson(@"
                {
                    ""ProviderType"": ""Xxx"",
                    ""Id"": ""tfm1\\xxx\\topdependency1"",
                    ""Name"":""TopDependency1"",
                    ""Caption"":""TopDependency1"",
                    ""SchemaItemType"":""Xxx"",
                    ""Resolved"":""true"",
                    ""TopLevel"":""true""
                }");

            var dependencyChild1 = IDependencyFactory.FromJson(@"
                {
                    ""ProviderType"": ""Xxx"",
                    ""Id"": ""tfm1\\xxx\\childdependency1"",
                    ""Name"":""ChildDependency1"",
                    ""Caption"":""ChildDependency1"",
                    ""SchemaItemType"":""Xxx"",
                    ""Resolved"":""true"",
                    ""TopLevel"":""false""
                }");

            var dependencyModelAdded1 = IDependencyModelFactory.FromJson(@"
                {
                    ""ProviderType"": ""Xxx"",
                    ""Id"": ""addeddependency1"",
                    ""Name"":""AddedDependency1"",
                    ""Caption"":""AddedDependency1"",
                    ""SchemaItemType"":""Xxx"",
                    ""Resolved"":""true"",
                    ""TopLevel"":""false""
                }",
                                                                         icon: KnownMonikers.Uninstall,
                                                                         expandedIcon: KnownMonikers.Uninstall);

            var dependencyModelAdded2 = IDependencyModelFactory.FromJson(@"
                {
                    ""ProviderType"": ""Xxx"",
                    ""Id"": ""addeddependency2"",
                    ""Name"":""AddedDependency2"",
                    ""Caption"":""AddedDependency2"",
                    ""SchemaItemType"":""Xxx"",
                    ""Resolved"":""true"",
                    ""TopLevel"":""false""
                }",
                                                                         icon: KnownMonikers.Uninstall,
                                                                         expandedIcon: KnownMonikers.Uninstall);

            var dependencyModelAdded3 = IDependencyModelFactory.FromJson(@"
                {
                    ""ProviderType"": ""Xxx"",
                    ""Id"": ""addeddependency3"",
                    ""Name"":""AddedDependency3"",
                    ""Caption"":""AddedDependency3"",
                    ""SchemaItemType"":""Xxx"",
                    ""Resolved"":""true"",
                    ""TopLevel"":""false""
                }",
                                                                         icon: KnownMonikers.Uninstall,
                                                                         expandedIcon: KnownMonikers.Uninstall);

            var dependencyAdded2Changed = IDependencyFactory.FromJson(@"
                {
                    ""ProviderType"": ""Xxx"",
                    ""Id"": ""tfm1\\xxx\\addeddependency2"",
                    ""Name"":""AddedDependency2Changed"",
                    ""Caption"":""AddedDependency2Changed"",
                    ""SchemaItemType"":""Xxx"",
                    ""Resolved"":""true"",
                    ""TopLevel"":""true""
                }");

            var dependencyRemoved1 = IDependencyFactory.FromJson(@"
                {
                    ""ProviderType"": ""Xxx"",
                    ""Id"": ""tfm1\\xxx\\Removeddependency1"",
                    ""Name"":""RemovedDependency1"",
                    ""Caption"":""RemovedDependency1"",
                    ""SchemaItemType"":""Xxx"",
                    ""Resolved"":""true"",
                    ""TopLevel"":""false""
                }");

            var dependencyInsteadRemoved1 = IDependencyFactory.FromJson(@"
                {
                    ""ProviderType"": ""Xxx"",
                    ""Id"": ""tfm1\\xxx\\InsteadRemoveddependency1"",
                    ""Name"":""InsteadRemovedDependency1"",
                    ""Caption"":""InsteadRemovedDependency1"",
                    ""SchemaItemType"":""Xxx"",
                    ""Resolved"":""true"",
                    ""TopLevel"":""false""
                }");

            Assert.True(dependencyTop1.TopLevel);
            Assert.False(dependencyChild1.TopLevel);
            Assert.False(dependencyRemoved1.TopLevel);

            var catalogs         = IProjectCatalogSnapshotFactory.Create();
            var previousSnapshot = ITargetedDependenciesSnapshotFactory.Implement(
                projectPath: projectPath,
                targetFramework: targetFramework,
                catalogs: catalogs,
                dependenciesWorld: new [] { dependencyTop1, dependencyChild1, dependencyRemoved1 },
                topLevelDependencies: new [] { dependencyTop1 });

            var changes = new DependenciesChangesBuilder();

            changes.Added(dependencyModelAdded1);
            changes.Added(dependencyModelAdded2);
            changes.Added(dependencyModelAdded3);
            changes.Removed("Xxx", "Removeddependency1");

            var snapshotFilter = new TestDependenciesSnapshotFilter()
                                 .BeforeAddReject(@"tfm1\xxx\addeddependency1")
                                 .BeforeAddAccept(@"tfm1\xxx\addeddependency2", dependencyAdded2Changed)
                                 .BeforeAddAccept(@"tfm1\xxx\addeddependency3")
                                 .BeforeRemoveAccept(@"tfm1\xxx\Removeddependency1", dependencyInsteadRemoved1);

            var snapshot = TargetedDependenciesSnapshot.FromChanges(
                projectPath,
                previousSnapshot,
                changes.Build(),
                catalogs,
                ImmutableArray.Create <IDependenciesSnapshotFilter>(snapshotFilter),
                new Dictionary <string, IProjectDependenciesSubTreeProvider>(),
                null);

            Assert.NotSame(previousSnapshot, snapshot);

            Assert.Same(previousSnapshot.TargetFramework, snapshot.TargetFramework);
            Assert.Same(projectPath, snapshot.ProjectPath);
            Assert.Same(catalogs, snapshot.Catalogs);
            AssertEx.CollectionLength(snapshot.TopLevelDependencies, 2);
            Assert.Contains(snapshot.TopLevelDependencies, x => x.Id.Equals(@"tfm1\xxx\topdependency1"));
            Assert.Contains(snapshot.TopLevelDependencies, x => x.Id.Equals(@"tfm1\xxx\addeddependency2") && x.Caption.Equals("AddedDependency2Changed"));
            AssertEx.CollectionLength(snapshot.DependenciesWorld, 5);
            Assert.True(snapshot.DependenciesWorld.ContainsKey(@"tfm1\xxx\topdependency1"));
            Assert.True(snapshot.DependenciesWorld.ContainsKey(@"tfm1\xxx\childdependency1"));
            Assert.True(snapshot.DependenciesWorld.ContainsKey(@"tfm1\xxx\addeddependency2"));
            Assert.True(snapshot.DependenciesWorld.ContainsKey(@"tfm1\xxx\InsteadRemoveddependency1"));
            Assert.True(snapshot.DependenciesWorld.ContainsKey(@"tfm1\xxx\addeddependency3"));
        }
        /// <summary>
        /// Applies changes to <paramref name="previousSnapshot"/> and produces a new snapshot if required.
        /// If no changes are made, <paramref name="previousSnapshot"/> is returned unmodified.
        /// </summary>
        /// <returns>An updated snapshot, or <paramref name="previousSnapshot"/> if no changes occured.</returns>
        public static TargetedDependenciesSnapshot FromChanges(
            string projectPath,
            TargetedDependenciesSnapshot previousSnapshot,
            IDependenciesChanges changes,
            IProjectCatalogSnapshot?catalogs,
            ImmutableArray <IDependenciesSnapshotFilter> snapshotFilters,
            IReadOnlyDictionary <string, IProjectDependenciesSubTreeProvider> subTreeProviderByProviderType,
            IImmutableSet <string>?projectItemSpecs)
        {
            Requires.NotNullOrWhiteSpace(projectPath, nameof(projectPath));
            Requires.NotNull(previousSnapshot, nameof(previousSnapshot));
            Requires.NotNull(changes, nameof(changes));
            Requires.Argument(!snapshotFilters.IsDefault, nameof(snapshotFilters), "Cannot be default.");
            Requires.NotNull(subTreeProviderByProviderType, nameof(subTreeProviderByProviderType));

            bool anyChanges = false;

            ITargetFramework targetFramework = previousSnapshot.TargetFramework;

            var worldBuilder = previousSnapshot.DependenciesWorld.ToBuilder();

            if (changes.RemovedNodes.Count != 0)
            {
                var context = new RemoveDependencyContext(worldBuilder);

                foreach (IDependencyModel removed in changes.RemovedNodes)
                {
                    Remove(context, removed);
                }
            }

            if (changes.AddedNodes.Count != 0)
            {
                var context = new AddDependencyContext(worldBuilder);

                foreach (IDependencyModel added in changes.AddedNodes)
                {
                    Add(context, added);
                }
            }

            // Also factor in any changes to path/framework/catalogs
            anyChanges =
                anyChanges ||
                !StringComparers.Paths.Equals(projectPath, previousSnapshot.ProjectPath) ||
                !targetFramework.Equals(previousSnapshot.TargetFramework) ||
                !Equals(catalogs, previousSnapshot.Catalogs);

            if (anyChanges)
            {
                return(new TargetedDependenciesSnapshot(
                           projectPath,
                           targetFramework,
                           catalogs,
                           worldBuilder.ToImmutable()));
            }

            return(previousSnapshot);

            void Remove(RemoveDependencyContext context, IDependencyModel dependencyModel)
            {
                string dependencyId = Dependency.GetID(
                    targetFramework, dependencyModel.ProviderType, dependencyModel.Id);

                if (!context.TryGetDependency(dependencyId, out IDependency dependency))
                {
                    return;
                }

                context.Reset();

                foreach (IDependenciesSnapshotFilter filter in snapshotFilters)
                {
                    filter.BeforeRemove(
                        targetFramework,
                        dependency,
                        context);

                    anyChanges |= context.Changed;

                    if (!context.GetResult(filter))
                    {
                        // TODO breaking here denies later filters the opportunity to modify builders
                        return;
                    }
                }

                worldBuilder.Remove(dependencyId);
                anyChanges = true;
            }

            void Add(AddDependencyContext context, IDependencyModel dependencyModel)
            {
                // Create the unfiltered dependency
                IDependency?dependency = new Dependency(dependencyModel, targetFramework, projectPath);

                context.Reset();

                foreach (IDependenciesSnapshotFilter filter in snapshotFilters)
                {
                    filter.BeforeAddOrUpdate(
                        targetFramework,
                        dependency,
                        subTreeProviderByProviderType,
                        projectItemSpecs,
                        context);

                    dependency = context.GetResult(filter);

                    if (dependency == null)
                    {
                        break;
                    }
                }

                if (dependency != null)
                {
                    // A dependency was accepted
                    worldBuilder.Remove(dependency.Id);
                    worldBuilder.Add(dependency.Id, dependency);
                    anyChanges = true;
                }
                else
                {
                    // Even though the dependency was rejected, it's possible that filters made
                    // changes to other dependencies.
                    anyChanges |= context.Changed;
                }
            }
        }
示例#21
0
        public void FromChanges_UpdatesTopLevelDependencies()
        {
            const string projectPath     = @"c:\somefolder\someproject\a.csproj";
            var          targetFramework = new TargetFramework("tfm1");

            var dependencyTopPrevious = IDependencyFactory.FromJson(@"
                {
                    ""ProviderType"": ""Xxx"",
                    ""Id"": ""tfm1\\xxx\\topdependency1"",
                    ""Name"": ""TopDependency1"",
                    ""Caption"": ""TopDependency1"",
                    ""SchemaItemType"": ""Xxx"",
                    ""Resolved"": ""true""
                }");

            var dependencyModelTopAdded = IDependencyModelFactory.FromJson(@"
                {
                    ""ProviderType"": ""Xxx"",
                    ""Id"": ""topdependency1"",
                    ""Name"": ""TopDependency1"",
                    ""Caption"": ""TopDependency1"",
                    ""SchemaItemType"": ""Xxx"",
                    ""Resolved"": ""true""
                }",
                                                                           icon: KnownMonikers.Uninstall,
                                                                           expandedIcon: KnownMonikers.Uninstall);

            var dependencyTopUpdated = IDependencyFactory.FromJson(@"
                {
                    ""ProviderType"": ""Xxx"",
                    ""Id"": ""tfm1\\xxx\\topdependency1"",
                    ""Name"": ""TopDependency1"",
                    ""Caption"": ""TopDependency1"",
                    ""SchemaItemType"": ""Xxx"",
                    ""Resolved"": ""true""
                }");

            var catalogs         = IProjectCatalogSnapshotFactory.Create();
            var previousSnapshot = ITargetedDependenciesSnapshotFactory.Implement(
                projectPath: projectPath,
                targetFramework: targetFramework,
                catalogs: catalogs,
                dependenciesWorld: new[] { dependencyTopPrevious },
                topLevelDependencies: new[] { dependencyTopPrevious });

            var changes = new DependenciesChangesBuilder();

            changes.Added(dependencyModelTopAdded);

            var snapshotFilter = new TestDependenciesSnapshotFilter()
                                 .BeforeAddAccept(@"tfm1\xxx\topdependency1", dependencyTopUpdated);

            var snapshot = TargetedDependenciesSnapshot.FromChanges(
                projectPath,
                previousSnapshot,
                changes.Build(),
                catalogs,
                ImmutableArray.Create <IDependenciesSnapshotFilter>(snapshotFilter),
                new Dictionary <string, IProjectDependenciesSubTreeProvider>(),
                null);

            Assert.NotSame(previousSnapshot, snapshot);
            Assert.Same(dependencyTopUpdated, snapshot.TopLevelDependencies.Single());
        }
示例#22
0
        public void FromChanges_ReportedChangesAfterBeforeRemoveFilterDeclinedChange()
        {
            const string projectPath     = @"c:\somefolder\someproject\a.csproj";
            var          targetFramework = new TargetFramework("tfm1");

            var dependencyTop1 = new TestDependency
            {
                ProviderType   = "Xxx",
                Id             = "tfm1\\xxx\\topdependency1",
                Name           = "TopDependency1",
                Caption        = "TopDependency1",
                SchemaItemType = "Xxx",
                Resolved       = true,
                TopLevel       = true
            };

            var dependencyChild1 = new TestDependency
            {
                ProviderType   = "Xxx",
                Id             = "tfm1\\xxx\\childdependency1",
                Name           = "ChildDependency1",
                Caption        = "ChildDependency1",
                SchemaItemType = "Xxx",
                Resolved       = true,
                TopLevel       = false
            };

            var catalogs         = IProjectCatalogSnapshotFactory.Create();
            var previousSnapshot = ITargetedDependenciesSnapshotFactory.Implement(
                projectPath: projectPath,
                targetFramework: targetFramework,
                catalogs: catalogs,
                dependenciesWorld: new [] { dependencyTop1, dependencyChild1 },
                topLevelDependencies: new [] { dependencyTop1 });

            var changes = new DependenciesChangesBuilder();

            changes.Removed("Xxx", "topdependency1");

            var addedOnRemove = new TestDependency {
                Id = "SomethingElse", TopLevel = false
            };

            var snapshotFilter = new TestDependenciesSnapshotFilter()
                                 .BeforeRemoveReject(@"tfm1\xxx\topdependency1", addOrUpdate: addedOnRemove);

            var snapshot = TargetedDependenciesSnapshot.FromChanges(
                projectPath,
                previousSnapshot,
                changes.Build(),
                catalogs,
                ImmutableArray.Create <IDependenciesSnapshotFilter>(snapshotFilter),
                new Dictionary <string, IProjectDependenciesSubTreeProvider>(),
                null);

            Assert.NotSame(previousSnapshot, snapshot);

            Assert.Same(previousSnapshot.TargetFramework, snapshot.TargetFramework);
            Assert.Same(projectPath, snapshot.ProjectPath);
            Assert.Same(catalogs, snapshot.Catalogs);
            Assert.Single(snapshot.TopLevelDependencies);
            AssertEx.CollectionLength(snapshot.DependenciesWorld, 3);
            Assert.Contains(addedOnRemove, snapshot.DependenciesWorld.Values);
        }
        /// <summary>
        /// For each target framework in <paramref name="changes"/>, applies the corresponding
        /// <see cref="IDependenciesChanges"/> to <paramref name="previousSnapshot"/> in order to produce
        /// and return an updated <see cref="DependenciesSnapshot"/> object.
        /// If no changes are made, <paramref name="previousSnapshot"/> is returned unmodified.
        /// </summary>
        /// <remarks>
        /// As part of the update, each <see cref="IDependenciesSnapshotFilter"/> in <paramref name="snapshotFilters"/>
        /// is given a chance to influence the addition and removal of dependency data in the returned snapshot.
        /// </remarks>
        /// <returns>An updated snapshot, or <paramref name="previousSnapshot"/> if no changes occured.</returns>
        public static DependenciesSnapshot FromChanges(
            string projectPath,
            DependenciesSnapshot previousSnapshot,
            ImmutableDictionary <ITargetFramework, IDependenciesChanges> changes,
            IProjectCatalogSnapshot catalogs,
            ITargetFramework activeTargetFramework,
            ImmutableArray <IDependenciesSnapshotFilter> snapshotFilters,
            IReadOnlyDictionary <string, IProjectDependenciesSubTreeProvider> subTreeProviderByProviderType,
            IImmutableSet <string> projectItemSpecs)
        {
            Requires.NotNullOrWhiteSpace(projectPath, nameof(projectPath));
            Requires.NotNull(previousSnapshot, nameof(previousSnapshot));
            Requires.NotNull(changes, nameof(changes));
            // catalogs can be null
            Requires.Argument(!snapshotFilters.IsDefault, nameof(snapshotFilters), "Cannot be default.");
            Requires.NotNull(subTreeProviderByProviderType, nameof(subTreeProviderByProviderType));
            // projectItemSpecs can be null

            var builder = previousSnapshot.Targets.ToBuilder();

            bool targetChanged = false;

            foreach ((ITargetFramework targetFramework, IDependenciesChanges dependenciesChanges) in changes)
            {
                if (!builder.TryGetValue(targetFramework, out ITargetedDependenciesSnapshot previousTargetedSnapshot))
                {
                    previousTargetedSnapshot = TargetedDependenciesSnapshot.CreateEmpty(projectPath, targetFramework, catalogs);
                }

                ITargetedDependenciesSnapshot newTargetedSnapshot = TargetedDependenciesSnapshot.FromChanges(
                    projectPath,
                    previousTargetedSnapshot,
                    dependenciesChanges,
                    catalogs,
                    snapshotFilters,
                    subTreeProviderByProviderType,
                    projectItemSpecs);

                if (!ReferenceEquals(previousTargetedSnapshot, newTargetedSnapshot))
                {
                    builder[targetFramework] = newTargetedSnapshot;
                    targetChanged            = true;
                }
            }

            targetChanged |= RemoveTargetFrameworksWithNoDependencies();

            ITargetFramework activeTarget = activeTargetFramework ?? previousSnapshot.ActiveTarget;

            if (targetChanged)
            {
                // Targets have changed
                return(new DependenciesSnapshot(
                           previousSnapshot.ProjectPath,
                           activeTarget,
                           builder.ToImmutable()));
            }

            if (!activeTarget.Equals(previousSnapshot.ActiveTarget))
            {
                // The active target changed
                return(new DependenciesSnapshot(
                           previousSnapshot.ProjectPath,
                           activeTarget,
                           previousSnapshot.Targets));
            }

            // Nothing has changed, so return the same snapshot
            return(previousSnapshot);

            // Active target differs

            bool RemoveTargetFrameworksWithNoDependencies()
            {
                // This is a long-winded way of doing this that minimises allocations

                List <ITargetFramework> emptyFrameworks = null;
                bool anythingRemoved = false;

                foreach ((ITargetFramework targetFramework, ITargetedDependenciesSnapshot targetedSnapshot) in builder)
                {
                    if (targetedSnapshot.DependenciesWorld.Count == 0)
                    {
                        if (emptyFrameworks == null)
                        {
                            anythingRemoved = true;
                            emptyFrameworks = new List <ITargetFramework>(builder.Count);
                        }

                        emptyFrameworks.Add(targetFramework);
                    }
                }

                if (emptyFrameworks != null)
                {
                    foreach (ITargetFramework framework in emptyFrameworks)
                    {
                        builder.Remove(framework);
                    }
                }

                return(anythingRemoved);
            }
        }