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());
        }
Example #10
0
        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();
        }
Example #13
0
        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");
        }
Example #14
0
        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");
        }
Example #15
0
        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();
        }
Example #16
0
        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);
        }