github.com/grafana/pyroscope@v1.18.0/pkg/frontend/vcs/source/golang/modules_test.go (about) 1 package golang 2 3 import ( 4 "context" 5 "io" 6 "net/http" 7 "strings" 8 "testing" 9 10 "github.com/stretchr/testify/require" 11 "golang.org/x/mod/modfile" 12 "golang.org/x/mod/module" 13 ) 14 15 var ( 16 modf *modfile.File 17 mainModule = module.Version{ 18 Path: "github.com/grafana/pyroscope", 19 Version: "v0.5.0", 20 } 21 ) 22 23 func init() { 24 var err error 25 modf, err = modfile.Parse( 26 "go.mod", 27 []byte(` 28 module github.com/grafana/pyroscope 29 30 go 1.16 31 32 require ( 33 github.com/grafana/grafana-plugin-sdk-go v0.3.0 34 connectrpc.com/connect v1.14.0 35 connectrpc.com/grpchealth v1.3.0 36 ) 37 38 require ( 39 github.com/!azure/go-autorest v0.11.16-0.20210324193631-8d5b6a9c4f9e // indirect 40 golang.org/x/crypto v0.0.0-20210322153248-8c942d7d6b5c // indirect 41 golang.org/x/term v0.15.0 // indirect 42 golang.org/x/tools v0.15.0 // indirect 43 google.golang.org/api v0.152.0 // indirect 44 k8s.io/utils v0.0.0-20230711102312-30195339c3c7 // indirect 45 ) 46 47 replace ( 48 github.com/thanos-io/objstore => github.com/grafana/objstore v0.0.0-20231121154247-84f91ea90e72 49 gopkg.in/yaml.v3 => github.com/colega/go-yaml-yaml v0.0.0-20220720105220-255a8d16d094 50 github.com/grafana/pyroscope/api => ./api 51 ) 52 `), nil) 53 if err != nil { 54 panic(err) 55 } 56 } 57 58 func TestParseModulePath(t *testing.T) { 59 for _, tt := range []struct { 60 input string 61 expectedOk bool 62 expected Module 63 }{ 64 { 65 "github.com/armon/go-metrics@v0.4.1/metrics.go", 66 true, 67 Module{ 68 Version: module.Version{ 69 Path: "github.com/armon/go-metrics", 70 Version: "v0.4.1", 71 }, 72 FilePath: "metrics.go", 73 }, 74 }, 75 { 76 "/go/pkg/mod/github.com/golang/protobuf@v1.5.3/proto/wire.go", 77 true, 78 Module{ 79 Version: module.Version{ 80 Path: "github.com/golang/protobuf", 81 Version: "v1.5.3", 82 }, 83 FilePath: "proto/wire.go", 84 }, 85 }, 86 { 87 "/go/pkg/mod/golang.org/x/net@v1.5.3/http2/hpack/tables.go", 88 true, 89 Module{ 90 Version: module.Version{ 91 Path: "golang.org/x/net", 92 Version: "v1.5.3", 93 }, 94 FilePath: "http2/hpack/tables.go", 95 }, 96 }, 97 { 98 "/go/pkg/mod/golang.org/x/net/http2/hpack/tables.go", 99 false, 100 Module{}, 101 }, 102 { 103 "/go/pkg/mod/golang.org/x/net@v1.0tables.go", 104 false, 105 Module{}, 106 }, 107 { 108 "/Users/pyroscope/.golang/packages/pkg/mod/golang.org/toolchain@v0.0.1-go1.24.6.darwin-arm64/src/net/http/server.go", 109 true, 110 Module{ 111 Version: module.Version{ 112 Path: "golang.org/toolchain", 113 Version: "v0.0.1-go1.24.6.darwin-arm64", 114 }, 115 FilePath: "src/net/http/server.go", 116 }, 117 }, 118 { 119 "/Users/pyroscope/.golang/packages/pkg/mod/github.com/opentracing-contrib/go-stdlib@v1.0.0/nethttp/server.go", 120 true, 121 Module{ 122 Version: module.Version{ 123 Path: "github.com/opentracing-contrib/go-stdlib", 124 Version: "v1.0.0", 125 }, 126 FilePath: "nethttp/server.go", 127 }, 128 }, 129 } { 130 t.Run(tt.input, func(t *testing.T) { 131 mod, ok := ParseModuleFromPath(tt.input) 132 require.Equal(t, tt.expectedOk, ok) 133 require.Equal(t, tt.expected, mod) 134 }) 135 } 136 } 137 138 func Test_ApplyGoModule(t *testing.T) { 139 for _, tt := range []struct { 140 in Module 141 expected Module 142 }{ 143 { 144 Module{ 145 Version: module.Version{ 146 Path: "github.com/grafana/grafana-plugin-sdk-go", 147 Version: "v0.4.0", 148 }, 149 FilePath: "plugin.go", 150 }, 151 Module{ 152 Version: module.Version{ 153 Path: "github.com/grafana/grafana-plugin-sdk-go", 154 Version: "v0.3.0", 155 }, 156 FilePath: "plugin.go", 157 }, 158 }, 159 { 160 Module{ 161 Version: module.Version{ 162 Path: "github.com/thanos-io/objstore", 163 Version: "v0.4.0", 164 }, 165 FilePath: "client.go", 166 }, 167 Module{ 168 Version: module.Version{ 169 Path: "github.com/grafana/objstore", 170 Version: "v0.0.0-20231121154247-84f91ea90e72", 171 }, 172 FilePath: "client.go", 173 }, 174 }, 175 { 176 Module{ 177 Version: module.Version{ 178 Path: "github.com/grafana/pyroscope/api", 179 Version: "v0.4.0", 180 }, 181 FilePath: "foo_gen.go", 182 }, 183 Module{ 184 Version: module.Version{ 185 Path: "github.com/grafana/pyroscope/api", 186 Version: "v0.5.0", 187 }, 188 FilePath: "foo_gen.go", 189 }, 190 }, 191 } { 192 t.Run(tt.in.String(), func(t *testing.T) { 193 tt.in.applyGoMod(mainModule, modf) 194 require.Equal(t, tt.expected, tt.in) 195 }) 196 } 197 } 198 199 type ClientMock struct{} 200 201 func (c *ClientMock) Do(req *http.Request) (*http.Response, error) { 202 switch req.URL.String() { 203 case "https://golang.org/x/oauth2?go-get=1": 204 return &http.Response{ 205 StatusCode: 200, 206 Body: io.NopCloser( 207 strings.NewReader(` 208 <html> 209 <head> 210 <meta name="go-import" content="golang.org/x/oauth2 git https://go.googlesource.com/oauth2"> 211 </head> 212 </html>`, 213 ), 214 ), 215 }, nil 216 case "https://k8s.io/utils?go-get=1": 217 return &http.Response{ 218 StatusCode: 200, 219 Body: io.NopCloser( 220 strings.NewReader(` 221 <html><head> 222 <meta name="go-import" 223 content="k8s.io/utils 224 git https://github.com/kubernetes/utils"> 225 <meta name="go-source" 226 content="k8s.io/utils 227 https://github.com/kubernetes/utils 228 https://github.com/kubernetes/utils/tree/master{/dir} 229 https://github.com/kubernetes/utils/blob/master{/dir}/{file}#L{line}"> 230 </head></html>`, 231 ), 232 ), 233 }, nil 234 case "https://cloud.google.com/go/storage?go-get=1": 235 return &http.Response{ 236 StatusCode: 200, 237 Body: io.NopCloser( 238 strings.NewReader(` 239 <!DOCTYPE html> 240 <html> 241 <head> 242 243 <meta name="go-import" content="cloud.google.com/go git https://github.com/googleapis/google-cloud-go"> 244 <meta name="go-source" content="cloud.google.com/go https://github.com/googleapis/google-cloud-go https://github.com/GoogleCloudPlatform/gcloud-golang/tree/master{/dir} https://github.com/GoogleCloudPlatform/gcloud-golang/tree/master{/dir}/{file}#L{line}"> 245 <meta name="robots" content="noindex"> 246 <meta http-equiv="refresh" content="0; url=https://pkg.go.dev/cloud.google.com/go/storage"> 247 </head> 248 <body> 249 Redirecting to <a href="https://pkg.go.dev/cloud.google.com/go/storage">pkg.go.dev/cloud.google.com/go/storage</a>. 250 </body> 251 </html>`, 252 ), 253 ), 254 }, nil 255 case "https://google.golang.org/protobuf?go-get=1": 256 return &http.Response{ 257 StatusCode: 200, 258 Body: io.NopCloser( 259 strings.NewReader(` 260 <!DOCTYPE html> 261 <html> 262 <head> 263 <meta name="go-import" content="google.golang.org/protobuf git https://go.googlesource.com/protobuf"> 264 265 <meta name="go-source" content="google.golang.org/protobuf https://github.com/protocolbuffers/protobuf-go https://github.com/protocolbuffers/protobuf-go/tree/master{/dir} https://github.com/protocolbuffers/protobuf-go/tree/master{/dir}/{file}#L{line}"> 266 267 <meta http-equiv="refresh" content="0; url=https://pkg.go.dev/google.golang.org/protobuf"> 268 </head> 269 <body> 270 <a href="https://pkg.go.dev/google.golang.org/protobuf">Redirecting to documentation...</a> 271 </body> 272 </html>`, 273 ), 274 ), 275 }, nil 276 } 277 return &http.Response{ 278 StatusCode: 404, 279 Body: http.NoBody, 280 }, nil 281 } 282 283 func Test_Resolve(t *testing.T) { 284 for _, tt := range []struct { 285 in Module 286 main module.Version 287 modfile *modfile.File 288 289 expected Module 290 expectedErr bool 291 }{ 292 { 293 expectedErr: true, 294 }, 295 { 296 in: Module{ 297 Version: module.Version{ 298 Path: "gopkg.in/yaml.v3", 299 }, 300 }, 301 main: mainModule, 302 modfile: modf, 303 expected: Module{ 304 Version: module.Version{ 305 Path: "github.com/colega/go-yaml-yaml", 306 Version: "v0.0.0-20220720105220-255a8d16d094", 307 }, 308 }, 309 }, 310 { 311 in: Module{ 312 Version: module.Version{ 313 Path: "gopkg.in/yaml.v3", 314 }, 315 }, 316 main: mainModule, 317 expected: Module{ 318 Version: module.Version{ 319 Path: "github.com/go-yaml/yaml", 320 }, 321 }, 322 }, 323 { 324 in: Module{ 325 Version: module.Version{ 326 Path: "github.com/go-yaml/yaml/v56", 327 }, 328 }, 329 main: mainModule, 330 expected: Module{ 331 Version: module.Version{ 332 Path: "github.com/go-yaml/yaml", 333 }, 334 }, 335 }, 336 { 337 in: Module{ 338 Version: module.Version{ 339 Path: "gopkg.in/alecthomas/kingpin.v2", 340 }, 341 }, 342 main: mainModule, 343 modfile: modf, 344 expected: Module{ 345 Version: module.Version{ 346 Path: "github.com/alecthomas/kingpin", 347 }, 348 }, 349 }, 350 { 351 in: Module{ 352 Version: module.Version{ 353 Path: "gopkg.in/kingpin.v2", 354 }, 355 }, 356 expected: Module{ 357 Version: module.Version{ 358 Path: "github.com/go-kingpin/kingpin", 359 }, 360 }, 361 }, 362 { 363 in: Module{ 364 Version: module.Version{ 365 Path: "golang.org/x/oauth2", 366 }, 367 }, 368 main: mainModule, 369 modfile: modf, 370 expected: Module{ 371 Version: module.Version{ 372 Path: "go.googlesource.com/oauth2", 373 }, 374 }, 375 }, 376 { 377 in: Module{ 378 Version: module.Version{ 379 Path: "k8s.io/utils", 380 Version: "v0.0.0-20231121154247-84f91ea90e72", 381 }, 382 FilePath: "client.go", 383 }, 384 main: mainModule, 385 modfile: modf, 386 expected: Module{ 387 Version: module.Version{ 388 Path: "github.com/kubernetes/utils", 389 Version: "v0.0.0-20230711102312-30195339c3c7", 390 }, 391 FilePath: "client.go", 392 }, 393 }, 394 { 395 in: Module{ 396 Version: module.Version{ 397 Path: "cloud.google.com/go/storage", 398 Version: "v0.0.0-20231121154247-84f91ea90e72", 399 }, 400 FilePath: "client.go", 401 }, 402 main: mainModule, 403 modfile: modf, 404 expected: Module{ 405 Version: module.Version{ 406 Path: "github.com/googleapis/google-cloud-go/storage", 407 Version: "v0.0.0-20231121154247-84f91ea90e72", 408 }, 409 FilePath: "client.go", 410 }, 411 }, 412 { 413 in: Module{ 414 Version: module.Version{ 415 Path: "google.golang.org/protobuf", 416 }, 417 }, 418 main: mainModule, 419 modfile: modf, 420 expected: Module{ 421 Version: module.Version{ 422 Path: "github.com/protocolbuffers/protobuf-go", 423 }, 424 }, 425 }, 426 } { 427 t.Run(tt.in.String(), func(t *testing.T) { 428 err := tt.in.Resolve(context.Background(), tt.main, tt.modfile, &ClientMock{}) 429 if tt.expectedErr { 430 require.Error(t, err) 431 return 432 } 433 require.NoError(t, err) 434 require.Equal(t, tt.expected, tt.in) 435 }) 436 } 437 } 438 439 func Test_ParseGithubRepo(t *testing.T) { 440 for _, tt := range []struct { 441 input Module 442 expected GitHubFile 443 expectedErr bool 444 }{ 445 { 446 Module{}, 447 GitHubFile{}, 448 true, 449 }, 450 { 451 Module{ 452 Version: module.Version{ 453 Path: "github.com/grafana/grafana-plugin-sdk-go", 454 }, 455 }, 456 GitHubFile{ 457 Owner: "grafana", 458 Repo: "grafana-plugin-sdk-go", 459 Ref: "main", 460 }, 461 false, 462 }, 463 { 464 Module{ 465 Version: module.Version{ 466 Path: "github.com/grafana/grafana-plugin-sdk-go", 467 Version: "v0.3.0", 468 }, 469 FilePath: "plugin.go", 470 }, 471 GitHubFile{ 472 Owner: "grafana", 473 Repo: "grafana-plugin-sdk-go", 474 Ref: "v0.3.0", 475 Path: "plugin.go", 476 }, 477 false, 478 }, 479 { 480 Module{ 481 Version: module.Version{ 482 Path: "github.com/grafana/grafana-plugin-sdk-go/bar", 483 Version: "v0.3.0-rc1", 484 }, 485 FilePath: "plugin.go", 486 }, 487 GitHubFile{ 488 Owner: "grafana", 489 Repo: "grafana-plugin-sdk-go", 490 Ref: "v0.3.0-rc1", 491 Path: "bar/plugin.go", 492 }, 493 false, 494 }, 495 { 496 Module{ 497 Version: module.Version{ 498 Path: "github.com/grafana/grafana-plugin-sdk-go/bar", 499 Version: "v0.14.1-0.20230710114240-c316eb95ae5b", 500 }, 501 FilePath: "plugin.go", 502 }, 503 GitHubFile{ 504 Owner: "grafana", 505 Repo: "grafana-plugin-sdk-go", 506 Ref: "c316eb95ae5b", 507 Path: "bar/plugin.go", 508 }, 509 false, 510 }, 511 } { 512 t.Run(tt.input.String(), func(t *testing.T) { 513 actual, err := tt.input.GithubFile() 514 if tt.expectedErr { 515 require.Error(t, err) 516 return 517 } 518 require.NoError(t, err) 519 require.Equal(t, tt.expected, actual) 520 }) 521 } 522 } 523 524 func Test_GoogleSourceURL(t *testing.T) { 525 for _, tt := range []struct { 526 in Module 527 expected string 528 expectedErr bool 529 }{ 530 { 531 Module{}, 532 "", 533 true, 534 }, 535 { 536 Module{ 537 Version: module.Version{ 538 Path: "go.googlesource.com/", 539 }, 540 }, 541 "", 542 true, 543 }, 544 { 545 Module{ 546 Version: module.Version{ 547 Path: "go.googlesource.com/oauth2", 548 }, 549 FilePath: "amazon/amazon.go", 550 }, 551 "https://go.googlesource.com/oauth2/+/master/amazon/amazon.go?format=TEXT", 552 false, 553 }, 554 { 555 Module{ 556 Version: module.Version{ 557 Path: "go.googlesource.com/oauth2/amazon", 558 }, 559 FilePath: "amazon.go", 560 }, 561 "https://go.googlesource.com/oauth2/+/master/amazon/amazon.go?format=TEXT", 562 false, 563 }, 564 { 565 Module{ 566 Version: module.Version{ 567 Path: "go.googlesource.com/oauth2/amazon", 568 Version: "v0.16.0", 569 }, 570 FilePath: "amazon.go", 571 }, 572 "https://go.googlesource.com/oauth2/+/v0.16.0/amazon/amazon.go?format=TEXT", 573 false, 574 }, 575 { 576 Module{ 577 Version: module.Version{ 578 Path: "go.googlesource.com/oauth2/amazon", 579 Version: "v0.16.0-0.20230710114240-c316eb95ae5b", 580 }, 581 FilePath: "amazon.go", 582 }, 583 "https://go.googlesource.com/oauth2/+/c316eb95ae5b/amazon/amazon.go?format=TEXT", 584 false, 585 }, 586 } { 587 t.Run(tt.in.String(), func(t *testing.T) { 588 actual, err := tt.in.GoogleSourceURL() 589 if tt.expectedErr { 590 require.Error(t, err) 591 return 592 } 593 require.NoError(t, err) 594 require.Equal(t, tt.expected, actual) 595 }) 596 } 597 } 598 599 func Test_refFromVersion(t *testing.T) { 600 for _, tt := range []struct { 601 input string 602 expected string 603 }{ 604 { 605 "v0.3.0", 606 "v0.3.0", 607 }, 608 { 609 "v0.3.0-rc1", 610 "v0.3.0-rc1", 611 }, 612 { 613 "v0.3.0-0.20230710114240-c316eb95ae5b", 614 "c316eb95ae5b", 615 }, 616 { 617 "v2.2.6+incompatible", 618 "v2.2.6", 619 }, 620 } { 621 t.Run(tt.input, func(t *testing.T) { 622 actual, err := refFromVersion(tt.input) 623 require.NoError(t, err) 624 require.Equal(t, tt.expected, actual) 625 }) 626 } 627 }