mirror of
https://github.com/sot-tech/mochi.git
synced 2026-07-09 18:18:11 -07:00
initial middleware refactor
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
// Copyright 2016 The Chihaya Authors. All rights reserved.
|
||||
// Use of this source code is governed by the BSD 2-Clause license,
|
||||
// which can be found in the LICENSE file.
|
||||
|
||||
// Package bencode implements bencoding of data as defined in BEP 3 using
|
||||
// type assertion over reflection for performance.
|
||||
package bencode
|
||||
|
||||
// Dict represents a bencode dictionary.
|
||||
type Dict map[string]interface{}
|
||||
|
||||
// NewDict allocates the memory for a Dict.
|
||||
func NewDict() Dict {
|
||||
return make(Dict)
|
||||
}
|
||||
|
||||
// List represents a bencode list.
|
||||
type List []interface{}
|
||||
|
||||
// NewList allocates the memory for a List.
|
||||
func NewList() List {
|
||||
return make(List, 0)
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
// Copyright 2016 The Chihaya Authors. All rights reserved.
|
||||
// Use of this source code is governed by the BSD 2-Clause license,
|
||||
// which can be found in the LICENSE file.
|
||||
|
||||
package bencode
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// A Decoder reads bencoded objects from an input stream.
|
||||
type Decoder struct {
|
||||
r *bufio.Reader
|
||||
}
|
||||
|
||||
// NewDecoder returns a new decoder that reads from r.
|
||||
func NewDecoder(r io.Reader) *Decoder {
|
||||
return &Decoder{r: bufio.NewReader(r)}
|
||||
}
|
||||
|
||||
// Decode unmarshals the next bencoded value in the stream.
|
||||
func (dec *Decoder) Decode() (interface{}, error) {
|
||||
return unmarshal(dec.r)
|
||||
}
|
||||
|
||||
// Unmarshal deserializes and returns the bencoded value in buf.
|
||||
func Unmarshal(buf []byte) (interface{}, error) {
|
||||
r := bufio.NewReader(bytes.NewBuffer(buf))
|
||||
return unmarshal(r)
|
||||
}
|
||||
|
||||
// unmarshal reads bencoded values from a bufio.Reader
|
||||
func unmarshal(r *bufio.Reader) (interface{}, error) {
|
||||
tok, err := r.ReadByte()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch tok {
|
||||
case 'i':
|
||||
return readTerminatedInt(r, 'e')
|
||||
|
||||
case 'l':
|
||||
list := NewList()
|
||||
for {
|
||||
ok, err := readTerminator(r, 'e')
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if ok {
|
||||
break
|
||||
}
|
||||
|
||||
v, err := unmarshal(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
list = append(list, v)
|
||||
}
|
||||
return list, nil
|
||||
|
||||
case 'd':
|
||||
dict := NewDict()
|
||||
for {
|
||||
ok, err := readTerminator(r, 'e')
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if ok {
|
||||
break
|
||||
}
|
||||
|
||||
v, err := unmarshal(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
key, ok := v.(string)
|
||||
if !ok {
|
||||
return nil, errors.New("bencode: non-string map key")
|
||||
}
|
||||
|
||||
dict[key], err = unmarshal(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return dict, nil
|
||||
|
||||
default:
|
||||
err = r.UnreadByte()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
length, err := readTerminatedInt(r, ':')
|
||||
if err != nil {
|
||||
return nil, errors.New("bencode: unknown input sequence")
|
||||
}
|
||||
|
||||
buf := make([]byte, length)
|
||||
n, err := r.Read(buf)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if int64(n) != length {
|
||||
return nil, errors.New("bencode: short read")
|
||||
}
|
||||
|
||||
return string(buf), nil
|
||||
}
|
||||
}
|
||||
|
||||
func readTerminator(r *bufio.Reader, term byte) (bool, error) {
|
||||
tok, err := r.ReadByte()
|
||||
if err != nil {
|
||||
return false, err
|
||||
} else if tok == term {
|
||||
return true, nil
|
||||
}
|
||||
return false, r.UnreadByte()
|
||||
}
|
||||
|
||||
func readTerminatedInt(r *bufio.Reader, term byte) (int64, error) {
|
||||
buf, err := r.ReadSlice(term)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
} else if len(buf) <= 1 {
|
||||
return 0, errors.New("bencode: empty integer field")
|
||||
}
|
||||
|
||||
return strconv.ParseInt(string(buf[:len(buf)-1]), 10, 64)
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
// Copyright 2016 The Chihaya Authors. All rights reserved.
|
||||
// Use of this source code is governed by the BSD 2-Clause license,
|
||||
// which can be found in the LICENSE file.
|
||||
|
||||
package bencode
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var unmarshalTests = []struct {
|
||||
input string
|
||||
expected interface{}
|
||||
}{
|
||||
{"i42e", int64(42)},
|
||||
{"i-42e", int64(-42)},
|
||||
|
||||
{"7:example", "example"},
|
||||
|
||||
{"l3:one3:twoe", List{"one", "two"}},
|
||||
{"le", List{}},
|
||||
|
||||
{"d3:one2:aa3:two2:bbe", Dict{"one": "aa", "two": "bb"}},
|
||||
{"de", Dict{}},
|
||||
}
|
||||
|
||||
func TestUnmarshal(t *testing.T) {
|
||||
for _, tt := range unmarshalTests {
|
||||
got, err := Unmarshal([]byte(tt.input))
|
||||
assert.Nil(t, err, "unmarshal should not fail")
|
||||
assert.Equal(t, got, tt.expected, "unmarshalled values should match the expected results")
|
||||
}
|
||||
}
|
||||
|
||||
type bufferLoop struct {
|
||||
val string
|
||||
}
|
||||
|
||||
func (r *bufferLoop) Read(b []byte) (int, error) {
|
||||
n := copy(b, r.val)
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func BenchmarkUnmarshalScalar(b *testing.B) {
|
||||
d1 := NewDecoder(&bufferLoop{"7:example"})
|
||||
d2 := NewDecoder(&bufferLoop{"i42e"})
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
d1.Decode()
|
||||
d2.Decode()
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalLarge(t *testing.T) {
|
||||
data := Dict{
|
||||
"k1": List{"a", "b", "c"},
|
||||
"k2": int64(42),
|
||||
"k3": "val",
|
||||
"k4": int64(-42),
|
||||
}
|
||||
|
||||
buf, _ := Marshal(data)
|
||||
dec := NewDecoder(&bufferLoop{string(buf)})
|
||||
|
||||
got, err := dec.Decode()
|
||||
assert.Nil(t, err, "decode should not fail")
|
||||
assert.Equal(t, got, data, "encoding and decoding should equal the original value")
|
||||
}
|
||||
|
||||
func BenchmarkUnmarshalLarge(b *testing.B) {
|
||||
data := map[string]interface{}{
|
||||
"k1": []string{"a", "b", "c"},
|
||||
"k2": 42,
|
||||
"k3": "val",
|
||||
"k4": uint(42),
|
||||
}
|
||||
|
||||
buf, _ := Marshal(data)
|
||||
dec := NewDecoder(&bufferLoop{string(buf)})
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
dec.Decode()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
// Copyright 2016 The Chihaya Authors. All rights reserved.
|
||||
// Use of this source code is governed by the BSD 2-Clause license,
|
||||
// which can be found in the LICENSE file.
|
||||
|
||||
package bencode
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// An Encoder writes bencoded objects to an output stream.
|
||||
type Encoder struct {
|
||||
w io.Writer
|
||||
}
|
||||
|
||||
// NewEncoder returns a new encoder that writes to w.
|
||||
func NewEncoder(w io.Writer) *Encoder {
|
||||
return &Encoder{w: w}
|
||||
}
|
||||
|
||||
// Encode writes the bencoding of v to the stream.
|
||||
func (enc *Encoder) Encode(v interface{}) error {
|
||||
return marshal(enc.w, v)
|
||||
}
|
||||
|
||||
// Marshal returns the bencoding of v.
|
||||
func Marshal(v interface{}) ([]byte, error) {
|
||||
buf := &bytes.Buffer{}
|
||||
err := marshal(buf, v)
|
||||
return buf.Bytes(), err
|
||||
}
|
||||
|
||||
// Marshaler is the interface implemented by objects that can marshal
|
||||
// themselves.
|
||||
type Marshaler interface {
|
||||
MarshalBencode() ([]byte, error)
|
||||
}
|
||||
|
||||
// marshal writes types bencoded to an io.Writer
|
||||
func marshal(w io.Writer, data interface{}) error {
|
||||
switch v := data.(type) {
|
||||
case Marshaler:
|
||||
bencoded, err := v.MarshalBencode()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = w.Write(bencoded)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case string:
|
||||
marshalString(w, v)
|
||||
|
||||
case int:
|
||||
marshalInt(w, int64(v))
|
||||
|
||||
case uint:
|
||||
marshalUint(w, uint64(v))
|
||||
|
||||
case int16:
|
||||
marshalInt(w, int64(v))
|
||||
|
||||
case uint16:
|
||||
marshalUint(w, uint64(v))
|
||||
|
||||
case int64:
|
||||
marshalInt(w, v)
|
||||
|
||||
case uint64:
|
||||
marshalUint(w, v)
|
||||
|
||||
case []byte:
|
||||
marshalBytes(w, v)
|
||||
|
||||
case time.Duration: // Assume seconds
|
||||
marshalInt(w, int64(v/time.Second))
|
||||
|
||||
case Dict:
|
||||
marshal(w, map[string]interface{}(v))
|
||||
|
||||
case []Dict:
|
||||
w.Write([]byte{'l'})
|
||||
for _, val := range v {
|
||||
err := marshal(w, val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
w.Write([]byte{'e'})
|
||||
|
||||
case map[string]interface{}:
|
||||
w.Write([]byte{'d'})
|
||||
for key, val := range v {
|
||||
marshalString(w, key)
|
||||
err := marshal(w, val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
w.Write([]byte{'e'})
|
||||
|
||||
case []string:
|
||||
w.Write([]byte{'l'})
|
||||
for _, val := range v {
|
||||
err := marshal(w, val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
w.Write([]byte{'e'})
|
||||
|
||||
case List:
|
||||
marshal(w, []interface{}(v))
|
||||
|
||||
case []interface{}:
|
||||
w.Write([]byte{'l'})
|
||||
for _, val := range v {
|
||||
err := marshal(w, val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
w.Write([]byte{'e'})
|
||||
|
||||
default:
|
||||
return fmt.Errorf("attempted to marshal unsupported type:\n%t", v)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func marshalInt(w io.Writer, v int64) {
|
||||
w.Write([]byte{'i'})
|
||||
w.Write([]byte(strconv.FormatInt(v, 10)))
|
||||
w.Write([]byte{'e'})
|
||||
}
|
||||
|
||||
func marshalUint(w io.Writer, v uint64) {
|
||||
w.Write([]byte{'i'})
|
||||
w.Write([]byte(strconv.FormatUint(v, 10)))
|
||||
w.Write([]byte{'e'})
|
||||
}
|
||||
|
||||
func marshalBytes(w io.Writer, v []byte) {
|
||||
w.Write([]byte(strconv.Itoa(len(v))))
|
||||
w.Write([]byte{':'})
|
||||
w.Write(v)
|
||||
}
|
||||
|
||||
func marshalString(w io.Writer, v string) {
|
||||
marshalBytes(w, []byte(v))
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
// Copyright 2016 The Chihaya Authors. All rights reserved.
|
||||
// Use of this source code is governed by the BSD 2-Clause license,
|
||||
// which can be found in the LICENSE file.
|
||||
|
||||
package bencode
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var marshalTests = []struct {
|
||||
input interface{}
|
||||
expected []string
|
||||
}{
|
||||
{int(42), []string{"i42e"}},
|
||||
{int(-42), []string{"i-42e"}},
|
||||
{uint(43), []string{"i43e"}},
|
||||
{int64(44), []string{"i44e"}},
|
||||
{uint64(45), []string{"i45e"}},
|
||||
{int16(44), []string{"i44e"}},
|
||||
{uint16(45), []string{"i45e"}},
|
||||
|
||||
{"example", []string{"7:example"}},
|
||||
{[]byte("example"), []string{"7:example"}},
|
||||
{30 * time.Minute, []string{"i1800e"}},
|
||||
|
||||
{[]string{"one", "two"}, []string{"l3:one3:twoe", "l3:two3:onee"}},
|
||||
{[]interface{}{"one", "two"}, []string{"l3:one3:twoe", "l3:two3:onee"}},
|
||||
{[]string{}, []string{"le"}},
|
||||
|
||||
{map[string]interface{}{"one": "aa", "two": "bb"}, []string{"d3:one2:aa3:two2:bbe", "d3:two2:bb3:one2:aae"}},
|
||||
{map[string]interface{}{}, []string{"de"}},
|
||||
}
|
||||
|
||||
func TestMarshal(t *testing.T) {
|
||||
for _, test := range marshalTests {
|
||||
got, err := Marshal(test.input)
|
||||
assert.Nil(t, err, "marshal should not fail")
|
||||
assert.Contains(t, test.expected, string(got), "the marshaled result should be one of the expected permutations")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMarshalScalar(b *testing.B) {
|
||||
buf := &bytes.Buffer{}
|
||||
encoder := NewEncoder(buf)
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
encoder.Encode("test")
|
||||
encoder.Encode(123)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMarshalLarge(b *testing.B) {
|
||||
data := map[string]interface{}{
|
||||
"k1": []string{"a", "b", "c"},
|
||||
"k2": 42,
|
||||
"k3": "val",
|
||||
"k4": uint(42),
|
||||
}
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
encoder := NewEncoder(buf)
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
encoder.Encode(data)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright 2016 The Chihaya Authors. All rights reserved.
|
||||
// Use of this source code is governed by the BSD 2-Clause license,
|
||||
// which can be found in the LICENSE file.
|
||||
|
||||
// Package clientid implements the parsing of BitTorrent ClientIDs from
|
||||
// BitTorrent PeerIDs.
|
||||
package clientid
|
||||
|
||||
// New returns the part of a PeerID that identifies a peer's client software.
|
||||
func New(peerID string) (clientID string) {
|
||||
length := len(peerID)
|
||||
if length >= 6 {
|
||||
if peerID[0] == '-' {
|
||||
if length >= 7 {
|
||||
clientID = peerID[1:7]
|
||||
}
|
||||
} else {
|
||||
clientID = peerID[:6]
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
// Copyright 2016 The Chihaya Authors. All rights reserved.
|
||||
// Use of this source code is governed by the BSD 2-Clause license,
|
||||
// which can be found in the LICENSE file.
|
||||
|
||||
package clientid
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestClientID(t *testing.T) {
|
||||
var clientTable = []struct {
|
||||
peerID string
|
||||
clientID string
|
||||
}{
|
||||
{"-AZ3034-6wfG2wk6wWLc", "AZ3034"},
|
||||
{"-AZ3042-6ozMq5q6Q3NX", "AZ3042"},
|
||||
{"-BS5820-oy4La2MWGEFj", "BS5820"},
|
||||
{"-AR6360-6oZyyMWoOOBe", "AR6360"},
|
||||
{"-AG2083-s1hiF8vGAAg0", "AG2083"},
|
||||
{"-AG3003-lEl2Mm4NEO4n", "AG3003"},
|
||||
{"-MR1100-00HS~T7*65rm", "MR1100"},
|
||||
{"-LK0140-ATIV~nbEQAMr", "LK0140"},
|
||||
{"-KT2210-347143496631", "KT2210"},
|
||||
{"-TR0960-6ep6svaa61r4", "TR0960"},
|
||||
{"-XX1150-dv220cotgj4d", "XX1150"},
|
||||
{"-AZ2504-192gwethivju", "AZ2504"},
|
||||
{"-KT4310-3L4UvarKuqIu", "KT4310"},
|
||||
{"-AZ2060-0xJQ02d4309O", "AZ2060"},
|
||||
{"-BD0300-2nkdf08Jd890", "BD0300"},
|
||||
{"-A~0010-a9mn9DFkj39J", "A~0010"},
|
||||
{"-UT2300-MNu93JKnm930", "UT2300"},
|
||||
{"-UT2300-KT4310KT4301", "UT2300"},
|
||||
|
||||
{"T03A0----f089kjsdf6e", "T03A0-"},
|
||||
{"S58B-----nKl34GoNb75", "S58B--"},
|
||||
{"M4-4-0--9aa757Efd5Bl", "M4-4-0"},
|
||||
|
||||
{"AZ2500BTeYUzyabAfo6U", "AZ2500"}, // BitTyrant
|
||||
{"exbc0JdSklm834kj9Udf", "exbc0J"}, // Old BitComet
|
||||
{"FUTB0L84j542mVc84jkd", "FUTB0L"}, // Alt BitComet
|
||||
{"XBT054d-8602Jn83NnF9", "XBT054"}, // XBT
|
||||
{"OP1011affbecbfabeefb", "OP1011"}, // Opera
|
||||
{"-ML2.7.2-kgjjfkd9762", "ML2.7."}, // MLDonkey
|
||||
{"-BOWA0C-SDLFJWEIORNM", "BOWA0C"}, // Bits on Wheels
|
||||
{"Q1-0-0--dsn34DFn9083", "Q1-0-0"}, // Queen Bee
|
||||
{"Q1-10-0-Yoiumn39BDfO", "Q1-10-"}, // Queen Bee Alt
|
||||
{"346------SDFknl33408", "346---"}, // TorreTopia
|
||||
{"QVOD0054ABFFEDCCDEDB", "QVOD00"}, // Qvod
|
||||
|
||||
{"", ""},
|
||||
{"-", ""},
|
||||
{"12345", ""},
|
||||
{"-12345", ""},
|
||||
{"123456", "123456"},
|
||||
{"-123456", "123456"},
|
||||
}
|
||||
|
||||
for _, tt := range clientTable {
|
||||
if parsedID := New(tt.peerID); parsedID != tt.clientID {
|
||||
t.Error("Incorrectly parsed peer ID", tt.peerID, "as", parsedID)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
// Copyright 2016 The Chihaya Authors. All rights reserved.
|
||||
// Use of this source code is governed by the BSD 2-Clause license,
|
||||
// which can be found in the LICENSE file.
|
||||
|
||||
// Package event implements type-level constraints for dealing with the events
|
||||
// communicated via BitTorrent announce.
|
||||
package event
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ErrUnknownEvent is returned when New fails to return an event.
|
||||
var ErrUnknownEvent = errors.New("unknown event")
|
||||
|
||||
// Event represents an event done by a BitTorrent client.
|
||||
type event uint8
|
||||
|
||||
const (
|
||||
// None is the event when a BitTorrent client announces due to time lapsed
|
||||
// since the previous announce.
|
||||
None event = iota
|
||||
|
||||
// Started is the event sent by a BitTorrent client when it joins a swarm.
|
||||
Started
|
||||
|
||||
// Stopped is the event sent by a BitTorrent client when it leaves a swarm.
|
||||
Stopped
|
||||
|
||||
// Completed is the event sent by a BitTorrent client when it finishes
|
||||
// downloading all of the required chunks.
|
||||
Completed
|
||||
)
|
||||
|
||||
var (
|
||||
eventToString = make(map[event]string)
|
||||
stringToEvent = make(map[string]event)
|
||||
)
|
||||
|
||||
func init() {
|
||||
eventToString[None] = "none"
|
||||
eventToString[Started] = "started"
|
||||
eventToString[Stopped] = "stopped"
|
||||
eventToString[Completed] = "completed"
|
||||
|
||||
stringToEvent[""] = None
|
||||
stringToEvent["none"] = None
|
||||
stringToEvent["started"] = Started
|
||||
stringToEvent["stopped"] = Stopped
|
||||
stringToEvent["completed"] = Completed
|
||||
}
|
||||
|
||||
// New returns the proper Event given a string.
|
||||
func New(eventStr string) (event, error) {
|
||||
if e, ok := stringToEvent[strings.ToLower(eventStr)]; ok {
|
||||
return e, nil
|
||||
}
|
||||
|
||||
return None, ErrUnknownEvent
|
||||
}
|
||||
|
||||
// String implements Stringer for an event.
|
||||
func (e event) String() string {
|
||||
if name, ok := eventToString[e]; ok {
|
||||
return name
|
||||
}
|
||||
|
||||
panic("event: event has no associated name")
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright 2016 The Chihaya Authors. All rights reserved.
|
||||
// Use of this source code is governed by the BSD 2-Clause license,
|
||||
// which can be found in the LICENSE file.
|
||||
|
||||
package event
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
var table = []struct {
|
||||
data string
|
||||
expected event
|
||||
expectedErr error
|
||||
}{
|
||||
{"", None, nil},
|
||||
{"NONE", None, nil},
|
||||
{"none", None, nil},
|
||||
{"started", Started, nil},
|
||||
{"stopped", Stopped, nil},
|
||||
{"completed", Completed, nil},
|
||||
{"notAnEvent", None, ErrUnknownEvent},
|
||||
}
|
||||
|
||||
for _, tt := range table {
|
||||
got, err := New(tt.data)
|
||||
assert.Equal(t, err, tt.expectedErr, "errors should equal the expected value")
|
||||
assert.Equal(t, got, tt.expected, "events should equal the expected value")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user