github.com/grailbio/bigslice@v0.0.0-20230519005545-30c4c12152ad/exec/invocation_test.go (about)

     1  // Copyright 2022 GRAIL, Inc. All rights reserved.
     2  // Use of this source code is governed by the Apache 2.0
     3  // license that can be found in the LICENSE file.
     4  
     5  package exec
     6  
     7  import (
     8  	"bytes"
     9  	"encoding/gob"
    10  	"testing"
    11  
    12  	"github.com/grailbio/bigslice"
    13  	"github.com/grailbio/testutil/assert"
    14  	"github.com/grailbio/testutil/expect"
    15  )
    16  
    17  type testInterface interface{ IsTestInterface() }
    18  type testInterfaceImpl struct {
    19  	Dummy int
    20  }
    21  
    22  func (s *testInterfaceImpl) IsTestInterface() {}
    23  
    24  func init() {
    25  	// When we declare a Func argument to be an interface (testInterface), we
    26  	// still need to register the implementation, per normal gob usage
    27  	// expectation.
    28  	gob.Register(&testInterfaceImpl{})
    29  }
    30  
    31  // unregistered is a gob-encodable type for testing invocation argument
    32  // encoding/decoding.  Note that it is not registered with gob.Register, so it
    33  // would normally not be allowed to be encoded as an interface value.
    34  type unregistered struct {
    35  	Dummy int
    36  }
    37  
    38  var fnTestInvocationEncoding = bigslice.Func(
    39  	// See TestInvocationGob for the cases exercised by each argument type.
    40  	func(
    41  		int,
    42  		unregistered,
    43  		*unregistered,
    44  		testInterfaceImpl,
    45  		testInterface,
    46  		interface{},
    47  		*Result,
    48  		bigslice.Slice,
    49  	) bigslice.Slice {
    50  		return bigslice.Const(1, []int{})
    51  	})
    52  
    53  // TestInvocationGob verifies that we can round-trip through gob.  We implement
    54  // custom encoding/decoding so that we can serialize arbitary Func arguments as
    55  // interface values without registering the types with gob, as we capture
    56  // the argument types ourselves (for typechecking).
    57  func TestInvocationGob(t *testing.T) {
    58  	inv := makeExecInvocation(
    59  		fnTestInvocationEncoding.Invocation(
    60  			"test",
    61  			[]interface{}{
    62  				2,                             // primitive
    63  				unregistered{Dummy: 3},        // struct not registered with gob
    64  				&unregistered{Dummy: 5},       // pointer to struct not registered with gob
    65  				testInterfaceImpl{Dummy: 7},   // struct registered with gob
    66  				&testInterfaceImpl{Dummy: 11}, // concrete as interface
    67  				&testInterfaceImpl{Dummy: 13}, // concrete as empty interface
    68  				&Result{},                     // *Result as concrete
    69  				&Result{},                     // *Result as interface (Slice)
    70  			}...,
    71  		),
    72  	)
    73  	// Simulate replacement of *Result arguments with invocation references, as
    74  	// we do when we send invocations to workers.
    75  	inv.Args[6] = invocationRef{Index: 17}
    76  	inv.Args[7] = invocationRef{Index: 19}
    77  	var (
    78  		b   bytes.Buffer
    79  		enc = gob.NewEncoder(&b)
    80  		dec = gob.NewDecoder(&b)
    81  		got execInvocation
    82  	)
    83  	assert.NoError(t, enc.Encode(inv))
    84  	assert.NoError(t, dec.Decode(&got))
    85  	expect.EQ(t, got, inv)
    86  }