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