public void Dispose_Connected_TimerDisposed() { // Arrange SetupSolutionBinding(isConnected: true, issues: new List <SonarQubeIssue>()); var issuesProvider = new SonarQubeIssuesProvider(mockSqService.Object, "sqkey", mockTimerFactory.Object, testLogger); // Act issuesProvider.Dispose(); issuesProvider.Dispose(); issuesProvider.Dispose(); // Assert mockTimer.Verify(x => x.Dispose(), Times.Once); }
public void Dispose_Disconnected_TimerDisposed() { // Arrange SetupSolutionBinding(isConnected: true, issues: null); var issuesProvider = new SonarQubeIssuesProvider(mockSqService.Object, "sqKey", mockTimerFactory.Object); WaitForInitialFetchTaskToStart(); // Act issuesProvider.Dispose(); issuesProvider.Dispose(); issuesProvider.Dispose(); // Assert mockTimer.Verify(x => x.Dispose(), Times.Once); }
public void Constructor_Connected_StartsMonitoringAndSyncs() { // Arrange SetupSolutionBinding(isConnected: true, issues: new List <SonarQubeIssue>()); // Act using (var issuesProvider = new SonarQubeIssuesProvider(mockSqService.Object, "keyXXX", mockTimerFactory.Object, testLogger)) { WaitForInitialFetchTaskToStart(); // Assert - issues are fetched and timer is started VerifyTimerStart(Times.Once()); timerRunning.Should().Be(true); VerifyServiceGetIssues(Times.Once(), "keyXXX"); } }
public void GetIssues_ErrorFetchingOnTimerElapsed_IsSuppressed() { // Tests that the timer trigger that causes the data to be refetched won't propagate errors // if the GetIssues call throws. var issue1 = new SonarQubeIssue("folder1/file1", "hash1", 0, "message", "sqkey:sqkey:projectId", SonarQubeIssueResolutionState.FalsePositive, "S101"); SetupSolutionBinding(isConnected: true, issues: new List <SonarQubeIssue> { issue1 }); // 1. Create the issue provider and call GetIssues to make sure the issues are cached var issuesProvider = new SonarQubeIssuesProvider(mockSqService.Object, "sqkey", mockTimerFactory.Object, testLogger); var matches = issuesProvider.GetSuppressedIssues("projectId", "folder1/file1"); VerifyServiceGetIssues(Times.Once()); matches.Count().Should().Be(1); testLogger.AssertPartialOutputStrings("Checking for suppressions", "1"); // 2. Configure service to throw, then execute the fetch trigger int fetchCallCount = 0; testLogger.Reset(); Func <IList <SonarQubeIssue> > serviceFetchIssuesTask = () => { fetchCallCount++; throw new ApplicationException("dummy error from mock"); }; SetupSolutionBinding(isConnected: true, serviceFetchIssuesTask: serviceFetchIssuesTask); RaiseTimerElapsed(DateTime.UtcNow); // 3. Fetch issues again - should used cached issues matches = issuesProvider.GetSuppressedIssues("projectId", "folder1/file1"); VerifyServiceGetIssues(Times.Exactly(2)); matches.Count().Should().Be(1); fetchCallCount.Should().Be(1); VerifyTimerStart(Times.Exactly(1)); // once, on construction testLogger.AssertPartialOutputStrings("Checking for suppressions", "dummy error from mock"); }
public void GetIssues_IssuesNotYetFetch_WaitsForIssuesToBeFetched() { var issue1 = new SonarQubeIssue("folder1/file1", "hash1", 0, "message", "sqkey:sqkey:projectID1", SonarQubeIssueResolutionState.FalsePositive, "S101"); int callbackCount = 0; bool callbackCompleted = false; Func <IList <SonarQubeIssue> > serviceFetchIssuesTask = () => { callbackCount++; InitialFetchWaitHandle.Set(); // signal so the test can continue Thread.Sleep(Debugger.IsAttached ? 5000 : 500); callbackCompleted = true; return(new List <SonarQubeIssue> { issue1 }); }; SetupSolutionBinding(isConnected: true, serviceFetchIssuesTask: serviceFetchIssuesTask); // 1. Create the issue provider // The initial fetch should be triggered, but not yet completed var issuesProvider = new SonarQubeIssuesProvider(mockSqService.Object, "sqKey", mockTimerFactory.Object, testLogger); WaitForInitialFetchTaskToStart(); // 2. Now request the issues - should wait until the issues have been retrieved var matches = issuesProvider.GetSuppressedIssues("projectid1", "folder1/file1"); VerifyServiceGetIssues(Times.Once(), "sqKey"); callbackCount.Should().Be(1); callbackCompleted.Should().BeTrue(); matches.Count().Should().Be(1); CheckExpectedIssueReturned("hash1", matches); // 3. Now fetch again - should not wait again matches = issuesProvider.GetSuppressedIssues("folder1/file1", "projectid1"); VerifyServiceGetIssues(Times.Once()); callbackCount.Should().Be(1); }
private void RefreshWorkflow(BindingConfiguration configuration) { // There might be some race condition here if an analysis is triggered while the delegates are reset and set back // to the new workflow behavior. This race condition would lead to some issues being reported using the old mode // instead of the new one but everything will be fixed on the next analysis. ResetState(); switch (configuration?.Mode) { case SonarLintMode.Standalone: this.currentWorklow = new SonarAnalyzerStandaloneWorkflow(this.workspace); break; case SonarLintMode.LegacyConnected: case SonarLintMode.Connected: var sonarQubeIssueProvider = new SonarQubeIssuesProvider(sonarQubeService, configuration.Project.ProjectKey, new TimerFactory()); this.disposableObjects.Add(sonarQubeIssueProvider); var liveIssueFactory = new LiveIssueFactory(workspace, vsSolution); var suppressionHandler = new SuppressionHandler(liveIssueFactory, sonarQubeIssueProvider); if (configuration.Mode == SonarLintMode.Connected) { var qualityProfileProvider = new SonarQubeQualityProfileProvider(sonarQubeService, logger); this.disposableObjects.Add(qualityProfileProvider); var cachingProvider = new QualityProfileProviderCachingDecorator(qualityProfileProvider, configuration.Project, sonarQubeService, new TimerFactory()); this.disposableObjects.Add(cachingProvider); this.currentWorklow = new SonarAnalyzerConnectedWorkflow(this.workspace, cachingProvider, configuration.Project, suppressionHandler); } else // Legacy { this.currentWorklow = new SonarAnalyzerLegacyConnectedWorkflow(this.workspace, suppressionHandler, this.logger); } break; default: break; } }
public void Event_OnSolutionBecomingUnbound_StopsTimer() { // 1. Bound and connected initially SetupSolutionBinding(isBound: true, isConnected: true, projectKey: "keyXXX", issues: new List <SonarQubeIssue>()); var issuesProvider = new SonarQubeIssuesProvider(mockSqService.Object, mockTracker.Object, mockTimerFactory.Object); VerifyServiceGetIssues(Times.Once()); VerifyTimerStart(Times.Once()); timerRunning.Should().Be(true); // 2. Event -> unbound solution SetupSolutionBinding(isBound: false, isConnected: false, projectKey: null, issues: null); mockTracker.Raise(e => e.SolutionBindingChanged += null, new ActiveSolutionBindingEventArgs(false, null)); VerifyServiceGetIssues(Times.Once()); // issues not fetched again VerifyTimerStop(Times.Once()); timerRunning.Should().Be(false); }
public void Event_OnSolutionBecomingBound_SynchronizesAndStartsTimer() { // 1. Initially not bound SetupSolutionBinding(isBound: false, isConnected: false, projectKey: null, issues: null); var issuesProvider = new SonarQubeIssuesProvider(mockSqService.Object, mockTracker.Object, mockTimerFactory.Object); VerifyServiceGetIssues(Times.Never()); VerifyTimerStart(Times.Never()); timerRunning.Should().Be(false); // 2. Event -> unbound solution SetupSolutionBinding(isBound: true, isConnected: true, projectKey: "keyXXX", issues: new List <SonarQubeIssue>()); RaiseSolutionBoundEvent(true, "keyYYY"); VerifyServiceGetIssues(Times.Once()); VerifyTimerStart(Times.Once()); timerRunning.Should().Be(true); }
public void Dispose_BoundSolution_StopsMonitoring() { // Arrange SetupSolutionBinding(isBound: true, isConnected: true, projectKey: "keyXXX", issues: new List <SonarQubeIssue>()); var issuesProvider = new SonarQubeIssuesProvider(mockSqService.Object, mockTracker.Object, mockTimerFactory.Object); // 1. Dispose issuesProvider.Dispose(); issuesProvider.Dispose(); issuesProvider.Dispose(); // Assert mockTimer.Verify(x => x.Dispose(), Times.Once); // 2. Check solution events are no longer being tracked mockSqService.ResetCalls(); RaiseSolutionBoundEvent(true, "keyABC"); VerifyServiceGetIssues(Times.Never()); }
public void GetSuppressedIssues_WhenProjectHasModulesAndIssueIsFileLevelAndIsNotFound_ReturnsNoIssue() { // Arrange var sonarQubeIssue1 = new SonarQubeIssue("\\foo\\foo.cs", null, null, "message", "sqkey:sqkey:projectId", "S1", true); var sonarQubeIssue2 = new SonarQubeIssue("/foo/foo.cs", null, null, "message", "sqkey:sqkey:projectId", "S2", true); var sonarQubeIssue3 = new SonarQubeIssue("foo\\foo.cs", null, null, "message", "sqkey:sqkey:projectId", "S3", true); var sonarQubeIssue4 = new SonarQubeIssue("foo/foo.cs", null, null, "message", "sqkey:sqkey:projectId", "S4", true); var sonarQubeIssue5 = new SonarQubeIssue("bar/bar.cs", null, null, "message", "sqkey:sqkey:projectId", "S5", true); SetupSolutionBinding(true, new List <SonarQubeIssue> { sonarQubeIssue1, sonarQubeIssue2, sonarQubeIssue3, sonarQubeIssue4, sonarQubeIssue5 }, new List <SonarQubeModule> { new SonarQubeModule("sqkey", "", ""), new SonarQubeModule("sqkey:sqkey:projectId", "", "src/bar") }); var issuesProvider = new SonarQubeIssuesProvider(mockSqService.Object, "sqkey", mockTimerFactory.Object, testLogger); WaitForInitialFetchTaskToStart(); VerifyServiceGetIssues(Times.Exactly(1)); // issues should be fetched on creation // We're deliberately faking SonarQube returning paths with \ instead of / which // the code should handle, but with an assertion since it means the format returned // by SonarQube has changed. using (new AssertIgnoreScope()) { // Act / Assert - #1 - file extension is not right var matches = issuesProvider.GetSuppressedIssues("guid doesn't matter", "C:\\AwesomeProject\\src\\bar\\foo\\foo.vb"); matches.Should().BeEmpty(); // Act / Assert - #2 - path is not normalized while comparison is strict for delimiters matches = issuesProvider.GetSuppressedIssues("guid doesn't matter", "C:/AwesomeProject/src/bar/foo/foo.cs"); matches.Should().BeEmpty(); // Act / Assert - #3 - current file is one level up compared to remote file matches = issuesProvider.GetSuppressedIssues("guid doesn't matter", "C:\\AwesomeProject\\src\\bar\\foo.cs"); matches.Should().BeEmpty(); } }
public void GetIssues_NoIssuesOnServer_ReturnsEmptyList() { SetupSolutionBinding(isConnected: true, issues: null); // 1. Created -> issues fetch in background var issuesProvider = new SonarQubeIssuesProvider(mockSqService.Object, "sqKey", mockTimerFactory.Object); WaitForInitialFetchTaskToStart(); VerifyServiceGetIssues(Times.Exactly(1), "sqKey"); // 2. SonarQube project key doesn't match -> no issues var matches = issuesProvider.GetSuppressedIssues("any project", "any file"); matches.Should().NotBeNull(); matches.Should().BeEmpty(); // Cached issues should be used after first fetch. Should not refetch just // because the initial fetch returned no items. VerifyServiceGetIssues(Times.Exactly(1)); }
public void GetSuppressedIssues_WhenProjectHasModulesAndIssueIsFileLevelAndIsNotFound_ReturnsNoIssue() { // Arrange var issues = new [] { CreateIssue("foo\\foo.cs", "sqkey:sqkey:projectId", "S2"), CreateIssue("foo\\foo.cs", "sqkey:sqkey:projectId", "S3"), CreateIssue("foo\\foo.cs", "sqkey:sqkey:projectId", "S4"), CreateIssue("bar\\bar.cs", "sqkey:sqkey:projectId", "S5"), }; SetupSolutionBinding(true, issues, new List <SonarQubeModule> { new SonarQubeModule("sqkey", "", ""), new SonarQubeModule("sqkey:sqkey:projectId", "", "src\\bar") }); var issuesProvider = new SonarQubeIssuesProvider(mockSqService.Object, "sqkey", mockTimerFactory.Object, testLogger); WaitForInitialFetchTaskToStart(); VerifyServiceGetIssues(Times.Exactly(1)); // issues should be fetched on creation // Act / Assert - #1 - file extension is not right var matches = issuesProvider.GetSuppressedIssues("guid doesn't matter", "C:\\AwesomeProject\\src\\bar\\foo\\foo.vb"); matches.Should().BeEmpty(); using (new AssertIgnoreScope()) { // Act / Assert - #2 - path is not normalized while comparison is strict for delimiters matches = issuesProvider.GetSuppressedIssues("guid doesn't matter", "C:/AwesomeProject/src/bar/foo/foo.cs"); matches.Should().BeEmpty(); } // Act / Assert - #3 - current file is one level up compared to remote file matches = issuesProvider.GetSuppressedIssues("guid doesn't matter", "C:\\AwesomeProject\\src\\bar\\foo.cs"); matches.Should().BeEmpty(); }
public void GetSuppressedIssues_WhenProjectHasNoModuleAndIssueIsOnAFileAtRootLevelWithNoModules_FalseMatch() { // On this test we are in an unlikely situation of having an issue suppressed only on a file(1) which is associated // with the root module and whose name also exists deeper in the file system hierarchy. // (1) If some issue was suppressed for the file deeper in the hierarchy it wouldn't find the wrong match as we // test from the longest matching to the shortest. // Arrange var sonarQubeIssue1 = new SonarQubeIssue("foo.cs", null, null, "message", "sqkey", "S1", true); var sonarQubeIssue2 = new SonarQubeIssue("toto/foo.cs", null, null, "message", "sqkey", "S2", true); SetupSolutionBinding(true, new List <SonarQubeIssue> { sonarQubeIssue1, sonarQubeIssue2 }, new List <SonarQubeModule> { new SonarQubeModule("sqkey", "", "") }); var issuesProvider = new SonarQubeIssuesProvider(mockSqService.Object, "sqkey", mockTimerFactory.Object, testLogger); WaitForInitialFetchTaskToStart(); VerifyServiceGetIssues(Times.Exactly(1)); // issues should be fetched on creation // Act / Assert - same file name exists at multiple levels in the hierarchy // This is the False Match... var matches = issuesProvider.GetSuppressedIssues("", "C:\\AwesomeProject\\src\\bar\\foo\\foo.cs"); matches.Should().HaveCount(1); matches.First().RuleId.Should().Be("S1"); // ... and this is the correct one matches = issuesProvider.GetSuppressedIssues("", "C:\\AwesomeProject\\src\\bar\\foo.cs"); matches.Should().HaveCount(1); matches.First().RuleId.Should().Be("S1"); }
public void GetSuppressedIssues_WhenProjectHasModulesAndIssueIsFileLevelAndIsFound_ReturnsExpectedIssue() { // Arrange var sonarQubeIssue1 = new SonarQubeIssue("\\foo\\foo.cs", null, null, "message", "sqkey:sqkey:projectId", "S1", true); var sonarQubeIssue2 = new SonarQubeIssue("/foo/foo.cs", null, null, "message", "sqkey:sqkey:projectId", "S2", true); var sonarQubeIssue3 = new SonarQubeIssue("foo\\FOO.cs", null, null, "message", "sqkey:sqkey:projectId", "S3", true); var sonarQubeIssue4 = new SonarQubeIssue("FOO/foo.cs", null, null, "message", "sqkey:sqkey:projectId", "S4", true); var sonarQubeIssue5 = new SonarQubeIssue("bar/bar.cs", null, null, "message", "sqkey:sqkey:projectId", "S5", true); SetupSolutionBinding(true, new List <SonarQubeIssue> { sonarQubeIssue1, sonarQubeIssue2, sonarQubeIssue3, sonarQubeIssue4, sonarQubeIssue5 }, new List <SonarQubeModule> { new SonarQubeModule("sqkey", "", ""), new SonarQubeModule("sqkey:sqkey:projectId", "", "src/bar") }); var issuesProvider = new SonarQubeIssuesProvider(mockSqService.Object, "sqkey", mockTimerFactory.Object, testLogger); WaitForInitialFetchTaskToStart(); VerifyServiceGetIssues(Times.Exactly(1)); // issues should be fetched on creation // Act IEnumerable <SonarQubeIssue> matches; // We're deliberately faking SonarQube returning paths with \ instead of / which // the code should handle, but with an assertion since it means the format returned // by SonarQube has changed. using (new AssertIgnoreScope()) { matches = issuesProvider.GetSuppressedIssues("guid doesn't matter", "C:\\AwesomeProject\\src\\bar\\foo\\foo.cs"); } // Assert matches.Should().HaveCount(4); matches.Should().OnlyContain(x => x.RuleId != "S5"); }
public void GetSuppressedIssues_WhenProjectHasModulesAndIssueIsModuleLevelAndIsNotFound_ReturnsNoIssue() { // Arrange var sonarQubeIssue1 = new SonarQubeIssue(null, null, null, "message", "sqkey:sqkey:projectId2", "S1", true); var sonarQubeIssue2 = new SonarQubeIssue("/foo/bar.cs", "hash", 123, "message", "sqkey:sqkey:projectId", "S2", true); var sonarQubeIssue3 = new SonarQubeIssue("/foo/bar.cs", "hash", 12, "message", "FOOBAR", "S3", true); SetupSolutionBinding(true, new List <SonarQubeIssue> { sonarQubeIssue1, sonarQubeIssue2, sonarQubeIssue3 }); var issuesProvider = new SonarQubeIssuesProvider(mockSqService.Object, "sqkey", mockTimerFactory.Object, testLogger); WaitForInitialFetchTaskToStart(); VerifyServiceGetIssues(Times.Exactly(1)); // issues should be fetched on creation // Act var matches = issuesProvider.GetSuppressedIssues("FOOBAR", null); // Assert matches.Should().BeEmpty(); }
public void GetSuppressedIssues_WhenProjectHasNoModulesAndIssueIsOnAFileWhoseRelativePathExistsMultipleTimes_FalseMatch() { // Arrange var sonarQubeIssue1 = new SonarQubeIssue("aaa/foo.cs", null, null, "message", "sqkey", "S1", true); var sonarQubeIssue2 = new SonarQubeIssue("toto/foo.cs", null, null, "message", "sqkey", "S2", true); SetupSolutionBinding(true, new List <SonarQubeIssue> { sonarQubeIssue1, sonarQubeIssue2 }, new List <SonarQubeModule> { new SonarQubeModule("sqkey", "", "") }); var issuesProvider = new SonarQubeIssuesProvider(mockSqService.Object, "sqkey", mockTimerFactory.Object, testLogger); WaitForInitialFetchTaskToStart(); VerifyServiceGetIssues(Times.Exactly(1)); // issues should be fetched on creation // Act / Assert // #1 - matches the correct file... var matches = issuesProvider.GetSuppressedIssues("", "C:\\aaa\\foo.cs"); matches.Should().HaveCount(1); matches.First().RuleId.Should().Be("S1"); // #2 - ...but also matches a wrong file deeper in the hierarchy with the same suffix... matches = issuesProvider.GetSuppressedIssues("", "C:\\bar\\bar2\\aaa\\foo.cs"); matches.Should().HaveCount(1); matches.First().RuleId.Should().Be("S1"); // #3 - ... but no match for files upper in the hierarchy. matches = issuesProvider.GetSuppressedIssues("", "C:\\foo.cs"); matches.Should().BeEmpty(); }
public void GetSuppressedIssues_WhenProjectHasModulesAndIssueIsModuleLevelFound_ReturnsExpectedIssue() { // Arrange var sonarQubeIssue1 = CreateIssue(filePath: null, "sqkey:sqkey:projectId2", "S1"); var sonarQubeIssue2 = CreateIssue(filePath: null, "sqkey:sqkey:projectId", "S2"); var sonarQubeIssue3 = CreateIssue("foo\\bar.cs", "sqkey:sqkey:projectId", "S3"); SetupSolutionBinding(true, new List <SonarQubeIssue> { sonarQubeIssue1, sonarQubeIssue2, sonarQubeIssue3 }); var issuesProvider = new SonarQubeIssuesProvider(mockSqService.Object, "sqkey", mockTimerFactory.Object, testLogger); WaitForInitialFetchTaskToStart(); VerifyServiceGetIssues(Times.Exactly(1)); // issues should be fetched on creation // Act var matches = issuesProvider.GetSuppressedIssues("projectId", null); // Assert matches.Should().HaveCount(1); matches.First().RuleId.Should().Be("S2"); }
public void Constructor_TimerIsInitialized() { // Arrange SetupSolutionBinding(isBound: true, isConnected: false, projectKey: null, issues: null); mockTimer.SetupSet(t => t.AutoReset = true).Verifiable(); // 1. Construction -> timer initialised var issuesProvider = new SonarQubeIssuesProvider(mockSqService.Object, mockTracker.Object, mockTimerFactory.Object); // Assert mockTimerFactory.VerifyAll(); mockTimer.VerifySet(t => t.AutoReset = true, Times.Once); VerifyTimerStart(Times.Once()); timerRunning.Should().Be(true); VerifyServiceIsConnected(Times.Once()); // bound -> check connection status... VerifyServiceGetIssues(Times.Never()); // ... but don't try to synch since not connected // 2. Timer event raised -> check attempt is made to synchronize data RaiseTimerElapsed(DateTime.UtcNow); VerifyServiceIsConnected(Times.Exactly(2)); VerifyServiceGetIssues(Times.Never()); // still not connected so can't get data timerRunning.Should().Be(true); }