runtraversal_test.go 5.99 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216
package runtraversal

import (
	"bytes"
	"errors"
	"io"
	"testing"

	"github.com/ipfs/go-graphsync/testutil"
	"github.com/stretchr/testify/require"

	ipld "github.com/ipld/go-ipld-prime"
	cidlink "github.com/ipld/go-ipld-prime/linking/cid"
)

type fakeResponseKey struct {
	Link ipld.Link
	Data string
}

type fakeResponseSender struct {
	stubbedResponses  map[fakeResponseKey]error
	expectedResponses map[fakeResponseKey]struct{}
	receivedResponses map[fakeResponseKey]struct{}
}

func newFakeResponseSender() *fakeResponseSender {
	return &fakeResponseSender{
		stubbedResponses:  make(map[fakeResponseKey]error),
		expectedResponses: make(map[fakeResponseKey]struct{}),
		receivedResponses: make(map[fakeResponseKey]struct{}),
	}
}

func (frs *fakeResponseSender) SendResponse(
	link ipld.Link,
	data []byte,
) error {
	frs.receivedResponses[fakeResponseKey{link, string(data)}] = struct{}{}
	return frs.stubbedResponses[fakeResponseKey{link, string(data)}]
}

func (frs *fakeResponseSender) expectResponse(link ipld.Link, data []byte, returnVal error) {
	frs.expectedResponses[fakeResponseKey{link, string(data)}] = struct{}{}
	frs.stubbedResponses[fakeResponseKey{link, string(data)}] = returnVal
}

func (frs *fakeResponseSender) verifyExpectations(t *testing.T) {
	require.Equal(t, frs.expectedResponses, frs.receivedResponses)
}

type loadedLink struct {
	link    ipld.Link
	linkCtx ipld.LinkContext
}

type traverseOutcome struct {
	isError bool
	err     error
	data    []byte
}
type fakeTraverser struct {
	finalError       error
	currentLink      int
	loadedLinks      []loadedLink
	receivedOutcomes []traverseOutcome
	expectedOutcomes []traverseOutcome
}

// IsComplete returns the completion state (boolean) and if so, the final error result from IPLD
func (ft *fakeTraverser) IsComplete() (bool, error) {
	if ft.currentLink >= len(ft.loadedLinks) {
		return true, ft.finalError
	}
	return false, nil
}

// Current request returns the current link waiting to be loaded
func (ft *fakeTraverser) CurrentRequest() (ipld.Link, ipld.LinkContext) {
	ll := ft.loadedLinks[ft.currentLink]
	ft.currentLink++
	return ll.link, ll.linkCtx
}

// Advance advances the traversal successfully by supplying the given reader as the result of the next IPLD load
func (ft *fakeTraverser) Advance(reader io.Reader) error {
	buf := new(bytes.Buffer)
	_, _ = io.Copy(buf, reader)
	ft.receivedOutcomes = append(ft.receivedOutcomes, traverseOutcome{false, nil, buf.Bytes()})
	return nil
}

// Error errors the traversal by returning the given error as the result of the next IPLD load
func (ft *fakeTraverser) Error(err error) {
	ft.receivedOutcomes = append(ft.receivedOutcomes, traverseOutcome{true, err, nil})
}

func (ft *fakeTraverser) verifyExpectations(t *testing.T) {
	require.Equal(t, ft.expectedOutcomes, ft.receivedOutcomes)
}

type fakeLoader struct {
	expectedLoads []loadedLink
	receivedLoads []loadedLink
	loadReturns   []traverseOutcome
	currentLoader int
}

func (fl *fakeLoader) Load(link ipld.Link, linkCtx ipld.LinkContext) (io.Reader, error) {
	fl.receivedLoads = append(fl.receivedLoads, loadedLink{link, linkCtx})
	outcome := fl.loadReturns[fl.currentLoader]
	fl.currentLoader++
	if outcome.isError {
		return nil, outcome.err
	}
	return bytes.NewBuffer(outcome.data), nil
}

func (fl *fakeLoader) verifyExpectations(t *testing.T) {
	require.Equal(t, fl.expectedLoads, fl.receivedLoads)
}

func TestRunTraversal(t *testing.T) {
	blks := testutil.GenerateBlocksOfSize(5, 100)
	links := make([]loadedLink, 0, 5)
	for _, blk := range blks {
		links = append(links, loadedLink{link: cidlink.Link{Cid: blk.Cid()}, linkCtx: ipld.LinkContext{}})
	}
	testCases := map[string]struct {
		linksToLoad          []loadedLink
		linkLoadsExpected    int
		loadOutcomes         []traverseOutcome
		loadOutcomesExpected int
		errorsOnSend         []error
		finalError           error
		expectedError        error
	}{
		"normal operation": {
			linksToLoad:       links,
			linkLoadsExpected: 5,
			loadOutcomes: []traverseOutcome{
				{false, nil, blks[0].RawData()},
				{false, nil, blks[1].RawData()},
				{false, nil, blks[2].RawData()},
				{false, nil, blks[3].RawData()},
				{false, nil, blks[4].RawData()},
			},
			errorsOnSend: []error{
				nil, nil, nil, nil, nil,
			},
		},
		"error on complete": {
			linksToLoad:       links,
			linkLoadsExpected: 5,
			loadOutcomes: []traverseOutcome{
				{false, nil, blks[0].RawData()},
				{false, nil, blks[1].RawData()},
				{false, nil, blks[2].RawData()},
				{false, nil, blks[3].RawData()},
				{false, nil, blks[4].RawData()},
			},
			errorsOnSend: []error{
				nil, nil, nil, nil, nil,
			},
			finalError:    errors.New("traverse failed"),
			expectedError: errors.New("traverse failed"),
		},
		"error on load": {
			linksToLoad:       links[:3],
			linkLoadsExpected: 3,
			loadOutcomes: []traverseOutcome{
				{false, nil, blks[0].RawData()},
				{false, nil, blks[1].RawData()},
				{true, errors.New("block not found"), nil},
			},
			errorsOnSend: []error{
				nil, nil, nil,
			},
		},
		"error on send": {
			linksToLoad:       links,
			linkLoadsExpected: 3,
			loadOutcomes: []traverseOutcome{
				{false, nil, blks[0].RawData()},
				{false, nil, blks[1].RawData()},
				{false, nil, blks[2].RawData()},
			},
			errorsOnSend: []error{
				nil, nil, errors.New("something went wrong"),
			},
			expectedError: errors.New("something went wrong"),
		},
	}
	for testCase, data := range testCases {
		t.Run(testCase, func(t *testing.T) {
			fl := &fakeLoader{
				expectedLoads: data.linksToLoad[:data.linkLoadsExpected],
				loadReturns:   data.loadOutcomes,
			}
			ft := &fakeTraverser{
				finalError:       data.finalError,
				loadedLinks:      data.linksToLoad,
				expectedOutcomes: data.loadOutcomes,
			}
			frs := newFakeResponseSender()
			for i, err := range data.errorsOnSend {
				frs.expectResponse(data.linksToLoad[i].link, data.loadOutcomes[i].data, err)
			}
			err := RunTraversal(fl.Load, ft, frs.SendResponse)
			require.Equal(t, data.expectedError, err)
			fl.verifyExpectations(t)
			frs.verifyExpectations(t)
			ft.verifyExpectations(t)
		})
	}
}