github.com/osdi23p228/fabric@v0.0.0-20221218062954-77808885f5db/core/chaincode/platforms/golang/platform_test.go (about)

     1  /*
     2  Copyright IBM Corp. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package golang
     8  
     9  import (
    10  	"archive/tar"
    11  	"bytes"
    12  	"compress/gzip"
    13  	"fmt"
    14  	"io"
    15  	"io/ioutil"
    16  	"os"
    17  	"path/filepath"
    18  	"strconv"
    19  	"strings"
    20  	"testing"
    21  
    22  	pb "github.com/hyperledger/fabric-protos-go/peer"
    23  	"github.com/osdi23p228/fabric/core/chaincode/platforms/util"
    24  	"github.com/osdi23p228/fabric/core/config/configtest"
    25  	"github.com/pkg/errors"
    26  	"github.com/spf13/viper"
    27  	"github.com/stretchr/testify/assert"
    28  	"github.com/stretchr/testify/require"
    29  )
    30  
    31  func init() {
    32  	// Significantly reduce execution time of deployment payload tests.
    33  	gzipCompressionLevel = gzip.NoCompression
    34  }
    35  
    36  func generateFakeCDS(ccname, path, file string, mode int64) (*pb.ChaincodeDeploymentSpec, error) {
    37  	codePackage := bytes.NewBuffer(nil)
    38  	gw := gzip.NewWriter(codePackage)
    39  	tw := tar.NewWriter(gw)
    40  
    41  	payload := make([]byte, 25)
    42  	err := tw.WriteHeader(&tar.Header{Name: file, Mode: mode, Size: int64(len(payload))})
    43  	if err != nil {
    44  		return nil, err
    45  	}
    46  	if !strings.HasSuffix(file, "/") {
    47  		_, err = tw.Write(payload)
    48  		if err != nil {
    49  			return nil, err
    50  		}
    51  	}
    52  
    53  	tw.Close()
    54  	gw.Close()
    55  
    56  	cds := &pb.ChaincodeDeploymentSpec{
    57  		ChaincodeSpec: &pb.ChaincodeSpec{
    58  			ChaincodeId: &pb.ChaincodeID{
    59  				Name: ccname,
    60  				Path: path,
    61  			},
    62  		},
    63  		CodePackage: codePackage.Bytes(),
    64  	}
    65  
    66  	return cds, nil
    67  }
    68  
    69  func TestName(t *testing.T) {
    70  	platform := &Platform{}
    71  	assert.Equal(t, "GOLANG", platform.Name())
    72  }
    73  
    74  func TestValidatePath(t *testing.T) {
    75  	reset := setupGopath(t, "testdata")
    76  	defer reset()
    77  
    78  	var tests = []struct {
    79  		path string
    80  		succ bool
    81  	}{
    82  		{path: "http://example.com/", succ: false},
    83  		{path: "./testdata/missing", succ: false},
    84  		{path: "chaincodes/noop", succ: true},
    85  		{path: "testdata/ccmodule", succ: true},
    86  	}
    87  
    88  	for _, tt := range tests {
    89  		platform := &Platform{}
    90  		err := platform.ValidatePath(tt.path)
    91  		if tt.succ {
    92  			assert.NoError(t, err, "expected %s to be a valid path", tt.path)
    93  		} else {
    94  			assert.Errorf(t, err, "expected %s to be an invalid path", tt.path)
    95  		}
    96  	}
    97  }
    98  
    99  func TestNormalizePath(t *testing.T) {
   100  	tempdir, err := ioutil.TempDir("", "normalize-path")
   101  	require.NoError(t, err, "failed to create temporary directory")
   102  	defer os.RemoveAll(tempdir)
   103  
   104  	var tests = []struct {
   105  		path   string
   106  		result string
   107  	}{
   108  		{path: "github.com/osdi23p228/fabric/cmd/peer", result: "github.com/osdi23p228/fabric/cmd/peer"},
   109  		{path: "testdata/ccmodule", result: "ccmodule"},
   110  		{path: "missing", result: "missing"},
   111  		{path: tempdir, result: tempdir}, // /dev/null is returned from `go env GOMOD`
   112  	}
   113  	for i, tt := range tests {
   114  		t.Run(strconv.Itoa(i), func(t *testing.T) {
   115  			platform := &Platform{}
   116  			result, err := platform.NormalizePath(tt.path)
   117  			assert.NoError(t, err, "normalize path failed")
   118  			assert.Equalf(t, tt.result, result, "want result %s got %s", tt.result, result)
   119  		})
   120  	}
   121  }
   122  
   123  // copied from the tar package
   124  const (
   125  	c_ISUID = 04000   // Set uid
   126  	c_ISGID = 02000   // Set gid
   127  	c_ISREG = 0100000 // Regular file
   128  )
   129  
   130  func TestValidateCodePackage(t *testing.T) {
   131  	tests := []struct {
   132  		name            string
   133  		path            string
   134  		file            string
   135  		mode            int64
   136  		successExpected bool
   137  	}{
   138  		{name: "NoCode", path: "path/to/somewhere", file: "/src/path/to/somewhere/main.go", mode: c_ISREG | 0400, successExpected: false},
   139  		{name: "NoCode", path: "path/to/somewhere", file: "src/path/to/somewhere/main.go", mode: c_ISREG | 0400, successExpected: true},
   140  		{name: "NoCode", path: "path/to/somewhere", file: "src/path/to/somewhere/main.go", mode: c_ISREG | 0644, successExpected: true},
   141  		{name: "NoCode", path: "path/to/somewhere", file: "src/path/to/somewhere/main.go", mode: c_ISREG | 0755, successExpected: true},
   142  		{name: "NoCode", path: "path/to/directory/", file: "src/path/to/directory/", mode: c_ISDIR | 0755, successExpected: true},
   143  		{name: "NoCode", path: "path/to/directory", file: "src/path/to/directory", mode: c_ISDIR | 0755, successExpected: true},
   144  		{name: "NoCode", path: "path/to/setuid", file: "src/path/to/setuid", mode: c_ISUID | 0755, successExpected: false},
   145  		{name: "NoCode", path: "path/to/setgid", file: "src/path/to/setgid", mode: c_ISGID | 0755, successExpected: false},
   146  		{name: "NoCode", path: "path/to/sticky/", file: "src/path/to/sticky/", mode: c_ISDIR | c_ISGID | 0755, successExpected: false},
   147  		{name: "NoCode", path: "path/to/somewhere", file: "META-INF/path/to/a/meta3", mode: 0100400, successExpected: true},
   148  	}
   149  
   150  	for _, tt := range tests {
   151  		cds, err := generateFakeCDS(tt.name, tt.path, tt.file, tt.mode)
   152  		assert.NoError(t, err, "failed to generate fake cds")
   153  
   154  		platform := &Platform{}
   155  		err = platform.ValidateCodePackage(cds.CodePackage)
   156  		if tt.successExpected {
   157  			assert.NoError(t, err, "expected success for path: %s, file: %s", tt.path, tt.file)
   158  		} else {
   159  			assert.Errorf(t, err, "expected error for path: %s, file: %s", tt.path, tt.file)
   160  		}
   161  	}
   162  }
   163  
   164  func Test_findSource(t *testing.T) {
   165  	t.Run("Gopath", func(t *testing.T) {
   166  		source, err := findSource(&CodeDescriptor{
   167  			Module:       false,
   168  			Source:       filepath.FromSlash("testdata/src/chaincodes/noop"),
   169  			MetadataRoot: filepath.FromSlash("testdata/src/chaincodes/noop/META-INF"),
   170  			Path:         "chaincodes/noop",
   171  		})
   172  		require.NoError(t, err, "failed to find source")
   173  		assert.Contains(t, source, "src/chaincodes/noop/chaincode.go")
   174  		assert.Contains(t, source, "META-INF/statedb/couchdb/indexes/indexOwner.json")
   175  		assert.NotContains(t, source, "src/chaincodes/noop/go.mod")
   176  		assert.NotContains(t, source, "src/chaincodes/noop/go.sum")
   177  		assert.Len(t, source, 2)
   178  	})
   179  
   180  	t.Run("Module", func(t *testing.T) {
   181  		source, err := findSource(&CodeDescriptor{
   182  			Module:       true,
   183  			Source:       filepath.FromSlash("testdata/ccmodule"),
   184  			MetadataRoot: filepath.FromSlash("testdata/ccmodule/META-INF"),
   185  			Path:         "ccmodule",
   186  		})
   187  		require.NoError(t, err, "failed to find source")
   188  		assert.Len(t, source, 7)
   189  		assert.Contains(t, source, "META-INF/statedb/couchdb/indexes/indexOwner.json")
   190  		assert.Contains(t, source, "src/go.mod")
   191  		assert.Contains(t, source, "src/go.sum")
   192  		assert.Contains(t, source, "src/chaincode.go")
   193  		assert.Contains(t, source, "src/customlogger/customlogger.go")
   194  		assert.Contains(t, source, "src/nested/chaincode.go")
   195  		assert.Contains(t, source, "src/nested/META-INF/statedb/couchdb/indexes/nestedIndexOwner.json")
   196  	})
   197  
   198  	t.Run("NonExistent", func(t *testing.T) {
   199  		_, err := findSource(&CodeDescriptor{Path: "acme.com/this/should/not/exist"})
   200  		assert.Error(t, err)
   201  		assert.True(t, os.IsNotExist(errors.Cause(err)))
   202  	})
   203  }
   204  
   205  func tarContents(t *testing.T, payload []byte) []string {
   206  	var files []string
   207  
   208  	is := bytes.NewReader(payload)
   209  	gr, err := gzip.NewReader(is)
   210  	require.NoError(t, err, "failed to create new gzip reader")
   211  
   212  	tr := tar.NewReader(gr)
   213  	for {
   214  		header, err := tr.Next()
   215  		if err == io.EOF {
   216  			break
   217  		}
   218  		assert.NoError(t, err)
   219  
   220  		if header.Typeflag == tar.TypeReg {
   221  			files = append(files, header.Name)
   222  		}
   223  	}
   224  
   225  	return files
   226  }
   227  
   228  func TestGopathDeploymentPayload(t *testing.T) {
   229  	reset := setupGopath(t, "testdata")
   230  	defer reset()
   231  
   232  	platform := &Platform{}
   233  
   234  	t.Run("IncludesMetadata", func(t *testing.T) {
   235  		payload, err := platform.GetDeploymentPayload("chaincodes/noop")
   236  		assert.NoError(t, err)
   237  
   238  		contents := tarContents(t, payload)
   239  		assert.Contains(t, contents, "META-INF/statedb/couchdb/indexes/indexOwner.json")
   240  	})
   241  
   242  	var tests = []struct {
   243  		path string
   244  		succ bool
   245  	}{
   246  		{path: "testdata/src/chaincodes/noop", succ: true},
   247  		{path: "chaincodes/noop", succ: true},
   248  		{path: "bad/chaincodes/noop", succ: false},
   249  		{path: "chaincodes/BadImport", succ: false},
   250  		{path: "chaincodes/BadMetadataInvalidIndex", succ: false},
   251  		{path: "chaincodes/BadMetadataUnexpectedFolderContent", succ: false},
   252  		{path: "chaincodes/BadMetadataIgnoreHiddenFile", succ: true},
   253  		{path: "chaincodes/empty", succ: false},
   254  	}
   255  
   256  	for _, tt := range tests {
   257  		t.Run(tt.path, func(t *testing.T) {
   258  			_, err := platform.GetDeploymentPayload(tt.path)
   259  			if tt.succ {
   260  				assert.NoError(t, err, "expected success for path: %s", tt.path)
   261  			} else {
   262  				assert.Errorf(t, err, "expected error for path: %s", tt.path)
   263  			}
   264  		})
   265  	}
   266  }
   267  
   268  func TestModuleDeploymentPayload(t *testing.T) {
   269  	platform := &Platform{}
   270  
   271  	t.Run("TopLevel", func(t *testing.T) {
   272  		dp, err := platform.GetDeploymentPayload("testdata/ccmodule")
   273  		assert.NoError(t, err)
   274  		contents := tarContents(t, dp)
   275  		assert.ElementsMatch(t, contents, []string{
   276  			"META-INF/statedb/couchdb/indexes/indexOwner.json", // top level metadata
   277  			"src/chaincode.go",
   278  			"src/customlogger/customlogger.go",
   279  			"src/go.mod",
   280  			"src/go.sum",
   281  			"src/nested/META-INF/statedb/couchdb/indexes/nestedIndexOwner.json",
   282  			"src/nested/chaincode.go",
   283  		})
   284  	})
   285  
   286  	t.Run("NestedPackage", func(t *testing.T) {
   287  		dp, err := platform.GetDeploymentPayload("testdata/ccmodule/nested")
   288  		assert.NoError(t, err)
   289  		contents := tarContents(t, dp)
   290  		assert.ElementsMatch(t, contents, []string{
   291  			"META-INF/statedb/couchdb/indexes/nestedIndexOwner.json", // nested metadata
   292  			"src/META-INF/statedb/couchdb/indexes/indexOwner.json",
   293  			"src/chaincode.go",
   294  			"src/customlogger/customlogger.go",
   295  			"src/go.mod",
   296  			"src/go.sum",
   297  			"src/nested/chaincode.go",
   298  		})
   299  	})
   300  }
   301  
   302  func setupGopath(t *testing.T, path string) func() {
   303  	initialGopath, gopathSet := os.LookupEnv("GOPATH")
   304  	initialGo111Module, go111ModuleSet := os.LookupEnv("GO111MODULE")
   305  
   306  	if path == "" {
   307  		err := os.Unsetenv("GOPATH")
   308  		require.NoError(t, err)
   309  	} else {
   310  		absPath, err := filepath.Abs(path)
   311  		require.NoErrorf(t, err, "expected to calculate absolute path from %s", path)
   312  		err = os.Setenv("GOPATH", absPath)
   313  		require.NoError(t, err, "failed to set GOPATH")
   314  		err = os.Setenv("GO111MODULE", "off")
   315  		require.NoError(t, err, "failed set GO111MODULE")
   316  	}
   317  
   318  	return func() {
   319  		if !gopathSet {
   320  			os.Unsetenv("GOPATH")
   321  		} else {
   322  			os.Setenv("GOPATH", initialGopath)
   323  		}
   324  		if !go111ModuleSet {
   325  			os.Unsetenv("GO111MODULE")
   326  		} else {
   327  			os.Setenv("GO111MODULE", initialGo111Module)
   328  		}
   329  	}
   330  }
   331  
   332  func TestGenerateDockerFile(t *testing.T) {
   333  	platform := &Platform{}
   334  
   335  	viper.Set("chaincode.golang.runtime", "buildimage")
   336  	expected := "FROM buildimage\nADD binpackage.tar /usr/local/bin"
   337  	dockerfile, err := platform.GenerateDockerfile()
   338  	assert.NoError(t, err)
   339  	assert.Equal(t, expected, dockerfile)
   340  
   341  	viper.Set("chaincode.golang.runtime", "another-buildimage")
   342  	expected = "FROM another-buildimage\nADD binpackage.tar /usr/local/bin"
   343  	dockerfile, err = platform.GenerateDockerfile()
   344  	assert.NoError(t, err)
   345  	assert.Equal(t, expected, dockerfile)
   346  }
   347  
   348  func TestGetLDFlagsOpts(t *testing.T) {
   349  	viper.Set("chaincode.golang.dynamicLink", true)
   350  	if getLDFlagsOpts() != dynamicLDFlagsOpts {
   351  		t.Error("Error handling chaincode.golang.dynamicLink configuration. ldflags should be for dynamic linkink")
   352  	}
   353  	viper.Set("chaincode.golang.dynamicLink", false)
   354  	if getLDFlagsOpts() != staticLDFlagsOpts {
   355  		t.Error("Error handling chaincode.golang.dynamicLink configuration. ldflags should be for static linkink")
   356  	}
   357  }
   358  
   359  func TestDockerBuildOptions(t *testing.T) {
   360  	platform := &Platform{}
   361  
   362  	t.Run("GOPROXY and GOSUMDB not set", func(t *testing.T) {
   363  		opts, err := platform.DockerBuildOptions("the-path")
   364  		assert.NoError(t, err, "unexpected error from DockerBuildOptions")
   365  
   366  		expectedOpts := util.DockerBuildOptions{
   367  			Cmd: `
   368  set -e
   369  if [ -f "/chaincode/input/src/go.mod" ] && [ -d "/chaincode/input/src/vendor" ]; then
   370      cd /chaincode/input/src
   371      GO111MODULE=on go build -v -mod=vendor -ldflags "-linkmode external -extldflags '-static'" -o /chaincode/output/chaincode the-path
   372  elif [ -f "/chaincode/input/src/go.mod" ]; then
   373      cd /chaincode/input/src
   374      GO111MODULE=on go build -v -mod=readonly -ldflags "-linkmode external -extldflags '-static'" -o /chaincode/output/chaincode the-path
   375  elif [ -f "/chaincode/input/src/the-path/go.mod" ] && [ -d "/chaincode/input/src/the-path/vendor" ]; then
   376      cd /chaincode/input/src/the-path
   377      GO111MODULE=on go build -v -mod=vendor -ldflags "-linkmode external -extldflags '-static'" -o /chaincode/output/chaincode .
   378  elif [ -f "/chaincode/input/src/the-path/go.mod" ]; then
   379      cd /chaincode/input/src/the-path
   380      GO111MODULE=on go build -v -mod=readonly -ldflags "-linkmode external -extldflags '-static'" -o /chaincode/output/chaincode .
   381  else
   382      GOPATH=/chaincode/input:$GOPATH go build -v -ldflags "-linkmode external -extldflags '-static'" -o /chaincode/output/chaincode the-path
   383  fi
   384  echo Done!
   385  `,
   386  			Env: []string{"GOPROXY=https://proxy.golang.org"},
   387  		}
   388  		assert.Equal(t, expectedOpts, opts)
   389  	})
   390  
   391  	t.Run("GOPROXY and GOSUMDB set", func(t *testing.T) {
   392  		oldGoproxy, set := os.LookupEnv("GOPROXY")
   393  		if set {
   394  			defer os.Setenv("GOPROXY", oldGoproxy)
   395  		}
   396  		os.Setenv("GOPROXY", "the-goproxy")
   397  
   398  		oldGosumdb, set := os.LookupEnv("GOSUMDB")
   399  		if set {
   400  			defer os.Setenv("GOSUMDB", oldGosumdb)
   401  		}
   402  		os.Setenv("GOSUMDB", "the-gosumdb")
   403  
   404  		opts, err := platform.DockerBuildOptions("the-path")
   405  		assert.NoError(t, err, "unexpected error from DockerBuildOptions")
   406  
   407  		expectedOpts := util.DockerBuildOptions{
   408  			Cmd: `
   409  set -e
   410  if [ -f "/chaincode/input/src/go.mod" ] && [ -d "/chaincode/input/src/vendor" ]; then
   411      cd /chaincode/input/src
   412      GO111MODULE=on go build -v -mod=vendor -ldflags "-linkmode external -extldflags '-static'" -o /chaincode/output/chaincode the-path
   413  elif [ -f "/chaincode/input/src/go.mod" ]; then
   414      cd /chaincode/input/src
   415      GO111MODULE=on go build -v -mod=readonly -ldflags "-linkmode external -extldflags '-static'" -o /chaincode/output/chaincode the-path
   416  elif [ -f "/chaincode/input/src/the-path/go.mod" ] && [ -d "/chaincode/input/src/the-path/vendor" ]; then
   417      cd /chaincode/input/src/the-path
   418      GO111MODULE=on go build -v -mod=vendor -ldflags "-linkmode external -extldflags '-static'" -o /chaincode/output/chaincode .
   419  elif [ -f "/chaincode/input/src/the-path/go.mod" ]; then
   420      cd /chaincode/input/src/the-path
   421      GO111MODULE=on go build -v -mod=readonly -ldflags "-linkmode external -extldflags '-static'" -o /chaincode/output/chaincode .
   422  else
   423      GOPATH=/chaincode/input:$GOPATH go build -v -ldflags "-linkmode external -extldflags '-static'" -o /chaincode/output/chaincode the-path
   424  fi
   425  echo Done!
   426  `,
   427  			Env: []string{"GOPROXY=the-goproxy", "GOSUMDB=the-gosumdb"},
   428  		}
   429  		assert.Equal(t, expectedOpts, opts)
   430  	})
   431  }
   432  
   433  func TestDescribeCode(t *testing.T) {
   434  	abs, err := filepath.Abs(filepath.FromSlash("testdata/ccmodule"))
   435  	assert.NoError(t, err)
   436  
   437  	t.Run("TopLevelModulePackage", func(t *testing.T) {
   438  		cd, err := DescribeCode("testdata/ccmodule")
   439  		assert.NoError(t, err)
   440  		expected := &CodeDescriptor{
   441  			Source:       abs,
   442  			MetadataRoot: filepath.Join(abs, "META-INF"),
   443  			Path:         "ccmodule",
   444  			Module:       true,
   445  		}
   446  		assert.Equal(t, expected, cd)
   447  	})
   448  
   449  	t.Run("NestedModulePackage", func(t *testing.T) {
   450  		cd, err := DescribeCode("testdata/ccmodule/nested")
   451  		assert.NoError(t, err)
   452  		expected := &CodeDescriptor{
   453  			Source:       abs,
   454  			MetadataRoot: filepath.Join(abs, "nested", "META-INF"),
   455  			Path:         "ccmodule/nested",
   456  			Module:       true,
   457  		}
   458  		assert.Equal(t, expected, cd)
   459  	})
   460  }
   461  
   462  func TestRegularFileExists(t *testing.T) {
   463  	t.Run("RegularFile", func(t *testing.T) {
   464  		ok, err := regularFileExists("testdata/ccmodule/go.mod")
   465  		assert.NoError(t, err)
   466  		assert.True(t, ok)
   467  	})
   468  	t.Run("MissingFile", func(t *testing.T) {
   469  		ok, err := regularFileExists("testdata/missing.file")
   470  		assert.NoError(t, err)
   471  		assert.False(t, ok)
   472  	})
   473  	t.Run("Directory", func(t *testing.T) {
   474  		ok, err := regularFileExists("testdata")
   475  		assert.NoError(t, err)
   476  		assert.False(t, ok)
   477  	})
   478  }
   479  
   480  func TestMain(m *testing.M) {
   481  	viper.SetConfigName("core")
   482  	viper.SetEnvPrefix("CORE")
   483  	configtest.AddDevConfigPath(nil)
   484  	viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
   485  	viper.AutomaticEnv()
   486  	if err := viper.ReadInConfig(); err != nil {
   487  		fmt.Printf("could not read config %s\n", err)
   488  		os.Exit(-1)
   489  	}
   490  	os.Exit(m.Run())
   491  }