github.com/cosmos/cosmos-sdk@v0.50.10/x/gov/client/cli/util_test.go (about) 1 package cli 2 3 import ( 4 "bytes" 5 "encoding/base64" 6 "fmt" 7 "io" 8 "os" 9 "strings" 10 "testing" 11 12 "github.com/spf13/cobra" 13 "github.com/stretchr/testify/assert" 14 "github.com/stretchr/testify/require" 15 16 sdkmath "cosmossdk.io/math" 17 18 "github.com/cosmos/cosmos-sdk/client" 19 "github.com/cosmos/cosmos-sdk/codec" 20 codectypes "github.com/cosmos/cosmos-sdk/codec/types" 21 "github.com/cosmos/cosmos-sdk/testutil" 22 "github.com/cosmos/cosmos-sdk/testutil/testdata" 23 sdk "github.com/cosmos/cosmos-sdk/types" 24 banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" 25 v1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1" 26 "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" 27 stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" 28 ) 29 30 func TestParseSubmitLegacyProposal(t *testing.T) { 31 okJSON := testutil.WriteToNewTempFile(t, ` 32 { 33 "title": "Test Proposal", 34 "description": "My awesome proposal", 35 "type": "Text", 36 "deposit": "1000test" 37 } 38 `) 39 40 badJSON := testutil.WriteToNewTempFile(t, "bad json") 41 fs := NewCmdSubmitLegacyProposal().Flags() 42 43 // nonexistent json 44 fs.Set(FlagProposal, "fileDoesNotExist") 45 _, err := parseSubmitLegacyProposal(fs) 46 require.Error(t, err) 47 48 // invalid json 49 fs.Set(FlagProposal, badJSON.Name()) 50 _, err = parseSubmitLegacyProposal(fs) 51 require.Error(t, err) 52 53 // ok json 54 fs.Set(FlagProposal, okJSON.Name()) 55 proposal1, err := parseSubmitLegacyProposal(fs) 56 require.Nil(t, err, "unexpected error") 57 require.Equal(t, "Test Proposal", proposal1.Title) 58 require.Equal(t, "My awesome proposal", proposal1.Description) 59 require.Equal(t, "Text", proposal1.Type) 60 require.Equal(t, "1000test", proposal1.Deposit) 61 62 // flags that can't be used with --proposal 63 for _, incompatibleFlag := range ProposalFlags { 64 fs.Set(incompatibleFlag, "some value") 65 _, err := parseSubmitLegacyProposal(fs) 66 require.Error(t, err) 67 fs.Set(incompatibleFlag, "") 68 } 69 70 // no --proposal, only flags 71 fs.Set(FlagProposal, "") 72 flagTestCases := map[string]struct { 73 pTitle string 74 pDescription string 75 pType string 76 expErr bool 77 errMsg string 78 }{ 79 "valid flags": { 80 pTitle: proposal1.Title, 81 pDescription: proposal1.Description, 82 pType: proposal1.Type, 83 }, 84 "empty type": { 85 pTitle: proposal1.Title, 86 pDescription: proposal1.Description, 87 expErr: true, 88 errMsg: "proposal type is required", 89 }, 90 "empty title": { 91 pDescription: proposal1.Description, 92 pType: proposal1.Type, 93 expErr: true, 94 errMsg: "proposal title is required", 95 }, 96 "empty description": { 97 pTitle: proposal1.Title, 98 pType: proposal1.Type, 99 expErr: true, 100 errMsg: "proposal description is required", 101 }, 102 } 103 for name, tc := range flagTestCases { 104 t.Run(name, func(t *testing.T) { 105 fs.Set(FlagTitle, tc.pTitle) 106 fs.Set(FlagDescription, tc.pDescription) 107 fs.Set(FlagProposalType, tc.pType) 108 fs.Set(FlagDeposit, proposal1.Deposit) 109 proposal2, err := parseSubmitLegacyProposal(fs) 110 111 if tc.expErr { 112 require.Error(t, err) 113 require.Contains(t, err.Error(), tc.errMsg) 114 } else { 115 require.NoError(t, err) 116 require.Equal(t, proposal1.Title, proposal2.Title) 117 require.Equal(t, proposal1.Description, proposal2.Description) 118 require.Equal(t, proposal1.Type, proposal2.Type) 119 require.Equal(t, proposal1.Deposit, proposal2.Deposit) 120 } 121 }) 122 } 123 124 err = okJSON.Close() 125 require.Nil(t, err, "unexpected error") 126 err = badJSON.Close() 127 require.Nil(t, err, "unexpected error") 128 } 129 130 func TestParseSubmitProposal(t *testing.T) { 131 _, _, addr := testdata.KeyTestPubAddr() 132 interfaceRegistry := codectypes.NewInterfaceRegistry() 133 cdc := codec.NewProtoCodec(interfaceRegistry) 134 banktypes.RegisterInterfaces(interfaceRegistry) 135 stakingtypes.RegisterInterfaces(interfaceRegistry) 136 v1beta1.RegisterInterfaces(interfaceRegistry) 137 v1.RegisterInterfaces(interfaceRegistry) 138 expectedMetadata := []byte{42} 139 140 okJSON := testutil.WriteToNewTempFile(t, fmt.Sprintf(` 141 { 142 "messages": [ 143 { 144 "@type": "/cosmos.bank.v1beta1.MsgSend", 145 "from_address": "%s", 146 "to_address": "%s", 147 "amount":[{"denom": "stake","amount": "10"}] 148 }, 149 { 150 "@type": "/cosmos.staking.v1beta1.MsgDelegate", 151 "delegator_address": "%s", 152 "validator_address": "%s", 153 "amount":{"denom": "stake","amount": "10"} 154 }, 155 { 156 "@type": "/cosmos.gov.v1.MsgExecLegacyContent", 157 "authority": "%s", 158 "content": { 159 "@type": "/cosmos.gov.v1beta1.TextProposal", 160 "title": "My awesome title", 161 "description": "My awesome description" 162 } 163 } 164 ], 165 "metadata": "%s", 166 "title": "My awesome title", 167 "summary": "My awesome summary", 168 "deposit": "1000test", 169 "expedited": true 170 } 171 `, addr, addr, addr, addr, addr, base64.StdEncoding.EncodeToString(expectedMetadata))) 172 173 badJSON := testutil.WriteToNewTempFile(t, "bad json") 174 175 // nonexistent json 176 _, _, _, err := parseSubmitProposal(cdc, "fileDoesNotExist") 177 require.Error(t, err) 178 179 // invalid json 180 _, _, _, err = parseSubmitProposal(cdc, badJSON.Name()) 181 require.Error(t, err) 182 183 // ok json 184 proposal, msgs, deposit, err := parseSubmitProposal(cdc, okJSON.Name()) 185 require.NoError(t, err, "unexpected error") 186 require.Equal(t, sdk.NewCoins(sdk.NewCoin("test", sdkmath.NewInt(1000))), deposit) 187 require.Equal(t, base64.StdEncoding.EncodeToString(expectedMetadata), proposal.Metadata) 188 require.Len(t, msgs, 3) 189 msg1, ok := msgs[0].(*banktypes.MsgSend) 190 require.True(t, ok) 191 require.Equal(t, addr.String(), msg1.FromAddress) 192 require.Equal(t, addr.String(), msg1.ToAddress) 193 require.Equal(t, sdk.NewCoins(sdk.NewCoin("stake", sdkmath.NewInt(10))), msg1.Amount) 194 msg2, ok := msgs[1].(*stakingtypes.MsgDelegate) 195 require.True(t, ok) 196 require.Equal(t, addr.String(), msg2.DelegatorAddress) 197 require.Equal(t, addr.String(), msg2.ValidatorAddress) 198 require.Equal(t, sdk.NewCoin("stake", sdkmath.NewInt(10)), msg2.Amount) 199 msg3, ok := msgs[2].(*v1.MsgExecLegacyContent) 200 require.True(t, ok) 201 require.Equal(t, addr.String(), msg3.Authority) 202 textProp, ok := msg3.Content.GetCachedValue().(*v1beta1.TextProposal) 203 require.True(t, ok) 204 require.Equal(t, "My awesome title", textProp.Title) 205 require.Equal(t, "My awesome description", textProp.Description) 206 require.Equal(t, "My awesome title", proposal.Title) 207 require.Equal(t, "My awesome summary", proposal.Summary) 208 require.Equal(t, true, proposal.Expedited) 209 210 err = okJSON.Close() 211 require.Nil(t, err, "unexpected error") 212 err = badJSON.Close() 213 require.Nil(t, err, "unexpected error") 214 } 215 216 func getCommandHelp(t *testing.T, cmd *cobra.Command) string { 217 // Create a pipe, so we can capture the help sent to stdout. 218 reader, writer, err := os.Pipe() 219 require.NoError(t, err, "creating os.Pipe()") 220 outChan := make(chan string) 221 defer func(origCmdOut io.Writer) { 222 cmd.SetOut(origCmdOut) 223 // Ignoring these errors since we're just ensuring cleanup here, 224 // and they will return an error if already called (which we don't care about). 225 _ = reader.Close() 226 _ = writer.Close() 227 close(outChan) 228 }(cmd.OutOrStdout()) 229 cmd.SetOut(writer) 230 231 // Do the reading in a separate goroutine from the writing (a best practice). 232 go func() { 233 var b bytes.Buffer 234 _, buffErr := io.Copy(&b, reader) 235 if buffErr != nil { 236 // Due to complexities of goroutines and multiple channels, I'm sticking with a 237 // single channel and just putting the error in there (which I'll test for later). 238 b.WriteString("buffer error: " + buffErr.Error()) 239 } 240 outChan <- b.String() 241 }() 242 243 err = cmd.Help() 244 require.NoError(t, err, "cmd.Help()") 245 require.NoError(t, writer.Close(), "pipe writer .Close()") 246 rv := <-outChan 247 require.NotContains(t, rv, "buffer error: ", "buffer output") 248 return rv 249 } 250 251 func TestAddGovPropFlagsToCmd(t *testing.T) { 252 cmd := &cobra.Command{ 253 Short: "Just a test command that does nothing but we can add flags to it.", 254 Run: func(cmd *cobra.Command, args []string) { 255 t.Errorf("The cmd has run with the args %q, but Run shouldn't have been called.", args) 256 }, 257 } 258 testFunc := func() { 259 AddGovPropFlagsToCmd(cmd) 260 } 261 require.NotPanics(t, testFunc, "AddGovPropFlagsToCmd") 262 263 help := getCommandHelp(t, cmd) 264 265 expDepositDesc := "The deposit to include with the governance proposal" 266 expMetadataDesc := "The metadata to include with the governance proposal" 267 expTitleDesc := "The title to put on the governance proposal" 268 expSummaryDesc := "The summary to include with the governance proposal" 269 // Regexp notes: (?m:...) = multi-line mode so ^ and $ match the beginning and end of each line. 270 // Each regexp assertion checks for a line containing only a specific flag and its description. 271 assert.Regexp(t, `(?m:^\s+--`+FlagDeposit+` string\s+`+expDepositDesc+`$)`, help, "help output") 272 assert.Regexp(t, `(?m:^\s+--`+FlagMetadata+` string\s+`+expMetadataDesc+`$)`, help, "help output") 273 assert.Regexp(t, `(?m:^\s+--`+FlagTitle+` string\s+`+expTitleDesc+`$)`, help, "help output") 274 assert.Regexp(t, `(?m:^\s+--`+FlagSummary+` string\s+`+expSummaryDesc+`$)`, help, "help output") 275 } 276 277 func TestReadGovPropFlags(t *testing.T) { 278 fromAddr := sdk.AccAddress("from_addr___________") 279 argDeposit := "--" + FlagDeposit 280 argMetadata := "--" + FlagMetadata 281 argTitle := "--" + FlagTitle 282 argSummary := "--" + FlagSummary 283 284 // cz is a shorter way to define coins objects for these tests. 285 cz := func(coins string) sdk.Coins { 286 rv, err := sdk.ParseCoinsNormalized(coins) 287 require.NoError(t, err, "ParseCoinsNormalized(%q)", coins) 288 return rv 289 } 290 291 tests := []struct { 292 name string 293 fromAddr sdk.AccAddress 294 args []string 295 exp *v1.MsgSubmitProposal 296 expErr []string 297 }{ 298 { 299 name: "no args no from", 300 fromAddr: nil, 301 args: []string{}, 302 exp: &v1.MsgSubmitProposal{ 303 InitialDeposit: nil, 304 Proposer: "", 305 Metadata: "", 306 Title: "", 307 Summary: "", 308 }, 309 }, 310 { 311 name: "only from defined", 312 fromAddr: fromAddr, 313 args: []string{}, 314 exp: &v1.MsgSubmitProposal{ 315 InitialDeposit: nil, 316 Proposer: fromAddr.String(), 317 Metadata: "", 318 Title: "", 319 Summary: "", 320 }, 321 }, 322 323 // only deposit tests. 324 { 325 name: "only deposit empty string", 326 fromAddr: nil, 327 args: []string{argDeposit, ""}, 328 exp: &v1.MsgSubmitProposal{ 329 InitialDeposit: nil, 330 Proposer: "", 331 Metadata: "", 332 Title: "", 333 Summary: "", 334 }, 335 }, 336 { 337 name: "only deposit one coin", 338 fromAddr: nil, 339 args: []string{argDeposit, "1bigcoin"}, 340 exp: &v1.MsgSubmitProposal{ 341 InitialDeposit: cz("1bigcoin"), 342 Proposer: "", 343 Metadata: "", 344 Title: "", 345 Summary: "", 346 }, 347 }, 348 { 349 name: "only deposit invalid coins", 350 fromAddr: nil, 351 args: []string{argDeposit, "not really coins"}, 352 expErr: []string{"invalid deposit", "invalid decimal coin expression", "not really coins"}, 353 }, 354 { 355 name: "only deposit two coins", 356 fromAddr: nil, 357 args: []string{argDeposit, "1acoin,2bcoin"}, 358 exp: &v1.MsgSubmitProposal{ 359 InitialDeposit: cz("1acoin,2bcoin"), 360 Proposer: "", 361 Metadata: "", 362 Title: "", 363 Summary: "", 364 }, 365 }, 366 { 367 name: "only deposit two coins other order", 368 fromAddr: nil, 369 args: []string{argDeposit, "2bcoin,1acoin"}, 370 exp: &v1.MsgSubmitProposal{ 371 InitialDeposit: cz("1acoin,2bcoin"), 372 Proposer: "", 373 Metadata: "", 374 Title: "", 375 Summary: "", 376 }, 377 }, 378 { 379 name: "only deposit coin 1 of 3 bad", 380 fromAddr: nil, 381 args: []string{argDeposit, "1bad^coin,2bcoin,3ccoin"}, 382 expErr: []string{"invalid deposit", "invalid decimal coin expression", "1bad^coin"}, 383 }, 384 { 385 name: "only deposit coin 2 of 3 bad", 386 fromAddr: nil, 387 args: []string{argDeposit, "1acoin,2bad^coin,3ccoin"}, 388 expErr: []string{"invalid deposit", "invalid decimal coin expression", "2bad^coin"}, 389 }, 390 { 391 name: "only deposit coin 3 of 3 bad", 392 fromAddr: nil, 393 args: []string{argDeposit, "1acoin,2bcoin,3bad^coin"}, 394 expErr: []string{"invalid deposit", "invalid decimal coin expression", "3bad^coin"}, 395 }, 396 // As far as I can tell, there's no way to make flagSet.GetString return an error for a defined string flag. 397 // So I don't have a test for the "could not read deposit" error case. 398 399 // only metadata tests. 400 { 401 name: "only metadata empty", 402 fromAddr: nil, 403 args: []string{argMetadata, ""}, 404 exp: &v1.MsgSubmitProposal{ 405 InitialDeposit: nil, 406 Proposer: "", 407 Metadata: "", 408 Title: "", 409 Summary: "", 410 }, 411 }, 412 { 413 name: "only metadata simple", 414 fromAddr: nil, 415 args: []string{argMetadata, "just some metadata"}, 416 exp: &v1.MsgSubmitProposal{ 417 InitialDeposit: nil, 418 Proposer: "", 419 Metadata: "just some metadata", 420 Title: "", 421 Summary: "", 422 }, 423 }, 424 { 425 name: "only metadata super long", 426 fromAddr: nil, 427 args: []string{argMetadata, strings.Repeat("Long", 1_000_000)}, 428 exp: &v1.MsgSubmitProposal{ 429 InitialDeposit: nil, 430 Proposer: "", 431 Metadata: strings.Repeat("Long", 1_000_000), 432 Title: "", 433 Summary: "", 434 }, 435 }, 436 // As far as I can tell, there's no way to make flagSet.GetString return an error for a defined string flag. 437 // So I don't have a test for the "could not read metadata" error case. 438 439 // only title tests. 440 { 441 name: "only title empty", 442 fromAddr: nil, 443 args: []string{argTitle, ""}, 444 exp: &v1.MsgSubmitProposal{ 445 InitialDeposit: nil, 446 Proposer: "", 447 Metadata: "", 448 Title: "", 449 Summary: "", 450 }, 451 }, 452 { 453 name: "only title simple", 454 fromAddr: nil, 455 args: []string{argTitle, "just a title"}, 456 exp: &v1.MsgSubmitProposal{ 457 InitialDeposit: nil, 458 Proposer: "", 459 Metadata: "", 460 Title: "just a title", 461 Summary: "", 462 }, 463 }, 464 { 465 name: "only title super long", 466 fromAddr: nil, 467 args: []string{argTitle, strings.Repeat("Long", 1_000_000)}, 468 exp: &v1.MsgSubmitProposal{ 469 InitialDeposit: nil, 470 Proposer: "", 471 Metadata: "", 472 Title: strings.Repeat("Long", 1_000_000), 473 Summary: "", 474 }, 475 }, 476 // As far as I can tell, there's no way to make flagSet.GetString return an error for a defined string flag. 477 // So I don't have a test for the "could not read title" error case. 478 479 // only summary tests. 480 { 481 name: "only summary empty", 482 fromAddr: nil, 483 args: []string{argSummary, ""}, 484 exp: &v1.MsgSubmitProposal{ 485 InitialDeposit: nil, 486 Proposer: "", 487 Metadata: "", 488 Title: "", 489 Summary: "", 490 }, 491 }, 492 { 493 name: "only summary simple", 494 fromAddr: nil, 495 args: []string{argSummary, "just a short summary"}, 496 exp: &v1.MsgSubmitProposal{ 497 InitialDeposit: nil, 498 Proposer: "", 499 Metadata: "", 500 Title: "", 501 Summary: "just a short summary", 502 }, 503 }, 504 { 505 name: "only summary super long", 506 fromAddr: nil, 507 args: []string{argSummary, strings.Repeat("Long", 1_000_000)}, 508 exp: &v1.MsgSubmitProposal{ 509 InitialDeposit: nil, 510 Proposer: "", 511 Metadata: "", 512 Title: "", 513 Summary: strings.Repeat("Long", 1_000_000), 514 }, 515 }, 516 // As far as I can tell, there's no way to make flagSet.GetString return an error for a defined string flag. 517 // So I don't have a test for the "could not read summary" error case. 518 519 // Combo tests. 520 { 521 name: "all together order 1", 522 fromAddr: fromAddr, 523 args: []string{ 524 argDeposit, "56depcoin", 525 argMetadata, "my proposal is cool", 526 argTitle, "Simple Gov Prop Title", 527 argSummary, "This is just a test summary on a simple gov prop.", 528 }, 529 exp: &v1.MsgSubmitProposal{ 530 InitialDeposit: cz("56depcoin"), 531 Proposer: fromAddr.String(), 532 Metadata: "my proposal is cool", 533 Title: "Simple Gov Prop Title", 534 Summary: "This is just a test summary on a simple gov prop.", 535 }, 536 }, 537 { 538 name: "all together order 2", 539 fromAddr: fromAddr, 540 args: []string{ 541 argTitle, "This title is a *bit* more complex.", 542 argSummary, "This\nis\na\ncrazy\nsummary", 543 argDeposit, "78coolcoin", 544 argMetadata, "this proposal is cooler", 545 }, 546 exp: &v1.MsgSubmitProposal{ 547 InitialDeposit: cz("78coolcoin"), 548 Proposer: fromAddr.String(), 549 Metadata: "this proposal is cooler", 550 Title: "This title is a *bit* more complex.", 551 Summary: "This\nis\na\ncrazy\nsummary", 552 }, 553 }, 554 { 555 name: "all except proposer", 556 fromAddr: nil, 557 args: []string{ 558 argMetadata, "https://example.com/lucky", 559 argDeposit, "33luckycoin", 560 argSummary, "This proposal will bring you luck and good fortune in the new year.", 561 argTitle, "Increase Luck", 562 }, 563 exp: &v1.MsgSubmitProposal{ 564 InitialDeposit: cz("33luckycoin"), 565 Proposer: "", 566 Metadata: "https://example.com/lucky", 567 Title: "Increase Luck", 568 Summary: "This proposal will bring you luck and good fortune in the new year.", 569 }, 570 }, 571 { 572 name: "all except proposer but all empty", 573 fromAddr: nil, 574 args: []string{ 575 argMetadata, "", 576 argDeposit, "", 577 argSummary, "", 578 argTitle, "", 579 }, 580 exp: &v1.MsgSubmitProposal{ 581 InitialDeposit: nil, 582 Proposer: "", 583 Metadata: "", 584 Title: "", 585 Summary: "", 586 }, 587 }, 588 { 589 name: "all except deposit", 590 fromAddr: fromAddr, 591 args: []string{ 592 argTitle, "This is a Title", 593 argSummary, "This is a useless summary", 594 argMetadata, "worthless metadata", 595 }, 596 exp: &v1.MsgSubmitProposal{ 597 InitialDeposit: nil, 598 Proposer: fromAddr.String(), 599 Metadata: "worthless metadata", 600 Title: "This is a Title", 601 Summary: "This is a useless summary", 602 }, 603 expErr: nil, 604 }, 605 { 606 name: "all except metadata", 607 fromAddr: fromAddr, 608 args: []string{ 609 argTitle, "Bland Title", 610 argSummary, "Boring summary", 611 argDeposit, "99mdcoin", 612 }, 613 exp: &v1.MsgSubmitProposal{ 614 InitialDeposit: cz("99mdcoin"), 615 Proposer: fromAddr.String(), 616 Metadata: "", 617 Title: "Bland Title", 618 Summary: "Boring summary", 619 }, 620 }, 621 { 622 name: "all except title", 623 fromAddr: fromAddr, 624 args: []string{ 625 argMetadata, "this metadata does not have the title either", 626 argDeposit, "71whatcoin", 627 argSummary, "This is a summary on a titleless proposal.", 628 }, 629 exp: &v1.MsgSubmitProposal{ 630 InitialDeposit: cz("71whatcoin"), 631 Proposer: fromAddr.String(), 632 Metadata: "this metadata does not have the title either", 633 Title: "", 634 Summary: "This is a summary on a titleless proposal.", 635 }, 636 expErr: nil, 637 }, 638 { 639 name: "all except summary", 640 fromAddr: fromAddr, 641 args: []string{ 642 argMetadata, "28", 643 argTitle, "Now This is What I Call A Governance Proposal 28", 644 argDeposit, "42musiccoin", 645 }, 646 exp: &v1.MsgSubmitProposal{ 647 InitialDeposit: cz("42musiccoin"), 648 Proposer: fromAddr.String(), 649 Metadata: "28", 650 Title: "Now This is What I Call A Governance Proposal 28", 651 Summary: "", 652 }, 653 expErr: nil, 654 }, 655 } 656 657 for _, tc := range tests { 658 t.Run(tc.name, func(t *testing.T) { 659 cmd := &cobra.Command{ 660 Short: tc.name, 661 Run: func(cmd *cobra.Command, args []string) { 662 t.Errorf("The cmd for %q has run with the args %q, but Run shouldn't have been called.", tc.name, args) 663 }, 664 } 665 AddGovPropFlagsToCmd(cmd) 666 err := cmd.ParseFlags(tc.args) 667 require.NoError(t, err, "parsing test case args using cmd: %q", tc.args) 668 flagSet := cmd.Flags() 669 670 clientCtx := client.Context{ 671 FromAddress: tc.fromAddr, 672 } 673 674 var msg *v1.MsgSubmitProposal 675 testFunc := func() { 676 msg, err = ReadGovPropFlags(clientCtx, flagSet) 677 } 678 require.NotPanics(t, testFunc, "ReadGovPropFlags") 679 if len(tc.expErr) > 0 { 680 require.Error(t, err, "ReadGovPropFlags error") 681 for _, exp := range tc.expErr { 682 assert.ErrorContains(t, err, exp, "ReadGovPropFlags error") 683 } 684 } else { 685 require.NoError(t, err, "ReadGovPropFlags error") 686 } 687 assert.Equal(t, tc.exp, msg, "ReadGovPropFlags msg") 688 }) 689 } 690 }