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