github.com/hashicorp/packer@v1.14.3/command/build_test.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: BUSL-1.1
     3  
     4  package command
     5  
     6  import (
     7  	"fmt"
     8  	"math"
     9  	"os"
    10  	"path/filepath"
    11  	"regexp"
    12  	"runtime"
    13  	"strings"
    14  	"testing"
    15  
    16  	"github.com/google/go-cmp/cmp"
    17  	"github.com/hashicorp/go-uuid"
    18  )
    19  
    20  var (
    21  	spaghettiCarbonara = `spaghetti
    22  carbonara
    23  `
    24  	lasagna = `lasagna
    25  tomato
    26  mozza
    27  cooking...
    28  `
    29  	tiramisu = `whip_york
    30  mascarpone
    31  whipped_egg_white
    32  dress
    33  `
    34  	one = "1\n"
    35  	two = "2\n"
    36  )
    37  
    38  func TestBuild(t *testing.T) {
    39  	tc := []struct {
    40  		name         string
    41  		args         []string
    42  		expectedCode int
    43  		fileCheck
    44  	}{
    45  		{
    46  			name: "var-args: json - json varfile sets an apple env var",
    47  			args: []string{
    48  				"-var-file=" + filepath.Join(testFixture("var-arg"), "apple.json"),
    49  				filepath.Join(testFixture("var-arg"), "fruit_builder.json"),
    50  			},
    51  			fileCheck: fileCheck{expected: []string{"apple.txt"}},
    52  		},
    53  		{
    54  			name: "json - json varfile sets an apple env var, " +
    55  				"override with banana cli var",
    56  			args: []string{
    57  				"-var", "fruit=banana",
    58  				"-var-file=" + filepath.Join(testFixture("var-arg"), "apple.json"),
    59  				filepath.Join(testFixture("var-arg"), "fruit_builder.json"),
    60  			},
    61  			fileCheck: fileCheck{expected: []string{"banana.txt"}},
    62  		},
    63  		{
    64  			name: "var-args: json - arg sets a pear env var",
    65  			args: []string{
    66  				"-var=fruit=pear",
    67  				filepath.Join(testFixture("var-arg"), "fruit_builder.json"),
    68  			},
    69  			fileCheck: fileCheck{expected: []string{"pear.txt"}},
    70  		},
    71  
    72  		{
    73  			name: "var-args: json - nonexistent var file errs",
    74  			args: []string{
    75  				"-var-file=" + filepath.Join(testFixture("var-arg"), "potato.json"),
    76  				filepath.Join(testFixture("var-arg"), "fruit_builder.json"),
    77  			},
    78  			expectedCode: 1,
    79  			fileCheck:    fileCheck{notExpected: []string{"potato.txt"}},
    80  		},
    81  
    82  		{
    83  			name: "var-args: hcl - nonexistent json var file errs",
    84  			args: []string{
    85  				"-var-file=" + filepath.Join(testFixture("var-arg"), "potato.json"),
    86  				testFixture("var-arg"),
    87  			},
    88  			expectedCode: 1,
    89  			fileCheck:    fileCheck{notExpected: []string{"potato.txt"}},
    90  		},
    91  
    92  		{
    93  			name: "var-args: hcl - nonexistent hcl var file errs",
    94  			args: []string{
    95  				"-var-file=" + filepath.Join(testFixture("var-arg"), "potato.hcl"),
    96  				testFixture("var-arg"),
    97  			},
    98  			expectedCode: 1,
    99  			fileCheck:    fileCheck{notExpected: []string{"potato.hcl"}},
   100  		},
   101  
   102  		{
   103  			name: "var-args: hcl - auto varfile sets a chocolate env var",
   104  			args: []string{
   105  				testFixture("var-arg"),
   106  			},
   107  			fileCheck: fileCheck{expected: []string{"chocolate.txt"}},
   108  		},
   109  		{
   110  			name: "var-args: json - auto varfile sets a peanut env var",
   111  			args: []string{
   112  				testFixture("var-arg", "var-arg-test-autovar-json"),
   113  			},
   114  			fileCheck: fileCheck{expected: []string{"peanut.txt"}},
   115  		},
   116  
   117  		{
   118  			name: "var-args: hcl - auto varfile and json -auto varfile sets the value in json auto varfile",
   119  			args: []string{
   120  				testFixture("var-arg", "var-arg-tests"),
   121  			},
   122  			fileCheck: fileCheck{expected: []string{"peanut.txt"}},
   123  		},
   124  
   125  		{
   126  			name: "var-args: hcl - hcl varfile sets a apple env var",
   127  			args: []string{
   128  				"-var-file=" + filepath.Join(testFixture("var-arg"), "apple.hcl"),
   129  				testFixture("var-arg"),
   130  			},
   131  			fileCheck: fileCheck{expected: []string{"apple.txt"}},
   132  		},
   133  
   134  		{
   135  			name: "var-args: hcl - json varfile sets a apple env var",
   136  			args: []string{
   137  				"-var-file=" + filepath.Join(testFixture("var-arg"), "apple.json"),
   138  				testFixture("var-arg"),
   139  			},
   140  			fileCheck: fileCheck{expected: []string{"apple.txt"}},
   141  		},
   142  		{
   143  			name: "var-args: banana json var file then hcl var file sets apple env var",
   144  			args: []string{
   145  				"-var-file=" + filepath.Join(testFixture("var-arg"), "banana.json"),
   146  				"-var-file=" + filepath.Join(testFixture("var-arg"), "apple.hcl"),
   147  				testFixture("var-arg"),
   148  			},
   149  			fileCheck: fileCheck{expected: []string{"apple.txt"}},
   150  		},
   151  		{
   152  			name: "var-args:  apple hcl var file then banana json var file sets banana env var",
   153  			args: []string{
   154  				"-var-file=" + filepath.Join(testFixture("var-arg"), "apple.hcl"),
   155  				"-var-file=" + filepath.Join(testFixture("var-arg"), "banana.json"),
   156  				testFixture("var-arg"),
   157  			},
   158  			fileCheck: fileCheck{expected: []string{"banana.txt"}},
   159  		},
   160  
   161  		{
   162  			name: "var-args: hcl - arg sets a tomato env var",
   163  			args: []string{
   164  				"-var=fruit=tomato",
   165  				testFixture("var-arg"),
   166  			},
   167  			fileCheck: fileCheck{expected: []string{"tomato.txt"}},
   168  		},
   169  
   170  		{
   171  			name: "source name: HCL",
   172  			args: []string{
   173  				"-parallel-builds=1", // to ensure order is kept
   174  				testFixture("build-name-and-type"),
   175  			},
   176  			fileCheck: fileCheck{
   177  				expectedContent: map[string]string{
   178  					"manifest.json": `{
   179    "builds": [
   180      {
   181        "name": "test",
   182        "builder_type": "null",
   183        "files": null,
   184        "artifact_id": "Null",
   185        "packer_run_uuid": "",
   186        "custom_data": null
   187      },
   188      {
   189        "name": "potato",
   190        "builder_type": "null",
   191        "files": null,
   192        "artifact_id": "Null",
   193        "packer_run_uuid": "",
   194        "custom_data": null
   195      }
   196    ],
   197    "last_run_uuid": ""
   198  }`,
   199  				},
   200  			},
   201  		},
   202  
   203  		{
   204  			name: "build name: JSON except potato",
   205  			args: []string{
   206  				"-except=potato",
   207  				"-parallel-builds=1", // to ensure order is kept
   208  				filepath.Join(testFixture("build-name-and-type"), "all.json"),
   209  			},
   210  			fileCheck: fileCheck{
   211  				expected: []string{
   212  					"null.test.txt",
   213  					"null.potato.txt",
   214  				},
   215  				expectedContent: map[string]string{
   216  					"manifest.json": `{
   217    "builds": [
   218      {
   219        "name": "test",
   220        "builder_type": "null",
   221        "files": null,
   222        "artifact_id": "Null",
   223        "packer_run_uuid": "",
   224        "custom_data": null
   225      }
   226    ],
   227    "last_run_uuid": ""
   228  }`,
   229  				},
   230  			},
   231  		},
   232  
   233  		{
   234  			name: "build name: JSON only potato",
   235  			args: []string{
   236  				"-only=potato",
   237  				"-parallel-builds=1", // to ensure order is kept
   238  				filepath.Join(testFixture("build-name-and-type"), "all.json"),
   239  			},
   240  			fileCheck: fileCheck{
   241  				expectedContent: map[string]string{
   242  					"manifest.json": `{
   243    "builds": [
   244      {
   245        "name": "potato",
   246        "builder_type": "null",
   247        "files": null,
   248        "artifact_id": "Null",
   249        "packer_run_uuid": "",
   250        "custom_data": null
   251      }
   252    ],
   253    "last_run_uuid": ""
   254  }`,
   255  				},
   256  			},
   257  		},
   258  
   259  		// only / except HCL2
   260  		{
   261  			name: "hcl - 'except' a build block",
   262  			args: []string{
   263  				"-except=my_build.*",
   264  				testFixture("hcl-only-except"),
   265  			},
   266  			fileCheck: fileCheck{
   267  				expected:    []string{"cherry.txt"},
   268  				notExpected: []string{"chocolate.txt", "vanilla.txt"},
   269  			},
   270  		},
   271  
   272  		{
   273  			name: "hcl - 'only' a build block",
   274  			args: []string{
   275  				"-only=my_build.*",
   276  				testFixture("hcl-only-except"),
   277  			},
   278  			fileCheck: fileCheck{
   279  				notExpected: []string{"cherry.txt"},
   280  				expected:    []string{"chocolate.txt", "vanilla.txt"},
   281  			},
   282  		},
   283  
   284  		// recipes
   285  		{
   286  			name: "hcl - recipes",
   287  			args: []string{
   288  				testFixture("hcl", "recipes"),
   289  			},
   290  			fileCheck: fileCheck{
   291  				expectedContent: map[string]string{
   292  					"NULL.spaghetti_carbonara.txt": spaghettiCarbonara,
   293  					"NULL.lasagna.txt":             lasagna,
   294  					"NULL.tiramisu.txt":            tiramisu,
   295  				},
   296  			},
   297  		},
   298  
   299  		{
   300  			name: "hcl - recipes - except carbonara",
   301  			args: []string{
   302  				"-except", "recipes.null.spaghetti_carbonara",
   303  				testFixture("hcl", "recipes"),
   304  			},
   305  			fileCheck: fileCheck{
   306  				notExpected: []string{"NULL.spaghetti_carbonara.txt"},
   307  				expectedContent: map[string]string{
   308  					"NULL.lasagna.txt":  lasagna,
   309  					"NULL.tiramisu.txt": tiramisu,
   310  				},
   311  			},
   312  		},
   313  
   314  		{
   315  			name: "hcl - recipes - only lasagna",
   316  			args: []string{
   317  				"-only", "*lasagna",
   318  				testFixture("hcl", "recipes"),
   319  			},
   320  			fileCheck: fileCheck{
   321  				notExpected: []string{
   322  					"NULL.spaghetti_carbonara.txt",
   323  					"NULL.tiramisu.txt",
   324  				},
   325  				expectedContent: map[string]string{
   326  					"NULL.lasagna.txt": lasagna,
   327  				},
   328  			},
   329  		},
   330  		{
   331  			name: "hcl - recipes - only recipes",
   332  			args: []string{
   333  				"-only", "recipes.*",
   334  				testFixture("hcl", "recipes"),
   335  			},
   336  			fileCheck: fileCheck{
   337  				notExpected: []string{
   338  					"NULL.tiramisu.txt",
   339  				},
   340  				expectedContent: map[string]string{
   341  					"NULL.spaghetti_carbonara.txt": spaghettiCarbonara,
   342  					"NULL.lasagna.txt":             lasagna,
   343  				},
   344  			},
   345  		},
   346  		{
   347  			name: "hcl - build.name accessible",
   348  			args: []string{
   349  				filepath.Join(testFixture("build-name-and-type"), "buildname.pkr.hcl"),
   350  			},
   351  			fileCheck: fileCheck{
   352  				expected: []string{
   353  					"pineapple.pizza.txt",
   354  				},
   355  			},
   356  		},
   357  
   358  		{
   359  			name: "hcl - valid validation rule for default value",
   360  			args: []string{
   361  				filepath.Join(testFixture("hcl", "validation", "map")),
   362  			},
   363  			expectedCode: 0,
   364  		},
   365  
   366  		{
   367  			name: "hcl - valid setting from varfile",
   368  			args: []string{
   369  				"-var-file", filepath.Join(testFixture("hcl", "validation", "map", "valid_value.pkrvars.hcl")),
   370  				filepath.Join(testFixture("hcl", "validation", "map")),
   371  			},
   372  			expectedCode: 0,
   373  		},
   374  
   375  		{
   376  			name: "hcl - invalid setting from varfile",
   377  			args: []string{
   378  				"-var-file", filepath.Join(testFixture("hcl", "validation", "map", "invalid_value.pkrvars.hcl")),
   379  				filepath.Join(testFixture("hcl", "validation", "map")),
   380  			},
   381  			expectedCode: 1,
   382  		},
   383  
   384  		{
   385  			name: "hcl - valid cmd ( invalid varfile bypased )",
   386  			args: []string{
   387  				"-var-file", filepath.Join(testFixture("hcl", "validation", "map", "invalid_value.pkrvars.hcl")),
   388  				"-var", `image_metadata={key = "new_value", something = { foo = "bar" }}`,
   389  				filepath.Join(testFixture("hcl", "validation", "map")),
   390  			},
   391  			expectedCode: 0,
   392  		},
   393  
   394  		{
   395  			name: "hcl - invalid cmd ( valid varfile bypased )",
   396  			args: []string{
   397  				"-var-file", filepath.Join(testFixture("hcl", "validation", "map", "valid_value.pkrvars.hcl")),
   398  				"-var", `image_metadata={key = "?", something = { foo = "wrong" }}`,
   399  				filepath.Join(testFixture("hcl", "validation", "map")),
   400  			},
   401  			expectedCode: 1,
   402  		},
   403  		{
   404  			name: "hcl - execute and use datasource",
   405  			args: []string{
   406  				testFixture("hcl", "datasource.pkr.hcl"),
   407  			},
   408  			fileCheck: fileCheck{
   409  				expectedContent: map[string]string{
   410  					"chocolate.txt": "chocolate",
   411  				},
   412  			},
   413  		},
   414  		{
   415  			name: "hcl - dynamic source blocks in a build block",
   416  			args: []string{
   417  				testFixture("hcl", "dynamic", "build.pkr.hcl"),
   418  			},
   419  			fileCheck: fileCheck{
   420  				expectedContent: map[string]string{
   421  					"dummy.txt":       "layers/base/main/files",
   422  					"postgres/13.txt": "layers/base/main/files\nlayers/base/init/files\nlayers/postgres/files",
   423  				},
   424  				expected: []string{"dummy-fooo.txt", "dummy-baar.txt", "postgres/13-fooo.txt", "postgres/13-baar.txt"},
   425  			},
   426  		},
   427  
   428  		{
   429  			name: "hcl - variables can be used in shared post-processor fields",
   430  			args: []string{
   431  				testFixture("hcl", "var-in-pp-name.pkr.hcl"),
   432  			},
   433  			fileCheck: fileCheck{
   434  				expectedContent: map[string]string{
   435  					"example1.1.txt": one,
   436  					"example2.2.txt": two,
   437  				},
   438  				notExpected: []string{
   439  					"example1.2.txt",
   440  					"example2.1.txt",
   441  				},
   442  			},
   443  		},
   444  		{
   445  			name: "hcl - using build variables in post-processor",
   446  			args: []string{
   447  				testFixture("hcl", "build-var-in-pp.pkr.hcl"),
   448  			},
   449  			fileCheck: fileCheck{
   450  				expectedContent: map[string]string{
   451  					"example.2.txt": two,
   452  				},
   453  			},
   454  		},
   455  
   456  		{
   457  			name: "hcl - test crash #11381",
   458  			args: []string{
   459  				testFixture("hcl", "nil-component-crash.pkr.hcl"),
   460  			},
   461  			expectedCode: 1,
   462  		},
   463  		{
   464  			name: "hcl - using variables in build block",
   465  			args: []string{
   466  				testFixture("hcl", "vars-in-build-block.pkr.hcl"),
   467  			},
   468  			fileCheck: fileCheck{
   469  				expectedContent: map[string]string{
   470  					"example.2.txt": two,
   471  				},
   472  			},
   473  		},
   474  		{
   475  			name: "hcl - recursive local using input var",
   476  			args: []string{
   477  				testFixture("hcl", "recursive_local_with_input"),
   478  			},
   479  			fileCheck: fileCheck{
   480  				expectedContent: map[string]string{
   481  					"hey.txt": "hello",
   482  				},
   483  			},
   484  		},
   485  		{
   486  			name: "hcl - recursive local using an unset input var",
   487  			args: []string{
   488  				testFixture("hcl", "recursive_local_with_unset_input"),
   489  			},
   490  			fileCheck:    fileCheck{},
   491  			expectedCode: 1,
   492  		},
   493  		{
   494  			name: "hcl - var with default value empty object/list can be set",
   495  			args: []string{
   496  				testFixture("hcl", "empty_object"),
   497  			},
   498  			fileCheck: fileCheck{
   499  				expectedContent: map[string]string{
   500  					"foo.txt": "yo",
   501  				},
   502  			},
   503  		},
   504  		{
   505  			name: "hcl - unknown ",
   506  			args: []string{
   507  				testFixture("hcl", "data-source-validation.pkr.hcl"),
   508  			},
   509  			fileCheck: fileCheck{
   510  				expectedContent: map[string]string{
   511  					"foo.txt": "foo",
   512  				},
   513  				expected: []string{
   514  					"s3cr3t",
   515  				},
   516  			},
   517  		},
   518  	}
   519  
   520  	for _, tt := range tc {
   521  		t.Run(tt.name, func(t *testing.T) {
   522  			defer tt.cleanup(t)
   523  			t.Logf("Running build on %s", tt.args)
   524  			run(t, tt.args, tt.expectedCode)
   525  			tt.fileCheck.verify(t, "")
   526  		})
   527  	}
   528  }
   529  
   530  func Test_build_output(t *testing.T) {
   531  
   532  	tc := []struct {
   533  		command     []string
   534  		env         []string
   535  		expected    []string
   536  		notExpected []string
   537  		runtime     string
   538  	}{
   539  		{[]string{"build", "--color=false", testFixture("hcl", "reprepare", "shell-local.pkr.hcl")},
   540  			nil,
   541  			[]string{"null.example: hello from the NULL builder packeruser", "Build 'null.example' finished after"},
   542  			[]string{},
   543  			"posix"},
   544  		{[]string{"build", "--color=false", testFixture("hcl", "reprepare", "shell-local-windows.pkr.hcl")},
   545  			nil,
   546  			[]string{"null.example: hello from the NULL  builder packeruser", "Build 'null.example' finished after"},
   547  			[]string{},
   548  			"windows"},
   549  		{[]string{"build", "--color=false", testFixture("hcl", "provisioner-override.pkr.hcl")},
   550  			nil,
   551  			[]string{"null.example1: yes overridden", "null.example2: not overridden"},
   552  			[]string{"null.example2: yes overridden", "null.example1: not overridden"},
   553  			"posix"},
   554  		{[]string{"build", "--color=false", testFixture("provisioners", "provisioner-override.json")},
   555  			nil,
   556  			[]string{"example1: yes overridden", "example2: not overridden"},
   557  			[]string{"example2: yes overridden", "example1: not overridden"},
   558  			"posix"},
   559  	}
   560  
   561  	for _, tc := range tc {
   562  		if (runtime.GOOS == "windows") != (tc.runtime == "windows") {
   563  			continue
   564  		}
   565  		t.Run(fmt.Sprintf("packer %s", tc.command), func(t *testing.T) {
   566  			p := helperCommand(t, tc.command...)
   567  			p.Env = append(p.Env, tc.env...)
   568  			bs, err := p.Output()
   569  			if err != nil {
   570  				t.Fatalf("%v: %s", err, bs)
   571  			}
   572  			for _, expected := range tc.expected {
   573  				if !strings.Contains(string(bs), expected) {
   574  					t.Fatalf("Should contain output %s.\nReceived: %s", tc.expected, string(bs))
   575  				}
   576  			}
   577  			for _, notExpected := range tc.notExpected {
   578  				if strings.Contains(string(bs), notExpected) {
   579  					t.Fatalf("Should NOT contain output %s.\nReceived: %s", tc.expected, string(bs))
   580  				}
   581  			}
   582  		})
   583  	}
   584  }
   585  
   586  func TestBuildOnlyFileCommaFlags(t *testing.T) {
   587  	c := &BuildCommand{
   588  		Meta: TestMetaFile(t),
   589  	}
   590  
   591  	args := []string{
   592  		"-parallel-builds=1",
   593  		"-only=chocolate,vanilla",
   594  		filepath.Join(testFixture("build-only"), "template.json"),
   595  	}
   596  
   597  	defer cleanup()
   598  
   599  	if code := c.Run(args); code != 0 {
   600  		fatalCommand(t, c.Meta)
   601  	}
   602  
   603  	for _, f := range []string{"chocolate.txt", "vanilla.txt",
   604  		"apple.txt", "peach.txt", "pear.txt", "unnamed.txt"} {
   605  		if !fileExists(f) {
   606  			t.Errorf("Expected to find %s", f)
   607  		}
   608  	}
   609  
   610  	if fileExists("cherry.txt") {
   611  		t.Error("Expected NOT to find cherry.txt")
   612  	}
   613  
   614  	if !fileExists("tomato.txt") {
   615  		t.Error("Expected to find tomato.txt")
   616  	}
   617  }
   618  
   619  func TestBuildStdin(t *testing.T) {
   620  	c := &BuildCommand{
   621  		Meta: TestMetaFile(t),
   622  	}
   623  	f, err := os.Open(filepath.Join(testFixture("build-only"), "template.json"))
   624  	if err != nil {
   625  		t.Fatal(err)
   626  	}
   627  	defer f.Close()
   628  
   629  	stdin := os.Stdin
   630  	os.Stdin = f
   631  	defer func() { os.Stdin = stdin }()
   632  
   633  	defer cleanup()
   634  	if code := c.Run([]string{"-parallel-builds=1", "-"}); code != 0 {
   635  		fatalCommand(t, c.Meta)
   636  	}
   637  
   638  	for _, f := range []string{"vanilla.txt", "cherry.txt", "chocolate.txt",
   639  		"unnamed.txt"} {
   640  		if !fileExists(f) {
   641  			t.Errorf("Expected to find %s", f)
   642  		}
   643  	}
   644  }
   645  
   646  func TestBuildOnlyFileMultipleFlags(t *testing.T) {
   647  	c := &BuildCommand{
   648  		Meta: TestMetaFile(t),
   649  	}
   650  
   651  	args := []string{
   652  		"-parallel-builds=1",
   653  		"-only=chocolate",
   654  		"-only=cherry",
   655  		"-only=apple", // ignored
   656  		"-only=peach", // ignored
   657  		"-only=pear",  // ignored
   658  		filepath.Join(testFixture("build-only"), "template.json"),
   659  	}
   660  
   661  	defer cleanup()
   662  
   663  	if code := c.Run(args); code != 0 {
   664  		fatalCommand(t, c.Meta)
   665  	}
   666  
   667  	for _, f := range []string{"vanilla.txt", "tomato.txt"} {
   668  		if fileExists(f) {
   669  			t.Errorf("Expected NOT to find %s", f)
   670  		}
   671  	}
   672  	for _, f := range []string{"chocolate.txt", "cherry.txt",
   673  		"apple.txt", "peach.txt", "pear.txt", "unnamed.txt"} {
   674  		if !fileExists(f) {
   675  			t.Errorf("Expected to find %s", f)
   676  		}
   677  	}
   678  }
   679  
   680  func TestBuildProvisionAndPosProcessWithBuildVariablesSharing(t *testing.T) {
   681  	c := &BuildCommand{
   682  		Meta: TestMetaFile(t),
   683  	}
   684  
   685  	args := []string{
   686  		filepath.Join(testFixture("build-variable-sharing"), "template.json"),
   687  	}
   688  
   689  	files := []string{
   690  		"provisioner.Null.txt",
   691  		"post-processor.Null.txt",
   692  	}
   693  
   694  	defer cleanup(files...)
   695  
   696  	if code := c.Run(args); code != 0 {
   697  		fatalCommand(t, c.Meta)
   698  	}
   699  
   700  	for _, f := range files {
   701  		if !fileExists(f) {
   702  			t.Errorf("Expected to find %s", f)
   703  		}
   704  	}
   705  }
   706  
   707  func TestBuildEverything(t *testing.T) {
   708  	c := &BuildCommand{
   709  		Meta: TestMetaFile(t),
   710  	}
   711  
   712  	args := []string{
   713  		"-parallel-builds=1",
   714  		`-except=`,
   715  		filepath.Join(testFixture("build-only"), "template.json"),
   716  	}
   717  
   718  	defer cleanup()
   719  
   720  	if code := c.Run(args); code != 0 {
   721  		fatalCommand(t, c.Meta)
   722  	}
   723  
   724  	for _, f := range []string{"chocolate.txt", "vanilla.txt", "tomato.txt",
   725  		"apple.txt", "cherry.txt", "pear.txt", "peach.txt", "unnamed.txt"} {
   726  		if !fileExists(f) {
   727  			t.Errorf("Expected to find %s", f)
   728  		}
   729  	}
   730  }
   731  
   732  func TestBuildExceptFileCommaFlags(t *testing.T) {
   733  	c := &BuildCommand{
   734  		Meta: TestMetaFile(t),
   735  	}
   736  	tc := []struct {
   737  		name                     string
   738  		args                     []string
   739  		expectedFiles            []string
   740  		buildNotExpectedFiles    []string
   741  		postProcNotExpectedFiles []string
   742  	}{
   743  		{
   744  			name: "JSON: except build and post-processor",
   745  			args: []string{
   746  				"-parallel-builds=1",
   747  				"-except=chocolate,vanilla,tomato",
   748  				filepath.Join(testFixture("build-only"), "template.json"),
   749  			},
   750  			expectedFiles:            []string{"apple.txt", "cherry.txt", "peach.txt"},
   751  			buildNotExpectedFiles:    []string{"chocolate.txt", "vanilla.txt", "tomato.txt", "unnamed.txt"},
   752  			postProcNotExpectedFiles: []string{"pear.txt, banana.txt"},
   753  		},
   754  		{
   755  			name: "HCL2: except build and post-processor",
   756  			args: []string{
   757  				"-parallel-builds=1",
   758  				"-except=file.chocolate,file.vanilla,tomato",
   759  				filepath.Join(testFixture("build-only"), "template.pkr.hcl"),
   760  			},
   761  			expectedFiles:            []string{"apple.txt", "cherry.txt", "peach.txt"},
   762  			buildNotExpectedFiles:    []string{"chocolate.txt", "vanilla.txt", "tomato.txt", "unnamed.txt"},
   763  			postProcNotExpectedFiles: []string{"pear.txt, banana.txt"},
   764  		},
   765  		{
   766  			name: "HCL2-JSON: except build and post-processor",
   767  			args: []string{
   768  				"-parallel-builds=1",
   769  				"-except=file.chocolate,file.vanilla,tomato",
   770  				filepath.Join(testFixture("build-only"), "template.pkr.json"),
   771  			},
   772  			expectedFiles:            []string{"apple.txt", "cherry.txt", "peach.txt"},
   773  			buildNotExpectedFiles:    []string{"chocolate.txt", "vanilla.txt", "tomato.txt", "unnamed.txt"},
   774  			postProcNotExpectedFiles: []string{"pear.txt, banana.txt"},
   775  		},
   776  	}
   777  
   778  	for _, tt := range tc {
   779  		t.Run(tt.name, func(t *testing.T) {
   780  			defer cleanup()
   781  
   782  			if code := c.Run(tt.args); code != 0 {
   783  				fatalCommand(t, c.Meta)
   784  			}
   785  
   786  			for _, f := range tt.buildNotExpectedFiles {
   787  				if fileExists(f) {
   788  					t.Errorf("build not skipped: Expected NOT to find %s", f)
   789  				}
   790  			}
   791  			for _, f := range tt.postProcNotExpectedFiles {
   792  				if fileExists(f) {
   793  					t.Errorf("post-processor not skipped: Expected NOT to find %s", f)
   794  				}
   795  			}
   796  			for _, f := range tt.expectedFiles {
   797  				if !fileExists(f) {
   798  					t.Errorf("Expected to find %s", f)
   799  				}
   800  			}
   801  		})
   802  	}
   803  }
   804  
   805  func testHCLOnlyExceptFlags(t *testing.T, args, present, notPresent []string, expectReturn int) {
   806  	c := &BuildCommand{
   807  		Meta: TestMetaFile(t),
   808  	}
   809  
   810  	defer cleanup()
   811  
   812  	finalArgs := []string{"-parallel-builds=1"}
   813  	finalArgs = append(finalArgs, args...)
   814  	finalArgs = append(finalArgs, testFixture("hcl-only-except"))
   815  
   816  	if code := c.Run(finalArgs); code != expectReturn {
   817  		fatalCommand(t, c.Meta)
   818  	}
   819  
   820  	for _, f := range notPresent {
   821  		if fileExists(f) {
   822  			t.Errorf("Expected NOT to find %s", f)
   823  		}
   824  	}
   825  	for _, f := range present {
   826  		if !fileExists(f) {
   827  			t.Errorf("Expected to find %s", f)
   828  		}
   829  	}
   830  }
   831  
   832  func TestHCL2PostProcessorForceFlag(t *testing.T) {
   833  	t.Helper()
   834  
   835  	UUID, _ := uuid.GenerateUUID()
   836  	// Manifest will only clean with force if the build's PACKER_RUN_UUID are different
   837  	t.Setenv("PACKER_RUN_UUID", UUID)
   838  
   839  	args := []string{
   840  		filepath.Join(testFixture("hcl"), "force.pkr.hcl"),
   841  	}
   842  	fCheck := fileCheck{
   843  		expectedContent: map[string]string{
   844  			"manifest.json": fmt.Sprintf(`{
   845    "builds": [
   846      {
   847        "name": "potato",
   848        "builder_type": "null",
   849        "files": null,
   850        "artifact_id": "Null",
   851        "packer_run_uuid": %q,
   852        "custom_data": null
   853      }
   854    ],
   855    "last_run_uuid": %q
   856  }`, UUID, UUID),
   857  		},
   858  	}
   859  	defer fCheck.cleanup(t)
   860  
   861  	c := &BuildCommand{
   862  		Meta: TestMetaFile(t),
   863  	}
   864  	if code := c.Run(args); code != 0 {
   865  		fatalCommand(t, c.Meta)
   866  	}
   867  	fCheck.verify(t, "")
   868  
   869  	// Second build should override previous manifest
   870  	UUID, _ = uuid.GenerateUUID()
   871  	t.Setenv("PACKER_RUN_UUID", UUID)
   872  
   873  	args = []string{
   874  		"-force",
   875  		filepath.Join(testFixture("hcl"), "force.pkr.hcl"),
   876  	}
   877  	fCheck = fileCheck{
   878  		expectedContent: map[string]string{
   879  			"manifest.json": fmt.Sprintf(`{
   880    "builds": [
   881      {
   882        "name": "potato",
   883        "builder_type": "null",
   884        "files": null,
   885        "artifact_id": "Null",
   886        "packer_run_uuid": %q,
   887        "custom_data": null
   888      }
   889    ],
   890    "last_run_uuid": %q
   891  }`, UUID, UUID),
   892  		},
   893  	}
   894  
   895  	c = &BuildCommand{
   896  		Meta: TestMetaFile(t),
   897  	}
   898  	if code := c.Run(args); code != 0 {
   899  		fatalCommand(t, c.Meta)
   900  	}
   901  	fCheck.verify(t, "")
   902  }
   903  
   904  func TestBuildCommand_HCLOnlyExceptOptions(t *testing.T) {
   905  	tests := []struct {
   906  		args         []string
   907  		present      []string
   908  		notPresent   []string
   909  		expectReturn int
   910  	}{
   911  		{
   912  			[]string{"-only=chocolate"},
   913  			[]string{},
   914  			[]string{"chocolate.txt", "vanilla.txt", "cherry.txt"},
   915  			1,
   916  		},
   917  		{
   918  			[]string{"-only=*chocolate*"},
   919  			[]string{"chocolate.txt"},
   920  			[]string{"vanilla.txt", "cherry.txt"},
   921  			0,
   922  		},
   923  		{
   924  			[]string{"-except=*chocolate*"},
   925  			[]string{"vanilla.txt", "cherry.txt"},
   926  			[]string{"chocolate.txt"},
   927  			0,
   928  		},
   929  		{
   930  			[]string{"-except=*ch*"},
   931  			[]string{"vanilla.txt"},
   932  			[]string{"chocolate.txt", "cherry.txt"},
   933  			0,
   934  		},
   935  		{
   936  			[]string{"-only=*chocolate*", "-only=*vanilla*"},
   937  			[]string{"chocolate.txt", "vanilla.txt"},
   938  			[]string{"cherry.txt"},
   939  			0,
   940  		},
   941  		{
   942  			[]string{"-except=*chocolate*", "-except=*vanilla*"},
   943  			[]string{"cherry.txt"},
   944  			[]string{"chocolate.txt", "vanilla.txt"},
   945  			0,
   946  		},
   947  		{
   948  			[]string{"-only=my_build.file.chocolate"},
   949  			[]string{"chocolate.txt"},
   950  			[]string{"vanilla.txt", "cherry.txt"},
   951  			0,
   952  		},
   953  		{
   954  			[]string{"-except=my_build.file.chocolate"},
   955  			[]string{"vanilla.txt", "cherry.txt"},
   956  			[]string{"chocolate.txt"},
   957  			0,
   958  		},
   959  		{
   960  			[]string{"-only=file.cherry"},
   961  			[]string{"cherry.txt"},
   962  			[]string{"vanilla.txt", "chocolate.txt"},
   963  			0,
   964  		},
   965  	}
   966  
   967  	for _, tt := range tests {
   968  		t.Run(fmt.Sprintf("%s", tt.args), func(t *testing.T) {
   969  			testHCLOnlyExceptFlags(t, tt.args, tt.present, tt.notPresent, tt.expectReturn)
   970  		})
   971  	}
   972  }
   973  
   974  func TestBuildWithNonExistingBuilder(t *testing.T) {
   975  	c := &BuildCommand{
   976  		Meta: TestMetaFile(t),
   977  	}
   978  
   979  	args := []string{
   980  		"-parallel-builds=1",
   981  		`-except=`,
   982  		filepath.Join(testFixture("build-only"), "not-found.json"),
   983  	}
   984  
   985  	defer cleanup()
   986  
   987  	if code := c.Run(args); code != 1 {
   988  		t.Errorf("Expected to find exit code 1, found %d", code)
   989  	}
   990  	if !fileExists("chocolate.txt") {
   991  		t.Errorf("Expected to find chocolate.txt")
   992  	}
   993  	if fileExists("vanilla.txt") {
   994  		t.Errorf("NOT expected to find vanilla.tx")
   995  	}
   996  }
   997  
   998  func run(t *testing.T, args []string, expectedCode int) {
   999  	t.Helper()
  1000  
  1001  	c := &BuildCommand{
  1002  		Meta: TestMetaFile(t),
  1003  	}
  1004  
  1005  	if code := c.Run(args); code != expectedCode {
  1006  		fatalCommand(t, c.Meta)
  1007  	}
  1008  }
  1009  
  1010  type fileCheck struct {
  1011  	expected, notExpected []string
  1012  	expectedContent       map[string]string
  1013  }
  1014  
  1015  func (fc fileCheck) cleanup(t *testing.T) {
  1016  	for _, file := range fc.expectedFiles() {
  1017  		t.Logf("removing %v", file)
  1018  		if err := os.Remove(file); err != nil {
  1019  			t.Errorf("failed to remove file %s: %v", file, err)
  1020  		}
  1021  	}
  1022  }
  1023  
  1024  func (fc fileCheck) expectedFiles() []string {
  1025  	expected := fc.expected
  1026  	for file := range fc.expectedContent {
  1027  		expected = append(expected, file)
  1028  	}
  1029  	return expected
  1030  }
  1031  
  1032  func (fc fileCheck) verify(t *testing.T, dir string) {
  1033  	for _, f := range fc.expectedFiles() {
  1034  		if _, err := os.Stat(filepath.Join(dir, f)); err != nil {
  1035  			t.Errorf("Expected to find %s: %v", f, err)
  1036  		}
  1037  	}
  1038  	for _, f := range fc.notExpected {
  1039  		if _, err := os.Stat(filepath.Join(dir, f)); err == nil {
  1040  			t.Errorf("Expected to not find %s", f)
  1041  		}
  1042  	}
  1043  	for file, expectedContent := range fc.expectedContent {
  1044  		content, err := os.ReadFile(filepath.Join(dir, file))
  1045  		if err != nil {
  1046  			t.Fatalf("os.ReadFile: %v", err)
  1047  		}
  1048  		if diff := cmp.Diff(expectedContent, string(content)); diff != "" {
  1049  			t.Errorf("content of %s differs: %s", file, diff)
  1050  		}
  1051  	}
  1052  }
  1053  
  1054  func cleanup(moreFiles ...string) {
  1055  	os.RemoveAll("chocolate.txt")
  1056  	os.RemoveAll("vanilla.txt")
  1057  	os.RemoveAll("cherry.txt")
  1058  	os.RemoveAll("apple.txt")
  1059  	os.RemoveAll("peach.txt")
  1060  	os.RemoveAll("banana.txt")
  1061  	os.RemoveAll("pear.txt")
  1062  	os.RemoveAll("tomato.txt")
  1063  	os.RemoveAll("unnamed.txt")
  1064  	os.RemoveAll("roses.txt")
  1065  	os.RemoveAll("fuchsias.txt")
  1066  	os.RemoveAll("lilas.txt")
  1067  	os.RemoveAll("campanules.txt")
  1068  	os.RemoveAll("ducky.txt")
  1069  	os.RemoveAll("banana.txt")
  1070  	for _, file := range moreFiles {
  1071  		os.RemoveAll(file)
  1072  	}
  1073  }
  1074  
  1075  func TestBuildCommand_ParseArgs(t *testing.T) {
  1076  	defaultMeta := TestMetaFile(t)
  1077  	type fields struct {
  1078  		Meta Meta
  1079  	}
  1080  	type args struct {
  1081  		args []string
  1082  	}
  1083  	tests := []struct {
  1084  		fields       fields
  1085  		args         args
  1086  		wantCfg      *BuildArgs
  1087  		wantExitCode int
  1088  	}{
  1089  		{fields{defaultMeta},
  1090  			args{[]string{"file.json"}},
  1091  			&BuildArgs{
  1092  				MetaArgs:       MetaArgs{Path: "file.json"},
  1093  				ParallelBuilds: math.MaxInt64,
  1094  				Color:          true,
  1095  			},
  1096  			0,
  1097  		},
  1098  		{fields{defaultMeta},
  1099  			args{[]string{"-parallel-builds=10", "file.json"}},
  1100  			&BuildArgs{
  1101  				MetaArgs:       MetaArgs{Path: "file.json"},
  1102  				ParallelBuilds: 10,
  1103  				Color:          true,
  1104  			},
  1105  			0,
  1106  		},
  1107  		{fields{defaultMeta},
  1108  			args{[]string{"-parallel-builds=1", "file.json"}},
  1109  			&BuildArgs{
  1110  				MetaArgs:       MetaArgs{Path: "file.json"},
  1111  				ParallelBuilds: 1,
  1112  				Color:          true,
  1113  			},
  1114  			0,
  1115  		},
  1116  		{fields{defaultMeta},
  1117  			args{[]string{"-parallel-builds=5", "file.json"}},
  1118  			&BuildArgs{
  1119  				MetaArgs:       MetaArgs{Path: "file.json"},
  1120  				ParallelBuilds: 5,
  1121  				Color:          true,
  1122  			},
  1123  			0,
  1124  		},
  1125  		{fields{defaultMeta},
  1126  			args{[]string{"-parallel-builds=1", "-parallel-builds=5", "otherfile.json"}},
  1127  			&BuildArgs{
  1128  				MetaArgs:       MetaArgs{Path: "otherfile.json"},
  1129  				ParallelBuilds: 5,
  1130  				Color:          true,
  1131  			},
  1132  			0,
  1133  		},
  1134  	}
  1135  	for _, tt := range tests {
  1136  		t.Run(fmt.Sprintf("%s", tt.args.args), func(t *testing.T) {
  1137  			c := &BuildCommand{
  1138  				Meta: tt.fields.Meta,
  1139  			}
  1140  			gotCfg, gotExitCode := c.ParseArgs(tt.args.args)
  1141  			if diff := cmp.Diff(gotCfg, tt.wantCfg); diff != "" {
  1142  				t.Fatalf("BuildCommand.ParseArgs() unexpected cfg %s", diff)
  1143  			}
  1144  			if gotExitCode != tt.wantExitCode {
  1145  				t.Fatalf("BuildCommand.ParseArgs() gotExitCode = %v, want %v", gotExitCode, tt.wantExitCode)
  1146  			}
  1147  		})
  1148  	}
  1149  }
  1150  
  1151  // TestProvisionerOnlyExcept checks that only/except blocks in provisioners/post-processors behave as expected
  1152  func TestProvisionerAndPostProcessorOnlyExcept(t *testing.T) {
  1153  	tests := []struct {
  1154  		name         string
  1155  		args         []string
  1156  		expectedCode int
  1157  		outputCheck  func(string, string) error
  1158  	}{
  1159  		{
  1160  			"json - only named build",
  1161  			[]string{
  1162  				"-only", "packer",
  1163  				testFixture("provisioners", "provisioner-only-except.json"),
  1164  			},
  1165  			0,
  1166  			func(out, _ string) error {
  1167  				if !strings.Contains(out, "packer provisioner packer and null") {
  1168  					return fmt.Errorf("missing expected provisioner output")
  1169  				}
  1170  
  1171  				if !strings.Contains(out, "packer post-processor packer and null") {
  1172  					return fmt.Errorf("missing expected post-processor output")
  1173  				}
  1174  
  1175  				if strings.Contains(out, "null post-processor") || strings.Contains(out, "null provisioner") {
  1176  					return fmt.Errorf("found traces of unnamed provisioner/post-processor, should not")
  1177  				}
  1178  
  1179  				return nil
  1180  			},
  1181  		},
  1182  		{
  1183  			"json - only unnamed build",
  1184  			[]string{
  1185  				"-only", "null",
  1186  				testFixture("provisioners", "provisioner-only-except.json"),
  1187  			},
  1188  			0,
  1189  			func(out, _ string) error {
  1190  				if !strings.Contains(out, "null provisioner null and null") {
  1191  					return fmt.Errorf("missing expected provisioner output")
  1192  				}
  1193  
  1194  				if !strings.Contains(out, "null post-processor null and null") {
  1195  					return fmt.Errorf("missing expected post-processor output")
  1196  				}
  1197  
  1198  				if strings.Contains(out, "packer post-processor") || strings.Contains(out, "packer provisioner") {
  1199  					return fmt.Errorf("found traces of named provisioner/post-processor, should not")
  1200  				}
  1201  
  1202  				return nil
  1203  			},
  1204  		},
  1205  		{
  1206  			"hcl - only one source build",
  1207  			[]string{
  1208  				"-only", "null.packer",
  1209  				testFixture("provisioners", "provisioner-only-except.pkr.hcl"),
  1210  			},
  1211  			0,
  1212  			func(out, _ string) error {
  1213  				if !strings.Contains(out, "packer provisioner packer and null") {
  1214  					return fmt.Errorf("missing expected provisioner output")
  1215  				}
  1216  
  1217  				if !strings.Contains(out, "packer post-processor packer and null") {
  1218  					return fmt.Errorf("missing expected post-processor output")
  1219  				}
  1220  
  1221  				if strings.Contains(out, "other post-processor") || strings.Contains(out, "other provisioner") {
  1222  					return fmt.Errorf("found traces of other provisioner/post-processor, should not")
  1223  				}
  1224  
  1225  				return nil
  1226  			},
  1227  		},
  1228  		{
  1229  			"hcl - only other build",
  1230  			[]string{
  1231  				"-only", "null.other",
  1232  				testFixture("provisioners", "provisioner-only-except.pkr.hcl"),
  1233  			},
  1234  			0,
  1235  			func(out, _ string) error {
  1236  				if !strings.Contains(out, "other provisioner other and null") {
  1237  					return fmt.Errorf("missing expected provisioner output")
  1238  				}
  1239  
  1240  				if !strings.Contains(out, "other post-processor other and null") {
  1241  					return fmt.Errorf("missing expected post-processor output")
  1242  				}
  1243  
  1244  				if strings.Contains(out, "packer post-processor") || strings.Contains(out, "packer provisioner") {
  1245  					return fmt.Errorf("found traces of \"packer\" source provisioner/post-processor, should not")
  1246  				}
  1247  
  1248  				return nil
  1249  			},
  1250  		},
  1251  	}
  1252  
  1253  	for _, tt := range tests {
  1254  		t.Run(tt.name, func(t *testing.T) {
  1255  			c := &BuildCommand{
  1256  				Meta: TestMetaFile(t),
  1257  			}
  1258  
  1259  			exitCode := c.Run(tt.args)
  1260  			if exitCode != tt.expectedCode {
  1261  				t.Errorf("process exit code mismatch: expected %d, got %d",
  1262  					tt.expectedCode,
  1263  					exitCode)
  1264  			}
  1265  
  1266  			out, stderr := GetStdoutAndErrFromTestMeta(t, c.Meta)
  1267  			err := tt.outputCheck(out, stderr)
  1268  			if err != nil {
  1269  				if len(out) != 0 {
  1270  					t.Logf("command stdout: %q", out)
  1271  				}
  1272  
  1273  				if len(stderr) != 0 {
  1274  					t.Logf("command stderr: %q", stderr)
  1275  				}
  1276  				t.Error(err.Error())
  1277  			}
  1278  		})
  1279  	}
  1280  }
  1281  
  1282  // TestBuildCmd aims to test the build command, with output validation
  1283  func TestBuildCmd(t *testing.T) {
  1284  	tests := []struct {
  1285  		name         string
  1286  		args         []string
  1287  		expectedCode int
  1288  		outputCheck  func(string, string) error
  1289  	}{
  1290  		{
  1291  			name: "hcl - no build block error",
  1292  			args: []string{
  1293  				testFixture("hcl", "no_build.pkr.hcl"),
  1294  			},
  1295  			expectedCode: 1,
  1296  			outputCheck: func(_, err string) error {
  1297  				if !strings.Contains(err, "Error: Missing build block") {
  1298  					return fmt.Errorf("expected 'Error: Missing build block' in output, did not find it")
  1299  				}
  1300  
  1301  				nbErrs := strings.Count(err, "Error: ")
  1302  				if nbErrs != 1 {
  1303  					return fmt.Errorf(
  1304  						"error: too many errors in stdout for build block, expected 1, got %d",
  1305  						nbErrs)
  1306  				}
  1307  
  1308  				return nil
  1309  			},
  1310  		},
  1311  		{
  1312  			name: "hcl - undefined var set in pkrvars",
  1313  			args: []string{
  1314  				testFixture("hcl", "variables", "ref_non_existing"),
  1315  			},
  1316  			expectedCode: 0,
  1317  			outputCheck: func(out, err string) error {
  1318  				nbWarns := strings.Count(out, "Warning: ")
  1319  				if nbWarns != 0 {
  1320  					return fmt.Errorf(
  1321  						"error: too many warnings in build output, expected 0, got %d",
  1322  						nbWarns)
  1323  				}
  1324  
  1325  				nbErrs := strings.Count(err, "Error: ")
  1326  				if nbErrs != 0 {
  1327  					return fmt.Errorf("error: expected build to succeed without errors, got %d",
  1328  						nbErrs)
  1329  				}
  1330  				return nil
  1331  			},
  1332  		},
  1333  		{
  1334  			name: "hcl - build block without source",
  1335  			args: []string{
  1336  				testFixture("hcl", "build_no_source.pkr.hcl"),
  1337  			},
  1338  			expectedCode: 1,
  1339  			outputCheck: func(_, err string) error {
  1340  				if !strings.Contains(err, "Error: missing source reference") {
  1341  					return fmt.Errorf("expected 'Error: missing source reference' in output, did not find it")
  1342  				}
  1343  
  1344  				nbErrs := strings.Count(err, "Error: ")
  1345  				if nbErrs != 1 {
  1346  					return fmt.Errorf(
  1347  						"error: too many errors in stderr for build, expected 1, got %d",
  1348  						nbErrs)
  1349  				}
  1350  
  1351  				logRegex := regexp.MustCompile("on.*build_no_source.pkr.hcl line 1")
  1352  				if !logRegex.MatchString(err) {
  1353  					return fmt.Errorf("error: missing context for error message")
  1354  				}
  1355  
  1356  				return nil
  1357  			},
  1358  		},
  1359  		{
  1360  			name: "hcl - exclude post-processor, expect no warning",
  1361  			args: []string{
  1362  				"-except", "manifest",
  1363  				testFixture("hcl", "test_except_manifest.pkr.hcl"),
  1364  			},
  1365  			expectedCode: 0,
  1366  			outputCheck: func(out, err string) error {
  1367  				for _, stream := range []string{out, err} {
  1368  					if strings.Contains(stream, "Warning: an 'except' option was passed, but did not match any build") {
  1369  						return fmt.Errorf("Unexpected warning for build no match with except")
  1370  					}
  1371  
  1372  					if strings.Contains(stream, "Running post-processor:") {
  1373  						return fmt.Errorf("Should not run post-processors, but found one")
  1374  					}
  1375  				}
  1376  
  1377  				return nil
  1378  			},
  1379  		},
  1380  	}
  1381  
  1382  	for _, tt := range tests {
  1383  		t.Run(tt.name, func(t *testing.T) {
  1384  			c := &BuildCommand{
  1385  				Meta: TestMetaFile(t),
  1386  			}
  1387  
  1388  			exitCode := c.Run(tt.args)
  1389  			if exitCode != tt.expectedCode {
  1390  				t.Errorf("process exit code mismatch: expected %d, got %d",
  1391  					tt.expectedCode,
  1392  					exitCode)
  1393  			}
  1394  
  1395  			out, stderr := GetStdoutAndErrFromTestMeta(t, c.Meta)
  1396  			err := tt.outputCheck(out, stderr)
  1397  			if err != nil {
  1398  				if len(out) != 0 {
  1399  					t.Logf("command stdout: %q", out)
  1400  				}
  1401  
  1402  				if len(stderr) != 0 {
  1403  					t.Logf("command stderr: %q", stderr)
  1404  				}
  1405  				t.Error(err.Error())
  1406  			}
  1407  		})
  1408  	}
  1409  }