Beispiel #1
0
        internal static OrleansTaskScheduler InitializeSchedulerForTesting(
            ISchedulingContext context,
            ILoggerFactory loggerFactory)
        {
            var services = new ServiceCollection();

            services.AddOptions();
            services.AddLogging();
            services.AddSingleton <ExecutorService>();
            services.AddSingleton <SchedulerStatisticsGroup>();
            services.AddSingleton <StageAnalysisStatisticsGroup>();
            services.AddSingleton(loggerFactory);
            services.Configure <SchedulingOptions>(options =>
            {
                options.MaxActiveThreads                 = 4;
                options.DelayWarningThreshold            = TimeSpan.FromMilliseconds(100);
                options.ActivationSchedulingQuantum      = TimeSpan.FromMilliseconds(100);
                options.TurnWarningLengthThreshold       = TimeSpan.FromMilliseconds(100);
                options.StoppedActivationWarningInterval = TimeSpan.FromMilliseconds(200);
            });

            var serviceProvider = services.BuildServiceProvider();

            var scheduler = ActivatorUtilities.CreateInstance <OrleansTaskScheduler>(serviceProvider);

            scheduler.Start();
            WorkItemGroup ignore = scheduler.RegisterWorkContext(context);

            return(scheduler);
        }
Beispiel #2
0
        public async Task Can_delete_resource_with_OneToOne_relationship_from_dependent_side()
        {
            // Arrange
            RgbColor existingColor = _fakers.RgbColor.Generate();

            existingColor.Group = _fakers.WorkItemGroup.Generate();

            await _testContext.RunOnDatabaseAsync(async dbContext =>
            {
                dbContext.RgbColors.Add(existingColor);
                await dbContext.SaveChangesAsync();
            });

            string route = "/rgbColors/" + existingColor.StringId;

            // Act
            (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecuteDeleteAsync <string>(route);

            // Assert
            httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent);

            responseDocument.Should().BeEmpty();

            await _testContext.RunOnDatabaseAsync(async dbContext =>
            {
                RgbColor colorsInDatabase = await dbContext.RgbColors.FirstWithIdOrDefaultAsync(existingColor.Id);

                colorsInDatabase.Should().BeNull();

                WorkItemGroup groupInDatabase = await dbContext.Groups.FirstWithIdAsync(existingColor.Group.Id);

                groupInDatabase.Color.Should().BeNull();
            });
        }
Beispiel #3
0
        internal static WorkItemGroup CreateWorkItemGroupForTesting(
            IGrainContext context,
            ILoggerFactory loggerFactory)
        {
            var services = new ServiceCollection();

            services.AddOptions();
            services.AddLogging();
            services.AddSingleton <SchedulerStatisticsGroup>();
            services.AddSingleton <StageAnalysisStatisticsGroup>();
            services.AddSingleton(loggerFactory);
            services.Configure <SchedulingOptions>(options =>
            {
                options.DelayWarningThreshold            = TimeSpan.FromMilliseconds(100);
                options.ActivationSchedulingQuantum      = TimeSpan.FromMilliseconds(100);
                options.TurnWarningLengthThreshold       = TimeSpan.FromMilliseconds(100);
                options.StoppedActivationWarningInterval = TimeSpan.FromMilliseconds(200);
            });

            var s      = services.BuildServiceProvider();
            var result = new WorkItemGroup(
                context,
                s.GetRequiredService <ILogger <WorkItemGroup> >(),
                s.GetRequiredService <ILogger <ActivationTaskScheduler> >(),
                s.GetRequiredService <SchedulerStatisticsGroup>(),
                s.GetRequiredService <IOptions <StatisticsOptions> >(),
                s.GetRequiredService <IOptions <SchedulingOptions> >());

            return(result);
        }
