github.com/decred/politeia@v1.4.0/politeiad/batch_test.go (about) 1 // Copyright (c) 2021 The Decred developers 2 // Use of this source code is governed by an ISC 3 // license that can be found in the LICENSE file. 4 package main 5 6 import ( 7 "testing" 8 9 v2 "github.com/decred/politeia/politeiad/api/v2" 10 "github.com/pkg/errors" 11 ) 12 13 func TestExecConcurrently(t *testing.T) { 14 // NOTE: these tests should be executed using the -race flag since 15 // they are testing the concurrent execution of plugin commands. 16 17 // Setup random tokens 18 var ( 19 token = "114cb8a95cb86355" 20 invalidToken = "zzz" 21 ) 22 23 // Setup a basic test case of plugin commands. The 24 // command payload is used to mark the ordering of 25 // the plugin commands so that the test can verify 26 // that the batch does not change the ordering. 27 pluginCmds := []v2.PluginCmd{ 28 // Success with a token 29 { 30 Token: token, 31 ID: testPluginID, 32 Command: testCmdSuccess, 33 Payload: "0", 34 }, 35 // Success without a token 36 { 37 Token: token, 38 ID: testPluginID, 39 Command: testCmdSuccess, 40 Payload: "1", 41 }, 42 // Invalid token 43 { 44 Token: invalidToken, 45 ID: testPluginID, 46 Command: testCmdSuccess, 47 Payload: "2", 48 }, 49 // Error case 50 { 51 Token: token, 52 ID: testPluginID, 53 Command: testCmdError, 54 Payload: "3", 55 }, 56 } 57 58 // Setup the batch and execute the commands 59 b := newBatch(pluginCmds) 60 b.execConcurrently(testPluginRead) 61 62 // Verify the replies 63 for i, entry := range b.entries { 64 // Verify that the batch entries are in the same order 65 // that the plugin commands were provided in. The 66 // command payloads were set to unique strings in order 67 // to verify this. 68 pluginCmd := pluginCmds[i] 69 if entry.cmd.Payload != pluginCmd.Payload { 70 t.Errorf("batch commands ordered incorrectly: got %v, want %v", 71 entry.cmd.Payload, pluginCmd.Payload) 72 } 73 74 // Verify that either a reply payload was returned or 75 // the error field has been populated. 76 switch { 77 case entry.reply != "" && entry.err != nil: 78 // Both fields were populated 79 t.Errorf("both the reply payload and the error were populated") 80 81 case entry.reply == "" && entry.err == nil: 82 // Neither field were populated 83 t.Errorf("neither the reply payload or the error were populated") 84 } 85 86 // Verify that the reply is correct 87 switch { 88 case entry.cmd.Token == invalidToken: 89 // An invalid token was provided. The error 90 // should be an invalid token user error. 91 var ue v2.UserErrorReply 92 if !errors.As(entry.err, &ue) || 93 ue.ErrorCode != v2.ErrorCodeTokenInvalid { 94 t.Errorf("wrong error; got %v, want %v user error", 95 entry.err, v2.ErrorCodes[v2.ErrorCodeTokenInvalid]) 96 } 97 98 case entry.cmd.Command == testCmdSuccess: 99 // Success case 100 if entry.reply != successReply { 101 t.Errorf("wrong reply payload; got %v, want %v", 102 entry.reply, successReply) 103 } 104 105 case entry.cmd.Command == testCmdError: 106 // Error case 107 if !errors.Is(entry.err, errorReply) { 108 t.Errorf("wrong error reply; got %v, want %v", 109 entry.err, errorReply) 110 } 111 112 default: 113 t.Fatalf("unhandled test case: %v %v", entry, pluginCmd) 114 } 115 } 116 117 // Test concurrency race conditions by executing a large 118 // number of plugin command. If a race condition occurs, 119 // the golang race detector should pick it up. 120 cmdCount := 1000 121 pluginCmds = make([]v2.PluginCmd, 0, cmdCount) 122 for i := 0; i < cmdCount; i++ { 123 pluginCmds = append(pluginCmds, v2.PluginCmd{ 124 Token: "", 125 ID: testPluginID, 126 Command: testCmdSuccess, 127 Payload: "", 128 }) 129 } 130 131 // Setup the batch and execute the commands. If a race 132 // condition does not occur then this test passes. 133 b = newBatch(pluginCmds) 134 b.execConcurrently(testPluginRead) 135 } 136 137 const ( 138 // testPluginID is the plugin ID for the test plugin. 139 testPluginID = "test-plugin" 140 141 // Plugin test commands 142 testCmdSuccess = "test-cmd-success" 143 testCmdError = "test-cmd-error" 144 ) 145 146 var ( 147 // Expected replies 148 successReply = "success-reply-payload" 149 errorReply = errors.New("test command error reply") 150 ) 151 152 // testPluginRead is the plugin read function that is used for testing. 153 func testPluginRead(token []byte, pluginID, cmd, payload string) (string, error) { 154 switch cmd { 155 case testCmdSuccess: 156 return successReply, nil 157 case testCmdError: 158 return "", errorReply 159 } 160 return "", errors.Errorf("invalid command '%v'", cmd) 161 }