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  }