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