github.com/ouraigua/jenkins-library@v0.0.0-20231028010029-fbeaf2f3aa9b/cmd/sonarExecuteScan_test.go (about) 1 //go:build unit 2 // +build unit 3 4 package cmd 5 6 import ( 7 "fmt" 8 "net/http" 9 "os" 10 "os/exec" 11 "path/filepath" 12 "testing" 13 14 "github.com/bmatcuk/doublestar" 15 "github.com/jarcoal/httpmock" 16 "github.com/pkg/errors" 17 "github.com/stretchr/testify/assert" 18 "github.com/stretchr/testify/require" 19 20 piperHttp "github.com/SAP/jenkins-library/pkg/http" 21 "github.com/SAP/jenkins-library/pkg/mock" 22 "github.com/SAP/jenkins-library/pkg/piperutils" 23 SonarUtils "github.com/SAP/jenkins-library/pkg/sonar" 24 ) 25 26 // TODO: extract to mock package 27 type mockDownloader struct { 28 shouldFail bool 29 requestedURL []string 30 requestedFile []string 31 } 32 33 func (m *mockDownloader) DownloadFile(url, filename string, header http.Header, cookies []*http.Cookie) error { 34 m.requestedURL = append(m.requestedURL, url) 35 m.requestedFile = append(m.requestedFile, filename) 36 if m.shouldFail { 37 return errors.New("something happened") 38 } 39 return nil 40 } 41 42 func (m *mockDownloader) SetOptions(options piperHttp.ClientOptions) {} 43 44 func mockFileUtilsExists(exists bool) func(string) (bool, error) { 45 return func(filename string) (bool, error) { 46 if exists { 47 return true, nil 48 } 49 return false, errors.New("something happened") 50 } 51 } 52 53 func mockExecLookPath(executable string) (string, error) { 54 if executable == "local-sonar-scanner" { 55 return "/usr/bin/sonar-scanner", nil 56 } 57 return "", errors.New("something happened") 58 } 59 60 func mockFileUtilsUnzip(t *testing.T, expectSrc string) func(string, string) ([]string, error) { 61 return func(src, dest string) ([]string, error) { 62 assert.Equal(t, filepath.Join(dest, expectSrc), src) 63 return []string{}, nil 64 } 65 } 66 67 func mockOsRename(t *testing.T, expectOld, expectNew string) func(string, string) error { 68 return func(old, new string) error { 69 assert.Regexp(t, expectOld, old) 70 assert.Equal(t, expectNew, new) 71 return nil 72 } 73 } 74 75 func mockOsStat(exists map[string]bool) func(name string) (os.FileInfo, error) { 76 return func(name string) (os.FileInfo, error) { 77 _, exists := exists[name] 78 if exists { 79 // Exploits the fact that FileInfo result from os.Stat() is ignored anyway 80 return nil, nil 81 } 82 return nil, errors.New("something happened") 83 } 84 } 85 86 func mockGlob(matchesForPatterns map[string][]string) func(pattern string) ([]string, error) { 87 return func(pattern string) ([]string, error) { 88 matches, exists := matchesForPatterns[pattern] 89 if exists { 90 return matches, nil 91 } 92 return nil, errors.New("something happened") 93 } 94 } 95 96 func createTaskReportFile(t *testing.T, workingDir string) { 97 require.NoError(t, os.MkdirAll(filepath.Join(workingDir, ".scannerwork"), 0o755)) 98 require.NoError(t, os.WriteFile(filepath.Join(workingDir, ".scannerwork", "report-task.txt"), []byte(taskReportContent), 0o755)) 99 require.FileExists(t, filepath.Join(workingDir, ".scannerwork", "report-task.txt")) 100 } 101 102 const sonarServerURL = "https://sonarcloud.io" 103 104 const taskReportContent = ` 105 projectKey=piper-test 106 serverUrl=` + sonarServerURL + ` 107 serverVersion=8.0.0.12345 108 dashboardUrl=` + sonarServerURL + `/dashboard/index/piper-test 109 ceTaskId=AXERR2JBbm9IiM5TEST 110 ceTaskUrl=` + sonarServerURL + `/api/ce/task?id=AXERR2JBbm9IiMTEST 111 ` 112 113 const measuresComponentResponse = ` 114 { 115 "component": { 116 "key": "com.sap.piper.test", 117 "name": "com.sap.piper.test", 118 "qualifier": "TRK", 119 "measures": [ 120 { 121 "metric": "line_coverage", 122 "value": "80.4", 123 "bestValue": false 124 }, 125 { 126 "metric": "branch_coverage", 127 "value": "81.0", 128 "bestValue": false 129 }, 130 { 131 "metric": "coverage", 132 "value": "80.7", 133 "bestValue": false 134 }, 135 { 136 "metric": "extra_valie", 137 "value": "42.7", 138 "bestValue": false 139 } 140 ] 141 } 142 } 143 ` 144 145 func TestRunSonar(t *testing.T) { 146 mockRunner := mock.ExecMockRunner{} 147 mockDownloadClient := mockDownloader{shouldFail: false} 148 apiClient := &piperHttp.Client{} 149 apiClient.SetOptions(piperHttp.ClientOptions{MaxRetries: -1, UseDefaultTransport: true}) 150 // mock SonarQube API calls 151 httpmock.Activate() 152 defer httpmock.DeactivateAndReset() 153 // add response handler 154 httpmock.RegisterResponder(http.MethodGet, sonarServerURL+"/api/"+SonarUtils.EndpointCeTask+"", httpmock.NewStringResponder(http.StatusOK, `{ "task": { "componentId": "AXERR2JBbm9IiM5TEST", "status": "SUCCESS" }}`)) 155 httpmock.RegisterResponder(http.MethodGet, sonarServerURL+"/api/"+SonarUtils.EndpointIssuesSearch+"", httpmock.NewStringResponder(http.StatusOK, `{ "total": 0 }`)) 156 httpmock.RegisterResponder(http.MethodGet, sonarServerURL+"/api/"+SonarUtils.EndpointMeasuresComponent+"", httpmock.NewStringResponder(http.StatusOK, measuresComponentResponse)) 157 158 t.Run("default", func(t *testing.T) { 159 // init 160 tmpFolder := t.TempDir() 161 createTaskReportFile(t, tmpFolder) 162 163 sonar = sonarSettings{ 164 workingDir: tmpFolder, 165 binary: "sonar-scanner", 166 environment: []string{}, 167 options: []string{}, 168 } 169 options := sonarExecuteScanOptions{ 170 CustomTLSCertificateLinks: []string{}, 171 Token: "secret-ABC", 172 ServerURL: sonarServerURL, 173 Organization: "SAP", 174 Version: "1.2.3", 175 VersioningModel: "major", 176 PullRequestProvider: "GitHub", 177 } 178 fileUtilsExists = mockFileUtilsExists(true) 179 // test 180 err := runSonar(options, &mockDownloadClient, &mockRunner, apiClient, &mock.FilesMock{}, &sonarExecuteScanInflux{}) 181 // assert 182 assert.NoError(t, err) 183 assert.Contains(t, sonar.options, "-Dsonar.projectVersion=1") 184 assert.Contains(t, sonar.options, "-Dsonar.organization=SAP") 185 assert.Contains(t, sonar.environment, "SONAR_HOST_URL="+sonarServerURL) 186 assert.Contains(t, sonar.environment, "SONAR_TOKEN=secret-ABC") 187 assert.Contains(t, sonar.environment, "SONAR_SCANNER_OPTS=-Djavax.net.ssl.trustStore="+filepath.Join(getWorkingDir(), ".certificates", "cacerts")+" -Djavax.net.ssl.trustStorePassword=changeit") 188 }) 189 t.Run("with custom options", func(t *testing.T) { 190 // init 191 tmpFolder := t.TempDir() 192 createTaskReportFile(t, tmpFolder) 193 194 sonar = sonarSettings{ 195 workingDir: tmpFolder, 196 binary: "sonar-scanner", 197 environment: []string{}, 198 options: []string{}, 199 } 200 options := sonarExecuteScanOptions{ 201 Options: []string{"-Dsonar.projectKey=piper"}, 202 PullRequestProvider: "GitHub", 203 } 204 fileUtilsExists = mockFileUtilsExists(true) 205 defer func() { 206 fileUtilsExists = piperutils.FileExists 207 }() 208 // test 209 err := runSonar(options, &mockDownloadClient, &mockRunner, apiClient, &mock.FilesMock{}, &sonarExecuteScanInflux{}) 210 // assert 211 assert.NoError(t, err) 212 assert.Contains(t, sonar.options, "-Dsonar.projectKey=piper") 213 }) 214 t.Run("with binaries option", func(t *testing.T) { 215 // init 216 tmpFolder := t.TempDir() 217 createTaskReportFile(t, tmpFolder) 218 219 sonar = sonarSettings{ 220 workingDir: tmpFolder, 221 binary: "sonar-scanner", 222 environment: []string{}, 223 options: []string{}, 224 } 225 fileUtilsExists = mockFileUtilsExists(true) 226 227 globMatches := make(map[string][]string) 228 globMatches[pomXMLPattern] = []string{"pom.xml", "application/pom.xml"} 229 doublestarGlob = mockGlob(globMatches) 230 231 existsMap := make(map[string]bool) 232 existsMap[filepath.Join("target", "classes")] = true 233 existsMap[filepath.Join("target", "test-classes")] = true 234 existsMap[filepath.Join("application", "target", "classes")] = true 235 osStat = mockOsStat(existsMap) 236 237 defer func() { 238 fileUtilsExists = piperutils.FileExists 239 doublestarGlob = doublestar.Glob 240 osStat = os.Stat 241 }() 242 options := sonarExecuteScanOptions{ 243 InferJavaBinaries: true, 244 PullRequestProvider: "GitHub", 245 } 246 // test 247 err := runSonar(options, &mockDownloadClient, &mockRunner, apiClient, &mock.FilesMock{}, &sonarExecuteScanInflux{}) 248 // assert 249 assert.NoError(t, err) 250 assert.Contains(t, sonar.options, fmt.Sprintf("-Dsonar.java.binaries=%s,%s,%s", 251 filepath.Join("target", "classes"), 252 filepath.Join("target", "test-classes"), 253 filepath.Join("application", "target", "classes"))) 254 }) 255 t.Run("with binaries option already given", func(t *testing.T) { 256 // init 257 tmpFolder := t.TempDir() 258 createTaskReportFile(t, tmpFolder) 259 260 sonar = sonarSettings{ 261 workingDir: tmpFolder, 262 binary: "sonar-scanner", 263 environment: []string{}, 264 options: []string{}, 265 } 266 fileUtilsExists = mockFileUtilsExists(true) 267 268 globMatches := make(map[string][]string) 269 globMatches[pomXMLPattern] = []string{"pom.xml"} 270 doublestarGlob = mockGlob(globMatches) 271 272 existsMap := make(map[string]bool) 273 existsMap[filepath.Join("target", "classes")] = true 274 osStat = mockOsStat(existsMap) 275 276 defer func() { 277 fileUtilsExists = piperutils.FileExists 278 doublestarGlob = doublestar.Glob 279 osStat = os.Stat 280 }() 281 options := sonarExecuteScanOptions{ 282 Options: []string{"-Dsonar.java.binaries=user/provided"}, 283 InferJavaBinaries: true, 284 PullRequestProvider: "GitHub", 285 } 286 // test 287 err := runSonar(options, &mockDownloadClient, &mockRunner, apiClient, &mock.FilesMock{}, &sonarExecuteScanInflux{}) 288 // assert 289 assert.NoError(t, err) 290 assert.NotContains(t, sonar.options, fmt.Sprintf("-Dsonar.java.binaries=%s", 291 filepath.Join("target", "classes"))) 292 assert.Contains(t, sonar.options, "-Dsonar.java.binaries=user/provided") 293 }) 294 t.Run("projectKey, coverageExclusions, m2Path, verbose", func(t *testing.T) { 295 // init 296 tmpFolder := t.TempDir() 297 createTaskReportFile(t, tmpFolder) 298 299 sonar = sonarSettings{ 300 workingDir: tmpFolder, 301 binary: "sonar-scanner", 302 environment: []string{}, 303 options: []string{}, 304 } 305 options := sonarExecuteScanOptions{ 306 ProjectKey: "mock-project-key", 307 M2Path: "my/custom/m2", // assumed to be resolved via alias from mavenExecute 308 InferJavaLibraries: true, 309 CoverageExclusions: []string{"one", "**/two", "three**"}, 310 PullRequestProvider: "GitHub", 311 } 312 GeneralConfig.Verbose = true 313 defer func() { GeneralConfig.Verbose = false }() 314 fileUtilsExists = mockFileUtilsExists(true) 315 defer func() { 316 fileUtilsExists = piperutils.FileExists 317 }() 318 // test 319 err := runSonar(options, &mockDownloadClient, &mockRunner, apiClient, &mock.FilesMock{}, &sonarExecuteScanInflux{}) 320 // assert 321 assert.NoError(t, err) 322 assert.Contains(t, sonar.options, "-Dsonar.projectKey=mock-project-key") 323 assert.Contains(t, sonar.options, fmt.Sprintf("-Dsonar.java.libraries=%s", 324 filepath.Join("my/custom/m2", "**"))) 325 assert.Contains(t, sonar.options, "-Dsonar.coverage.exclusions=one,**/two,three**") 326 assert.Contains(t, sonar.options, "-Dsonar.verbose=true") 327 }) 328 } 329 330 func TestSonarHandlePullRequest(t *testing.T) { 331 t.Run("default", func(t *testing.T) { 332 // init 333 sonar = sonarSettings{ 334 binary: "sonar-scanner", 335 environment: []string{}, 336 options: []string{}, 337 } 338 options := sonarExecuteScanOptions{ 339 ChangeID: "123", 340 PullRequestProvider: "GitHub", 341 ChangeBranch: "feat/bogus", 342 ChangeTarget: "master", 343 Owner: "SAP", 344 Repository: "jenkins-library", 345 } 346 // test 347 err := handlePullRequest(options) 348 // assert 349 assert.NoError(t, err) 350 assert.Contains(t, sonar.options, "sonar.pullrequest.key=123") 351 assert.Contains(t, sonar.options, "sonar.pullrequest.provider=github") 352 assert.Contains(t, sonar.options, "sonar.pullrequest.base=master") 353 assert.Contains(t, sonar.options, "sonar.pullrequest.branch=feat/bogus") 354 assert.Contains(t, sonar.options, "sonar.pullrequest.github.repository=SAP/jenkins-library") 355 }) 356 t.Run("unsupported scm provider", func(t *testing.T) { 357 // init 358 sonar = sonarSettings{ 359 binary: "sonar-scanner", 360 environment: []string{}, 361 options: []string{}, 362 } 363 options := sonarExecuteScanOptions{ 364 ChangeID: "123", 365 PullRequestProvider: "Gerrit", 366 } 367 // test 368 err := handlePullRequest(options) 369 // assert 370 assert.Error(t, err) 371 assert.Equal(t, "Pull-Request provider 'gerrit' is not supported!", err.Error()) 372 }) 373 t.Run("legacy", func(t *testing.T) { 374 // init 375 sonar = sonarSettings{ 376 binary: "sonar-scanner", 377 environment: []string{}, 378 options: []string{}, 379 } 380 options := sonarExecuteScanOptions{ 381 LegacyPRHandling: true, 382 ChangeID: "123", 383 Owner: "SAP", 384 Repository: "jenkins-library", 385 GithubToken: "some-token", 386 DisableInlineComments: true, 387 } 388 // test 389 err := handlePullRequest(options) 390 // assert 391 assert.NoError(t, err) 392 assert.Contains(t, sonar.options, "sonar.analysis.mode=preview") 393 assert.Contains(t, sonar.options, "sonar.github.pullRequest=123") 394 assert.Contains(t, sonar.options, "sonar.github.oauth=some-token") 395 assert.Contains(t, sonar.options, "sonar.github.repository=SAP/jenkins-library") 396 assert.Contains(t, sonar.options, "sonar.github.disableInlineComments=true") 397 }) 398 } 399 400 func TestSonarLoadScanner(t *testing.T) { 401 mockClient := mockDownloader{shouldFail: false} 402 403 t.Run("use preinstalled sonar-scanner", func(t *testing.T) { 404 // init 405 ignore := "" 406 sonar = sonarSettings{ 407 binary: "local-sonar-scanner", 408 environment: []string{}, 409 options: []string{}, 410 } 411 execLookPath = mockExecLookPath 412 defer func() { execLookPath = exec.LookPath }() 413 // test 414 err := loadSonarScanner(ignore, &mockClient) 415 // assert 416 assert.NoError(t, err) 417 assert.Equal(t, "local-sonar-scanner", sonar.binary) 418 }) 419 420 t.Run("use downloaded sonar-scanner", func(t *testing.T) { 421 // init 422 url := "https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-4.6.2.2472-linux.zip" 423 sonar = sonarSettings{ 424 binary: "sonar-scanner", 425 environment: []string{}, 426 options: []string{}, 427 } 428 execLookPath = mockExecLookPath 429 fileUtilsUnzip = mockFileUtilsUnzip(t, "sonar-scanner-cli-4.6.2.2472-linux.zip") 430 osRename = mockOsRename(t, "sonar-scanner-4.6.2.2472-linux", ".sonar-scanner") 431 defer func() { 432 execLookPath = exec.LookPath 433 fileUtilsUnzip = piperutils.Unzip 434 osRename = os.Rename 435 }() 436 // test 437 err := loadSonarScanner(url, &mockClient) 438 // assert 439 assert.NoError(t, err) 440 assert.Equal(t, url, mockClient.requestedURL[0]) 441 assert.Regexp(t, "sonar-scanner-cli-4.6.2.2472-linux.zip$", mockClient.requestedFile[0]) 442 assert.Equal(t, filepath.Join(getWorkingDir(), ".sonar-scanner", "bin", "sonar-scanner"), sonar.binary) 443 }) 444 } 445 446 func TestSonarLoadCertificates(t *testing.T) { 447 mockRunner := mock.ExecMockRunner{} 448 mockClient := mockDownloader{shouldFail: false} 449 450 t.Run("use local trust store", func(t *testing.T) { 451 // init 452 sonar = sonarSettings{ 453 binary: "sonar-scanner", 454 environment: []string{}, 455 options: []string{}, 456 } 457 fileUtilsExists = mockFileUtilsExists(true) 458 defer func() { fileUtilsExists = piperutils.FileExists }() 459 // test 460 err := loadCertificates([]string{}, &mockClient, &mockRunner) 461 // assert 462 assert.NoError(t, err) 463 assert.Contains(t, sonar.environment, "SONAR_SCANNER_OPTS=-Djavax.net.ssl.trustStore="+filepath.Join(getWorkingDir(), ".certificates", "cacerts")+" -Djavax.net.ssl.trustStorePassword=changeit") 464 }) 465 466 t.Run("use local trust store with downloaded certificates", func(t *testing.T) { 467 // init 468 sonar = sonarSettings{ 469 binary: "sonar-scanner", 470 environment: []string{}, 471 options: []string{}, 472 } 473 fileUtilsExists = mockFileUtilsExists(false) 474 // test 475 err := loadCertificates([]string{"https://sap.com/custom-1.crt", "https://sap.com/custom-2.crt"}, &mockClient, &mockRunner) 476 // assert 477 assert.NoError(t, err) 478 assert.Equal(t, "https://sap.com/custom-1.crt", mockClient.requestedURL[0]) 479 assert.Equal(t, "https://sap.com/custom-2.crt", mockClient.requestedURL[1]) 480 assert.Regexp(t, "custom-1.crt$", mockClient.requestedFile[0]) 481 assert.Regexp(t, "custom-2.crt$", mockClient.requestedFile[1]) 482 assert.Contains(t, sonar.environment, "SONAR_SCANNER_OPTS=-Djavax.net.ssl.trustStore="+filepath.Join(getWorkingDir(), ".certificates", "cacerts")+" -Djavax.net.ssl.trustStorePassword=changeit") 483 }) 484 485 t.Run("use no trust store", func(t *testing.T) { 486 // init 487 sonar = sonarSettings{ 488 binary: "sonar-scanner", 489 environment: []string{}, 490 options: []string{}, 491 } 492 fileUtilsExists = mockFileUtilsExists(false) 493 // test 494 err := loadCertificates([]string{}, &mockClient, &mockRunner) 495 // assert 496 assert.NoError(t, err) 497 assert.Empty(t, sonar.environment) 498 }) 499 }