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