github.com/yous1230/fabric@v2.0.0-beta.0.20191224111736-74345bee6ac2+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 }