Beispiel #4
0
        public async Task Can_create_OneToOne_relationship_from_principal_side()
        {
            // Arrange
            WorkItemGroup existingGroup = _fakers.WorkItemGroup.Generate();

            existingGroup.Color = _fakers.RgbColor.Generate();

            await _testContext.RunOnDatabaseAsync(async dbContext =>
            {
                dbContext.Groups.Add(existingGroup);
                await dbContext.SaveChangesAsync();
            });

            var requestBody = new
            {
                data = new
                {
                    type          = "workItemGroups",
                    relationships = new
                    {
                        color = new
                        {
                            data = new
                            {
                                type = "rgbColors",
                                id   = existingGroup.Color.StringId
                            }
                        }
                    }
                }
            };

            const string route = "/workItemGroups";

            // Act
            (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecutePostAsync <Document>(route, requestBody);

            // Assert
            httpResponse.Should().HaveStatusCode(HttpStatusCode.Created);

            responseDocument.SingleData.Should().NotBeNull();
            responseDocument.SingleData.Attributes.Should().NotBeEmpty();
            responseDocument.SingleData.Relationships.Should().NotBeEmpty();

            string newGroupId = responseDocument.SingleData.Id;

            newGroupId.Should().NotBeNullOrEmpty();

            await _testContext.RunOnDatabaseAsync(async dbContext =>
            {
                List <WorkItemGroup> groupsInDatabase = await dbContext.Groups.Include(group => group.Color).ToListAsync();

                WorkItemGroup newGroupInDatabase = groupsInDatabase.Single(group => group.StringId == newGroupId);
                newGroupInDatabase.Color.Should().NotBeNull();
                newGroupInDatabase.Color.Id.Should().Be(existingGroup.Color.Id);

                WorkItemGroup existingGroupInDatabase = groupsInDatabase.Single(group => group.Id == existingGroup.Id);
                existingGroupInDatabase.Color.Should().BeNull();
            });
        }
 public void SiloStatusChangeNotification(SiloAddress updatedSilo, SiloStatus status)
 {
     WorkItemGroup.QueueAction(() =>
     {
         Utils.SafeExecute(() => this.OnSiloStatusChange(updatedSilo, status), this.logger);
     });
 }
        public async Task Can_clear_OneToOne_relationship()
        {
            // Arrange
            WorkItemGroup existingGroup = _fakers.WorkItemGroup.Generate();

            existingGroup.Color = _fakers.RgbColor.Generate();

            await _testContext.RunOnDatabaseAsync(async dbContext =>
            {
                dbContext.Groups.AddRange(existingGroup);
                await dbContext.SaveChangesAsync();
            });

            var requestBody = new
            {
                data = (object)null
            };

            string route = $"/workItemGroups/{existingGroup.StringId}/relationships/color";

            // Act
            (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePatchAsync <string>(route, requestBody);

            // Assert
            httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent);

            responseDocument.Should().BeEmpty();

            await _testContext.RunOnDatabaseAsync(async dbContext =>
            {
                WorkItemGroup groupInDatabase = await dbContext.Groups.Include(group => group.Color).FirstWithIdOrDefaultAsync(existingGroup.Id);

                groupInDatabase.Color.Should().BeNull();
            });
        }
Beispiel #7
0
        public async Task Can_create_OneToOne_relationship_from_principal_side()
        {
            // Arrange
            WorkItemGroup existingGroup = _fakers.WorkItemGroup.Generate();

            existingGroup.Color = _fakers.RgbColor.Generate();

            RgbColor existingColor = _fakers.RgbColor.Generate();

            await _testContext.RunOnDatabaseAsync(async dbContext =>
            {
                dbContext.AddRange(existingGroup, existingColor);
                await dbContext.SaveChangesAsync();
            });

            var requestBody = new
            {
                data = new
                {
                    type          = "workItemGroups",
                    id            = existingGroup.StringId,
                    relationships = new
                    {
                        color = new
                        {
                            data = new
                            {
                                type = "rgbColors",
                                id   = existingColor.StringId
                            }
                        }
                    }
                }
            };

            string route = "/workItemGroups/" + existingGroup.StringId;

            // Act
            (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecutePatchAsync <Document>(route, requestBody);

            httpResponse.Should().HaveStatusCode(HttpStatusCode.OK);

            responseDocument.SingleData.Should().NotBeNull();

            await _testContext.RunOnDatabaseAsync(async dbContext =>
            {
                List <RgbColor> colorsInDatabase = await dbContext.RgbColors.Include(rgbColor => rgbColor.Group).ToListAsync();

                RgbColor colorInDatabase1 = colorsInDatabase.Single(color => color.Id == existingGroup.Color.Id);
                colorInDatabase1.Group.Should().BeNull();

                RgbColor colorInDatabase2 = colorsInDatabase.Single(color => color.Id == existingColor.Id);
                colorInDatabase2.Group.Should().NotBeNull();
                colorInDatabase2.Group.Id.Should().Be(existingGroup.Id);
            });
        }
Beispiel #8
0
        public async Task Sched_Stopped_WorkItemGroup()
        {
            var           context       = new UnitTestSchedulingContext();
            var           scheduler     = this.orleansTaskScheduler = TestInternalHelper.InitializeSchedulerForTesting(context, this.loggerFactory);
            WorkItemGroup workItemGroup = this.orleansTaskScheduler.GetWorkItemGroup(context);

            void CheckScheduler(object state)
            {
                Assert.IsType <string>(state);
                Assert.Equal("some state", state as string);
                Assert.IsType <ActivationTaskScheduler>(TaskScheduler.Current);
            }

            Task <Task> ScheduleTask() => Task.Factory.StartNew(
                state =>
            {
                CheckScheduler(state);

                return(Task.Factory.StartNew(
                           async s =>
                {
                    CheckScheduler(s);
                    await Task.Delay(50);
                    CheckScheduler(s);
                },
                           state).Unwrap());
            },
                "some state",
                CancellationToken.None,
                TaskCreationOptions.DenyChildAttach,
                workItemGroup.TaskScheduler);

            // Check that the WorkItemGroup is functioning.
            await await ScheduleTask();

            workItemGroup.Stop();

            var taskAfterStopped = ScheduleTask();
            var resultTask       = await Task.WhenAny(taskAfterStopped, Task.Delay(TimeSpan.FromSeconds(10)));

            Assert.Same(taskAfterStopped, resultTask);

            await await taskAfterStopped;

            // Wait for the WorkItemGroup to upgrade the warning to an error and try again.
            // This delay is based upon SchedulingOptions.StoppedActivationWarningInterval.
            await Task.Delay(TimeSpan.FromMilliseconds(300));

            taskAfterStopped = ScheduleTask();
            resultTask       = await Task.WhenAny(taskAfterStopped, Task.Delay(TimeSpan.FromSeconds(10)));

            Assert.Same(taskAfterStopped, resultTask);

            await await taskAfterStopped;
        }
Beispiel #9
0
        internal static OrleansTaskScheduler InitializeSchedulerForTesting(ISchedulingContext context, ICorePerformanceMetrics performanceMetrics, ILoggerFactory loggerFactory)
        {
            StatisticsCollector.StatisticsCollectionLevel = StatisticsLevel.Info;
            SchedulerStatisticsGroup.Init(loggerFactory);
            var scheduler = OrleansTaskScheduler.CreateTestInstance(4, performanceMetrics, loggerFactory);

            scheduler.Start();
            WorkItemGroup ignore = scheduler.RegisterWorkContext(context);

            return(scheduler);
        }
        public async Task Can_replace_OneToOne_relationship_from_principal_side()
        {
            // Arrange
            List <WorkItemGroup> existingGroups = _fakers.WorkItemGroup.Generate(2);

            existingGroups[0].Color = _fakers.RgbColor.Generate();
            existingGroups[1].Color = _fakers.RgbColor.Generate();

            await _testContext.RunOnDatabaseAsync(async dbContext =>
            {
                dbContext.Groups.AddRange(existingGroups);
                await dbContext.SaveChangesAsync();
            });

            var requestBody = new
            {
                data = new
                {
                    type = "rgbColors",
                    id   = existingGroups[0].Color.StringId
                }
            };

            string route = $"/workItemGroups/{existingGroups[1].StringId}/relationships/color";

            // Act
            (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePatchAsync <string>(route, requestBody);

            httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent);

            responseDocument.Should().BeEmpty();

            await _testContext.RunOnDatabaseAsync(async dbContext =>
            {
                List <WorkItemGroup> groupsInDatabase = await dbContext.Groups.Include(group => group.Color).ToListAsync();

                WorkItemGroup groupInDatabase1 = groupsInDatabase.Single(group => group.Id == existingGroups[0].Id);
                groupInDatabase1.Color.Should().BeNull();

                WorkItemGroup groupInDatabase2 = groupsInDatabase.Single(group => group.Id == existingGroups[1].Id);
                groupInDatabase2.Color.Should().NotBeNull();
                groupInDatabase2.Color.Id.Should().Be(existingGroups[0].Color.Id);

                List <RgbColor> colorsInDatabase = await dbContext.RgbColors.Include(color => color.Group).ToListAsync();

                RgbColor colorInDatabase1 = colorsInDatabase.Single(color => color.Id == existingGroups[0].Color.Id);
                colorInDatabase1.Group.Should().NotBeNull();
                colorInDatabase1.Group.Id.Should().Be(existingGroups[1].Id);

                RgbColor colorInDatabase2 = colorsInDatabase.SingleOrDefault(color => color.Id == existingGroups[1].Color.Id);
                colorInDatabase1.Should().NotBeNull();
                colorInDatabase2 !.Group.Should().BeNull();
            });
        }
        internal static OrleansTaskScheduler InitializeSchedulerForTesting(ISchedulingContext context)
        {
            StatisticsCollector.StatisticsCollectionLevel = StatisticsLevel.Info;
            SchedulerStatisticsGroup.Init();
            var scheduler = OrleansTaskScheduler.CreateTestInstance(4);

            scheduler.Start();
            WorkItemGroup ignore = scheduler.RegisterWorkContext(context);

            return(scheduler);
        }
Beispiel #12
0
        public async Task Can_partially_update_resource_with_guid_ID()
        {
            // Arrange
            WorkItemGroup existingGroup = _fakers.WorkItemGroup.Generate();
            string        newName       = _fakers.WorkItemGroup.Generate().Name;

            await _testContext.RunOnDatabaseAsync(async dbContext =>
            {
                dbContext.Groups.Add(existingGroup);
                await dbContext.SaveChangesAsync();
            });

            var requestBody = new
            {
                data = new
                {
                    type       = "workItemGroups",
                    id         = existingGroup.StringId,
                    attributes = new
                    {
                        name = newName
                    }
                }
            };

            string route = "/workItemGroups/" + existingGroup.StringId;

            // Act
            (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecutePatchAsync <Document>(route, requestBody);

            // Assert
            httpResponse.Should().HaveStatusCode(HttpStatusCode.OK);

            responseDocument.SingleData.Should().NotBeNull();
            responseDocument.SingleData.Type.Should().Be("workItemGroups");
            responseDocument.SingleData.Id.Should().Be(existingGroup.StringId);
            responseDocument.SingleData.Attributes["name"].Should().Be(newName);
            responseDocument.SingleData.Attributes["isPublic"].Should().Be(existingGroup.IsPublic);
            responseDocument.SingleData.Relationships.Should().NotBeEmpty();

            await _testContext.RunOnDatabaseAsync(async dbContext =>
            {
                WorkItemGroup groupInDatabase = await dbContext.Groups.FirstWithIdAsync(existingGroup.Id);

                groupInDatabase.Name.Should().Be(newName);
                groupInDatabase.IsPublic.Should().Be(existingGroup.IsPublic);
            });

            PropertyInfo property = typeof(WorkItemGroup).GetProperty(nameof(Identifiable.Id));

            property.Should().NotBeNull().And.Subject.PropertyType.Should().Be(typeof(Guid));
        }
Beispiel #13
0
        /// <summary>
        /// Send a ping to a remote silo via an intermediary silo. This is intended to be called from a <see cref="SiloHealthMonitor"/>
        /// in order to initiate the call from the <see cref="MembershipSystemTarget"/>'s context
        /// </summary>
        /// <param name="intermediary">The intermediary which will directly probe the target.</param>
        /// <param name="target">The target which will be probed.</param>
        /// <param name="probeTimeout">The timeout for the eventual direct probe request.</param>
        /// <param name="probeNumber">The probe number, for diagnostic purposes.</param>
        /// <returns>The result of pinging the remote silo.</returns>
        public Task <IndirectProbeResponse> ProbeRemoteSiloIndirectly(SiloAddress intermediary, SiloAddress target, TimeSpan probeTimeout, int probeNumber)
        {
            Task <IndirectProbeResponse> ProbeIndirectly()
            {
                var remoteOracle = this.grainFactory.GetSystemTarget <IMembershipService>(Constants.MembershipServiceType, intermediary);

                return(remoteOracle.ProbeIndirectly(target, probeTimeout, probeNumber));
            }

            var workItem = new AsyncClosureWorkItem <IndirectProbeResponse>(ProbeIndirectly, this);

            WorkItemGroup.QueueWorkItem(workItem);
            return(workItem.Task);
        }
Beispiel #14
0
        public async Task Can_create_resource_with_client_generated_guid_ID_having_side_effects_with_fieldset()
        {
            // Arrange
            WorkItemGroup newGroup = _fakers.WorkItemGroup.Generate();

            newGroup.Id = Guid.NewGuid();

            var requestBody = new
            {
                data = new
                {
                    type       = "workItemGroups",
                    id         = newGroup.StringId,
                    attributes = new
                    {
                        name = newGroup.Name
                    }
                }
            };

            const string route = "/workItemGroups?fields[workItemGroups]=name";

            // Act
            (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecutePostAsync <Document>(route, requestBody);

            // Assert
            httpResponse.Should().HaveStatusCode(HttpStatusCode.Created);

            responseDocument.SingleData.Should().NotBeNull();
            responseDocument.SingleData.Type.Should().Be("workItemGroups");
            responseDocument.SingleData.Id.Should().Be(newGroup.StringId);
            responseDocument.SingleData.Attributes.Should().HaveCount(1);
            responseDocument.SingleData.Attributes["name"].Should().Be(newGroup.Name);
            responseDocument.SingleData.Relationships.Should().BeNull();

            await _testContext.RunOnDatabaseAsync(async dbContext =>
            {
                WorkItemGroup groupInDatabase = await dbContext.Groups.FirstWithIdAsync(newGroup.Id);

                groupInDatabase.Name.Should().Be(newGroup.Name);
            });

            PropertyInfo property = typeof(WorkItemGroup).GetProperty(nameof(Identifiable.Id));

            property.Should().NotBeNull().And.Subject.PropertyType.Should().Be(typeof(Guid));
        }
        private async Task <bool> MoveWorkItemGroup(WorkItemGroup group, string processId, string witRefName, string pageId, string sectionId, string oldSectionId, string oldPageId = null)
        {
            var client = GetHttpClient(
                $"{new UriBuilder(Options.Organisation)}/_apis/work/processes" +
                $"/{processId}/workItemTypes/{witRefName}/layout/pages/{pageId}/sections/{sectionId}/groups/{group.Id}?" +
                $"removeFromSectionId={oldSectionId}" +
                (!string.IsNullOrEmpty(oldPageId) ? $"&removeFromPageId={oldPageId}" : ""),
                "api-version=6.1-preview.1");

            DefaultContractResolver contractResolver = new DefaultContractResolver
            {
                NamingStrategy = new CamelCaseNamingStrategy()
            };
            var jsonSettings = new JsonSerializerSettings
            {
                NullValueHandling = NullValueHandling.Ignore,
                ContractResolver  = contractResolver,
            };
            string body = JsonConvert.SerializeObject(group.ToJson(), jsonSettings);

            var content = new StringContent(body, Encoding.UTF8, "application/json");
            var result  = await client.PutAsync(client.BaseAddress, content);

            var responseContent = await result.Content.ReadAsStringAsync();

            if (result.StatusCode != HttpStatusCode.OK && result.StatusCode != HttpStatusCode.Created)
            {
                Log.LogError("Error moving {DefinitionType}: {processId}::{witRefName}::{pageId}::{sectionId}::{groupLabel}. Please migrate it manually. \r\nUrl: PUT {Url}\r\n{ErrorText}", typeof(WorkItemGroup).Name, processId, witRefName, pageId, sectionId, group.Label, result.RequestMessage.RequestUri.ToString(), responseContent);
                return(false);
            }
            else
            {
                var targetObject = JsonConvert.DeserializeObject <WorkItemGroup>(responseContent);
                group.Id = targetObject.Id;
                return(true);
            }
        }
        public void Sched_Task_Turn_Execution_Order()
        {
            // A unit test that checks that any turn is indeed run till completion before any other turn?
            // For example, you have a long running main turn and in the middle it spawns a lot of short CWs (on Done promise) and StartNew.
            // You test that no CW/StartNew runs until the main turn is fully done. And run in stress.

            UnitTestSchedulingContext context             = new UnitTestSchedulingContext();
            OrleansTaskScheduler      masterScheduler     = this.orleansTaskScheduler = TestInternalHelper.InitializeSchedulerForTesting(context, this.performanceMetrics, this.loggerFactory);
            WorkItemGroup             workItemGroup       = this.orleansTaskScheduler.GetWorkItemGroup(context);
            ActivationTaskScheduler   activationScheduler = workItemGroup.TaskRunner;

            this.mainDone  = false;
            this.stageNum1 = this.stageNum2 = 0;

            var result1 = new TaskCompletionSource <bool>();
            var result2 = new TaskCompletionSource <bool>();

            Task wrapper       = null;
            Task finalTask1    = null;
            Task finalPromise2 = null;

            masterScheduler.QueueWorkItem(new ClosureWorkItem(() =>
            {
                Log(1, "Outer ClosureWorkItem " + Task.CurrentId + " starting");
                Assert.Equal(activationScheduler, TaskScheduler.Current);   // "TaskScheduler.Current #0"

                Log(2, "Starting wrapper Task");
                wrapper = Task.Factory.StartNew(() =>
                {
                    Log(3, "Inside wrapper Task Id=" + Task.CurrentId);
                    Assert.Equal(activationScheduler, TaskScheduler.Current);   // "TaskScheduler.Current #1"

                    // Execution chain #1
                    Log(4, "Wrapper Task Id=" + Task.CurrentId + " creating Task chain");
                    Task task1 = Task.Factory.StartNew(() =>
                    {
                        Log(5, "#11 Inside sub-Task Id=" + Task.CurrentId);
                        Assert.Equal(activationScheduler, TaskScheduler.Current);   // "TaskScheduler.Current #11"
                        SubProcess1(11);
                    });
                    Task task2 = task1.ContinueWith((Task task) =>
                    {
                        Log(6, "#12 Inside continuation Task Id=" + Task.CurrentId);
                        Assert.Equal(activationScheduler, TaskScheduler.Current);   // "TaskScheduler.Current #12"
                        if (task.IsFaulted)
                        {
                            throw task.Exception.Flatten();
                        }
                        SubProcess1(12);
                    });
                    Task task3 = task2.ContinueWith(task =>
                    {
                        Log(7, "#13 Inside continuation Task Id=" + Task.CurrentId);
                        Assert.Equal(activationScheduler, TaskScheduler.Current);   // "TaskScheduler.Current #13"
                        if (task.IsFaulted)
                        {
                            throw task.Exception.Flatten();
                        }
                        SubProcess1(13);
                    });
                    finalTask1 = task3.ContinueWith(task =>
                    {
                        Log(8, "#14 Inside final continuation Task Id=" + Task.CurrentId);
                        Assert.Equal(activationScheduler, TaskScheduler.Current);   // "TaskScheduler.Current #14"
                        if (task.IsFaulted)
                        {
                            throw task.Exception.Flatten();
                        }
                        SubProcess1(14);
                        result1.SetResult(true);
                    });

                    // Execution chain #2
                    Log(9, "Wrapper Task " + Task.CurrentId + " creating AC chain");
                    Task promise2 = Task.Factory.StartNew(() =>
                    {
                        Log(10, "#21 Inside sub-Task Id=" + Task.CurrentId);
                        Assert.Equal(activationScheduler, TaskScheduler.Current);   // "TaskScheduler.Current #21"
                        SubProcess2(21);
                    });
                    finalPromise2 = promise2.ContinueWith((_) =>
                    {
                        Log(11, "#22 Inside final continuation Task Id=" + Task.CurrentId);
                        Assert.Equal(activationScheduler, TaskScheduler.Current);   // "TaskScheduler.Current #22"
                        SubProcess2(22);
                        result2.SetResult(true);
                    });
                    finalPromise2.Ignore();

                    Log(12, "Wrapper Task Id=" + Task.CurrentId + " sleeping #2");
                    Thread.Sleep(TimeSpan.FromSeconds(1));

                    Log(13, "Wrapper Task Id=" + Task.CurrentId + " finished");
                });

                Log(14, "Outer ClosureWorkItem Task Id=" + Task.CurrentId + " sleeping");
                Thread.Sleep(TimeSpan.FromSeconds(1));
                Log(15, "Outer ClosureWorkItem Task Id=" + Task.CurrentId + " awake");

                Log(16, "Finished Outer ClosureWorkItem Task Id=" + wrapper.Id);
                this.mainDone = true;
            }), context);

            Log(17, "Waiting for ClosureWorkItem to spawn wrapper Task");
            for (int i = 0; i < 5 * WaitFactor; i++)
            {
                if (wrapper != null)
                {
                    break;
                }
                Thread.Sleep(TimeSpan.FromSeconds(1).Multiply(WaitFactor));
            }
            Assert.NotNull(wrapper); // Wrapper Task was not created

            Log(18, "Waiting for wrapper Task Id=" + wrapper.Id + " to complete");
            bool finished = wrapper.Wait(TimeSpan.FromSeconds(4 * WaitFactor));

            Log(19, "Done waiting for wrapper Task Id=" + wrapper.Id + " Finished=" + finished);
            if (!finished)
            {
                throw new TimeoutException();
            }
            Assert.False(wrapper.IsFaulted, "Wrapper Task faulted: " + wrapper.Exception);
            Assert.True(wrapper.IsCompleted, "Wrapper Task should be completed");

            Log(20, "Waiting for TaskWorkItem to complete");
            for (int i = 0; i < 15 * WaitFactor; i++)
            {
                if (this.mainDone)
                {
                    break;
                }
                Thread.Sleep(1000 * WaitFactor);
            }
            Log(21, "Done waiting for TaskWorkItem to complete MainDone=" + this.mainDone);
            Assert.True(this.mainDone, "Main Task should be completed");
            Assert.NotNull(finalTask1);    // Task chain #1 not created
            Assert.NotNull(finalPromise2); // Task chain #2 not created

            Log(22, "Waiting for final task #1 to complete");
            bool ok = finalTask1.Wait(TimeSpan.FromSeconds(4 * WaitFactor));

            Log(23, "Done waiting for final task #1 complete Ok=" + ok);
            if (!ok)
            {
                throw new TimeoutException();
            }
            Assert.False(finalTask1.IsFaulted, "Final Task faulted: " + finalTask1.Exception);
            Assert.True(finalTask1.IsCompleted, "Final Task completed");
            Assert.True(result1.Task.Result, "Timeout-1");

            Log(24, "Waiting for final promise #2 to complete");
            finalPromise2.Wait(TimeSpan.FromSeconds(4 * WaitFactor));
            Log(25, "Done waiting for final promise #2");
            Assert.False(finalPromise2.IsFaulted, "Final Task faulted: " + finalPromise2.Exception);
            Assert.True(finalPromise2.IsCompleted, "Final Task completed");
            Assert.True(result2.Task.Result, "Timeout-2");

            Assert.NotEqual(0, this.stageNum1); // "Work items did not get executed-1"
            Assert.Equal(14, this.stageNum1);   // "Work items executed out of order-1"
            Assert.NotEqual(0, this.stageNum2); // "Work items did not get executed-2"
            Assert.Equal(22, this.stageNum2);   // "Work items executed out of order-2"
        }
 /// <summary>
 /// Move a work item group from one Layout->Section to another Layout->Section
 /// </summary>
 /// <param name="group"></param>
 /// <param name="processId"></param>
 /// <param name="witRefName"></param>
 /// <param name="pageId"></param>
 /// <param name="sectionId"></param>
 /// <param name="oldSectionId"></param>
 /// <returns></returns>
 public async Task <bool> MoveWorkItemGroupWithinPage(WorkItemGroup group, string processId, string witRefName, string pageId, string sectionId, string oldSectionId)
 {
     return(await MoveWorkItemGroup(group, processId, witRefName, pageId, sectionId, oldSectionId));
 }
        private async Task SyncWorkItemType(WorkItemTypeModel sourceWit, string processId)
        {
            var targetWit = TargetModel.WorkItemTypes.ContainsKey(sourceWit.WorkItemType.Id) ? TargetModel.WorkItemTypes[sourceWit.WorkItemType.Id] : new();

            targetWit.WorkItemType = await Target.SyncDefinition(sourceWit.WorkItemType, targetWit.WorkItemType, processId);

            foreach (var state in sourceWit.States.Where(x => x.CustomizationType == "custom"))
            {
                if (state.StateCategory == "Completed")
                {
                    Log.LogWarning("Cannot modify [Completed] category on work item state [{0}] on wit type [{1}].", state.Name, sourceWit.WorkItemType.ReferenceName);
                }
                else
                {
                    await SyncDefinitionType <WorkItemState>(
                        TargetModel.WorkItemStates, state,
                        TargetModel.WorkItemStates.Values.FirstOrDefault(x => x.Name == state.Name),
                        processId, targetWit.WorkItemType.ReferenceName);
                }
            }
            foreach (var field in sourceWit.Fields)
            {
                var existingField = TargetModel.WorkItemFields.Values.FirstOrDefault(x => x.ReferenceName == field.ReferenceName);
                //if (existingField == null || (existingField != null && field.Customization != "system")) // I don't think you can modify
                //{
                await SyncDefinitionType <WorkItemTypeField>(
                    TargetModel.WorkItemFields,
                    field,
                    existingField,
                    processId, targetWit.WorkItemType.ReferenceName);

                //}
            }
            foreach (var rule in sourceWit.Rules)
            {
                await SyncDefinitionType <WorkItemRule>(
                    TargetModel.WorkItemRules,
                    rule,
                    TargetModel.WorkItemRules.Values.FirstOrDefault(x => x.Name == rule.Name),
                    processId, targetWit.WorkItemType.ReferenceName);
            }
            foreach (var behavior in sourceWit.Behaviors)
            {
                await SyncDefinitionType <WorkItemTypeBehavior>(
                    TargetModel.WorkItemTypeBehaviors,
                    behavior,
                    targetWit.Behaviors.FirstOrDefault(x => x.Id == behavior.Id),
                    processId, targetWit.WorkItemType.ReferenceName);
            }

            #region Sync Pages ...
            // Making sure the pages themselves are in sync is not too hard.. let's do them first
            foreach (var page in sourceWit.Layout.Pages)
            {
                var targetPage = targetWit.Layout.Pages
                                 .FirstOrDefault(x => x.Label.Equals(page.Label, StringComparison.OrdinalIgnoreCase));

                await SyncDefinitionType <WorkItemPage>(
                    TargetModel.WorkItemPages,
                    page, targetPage,
                    processId, targetWit.WorkItemType.ReferenceName);
            }
            #endregion

            #region Sync Sections and Groups ...
            // Now that we know all the target pages are present we can iterate over what resides on pages..
            // A page's constituent parts is a little more difficult because you need to support an existing
            // group or control that has moved pages, sections or groups

            foreach (var sourcePage in sourceWit.Layout.Pages)
            {
                var targetPage = targetWit.Layout.Pages
                                 .FirstOrDefault(x => x.Label.Equals(sourcePage.Label, StringComparison.OrdinalIgnoreCase));

                foreach (var sourceSection in sourcePage.Sections)
                {
                    foreach (var sourceGroup in sourceSection.Groups)
                    {
                        var sourceGroupKey = $"{sourceWit.WorkItemType.ReferenceName}::{sourcePage.Label}::{sourceSection.Id}::{sourceGroup.Label}";
                        // first let's see if the target has any inherited group for this source group ..
                        // It will have a group.inherits != null/"" if it is inherited.. you can edit "system" groups
                        if (!sourceGroup.Inherited) // It's a custom group
                        {
                            // look for the group on the flattened set of groups.. remember flat groups are keyed on $"{wit.ReferenceName}::{page.Label}::{section.Id}::{group.Label}"
                            var existingGroup = TargetModel.WorkItemGroups.Select(x => new { x.Key, x.Value })
                                                .FirstOrDefault(x =>
                                                                x.Key.StartsWith($"{targetWit.WorkItemType.ReferenceName}::") &&
                                                                x.Value.Label.Equals(sourceGroup.Label, StringComparison.OrdinalIgnoreCase));

                            if (existingGroup != null)
                            {
                                WorkItemGroup finalTargetGroup = null;
                                WorkItemPage  finalTargetPage  = null;
                                // here we know the group exists.. we need to check if its on the same page
                                if (sourceGroupKey.Equals(existingGroup.Key, StringComparison.OrdinalIgnoreCase))
                                {
                                    // It's on the same page/section.. no need to move
                                    Log.LogInformation("Target group [{0}:{1}] located on same page/section. Skipping group location sync..", targetPage.Label, existingGroup.Value.Label);
                                    finalTargetPage  = targetPage;
                                    finalTargetGroup = existingGroup.Value;
                                }
                                else
                                {
                                    // It's on a different page or section.. we need to move it .. but how can we tell
                                    // if it moved to a different page or just a different section? We split and compare
                                    var sourceSplit   = sourceGroupKey.Split("::".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
                                    var existingSplit = existingGroup.Key.Split("::".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
                                    var existingPage  = targetWit.Layout.Pages.FirstOrDefault(p => p.Label.Equals(existingSplit[1]));

                                    if (sourceSplit[1].Equals(existingSplit[1], StringComparison.OrdinalIgnoreCase))
                                    {
                                        // Its on the same page.. it must be section change.. lets move it to the new section
                                        var tempTargetGroup = existingGroup.Value.CloneAsNew();
                                        tempTargetGroup.Id = existingGroup.Value.Id;
                                        if (await Target.MoveWorkItemGroupWithinPage(
                                                tempTargetGroup, processId, sourceWit.WorkItemType.ReferenceName,
                                                targetPage.Id, sourceSplit[2], existingSplit[2]))
                                        {
                                            Log.LogInformation("Target group [{0}] located on same page but different section. Moved from [{1}] to [{2}] ..", sourceGroup.Label, sourceSplit[2], existingSplit[2]);
                                        }
                                        else
                                        {
                                            Log.LogError("Target group [{0}] located on same page but different section. Unable to move from [{1}] to [{2}] ..", sourceGroup.Label, sourceSplit[2], existingSplit[2]);
                                        }
                                        finalTargetPage  = existingPage;
                                        finalTargetGroup = tempTargetGroup;
                                    }
                                    else
                                    {
                                        // Its on a different page .. lets move pages
                                        var tempTargetGroup = existingGroup.Value.CloneAsNew();
                                        tempTargetGroup.Id = existingGroup.Value.Id;
                                        if (await Target.MoveWorkItemGroupToNewPage(
                                                tempTargetGroup, processId, targetWit.WorkItemType.ReferenceName,
                                                targetPage.Id, sourceSplit[2], existingPage.Id, existingSplit[2]))
                                        {
                                            Log.LogInformation("Target group located on different page. Moved from [{0}:{1}] to [{2}:{3}] ..", sourceSplit[1], sourceSplit[2], existingSplit[1], existingSplit[2]);
                                        }
                                        else
                                        {
                                            Log.LogError("Target group located on different page. Unable to move from [{0}:{1}] to [{2}:{3}]!", existingSplit[1], existingSplit[2], targetPage.Label, sourceSplit[2]);
                                        }
                                        finalTargetPage  = existingPage;
                                        finalTargetGroup = tempTargetGroup;
                                    }
                                }

                                // TODO Finish this!
                                // Iterate through the source controls and make sure the target has all the controls from source
                                foreach (var sourceControl in sourceGroup.Controls)
                                {
                                    if (sourceControl.ControlType == "HtmlFieldControl")
                                    {
                                        Log.LogWarning("Skipped HTML control sync [{0}] as it should have already been migrated as part of the group sync.", sourceControl.Label);
                                    }
                                    else
                                    {
                                        // Let's see if we can't find the control already present in the "final target"
                                        var targetControl = finalTargetGroup.Controls.FirstOrDefault(ctl => ctl.Id.Equals(sourceControl.Id, StringComparison.OrdinalIgnoreCase));

                                        if (targetControl == null)
                                        {
                                            // Let's see if its in another group perhaps.. if so we might want to move it.. that would imply that the group it was in is no longer
                                            WorkItemGroup oldGroup = null;
                                            foreach (var tempSection in finalTargetPage.Sections)
                                            {
                                                oldGroup = tempSection.Groups.FirstOrDefault(g => g.Controls.Any(c => c.Id.Equals(sourceControl.Id, StringComparison.OrdinalIgnoreCase)));
                                                if (oldGroup != null)
                                                {
                                                    break;
                                                }
                                            }

                                            if (oldGroup == null) // It must be a new control
                                            {
                                                if (await Target.AddWorkItemControlToGroup(sourceControl.CloneAsNew(), processId, sourceWit.WorkItemType.ReferenceName, finalTargetGroup.Id, sourceControl.Id))
                                                {
                                                    Log.LogInformation("Attached control [{0}] to group [{1}].", sourceControl.Label, finalTargetGroup.Label);
                                                }
                                                else
                                                {
                                                    Log.LogError("Failed to attach control [{0}] to new group [{1}]!", sourceControl.Label, finalTargetGroup.Label);
                                                }
                                            }
                                            else
                                            {
                                                // It must be control movement between groups
                                                if (await Target.MoveWorkItemControlToOtherGroup(sourceControl.CloneAsNew(), processId, sourceWit.WorkItemType.ReferenceName, finalTargetGroup.Id, sourceControl.Id, oldGroup.Id))
                                                {
                                                    Log.LogInformation("Moved control [{0}] from [{1}] to existing group [{2}].", sourceControl.Id, oldGroup.Label, finalTargetGroup.Label);
                                                }
                                                else
                                                {
                                                    Log.LogError("Failed to move control [{0}] from [{1}] to existing group [{2}].", sourceControl.Id, oldGroup.Label, finalTargetGroup.Label);
                                                }
                                            }
                                        }
                                        else
                                        {
                                            {
                                                Log.LogInformation("Target already contains control [{0}] in proper group [{1}].", sourceControl.Label, finalTargetGroup.Label);
                                            }
                                        }
                                    }
                                }
                            }
                            else
                            {
                                // Target doesn't have the group at all
                                WorkItemGroup newGroup = await SyncDefinitionType <WorkItemGroup>(TargetModel.WorkItemGroups, sourceGroup,
                                                                                                  null, processId, sourceWit.WorkItemType.ReferenceName, targetPage.Id, sourceSection.Id);

                                // Add all the controls
                                foreach (var sourceControl in sourceGroup.Controls.Where(c => !c.ControlType.Equals("HtmlFieldControl", StringComparison.OrdinalIgnoreCase)))
                                {
                                    if (await Target.AddWorkItemControlToGroup(sourceControl.CloneAsNew(), processId, sourceWit.WorkItemType.ReferenceName, newGroup.Id, sourceControl.Id))
                                    {
                                        Log.LogInformation("Attached control [{0}] to new group [{1}].", sourceControl.Label, newGroup.Label);
                                    }
                                    else
                                    {
                                        Log.LogError("Failed to attach control [{0}] to new group [{1}]!", sourceControl.Label, newGroup.Label);
                                    }
                                }
                            }
                        }
                    }
                }
            }

            #endregion

            #region Sync Controls ...
            // Let's get a fresh layout from the target, now that we know pages and groups are aligned.
            await LoadLayout(TargetModel, targetWit, Target, processId);

            // At this point all Pages, Sections and Groups should be aligned.. lets sync the controls
            foreach (var sourcePage in sourceWit.Layout.Pages)
            {
                foreach (var sourceSection in sourcePage.Sections)
                {
                    foreach (var sourceGroup in sourceSection.Groups)
                    {
                        foreach (var sourceControl in sourceGroup.Controls)
                        {
                        }
                    }
                }
            }
            #endregion
            Log.LogInformation($"Completed sync of work item type [{Source.Options.Name}::{sourceWit.WorkItemType.Name}] in [{Target.Options.Name}::{targetWit.WorkItemType.Name}].");
        }