github.com/buildpacks/pack@v0.33.3-0.20240516162812-884dd1837311/pkg/project/project_test.go (about) 1 package project 2 3 import ( 4 "log" 5 "os" 6 "reflect" 7 "testing" 8 9 "github.com/buildpacks/lifecycle/api" 10 "github.com/heroku/color" 11 "github.com/sclevine/spec" 12 "github.com/sclevine/spec/report" 13 14 "github.com/buildpacks/pack/pkg/logging" 15 h "github.com/buildpacks/pack/testhelpers" 16 ) 17 18 func TestProject(t *testing.T) { 19 h.RequireDocker(t) 20 color.Disable(true) 21 defer color.Disable(false) 22 23 spec.Run(t, "Provider", testProject, spec.Parallel(), spec.Report(report.Terminal{})) 24 } 25 26 func testProject(t *testing.T, when spec.G, it spec.S) { 27 var ( 28 logger *logging.LogWithWriters 29 readStdout func() string 30 ) 31 32 it.Before(func() { 33 var stdout *color.Console 34 stdout, readStdout = h.MockWriterAndOutput() 35 stderr, _ := h.MockWriterAndOutput() 36 logger = logging.NewLogWithWriters(stdout, stderr) 37 }) 38 39 when("#ReadProjectDescriptor", func() { 40 it("should parse a valid v0.2 project.toml file", func() { 41 projectToml := ` 42 [_] 43 name = "gallant 0.2" 44 schema-version="0.2" 45 [[_.licenses]] 46 type = "MIT" 47 [_.metadata] 48 pipeline = "Lucerne" 49 [io.buildpacks] 50 exclude = [ "*.jar" ] 51 [[io.buildpacks.pre.group]] 52 uri = "https://example.com/buildpack/pre" 53 [[io.buildpacks.post.group]] 54 uri = "https://example.com/buildpack/post" 55 [[io.buildpacks.group]] 56 id = "example/lua" 57 version = "1.0" 58 [[io.buildpacks.group]] 59 uri = "https://example.com/buildpack" 60 [[io.buildpacks.build.env]] 61 name = "JAVA_OPTS" 62 value = "-Xmx300m" 63 [[io.buildpacks.env.build]] 64 name = "JAVA_OPTS" 65 value = "this-should-get-overridden-because-its-deprecated" 66 ` 67 tmpProjectToml, err := createTmpProjectTomlFile(projectToml) 68 if err != nil { 69 t.Fatal(err) 70 } 71 72 projectDescriptor, err := ReadProjectDescriptor(tmpProjectToml.Name(), logger) 73 if err != nil { 74 t.Fatal(err) 75 } 76 77 var expected string 78 79 expected = "gallant 0.2" 80 if projectDescriptor.Project.Name != expected { 81 t.Fatalf("Expected\n-----\n%#v\n-----\nbut got\n-----\n%#v\n", 82 expected, projectDescriptor.Project.Name) 83 } 84 85 expectedVersion := api.MustParse("0.2") 86 if !reflect.DeepEqual(expectedVersion, projectDescriptor.SchemaVersion) { 87 t.Fatalf("Expected\n-----\n%#v\n-----\nbut got\n-----\n%#v\n", 88 expectedVersion, projectDescriptor.SchemaVersion) 89 } 90 91 expected = "example/lua" 92 if projectDescriptor.Build.Buildpacks[0].ID != expected { 93 t.Fatalf("Expected\n-----\n%#v\n-----\nbut got\n-----\n%#v\n", 94 expected, projectDescriptor.Build.Buildpacks[0].ID) 95 } 96 97 expected = "1.0" 98 if projectDescriptor.Build.Buildpacks[0].Version != expected { 99 t.Fatalf("Expected\n-----\n%#v\n-----\nbut got\n-----\n%#v\n", 100 expected, projectDescriptor.Build.Buildpacks[0].Version) 101 } 102 103 expected = "https://example.com/buildpack" 104 if projectDescriptor.Build.Buildpacks[1].URI != expected { 105 t.Fatalf("Expected\n-----\n%#v\n-----\nbut got\n-----\n%#v\n", 106 expected, projectDescriptor.Build.Buildpacks[1].URI) 107 } 108 109 expected = "https://example.com/buildpack/pre" 110 if projectDescriptor.Build.Pre.Buildpacks[0].URI != expected { 111 t.Fatalf("Expected\n-----\n%#v\n-----\nbut got\n-----\n%#v\n", 112 expected, projectDescriptor.Build.Pre.Buildpacks[0].URI) 113 } 114 115 expected = "https://example.com/buildpack/post" 116 if projectDescriptor.Build.Post.Buildpacks[0].URI != expected { 117 t.Fatalf("Expected\n-----\n%#v\n-----\nbut got\n-----\n%#v\n", 118 expected, projectDescriptor.Build.Post.Buildpacks[0].URI) 119 } 120 121 expected = "JAVA_OPTS" 122 if projectDescriptor.Build.Env[0].Name != expected { 123 t.Fatalf("Expected\n-----\n%#v\n-----\nbut got\n-----\n%#v\n", 124 expected, projectDescriptor.Build.Env[0].Name) 125 } 126 127 expected = "-Xmx300m" 128 if projectDescriptor.Build.Env[0].Value != expected { 129 t.Fatalf("Expected\n-----\n%#v\n-----\nbut got\n-----\n%#v\n", 130 expected, projectDescriptor.Build.Env[0].Value) 131 } 132 133 expected = "MIT" 134 if projectDescriptor.Project.Licenses[0].Type != expected { 135 t.Fatalf("Expected\n-----\n%#v\n-----\nbut got\n-----\n%#v\n", 136 expected, projectDescriptor.Project.Licenses[0].Type) 137 } 138 139 expected = "Lucerne" 140 if projectDescriptor.Metadata["pipeline"] != expected { 141 t.Fatalf("Expected\n-----\n%#v\n-----\nbut got\n-----\n%#v\n", 142 expected, projectDescriptor.Metadata["pipeline"]) 143 } 144 }) 145 it("should be backwards compatible with older v0.2 project.toml file", func() { 146 projectToml := ` 147 [_] 148 name = "gallant 0.2" 149 schema-version="0.2" 150 [[io.buildpacks.env.build]] 151 name = "JAVA_OPTS" 152 value = "-Xmx300m" 153 ` 154 tmpProjectToml, err := createTmpProjectTomlFile(projectToml) 155 if err != nil { 156 t.Fatal(err) 157 } 158 159 projectDescriptor, err := ReadProjectDescriptor(tmpProjectToml.Name(), logger) 160 if err != nil { 161 t.Fatal(err) 162 } 163 164 var expected string 165 166 expected = "JAVA_OPTS" 167 if projectDescriptor.Build.Env[0].Name != expected { 168 t.Fatalf("Expected\n-----\n%#v\n-----\nbut got\n-----\n%#v\n", 169 expected, projectDescriptor.Build.Env[0].Name) 170 } 171 172 expected = "-Xmx300m" 173 if projectDescriptor.Build.Env[0].Value != expected { 174 t.Fatalf("Expected\n-----\n%#v\n-----\nbut got\n-----\n%#v\n", 175 expected, projectDescriptor.Build.Env[0].Value) 176 } 177 }) 178 it("should parse a valid v0.1 project.toml file", func() { 179 projectToml := ` 180 [project] 181 name = "gallant" 182 version = "1.0.2" 183 source-url = "https://github.com/buildpacks/pack" 184 [[project.licenses]] 185 type = "MIT" 186 [build] 187 exclude = [ "*.jar" ] 188 [[build.buildpacks]] 189 id = "example/lua" 190 version = "1.0" 191 [[build.buildpacks]] 192 uri = "https://example.com/buildpack" 193 [[build.env]] 194 name = "JAVA_OPTS" 195 value = "-Xmx300m" 196 [metadata] 197 pipeline = "Lucerne" 198 ` 199 tmpProjectToml, err := createTmpProjectTomlFile(projectToml) 200 if err != nil { 201 t.Fatal(err) 202 } 203 204 projectDescriptor, err := ReadProjectDescriptor(tmpProjectToml.Name(), logger) 205 if err != nil { 206 t.Fatal(err) 207 } 208 209 var expected string 210 211 expected = "gallant" 212 if projectDescriptor.Project.Name != expected { 213 t.Fatalf("Expected\n-----\n%#v\n-----\nbut got\n-----\n%#v\n", 214 expected, projectDescriptor.Project.Name) 215 } 216 217 expectedVersion := api.MustParse("0.1") 218 if !reflect.DeepEqual(expectedVersion, projectDescriptor.SchemaVersion) { 219 t.Fatalf("Expected\n-----\n%#v\n-----\nbut got\n-----\n%#v\n", 220 expectedVersion, projectDescriptor.SchemaVersion) 221 } 222 223 expected = "1.0.2" 224 if projectDescriptor.Project.Version != expected { 225 t.Fatalf("Expected\n-----\n%#v\n-----\nbut got\n-----\n%#v\n", 226 expected, projectDescriptor.Project.Version) 227 } 228 229 expected = "https://github.com/buildpacks/pack" 230 if projectDescriptor.Project.SourceURL != expected { 231 t.Fatalf("Expected\n-----\n%#v\n-----\nbut got\n-----\n%#v\n", 232 expected, projectDescriptor.Project.SourceURL) 233 } 234 235 expected = "example/lua" 236 if projectDescriptor.Build.Buildpacks[0].ID != expected { 237 t.Fatalf("Expected\n-----\n%#v\n-----\nbut got\n-----\n%#v\n", 238 expected, projectDescriptor.Build.Buildpacks[0].ID) 239 } 240 241 expected = "1.0" 242 if projectDescriptor.Build.Buildpacks[0].Version != expected { 243 t.Fatalf("Expected\n-----\n%#v\n-----\nbut got\n-----\n%#v\n", 244 expected, projectDescriptor.Build.Buildpacks[0].Version) 245 } 246 247 expected = "https://example.com/buildpack" 248 if projectDescriptor.Build.Buildpacks[1].URI != expected { 249 t.Fatalf("Expected\n-----\n%#v\n-----\nbut got\n-----\n%#v\n", 250 expected, projectDescriptor.Build.Buildpacks[1].URI) 251 } 252 253 expected = "JAVA_OPTS" 254 if projectDescriptor.Build.Env[0].Name != expected { 255 t.Fatalf("Expected\n-----\n%#v\n-----\nbut got\n-----\n%#v\n", 256 expected, projectDescriptor.Build.Env[0].Name) 257 } 258 259 expected = "-Xmx300m" 260 if projectDescriptor.Build.Env[0].Value != expected { 261 t.Fatalf("Expected\n-----\n%#v\n-----\nbut got\n-----\n%#v\n", 262 expected, projectDescriptor.Build.Env[0].Value) 263 } 264 265 expected = "MIT" 266 if projectDescriptor.Project.Licenses[0].Type != expected { 267 t.Fatalf("Expected\n-----\n%#v\n-----\nbut got\n-----\n%#v\n", 268 expected, projectDescriptor.Project.Licenses[0].Type) 269 } 270 271 expected = "Lucerne" 272 if projectDescriptor.Metadata["pipeline"] != expected { 273 t.Fatalf("Expected\n-----\n%#v\n-----\nbut got\n-----\n%#v\n", 274 expected, projectDescriptor.Metadata["pipeline"]) 275 } 276 }) 277 278 it("should create empty build ENV", func() { 279 projectToml := ` 280 [project] 281 name = "gallant" 282 ` 283 tmpProjectToml, err := createTmpProjectTomlFile(projectToml) 284 if err != nil { 285 t.Fatal(err) 286 } 287 288 projectDescriptor, err := ReadProjectDescriptor(tmpProjectToml.Name(), logger) 289 if err != nil { 290 t.Fatal(err) 291 } 292 293 expected := 0 294 if len(projectDescriptor.Build.Env) != 0 { 295 t.Fatalf("Expected\n-----\n%d\n-----\nbut got\n-----\n%d\n", 296 expected, len(projectDescriptor.Build.Env)) 297 } 298 299 for _, envVar := range projectDescriptor.Build.Env { 300 t.Fatalf("Expected\n-----\n%#v\n-----\nbut got\n-----\n%#v\n", 301 "[]", envVar) 302 } 303 }) 304 305 it("should fail for an invalid project.toml path", func() { 306 _, err := ReadProjectDescriptor("/path/that/does/not/exist/project.toml", logger) 307 308 if !os.IsNotExist(err) { 309 t.Fatalf("Expected\n-----\n%#v\n-----\nbut got\n-----\n%#v\n", 310 "project.toml does not exist error", "no error") 311 } 312 }) 313 314 it("should enforce mutual exclusivity between exclude and include", func() { 315 projectToml := ` 316 [project] 317 name = "bad excludes and includes" 318 319 [build] 320 exclude = [ "*.jar" ] 321 include = [ "*.jpg" ] 322 ` 323 tmpProjectToml, err := createTmpProjectTomlFile(projectToml) 324 if err != nil { 325 t.Fatal(err) 326 } 327 _, err = ReadProjectDescriptor(tmpProjectToml.Name(), logger) 328 if err == nil { 329 t.Fatalf( 330 "Expected error for having both exclude and include defined") 331 } 332 }) 333 334 it("should have an id or uri defined for buildpacks", func() { 335 projectToml := ` 336 [project] 337 name = "missing buildpacks id and uri" 338 339 [[build.buildpacks]] 340 version = "1.2.3" 341 ` 342 tmpProjectToml, err := createTmpProjectTomlFile(projectToml) 343 if err != nil { 344 t.Fatal(err) 345 } 346 347 _, err = ReadProjectDescriptor(tmpProjectToml.Name(), logger) 348 if err == nil { 349 t.Fatalf("Expected error for NOT having id or uri defined for buildpacks") 350 } 351 }) 352 353 it("should not allow both uri and version", func() { 354 projectToml := ` 355 [project] 356 name = "cannot have both uri and version defined" 357 358 [[build.buildpacks]] 359 uri = "https://example.com/buildpack" 360 version = "1.2.3" 361 ` 362 tmpProjectToml, err := createTmpProjectTomlFile(projectToml) 363 if err != nil { 364 t.Fatal(err) 365 } 366 367 _, err = ReadProjectDescriptor(tmpProjectToml.Name(), logger) 368 if err == nil { 369 t.Fatal("Expected error for having both uri and version defined for a buildpack(s)") 370 } 371 }) 372 373 it("should require either a type or uri for licenses", func() { 374 projectToml := ` 375 [project] 376 name = "licenses should have either a type or uri defined" 377 378 [[project.licenses]] 379 ` 380 tmpProjectToml, err := createTmpProjectTomlFile(projectToml) 381 if err != nil { 382 t.Fatal(err) 383 } 384 385 _, err = ReadProjectDescriptor(tmpProjectToml.Name(), logger) 386 if err == nil { 387 t.Fatal("Expected error for having neither type or uri defined for licenses") 388 } 389 }) 390 391 it("should warn when no schema version is declared", func() { 392 projectToml := `` 393 tmpProjectToml, err := createTmpProjectTomlFile(projectToml) 394 if err != nil { 395 t.Fatal(err) 396 } 397 398 _, err = ReadProjectDescriptor(tmpProjectToml.Name(), logger) 399 h.AssertNil(t, err) 400 401 h.AssertContains(t, readStdout(), "Warning: No schema version declared in project.toml, defaulting to schema version 0.1\n") 402 }) 403 404 it("should warn when unsupported keys are declared with schema v0.1", func() { 405 projectToml := ` 406 [_] 407 schema-version = "0.1" 408 409 [unsupported-table] 410 unsupported-key = "some value" 411 ` 412 tmpProjectToml, err := createTmpProjectTomlFile(projectToml) 413 if err != nil { 414 t.Fatal(err) 415 } 416 417 _, err = ReadProjectDescriptor(tmpProjectToml.Name(), logger) 418 h.AssertNil(t, err) 419 420 h.AssertContains(t, readStdout(), "Warning: The following keys declared in project.toml are not supported in schema version 0.1:\nWarning: - unsupported-table\nWarning: - unsupported-table.unsupported-key\nWarning: The above keys will be ignored. If this is not intentional, maybe try updating your schema version.\n") 421 }) 422 423 it("should warn when unsupported keys are declared with schema v0.2", func() { 424 projectToml := ` 425 [_] 426 schema-version = "0.2" 427 428 [unsupported-table] 429 unsupported-key = "some value" 430 ` 431 tmpProjectToml, err := createTmpProjectTomlFile(projectToml) 432 if err != nil { 433 t.Fatal(err) 434 } 435 436 _, err = ReadProjectDescriptor(tmpProjectToml.Name(), logger) 437 h.AssertNil(t, err) 438 439 h.AssertContains(t, readStdout(), "Warning: The following keys declared in project.toml are not supported in schema version 0.2:\nWarning: - unsupported-table\nWarning: - unsupported-table.unsupported-key\nWarning: The above keys will be ignored. If this is not intentional, maybe try updating your schema version.\n") 440 }) 441 }) 442 } 443 444 func createTmpProjectTomlFile(projectToml string) (*os.File, error) { 445 tmpProjectToml, err := os.CreateTemp(os.TempDir(), "project-") 446 if err != nil { 447 log.Fatal("Failed to create temporary project toml file", err) 448 } 449 450 if _, err := tmpProjectToml.Write([]byte(projectToml)); err != nil { 451 log.Fatal("Failed to write to temporary file", err) 452 } 453 return tmpProjectToml, err 454 }