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  }