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  }