/// <summary> /// Creates fingerprints from any matching diagnostics /// </summary> /// <param name="Job">The job that was run</param> /// <param name="JobStep">The job step that was run</param> /// <param name="Diagnostics">List of diagnostics that were produced by the build. Items should be removed from this list if they match.</param> /// <param name="Issues">List which receives all the matched issues.</param> public virtual void Match(InputJob Job, InputJobStep JobStep, List <InputDiagnostic> Diagnostics, List <Issue> Issues) { for (int Idx = 0; Idx < Diagnostics.Count; Idx++) { InputDiagnostic Diagnostic = Diagnostics[Idx]; if (TryMatch(Job, JobStep, Diagnostic, Issues)) { Diagnostics.RemoveAt(Idx); Idx--; } } }
/// <summary> /// Adds a new build history for a stream /// </summary> /// <param name="Issue">The issue to add a build to</param> /// <param name="InputJob">The job containing the error</param> /// <param name="InputJobStep">The job step containing the error</param> /// <param name="InputErrorUrl">Url of the error</param> /// <param name="State">Current persistent state. Used to find previous build history.</param> void AddFailureToIssue(Issue Issue, InputJob InputJob, InputJobStep InputJobStep, string InputErrorUrl, PersistentState State) { // Find or add a step name to history mapping Dictionary <string, IssueHistory> StepNameToHistory; if (!Issue.Streams.TryGetValue(InputJob.Stream, out StepNameToHistory)) { StepNameToHistory = new Dictionary <string, IssueHistory>(); Issue.Streams.Add(InputJob.Stream, StepNameToHistory); } // Find or add a history for this step IssueHistory History; if (!StepNameToHistory.TryGetValue(InputJobStep.Name, out History)) { History = new IssueHistory(State.FindBuildBefore(InputJob.Stream, InputJob.Change, InputJobStep.Name)); StepNameToHistory.Add(InputJobStep.Name, History); } // Add the new build History.AddFailedBuild(CreateBuildForJobStep(InputJob, InputJobStep, InputErrorUrl)); }
/// <summary> /// Tries to create a fingerprint from an individual diagnostic. /// </summary> /// <param name="Job">The job that was run</param> /// <param name="JobStep">The job step that was run</param> /// <param name="Diagnostic">A diagnostic from the given job step</param> /// <param name="Issues">List which receives all the matched issues.</param> /// <returns>True if this diagnostic should be removed (usually because a fingerprint was created)</returns> public abstract bool TryMatch(InputJob Job, InputJobStep JobStep, InputDiagnostic Diagnostic, List <Issue> Issues);
/// <summary> /// Creates a TrackedBuild instance for the given jobstep /// </summary> /// <param name="InputJob">The job to create a build for</param> /// <param name="InputJobStep">The step to create a build for</param> /// <param name="InputErrorUrl">The error Url</param> /// <returns>New build instance</returns> IssueBuild CreateBuildForJobStep(InputJob InputJob, InputJobStep InputJobStep, string InputErrorUrl) { return(new IssueBuild(InputJob.Change, InputJob.Name, InputJob.Url, InputJobStep.Name, InputJobStep.Url, InputErrorUrl)); }
/// <summary> /// Finds or adds an issue for a particular issue /// </summary> /// <param name="Perforce">Perforce connection used to find possible causers</param> /// <param name="State">The current set of tracked issues</param> /// <param name="Build">The new build</param> /// <param name="PreviousChange">The last changelist that was built before this one</param> /// <param name="InputJob">Job containing the step to add</param> /// <param name="InputJobStep">The job step to add</param> Issue MergeIntoExistingIssue(PerforceConnection Perforce, PersistentState State, InputJob InputJob, InputJobStep InputJobStep, Issue InputIssue, Lazy <IReadOnlyList <ChangeInfo> > LazyChanges) { // Find the pattern matcher for this fingerprint Matcher Matcher = CategoryToMatcher[InputIssue.Category]; // Check if it can be added to an existing open issue foreach (Issue Issue in State.Issues) { // Check this issue already exists in the current stream Dictionary <string, IssueHistory> StepNameToHistory; if (!Issue.Streams.TryGetValue(InputJob.Stream, out StepNameToHistory)) { continue; } // Check that this issue has not already been closed IssueHistory History; if (StepNameToHistory.TryGetValue(InputJobStep.Name, out History)) { if (!History.CanAddFailedBuild(InputJob.Change)) { continue; } } else { if (!StepNameToHistory.Values.Any(x => x.CanAddFailedBuild(InputJob.Change))) { continue; } } // Try to merge the fingerprint if (!Matcher.CanMerge(InputIssue, Issue)) { continue; } // Add the new build Matcher.Merge(InputIssue, Issue); return(Issue); } // Check if this issue can be merged with an issue built in another stream IReadOnlyList <ChangeInfo> Changes = LazyChanges.Value; if (Changes != null && Changes.Count > 0) { SortedSet <int> SourceChanges = new SortedSet <int>(Changes.SelectMany(x => x.SourceChanges)); foreach (Issue Issue in State.Issues) { // Check if this issue does not already contain this stream, but contains one of the causing changes if (Issue.Streams.ContainsKey(InputJob.Stream)) { continue; } if (!SourceChanges.Any(x => Issue.SourceChanges.Contains(x))) { continue; } if (!Matcher.CanMerge(InputIssue, Issue)) { continue; } // Merge the issue Matcher.Merge(InputIssue, Issue); return(Issue); } } // Check if it can be merged into an issue that's been created for this job. We only do this after exhausting all other options. foreach (Issue Issue in State.Issues) { if (Issue.InitialJobUrl == InputIssue.InitialJobUrl && Matcher.CanMergeInitialJob(InputIssue, Issue)) { Matcher.Merge(InputIssue, Issue); return(Issue); } } return(null); }
/// <summary> /// Adds diagnostics from a job step into the issue database /// </summary> /// <param name="Perforce">Perforce connection used to find possible causers</param> /// <param name="State">The current set of tracked issues</param> /// <param name="Build">The new build</param> /// <param name="PreviousChange">The last changelist that was built before this one</param> /// <param name="InputJob">Job containing the step to add</param> /// <param name="InputJobStep">The job step to add</param> void AddStep(PerforceConnection Perforce, PersistentState State, InputJob InputJob, InputJobStep InputJobStep) { // Create a lazily evaluated list of changes that are responsible for any errors Lazy <IReadOnlyList <ChangeInfo> > LazyChanges = new Lazy <IReadOnlyList <ChangeInfo> >(() => FindChanges(Perforce, State, InputJob)); // Create issues for any diagnostics in this step List <Issue> InputIssues = new List <Issue>(); foreach (Matcher Matcher in Matchers) { Matcher.Match(InputJob, InputJobStep, InputJobStep.Diagnostics, InputIssues); } // Merge the issues together List <Issue> NewIssues = new List <Issue>(); foreach (Issue InputIssue in InputIssues) { Issue OutputIssue = MergeIntoExistingIssue(Perforce, State, InputJob, InputJobStep, InputIssue, LazyChanges); if (OutputIssue == null) { NewIssues.Add(InputIssue); State.Issues.Add(InputIssue); OutputIssue = InputIssue; } AddFailureToIssue(OutputIssue, InputJob, InputJobStep, InputIssue.Diagnostics[0].ErrorUrl, State); } // Update the watchers for any new issues foreach (Issue NewIssue in NewIssues) { IReadOnlyList <ChangeInfo> Changes = LazyChanges.Value; if (Changes != null) { // Find the pattern matcher for this issue Matcher Matcher = CategoryToMatcher[NewIssue.Category]; // Update the causers List <ChangeInfo> Causers = Matcher.FindCausers(Perforce, NewIssue, Changes); foreach (ChangeInfo Causer in Causers) { NewIssue.SourceChanges.UnionWith(Causer.SourceChanges); NewIssue.PendingWatchers.Add(Causer.Record.User); } } } }