github.com/yogeshkumararora/slsa-github-generator@v1.10.1-0.20240520161934-11278bd5afb4/internal/builders/go/main_test.go (about)

     1  // Copyright 2023 SLSA Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package main
    16  
    17  import (
    18  	"bufio"
    19  	"errors"
    20  	"os"
    21  	"os/exec"
    22  	"path/filepath"
    23  	"regexp"
    24  	"testing"
    25  
    26  	"github.com/google/go-cmp/cmp"
    27  	"github.com/google/go-cmp/cmp/cmpopts"
    28  
    29  	"github.com/yogeshkumararora/slsa-github-generator/internal/builders/go/pkg"
    30  	"github.com/yogeshkumararora/slsa-github-generator/internal/utils"
    31  )
    32  
    33  func checkWorkingDir(t *testing.T, wd, expected string) {
    34  	var expectedWd string
    35  	var err error
    36  	if expected != "" {
    37  		expectedWd, err = filepath.Abs(expected)
    38  		if err != nil {
    39  			t.Errorf("Abs: %v", err)
    40  		}
    41  	} else {
    42  		expectedWd, err = os.Getwd()
    43  		if err != nil {
    44  			t.Errorf("Getwd: %v", err)
    45  		}
    46  	}
    47  
    48  	if expectedWd != wd {
    49  		t.Errorf(cmp.Diff(wd, expectedWd))
    50  	}
    51  }
    52  
    53  func errInvalidDirectoryFunc(t *testing.T, got error) {
    54  	want := pkg.ErrInvalidDirectory
    55  	if !errors.Is(got, want) {
    56  		t.Fatalf("unexpected error: %v", cmp.Diff(got, want, cmpopts.EquateErrors()))
    57  	}
    58  }
    59  
    60  func errUnsupportedVersionFunc(t *testing.T, got error) {
    61  	want := pkg.ErrUnsupportedVersion
    62  	if !errors.Is(got, want) {
    63  		t.Fatalf("unexpected error: %v", cmp.Diff(got, want, cmpopts.EquateErrors()))
    64  	}
    65  }
    66  
    67  func errInvalidEnvironmentVariableFunc(t *testing.T, got error) {
    68  	want := pkg.ErrInvalidEnvironmentVariable
    69  	if !errors.Is(got, want) {
    70  		t.Fatalf("unexpected error: %v", cmp.Diff(got, want, cmpopts.EquateErrors()))
    71  	}
    72  }
    73  
    74  func Test_runBuild(t *testing.T) {
    75  	tests := []struct {
    76  		subject    string
    77  		name       string
    78  		config     string
    79  		evalEnvs   string
    80  		workingDir string
    81  		err        func(*testing.T, error)
    82  		commands   []string
    83  		envs       []string
    84  	}{
    85  		{
    86  			name:     "two ldflags",
    87  			subject:  "binary-linux-amd64",
    88  			config:   "./testdata/two-ldflags.yml",
    89  			evalEnvs: "VERSION_LDFLAGS:bla, ELSE:else",
    90  			commands: []string{
    91  				"-trimpath",
    92  				"-tags=netgo",
    93  				"-ldflags=bla something-else",
    94  				"-o",
    95  				"binary-linux-amd64",
    96  			},
    97  			envs: []string{
    98  				"GOOS=linux",
    99  				"GOARCH=amd64",
   100  				"GO111MODULE=on",
   101  				"CGO_ENABLED=0",
   102  			},
   103  		},
   104  		{
   105  			name:     "two ldflags empty env",
   106  			subject:  "binary-linux-amd64",
   107  			config:   "./testdata/two-ldflags-emptyenv.yml",
   108  			evalEnvs: "VERSION_LDFLAGS:bla, ELSE:else",
   109  			commands: []string{
   110  				"-trimpath",
   111  				"-tags=netgo",
   112  				"-ldflags=bla something-else",
   113  				"-o",
   114  				"binary-linux-amd64",
   115  			},
   116  			envs: []string{
   117  				"GOOS=linux",
   118  				"GOARCH=amd64",
   119  			},
   120  		},
   121  		{
   122  			name:     "two ldflags no env",
   123  			subject:  "binary-linux-amd64",
   124  			config:   "./testdata/two-ldflags-noenv.yml",
   125  			evalEnvs: "VERSION_LDFLAGS:bla, ELSE:else",
   126  			commands: []string{
   127  				"-trimpath",
   128  				"-tags=netgo",
   129  				"-ldflags=bla something-else",
   130  				"-o",
   131  				"binary-linux-amd64",
   132  			},
   133  			envs: []string{
   134  				"GOOS=linux",
   135  				"GOARCH=amd64",
   136  			},
   137  		},
   138  		{
   139  			name:     "two ldflags empty flags",
   140  			subject:  "binary-linux-amd64",
   141  			config:   "./testdata/two-ldflags-emptyflags.yml",
   142  			evalEnvs: "VERSION_LDFLAGS:bla, ELSE:else",
   143  			commands: []string{
   144  				"-ldflags=bla something-else",
   145  				"-o",
   146  				"binary-linux-amd64",
   147  			},
   148  			envs: []string{
   149  				"GOOS=linux",
   150  				"GOARCH=amd64",
   151  				"GO111MODULE=on",
   152  				"CGO_ENABLED=0",
   153  			},
   154  		},
   155  		{
   156  			name:     "two ldflags no flags",
   157  			subject:  "binary-linux-amd64",
   158  			config:   "./testdata/two-ldflags-noflags.yml",
   159  			evalEnvs: "VERSION_LDFLAGS:bla, ELSE:else",
   160  			commands: []string{
   161  				"-ldflags=bla something-else",
   162  				"-o",
   163  				"binary-linux-amd64",
   164  			},
   165  			envs: []string{
   166  				"GOOS=linux",
   167  				"GOARCH=amd64",
   168  				"GO111MODULE=on",
   169  				"CGO_ENABLED=0",
   170  			},
   171  		},
   172  		{
   173  			name:     "one ldflags",
   174  			subject:  "binary-linux-amd64",
   175  			config:   "./testdata/one-ldflags.yml",
   176  			evalEnvs: "VERSION_LDFLAGS:bla, ELSE:else",
   177  			commands: []string{
   178  				"-trimpath",
   179  				"-tags=netgo",
   180  				"-ldflags=something-else",
   181  				"-o",
   182  				"binary-linux-amd64",
   183  			},
   184  			envs: []string{
   185  				"GOOS=linux",
   186  				"GOARCH=amd64",
   187  				"GO111MODULE=on",
   188  				"CGO_ENABLED=0",
   189  			},
   190  		},
   191  		{
   192  			name:     "no ldflags",
   193  			subject:  "binary-linux-amd64",
   194  			config:   "./testdata/two-ldflags-noldflags.yml",
   195  			evalEnvs: "VERSION_LDFLAGS:bla, ELSE:else",
   196  			commands: []string{
   197  				"-trimpath",
   198  				"-tags=netgo",
   199  				"-o",
   200  				"binary-linux-amd64",
   201  			},
   202  			envs: []string{
   203  				"GOOS=linux",
   204  				"GOARCH=amd64",
   205  				"GO111MODULE=on",
   206  				"CGO_ENABLED=0",
   207  			},
   208  		},
   209  		{
   210  			name:     "empty ldflags",
   211  			subject:  "binary-linux-amd64",
   212  			config:   "./testdata/emptyldflags.yml",
   213  			evalEnvs: "VERSION_LDFLAGS:bla, ELSE:else",
   214  			commands: []string{
   215  				"-trimpath",
   216  				"-tags=netgo",
   217  				"-o",
   218  				"binary-linux-amd64",
   219  			},
   220  			envs: []string{
   221  				"GOOS=linux",
   222  				"GOARCH=amd64",
   223  				"GO111MODULE=on",
   224  				"CGO_ENABLED=0",
   225  			},
   226  		},
   227  		{
   228  			name:     "valid main",
   229  			subject:  "binary-linux-amd64",
   230  			config:   "./testdata/valid-main.yml",
   231  			evalEnvs: "VERSION_LDFLAGS:bla, ELSE:else",
   232  			commands: []string{
   233  				"-trimpath",
   234  				"-tags=netgo",
   235  				"-ldflags=bla something-else",
   236  				"-o",
   237  				"binary-linux-amd64",
   238  				"./path/to/main.go",
   239  			},
   240  			envs: []string{
   241  				"GOOS=linux",
   242  				"GOARCH=amd64",
   243  				"GO111MODULE=on",
   244  				"CGO_ENABLED=0",
   245  			},
   246  		},
   247  		{
   248  			name:     "valid working dir",
   249  			subject:  "binary-linux-amd64",
   250  			config:   "./testdata/valid-working-dir.yml",
   251  			evalEnvs: "VERSION_LDFLAGS:bla, ELSE:else",
   252  			commands: []string{
   253  				"-trimpath",
   254  				"-tags=netgo",
   255  				"-ldflags=bla something-else",
   256  				"-o",
   257  				"binary-linux-amd64",
   258  				"main.go",
   259  			},
   260  			envs: []string{
   261  				"GOOS=linux",
   262  				"GOARCH=amd64",
   263  				"GO111MODULE=on",
   264  				"CGO_ENABLED=0",
   265  			},
   266  			workingDir: "./valid/path/",
   267  		},
   268  		{
   269  			name:   "invalid main",
   270  			config: "./pkg/testdata/releaser-invalid-main.yml",
   271  			err:    errInvalidDirectoryFunc,
   272  		},
   273  		{
   274  			name:   "missing version",
   275  			config: "./pkg/testdata/releaser-noversion.yml",
   276  			err:    errUnsupportedVersionFunc,
   277  		},
   278  		{
   279  			name:   "invalid version",
   280  			config: "./pkg/testdata/releaser-invalid-version.yml",
   281  			err:    errUnsupportedVersionFunc,
   282  		},
   283  		{
   284  			name:   "invalid envs",
   285  			config: "./pkg/testdata/releaser-invalid-envs.yml",
   286  			err:    errInvalidEnvironmentVariableFunc,
   287  		},
   288  		{
   289  			name:   "invalid path",
   290  			config: "../pkg/testdata/releaser-invalid-main.yml",
   291  			err:    errInvalidDirectoryFunc,
   292  		},
   293  		{
   294  			name:   "invalid dir path",
   295  			config: "../pkg/testdata/releaser-invalid-dir.yml",
   296  			err:    errInvalidDirectoryFunc,
   297  		},
   298  	}
   299  
   300  	for _, tt := range tests {
   301  		tt := tt // Re-initializing variable so it is not changed while executing the closure below
   302  		t.Run(tt.name, func(t *testing.T) {
   303  			// *** WARNING: do not enable t.Parallel(), because we're writing to environment variables ***.
   304  			file, err := os.CreateTemp("", "")
   305  			if err != nil {
   306  				t.Fatalf("unable to create a temp env file: %s", err)
   307  			}
   308  			defer os.Remove(file.Name())
   309  			// http://craigwickesser.com/2015/01/capture-stdout-in-go/
   310  
   311  			t.Setenv("GITHUB_OUTPUT", file.Name())
   312  
   313  			err = runBuild(true,
   314  				tt.config,
   315  				tt.evalEnvs)
   316  
   317  			if tt.err != nil {
   318  				tt.err(t, err)
   319  			}
   320  
   321  			if err != nil {
   322  				return
   323  			}
   324  
   325  			if ret, err := file.Seek(0, 0); ret != 0 || err != nil {
   326  				t.Errorf("seek to zero")
   327  			}
   328  			cmd, env, subject, wd, err := extract(file)
   329  			if err != nil {
   330  				t.Errorf("extract: %v", err)
   331  			}
   332  
   333  			goc, err := exec.LookPath("go")
   334  			if err != nil {
   335  				t.Errorf("exec.LookPath: %v", err)
   336  			}
   337  
   338  			if !cmp.Equal(subject, tt.subject) {
   339  				t.Errorf(cmp.Diff(subject, tt.subject))
   340  			}
   341  
   342  			commands := append([]string{goc, "build", "-mod=vendor"}, tt.commands...)
   343  			if !cmp.Equal(cmd, commands) {
   344  				t.Errorf(cmp.Diff(cmd, commands))
   345  			}
   346  
   347  			checkWorkingDir(t, wd, tt.workingDir)
   348  
   349  			sorted := cmpopts.SortSlices(func(a, b string) bool { return a < b })
   350  			if !cmp.Equal(env, tt.envs, sorted) {
   351  				t.Errorf(cmp.Diff(env, tt.envs))
   352  			}
   353  		})
   354  	}
   355  }
   356  
   357  func extract(file *os.File) ([]string, []string, string, string, error) {
   358  	rsubject := regexp.MustCompile(`^go-binary-name=(.*)$`)
   359  	rcmd := regexp.MustCompile(`^go-command=(.*)$`)
   360  	renv := regexp.MustCompile(`^go-env=(.*)$`)
   361  	rwd := regexp.MustCompile(`^go-working-dir=(.*)$`)
   362  	var subject string
   363  	var scmd string
   364  	var senv string
   365  	var wd string
   366  
   367  	scanner := bufio.NewScanner(file)
   368  	for scanner.Scan() {
   369  		n := rsubject.FindStringSubmatch(scanner.Text())
   370  		if len(n) > 1 {
   371  			subject = n[1]
   372  		}
   373  
   374  		c := rcmd.FindStringSubmatch(scanner.Text())
   375  		if len(c) > 1 {
   376  			scmd = c[1]
   377  		}
   378  
   379  		e := renv.FindStringSubmatch(scanner.Text())
   380  		if len(e) > 1 {
   381  			senv = e[1]
   382  		}
   383  
   384  		w := rwd.FindStringSubmatch(scanner.Text())
   385  		if len(w) > 1 {
   386  			wd = w[1]
   387  		}
   388  
   389  		if subject != "" && scmd != "" && senv != "" && wd != "" {
   390  			break
   391  		}
   392  	}
   393  	if err := scanner.Err(); err != nil {
   394  		return []string{}, []string{}, "", "", err
   395  	}
   396  
   397  	cmd, err := utils.UnmarshalList(scmd)
   398  	if err != nil {
   399  		return []string{}, []string{}, "", "", err
   400  	}
   401  
   402  	env, err := utils.UnmarshalList(senv)
   403  	if err != nil {
   404  		return []string{}, []string{}, "", "", err
   405  	}
   406  
   407  	return cmd, env, subject, wd, nil
   408  }