github.com/grafana/pyroscope@v1.18.0/pkg/frontend/vcs/config/config_test.go (about) 1 package config 2 3 import ( 4 "testing" 5 6 "github.com/stretchr/testify/assert" 7 "github.com/stretchr/testify/require" 8 ) 9 10 func TestParsePyroscopeConfig(t *testing.T) { 11 t.Run("valid go config", func(t *testing.T) { 12 yaml := `source_code: 13 mappings: 14 - language: go 15 path: 16 - prefix: GOROOT 17 source: 18 github: 19 owner: golang 20 repo: go 21 ref: go1.24.8 22 path: src 23 ` 24 config, err := ParsePyroscopeConfig([]byte(yaml)) 25 require.NoError(t, err) 26 require.NotNil(t, config) 27 28 require.Len(t, config.SourceCode.Mappings, 1) 29 30 mapping := config.SourceCode.Mappings[0] 31 assert.Equal(t, "go", mapping.Language) 32 require.Len(t, mapping.Path, 1) 33 assert.Equal(t, "GOROOT", mapping.Path[0].Prefix) 34 require.NotNil(t, mapping.Source.GitHub) 35 assert.Equal(t, "golang", mapping.Source.GitHub.Owner) 36 assert.Equal(t, "go", mapping.Source.GitHub.Repo) 37 assert.Equal(t, "go1.24.8", mapping.Source.GitHub.Ref) 38 assert.Equal(t, "src", mapping.Source.GitHub.Path) 39 }) 40 41 t.Run("valid java config with multiple mappings", func(t *testing.T) { 42 yaml := `source_code: 43 mappings: 44 - language: java 45 path: 46 - prefix: org/example/rideshare 47 source: 48 local: 49 path: src/main/java/org/example/rideshare 50 - language: java 51 path: 52 - prefix: java 53 source: 54 github: 55 owner: openjdk 56 repo: jdk 57 ref: jdk-17+0 58 path: src/java.base/share/classes/java 59 - language: java 60 path: 61 - prefix: org/springframework/http 62 source: 63 github: 64 owner: spring-projects 65 repo: spring-framework 66 ref: v5.3.20 67 path: spring-web/src/main/java/org/springframework/http 68 ` 69 config, err := ParsePyroscopeConfig([]byte(yaml)) 70 require.NoError(t, err) 71 require.NotNil(t, config) 72 73 require.Len(t, config.SourceCode.Mappings, 3) 74 75 // Check first mapping (local) 76 mapping1 := config.SourceCode.Mappings[0] 77 assert.Equal(t, "java", mapping1.Language) 78 require.Len(t, mapping1.Path, 1) 79 assert.Equal(t, "org/example/rideshare", mapping1.Path[0].Prefix) 80 require.NotNil(t, mapping1.Source.Local) 81 assert.Equal(t, "src/main/java/org/example/rideshare", mapping1.Source.Local.Path) 82 83 // Check second mapping (github) 84 mapping2 := config.SourceCode.Mappings[1] 85 assert.Equal(t, "java", mapping2.Language) 86 require.Len(t, mapping2.Path, 1) 87 assert.Equal(t, "java", mapping2.Path[0].Prefix) 88 require.NotNil(t, mapping2.Source.GitHub) 89 assert.Equal(t, "openjdk", mapping2.Source.GitHub.Owner) 90 91 // Check third mapping (github) 92 mapping3 := config.SourceCode.Mappings[2] 93 assert.Equal(t, "java", mapping3.Language) 94 require.Len(t, mapping3.Path, 1) 95 assert.Equal(t, "org/springframework/http", mapping3.Path[0].Prefix) 96 require.NotNil(t, mapping3.Source.GitHub) 97 assert.Equal(t, "spring-projects", mapping3.Source.GitHub.Owner) 98 }) 99 100 t.Run("invalid - missing language", func(t *testing.T) { 101 yaml := `source_code: 102 mappings: 103 - path: 104 - prefix: GOROOT 105 source: 106 github: 107 owner: golang 108 repo: go 109 ref: go1.24.8 110 path: src 111 ` 112 _, err := ParsePyroscopeConfig([]byte(yaml)) 113 require.Error(t, err) 114 assert.Contains(t, err.Error(), "language") 115 }) 116 117 t.Run("invalid - missing source config", func(t *testing.T) { 118 yaml := `source_code: 119 mappings: 120 - language: go 121 path: 122 - prefix: GOROOT 123 ` 124 _, err := ParsePyroscopeConfig([]byte(yaml)) 125 require.Error(t, err) 126 assert.Contains(t, err.Error(), "no source type supplied") 127 }) 128 129 t.Run("invalid - missing path and function_name", func(t *testing.T) { 130 yaml := `source_code: 131 mappings: 132 - language: java 133 source: 134 local: 135 path: src/main/java 136 ` 137 _, err := ParsePyroscopeConfig([]byte(yaml)) 138 require.Error(t, err) 139 assert.Contains(t, err.Error(), "at least one path or a function_name match is required") 140 }) 141 142 t.Run("invalid - unsupported language", func(t *testing.T) { 143 yaml := `source_code: 144 mappings: 145 - language: rust 146 path: 147 - prefix: src 148 source: 149 github: 150 owner: rust-lang 151 repo: rust 152 ref: 1.75.0 153 path: src 154 ` 155 _, err := ParsePyroscopeConfig([]byte(yaml)) 156 require.Error(t, err) 157 assert.Contains(t, err.Error(), "unsupported") 158 }) 159 160 t.Run("invalid yaml syntax", func(t *testing.T) { 161 yaml := `source_code: 162 mappings: 163 - language: go 164 path: 165 - prefix: GOROOT 166 source: 167 github: 168 owner: golang 169 repo: go 170 ref: go1.24.8 171 path: src 172 github: 173 owner: duplicate 174 ` 175 _, err := ParsePyroscopeConfig([]byte(yaml)) 176 require.Error(t, err) 177 }) 178 179 t.Run("wrong version", func(t *testing.T) { 180 yaml := `version: v1alpha1 181 ` 182 _, err := ParsePyroscopeConfig([]byte(yaml)) 183 require.Error(t, err) 184 }) 185 } 186 187 func TestFindMapping(t *testing.T) { 188 config := &PyroscopeConfig{ 189 SourceCode: SourceCodeConfig{ 190 Mappings: []MappingConfig{ 191 { 192 Language: "java", 193 Path: []Match{ 194 {Prefix: "org/example/rideshare"}, 195 }, 196 Source: Source{ 197 Local: &LocalMappingConfig{ 198 Path: "src/main/java/org/example/rideshare", 199 }, 200 }, 201 }, 202 { 203 Language: "java", 204 Path: []Match{ 205 {Prefix: "java"}, 206 }, 207 Source: Source{ 208 GitHub: &GitHubMappingConfig{ 209 Owner: "openjdk", 210 Repo: "jdk", 211 Ref: "jdk-17+0", 212 Path: "src/java.base/share/classes/java", 213 }, 214 }, 215 }, 216 { 217 Language: "java", 218 Path: []Match{ 219 {Prefix: "org/springframework/http"}, 220 }, 221 Source: Source{ 222 GitHub: &GitHubMappingConfig{ 223 Owner: "spring-projects", 224 Repo: "spring-framework", 225 Ref: "v5.3.20", 226 Path: "spring-web/src/main/java/org/springframework/http", 227 }, 228 }, 229 }, 230 }, 231 }, 232 } 233 234 tests := []struct { 235 name string 236 fileSpec FileSpec 237 expectedPrefix string 238 expectedSource string // "local" or "github" 239 shouldBeNil bool 240 }{ 241 { 242 name: "exact match for local", 243 fileSpec: FileSpec{Path: "org/example/rideshare"}, 244 expectedPrefix: "org/example/rideshare", 245 expectedSource: "local", 246 }, 247 { 248 name: "subdirectory of local", 249 fileSpec: FileSpec{Path: "org/example/rideshare/App.java"}, 250 expectedPrefix: "org/example/rideshare", 251 expectedSource: "local", 252 }, 253 { 254 name: "exact match for java", 255 fileSpec: FileSpec{Path: "java"}, 256 expectedPrefix: "java", 257 expectedSource: "github", 258 }, 259 { 260 name: "subdirectory of java", 261 fileSpec: FileSpec{Path: "java/util/ArrayList.java"}, 262 expectedPrefix: "java", 263 expectedSource: "github", 264 }, 265 { 266 name: "exact match for springframework", 267 fileSpec: FileSpec{Path: "org/springframework/http"}, 268 expectedPrefix: "org/springframework/http", 269 expectedSource: "github", 270 }, 271 { 272 name: "subdirectory of springframework", 273 fileSpec: FileSpec{Path: "org/springframework/http/HttpStatus.java"}, 274 expectedPrefix: "org/springframework/http", 275 expectedSource: "github", 276 }, 277 { 278 name: "longest prefix match", 279 fileSpec: FileSpec{Path: "org/springframework/http/converter/HttpMessageConverter.java"}, 280 expectedPrefix: "org/springframework/http", 281 expectedSource: "github", 282 }, 283 { 284 name: "no match", 285 fileSpec: FileSpec{Path: "com/google/common/collect/Lists.java"}, 286 shouldBeNil: true, 287 }, 288 { 289 name: "partial prefix should not match", 290 fileSpec: FileSpec{Path: "organization/test/File.java"}, 291 shouldBeNil: true, 292 }, 293 } 294 295 for _, tt := range tests { 296 t.Run(tt.name, func(t *testing.T) { 297 result := config.FindMapping(tt.fileSpec) 298 if tt.shouldBeNil { 299 assert.Nil(t, result) 300 } else { 301 require.NotNil(t, result) 302 require.Len(t, result.Path, 1) 303 assert.Equal(t, tt.expectedPrefix, result.Path[0].Prefix) 304 switch tt.expectedSource { 305 case "local": 306 assert.NotNil(t, result.Source.Local) 307 case "github": 308 assert.NotNil(t, result.Source.GitHub) 309 } 310 } 311 }) 312 } 313 }