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