github.com/sykesm/fabric@v1.1.0-preview.0.20200129034918-2aa12b1a0181/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 }